@palbase/backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/endpoint.ts","../src/middleware.ts","../src/errors.ts","../src/worker.ts","../src/job.ts","../src/webhook.ts","../src/hooks.ts","../src/index.ts"],"sourcesContent":["import type { ZodSchema, z } from \"zod\";\nimport type { AuthConfig, HttpMethod, User } from \"./types.js\";\nimport type { MiddlewareContext } from \"./middleware.js\";\n\n/** Rate limit configuration for an endpoint. */\nexport interface RateLimitConfig {\n /** Maximum number of requests in the window. */\n max: number;\n /** Window duration in seconds. */\n window: number;\n}\n\n/** Database client interface injected into endpoint context. */\nexport interface DBClient {\n insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;\n update(table: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;\n delete(table: string, id: string): Promise<void>;\n findById(table: string, id: string): Promise<Record<string, unknown> | null>;\n findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;\n}\n\n/** Logger interface injected into endpoint context. */\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\n/** Cache client interface injected into endpoint context. */\nexport interface CacheClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ttl?: number): Promise<void>;\n del(key: string): Promise<void>;\n incr(key: string): Promise<number>;\n}\n\n/** Palbase service bindings for accessing other modules. */\nexport interface PalbaseBindings {\n auth: Record<string, unknown>;\n storage: Record<string, unknown>;\n}\n\n/** Endpoint context — injected into every handler. */\nexport interface EndpointContext<TInput = unknown> {\n input: TInput;\n params: Record<string, string>;\n query: Record<string, string>;\n headers: Record<string, string>;\n user: User | null;\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n palbase: PalbaseBindings;\n requestId: string;\n projectId: string;\n environmentId: string;\n}\n\n/** Middleware function signature — uses MiddlewareContext (no input, not yet validated). */\nexport type Middleware = (\n ctx: MiddlewareContext,\n next: () => Promise<void>,\n) => Promise<void>;\n\n/** Configuration for defining an endpoint. */\nexport interface EndpointConfig<\n TInputSchema extends ZodSchema = ZodSchema,\n TOutputSchema extends ZodSchema = ZodSchema,\n> {\n method: HttpMethod;\n auth?: Partial<AuthConfig>;\n rateLimit?: RateLimitConfig;\n input?: TInputSchema;\n output?: TOutputSchema;\n middleware?: Middleware[];\n handler: (ctx: EndpointContext<z.infer<TInputSchema>>) => Promise<z.infer<TOutputSchema>>;\n}\n\n/** Define a type-safe endpoint. Input schema infers the ctx.input type. */\nexport function defineEndpoint<\n TInputSchema extends ZodSchema,\n TOutputSchema extends ZodSchema,\n>(config: EndpointConfig<TInputSchema, TOutputSchema>): EndpointConfig<TInputSchema, TOutputSchema> {\n return config;\n}\n","import type { DBClient, Logger, CacheClient, PalbaseBindings } from \"./endpoint.js\";\nimport type { User } from \"./types.js\";\n\n/** Middleware context — subset of EndpointContext without input (not yet validated). */\nexport interface MiddlewareContext {\n params: Record<string, string>;\n query: Record<string, string>;\n headers: Record<string, string>;\n user: User | null;\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n palbase: PalbaseBindings;\n requestId: string;\n projectId: string;\n environmentId: string;\n}\n\n/** Middleware function signature — receives context and next function. */\nexport type MiddlewareHandler = (\n ctx: MiddlewareContext,\n next: () => Promise<void>,\n) => Promise<void>;\n\n/**\n * Define a middleware function for use in the middleware/ directory or\n * as endpoint-specific middleware.\n *\n * Middleware runs before the handler. Call `next()` to pass control\n * to the next middleware or handler. If `next()` is not called, the\n * handler will not execute.\n *\n * Errors thrown in middleware are caught by the pipeline and returned\n * as error responses.\n */\nexport function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler {\n return fn;\n}\n","/** HTTP error with structured error response format. */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly error: string;\n public readonly errorDescription: string;\n\n constructor(status: number, error: string, errorDescription: string) {\n super(errorDescription);\n this.name = \"HttpError\";\n this.status = status;\n this.error = error;\n this.errorDescription = errorDescription;\n }\n\n /**\n * Serialize to the standard Palbase error response format.\n * The `requestId` is injected by the runtime layer from the request context.\n * When called without arguments (e.g. JSON.stringify), request_id is omitted.\n */\n toJSON(requestId?: string): { error: string; error_description: string; status: number; request_id?: string } {\n const result: { error: string; error_description: string; status: number; request_id?: string } = {\n error: this.error,\n error_description: this.errorDescription,\n status: this.status,\n };\n if (requestId) {\n result.request_id = requestId;\n }\n return result;\n }\n}\n","import type { EndpointContext } from \"./endpoint.js\";\n\n/** Backoff strategy for worker retries. */\nexport type BackoffStrategy = \"exponential\" | \"linear\" | \"fixed\";\n\n/** Configuration for a background worker. */\nexport interface WorkerConfig<TPayload = unknown> {\n /** Worker name — must be unique across the project. */\n name: string;\n /** Maximum number of retries before sending to dead letter queue. Defaults to 3. */\n retry?: number;\n /** Execution timeout in seconds. Defaults to 30. */\n timeout?: number;\n /** Backoff strategy between retries. Defaults to \"exponential\". */\n backoff?: BackoffStrategy;\n /** Handler function that processes the job payload. */\n handler: (\n ctx: Omit<EndpointContext<TPayload>, \"input\" | \"params\" | \"query\" | \"headers\">,\n payload: TPayload,\n ) => Promise<void>;\n}\n\n/** Resolved worker configuration with defaults applied. */\nexport interface ResolvedWorkerConfig<TPayload = unknown> {\n name: string;\n retry: number;\n timeout: number;\n backoff: BackoffStrategy;\n handler: (\n ctx: Omit<EndpointContext<TPayload>, \"input\" | \"params\" | \"query\" | \"headers\">,\n payload: TPayload,\n ) => Promise<void>;\n}\n\n/** Valid worker name pattern: alphanumeric, underscore, hyphen only.\n * Prevents Redis key injection via colons or path separators. */\nconst VALID_WORKER_NAME = /^[a-zA-Z0-9_-]+$/;\n\n/** Default values for worker configuration. */\nconst WORKER_DEFAULTS = {\n retry: 3,\n timeout: 30,\n backoff: \"exponential\" as BackoffStrategy,\n} as const;\n\n/**\n * Define a background worker that processes jobs from the queue.\n *\n * Workers are placed in the `workers/` directory and auto-discovered at deploy time.\n *\n * @example\n * ```ts\n * export default defineWorker({\n * name: \"send-email\",\n * retry: 5,\n * timeout: 60,\n * backoff: \"exponential\",\n * handler: async (ctx, payload: { to: string; subject: string }) => {\n * await sendEmail(payload.to, payload.subject);\n * },\n * });\n * ```\n */\nexport function defineWorker<TPayload = unknown>(\n config: WorkerConfig<TPayload>,\n): ResolvedWorkerConfig<TPayload> {\n if (!config.name || config.name.trim() === \"\") {\n throw new Error(\"Worker name is required\");\n }\n\n if (!VALID_WORKER_NAME.test(config.name)) {\n throw new Error(\n `Invalid worker name \"${config.name}\": must match [a-zA-Z0-9_-]+`,\n );\n }\n\n if (config.retry !== undefined && (config.retry < 0 || !Number.isInteger(config.retry))) {\n throw new Error(\"Worker retry must be a non-negative integer\");\n }\n\n if (config.timeout !== undefined && config.timeout <= 0) {\n throw new Error(\"Worker timeout must be a positive number\");\n }\n\n if (\n config.backoff !== undefined &&\n ![\"exponential\", \"linear\", \"fixed\"].includes(config.backoff)\n ) {\n throw new Error(`Invalid backoff strategy: ${config.backoff}`);\n }\n\n return {\n name: config.name,\n retry: config.retry ?? WORKER_DEFAULTS.retry,\n timeout: config.timeout ?? WORKER_DEFAULTS.timeout,\n backoff: config.backoff ?? WORKER_DEFAULTS.backoff,\n handler: config.handler,\n };\n}\n","import type { DBClient, Logger, CacheClient, PalbaseBindings } from \"./endpoint.js\";\n\n/** Context injected into job handlers — subset of EndpointContext without request-specific fields. */\nexport interface JobContext {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: {\n enqueue(workerName: string, payload: unknown): Promise<string>;\n };\n palbase: PalbaseBindings;\n projectId: string;\n environmentId: string;\n}\n\n/** Configuration for defining a scheduled job. */\nexport interface JobConfig {\n /** Unique job name — alphanumeric, underscore, hyphen only. */\n name: string;\n /** Cron expression (e.g., \"0 3 * * *\"). */\n schedule: string;\n /** Execution timeout in seconds. Defaults to 30. */\n timeout?: number;\n /** Job handler function. */\n handler: (ctx: JobContext) => Promise<void>;\n}\n\n/** Resolved job configuration with defaults applied. */\nexport interface ResolvedJobConfig {\n name: string;\n schedule: string;\n timeout: number;\n handler: (ctx: JobContext) => Promise<void>;\n}\n\n/** Valid job name pattern: alphanumeric, underscore, hyphen only. */\nconst VALID_JOB_NAME = /^[a-zA-Z0-9_-]+$/;\n\n/** Maximum allowed timeout in seconds (5 minutes, matching sandbox limits). */\nconst MAX_TIMEOUT_SECONDS = 300;\n\n/** Default values for job configuration. */\nconst JOB_DEFAULTS = {\n timeout: 30,\n} as const;\n\n/**\n * Cron expression validation.\n * Supports standard 5-field cron: minute hour day-of-month month day-of-week.\n * Each field allows: number, *, ranges (1-5), steps (star/2), lists (1,3,5).\n */\nfunction validateCronExpression(expression: string): string | null {\n const trimmed = expression.trim();\n if (trimmed === \"\") {\n return \"Cron expression is required\";\n }\n\n const parts = trimmed.split(/\\s+/);\n if (parts.length !== 5) {\n return `Invalid cron expression \"${trimmed}\": expected 5 fields (minute hour day month weekday), got ${parts.length}`;\n }\n\n const fieldNames = [\"minute\", \"hour\", \"day of month\", \"month\", \"day of week\"];\n const fieldRanges: [number, number][] = [\n [0, 59],\n [0, 23],\n [1, 31],\n [1, 12],\n [0, 7],\n ];\n\n for (let i = 0; i < 5; i++) {\n const field = parts[i]!;\n const name = fieldNames[i]!;\n const [min, max] = fieldRanges[i]!;\n\n const error = validateCronField(field, name, min, max);\n if (error !== null) {\n return error;\n }\n }\n\n return null;\n}\n\nfunction validateCronField(\n field: string,\n name: string,\n min: number,\n max: number,\n): string | null {\n // Split by comma for lists\n const listParts = field.split(\",\");\n for (const part of listParts) {\n // Check for step: */2, 1-5/2\n const stepParts = part.split(\"/\");\n if (stepParts.length > 2) {\n return `Invalid ${name} field: \"${field}\"`;\n }\n\n const base = stepParts[0]!;\n const step = stepParts[1];\n\n if (step !== undefined) {\n const stepNum = Number(step);\n if (!Number.isInteger(stepNum) || stepNum < 1) {\n return `Invalid step value in ${name} field: \"${field}\"`;\n }\n }\n\n if (base === \"*\") {\n continue;\n }\n\n // Check for range: 1-5\n if (base.includes(\"-\")) {\n const rangeParts = base.split(\"-\");\n if (rangeParts.length !== 2) {\n return `Invalid range in ${name} field: \"${field}\"`;\n }\n const rangeStart = Number(rangeParts[0]);\n const rangeEnd = Number(rangeParts[1]);\n if (\n !Number.isInteger(rangeStart) ||\n !Number.isInteger(rangeEnd) ||\n rangeStart < min ||\n rangeEnd > max ||\n rangeStart > rangeEnd\n ) {\n return `Invalid range in ${name} field: \"${field}\"`;\n }\n continue;\n }\n\n // Single number\n const num = Number(base);\n if (!Number.isInteger(num) || num < min || num > max) {\n return `Invalid value in ${name} field: \"${field}\"`;\n }\n }\n\n return null;\n}\n\n/**\n * Define a scheduled cron job.\n *\n * Jobs are placed in the `jobs/` directory and auto-discovered at deploy time.\n *\n * @example\n * ```ts\n * export default defineJob({\n * name: \"cleanup-expired\",\n * schedule: \"0 3 * * *\", // every day at 3 AM\n * timeout: 60,\n * handler: async (ctx) => {\n * await ctx.db.delete(\"sessions\", \"expired < NOW()\");\n * ctx.log.info(\"Cleaned up expired sessions\");\n * },\n * });\n * ```\n */\nexport function defineJob(config: JobConfig): ResolvedJobConfig {\n if (!config.name || config.name.trim() === \"\") {\n throw new Error(\"Job name is required\");\n }\n\n if (!VALID_JOB_NAME.test(config.name)) {\n throw new Error(\n `Invalid job name \"${config.name}\": must match [a-zA-Z0-9_-]+`,\n );\n }\n\n if (!config.schedule || config.schedule.trim() === \"\") {\n throw new Error(\"Job schedule is required\");\n }\n\n const cronError = validateCronExpression(config.schedule);\n if (cronError !== null) {\n throw new Error(cronError);\n }\n\n if (!config.handler) {\n throw new Error(\"Job handler is required\");\n }\n\n if (config.timeout !== undefined && config.timeout <= 0) {\n throw new Error(\"Job timeout must be a positive number\");\n }\n\n if (config.timeout !== undefined && !Number.isInteger(config.timeout)) {\n throw new Error(\"Job timeout must be an integer\");\n }\n\n if (config.timeout !== undefined && config.timeout > MAX_TIMEOUT_SECONDS) {\n throw new Error(\n `Job timeout ${config.timeout}s exceeds maximum ${MAX_TIMEOUT_SECONDS}s`,\n );\n }\n\n return {\n name: config.name,\n schedule: config.schedule.trim(),\n timeout: config.timeout ?? JOB_DEFAULTS.timeout,\n handler: config.handler,\n };\n}\n","import type { DBClient, Logger, CacheClient, PalbaseBindings } from \"./endpoint.js\";\n\n/** Supported webhook provider types. */\nexport type WebhookProvider =\n | \"stripe\"\n | \"github\"\n | \"twilio\"\n | \"sendgrid\"\n | \"slack\"\n | \"discord\"\n | \"livekit\";\n\n/** Context injected into webhook handlers — no user (webhooks are machine-to-machine). */\nexport interface WebhookContext {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: {\n enqueue(workerName: string, payload: unknown): Promise<string>;\n };\n palbase: PalbaseBindings;\n projectId: string;\n environmentId: string;\n requestId: string;\n}\n\n/** Secret reference — resolves from environment variables at runtime. */\nexport interface EnvSecretRef {\n env: string;\n}\n\n/** Provider-specific event maps for type-safe event handlers. */\nexport interface ProviderEventMap {\n stripe: {\n \"checkout.session.completed\": Record<string, unknown>;\n \"payment_intent.succeeded\": Record<string, unknown>;\n \"payment_intent.payment_failed\": Record<string, unknown>;\n \"customer.subscription.created\": Record<string, unknown>;\n \"customer.subscription.updated\": Record<string, unknown>;\n \"customer.subscription.deleted\": Record<string, unknown>;\n \"invoice.paid\": Record<string, unknown>;\n \"invoice.payment_failed\": Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n github: {\n push: Record<string, unknown>;\n pull_request: Record<string, unknown>;\n issues: Record<string, unknown>;\n \"pull_request.opened\": Record<string, unknown>;\n \"pull_request.closed\": Record<string, unknown>;\n \"pull_request.merged\": Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n twilio: {\n \"message.received\": Record<string, unknown>;\n \"message.sent\": Record<string, unknown>;\n \"call.completed\": Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n sendgrid: {\n delivered: Record<string, unknown>;\n bounce: Record<string, unknown>;\n open: Record<string, unknown>;\n click: Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n slack: {\n url_verification: Record<string, unknown>;\n event_callback: Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n discord: {\n PING: Record<string, unknown>;\n MESSAGE_CREATE: Record<string, unknown>;\n INTERACTION_CREATE: Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n livekit: {\n \"room.started\": Record<string, unknown>;\n \"room.finished\": Record<string, unknown>;\n \"participant.joined\": Record<string, unknown>;\n \"participant.left\": Record<string, unknown>;\n [key: string]: Record<string, unknown>;\n };\n}\n\n/** Event handler function type. */\nexport type WebhookEventHandler<TEvent = Record<string, unknown>> = (\n ctx: WebhookContext,\n event: TEvent,\n) => Promise<void>;\n\n/** Provider-based webhook configuration. */\nexport interface ProviderWebhookConfig<P extends WebhookProvider = WebhookProvider> {\n provider: P;\n secret: EnvSecretRef;\n events: {\n [K in keyof ProviderEventMap[P]]?: WebhookEventHandler<ProviderEventMap[P][K]>;\n };\n}\n\n/** Incoming request representation for custom verify functions. */\nexport interface WebhookRequest {\n headers: Record<string, string>;\n body: string;\n method: string;\n url: string;\n}\n\n/** Custom webhook configuration. */\nexport interface CustomWebhookConfig {\n path: string;\n verify?: (req: WebhookRequest) => Promise<boolean> | boolean;\n handler: (ctx: WebhookContext, payload: Record<string, unknown>) => Promise<void>;\n}\n\n/** Resolved provider webhook (internal). */\nexport interface ResolvedProviderWebhook<P extends WebhookProvider = WebhookProvider> {\n type: \"provider\";\n provider: P;\n secret: EnvSecretRef;\n events: Record<string, WebhookEventHandler>;\n}\n\n/** Resolved custom webhook (internal). */\nexport interface ResolvedCustomWebhook {\n type: \"custom\";\n path: string;\n verify?: (req: WebhookRequest) => Promise<boolean> | boolean;\n handler: (ctx: WebhookContext, payload: Record<string, unknown>) => Promise<void>;\n}\n\n/** Union of resolved webhook types. */\nexport type ResolvedWebhookConfig = ResolvedProviderWebhook | ResolvedCustomWebhook;\n\n/** Valid webhook path pattern: starts with /, alphanumeric + hyphen + slash. */\nconst VALID_WEBHOOK_PATH = /^\\/[a-zA-Z0-9/_-]+$/;\n\n/**\n * Define a provider-based webhook.\n *\n * @example\n * ```ts\n * export default defineWebhook({\n * provider: \"stripe\",\n * secret: { env: \"STRIPE_WEBHOOK_SECRET\" },\n * events: {\n * \"checkout.session.completed\": async (ctx, event) => {\n * await ctx.db.insert(\"orders\", { status: \"paid\" });\n * },\n * },\n * });\n * ```\n */\nexport function defineWebhook<P extends WebhookProvider>(\n config: ProviderWebhookConfig<P>,\n): ResolvedProviderWebhook<P>;\n\n/**\n * Define a custom webhook with optional verification.\n *\n * @example\n * ```ts\n * export default defineWebhook({\n * path: \"/webhooks/livekit\",\n * verify: async (req) => req.headers[\"x-api-key\"] === \"secret\",\n * handler: async (ctx, payload) => {\n * ctx.log.info(\"Received webhook\", payload);\n * },\n * });\n * ```\n */\nexport function defineWebhook(config: CustomWebhookConfig): ResolvedCustomWebhook;\n\nexport function defineWebhook(\n config: ProviderWebhookConfig | CustomWebhookConfig,\n): ResolvedWebhookConfig {\n if (\"provider\" in config) {\n return validateProviderWebhook(config);\n }\n return validateCustomWebhook(config);\n}\n\nfunction validateProviderWebhook<P extends WebhookProvider>(\n config: ProviderWebhookConfig<P>,\n): ResolvedProviderWebhook<P> {\n if (!config.provider) {\n throw new Error(\"Webhook provider is required\");\n }\n\n const validProviders: WebhookProvider[] = [\n \"stripe\", \"github\", \"twilio\", \"sendgrid\", \"slack\", \"discord\", \"livekit\",\n ];\n if (!validProviders.includes(config.provider)) {\n throw new Error(\n `Invalid webhook provider \"${config.provider}\": must be one of ${validProviders.join(\", \")}`,\n );\n }\n\n if (!config.secret) {\n throw new Error(\"Webhook secret is required (use { env: \\\"SECRET_NAME\\\" })\");\n }\n\n if (typeof config.secret.env !== \"string\" || config.secret.env.trim() === \"\") {\n throw new Error(\"Webhook secret env name must be a non-empty string\");\n }\n\n if (!config.events || Object.keys(config.events).length === 0) {\n throw new Error(\"At least one event handler is required\");\n }\n\n for (const [eventName, handler] of Object.entries(config.events)) {\n if (typeof handler !== \"function\") {\n throw new Error(`Event handler for \"${eventName}\" must be a function`);\n }\n }\n\n return {\n type: \"provider\",\n provider: config.provider,\n secret: config.secret,\n events: config.events as Record<string, WebhookEventHandler>,\n };\n}\n\nfunction validateCustomWebhook(config: CustomWebhookConfig): ResolvedCustomWebhook {\n if (!config.path || config.path.trim() === \"\") {\n throw new Error(\"Webhook path is required\");\n }\n\n if (!VALID_WEBHOOK_PATH.test(config.path)) {\n throw new Error(\n `Invalid webhook path \"${config.path}\": must start with / and contain only alphanumeric, hyphen, underscore, slash`,\n );\n }\n\n if (!config.handler) {\n throw new Error(\"Webhook handler is required\");\n }\n\n if (typeof config.handler !== \"function\") {\n throw new Error(\"Webhook handler must be a function\");\n }\n\n if (config.verify !== undefined && typeof config.verify !== \"function\") {\n throw new Error(\"Webhook verify must be a function\");\n }\n\n return {\n type: \"custom\",\n path: config.path,\n verify: config.verify,\n handler: config.handler,\n };\n}\n","import type { DBClient, Logger, CacheClient, PalbaseBindings } from \"./endpoint.js\";\n\n/** Context injected into hook handlers. */\nexport interface HookContext {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: {\n enqueue(workerName: string, payload: unknown): Promise<string>;\n };\n palbase: PalbaseBindings;\n projectId: string;\n environmentId: string;\n}\n\n// --- Auth Event Payloads ---\n\n/** Payload for auth.onUserCreated hook. */\nexport interface UserCreatedEvent {\n user: {\n id: string;\n email: string;\n role: string;\n metadata: Record<string, unknown>;\n createdAt: string;\n };\n}\n\n/** Payload for auth.onSignIn hook. */\nexport interface SignInEvent {\n user: {\n id: string;\n email: string;\n role: string;\n };\n provider: string;\n timestamp: string;\n}\n\n/** Payload for auth.onSignOut hook. */\nexport interface SignOutEvent {\n user: {\n id: string;\n email: string;\n };\n timestamp: string;\n}\n\n/** Payload for auth.onPasswordReset hook. */\nexport interface PasswordResetEvent {\n user: {\n id: string;\n email: string;\n };\n timestamp: string;\n}\n\n// --- Storage Event Payloads ---\n\n/** Payload for storage.onFileUploaded hook. */\nexport interface FileUploadedEvent {\n file: {\n id: string;\n name: string;\n bucket: string;\n path: string;\n size: number;\n contentType: string;\n };\n}\n\n/** Payload for storage.onFileDeleted hook. */\nexport interface FileDeletedEvent {\n file: {\n id: string;\n name: string;\n bucket: string;\n path: string;\n };\n}\n\n// --- Documents Event Payloads ---\n\n/** Payload for documents.onDocumentCreated hook. */\nexport interface DocumentCreatedEvent {\n document: {\n id: string;\n collection: string;\n data: Record<string, unknown>;\n };\n}\n\n/** Payload for documents.onDocumentUpdated hook. */\nexport interface DocumentUpdatedEvent {\n document: {\n id: string;\n collection: string;\n data: Record<string, unknown>;\n previousData: Record<string, unknown>;\n };\n}\n\n/** Payload for documents.onDocumentDeleted hook. */\nexport interface DocumentDeletedEvent {\n document: {\n id: string;\n collection: string;\n data: Record<string, unknown>;\n };\n}\n\n// --- Hook Handler Types ---\n\nexport type HookHandler<TEvent> = (ctx: HookContext, event: TEvent) => Promise<void>;\n\n/** Resolved hook configuration (internal). */\nexport interface ResolvedHook<TEvent = unknown> {\n module: string;\n event: string;\n handler: HookHandler<TEvent>;\n}\n\n// --- Auth Hooks ---\n\nexport const auth = {\n onUserCreated(handler: HookHandler<UserCreatedEvent>): ResolvedHook<UserCreatedEvent> {\n return { module: \"auth\", event: \"user.created\", handler };\n },\n\n onSignIn(handler: HookHandler<SignInEvent>): ResolvedHook<SignInEvent> {\n return { module: \"auth\", event: \"user.sign_in\", handler };\n },\n\n onSignOut(handler: HookHandler<SignOutEvent>): ResolvedHook<SignOutEvent> {\n return { module: \"auth\", event: \"user.sign_out\", handler };\n },\n\n onPasswordReset(handler: HookHandler<PasswordResetEvent>): ResolvedHook<PasswordResetEvent> {\n return { module: \"auth\", event: \"user.password_reset\", handler };\n },\n};\n\n// --- Storage Hooks ---\n\nexport const storage = {\n onFileUploaded(handler: HookHandler<FileUploadedEvent>): ResolvedHook<FileUploadedEvent> {\n return { module: \"storage\", event: \"file.uploaded\", handler };\n },\n\n onFileDeleted(handler: HookHandler<FileDeletedEvent>): ResolvedHook<FileDeletedEvent> {\n return { module: \"storage\", event: \"file.deleted\", handler };\n },\n};\n\n// --- Documents Hooks ---\n\nexport const documents = {\n onDocumentCreated(handler: HookHandler<DocumentCreatedEvent>): ResolvedHook<DocumentCreatedEvent> {\n return { module: \"documents\", event: \"document.created\", handler };\n },\n\n onDocumentUpdated(handler: HookHandler<DocumentUpdatedEvent>): ResolvedHook<DocumentUpdatedEvent> {\n return { module: \"documents\", event: \"document.updated\", handler };\n },\n\n onDocumentDeleted(handler: HookHandler<DocumentDeletedEvent>): ResolvedHook<DocumentDeletedEvent> {\n return { module: \"documents\", event: \"document.deleted\", handler };\n },\n};\n","export { defineEndpoint } from \"./endpoint.js\";\nexport type {\n EndpointConfig,\n EndpointContext,\n RateLimitConfig,\n DBClient,\n Logger,\n CacheClient,\n PalbaseBindings,\n Middleware,\n} from \"./endpoint.js\";\nexport { defineMiddleware } from \"./middleware.js\";\nexport type { MiddlewareContext, MiddlewareHandler } from \"./middleware.js\";\nexport type { User, HttpMethod, AuthConfig } from \"./types.js\";\nexport { HttpError } from \"./errors.js\";\nexport { defineWorker } from \"./worker.js\";\nexport type {\n WorkerConfig,\n ResolvedWorkerConfig,\n BackoffStrategy,\n} from \"./worker.js\";\nexport { defineJob } from \"./job.js\";\nexport type { JobConfig, ResolvedJobConfig, JobContext } from \"./job.js\";\nexport { defineWebhook } from \"./webhook.js\";\nexport type {\n WebhookProvider,\n WebhookContext,\n EnvSecretRef,\n ProviderWebhookConfig,\n CustomWebhookConfig,\n ResolvedProviderWebhook,\n ResolvedCustomWebhook,\n ResolvedWebhookConfig,\n WebhookEventHandler,\n WebhookRequest,\n ProviderEventMap,\n} from \"./webhook.js\";\nexport { auth, storage, documents } from \"./hooks.js\";\nexport type {\n HookContext,\n HookHandler,\n ResolvedHook,\n UserCreatedEvent,\n SignInEvent,\n SignOutEvent,\n PasswordResetEvent,\n FileUploadedEvent,\n FileDeletedEvent,\n DocumentCreatedEvent,\n DocumentUpdatedEvent,\n DocumentDeletedEvent,\n} from \"./hooks.js\";\nexport { z } from \"zod\";\n"],"mappings":"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","names":[]}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/test/index.ts
21
+ var test_exports = {};
22
+ __export(test_exports, {
23
+ createMockDB: () => createMockDB,
24
+ createTestContext: () => createTestContext
25
+ });
26
+ module.exports = __toCommonJS(test_exports);
27
+
28
+ // src/test/mock-db.ts
29
+ function createMockDB() {
30
+ const store = /* @__PURE__ */ new Map();
31
+ const tracked = {
32
+ inserted: /* @__PURE__ */ new Map(),
33
+ updated: /* @__PURE__ */ new Map(),
34
+ deleted: /* @__PURE__ */ new Map()
35
+ };
36
+ return {
37
+ async insert(table, data) {
38
+ const record = { id: crypto.randomUUID(), ...data };
39
+ if (!store.has(table)) store.set(table, []);
40
+ store.get(table).push(record);
41
+ if (!tracked.inserted.has(table)) tracked.inserted.set(table, []);
42
+ tracked.inserted.get(table).push(record);
43
+ return record;
44
+ },
45
+ async update(table, id, data) {
46
+ const rows = store.get(table) ?? [];
47
+ const idx = rows.findIndex((r) => r["id"] === id);
48
+ const updated = idx >= 0 ? { ...rows[idx], ...data } : { id, ...data };
49
+ if (idx >= 0) {
50
+ rows[idx] = updated;
51
+ }
52
+ if (!tracked.updated.has(table)) tracked.updated.set(table, []);
53
+ tracked.updated.get(table).push(updated);
54
+ return updated;
55
+ },
56
+ async delete(table, id) {
57
+ const rows = store.get(table) ?? [];
58
+ const idx = rows.findIndex((r) => r["id"] === id);
59
+ if (idx >= 0) rows.splice(idx, 1);
60
+ if (!tracked.deleted.has(table)) tracked.deleted.set(table, []);
61
+ tracked.deleted.get(table).push(id);
62
+ },
63
+ async findById(table, id) {
64
+ const rows = store.get(table) ?? [];
65
+ return rows.find((r) => r["id"] === id) ?? null;
66
+ },
67
+ async findMany(table, query) {
68
+ const rows = store.get(table) ?? [];
69
+ if (!query) return rows;
70
+ return rows.filter(
71
+ (row) => Object.entries(query).every(([key, val]) => row[key] === val)
72
+ );
73
+ },
74
+ inserted(table) {
75
+ return tracked.inserted.get(table) ?? [];
76
+ },
77
+ updated(table) {
78
+ return tracked.updated.get(table) ?? [];
79
+ },
80
+ deleted(table) {
81
+ return tracked.deleted.get(table) ?? [];
82
+ },
83
+ seed(table, data) {
84
+ store.set(table, [...data]);
85
+ }
86
+ };
87
+ }
88
+
89
+ // src/test/context.ts
90
+ function createMockLogger(logs) {
91
+ return {
92
+ info(message, ...args) {
93
+ logs.push({ level: "info", message, args });
94
+ },
95
+ warn(message, ...args) {
96
+ logs.push({ level: "warn", message, args });
97
+ },
98
+ error(message, ...args) {
99
+ logs.push({ level: "error", message, args });
100
+ },
101
+ debug(message, ...args) {
102
+ logs.push({ level: "debug", message, args });
103
+ }
104
+ };
105
+ }
106
+ function createMockCache() {
107
+ const store = /* @__PURE__ */ new Map();
108
+ return {
109
+ async get(key) {
110
+ return store.get(key) ?? null;
111
+ },
112
+ async set(key, value, _ttl) {
113
+ store.set(key, value);
114
+ },
115
+ async del(key) {
116
+ store.delete(key);
117
+ },
118
+ async incr(key) {
119
+ const current = parseInt(store.get(key) ?? "0", 10);
120
+ const next = current + 1;
121
+ store.set(key, String(next));
122
+ return next;
123
+ }
124
+ };
125
+ }
126
+ function createMockPalbase() {
127
+ return {
128
+ auth: {},
129
+ storage: {}
130
+ };
131
+ }
132
+ function createTestContext(options = {}) {
133
+ const logs = [];
134
+ const db = createMockDB();
135
+ if (options.db?.seed) {
136
+ for (const [table, data] of Object.entries(options.db.seed)) {
137
+ db.seed(table, data);
138
+ }
139
+ }
140
+ const ctx = {
141
+ input: options.input ?? {},
142
+ params: options.params ?? {},
143
+ query: options.query ?? {},
144
+ headers: options.headers ?? {},
145
+ user: options.user ?? null,
146
+ db,
147
+ env: options.env ?? {},
148
+ log: createMockLogger(logs),
149
+ cache: createMockCache(),
150
+ palbase: createMockPalbase(),
151
+ requestId: "req_test_000000000000",
152
+ projectId: "proj_test_000000000000",
153
+ environmentId: "env_test_000000000000",
154
+ logs
155
+ };
156
+ return ctx;
157
+ }
158
+ // Annotate the CommonJS export names for ESM import in node:
159
+ 0 && (module.exports = {
160
+ createMockDB,
161
+ createTestContext
162
+ });
163
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/index.ts","../../src/test/mock-db.ts","../../src/test/context.ts"],"sourcesContent":["export { createTestContext } from \"./context.js\";\nexport type { TestContextOptions, TestContext, LogEntry } from \"./context.js\";\nexport { createMockDB } from \"./mock-db.js\";\nexport type { MockDBClient } from \"./mock-db.js\";\n","import type { DBClient } from \"../endpoint.js\";\n\n/** Tracked records for assertions. */\ninterface TrackedRecords {\n inserted: Map<string, Record<string, unknown>[]>;\n updated: Map<string, Record<string, unknown>[]>;\n deleted: Map<string, string[]>;\n}\n\n/** Mock DB client with tracking and seed data support. */\nexport interface MockDBClient extends DBClient {\n /** Get records inserted into a table. */\n inserted(table: string): Record<string, unknown>[];\n /** Get records updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Get IDs deleted from a table. */\n deleted(table: string): string[];\n /** Pre-seed data into a table for findById/findMany. */\n seed(table: string, data: Record<string, unknown>[]): void;\n}\n\n/** Create a mock DB client with in-memory tracking. */\nexport function createMockDB(): MockDBClient {\n const store = new Map<string, Record<string, unknown>[]>();\n const tracked: TrackedRecords = {\n inserted: new Map(),\n updated: new Map(),\n deleted: new Map(),\n };\n\n return {\n async insert(table: string, data: Record<string, unknown>) {\n const record = { id: crypto.randomUUID(), ...data };\n if (!store.has(table)) store.set(table, []);\n store.get(table)!.push(record);\n if (!tracked.inserted.has(table)) tracked.inserted.set(table, []);\n tracked.inserted.get(table)!.push(record);\n return record;\n },\n\n async update(table: string, id: string, data: Record<string, unknown>) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n const updated = idx >= 0\n ? { ...rows[idx], ...data }\n : { id, ...data };\n if (idx >= 0) {\n rows[idx] = updated;\n }\n if (!tracked.updated.has(table)) tracked.updated.set(table, []);\n tracked.updated.get(table)!.push(updated);\n return updated;\n },\n\n async delete(table: string, id: string) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n if (idx >= 0) rows.splice(idx, 1);\n if (!tracked.deleted.has(table)) tracked.deleted.set(table, []);\n tracked.deleted.get(table)!.push(id);\n },\n\n async findById(table: string, id: string) {\n const rows = store.get(table) ?? [];\n return rows.find((r) => r[\"id\"] === id) ?? null;\n },\n\n async findMany(table: string, query?: Record<string, unknown>) {\n const rows = store.get(table) ?? [];\n if (!query) return rows;\n return rows.filter((row) =>\n Object.entries(query).every(([key, val]) => row[key] === val),\n );\n },\n\n inserted(table: string) {\n return tracked.inserted.get(table) ?? [];\n },\n\n updated(table: string) {\n return tracked.updated.get(table) ?? [];\n },\n\n deleted(table: string) {\n return tracked.deleted.get(table) ?? [];\n },\n\n seed(table: string, data: Record<string, unknown>[]) {\n store.set(table, [...data]);\n },\n };\n}\n","import type {\n CacheClient,\n EndpointContext,\n Logger,\n PalbaseBindings,\n} from \"../endpoint.js\";\nimport type { User } from \"../types.js\";\nimport { createMockDB, type MockDBClient } from \"./mock-db.js\";\n\n/** Options for creating a test context. */\nexport interface TestContextOptions<TInput = unknown> {\n user?: User | null;\n input?: TInput;\n params?: Record<string, string>;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n env?: Record<string, string>;\n db?: { seed?: Record<string, Record<string, unknown>[]> };\n}\n\n/** Log entry captured by the mock logger. */\nexport interface LogEntry {\n level: \"info\" | \"warn\" | \"error\" | \"debug\";\n message: string;\n args: unknown[];\n}\n\n/** Test context with mock helpers exposed. */\nexport interface TestContext<TInput = unknown> extends EndpointContext<TInput> {\n db: MockDBClient;\n /** Captured log entries. */\n logs: LogEntry[];\n}\n\n/** Create a mock logger that captures entries. */\nfunction createMockLogger(logs: LogEntry[]): Logger {\n return {\n info(message: string, ...args: unknown[]) {\n logs.push({ level: \"info\", message, args });\n },\n warn(message: string, ...args: unknown[]) {\n logs.push({ level: \"warn\", message, args });\n },\n error(message: string, ...args: unknown[]) {\n logs.push({ level: \"error\", message, args });\n },\n debug(message: string, ...args: unknown[]) {\n logs.push({ level: \"debug\", message, args });\n },\n };\n}\n\n/** Create a mock in-memory cache. */\nfunction createMockCache(): CacheClient {\n const store = new Map<string, string>();\n\n return {\n async get(key: string) {\n return store.get(key) ?? null;\n },\n async set(key: string, value: string, _ttl?: number) {\n store.set(key, value);\n },\n async del(key: string) {\n store.delete(key);\n },\n async incr(key: string) {\n const current = parseInt(store.get(key) ?? \"0\", 10);\n const next = current + 1;\n store.set(key, String(next));\n return next;\n },\n };\n}\n\n/** Create a mock Palbase service bindings. */\nfunction createMockPalbase(): PalbaseBindings {\n return {\n auth: {},\n storage: {},\n };\n}\n\n/** Create a fully mocked EndpointContext for testing user endpoints. */\nexport function createTestContext<TInput = unknown>(\n options: TestContextOptions<TInput> = {},\n): TestContext<TInput> {\n const logs: LogEntry[] = [];\n const db = createMockDB();\n\n // Seed data if provided\n if (options.db?.seed) {\n for (const [table, data] of Object.entries(options.db.seed)) {\n db.seed(table, data);\n }\n }\n\n const ctx: TestContext<TInput> = {\n input: (options.input ?? {}) as TInput,\n params: options.params ?? {},\n query: options.query ?? {},\n headers: options.headers ?? {},\n user: options.user ?? null,\n db,\n env: options.env ?? {},\n log: createMockLogger(logs),\n cache: createMockCache(),\n palbase: createMockPalbase(),\n requestId: \"req_test_000000000000\",\n projectId: \"proj_test_000000000000\",\n environmentId: \"env_test_000000000000\",\n logs,\n };\n\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsBO,SAAS,eAA6B;AAC3C,QAAM,QAAQ,oBAAI,IAAuC;AACzD,QAAM,UAA0B;AAAA,IAC9B,UAAU,oBAAI,IAAI;AAAA,IAClB,SAAS,oBAAI,IAAI;AAAA,IACjB,SAAS,oBAAI,IAAI;AAAA,EACnB;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,OAAe,MAA+B;AACzD,YAAM,SAAS,EAAE,IAAI,OAAO,WAAW,GAAG,GAAG,KAAK;AAClD,UAAI,CAAC,MAAM,IAAI,KAAK,EAAG,OAAM,IAAI,OAAO,CAAC,CAAC;AAC1C,YAAM,IAAI,KAAK,EAAG,KAAK,MAAM;AAC7B,UAAI,CAAC,QAAQ,SAAS,IAAI,KAAK,EAAG,SAAQ,SAAS,IAAI,OAAO,CAAC,CAAC;AAChE,cAAQ,SAAS,IAAI,KAAK,EAAG,KAAK,MAAM;AACxC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,OAAe,IAAY,MAA+B;AACrE,YAAM,OAAO,MAAM,IAAI,KAAK,KAAK,CAAC;AAClC,YAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,IAAI,MAAM,EAAE;AAChD,YAAM,UAAU,OAAO,IACnB,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,KAAK,IACxB,EAAE,IAAI,GAAG,KAAK;AAClB,UAAI,OAAO,GAAG;AACZ,aAAK,GAAG,IAAI;AAAA,MACd;AACA,UAAI,CAAC,QAAQ,QAAQ,IAAI,KAAK,EAAG,SAAQ,QAAQ,IAAI,OAAO,CAAC,CAAC;AAC9D,cAAQ,QAAQ,IAAI,KAAK,EAAG,KAAK,OAAO;AACxC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,OAAe,IAAY;AACtC,YAAM,OAAO,MAAM,IAAI,KAAK,KAAK,CAAC;AAClC,YAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,IAAI,MAAM,EAAE;AAChD,UAAI,OAAO,EAAG,MAAK,OAAO,KAAK,CAAC;AAChC,UAAI,CAAC,QAAQ,QAAQ,IAAI,KAAK,EAAG,SAAQ,QAAQ,IAAI,OAAO,CAAC,CAAC;AAC9D,cAAQ,QAAQ,IAAI,KAAK,EAAG,KAAK,EAAE;AAAA,IACrC;AAAA,IAEA,MAAM,SAAS,OAAe,IAAY;AACxC,YAAM,OAAO,MAAM,IAAI,KAAK,KAAK,CAAC;AAClC,aAAO,KAAK,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,EAAE,KAAK;AAAA,IAC7C;AAAA,IAEA,MAAM,SAAS,OAAe,OAAiC;AAC7D,YAAM,OAAO,MAAM,IAAI,KAAK,KAAK,CAAC;AAClC,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,KAAK;AAAA,QAAO,CAAC,QAClB,OAAO,QAAQ,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,GAAG,MAAM,IAAI,GAAG,MAAM,GAAG;AAAA,MAC9D;AAAA,IACF;AAAA,IAEA,SAAS,OAAe;AACtB,aAAO,QAAQ,SAAS,IAAI,KAAK,KAAK,CAAC;AAAA,IACzC;AAAA,IAEA,QAAQ,OAAe;AACrB,aAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;AAAA,IACxC;AAAA,IAEA,QAAQ,OAAe;AACrB,aAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;AAAA,IACxC;AAAA,IAEA,KAAK,OAAe,MAAiC;AACnD,YAAM,IAAI,OAAO,CAAC,GAAG,IAAI,CAAC;AAAA,IAC5B;AAAA,EACF;AACF;;;ACxDA,SAAS,iBAAiB,MAA0B;AAClD,SAAO;AAAA,IACL,KAAK,YAAoB,MAAiB;AACxC,WAAK,KAAK,EAAE,OAAO,QAAQ,SAAS,KAAK,CAAC;AAAA,IAC5C;AAAA,IACA,KAAK,YAAoB,MAAiB;AACxC,WAAK,KAAK,EAAE,OAAO,QAAQ,SAAS,KAAK,CAAC;AAAA,IAC5C;AAAA,IACA,MAAM,YAAoB,MAAiB;AACzC,WAAK,KAAK,EAAE,OAAO,SAAS,SAAS,KAAK,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM,YAAoB,MAAiB;AACzC,WAAK,KAAK,EAAE,OAAO,SAAS,SAAS,KAAK,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AAGA,SAAS,kBAA+B;AACtC,QAAM,QAAQ,oBAAI,IAAoB;AAEtC,SAAO;AAAA,IACL,MAAM,IAAI,KAAa;AACrB,aAAO,MAAM,IAAI,GAAG,KAAK;AAAA,IAC3B;AAAA,IACA,MAAM,IAAI,KAAa,OAAe,MAAe;AACnD,YAAM,IAAI,KAAK,KAAK;AAAA,IACtB;AAAA,IACA,MAAM,IAAI,KAAa;AACrB,YAAM,OAAO,GAAG;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,KAAa;AACtB,YAAM,UAAU,SAAS,MAAM,IAAI,GAAG,KAAK,KAAK,EAAE;AAClD,YAAM,OAAO,UAAU;AACvB,YAAM,IAAI,KAAK,OAAO,IAAI,CAAC;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,SAAS,oBAAqC;AAC5C,SAAO;AAAA,IACL,MAAM,CAAC;AAAA,IACP,SAAS,CAAC;AAAA,EACZ;AACF;AAGO,SAAS,kBACd,UAAsC,CAAC,GAClB;AACrB,QAAM,OAAmB,CAAC;AAC1B,QAAM,KAAK,aAAa;AAGxB,MAAI,QAAQ,IAAI,MAAM;AACpB,eAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,QAAQ,GAAG,IAAI,GAAG;AAC3D,SAAG,KAAK,OAAO,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,MAA2B;AAAA,IAC/B,OAAQ,QAAQ,SAAS,CAAC;AAAA,IAC1B,QAAQ,QAAQ,UAAU,CAAC;AAAA,IAC3B,OAAO,QAAQ,SAAS,CAAC;AAAA,IACzB,SAAS,QAAQ,WAAW,CAAC;AAAA,IAC7B,MAAM,QAAQ,QAAQ;AAAA,IACtB;AAAA,IACA,KAAK,QAAQ,OAAO,CAAC;AAAA,IACrB,KAAK,iBAAiB,IAAI;AAAA,IAC1B,OAAO,gBAAgB;AAAA,IACvB,SAAS,kBAAkB;AAAA,IAC3B,WAAW;AAAA,IACX,WAAW;AAAA,IACX,eAAe;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,45 @@
1
+ import { D as DBClient, E as EndpointContext, U as User } from '../endpoint-tZi55HU8.cjs';
2
+ import 'zod';
3
+
4
+ /** Mock DB client with tracking and seed data support. */
5
+ interface MockDBClient extends DBClient {
6
+ /** Get records inserted into a table. */
7
+ inserted(table: string): Record<string, unknown>[];
8
+ /** Get records updated in a table. */
9
+ updated(table: string): Record<string, unknown>[];
10
+ /** Get IDs deleted from a table. */
11
+ deleted(table: string): string[];
12
+ /** Pre-seed data into a table for findById/findMany. */
13
+ seed(table: string, data: Record<string, unknown>[]): void;
14
+ }
15
+ /** Create a mock DB client with in-memory tracking. */
16
+ declare function createMockDB(): MockDBClient;
17
+
18
+ /** Options for creating a test context. */
19
+ interface TestContextOptions<TInput = unknown> {
20
+ user?: User | null;
21
+ input?: TInput;
22
+ params?: Record<string, string>;
23
+ query?: Record<string, string>;
24
+ headers?: Record<string, string>;
25
+ env?: Record<string, string>;
26
+ db?: {
27
+ seed?: Record<string, Record<string, unknown>[]>;
28
+ };
29
+ }
30
+ /** Log entry captured by the mock logger. */
31
+ interface LogEntry {
32
+ level: "info" | "warn" | "error" | "debug";
33
+ message: string;
34
+ args: unknown[];
35
+ }
36
+ /** Test context with mock helpers exposed. */
37
+ interface TestContext<TInput = unknown> extends EndpointContext<TInput> {
38
+ db: MockDBClient;
39
+ /** Captured log entries. */
40
+ logs: LogEntry[];
41
+ }
42
+ /** Create a fully mocked EndpointContext for testing user endpoints. */
43
+ declare function createTestContext<TInput = unknown>(options?: TestContextOptions<TInput>): TestContext<TInput>;
44
+
45
+ export { type LogEntry, type MockDBClient, type TestContext, type TestContextOptions, createMockDB, createTestContext };
@@ -0,0 +1,45 @@
1
+ import { D as DBClient, E as EndpointContext, U as User } from '../endpoint-tZi55HU8.js';
2
+ import 'zod';
3
+
4
+ /** Mock DB client with tracking and seed data support. */
5
+ interface MockDBClient extends DBClient {
6
+ /** Get records inserted into a table. */
7
+ inserted(table: string): Record<string, unknown>[];
8
+ /** Get records updated in a table. */
9
+ updated(table: string): Record<string, unknown>[];
10
+ /** Get IDs deleted from a table. */
11
+ deleted(table: string): string[];
12
+ /** Pre-seed data into a table for findById/findMany. */
13
+ seed(table: string, data: Record<string, unknown>[]): void;
14
+ }
15
+ /** Create a mock DB client with in-memory tracking. */
16
+ declare function createMockDB(): MockDBClient;
17
+
18
+ /** Options for creating a test context. */
19
+ interface TestContextOptions<TInput = unknown> {
20
+ user?: User | null;
21
+ input?: TInput;
22
+ params?: Record<string, string>;
23
+ query?: Record<string, string>;
24
+ headers?: Record<string, string>;
25
+ env?: Record<string, string>;
26
+ db?: {
27
+ seed?: Record<string, Record<string, unknown>[]>;
28
+ };
29
+ }
30
+ /** Log entry captured by the mock logger. */
31
+ interface LogEntry {
32
+ level: "info" | "warn" | "error" | "debug";
33
+ message: string;
34
+ args: unknown[];
35
+ }
36
+ /** Test context with mock helpers exposed. */
37
+ interface TestContext<TInput = unknown> extends EndpointContext<TInput> {
38
+ db: MockDBClient;
39
+ /** Captured log entries. */
40
+ logs: LogEntry[];
41
+ }
42
+ /** Create a fully mocked EndpointContext for testing user endpoints. */
43
+ declare function createTestContext<TInput = unknown>(options?: TestContextOptions<TInput>): TestContext<TInput>;
44
+
45
+ export { type LogEntry, type MockDBClient, type TestContext, type TestContextOptions, createMockDB, createTestContext };
@@ -0,0 +1,135 @@
1
+ // src/test/mock-db.ts
2
+ function createMockDB() {
3
+ const store = /* @__PURE__ */ new Map();
4
+ const tracked = {
5
+ inserted: /* @__PURE__ */ new Map(),
6
+ updated: /* @__PURE__ */ new Map(),
7
+ deleted: /* @__PURE__ */ new Map()
8
+ };
9
+ return {
10
+ async insert(table, data) {
11
+ const record = { id: crypto.randomUUID(), ...data };
12
+ if (!store.has(table)) store.set(table, []);
13
+ store.get(table).push(record);
14
+ if (!tracked.inserted.has(table)) tracked.inserted.set(table, []);
15
+ tracked.inserted.get(table).push(record);
16
+ return record;
17
+ },
18
+ async update(table, id, data) {
19
+ const rows = store.get(table) ?? [];
20
+ const idx = rows.findIndex((r) => r["id"] === id);
21
+ const updated = idx >= 0 ? { ...rows[idx], ...data } : { id, ...data };
22
+ if (idx >= 0) {
23
+ rows[idx] = updated;
24
+ }
25
+ if (!tracked.updated.has(table)) tracked.updated.set(table, []);
26
+ tracked.updated.get(table).push(updated);
27
+ return updated;
28
+ },
29
+ async delete(table, id) {
30
+ const rows = store.get(table) ?? [];
31
+ const idx = rows.findIndex((r) => r["id"] === id);
32
+ if (idx >= 0) rows.splice(idx, 1);
33
+ if (!tracked.deleted.has(table)) tracked.deleted.set(table, []);
34
+ tracked.deleted.get(table).push(id);
35
+ },
36
+ async findById(table, id) {
37
+ const rows = store.get(table) ?? [];
38
+ return rows.find((r) => r["id"] === id) ?? null;
39
+ },
40
+ async findMany(table, query) {
41
+ const rows = store.get(table) ?? [];
42
+ if (!query) return rows;
43
+ return rows.filter(
44
+ (row) => Object.entries(query).every(([key, val]) => row[key] === val)
45
+ );
46
+ },
47
+ inserted(table) {
48
+ return tracked.inserted.get(table) ?? [];
49
+ },
50
+ updated(table) {
51
+ return tracked.updated.get(table) ?? [];
52
+ },
53
+ deleted(table) {
54
+ return tracked.deleted.get(table) ?? [];
55
+ },
56
+ seed(table, data) {
57
+ store.set(table, [...data]);
58
+ }
59
+ };
60
+ }
61
+
62
+ // src/test/context.ts
63
+ function createMockLogger(logs) {
64
+ return {
65
+ info(message, ...args) {
66
+ logs.push({ level: "info", message, args });
67
+ },
68
+ warn(message, ...args) {
69
+ logs.push({ level: "warn", message, args });
70
+ },
71
+ error(message, ...args) {
72
+ logs.push({ level: "error", message, args });
73
+ },
74
+ debug(message, ...args) {
75
+ logs.push({ level: "debug", message, args });
76
+ }
77
+ };
78
+ }
79
+ function createMockCache() {
80
+ const store = /* @__PURE__ */ new Map();
81
+ return {
82
+ async get(key) {
83
+ return store.get(key) ?? null;
84
+ },
85
+ async set(key, value, _ttl) {
86
+ store.set(key, value);
87
+ },
88
+ async del(key) {
89
+ store.delete(key);
90
+ },
91
+ async incr(key) {
92
+ const current = parseInt(store.get(key) ?? "0", 10);
93
+ const next = current + 1;
94
+ store.set(key, String(next));
95
+ return next;
96
+ }
97
+ };
98
+ }
99
+ function createMockPalbase() {
100
+ return {
101
+ auth: {},
102
+ storage: {}
103
+ };
104
+ }
105
+ function createTestContext(options = {}) {
106
+ const logs = [];
107
+ const db = createMockDB();
108
+ if (options.db?.seed) {
109
+ for (const [table, data] of Object.entries(options.db.seed)) {
110
+ db.seed(table, data);
111
+ }
112
+ }
113
+ const ctx = {
114
+ input: options.input ?? {},
115
+ params: options.params ?? {},
116
+ query: options.query ?? {},
117
+ headers: options.headers ?? {},
118
+ user: options.user ?? null,
119
+ db,
120
+ env: options.env ?? {},
121
+ log: createMockLogger(logs),
122
+ cache: createMockCache(),
123
+ palbase: createMockPalbase(),
124
+ requestId: "req_test_000000000000",
125
+ projectId: "proj_test_000000000000",
126
+ environmentId: "env_test_000000000000",
127
+ logs
128
+ };
129
+ return ctx;
130
+ }
131
+ export {
132
+ createMockDB,
133
+ createTestContext
134
+ };
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/mock-db.ts","../../src/test/context.ts"],"sourcesContent":["import type { DBClient } from \"../endpoint.js\";\n\n/** Tracked records for assertions. */\ninterface TrackedRecords {\n inserted: Map<string, Record<string, unknown>[]>;\n updated: Map<string, Record<string, unknown>[]>;\n deleted: Map<string, string[]>;\n}\n\n/** Mock DB client with tracking and seed data support. */\nexport interface MockDBClient extends DBClient {\n /** Get records inserted into a table. */\n inserted(table: string): Record<string, unknown>[];\n /** Get records updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Get IDs deleted from a table. */\n deleted(table: string): string[];\n /** Pre-seed data into a table for findById/findMany. */\n seed(table: string, data: Record<string, unknown>[]): void;\n}\n\n/** Create a mock DB client with in-memory tracking. */\nexport function createMockDB(): MockDBClient {\n const store = new Map<string, Record<string, unknown>[]>();\n const tracked: TrackedRecords = {\n inserted: new Map(),\n updated: new Map(),\n deleted: new Map(),\n };\n\n return {\n async insert(table: string, data: Record<string, unknown>) {\n const record = { id: crypto.randomUUID(), ...data };\n if (!store.has(table)) store.set(table, []);\n store.get(table)!.push(record);\n if (!tracked.inserted.has(table)) tracked.inserted.set(table, []);\n tracked.inserted.get(table)!.push(record);\n return record;\n },\n\n async update(table: string, id: string, data: Record<string, unknown>) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n const updated = idx >= 0\n ? { ...rows[idx], ...data }\n : { id, ...data };\n if (idx >= 0) {\n rows[idx] = updated;\n }\n if (!tracked.updated.has(table)) tracked.updated.set(table, []);\n tracked.updated.get(table)!.push(updated);\n return updated;\n },\n\n async delete(table: string, id: string) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n if (idx >= 0) rows.splice(idx, 1);\n if (!tracked.deleted.has(table)) tracked.deleted.set(table, []);\n tracked.deleted.get(table)!.push(id);\n },\n\n async findById(table: string, id: string) {\n const rows = store.get(table) ?? [];\n return rows.find((r) => r[\"id\"] === id) ?? null;\n },\n\n async findMany(table: string, query?: Record<string, unknown>) {\n const rows = store.get(table) ?? [];\n if (!query) return rows;\n return rows.filter((row) =>\n Object.entries(query).every(([key, val]) => row[key] === val),\n );\n },\n\n inserted(table: string) {\n return tracked.inserted.get(table) ?? [];\n },\n\n updated(table: string) {\n return tracked.updated.get(table) ?? [];\n },\n\n deleted(table: string) {\n return tracked.deleted.get(table) ?? [];\n },\n\n seed(table: string, data: Record<string, unknown>[]) {\n store.set(table, [...data]);\n },\n };\n}\n","import type {\n CacheClient,\n EndpointContext,\n Logger,\n PalbaseBindings,\n} from \"../endpoint.js\";\nimport type { User } from \"../types.js\";\nimport { createMockDB, type MockDBClient } from \"./mock-db.js\";\n\n/** Options for creating a test context. */\nexport interface TestContextOptions<TInput = unknown> {\n user?: User | null;\n input?: TInput;\n params?: Record<string, string>;\n query?: Record<string, string>;\n headers?: Record<string, string>;\n env?: Record<string, string>;\n db?: { seed?: Record<string, Record<string, unknown>[]> };\n}\n\n/** Log entry captured by the mock logger. */\nexport interface LogEntry {\n level: \"info\" | \"warn\" | \"error\" | \"debug\";\n message: string;\n args: unknown[];\n}\n\n/** Test context with mock helpers exposed. */\nexport interface TestContext<TInput = unknown> extends EndpointContext<TInput> {\n db: MockDBClient;\n /** Captured log entries. */\n logs: LogEntry[];\n}\n\n/** Create a mock logger that captures entries. */\nfunction createMockLogger(logs: LogEntry[]): Logger {\n return {\n info(message: string, ...args: unknown[]) {\n logs.push({ level: \"info\", message, args });\n },\n warn(message: string, ...args: unknown[]) {\n logs.push({ level: \"warn\", message, args });\n },\n error(message: string, ...args: unknown[]) {\n logs.push({ level: \"error\", message, args });\n },\n debug(message: string, ...args: unknown[]) {\n logs.push({ level: \"debug\", message, args });\n },\n };\n}\n\n/** Create a mock in-memory cache. */\nfunction createMockCache(): CacheClient {\n const store = new Map<string, string>();\n\n return {\n async get(key: string) {\n return store.get(key) ?? null;\n },\n async set(key: string, value: string, _ttl?: number) {\n store.set(key, value);\n },\n async del(key: string) {\n store.delete(key);\n },\n async incr(key: string) {\n const current = parseInt(store.get(key) ?? \"0\", 10);\n const next = current + 1;\n store.set(key, String(next));\n return next;\n },\n };\n}\n\n/** Create a mock Palbase service bindings. */\nfunction createMockPalbase(): PalbaseBindings {\n return {\n auth: {},\n storage: {},\n };\n}\n\n/** Create a fully mocked EndpointContext for testing user endpoints. */\nexport function createTestContext<TInput = unknown>(\n options: TestContextOptions<TInput> = {},\n): TestContext<TInput> {\n const logs: LogEntry[] = [];\n const db = createMockDB();\n\n // Seed data if provided\n if (options.db?.seed) {\n for (const [table, data] of Object.entries(options.db.seed)) {\n db.seed(table, data);\n }\n }\n\n const ctx: TestContext<TInput> = {\n input: (options.input ?? {}) as TInput,\n params: options.params ?? {},\n query: options.query ?? {},\n headers: options.headers ?? {},\n user: options.user ?? null,\n db,\n env: options.env ?? {},\n log: createMockLogger(logs),\n cache: createMockCache(),\n palbase: createMockPalbase(),\n requestId: \"req_test_000000000000\",\n projectId: \"proj_test_000000000000\",\n environmentId: \"env_test_000000000000\",\n logs,\n };\n\n return ctx;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@palbase/backend",
3
+ "version": "0.1.0",
4
+ "description": "Palbase Backend SDK — defineEndpoint, context types, schema DSL",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/seklabsnet/palbase-ts.git",
9
+ "directory": "backend"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "./test": {
27
+ "import": {
28
+ "types": "./dist/test/index.d.ts",
29
+ "default": "./dist/test/index.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/test/index.d.cts",
33
+ "default": "./dist/test/index.cjs"
34
+ }
35
+ },
36
+ "./db": {
37
+ "import": {
38
+ "types": "./dist/db/index.d.ts",
39
+ "default": "./dist/db/index.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/db/index.d.cts",
43
+ "default": "./dist/db/index.cjs"
44
+ }
45
+ }
46
+ },
47
+ "files": [
48
+ "dist"
49
+ ],
50
+ "dependencies": {
51
+ "zod": "^3.24.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^25.5.2",
55
+ "tsup": "^8.0.0",
56
+ "typescript": "^5.7.0",
57
+ "vitest": "^3.0.0"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest",
63
+ "typecheck": "tsc --noEmit"
64
+ }
65
+ }