buster/packages/server-shared
jacob-buster cd634843a2 Dashboard filters built 2025-10-03 16:57:41 -06:00
..
.cursor Added additional enum fixes 2025-09-17 17:22:26 -06:00
scripts Move to scoped database imports 2025-09-18 12:36:37 -06:00
src Dashboard filters built 2025-10-03 16:57:41 -06:00
.gitignore Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
CLAUDE.md CLAUDE.md and README.md updates... 2025-09-15 15:06:41 -06:00
README.md CLAUDE.md and README.md updates... 2025-09-15 15:06:41 -06:00
biome.json fix broken linting 2025-07-17 13:18:32 -06:00
package.json Adding the Backend for library 2025-10-02 13:09:13 -06:00
tsconfig.json fix all of the bugs 2025-07-12 22:14:08 -06:00
turbo.json Add additional asset queries 2025-08-04 22:13:09 -06:00
vitest.config.ts Add some unit tests 2025-07-04 01:38:58 -06:00

README.md

Server Shared Package

The API contract layer for the Buster monorepo. All request and response types for server communication live here.

Installation

pnpm add @buster/server-shared

Overview

@buster/server-shared is the single source of truth for:

  • API request schemas and types
  • API response schemas and types
  • Shared validation logic
  • Type flow from database to clients

Architecture

Types flow through the system in this order:

@buster/database → @buster/server-shared → Apps (web, cli, etc.)

Usage

Defining API Types

All API types must be defined as Zod schemas first:

import { z } from 'zod';

// Define request schema with descriptions
export const CreateUserRequestSchema = z.object({
  email: z.string().email().describe('User email address'),
  name: z.string().min(1).describe('User full name'),
  orgId: z.string().uuid().describe('Organization identifier'),
  role: z.enum(['admin', 'member', 'viewer']).describe('User role')
});

// Export inferred type
export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;

Using in Server

import { CreateUserRequestSchema } from '@buster/server-shared';
import { zValidator } from '@hono/zod-validator';

app.post('/users', 
  zValidator('json', CreateUserRequestSchema),
  async (c) => {
    const data = c.req.valid('json');
    // data is fully typed as CreateUserRequest
  }
);

Using in Client

import type { CreateUserRequest, CreateUserResponse } from '@buster/server-shared';

async function createUser(data: CreateUserRequest): Promise<CreateUserResponse> {
  const response = await fetch('/api/v2/users', {
    method: 'POST',
    body: JSON.stringify(data)
  });
  return response.json();
}

File Organization

server-shared/
├── src/
│   ├── users/
│   │   ├── requests.ts      # User request schemas
│   │   ├── responses.ts     # User response schemas
│   │   └── index.ts         # Barrel export
│   ├── chats/
│   ├── dashboards/
│   └── index.ts             # Main export

Type Patterns

Request Types

export const CreateDashboardRequestSchema = z.object({
  name: z.string().describe('Dashboard name'),
  dataSourceId: z.string().uuid().describe('Data source ID'),
  isPublic: z.boolean().default(false).describe('Public visibility')
});

export type CreateDashboardRequest = z.infer<typeof CreateDashboardRequestSchema>;

Response Types

import type { Dashboard } from '@buster/database';

export const CreateDashboardResponseSchema = z.object({
  dashboard: z.custom<Dashboard>().describe('Created dashboard'),
  permissions: z.array(z.string()).describe('User permissions')
});

export type CreateDashboardResponse = z.infer<typeof CreateDashboardResponseSchema>;

Paginated Responses

export const PaginatedResponseSchema = <T extends z.ZodType>(itemSchema: T) =>
  z.object({
    items: z.array(itemSchema),
    total: z.number(),
    page: z.number(),
    pageSize: z.number(),
    hasMore: z.boolean()
  });

Error Responses

export const ErrorResponseSchema = z.object({
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.record(z.unknown()).optional()
  })
});

Best Practices

DO:

  • Define ALL API types as Zod schemas with descriptions
  • Export both schema and inferred type
  • Import database types as type-only
  • Use const assertions for string literals
  • Organize by feature/domain
  • Validate at API boundaries

DON'T:

  • Import database package as values
  • Define types without Zod schemas
  • Use .parse() unnecessarily when types are sufficient
  • Create circular dependencies
  • Mix request/response types in same file

Database Type Imports

Always import database types as type-only:

// ✅ Correct
import type { User, Organization } from '@buster/database';

// ❌ Wrong - causes build failures
import { User, Organization } from '@buster/database';

Testing

import { CreateUserRequestSchema } from '@buster/server-shared';

describe('CreateUserRequestSchema', () => {
  it('validates valid data', () => {
    const data = {
      email: 'test@example.com',
      name: 'Test User',
      orgId: '123e4567-e89b-12d3-a456-426614174000',
      role: 'member'
    };
    
    const result = CreateUserRequestSchema.safeParse(data);
    expect(result.success).toBe(true);
  });
  
  it('rejects invalid email', () => {
    const data = {
      email: 'not-an-email',
      name: 'Test User',
      orgId: '123e4567-e89b-12d3-a456-426614174000',
      role: 'member'
    };
    
    const result = CreateUserRequestSchema.safeParse(data);
    expect(result.success).toBe(false);
  });
});

Development

# Build
turbo build --filter=@buster/server-shared

# Test
turbo test:unit --filter=@buster/server-shared

# Lint
turbo lint --filter=@buster/server-shared