@palbase/backend 24.3.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.
- package/dist/bin/palbase-backend.cjs +101 -60
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +17 -13
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-EIXCY4SS.js → chunk-43A3KGWL.js} +80 -49
- package/dist/chunk-43A3KGWL.js.map +1 -0
- package/dist/{chunk-ERDL5VAE.js → chunk-5CMLOAEF.js} +2 -2
- package/dist/chunk-OEQBHE2Z.js +825 -0
- package/dist/chunk-OEQBHE2Z.js.map +1 -0
- package/dist/{chunk-7Z6MGMXQ.js → chunk-XJ2RSHEU.js} +11 -5
- package/dist/chunk-XJ2RSHEU.js.map +1 -0
- package/dist/{chunk-UWSYTUGM.js → chunk-ZQRWW37O.js} +44 -1
- package/dist/chunk-ZQRWW37O.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +29 -13
- package/dist/db/env.d.ts +29 -13
- package/dist/db/index.cjs +212 -111
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +11 -1
- package/dist/engine/index.cjs +87 -50
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +2 -2
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +3 -3
- package/dist/{index-DEneI8Mn.d.ts → index-BF1f0DfA.d.ts} +5 -2
- package/dist/{index-C-ALG22n.d.cts → index-CoaDN9dL.d.cts} +5 -2
- package/dist/{index-BTMYod_l.d.ts → index-Ct1iiB4N.d.ts} +203 -61
- package/dist/{index-BLAbr9ZH.d.cts → index-CwaWRhyc.d.cts} +203 -61
- package/dist/index.cjs +550 -297
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -20
- package/dist/index.d.ts +122 -20
- package/dist/index.js +164 -217
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +100 -36
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.js +59 -2
- package/dist/openapi/index.js.map +1 -1
- package/docs/README.md +64 -31
- package/docs/endpoints.md +25 -28
- package/docs/llms-full.txt +399 -153
- package/docs/schema.md +272 -91
- package/docs/services.md +39 -4
- package/package.json +1 -1
- package/template/AGENTS.md +119 -314
- package/template/CLAUDE.md +13 -0
- package/template/controllers/notes.controller.ts +6 -13
- package/template/db/public.ts +38 -0
- package/template/models/notes/create.ts +38 -0
- package/template/package.json +6 -3
- package/template/services/note.service.test.ts +45 -0
- package/template/services/note.service.ts +2 -2
- package/dist/chunk-7Z6MGMXQ.js.map +0 -1
- package/dist/chunk-D5CQES25.js +0 -556
- package/dist/chunk-D5CQES25.js.map +0 -1
- package/dist/chunk-EIXCY4SS.js.map +0 -1
- package/dist/chunk-UWSYTUGM.js.map +0 -1
- package/template/db/schema.ts +0 -35
- /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-
|
|
7
|
-
import "../chunk-
|
|
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-
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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,
|
|
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-
|
|
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-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
-
|
|
429
|
-
|
|
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 =
|
|
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 ${
|
|
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 =
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
1480
|
+
function setSchema(schemas) {
|
|
1473
1481
|
cachedVectorSchema = null;
|
|
1474
1482
|
cachedTrgmSchema = null;
|
|
1475
|
-
const
|
|
1476
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
2466
|
+
//# sourceMappingURL=chunk-43A3KGWL.js.map
|