@palbase/backend 30.0.1 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/bin/palbase-backend.cjs +210 -24
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +7 -7
  4. package/dist/{chunk-TWX6JTGJ.js → chunk-26GH54D2.js} +3 -5
  5. package/dist/{chunk-TWX6JTGJ.js.map → chunk-26GH54D2.js.map} +1 -1
  6. package/dist/{chunk-VVMJEVQP.js → chunk-BRLJOXWS.js} +2 -2
  7. package/dist/{chunk-3TUJWHC2.js → chunk-C4ZA5AT2.js} +378 -19
  8. package/dist/chunk-C4ZA5AT2.js.map +1 -0
  9. package/dist/{chunk-RADVOY7Y.js → chunk-DCDHAKF3.js} +147 -33
  10. package/dist/chunk-DCDHAKF3.js.map +1 -0
  11. package/dist/{chunk-ENZ2RFFJ.js → chunk-GOPZPM2A.js} +3 -310
  12. package/dist/chunk-GOPZPM2A.js.map +1 -0
  13. package/dist/{chunk-CGNN2PUH.js → chunk-KATPXCJ5.js} +37 -4
  14. package/dist/{chunk-CGNN2PUH.js.map → chunk-KATPXCJ5.js.map} +1 -1
  15. package/dist/{chunk-YRVIWHJC.js → chunk-N54QZER3.js} +23 -323
  16. package/dist/chunk-N54QZER3.js.map +1 -0
  17. package/dist/chunk-QMFOL2K6.js +371 -0
  18. package/dist/chunk-QMFOL2K6.js.map +1 -0
  19. package/dist/chunk-XOX6RFPZ.js +383 -0
  20. package/dist/chunk-XOX6RFPZ.js.map +1 -0
  21. package/dist/db/index.cjs +41 -2
  22. package/dist/db/index.cjs.map +1 -1
  23. package/dist/db/index.d.cts +1 -1
  24. package/dist/db/index.d.ts +1 -1
  25. package/dist/db/index.js +12 -13
  26. package/dist/engine/index.cjs +210 -24
  27. package/dist/engine/index.cjs.map +1 -1
  28. package/dist/engine/index.d.cts +3 -3
  29. package/dist/engine/index.d.ts +3 -3
  30. package/dist/engine/index.js +7 -7
  31. package/dist/{index-DtCgaZAg.d.cts → index-BWgnGj68.d.cts} +185 -35
  32. package/dist/{index-b-Q3l7W5.d.ts → index-Bi74dcOu.d.ts} +9 -2
  33. package/dist/{index-8qy3kIuA.d.ts → index-Dg10RnZO.d.ts} +185 -35
  34. package/dist/{index-Zi7MptvP.d.cts → index-g6iQyYci.d.cts} +9 -2
  35. package/dist/index.cjs +135 -8
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -23
  38. package/dist/index.d.ts +6 -23
  39. package/dist/index.js +48 -48
  40. package/dist/index.js.map +1 -1
  41. package/dist/openapi/index.d.cts +2 -2
  42. package/dist/openapi/index.d.ts +2 -2
  43. package/dist/openapi/index.js +3 -4
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-CEYLH-Iz.d.cts → registry-6VT5RPeO.d.cts} +1 -1
  46. package/dist/{registry-BGJ-Al6F.d.ts → registry-B-sxJJN0.d.ts} +1 -1
  47. package/dist/test/index.cjs +733 -17
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +59 -4
  50. package/dist/test/index.d.ts +59 -4
  51. package/dist/test/index.js +56 -16
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +1 -1
  54. package/docs/auth.md +102 -20
  55. package/docs/database.md +54 -0
  56. package/docs/llms-full.txt +215 -21
  57. package/docs/schema.md +58 -0
  58. package/package.json +1 -1
  59. package/template/db/public.ts +23 -0
  60. package/template/package.json +1 -1
  61. package/dist/chunk-3TUJWHC2.js.map +0 -1
  62. package/dist/chunk-ENZ2RFFJ.js.map +0 -1
  63. package/dist/chunk-RADVOY7Y.js.map +0 -1
  64. package/dist/chunk-SG4UTNOP.js +0 -374
  65. package/dist/chunk-SG4UTNOP.js.map +0 -1
  66. package/dist/chunk-VXPNPVAG.js +0 -40
  67. package/dist/chunk-VXPNPVAG.js.map +0 -1
  68. package/dist/chunk-YRVIWHJC.js.map +0 -1
  69. /package/dist/{chunk-VVMJEVQP.js.map → chunk-BRLJOXWS.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/**\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.public.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/**\n * Eşzamanlılık yüzünden BAŞARISIZ OLAN, ama TEKRAR DENENİRSE başarılı olabilecek\n * hataların ortak yüzeyi (FR-036).\n *\n * `retryable: true` bir tahmin değil, Postgres'in söylediği şey: `40001` ve\n * `40P01` \"bu işlemi baştan çalıştır\" anlamına gelir. Kümenin DAR tutulması\n * kasıtlı — `23505` buraya girseydi, tekrar denemek aynı cevabı verir ve\n * döngü sonsuza kadar dönerdi.\n */\nexport interface RetryableError extends Error {\n readonly retryable: true;\n /** Beş karakterlik SQLSTATE — hangi eşzamanlılık hatası olduğunu SÖYLER. */\n readonly sqlstate: string;\n}\n\n/**\n * `e` tekrar denenebilir mi?\n *\n * `instanceof` DEĞİL şekil kontrolü — ve sebebi ÖLÇÜLMÜŞ (bkz. {@link\n * UniqueViolation.is}): bir controller bundle'ı kendi `@palbase/backend`\n * kopyasını inline'lıyor, hatayı fırlatan motor ise runtime'ın kopyası. İki\n * sınıf kimliği, ve `e instanceof SerializationFailure` yazan tek yerde\n * sessizce hiç eşleşmiyor.\n */\nexport function isRetryable(e: unknown): e is RetryableError {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as { retryable?: unknown }).retryable === true &&\n typeof (e as { sqlstate?: unknown }).sqlstate === \"string\"\n );\n}\n\n/**\n * `40001 serialization_failure` — SERIALIZABLE ya da REPEATABLE READ altında\n * eşzamanlı bir işlem bu işlemi geçersiz kıldı.\n *\n * D-016: bu hata BLOKLANMIYOR, FIRLATILIYOR. Retry'sız bir izolasyon\n * yükseltmesi sunmak defect'tir — kullanıcıya çalışmayan bir düğme vermektir:\n * SERIALIZABLE'a çıkıp `40001`'i yakalamayan kod, önceden çalışan işlemleri\n * rastgele kaybetmeye başlar.\n */\nexport class SerializationFailure extends Conflict implements RetryableError {\n public readonly retryable = true as const;\n public readonly sqlstate: string;\n\n static is(e: unknown): e is SerializationFailure {\n return (\n typeof e === \"object\" && e !== null &&\n (e as { name?: unknown }).name === \"SerializationFailure\"\n );\n }\n\n constructor(sqlstate = \"40001\", message?: string, data?: unknown) {\n super(\n message ??\n \"işlem eşzamanlı bir işlemle serileştirilemedi — aynı işlemi baştan çalıştırın \" +\n \"($transaction'a { retry: n } verebilirsiniz)\",\n \"serialization_failure\",\n data,\n );\n this.name = \"SerializationFailure\";\n this.sqlstate = sqlstate;\n }\n}\n\n/**\n * `40P01 deadlock_detected` — iki işlem birbirinin kilidini bekliyordu;\n * Postgres birini seçip iptal etti.\n *\n * Bu hatayı GÖRMEK bir tasarım sinyali: kilit sırası deterministik değilse\n * tekrar denemek yalnız maliyeti öteler. `Database.$lockRows(...)` (FR-035)\n * ya da çok satırlı yazmanın CTE'si (FR-015) sırayı sabitler.\n */\nexport class DeadlockDetected extends Conflict implements RetryableError {\n public readonly retryable = true as const;\n public readonly sqlstate: string;\n\n static is(e: unknown): e is DeadlockDetected {\n return (\n typeof e === \"object\" && e !== null &&\n (e as { name?: unknown }).name === \"DeadlockDetected\"\n );\n }\n\n constructor(sqlstate = \"40P01\", message?: string, data?: unknown) {\n super(\n message ??\n \"deadlock: iki işlem birbirinin kilidini bekledi ve bu işlem iptal edildi — \" +\n \"kilit sırasını sabitlemek için Database.$lockRows(tablo, id'ler) kullanın\",\n \"deadlock_detected\",\n data,\n );\n this.name = \"DeadlockDetected\";\n this.sqlstate = sqlstate;\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":["HTTP_ERROR_BRAND","Symbol","for","ENGINE_RAISED","markEngineRaised","e","isEngineRaised","isHttpError","err","status","error","errorDescription","HttpError","Error","data","name","undefined","toJSON","requestId","result","error_description","request_id","PalError","code","description","NamedHttpError","defaultCode","message","defaultMessage","spaced","replace","charAt","toUpperCase","slice","toLowerCase","BadRequest","Unauthorized","Forbidden","NotFound","Conflict","UniqueViolation","is","constraint","isRetryable","retryable","sqlstate","SerializationFailure","DeadlockDetected","TooManyRequests"]}
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/**\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.public.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/**\n * Eşzamanlılık yüzünden BAŞARISIZ OLAN, ama TEKRAR DENENİRSE başarılı olabilecek\n * hataların ortak yüzeyi (FR-036).\n *\n * `retryable: true` bir tahmin değil, Postgres'in söylediği şey: `40001` ve\n * `40P01` \"bu işlemi baştan çalıştır\" anlamına gelir. Kümenin DAR tutulması\n * kasıtlı — `23505` buraya girseydi, tekrar denemek aynı cevabı verir ve\n * döngü sonsuza kadar dönerdi.\n */\nexport interface RetryableError extends Error {\n readonly retryable: true;\n /** Beş karakterlik SQLSTATE — hangi eşzamanlılık hatası olduğunu SÖYLER. */\n readonly sqlstate: string;\n}\n\n/**\n * `e` tekrar denenebilir mi?\n *\n * `instanceof` DEĞİL şekil kontrolü — ve sebebi ÖLÇÜLMÜŞ (bkz. {@link\n * UniqueViolation.is}): bir controller bundle'ı kendi `@palbase/backend`\n * kopyasını inline'lıyor, hatayı fırlatan motor ise runtime'ın kopyası. İki\n * sınıf kimliği, ve `e instanceof SerializationFailure` yazan tek yerde\n * sessizce hiç eşleşmiyor.\n */\nexport function isRetryable(e: unknown): e is RetryableError {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as { retryable?: unknown }).retryable === true &&\n typeof (e as { sqlstate?: unknown }).sqlstate === \"string\"\n );\n}\n\n/**\n * `40001 serialization_failure` — SERIALIZABLE ya da REPEATABLE READ altında\n * eşzamanlı bir işlem bu işlemi geçersiz kıldı.\n *\n * D-016: bu hata BLOKLANMIYOR, FIRLATILIYOR. Retry'sız bir izolasyon\n * yükseltmesi sunmak defect'tir — kullanıcıya çalışmayan bir düğme vermektir:\n * SERIALIZABLE'a çıkıp `40001`'i yakalamayan kod, önceden çalışan işlemleri\n * rastgele kaybetmeye başlar.\n */\nexport class SerializationFailure extends Conflict implements RetryableError {\n public readonly retryable = true as const;\n public readonly sqlstate: string;\n\n static is(e: unknown): e is SerializationFailure {\n return (\n typeof e === \"object\" && e !== null &&\n (e as { name?: unknown }).name === \"SerializationFailure\"\n );\n }\n\n constructor(sqlstate = \"40001\", message?: string, data?: unknown) {\n super(\n message ??\n \"işlem eşzamanlı bir işlemle serileştirilemedi — aynı işlemi baştan çalıştırın \" +\n \"($transaction'a { retry: n } verebilirsiniz)\",\n \"serialization_failure\",\n data,\n );\n this.name = \"SerializationFailure\";\n this.sqlstate = sqlstate;\n }\n}\n\n/**\n * `40P01 deadlock_detected` — iki işlem birbirinin kilidini bekliyordu;\n * Postgres birini seçip iptal etti.\n *\n * Bu hatayı GÖRMEK bir tasarım sinyali: kilit sırası deterministik değilse\n * tekrar denemek yalnız maliyeti öteler. `Database.$lockRows(...)` (FR-035)\n * ya da çok satırlı yazmanın CTE'si (FR-015) sırayı sabitler.\n */\nexport class DeadlockDetected extends Conflict implements RetryableError {\n public readonly retryable = true as const;\n public readonly sqlstate: string;\n\n static is(e: unknown): e is DeadlockDetected {\n return (\n typeof e === \"object\" && e !== null &&\n (e as { name?: unknown }).name === \"DeadlockDetected\"\n );\n }\n\n constructor(sqlstate = \"40P01\", message?: string, data?: unknown) {\n super(\n message ??\n \"deadlock: iki işlem birbirinin kilidini bekledi ve bu işlem iptal edildi — \" +\n \"kilit sırasını sabitlemek için Database.$lockRows(tablo, id'ler) kullanın\",\n \"deadlock_detected\",\n data,\n );\n this.name = \"DeadlockDetected\";\n this.sqlstate = sqlstate;\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":["HTTP_ERROR_BRAND","Symbol","for","ENGINE_RAISED","markEngineRaised","e","isEngineRaised","isHttpError","err","status","error","errorDescription","HttpError","Error","data","name","undefined","toJSON","requestId","result","error_description","request_id","PalError","code","description","NamedHttpError","defaultCode","message","defaultMessage","spaced","replace","charAt","toUpperCase","slice","toLowerCase","BadRequest","Unauthorized","Forbidden","NotFound","Conflict","UniqueViolation","is","constraint","isRetryable","retryable","sqlstate","SerializationFailure","DeadlockDetected","TooManyRequests"]}
@@ -1,311 +1,12 @@
1
1
  import {
2
- qualifiedTableKey,
3
- withRetry
4
- } from "./chunk-SG4UTNOP.js";
2
+ CODECS
3
+ } from "./chunk-XOX6RFPZ.js";
5
4
  import {
6
- TxPlanBuilder,
7
- runTxPlan
8
- } from "./chunk-3TUJWHC2.js";
5
+ qualifiedTableKey
6
+ } from "./chunk-C4ZA5AT2.js";
9
7
  import {
10
8
  __name
11
- } from "./chunk-VXPNPVAG.js";
12
-
13
- // src/runtime.ts
14
- import { AsyncLocalStorage } from "async_hooks";
15
- var __requestALS = new AsyncLocalStorage();
16
- var runtime = null;
17
- function __setRuntime(services) {
18
- runtime = services;
19
- }
20
- __name(__setRuntime, "__setRuntime");
21
- function __runWithRuntime(services, fn) {
22
- return __requestALS.run({
23
- runtime: services
24
- }, fn);
25
- }
26
- __name(__runWithRuntime, "__runWithRuntime");
27
- function __getRuntime() {
28
- const scoped = __requestALS.getStore();
29
- if (scoped) return scoped.runtime;
30
- if (runtime === null) {
31
- throw new Error("Palbase services accessed outside a request scope. The Database/Documents/\u2026 singletons are only available inside an endpoint handler (or after the runtime has called __runWithRuntime / __setRuntime).");
32
- }
33
- return runtime;
34
- }
35
- __name(__getRuntime, "__getRuntime");
36
- var LIFECYCLE = /* @__PURE__ */ Symbol.for("palbase.backend.lifecycleHooks");
37
- function declaredLifecycle() {
38
- const g = globalThis;
39
- return g[LIFECYCLE] ??= {
40
- start: [],
41
- shutdown: []
42
- };
43
- }
44
- __name(declaredLifecycle, "declaredLifecycle");
45
- function onStart(name, hook) {
46
- declaredLifecycle().start.push({
47
- name,
48
- run: hook
49
- });
50
- }
51
- __name(onStart, "onStart");
52
- function onShutdown(name, hook) {
53
- declaredLifecycle().shutdown.push({
54
- name,
55
- run: hook
56
- });
57
- }
58
- __name(onShutdown, "onShutdown");
59
- function reason(err) {
60
- return err instanceof Error ? err.message : String(err);
61
- }
62
- __name(reason, "reason");
63
- async function drain(hooks) {
64
- for (const h of [
65
- ...hooks
66
- ].reverse()) {
67
- try {
68
- await h.run();
69
- } catch (err) {
70
- console.error(`[palbase] shutdown hook "${h.name}" failed: ${reason(err)}`, err);
71
- }
72
- }
73
- }
74
- __name(drain, "drain");
75
- async function __runStartHooks() {
76
- const slot3 = declaredLifecycle();
77
- const start = slot3.start.splice(0);
78
- const shutdown = slot3.shutdown.splice(0);
79
- for (const h of start) {
80
- try {
81
- await h.run();
82
- } catch (err) {
83
- await drain(shutdown);
84
- throw new Error(`[palbase] start hook "${h.name}" failed: ${reason(err)}`, {
85
- cause: err
86
- });
87
- }
88
- }
89
- let drained = false;
90
- return async () => {
91
- if (drained) return;
92
- drained = true;
93
- await drain(shutdown);
94
- };
95
- }
96
- __name(__runStartHooks, "__runStartHooks");
97
- function __resetLifecycleHooks() {
98
- const g = globalThis;
99
- delete g[LIFECYCLE];
100
- }
101
- __name(__resetLifecycleHooks, "__resetLifecycleHooks");
102
- function makeServiceProxy(key) {
103
- const handler = {
104
- get(_target, prop, receiver) {
105
- const client = __getRuntime()[key];
106
- const value = Reflect.get(client, prop, receiver);
107
- return typeof value === "function" ? value.bind(client) : value;
108
- }
109
- };
110
- return new Proxy({}, handler);
111
- }
112
- __name(makeServiceProxy, "makeServiceProxy");
113
- function makeTableProxy(ops, prefix) {
114
- return new Proxy({}, {
115
- get(_t, prop) {
116
- if (typeof prop !== "string") return void 0;
117
- const name = `${prefix}${prop}`;
118
- return {
119
- insert: /* @__PURE__ */ __name((data) => ops().insert(name, data), "insert"),
120
- // DÖRDÜNCÜ FİİL, aynı gerekçeyle: tip söz veriyor, ops katmanı
121
- // uyguluyor, ve bir handler'ın gerçekten dokunduğu yer BURASI.
122
- // `runtime-table-verbs` kapısı bunu adıyla saydı.
123
- aggregate: /* @__PURE__ */ __name((q) => ops().aggregate(name, q), "aggregate"),
124
- insertMany: /* @__PURE__ */ __name((rows, opts) => ops().insertMany(name, rows, opts), "insertMany"),
125
- update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
126
- delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
127
- findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
128
- findMany: /* @__PURE__ */ __name((q) => {
129
- const { where, ...opts } = q ?? {};
130
- return ops().findMany(name, where, opts);
131
- }, "findMany"),
132
- put: /* @__PURE__ */ __name((q) => ops().put(name, q.data, {
133
- onConflict: q.onConflict
134
- }), "put"),
135
- // THREE VERBS THE TYPE PROMISED AND THIS PROXY DID NOT EMIT.
136
- //
137
- // `EnvTypedTableBase` declares `updateMany`, `deleteMany` and `count`
138
- // (typed-db.ts) and the ops layer implements all three — only this
139
- // proxy, which is what a handler actually touches, left them out. So
140
- // the type said the verb exists, autocomplete offered it, and the call
141
- // answered `undefined is not a function`.
142
- //
143
- // Older than this run, but the run rewrote this proxy for
144
- // `Database.schema(name).tables.*` and would have carried the gap onto
145
- // the new surface too.
146
- updateMany: /* @__PURE__ */ __name((q) => ops().updateMany(name, q.where, q.set), "updateMany"),
147
- deleteMany: /* @__PURE__ */ __name((q) => ops().deleteMany(name, q.where), "deleteMany"),
148
- count: /* @__PURE__ */ __name((q) => ops().count(name, q?.where), "count"),
149
- search: /* @__PURE__ */ __name((params) => ops().search(name, params), "search"),
150
- similar: /* @__PURE__ */ __name((id, params) => ops().similar(name, id, params), "similar"),
151
- recommend: /* @__PURE__ */ __name((params) => ops().recommend(name, params), "recommend"),
152
- facets: /* @__PURE__ */ __name((params) => ops().facets(name, params), "facets"),
153
- supersede: /* @__PURE__ */ __name((id, row) => ops().supersede(name, id, row), "supersede"),
154
- claim: /* @__PURE__ */ __name((unique, extra) => ops().claim(name, unique, extra), "claim")
155
- };
156
- }
157
- });
158
- }
159
- __name(makeTableProxy, "makeTableProxy");
160
- var rawDatabase = makeServiceProxy("Database");
161
- function makeTypedSurface(raw) {
162
- const reco = raw;
163
- const ops = {
164
- $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
165
- $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
166
- $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
167
- $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
168
- $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
169
- $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
170
- $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
171
- $updateMany: /* @__PURE__ */ __name((table, where, set) => raw.updateMany(table, where, set), "$updateMany"),
172
- $deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
173
- $count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
174
- $search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
175
- $similar: /* @__PURE__ */ __name((table, id, params) => reco.similar(table, id, params), "$similar"),
176
- $recommend: /* @__PURE__ */ __name((table, params) => reco.recommend(table, params), "$recommend"),
177
- $facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
178
- $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
179
- $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
180
- $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
181
- $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
182
- $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
183
- $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
184
- };
185
- const base = Object.assign(ops, {
186
- // Both surfaces get it: a savepoint on the service transaction is as useful
187
- // as one on the request's, and each is bound to its own connection.
188
- $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
189
- $transaction(fn, opts) {
190
- return withRetry(() => {
191
- const builder = new TxPlanBuilder();
192
- return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
193
- }, opts ?? {});
194
- }
195
- });
196
- return new Proxy(base, {
197
- get(target, prop, receiver) {
198
- if (prop === "tables") return makeTableProxy(() => reco, "");
199
- if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
200
- return makeTableProxy(() => reco, qualifiedTableKey(prop, ""));
201
- }
202
- return Reflect.get(target, prop, receiver);
203
- }
204
- });
205
- }
206
- __name(makeTypedSurface, "makeTypedSurface");
207
- function makeTxTablesAccessor(builder, prefix = "") {
208
- const tablesProxy = new Proxy({}, {
209
- get(_t, prop) {
210
- if (typeof prop !== "string") return void 0;
211
- return builder.table(prefix + prop);
212
- }
213
- });
214
- return tablesProxy;
215
- }
216
- __name(makeTxTablesAccessor, "makeTxTablesAccessor");
217
- function makeTxPlanHandle(builder) {
218
- const publicTables = makeTxTablesAccessor(builder);
219
- return new Proxy({}, {
220
- get(_t, prop) {
221
- if (typeof prop !== "string") return void 0;
222
- if (prop === "tables") return publicTables;
223
- return makeTxTablesAccessor(builder, qualifiedTableKey(prop, ""));
224
- }
225
- });
226
- }
227
- __name(makeTxPlanHandle, "makeTxPlanHandle");
228
- var Database = Object.assign(makeTypedSurface(rawDatabase), {
229
- /**
230
- * Lazily resolve the runtime's service-role sibling on each call. We do NOT
231
- * cache it: `rawDatabase.asService()` reads the CURRENT request scope through
232
- * the runtime proxy, and the per-request runtime injects a service client
233
- * bound to that request's identity headers — caching would leak one request's
234
- * sibling into another concurrent request.
235
- */
236
- $asService() {
237
- return makeTypedSurface(rawDatabase.asService());
238
- }
239
- });
240
- var Documents = makeServiceProxy("Documents");
241
- function makeBucketsAccessor(storage) {
242
- return new Proxy({}, {
243
- get(_t, prop) {
244
- if (typeof prop !== "string") return void 0;
245
- return storage().bucket(prop);
246
- }
247
- });
248
- }
249
- __name(makeBucketsAccessor, "makeBucketsAccessor");
250
- var rawStorage = makeServiceProxy("Storage");
251
- var Storage = Object.assign({
252
- // FORWARDED explicitly, not assigned onto the service proxy.
253
- //
254
- // `Object.assign(rawStorage, {buckets})` writes onto the proxy's TARGET, and
255
- // the proxy's only trap is `get`, which forwards every read to the module
256
- // client — so the property landed somewhere nothing reads and
257
- // `Storage.buckets.docs` was `undefined` in a deployed handler. It
258
- // typechecked, every test passed, and the live call answered
259
- // "TypeError: undefined is not an object". `Database` never had the bug
260
- // because it builds a plain surface the same way this now does.
261
- bucket: /* @__PURE__ */ __name((name) => rawStorage.bucket(name), "bucket")
262
- }, {
263
- buckets: makeBucketsAccessor(() => rawStorage)
264
- });
265
- var Cache = makeServiceProxy("Cache");
266
- var Secrets = makeServiceProxy("Secrets");
267
- var Auth = makeServiceProxy("Auth");
268
- var Log = makeServiceProxy("Log");
269
- var Notifications = makeServiceProxy("Notifications");
270
- var rawFlags = makeServiceProxy("Flags");
271
- var Flags = Object.assign({
272
- isEnabled(flagName, context) {
273
- return rawFlags.isEnabled(flagName, context);
274
- },
275
- getVariant(flagName, context) {
276
- return rawFlags.getVariant(flagName, context);
277
- },
278
- getAll(context) {
279
- return rawFlags.getAll(context);
280
- },
281
- /**
282
- * Resolve a flag's value, with an optional fallback.
283
- *
284
- * FORWARDED as of 2026-08-15. This surface is written out by hand, method
285
- * by method, and `get` was missing from it — so the client implemented it,
286
- * thirty assertions covered it, and `Flags.get("x")` was `undefined` in a
287
- * deployed handler. Exactly the shape of the `Storage.buckets` defect found
288
- * the same day: a hand-maintained forwarding list is a list somebody has to
289
- * remember to update.
290
- */
291
- get(flagName, defaultOrContext, maybeContext) {
292
- return rawFlags.get(flagName, defaultOrContext, maybeContext);
293
- },
294
- setOverride(key, value) {
295
- return rawFlags.setOverride(key, value);
296
- }
297
- }, {
298
- /**
299
- * Lazily resolve the runtime's cross-user sibling on each call. We do NOT
300
- * cache it: `rawFlags.asService()` reads the CURRENT request scope through
301
- * the runtime proxy, so caching would leak one request's sibling into
302
- * another concurrent request. Mirrors `Database.asService()`.
303
- */
304
- asService() {
305
- return rawFlags.asService();
306
- }
307
- });
308
- var Realtime = makeServiceProxy("Realtime");
9
+ } from "./chunk-KATPXCJ5.js";
309
10
 
310
11
  // src/refusals.ts
311
12
  var DECLARATION_REFUSAL = /* @__PURE__ */ Symbol.for("palbase.backend.declarationRefusal");
@@ -327,6 +28,9 @@ __name(isDeclarationRefused, "isDeclarationRefused");
327
28
 
328
29
  // src/db/env-gen.ts
329
30
  function baseTsType(def) {
31
+ if (def.codec !== void 0 && def.codec in CODECS) {
32
+ return CODECS[def.codec].tsType;
33
+ }
330
34
  switch (def.type) {
331
35
  case "uuid":
332
36
  case "text":
@@ -504,7 +208,21 @@ function tableBlock(table, relations, indent) {
504
208
  ].join("\n");
505
209
  }
506
210
  __name(tableBlock, "tableBlock");
211
+ function refuseFreeFormTransforms(schemas) {
212
+ for (const schema of schemas) {
213
+ for (const table of Object.values(schema.tables)) {
214
+ for (const [col, builder] of Object.entries(table.columns)) {
215
+ const def = builder._def;
216
+ if (def.transform === void 0 || def.codec !== void 0) continue;
217
+ const where = qualifiedTableKey(schema.name, table.name);
218
+ throw new DeclarationRefused(`table "${where}" column "${col}" declares .transform<T>(): its target type does not exist at runtime, so the generated types would say something the engine contradicts. Declare a named codec instead \u2014 .asNumber() for a JS number, .asDecimal() for the exact string.`);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ __name(refuseFreeFormTransforms, "refuseFreeFormTransforms");
507
224
  function makeEnvDts(schemas) {
225
+ refuseFreeFormTransforms(schemas);
508
226
  const relations = buildRelations(schemas);
509
227
  const publicSchema = schemas.find((s) => s.name === "public");
510
228
  const others = schemas.filter((s) => s.name !== "public");
@@ -961,24 +679,6 @@ function assertNoOrphanEntryPoints(registered, owned2) {
961
679
  __name(assertNoOrphanEntryPoints, "assertNoOrphanEntryPoints");
962
680
 
963
681
  export {
964
- __requestALS,
965
- __setRuntime,
966
- __runWithRuntime,
967
- __getRuntime,
968
- onStart,
969
- onShutdown,
970
- __runStartHooks,
971
- __resetLifecycleHooks,
972
- Database,
973
- Documents,
974
- Storage,
975
- Cache,
976
- Secrets,
977
- Auth,
978
- Log,
979
- Notifications,
980
- Flags,
981
- Realtime,
982
682
  DECLARATION_REFUSAL,
983
683
  DeclarationRefused,
984
684
  isDeclarationRefused,
@@ -997,4 +697,4 @@ export {
997
697
  buildContainer,
998
698
  assertNoOrphanEntryPoints
999
699
  };
1000
- //# sourceMappingURL=chunk-YRVIWHJC.js.map
700
+ //# sourceMappingURL=chunk-N54QZER3.js.map