@palbase/backend 24.2.0 → 25.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 (61) hide show
  1. package/dist/bin/palbase-backend.cjs +101 -60
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +17 -13
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-EIXCY4SS.js → chunk-43A3KGWL.js} +80 -49
  6. package/dist/chunk-43A3KGWL.js.map +1 -0
  7. package/dist/{chunk-ERDL5VAE.js → chunk-5CMLOAEF.js} +2 -2
  8. package/dist/chunk-OEQBHE2Z.js +825 -0
  9. package/dist/chunk-OEQBHE2Z.js.map +1 -0
  10. package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
  11. package/dist/chunk-XJ2RSHEU.js.map +1 -0
  12. package/dist/{chunk-UWSYTUGM.js → chunk-ZQRWW37O.js} +44 -1
  13. package/dist/chunk-ZQRWW37O.js.map +1 -0
  14. package/dist/db/env.cjs.map +1 -1
  15. package/dist/db/env.d.cts +29 -13
  16. package/dist/db/env.d.ts +29 -13
  17. package/dist/db/index.cjs +233 -110
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +1 -1
  20. package/dist/db/index.d.ts +1 -1
  21. package/dist/db/index.js +11 -1
  22. package/dist/engine/index.cjs +87 -50
  23. package/dist/engine/index.cjs.map +1 -1
  24. package/dist/engine/index.d.cts +2 -2
  25. package/dist/engine/index.d.ts +2 -2
  26. package/dist/engine/index.js +3 -3
  27. package/dist/{index-C0PMn5jl.d.ts → index-BF1f0DfA.d.ts} +5 -2
  28. package/dist/{index-DAwHMppB.d.cts → index-CoaDN9dL.d.cts} +5 -2
  29. package/dist/{index-ByBMibIJ.d.ts → index-Ct1iiB4N.d.ts} +232 -60
  30. package/dist/{index-D4rts8T7.d.cts → index-CwaWRhyc.d.cts} +232 -60
  31. package/dist/index.cjs +572 -296
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +124 -20
  34. package/dist/index.d.ts +124 -20
  35. package/dist/index.js +164 -216
  36. package/dist/index.js.map +1 -1
  37. package/dist/openapi/index.cjs +100 -36
  38. package/dist/openapi/index.cjs.map +1 -1
  39. package/dist/openapi/index.js +59 -2
  40. package/dist/openapi/index.js.map +1 -1
  41. package/docs/README.md +64 -31
  42. package/docs/endpoints.md +25 -28
  43. package/docs/llms-full.txt +465 -148
  44. package/docs/schema.md +338 -86
  45. package/docs/services.md +39 -4
  46. package/package.json +1 -1
  47. package/template/AGENTS.md +119 -314
  48. package/template/CLAUDE.md +13 -0
  49. package/template/controllers/notes.controller.ts +6 -13
  50. package/template/db/public.ts +38 -0
  51. package/template/models/notes/create.ts +38 -0
  52. package/template/package.json +6 -3
  53. package/template/services/note.service.test.ts +45 -0
  54. package/template/services/note.service.ts +2 -2
  55. package/dist/chunk-7Z6MGMXQ.js.map +0 -1
  56. package/dist/chunk-EIXCY4SS.js.map +0 -1
  57. package/dist/chunk-LCL7TUAI.js +0 -534
  58. package/dist/chunk-LCL7TUAI.js.map +0 -1
  59. package/dist/chunk-UWSYTUGM.js.map +0 -1
  60. package/template/db/schema.ts +0 -35
  61. /package/dist/{chunk-ERDL5VAE.js.map → chunk-5CMLOAEF.js.map} +0 -0
@@ -3,13 +3,13 @@ import {
3
3
  BootRefused,
4
4
  createApp,
5
5
  loadConfig
6
- } from "../chunk-EIXCY4SS.js";
7
- import "../chunk-7Z6MGMXQ.js";
6
+ } from "../chunk-43A3KGWL.js";
7
+ import "../chunk-XJ2RSHEU.js";
8
8
  import "../chunk-XABBC7JP.js";
9
9
  import "../chunk-P2Q27SGP.js";
10
10
  import {
11
11
  getRegisteredControllers
12
- } from "../chunk-UWSYTUGM.js";
12
+ } from "../chunk-ZQRWW37O.js";
13
13
  import "../chunk-7D4SUZUM.js";
14
14
 
15
15
  // src/bin/palbase-backend.ts
@@ -70,16 +70,20 @@ async function findControllers(root) {
70
70
  await walk(dir);
71
71
  return out.sort();
72
72
  }
73
- async function importSchema(root) {
74
- for (const candidate of ["db/schema.ts", "db/schema.js"]) {
75
- const path = join(root, candidate);
76
- try {
77
- await stat(path);
78
- return (await import(pathToFileURL(path).href)).default;
79
- } catch {
80
- }
73
+ async function importSchemas(root) {
74
+ let entries;
75
+ try {
76
+ entries = (await readdir(join(root, "db"))).sort();
77
+ } catch {
78
+ return [];
79
+ }
80
+ const out = [];
81
+ for (const entry of entries) {
82
+ if (!entry.endsWith(".ts") && !entry.endsWith(".js")) continue;
83
+ if (entry.endsWith(".d.ts")) continue;
84
+ out.push((await import(pathToFileURL(join(root, "db", entry)).href)).default);
81
85
  }
82
- return void 0;
86
+ return out;
83
87
  }
84
88
  async function main() {
85
89
  const command = process.argv[2] ?? "serve";
@@ -145,7 +149,7 @@ Put them in a .env beside package.json, or export them. A local stack
145
149
  }
146
150
  throw e;
147
151
  }
148
- const app = await createApp({ config, controllers, schema: await importSchema(root) });
152
+ const app = await createApp({ config, controllers, schemas: await importSchemas(root) });
149
153
  if (command === "routes") {
150
154
  for (const route of app.routes) console.log(route.id);
151
155
  await app.shutdown();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/palbase-backend.ts"],"sourcesContent":["#!/usr/bin/env bun\n/**\n * palbase-backend — run this project's backend.\n *\n * npm run dev → palbase-backend dev (reloads on change)\n * npm start → palbase-backend serve\n *\n * There is no scaffolding step and no server file to write. The project IS the\n * backend: every `controllers/*.controller.ts` is imported, which is what\n * registers it, and the engine is built around whatever registered.\n *\n * It is the same engine the deployed runtime builds — not a development\n * stand-in. A dev server that is a different program from the production one is\n * a source of \"works locally\" reports, and this one has no second implementation\n * to disagree with.\n */\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { createApp, loadConfig, BootRefused } from \"../engine/index.js\";\nimport { getRegisteredControllers } from \"../decorators/controller.js\";\n\nconst USAGE = `palbase-backend — run this project's backend\n\n palbase-backend serve start the backend\n palbase-backend dev start it and reload on change\n palbase-backend routes print the route table and exit\n\nConfiguration comes from the environment (a .env beside package.json is read):\n\n DATABASE_URL required — the stack's Postgres\n AUTH_JWKS_URL required — where your stack publishes its signing keys\n MODULE_BASE_URL where Documents/Storage/Notifications/Flags/Realtime live\n PALBASE_ANON_KEY publishable key, sent on module calls\n PALBASE_SERVICE_ROLE_KEY secret key, for privileged module calls\n PORT default 3000\n`;\n\nfunction die(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\n/** Read a `.env` beside the project, without adding a dependency for it. */\nasync function loadDotEnv(root: string): Promise<void> {\n const file = Bun.file(join(root, \".env\"));\n if (!(await file.exists())) return;\n for (const raw of (await file.text()).split(\"\\n\")) {\n const line = raw.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq < 1) continue;\n const key = line.slice(0, eq).trim();\n // The environment wins: an exported value is the operator being explicit,\n // and a file quietly overriding it is how a \"why is it still pointing at\n // the old database\" hour begins.\n if (process.env[key] !== undefined) continue;\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n process.env[key] = value;\n }\n}\n\n/** Every `*.controller.ts` under `controllers/`, sorted, recursively. */\nasync function findControllers(root: string): Promise<string[]> {\n const dir = join(root, \"controllers\");\n try {\n if (!(await stat(dir)).isDirectory()) return [];\n } catch {\n return [];\n }\n const out: string[] = [];\n const walk = async (d: string): Promise<void> => {\n for (const entry of await readdir(d, { withFileTypes: true })) {\n const full = join(d, entry.name);\n if (entry.isDirectory()) await walk(full);\n else if (/\\.controller\\.(ts|js|mts|mjs)$/.test(entry.name)) out.push(full);\n }\n };\n await walk(dir);\n // Sorted so route precedence is the same on every machine.\n return out.sort();\n}\n\nasync function importSchema(root: string): Promise<unknown> {\n for (const candidate of [\"db/schema.ts\", \"db/schema.js\"]) {\n const path = join(root, candidate);\n try {\n await stat(path);\n return (await import(pathToFileURL(path).href)).default;\n } catch {\n // Not every project declares a schema; the typed `.tables` surface is\n // simply absent then.\n }\n }\n return undefined;\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2] ?? \"serve\";\n if (command === \"--help\" || command === \"-h\" || command === \"help\") {\n console.log(USAGE);\n return;\n }\n if (![\"serve\", \"dev\", \"routes\"].includes(command)) {\n die(`palbase-backend: unknown command \"${command}\".\\n\\n${USAGE}`);\n }\n if (typeof Bun === \"undefined\") {\n die(\n \"palbase-backend needs Bun: the engine serves `fetch` natively and opens its own\\n\" +\n \"Postgres pool through Bun.sql. Install it from https://bun.sh, then re-run.\",\n );\n }\n\n // `dev` is `serve` under Bun's watcher. Re-exec rather than reimplement:\n // one code path serves, and the reload is the runtime's job, not ours.\n if (command === \"dev\" && !process.env.PALBASE_WATCHING) {\n const self = Bun.fileURLToPath(import.meta.url);\n const child = Bun.spawn([\"bun\", \"--watch\", self, \"serve\"], {\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n env: { ...process.env, PALBASE_WATCHING: \"1\" },\n });\n process.exit(await child.exited);\n }\n\n const root = resolve(process.env.PALBASE_PROJECT_DIR ?? process.cwd());\n await loadDotEnv(root);\n\n const files = await findControllers(root);\n if (files.length === 0) {\n die(\n `palbase-backend: no controllers found under ${join(root, \"controllers\")}.\\n` +\n `A backend is its controllers — add one and run again:\\n\\n` +\n ` // controllers/hello.controller.ts\\n` +\n ` import { Controller, Get } from \"@palbase/backend\";\\n` +\n ` @Controller(\"/hello\")\\n` +\n ` class HelloController {\\n` +\n ` @Get(\"\") hi(): Promise<{ ok: boolean }> { return Promise.resolve({ ok: true }); }\\n` +\n ` }\\n`,\n );\n }\n\n // Importing IS the registration — the decorator records each class as it runs.\n for (const file of files) await import(pathToFileURL(file).href);\n const controllers = getRegisteredControllers();\n if (controllers.length === 0) {\n die(\n `palbase-backend: ${files.length} controller file(s) loaded but none registered.\\n` +\n `Every one of them is missing its @Controller decorator, or the files were\\n` +\n `compiled with decorators stripped. Nothing would answer, so this is fatal.`,\n );\n }\n\n let config;\n try {\n config = loadConfig(process.env as Record<string, string | undefined>);\n } catch (e) {\n if (e instanceof BootRefused) {\n die(\n `${e.message}\\n\\n` +\n `Put them in a .env beside package.json, or export them. A local stack\\n` +\n `(docker compose up) publishes both.`,\n );\n }\n throw e;\n }\n\n const app = await createApp({ config, controllers, schema: await importSchema(root) });\n\n if (command === \"routes\") {\n for (const route of app.routes) console.log(route.id);\n await app.shutdown();\n return;\n }\n\n Bun.serve({ port: config.port, idleTimeout: 60, fetch: app.handle });\n console.log(`palbase-backend listening on http://localhost:${config.port}`);\n console.log(` ${app.routes.length} endpoint(s) from ${files.length} controller file(s)`);\n for (const route of app.routes) console.log(` ${route.id}`);\n\n for (const signal of [\"SIGTERM\", \"SIGINT\"] as const) {\n process.on(signal, () => {\n void app.shutdown().finally(() => process.exit(0));\n });\n }\n}\n\n// Not a top-level await: this file is also emitted in a CommonJS flavour, where\n// one is a build error. The rejection handler is the point either way — an\n// unhandled one exits 0 on some runtimes, which would report a dead backend as\n// a successful start.\nmain().catch((e: unknown) => {\n console.error(e instanceof Error ? e.message : e);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/bin/palbase-backend.ts"],"sourcesContent":["#!/usr/bin/env bun\n/**\n * palbase-backend — run this project's backend.\n *\n * npm run dev → palbase-backend dev (reloads on change)\n * npm start → palbase-backend serve\n *\n * There is no scaffolding step and no server file to write. The project IS the\n * backend: every `controllers/*.controller.ts` is imported, which is what\n * registers it, and the engine is built around whatever registered.\n *\n * It is the same engine the deployed runtime builds — not a development\n * stand-in. A dev server that is a different program from the production one is\n * a source of \"works locally\" reports, and this one has no second implementation\n * to disagree with.\n */\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { createApp, loadConfig, BootRefused } from \"../engine/index.js\";\nimport { getRegisteredControllers } from \"../decorators/controller.js\";\n\nconst USAGE = `palbase-backend — run this project's backend\n\n palbase-backend serve start the backend\n palbase-backend dev start it and reload on change\n palbase-backend routes print the route table and exit\n\nConfiguration comes from the environment (a .env beside package.json is read):\n\n DATABASE_URL required — the stack's Postgres\n AUTH_JWKS_URL required — where your stack publishes its signing keys\n MODULE_BASE_URL where Documents/Storage/Notifications/Flags/Realtime live\n PALBASE_ANON_KEY publishable key, sent on module calls\n PALBASE_SERVICE_ROLE_KEY secret key, for privileged module calls\n PORT default 3000\n`;\n\nfunction die(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\n/** Read a `.env` beside the project, without adding a dependency for it. */\nasync function loadDotEnv(root: string): Promise<void> {\n const file = Bun.file(join(root, \".env\"));\n if (!(await file.exists())) return;\n for (const raw of (await file.text()).split(\"\\n\")) {\n const line = raw.trim();\n if (!line || line.startsWith(\"#\")) continue;\n const eq = line.indexOf(\"=\");\n if (eq < 1) continue;\n const key = line.slice(0, eq).trim();\n // The environment wins: an exported value is the operator being explicit,\n // and a file quietly overriding it is how a \"why is it still pointing at\n // the old database\" hour begins.\n if (process.env[key] !== undefined) continue;\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n process.env[key] = value;\n }\n}\n\n/** Every `*.controller.ts` under `controllers/`, sorted, recursively. */\nasync function findControllers(root: string): Promise<string[]> {\n const dir = join(root, \"controllers\");\n try {\n if (!(await stat(dir)).isDirectory()) return [];\n } catch {\n return [];\n }\n const out: string[] = [];\n const walk = async (d: string): Promise<void> => {\n for (const entry of await readdir(d, { withFileTypes: true })) {\n const full = join(d, entry.name);\n if (entry.isDirectory()) await walk(full);\n else if (/\\.controller\\.(ts|js|mts|mjs)$/.test(entry.name)) out.push(full);\n }\n };\n await walk(dir);\n // Sorted so route precedence is the same on every machine.\n return out.sort();\n}\n\nasync function importSchemas(root: string): Promise<unknown[]> {\n // ONE SCHEMA PER FILE, so the whole directory is the declaration. Reading a\n // single well-known file would take the first one and leave the rest of the\n // project's tables with no typed surface and no complaint.\n //\n // SORTED, because readdir order is not specified: an unsorted set would hand\n // the runtime a different answer from one boot to the next.\n let entries: string[];\n try {\n entries = (await readdir(join(root, \"db\"))).sort();\n } catch {\n // Not every project declares a database; the typed `.tables` surface is\n // simply absent then.\n return [];\n }\n const out: unknown[] = [];\n for (const entry of entries) {\n if (!entry.endsWith(\".ts\") && !entry.endsWith(\".js\")) continue;\n if (entry.endsWith(\".d.ts\")) continue;\n out.push((await import(pathToFileURL(join(root, \"db\", entry)).href)).default);\n }\n return out;\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2] ?? \"serve\";\n if (command === \"--help\" || command === \"-h\" || command === \"help\") {\n console.log(USAGE);\n return;\n }\n if (![\"serve\", \"dev\", \"routes\"].includes(command)) {\n die(`palbase-backend: unknown command \"${command}\".\\n\\n${USAGE}`);\n }\n if (typeof Bun === \"undefined\") {\n die(\n \"palbase-backend needs Bun: the engine serves `fetch` natively and opens its own\\n\" +\n \"Postgres pool through Bun.sql. Install it from https://bun.sh, then re-run.\",\n );\n }\n\n // `dev` is `serve` under Bun's watcher. Re-exec rather than reimplement:\n // one code path serves, and the reload is the runtime's job, not ours.\n if (command === \"dev\" && !process.env.PALBASE_WATCHING) {\n const self = Bun.fileURLToPath(import.meta.url);\n const child = Bun.spawn([\"bun\", \"--watch\", self, \"serve\"], {\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n env: { ...process.env, PALBASE_WATCHING: \"1\" },\n });\n process.exit(await child.exited);\n }\n\n const root = resolve(process.env.PALBASE_PROJECT_DIR ?? process.cwd());\n await loadDotEnv(root);\n\n const files = await findControllers(root);\n if (files.length === 0) {\n die(\n `palbase-backend: no controllers found under ${join(root, \"controllers\")}.\\n` +\n `A backend is its controllers — add one and run again:\\n\\n` +\n ` // controllers/hello.controller.ts\\n` +\n ` import { Controller, Get } from \"@palbase/backend\";\\n` +\n ` @Controller(\"/hello\")\\n` +\n ` class HelloController {\\n` +\n ` @Get(\"\") hi(): Promise<{ ok: boolean }> { return Promise.resolve({ ok: true }); }\\n` +\n ` }\\n`,\n );\n }\n\n // Importing IS the registration — the decorator records each class as it runs.\n for (const file of files) await import(pathToFileURL(file).href);\n const controllers = getRegisteredControllers();\n if (controllers.length === 0) {\n die(\n `palbase-backend: ${files.length} controller file(s) loaded but none registered.\\n` +\n `Every one of them is missing its @Controller decorator, or the files were\\n` +\n `compiled with decorators stripped. Nothing would answer, so this is fatal.`,\n );\n }\n\n let config;\n try {\n config = loadConfig(process.env as Record<string, string | undefined>);\n } catch (e) {\n if (e instanceof BootRefused) {\n die(\n `${e.message}\\n\\n` +\n `Put them in a .env beside package.json, or export them. A local stack\\n` +\n `(docker compose up) publishes both.`,\n );\n }\n throw e;\n }\n\n const app = await createApp({ config, controllers, schemas: await importSchemas(root) });\n\n if (command === \"routes\") {\n for (const route of app.routes) console.log(route.id);\n await app.shutdown();\n return;\n }\n\n Bun.serve({ port: config.port, idleTimeout: 60, fetch: app.handle });\n console.log(`palbase-backend listening on http://localhost:${config.port}`);\n console.log(` ${app.routes.length} endpoint(s) from ${files.length} controller file(s)`);\n for (const route of app.routes) console.log(` ${route.id}`);\n\n for (const signal of [\"SIGTERM\", \"SIGINT\"] as const) {\n process.on(signal, () => {\n void app.shutdown().finally(() => process.exit(0));\n });\n }\n}\n\n// Not a top-level await: this file is also emitted in a CommonJS flavour, where\n// one is a build error. The rejection handler is the point either way — an\n// unhandled one exits 0 on some runtimes, which would report a dead backend as\n// a successful start.\nmain().catch((e: unknown) => {\n console.error(e instanceof Error ? e.message : e);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  __requestALS,
3
3
  __runStartHooks,
4
4
  __runWithRuntime
5
- } from "./chunk-7Z6MGMXQ.js";
5
+ } from "./chunk-XJ2RSHEU.js";
6
6
  import {
7
7
  assertUsableFilter,
8
8
  assertUsableWriteValues
@@ -15,7 +15,7 @@ import {
15
15
  isHttpError,
16
16
  markEngineRaised,
17
17
  resolveEffectiveAuth
18
- } from "./chunk-UWSYTUGM.js";
18
+ } from "./chunk-ZQRWW37O.js";
19
19
 
20
20
  // src/engine/config.ts
21
21
  var BootRefused = class extends Error {
@@ -333,6 +333,11 @@ function makeMemoryCache(opts = {}) {
333
333
  function quoteIdent(name) {
334
334
  return `"${name.replace(/"/g, '""')}"`;
335
335
  }
336
+ function quoteTable(key) {
337
+ const dot = key.indexOf(".");
338
+ if (dot === -1) return quoteIdent(key);
339
+ return `${quoteIdent(key.slice(0, dot))}.${quoteIdent(key.slice(dot + 1))}`;
340
+ }
336
341
  function encodePgArray(value) {
337
342
  const element = (x) => {
338
343
  if (x === null || x === void 0) return "NULL";
@@ -405,8 +410,8 @@ function toVectorLiteral(v) {
405
410
  }
406
411
  function vectorColumnsOf(schema, table) {
407
412
  const out = /* @__PURE__ */ new Set();
408
- for (const [key, def] of Object.entries(schema.tables ?? {})) {
409
- if ((def.name ?? key) !== table) continue;
413
+ const def = tableDefOf(table, schema);
414
+ if (def) {
410
415
  for (const [col, c] of Object.entries(def.columns ?? {})) {
411
416
  const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
412
417
  if (d !== null && typeof d === "object" && d.type === "vector") out.add(col);
@@ -414,6 +419,9 @@ function vectorColumnsOf(schema, table) {
414
419
  }
415
420
  return out;
416
421
  }
422
+ function tableDefOf(key, schema = currentSchema) {
423
+ return schema.tables?.[key] ?? null;
424
+ }
417
425
  function reviveVectors(row, vectorCols) {
418
426
  if (row === null || typeof row !== "object" || vectorCols.size === 0) return row;
419
427
  const out = row;
@@ -425,8 +433,8 @@ function reviveVectors(row, vectorCols) {
425
433
  }
426
434
  function transformsOf(schema, table) {
427
435
  const out = /* @__PURE__ */ new Map();
428
- for (const [key, def] of Object.entries(schema.tables ?? {})) {
429
- if ((def.name ?? key) !== table) continue;
436
+ const def = tableDefOf(table, schema);
437
+ if (def) {
430
438
  for (const [col, c] of Object.entries(def.columns ?? {})) {
431
439
  const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
432
440
  const t = d?.transform;
@@ -475,7 +483,7 @@ var METRIC_OPERATOR = {
475
483
  inner_product: "<#>"
476
484
  };
477
485
  function searchConfigFor(table) {
478
- const t = currentSchema.tables?.[table];
486
+ const t = tableDefOf(table);
479
487
  if (!t) return null;
480
488
  const columns = t.columns ?? {};
481
489
  const defOf = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
@@ -612,7 +620,7 @@ async function fetchFacets(live, table, colSet, facets, where, extraWhere = () =
612
620
  };
613
621
  const whereSql = compileWhere(table, colSet, where, add) + extraWhere(add);
614
622
  const parts = facets.map(
615
- (col) => `(SELECT '${col.replace(/'/g, "''")}' AS f, t.${quoteIdent(col)}::text AS v, count(*) AS n FROM ${quoteIdent(table)} t WHERE true${whereSql} GROUP BY 2 ORDER BY 3 DESC LIMIT 20)`
623
+ (col) => `(SELECT '${col.replace(/'/g, "''")}' AS f, t.${quoteIdent(col)}::text AS v, count(*) AS n FROM ${quoteTable(table)} t WHERE true${whereSql} GROUP BY 2 ORDER BY 3 DESC LIMIT 20)`
616
624
  );
617
625
  const rows = await live.unsafe(parts.join(" UNION ALL "), bind);
618
626
  const out = {};
@@ -638,7 +646,7 @@ function excludeSql(pk, ids, add) {
638
646
  return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
639
647
  }
640
648
  function schemaColumns(table) {
641
- const t = Object.values(currentSchema.tables ?? {}).find((x) => x?.name === table);
649
+ const t = tableDefOf(table);
642
650
  const cols = t?.columns ? Object.keys(t.columns) : [];
643
651
  return cols.length > 0 ? new Set(cols) : null;
644
652
  }
@@ -799,7 +807,7 @@ function createOps(tx) {
799
807
  const cols = Object.keys(data);
800
808
  if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
801
809
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
802
- const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
810
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
803
811
  const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "insert"));
804
812
  const inserted = rows[0];
805
813
  if (!inserted) {
@@ -832,7 +840,7 @@ function createOps(tx) {
832
840
  if (cols.length === 0) throw new Error(`upsert into ${table}: no columns given`);
833
841
  if (searchConfigFor(table)?.validity) {
834
842
  const ph = cols.map((_, i) => `$${i + 1}`).join(", ");
835
- const insertSql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
843
+ const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
836
844
  const live = await at();
837
845
  const bindData = asBindParams(table, cols, data, "upsert");
838
846
  const first = await live.unsafe(insertSql, bindData);
@@ -840,7 +848,7 @@ function createOps(tx) {
840
848
  const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
841
849
  const condBind = conflict.map((c) => data[c]);
842
850
  const cur = await live.unsafe(
843
- `SELECT * FROM ${quoteIdent(table)} WHERE ${condSql} AND "valid_to" IS NULL`,
851
+ `SELECT * FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`,
844
852
  condBind
845
853
  );
846
854
  const old = cur[0];
@@ -849,7 +857,7 @@ function createOps(tx) {
849
857
  }
850
858
  const pk = Object.keys(old).includes("id") ? "id" : Object.keys(old)[0];
851
859
  await live.unsafe(
852
- `UPDATE ${quoteIdent(table)} SET "valid_to" = now() WHERE ${quoteIdent(pk)} = $1 AND "valid_to" IS NULL`,
860
+ `UPDATE ${quoteTable(table)} SET "valid_to" = now() WHERE ${quoteIdent(pk)} = $1 AND "valid_to" IS NULL`,
853
861
  [old[pk]]
854
862
  );
855
863
  const second = await live.unsafe(insertSql, bindData);
@@ -858,7 +866,7 @@ function createOps(tx) {
858
866
  }
859
867
  const fresh = second[0];
860
868
  await live.unsafe(
861
- `UPDATE ${quoteIdent(table)} SET "superseded_by" = $2 WHERE ${quoteIdent(pk)} = $1`,
869
+ `UPDATE ${quoteTable(table)} SET "superseded_by" = $2 WHERE ${quoteIdent(pk)} = $1`,
862
870
  [old[pk], fresh[pk]]
863
871
  );
864
872
  return asTableRow(table, second[0]);
@@ -867,7 +875,7 @@ function createOps(tx) {
867
875
  const conflictSet = new Set(conflict);
868
876
  const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
869
877
  const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
870
- const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
878
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
871
879
  const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data, "upsert"));
872
880
  const row = rows[0];
873
881
  if (!row) {
@@ -881,12 +889,12 @@ function createOps(tx) {
881
889
  const cols = Object.keys(data);
882
890
  if (cols.length === 0) return ops.findById(table, id);
883
891
  const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
884
- const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
892
+ const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
885
893
  const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data, "update"), id]);
886
894
  return rows[0] ? asTableRow(table, rows[0]) : null;
887
895
  },
888
896
  async delete(table, id) {
889
- await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
897
+ await (await at()).unsafe(`DELETE FROM ${quoteTable(table)} WHERE id = $1`, [id]);
890
898
  },
891
899
  /**
892
900
  * Update every row the filter matches, in ONE statement.
@@ -929,7 +937,7 @@ function createOps(tx) {
929
937
  const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set, "updateMany")[0])}`).join(", ");
930
938
  const whereSql = compileWhere(table, known, where, add, "updateMany");
931
939
  assertHasPredicate(whereSql, "updateMany", table);
932
- const sql = `UPDATE ${quoteIdent(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *`;
940
+ const sql = `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *`;
933
941
  return asTableRows(table, await (await at()).unsafe(sql, params));
934
942
  },
935
943
  /**
@@ -946,7 +954,7 @@ function createOps(tx) {
946
954
  const known = schemaColumns(table);
947
955
  const whereSql = compileWhere(table, known, where, add, "deleteMany");
948
956
  assertHasPredicate(whereSql, "deleteMany", table);
949
- const sql = `DELETE FROM ${quoteIdent(table)} AS t WHERE true${whereSql} RETURNING 1`;
957
+ const sql = `DELETE FROM ${quoteTable(table)} AS t WHERE true${whereSql} RETURNING 1`;
950
958
  const rows = await (await at()).unsafe(sql, params);
951
959
  return rows.length;
952
960
  },
@@ -967,13 +975,13 @@ function createOps(tx) {
967
975
  };
968
976
  const known = schemaColumns(table);
969
977
  const whereSql = compileWhere(table, known, where, add, "count");
970
- const sql = `SELECT count(*) AS n FROM ${quoteIdent(table)} t WHERE true${whereSql}`;
978
+ const sql = `SELECT count(*) AS n FROM ${quoteTable(table)} t WHERE true${whereSql}`;
971
979
  const rows = await (await at()).unsafe(sql, params);
972
980
  return Number(rows[0]?.n ?? 0);
973
981
  },
974
982
  async findById(table, id) {
975
983
  const rows = await (await at()).unsafe(
976
- `SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
984
+ `SELECT * FROM ${quoteTable(table)} WHERE id = $1`,
977
985
  [id]
978
986
  );
979
987
  return rows[0] ? asTableRow(table, rows[0]) : null;
@@ -989,7 +997,7 @@ function createOps(tx) {
989
997
  const order = orderClause(table, known, opts.orderBy);
990
998
  const limit = limitClause(opts.limit);
991
999
  const offset = offsetClause(opts.offset, opts.limit);
992
- const sql = `SELECT * FROM ${quoteIdent(table)} t WHERE true${whereSql}${order}${limit}${offset}`;
1000
+ const sql = `SELECT * FROM ${quoteTable(table)} t WHERE true${whereSql}${order}${limit}${offset}`;
993
1001
  return asTableRows(table, await (await at()).unsafe(sql, params));
994
1002
  },
995
1003
  /**
@@ -1074,7 +1082,7 @@ function createOps(tx) {
1074
1082
  };
1075
1083
  const userWhereC = compileWhere(table, cfg.colSet, params.where ?? {}, addC);
1076
1084
  const whereC = userWhereC + excludeSql(cfg.pk, excl, addC) + validityFor(addC);
1077
- const parentJoinC = ` JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = c.${pidQ} WHERE true${whereC} AND `;
1085
+ const parentJoinC = ` JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = c.${pidQ} WHERE true${whereC} AND `;
1078
1086
  const liveC = await at();
1079
1087
  const K2 = 50;
1080
1088
  const cpool = Math.max(limit * 9, 90);
@@ -1118,7 +1126,7 @@ function createOps(tx) {
1118
1126
  } else {
1119
1127
  fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
1120
1128
  }
1121
- const inner = `${fusedSrc}, ranked AS (SELECT f.pid, f.chunk_seq, f.cscore, ROW_NUMBER() OVER (PARTITION BY f.pid ORDER BY f.cscore DESC, f.chunk_seq) AS rn FROM fused f), grouped AS (SELECT r.pid, MAX(r.cscore) AS _score, json_agg(json_build_object('content', c.content, 'score', r.cscore, 'chunkSeq', r.chunk_seq, 'charStart', c.char_start) ORDER BY r.cscore DESC, r.chunk_seq) FILTER (WHERE r.rn <= ${bpr}) AS blocks FROM ranked r JOIN ${quoteIdent(ck.table)} c ON c.${pidQ} = r.pid AND c.chunk_seq = r.chunk_seq GROUP BY r.pid) SELECT ${colListC}, ${scoreC}::float8 AS _score, g.blocks AS blocks${kwnCol} FROM grouped g JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid`;
1129
+ const inner = `${fusedSrc}, ranked AS (SELECT f.pid, f.chunk_seq, f.cscore, ROW_NUMBER() OVER (PARTITION BY f.pid ORDER BY f.cscore DESC, f.chunk_seq) AS rn FROM fused f), grouped AS (SELECT r.pid, MAX(r.cscore) AS _score, json_agg(json_build_object('content', c.content, 'score', r.cscore, 'chunkSeq', r.chunk_seq, 'charStart', c.char_start) ORDER BY r.cscore DESC, r.chunk_seq) FILTER (WHERE r.rn <= ${bpr}) AS blocks FROM ranked r JOIN ${quoteIdent(ck.table)} c ON c.${pidQ} = r.pid AND c.chunk_seq = r.chunk_seq GROUP BY r.pid) SELECT ${colListC}, ${scoreC}::float8 AS _score, g.blocks AS blocks${kwnCol} FROM grouped g JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid`;
1122
1130
  return msC === "" ? `SELECT * FROM (${inner}) q ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT * FROM (${inner}) q WHERE q._score >= ${msC} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
1123
1131
  };
1124
1132
  let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
@@ -1175,7 +1183,7 @@ function createOps(tx) {
1175
1183
  let exactOrder = "";
1176
1184
  if (userWhere !== "") {
1177
1185
  const probeRows = await live.unsafe(
1178
- `SELECT count(*)::int AS n FROM (SELECT 1 FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} LIMIT ${SELECTIVITY_EXACT_THRESHOLD + 1}) s`,
1186
+ `SELECT count(*)::int AS n FROM (SELECT 1 FROM ${quoteTable(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} LIMIT ${SELECTIVITY_EXACT_THRESHOLD + 1}) s`,
1179
1187
  bind.slice()
1180
1188
  );
1181
1189
  const n = probeRows?.[0]?.n;
@@ -1184,7 +1192,7 @@ function createOps(tx) {
1184
1192
  }
1185
1193
  }
1186
1194
  const vp = add(toVectorLiteral(qv));
1187
- semSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY (t.${quoteIdent(leg.column)} OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)${exactOrder}) AS r FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} ORDER BY r LIMIT ${pool}`;
1195
+ semSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY (t.${quoteIdent(leg.column)} OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)${exactOrder}) AS r FROM ${quoteTable(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} ORDER BY r LIMIT ${pool}`;
1188
1196
  }
1189
1197
  let qpRef = "";
1190
1198
  let qpIdx = -1;
@@ -1193,7 +1201,7 @@ function createOps(tx) {
1193
1201
  const qp = add(qText);
1194
1202
  qpIdx = bind.length - 1;
1195
1203
  qpRef = qp;
1196
- kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteIdent(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
1204
+ kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteTable(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
1197
1205
  }
1198
1206
  const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
1199
1207
  const msP = params.minScore === void 0 ? "" : add(params.minScore);
@@ -1204,14 +1212,14 @@ function createOps(tx) {
1204
1212
  let orderScore;
1205
1213
  if (semSql !== "" && kwIn !== "") {
1206
1214
  const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
1207
- inner = `WITH sem AS (${semSql}), kw AS (${kwIn}), fused AS (SELECT COALESCE(sem.id, kw.id) AS id, (COALESCE(1.0/(${K} + sem.r), 0) + COALESCE(1.0/(${K} + kw.r), 0))::float8 AS _score FROM sem FULL OUTER JOIN kw ON sem.id = kw.id) SELECT ${colList}, ${score} AS _score${hlCol}${kwnCol} FROM fused JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id`;
1215
+ inner = `WITH sem AS (${semSql}), kw AS (${kwIn}), fused AS (SELECT COALESCE(sem.id, kw.id) AS id, (COALESCE(1.0/(${K} + sem.r), 0) + COALESCE(1.0/(${K} + kw.r), 0))::float8 AS _score FROM sem FULL OUTER JOIN kw ON sem.id = kw.id) SELECT ${colList}, ${score} AS _score${hlCol}${kwnCol} FROM fused JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id`;
1208
1216
  orderScore = scoreMul === "" ? "fused._score" : "_score";
1209
1217
  } else {
1210
1218
  const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
1211
1219
  const alias = semSql !== "" ? "sem" : "kw";
1212
1220
  const base = `(1.0/(${K} + ${alias}.r))::float8`;
1213
1221
  const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
1214
- inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
1222
+ inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
1215
1223
  orderScore = "_score";
1216
1224
  }
1217
1225
  return msP === "" ? `${inner} ORDER BY ${orderScore} DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT * FROM (${inner}) q WHERE q._score >= ${msP} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
@@ -1220,7 +1228,7 @@ function createOps(tx) {
1220
1228
  if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
1221
1229
  const tsch = await trgmSchemaOf(live);
1222
1230
  const expr = ftsExprOf(cfg.ftsCols);
1223
- const trgmKw = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) DESC) AS r FROM ${quoteIdent(table)} t WHERE ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) > 0.3${whereSql} ORDER BY r LIMIT ${pool}`;
1231
+ const trgmKw = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) DESC) AS r FROM ${quoteTable(table)} t WHERE ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) > 0.3${whereSql} ORDER BY r LIMIT ${pool}`;
1224
1232
  const retryBind = bind.slice();
1225
1233
  if (qpIdx >= 0) retryBind[qpIdx] = params.query;
1226
1234
  rows = await live.unsafe(assemble(trgmKw, false), retryBind);
@@ -1282,7 +1290,7 @@ function createOps(tx) {
1282
1290
  throw new Error(`similar(${table}): tabloda vekt\xF6r kolonu yok \u2014 benzerlik semantik vekt\xF6r ister (FR-022)`);
1283
1291
  }
1284
1292
  const rows = await live.unsafe(
1285
- `SELECT t.${quoteIdent(leg.column)} AS v FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(cfg.pk)} = $1`,
1293
+ `SELECT t.${quoteIdent(leg.column)} AS v FROM ${quoteTable(table)} t WHERE t.${quoteIdent(cfg.pk)} = $1`,
1286
1294
  [id]
1287
1295
  );
1288
1296
  v = rows?.[0]?.v;
@@ -1339,7 +1347,7 @@ function createOps(tx) {
1339
1347
  };
1340
1348
  const avgSel = (ids) => {
1341
1349
  const inList = ids.map((x) => add(x)).join(", ");
1342
- return cfg.chunk !== void 0 ? `SELECT ${quoteIdent(sch)}.avg(c.embedding) AS v FROM ${quoteIdent(cfg.chunk.table)} c WHERE c.${quoteIdent(`parent_${cfg.pk}`)} IN (${inList}) AND c.embedding IS NOT NULL` : `SELECT ${quoteIdent(sch)}.avg(t.${quoteIdent(leg.column)}) AS v FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(cfg.pk)} IN (${inList}) AND t.${quoteIdent(leg.column)} IS NOT NULL`;
1350
+ return cfg.chunk !== void 0 ? `SELECT ${quoteIdent(sch)}.avg(c.embedding) AS v FROM ${quoteIdent(cfg.chunk.table)} c WHERE c.${quoteIdent(`parent_${cfg.pk}`)} IN (${inList}) AND c.embedding IS NOT NULL` : `SELECT ${quoteIdent(sch)}.avg(t.${quoteIdent(leg.column)}) AS v FROM ${quoteTable(table)} t WHERE t.${quoteIdent(cfg.pk)} IN (${inList}) AND t.${quoteIdent(leg.column)} IS NOT NULL`;
1343
1351
  };
1344
1352
  const targetSql = negative.length === 0 ? `WITH pos AS (${avgSel(positive)}) SELECT pos.v AS v, (pos.v IS NULL) AS pos_missing FROM pos` : `WITH pos AS (${avgSel(positive)}), neg AS (${avgSel(negative)}) SELECT (pos.v OPERATOR(${quoteIdent(sch)}.+) (pos.v OPERATOR(${quoteIdent(sch)}.-) neg.v)) AS v, (pos.v IS NULL) AS pos_missing, (neg.v IS NULL) AS neg_missing FROM pos, neg`;
1345
1353
  const rows = await live.unsafe(targetSql, bind);
@@ -1387,7 +1395,7 @@ function createOps(tx) {
1387
1395
  return live.savepoint(async (sp) => {
1388
1396
  const newId = row[cfg.pk] ?? crypto.randomUUID();
1389
1397
  const closed = await sp.unsafe(
1390
- `UPDATE ${quoteIdent(table)} SET "valid_to" = now(), "superseded_by" = $1 WHERE ${quoteIdent(cfg.pk)} = $2 AND "valid_to" IS NULL RETURNING ${quoteIdent(cfg.pk)}`,
1398
+ `UPDATE ${quoteTable(table)} SET "valid_to" = now(), "superseded_by" = $1 WHERE ${quoteIdent(cfg.pk)} = $2 AND "valid_to" IS NULL RETURNING ${quoteIdent(cfg.pk)}`,
1391
1399
  [newId, id]
1392
1400
  );
1393
1401
  if (closed.length === 0) {
@@ -1397,7 +1405,7 @@ function createOps(tx) {
1397
1405
  const cols = Object.keys(data);
1398
1406
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
1399
1407
  const rows = await sp.unsafe(
1400
- `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`,
1408
+ `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`,
1401
1409
  asBindParams(table, cols, data, "supersede")
1402
1410
  );
1403
1411
  if (!rows[0]) {
@@ -1469,27 +1477,50 @@ function createOps(tx) {
1469
1477
  return ops;
1470
1478
  }
1471
1479
  var currentSchema = {};
1472
- function setSchema(schema) {
1480
+ function setSchema(schemas) {
1473
1481
  cachedVectorSchema = null;
1474
1482
  cachedTrgmSchema = null;
1475
- const s = schema;
1476
- currentSchema = (s && "default" in s ? s.default : s) ?? {};
1483
+ const tables = {};
1484
+ for (const entry of schemas) {
1485
+ const mod = entry;
1486
+ const def = (mod && "default" in mod ? mod.default : mod) ?? {};
1487
+ const schemaName = def.name ?? "public";
1488
+ for (const [key, table] of Object.entries(def.tables ?? {})) {
1489
+ tables[qualifiedTableKey(schemaName, table.name ?? key)] = table;
1490
+ }
1491
+ }
1492
+ currentSchema = { tables };
1493
+ }
1494
+ function qualifiedTableKey(schema, table) {
1495
+ return schema === "" || schema === "public" ? table : `${schema}.${table}`;
1477
1496
  }
1478
1497
  function withTables(ops, schema = currentSchema) {
1498
+ const bind = (key) => ({
1499
+ insert: (data) => ops.insert(key, data),
1500
+ update: (id, data) => ops.update(key, id, data),
1501
+ delete: (id) => ops.delete(key, id),
1502
+ findById: (id) => ops.findById(key, id),
1503
+ findMany: (query, opts) => ops.findMany(key, query ?? {}, opts),
1504
+ upsert: (data, opts) => ops.upsert(key, data, opts)
1505
+ });
1479
1506
  const tables = {};
1480
1507
  for (const key of Object.keys(schema.tables ?? {})) {
1481
- const name = schema.tables?.[key]?.name ?? key;
1482
- tables[key] = {
1483
- insert: (data) => ops.insert(name, data),
1484
- update: (id, data) => ops.update(name, id, data),
1485
- delete: (id) => ops.delete(name, id),
1486
- findById: (id) => ops.findById(name, id),
1487
- findMany: (query, opts) => ops.findMany(name, query ?? {}, opts),
1488
- upsert: (data, opts) => ops.upsert(name, data, opts)
1489
- };
1508
+ if (!key.includes(".")) tables[key] = bind(key);
1490
1509
  }
1510
+ const schemaOf = (name) => {
1511
+ const out = {};
1512
+ const prefix = `${name}.`;
1513
+ for (const key of Object.keys(schema.tables ?? {})) {
1514
+ if (name === "public") {
1515
+ if (!key.includes(".")) out[key] = bind(key);
1516
+ } else if (key.startsWith(prefix)) {
1517
+ out[key.slice(prefix.length)] = bind(key);
1518
+ }
1519
+ }
1520
+ return { tables: out };
1521
+ };
1491
1522
  const base = /* @__PURE__ */ Object.create(null);
1492
- return Object.assign(base, ops, { tables });
1523
+ return Object.assign(base, ops, { tables, schema: schemaOf });
1493
1524
  }
1494
1525
  var SERVICE_LOCK_TIMEOUT = "5s";
1495
1526
  function isAbortedTransaction(e) {
@@ -1990,7 +2021,7 @@ async function defaultSqlDriver(config) {
1990
2021
  var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
1991
2022
  async function createApp(opts) {
1992
2023
  const { config, controllers } = opts;
1993
- setSchema(opts.schema ?? {});
2024
+ setSchema(opts.schemas ?? []);
1994
2025
  setSecretReader(opts.secretReader ?? null);
1995
2026
  const routes = buildRouteTable(controllers);
1996
2027
  if (routes.length === 0) {
@@ -2432,4 +2463,4 @@ export {
2432
2463
  installEgressFence,
2433
2464
  createApp
2434
2465
  };
2435
- //# sourceMappingURL=chunk-EIXCY4SS.js.map
2466
+ //# sourceMappingURL=chunk-43A3KGWL.js.map