move enum types

This commit is contained in:
Nate Kelley 2025-08-04 18:04:32 -06:00
parent 87ea87e963
commit ffeee17365
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
39 changed files with 345 additions and 170 deletions

View File

@ -19,7 +19,8 @@
{ "path": "../packages/vitest-config" },
{ "path": "../packages/web-tools" },
{ "path": "../packages/sandbox" },
{ "path": "../packages/env-utils" }
{ "path": "../packages/env-utils" },
{ "path": "../packages/server-utils" }
],
"settings": {
"editor.defaultFormatter": "biomejs.biome",

View File

@ -742,7 +742,7 @@ impl FromSql<sql_types::MessageFeedbackEnum, Pg> for MessageFeedback {
Serialize,
)]
#[diesel(sql_type = sql_types::WorkspaceSharingEnum)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceSharing {
#[serde(alias = "none")]
None,

View File

@ -1,10 +1,6 @@
import { getUserOrganizationId, updateReport } from '@buster/database';
import {
type ShareUpdateRequest,
ShareUpdateRequestSchema,
type ShareUpdateResponse,
type UpdateReportResponse,
} from '@buster/server-shared/reports';
import type { ShareUpdateResponse, UpdateReportResponse } from '@buster/server-shared/reports';
import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';

View File

@ -1,11 +1,8 @@
import type { ShareAssetType } from '@buster/server-shared/share';
import type { BusterCollection, BusterCollectionListItem } from '@/api/asset_interfaces/collection';
import type {
ShareDeleteRequest,
ShareUpdateRequest
} from '@/api/asset_interfaces/shared_interfaces';
import mainApi from '@/api/buster_rest/instances';
import { SharePostRequest } from '@buster/server-shared/share';
import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
export const collectionsGetList = async (params: {
/** Current page number (1-based indexing) */

View File

@ -3,10 +3,7 @@ import type {
BusterDashboardResponse,
DashboardConfig
} from '@/api/asset_interfaces/dashboard';
import type {
ShareDeleteRequest,
ShareUpdateRequest
} from '@/api/asset_interfaces/shared_interfaces';
import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
import mainApi from '@/api/buster_rest/instances';
import { serverFetch } from '@/api/createServerInstance';
import { SharePostRequest } from '@buster/server-shared/share';

View File

@ -1,8 +1,6 @@
import type {
BulkUpdateMetricVerificationStatusRequest,
BulkUpdateMetricVerificationStatusResponse,
ShareDeleteRequest,
ShareUpdateRequest,
DeleteMetricRequest,
DeleteMetricResponse,
DuplicateMetricRequest,
@ -18,6 +16,7 @@ import type {
UpdateMetricResponse,
ShareUpdateResponse
} from '@buster/server-shared/metrics';
import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
import { serverFetch } from '@/api/createServerInstance';
import { mainApi } from '../instances';
import { SharePostRequest } from '@buster/server-shared/share';

View File

@ -10,7 +10,6 @@ import { useMemoizedFn } from '@/hooks';
import { queryKeys } from '@/api/query_keys';
import type { RustApiError } from '../errors';
import type {
GetReportsListResponse,
GetReportIndividualResponse,
UpdateReportRequest,
UpdateReportResponse
@ -129,9 +128,6 @@ export const useUpdateReport = () => {
queryKeys.reportsGetReport(reportId).queryKey,
create(previousReport, (draft) => {
if (data.name !== undefined) draft.name = data.name;
if (data.description !== undefined) draft.description = data.description;
if (data.publicly_accessible !== undefined)
draft.publicly_accessible = data.publicly_accessible;
if (data.content !== undefined) draft.content = data.content;
})
);

View File

@ -64,16 +64,20 @@ export const AccessDropdown: React.FC<AccessDropdownProps> = React.memo(
// Using a type-safe switch to handle all ShareRole values
switch (value) {
case 'fullAccess':
case 'full_access':
return 'Full access';
case 'canEdit':
case 'can_edit':
return 'Can edit';
case 'canView':
case 'can_view':
return 'Can view';
case 'owner':
return 'Owner';
case 'remove':
return 'Remove';
case 'viewer':
return 'Viewer';
case 'can_filter':
return 'Can filter';
case 'none':
return 'Not shared';
default:
@ -86,7 +90,13 @@ export const AccessDropdown: React.FC<AccessDropdownProps> = React.memo(
if (value === 'remove' || value === 'notShared') {
onChangeShareLevel?.(null);
} else {
onChangeShareLevel?.(value as ShareRole);
if (props.type === 'workspace') {
(onChangeShareLevel as (level: WorkspaceShareRole | null) => void)?.(
value as WorkspaceShareRole
);
} else {
(onChangeShareLevel as (level: ShareRole | null) => void)?.(value as ShareRole);
}
}
});
@ -122,17 +132,17 @@ AccessDropdown.displayName = 'AccessDropdown';
const metricItems: DropdownItem<ShareRole>[] = [
{
value: 'fullAccess',
value: 'full_access',
label: 'Full access',
secondaryLabel: 'Can edit and share with others.'
},
{
value: 'canEdit',
value: 'can_edit',
label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.'
},
{
value: 'canView',
value: 'can_view',
label: 'Can view',
secondaryLabel: 'Can view asset but not edit.'
}
@ -140,17 +150,17 @@ const metricItems: DropdownItem<ShareRole>[] = [
const dashboardItems: DropdownItem<ShareRole>[] = [
{
value: 'fullAccess',
value: 'full_access',
label: 'Full access',
secondaryLabel: 'Can edit and share with others.'
},
{
value: 'canEdit',
value: 'can_edit',
label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.'
},
{
value: 'canView',
value: 'can_view',
label: 'Can view',
secondaryLabel: 'Can view dashboard and metrics but not edit.'
}
@ -158,17 +168,17 @@ const dashboardItems: DropdownItem<ShareRole>[] = [
const collectionItems: DropdownItem<ShareRole>[] = [
{
value: 'fullAccess',
value: 'full_access',
label: 'Full access',
secondaryLabel: 'Can edit and share with others.'
},
{
value: 'canEdit',
value: 'can_edit',
label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.'
},
{
value: 'canView',
value: 'can_view',
label: 'Can view',
secondaryLabel: 'Can view assets but not edit.'
}
@ -176,17 +186,17 @@ const collectionItems: DropdownItem<ShareRole>[] = [
const reportItems: DropdownItem<ShareRole>[] = [
{
value: 'fullAccess',
value: 'full_access',
label: 'Full access',
secondaryLabel: 'Can edit and share with others.'
},
{
value: 'canEdit',
value: 'can_edit',
label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.'
},
{
value: 'canView',
value: 'can_view',
label: 'Can view',
secondaryLabel: 'Can view asset but not edit.'
}
@ -194,17 +204,17 @@ const reportItems: DropdownItem<ShareRole>[] = [
const workspaceItems: DropdownItem<WorkspaceShareRole>[] = [
{
value: 'fullAccess',
value: 'full_access',
label: 'Full access',
secondaryLabel: 'Can edit and share with others.'
},
{
value: 'canEdit',
value: 'can_edit',
label: 'Can edit',
secondaryLabel: 'Can edit, but not share with others.'
},
{
value: 'canView',
value: 'can_view',
label: 'Can view',
secondaryLabel: 'Cannot edit or share with others.'
},

View File

@ -18,13 +18,13 @@ const mockShareConfig: ShareConfig = {
individual_permissions: [
{
email: 'test_with_a_long_name_like_super_long_name@test.com',
role: 'canView',
role: 'can_view',
name: 'Test User',
avatar_url: null
},
{
email: 'test2@test.com',
role: 'fullAccess',
role: 'full_access',
name: 'Test User 2 with a long name like super long name',
avatar_url: null
}
@ -79,7 +79,7 @@ export const ViewerPermission: Story = {
assetType: 'metric',
shareAssetConfig: {
...mockShareConfig,
permission: 'canView'
permission: 'can_view'
}
}
};

View File

@ -124,7 +124,7 @@ const ShareMenuContentShare: React.FC<ShareMenuContentBodyProps> = React.memo(
const payload: Parameters<typeof onUpdateMetricShare>[0] = {
id: assetId,
params: {
workspace_sharing: role
workspace_sharing: role || 'none'
}
};

View File

@ -2,12 +2,12 @@ import type { MetricListItem } from '@buster/server-shared/metrics';
import type { VerificationStatus } from '@buster/server-shared/share';
const statusRecordText: Record<VerificationStatus, string> = {
['verified']: 'Verified',
['requested']: 'Requested',
['inReview']: 'In review',
['backlogged']: 'Backlogged',
['notVerified']: 'Not verified',
['notRequested']: 'Not requested'
verified: 'Verified',
requested: 'Requested',
inReview: 'In review',
backlogged: 'Backlogged',
notVerified: 'Not verified',
notRequested: 'Not requested'
};
export const getTooltipText = (status: VerificationStatus) => {

View File

@ -5,17 +5,17 @@ export const getIsOwner = (role: ShareRole | null | undefined) => {
};
export const getIsEffectiveOwner = (role: ShareRole | null | undefined) => {
return role === 'fullAccess' || role === 'owner';
return role === 'full_access' || role === 'owner';
};
export const canEdit = (role: ShareRole | null | undefined) => {
return role === 'canEdit' || role === 'fullAccess' || role === 'owner';
return role === 'can_edit' || role === 'full_access' || role === 'owner';
};
export const canShare = (role: ShareRole | null | undefined) => {
return role === 'fullAccess' || role === 'owner';
return role === 'full_access' || role === 'owner';
};
export const canFilter = (role: ShareRole | null | undefined) => {
return role === 'fullAccess' || role === 'owner' || role === 'canEdit';
return role === 'full_access' || role === 'owner' || role === 'can_edit';
};

View File

@ -1,21 +1,8 @@
{
"name": "@buster/server-shared",
"version": "0.0.1",
"type": "module",
"module": "src/index.ts",
"private": false,
"scripts": {
"prebuild": "tsx scripts/type-import-check.ts",
"build": "tsc --build",
"build:dry-run": "tsc --build",
"dev": "tsc --watch",
"lint": "biome check --write",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests",
"test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts",
"test:watch": "vitest"
},
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
@ -82,16 +69,23 @@
"default": "./dist/lib/report/index.js"
}
},
"module": "src/index.ts",
"scripts": {
"prebuild": "tsx scripts/type-import-check.ts",
"build": "tsc --build",
"build:dry-run": "tsc --build",
"dev": "tsc --watch",
"lint": "biome check --write",
"test": "vitest run",
"test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts",
"test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests",
"test:watch": "vitest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@buster/database": "workspace:*",
"@buster/typescript-config": "workspace:*",
"@buster/vitest-config": "workspace:*",
"@platejs/autoformat": "catalog:",
"@platejs/basic-nodes": "catalog:",
"@platejs/markdown": "catalog:",
"platejs": "catalog:",
"remark-gfm": "catalog:",
"remark-math": "^6.0.0",
"zod": "catalog:"
},
"devDependencies": {

View File

@ -53,8 +53,6 @@ export const BulkUpdateMetricVerificationStatusRequestSchema = z.array(
})
);
export const ShareDeleteRequestSchema = z.array(z.string());
export const ShareUpdateRequestSchema = z.object({
users: z
.array(
@ -79,5 +77,3 @@ export type DuplicateMetricRequest = z.infer<typeof DuplicateMetricRequestSchema
export type BulkUpdateMetricVerificationStatusRequest = z.infer<
typeof BulkUpdateMetricVerificationStatusRequestSchema
>;
export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>;
export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>;

View File

@ -2,3 +2,4 @@ export * from './reports.types';
export * from './requests';
export * from './responses';
export * from './reports.types';
export * from './report-elements';

View File

@ -1,7 +1,7 @@
import type { ReportElements } from '@buster/database';
import { z } from 'zod';
import { AssetCollectionsSchema } from '../collections/shared-asset-collections';
import { IndividualPermissionsSchema } from '../shared-permissions';
import { IndividualPermissionsSchema } from '../share';
import { VersionsSchema } from '../version-shared';
export const ReportListItemSchema = z.object({

View File

@ -1,6 +1,5 @@
import type { ReportElements } from '@buster/database';
import { z } from 'zod';
import { WorkspaceSharingSchema } from '../shared-permissions';
import { PaginatedRequestSchema } from '../type-utilities/pagination';
export const GetReportsListRequestSchema = PaginatedRequestSchema;
@ -15,12 +14,3 @@ export const UpdateReportRequestSchema = z
export type UpdateReportRequest = z.infer<typeof UpdateReportRequestSchema>;
export type GetReportsListRequest = z.infer<typeof GetReportsListRequestSchema>;
export const ShareUpdateRequestSchema = z.object({
publicly_accessible: z.boolean().optional(),
public_expiry_date: z.string().optional(),
public_password: z.string().optional(),
workspace_sharing: WorkspaceSharingSchema.optional(),
});
export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>;

View File

@ -0,0 +1,17 @@
import type { assetPermissionRoleEnum } from '@buster/database';
import { z } from 'zod';
type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
const AssetPermissionRoleEnums: Record<AssetPermissionRoleBase, AssetPermissionRoleBase> =
Object.freeze({
owner: 'owner',
viewer: 'viewer',
full_access: 'full_access',
can_edit: 'can_edit',
can_filter: 'can_filter',
can_view: 'can_view',
});
export const AssetPermissionRoleSchema = z.enum(
Object.values(AssetPermissionRoleEnums) as [AssetPermissionRoleBase, ...AssetPermissionRoleBase[]]
);

View File

@ -1,3 +1,5 @@
export * from './share-interfaces.types';
export * from './verification.types';
export * from './requests';
export * from './individual-permissions';
export * from './assets';

View File

@ -0,0 +1,16 @@
import { z } from 'zod';
import { AssetPermissionRoleSchema } from './assets';
export const IndividualPermissionSchema = z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string(),
avatar_url: z.string().nullable(),
role: AssetPermissionRoleSchema,
});
export type IndividualPermission = z.infer<typeof IndividualPermissionSchema>;
export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema);
export type IndividualPermissions = z.infer<typeof IndividualPermissionsSchema>;

View File

@ -1,5 +1,5 @@
import { z } from 'zod';
import { ShareRoleSchema } from './share-interfaces.types';
import { ShareRoleSchema, WorkspaceShareRoleSchema } from './share-interfaces.types';
export const SharePostRequestSchema = z.array(
z.object({
@ -11,3 +11,26 @@ export const SharePostRequestSchema = z.array(
);
export type SharePostRequest = z.infer<typeof SharePostRequestSchema>;
//Used for updating share permissions for a report, collection, or metric
export const ShareUpdateRequestSchema = z.object({
publicly_accessible: z.boolean().optional(),
public_expiry_date: z.string().optional(),
public_password: z.string().optional(),
workspace_sharing: WorkspaceShareRoleSchema.optional(),
users: z
.array(
z.object({
email: z.string(),
role: ShareRoleSchema,
})
)
.optional(),
});
export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>;
//Used for deleting share permissions for a report, collection, or metric
export const ShareDeleteRequestSchema = z.array(z.string());
export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>;

View File

@ -8,7 +8,7 @@ import {
describe('ShareRoleSchema', () => {
it('should accept valid role values', () => {
const validRoles = ['owner', 'fullAccess', 'canEdit', 'canView'];
const validRoles = ['owner', 'full_access', 'can_edit', 'can_view'];
for (const role of validRoles) {
const result = ShareRoleSchema.safeParse(role);
@ -148,7 +148,7 @@ describe('ShareIndividualSchema', () => {
});
it('should handle all valid role combinations', () => {
const validRoles = ['owner', 'fullAccess', 'canEdit', 'canView'];
const validRoles = ['owner', 'full_access', 'can_edit', 'can_view'];
for (const role of validRoles) {
const individual = {
@ -233,7 +233,7 @@ describe('ShareConfigSchema', () => {
public_enabled_by: 'system',
publicly_accessible: true,
public_password: null,
permission: 'fullAccess',
permission: 'full_access',
workspace_sharing: null,
workspace_member_count: null,
};
@ -244,12 +244,12 @@ describe('ShareConfigSchema', () => {
if (result.success) {
expect(result.data.individual_permissions).toEqual([]);
expect(result.data.publicly_accessible).toBe(true);
expect(result.data.permission).toBe('fullAccess');
expect(result.data.permission).toBe('full_access');
}
});
it('should validate all permission field values', () => {
const validPermissions = ['owner', 'fullAccess', 'canEdit', 'canView'];
const validPermissions = ['owner', 'full_access', 'can_edit', 'can_view'];
for (const permission of validPermissions) {
const config = {
@ -361,12 +361,12 @@ describe('ShareConfigSchema', () => {
},
{
email: 'editor@company.com',
role: 'canEdit',
role: 'can_edit',
name: 'Editor User',
},
{
email: 'viewer@external.com',
role: 'canView',
role: 'can_view',
// name is optional
},
],
@ -374,7 +374,7 @@ describe('ShareConfigSchema', () => {
public_enabled_by: 'admin@company.com',
publicly_accessible: true,
public_password: 'complex_password_123!',
permission: 'fullAccess',
permission: 'full_access',
workspace_sharing: null,
workspace_member_count: null,
};
@ -385,8 +385,8 @@ describe('ShareConfigSchema', () => {
if (result.success) {
expect(result.data.individual_permissions).toHaveLength(3);
expect(result.data.individual_permissions?.[0]?.role).toBe('owner');
expect(result.data.individual_permissions?.[1]?.role).toBe('canEdit');
expect(result.data.individual_permissions?.[2]?.role).toBe('canView');
expect(result.data.individual_permissions?.[1]?.role).toBe('can_edit');
expect(result.data.individual_permissions?.[2]?.role).toBe('can_view');
expect(result.data.individual_permissions?.[2]?.name).toBeUndefined();
}
});

View File

@ -1,15 +1,37 @@
import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database';
import { z } from 'zod';
import { AssetTypeSchema } from '../assets/asset-types.types';
export const ShareRoleSchema = z.enum([
'owner', //owner of the asset
'fullAccess', //same as owner, can share with others
'canEdit', //can edit, cannot share
'canView', //can view asset
]);
type ShareRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
export const ShareRoleEnumsConversions: Record<ShareRoleBase, ShareRoleBase> = Object.freeze({
owner: 'owner',
full_access: 'full_access',
can_edit: 'can_edit',
can_view: 'can_view',
viewer: 'viewer',
can_filter: 'can_filter',
});
export const WorkspaceShareRoleSchema = z.enum([...ShareRoleSchema.options, 'none']);
export const ShareRoleSchema = z.enum(
Object.values(ShareRoleEnumsConversions) as [ShareRoleBase, ...ShareRoleBase[]]
);
//type TeamRoleBase = (typeof teamRoleEnum.enumValues)[number] | 'none';
type WorkspaceShareRoleBase = (typeof workspaceSharingEnum.enumValues)[number];
const WorkspaceShareRoleEnumsConversions: Record<WorkspaceShareRoleBase, WorkspaceShareRoleBase> =
Object.freeze({
full_access: 'full_access',
can_edit: 'can_edit',
can_view: 'can_view',
none: 'none',
});
export const WorkspaceShareRoleSchema = z.enum(
Object.values(WorkspaceShareRoleEnumsConversions) as [
WorkspaceShareRoleBase,
...WorkspaceShareRoleBase[],
]
);
export const ShareAssetTypeSchema = AssetTypeSchema;
export const ShareIndividualSchema = z.object({

View File

@ -1,12 +1,19 @@
import type { verificationEnum } from '@buster/database';
import { z } from 'zod';
export const VerificationStatusSchema = z.enum([
'notRequested',
'requested',
'inReview',
'verified',
'backlogged',
'notVerified',
]);
type VerificationStatusBase = (typeof verificationEnum.enumValues)[number] | 'notVerified';
const VerificationStatusEnums: Record<VerificationStatusBase, VerificationStatusBase> =
Object.freeze({
notRequested: 'notRequested',
requested: 'requested',
inReview: 'inReview',
verified: 'verified',
backlogged: 'backlogged',
notVerified: 'notVerified',
});
export const VerificationStatusSchema = z.enum(
Object.values(VerificationStatusEnums) as [VerificationStatusBase, ...VerificationStatusBase[]]
);
export type VerificationStatus = z.infer<typeof VerificationStatusSchema>;

View File

@ -1,43 +0,0 @@
import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database';
import { z } from 'zod';
type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
const AssetPermissionRoleEnums: Record<AssetPermissionRoleBase, AssetPermissionRoleBase> =
Object.freeze({
owner: 'owner',
viewer: 'viewer',
full_access: 'full_access',
can_edit: 'can_edit',
can_filter: 'can_filter',
can_view: 'can_view',
});
export const AssetPermissionRoleSchema = z.enum(
Object.values(AssetPermissionRoleEnums) as [AssetPermissionRoleBase, ...AssetPermissionRoleBase[]]
);
export const IndividualPermissionSchema = z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string(),
avatar_url: z.string().nullable(),
role: AssetPermissionRoleSchema,
});
export type IndividualPermission = z.infer<typeof IndividualPermissionSchema>;
export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema);
export type IndividualPermissions = z.infer<typeof IndividualPermissionsSchema>;
type WorkspaceSharingBase = (typeof workspaceSharingEnum.enumValues)[number];
const WorkspaceSharingEnums: Record<WorkspaceSharingBase, WorkspaceSharingBase> = Object.freeze({
none: 'none',
can_view: 'can_view',
can_edit: 'can_edit',
full_access: 'full_access',
});
export const WorkspaceSharingSchema = z.enum(
Object.values(WorkspaceSharingEnums) as [WorkspaceSharingBase, ...WorkspaceSharingBase[]]
);

38
packages/server-utils/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# TypeScript build artifacts
dist/
build/
*.tsbuildinfo
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Coverage
coverage/
*.lcov
.nyc_output
# Node modules
node_modules/
# Temporary files
*.tmp
*.temp
.DS_Store
# Environment files
.env.local
.env.*.local
# Test artifacts
junit.xml
test-results/
# IDE
.idea/
.vscode/
*.swp
*.swo

View File

@ -0,0 +1,7 @@
# @buster/server-utils
Server utilities for Buster that can be used in any server-side application across the monorepo.
## Overview
This package provides common server-side utilities and helper functions that are shared across different backend applications in the Buster ecosystem. It centralizes reusable server logic to avoid code duplication and ensure consistency across services.

View File

@ -0,0 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"extends": ["../../biome.json"],
"files": {
"include": ["src/**/*", "scripts/**/*"]
}
}

10
packages/server-utils/env.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV?: 'development' | 'production' | 'test';
// Add your environment variables here
}
}
}
export {};

View File

@ -0,0 +1,40 @@
{
"name": "@buster/server-utils",
"description": "Server utilities for Buster that can be used in any server-side application",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./report": {
"types": "./dist/report/index.d.ts",
"default": "./dist/report/index.js"
}
},
"scripts": {
"prebuild": "[ \"$SKIP_ENV_CHECK\" = \"true\" ] || tsx scripts/validate-env.ts",
"build": "tsc",
"build:dry-run": "tsc",
"typecheck": "tsc --noEmit",
"dev": "tsc --watch",
"lint": "biome check --write",
"test": "vitest run",
"test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests",
"test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@buster/typescript-config": "workspace:*",
"@buster/vitest-config": "workspace:*",
"@buster/env-utils": "workspace:*",
"@buster/database": "workspace:*",
"zod": "catalog:",
"@platejs/autoformat": "catalog:",
"@platejs/basic-nodes": "catalog:",
"@platejs/markdown": "catalog:",
"platejs": "catalog:",
"remark-gfm": "catalog:",
"remark-math": "^6.0.0"
}
}

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
// This script uses the shared env-utils to validate environment variables
import { loadRootEnv, validateEnv } from '@buster/env-utils';
// Load environment variables from root .env file
loadRootEnv();
// Define required environment variables for this package
const requiredEnv = {
// NODE_ENV is optional - will default to 'development' if not set
// Add your required environment variables here:
// DATABASE_URL: process.env.DATABASE_URL,
// API_KEY: process.env.API_KEY,
};
// Validate environment variables
const { hasErrors } = validateEnv(requiredEnv);
if (hasErrors) {
process.exit(1);
}

View File

@ -0,0 +1,10 @@
{
"extends": "@buster/typescript-config/base.json",
"compilerOptions": {
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json",
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*", "env.d.ts"],
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
}

View File

@ -0,0 +1,3 @@
import { baseConfig } from '@buster/vitest-config';
export default baseConfig;

View File

@ -1043,6 +1043,31 @@ importers:
'@buster/vitest-config':
specifier: workspace:*
version: link:../vitest-config
zod:
specifier: 'catalog:'
version: 3.25.1
devDependencies:
tsx:
specifier: 'catalog:'
version: 4.20.3
vitest:
specifier: 'catalog:'
version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
packages/server-utils:
dependencies:
'@buster/database':
specifier: workspace:*
version: link:../database
'@buster/env-utils':
specifier: workspace:*
version: link:../env-utils
'@buster/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@buster/vitest-config':
specifier: workspace:*
version: link:../vitest-config
'@platejs/autoformat':
specifier: 'catalog:'
version: 49.0.0(platejs@49.2.4(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -1064,13 +1089,6 @@ importers:
zod:
specifier: 'catalog:'
version: 3.25.1
devDependencies:
tsx:
specifier: 'catalog:'
version: 4.20.3
vitest:
specifier: 'catalog:'
version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
packages/slack:
dependencies:
@ -6783,7 +6801,6 @@ packages:
bun@1.2.18:
resolution: {integrity: sha512-OR+EpNckoJN4tHMVZPaTPxDj2RgpJgJwLruTIFYbO3bQMguLd0YrmkWKYqsiihcLgm2ehIjF/H1RLfZiRa7+qQ==}
cpu: [arm64, x64, aarch64]
os: [darwin, linux, win32]
hasBin: true
@ -19222,14 +19239,14 @@ snapshots:
msw: 2.10.4(@types/node@20.19.4)(typescript@5.8.3)
vite: 6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/mocker@3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))':
'@vitest/mocker@3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
msw: 2.10.4(@types/node@24.0.10)(typescript@5.8.3)
vite: 6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
vite: 6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/pretty-format@2.0.5':
dependencies:
@ -26558,7 +26575,7 @@ snapshots:
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4