@palbase/backend 30.0.1 → 32.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 (67) hide show
  1. package/dist/bin/palbase-backend.cjs +136 -3
  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-YRVIWHJC.js → chunk-5C5UCILO.js} +23 -323
  7. package/dist/chunk-5C5UCILO.js.map +1 -0
  8. package/dist/{chunk-RADVOY7Y.js → chunk-AZJIKCOR.js} +113 -15
  9. package/dist/chunk-AZJIKCOR.js.map +1 -0
  10. package/dist/{chunk-VVMJEVQP.js → chunk-BRLJOXWS.js} +2 -2
  11. package/dist/{chunk-3TUJWHC2.js → chunk-IXAX5CON.js} +378 -19
  12. package/dist/chunk-IXAX5CON.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-KGP6ALIU.js +361 -0
  16. package/dist/chunk-KGP6ALIU.js.map +1 -0
  17. package/dist/chunk-XEGZ3S2Q.js +346 -0
  18. package/dist/chunk-XEGZ3S2Q.js.map +1 -0
  19. package/dist/{chunk-ENZ2RFFJ.js → chunk-Z4CZRMNF.js} +3 -310
  20. package/dist/chunk-Z4CZRMNF.js.map +1 -0
  21. package/dist/db/index.cjs +29 -0
  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 +136 -3
  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-b-Q3l7W5.d.ts → index-BILC9WwS.d.ts} +9 -2
  32. package/dist/{index-8qy3kIuA.d.ts → index-BgWnP07a.d.ts} +119 -17
  33. package/dist/{index-Zi7MptvP.d.cts → index-CWGiJ2Up.d.cts} +9 -2
  34. package/dist/{index-DtCgaZAg.d.cts → index-DgYkdNT9.d.cts} +119 -17
  35. package/dist/index.cjs +83 -3
  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-BcRYIQ-R.d.cts} +1 -1
  46. package/dist/{registry-BGJ-Al6F.d.ts → registry-ClzjxIWy.d.ts} +1 -1
  47. package/dist/test/index.cjs +703 -14
  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 +51 -13
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +1 -1
  54. package/docs/database.md +54 -0
  55. package/docs/llms-full.txt +87 -1
  56. package/docs/schema.md +32 -0
  57. package/package.json +1 -1
  58. package/template/package.json +1 -1
  59. package/dist/chunk-3TUJWHC2.js.map +0 -1
  60. package/dist/chunk-ENZ2RFFJ.js.map +0 -1
  61. package/dist/chunk-RADVOY7Y.js.map +0 -1
  62. package/dist/chunk-SG4UTNOP.js +0 -374
  63. package/dist/chunk-SG4UTNOP.js.map +0 -1
  64. package/dist/chunk-VXPNPVAG.js +0 -40
  65. package/dist/chunk-VXPNPVAG.js.map +0 -1
  66. package/dist/chunk-YRVIWHJC.js.map +0 -1
  67. /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"]}
@@ -0,0 +1,361 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-KATPXCJ5.js";
4
+
5
+ // src/db/columns.ts
6
+ var CODECS = {
7
+ // Exact-precision column → JS number. Safe below 2^53; a value above it is
8
+ // not representable and the author wants `asDecimal()` instead.
9
+ number: {
10
+ fromDb: /* @__PURE__ */ __name((v) => Number(v), "fromDb"),
11
+ toDb: /* @__PURE__ */ __name((v) => String(v), "toDb"),
12
+ tsType: "number"
13
+ },
14
+ // Exact-precision column → the string Postgres sent. It exists so the
15
+ // DECLARATION is explicit: "this column is a decimal I handle as text",
16
+ // rather than the ABSENCE of a declaration meaning the same thing by default.
17
+ decimal: {
18
+ fromDb: /* @__PURE__ */ __name((v) => String(v), "fromDb"),
19
+ toDb: /* @__PURE__ */ __name((v) => String(v), "toDb"),
20
+ tsType: "string"
21
+ }
22
+ };
23
+ function refuseOnVector(def, modifier) {
24
+ if (def.type === "vector") {
25
+ throw new Error(`vector column: .${modifier}() is not supported (FR-002 \u2014 allowed: nullable()/notNull())`);
26
+ }
27
+ }
28
+ __name(refuseOnVector, "refuseOnVector");
29
+ var ColumnBuilder = class _ColumnBuilder {
30
+ static {
31
+ __name(this, "ColumnBuilder");
32
+ }
33
+ _def;
34
+ constructor(type, existingDef) {
35
+ this._def = existingDef ?? {
36
+ type,
37
+ nullable: false,
38
+ primaryKey: false
39
+ };
40
+ }
41
+ /** Mark this column as the primary key. */
42
+ primaryKey() {
43
+ refuseOnVector(this._def, "primaryKey");
44
+ this._def.primaryKey = true;
45
+ return new _ColumnBuilder(this._def.type, this._def);
46
+ }
47
+ /** Mark this column as NOT NULL (default). */
48
+ notNull() {
49
+ this._def.nullable = false;
50
+ return new _ColumnBuilder(this._def.type, this._def);
51
+ }
52
+ /** Allow NULL values. */
53
+ nullable() {
54
+ this._def.nullable = true;
55
+ return new _ColumnBuilder(this._def.type, this._def);
56
+ }
57
+ /** Set a default value. */
58
+ default(value) {
59
+ refuseOnVector(this._def, "default");
60
+ this._def.defaultValue = value;
61
+ return new _ColumnBuilder(this._def.type, this._def);
62
+ }
63
+ /** UUID: generate a random default (gen_random_uuid()). */
64
+ defaultRandom() {
65
+ refuseOnVector(this._def, "defaultRandom");
66
+ this._def.defaultRandom = true;
67
+ return new _ColumnBuilder(this._def.type, this._def);
68
+ }
69
+ /** Timestamp: default to now(). */
70
+ defaultNow() {
71
+ refuseOnVector(this._def, "defaultNow");
72
+ this._def.defaultNow = true;
73
+ return new _ColumnBuilder(this._def.type, this._def);
74
+ }
75
+ /**
76
+ * The DATABASE assigns this column's value — a trigger, a rule, an identity.
77
+ *
78
+ * The column becomes optional on INSERT (the author has nothing to send) while
79
+ * the DDL stays free of a DEFAULT this schema would not honour. It is NOT
80
+ * `default()`: that declares a value the schema promises to write.
81
+ *
82
+ * Naming: deliberately not `generated()`. Postgres has GENERATED columns and
83
+ * they are a different thing; borrowing the word would send a reader — or a
84
+ * model writing a schema — to the wrong feature.
85
+ */
86
+ dbAssigned() {
87
+ this._def.dbAssigned = true;
88
+ return new _ColumnBuilder(this._def.type, this._def);
89
+ }
90
+ /** Add a foreign key reference. */
91
+ /**
92
+ * Declares that this column used to be called `previous`.
93
+ *
94
+ * A schema diff sees one name gone and another present; it cannot know whether
95
+ * you renamed a column or dropped one and added another, and the two are very
96
+ * different — the second loses every value. Saying so here turns the plan into
97
+ * `ALTER TABLE … RENAME COLUMN` instead.
98
+ *
99
+ * Once the rename has been applied the annotation is inert (the old name is no
100
+ * longer there to rename), so it can be deleted at your leisure.
101
+ */
102
+ renamedFrom(previous) {
103
+ this._def.renamedFrom = previous;
104
+ return this;
105
+ }
106
+ /**
107
+ * See {@link ColumnDef.ignored}.
108
+ *
109
+ * COPIES the def rather than mutating it. The constructor takes an existing
110
+ * def BY REFERENCE, so every builder derived from another shares one object —
111
+ * `const a = slug.unique()` leaves `a._def === slug._def`. An in-place
112
+ * `ignored = true` therefore marks every column sharing that def, including
113
+ * one another table actively reads, and the gate would let THAT column be
114
+ * dropped. Measured before this copy existed.
115
+ *
116
+ * The aliasing is older than this method and other fields leak through it too.
117
+ * The reason this one cannot wait: every other leak produces a VISIBLE schema
118
+ * difference — the plan shows it, the DDL shows it. This one is invisible by
119
+ * design (no DDL, no diff, no plan line), so its only effect is to disarm a
120
+ * safety gate in silence.
121
+ */
122
+ ignored() {
123
+ return new _ColumnBuilder(this._def.type, {
124
+ ...this._def,
125
+ ignored: true
126
+ });
127
+ }
128
+ /**
129
+ * Foreign key onto another table's column.
130
+ *
131
+ * The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
132
+ * the second table does not exist yet when the first is built; a direct
133
+ * reference makes TypeScript chase its own tail (TS7022 — measured, and making
134
+ * the return type independent of the target does NOT help). The thunk is
135
+ * invoked in `defineSchema`, where every binding exists and every table
136
+ * already knows its name.
137
+ *
138
+ * In a cycle, ONE side needs an explicit return type:
139
+ * `references((): AnyColumn => y.id)`. One side is enough — measured.
140
+ * For a self-reference use `selfReferences(column)`: no thunk, no annotation.
141
+ *
142
+ * `as` names the FORWARD relation (`author_id` → `author` by default);
143
+ * `reverseAs` names the REVERSE one on the parent, whose default is this
144
+ * table's own name (`users.posts`). Two foreign keys from one table onto one
145
+ * parent therefore need a `reverseAs` on at least one of them — the reverse
146
+ * names would otherwise both be this table's name.
147
+ */
148
+ references(target, opts) {
149
+ refuseOnVector(this._def, "references");
150
+ if (typeof target !== "function") {
151
+ throw new Error(`references(...) takes a callback: write references(() => otherTable.column). The two-string form references("table", "column") is gone \u2014 a string cannot be type-checked and cannot point at a table that does not exist yet.`);
152
+ }
153
+ this._def.referencesThunk = target;
154
+ if (opts?.as !== void 0) this._def.refAs = opts.as;
155
+ if (opts?.reverseAs !== void 0) this._def.reverseAs = opts.reverseAs;
156
+ if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
157
+ if (opts?.index === false) this._def.index = false;
158
+ return new _ColumnBuilder(this._def.type, this._def);
159
+ }
160
+ /**
161
+ * Bu kolon `increment()` / `decrement()` ile güncelleniyor (FR-049).
162
+ *
163
+ * Deploy kontrolcü kodunu OKUMAZ, o yüzden bildirimin söylemesi gerekiyor —
164
+ * ve söylediği anda plan bir şeyi görebiliyor: aynı kolon hem sayaç hem
165
+ * indeksliyse her güncelleme HOT'u kaybeder.
166
+ *
167
+ * ZİNCİR (D-028+D-030): HOT kaybı → ölü tuple → autovacuum yükü → ve
168
+ * autovacuum worker'ları KÜME GENELİNDE bir kaynak, yani bedeli başka
169
+ * kiracıların tabloları da öder.
170
+ *
171
+ * Sektörde çare "dokümana uyarı yaz"dır, çünkü index'i ekleyen kişi
172
+ * `increment()`'i yazan kişi değildir. Bu bildirimde ikisi de YAN YANA
173
+ * duruyor.
174
+ *
175
+ * Uyarı, HATA DEĞİL: sayaç kolonunu indekslemek bazen doğru karardır.
176
+ */
177
+ counter() {
178
+ this._def.counter = true;
179
+ return new _ColumnBuilder(this._def.type, this._def);
180
+ }
181
+ /**
182
+ * Foreign key onto THIS table (`parent_id → id`) — category trees, comment
183
+ * replies, org charts.
184
+ *
185
+ * No thunk and no type annotation: the target table is the one being declared,
186
+ * so there is nothing to defer and nothing for TypeScript to chase in a circle.
187
+ * Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
188
+ * always goes through a callback; measured, we do not need one.
189
+ */
190
+ selfReferences(column, opts) {
191
+ refuseOnVector(this._def, "selfReferences");
192
+ this._def.selfRefColumn = column;
193
+ if (opts?.as !== void 0) this._def.refAs = opts.as;
194
+ if (opts?.onDelete !== void 0) this._def.onDeleteAction = opts.onDelete;
195
+ return new _ColumnBuilder(this._def.type, this._def);
196
+ }
197
+ /** Set the ON DELETE action for a foreign key reference. */
198
+ onDelete(action) {
199
+ this._def.onDeleteAction = action;
200
+ return new _ColumnBuilder(this._def.type, this._def);
201
+ }
202
+ /** Add a single-column UNIQUE constraint. */
203
+ unique() {
204
+ refuseOnVector(this._def, "unique");
205
+ this._def.unique = true;
206
+ return new _ColumnBuilder(this._def.type, this._def);
207
+ }
208
+ /**
209
+ * Declare how this column's value is projected in and out of the process.
210
+ *
211
+ * The DDL does not move: `numeric` stays `numeric`, and the driver still hands
212
+ * back what Postgres sent. What changes is the type the row surface exposes —
213
+ * it becomes `Target`:
214
+ *
215
+ * amount: numeric().transform<number>({ fromDb: Number, toDb: String })
216
+ *
217
+ * `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
218
+ * arbitrary precision), and that is exactly why this exists: application code
219
+ * that does arithmetic on the column otherwise rewrites the same
220
+ * `Number(row.amount)` / `String(x)` pair in every controller that touches it,
221
+ * and each rewrite is a place the two directions can drift apart.
222
+ *
223
+ * A transform is a PROJECTION, never a constraint: it lives only in this
224
+ * process, so it can neither validate nor migrate what is stored.
225
+ */
226
+ /**
227
+ * Surface this exact-precision column as a JS `number` (FR-001).
228
+ *
229
+ * `numeric`/`bigint` arrive as strings because a JS number cannot hold their
230
+ * full range — correct, and exactly why this exists: application code that
231
+ * does arithmetic on the column otherwise rewrites the same `Number(row.x)` /
232
+ * `String(v)` pair in every caller, and each rewrite is a place the two
233
+ * directions can drift apart.
234
+ */
235
+ asNumber() {
236
+ return this.withCodec("number", "asNumber");
237
+ }
238
+ /**
239
+ * Surface this exact-precision column as a `string` — the value Postgres
240
+ * sent, DECLARED rather than defaulted (FR-001).
241
+ */
242
+ asDecimal() {
243
+ return this.withCodec("decimal", "asDecimal");
244
+ }
245
+ withCodec(codec, method) {
246
+ if (this._def.type !== "bigint" && this._def.type !== "numeric") {
247
+ throw new Error(`${this._def.type} column: .${method}() is only available on bigint()/numeric() \u2014 those are the exact-precision types that surface as string.`);
248
+ }
249
+ if (this._def.transform !== void 0) {
250
+ throw new Error(`.${method}() and .transform() are two ways to say the same thing on one column \u2014 keep .${method}().`);
251
+ }
252
+ this._def.codec = codec;
253
+ return new _ColumnBuilder(this._def.type, this._def);
254
+ }
255
+ transform(fns) {
256
+ this._def.transform = fns;
257
+ return new _ColumnBuilder(this._def.type, this._def);
258
+ }
259
+ };
260
+ function uuid() {
261
+ return new ColumnBuilder("uuid");
262
+ }
263
+ __name(uuid, "uuid");
264
+ function text() {
265
+ return new ColumnBuilder("text");
266
+ }
267
+ __name(text, "text");
268
+ function integer() {
269
+ return new ColumnBuilder("integer");
270
+ }
271
+ __name(integer, "integer");
272
+ function bigint() {
273
+ return new ColumnBuilder("bigint");
274
+ }
275
+ __name(bigint, "bigint");
276
+ function numeric() {
277
+ return new ColumnBuilder("numeric");
278
+ }
279
+ __name(numeric, "numeric");
280
+ function boolean() {
281
+ return new ColumnBuilder("boolean");
282
+ }
283
+ __name(boolean, "boolean");
284
+ function timestamp() {
285
+ return new ColumnBuilder("timestamp");
286
+ }
287
+ __name(timestamp, "timestamp");
288
+ function jsonb() {
289
+ return new ColumnBuilder("jsonb");
290
+ }
291
+ __name(jsonb, "jsonb");
292
+ function enumType(name, values) {
293
+ const builder = new ColumnBuilder("enum");
294
+ builder._def.enumName = name;
295
+ builder._def.enumValues = [
296
+ ...values
297
+ ];
298
+ return builder;
299
+ }
300
+ __name(enumType, "enumType");
301
+ function vector(dimensions) {
302
+ if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 2e3) {
303
+ throw new Error(`vector(): dimensions must be an integer in [1, 2000], got ${String(dimensions)}`);
304
+ }
305
+ const b = new ColumnBuilder("vector");
306
+ b._def.dimensions = dimensions;
307
+ return b;
308
+ }
309
+ __name(vector, "vector");
310
+ function ownedByUser() {
311
+ const b = new ColumnBuilder("text");
312
+ b._def.nullable = false;
313
+ b._def.references = {
314
+ table: "auth.users",
315
+ column: "id"
316
+ };
317
+ b._def.onDeleteAction = "cascade";
318
+ b._def.owns = true;
319
+ return b;
320
+ }
321
+ __name(ownedByUser, "ownedByUser");
322
+ function userRef(opts) {
323
+ const b = new ColumnBuilder("text");
324
+ b._def.references = {
325
+ table: "auth.users",
326
+ column: "id"
327
+ };
328
+ b._def.onDeleteAction = opts.onDelete;
329
+ if (opts.as !== void 0) b._def.refAs = opts.as;
330
+ return b;
331
+ }
332
+ __name(userRef, "userRef");
333
+ function installationRef(opts) {
334
+ const b = new ColumnBuilder("text");
335
+ b._def.references = {
336
+ table: "auth.installations",
337
+ column: "id"
338
+ };
339
+ b._def.onDeleteAction = opts.onDelete;
340
+ if (opts.as !== void 0) b._def.refAs = opts.as;
341
+ return b;
342
+ }
343
+ __name(installationRef, "installationRef");
344
+
345
+ export {
346
+ CODECS,
347
+ uuid,
348
+ text,
349
+ integer,
350
+ bigint,
351
+ numeric,
352
+ boolean,
353
+ timestamp,
354
+ jsonb,
355
+ enumType,
356
+ vector,
357
+ ownedByUser,
358
+ userRef,
359
+ installationRef
360
+ };
361
+ //# sourceMappingURL=chunk-KGP6ALIU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/columns.ts"],"sourcesContent":["/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/**\n * The ON DELETE actions permitted on a foreign key to the built-in auth users\n * (`auth.users`). Both let a user's rows be removed (`cascade`) or detached\n * (`set null`) when the account is erased; `restrict` / `no action` would BLOCK\n * erasure and are therefore excluded. This is the CLIENT-SIDE mirror of the\n * server's auth-FK deletion policy — the server (validateAuthUserFK) is the real\n * boundary, this narrows the type so the common mistake is caught at compile time.\n */\nexport type AuthUserOnDelete = Extract<OnDeleteAction, 'cascade' | 'set null'>;\n\n/** Column type identifiers. */\nexport type ColumnType =\n | 'uuid'\n | 'text'\n | 'integer'\n | 'bigint'\n | 'numeric'\n | 'boolean'\n | 'timestamp'\n | 'jsonb'\n | 'enum'\n | 'vector';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n /** Pending FK target, resolved by `defineSchema` once every binding exists. */\n referencesThunk?: () => ColumnBuilder;\n /** FK onto THIS table — no thunk needed, the target is the declaring table. */\n selfRefColumn?: string;\n /** This column OWNS the row: erasure, RLS and the `owner` relation read it. */\n owns?: true;\n /** Explicit name for the FORWARD relation (child → parent), when the one\n * derived from the column would be ambiguous or unclear. */\n refAs?: string;\n /**\n * Explicit name for the REVERSE relation (parent → children).\n *\n * Separate from {@link refAs} because the two directions are different facts:\n * the forward name describes the parent this row points at (`author`), the\n * reverse one describes the rows hanging off the parent (`posts`). One option\n * naming both made an ordinary schema undeclarable — `posts.author_id` and\n * `comments.author_id` both named `{ as: \"author\" }` collided on `users`, and\n * the refusal asked for the `{ as }` they had both already written.\n */\n reverseAs?: string;\n /** The table this column belongs to; set by `defineTable`. */\n ownerTable?: { name: string; columns: Record<string, ColumnBuilder> };\n /**\n * The name this column used to have. A diff cannot tell a rename from a drop and\n * an add — both leave one name gone and another present — so the intent has to be\n * declared. Without it, renaming a column loses its data.\n */\n renamedFrom?: string;\n /**\n * This release's PROMISE that it does not reference this column — neither\n * reads it nor writes it, and never names it in a filter, a sort or a SET.\n *\n * The contraction gate reads it and nothing else does: dropping a column is\n * refused while the RUNNING release's declaration still lacks this mark, so\n * removing a column is two deploys — mark it, ship, then drop it.\n *\n * The word is `ignored` and not `deprecated` deliberately: RFC 9745 defines\n * deprecation as changing NO behaviour, and this changes what a deploy will\n * accept. Rails calls the same thing `ignored_columns`.\n */\n ignored?: boolean;\n onDeleteAction?: OnDeleteAction;\n /** FR-044: yalnız AÇIKÇA `false` bildirilince var. Türev FK index'ini kapatır. */\n index?: boolean;\n /** FR-049: kolon `increment()` ile güncelleniyor. Plan HOT çakışmasını uyarır. */\n counter?: boolean;\n enumName?: string;\n enumValues?: string[];\n unique?: boolean;\n /**\n * The value is written by the DATABASE — a trigger, a rule, an identity — not by\n * the author and not by a DEFAULT this schema declares. It makes the column\n * optional on INSERT without putting a DEFAULT in the DDL.\n *\n * Before this existed the only way to keep a trigger-filled column off the\n * INSERT type was to give it a fake `default()`: a value the schema claimed to\n * write and the trigger immediately overwrote. That made the schema lie about\n * its own data.\n */\n dbAssigned?: boolean;\n /** vector(n): the declared dimension count — part of the TYPE (typmod), read\n * by the wire serializer and the deploy's auto-index (FR-001). */\n dimensions?: number;\n /**\n * How the stored value is projected in and out of this process (FR-009).\n *\n * NOT part of the DDL: the column's Postgres type is unchanged and this pair\n * is never serialized into a migration. It exists so the row surface can hand\n * back the type the application actually works with.\n */\n /**\n * The named codec this column declares (FR-001). Unlike `transform`, the NAME\n * survives into the runtime object, so `makeEnvDts` can emit the matching\n * TypeScript type instead of falling back to the storage type. Not part of\n * the DDL: the column's Postgres type is unchanged.\n */\n codec?: CodecName;\n transform?: ColumnTransform;\n}\n\n/**\n * The read/write pair a column may declare (FR-009).\n *\n * `fromDb` takes whatever the driver produced for this column and returns the\n * value the application sees; `toDb` is its inverse on the way out. Kept\n * deliberately unexported — a column declares one inline, nobody needs to name\n * the shape.\n */\ninterface ColumnTransform<T = unknown> {\n fromDb: (value: unknown) => T;\n toDb: (value: T) => unknown;\n}\n\n/**\n * The closed set of named column codecs (FR-001).\n *\n * WHY A CLOSED SET rather than `.transform<T>()`. A transform's target type is\n * a TYPE parameter — erased at runtime — so the generator that reads the\n * bundled schema object cannot learn it. `palbase-env.d.ts` therefore said\n * `string` while the engine handed the application a number, and a consumer\n * project wrote a hand-rolled codec module plus 141 call sites to compensate.\n *\n * A codec is NAMED instead of typed: the name lands in `_def.codec`, travels in\n * the runtime object to every reader, and BOTH the conversion and the emitted\n * TypeScript type are derived from it. One declaration, one truth, and no way\n * for the two to disagree.\n */\nexport type CodecName = 'number' | 'decimal';\n\nexport const CODECS: Record<\n CodecName,\n { fromDb: (value: unknown) => unknown; toDb: (value: unknown) => unknown; tsType: 'number' | 'string' }\n> = {\n // Exact-precision column → JS number. Safe below 2^53; a value above it is\n // not representable and the author wants `asDecimal()` instead.\n number: { fromDb: (v) => Number(v), toDb: (v) => String(v), tsType: 'number' },\n // Exact-precision column → the string Postgres sent. It exists so the\n // DECLARATION is explicit: \"this column is a decimal I handle as text\",\n // rather than the ABSENCE of a declaration meaning the same thing by default.\n decimal: { fromDb: (v) => String(v), toDb: (v) => String(v), tsType: 'string' },\n};\n\n/** FR-002: a vector column cannot carry keys/defaults/references — the modifier\n * is named in the error so the author fixes the right line. */\nfunction refuseOnVector(def: ColumnDef, modifier: string): void {\n if (def.type === 'vector') {\n throw new Error(`vector column: .${modifier}() is not supported (FR-002 — allowed: nullable()/notNull())`);\n }\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\ndeclare const __colPayload: unique symbol;\ndeclare const __colTransform: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n * P — jsonb payload shape (unknown unless jsonb<T>() supplied one)\n * T — transform target type (`never` when the column declares no transform;\n * `never` is the sentinel because it is the only type that survives\n * `[T] extends [never]` and never collides with a real target type)\n *\n * All six params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The six `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n P = unknown,\n // `unknown`, not `never`: the schema's own constraint is a BARE\n // `ColumnBuilder`, whose T lands on this default. With `never` there, a\n // column that declares `.transform<number>()` is not assignable to the\n // constraint at all — `number` does not extend `never` — so a transform\n // could not appear in a schema and the whole table's `RowShape` collapsed.\n // Measured: TS2322 on `defineSchema`.\n T = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n declare readonly [__colPayload]: P;\n declare readonly [__colTransform]: T;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E, P, T> {\n refuseOnVector(this._def, 'primaryKey');\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E, P, T> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E, P, T> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E, P, T> {\n refuseOnVector(this._def, 'default');\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E, P, T>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E, P, T> {\n refuseOnVector(this._def, 'defaultRandom');\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E, P, T> {\n refuseOnVector(this._def, 'defaultNow');\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E, P, T>(this._def.type as K, this._def);\n }\n\n /**\n * The DATABASE assigns this column's value — a trigger, a rule, an identity.\n *\n * The column becomes optional on INSERT (the author has nothing to send) while\n * the DDL stays free of a DEFAULT this schema would not honour. It is NOT\n * `default()`: that declares a value the schema promises to write.\n *\n * Naming: deliberately not `generated()`. Postgres has GENERATED columns and\n * they are a different thing; borrowing the word would send a reader — or a\n * model writing a schema — to the wrong feature.\n */\n dbAssigned(): ColumnBuilder<K, N, true, E, P, T> {\n this._def.dbAssigned = true;\n return new ColumnBuilder<K, N, true, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n /**\n * Declares that this column used to be called `previous`.\n *\n * A schema diff sees one name gone and another present; it cannot know whether\n * you renamed a column or dropped one and added another, and the two are very\n * different — the second loses every value. Saying so here turns the plan into\n * `ALTER TABLE … RENAME COLUMN` instead.\n *\n * Once the rename has been applied the annotation is inert (the old name is no\n * longer there to rename), so it can be deleted at your leisure.\n */\n renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T> {\n this._def.renamedFrom = previous;\n return this as unknown as ColumnBuilder<K, N, D, E, P, T>;\n }\n\n /**\n * See {@link ColumnDef.ignored}.\n *\n * COPIES the def rather than mutating it. The constructor takes an existing\n * def BY REFERENCE, so every builder derived from another shares one object —\n * `const a = slug.unique()` leaves `a._def === slug._def`. An in-place\n * `ignored = true` therefore marks every column sharing that def, including\n * one another table actively reads, and the gate would let THAT column be\n * dropped. Measured before this copy existed.\n *\n * The aliasing is older than this method and other fields leak through it too.\n * The reason this one cannot wait: every other leak produces a VISIBLE schema\n * difference — the plan shows it, the DDL shows it. This one is invisible by\n * design (no DDL, no diff, no plan line), so its only effect is to disarm a\n * safety gate in silence.\n */\n ignored(): ColumnBuilder<K, N, D, E, P, T> {\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, {\n ...this._def,\n ignored: true,\n });\n }\n\n /**\n * Foreign key onto another table's column.\n *\n * The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)\n * the second table does not exist yet when the first is built; a direct\n * reference makes TypeScript chase its own tail (TS7022 — measured, and making\n * the return type independent of the target does NOT help). The thunk is\n * invoked in `defineSchema`, where every binding exists and every table\n * already knows its name.\n *\n * In a cycle, ONE side needs an explicit return type:\n * `references((): AnyColumn => y.id)`. One side is enough — measured.\n * For a self-reference use `selfReferences(column)`: no thunk, no annotation.\n *\n * `as` names the FORWARD relation (`author_id` → `author` by default);\n * `reverseAs` names the REVERSE one on the parent, whose default is this\n * table's own name (`users.posts`). Two foreign keys from one table onto one\n * parent therefore need a `reverseAs` on at least one of them — the reverse\n * names would otherwise both be this table's name.\n */\n references(\n target: () => AnyColumn,\n opts?: {\n as?: string;\n reverseAs?: string;\n onDelete?: OnDeleteAction;\n /**\n * Bu FK kolonu için TÜREVİ index üretilsin mi (FR-044). Varsayılan açık.\n *\n * Postgres bir foreign key'i otomatik indekslemez — yalnız hedef\n * taraftaki unique kısıt vardır. Bedeli FK üzerinden her JOIN'de ve her\n * `ON DELETE CASCADE`'de ödenir: bir parent silinirken child tablo tam\n * taranır.\n *\n * `false` demek gerçek bir ihtiyaç, nezaket değil (D-028): FK kolonu aynı\n * zamanda SIK GÜNCELLENEN bir kolonsa index HOT güncellemeyi kırar — ve\n * HOT, DEĞİŞEN kolon indeksliyse kırılır, tablo indeksli diye değil.\n */\n index?: boolean;\n },\n ): ColumnBuilder<K, N, D, E, P, T> {\n refuseOnVector(this._def, 'references');\n if (typeof target !== 'function') {\n // Fail where the mistake is. Storing a non-thunk here surfaces three\n // layers later as \"def.referencesThunk is not a function\", inside\n // defineSchema, naming neither the column nor the call that was wrong.\n throw new Error(\n `references(...) takes a callback: write references(() => otherTable.column). ` +\n `The two-string form references(\"table\", \"column\") is gone — a string cannot be type-checked ` +\n `and cannot point at a table that does not exist yet.`,\n );\n }\n this._def.referencesThunk = target;\n if (opts?.as !== undefined) this._def.refAs = opts.as;\n if (opts?.reverseAs !== undefined) this._def.reverseAs = opts.reverseAs;\n if (opts?.onDelete !== undefined) this._def.onDeleteAction = opts.onDelete;\n // Yalnız AÇIKÇA false bildirilince taşınıyor: `undefined` \"bildirilmedi,\n // türet\" demek ve wire'da hiç görünmemeli (bayt-aynılık).\n if (opts?.index === false) this._def.index = false;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /**\n * Bu kolon `increment()` / `decrement()` ile güncelleniyor (FR-049).\n *\n * Deploy kontrolcü kodunu OKUMAZ, o yüzden bildirimin söylemesi gerekiyor —\n * ve söylediği anda plan bir şeyi görebiliyor: aynı kolon hem sayaç hem\n * indeksliyse her güncelleme HOT'u kaybeder.\n *\n * ZİNCİR (D-028+D-030): HOT kaybı → ölü tuple → autovacuum yükü → ve\n * autovacuum worker'ları KÜME GENELİNDE bir kaynak, yani bedeli başka\n * kiracıların tabloları da öder.\n *\n * Sektörde çare \"dokümana uyarı yaz\"dır, çünkü index'i ekleyen kişi\n * `increment()`'i yazan kişi değildir. Bu bildirimde ikisi de YAN YANA\n * duruyor.\n *\n * Uyarı, HATA DEĞİL: sayaç kolonunu indekslemek bazen doğru karardır.\n */\n counter(): ColumnBuilder<K, N, D, E, P, T> {\n this._def.counter = true;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /**\n * Foreign key onto THIS table (`parent_id → id`) — category trees, comment\n * replies, org charts.\n *\n * No thunk and no type annotation: the target table is the one being declared,\n * so there is nothing to defer and nothing for TypeScript to chase in a circle.\n * Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference\n * always goes through a callback; measured, we do not need one.\n */\n selfReferences(\n column: string,\n opts?: { as?: string; onDelete?: OnDeleteAction },\n ): ColumnBuilder<K, N, D, E, P, T> {\n refuseOnVector(this._def, 'selfReferences');\n this._def.selfRefColumn = column;\n if (opts?.as !== undefined) this._def.refAs = opts.as;\n if (opts?.onDelete !== undefined) this._def.onDeleteAction = opts.onDelete;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /** Add a single-column UNIQUE constraint. */\n unique(): ColumnBuilder<K, N, D, E, P, T> {\n refuseOnVector(this._def, 'unique');\n this._def.unique = true;\n return new ColumnBuilder<K, N, D, E, P, T>(this._def.type as K, this._def);\n }\n\n /**\n * Declare how this column's value is projected in and out of the process.\n *\n * The DDL does not move: `numeric` stays `numeric`, and the driver still hands\n * back what Postgres sent. What changes is the type the row surface exposes —\n * it becomes `Target`:\n *\n * amount: numeric().transform<number>({ fromDb: Number, toDb: String })\n *\n * `numeric` surfacing as `string` is CORRECT (a JS number cannot hold\n * arbitrary precision), and that is exactly why this exists: application code\n * that does arithmetic on the column otherwise rewrites the same\n * `Number(row.amount)` / `String(x)` pair in every controller that touches it,\n * and each rewrite is a place the two directions can drift apart.\n *\n * A transform is a PROJECTION, never a constraint: it lives only in this\n * process, so it can neither validate nor migrate what is stored.\n */\n /**\n * Surface this exact-precision column as a JS `number` (FR-001).\n *\n * `numeric`/`bigint` arrive as strings because a JS number cannot hold their\n * full range — correct, and exactly why this exists: application code that\n * does arithmetic on the column otherwise rewrites the same `Number(row.x)` /\n * `String(v)` pair in every caller, and each rewrite is a place the two\n * directions can drift apart.\n */\n asNumber(): ColumnBuilder<K, N, D, E, P, number> {\n return this.withCodec<number>('number', 'asNumber');\n }\n\n /**\n * Surface this exact-precision column as a `string` — the value Postgres\n * sent, DECLARED rather than defaulted (FR-001).\n */\n asDecimal(): ColumnBuilder<K, N, D, E, P, string> {\n return this.withCodec<string>('decimal', 'asDecimal');\n }\n\n private withCodec<Target>(codec: CodecName, method: string): ColumnBuilder<K, N, D, E, P, Target> {\n if (this._def.type !== 'bigint' && this._def.type !== 'numeric') {\n throw new Error(\n `${this._def.type} column: .${method}() is only available on bigint()/numeric() — ` +\n `those are the exact-precision types that surface as string.`,\n );\n }\n if (this._def.transform !== undefined) {\n throw new Error(\n `.${method}() and .transform() are two ways to say the same thing on one column — keep .${method}().`,\n );\n }\n this._def.codec = codec;\n return new ColumnBuilder<K, N, D, E, P, Target>(this._def.type as K, this._def);\n }\n\n transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target> {\n // The cast is the variance, not a shortcut: `toDb` takes `Target`, and a\n // `ColumnTransform<unknown>` would have to accept anything. The stored pair\n // is only ever called with this column's own values.\n this._def.transform = fns as ColumnTransform;\n return new ColumnBuilder<K, N, D, E, P, Target>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" | \"bigint\" | \"numeric\" → string (or string | null when N = true)\n * Note: bigint/numeric surface as string — JS number loses precision past 2^53,\n * and pgx/PostgREST serialize int8/numeric as strings. App code uses\n * BigInt(row.amount) for bigint, or a decimal lib for numeric.\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → P (the dev-supplied payload shape from jsonb<T>(), else unknown)\n * - \"enum\" → E (the union of literal values)\n *\n * A declared `.transform<T>()` OVERRIDES the table above: the column then\n * surfaces as T (or T | null when nullable), because that is the value the\n * application is handed. Nullability is still the column's, not the\n * transform's — `fromDb` is not called for a NULL.\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<ColumnType, infer N, boolean, unknown, unknown, infer T>\n ? [unknown] extends [T]\n ? ColStoredValue<C>\n : N extends true\n ? T | null\n : T\n : never;\n\n/** The value as the DATABASE hands it over — the branch table above, before any\n * transform. This is what a column's `fromDb` receives. */\ntype ColStoredValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp' | 'bigint' | 'numeric', infer N, infer _D, infer _E, infer _P>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E, infer _P>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E, infer _P>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer N, infer _D, infer _E, infer P>\n ? N extends true\n ? P | null\n : P\n : C extends ColumnBuilder<'vector', infer N, infer _D, infer _E, infer _P>\n ? N extends true\n ? number[] | null\n : number[]\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<ColumnType, true, boolean, unknown, unknown, unknown>\n ? true\n : C extends ColumnBuilder<ColumnType, boolean, true, unknown, unknown, unknown>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. Emits int4 (max ~2.1B). */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/**\n * Create a BIGINT column (Postgres int8, max ~9.2×10^18).\n * Surfaces as `string` in row/insert types — JS number loses precision past 2^53\n * and pgx/PostgREST serialize int8 as a JSON string. Use BigInt(row.column) in app code.\n */\nexport function bigint(): ColumnBuilder<'bigint', false, false, never> {\n return new ColumnBuilder('bigint');\n}\n\n/**\n * Create a NUMERIC column (Postgres `numeric`/`decimal`, arbitrary precision).\n * For exact fractional values (money with cents as a decimal, rates, weights)\n * where int4/int8 don't fit. Surfaces as `string` in row/insert types — JS\n * number can't hold arbitrary-precision decimals without rounding, and\n * pgx/PostgREST serialize numeric as a JSON string. Parse with a decimal lib\n * (or BigInt for scaled integers) in app code.\n */\nexport function numeric(): ColumnBuilder<'numeric', false, false, never> {\n return new ColumnBuilder('numeric');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/**\n * Create a JSONB column. Pass a payload type to make the generated row/insert\n * type concrete instead of `unknown`:\n *\n * tags: jsonb<string[]>() // row.tags: string[]\n * meta: jsonb<{ tier: string }>() // row.meta: { tier: string }\n * raw: jsonb() // row.raw: unknown (back-compat)\n *\n * The runtime accepts a plain JS object/array directly (no JSON.stringify); the\n * generic only refines the TYPE the env codegen emits.\n */\nexport function jsonb<T = unknown>(): ColumnBuilder<'jsonb', false, false, never, T> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n\n/** vector(n) — pgvector kolonu. n TİPİN parçasıdır (typmod) ve [1, 2000] —\n * 2000 = pgvector'ün HNSW-indekslenebilir tavanı; auto-index bu beyanla bağlı\n * (spec FR-001, D-3). */\nexport function vector(dimensions: number): ColumnBuilder<'vector', false, false, unknown, number[]> {\n if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 2000) {\n throw new Error(`vector(): dimensions must be an integer in [1, 2000], got ${String(dimensions)}`);\n }\n const b = new ColumnBuilder('vector') as ColumnBuilder<'vector', false, false, unknown, number[]>;\n (b._def as { dimensions?: number }).dimensions = dimensions;\n return b;\n}\n\n/**\n * Any column, whatever its type parameters.\n *\n * Exported so a cycle can be broken from ONE side:\n * `references((): AnyColumn => y.id)`.\n */\nexport type AnyColumn = ColumnBuilder;\n\n/**\n * The column that OWNS this row: a `text` FK onto `auth.users(id)`, NOT NULL,\n * ON DELETE CASCADE.\n *\n * Ownership drives account erasure, so cascade is the only correct action and\n * takes no argument. The referencing column must be `text` (palauth ids are\n * `usr_<uuid>`) and NOT NULL — both are implied here rather than left to the\n * caller, so three rules the type could not express before become UNWRITABLE.\n *\n * At most ONE per table. The old shape let several columns reference\n * `auth.users` and picked the FIRST IN DECLARATION ORDER as the owner — moving a\n * `created_by` above a `user_id` silently changed which rows an account deletion\n * took with it. A second one is now rejected at push.\n *\n * For a column that merely POINTS at a user without owning the row\n * (`created_by`, `edited_by`), use `userRef({ onDelete })`.\n */\nexport function ownedByUser(): ColumnBuilder<'text', false, false, never> {\n const b = new ColumnBuilder<'text', false, false, never>('text');\n b._def.nullable = false;\n b._def.references = { table: 'auth.users', column: 'id' };\n b._def.onDeleteAction = 'cascade';\n b._def.owns = true;\n return b;\n}\n\n/**\n * A plain FK onto `auth.users(id)` that does NOT own the row.\n *\n * `created_by` / `edited_by`: deleting that user must not delete the row. ON\n * DELETE is required and limited to `cascade | set null` so an erasure request\n * is never blocked by a lingering FK; `set null` needs a nullable column.\n */\nexport function userRef(opts: { onDelete: AuthUserOnDelete; as?: string }): ColumnBuilder<'text', boolean, false, never> {\n const b = new ColumnBuilder<'text', boolean, false, never>('text');\n b._def.references = { table: 'auth.users', column: 'id' };\n b._def.onDeleteAction = opts.onDelete;\n if (opts.as !== undefined) b._def.refAs = opts.as;\n return b;\n}\n\n/**\n * A plain FK onto `auth.installations(id)` — the app-scoped verified-device\n * anchor.\n *\n * An installation is an APP INSTALL, not a user: this is NOT ownership. A\n * user-owned row still needs its own `ownedByUser()` so account erasure removes\n * it; an installation reference alone does not tie a row to a user's deletion.\n */\nexport function installationRef(opts: { onDelete: AuthUserOnDelete; as?: string }): ColumnBuilder<'text', boolean, false, never> {\n const b = new ColumnBuilder<'text', boolean, false, never>('text');\n b._def.references = { table: 'auth.installations', column: 'id' };\n b._def.onDeleteAction = opts.onDelete;\n if (opts.as !== undefined) b._def.refAs = opts.as;\n return b;\n}\n"],"mappings":"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","names":["CODECS","number","fromDb","v","Number","toDb","String","tsType","decimal","refuseOnVector","def","modifier","type","Error","ColumnBuilder","_def","existingDef","nullable","primaryKey","notNull","default","value","defaultValue","defaultRandom","defaultNow","dbAssigned","renamedFrom","previous","ignored","references","target","opts","referencesThunk","as","undefined","refAs","reverseAs","onDelete","onDeleteAction","index","counter","selfReferences","column","selfRefColumn","action","unique","asNumber","withCodec","asDecimal","codec","method","transform","fns","uuid","text","integer","bigint","numeric","boolean","timestamp","jsonb","enumType","name","values","builder","enumName","enumValues","vector","dimensions","isInteger","b","ownedByUser","table","owns","userRef","installationRef"]}