@databricks/appkit 0.67.0 → 0.69.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/CLAUDE.md +2 -2
- package/dist/appkit/package.js +1 -1
- package/dist/core/agent/skills/types.d.ts +6 -1
- package/dist/core/agent/skills/types.d.ts.map +1 -1
- package/dist/database/contract/index.js +1 -1
- package/dist/database/contract/wire.d.ts.map +1 -1
- package/dist/database/contract/wire.js +5 -1
- package/dist/database/contract/wire.js.map +1 -1
- package/dist/database/errors.js +39 -3
- package/dist/database/errors.js.map +1 -1
- package/dist/database/runtime/data-path.js.map +1 -1
- package/dist/database/runtime/engine/drizzle-data-path.js +1 -1
- package/dist/database/runtime/engine/drizzle-data-path.js.map +1 -1
- package/dist/database/runtime/engine/translate.js +9 -2
- package/dist/database/runtime/engine/translate.js.map +1 -1
- package/dist/database/schema-builder/define-schema.d.ts +5 -1
- package/dist/database/schema-builder/define-schema.d.ts.map +1 -1
- package/dist/database/schema-builder/define-schema.js +4 -0
- package/dist/database/schema-builder/define-schema.js.map +1 -1
- package/dist/database/schema-builder/types.d.ts +7 -2
- package/dist/database/schema-builder/types.d.ts.map +1 -1
- package/dist/database/schema-builder/types.js.map +1 -1
- package/dist/plugins/agents/active-stream-tracker.js +52 -0
- package/dist/plugins/agents/active-stream-tracker.js.map +1 -0
- package/dist/plugins/agents/adapter-extensions.js +50 -0
- package/dist/plugins/agents/adapter-extensions.js.map +1 -0
- package/dist/plugins/agents/agents.d.ts +6 -77
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +57 -431
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/approval.js +28 -0
- package/dist/plugins/agents/approval.js.map +1 -0
- package/dist/plugins/agents/builtin-tools.js +39 -0
- package/dist/plugins/agents/builtin-tools.js.map +1 -0
- package/dist/plugins/agents/prompt.js +27 -0
- package/dist/plugins/agents/prompt.js.map +1 -0
- package/dist/plugins/agents/registry.js +56 -0
- package/dist/plugins/agents/registry.js.map +1 -0
- package/dist/plugins/agents/resolve-config.js +40 -0
- package/dist/plugins/agents/resolve-config.js.map +1 -0
- package/dist/plugins/agents/skill-loader.js +139 -0
- package/dist/plugins/agents/skill-loader.js.map +1 -0
- package/dist/plugins/agents/tool-dispatch.js +124 -0
- package/dist/plugins/agents/tool-dispatch.js.map +1 -0
- package/dist/plugins/database/crud/codecs.js +78 -0
- package/dist/plugins/database/crud/codecs.js.map +1 -0
- package/dist/plugins/database/crud/contract.js +151 -0
- package/dist/plugins/database/crud/contract.js.map +1 -0
- package/dist/plugins/database/crud/exposure.js +40 -0
- package/dist/plugins/database/crud/exposure.js.map +1 -0
- package/dist/plugins/database/crud/query.js +244 -0
- package/dist/plugins/database/crud/query.js.map +1 -0
- package/dist/plugins/database/crud/routes.js +124 -0
- package/dist/plugins/database/crud/routes.js.map +1 -0
- package/dist/plugins/database/database.d.ts +6 -0
- package/dist/plugins/database/database.d.ts.map +1 -1
- package/dist/plugins/database/database.js +58 -4
- package/dist/plugins/database/database.js.map +1 -1
- package/dist/plugins/database/defaults.js +28 -1
- package/dist/plugins/database/defaults.js.map +1 -1
- package/dist/plugins/database/entity-client.js +1 -1
- package/dist/plugins/database/entity-types.d.ts +13 -4
- package/dist/plugins/database/entity-types.d.ts.map +1 -1
- package/dist/plugins/database/lifecycle.js +2 -1
- package/dist/plugins/database/lifecycle.js.map +1 -1
- package/dist/plugins/database/types.d.ts +32 -0
- package/dist/plugins/database/types.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +54 -54
- package/docs/api/appkit/Function.database.md +3 -3
- package/docs/api/appkit/Function.defineSchema.md +14 -6
- package/docs/api/appkit/Interface.Schema.md +10 -2
- package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +20 -0
- package/docs/api/appkit.md +2 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../../../src/plugins/database/crud/routes.ts"],"sourcesContent":["import type { Request, Response } from \"express\";\n\nimport {\n classifyDatabaseError,\n type DatabaseErrorDetail,\n DatabasePluginError,\n invalidDatabaseInput,\n} from \"../../../database/errors\";\nimport type {\n IdValue,\n IncludeSpec,\n OrderSpec,\n Row,\n WhereClause,\n} from \"../../../database/runtime\";\nimport { MAX_RESPONSE_BYTES } from \"../defaults\";\nimport type { ReadSerializer } from \"../types\";\nimport type { JsonValue } from \"./codecs\";\nimport type { CrudTable } from \"./contract\";\nimport { decodeDetailQuery, decodeListQuery } from \"./query\";\n\n/** The `EntityClient` subset a generated read drives. */\nexport interface CrudReadEntity {\n where(where: WhereClause): CrudReadEntity;\n order(order: OrderSpec): CrudReadEntity;\n select(columns: string[]): CrudReadEntity;\n include(include: IncludeSpec): CrudReadEntity;\n limit(limit: number): CrudReadEntity;\n offset(offset: number): CrudReadEntity;\n toArray(): Promise<Row[]>;\n find(id: IdValue): Promise<Row | null>;\n}\n\n/** Everything one table's generated reads need from the plugin instance. */\nexport interface ReadRouteDeps {\n readonly table: CrudTable;\n /** Resolved per request so a draining plugin cannot serve a stale client. */\n entity(): CrudReadEntity;\n readonly serialize?: ReadSerializer;\n runRouteSpan(\n operation: \"list\" | \"detail\",\n route: string,\n run: () => Promise<void>,\n ): Promise<void>;\n}\n\ntype ReadHandler = (req: Request, res: Response) => Promise<void>;\n\n/** Low-cardinality span outcome for one failed generated read. */\nexport function readRouteOutcome(\n error: unknown,\n): \"not_found\" | \"rejected\" | \"failed\" {\n const { statusCode } = classifyDatabaseError(error, \"read\");\n if (statusCode === 404) return \"not_found\";\n return statusCode < 500 ? \"rejected\" : \"failed\";\n}\n\n/** Express normalizes `req.query`; the decoders need the untouched string. */\nfunction rawQuery(req: Request): string {\n const url = req.originalUrl ?? req.url;\n const start = url.indexOf(\"?\");\n return start === -1 ? \"\" : url.slice(start + 1);\n}\n\n/**\n * Append the primary key so equal sort keys cannot reshuffle between pages.\n * A keyless table has no unique tie-breaker to append, so it must order itself\n * and its pages stay stable only while no concurrent write reorders them.\n */\nfunction stableOrder(\n primaryKey: string | undefined,\n order: OrderSpec | undefined,\n): OrderSpec {\n if (primaryKey)\n return { ...order, [primaryKey]: order?.[primaryKey] ?? \"asc\" };\n if (!order) {\n throw invalidDatabaseInput(\n [\"order\"],\n \"A table without a primary key requires an explicit order\",\n );\n }\n return order;\n}\n\nfunction serializeRow(\n deps: ReadRouteDeps,\n operation: \"list\" | \"detail\",\n row: Row,\n): JsonValue {\n const projected = deps.table.projectPublicRow(row);\n if (!deps.serialize) return projected;\n const shaped = deps.serialize(projected as Record<string, unknown>, {\n entity: deps.table.name,\n operation,\n });\n return deps.table.sanitizeSerializedRow(shaped);\n}\n\n/**\n * Row data is never cacheable by a shared proxy or a browser: the same URL can\n * answer differently once the underlying table or the caller's rights change.\n */\nfunction writeJson(res: Response, status: number, payload: string): void {\n res.status(status);\n res.type(\"application/json\");\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.send(payload);\n}\n\n/** Measure the encoded body before sending so no partial response escapes. */\nfunction sendJson(res: Response, body: JsonValue): void {\n const payload = JSON.stringify(body);\n if (Buffer.byteLength(payload, \"utf8\") > MAX_RESPONSE_BYTES) {\n throw new DatabasePluginError(\"PAYLOAD_TOO_LARGE\", \"read\");\n }\n writeJson(res, 200, payload);\n}\n\n/**\n * Encode the list envelope one row at a time, charging each encoded row\n * against the byte budget before the next row is shaped, so one response\n * never costs more than the budget in memory.\n */\nfunction encodeListPayload(\n deps: ReadRouteDeps,\n rows: Row[],\n limit: number,\n offset: number,\n): string {\n const prefix = '{\"items\":[';\n const suffix = `],\"limit\":${limit},\"offset\":${offset}}`;\n let bytes =\n Buffer.byteLength(prefix, \"utf8\") + Buffer.byteLength(suffix, \"utf8\");\n const items: string[] = [];\n for (const row of rows) {\n const encoded = JSON.stringify(serializeRow(deps, \"list\", row));\n bytes += Buffer.byteLength(encoded, \"utf8\") + (items.length > 0 ? 1 : 0);\n if (bytes > MAX_RESPONSE_BYTES) {\n throw new DatabasePluginError(\"PAYLOAD_TOO_LARGE\", \"read\");\n }\n items.push(encoded);\n }\n return prefix + items.join(\",\") + suffix;\n}\n\n/** Answer with the failure's safe category and the field it concerns. */\nfunction writeError(res: Response, error: unknown): void {\n if (res.headersSent) return;\n const safe = classifyDatabaseError(error, \"read\");\n const body: { error: string; details?: readonly DatabaseErrorDetail[] } = {\n error: safe.clientMessage,\n };\n if (safe.details && safe.details.length > 0) body.details = safe.details;\n writeJson(res, safe.statusCode, JSON.stringify(body));\n}\n\n/** `GET /:table` — one bounded page in the `{ items, limit, offset }` envelope. */\nexport function createListHandler(deps: ReadRouteDeps): ReadHandler {\n const primaryKey = deps.table.primaryKey?.meta.columnName;\n const route = `/${deps.table.name}`;\n\n return async (req, res) => {\n try {\n await deps.runRouteSpan(\"list\", route, async () => {\n const decoded = decodeListQuery(deps.table, rawQuery(req));\n let query = deps\n .entity()\n .order(stableOrder(primaryKey, decoded.order))\n .limit(decoded.limit)\n .offset(decoded.offset);\n if (decoded.where) query = query.where(decoded.where);\n if (decoded.select) query = query.select(decoded.select);\n if (decoded.include) query = query.include(decoded.include);\n\n const rows = await query.toArray();\n writeJson(\n res,\n 200,\n encodeListPayload(deps, rows, decoded.limit, decoded.offset),\n );\n });\n } catch (error) {\n writeError(res, error);\n }\n };\n}\n\n/** `GET /:table/:id` — one public row, or 404 when nothing matches. */\nexport function createDetailHandler(deps: ReadRouteDeps): ReadHandler {\n const route = `/${deps.table.name}/:id`;\n\n return async (req, res) => {\n try {\n await deps.runRouteSpan(\"detail\", route, async () => {\n const id = deps.table.decodeId(req.params.id);\n const decoded = decodeDetailQuery(deps.table, rawQuery(req));\n let query = deps.entity();\n if (decoded.select) query = query.select(decoded.select);\n if (decoded.include) query = query.include(decoded.include);\n\n const row = await query.find(id);\n if (row === null) throw new DatabasePluginError(\"NOT_FOUND\", \"read\");\n sendJson(res, serializeRow(deps, \"detail\", row));\n });\n } catch (error) {\n writeError(res, error);\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { PluginManifest } from "../../registry/types.js";
|
|
|
7
7
|
import "../../registry/index.js";
|
|
8
8
|
import { DatabaseExports } from "./entity-types.js";
|
|
9
9
|
import { IDatabaseConfig } from "./types.js";
|
|
10
|
+
import express from "express";
|
|
10
11
|
|
|
11
12
|
//#region src/plugins/database/database.d.ts
|
|
12
13
|
/** Schema-driven database plugin */
|
|
@@ -18,13 +19,18 @@ declare class DatabasePlugin<TSchema extends Schema> extends Plugin<IDatabaseCon
|
|
|
18
19
|
private setupPromise;
|
|
19
20
|
private draining;
|
|
20
21
|
private shutdownPromise;
|
|
22
|
+
private exposedTables;
|
|
21
23
|
constructor(config: IDatabaseConfig<TSchema>);
|
|
22
24
|
/** Build and verify one candidate state before publishing its exports. */
|
|
23
25
|
setup(): Promise<void>;
|
|
26
|
+
/** Register generated reads for explicitly exposed tables only. */
|
|
27
|
+
injectRoutes(router: express.Router): void;
|
|
24
28
|
/** Return the typed database API only while the plugin is active. */
|
|
25
29
|
exports(): DatabaseExports;
|
|
26
30
|
/** Stop new work, wait for setup, and close the owned pool exactly once. */
|
|
27
31
|
shutdown(): Promise<void>;
|
|
32
|
+
/** Trace one generated read with allowlisted, low-cardinality attributes. */
|
|
33
|
+
private runReadSpan;
|
|
28
34
|
}
|
|
29
35
|
/** Create a typed database plugin registration for a finalized schema. */
|
|
30
36
|
declare function database<TSchema extends Schema>(config: IDatabaseConfig<TSchema>): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","names":[],"sources":["../../../src/plugins/database/database.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"database.d.ts","names":[],"sources":["../../../src/plugins/database/database.ts"],"mappings":";;;;;;;;;;;;;cAyBa,cAAA,iBAA+B,MAAA,UAAgB,MAAA,CAC1D,eAAA,CAAgB,OAAA;;SAGT,QAAA,EAAuB,cAAA;EAAA,UACZ,MAAA,EAAQ,eAAA,CAAgB,OAAA;EAAA,QAClC,KAAA;EAAA,QACA,YAAA;EAAA,QACA,QAAA;EAAA,QACA,eAAA;EAAA,QACA,aAAA;cAEI,MAAA,EAAQ,eAAA,CAAgB,OAAA;EARN;EAkBxB,KAAA,CAAA,GAAS,OAAA;EAjBW;EA2C1B,YAAA,CAAa,MAAA,EAAQ,OAAA,CAAQ,MAAA;EApCT;EA+EpB,OAAA,CAAA,GAOO,eAAA;EAlDc;EAsDf,QAAA,CAAA,GAAY,OAAA;EAAA;EAAA,QAoBV,WAAA;AAAA;;iBA+BM,QAAA,iBAAyB,MAAA,CAAA,CACvC,MAAA,EAAQ,eAAA,CAAgB,OAAA;UAGe,iBAAA,CACnC,gBAAA,EACA,cAAA,CAAe,OAAA"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Plugin } from "../../plugin/plugin.js";
|
|
2
2
|
import "../../plugin/index.js";
|
|
3
|
-
import { DatabasePluginError } from "../../database/errors.js";
|
|
3
|
+
import { DatabasePluginError, databaseSetupFailed } from "../../database/errors.js";
|
|
4
|
+
import { compileCrudTables } from "./crud/contract.js";
|
|
5
|
+
import { resolveExposedTables } from "./crud/exposure.js";
|
|
6
|
+
import { createDetailHandler, createListHandler, readRouteOutcome } from "./crud/routes.js";
|
|
4
7
|
import { createDatabaseState } from "./lifecycle.js";
|
|
5
8
|
import manifest_default from "./manifest.js";
|
|
6
9
|
|
|
@@ -13,24 +16,57 @@ var DatabasePlugin = class extends Plugin {
|
|
|
13
16
|
setupPromise = null;
|
|
14
17
|
draining = false;
|
|
15
18
|
shutdownPromise = null;
|
|
19
|
+
exposedTables = [];
|
|
16
20
|
constructor(config) {
|
|
17
21
|
super({ schema: config.schema });
|
|
18
|
-
this.config = {
|
|
22
|
+
this.config = {
|
|
23
|
+
schema: config.schema,
|
|
24
|
+
crudRoutes: config.crudRoutes,
|
|
25
|
+
hooks: config.hooks
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
/** Build and verify one candidate state before publishing its exports. */
|
|
21
29
|
async setup() {
|
|
22
|
-
if (this.draining || this.state) throw
|
|
30
|
+
if (this.draining || this.state) throw databaseSetupFailed();
|
|
23
31
|
if (!this.setupPromise) this.setupPromise = (async () => {
|
|
32
|
+
this.exposedTables = resolveExposedTables(this.config.crudRoutes, Object.keys(this.config.schema.$tables));
|
|
24
33
|
const candidate = await createDatabaseState(this.config.schema, (operation, options) => this.execute(operation, options));
|
|
25
34
|
if (this.draining) {
|
|
26
35
|
candidate.deactivate();
|
|
27
36
|
await candidate.pool.end().catch(() => void 0);
|
|
28
|
-
throw
|
|
37
|
+
throw databaseSetupFailed();
|
|
29
38
|
}
|
|
30
39
|
this.state = candidate;
|
|
31
40
|
})();
|
|
32
41
|
return this.setupPromise;
|
|
33
42
|
}
|
|
43
|
+
/** Register generated reads for explicitly exposed tables only. */
|
|
44
|
+
injectRoutes(router) {
|
|
45
|
+
if (this.exposedTables.length === 0) return;
|
|
46
|
+
const tables = compileCrudTables(Object.fromEntries(this.exposedTables.map((name) => [name, this.config.schema.$tables[name]])));
|
|
47
|
+
const serializers = this.config.hooks;
|
|
48
|
+
const entities = () => this.exports();
|
|
49
|
+
for (const table of tables.values()) {
|
|
50
|
+
const deps = {
|
|
51
|
+
table,
|
|
52
|
+
entity: () => entities()[table.name],
|
|
53
|
+
serialize: serializers?.[table.name]?.serialize,
|
|
54
|
+
runRouteSpan: (operation, route, run) => this.runReadSpan(table.name, operation, route, run)
|
|
55
|
+
};
|
|
56
|
+
this.route(router, {
|
|
57
|
+
name: `${table.name}.list`,
|
|
58
|
+
method: "get",
|
|
59
|
+
path: `/${table.name}`,
|
|
60
|
+
handler: createListHandler(deps)
|
|
61
|
+
});
|
|
62
|
+
if (table.primaryKey) this.route(router, {
|
|
63
|
+
name: `${table.name}.detail`,
|
|
64
|
+
method: "get",
|
|
65
|
+
path: `/${table.name}/:id`,
|
|
66
|
+
handler: createDetailHandler(deps)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
34
70
|
/** Return the typed database API only while the plugin is active. */
|
|
35
71
|
exports() {
|
|
36
72
|
if (!this.state || this.draining) throw new DatabasePluginError("INTERNAL", "read");
|
|
@@ -53,6 +89,24 @@ var DatabasePlugin = class extends Plugin {
|
|
|
53
89
|
})();
|
|
54
90
|
return this.shutdownPromise;
|
|
55
91
|
}
|
|
92
|
+
/** Trace one generated read with allowlisted, low-cardinality attributes. */
|
|
93
|
+
runReadSpan(table, operation, route, run) {
|
|
94
|
+
return this.telemetry.startActiveSpan("database.crud.route", { attributes: {
|
|
95
|
+
table_name: table,
|
|
96
|
+
operation,
|
|
97
|
+
"http.route": `/api/${this.name}${route}`
|
|
98
|
+
} }, async (span) => {
|
|
99
|
+
try {
|
|
100
|
+
await run();
|
|
101
|
+
span.setAttribute("outcome", "success");
|
|
102
|
+
} catch (error) {
|
|
103
|
+
span.setAttribute("outcome", readRouteOutcome(error));
|
|
104
|
+
throw error;
|
|
105
|
+
} finally {
|
|
106
|
+
span.end();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
56
110
|
};
|
|
57
111
|
/** Create a typed database plugin registration for a finalized schema. */
|
|
58
112
|
function database(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","names":["manifest"],"sources":["../../../src/plugins/database/database.ts"],"sourcesContent":["import type { BasePluginConfig, PluginConstructor } from \"shared\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"database.js","names":["manifest"],"sources":["../../../src/plugins/database/database.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePluginConfig, PluginConstructor } from \"shared\";\n\nimport {\n DatabasePluginError,\n databaseSetupFailed,\n} from \"../../database/errors\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { Plugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { compileCrudTables } from \"./crud/contract\";\nimport { resolveExposedTables } from \"./crud/exposure\";\nimport {\n type CrudReadEntity,\n createDetailHandler,\n createListHandler,\n type ReadRouteDeps,\n readRouteOutcome,\n} from \"./crud/routes\";\nimport type { DatabaseExports } from \"./entity-types\";\nimport { createDatabaseState, type DatabaseState } from \"./lifecycle\";\nimport manifest from \"./manifest.json\";\nimport type { IDatabaseConfig, ReadSerializer } from \"./types\";\n\n/** Schema-driven database plugin */\nexport class DatabasePlugin<TSchema extends Schema> extends Plugin<\n IDatabaseConfig<TSchema>\n> {\n /** Plugin metadata and required PostgreSQL resource. */\n static manifest = manifest as PluginManifest<\"database\">;\n declare protected config: IDatabaseConfig<TSchema>;\n private state: DatabaseState | null = null;\n private setupPromise: Promise<void> | null = null;\n private draining = false;\n private shutdownPromise: Promise<void> | null = null;\n private exposedTables: string[] = [];\n\n constructor(config: IDatabaseConfig<TSchema>) {\n super({ schema: config.schema });\n this.config = {\n schema: config.schema,\n crudRoutes: config.crudRoutes,\n hooks: config.hooks,\n };\n }\n\n /** Build and verify one candidate state before publishing its exports. */\n async setup(): Promise<void> {\n if (this.draining || this.state) throw databaseSetupFailed();\n if (!this.setupPromise) {\n const attempt = (async () => {\n this.exposedTables = resolveExposedTables(\n this.config.crudRoutes,\n Object.keys(this.config.schema.$tables),\n );\n const candidate = await createDatabaseState(\n this.config.schema,\n (operation, options) => this.execute(operation, options),\n );\n if (this.draining) {\n // Setup may finish while shutdown is waiting; never publish that state.\n candidate.deactivate();\n await candidate.pool.end().catch(() => undefined);\n throw databaseSetupFailed();\n }\n this.state = candidate;\n })();\n this.setupPromise = attempt;\n }\n return this.setupPromise;\n }\n\n /** Register generated reads for explicitly exposed tables only. */\n injectRoutes(router: express.Router): void {\n if (this.exposedTables.length === 0) return;\n const tables = compileCrudTables(\n Object.fromEntries(\n this.exposedTables.map((name) => [\n name,\n this.config.schema.$tables[name],\n ]),\n ),\n );\n const serializers = this.config.hooks as\n | Record<string, { serialize?: ReadSerializer } | undefined>\n | undefined;\n // Every exposed name is a declared table, so its export is an entity client.\n const entities = () =>\n this.exports() as unknown as Record<string, CrudReadEntity>;\n\n for (const table of tables.values()) {\n const deps: ReadRouteDeps = {\n table,\n entity: () => entities()[table.name],\n serialize: serializers?.[table.name]?.serialize,\n runRouteSpan: (operation, route, run) =>\n this.runReadSpan(table.name, operation, route, run),\n };\n this.route(router, {\n name: `${table.name}.list`,\n method: \"get\",\n path: `/${table.name}`,\n handler: createListHandler(deps),\n });\n if (table.primaryKey) {\n this.route(router, {\n name: `${table.name}.detail`,\n method: \"get\",\n path: `/${table.name}/:id`,\n handler: createDetailHandler(deps),\n });\n }\n }\n }\n\n /** Return the typed database API only while the plugin is active. */\n exports() {\n if (!this.state || this.draining)\n throw new DatabasePluginError(\"INTERNAL\", \"read\");\n // AppKit binds exported functions onto this object on every access.\n return Object.assign(\n Object.create(null),\n this.state.exports,\n ) as DatabaseExports;\n }\n\n /** Stop new work, wait for setup, and close the owned pool exactly once. */\n async shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.draining = true;\n this.shutdownPromise = (async () => {\n await this.setupPromise?.catch(() => undefined);\n const state = this.state;\n state?.deactivate();\n this.state = null;\n if (state) {\n try {\n await state.pool.end();\n } catch {\n throw new DatabasePluginError(\"INTERNAL\", \"shutdown\");\n }\n }\n })();\n return this.shutdownPromise;\n }\n\n /** Trace one generated read with allowlisted, low-cardinality attributes. */\n private runReadSpan(\n table: string,\n operation: \"list\" | \"detail\",\n route: string,\n run: () => Promise<void>,\n ): Promise<void> {\n return this.telemetry.startActiveSpan(\n \"database.crud.route\",\n {\n attributes: {\n table_name: table,\n operation,\n \"http.route\": `/api/${this.name}${route}`,\n },\n },\n async (span) => {\n try {\n await run();\n span.setAttribute(\"outcome\", \"success\");\n } catch (error) {\n span.setAttribute(\"outcome\", readRouteOutcome(error));\n throw error;\n } finally {\n span.end();\n }\n },\n );\n }\n}\n\n/** Create a typed database plugin registration for a finalized schema. */\nexport function database<TSchema extends Schema>(\n config: IDatabaseConfig<TSchema>,\n) {\n return {\n plugin: DatabasePlugin as unknown as PluginConstructor<\n BasePluginConfig,\n DatabasePlugin<TSchema>\n >,\n config,\n name: \"database\" as const,\n };\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,34 @@ const databaseWriteDefaults = {
|
|
|
6
6
|
cache: { enabled: false },
|
|
7
7
|
retry: { enabled: false }
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Server-side `statement_timeout` applied to every pooled connection (ms).
|
|
11
|
+
* PostgreSQL cancels the statement itself, so an expensive caller-supplied
|
|
12
|
+
* filter (for example a leading-wildcard `ilike`) cannot hold its pooled
|
|
13
|
+
* connection past the deadline.
|
|
14
|
+
*/
|
|
15
|
+
const STATEMENT_TIMEOUT_MS = 3e4;
|
|
16
|
+
/** Max encoded size of a generated read query string. */
|
|
17
|
+
const MAX_QUERY_BYTES = 8 * 1024;
|
|
18
|
+
/** Max nesting depth of `and`/`or` groups in one generated filter. */
|
|
19
|
+
const MAX_WHERE_DEPTH = 5;
|
|
20
|
+
/** Max column conditions across one generated filter. */
|
|
21
|
+
const MAX_WHERE_CONDITIONS = 50;
|
|
22
|
+
/** Max members in one `and`/`or` group. */
|
|
23
|
+
const MAX_GROUP_ITEMS = 20;
|
|
24
|
+
/** Max columns one generated `order` may name. */
|
|
25
|
+
const MAX_ORDER_FIELDS = 10;
|
|
26
|
+
/** Max accepted generated `offset`. */
|
|
27
|
+
const MAX_OFFSET = 1e4;
|
|
28
|
+
/** Max rows one generated read may materialize across its include tree. */
|
|
29
|
+
const MAX_MATERIALIZED_NODES = 1e4;
|
|
30
|
+
/** Max nesting depth of read-serializer output. */
|
|
31
|
+
const MAX_SERIALIZED_DEPTH = 32;
|
|
32
|
+
/** Max node count of read-serializer output. */
|
|
33
|
+
const MAX_SERIALIZED_NODES = 1e4;
|
|
34
|
+
/** Max UTF-8 byte length of one generated read response body. */
|
|
35
|
+
const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
9
36
|
|
|
10
37
|
//#endregion
|
|
11
|
-
export { databaseReadDefaults, databaseWriteDefaults };
|
|
38
|
+
export { MAX_GROUP_ITEMS, MAX_MATERIALIZED_NODES, MAX_OFFSET, MAX_ORDER_FIELDS, MAX_QUERY_BYTES, MAX_RESPONSE_BYTES, MAX_SERIALIZED_DEPTH, MAX_SERIALIZED_NODES, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, STATEMENT_TIMEOUT_MS, databaseReadDefaults, databaseWriteDefaults };
|
|
12
39
|
//# sourceMappingURL=defaults.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/database/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/** Default interceptor policy for bounded reads. */\nexport const databaseReadDefaults: PluginExecuteConfig = {\n retry: { enabled: false },\n};\n\n/** Default interceptor policy for mutations. */\nexport const databaseWriteDefaults: PluginExecuteConfig = {\n cache: { enabled: false },\n retry: { enabled: false },\n};\n"],"mappings":";;AAGA,MAAa,uBAA4C,EACvD,OAAO,EAAE,SAAS,OAAO,EAC1B;;AAGD,MAAa,wBAA6C;CACxD,OAAO,EAAE,SAAS,OAAO;CACzB,OAAO,EAAE,SAAS,OAAO;CAC1B"}
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/database/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/** Default interceptor policy for bounded reads. */\nexport const databaseReadDefaults: PluginExecuteConfig = {\n retry: { enabled: false },\n};\n\n/** Default interceptor policy for mutations. */\nexport const databaseWriteDefaults: PluginExecuteConfig = {\n cache: { enabled: false },\n retry: { enabled: false },\n};\n\n// Connection posture: how long any one statement may hold a pooled connection.\n/**\n * Server-side `statement_timeout` applied to every pooled connection (ms).\n * PostgreSQL cancels the statement itself, so an expensive caller-supplied\n * filter (for example a leading-wildcard `ilike`) cannot hold its pooled\n * connection past the deadline.\n */\nexport const STATEMENT_TIMEOUT_MS = 30_000;\n\n// Generated-read limits. The wire caps a typed caller shares with HTTP live in\n// `database/contract`; these bound only what an untrusted request may ask for.\n\n// Request decoding: rejected before any database work is scheduled.\n/** Max encoded size of a generated read query string. */\nexport const MAX_QUERY_BYTES = 8 * 1024;\n/** Max nesting depth of `and`/`or` groups in one generated filter. */\nexport const MAX_WHERE_DEPTH = 5;\n/** Max column conditions across one generated filter. */\nexport const MAX_WHERE_CONDITIONS = 50;\n/** Max members in one `and`/`or` group. */\nexport const MAX_GROUP_ITEMS = 20;\n/** Max columns one generated `order` may name. */\nexport const MAX_ORDER_FIELDS = 10;\n/** Max accepted generated `offset`. */\nexport const MAX_OFFSET = 10_000;\n/** Max rows one generated read may materialize across its include tree. */\nexport const MAX_MATERIALIZED_NODES = 10_000;\n\n// Response shaping: bounds trusted output on its way to the wire.\n/** Max nesting depth of read-serializer output. */\nexport const MAX_SERIALIZED_DEPTH = 32;\n/** Max node count of read-serializer output. */\nexport const MAX_SERIALIZED_NODES = 10_000;\n/** Max UTF-8 byte length of one generated read response body. */\nexport const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;\n"],"mappings":";;AAGA,MAAa,uBAA4C,EACvD,OAAO,EAAE,SAAS,OAAO,EAC1B;;AAGD,MAAa,wBAA6C;CACxD,OAAO,EAAE,SAAS,OAAO;CACzB,OAAO,EAAE,SAAS,OAAO;CAC1B;;;;;;;AASD,MAAa,uBAAuB;;AAOpC,MAAa,kBAAkB,IAAI;;AAEnC,MAAa,kBAAkB;;AAE/B,MAAa,uBAAuB;;AAEpC,MAAa,kBAAkB;;AAE/B,MAAa,mBAAmB;;AAEhC,MAAa,aAAa;;AAE1B,MAAa,yBAAyB;;AAItC,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;;AAEpC,MAAa,qBAAqB,IAAI,OAAO"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DatabasePluginError, classifyDatabaseError, databaseErrorFromStatus } from "../../database/errors.js";
|
|
2
|
-
import { andWhere, validateLimit, validateOffset } from "../../database/runtime/data-path.js";
|
|
3
2
|
import { databaseReadDefaults, databaseWriteDefaults } from "./defaults.js";
|
|
3
|
+
import { andWhere, validateLimit, validateOffset } from "../../database/runtime/data-path.js";
|
|
4
4
|
|
|
5
5
|
//#region src/plugins/database/entity-client.ts
|
|
6
6
|
/** Clone plain acyclic input so caller mutation cannot change a built query. */
|
|
@@ -17,7 +17,9 @@ type PublicRowOf<K extends EntityName> = PublicRowOfFor<DatabaseRegistry, K>;
|
|
|
17
17
|
type RelationTargetFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, Relation extends keyof IncludesOfFor<Registry, K>> = IncludesOfFor<Registry, K>[Relation] extends {
|
|
18
18
|
to: infer Target extends EntityNameFor<Registry>;
|
|
19
19
|
} ? Target : never;
|
|
20
|
-
|
|
20
|
+
/** How many further relation edges may nest inside one include level. */
|
|
21
|
+
type RemainingEdges = 0 | 1;
|
|
22
|
+
type IncludeOptionsFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, Many extends boolean, Remaining extends RemainingEdges = 0> = {
|
|
21
23
|
readonly select?: readonly (keyof RowOfFor<Registry, K> & string)[];
|
|
22
24
|
readonly where?: FiltersOfFor<Registry, K>;
|
|
23
25
|
readonly order?: Partial<Record<keyof RowOfFor<Registry, K> & string, OrderDirection>>;
|
|
@@ -25,13 +27,20 @@ type IncludeOptionsFor<Registry extends RegistryShape<Registry>, K extends Entit
|
|
|
25
27
|
readonly limit?: number;
|
|
26
28
|
} : {
|
|
27
29
|
readonly limit?: never;
|
|
30
|
+
}) & (Remaining extends 1 ? {
|
|
31
|
+
readonly include?: IncludeArgFor<Registry, K, 0>;
|
|
32
|
+
} : {
|
|
33
|
+
readonly include?: never;
|
|
28
34
|
});
|
|
29
|
-
type IncludeArgFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry
|
|
35
|
+
type IncludeArgFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, Remaining extends RemainingEdges = 1> = { readonly [Relation in keyof IncludesOfFor<Registry, K>]?: boolean | IncludeOptionsFor<Registry, RelationTargetFor<Registry, K, Relation>, IncludesOfFor<Registry, K>[Relation] extends {
|
|
30
36
|
many: infer Many extends boolean;
|
|
31
|
-
} ? Many : false> };
|
|
32
|
-
type
|
|
37
|
+
} ? Many : false, Remaining> };
|
|
38
|
+
type SelectedRowFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, Config> = Config extends {
|
|
33
39
|
readonly select: infer Columns extends readonly (keyof RowOfFor<Registry, K> & string)[];
|
|
34
40
|
} ? Pick<RowOfFor<Registry, K>, Columns[number]> : PublicRowOfFor<Registry, K>;
|
|
41
|
+
type IncludedRowFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, Config> = Config extends {
|
|
42
|
+
readonly include: infer Nested;
|
|
43
|
+
} ? SelectedRowFor<Registry, K, Config> & IncludedResultFor<Registry, K, Nested> : SelectedRowFor<Registry, K, Config>;
|
|
35
44
|
type IncludedResultFor<Registry extends RegistryShape<Registry>, K extends EntityNameFor<Registry>, I> = I extends Record<string, unknown> ? { [Relation in keyof I & keyof IncludesOfFor<Registry, K> as I[Relation] extends false ? never : Relation]: IncludesOfFor<Registry, K>[Relation] extends {
|
|
36
45
|
to: infer Target extends EntityNameFor<Registry>;
|
|
37
46
|
many: infer Many;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-types.d.ts","names":[],"sources":["../../../src/plugins/database/entity-types.ts"],"mappings":";;;;;KAQK,aAAA,6BACkB,CAAA,GAAI,qBAAA;AAAA,KAGf,aAAA,WAAwB,aAAA,CAAc,CAAA,KAAM,OAAA,OAChD,CAAA;AAAA,KAGH,QAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,CAAA,CAAE,CAAA,UAAW,qBAAA,GAAwB,CAAA,CAAE,CAAA;AAAA,KACtC,QAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KACX,cAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KACX,WAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KACX,WAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KACX,YAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KACX,aAAA,WACO,aAAA,CAAc,CAAA,aACd,aAAA,CAAc,CAAA,KACtB,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KAEJ,UAAA,GAAa,aAAA,CAAc,gBAAA;AAAA,KAClC,WAAA,WAAsB,UAAA,IAAc,cAAA,CAAe,gBAAA,EAAkB,CAAA;AAAA,KAGrE,iBAAA,kBACc,aAAA,CAAc,QAAA,aACrB,aAAA,CAAc,QAAA,0BACD,aAAA,CAAc,QAAA,EAAU,CAAA,KAC7C,aAAA,CAAc,QAAA,EAAU,CAAA,EAAG,QAAA;EAC7B,EAAA,uBAAyB,aAAA,CAAc,QAAA;AAAA,IAErC,MAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"entity-types.d.ts","names":[],"sources":["../../../src/plugins/database/entity-types.ts"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { createLogger } from "../../logging/logger.js";
|
|
|
2
2
|
import { createLakebasePool } from "../../connectors/lakebase/index.js";
|
|
3
3
|
import { assertFinalizedSchema } from "../../database/schema-builder/define-schema.js";
|
|
4
4
|
import { DatabasePluginError, classifyDatabaseError } from "../../database/errors.js";
|
|
5
|
+
import { STATEMENT_TIMEOUT_MS } from "./defaults.js";
|
|
5
6
|
import { createDrizzleDataPath, createDrizzleDb } from "../../database/runtime/engine/drizzle-data-path.js";
|
|
6
7
|
import { EntityClient } from "./entity-client.js";
|
|
7
8
|
|
|
@@ -78,7 +79,7 @@ async function createDatabaseState(schema, execute) {
|
|
|
78
79
|
if (!active) throw new DatabasePluginError("INTERNAL", "runtime");
|
|
79
80
|
};
|
|
80
81
|
try {
|
|
81
|
-
pool = createLakebasePool();
|
|
82
|
+
pool = createLakebasePool({ statement_timeout: STATEMENT_TIMEOUT_MS });
|
|
82
83
|
const dataPath = createDrizzleDataPath(createDrizzleDb(pool, schema), schema, { columnAccess: "trusted" });
|
|
83
84
|
const exports = buildDatabaseExports({
|
|
84
85
|
schema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.js","names":[],"sources":["../../../src/plugins/database/lifecycle.ts"],"sourcesContent":["import { createLakebasePool } from \"../../connectors/lakebase\";\nimport {\n classifyDatabaseError,\n DatabasePluginError,\n} from \"../../database/errors\";\nimport type { DataPath } from \"../../database/runtime\";\nimport {\n createDrizzleDataPath,\n createDrizzleDb,\n} from \"../../database/runtime/engine/drizzle-data-path\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { createLogger } from \"../../logging/logger\";\nimport { EntityClient, type EntityExecute } from \"./entity-client\";\nimport type {\n DatabaseExports,\n SqlTag,\n TransactionClient,\n} from \"./entity-types\";\n\nconst logger = createLogger(\"database\");\n\n/** Resources owned by one successfully initialized plugin instance. */\nexport interface DatabaseState {\n readonly pool: ReturnType<typeof createLakebasePool>;\n readonly exports: DatabaseExports;\n readonly deactivate: () => void;\n}\n\ninterface ExportContext {\n readonly schema: Schema;\n readonly getDataPath: () => DataPath;\n readonly execute: EntityExecute;\n readonly assertActive: () => void;\n}\n\n/** Execute value-only SQL on the bound DataPath without per-statement retries. */\nfunction buildSql(context: ExportContext): SqlTag {\n return async <T = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ) => {\n try {\n context.assertActive();\n // DataPath accepts value interpolation only and keeps Drizzle private.\n return await context.getDataPath().raw<T>(strings, ...values);\n } catch (error) {\n throw classifyDatabaseError(error, \"read\");\n }\n };\n}\n\n/** Build entities and SQL bound to either the root pool or one transaction. */\nfunction buildTransactionClient(context: ExportContext): TransactionClient {\n const result: Record<string, unknown> = Object.create(null);\n for (const [name, table] of Object.entries(context.schema.$tables)) {\n result[name] = new EntityClient({\n table,\n getDataPath: context.getDataPath,\n execute: context.execute,\n assertActive: context.assertActive,\n });\n }\n result.sql = buildSql(context);\n return result as TransactionClient;\n}\n\n/** Add the transaction entry point to the root database surface. */\nfunction buildDatabaseExports(context: ExportContext): DatabaseExports {\n const result = buildTransactionClient(context) as DatabaseExports;\n result.transaction = async <T>(\n callback: (tx: TransactionClient) => Promise<T>,\n ) => {\n try {\n context.assertActive();\n return await context.getDataPath().transaction(async (txDataPath) => {\n let active = true;\n const assertTransactionActive = () => {\n context.assertActive();\n if (!active) throw new DatabasePluginError(\"INTERNAL\", \"transaction\");\n };\n // The outer transaction owns execution; inner clients use its connection.\n const directExecute: EntityExecute = async (operation) => ({\n ok: true,\n data: await operation(),\n });\n const tx = buildTransactionClient({\n ...context,\n getDataPath: () => txDataPath,\n execute: directExecute,\n assertActive: assertTransactionActive,\n });\n try {\n return await callback(tx);\n } finally {\n // Captured clients must not outlive the transaction callback.\n active = false;\n }\n });\n } catch (error) {\n throw classifyDatabaseError(error, \"transaction\");\n }\n };\n return result;\n}\n\n/** Validate the schema, create one pool-backed API, and verify connectivity. */\nexport async function createDatabaseState<TSchema extends Schema>(\n schema: TSchema,\n execute: EntityExecute,\n): Promise<DatabaseState> {\n try {\n assertFinalizedSchema(schema);\n } catch (error) {\n logger.error(\"Database schema failed validation: %O\", error);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n\n let active = true;\n let pool: ReturnType<typeof createLakebasePool> | undefined;\n const assertActive = () => {\n if (!active) throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n };\n try {\n pool = createLakebasePool();\n const db = createDrizzleDb(pool, schema);\n const dataPath = createDrizzleDataPath(db, schema, {\n columnAccess: \"trusted\",\n });\n const exports = buildDatabaseExports({\n schema,\n getDataPath: () => dataPath,\n execute,\n assertActive,\n });\n // Do not publish exports until an authenticated statement succeeds.\n await dataPath.raw`select 1`;\n return {\n pool,\n exports,\n deactivate: () => {\n active = false;\n },\n };\n } catch (error) {\n logger.error(\"Database setup failed: %O\", error);\n active = false;\n await pool?.end().catch(() => undefined);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"lifecycle.js","names":[],"sources":["../../../src/plugins/database/lifecycle.ts"],"sourcesContent":["import { createLakebasePool } from \"../../connectors/lakebase\";\nimport {\n classifyDatabaseError,\n DatabasePluginError,\n} from \"../../database/errors\";\nimport type { DataPath } from \"../../database/runtime\";\nimport {\n createDrizzleDataPath,\n createDrizzleDb,\n} from \"../../database/runtime/engine/drizzle-data-path\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { createLogger } from \"../../logging/logger\";\nimport { STATEMENT_TIMEOUT_MS } from \"./defaults\";\nimport { EntityClient, type EntityExecute } from \"./entity-client\";\nimport type {\n DatabaseExports,\n SqlTag,\n TransactionClient,\n} from \"./entity-types\";\n\nconst logger = createLogger(\"database\");\n\n/** Resources owned by one successfully initialized plugin instance. */\nexport interface DatabaseState {\n readonly pool: ReturnType<typeof createLakebasePool>;\n readonly exports: DatabaseExports;\n readonly deactivate: () => void;\n}\n\ninterface ExportContext {\n readonly schema: Schema;\n readonly getDataPath: () => DataPath;\n readonly execute: EntityExecute;\n readonly assertActive: () => void;\n}\n\n/** Execute value-only SQL on the bound DataPath without per-statement retries. */\nfunction buildSql(context: ExportContext): SqlTag {\n return async <T = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ) => {\n try {\n context.assertActive();\n // DataPath accepts value interpolation only and keeps Drizzle private.\n return await context.getDataPath().raw<T>(strings, ...values);\n } catch (error) {\n throw classifyDatabaseError(error, \"read\");\n }\n };\n}\n\n/** Build entities and SQL bound to either the root pool or one transaction. */\nfunction buildTransactionClient(context: ExportContext): TransactionClient {\n const result: Record<string, unknown> = Object.create(null);\n for (const [name, table] of Object.entries(context.schema.$tables)) {\n result[name] = new EntityClient({\n table,\n getDataPath: context.getDataPath,\n execute: context.execute,\n assertActive: context.assertActive,\n });\n }\n result.sql = buildSql(context);\n return result as TransactionClient;\n}\n\n/** Add the transaction entry point to the root database surface. */\nfunction buildDatabaseExports(context: ExportContext): DatabaseExports {\n const result = buildTransactionClient(context) as DatabaseExports;\n result.transaction = async <T>(\n callback: (tx: TransactionClient) => Promise<T>,\n ) => {\n try {\n context.assertActive();\n return await context.getDataPath().transaction(async (txDataPath) => {\n let active = true;\n const assertTransactionActive = () => {\n context.assertActive();\n if (!active) throw new DatabasePluginError(\"INTERNAL\", \"transaction\");\n };\n // The outer transaction owns execution; inner clients use its connection.\n const directExecute: EntityExecute = async (operation) => ({\n ok: true,\n data: await operation(),\n });\n const tx = buildTransactionClient({\n ...context,\n getDataPath: () => txDataPath,\n execute: directExecute,\n assertActive: assertTransactionActive,\n });\n try {\n return await callback(tx);\n } finally {\n // Captured clients must not outlive the transaction callback.\n active = false;\n }\n });\n } catch (error) {\n throw classifyDatabaseError(error, \"transaction\");\n }\n };\n return result;\n}\n\n/** Validate the schema, create one pool-backed API, and verify connectivity. */\nexport async function createDatabaseState<TSchema extends Schema>(\n schema: TSchema,\n execute: EntityExecute,\n): Promise<DatabaseState> {\n try {\n assertFinalizedSchema(schema);\n } catch (error) {\n logger.error(\"Database schema failed validation: %O\", error);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n\n let active = true;\n let pool: ReturnType<typeof createLakebasePool> | undefined;\n const assertActive = () => {\n if (!active) throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n };\n try {\n pool = createLakebasePool({ statement_timeout: STATEMENT_TIMEOUT_MS });\n const db = createDrizzleDb(pool, schema);\n const dataPath = createDrizzleDataPath(db, schema, {\n columnAccess: \"trusted\",\n });\n const exports = buildDatabaseExports({\n schema,\n getDataPath: () => dataPath,\n execute,\n assertActive,\n });\n // Do not publish exports until an authenticated statement succeeds.\n await dataPath.raw`select 1`;\n return {\n pool,\n exports,\n deactivate: () => {\n active = false;\n },\n };\n } catch (error) {\n logger.error(\"Database setup failed: %O\", error);\n active = false;\n await pool?.end().catch(() => undefined);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n}\n"],"mappings":";;;;;;;;;AAqBA,MAAM,SAAS,aAAa,WAAW;;AAiBvC,SAAS,SAAS,SAAgC;AAChD,QAAO,OACL,SACA,GAAG,WACA;AACH,MAAI;AACF,WAAQ,cAAc;AAEtB,UAAO,MAAM,QAAQ,aAAa,CAAC,IAAO,SAAS,GAAG,OAAO;WACtD,OAAO;AACd,SAAM,sBAAsB,OAAO,OAAO;;;;;AAMhD,SAAS,uBAAuB,SAA2C;CACzE,MAAM,SAAkC,OAAO,OAAO,KAAK;AAC3D,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,QAAQ,CAChE,QAAO,QAAQ,IAAI,aAAa;EAC9B;EACA,aAAa,QAAQ;EACrB,SAAS,QAAQ;EACjB,cAAc,QAAQ;EACvB,CAAC;AAEJ,QAAO,MAAM,SAAS,QAAQ;AAC9B,QAAO;;;AAIT,SAAS,qBAAqB,SAAyC;CACrE,MAAM,SAAS,uBAAuB,QAAQ;AAC9C,QAAO,cAAc,OACnB,aACG;AACH,MAAI;AACF,WAAQ,cAAc;AACtB,UAAO,MAAM,QAAQ,aAAa,CAAC,YAAY,OAAO,eAAe;IACnE,IAAI,SAAS;IACb,MAAM,gCAAgC;AACpC,aAAQ,cAAc;AACtB,SAAI,CAAC,OAAQ,OAAM,IAAI,oBAAoB,YAAY,cAAc;;IAGvE,MAAM,gBAA+B,OAAO,eAAe;KACzD,IAAI;KACJ,MAAM,MAAM,WAAW;KACxB;IACD,MAAM,KAAK,uBAAuB;KAChC,GAAG;KACH,mBAAmB;KACnB,SAAS;KACT,cAAc;KACf,CAAC;AACF,QAAI;AACF,YAAO,MAAM,SAAS,GAAG;cACjB;AAER,cAAS;;KAEX;WACK,OAAO;AACd,SAAM,sBAAsB,OAAO,cAAc;;;AAGrD,QAAO;;;AAIT,eAAsB,oBACpB,QACA,SACwB;AACxB,KAAI;AACF,wBAAsB,OAAO;UACtB,OAAO;AACd,SAAO,MAAM,yCAAyC,MAAM;AAC5D,QAAM,IAAI,oBAAoB,gBAAgB,QAAQ;;CAGxD,IAAI,SAAS;CACb,IAAI;CACJ,MAAM,qBAAqB;AACzB,MAAI,CAAC,OAAQ,OAAM,IAAI,oBAAoB,YAAY,UAAU;;AAEnE,KAAI;AACF,SAAO,mBAAmB,EAAE,mBAAmB,sBAAsB,CAAC;EAEtE,MAAM,WAAW,sBADN,gBAAgB,MAAM,OAAO,EACG,QAAQ,EACjD,cAAc,WACf,CAAC;EACF,MAAM,UAAU,qBAAqB;GACnC;GACA,mBAAmB;GACnB;GACA;GACD,CAAC;AAEF,QAAM,SAAS,GAAG;AAClB,SAAO;GACL;GACA;GACA,kBAAkB;AAChB,aAAS;;GAEZ;UACM,OAAO;AACd,SAAO,MAAM,6BAA6B,MAAM;AAChD,WAAS;AACT,QAAM,MAAM,KAAK,CAAC,YAAY,OAAU;AACxC,QAAM,IAAI,oBAAoB,gBAAgB,QAAQ"}
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import { Schema } from "../../database/schema-builder/types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/database/types.d.ts
|
|
4
|
+
/** Table names declared by one finalized schema. */
|
|
5
|
+
type SchemaTableName<TSchema extends Schema> = Extract<keyof TSchema["$tables"], string>;
|
|
6
|
+
/**
|
|
7
|
+
* Generated read exposure: off by default, every table, or an explicit list.
|
|
8
|
+
*
|
|
9
|
+
* Reads run as the app's service principal and apply no per-user filter, so an
|
|
10
|
+
* enabled table is readable by anyone the app admits. Enabling a table also
|
|
11
|
+
* makes it includable from its neighbours; a relation whose target stays off
|
|
12
|
+
* cannot be included, which keeps one table's data behind one decision.
|
|
13
|
+
*
|
|
14
|
+
* Text filters accept caller-supplied `like`/`ilike` patterns; a server-side
|
|
15
|
+
* `statement_timeout` cancels a pattern that would otherwise hold its pooled
|
|
16
|
+
* connection to completion.
|
|
17
|
+
*/
|
|
18
|
+
type CrudRoutesConfig<TSchema extends Schema> = boolean | {
|
|
19
|
+
readonly tables: readonly SchemaTableName<TSchema>[];
|
|
20
|
+
};
|
|
21
|
+
/** Which entity and generated operation produced the row being shaped. */
|
|
22
|
+
interface ReadSerializerContext {
|
|
23
|
+
readonly entity: string;
|
|
24
|
+
readonly operation: "list" | "detail";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shape one already private-safe row before it reaches the wire. A `Promise`
|
|
28
|
+
* is not assignable to the return type, so an async callback fails to compile:
|
|
29
|
+
* serializers run inside the response path and must not add latency there.
|
|
30
|
+
*/
|
|
31
|
+
type ReadSerializer = (row: Record<string, unknown>, context: ReadSerializerContext) => Record<string, unknown>;
|
|
4
32
|
/** Configuration for one schema-bound DatabasePlugin instance. */
|
|
5
33
|
type IDatabaseConfig<TSchema extends Schema> = {
|
|
6
34
|
readonly schema: TSchema;
|
|
35
|
+
readonly crudRoutes?: CrudRoutesConfig<TSchema>;
|
|
36
|
+
readonly hooks?: { readonly [TTable in SchemaTableName<TSchema>]?: {
|
|
37
|
+
readonly serialize?: ReadSerializer;
|
|
38
|
+
} };
|
|
7
39
|
};
|
|
8
40
|
//#endregion
|
|
9
41
|
export { IDatabaseConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/database/types.ts"],"mappings":";;;;KAGY,eAAA,iBAAgC,MAAA;EAAA,SACjC,MAAA,EAAQ,OAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/database/types.ts"],"mappings":";;;;KAGY,eAAA,iBAAgC,MAAA,IAAU,OAAA,OAC9C,OAAA;AADR;;;;;;;;;;;;AAAA,KAiBY,gBAAA,iBAAiC,MAAA;EAAA,SAE9B,MAAA,WAAiB,eAAA,CAAgB,OAAA;AAAA;;UAG/B,qBAAA;EAAA,SACN,MAAA;EAAA,SACA,SAAA;AAAA;;;;;;KAQC,cAAA,IACV,GAAA,EAAK,MAAA,mBACL,OAAA,EAAS,qBAAA,KACN,MAAA;;KAGO,eAAA,iBAAgC,MAAA;EAAA,SACjC,MAAA,EAAQ,OAAA;EAAA,SACR,UAAA,GAAa,gBAAA,CAAiB,OAAA;EAAA,SAC9B,KAAA,yBACa,eAAA,CAAgB,OAAA;IAAA,SACzB,SAAA,GAAY,cAAA;EAAA;AAAA"}
|