@palbase/backend 24.0.2 → 24.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/bin/palbase-backend.cjs +61 -9
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +2 -2
  4. package/dist/{chunk-T5IOSOE5.js → chunk-ERDL5VAE.js} +2 -2
  5. package/dist/{chunk-RPZNHY7J.js → chunk-HPUSV4AZ.js} +57 -11
  6. package/dist/chunk-HPUSV4AZ.js.map +1 -0
  7. package/dist/{chunk-H3JAISUY.js → chunk-UWSYTUGM.js} +11 -1
  8. package/dist/{chunk-H3JAISUY.js.map → chunk-UWSYTUGM.js.map} +1 -1
  9. package/dist/db/index.d.cts +2 -2
  10. package/dist/db/index.d.ts +2 -2
  11. package/dist/engine/index.cjs +61 -9
  12. package/dist/engine/index.cjs.map +1 -1
  13. package/dist/engine/index.d.cts +4 -4
  14. package/dist/engine/index.d.ts +4 -4
  15. package/dist/engine/index.js +2 -2
  16. package/dist/{index-BOS_rFBO.d.ts → index-ByBMibIJ.d.ts} +1 -1
  17. package/dist/{index-dJNhDZ7j.d.ts → index-C0PMn5jl.d.ts} +3 -3
  18. package/dist/{index-MoQ31B6M.d.cts → index-D4rts8T7.d.cts} +1 -1
  19. package/dist/{index-C84bLgeO.d.cts → index-DAwHMppB.d.cts} +3 -3
  20. package/dist/index.cjs +8 -0
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +23 -9
  23. package/dist/index.d.ts +23 -9
  24. package/dist/index.js +5 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/openapi/index.cjs.map +1 -1
  27. package/dist/openapi/index.d.cts +2 -2
  28. package/dist/openapi/index.d.ts +2 -2
  29. package/dist/openapi/index.js +2 -2
  30. package/dist/{registry-CEod_5sz.d.ts → registry-B8pld3fj.d.ts} +1 -1
  31. package/dist/{registry-1X-skBNu.d.cts → registry-CgJvDYUW.d.cts} +1 -1
  32. package/dist/test/index.cjs.map +1 -1
  33. package/dist/test/index.d.cts +1 -1
  34. package/dist/test/index.d.ts +1 -1
  35. package/dist/test/index.js.map +1 -1
  36. package/docs/README.md +5 -1
  37. package/docs/llms-full.txt +5 -1
  38. package/package.json +1 -1
  39. package/template/AGENTS.md +34 -22
  40. package/dist/chunk-RPZNHY7J.js.map +0 -1
  41. package/dist/{chunk-T5IOSOE5.js.map → chunk-ERDL5VAE.js.map} +0 -0
  42. package/dist/{endpoint-CcQ1a36a.d.cts → endpoint-BSGw1pTu.d.cts} +16 -16
  43. package/dist/{endpoint-0_DGBajf.d.ts → endpoint-DMwJjEHQ.d.ts} +16 -16
@@ -194,6 +194,14 @@ function assertZeroArgConstructor(Ctrl, kind) {
194
194
 
195
195
  // src/errors.ts
196
196
  var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
197
+ var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
198
+ function markEngineRaised(e) {
199
+ e[ENGINE_RAISED] = true;
200
+ return e;
201
+ }
202
+ function isEngineRaised(e) {
203
+ return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
204
+ }
197
205
  function isHttpError(err) {
198
206
  if (typeof err !== "object" || err === null) return false;
199
207
  const e = err;
@@ -321,6 +329,8 @@ export {
321
329
  isController,
322
330
  resolveController,
323
331
  assertZeroArgConstructor,
332
+ markEngineRaised,
333
+ isEngineRaised,
324
334
  isHttpError,
325
335
  HttpError,
326
336
  PalError,
@@ -332,4 +342,4 @@ export {
332
342
  UniqueViolation,
333
343
  TooManyRequests
334
344
  };
335
- //# sourceMappingURL=chunk-H3JAISUY.js.map
345
+ //# sourceMappingURL=chunk-UWSYTUGM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/decorators/registry.ts","../src/decorators/controller.ts","../src/errors.ts"],"sourcesContent":["// The decorator registry — the single plain-data store the method + parameter\n// decorators write into, and the deploy/dispatch pipeline reads back. No\n// `reflect-metadata`, no `emitDecoratorMetadata`: the registry is built from the\n// decorator arguments + the parameter INDEX that esbuild/tsc preserve for legacy\n// parameter decorators (verified — see the design spec §0/§4.1).\n//\n// A controller class carries its route metadata on a symbol-keyed static\n// property (`ROUTES`). `@Get`/`@Post`/… append a {@link RouteMeta} entry;\n// `@Body`/`@User`/… append a {@link ParamMeta} entry onto the route for the\n// method they decorate. Because parameter decorators run BEFORE the method\n// decorator for the same member (TS evaluates innermost-first, params before the\n// method), the route entry may not exist yet when a param decorator fires — so\n// param metadata is buffered per method name and merged when the method\n// decorator creates the route entry.\nimport type { AuthSpec, RateLimitConfig } from \"../endpoint.js\";\nimport type { UploadConfig } from \"./upload.js\";\nimport type { SseConfig } from \"./sse.js\";\nimport type { ZodTypeAny } from \"zod\";\n\n/** The HTTP verbs a route may declare, upper-cased (the runtime router +\n * OpenAPI lower-case on their own). */\nexport type HttpMethodUpper = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"QUERY\";\n\n/** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */\nexport interface RouteOptions {\n /** OVERRIDES the controller-level default auth for this one route. */\n auth?: AuthSpec;\n /** Per-route rate limit. */\n rateLimit?: RateLimitConfig;\n /** Direct-storage upload config — present ONLY on `@Upload` routes (the\n * `@Get`/`@Post`/… decorators never set it). Its presence is what MARKS a\n * route as an upload route through the whole pipeline (registry → flatten →\n * openapi → codegen). The bytes go client→storage directly; the method body\n * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */\n uploadConfig?: UploadConfig;\n /** Streaming config — present ONLY on `@Sse` routes (the `@Get`/`@Post`/…\n * decorators never set it). Its presence is what MARKS a route as a streaming\n * route through the whole pipeline (registry → flatten → openapi → codegen),\n * exactly as `uploadConfig` does for uploads — never a special HTTP verb. An\n * `@Sse` route registers POST like any input-bearing route, so the verb cannot\n * carry the distinction. See {@link SseConfig} (decorators/sse.ts). */\n sseConfig?: SseConfig;\n}\n\n/** The kind of value a parameter decorator injects. Drives both dispatch\n * (which request slice to inject) and codegen (which OpenAPI parameter source a\n * schema-bearing kind maps to). */\nexport type ParamKind =\n | \"body\"\n | \"query\"\n | \"param\"\n | \"headers\"\n | \"user\"\n | \"optionalUser\"\n | \"client\"\n | \"requestId\"\n | \"traceId\"\n | \"req\"\n // `@UploadedObject()` — injects the uploaded object (completion input) on an\n // `@Upload` route. No schema (the shape is the fixed UploadedObject type).\n | \"uploadedObject\"\n // `@SseOut()` — injects the frame writer on an `@Sse` route. No schema (the\n // shape is the fixed SseWriter type).\n | \"sseOut\"\n // `@Signal()` — injects the request's AbortSignal, which aborts when the\n // client disconnects. No schema. NOT derivable from `@Req()`: PBRequest\n // carries only request-scoped data and has no signal (endpoint.ts:358-363).\n | \"signal\";\n\n/** One parameter decorator's recorded metadata. `index` is the parameter\n * position esbuild/tsc preserve; `schema` is present for the schema-bearing\n * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */\nexport interface ParamMeta {\n index: number;\n kind: ParamKind;\n /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */\n schema?: ZodTypeAny;\n /** Path-param name for `@Param(\"id\")`. */\n name?: string;\n}\n\n/** One inferred throw site: the error CLASS name (e.g. \"TodoLocked\") and its\n * wire code (e.g. \"todo_locked\"). `status`, `hasData`, and the data JSON schema\n * are NOT carried here — they resolve from the error registry by `code` at\n * extract/openapi time (single source of truth). */\nexport interface ThrowDescriptor {\n name: string;\n code: string;\n}\n\n/** One route's recorded metadata: the verb + subpath + method name + options,\n * the ordered parameter metas, and the resolved return schema (injected by the\n * codegen step — see `returnSchema`). */\nexport interface RouteMeta {\n method: HttpMethodUpper;\n subpath: string;\n fnName: string;\n options: RouteOptions;\n params: ParamMeta[];\n /** Response schema for the route, if any. Derived from the method's RETURN\n * TYPE by codegen and written here via `recordReturn` (a generated top-level\n * IIFE injected per controller), not by an author-written decorator. */\n returnSchema?: ZodTypeAny;\n /** Error classes this route can throw, if inferred. Derived from the method\n * body + service call graph by the deploy stager's throw analysis and written\n * here via `recordThrows` (a generated top-level IIFE injected per controller,\n * the `recordReturn` twin), not by an author-written decorator. */\n throws?: ThrowDescriptor[];\n}\n\n/** Symbol the route metadata list is stored under on a controller class. Using\n * a symbol (not a string key) keeps it off the public structural surface and\n * avoids any chance of an authored property collision. */\nexport const ROUTES: unique symbol = Symbol.for(\"palbase.backend.routes\");\n\n/** Symbol the per-method buffered parameter metas are stored under while a class\n * is being decorated. Parameter decorators fire before the method decorator, so\n * they buffer here keyed by method name; the method decorator drains the buffer\n * into the route entry it creates. */\nconst PARAM_BUFFER: unique symbol = Symbol.for(\"palbase.backend.paramBuffer\");\n\n/** Symbol the per-method buffered return-type schemas are stored under while a\n * class's registry is being populated. The codegen-injected `recordReturn` call\n * can fire before OR after the method decorator; it buffers here keyed by method\n * name and `recordRoute` drains it into the route entry (and `recordReturn`\n * writes through if the route already exists). Buffering on BOTH sides means a\n * fully-formed route entry always carries its return schema — a raw-symbol\n * reader (the runtime) never has to re-merge. */\nconst RETURN_BUFFER: unique symbol = Symbol.for(\"palbase.backend.returnBuffer\");\n\n/** Symbol the per-method buffered throw descriptors are stored under while a\n * class's registry is being populated. The stager-injected `recordThrows` call\n * can fire before OR after the method decorator; it buffers here keyed by method\n * name and `recordRoute` drains it into the route entry (and `recordThrows`\n * writes through if the route already exists). Buffering on BOTH sides means a\n * fully-formed route entry always carries its throw descriptors — a raw-symbol\n * reader (the runtime) never has to re-merge. */\nconst THROWS_BUFFER: unique symbol = Symbol.for(\"palbase.backend.throwsBuffer\");\n\n/** A class constructor carrying the symbol-keyed registry slots. We type the\n * registry-bearing class as this so the decorators can read/write the slots\n * without `any` — a plain `Function` does not carry index signatures. */\ninterface RegistryCarrier {\n [ROUTES]?: RouteMeta[];\n [PARAM_BUFFER]?: Record<string, ParamMeta[]>;\n [RETURN_BUFFER]?: Record<string, ZodTypeAny>;\n [THROWS_BUFFER]?: Record<string, ThrowDescriptor[]>;\n}\n\n/** Coerce a decorated target (class constructor or its prototype) into the\n * registry carrier that owns the slots. Method/param decorators receive the\n * PROTOTYPE as their target; the class decorator receives the constructor. We\n * always anchor the registry on the CONSTRUCTOR so `getRoutes(ctor)` finds it. */\nfunction carrierOf(target: object): RegistryCarrier {\n // For instance-member decorators, `target` is the prototype; its `.constructor`\n // is the class. For a static member or the class decorator, `target` is the\n // constructor already. Resolve to the constructor either way.\n const ctor =\n typeof target === \"function\"\n ? (target as unknown as RegistryCarrier)\n : (((target as { constructor?: unknown }).constructor ??\n target) as unknown as RegistryCarrier);\n return ctor;\n}\n\n/** Get (creating if absent) the own route list for a class constructor. Own —\n * not inherited — so a subclass does not mutate its base's routes. */\nfunction ownRoutes(carrier: RegistryCarrier): RouteMeta[] {\n if (!Object.prototype.hasOwnProperty.call(carrier, ROUTES)) {\n carrier[ROUTES] = [];\n }\n return carrier[ROUTES] as RouteMeta[];\n}\n\n/** Get (creating if absent) the own per-method param buffer for a class. */\nfunction ownParamBuffer(carrier: RegistryCarrier): Record<string, ParamMeta[]> {\n if (!Object.prototype.hasOwnProperty.call(carrier, PARAM_BUFFER)) {\n carrier[PARAM_BUFFER] = {};\n }\n return carrier[PARAM_BUFFER] as Record<string, ParamMeta[]>;\n}\n\n/** Record a route (called by the method decorators). Drains any parameter\n * metas already buffered for `fnName` into the new route entry, then sorts them\n * by parameter index so dispatch can inject positionally. */\nexport function recordRoute(\n target: object,\n fnName: string,\n method: HttpMethodUpper,\n subpath: string,\n options: RouteOptions,\n): void {\n const carrier = carrierOf(target);\n const routes = ownRoutes(carrier);\n const buffer = ownParamBuffer(carrier);\n const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);\n const route: RouteMeta = { method, subpath, fnName, options, params };\n // Drain a buffered return schema (the recordReturn-ran-first ordering) so the\n // route entry is complete the moment it's created — a raw-symbol consumer\n // (the runtime extractor/worker) sees the return schema without re-merging.\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer && returnBuffer[fnName] !== undefined) {\n route.returnSchema = returnBuffer[fnName];\n }\n // Same drain for buffered throw descriptors (the recordThrows-ran-first\n // ordering) — the route entry is complete the moment it's created.\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer && throwsBuffer[fnName] !== undefined) {\n route.throws = throwsBuffer[fnName];\n }\n routes.push(route);\n}\n\n/** Record one parameter decorator (called by `@Body`/`@User`/…). Buffers per\n * method name; the method decorator merges the buffer into the route entry. If\n * the route already exists (method decorator ran first — TS does evaluate the\n * method decorator AFTER its parameter decorators, but we stay order-robust),\n * the meta is also appended directly so neither ordering loses it. */\nexport function recordParam(target: object, fnName: string, meta: ParamMeta): void {\n const carrier = carrierOf(target);\n const buffer = ownParamBuffer(carrier);\n (buffer[fnName] ??= []).push(meta);\n\n // Order-robust: if the route already exists, merge in place + keep sorted.\n const routes = carrier[ROUTES];\n if (routes) {\n const route = routes.find((r) => r.fnName === fnName);\n if (route) {\n route.params.push(meta);\n route.params.sort((a, b) => a.index - b.index);\n }\n }\n}\n\n/** Attach a return schema to the route for `fnName` (called by the codegen\n * injection that reads the method's return type). If the route does not exist\n * yet, the schema is buffered (RETURN_BUFFER) and drained into the route by\n * `recordRoute` when the method decorator runs. */\nexport function recordReturn(target: object, fnName: string, schema: ZodTypeAny): void {\n const carrier = carrierOf(target);\n const routes = carrier[ROUTES];\n const route = routes?.find((r) => r.fnName === fnName);\n if (route) {\n route.returnSchema = schema;\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(carrier, RETURN_BUFFER)) {\n carrier[RETURN_BUFFER] = {};\n }\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer) returnBuffer[fnName] = schema;\n}\n\n/** Attach the inferred throw descriptors to the route for `fnName` (called by\n * the stager-injected IIFE that carries the throw analysis result — the\n * `recordReturn` twin). If the route does not exist yet, the descriptors are\n * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the\n * method decorator runs. */\nexport function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void {\n const carrier = carrierOf(target);\n const routes = carrier[ROUTES];\n const route = routes?.find((r) => r.fnName === fnName);\n if (route) {\n route.throws = throws;\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(carrier, THROWS_BUFFER)) {\n carrier[THROWS_BUFFER] = {};\n }\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer) throwsBuffer[fnName] = throws;\n}\n\n/** Read the route metadata for a controller class (the deploy/dispatch entry\n * point). Applies any buffered return schemas + throw descriptors (for the\n * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy\n * so callers cannot mutate the registry.\n */\nexport function getRoutes(ctor: object): RouteMeta[] {\n const carrier = carrierOf(ctor);\n const routes = carrier[ROUTES] ?? [];\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer) {\n for (const route of routes) {\n const buffered = returnBuffer[route.fnName];\n if (buffered && route.returnSchema === undefined) {\n route.returnSchema = buffered;\n }\n }\n }\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer) {\n for (const route of routes) {\n const buffered = throwsBuffer[route.fnName];\n if (buffered && route.throws === undefined) {\n route.throws = buffered;\n }\n }\n }\n return routes.map((r) => ({\n ...r,\n params: r.params.slice(),\n ...(r.throws !== undefined ? { throws: r.throws.slice() } : {}),\n }));\n}\n","// `@Controller(basePath, options?)` — the class decorator that marks a class as\n// a Palbase backend controller. It stamps a non-enumerable `__palbase`\n// discriminant + the resolved controller metadata onto the class so the\n// deploy/dispatch pipeline (and `isController`/`resolveController`) can detect\n// and read it without `reflect-metadata`.\nimport type { AuthSpec } from \"../endpoint.js\";\nimport { getRoutes } from \"./registry.js\";\n\n/** The controller metadata stamped onto a `@Controller`-decorated class. The\n * default export of a `controllers/*.controller.ts` file resolves to this via\n * {@link resolveController}. */\nexport interface ControllerMeta {\n /** Discriminant the runtime + tooling read. */\n readonly __palbase: \"controller\";\n /** The base path every route in this controller mounts under (e.g. \"/todos\"). */\n basePath: string;\n /** Controller-level default auth, applied to routes that don't set their own\n * (`@Get(\"/x\", { auth })` overrides this). `undefined` ⇒ the application\n * default ({@link defineDefaultAuth}), and secure-by-default below that —\n * see {@link resolveEffectiveAuth} for the whole cascade. */\n defaultAuth?: AuthSpec;\n}\n\n/** Options accepted by `@Controller`. */\nexport interface ControllerOptions {\n /** Default auth for ALL routes in this controller (route-level overrides;\n * omitting it falls through to the application default declared with\n * {@link defineDefaultAuth}). */\n auth?: AuthSpec;\n}\n\n/** Symbol the controller metadata is stamped under. Symbol-keyed (not a string\n * property) so it never collides with an authored member and stays off the\n * structural surface. */\nexport const CONTROLLER_META: unique symbol = Symbol.for(\"palbase.backend.controllerMeta\");\n\n/**\n * Every class `@Controller` has decorated, in decoration order.\n *\n * This is what lets a controller file need no export at all: importing the file\n * runs the decorator, the decorator records the class here, and the runtime\n * reads the list. Without it the only handle on a class is its export name, so\n * every controller had to be exported AND named in a generated entry — the\n * ceremony NestJS still charges (`export class` PLUS\n * `@Module({controllers:[…]})`).\n *\n * Keyed on a well-known Symbol against globalThis rather than held in a module\n * variable, because a deployed bundle inlines its own copy of this package: two\n * copies would keep two lists, and the runtime would read the empty one. The\n * same hazard `runtimeHooks` exists for, closed the same way — one shared slot.\n */\nconst REGISTRY: unique symbol = Symbol.for(\"palbase.backend.allControllers\") as never;\n\nfunction registry(): unknown[] {\n const g = globalThis as unknown as Record<symbol, unknown[] | undefined>;\n const existing = g[REGISTRY];\n if (existing) return existing;\n const fresh: unknown[] = [];\n g[REGISTRY] = fresh;\n return fresh;\n}\n\n/**\n * The controller classes this process has loaded, in decoration order.\n *\n * Decoration order is import order, which the bundler fixes by sorting the\n * files it emits imports for — so two builds of one tree produce the same\n * route table, and route precedence is not a function of module-resolution\n * accidents.\n */\nexport function getRegisteredControllers(): readonly unknown[] {\n return registry().slice();\n}\n\n/** Empty the registry. For tests, which load controllers repeatedly. */\nexport function __resetRegisteredControllers(): void {\n registry().length = 0;\n}\n\n/**\n * The APPLICATION-level default auth.\n *\n * Held on globalThis under a well-known Symbol for exactly the reason\n * {@link REGISTRY} is: a deployed bundle inlines its own copy of this package,\n * and two copies keeping two defaults is how a security setting silently\n * becomes two different settings.\n */\nconst APP_DEFAULT_AUTH: unique symbol = Symbol.for(\"palbase.backend.appDefaultAuth\") as never;\n\nfunction appAuthSlot(): Record<symbol, AuthSpec | undefined> {\n return globalThis as unknown as Record<symbol, AuthSpec | undefined>;\n}\n\n/**\n * Declare the default auth for EVERY route in the application — the ring the\n * cascade consults when neither the route nor its controller says anything.\n *\n * The measured problem it removes: `auth: { verifiedEmail: true }` repeated by\n * hand on ten `@Controller`s. A security setting that must be repeated is a\n * security setting that will be forgotten — the eleventh controller opens the\n * door and nothing says so.\n *\n * Call it at MODULE SCOPE in a file the application imports (the controllers'\n * own barrel, or a module a controller imports). The cascade reads this slot\n * when the route table is built and when the spec is emitted — both of which\n * run after module loading — so declaration order does not matter, but being\n * imported at all does.\n *\n * @example\n * defineDefaultAuth({ verifiedEmail: true }); // every route, unless it says otherwise\n */\nexport function defineDefaultAuth(auth: AuthSpec): void {\n appAuthSlot()[APP_DEFAULT_AUTH] = auth;\n}\n\n/** The declared application default, or `undefined` when none was declared. */\nexport function getDefaultAuth(): AuthSpec | undefined {\n return appAuthSlot()[APP_DEFAULT_AUTH];\n}\n\n/** Clear the application default. For tests, which declare it repeatedly. */\nexport function __resetDefaultAuth(): void {\n delete appAuthSlot()[APP_DEFAULT_AUTH];\n}\n\n/**\n * THE auth cascade: route → controller → application → `true`.\n *\n * One function, every caller — the route table (`engine/router.ts`) and the\n * spec emitter (`openapi/controllers.ts`) ASK for the answer instead of\n * spelling the chain themselves. Two hand-written copies of a cascade is how\n * the build-time answer and the runtime answer come to disagree about who may\n * call an endpoint, and the disagreement shows up as an open door.\n *\n * The terminal `true` is secure-by-default and is load-bearing: a route that\n * declared nothing, under a controller that declared nothing, in an\n * application that declared nothing, is CLOSED.\n */\nexport function resolveEffectiveAuth(\n routeAuth: AuthSpec | undefined,\n controllerAuth: AuthSpec | undefined,\n): AuthSpec {\n return routeAuth ?? controllerAuth ?? getDefaultAuth() ?? true;\n}\n\n/** A class carrying the stamped controller metadata + discriminant. */\ninterface ControllerCarrier {\n __palbase?: \"controller\";\n [CONTROLLER_META]?: ControllerMeta;\n}\n\n/** The one path segment the platform owns. The isolate matches\n * `^/webhooks/([^/]+)$` on the raw request path BEFORE controller dispatch, so\n * anything a controller resolves to under it answers `404 webhook_not_found`\n * and never runs. */\nconst RESERVED_FIRST_SEGMENT = \"webhooks\";\n\n/**\n * Throw if `path` resolves under the reserved segment. Segments are compared the\n * way the isolate compares them — `split(\"/\").filter(Boolean)` — NOT by string\n * prefix, because empty segments collapse there: `@Controller(\"/\")` +\n * `@Post(\"/webhooks/x\")` composes to `//webhooks/x`, which the isolate serves as\n * `/webhooks/x`. A prefix check reads that as safe; the segment check does not.\n * `/webhooksy` stays allowed for the same reason — it is a different segment.\n *\n * Every verb is refused, not just the POST the isolate currently intercepts: the\n * reservation is of the URL namespace, so a `@Get(\"/webhooks/x\")` that happens\n * to work today would be silently shadowed the moment the isolate's method gate\n * widens. Refusing at build is recoverable; discovering it as a 404 is not.\n */\nfunction assertNotReserved(path: string, subject: string): void {\n const [first] = path.split(\"/\").filter(Boolean);\n if (first === RESERVED_FIRST_SEGMENT) {\n throw new Error(\n `${subject} resolves under the reserved /${RESERVED_FIRST_SEGMENT} path — ` +\n \"inbound webhooks are served there and would shadow this route\",\n );\n }\n}\n\n/**\n * Mark a class as a Palbase backend controller. `basePath` is the mount path\n * for every route the class declares; `options.auth` sets the controller-level\n * default auth (a route's own `auth` overrides it; absent ⇒ secure-by-default).\n *\n * @example\n * \\@Controller(\"/todos\", { auth: false })\n * export class TodosController {\n * \\@Get(\"\") list(\\@QueryParams(ListTodosQuery) q: ListTodosQuery): TodoSchema[] { … }\n * }\n */\nexport function Controller(basePath: string, options: ControllerOptions = {}) {\n return function <T extends abstract new (...args: never[]) => object>(ctor: T): T {\n // /webhooks/* belongs to the platform: the isolate matches the inbound\n // webhook route before controller dispatch, so a controller mounted here\n // would never receive a request. Silent shadowing is the failure mode this\n // whole change exists to remove, so refuse it at build.\n //\n // The COMPOSED path is what gets shadowed, not the base path. `@Controller(\"\")`\n // and `@Controller(\"/\")` both pass a base-path-only check while a\n // `@Post(\"/webhooks/stripe\")` inside them resolves to exactly the path the\n // isolate intercepts. Method decorators run BEFORE the class decorator (TS\n // evaluates members first), so every route this class declares is already in\n // the registry here — which is why the composed check can live at this one\n // seam instead of on the dispatch read path. The `@Controller(\"\") +\n // @Post(\"/webhooks/stripe\")` test is the lock on that ordering: if it ever\n // stopped holding, that test goes red.\n assertNotReserved(basePath, `@Controller(\"${basePath}\")`);\n for (const route of getRoutes(ctor)) {\n assertNotReserved(\n `${basePath}${route.subpath}`,\n `@${route.method}(\"${route.subpath}\") in @Controller(\"${basePath}\")`,\n );\n }\n\n const carrier = ctor as unknown as ControllerCarrier;\n const meta: ControllerMeta = {\n __palbase: \"controller\",\n basePath,\n ...(options.auth !== undefined ? { defaultAuth: options.auth } : {}),\n };\n // Non-enumerable so it doesn't leak onto instances / structural checks.\n Object.defineProperty(carrier, CONTROLLER_META, {\n value: meta,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n // The bare `__palbase` discriminant is the cheap detection marker the\n // runtime/extractor checks; keep it readable but non-enumerable.\n Object.defineProperty(carrier, \"__palbase\", {\n value: \"controller\",\n enumerable: false,\n configurable: true,\n writable: false,\n });\n // Record it, so importing the file is enough and exporting is optional.\n // Guarded against a double-decoration re-entering the same class twice.\n const all = registry();\n if (!all.includes(ctor)) all.push(ctor);\n return ctor;\n };\n}\n\n/** True when `value` is a `@Controller`-decorated class (cheap discriminant\n * check). Accepts the class constructor (the default export of a controller\n * file). */\nexport function isController(value: unknown): boolean {\n if (typeof value !== \"function\" && (typeof value !== \"object\" || value === null)) {\n return false;\n }\n const carrier = value as ControllerCarrier;\n return carrier.__palbase === \"controller\" && carrier[CONTROLLER_META] !== undefined;\n}\n\n/** Read the resolved controller metadata off a `@Controller`-decorated class.\n * Throws if the class was not decorated — callers should gate with\n * {@link isController} first (the loader does). */\nexport function resolveController(ctor: unknown): ControllerMeta {\n if (typeof ctor !== \"function\" && (typeof ctor !== \"object\" || ctor === null)) {\n throw new TypeError(\"resolveController: value is not a class\");\n }\n const meta = (ctor as ControllerCarrier)[CONTROLLER_META];\n if (!meta) {\n throw new TypeError(\n \"resolveController: class is not a @Controller — every controller file must `export default` a @Controller-decorated class\",\n );\n }\n return meta;\n}\n\n/**\n * A class the runtime constructs takes NO constructor parameters.\n *\n * ONE writer, four callers (controller, hook, job, webhook) and the build's own\n * check. Four hand-written copies of this message is how the four come to\n * disagree about what is refused — and the disagreement is silent, because a\n * class that slips past one of them still ends up with `undefined` fields.\n *\n * Why it is refused rather than injected: there is no container. The parameter\n * would arrive `undefined`, the code would compile, deploy, and fail at the\n * first request that touches the field — the most expensive place to learn it.\n */\nexport function assertZeroArgConstructor(Ctrl: unknown, kind: string): void {\n const arity = (Ctrl as { length?: number }).length ?? 0;\n if (arity === 0) return;\n const name = (Ctrl as { name?: string }).name ?? \"<anonymous>\";\n throw new Error(\n `${kind} ${name} declares a constructor with ${arity} parameter(s). ` +\n `A ${kind} is constructed by the runtime with a zero-argument constructor — ` +\n `there is no injector to supply them, so every parameter would arrive as ` +\n `undefined. Hold the dependency as a module-level singleton the ${kind} ` +\n \"imports (`const repo = makeRepo()` beside the class), and construct the \" +\n \"service directly in tests (e.g. `new TodoService(fakeDatabase().db)`).\",\n );\n}\n","/** HTTP error with structured error response format.\n *\n * The base class for the throwable error classes (`PalError`, `Conflict`,\n * `NotFound`, …). Construct one directly with `throw new HttpError(404,\n * \"todo_not_found\", \"No such todo\")`, or throw a named subclass\n * (`throw new NotFound(\"todo not found\")`). The runtime catches any `HttpError`\n * and emits the standard envelope; on the wire (and to iOS) it surfaces as\n * `BackendError.server(code, status, message, requestId)`.\n *\n * The optional `data` field carries a structured payload alongside the\n * standard envelope — for errors that need to ship extra context\n * (e.g. `new Conflict(\"locked\", \"title_locked\", { retryAfter: 30 })`). It rides\n * through to the iOS typed enum's associated value.\n */\n/**\n * The brand that identifies an HttpError ACROSS SDK instances.\n *\n * A process legitimately holds more than one copy of this SDK — the runtime\n * loads the engine from its own node_modules while the tenant's bundle carries\n * an inlined copy, which is why the controller registry and the error registry\n * are both anchored on `Symbol.for`. The one place that did not follow the\n * pattern was the engine's catch: `err instanceof HttpError` compares CLASS\n * IDENTITY, so a `throw new NotFound()` from the bundle's copy did not match\n * the engine's copy and every typed error in every deployed backend degraded to\n * `500 internal_error`. Measured through the edge on a real deploy: a route\n * throwing `NotFound` answered 500 while the runtime's own log printed the\n * error object with `status: 404` right beside it.\n *\n * `Symbol.for` puts this in the cross-realm registry, so every copy of the SDK\n * agrees on it by VALUE rather than by identity.\n */\nexport const HTTP_ERROR_BRAND: unique symbol = Symbol.for(\"palbase.backend.httpError\");\n\n/**\n * Whether a thrown value is an HttpError from ANY copy of this SDK.\n *\n * The shape is checked as well as the brand: the brand says \"this claims to be\n * one of ours\", the fields say the envelope can actually be built from it, and\n * a half-formed object must fall through to the 500 path rather than produce a\n * malformed response.\n */\nexport function isHttpError(err: unknown): err is HttpError {\n if (typeof err !== \"object\" || err === null) return false;\n const e = err as Record<PropertyKey, unknown>;\n return (\n e[HTTP_ERROR_BRAND] === true &&\n typeof e.status === \"number\" &&\n typeof e.error === \"string\" &&\n typeof e.errorDescription === \"string\"\n );\n}\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 /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */\n public readonly [HTTP_ERROR_BRAND] = true;\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\n/**\n * Throw with a custom HTTP status + wire code. The general-purpose escape hatch\n * when none of the named classes (`Conflict`/`NotFound`/…) fits.\n *\n * @example\n * throw new PalError(418, \"teapot\", \"I'm a teapot\");\n */\nexport class PalError extends HttpError {\n constructor(status: number, code: string, description: string, data?: unknown) {\n super(status, code, description, data);\n this.name = \"PalError\";\n }\n}\n\n/** Base for the named status classes. Each subclass fixes its HTTP status; the\n * `code` defaults to the class's canonical wire code (overridable), and the\n * `message` defaults to a human-readable label (overridable). */\nabstract class NamedHttpError extends HttpError {\n protected constructor(\n status: number,\n defaultCode: string,\n name: string,\n message?: string,\n code?: string,\n data?: unknown,\n ) {\n super(status, code ?? defaultCode, message ?? defaultMessage(name), data);\n this.name = name;\n }\n}\n\n/** Derive a default human-readable message from a class name\n * (\"NotFound\" → \"Not found\", \"TooManyRequests\" → \"Too many requests\"). */\nfunction defaultMessage(name: string): string {\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\");\n return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();\n}\n\n/**\n * 400 — the request was malformed or failed validation. Carries a fixed typed\n * payload: `new BadRequest({ fields: [{ field: \"email\", message: \"invalid\" }] })`.\n * The shape is declared once in the SDK so codegen surfaces `error.data.fields`\n * typed on the client.\n */\nexport class BadRequest extends NamedHttpError {\n public declare readonly data: BadRequestData;\n constructor(data: BadRequestData, message?: string) {\n super(400, \"bad_request\", \"BadRequest\", message, undefined, data);\n }\n}\n\n/** 401 — the caller is not authenticated. */\nexport class Unauthorized extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(401, \"unauthorized\", \"Unauthorized\", message, code, data);\n }\n}\n\n/** 403 — the caller is authenticated but not allowed. */\nexport class Forbidden extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(403, \"forbidden\", \"Forbidden\", message, code, data);\n }\n}\n\n/** 404 — the requested resource does not exist. */\nexport class NotFound extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(404, \"not_found\", \"NotFound\", message, code, data);\n }\n}\n\n/** 409 — the request conflicts with the current state. */\nexport class Conflict extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(409, \"conflict\", \"Conflict\", message, code, data);\n }\n}\n\n/**\n * 409 — a write was refused because it would duplicate an existing row.\n * Carries the NAME of the unique constraint Postgres named (`users_email_key`).\n *\n * The engine produces it: a statement rejected with SQLSTATE `23505` is\n * converted here rather than surfacing as an opaque driver error (see\n * `engine/db.ts`, `diagnosingDriver`). What that removes is the string match —\n * before this, the only way to act on a duplicate was to test the driver\n * message for \"duplicate key value violates unique constraint\", a contract\n * nobody signed that breaks on a Postgres upgrade, a locale, or a constraint\n * rename, silently and in production.\n *\n * THE NAME IS A FIELD AND STAYS OUT OF THE DEFAULT MESSAGE. The two are not\n * the same audience. `constraint` is read by the code that catches this — the\n * developer, who already knows the schema. `errorDescription` is the HTTP\n * response body, and an UNCAUGHT duplicate puts it in front of the\n * application's end user: `users_email_key` there discloses how the schema is\n * built to whoever sent the request. The platform's own data API took the same\n * decision one surface over and wrote down why —\n * `v2/internal/modules/database/internal/handler/pgerror.go:83-87` collapses\n * every 23xxx to a generic conflict, \"never disclose the constraint/column\n * name\". A thrower who WANTS the name on the wire passes it deliberately\n * (`new UniqueViolation(c, \\`\\${c} already exists\\`)`, or through `data`).\n *\n * @example\n * try {\n * await Database.tables.users.insert({ email });\n * } catch (e) {\n * if (UniqueViolation.is(e) && e.constraint === \"users_email_key\") {\n * throw new Conflict(\"That email is taken\", \"email_taken\");\n * }\n * throw e;\n * }\n */\nexport class UniqueViolation extends Conflict {\n /**\n * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK\n * constructed it.\n *\n * Use this instead of `instanceof`. Measured on a live stack: a controller\n * bundle INLINES its own copy of `@palbase/backend`, and the engine that\n * raises this error is the runtime's copy. Two copies, two class identities,\n * and `e instanceof UniqueViolation` is false in the one place a caller\n * writes it — a check that reads as correct and silently never matches.\n */\n static is(e: unknown): e is UniqueViolation {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as { name?: unknown }).name === \"UniqueViolation\" &&\n typeof (e as { constraint?: unknown }).constraint === \"string\"\n );\n }\n\n /** The unique constraint the statement violated, as Postgres named it.\n * `\"\"` when the driver did not say which — see `engine/db.ts`. */\n public readonly constraint: string;\n\n constructor(constraint: string, message?: string, code?: string, data?: unknown) {\n super(message ?? \"Unique constraint violated\", code ?? \"unique_violation\", data);\n this.name = \"UniqueViolation\";\n this.constraint = constraint;\n }\n}\n\n/** A single field-level validation failure carried by {@link BadRequest}. */\nexport interface FieldError {\n /** The offending field's name (dotted path for nested fields). */\n field: string;\n /** Human-readable reason the field failed. */\n message: string;\n}\n\n/** The fixed, typed payload {@link BadRequest} ships. */\nexport interface BadRequestData {\n /** The fields that failed validation. */\n fields: FieldError[];\n}\n\n/** The fixed, typed payload {@link TooManyRequests} ships. */\nexport interface TooManyRequestsData {\n /** Seconds the caller should wait before retrying. */\n retryAfter: number;\n}\n\n/**\n * 429 — the caller has exceeded the rate limit. Carries a fixed typed payload:\n * `new TooManyRequests({ retryAfter: 30 })`. The shape is declared once in the\n * SDK (error-registry pre-seed) so codegen surfaces `error.data.retryAfter`\n * typed on the client — no per-project definition needed.\n */\nexport class TooManyRequests extends NamedHttpError {\n public declare readonly data: TooManyRequestsData;\n constructor(data: TooManyRequestsData, message?: string) {\n super(429, \"too_many_requests\", \"TooManyRequests\", message, undefined, data);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/decorators/registry.ts","../src/decorators/controller.ts","../src/errors.ts"],"sourcesContent":["// The decorator registry — the single plain-data store the method + parameter\n// decorators write into, and the deploy/dispatch pipeline reads back. No\n// `reflect-metadata`, no `emitDecoratorMetadata`: the registry is built from the\n// decorator arguments + the parameter INDEX that esbuild/tsc preserve for legacy\n// parameter decorators (verified — see the design spec §0/§4.1).\n//\n// A controller class carries its route metadata on a symbol-keyed static\n// property (`ROUTES`). `@Get`/`@Post`/… append a {@link RouteMeta} entry;\n// `@Body`/`@User`/… append a {@link ParamMeta} entry onto the route for the\n// method they decorate. Because parameter decorators run BEFORE the method\n// decorator for the same member (TS evaluates innermost-first, params before the\n// method), the route entry may not exist yet when a param decorator fires — so\n// param metadata is buffered per method name and merged when the method\n// decorator creates the route entry.\nimport type { AuthSpec, RateLimitConfig } from \"../endpoint.js\";\nimport type { UploadConfig } from \"./upload.js\";\nimport type { SseConfig } from \"./sse.js\";\nimport type { ZodTypeAny } from \"zod\";\n\n/** The HTTP verbs a route may declare, upper-cased (the runtime router +\n * OpenAPI lower-case on their own). */\nexport type HttpMethodUpper = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"QUERY\";\n\n/** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */\nexport interface RouteOptions {\n /** OVERRIDES the controller-level default auth for this one route. */\n auth?: AuthSpec;\n /** Per-route rate limit. */\n rateLimit?: RateLimitConfig;\n /** Direct-storage upload config — present ONLY on `@Upload` routes (the\n * `@Get`/`@Post`/… decorators never set it). Its presence is what MARKS a\n * route as an upload route through the whole pipeline (registry → flatten →\n * openapi → codegen). The bytes go client→storage directly; the method body\n * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */\n uploadConfig?: UploadConfig;\n /** Streaming config — present ONLY on `@Sse` routes (the `@Get`/`@Post`/…\n * decorators never set it). Its presence is what MARKS a route as a streaming\n * route through the whole pipeline (registry → flatten → openapi → codegen),\n * exactly as `uploadConfig` does for uploads — never a special HTTP verb. An\n * `@Sse` route registers POST like any input-bearing route, so the verb cannot\n * carry the distinction. See {@link SseConfig} (decorators/sse.ts). */\n sseConfig?: SseConfig;\n}\n\n/** The kind of value a parameter decorator injects. Drives both dispatch\n * (which request slice to inject) and codegen (which OpenAPI parameter source a\n * schema-bearing kind maps to). */\nexport type ParamKind =\n | \"body\"\n | \"query\"\n | \"param\"\n | \"headers\"\n | \"user\"\n | \"optionalUser\"\n | \"client\"\n | \"requestId\"\n | \"traceId\"\n | \"req\"\n // `@UploadedObject()` — injects the uploaded object (completion input) on an\n // `@Upload` route. No schema (the shape is the fixed UploadedObject type).\n | \"uploadedObject\"\n // `@SseOut()` — injects the frame writer on an `@Sse` route. No schema (the\n // shape is the fixed SseWriter type).\n | \"sseOut\"\n // `@Signal()` — injects the request's AbortSignal, which aborts when the\n // client disconnects. No schema. NOT derivable from `@Req()`: PBRequest\n // carries only request-scoped data and has no signal (endpoint.ts:358-363).\n | \"signal\";\n\n/** One parameter decorator's recorded metadata. `index` is the parameter\n * position esbuild/tsc preserve; `schema` is present for the schema-bearing\n * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */\nexport interface ParamMeta {\n index: number;\n kind: ParamKind;\n /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */\n schema?: ZodTypeAny;\n /** Path-param name for `@Param(\"id\")`. */\n name?: string;\n}\n\n/** One inferred throw site: the error CLASS name (e.g. \"TodoLocked\") and its\n * wire code (e.g. \"todo_locked\"). `status`, `hasData`, and the data JSON schema\n * are NOT carried here — they resolve from the error registry by `code` at\n * extract/openapi time (single source of truth). */\nexport interface ThrowDescriptor {\n name: string;\n code: string;\n}\n\n/** One route's recorded metadata: the verb + subpath + method name + options,\n * the ordered parameter metas, and the resolved return schema (injected by the\n * codegen step — see `returnSchema`). */\nexport interface RouteMeta {\n method: HttpMethodUpper;\n subpath: string;\n fnName: string;\n options: RouteOptions;\n params: ParamMeta[];\n /** Response schema for the route, if any. Derived from the method's RETURN\n * TYPE by codegen and written here via `recordReturn` (a generated top-level\n * IIFE injected per controller), not by an author-written decorator. */\n returnSchema?: ZodTypeAny;\n /** Error classes this route can throw, if inferred. Derived from the method\n * body + service call graph by the deploy stager's throw analysis and written\n * here via `recordThrows` (a generated top-level IIFE injected per controller,\n * the `recordReturn` twin), not by an author-written decorator. */\n throws?: ThrowDescriptor[];\n}\n\n/** Symbol the route metadata list is stored under on a controller class. Using\n * a symbol (not a string key) keeps it off the public structural surface and\n * avoids any chance of an authored property collision. */\nexport const ROUTES: unique symbol = Symbol.for(\"palbase.backend.routes\");\n\n/** Symbol the per-method buffered parameter metas are stored under while a class\n * is being decorated. Parameter decorators fire before the method decorator, so\n * they buffer here keyed by method name; the method decorator drains the buffer\n * into the route entry it creates. */\nconst PARAM_BUFFER: unique symbol = Symbol.for(\"palbase.backend.paramBuffer\");\n\n/** Symbol the per-method buffered return-type schemas are stored under while a\n * class's registry is being populated. The codegen-injected `recordReturn` call\n * can fire before OR after the method decorator; it buffers here keyed by method\n * name and `recordRoute` drains it into the route entry (and `recordReturn`\n * writes through if the route already exists). Buffering on BOTH sides means a\n * fully-formed route entry always carries its return schema — a raw-symbol\n * reader (the runtime) never has to re-merge. */\nconst RETURN_BUFFER: unique symbol = Symbol.for(\"palbase.backend.returnBuffer\");\n\n/** Symbol the per-method buffered throw descriptors are stored under while a\n * class's registry is being populated. The stager-injected `recordThrows` call\n * can fire before OR after the method decorator; it buffers here keyed by method\n * name and `recordRoute` drains it into the route entry (and `recordThrows`\n * writes through if the route already exists). Buffering on BOTH sides means a\n * fully-formed route entry always carries its throw descriptors — a raw-symbol\n * reader (the runtime) never has to re-merge. */\nconst THROWS_BUFFER: unique symbol = Symbol.for(\"palbase.backend.throwsBuffer\");\n\n/** A class constructor carrying the symbol-keyed registry slots. We type the\n * registry-bearing class as this so the decorators can read/write the slots\n * without `any` — a plain `Function` does not carry index signatures. */\ninterface RegistryCarrier {\n [ROUTES]?: RouteMeta[];\n [PARAM_BUFFER]?: Record<string, ParamMeta[]>;\n [RETURN_BUFFER]?: Record<string, ZodTypeAny>;\n [THROWS_BUFFER]?: Record<string, ThrowDescriptor[]>;\n}\n\n/** Coerce a decorated target (class constructor or its prototype) into the\n * registry carrier that owns the slots. Method/param decorators receive the\n * PROTOTYPE as their target; the class decorator receives the constructor. We\n * always anchor the registry on the CONSTRUCTOR so `getRoutes(ctor)` finds it. */\nfunction carrierOf(target: object): RegistryCarrier {\n // For instance-member decorators, `target` is the prototype; its `.constructor`\n // is the class. For a static member or the class decorator, `target` is the\n // constructor already. Resolve to the constructor either way.\n const ctor =\n typeof target === \"function\"\n ? (target as unknown as RegistryCarrier)\n : (((target as { constructor?: unknown }).constructor ??\n target) as unknown as RegistryCarrier);\n return ctor;\n}\n\n/** Get (creating if absent) the own route list for a class constructor. Own —\n * not inherited — so a subclass does not mutate its base's routes. */\nfunction ownRoutes(carrier: RegistryCarrier): RouteMeta[] {\n if (!Object.prototype.hasOwnProperty.call(carrier, ROUTES)) {\n carrier[ROUTES] = [];\n }\n return carrier[ROUTES] as RouteMeta[];\n}\n\n/** Get (creating if absent) the own per-method param buffer for a class. */\nfunction ownParamBuffer(carrier: RegistryCarrier): Record<string, ParamMeta[]> {\n if (!Object.prototype.hasOwnProperty.call(carrier, PARAM_BUFFER)) {\n carrier[PARAM_BUFFER] = {};\n }\n return carrier[PARAM_BUFFER] as Record<string, ParamMeta[]>;\n}\n\n/** Record a route (called by the method decorators). Drains any parameter\n * metas already buffered for `fnName` into the new route entry, then sorts them\n * by parameter index so dispatch can inject positionally. */\nexport function recordRoute(\n target: object,\n fnName: string,\n method: HttpMethodUpper,\n subpath: string,\n options: RouteOptions,\n): void {\n const carrier = carrierOf(target);\n const routes = ownRoutes(carrier);\n const buffer = ownParamBuffer(carrier);\n const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);\n const route: RouteMeta = { method, subpath, fnName, options, params };\n // Drain a buffered return schema (the recordReturn-ran-first ordering) so the\n // route entry is complete the moment it's created — a raw-symbol consumer\n // (the runtime extractor/worker) sees the return schema without re-merging.\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer && returnBuffer[fnName] !== undefined) {\n route.returnSchema = returnBuffer[fnName];\n }\n // Same drain for buffered throw descriptors (the recordThrows-ran-first\n // ordering) — the route entry is complete the moment it's created.\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer && throwsBuffer[fnName] !== undefined) {\n route.throws = throwsBuffer[fnName];\n }\n routes.push(route);\n}\n\n/** Record one parameter decorator (called by `@Body`/`@User`/…). Buffers per\n * method name; the method decorator merges the buffer into the route entry. If\n * the route already exists (method decorator ran first — TS does evaluate the\n * method decorator AFTER its parameter decorators, but we stay order-robust),\n * the meta is also appended directly so neither ordering loses it. */\nexport function recordParam(target: object, fnName: string, meta: ParamMeta): void {\n const carrier = carrierOf(target);\n const buffer = ownParamBuffer(carrier);\n (buffer[fnName] ??= []).push(meta);\n\n // Order-robust: if the route already exists, merge in place + keep sorted.\n const routes = carrier[ROUTES];\n if (routes) {\n const route = routes.find((r) => r.fnName === fnName);\n if (route) {\n route.params.push(meta);\n route.params.sort((a, b) => a.index - b.index);\n }\n }\n}\n\n/** Attach a return schema to the route for `fnName` (called by the codegen\n * injection that reads the method's return type). If the route does not exist\n * yet, the schema is buffered (RETURN_BUFFER) and drained into the route by\n * `recordRoute` when the method decorator runs. */\nexport function recordReturn(target: object, fnName: string, schema: ZodTypeAny): void {\n const carrier = carrierOf(target);\n const routes = carrier[ROUTES];\n const route = routes?.find((r) => r.fnName === fnName);\n if (route) {\n route.returnSchema = schema;\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(carrier, RETURN_BUFFER)) {\n carrier[RETURN_BUFFER] = {};\n }\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer) returnBuffer[fnName] = schema;\n}\n\n/** Attach the inferred throw descriptors to the route for `fnName` (called by\n * the stager-injected IIFE that carries the throw analysis result — the\n * `recordReturn` twin). If the route does not exist yet, the descriptors are\n * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the\n * method decorator runs. */\nexport function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void {\n const carrier = carrierOf(target);\n const routes = carrier[ROUTES];\n const route = routes?.find((r) => r.fnName === fnName);\n if (route) {\n route.throws = throws;\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(carrier, THROWS_BUFFER)) {\n carrier[THROWS_BUFFER] = {};\n }\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer) throwsBuffer[fnName] = throws;\n}\n\n/** Read the route metadata for a controller class (the deploy/dispatch entry\n * point). Applies any buffered return schemas + throw descriptors (for the\n * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy\n * so callers cannot mutate the registry.\n */\nexport function getRoutes(ctor: object): RouteMeta[] {\n const carrier = carrierOf(ctor);\n const routes = carrier[ROUTES] ?? [];\n const returnBuffer = carrier[RETURN_BUFFER];\n if (returnBuffer) {\n for (const route of routes) {\n const buffered = returnBuffer[route.fnName];\n if (buffered && route.returnSchema === undefined) {\n route.returnSchema = buffered;\n }\n }\n }\n const throwsBuffer = carrier[THROWS_BUFFER];\n if (throwsBuffer) {\n for (const route of routes) {\n const buffered = throwsBuffer[route.fnName];\n if (buffered && route.throws === undefined) {\n route.throws = buffered;\n }\n }\n }\n return routes.map((r) => ({\n ...r,\n params: r.params.slice(),\n ...(r.throws !== undefined ? { throws: r.throws.slice() } : {}),\n }));\n}\n","// `@Controller(basePath, options?)` — the class decorator that marks a class as\n// a Palbase backend controller. It stamps a non-enumerable `__palbase`\n// discriminant + the resolved controller metadata onto the class so the\n// deploy/dispatch pipeline (and `isController`/`resolveController`) can detect\n// and read it without `reflect-metadata`.\nimport type { AuthSpec } from \"../endpoint.js\";\nimport { getRoutes } from \"./registry.js\";\n\n/** The controller metadata stamped onto a `@Controller`-decorated class. The\n * default export of a `controllers/*.controller.ts` file resolves to this via\n * {@link resolveController}. */\nexport interface ControllerMeta {\n /** Discriminant the runtime + tooling read. */\n readonly __palbase: \"controller\";\n /** The base path every route in this controller mounts under (e.g. \"/todos\"). */\n basePath: string;\n /** Controller-level default auth, applied to routes that don't set their own\n * (`@Get(\"/x\", { auth })` overrides this). `undefined` ⇒ the application\n * default ({@link defineDefaultAuth}), and secure-by-default below that —\n * see {@link resolveEffectiveAuth} for the whole cascade. */\n defaultAuth?: AuthSpec;\n}\n\n/** Options accepted by `@Controller`. */\nexport interface ControllerOptions {\n /** Default auth for ALL routes in this controller (route-level overrides;\n * omitting it falls through to the application default declared with\n * {@link defineDefaultAuth}). */\n auth?: AuthSpec;\n}\n\n/** Symbol the controller metadata is stamped under. Symbol-keyed (not a string\n * property) so it never collides with an authored member and stays off the\n * structural surface. */\nexport const CONTROLLER_META: unique symbol = Symbol.for(\"palbase.backend.controllerMeta\");\n\n/**\n * Every class `@Controller` has decorated, in decoration order.\n *\n * This is what lets a controller file need no export at all: importing the file\n * runs the decorator, the decorator records the class here, and the runtime\n * reads the list. Without it the only handle on a class is its export name, so\n * every controller had to be exported AND named in a generated entry — the\n * ceremony NestJS still charges (`export class` PLUS\n * `@Module({controllers:[…]})`).\n *\n * Keyed on a well-known Symbol against globalThis rather than held in a module\n * variable, because a deployed bundle inlines its own copy of this package: two\n * copies would keep two lists, and the runtime would read the empty one. The\n * same hazard `runtimeHooks` exists for, closed the same way — one shared slot.\n */\nconst REGISTRY: unique symbol = Symbol.for(\"palbase.backend.allControllers\") as never;\n\nfunction registry(): unknown[] {\n const g = globalThis as unknown as Record<symbol, unknown[] | undefined>;\n const existing = g[REGISTRY];\n if (existing) return existing;\n const fresh: unknown[] = [];\n g[REGISTRY] = fresh;\n return fresh;\n}\n\n/**\n * The controller classes this process has loaded, in decoration order.\n *\n * Decoration order is import order, which the bundler fixes by sorting the\n * files it emits imports for — so two builds of one tree produce the same\n * route table, and route precedence is not a function of module-resolution\n * accidents.\n */\nexport function getRegisteredControllers(): readonly unknown[] {\n return registry().slice();\n}\n\n/** Empty the registry. For tests, which load controllers repeatedly. */\nexport function __resetRegisteredControllers(): void {\n registry().length = 0;\n}\n\n/**\n * The APPLICATION-level default auth.\n *\n * Held on globalThis under a well-known Symbol for exactly the reason\n * {@link REGISTRY} is: a deployed bundle inlines its own copy of this package,\n * and two copies keeping two defaults is how a security setting silently\n * becomes two different settings.\n */\nconst APP_DEFAULT_AUTH: unique symbol = Symbol.for(\"palbase.backend.appDefaultAuth\") as never;\n\nfunction appAuthSlot(): Record<symbol, AuthSpec | undefined> {\n return globalThis as unknown as Record<symbol, AuthSpec | undefined>;\n}\n\n/**\n * Declare the default auth for EVERY route in the application — the ring the\n * cascade consults when neither the route nor its controller says anything.\n *\n * The measured problem it removes: `auth: { verifiedEmail: true }` repeated by\n * hand on ten `@Controller`s. A security setting that must be repeated is a\n * security setting that will be forgotten — the eleventh controller opens the\n * door and nothing says so.\n *\n * Call it at MODULE SCOPE in a file the application imports (the controllers'\n * own barrel, or a module a controller imports). The cascade reads this slot\n * when the route table is built and when the spec is emitted — both of which\n * run after module loading — so declaration order does not matter, but being\n * imported at all does.\n *\n * @example\n * defineDefaultAuth({ verifiedEmail: true }); // every route, unless it says otherwise\n */\nexport function defineDefaultAuth(auth: AuthSpec): void {\n appAuthSlot()[APP_DEFAULT_AUTH] = auth;\n}\n\n/** The declared application default, or `undefined` when none was declared. */\nexport function getDefaultAuth(): AuthSpec | undefined {\n return appAuthSlot()[APP_DEFAULT_AUTH];\n}\n\n/** Clear the application default. For tests, which declare it repeatedly. */\nexport function __resetDefaultAuth(): void {\n delete appAuthSlot()[APP_DEFAULT_AUTH];\n}\n\n/**\n * THE auth cascade: route → controller → application → `true`.\n *\n * One function, every caller — the route table (`engine/router.ts`) and the\n * spec emitter (`openapi/controllers.ts`) ASK for the answer instead of\n * spelling the chain themselves. Two hand-written copies of a cascade is how\n * the build-time answer and the runtime answer come to disagree about who may\n * call an endpoint, and the disagreement shows up as an open door.\n *\n * The terminal `true` is secure-by-default and is load-bearing: a route that\n * declared nothing, under a controller that declared nothing, in an\n * application that declared nothing, is CLOSED.\n */\nexport function resolveEffectiveAuth(\n routeAuth: AuthSpec | undefined,\n controllerAuth: AuthSpec | undefined,\n): AuthSpec {\n return routeAuth ?? controllerAuth ?? getDefaultAuth() ?? true;\n}\n\n/** A class carrying the stamped controller metadata + discriminant. */\ninterface ControllerCarrier {\n __palbase?: \"controller\";\n [CONTROLLER_META]?: ControllerMeta;\n}\n\n/** The one path segment the platform owns. The isolate matches\n * `^/webhooks/([^/]+)$` on the raw request path BEFORE controller dispatch, so\n * anything a controller resolves to under it answers `404 webhook_not_found`\n * and never runs. */\nconst RESERVED_FIRST_SEGMENT = \"webhooks\";\n\n/**\n * Throw if `path` resolves under the reserved segment. Segments are compared the\n * way the isolate compares them — `split(\"/\").filter(Boolean)` — NOT by string\n * prefix, because empty segments collapse there: `@Controller(\"/\")` +\n * `@Post(\"/webhooks/x\")` composes to `//webhooks/x`, which the isolate serves as\n * `/webhooks/x`. A prefix check reads that as safe; the segment check does not.\n * `/webhooksy` stays allowed for the same reason — it is a different segment.\n *\n * Every verb is refused, not just the POST the isolate currently intercepts: the\n * reservation is of the URL namespace, so a `@Get(\"/webhooks/x\")` that happens\n * to work today would be silently shadowed the moment the isolate's method gate\n * widens. Refusing at build is recoverable; discovering it as a 404 is not.\n */\nfunction assertNotReserved(path: string, subject: string): void {\n const [first] = path.split(\"/\").filter(Boolean);\n if (first === RESERVED_FIRST_SEGMENT) {\n throw new Error(\n `${subject} resolves under the reserved /${RESERVED_FIRST_SEGMENT} path — ` +\n \"inbound webhooks are served there and would shadow this route\",\n );\n }\n}\n\n/**\n * Mark a class as a Palbase backend controller. `basePath` is the mount path\n * for every route the class declares; `options.auth` sets the controller-level\n * default auth (a route's own `auth` overrides it; absent ⇒ secure-by-default).\n *\n * @example\n * \\@Controller(\"/todos\", { auth: false })\n * export class TodosController {\n * \\@Get(\"\") list(\\@QueryParams(ListTodosQuery) q: ListTodosQuery): TodoSchema[] { … }\n * }\n */\nexport function Controller(basePath: string, options: ControllerOptions = {}) {\n return function <T extends abstract new (...args: never[]) => object>(ctor: T): T {\n // /webhooks/* belongs to the platform: the isolate matches the inbound\n // webhook route before controller dispatch, so a controller mounted here\n // would never receive a request. Silent shadowing is the failure mode this\n // whole change exists to remove, so refuse it at build.\n //\n // The COMPOSED path is what gets shadowed, not the base path. `@Controller(\"\")`\n // and `@Controller(\"/\")` both pass a base-path-only check while a\n // `@Post(\"/webhooks/stripe\")` inside them resolves to exactly the path the\n // isolate intercepts. Method decorators run BEFORE the class decorator (TS\n // evaluates members first), so every route this class declares is already in\n // the registry here — which is why the composed check can live at this one\n // seam instead of on the dispatch read path. The `@Controller(\"\") +\n // @Post(\"/webhooks/stripe\")` test is the lock on that ordering: if it ever\n // stopped holding, that test goes red.\n assertNotReserved(basePath, `@Controller(\"${basePath}\")`);\n for (const route of getRoutes(ctor)) {\n assertNotReserved(\n `${basePath}${route.subpath}`,\n `@${route.method}(\"${route.subpath}\") in @Controller(\"${basePath}\")`,\n );\n }\n\n const carrier = ctor as unknown as ControllerCarrier;\n const meta: ControllerMeta = {\n __palbase: \"controller\",\n basePath,\n ...(options.auth !== undefined ? { defaultAuth: options.auth } : {}),\n };\n // Non-enumerable so it doesn't leak onto instances / structural checks.\n Object.defineProperty(carrier, CONTROLLER_META, {\n value: meta,\n enumerable: false,\n configurable: true,\n writable: false,\n });\n // The bare `__palbase` discriminant is the cheap detection marker the\n // runtime/extractor checks; keep it readable but non-enumerable.\n Object.defineProperty(carrier, \"__palbase\", {\n value: \"controller\",\n enumerable: false,\n configurable: true,\n writable: false,\n });\n // Record it, so importing the file is enough and exporting is optional.\n // Guarded against a double-decoration re-entering the same class twice.\n const all = registry();\n if (!all.includes(ctor)) all.push(ctor);\n return ctor;\n };\n}\n\n/** True when `value` is a `@Controller`-decorated class (cheap discriminant\n * check). Accepts the class constructor (the default export of a controller\n * file). */\nexport function isController(value: unknown): boolean {\n if (typeof value !== \"function\" && (typeof value !== \"object\" || value === null)) {\n return false;\n }\n const carrier = value as ControllerCarrier;\n return carrier.__palbase === \"controller\" && carrier[CONTROLLER_META] !== undefined;\n}\n\n/** Read the resolved controller metadata off a `@Controller`-decorated class.\n * Throws if the class was not decorated — callers should gate with\n * {@link isController} first (the loader does). */\nexport function resolveController(ctor: unknown): ControllerMeta {\n if (typeof ctor !== \"function\" && (typeof ctor !== \"object\" || ctor === null)) {\n throw new TypeError(\"resolveController: value is not a class\");\n }\n const meta = (ctor as ControllerCarrier)[CONTROLLER_META];\n if (!meta) {\n throw new TypeError(\n \"resolveController: class is not a @Controller — every controller file must `export default` a @Controller-decorated class\",\n );\n }\n return meta;\n}\n\n/**\n * A class the runtime constructs takes NO constructor parameters.\n *\n * ONE writer, four callers (controller, hook, job, webhook) and the build's own\n * check. Four hand-written copies of this message is how the four come to\n * disagree about what is refused — and the disagreement is silent, because a\n * class that slips past one of them still ends up with `undefined` fields.\n *\n * Why it is refused rather than injected: there is no container. The parameter\n * would arrive `undefined`, the code would compile, deploy, and fail at the\n * first request that touches the field — the most expensive place to learn it.\n */\nexport function assertZeroArgConstructor(Ctrl: unknown, kind: string): void {\n const arity = (Ctrl as { length?: number }).length ?? 0;\n if (arity === 0) return;\n const name = (Ctrl as { name?: string }).name ?? \"<anonymous>\";\n throw new Error(\n `${kind} ${name} declares a constructor with ${arity} parameter(s). ` +\n `A ${kind} is constructed by the runtime with a zero-argument constructor — ` +\n `there is no injector to supply them, so every parameter would arrive as ` +\n `undefined. Hold the dependency as a module-level singleton the ${kind} ` +\n \"imports (`const repo = makeRepo()` beside the class), and construct the \" +\n \"service directly in tests (e.g. `new TodoService(fakeDatabase().db)`).\",\n );\n}\n","/**\n * The brand that identifies an HttpError ACROSS SDK instances.\n *\n * A process legitimately holds more than one copy of this SDK — the runtime\n * loads the engine from its own node_modules while the tenant's bundle carries\n * an inlined copy, which is why the controller registry and the error registry\n * are both anchored on `Symbol.for`. The one place that did not follow the\n * pattern was the engine's catch: `err instanceof HttpError` compares CLASS\n * IDENTITY, so a `throw new NotFound()` from the bundle's copy did not match\n * the engine's copy and every typed error in every deployed backend degraded to\n * `500 internal_error`. Measured through the edge on a real deploy: a route\n * throwing `NotFound` answered 500 while the runtime's own log printed the\n * error object with `status: 404` right beside it.\n *\n * `Symbol.for` puts this in the cross-realm registry, so every copy of the SDK\n * agrees on it by VALUE rather than by identity.\n */\nexport const HTTP_ERROR_BRAND: unique symbol = Symbol.for(\"palbase.backend.httpError\");\n\n/**\n * Set on an `HttpError` the ENGINE built out of a driver failure, as opposed to\n * one the author constructed to ANSWER a request.\n *\n * The distinction cannot be read off the status, and 409 is why. The scaffold\n * teaches `throw new Conflict(\"title already taken\")` as the way to answer\n * (template/AGENTS.md), and the engine raises `UniqueViolation` — also a 409 —\n * when a write hits a unique index. Logging by status therefore either loses the\n * engine's event or writes an \"unhandled\" line every time an author takes the\n * documented path. Measured: it did the second.\n *\n * `Symbol.for` so the mark survives the bundle/runtime SDK split, the same way\n * {@link HTTP_ERROR_BRAND} does.\n */\nexport const ENGINE_RAISED: unique symbol = Symbol.for(\"palbase.backend.engineRaised\") as never;\n\n/** Mark `e` as engine-raised and return it, so a conversion site reads as one expression. */\nexport function markEngineRaised<E extends object>(e: E): E {\n (e as Record<symbol, unknown>)[ENGINE_RAISED] = true;\n return e;\n}\n\n/** Whether the engine built this error, rather than the author throwing it to answer. */\nexport function isEngineRaised(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as Record<symbol, unknown>)[ENGINE_RAISED] === true;\n}\n\n/**\n * Whether a thrown value is an HttpError from ANY copy of this SDK.\n *\n * The shape is checked as well as the brand: the brand says \"this claims to be\n * one of ours\", the fields say the envelope can actually be built from it, and\n * a half-formed object must fall through to the 500 path rather than produce a\n * malformed response.\n */\nexport function isHttpError(err: unknown): err is HttpError {\n if (typeof err !== \"object\" || err === null) return false;\n const e = err as Record<PropertyKey, unknown>;\n return (\n e[HTTP_ERROR_BRAND] === true &&\n typeof e.status === \"number\" &&\n typeof e.error === \"string\" &&\n typeof e.errorDescription === \"string\"\n );\n}\n\n/** HTTP error with structured error response format.\n *\n * The base class for the throwable error classes (`PalError`, `Conflict`,\n * `NotFound`, …). Construct one directly with `throw new HttpError(404,\n * \"todo_not_found\", \"No such todo\")`, or throw a named subclass\n * (`throw new NotFound(\"todo not found\")`). The runtime catches any `HttpError`\n * and emits the standard envelope; on the wire (and to iOS) it surfaces as\n * `BackendError.server(code, status, message, requestId)`.\n *\n * The optional `data` field carries a structured payload alongside the\n * standard envelope — for errors that need to ship extra context\n * (e.g. `new Conflict(\"locked\", \"title_locked\", { retryAfter: 30 })`). It rides\n * through to the iOS typed 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 /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */\n public readonly [HTTP_ERROR_BRAND] = true;\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\n/**\n * Throw with a custom HTTP status + wire code. The general-purpose escape hatch\n * when none of the named classes (`Conflict`/`NotFound`/…) fits.\n *\n * @example\n * throw new PalError(418, \"teapot\", \"I'm a teapot\");\n */\nexport class PalError extends HttpError {\n constructor(status: number, code: string, description: string, data?: unknown) {\n super(status, code, description, data);\n this.name = \"PalError\";\n }\n}\n\n/** Base for the named status classes. Each subclass fixes its HTTP status; the\n * `code` defaults to the class's canonical wire code (overridable), and the\n * `message` defaults to a human-readable label (overridable). */\nabstract class NamedHttpError extends HttpError {\n protected constructor(\n status: number,\n defaultCode: string,\n name: string,\n message?: string,\n code?: string,\n data?: unknown,\n ) {\n super(status, code ?? defaultCode, message ?? defaultMessage(name), data);\n this.name = name;\n }\n}\n\n/** Derive a default human-readable message from a class name\n * (\"NotFound\" → \"Not found\", \"TooManyRequests\" → \"Too many requests\"). */\nfunction defaultMessage(name: string): string {\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\");\n return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();\n}\n\n/**\n * 400 — the request was malformed or failed validation. Carries a fixed typed\n * payload: `new BadRequest({ fields: [{ field: \"email\", message: \"invalid\" }] })`.\n * The shape is declared once in the SDK so codegen surfaces `error.data.fields`\n * typed on the client.\n */\nexport class BadRequest extends NamedHttpError {\n public declare readonly data: BadRequestData;\n constructor(data: BadRequestData, message?: string) {\n super(400, \"bad_request\", \"BadRequest\", message, undefined, data);\n }\n}\n\n/** 401 — the caller is not authenticated. */\nexport class Unauthorized extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(401, \"unauthorized\", \"Unauthorized\", message, code, data);\n }\n}\n\n/** 403 — the caller is authenticated but not allowed. */\nexport class Forbidden extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(403, \"forbidden\", \"Forbidden\", message, code, data);\n }\n}\n\n/** 404 — the requested resource does not exist. */\nexport class NotFound extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(404, \"not_found\", \"NotFound\", message, code, data);\n }\n}\n\n/** 409 — the request conflicts with the current state. */\nexport class Conflict extends NamedHttpError {\n constructor(message?: string, code?: string, data?: unknown) {\n super(409, \"conflict\", \"Conflict\", message, code, data);\n }\n}\n\n/**\n * 409 — a write was refused because it would duplicate an existing row.\n * Carries the NAME of the unique constraint Postgres named (`users_email_key`).\n *\n * The engine produces it: a statement rejected with SQLSTATE `23505` is\n * converted here rather than surfacing as an opaque driver error (see\n * `engine/db.ts`, `diagnosingDriver`). What that removes is the string match —\n * before this, the only way to act on a duplicate was to test the driver\n * message for \"duplicate key value violates unique constraint\", a contract\n * nobody signed that breaks on a Postgres upgrade, a locale, or a constraint\n * rename, silently and in production.\n *\n * THE NAME IS A FIELD AND STAYS OUT OF THE DEFAULT MESSAGE. The two are not\n * the same audience. `constraint` is read by the code that catches this — the\n * developer, who already knows the schema. `errorDescription` is the HTTP\n * response body, and an UNCAUGHT duplicate puts it in front of the\n * application's end user: `users_email_key` there discloses how the schema is\n * built to whoever sent the request. The platform's own data API took the same\n * decision one surface over and wrote down why —\n * `v2/internal/modules/database/internal/handler/pgerror.go:83-87` collapses\n * every 23xxx to a generic conflict, \"never disclose the constraint/column\n * name\". A thrower who WANTS the name on the wire passes it deliberately\n * (`new UniqueViolation(c, \\`\\${c} already exists\\`)`, or through `data`).\n *\n * @example\n * try {\n * await Database.tables.users.insert({ email });\n * } catch (e) {\n * if (UniqueViolation.is(e) && e.constraint === \"users_email_key\") {\n * throw new Conflict(\"That email is taken\", \"email_taken\");\n * }\n * throw e;\n * }\n */\nexport class UniqueViolation extends Conflict {\n /**\n * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK\n * constructed it.\n *\n * Use this instead of `instanceof`. Measured on a live stack: a controller\n * bundle INLINES its own copy of `@palbase/backend`, and the engine that\n * raises this error is the runtime's copy. Two copies, two class identities,\n * and `e instanceof UniqueViolation` is false in the one place a caller\n * writes it — a check that reads as correct and silently never matches.\n */\n static is(e: unknown): e is UniqueViolation {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as { name?: unknown }).name === \"UniqueViolation\" &&\n typeof (e as { constraint?: unknown }).constraint === \"string\"\n );\n }\n\n /** The unique constraint the statement violated, as Postgres named it.\n * `\"\"` when the driver did not say which — see `engine/db.ts`. */\n public readonly constraint: string;\n\n constructor(constraint: string, message?: string, code?: string, data?: unknown) {\n super(message ?? \"Unique constraint violated\", code ?? \"unique_violation\", data);\n this.name = \"UniqueViolation\";\n this.constraint = constraint;\n }\n}\n\n/** A single field-level validation failure carried by {@link BadRequest}. */\nexport interface FieldError {\n /** The offending field's name (dotted path for nested fields). */\n field: string;\n /** Human-readable reason the field failed. */\n message: string;\n}\n\n/** The fixed, typed payload {@link BadRequest} ships. */\nexport interface BadRequestData {\n /** The fields that failed validation. */\n fields: FieldError[];\n}\n\n/** The fixed, typed payload {@link TooManyRequests} ships. */\nexport interface TooManyRequestsData {\n /** Seconds the caller should wait before retrying. */\n retryAfter: number;\n}\n\n/**\n * 429 — the caller has exceeded the rate limit. Carries a fixed typed payload:\n * `new TooManyRequests({ retryAfter: 30 })`. The shape is declared once in the\n * SDK (error-registry pre-seed) so codegen surfaces `error.data.retryAfter`\n * typed on the client — no per-project definition needed.\n */\nexport class TooManyRequests extends NamedHttpError {\n public declare readonly data: TooManyRequestsData;\n constructor(data: TooManyRequestsData, message?: string) {\n super(429, \"too_many_requests\", \"TooManyRequests\", message, undefined, data);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-MoQ31B6M.cjs';
2
- export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-CcQ1a36a.cjs';
1
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-D4rts8T7.cjs';
2
+ export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-BSGw1pTu.cjs';
3
3
  import './env.cjs';
4
4
  import '../stack.cjs';
5
5
  import 'zod';
@@ -1,5 +1,5 @@
1
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-BOS_rFBO.js';
2
- export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-0_DGBajf.js';
1
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-ByBMibIJ.js';
2
+ export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-DMwJjEHQ.js';
3
3
  import './env.js';
4
4
  import '../stack.js';
5
5
  import 'zod';
@@ -669,6 +669,14 @@ var Realtime = makeServiceProxy("Realtime");
669
669
 
670
670
  // src/errors.ts
671
671
  var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
672
+ var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
673
+ function markEngineRaised(e) {
674
+ e[ENGINE_RAISED] = true;
675
+ return e;
676
+ }
677
+ function isEngineRaised(e) {
678
+ return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
679
+ }
672
680
  function isHttpError(err) {
673
681
  if (typeof err !== "object" || err === null) return false;
674
682
  const e = err;
@@ -1190,7 +1198,14 @@ function asTableRows(table, rows) {
1190
1198
  const transforms = transformsOf(currentSchema, table);
1191
1199
  return rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
1192
1200
  }
1193
- function asBindParams(table, cols, data) {
1201
+ function asBindParams(table, cols, data, caller) {
1202
+ for (const c of cols) {
1203
+ if (data[c] === void 0) {
1204
+ throw new Error(
1205
+ `${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`
1206
+ );
1207
+ }
1208
+ }
1194
1209
  const vectorCols = vectorColumnsOf(currentSchema, table);
1195
1210
  const transforms = transformsOf(currentSchema, table);
1196
1211
  return cols.map((c) => {
@@ -1417,23 +1432,49 @@ function compileWhere(table, colSet, where, add, caller = "search") {
1417
1432
  if (colSet !== null && !colSet.has(col)) {
1418
1433
  throw new Error(`${caller}(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
1419
1434
  }
1435
+ if (cond === void 0) {
1436
+ throw new Error(
1437
+ `${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`
1438
+ );
1439
+ }
1420
1440
  const q = `t.${quoteIdent(col)}`;
1421
1441
  const bind = bindFor(col);
1422
1442
  if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
1443
+ if (Object.keys(cond).length === 0) {
1444
+ throw new Error(
1445
+ `${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`
1446
+ );
1447
+ }
1423
1448
  for (const [op, v] of Object.entries(cond)) {
1424
1449
  if (op === "in") {
1425
1450
  if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmal\u0131`);
1451
+ if (v.some((x) => x === void 0)) {
1452
+ throw new Error(
1453
+ `${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`
1454
+ );
1455
+ }
1426
1456
  if (v.length === 0) {
1427
1457
  parts.push("false");
1428
1458
  continue;
1429
1459
  }
1430
1460
  parts.push(`${q} IN (${v.map((x) => bind(x)).join(", ")})`);
1431
1461
  } else if (op in WHERE_OPS) {
1462
+ if (v === void 0) {
1463
+ throw new Error(
1464
+ `${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`
1465
+ );
1466
+ }
1467
+ if (v === null && (op === "neq" || op === "eq")) {
1468
+ parts.push(`${q} IS ${op === "neq" ? "NOT " : ""}NULL`);
1469
+ continue;
1470
+ }
1432
1471
  parts.push(`${q} ${WHERE_OPS[op]} ${bind(v)}`);
1433
1472
  } else {
1434
1473
  throw new Error(`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
1435
1474
  }
1436
1475
  }
1476
+ } else if (cond === null) {
1477
+ parts.push(`${q} IS NULL`);
1437
1478
  } else {
1438
1479
  parts.push(`${q} = ${bind(cond)}`);
1439
1480
  }
@@ -1526,7 +1567,7 @@ function createOps(tx) {
1526
1567
  if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
1527
1568
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
1528
1569
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
1529
- const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
1570
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "insert"));
1530
1571
  const inserted = rows[0];
1531
1572
  if (!inserted) {
1532
1573
  throw new Error(
@@ -1560,7 +1601,7 @@ function createOps(tx) {
1560
1601
  const ph = cols.map((_, i) => `$${i + 1}`).join(", ");
1561
1602
  const insertSql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
1562
1603
  const live = await at();
1563
- const bindData = asBindParams(table, cols, data);
1604
+ const bindData = asBindParams(table, cols, data, "upsert");
1564
1605
  const first = await live.unsafe(insertSql, bindData);
1565
1606
  if (first[0]) return asTableRow(table, first[0]);
1566
1607
  const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
@@ -1594,7 +1635,7 @@ function createOps(tx) {
1594
1635
  const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
1595
1636
  const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
1596
1637
  const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
1597
- const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
1638
+ const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "upsert"));
1598
1639
  const row = rows[0];
1599
1640
  if (!row) {
1600
1641
  throw new Error(
@@ -1608,7 +1649,7 @@ function createOps(tx) {
1608
1649
  if (cols.length === 0) return ops.findById(table, id);
1609
1650
  const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
1610
1651
  const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
1611
- const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
1652
+ const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data, "update"), id]);
1612
1653
  return rows[0] ? asTableRow(table, rows[0]) : null;
1613
1654
  },
1614
1655
  async delete(table, id) {
@@ -1639,13 +1680,20 @@ function createOps(tx) {
1639
1680
  `updateMany(${table}): nothing to set. An update with no columns is not a no-op worth pretending happened \u2014 pass the columns to write.`
1640
1681
  );
1641
1682
  }
1683
+ const known = schemaColumns(table);
1684
+ if (known !== null) {
1685
+ for (const c of cols) {
1686
+ if (!known.has(c)) {
1687
+ throw new Error(`updateMany(${table}): set kolonu "${c}" tabloda yok (FR-016)`);
1688
+ }
1689
+ }
1690
+ }
1642
1691
  const params = [];
1643
1692
  const add = (v) => {
1644
1693
  params.push(v);
1645
1694
  return `$${params.length}`;
1646
1695
  };
1647
- const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set)[0])}`).join(", ");
1648
- const known = schemaColumns(table);
1696
+ const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set, "updateMany")[0])}`).join(", ");
1649
1697
  const whereSql = compileWhere(table, known, where, add, "updateMany");
1650
1698
  assertHasPredicate(whereSql, "updateMany", table);
1651
1699
  const sql = `UPDATE ${quoteIdent(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *`;
@@ -2117,7 +2165,7 @@ function createOps(tx) {
2117
2165
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
2118
2166
  const rows = await sp.unsafe(
2119
2167
  `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`,
2120
- asBindParams(table, cols, data)
2168
+ asBindParams(table, cols, data, "supersede")
2121
2169
  );
2122
2170
  if (!rows[0]) {
2123
2171
  throw new Error(
@@ -2257,7 +2305,7 @@ function diagnosingDriver(sql, surface = "service") {
2257
2305
  );
2258
2306
  }
2259
2307
  if (isUniqueViolation(e)) {
2260
- return new UniqueViolation(constraintOf(e));
2308
+ return markEngineRaised(new UniqueViolation(constraintOf(e)));
2261
2309
  }
2262
2310
  if (surface === "service" && isForeignKeyViolation(e)) {
2263
2311
  return new Error(
@@ -2762,6 +2810,7 @@ async function defaultSqlDriver(config) {
2762
2810
  }
2763
2811
  return new g.Bun.SQL({ url: config.databaseUrl, max: config.poolMax });
2764
2812
  }
2813
+ var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
2765
2814
  async function createApp(opts) {
2766
2815
  const { config, controllers } = opts;
2767
2816
  setSchema(opts.schema ?? {});
@@ -3147,6 +3196,9 @@ async function createApp(opts) {
3147
3196
  } catch (err) {
3148
3197
  await db.rollback(err);
3149
3198
  if (isHttpError(err)) {
3199
+ if (isEngineRaised(err) || !AUTHOR_ANSWERED.has(err.status)) {
3200
+ log.error(`[engine] unhandled ${err.error} in ${hit.entry.id}`, err);
3201
+ }
3150
3202
  return envelope(
3151
3203
  err.error,
3152
3204
  err.errorDescription,