@palbase/backend 2.0.0 → 3.0.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.
Files changed (51) hide show
  1. package/README.md +24 -0
  2. package/dist/{chunk-4J3F32SH.js → chunk-B7EUJP5W.js} +38 -9
  3. package/dist/chunk-B7EUJP5W.js.map +1 -0
  4. package/dist/{chunk-L36JLUPO.js → chunk-PHAFZGHN.js} +43 -46
  5. package/dist/chunk-PHAFZGHN.js.map +1 -0
  6. package/dist/db/env.cjs +19 -0
  7. package/dist/db/env.cjs.map +1 -0
  8. package/dist/db/env.d.cts +45 -0
  9. package/dist/db/env.d.ts +45 -0
  10. package/dist/db/env.js +1 -0
  11. package/dist/db/env.js.map +1 -0
  12. package/dist/db/index.cjs +28 -231
  13. package/dist/db/index.cjs.map +1 -1
  14. package/dist/db/index.d.cts +4 -20
  15. package/dist/db/index.d.ts +4 -20
  16. package/dist/db/index.js +3 -233
  17. package/dist/db/index.js.map +1 -1
  18. package/dist/{endpoint-Djk5L6G2.d.ts → endpoint-DJ98tQd6.d.cts} +30 -68
  19. package/dist/{endpoint-BlcY2xNA.d.cts → endpoint-DJ98tQd6.d.ts} +30 -68
  20. package/dist/index-CXUs9iTQ.d.ts +294 -0
  21. package/dist/index-CZAwpQE1.d.cts +294 -0
  22. package/dist/index.cjs +229 -61
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +398 -154
  25. package/dist/index.d.ts +398 -154
  26. package/dist/index.js +147 -12
  27. package/dist/index.js.map +1 -1
  28. package/dist/test/index.cjs +41 -1
  29. package/dist/test/index.cjs.map +1 -1
  30. package/dist/test/index.d.cts +1 -2
  31. package/dist/test/index.d.ts +1 -2
  32. package/dist/test/index.js +1 -1
  33. package/docs/README.md +72 -0
  34. package/docs/background.md +62 -0
  35. package/docs/database.md +73 -0
  36. package/docs/endpoints.md +134 -0
  37. package/docs/errors.md +47 -0
  38. package/docs/events.md +67 -0
  39. package/docs/getting-started.md +65 -0
  40. package/docs/llms-full.txt +1038 -0
  41. package/docs/llms.txt +18 -0
  42. package/docs/migrations.md +98 -0
  43. package/docs/resources.md +94 -0
  44. package/docs/routing.md +67 -0
  45. package/docs/schema.md +92 -0
  46. package/docs/services.md +104 -0
  47. package/package.json +15 -3
  48. package/dist/chunk-4J3F32SH.js.map +0 -1
  49. package/dist/chunk-L36JLUPO.js.map +0 -1
  50. package/dist/schema-BqfEhIC0.d.cts +0 -133
  51. package/dist/schema-BqfEhIC0.d.ts +0 -133
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/endpoint.ts","../src/runtime.ts","../src/db/typed-db.ts","../src/db/schema.ts","../src/db/columns.ts","../src/middleware.ts","../src/errors.ts","../src/worker.ts","../src/job.ts","../src/webhook.ts","../src/hooks.ts"],"sourcesContent":["export { defineEndpoint } from \"./endpoint.js\";\nexport type {\n EndpointConfig,\n PBRequest,\n ClientInfo,\n RateLimitConfig,\n DBClient,\n TxClient,\n FileContext,\n QueueClient,\n Logger,\n CacheClient,\n PalbaseDocsClient,\n PalbaseCollectionRef,\n PalbaseDocumentRef,\n PalbaseDocumentSnapshot,\n PalbaseQuerySnapshot,\n PalbaseWhereOperator,\n PalbaseResult,\n Middleware,\n ErrorDef,\n ErrorMap,\n ErrorThrowers,\n} from \"./endpoint.js\";\nexport {\n Database,\n Documents,\n Storage,\n Cache,\n Queue,\n Log,\n Notifications,\n Flags,\n typedDatabase,\n __setRuntime,\n __runWithRuntime,\n __requestALS,\n __getRuntime,\n} from \"./runtime.js\";\nexport type { RuntimeServices } from \"./runtime.js\";\nexport type {\n PalbaseAuthClient,\n PalbaseStorageClient,\n PalbaseBucketClient,\n PalbaseRealtimeClient,\n PalbaseRealtimeChannel,\n PalbaseRealtimeMessage,\n PalbaseFunctionsClient,\n PalbaseInvokeOptions,\n PalbaseFlagsClient,\n PalbaseFlagContext,\n PalbaseFlagVariant,\n PalbaseFlag,\n PalbaseNotificationsClient,\n PalbasePushClient,\n PalbaseEmailClient,\n PalbaseSmsClient,\n PalbaseInboxClient,\n PalbasePreferencesClient,\n PalbaseAnalyticsClient,\n PalbaseAnalyticsQueryNamespace,\n PalbaseAnalyticsManagementNamespace,\n PalbaseLinksClient,\n PalbaseCmsClient,\n // Shared local types\n PalbaseUser,\n PalbaseSession,\n PalbaseDeviceInfo,\n PalbaseAttestAndroidParams,\n PalbaseAttestAndroidResult,\n PalbaseAttestiOSParams,\n PalbaseAttestiOSResult,\n PalbaseBindDeviceParams,\n PalbaseVerifyRequestSignatureParams,\n PalbaseFileObject,\n PalbaseSignedUrlResponse,\n PalbasePublicUrlResponse,\n PalbaseUploadOptions,\n PalbaseTransformOptions,\n PalbaseListOptions,\n PalbasePushSendParams,\n PalbasePushSendResponse,\n PalbaseEmailSendParams,\n PalbaseEmailSendResponse,\n PalbaseSmsSendParams,\n PalbaseSmsSendResponse,\n PalbaseInboxSendParams,\n PalbaseInboxSendResponse,\n PalbaseInboxMessage,\n PalbaseInboxListOptions,\n PalbaseInboxListResult,\n PalbasePreferences,\n PalbaseRegisterDeviceParams,\n PalbaseDeviceTokenView,\n PalbaseMultiChannelResponse,\n PalbaseAnalyticsProperties,\n PalbaseIdentifyTraits,\n PalbaseCountQueryInput,\n PalbaseCountResult,\n PalbaseEventsQueryInput,\n PalbaseEventsResult,\n PalbaseUsersQueryInput,\n PalbaseUsersResult,\n PalbaseFunnelQueryInput,\n PalbaseFunnelResult,\n PalbaseRetentionQueryInput,\n PalbaseRetentionResult,\n PalbaseCohortQueryInput,\n PalbaseCohortResult,\n PalbaseOverviewResult,\n PalbaseEventNamesResult,\n PalbaseUserDetailResult,\n PalbaseCreateLinkParams,\n PalbaseUpdateLinkParams,\n PalbaseLink,\n PalbaseLinkDetails,\n PalbaseLinkAnalytics,\n PalbaseQrCodeOptions,\n PalbaseMatchParams,\n PalbaseInitialLink,\n PalbaseListLinksOptions,\n PalbaseListLinksResult,\n PalbaseCmsFindOptions,\n PalbaseCmsFindOneOptions,\n} from \"./clients.js\";\nexport { defineSchema, table } from \"./db/schema.js\";\nexport type { SchemaDef, TableDef } from \"./db/schema.js\";\nexport { uuid, text, integer, boolean, timestamp, jsonb, enumType } from \"./db/columns.js\";\nexport type { ColumnBuilder, ColumnDef, ColumnType, OnDeleteAction } from \"./db/columns.js\";\nexport { makeTypedDB } from \"./db/typed-db.js\";\nexport type { TypedDB, TypedTx, TypedTable, InsertShape, RowShape } from \"./db/typed-db.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 WorkerContext,\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","import type { ZodSchema, z } from \"zod\";\nimport type { AuthConfig, HttpMethod, User } from \"./types.js\";\nimport type { MiddlewareContext } from \"./middleware.js\";\nimport type { SchemaDef } from \"./db/schema.js\";\nimport { HttpError } from \"./errors.js\";\nimport type {\n PalbaseAuthClient,\n PalbaseStorageClient,\n PalbaseRealtimeClient,\n PalbaseFunctionsClient,\n PalbaseFlagsClient,\n PalbaseNotificationsClient,\n PalbaseAnalyticsClient,\n PalbaseLinksClient,\n PalbaseCmsClient,\n} from \"./clients.js\";\n\n/** Uploaded file metadata injected into endpoint context when a file is present.\n * `data` is typed as `Uint8Array` for SDK portability (Buffer extends Uint8Array in Node).\n */\nexport interface FileContext {\n filename: string;\n contentType: string;\n size: number;\n data: Uint8Array;\n}\n\n/** Queue client for dispatching background jobs from within a handler. */\nexport interface QueueClient {\n /** Enqueue a job for the named worker. Returns the new job ID. */\n push(worker: string, payload: unknown): Promise<{ jobId: string }>;\n}\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/** The transaction-scoped client passed to `db.transaction(fn)` — same DB ops, no nested transaction. */\nexport type TxClient = Omit<DBClient, \"transaction\">;\n\n/** Database client interface injected into endpoint context. */\nexport interface DBClient {\n /** Run a read-only SQL query (executes in a READ ONLY transaction). */\n query(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]>;\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 * Run an interactive transaction. The callback receives a `tx` with the same\n * DB ops as this client; returning normally commits, throwing rolls back.\n * Nested transactions are not supported.\n */\n transaction<T>(fn: (tx: TxClient) => Promise<T>): Promise<T>;\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/**\n * Cache client interface injected into endpoint context.\n *\n * The cache is JSON-typed: values are serialized to/from JSON, so any JSON\n * value (objects, arrays, numbers, booleans, strings) round-trips. `get<T>`\n * therefore returns `T | null` rather than `string | null`.\n */\nexport interface CacheClient {\n /** Read a value. Returns `null` on a cache miss. */\n get<T = unknown>(key: string): Promise<T | null>;\n /** Write a JSON-serializable value with an optional TTL (seconds). */\n set(key: string, value: unknown, ttl?: number): Promise<void>;\n /** Delete a key. */\n del(key: string): Promise<void>;\n /** Atomically increment an integer counter, returning the new value. */\n incr(key: string): Promise<number>;\n /**\n * Stampede-safe read-through cache fill. On a hit, returns the cached value.\n * On a miss, a single caller (across all pod replicas, coordinated by a\n * distributed lock) runs `fn`, caches the result for `ttl` seconds, and\n * returns it; concurrent callers wait for that result instead of also\n * running `fn`. If no value lands within the lock's TTL, the call rejects —\n * it does NOT run `fn` on timeout (that would reintroduce the stampede).\n *\n * @param ttl value TTL in seconds.\n */\n getOrSet<T>(key: string, ttl: number, fn: () => Promise<T> | T): Promise<T>;\n}\n\n/** Result envelope used across the Palbase Server SDK clients. */\nexport interface PalbaseResult<T> {\n data: T | null;\n error: { message?: string; code?: string } | null;\n status?: number;\n}\n\n/** Document snapshot returned by docs.get(). */\nexport interface PalbaseDocumentSnapshot<T = Record<string, unknown>> {\n id: string;\n exists: boolean;\n data(): T | undefined;\n ref: { path: string };\n}\n\n/** Collection query snapshot returned by collection.get(). */\nexport interface PalbaseQuerySnapshot<T = Record<string, unknown>> {\n docs: PalbaseDocumentSnapshot<T>[];\n empty: boolean;\n size: number;\n}\n\n/** Comparison operators supported by docs.where(). */\nexport type PalbaseWhereOperator =\n | \"==\" | \"!=\" | \"<\" | \"<=\" | \">\" | \">=\" | \"in\" | \"array-contains\";\n\n/** Document reference exposed by Documents.collection(...).doc(...). */\nexport interface PalbaseDocumentRef<T = Record<string, unknown>> {\n readonly path: string;\n set(data: T): Promise<PalbaseResult<void>>;\n get(): Promise<PalbaseResult<PalbaseDocumentSnapshot<T>>>;\n update(data: Partial<T>): Promise<PalbaseResult<void>>;\n delete(): Promise<PalbaseResult<void>>;\n}\n\n/** Collection reference exposed by Documents.collection(...). */\nexport interface PalbaseCollectionRef<T = Record<string, unknown>> {\n readonly path: string;\n doc(id: string): PalbaseDocumentRef<T>;\n add(data: T): Promise<PalbaseResult<PalbaseDocumentRef<T>>>;\n where(field: string, op: PalbaseWhereOperator, value: unknown): PalbaseCollectionRef<T>;\n orderBy(field: string, direction?: \"asc\" | \"desc\"): PalbaseCollectionRef<T>;\n limit(n: number): PalbaseCollectionRef<T>;\n get(): Promise<PalbaseResult<PalbaseQuerySnapshot<T>>>;\n}\n\n/** Docs client surface available on the Documents singleton. */\nexport interface PalbaseDocsClient {\n collection<T = Record<string, unknown>>(path: string): PalbaseCollectionRef<T>;\n doc<T = Record<string, unknown>>(path: string): PalbaseDocumentRef<T>;\n}\n\n/** Calling-client metadata, derived from request headers.\n *\n * Populated from the `X-Palbase-*-Version` / platform headers the client SDKs\n * send. All fields are nullable: a request may come from a non-SDK caller\n * (curl, server-to-server) that sends none of them. The semver comparison\n * helpers (`appVersionAtLeast`, …) arrive in Phase 2 — Phase 1 surfaces only\n * the raw data fields. */\nexport interface ClientInfo {\n /** Palbase SDK version (`X-Palbase-Sdk-Version`), or null. */\n sdkVersion: string | null;\n /** Calling app's own version (`X-Palbase-Client-Version`), or null. */\n appVersion: string | null;\n /** Platform identifier (e.g. \"ios\", \"android\", \"web\"), or null. */\n platform: string | null;\n /** OS version string, or null. */\n osVersion: string | null;\n}\n\n/** Palbase module clients, as a structural bundle.\n *\n * NOT part of the endpoint surface anymore — endpoint handlers reach services\n * via the PascalCase singletons (`Database`, `Documents`, …). This type is\n * retained as an INTERNAL shape for the sibling contexts that still carry a\n * `ctx` (middleware, jobs, workers, hooks, webhooks — out of Phase 1 scope).\n * It is intentionally not re-exported from `index.ts`.\n *\n * Structurally typed against the runtime's `ServerClient`; the runtime injects\n * the real client, so mismatched names would surface as `undefined is not a\n * function` at call time — keep these in sync with `ServerClient`. */\nexport interface PalbaseModuleClients {\n auth: PalbaseAuthClient;\n storage: PalbaseStorageClient;\n docs: PalbaseDocsClient;\n realtime: PalbaseRealtimeClient;\n functions: PalbaseFunctionsClient;\n flags: PalbaseFlagsClient;\n notifications: PalbaseNotificationsClient;\n analytics: PalbaseAnalyticsClient;\n links: PalbaseLinksClient;\n cms: PalbaseCmsClient;\n}\n\n/** Declared-error definition.\n *\n * `code` is the stable snake_case identifier that lands on the wire envelope's\n * `error` field. `status` is the HTTP status code returned. `data`, if set,\n * is a Zod schema whose value rides on the envelope's `data` field — the CLI\n * codegen lowers it to the typed enum's associated value on iOS.\n *\n * `description` is optional human-readable text; the OpenAPI generator uses it\n * for the response description and the iOS codegen surfaces it in the\n * generated method's doc-comment.\n */\nexport interface ErrorDef<TData extends ZodSchema = ZodSchema> {\n status: number;\n code: string;\n description?: string;\n data?: TData;\n}\n\n/** Map of declared errors keyed by their TypeScript-side names.\n *\n * Keys are the friendly names the handler uses (`req.errors.todoLocked`);\n * `code` on each value is the wire identifier. The TS name is what the iOS\n * codegen lowers to (camelCase enum cases), and the wire `code` is what the\n * envelope's `error` field carries.\n */\nexport type ErrorMap = Record<string, ErrorDef>;\n\n/** Throwable proxy projected onto `req.errors` when `errors` is declared.\n *\n * Each entry is a constructor: declared errors with a `data` schema demand\n * the payload as a required argument; declared errors without `data` take\n * none. Throwing the result emits the standard envelope (with `data` when\n * present).\n *\n * throw req.errors.todoNotFound();\n * throw req.errors.todoLocked({ retryAfter: 30 });\n */\nexport type ErrorThrowers<TErrors extends ErrorMap | undefined> =\n TErrors extends ErrorMap\n ? {\n [K in keyof TErrors]: TErrors[K][\"data\"] extends ZodSchema\n ? (data: z.infer<NonNullable<TErrors[K][\"data\"]>>) => HttpError\n : () => HttpError;\n }\n : Record<string, never>;\n\n/** The request-scoped object passed to every endpoint handler.\n *\n * Replaces the old `ctx` god-object. `PBRequest` carries ONLY request-scoped\n * data — the typed `input`, route/query params, headers, the authenticated\n * `user`, calling-client metadata, trace ids, and the endpoint's declared\n * error throwers. Services (`Database`, `Documents`, `Cache`, …) are NOT on\n * the request: import them directly from `@palbase/backend` as singletons.\n *\n * import { defineEndpoint, Database } from \"@palbase/backend\";\n *\n * export default defineEndpoint({\n * method: \"POST\",\n * handler: async (req) => Database.insert(\"todos\", { title: req.input.title }),\n * });\n *\n * Generic parameters:\n * - `TInput` — the validated `input` type (inferred from the `input` Zod\n * schema by `defineEndpoint`). The user-facing form is single-generic:\n * `PBRequest<TodoInput>`.\n * - `TAuthed` — whether `user` is non-null. DEFAULTS to `true` (the common\n * case; the auth pipeline returns 401 before the handler when auth is\n * required, so a non-null `user` is runtime-honest). `defineEndpoint` passes\n * `IsAuthed<TAuth>` here, so `auth: false` → `User | null`.\n * - `TErrors` — the declared `errors` map, typing `req.errors.<name>(...)`.\n */\nexport interface PBRequest<\n TInput = unknown,\n TAuthed extends boolean = true,\n TErrors extends ErrorMap | undefined = undefined,\n> {\n /** Validated request input (body for POST/PUT/PATCH; `{}` otherwise). */\n input: TInput;\n /** Matched route params (e.g. `{ id }` for `/todos/[id]`). */\n params: Record<string, string>;\n /** Parsed query-string params. */\n query: Record<string, string>;\n /** Request headers (lowercase keys). */\n headers: Record<string, string>;\n /** Authenticated user. Non-null (`User`) by default; `User | null` only when\n * the endpoint's `auth` config disables enforcement (driven by `TAuthed`,\n * which `defineEndpoint` computes from the `auth` literal via {@link IsAuthed}). */\n user: TAuthed extends true ? User : User | null;\n /** Calling-client metadata derived from request headers (all nullable). */\n client: ClientInfo;\n /** Uploaded file, or null when the request has no file part. */\n file: FileContext | null;\n /** HTTP method of the incoming request (e.g. \"GET\", \"POST\"). */\n method: string;\n /** Per-request id (`req_<…>`), preserved for back-compat correlation. */\n requestId: string;\n /** W3C trace id (primary correlation key across modules). */\n traceId: string;\n /** W3C span id for this handler invocation. */\n spanId: string;\n /** Typed throwers for the endpoint's declared errors. Empty when the\n * `errors` block is absent on `defineEndpoint`. */\n errors: ErrorThrowers<TErrors>;\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/** The shape an endpoint's `auth` config may take.\n *\n * Either a bare boolean (`true`/`false`) or an object form (`{ required?,\n * role? }`). The object form is `Partial<AuthConfig>` so `required` may be\n * omitted — which the runtime treats as `required: true` (see {@link IsAuthed}).\n */\nexport type AuthSpec = boolean | Partial<AuthConfig>;\n\n/** Compute, at the type level, whether an endpoint authenticates its caller —\n * i.e. whether `req.user` should be `User` (non-null) instead of `User | null`.\n *\n * This mirrors the Go pipeline's enforcement exactly (extract_meta.js + auth.go):\n * a request is authenticated ⟺ `auth === true` OR (`auth` is an object whose\n * `required` is not explicitly `false`). So `req.user` stays nullable ONLY when\n * `auth` is absent (`undefined`), `auth === false`, or `auth: { required: false }`.\n *\n * | `TAuth` | `IsAuthed<TAuth>` |\n * |---------------------------------|-------------------|\n * | `undefined` | `false` |\n * | `true` | `true` |\n * | `false` | `false` |\n * | `{ required: true }` | `true` |\n * | `{ required: false }` | `false` |\n * | `{ role: 'admin' }` (no `required`) | `true` ⚠️ |\n *\n * Order matters: the `{ required: false }` branch is checked first so the\n * object case can't swallow it; `true` then `false` literals are handled\n * explicitly before the catch-all object branch (which encodes the\n * \"required omitted ⇒ required\" corner case).\n */\nexport type IsAuthed<TAuth> = TAuth extends { required: false }\n ? false\n : TAuth extends true\n ? true\n : TAuth extends false\n ? false\n : TAuth extends object\n ? true\n : false;\n\n/** Configuration for defining an endpoint.\n *\n * `TSchema` — optional `SchemaDef` carried on the `schema` field. The runtime\n * uses it to wire a typed `.tables.*` API; authors opt in per endpoint with\n * `typedDatabase(schema)` (see runtime.ts).\n *\n * `TAuth` — captures the literal type of the `auth` field (via the `const`\n * type parameter on `defineEndpoint`) so the handler's `req.user` can be\n * narrowed to non-null `User` when auth is required. See {@link IsAuthed}.\n *\n * `TErrors` — captures the literal `errors` map (via the `const` type\n * parameter on `defineEndpoint`) so `req.errors.<name>(...)` is typed per\n * declared entry. The OpenAPI generator emits each one as a discriminated\n * response under `responses[<status>]`, and the CLI codegen lowers them to\n * a typed `<Endpoint>.Error` enum on iOS.\n */\nexport interface EndpointConfig<\n TInputSchema extends ZodSchema = ZodSchema,\n TOutputSchema extends ZodSchema = ZodSchema,\n TSchema extends SchemaDef | undefined = undefined,\n TAuth extends AuthSpec | undefined = undefined,\n TErrors extends ErrorMap | undefined = undefined,\n> {\n method: HttpMethod;\n auth?: TAuth;\n rateLimit?: RateLimitConfig;\n input?: TInputSchema;\n output?: TOutputSchema;\n schema?: TSchema;\n errors?: TErrors;\n middleware?: Middleware[];\n handler: (\n req: PBRequest<z.infer<TInputSchema>, IsAuthed<TAuth>, TErrors>,\n ) => Promise<z.infer<TOutputSchema>>;\n}\n\n/** Define a type-safe endpoint. The input schema infers the `req.input` type.\n * Pass a `schema` (from `defineSchema`) and wrap `Database` with\n * `typedDatabase(schema)` in the handler for a typed `.tables.*` API.\n *\n * The `const TAuth` and `const TErrors` type parameters capture the `auth`\n * and `errors` fields as literals, so `req.user` narrows to non-null `User`\n * when auth is enforced and `req.errors.<name>(...)` is typed per declared\n * entry.\n */\nexport function defineEndpoint<\n TInputSchema extends ZodSchema,\n TOutputSchema extends ZodSchema,\n TSchema extends SchemaDef | undefined = undefined,\n const TAuth extends AuthSpec | undefined = undefined,\n const TErrors extends ErrorMap | undefined = undefined,\n>(\n config: EndpointConfig<TInputSchema, TOutputSchema, TSchema, TAuth, TErrors>,\n): EndpointConfig<TInputSchema, TOutputSchema, TSchema, TAuth, TErrors> {\n return config;\n}\n","/**\n * runtime.ts — request-scoped service singletons.\n *\n * The backend SDK no longer threads a `ctx` god-object through every handler.\n * Instead, endpoint authors import PascalCase service singletons directly:\n *\n * import { Database, Documents, Cache } from \"@palbase/backend\";\n *\n * export default defineEndpoint({\n * method: \"POST\",\n * handler: async (req) => {\n * const row = await Database.insert(\"todos\", { title: req.input.title });\n * return row;\n * },\n * });\n *\n * The singletons are thin Proxies. Every property access forwards to the live\n * client for the CURRENT request scope, resolved through {@link __getRuntime}.\n *\n * # Request-scope resolution (persistent app-server)\n *\n * The runtime is a long-running Node process that serves many concurrent\n * requests on one event loop (NOT a fresh subprocess per request). A single\n * module-global slot would let one in-flight request's services bleed into\n * another's. So the services are carried in an {@link AsyncLocalStorage} store\n * ({@link __requestALS}) that the runtime sets per request with\n * {@link __runWithRuntime}; every async continuation of that request reads its\n * own store. `__getRuntime` reads the ALS store first; the module-global slot\n * (set by {@link __setRuntime}) is only a fallback for callers that run OUTSIDE\n * an ALS scope (dev-server, unit tests, the legacy single-shot path). Because\n * each `br-<ref>` pod is single-tenant, there is no cross-tenant leakage; the\n * ALS store is what prevents cross-REQUEST leakage within the shared process.\n *\n * The seam that makes `import { Database } from \"@palbase/backend\"` resolve to\n * the runtime-injected client: `@palbase/backend` is marked esbuild-EXTERNAL\n * when the tenant bundle is built, and the package is installed globally in the\n * pod (NODE_PATH=/usr/local/lib/node_modules). So worker.js's\n * `require('@palbase/backend')` and the bundle's `import` resolve to ONE shared\n * module instance — the ALS store and `__setRuntime` slot on that instance are\n * visible to the singletons the bundle imported.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type {\n DBClient,\n CacheClient,\n QueueClient,\n Logger,\n PalbaseDocsClient,\n} from \"./endpoint.js\";\nimport type {\n PalbaseStorageClient,\n PalbaseNotificationsClient,\n PalbaseFlagsClient,\n} from \"./clients.js\";\nimport type { SchemaDef } from \"./db/schema.js\";\nimport type { TypedDB } from \"./db/typed-db.js\";\nimport { makeTypedDB } from \"./db/typed-db.js\";\n\n/** The set of live clients the runtime injects per request scope.\n *\n * EXCLUDED on purpose: Realtime, Functions, CMS, Links, Analytics, Auth. They\n * are not exposed as backend handler singletons (auth lives on the client SDK;\n * the rest are out of scope for backend endpoints). */\nexport interface RuntimeServices {\n Database: DBClient;\n Documents: PalbaseDocsClient;\n Storage: PalbaseStorageClient;\n Cache: CacheClient;\n Queue: QueueClient;\n Log: Logger;\n Notifications: PalbaseNotificationsClient;\n Flags: PalbaseFlagsClient;\n}\n\n/**\n * Per-request store. The persistent runtime runs each request inside\n * {@link __runWithRuntime}, so every async continuation of that request reads\n * its OWN `runtime` (and any other request-scoped fields the runtime adds).\n *\n * Exported with a `__` prefix so the runtime (worker.js) shares the SAME ALS\n * instance across the one module instance — two ALS instances would silently\n * not see each other's stores. NOT part of the public author-facing API.\n */\nexport const __requestALS = new AsyncLocalStorage<{ runtime: RuntimeServices }>();\n\n/** Process-global fallback slot. Used only OUTSIDE an ALS scope (dev-server,\n * unit tests, legacy single-shot worker). Inside the persistent server every\n * request runs in {@link __requestALS}, which takes precedence. */\nlet runtime: RuntimeServices | null = null;\n\n/** Install the live clients in the process-global fallback slot.\n *\n * Persistent-server requests should use {@link __runWithRuntime} instead; this\n * remains for dev-server / tests / the legacy single-shot path that run without\n * an ALS scope. NOT part of the public author-facing API. */\nexport function __setRuntime(services: RuntimeServices): void {\n runtime = services;\n}\n\n/** Run `fn` with `services` bound as the request-scoped runtime.\n *\n * The persistent worker calls this once per request so concurrent requests\n * never share a services slot. NOT part of the public author-facing API. */\nexport function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T {\n return __requestALS.run({ runtime: services }, fn);\n}\n\n/** Read the live clients, throwing if accessed outside a request scope.\n *\n * Resolves the ALS store first (persistent server, per-request), then the\n * process-global fallback (dev-server / tests). NOT part of the public\n * author-facing API — used by the runtime and the singleton Proxies. */\nexport function __getRuntime(): RuntimeServices {\n const scoped = __requestALS.getStore();\n if (scoped) return scoped.runtime;\n if (runtime === null) {\n throw new Error(\n \"Palbase services accessed outside a request scope. The Database/Documents/… \" +\n \"singletons are only available inside an endpoint handler (or after the \" +\n \"runtime has called __runWithRuntime / __setRuntime).\",\n );\n }\n return runtime;\n}\n\n/**\n * Build a Proxy singleton that forwards every property access to the live\n * client named `key` on the current runtime.\n *\n * The single `as RuntimeServices[K]` is the only contained cast in the surface:\n * `Reflect.get` on a typed object returns `unknown` for a `string | symbol`\n * key, but `prop` is constrained to keys of the client interface at the call\n * sites (the exported singletons are typed below), so the forward is sound.\n */\nfunction makeServiceProxy<K extends keyof RuntimeServices>(key: K): RuntimeServices[K] {\n const handler: ProxyHandler<RuntimeServices[K]> = {\n get(_target, prop, receiver) {\n const client = __getRuntime()[key];\n const value = Reflect.get(client as object, prop, receiver) as unknown;\n // Bind methods to their owning client so `this` stays correct when the\n // author destructures or calls `Database.query(...)`.\n return typeof value === \"function\" ? value.bind(client) : value;\n },\n };\n // The Proxy target is irrelevant (all access goes through `get`); the cast\n // names the surface type the singleton presents to authors.\n return new Proxy({} as RuntimeServices[K], handler);\n}\n\n/** The project's own Postgres (pgx, schema `env_<envId>`). Typed CRUD +\n * `query`/`transaction`. For a typed `.tables.<name>` API, wrap with\n * {@link typedDatabase}. */\nexport const Database: DBClient = makeServiceProxy(\"Database\");\n\n/** Firestore-like document client (PalDocs). */\nexport const Documents: PalbaseDocsClient = makeServiceProxy(\"Documents\");\n\n/** Object storage client (buckets, signed URLs). */\nexport const Storage: PalbaseStorageClient = makeServiceProxy(\"Storage\");\n\n/** JSON-typed cache (get/set/incr/getOrSet). */\nexport const Cache: CacheClient = makeServiceProxy(\"Cache\");\n\n/** Background job queue. */\nexport const Queue: QueueClient = makeServiceProxy(\"Queue\");\n\n/** Structured logger. */\nexport const Log: Logger = makeServiceProxy(\"Log\");\n\n/** Push / email / SMS / in-app notifications. */\nexport const Notifications: PalbaseNotificationsClient = makeServiceProxy(\"Notifications\");\n\n/** Feature flags. */\nexport const Flags: PalbaseFlagsClient = makeServiceProxy(\"Flags\");\n\n/**\n * Type the `Database` singleton against a `defineSchema()` result, returning a\n * facade with a typed `.tables.<name>.insert(...)` API alongside the raw\n * `query`/`transaction` ops.\n *\n * const Db = typedDatabase(schema);\n * const todo = await Db.tables.todos.insert({ title: \"x\" });\n *\n * This is per-endpoint (not global module augmentation), so one endpoint's\n * tables never leak into another's `Database` type.\n */\nexport function typedDatabase<TSchema extends SchemaDef>(\n schema: TSchema,\n): TypedDB<TSchema> & DBClient {\n // makeTypedDB wraps the raw DBClient with the typed `.tables` facade (and a\n // typed `transaction`). The full `& DBClient` surface also needs the raw ops\n // (query/insert/update/delete/findById/findMany), which we delegate straight\n // to the `Database` singleton — every call goes through its runtime Proxy.\n const typed = makeTypedDB(schema, Database);\n const rawOps: DBClient = {\n query: (sql, params) => Database.query(sql, params),\n insert: (table, data) => Database.insert(table, data),\n update: (table, id, data) => Database.update(table, id, data),\n delete: (table, id) => Database.delete(table, id),\n findById: (table, id) => Database.findById(table, id),\n findMany: (table, q) => Database.findMany(table, q),\n transaction: (fn) => Database.transaction(fn),\n };\n // typed.transaction (typed-tx) intentionally overrides rawOps.transaction so\n // the callback sees the typed `.tables` API.\n return Object.assign(rawOps, typed);\n}\n","/**\n * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.\n *\n * Derives INSERT and full-row TypeScript types from a `defineSchema()` result\n * and wraps the untyped runtime `DBClient` with a typed facade.\n *\n * No value-any. No `as unknown as X`. The two narrow `as` casts in\n * `makeTypedTable` are safe because:\n * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to\n * typed values; all value types are subsets of `unknown`, so the cast is\n * structurally sound.\n * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,\n * unknown>` which is the erased form of the typed row; we're narrowing back\n * to the precise shape that the schema declared.\n * Both casts are narrowing only (not widening) and correctness is guaranteed\n * by the schema the caller provides.\n */\n\nimport type { ColValue, ColIsOptionalOnInsert, ColumnBuilder } from \"./columns.js\";\nimport type { TableDef, SchemaDef } from \"./schema.js\";\nimport type { DBClient, TxClient } from \"../endpoint.js\";\n\n// ---------------------------------------------------------------------------\n// Key discriminators — split a column map into required vs optional keys.\n// ---------------------------------------------------------------------------\n\n/** Keys of C whose columns are required on INSERT (not nullable, no default). */\ntype RequiredKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;\n}[keyof C];\n\n/** Keys of C whose columns are optional on INSERT (nullable or has a default). */\ntype OptionalKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;\n}[keyof C];\n\n// ---------------------------------------------------------------------------\n// Public shape types — exported so callers can reference them directly.\n// ---------------------------------------------------------------------------\n\n/**\n * The TypeScript type for an INSERT payload for table `T`.\n * - Required: columns that are NOT NULL and have no DB-level default.\n * - Optional: columns that are nullable or carry a default.\n *\n * When all columns are optional, `RequiredKeys<C>` resolves to `never` and\n * the first part becomes `{}`, which is a neutral element for `&`.\n */\nexport type InsertShape<T extends TableDef> = {\n [K in RequiredKeys<T[\"columns\"]>]: ColValue<T[\"columns\"][K]>;\n} & {\n [K in OptionalKeys<T[\"columns\"]>]?: ColValue<T[\"columns\"][K]>;\n};\n\n/**\n * The TypeScript type for a full row returned by the DB for table `T`.\n * Every column is present; nullable columns resolve to `T | null`.\n */\nexport type RowShape<T extends TableDef> = {\n [K in keyof T[\"columns\"]]: ColValue<T[\"columns\"][K]>;\n};\n\n// ---------------------------------------------------------------------------\n// TypedTable + TypedDB interfaces.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor that mirrors the runtime DBClient surface. */\nexport interface TypedTable<T extends TableDef> {\n insert(data: InsertShape<T>): Promise<RowShape<T>>;\n update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<RowShape<T> | null>;\n findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;\n}\n\n/** A typed DB facade covering all tables declared in schema `S`. */\nexport interface TypedDB<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;\n}\n\n/** Transaction-scoped typed facade: same typed tables, no nested transaction. */\nexport interface TypedTx<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime factory.\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a typed table accessor that delegates every call to `raw` using the\n * runtime table name string. Two narrow `as` casts bridge the mapped-type\n * shapes to/from `Record<string, unknown>` — see module-level doc comment.\n *\n * The `raw` param is typed `TxClient` (the op surface shared by `DBClient` and\n * the transaction-scoped client) because this only ever calls the five\n * insert/update/delete/findById/findMany ops — never `transaction`. This lets\n * the same factory wrap both the top-level db and a tx without any cast.\n */\nfunction makeTypedTable<T extends TableDef<Record<string, ColumnBuilder>>>(\n name: string,\n raw: TxClient,\n): TypedTable<T> {\n return {\n insert: (data: InsertShape<T>) =>\n raw.insert(name, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n update: (id: string, data: Partial<InsertShape<T>>) =>\n raw.update(name, id, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n delete: (id: string) => raw.delete(name, id),\n\n findById: (id: string) =>\n raw.findById(name, id) as Promise<RowShape<T> | null>,\n\n findMany: (query?: Partial<RowShape<T>>) =>\n raw.findMany(name, query as Record<string, unknown> | undefined) as Promise<RowShape<T>[]>,\n };\n}\n\n/**\n * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from\n * the provided schema. No behavior change — all calls delegate to `raw` with\n * the table name as a plain string.\n *\n * `buildTables` is the reusable factory that wraps any op-bearing client\n * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped\n * client) into the typed tables map. It is used both for the top-level db\n * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`\n * the runtime yields so the callback sees the same typed `.tables` API.\n *\n * `transaction` delegates straight to `raw.transaction`; the two narrow\n * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings\n * from the dynamically-built tables object to the precise mapped type (TS\n * cannot infer through `Object.keys` iteration) — see module-level doc comment.\n */\nexport function makeTypedDB<S extends SchemaDef>(\n schema: S,\n raw: DBClient,\n): TypedDB<S> {\n function buildTables(client: TxClient): Record<string, TypedTable<TableDef>> {\n const tables = {} as Record<string, TypedTable<TableDef>>;\n for (const key of Object.keys(schema.tables)) {\n const tableDef = schema.tables[key];\n if (tableDef !== undefined) {\n tables[key] = makeTypedTable(tableDef.name, client);\n }\n }\n return tables;\n }\n\n const result = {\n tables: buildTables(raw),\n transaction: <T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T> =>\n raw.transaction((rawTx) => fn({ tables: buildTables(rawTx) } as TypedTx<S>)),\n };\n\n // Narrow cast: `result.tables` is structurally identical to\n // TypedDB<S>[\"tables\"] — each key maps to a TypedTable for the matching\n // TableDef. TS cannot infer the mapped-type result through Object.keys\n // iteration, so a single `as` bridges the gap.\n return result as TypedDB<S>;\n}\n","import type { ColumnBuilder } from \"./columns.js\";\n\n/**\n * A table definition with its columns.\n *\n * The `C` type parameter preserves the precise per-column phantom types so\n * that downstream mapped types (InsertShape, RowShape) can discriminate on\n * them. The default `Record<string, ColumnBuilder>` keeps all existing call\n * sites that use `TableDef` without a type argument (generator.ts, etc.)\n * compiling unchanged.\n */\nexport interface TableDef<\n C extends Record<string, ColumnBuilder> = Record<string, ColumnBuilder>,\n> {\n name: string;\n columns: C;\n}\n\n/**\n * A schema definition containing multiple tables.\n *\n * The `T` type parameter preserves the exact `TableDef<...>` type for each\n * table so that `SchemaDef[\"tables\"][\"rooms\"]` resolves to the precise\n * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.\n */\nexport interface SchemaDef<\n T extends Record<string, TableDef> = Record<string, TableDef>,\n> {\n tables: T;\n}\n\n/**\n * Define a table with a name and columns.\n *\n * The generic parameter `C` preserves each column's phantom type params so\n * that `InsertShape<T>` and `RowShape<T>` can derive precise TypeScript types\n * from the column builders. The runtime return shape is identical to before —\n * only the static type is widened.\n */\nexport function table<C extends Record<string, ColumnBuilder>>(\n name: string,\n columns: C,\n): TableDef<C> {\n return { name, columns };\n}\n\n/** Define a schema containing multiple tables. */\nexport function defineSchema<T extends Record<string, TableDef>>(\n tables: T,\n): SchemaDef<T> {\n return { tables };\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/** Column type identifiers. */\nexport type ColumnType = 'uuid' | 'text' | 'integer' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n *\n * All four params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The four `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E> {\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E> {\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E> {\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E> {\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): ColumnBuilder<K, N, D, E> {\n this._def.references = { table, column };\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" → string (or string | null when N = true)\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → unknown (opaque JSON)\n * - \"enum\" → E (the union of literal values)\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp', infer N, infer _D, infer _E>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer _N, infer _D, infer _E>\n ? unknown\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<infer _K, true, infer _D, infer _E>\n ? true\n : C extends ColumnBuilder<infer _K, infer _N, true, infer _E>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder<'jsonb', false, false, never> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n","import type { DBClient, Logger, CacheClient, PalbaseModuleClients } from \"./endpoint.js\";\nimport type { User } from \"./types.js\";\n\n/** Middleware context — subset of EndpointContext without input (not yet validated). */\nexport interface MiddlewareContext extends PalbaseModuleClients {\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 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.\n *\n * Two ways to construct one:\n *\n * 1. Directly: `throw new HttpError(404, \"todo_not_found\", \"No such todo\")`.\n * Surfaces on the wire (and to iOS) as `BackendError.server(code, status,\n * message, requestId)`.\n * 2. Via `ctx.errors.<name>(data?)` for declared errors — see\n * `EndpointConfig.errors`. The endpoint's OpenAPI spec describes each\n * one, and the CLI codegen emits a typed `<Endpoint>.Error` enum so iOS\n * callers can `catch <Endpoint>.Error.<case>` directly.\n *\n * The optional `data` field carries a structured payload alongside the\n * standard envelope — used by declared errors that need to ship extra context\n * (e.g. `todoLocked({ retryAfter: 30 })`). It rides through to the iOS typed\n * enum's associated value.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly error: string;\n public readonly errorDescription: string;\n public readonly data?: unknown;\n\n constructor(status: number, error: string, errorDescription: string, data?: unknown) {\n super(errorDescription);\n this.name = \"HttpError\";\n this.status = status;\n this.error = error;\n this.errorDescription = errorDescription;\n if (data !== undefined) {\n this.data = data;\n }\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 * When `data` is set, it is appended as a strict-superset field.\n */\n toJSON(requestId?: string): {\n error: string;\n error_description: string;\n status: number;\n request_id?: string;\n data?: unknown;\n } {\n const result: {\n error: string;\n error_description: string;\n status: number;\n request_id?: string;\n data?: unknown;\n } = {\n error: this.error,\n error_description: this.errorDescription,\n status: this.status,\n };\n if (requestId) {\n result.request_id = requestId;\n }\n if (this.data !== undefined) {\n result.data = this.data;\n }\n return result;\n }\n}\n","import type {\n DBClient,\n Logger,\n CacheClient,\n QueueClient,\n PalbaseModuleClients,\n ErrorThrowers,\n} from \"./endpoint.js\";\nimport type { User } from \"./types.js\";\n\n/** Context injected into worker handlers — the non-request-scoped service\n * surface (db/log/cache/queue + module clients), plus correlation ids. Workers\n * are not part of the Phase 1 `ctx → PBRequest` redesign, so they keep their\n * own `ctx`; the job payload arrives as the handler's second argument. */\nexport interface WorkerContext extends PalbaseModuleClients {\n user: User | null;\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: QueueClient;\n errors: ErrorThrowers<undefined>;\n requestId: string;\n projectId: string;\n environmentId: string;\n}\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: WorkerContext,\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: WorkerContext,\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, PalbaseModuleClients, QueueClient } from \"./endpoint.js\";\n\n/** Context injected into job handlers — subset of EndpointContext without request-specific fields. */\nexport interface JobContext extends PalbaseModuleClients {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: QueueClient;\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, PalbaseModuleClients, QueueClient } 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 extends PalbaseModuleClients {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: QueueClient;\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, PalbaseModuleClients, QueueClient } from \"./endpoint.js\";\n\n/** Context injected into hook handlers. */\nexport interface HookContext extends PalbaseModuleClients {\n db: DBClient;\n env: Record<string, string>;\n log: Logger;\n cache: CacheClient;\n queue: QueueClient;\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"],"mappings":"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","names":["table","table"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/runtime.ts","../src/db/schema.ts","../src/db/columns.ts","../src/db/typed-db.ts","../src/db/env-gen.ts","../src/handler.ts","../src/controller.ts","../src/middleware.ts","../src/errors.ts","../src/worker.ts","../src/job.ts","../src/webhook.ts","../src/resource.ts","../src/hooks.ts"],"sourcesContent":["export type {\n PBRequest,\n ClientInfo,\n RateLimitConfig,\n DBClient,\n TxClient,\n FileContext,\n QueueClient,\n Logger,\n CacheClient,\n PalbaseDocsClient,\n PalbaseCollectionRef,\n PalbaseDocumentRef,\n PalbaseDocumentSnapshot,\n PalbaseQuerySnapshot,\n PalbaseWhereOperator,\n PalbaseResult,\n Middleware,\n ErrorDef,\n ErrorMap,\n ErrorThrowers,\n} from \"./endpoint.js\";\nexport {\n Database,\n Documents,\n Storage,\n Cache,\n Queue,\n Log,\n Notifications,\n Flags,\n __setRuntime,\n __runWithRuntime,\n __requestALS,\n __getRuntime,\n} from \"./runtime.js\";\nexport type { RuntimeServices } from \"./runtime.js\";\nexport type {\n PalbaseAuthClient,\n PalbaseStorageClient,\n PalbaseBucketClient,\n PalbaseRealtimeClient,\n PalbaseRealtimeChannel,\n PalbaseRealtimeMessage,\n PalbaseFunctionsClient,\n PalbaseInvokeOptions,\n PalbaseFlagsClient,\n PalbaseFlagContext,\n PalbaseFlagVariant,\n PalbaseFlag,\n PalbaseNotificationsClient,\n PalbasePushClient,\n PalbaseEmailClient,\n PalbaseSmsClient,\n PalbaseInboxClient,\n PalbasePreferencesClient,\n PalbaseAnalyticsClient,\n PalbaseAnalyticsQueryNamespace,\n PalbaseAnalyticsManagementNamespace,\n PalbaseLinksClient,\n PalbaseCmsClient,\n // Shared local types\n PalbaseUser,\n PalbaseSession,\n PalbaseDeviceInfo,\n PalbaseAttestAndroidParams,\n PalbaseAttestAndroidResult,\n PalbaseAttestiOSParams,\n PalbaseAttestiOSResult,\n PalbaseBindDeviceParams,\n PalbaseVerifyRequestSignatureParams,\n PalbaseFileObject,\n PalbaseSignedUrlResponse,\n PalbasePublicUrlResponse,\n PalbaseUploadOptions,\n PalbaseTransformOptions,\n PalbaseListOptions,\n PalbasePushSendParams,\n PalbasePushSendResponse,\n PalbaseEmailSendParams,\n PalbaseEmailSendResponse,\n PalbaseSmsSendParams,\n PalbaseSmsSendResponse,\n PalbaseInboxSendParams,\n PalbaseInboxSendResponse,\n PalbaseInboxMessage,\n PalbaseInboxListOptions,\n PalbaseInboxListResult,\n PalbasePreferences,\n PalbaseRegisterDeviceParams,\n PalbaseDeviceTokenView,\n PalbaseMultiChannelResponse,\n PalbaseAnalyticsProperties,\n PalbaseIdentifyTraits,\n PalbaseCountQueryInput,\n PalbaseCountResult,\n PalbaseEventsQueryInput,\n PalbaseEventsResult,\n PalbaseUsersQueryInput,\n PalbaseUsersResult,\n PalbaseFunnelQueryInput,\n PalbaseFunnelResult,\n PalbaseRetentionQueryInput,\n PalbaseRetentionResult,\n PalbaseCohortQueryInput,\n PalbaseCohortResult,\n PalbaseOverviewResult,\n PalbaseEventNamesResult,\n PalbaseUserDetailResult,\n PalbaseCreateLinkParams,\n PalbaseUpdateLinkParams,\n PalbaseLink,\n PalbaseLinkDetails,\n PalbaseLinkAnalytics,\n PalbaseQrCodeOptions,\n PalbaseMatchParams,\n PalbaseInitialLink,\n PalbaseListLinksOptions,\n PalbaseListLinksResult,\n PalbaseCmsFindOptions,\n PalbaseCmsFindOneOptions,\n} from \"./clients.js\";\nexport { defineSchema } from \"./db/schema.js\";\nexport type { SchemaDef, TableDef, ColumnMap, SchemaInput } from \"./db/schema.js\";\nexport { uuid, text, integer, boolean, timestamp, jsonb, enumType } from \"./db/columns.js\";\nexport type { ColumnBuilder, ColumnDef, ColumnType, OnDeleteAction } from \"./db/columns.js\";\nexport { makeTypedDB } from \"./db/typed-db.js\";\nexport type {\n TypedDB,\n TypedTx,\n TypedTable,\n InsertShape,\n RowShape,\n EnvTypedDatabase,\n EnvTypedTx,\n EnvTypedTable,\n EnvTables,\n} from \"./db/typed-db.js\";\nexport type { Tables, TableTypes } from \"./db/env.js\";\nexport { makeEnvDts } from \"./db/env-gen.js\";\nexport { defineHandler } from \"./handler.js\";\nexport type { HandlerConfig, HandlerDef } from \"./handler.js\";\nexport { defineController, route } from \"./controller.js\";\nexport type { ControllerDef, RouteDef, RouteMap, HttpMethodUpper } from \"./controller.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 WorkerMeta,\n BackoffStrategy,\n} from \"./worker.js\";\nexport { defineJob } from \"./job.js\";\nexport type { JobConfig, ResolvedJobConfig, JobMeta } from \"./job.js\";\nexport { defineWebhook } from \"./webhook.js\";\nexport type {\n WebhookProvider,\n WebhookMeta,\n EnvSecretRef,\n ProviderWebhookConfig,\n CustomWebhookConfig,\n ResolvedProviderWebhook,\n ResolvedCustomWebhook,\n ResolvedWebhookConfig,\n WebhookEventHandler,\n WebhookRequest,\n ProviderEventMap,\n} from \"./webhook.js\";\nexport {\n Resource,\n __registerResource,\n __runResourceBoot,\n __shutdownResources,\n} from \"./resource.js\";\nexport type { ResourceEnv } from \"./resource.js\";\nexport { auth, storage, documents } from \"./hooks.js\";\nexport type {\n HookMeta,\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","/**\n * runtime.ts — request-scoped service singletons.\n *\n * The backend SDK no longer threads a `ctx` god-object through every handler.\n * Instead, endpoint authors import PascalCase service singletons directly:\n *\n * import { Database, Documents, Cache } from \"@palbase/backend\";\n *\n * export default defineHandler({\n * handler: async (req) => {\n * const row = await Database.insert(\"todos\", { title: req.input.title });\n * return row;\n * },\n * });\n *\n * The singletons are thin Proxies. Every property access forwards to the live\n * client for the CURRENT request scope, resolved through {@link __getRuntime}.\n *\n * # Request-scope resolution (persistent app-server)\n *\n * The runtime is a long-running Node process that serves many concurrent\n * requests on one event loop (NOT a fresh subprocess per request). A single\n * module-global slot would let one in-flight request's services bleed into\n * another's. So the services are carried in an {@link AsyncLocalStorage} store\n * ({@link __requestALS}) that the runtime sets per request with\n * {@link __runWithRuntime}; every async continuation of that request reads its\n * own store. `__getRuntime` reads the ALS store first; the module-global slot\n * (set by {@link __setRuntime}) is only a fallback for callers that run OUTSIDE\n * an ALS scope (dev-server, unit tests, the legacy single-shot path). Because\n * each `br-<ref>` pod is single-tenant, there is no cross-tenant leakage; the\n * ALS store is what prevents cross-REQUEST leakage within the shared process.\n *\n * The seam that makes `import { Database } from \"@palbase/backend\"` resolve to\n * the runtime-injected client: `@palbase/backend` is marked esbuild-EXTERNAL\n * when the tenant bundle is built, and the package is installed globally in the\n * pod (NODE_PATH=/usr/local/lib/node_modules). So worker.js's\n * `require('@palbase/backend')` and the bundle's `import` resolve to ONE shared\n * module instance — the ALS store and `__setRuntime` slot on that instance are\n * visible to the singletons the bundle imported.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type {\n DBClient,\n TxClient,\n CacheClient,\n QueueClient,\n Logger,\n PalbaseDocsClient,\n} from \"./endpoint.js\";\nimport type {\n PalbaseStorageClient,\n PalbaseNotificationsClient,\n PalbaseFlagsClient,\n} from \"./clients.js\";\nimport type { EnvTypedDatabase, EnvTypedTx, EnvTables } from \"./db/typed-db.js\";\n\n/** The set of live clients the runtime injects per request scope.\n *\n * EXCLUDED on purpose: Realtime, Functions, CMS, Links, Analytics, Auth. They\n * are not exposed as backend handler singletons (auth lives on the client SDK;\n * the rest are out of scope for backend endpoints). */\nexport interface RuntimeServices {\n Database: DBClient;\n Documents: PalbaseDocsClient;\n Storage: PalbaseStorageClient;\n Cache: CacheClient;\n Queue: QueueClient;\n Log: Logger;\n Notifications: PalbaseNotificationsClient;\n Flags: PalbaseFlagsClient;\n}\n\n/**\n * Per-request store. The persistent runtime runs each request inside\n * {@link __runWithRuntime}, so every async continuation of that request reads\n * its OWN `runtime` (and any other request-scoped fields the runtime adds).\n *\n * Exported with a `__` prefix so the runtime (worker.js) shares the SAME ALS\n * instance across the one module instance — two ALS instances would silently\n * not see each other's stores. NOT part of the public author-facing API.\n */\nexport const __requestALS = new AsyncLocalStorage<{ runtime: RuntimeServices }>();\n\n/** Process-global fallback slot. Used only OUTSIDE an ALS scope (dev-server,\n * unit tests, legacy single-shot worker). Inside the persistent server every\n * request runs in {@link __requestALS}, which takes precedence. */\nlet runtime: RuntimeServices | null = null;\n\n/** Install the live clients in the process-global fallback slot.\n *\n * Persistent-server requests should use {@link __runWithRuntime} instead; this\n * remains for dev-server / tests / the legacy single-shot path that run without\n * an ALS scope. NOT part of the public author-facing API. */\nexport function __setRuntime(services: RuntimeServices): void {\n runtime = services;\n}\n\n/** Run `fn` with `services` bound as the request-scoped runtime.\n *\n * The persistent worker calls this once per request so concurrent requests\n * never share a services slot. NOT part of the public author-facing API. */\nexport function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T {\n return __requestALS.run({ runtime: services }, fn);\n}\n\n/** Read the live clients, throwing if accessed outside a request scope.\n *\n * Resolves the ALS store first (persistent server, per-request), then the\n * process-global fallback (dev-server / tests). NOT part of the public\n * author-facing API — used by the runtime and the singleton Proxies. */\nexport function __getRuntime(): RuntimeServices {\n const scoped = __requestALS.getStore();\n if (scoped) return scoped.runtime;\n if (runtime === null) {\n throw new Error(\n \"Palbase services accessed outside a request scope. The Database/Documents/… \" +\n \"singletons are only available inside an endpoint handler (or after the \" +\n \"runtime has called __runWithRuntime / __setRuntime).\",\n );\n }\n return runtime;\n}\n\n/**\n * Build a Proxy singleton that forwards every property access to the live\n * client named `key` on the current runtime.\n *\n * The single `as RuntimeServices[K]` is the only contained cast in the surface:\n * `Reflect.get` on a typed object returns `unknown` for a `string | symbol`\n * key, but `prop` is constrained to keys of the client interface at the call\n * sites (the exported singletons are typed below), so the forward is sound.\n */\nfunction makeServiceProxy<K extends keyof RuntimeServices>(key: K): RuntimeServices[K] {\n const handler: ProxyHandler<RuntimeServices[K]> = {\n get(_target, prop, receiver) {\n const client = __getRuntime()[key];\n const value = Reflect.get(client as object, prop, receiver) as unknown;\n // Bind methods to their owning client so `this` stays correct when the\n // author destructures or calls `Database.query(...)`.\n return typeof value === \"function\" ? value.bind(client) : value;\n },\n };\n // The Proxy target is irrelevant (all access goes through `get`); the cast\n // names the surface type the singleton presents to authors.\n return new Proxy({} as RuntimeServices[K], handler);\n}\n\n/**\n * Build the `.tables` accessor for an op-bearing client (the top-level\n * `Database` or a transaction-scoped `tx`). Each `tables.<name>` access\n * returns a small object that forwards the five CRUD ops to the underlying\n * client using `name` as the string table identifier. The shapes are typed\n * against the generated `palbase-env.d.ts` (`EnvTables`); at runtime they are\n * plain string-keyed calls, so no schema value is needed here.\n *\n * Returns `EnvTables` — TS cannot infer the mapped type through the Proxy, so\n * a single structural narrowing names the surface (the proxy returns a\n * correctly-shaped accessor for whatever string member is read).\n */\nfunction makeTablesAccessor(ops: () => TxClient): EnvTables {\n const tablesProxy = new Proxy(\n {},\n {\n get(_t, prop: string | symbol) {\n if (typeof prop !== \"string\") return undefined;\n const name = prop;\n return {\n insert: (data: Record<string, unknown>) => ops().insert(name, data),\n update: (id: string, data: Record<string, unknown>) => ops().update(name, id, data),\n delete: (id: string) => ops().delete(name, id),\n findById: (id: string) => ops().findById(name, id),\n findMany: (query?: Record<string, unknown>) => ops().findMany(name, query),\n };\n },\n },\n );\n return tablesProxy as EnvTables;\n}\n\n/** The raw string-keyed `DBClient` for the current request scope. */\nconst rawDatabase: DBClient = makeServiceProxy(\"Database\");\n\n/**\n * The project's own Postgres (pgx, schema `env_<envId>`).\n *\n * Typed by default: `Database.tables.<name>.insert({...})` is typed against\n * the project's generated `palbase-env.d.ts` with NO import and NO generic.\n * The raw string ops (`query`/`insert`/`update`/`delete`/`findById`/`findMany`)\n * are also available for dynamic table names and read-only SQL.\n *\n * @example\n * import { Database } from \"@palbase/backend\";\n *\n * const todo = await Database.tables.todos.insert({ title: req.input.title });\n * todo.id; // string ✓\n * const rows = await Database.query(\"SELECT id FROM todos WHERE done = $1\", [false]);\n */\nexport const Database: EnvTypedDatabase = Object.assign(\n // Spread the raw ops onto a fresh object so the added `tables`/typed\n // `transaction` members live alongside them. Each op still forwards through\n // the request-scoped runtime Proxy.\n {\n query: (sql: string, params?: unknown[]) => rawDatabase.query(sql, params),\n insert: (table: string, data: Record<string, unknown>) => rawDatabase.insert(table, data),\n update: (table: string, id: string, data: Record<string, unknown>) =>\n rawDatabase.update(table, id, data),\n delete: (table: string, id: string) => rawDatabase.delete(table, id),\n findById: (table: string, id: string) => rawDatabase.findById(table, id),\n findMany: (table: string, query?: Record<string, unknown>) =>\n rawDatabase.findMany(table, query),\n } satisfies Omit<DBClient, \"transaction\">,\n {\n tables: makeTablesAccessor(() => rawDatabase),\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T> {\n return rawDatabase.transaction((rawTx) =>\n fn({ tables: makeTablesAccessor(() => rawTx) }),\n );\n },\n },\n);\n\n/** Firestore-like document client (PalDocs). */\nexport const Documents: PalbaseDocsClient = makeServiceProxy(\"Documents\");\n\n/** Object storage client (buckets, signed URLs). */\nexport const Storage: PalbaseStorageClient = makeServiceProxy(\"Storage\");\n\n/** JSON-typed cache (get/set/incr/getOrSet). */\nexport const Cache: CacheClient = makeServiceProxy(\"Cache\");\n\n/** Background job queue. */\nexport const Queue: QueueClient = makeServiceProxy(\"Queue\");\n\n/** Structured logger. */\nexport const Log: Logger = makeServiceProxy(\"Log\");\n\n/** Push / email / SMS / in-app notifications. */\nexport const Notifications: PalbaseNotificationsClient = makeServiceProxy(\"Notifications\");\n\n/** Feature flags. */\nexport const Flags: PalbaseFlagsClient = makeServiceProxy(\"Flags\");\n","import type { ColumnBuilder } from \"./columns.js\";\n\n/**\n * A map of column builders keyed by column name — the value you write under\n * each key of `defineSchema({ tables: { <name>: <columns> } })`.\n *\n * The default `Record<string, ColumnBuilder>` keeps bare references compiling\n * without a type argument.\n */\nexport type ColumnMap = Record<string, ColumnBuilder>;\n\n/**\n * A table definition with its name and columns.\n *\n * The runtime value shape — `{ name, columns }` — is the contract the Go\n * runtime's `schema_extract.js` reads (it keys tables by `tableDef.name`).\n * `defineSchema` derives `name` from the object key, so authors never repeat\n * the table name.\n *\n * The `C` type parameter preserves the precise per-column phantom types so\n * that downstream mapped types (InsertShape, RowShape) can discriminate on\n * them.\n */\nexport interface TableDef<C extends ColumnMap = ColumnMap> {\n name: string;\n columns: C;\n}\n\n/**\n * A schema definition containing multiple tables, keyed by table name.\n *\n * The `T` type parameter preserves the exact `TableDef<...>` type for each\n * table so that `SchemaDef[\"tables\"][\"rooms\"]` resolves to the precise\n * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.\n */\nexport interface SchemaDef<\n T extends Record<string, TableDef> = Record<string, TableDef>,\n> {\n tables: T;\n}\n\n/** The author-facing input to `defineSchema` — a `tables` map whose keys are\n * the table names and whose values are the column maps. */\nexport interface SchemaInput<T extends Record<string, ColumnMap> = Record<string, ColumnMap>> {\n tables: T;\n}\n\n/** Map the author's `{ tables: { <name>: <columns> } }` input to the\n * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape. */\ntype TablesFromInput<T extends Record<string, ColumnMap>> = {\n [K in keyof T]: TableDef<T[K]>;\n};\n\n/**\n * Define a schema. The table NAME comes from the object key — there is one\n * canonical form:\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * id: uuid().primaryKey().defaultRandom(),\n * title: text().notNull(),\n * },\n * },\n * });\n *\n * The returned value is `{ tables: { todos: { name: \"todos\", columns: {...} } } }`\n * — the exact shape the runtime schema extractor parses. Per-column phantom\n * types are preserved so `Database.tables.todos.insert({...})` is typed.\n *\n * @example\n * import { defineSchema, uuid, text, timestamp } from \"@palbase/backend\";\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * id: uuid().primaryKey().defaultRandom(),\n * title: text().notNull(),\n * done: boolean().default(false),\n * created_at: timestamp().defaultNow(),\n * },\n * },\n * });\n */\nexport function defineSchema<T extends Record<string, ColumnMap>>(\n input: SchemaInput<T>,\n): SchemaDef<TablesFromInput<T>> {\n const tables = {} as TablesFromInput<T>;\n for (const name of Object.keys(input.tables) as (keyof T)[]) {\n tables[name] = { name: name as string, columns: input.tables[name] };\n }\n return { tables };\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/** Column type identifiers. */\nexport type ColumnType = 'uuid' | 'text' | 'integer' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n *\n * All four params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The four `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E> {\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E> {\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E> {\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E> {\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): ColumnBuilder<K, N, D, E> {\n this._def.references = { table, column };\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" → string (or string | null when N = true)\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → unknown (opaque JSON)\n * - \"enum\" → E (the union of literal values)\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp', infer N, infer _D, infer _E>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer _N, infer _D, infer _E>\n ? unknown\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<infer _K, true, infer _D, infer _E>\n ? true\n : C extends ColumnBuilder<infer _K, infer _N, true, infer _E>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder<'jsonb', false, false, never> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n","/**\n * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.\n *\n * Derives INSERT and full-row TypeScript types from a `defineSchema()` result\n * and wraps the untyped runtime `DBClient` with a typed facade.\n *\n * No value-any. No `as unknown as X`. The two narrow `as` casts in\n * `makeTypedTable` are safe because:\n * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to\n * typed values; all value types are subsets of `unknown`, so the cast is\n * structurally sound.\n * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,\n * unknown>` which is the erased form of the typed row; we're narrowing back\n * to the precise shape that the schema declared.\n * Both casts are narrowing only (not widening) and correctness is guaranteed\n * by the schema the caller provides.\n */\n\nimport type { ColValue, ColIsOptionalOnInsert, ColumnBuilder } from \"./columns.js\";\nimport type { TableDef, SchemaDef } from \"./schema.js\";\nimport type { Tables, TableTypes } from \"./env.js\";\nimport type { DBClient, TxClient } from \"../endpoint.js\";\n\n// ---------------------------------------------------------------------------\n// Key discriminators — split a column map into required vs optional keys.\n// ---------------------------------------------------------------------------\n\n/** Keys of C whose columns are required on INSERT (not nullable, no default). */\ntype RequiredKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;\n}[keyof C];\n\n/** Keys of C whose columns are optional on INSERT (nullable or has a default). */\ntype OptionalKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;\n}[keyof C];\n\n// ---------------------------------------------------------------------------\n// Public shape types — exported so callers can reference them directly.\n// ---------------------------------------------------------------------------\n\n/**\n * The TypeScript type for an INSERT payload for table `T`.\n * - Required: columns that are NOT NULL and have no DB-level default.\n * - Optional: columns that are nullable or carry a default.\n *\n * When all columns are optional, `RequiredKeys<C>` resolves to `never` and\n * the first part becomes `{}`, which is a neutral element for `&`.\n */\nexport type InsertShape<T extends TableDef> = {\n [K in RequiredKeys<T[\"columns\"]>]: ColValue<T[\"columns\"][K]>;\n} & {\n [K in OptionalKeys<T[\"columns\"]>]?: ColValue<T[\"columns\"][K]>;\n};\n\n/**\n * The TypeScript type for a full row returned by the DB for table `T`.\n * Every column is present; nullable columns resolve to `T | null`.\n */\nexport type RowShape<T extends TableDef> = {\n [K in keyof T[\"columns\"]]: ColValue<T[\"columns\"][K]>;\n};\n\n// ---------------------------------------------------------------------------\n// TypedTable + TypedDB interfaces.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor that mirrors the runtime DBClient surface. */\nexport interface TypedTable<T extends TableDef> {\n insert(data: InsertShape<T>): Promise<RowShape<T>>;\n update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<RowShape<T> | null>;\n findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;\n}\n\n/** A typed DB facade covering all tables declared in schema `S`. */\nexport interface TypedDB<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;\n}\n\n/** Transaction-scoped typed facade: same typed tables, no nested transaction. */\nexport interface TypedTx<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime factory.\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a typed table accessor that delegates every call to `raw` using the\n * runtime table name string. Two narrow `as` casts bridge the mapped-type\n * shapes to/from `Record<string, unknown>` — see module-level doc comment.\n *\n * The `raw` param is typed `TxClient` (the op surface shared by `DBClient` and\n * the transaction-scoped client) because this only ever calls the five\n * insert/update/delete/findById/findMany ops — never `transaction`. This lets\n * the same factory wrap both the top-level db and a tx without any cast.\n */\nfunction makeTypedTable<T extends TableDef<Record<string, ColumnBuilder>>>(\n name: string,\n raw: TxClient,\n): TypedTable<T> {\n return {\n insert: (data: InsertShape<T>) =>\n raw.insert(name, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n update: (id: string, data: Partial<InsertShape<T>>) =>\n raw.update(name, id, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n delete: (id: string) => raw.delete(name, id),\n\n findById: (id: string) =>\n raw.findById(name, id) as Promise<RowShape<T> | null>,\n\n findMany: (query?: Partial<RowShape<T>>) =>\n raw.findMany(name, query as Record<string, unknown> | undefined) as Promise<RowShape<T>[]>,\n };\n}\n\n/**\n * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from\n * the provided schema. No behavior change — all calls delegate to `raw` with\n * the table name as a plain string.\n *\n * `buildTables` is the reusable factory that wraps any op-bearing client\n * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped\n * client) into the typed tables map. It is used both for the top-level db\n * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`\n * the runtime yields so the callback sees the same typed `.tables` API.\n *\n * `transaction` delegates straight to `raw.transaction`; the two narrow\n * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings\n * from the dynamically-built tables object to the precise mapped type (TS\n * cannot infer through `Object.keys` iteration) — see module-level doc comment.\n */\nexport function makeTypedDB<S extends SchemaDef>(\n schema: S,\n raw: DBClient,\n): TypedDB<S> {\n function buildTables(client: TxClient): Record<string, TypedTable<TableDef>> {\n const tables = {} as Record<string, TypedTable<TableDef>>;\n for (const key of Object.keys(schema.tables)) {\n const tableDef = schema.tables[key];\n if (tableDef !== undefined) {\n tables[key] = makeTypedTable(tableDef.name, client);\n }\n }\n return tables;\n }\n\n const result = {\n tables: buildTables(raw),\n transaction: <T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T> =>\n raw.transaction((rawTx) => fn({ tables: buildTables(rawTx) } as TypedTx<S>)),\n };\n\n // Narrow cast: `result.tables` is structurally identical to\n // TypedDB<S>[\"tables\"] — each key maps to a TypedTable for the matching\n // TableDef. TS cannot infer the mapped-type result through Object.keys\n // iteration, so a single `as` bridges the gap.\n return result as TypedDB<S>;\n}\n\n// ---------------------------------------------------------------------------\n// Env-augmentation-driven typed surface — the typed-by-default `Database`.\n//\n// These types read the globally-augmented `Tables` interface from\n// `@palbase/backend/env` (filled by the generated `palbase-env.d.ts`). They\n// back `Database.tables.<name>` so handler code is typed with no import and no\n// generic (C5). They DELIBERATELY do not reference `ColumnBuilder` — the env\n// `Tables` interface carries flat `row`/`insert` object types.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor derived from one env `Tables` entry's flat shapes. */\nexport interface EnvTypedTable<T extends TableTypes> {\n insert(data: T[\"insert\"]): Promise<T[\"row\"]>;\n update(id: string, data: Partial<T[\"insert\"]>): Promise<T[\"row\"]>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<T[\"row\"] | null>;\n findMany(query?: Partial<T[\"row\"]>): Promise<T[\"row\"][]>;\n}\n\n/** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`\n * interface. When no schema is declared `Tables` is empty, so `tables` is an\n * empty object — accessing `.tables.foo` is then a compile error (no member). */\nexport type EnvTables = {\n [K in keyof Tables]: EnvTypedTable<Tables[K]>;\n};\n\n/** Transaction-scoped typed facade for the env-augmented surface: same typed\n * tables, no nested transaction. */\nexport interface EnvTypedTx {\n tables: EnvTables;\n}\n\n/**\n * The typed-by-default Database surface: the raw string-keyed `DBClient` ops\n * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`\n * and a `transaction` whose callback receives the typed tables.\n *\n * `transaction` is declared here (overriding `DBClient[\"transaction\"]`) so the\n * `tx` the callback receives carries the typed `.tables` API.\n */\nexport interface EnvTypedDatabase extends Omit<DBClient, \"transaction\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n}\n","/**\n * env-gen.ts — generate the `palbase-env.d.ts` text from a `defineSchema()`\n * result.\n *\n * The CLI (`palbase serve` / codegen) and the deploy pipeline call\n * {@link makeEnvDts} with the project's schema and write the returned string to\n * `palbase-env.d.ts` at the project root. That file AUGMENTS the\n * `@palbase/backend/env` `Tables` interface (controlled global augmentation,\n * C5) so `Database.tables.<name>` is typed with no import and no generic.\n *\n * The output is FLAT: each table gets a `{ row: {...}; insert: {...} }` entry\n * with plain TypeScript object types. The phantom `ColumnBuilder<...>` type\n * NEVER appears in the generated `.d.ts` — that type only lives at authoring\n * time inside `db/schema.ts`.\n */\n\nimport type { ColumnDef } from \"./columns.js\";\nimport type { SchemaDef, TableDef } from \"./schema.js\";\n\n/** The TypeScript value type for a column, ignoring nullability (added by the\n * caller). Mirrors the `ColValue` mapped type in columns.ts exactly. */\nfunction baseTsType(def: ColumnDef): string {\n switch (def.type) {\n case \"uuid\":\n case \"text\":\n case \"timestamp\":\n return \"string\";\n case \"integer\":\n return \"number\";\n case \"boolean\":\n return \"boolean\";\n case \"jsonb\":\n return \"unknown\";\n case \"enum\": {\n const values = def.enumValues ?? [];\n if (values.length === 0) return \"string\";\n return values.map((v) => JSON.stringify(v)).join(\" | \");\n }\n default:\n return \"unknown\";\n }\n}\n\n/** The full row type for a column: base type, `| null` when nullable. */\nfunction rowType(def: ColumnDef): string {\n const base = baseTsType(def);\n return def.nullable ? `${base} | null` : base;\n}\n\n/** True when a column may be omitted on INSERT — nullable OR has any default.\n * Mirrors `ColIsOptionalOnInsert` in columns.ts. */\nfunction optionalOnInsert(def: ColumnDef): boolean {\n return (\n def.nullable === true ||\n def.defaultRandom === true ||\n def.defaultNow === true ||\n def.defaultValue !== undefined\n );\n}\n\n/** Emit the `row: {...}` and `insert: {...}` blocks for one table at the given\n * base indentation. */\nfunction tableBlock(table: TableDef, indent: string): string {\n const cols = Object.entries(table.columns);\n const rowLines = cols.map(([col, builder]) => {\n return `${indent} ${col}: ${rowType(builder._def)};`;\n });\n const insertLines = cols.map(([col, builder]) => {\n const def = builder._def;\n const opt = optionalOnInsert(def) ? \"?\" : \"\";\n return `${indent} ${col}${opt}: ${rowType(def)};`;\n });\n return [\n `${indent}${table.name}: {`,\n `${indent} row: {`,\n ...rowLines,\n `${indent} };`,\n `${indent} insert: {`,\n ...insertLines,\n `${indent} };`,\n `${indent}};`,\n ].join(\"\\n\");\n}\n\n/**\n * Generate the full `palbase-env.d.ts` text for a schema.\n *\n * @example\n * import { makeEnvDts } from \"@palbase/backend\";\n * import schema from \"./db/schema.js\";\n * writeFileSync(\"palbase-env.d.ts\", makeEnvDts(schema));\n */\nexport function makeEnvDts(schema: SchemaDef): string {\n const tableNames = Object.keys(schema.tables);\n const blocks = tableNames.map((name) => tableBlock(schema.tables[name]!, \" \"));\n const body = blocks.length > 0 ? `\\n${blocks.join(\"\\n\")}\\n ` : \"\";\n return `// AUTO-GENERATED by @palbase/backend — DO NOT EDIT.\n// Regenerated from db/schema.ts on every \\`palbase serve\\` / deploy.\n// Augments the @palbase/backend/env \\`Tables\\` interface so \\`Database.tables.*\\`\n// is typed with no import and no generic.\n\ndeclare module \"@palbase/backend/env\" {\n interface Tables {${body}}\n}\n\nexport {};\n`;\n}\n","import type { ZodSchema, z } from \"zod\";\nimport type {\n AuthSpec,\n IsAuthed,\n ErrorMap,\n Middleware,\n RateLimitConfig,\n PBRequest,\n} from \"./endpoint.js\";\n\n/**\n * handler.ts — `defineHandler`: an endpoint UNIT (schema + thin logic), one per\n * file, with NO routing.\n *\n * Everything that types `req` lives here (colocated): `auth` → `req.user`\n * nullability, `input` → `req.input`, `errors` → `req.errors`, `output` → the\n * return type. There is NO `method`/`path` on a handler — the HTTP verb + path\n * are declared in a controller's route map, NOT on the handler. A handler file\n * does:\n *\n * // handlers/places/import-nearby.ts\n * export default defineHandler({ auth, input, output, errors, handler });\n *\n * # Runtime-readable shape (read by the Go runtime's extract_meta.js)\n *\n * `defineHandler` returns a {@link HandlerDef}: a plain object marked with\n * `__palbase: \"handler\"` carrying the optional `auth`/`input`/`output`/`errors`\n * and the `handler` function. There is deliberately NO `method`/`path` on it —\n * a handler is route-agnostic.\n */\n\n/** Configuration accepted by {@link defineHandler}. Carries the schema +\n * auth/errors/middleware that type `req`, but NO `method` (routing lives in\n * controllers). */\nexport interface HandlerConfig<\n TInputSchema extends ZodSchema = ZodSchema,\n TOutputSchema extends ZodSchema = ZodSchema,\n TAuth extends AuthSpec | undefined = undefined,\n TErrors extends ErrorMap | undefined = undefined,\n> {\n auth?: TAuth;\n rateLimit?: RateLimitConfig;\n input?: TInputSchema;\n output?: TOutputSchema;\n errors?: TErrors;\n middleware?: Middleware[];\n handler: (\n req: PBRequest<z.infer<TInputSchema>, IsAuthed<TAuth>, TErrors>,\n ) => Promise<z.infer<TOutputSchema>> | z.infer<TOutputSchema>;\n}\n\n/**\n * The runtime-readable handler descriptor returned by {@link defineHandler}.\n *\n * Shape (the Go runtime's `extract_meta.js` reads this off the default export):\n *\n * {\n * __palbase: \"handler\",\n * auth?: AuthSpec,\n * rateLimit?: RateLimitConfig,\n * input?: ZodSchema,\n * output?: ZodSchema,\n * errors?: ErrorMap,\n * middleware?: Middleware[],\n * handler: (req) => result, // the invoked function\n * }\n *\n * NOTE: there is NO `method`/`path` — those come from the controller route map.\n */\nexport interface HandlerDef<\n TInputSchema extends ZodSchema = ZodSchema,\n TOutputSchema extends ZodSchema = ZodSchema,\n TAuth extends AuthSpec | undefined = undefined,\n TErrors extends ErrorMap | undefined = undefined,\n> {\n /** Discriminant the runtime + a controller route check read. */\n readonly __palbase: \"handler\";\n auth?: TAuth;\n rateLimit?: RateLimitConfig;\n input?: TInputSchema;\n output?: TOutputSchema;\n errors?: TErrors;\n middleware?: Middleware[];\n handler: (\n req: PBRequest<z.infer<TInputSchema>, IsAuthed<TAuth>, TErrors>,\n ) => Promise<z.infer<TOutputSchema>> | z.infer<TOutputSchema>;\n}\n\n/**\n * A {@link HandlerDef} with its schema/auth/errors type params ERASED — the\n * shape a controller's `route.*` stores. EVERY `defineHandler()` result (any\n * `auth`/`input`/`output`/`errors`) is assignable to this, because `handler` is\n * typed `(req: never) => unknown` (params are contravariant, so a handler taking\n * any concrete `PBRequest` satisfies a `never` param). A controller only needs\n * to route + carry the handler; the handler's own `req` typing is validated\n * where it is defined, so erasing the params here is sound and lets a route\n * accept a handler regardless of its `auth`/`errors` — without `any`.\n */\nexport interface AnyHandlerDef {\n readonly __palbase: \"handler\";\n auth?: AuthSpec;\n rateLimit?: RateLimitConfig;\n input?: ZodSchema;\n output?: ZodSchema;\n errors?: ErrorMap;\n middleware?: Middleware[];\n handler: (req: never) => unknown;\n}\n\n/**\n * Define an endpoint handler (schema + thin logic), with full `req` inference\n * and NO routing. Mounted by a controller's `route.*` map.\n *\n * @example\n * import { defineHandler, z, Database } from \"@palbase/backend\";\n *\n * export default defineHandler({\n * auth: { required: true },\n * input: z.object({ title: z.string() }),\n * output: z.object({ id: z.string() }),\n * errors: { titleTaken: { status: 409, code: \"title_taken\" } },\n * handler: (req) => Database.tables.todos.insert({ title: req.input.title }),\n * });\n */\nexport function defineHandler<\n TInputSchema extends ZodSchema,\n TOutputSchema extends ZodSchema,\n const TAuth extends AuthSpec | undefined = undefined,\n const TErrors extends ErrorMap | undefined = undefined,\n>(\n config: HandlerConfig<TInputSchema, TOutputSchema, TAuth, TErrors>,\n): HandlerDef<TInputSchema, TOutputSchema, TAuth, TErrors> {\n return { __palbase: \"handler\", ...config };\n}\n","import type { AnyHandlerDef } from \"./handler.js\";\n\n/**\n * controller.ts — `defineController` + `route.*`: the ROUTE MAP (method+path →\n * handler). Logic cannot be written here — a controller only wires existing\n * handlers, so the small-blast-radius / no-cross-file-type-sync property holds.\n *\n * ```ts\n * // controllers/places.controller.ts\n * import { defineController, route } from \"@palbase/backend\";\n * import importNearby from \"../handlers/places/import-nearby.js\";\n * import listFavorites from \"../handlers/places/list-favorites.js\";\n *\n * export default defineController(\"/places\", {\n * importNearby: route.post(\"/import\", importNearby), // map key = sugar\n * listFavorites: route.get (\"/favorites\", listFavorites),\n * });\n * ```\n *\n * # Runtime-readable object shapes (read by the Go runtime's extract_meta.js)\n *\n * The default export of a controller file is a `ControllerDef`:\n *\n * ControllerDef = {\n * __palbase: \"controller\",\n * basePath: string, // e.g. \"/places\"\n * routes: { [mapKey: string]: RouteDef } // mapKey is AUTHORING SUGAR only\n * }\n *\n * RouteDef = {\n * __palbase: \"route\",\n * method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\", // upper-case\n * path: string, // the SUBPATH passed to route.*(…), e.g. \"/import\"\n * handler: HandlerDef, // the imported defineHandler() result\n * }\n *\n * HandlerDef = {\n * __palbase: \"handler\",\n * auth?, rateLimit?, input?, output?, errors?, middleware?,\n * handler: (req) => result,\n * }\n *\n * The FULL path for a route is `basePath + path` (e.g. \"/places\" + \"/import\" =\n * \"/places/import\"). The operationId is NOT computed here — the runtime\n * `generator.go` derives it FLAT from `(method, full-path)` (e.g.\n * `postPlacesImport`). The map key (`importNearby`) is authoring sugar and is\n * NOT the operationId.\n */\n\n/** The five HTTP verbs a route may declare, upper-cased (matches the runtime\n * router + OpenAPI which lower-cases on its own). */\nexport type HttpMethodUpper = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/**\n * A single route entry: the HTTP verb, the SUBPATH (relative to the\n * controller's `basePath`), and the handler to invoke. Produced by `route.*`.\n */\nexport interface RouteDef<\n H extends AnyHandlerDef = AnyHandlerDef,\n M extends HttpMethodUpper = HttpMethodUpper,\n> {\n /** Discriminant the runtime + `defineController` read. */\n readonly __palbase: \"route\";\n /** Upper-case HTTP verb. */\n method: M;\n /** The subpath passed to `route.*(subpath, handler)` (relative to basePath). */\n path: string;\n /** The handler (a `defineHandler()` result) this route invokes. */\n handler: H;\n}\n\n/** The route map a controller declares: keys are authoring sugar, values must\n * each be a `route.*()` result. The mapped-over `Record<string, RouteDef>`\n * constraint is what makes a bare handler (or plain object) a TS error. */\nexport type RouteMap = Record<string, RouteDef>;\n\n/**\n * The controller descriptor returned by {@link defineController}. The default\n * export of a `controllers/*.ts` file.\n */\nexport interface ControllerDef<R extends RouteMap = RouteMap> {\n /** Discriminant the runtime reads. */\n readonly __palbase: \"controller\";\n /** The path every route in this controller is mounted under (e.g. \"/places\"). */\n basePath: string;\n /** The route map — keys are authoring sugar, values are RouteDefs. */\n routes: R;\n}\n\n/** Build a `route.*` factory for one verb. Accepts ANY `defineHandler()` result\n * via {@link AnyHandlerDef} — a handler's `auth`/`input`/`output`/`errors` are\n * validated where it is defined, so a route need not (and must not) re-constrain\n * them, otherwise a handler declaring `auth` would fail the bound. */\nfunction makeRoute<M extends HttpMethodUpper>(method: M) {\n return function <H extends AnyHandlerDef>(path: string, handler: H): RouteDef<H, M> {\n return { __palbase: \"route\", method, path, handler };\n };\n}\n\n/**\n * Route builders — one per HTTP verb. Each takes the SUBPATH (relative to the\n * controller `basePath`) and the handler to mount there.\n *\n * @example\n * import { defineController, route } from \"@palbase/backend\";\n * import create from \"../handlers/todos/create.js\";\n *\n * export default defineController(\"/todos\", {\n * create: route.post(\"/\", create),\n * });\n */\nexport const route = {\n get: makeRoute(\"GET\"),\n post: makeRoute(\"POST\"),\n put: makeRoute(\"PUT\"),\n patch: makeRoute(\"PATCH\"),\n delete: makeRoute(\"DELETE\"),\n} as const;\n\n/**\n * Define a controller: a base path + a route map (method+path → handler). The\n * route-map KEY is authoring sugar only (NOT the operationId). Each route value\n * MUST be a `route.*()` result — a bare handler or plain object is a TS error,\n * which is what keeps logic out of controllers structurally.\n *\n * @example\n * import { defineController, route } from \"@palbase/backend\";\n * import create from \"../handlers/todos/create.js\";\n * import list from \"../handlers/todos/list.js\";\n *\n * export default defineController(\"/todos\", {\n * create: route.post(\"/\", create),\n * list: route.get(\"/\", list),\n * });\n */\nexport function defineController<const R extends RouteMap>(\n basePath: string,\n routes: R,\n): ControllerDef<R> {\n return { __palbase: \"controller\", basePath, routes };\n}\n","import type { DBClient, Logger, CacheClient, PalbaseModuleClients } from \"./endpoint.js\";\nimport type { User } from \"./types.js\";\n\n/** Middleware context — subset of EndpointContext without input (not yet validated). */\nexport interface MiddlewareContext extends PalbaseModuleClients {\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 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.\n *\n * Two ways to construct one:\n *\n * 1. Directly: `throw new HttpError(404, \"todo_not_found\", \"No such todo\")`.\n * Surfaces on the wire (and to iOS) as `BackendError.server(code, status,\n * message, requestId)`.\n * 2. Via `req.errors.<name>(data?)` for declared errors — see a handler's\n * `errors` map (`defineHandler`). The endpoint's OpenAPI spec describes\n * each one, and the CLI codegen emits a typed `<Endpoint>.Error` enum so\n * iOS callers can `catch <Endpoint>.Error.<case>` directly.\n *\n * The optional `data` field carries a structured payload alongside the\n * standard envelope — used by declared errors that need to ship extra context\n * (e.g. `todoLocked({ retryAfter: 30 })`). It rides through to the iOS typed\n * enum's associated value.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly error: string;\n public readonly errorDescription: string;\n public readonly data?: unknown;\n\n constructor(status: number, error: string, errorDescription: string, data?: unknown) {\n super(errorDescription);\n this.name = \"HttpError\";\n this.status = status;\n this.error = error;\n this.errorDescription = errorDescription;\n if (data !== undefined) {\n this.data = data;\n }\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 * When `data` is set, it is appended as a strict-superset field.\n */\n toJSON(requestId?: string): {\n error: string;\n error_description: string;\n status: number;\n request_id?: string;\n data?: unknown;\n } {\n const result: {\n error: string;\n error_description: string;\n status: number;\n request_id?: string;\n data?: unknown;\n } = {\n error: this.error,\n error_description: this.errorDescription,\n status: this.status,\n };\n if (requestId) {\n result.request_id = requestId;\n }\n if (this.data !== undefined) {\n result.data = this.data;\n }\n return result;\n }\n}\n","import type { User } from \"./types.js\";\n\n/** Non-service, per-invocation data for background worker handlers.\n * Services (Database, Log, …) are imported as singletons, not passed here. */\nexport interface WorkerMeta {\n /** Branch-scoped env vars. */\n env: Record<string, string>;\n /** The user that enqueued the job, if any (background jobs are usually system-initiated). */\n user: User | null;\n /** Per-invocation id for correlation. */\n requestId: string;\n projectId: string;\n environmentId: string;\n}\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 payload: TPayload,\n meta: WorkerMeta,\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 payload: TPayload,\n meta: WorkerMeta,\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 (payload: { to: string; subject: string }, meta) => {\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","/** Non-service, per-invocation data for job handlers.\n * Services (Database, Log, …) are imported as singletons, not passed here. */\nexport interface JobMeta {\n /** Branch-scoped env vars. */\n env: Record<string, string>;\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: (meta: JobMeta) => 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: (meta: JobMeta) => 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 (meta) => {\n * await Database.delete(\"sessions\", \"expired < NOW()\");\n * 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","/** Supported webhook provider types. */\nexport type WebhookProvider =\n | \"stripe\"\n | \"github\"\n | \"twilio\"\n | \"sendgrid\"\n | \"slack\"\n | \"discord\"\n | \"livekit\";\n\n/** Non-service, per-invocation data for webhook handlers.\n * Services (Database, Log, …) are imported as singletons, not passed here. */\nexport interface WebhookMeta {\n /** Branch-scoped env vars. */\n env: Record<string, string>;\n /** Per-invocation id for correlation. */\n requestId: string;\n projectId: string;\n environmentId: 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 event: TEvent,\n meta: WebhookMeta,\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: (payload: Record<string, unknown>, meta: WebhookMeta) => 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: (payload: Record<string, unknown>, meta: WebhookMeta) => 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 (event, meta) => {\n * await Database.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 (payload, meta) => {\n * 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","/**\n * resource.ts — external connections as lifecycle-managed classes.\n *\n * A `Resource` subclass models one external connection (a pooled datastore, a\n * stateless API client, or a per-user factory). The framework discovers each\n * instance, calls `init(env)` ONCE at boot with the declared secret subset, and\n * `shutdown()` (reverse order) on SIGTERM. On top of that lifecycle the author\n * exposes their own clean facade methods.\n *\n * // resources/neo4j.ts\n * import { Resource } from \"@palbase/backend\";\n * import neo4j, { type Driver, type Session } from \"neo4j-driver\";\n *\n * export class Neo4jResource extends Resource {\n * static secrets = [\"NEO4J_URL\", \"NEO4J_USER\", \"NEO4J_PASSWORD\"] as const;\n * private driver!: Driver;\n * async init(env: { NEO4J_URL: string; NEO4J_USER: string; NEO4J_PASSWORD: string }) {\n * this.driver = neo4j.driver(env.NEO4J_URL, neo4j.auth.basic(env.NEO4J_USER, env.NEO4J_PASSWORD));\n * }\n * async shutdown() { await this.driver.close(); }\n * session(): Session { return this.driver.session(); }\n * }\n * export const neo4j = new Neo4jResource(); // framework finds + manages it\n *\n * # Boot scope (NOT request-ALS)\n *\n * Resources are instantiated once at process boot. This is deliberately NOT the\n * per-request {@link AsyncLocalStorage} scope used for `Database`/`Cache`/… — a\n * connection pool must outlive a single request. The runtime discovers\n * resources from the project's `resources/` directory, registers each via\n * {@link __registerResource}, then calls {@link __runResourceBoot} before\n * serving and {@link __shutdownResources} on SIGTERM. Discovery is a runtime\n * concern; the SDK provides the base class + registry + boot/shutdown hooks.\n */\n\n/** Map a declared `secrets` tuple to the `init(env)` argument type — a record\n * over the secret names, each `string`. An empty tuple maps to an empty\n * record. */\nexport type ResourceEnv<Secrets extends readonly string[]> = {\n [K in Secrets[number]]: string;\n};\n\n/**\n * Base class for external connections.\n *\n * - `static secrets` — OPTIONAL readonly tuple of env-var names this resource\n * needs. Drives (a) the `env` type passed to `init` and (b) the\n * missing-secret check at boot (deploy fails naming the absent secret).\n * - `init(env)` — called ONCE at boot with the declared secret subset. May be\n * sync (`void`) or async (`Promise<void>`).\n * - `shutdown()` — OPTIONAL drain hook, called on SIGTERM in reverse boot\n * order.\n *\n * @example\n * import { Resource } from \"@palbase/backend\";\n * import { Client } from \"@googlemaps/google-maps-services-js\";\n *\n * export class GoogleResource extends Resource {\n * static secrets = [\"GOOGLE_MAPS_KEY\"] as const;\n * private client = new Client();\n * private key = \"\";\n * init(env: { GOOGLE_MAPS_KEY: string }) { this.key = env.GOOGLE_MAPS_KEY; }\n * }\n * export const google = new GoogleResource();\n */\nexport abstract class Resource {\n /** The env-var names this resource needs. Optional; omit for none. */\n static secrets?: readonly string[];\n\n /** Set up the connection from the declared secrets. Called once at boot. */\n abstract init(env: Record<string, string>): void | Promise<void>;\n\n /** Drain/close the connection on SIGTERM. Optional. */\n shutdown?(): void | Promise<void>;\n}\n\n/** A value carrying an optional readonly `secrets` tuple — the constructor side\n * of a Resource subclass. Narrowed from `unknown` via {@link hasSecrets}. */\ninterface WithSecrets {\n secrets?: readonly string[];\n}\n\n/** Guard: does this value carry a `secrets` array (the static on a Resource\n * subclass)? Lets us read `secrets` off `resource.constructor` (typed\n * `Function`) without a cast — narrows from `unknown` instead. */\nfunction hasSecrets(value: unknown): value is WithSecrets {\n if (typeof value !== \"object\" && typeof value !== \"function\") return false;\n const secrets = (value as { secrets?: unknown }).secrets;\n return secrets === undefined || Array.isArray(secrets);\n}\n\n/** A registered resource plus whether its `init` has already run. The\n * `booted` flag makes {@link __runResourceBoot} idempotent (re-running boot,\n * e.g. across a re-entrant deploy path, never re-inits). */\ninterface RegistryEntry {\n resource: Resource;\n booted: boolean;\n}\n\n/** Module-level boot registry. Single per process (the br-pod is\n * single-project). Order is registration order; shutdown reverses it. */\nconst registry: RegistryEntry[] = [];\n\n/**\n * Register a resource instance with the boot registry. The runtime calls this\n * for each instance discovered under the project's `resources/` directory.\n * NOT part of the public author-facing API (prefixed `__`).\n */\nexport function __registerResource(resource: Resource): void {\n registry.push({ resource, booted: false });\n}\n\n/** Read the declared `secrets` tuple off a resource instance's constructor. */\nfunction declaredSecrets(resource: Resource): readonly string[] {\n const ctor: unknown = resource.constructor;\n return hasSecrets(ctor) ? (ctor.secrets ?? []) : [];\n}\n\n/**\n * Boot every registered resource that has not yet been booted: resolve its\n * declared secret subset from `envMap`, then await its `init(env)`. Idempotent\n * — an already-booted resource is skipped. Throws (failing deploy/boot) when a\n * declared secret is absent, naming the missing secret. NOT part of the public\n * author-facing API.\n */\nexport async function __runResourceBoot(envMap: Record<string, string>): Promise<void> {\n for (const entry of registry) {\n if (entry.booted) continue;\n const secrets = declaredSecrets(entry.resource);\n const env: Record<string, string> = {};\n for (const name of secrets) {\n const value = envMap[name];\n if (value === undefined) {\n throw new Error(\n `Resource ${entry.resource.constructor.name} requires secret \"${name}\" but it is not set. ` +\n `Set it with \\`palbase secret set ${name} ...\\` (or in Studio) and redeploy.`,\n );\n }\n env[name] = value;\n }\n await entry.resource.init(env);\n entry.booted = true;\n }\n}\n\n/**\n * Shut down every booted resource in REVERSE registration order, awaiting each\n * `shutdown()` (a no-op when undefined). Clears the registry afterwards so a\n * second call is a no-op. NOT part of the public author-facing API.\n */\nexport async function __shutdownResources(): Promise<void> {\n for (let i = registry.length - 1; i >= 0; i -= 1) {\n const entry = registry[i]!;\n if (entry.booted && entry.resource.shutdown) {\n await entry.resource.shutdown();\n }\n }\n registry.length = 0;\n}\n\n/** Clear the registry without running shutdown. TEST-only helper so each test\n * starts from a clean registry. NOT part of the public author-facing API. */\nexport function __resetResources(): void {\n registry.length = 0;\n}\n","/** Non-service, per-invocation data for hook handlers.\n * Services (Database, Log, …) are imported as singletons, not passed here. */\nexport interface HookMeta {\n /** Branch-scoped env vars. */\n env: Record<string, string>;\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 /** User's email, if they signed up with one (absent for phone-only users). */\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 /** User's email, if they have one (absent for phone-only users). */\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 /** User's email, if they have one (absent for phone-only users). */\n email?: string;\n };\n timestamp: string;\n}\n\n/** Payload for auth.onPasswordReset hook.\n * Password reset is inherently email-based, so `email` is always present here\n * (a phone-only passwordless user cannot trigger this event). */\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> = (event: TEvent, meta: HookMeta) => 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"],"mappings":"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","names":[]}