@palbase/backend 22.0.1 → 23.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 (73) hide show
  1. package/dist/bin/palbase-backend.cjs +764 -25
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-POYAFBLF.js → chunk-FSGSB42K.js} +13 -6
  5. package/dist/chunk-FSGSB42K.js.map +1 -0
  6. package/dist/{chunk-SSGAMC26.js → chunk-HAF67F2H.js} +114 -5
  7. package/dist/chunk-HAF67F2H.js.map +1 -0
  8. package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
  9. package/dist/chunk-OMRTHM4X.js.map +1 -0
  10. package/dist/{chunk-QYOHMVUW.js → chunk-REZU6UKT.js} +755 -24
  11. package/dist/chunk-REZU6UKT.js.map +1 -0
  12. package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
  13. package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
  14. package/dist/chunk-ZC6Q2BRD.js.map +1 -0
  15. package/dist/db/env.cjs.map +1 -1
  16. package/dist/db/env.d.cts +3 -21
  17. package/dist/db/env.d.ts +3 -21
  18. package/dist/db/index.cjs +117 -6
  19. package/dist/db/index.cjs.map +1 -1
  20. package/dist/db/index.d.cts +3 -2
  21. package/dist/db/index.d.ts +3 -2
  22. package/dist/db/index.js +7 -3
  23. package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BavvbW4P.d.ts} +116 -14
  24. package/dist/{endpoint-B0LpZixz.d.ts → endpoint-i8TTCohk.d.cts} +116 -14
  25. package/dist/engine/index.cjs +764 -25
  26. package/dist/engine/index.cjs.map +1 -1
  27. package/dist/engine/index.d.cts +6 -5
  28. package/dist/engine/index.d.ts +6 -5
  29. package/dist/engine/index.js +3 -3
  30. package/dist/{index-g-EzitI-.d.ts → index-B3jmmItD.d.ts} +203 -7
  31. package/dist/{index-BCNtlG1w.d.ts → index-B7YBEG5w.d.ts} +117 -93
  32. package/dist/{index-BGSCWlUa.d.cts → index-Bmvx1EvJ.d.cts} +203 -7
  33. package/dist/{index-B4W6d2VJ.d.cts → index-E7OscPJT.d.cts} +117 -93
  34. package/dist/index.cjs +509 -1085
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +203 -1133
  37. package/dist/index.d.ts +203 -1133
  38. package/dist/index.js +288 -900
  39. package/dist/index.js.map +1 -1
  40. package/dist/openapi/index.cjs +3 -57
  41. package/dist/openapi/index.cjs.map +1 -1
  42. package/dist/openapi/index.d.cts +3 -2
  43. package/dist/openapi/index.d.ts +3 -2
  44. package/dist/openapi/index.js +5 -22
  45. package/dist/openapi/index.js.map +1 -1
  46. package/dist/{registry-3BLYv4si.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
  47. package/dist/{registry-Cw0YEYCg.d.cts → registry-DY3d9l1k.d.ts} +13 -169
  48. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  49. package/dist/stack.cjs.map +1 -0
  50. package/dist/stack.d.cts +76 -0
  51. package/dist/stack.d.ts +76 -0
  52. package/dist/stack.js +1 -0
  53. package/docs/README.md +7 -6
  54. package/docs/endpoints.md +1 -1
  55. package/docs/errors.md +9 -0
  56. package/docs/llms-full.txt +17 -261
  57. package/docs/llms.txt +0 -2
  58. package/package.json +7 -7
  59. package/template/package.json +1 -1
  60. package/dist/chunk-N32VDWKH.js.map +0 -1
  61. package/dist/chunk-POYAFBLF.js.map +0 -1
  62. package/dist/chunk-QMVK4X3V.js.map +0 -1
  63. package/dist/chunk-QYOHMVUW.js.map +0 -1
  64. package/dist/chunk-SSGAMC26.js.map +0 -1
  65. package/dist/purchases/keys.cjs.map +0 -1
  66. package/dist/purchases/keys.d.cts +0 -42
  67. package/dist/purchases/keys.d.ts +0 -42
  68. package/dist/purchases/keys.js +0 -1
  69. package/docs/config.md +0 -147
  70. package/docs/resources.md +0 -97
  71. package/template/config/secrets.ts +0 -24
  72. /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
  73. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error-registry.ts"],"sourcesContent":["// defineError — project-defined typed error classes + the module-global error\n// registry the OpenAPI spec twins join against.\n//\n// `defineError(code, status, dataSchema?)` returns an `HttpError` subclass whose\n// constructor is typed by the zod schema (`new TodoLocked({ retryAfter: 30 })`).\n// Each call self-registers `{ code, status, className, dataSchema }` in a\n// registry anchored on `globalThis` under `Symbol.for(...)` — mirroring the\n// ROUTES pattern — so the deploy bundle's registrations land on the single SDK\n// instance the extractor `require()`s. The registry is pre-seeded with the named\n// built-in classes (`NotFound`, `Conflict`, …) so throw inference can resolve\n// ad-hoc `throw new NotFound(...)` sites by their canonical wire codes.\n//\n// Duplicate code with a DIFFERENT shape (status OR converted data schema) is a\n// hard error at registration time (deploy fails loudly — error codes are\n// project-unique); structurally-identical re-registration is idempotent (each\n// controller BUNDLE carries its own copy of a shared models/errors.ts, so the\n// same defineError line legitimately runs once per bundle in one process).\nimport {\n OpenAPIRegistry,\n OpenApiGeneratorV31,\n extendZodWithOpenApi,\n} from \"@asteasolutions/zod-to-openapi\";\nimport { z, type ZodTypeAny } from \"zod\";\nimport { HttpError } from \"./errors.js\";\n\nextendZodWithOpenApi(z);\n\n/** One registered error: the wire code, HTTP status, the class name the\n * analyzer sees, the optional data payload schema, and whether it is one of the\n * pre-seeded built-in named classes. */\nexport interface RegisteredError {\n code: string;\n status: number;\n className: string;\n dataSchema?: ZodTypeAny;\n /** JSON.stringify of the converted JSON schema — equal digest ⇔ the spec\n * twins would emit an identical `data` schema for this error. */\n dataDigest?: string;\n builtin: boolean;\n}\n\n/** The cross-module-instance registry anchor (the pinned contract symbol). */\nconst ERROR_REGISTRY: unique symbol = Symbol.for(\"palbase.backend.errorRegistry\");\n\ninterface RegistryCarrier {\n [ERROR_REGISTRY]?: Map<string, RegisteredError>;\n}\n\n/** Get (creating + pre-seeding if absent) the project-global error registry,\n * keyed by wire code. Anchored on `globalThis` so every copy of the SDK in a\n * process shares the one registry. */\n/** The fixed data schemas the built-in named errors ship. Declared HERE (not in\n * errors.ts, which stays zod-free) because this is where the registry converts\n * them to JSON-schema for the spec — keying codegen's typed `error.data`. Only\n * the errors that semantically carry a payload have a schema; the rest are\n * data-less. `.int()` is preserved so the wire/codegen sees an integer. */\nconst BUILTIN_DATA_SCHEMAS: Record<string, ZodTypeAny> = {\n bad_request: z.object({\n fields: z.array(z.object({ field: z.string(), message: z.string() })),\n }),\n too_many_requests: z.object({ retryAfter: z.number().int() }),\n};\n\nexport function getErrorRegistry(): Map<string, RegisteredError> {\n const g = globalThis as RegistryCarrier;\n if (!g[ERROR_REGISTRY]) {\n const m = new Map<string, RegisteredError>();\n for (const [code, status, className] of [\n [\"bad_request\", 400, \"BadRequest\"],\n [\"unauthorized\", 401, \"Unauthorized\"],\n [\"forbidden\", 403, \"Forbidden\"],\n [\"not_found\", 404, \"NotFound\"],\n [\"conflict\", 409, \"Conflict\"],\n [\"too_many_requests\", 429, \"TooManyRequests\"],\n ] as const) {\n const dataSchema = BUILTIN_DATA_SCHEMAS[code];\n m.set(code, {\n code,\n status,\n className,\n builtin: true,\n ...(dataSchema ? { dataSchema, dataDigest: digestOf(code, dataSchema) } : {}),\n });\n }\n g[ERROR_REGISTRY] = m;\n }\n return g[ERROR_REGISTRY];\n}\n\n/** Class returned for schema-bearing errors: ctor(data, message?). */\nexport interface DefinedErrorWithData<S extends ZodTypeAny> {\n new (data: z.input<S>, message?: string): HttpError;\n readonly code: string;\n readonly status: number;\n}\n\n/** Class returned for schema-less errors: ctor(message?). */\nexport interface DefinedError {\n new (message?: string): HttpError;\n readonly code: string;\n readonly status: number;\n}\n\n/**\n * Define a project error class. MUST be called with literal args (string\n * literal `code`, numeric literal `status`) at module top level — the deploy\n * stager's static analyzer reads those literals to resolve throw sites.\n *\n * @example\n * // models/todos/errors.ts\n * export const TodoLocked = defineError(\"todo_locked\", 409, z.object({ retryAfter: z.number() }));\n * // services/todo.service.ts\n * throw new TodoLocked({ retryAfter: 30 });\n */\nexport function defineError<S extends ZodTypeAny>(\n code: string,\n status: number,\n dataSchema: S,\n): DefinedErrorWithData<S>;\nexport function defineError(code: string, status: number): DefinedError;\nexport function defineError(code: string, status: number, dataSchema?: ZodTypeAny): unknown {\n if (!Number.isInteger(status) || status < 400 || status > 599) {\n throw new Error(\n `defineError: status for \"${code}\" must be a 4xx/5xx integer, got ${status} — ` +\n `error responses must not clobber success responses in the project spec.`,\n );\n }\n const registry = getErrorRegistry();\n const existing = registry.get(code);\n const className = defaultClassName(code);\n const dataDigest = dataSchema ? digestOf(code, dataSchema) : undefined;\n if (existing) {\n const sameShape =\n existing.status === status && !existing.builtin && existing.dataDigest === dataDigest;\n if (!sameShape) {\n throw new Error(\n `defineError: duplicate error code \"${code}\" with a different shape ` +\n `(existing: status ${existing.status}${existing.builtin ? \", built-in\" : \"\"}` +\n `${existing.status === status && !existing.builtin ? \", different data schema\" : \"\"}). ` +\n `Error codes are project-unique.`,\n );\n }\n }\n // Local factories so the class-constructor closures capture a NON-optional\n // schema binding (narrowing does not flow into closures created in a branch).\n const makeWithData = (schema: ZodTypeAny) =>\n class extends HttpError {\n static readonly code = code;\n static readonly status = status;\n constructor(data: unknown, message?: string) {\n super(status, code, message ?? humanize(code), schema.parse(data));\n this.name = className;\n }\n };\n const makeWithoutData = () =>\n class extends HttpError {\n static readonly code = code;\n static readonly status = status;\n constructor(message?: string) {\n super(status, code, message ?? humanize(code));\n this.name = className;\n }\n };\n const cls = dataSchema ? makeWithData(dataSchema) : makeWithoutData();\n Object.defineProperty(cls, \"name\", { value: className });\n registry.set(code, {\n code,\n status,\n className,\n builtin: false,\n ...(dataSchema ? { dataSchema } : {}),\n ...(dataDigest !== undefined ? { dataDigest } : {}),\n });\n return cls;\n}\n\n/** Convert a data schema through the SAME library the spec twins use and\n * return its JSON-schema digest (the duplicate-shape comparison key). Doubles\n * as the self-containment guard: a schema carrying `.openapi(refId)` metadata\n * (top-level or nested) converts to `$ref` pointers into components the\n * project spec never emits — a dangling ref every spec consumer would choke\n * on — so it is rejected here, at the definition site, with a fixable message. */\nfunction digestOf(code: string, dataSchema: ZodTypeAny): string {\n const TMP_REF = \"__PalbaseErrorDataDigest\";\n const tmpRegistry = new OpenAPIRegistry();\n // Wrap the schema as a property — exactly how the envelope embeds it — so a\n // TOP-LEVEL .openapi(refId) is not masked by the registration refId and\n // surfaces as the same $ref the real emission would produce.\n tmpRegistry.register(TMP_REF, z.object({ data: dataSchema }).openapi(TMP_REF));\n const generated = new OpenApiGeneratorV31(tmpRegistry.definitions).generateComponents();\n const out = generated.components?.schemas?.[TMP_REF];\n const digest = JSON.stringify(out ?? null);\n if (digest.includes('\"$ref\"')) {\n throw new Error(\n `defineError: dataSchema for \"${code}\" carries .openapi(refId) metadata — ` +\n `it would emit dangling $ref pointers in the project spec. ` +\n `Use a plain zod schema (z.object({...})) without .openapi(...).`,\n );\n }\n return digest;\n}\n\n/** \"todo_locked\" → \"TodoLocked\" (used as Error.name; the source class NAME the\n * analyzer sees is whatever the user assigned the const to — they should match\n * by convention, and the descriptor carries the analyzed const name anyway). */\nfunction defaultClassName(code: string): string {\n return code\n .split(\"_\")\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join(\"\");\n}\n\n/** \"todo_locked\" → \"Todo locked\" — the default human-readable message. */\nfunction humanize(code: string): string {\n const s = code.replace(/_/g, \" \");\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/env.ts"],"sourcesContent":["/**\n * `@palbase/backend/env` — the controlled global augmentation target for the\n * project's schema.\n *\n * `Database.tables.<name>` is typed against the `Tables` interface declared\n * here. By default `Tables` is EMPTY; the generated `palbase-env.d.ts`\n * (emitted from `db/schema.ts` by {@link makeEnvDts}) augments it with one\n * member per table:\n *\n * // palbase-env.d.ts (generated — do not edit)\n * declare module \"@palbase/backend/env\" {\n * interface Tables {\n * todos: { row: {...}; insert: {...} };\n * }\n * }\n *\n * Because a `br-<ref>` pod is single-project, augmenting one global `Tables`\n * interface is safe — there is no cross-project \"leak\" to worry about (a pod\n * only ever compiles one project's schema). This is the C5 decision: typed\n * `Database.tables.*` with NO import and NO generic in handler code.\n *\n * Authors who want the row type explicitly can import it:\n *\n * import type { Tables } from \"@palbase/backend/env\";\n * type Todo = Tables[\"todos\"][\"row\"];\n */\n\n/**\n * One typed table's shapes and its position in the user-rooted graph:\n *\n * - `row` — the full row, every column present.\n * - `insert` — the write payload, required vs optional columns.\n * - `owner` — the column that foreign-keys `auth.users` (declared in\n * `db/schema.ts` with `.referencesAuthUser(...)`), or `null` when the table\n * is not user-rooted. This is what makes a seed/fixture DSL able to OMIT the\n * owner column from the author-facing type: the apply step fills it.\n * - `children` — tables that foreign-key THIS table, mapped to the FK column on\n * the child (`{ todos: \"list_id\" }`). Lets a nested seed attach children\n * without the author ever writing the FK. Self-FKs and cycle back-edges are\n * omitted by the generator so recursive types over `children` terminate.\n *\n * The generated `palbase-env.d.ts` fills `row`/`insert` with flat object types —\n * no `ColumnBuilder` phantom types ever appear in the generated output — and\n * `owner`/`children` with string literals.\n */\nexport interface TableTypes {\n row: Record<string, unknown>;\n insert: Record<string, unknown>;\n owner: string | null;\n children: Record<string, string>;\n}\n\n/**\n * The project's tables, keyed by table name. EMPTY by default; the generated\n * `palbase-env.d.ts` augments this interface (module augmentation), so\n * `Database.tables` is typed everywhere with no per-file import.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target — filled by generated palbase-env.d.ts.\nexport interface Tables {}\n\n/**\n * One typed bucket: which renditions `config/storage.ts` declared for it.\n *\n * `variants` is a UNION of the declared names, so asking for one the bucket\n * does not have is a compile error rather than a 404 somebody finds in\n * production. The generated `palbase-env.d.ts` fills it; the default `never`\n * means a bucket declared without variants accepts no variant name at all,\n * which is exactly right.\n */\nexport interface BucketTypes {\n variants: string;\n}\n\n/**\n * The project's storage buckets, keyed by bucket name. EMPTY by default; the\n * generated `palbase-env.d.ts` augments it, so `Storage.buckets` is typed\n * everywhere with no per-file import the same mechanism `Tables` uses, and\n * deliberately so: an author learns one pattern.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target filled by generated palbase-env.d.ts.\nexport interface Buckets {}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../src/db/env.ts"],"sourcesContent":["/**\n * `@palbase/backend/env` — the controlled global augmentation target for the\n * project's schema.\n *\n * `Database.tables.<name>` is typed against the `Tables` interface declared\n * here. By default `Tables` is EMPTY; the generated `palbase-env.d.ts`\n * (emitted from `db/schema.ts` by {@link makeEnvDts}) augments it with one\n * member per table:\n *\n * // palbase-env.d.ts (generated — do not edit)\n * declare module \"@palbase/backend/env\" {\n * interface Tables {\n * todos: { row: {...}; insert: {...} };\n * }\n * }\n *\n * Because a `br-<ref>` pod is single-project, augmenting one global `Tables`\n * interface is safe — there is no cross-project \"leak\" to worry about (a pod\n * only ever compiles one project's schema). This is the C5 decision: typed\n * `Database.tables.*` with NO import and NO generic in handler code.\n *\n * Authors who want the row type explicitly can import it:\n *\n * import type { Tables } from \"@palbase/backend/env\";\n * type Todo = Tables[\"todos\"][\"row\"];\n */\n\n/**\n * One typed table's shapes and its position in the user-rooted graph:\n *\n * - `row` — the full row, every column present.\n * - `insert` — the write payload, required vs optional columns.\n * - `owner` — the column that foreign-keys `auth.users` (declared in\n * `db/schema.ts` with `.referencesAuthUser(...)`), or `null` when the table\n * is not user-rooted. This is what makes a seed/fixture DSL able to OMIT the\n * owner column from the author-facing type: the apply step fills it.\n * - `children` — tables that foreign-key THIS table, mapped to the FK column on\n * the child (`{ todos: \"list_id\" }`). Lets a nested seed attach children\n * without the author ever writing the FK. Self-FKs and cycle back-edges are\n * omitted by the generator so recursive types over `children` terminate.\n *\n * The generated `palbase-env.d.ts` fills `row`/`insert` with flat object types —\n * no `ColumnBuilder` phantom types ever appear in the generated output — and\n * `owner`/`children` with string literals.\n */\nexport interface TableTypes {\n row: Record<string, unknown>;\n insert: Record<string, unknown>;\n owner: string | null;\n children: Record<string, string>;\n}\n\n/**\n * The project's tables, keyed by table name. EMPTY by default; the generated\n * `palbase-env.d.ts` augments this interface (module augmentation), so\n * `Database.tables` is typed everywhere with no per-file import.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target — filled by generated palbase-env.d.ts.\nexport interface Tables {}\n\n// BUCKETS MOVED TO `@palbase/backend/stack` (2026-08-29).\n//\n// `BucketTypes` and `Buckets` lived here because `config/storage.ts` declared\n// buckets and the deploy folded them into `palbase-env.d.ts` beside the schema's\n// tables. Buckets never came from the schema, and they do not come from a\n// declaration any more: the STACK holds them, so their types are generated into\n// `palbase-stack.d.ts` with the secrets and flags.\n//\n// Re-exported here so `Storage.buckets` keeps its one import site and no caller\n// has to learn that the source moved.\nexport type { BucketTypes, Buckets } from \"../stack.js\";\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/dist/db/env.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ export { BucketTypes, Buckets } from '../stack.cjs';
2
+
1
3
  /**
2
4
  * `@palbase/backend/env` — the controlled global augmentation target for the
3
5
  * project's schema.
@@ -55,25 +57,5 @@ interface TableTypes {
55
57
  */
56
58
  interface Tables {
57
59
  }
58
- /**
59
- * One typed bucket: which renditions `config/storage.ts` declared for it.
60
- *
61
- * `variants` is a UNION of the declared names, so asking for one the bucket
62
- * does not have is a compile error rather than a 404 somebody finds in
63
- * production. The generated `palbase-env.d.ts` fills it; the default `never`
64
- * means a bucket declared without variants accepts no variant name at all,
65
- * which is exactly right.
66
- */
67
- interface BucketTypes {
68
- variants: string;
69
- }
70
- /**
71
- * The project's storage buckets, keyed by bucket name. EMPTY by default; the
72
- * generated `palbase-env.d.ts` augments it, so `Storage.buckets` is typed
73
- * everywhere with no per-file import — the same mechanism `Tables` uses, and
74
- * deliberately so: an author learns one pattern.
75
- */
76
- interface Buckets {
77
- }
78
60
 
79
- export type { BucketTypes, Buckets, TableTypes, Tables };
61
+ export type { TableTypes, Tables };
package/dist/db/env.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { BucketTypes, Buckets } from '../stack.js';
2
+
1
3
  /**
2
4
  * `@palbase/backend/env` — the controlled global augmentation target for the
3
5
  * project's schema.
@@ -55,25 +57,5 @@ interface TableTypes {
55
57
  */
56
58
  interface Tables {
57
59
  }
58
- /**
59
- * One typed bucket: which renditions `config/storage.ts` declared for it.
60
- *
61
- * `variants` is a UNION of the declared names, so asking for one the bucket
62
- * does not have is a compile error rather than a 404 somebody finds in
63
- * production. The generated `palbase-env.d.ts` fills it; the default `never`
64
- * means a bucket declared without variants accepts no variant name at all,
65
- * which is exactly right.
66
- */
67
- interface BucketTypes {
68
- variants: string;
69
- }
70
- /**
71
- * The project's storage buckets, keyed by bucket name. EMPTY by default; the
72
- * generated `palbase-env.d.ts` augments it, so `Storage.buckets` is typed
73
- * everywhere with no per-file import — the same mechanism `Tables` uses, and
74
- * deliberately so: an author learns one pattern.
75
- */
76
- interface Buckets {
77
- }
78
60
 
79
- export type { BucketTypes, Buckets, TableTypes, Tables };
61
+ export type { TableTypes, Tables };
package/dist/db/index.cjs CHANGED
@@ -36,11 +36,13 @@ __export(db_exports, {
36
36
  jsonb: () => jsonb,
37
37
  makeTypedDB: () => makeTypedDB,
38
38
  now: () => now,
39
+ openai: () => openai,
39
40
  policy: () => policy,
40
41
  raw: () => raw,
41
42
  text: () => text,
42
43
  timestamp: () => timestamp,
43
- uuid: () => uuid
44
+ uuid: () => uuid,
45
+ vector: () => vector
44
46
  });
45
47
  module.exports = __toCommonJS(db_exports);
46
48
 
@@ -118,8 +120,82 @@ function defineSchema(input) {
118
120
  if (table.raw !== void 0 && table.raw.length > 0) tableDef.raw = table.raw.slice();
119
121
  if (table.checks !== void 0 && table.checks.length > 0) tableDef.checks = table.checks.slice();
120
122
  if (table.indexes !== void 0 && table.indexes.length > 0) tableDef.indexes = table.indexes.slice();
123
+ if (table.search !== void 0) {
124
+ const s = table.search;
125
+ if (s.from !== void 0) {
126
+ if (s.vector !== void 0) {
127
+ throw new Error(
128
+ `table ${String(name)}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
129
+ );
130
+ }
131
+ if (s.from.length === 0) {
132
+ throw new Error(`table ${String(name)}: search.from bo\u015F olamaz`);
133
+ }
134
+ if (s.model === void 0) {
135
+ throw new Error(
136
+ `table ${String(name)}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
137
+ );
138
+ }
139
+ if (Array.isArray(s.text) && s.text.length === 0) {
140
+ throw new Error(`table ${String(name)}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
141
+ }
142
+ } else {
143
+ if (typeof s.text === "boolean") {
144
+ throw new Error(`table ${String(name)}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
145
+ }
146
+ if (s.text === void 0 && s.vector === void 0) {
147
+ throw new Error(
148
+ `table ${String(name)}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
149
+ );
150
+ }
151
+ if (s.text !== void 0 && s.text.length === 0) {
152
+ throw new Error(`table ${String(name)}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
153
+ }
154
+ const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
155
+ for (const leg of legs) {
156
+ if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
157
+ throw new Error(`table ${String(name)}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
158
+ }
159
+ if (leg.model === void 0 && leg.from !== void 0) {
160
+ throw new Error(`table ${String(name)}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
161
+ }
162
+ }
163
+ }
164
+ tableDef.search = s;
165
+ }
166
+ if (table.memory !== void 0) {
167
+ if (table.memory.from.length === 0) {
168
+ throw new Error(`table ${String(name)}: memory.from bo\u015F olamaz`);
169
+ }
170
+ tableDef.memory = table.memory;
171
+ }
121
172
  tables[name] = tableDef;
122
173
  }
174
+ for (const [name, def] of Object.entries(tables)) {
175
+ const m = def.memory;
176
+ if (m === void 0) continue;
177
+ const target = Object.values(tables).find((t) => t.name === m.into);
178
+ if (target === void 0) {
179
+ throw new Error(`table ${name}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
180
+ }
181
+ const subject = m.subject ?? "owner";
182
+ if (!(subject in def.columns)) {
183
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
184
+ }
185
+ if (!(subject in target.columns)) {
186
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
187
+ }
188
+ const factCol = target.columns["fact"];
189
+ const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
190
+ if (factDef === void 0 || factDef.type !== "text") {
191
+ throw new Error(`table ${name}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
192
+ }
193
+ for (const c of m.from) {
194
+ if (!(c in def.columns)) {
195
+ throw new Error(`table ${name}: memory.from kolonu "${c}" kaynak tabloda yok`);
196
+ }
197
+ }
198
+ }
123
199
  const extensions = [...new Set(input.extensions ?? [])];
124
200
  return { tables, extensions };
125
201
  }
@@ -137,8 +213,6 @@ var PALBASE_EXTENSIONS = [
137
213
  "citext",
138
214
  // case-insensitive text type
139
215
  // Geospatial / location
140
- "postgis",
141
- // geospatial types + queries (maps, "near me")
142
216
  "cube",
143
217
  // multi-dimensional cubes (dependency of earthdistance)
144
218
  "earthdistance",
@@ -153,8 +227,6 @@ var PALBASE_EXTENSIONS = [
153
227
  // GiST operator classes for scalar types — needed for EXCLUDE
154
228
  // constraints that mix "=" with a range/&& overlap (e.g. no-double-booking).
155
229
  // Scheduling
156
- "pg_cron",
157
- // schedule jobs inside the database
158
230
  // Crypto / ids (also installed by default; listable for explicitness)
159
231
  "pgcrypto",
160
232
  // cryptographic functions (hashing, encryption)
@@ -169,6 +241,11 @@ function isPalbaseExtension(name) {
169
241
  }
170
242
 
171
243
  // src/db/columns.ts
244
+ function refuseOnVector(def, modifier) {
245
+ if (def.type === "vector") {
246
+ throw new Error(`vector column: .${modifier}() is not supported (FR-002 \u2014 allowed: nullable()/notNull())`);
247
+ }
248
+ }
172
249
  var ColumnBuilder = class _ColumnBuilder {
173
250
  _def;
174
251
  constructor(type, existingDef) {
@@ -180,6 +257,7 @@ var ColumnBuilder = class _ColumnBuilder {
180
257
  }
181
258
  /** Mark this column as the primary key. */
182
259
  primaryKey() {
260
+ refuseOnVector(this._def, "primaryKey");
183
261
  this._def.primaryKey = true;
184
262
  return new _ColumnBuilder(this._def.type, this._def);
185
263
  }
@@ -195,16 +273,19 @@ var ColumnBuilder = class _ColumnBuilder {
195
273
  }
196
274
  /** Set a default value. */
197
275
  default(value) {
276
+ refuseOnVector(this._def, "default");
198
277
  this._def.defaultValue = value;
199
278
  return new _ColumnBuilder(this._def.type, this._def);
200
279
  }
201
280
  /** UUID: generate a random default (gen_random_uuid()). */
202
281
  defaultRandom() {
282
+ refuseOnVector(this._def, "defaultRandom");
203
283
  this._def.defaultRandom = true;
204
284
  return new _ColumnBuilder(this._def.type, this._def);
205
285
  }
206
286
  /** Timestamp: default to now(). */
207
287
  defaultNow() {
288
+ refuseOnVector(this._def, "defaultNow");
208
289
  this._def.defaultNow = true;
209
290
  return new _ColumnBuilder(this._def.type, this._def);
210
291
  }
@@ -225,6 +306,7 @@ var ColumnBuilder = class _ColumnBuilder {
225
306
  return this;
226
307
  }
227
308
  references(table, column) {
309
+ refuseOnVector(this._def, "references");
228
310
  this._def.references = { table, column };
229
311
  return new _ColumnBuilder(this._def.type, this._def);
230
312
  }
@@ -249,6 +331,7 @@ var ColumnBuilder = class _ColumnBuilder {
249
331
  * as the real boundary; this signature is the compile-time DX mirror.
250
332
  */
251
333
  referencesAuthUser(onDelete) {
334
+ refuseOnVector(this._def, "referencesAuthUser");
252
335
  this._def.references = { table: "auth.users", column: "id" };
253
336
  this._def.onDeleteAction = onDelete;
254
337
  return new _ColumnBuilder(this._def.type, this._def);
@@ -272,6 +355,7 @@ var ColumnBuilder = class _ColumnBuilder {
272
355
  * boundary; this signature is the compile-time DX mirror.
273
356
  */
274
357
  referencesInstallation(onDelete) {
358
+ refuseOnVector(this._def, "referencesInstallation");
275
359
  this._def.references = { table: "auth.installations", column: "id" };
276
360
  this._def.onDeleteAction = onDelete;
277
361
  return new _ColumnBuilder(this._def.type, this._def);
@@ -283,6 +367,7 @@ var ColumnBuilder = class _ColumnBuilder {
283
367
  }
284
368
  /** Add a single-column UNIQUE constraint. */
285
369
  unique() {
370
+ refuseOnVector(this._def, "unique");
286
371
  this._def.unique = true;
287
372
  return new _ColumnBuilder(this._def.type, this._def);
288
373
  }
@@ -314,12 +399,36 @@ function enumType(name, values) {
314
399
  builder._def.enumValues = [...values];
315
400
  return builder;
316
401
  }
402
+ function vector(dimensions) {
403
+ if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 2e3) {
404
+ throw new Error(`vector(): dimensions must be an integer in [1, 2000], got ${String(dimensions)}`);
405
+ }
406
+ const b = new ColumnBuilder("vector");
407
+ b._def.dimensions = dimensions;
408
+ return b;
409
+ }
317
410
 
318
411
  // src/db/raw.ts
319
412
  function raw(name, up, opts) {
320
413
  return { name, up, ...opts?.down != null ? { down: opts.down } : {} };
321
414
  }
322
415
 
416
+ // src/db/embedding.ts
417
+ var openai = {
418
+ embedding(model, opts) {
419
+ return {
420
+ provider: "openai",
421
+ model,
422
+ apiKeyName: opts?.apiKeyName ?? "OPENAI_API_KEY",
423
+ ...opts?.dimensions !== void 0 ? { dimensions: opts.dimensions } : {},
424
+ ...opts?.baseURL !== void 0 ? { baseURL: opts.baseURL } : {}
425
+ };
426
+ },
427
+ chat(model) {
428
+ return { provider: "openai", model };
429
+ }
430
+ };
431
+
323
432
  // src/db/tx-plan.ts
324
433
  var TxRefError = class extends Error {
325
434
  constructor(message) {
@@ -783,10 +892,12 @@ function makeTypedDB(schema, raw2) {
783
892
  jsonb,
784
893
  makeTypedDB,
785
894
  now,
895
+ openai,
786
896
  policy,
787
897
  raw,
788
898
  text,
789
899
  timestamp,
790
- uuid
900
+ uuid,
901
+ vector
791
902
  });
792
903
  //# sourceMappingURL=index.cjs.map