@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
package/CLAUDE.md
CHANGED
|
@@ -93,7 +93,7 @@ npx @databricks/appkit docs <query>
|
|
|
93
93
|
- [Function: createWorkspaceClient()](./docs/api/appkit/Function.createWorkspaceClient.md): Construct an AppKit workspace client.
|
|
94
94
|
- [Function: database()](./docs/api/appkit/Function.database.md): Create a typed database plugin registration for a finalized schema.
|
|
95
95
|
- [Function: defineManifest()](./docs/api/appkit/Function.defineManifest.md): Validates a raw manifest (typically a manifest.json import) against the
|
|
96
|
-
- [Function: defineSchema()](./docs/api/appkit/Function.defineSchema.md):
|
|
96
|
+
- [Function: defineSchema()](./docs/api/appkit/Function.defineSchema.md): Compile one declared schema. The returned type keeps the table names the
|
|
97
97
|
- [Function: defineTool()](./docs/api/appkit/Function.defineTool.md): Defines a single tool entry for a plugin's internal registry.
|
|
98
98
|
- [Function: enumColumn()](./docs/api/appkit/Function.enumColumn.md): Parameters
|
|
99
99
|
- [Function: executeFromRegistry()](./docs/api/appkit/Function.executeFromRegistry.md): Validates tool-call arguments against the entry's schema and invokes its
|
|
@@ -171,7 +171,7 @@ npx @databricks/appkit docs <query>
|
|
|
171
171
|
- [Interface: ResourceRequirement](./docs/api/appkit/Interface.ResourceRequirement.md): Declares a resource requirement for a plugin.
|
|
172
172
|
- [Interface: RunAgentInput](./docs/api/appkit/Interface.RunAgentInput.md): Properties
|
|
173
173
|
- [Interface: RunAgentResult](./docs/api/appkit/Interface.RunAgentResult.md): Properties
|
|
174
|
-
- [Interface: Schema](./docs/api/appkit/Interface.Schema.md):
|
|
174
|
+
- [Interface: Schema<TTableName>](./docs/api/appkit/Interface.Schema.md): One finalized schema. TTableName keeps the declared names in the type, so
|
|
175
175
|
- [Interface: SearchRequest](./docs/api/appkit/Interface.SearchRequest.md): Properties
|
|
176
176
|
- [Interface: SearchResponse<T>](./docs/api/appkit/Interface.SearchResponse.md): Type Parameters
|
|
177
177
|
- [Interface: SearchResult<T>](./docs/api/appkit/Interface.SearchResult.md): Type Parameters
|
package/dist/appkit/package.js
CHANGED
|
@@ -19,7 +19,12 @@ interface SkillDefinition {
|
|
|
19
19
|
source: SkillSource;
|
|
20
20
|
/** Absolute directory containing `SKILL.md` and any bundled resources. */
|
|
21
21
|
dir: string;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Relative posix paths of bundled resource files (excludes `SKILL.md`).
|
|
24
|
+
* Bundle skills recurse into subdirectories; volume skills list one level
|
|
25
|
+
* only, so nested files under a volume skill are not exposed to
|
|
26
|
+
* `read_skill_file`.
|
|
27
|
+
*/
|
|
23
28
|
files: string[];
|
|
24
29
|
/**
|
|
25
30
|
* Optional advisory tool allowlist from frontmatter `allowed-tools`. Surfaced
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/core/agent/skills/types.ts"],"mappings":";;KACY,WAAA;;;;;AASZ;;;UAAiB,eAAA;EAEf;EAAA,IAAA;EAIA;EAFA,WAAA;EAIQ;EAFR,IAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/core/agent/skills/types.ts"],"mappings":";;KACY,WAAA;;;;;AASZ;;;UAAiB,eAAA;EAEf;EAAA,IAAA;EAIA;EAFA,WAAA;EAIQ;EAFR,IAAA;EAWA;EATA,MAAA,EAAQ,WAAA;EAeI;EAbZ,GAAA;EAiBe;;;;;AAYjB;EAtBE,KAAA;;;;;;EAMA,YAAA;AAAA;;UAIe,iBAAA;EAaQ;EAXvB,IAAA;EACA,WAAA;AAAA;;;;;;;UASe,oBAAA;EACf,SAAA,EAAW,GAAA,SAAY,eAAA;EACvB,SAAA,EAAW,GAAA;EACX,OAAA,EAAS,iBAAA;AAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "./wire.js";
|
|
1
|
+
import { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_INCLUDE_DEPTH, MAX_INCLUDE_NODES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "./wire.js";
|
|
2
2
|
|
|
3
3
|
export { };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.d.ts","names":[],"sources":["../../../src/database/contract/wire.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"wire.d.ts","names":[],"sources":["../../../src/database/contract/wire.ts"],"mappings":";;KAsBY,OAAA;;KAEA,cAAA"}
|
|
@@ -15,6 +15,10 @@ const MAX_WHERE_DEPTH = 5;
|
|
|
15
15
|
const MAX_WHERE_GROUP_ITEMS = 20;
|
|
16
16
|
/** Max column conditions accepted across one runtime predicate tree. */
|
|
17
17
|
const MAX_WHERE_CONDITIONS = 50;
|
|
18
|
+
/** Max number of relation edges one include path may traverse. */
|
|
19
|
+
const MAX_INCLUDE_DEPTH = 2;
|
|
20
|
+
/** Max number of relation nodes across a complete include tree. */
|
|
21
|
+
const MAX_INCLUDE_NODES = 25;
|
|
18
22
|
/** Filter operators usable in the runtime WHERE translator and the `where` spec type. */
|
|
19
23
|
const FILTER_OPERATORS = Object.freeze([
|
|
20
24
|
"eq",
|
|
@@ -33,5 +37,5 @@ function isFilterOperator(token) {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
//#endregion
|
|
36
|
-
export { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator };
|
|
40
|
+
export { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_INCLUDE_DEPTH, MAX_INCLUDE_NODES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator };
|
|
37
41
|
//# sourceMappingURL=wire.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.js","names":[],"sources":["../../../src/database/contract/wire.ts"],"sourcesContent":["/** Max number of values allowed in an `in.(…)` list. */\nexport const IN_CAP = 100;\n/** Hard ceiling for a runtime query limit. */\nexport const MAX_LIMIT = 500;\n/** Hard ceiling for a runtime query offset. OFFSET scans are unbounded in cost. */\nexport const MAX_OFFSET = 10_000;\n/** Default page size when no `.limit()` is supplied. */\nexport const DEFAULT_LIMIT = 50;\n/** Max number of relations resolvable in a single `.include()`. */\nexport const MAX_INCLUDES = 10;\n/** Max nesting depth of `and`/`or` groups in one runtime predicate. */\nexport const MAX_WHERE_DEPTH = 5;\n/** Max members accepted by one runtime `and`/`or` group. */\nexport const MAX_WHERE_GROUP_ITEMS = 20;\n/** Max column conditions accepted across one runtime predicate tree. */\nexport const MAX_WHERE_CONDITIONS = 50;\n\n/** Scalar values accepted by primary-key operations. */\nexport type IdValue = string | number | bigint;\n/** Ordering accepted by typed clients and the runtime adapter. */\nexport type OrderDirection = \"asc\" | \"desc\";\n/** Filter operators usable in the runtime WHERE translator and the `where` spec type. */\nexport const FILTER_OPERATORS = Object.freeze([\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"like\",\n \"ilike\",\n \"in\",\n \"is\",\n] as const);\n\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\nexport function isFilterOperator(token: string): token is FilterOperator {\n return (FILTER_OPERATORS as readonly string[]).includes(token);\n}\n"],"mappings":";;AACA,MAAa,SAAS;;AAEtB,MAAa,YAAY;;AAEzB,MAAa,aAAa;;AAE1B,MAAa,gBAAgB;;AAE7B,MAAa,eAAe;;AAE5B,MAAa,kBAAkB;;AAE/B,MAAa,wBAAwB;;AAErC,MAAa,uBAAuB;;
|
|
1
|
+
{"version":3,"file":"wire.js","names":[],"sources":["../../../src/database/contract/wire.ts"],"sourcesContent":["/** Max number of values allowed in an `in.(…)` list. */\nexport const IN_CAP = 100;\n/** Hard ceiling for a runtime query limit. */\nexport const MAX_LIMIT = 500;\n/** Hard ceiling for a runtime query offset. OFFSET scans are unbounded in cost. */\nexport const MAX_OFFSET = 10_000;\n/** Default page size when no `.limit()` is supplied. */\nexport const DEFAULT_LIMIT = 50;\n/** Max number of relations resolvable in a single `.include()`. */\nexport const MAX_INCLUDES = 10;\n/** Max nesting depth of `and`/`or` groups in one runtime predicate. */\nexport const MAX_WHERE_DEPTH = 5;\n/** Max members accepted by one runtime `and`/`or` group. */\nexport const MAX_WHERE_GROUP_ITEMS = 20;\n/** Max column conditions accepted across one runtime predicate tree. */\nexport const MAX_WHERE_CONDITIONS = 50;\n/** Max number of relation edges one include path may traverse. */\nexport const MAX_INCLUDE_DEPTH = 2;\n/** Max number of relation nodes across a complete include tree. */\nexport const MAX_INCLUDE_NODES = 25;\n\n/** Scalar values accepted by primary-key operations. */\nexport type IdValue = string | number | bigint;\n/** Ordering accepted by typed clients and the runtime adapter. */\nexport type OrderDirection = \"asc\" | \"desc\";\n/** Filter operators usable in the runtime WHERE translator and the `where` spec type. */\nexport const FILTER_OPERATORS = Object.freeze([\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"like\",\n \"ilike\",\n \"in\",\n \"is\",\n] as const);\n\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\nexport function isFilterOperator(token: string): token is FilterOperator {\n return (FILTER_OPERATORS as readonly string[]).includes(token);\n}\n"],"mappings":";;AACA,MAAa,SAAS;;AAEtB,MAAa,YAAY;;AAEzB,MAAa,aAAa;;AAE1B,MAAa,gBAAgB;;AAE7B,MAAa,eAAe;;AAE5B,MAAa,kBAAkB;;AAE/B,MAAa,wBAAwB;;AAErC,MAAa,uBAAuB;;AAEpC,MAAa,oBAAoB;;AAEjC,MAAa,oBAAoB;;AAOjC,MAAa,mBAAmB,OAAO,OAAO;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAU;AAIX,SAAgB,iBAAiB,OAAwC;AACvE,QAAQ,iBAAuC,SAAS,MAAM"}
|
package/dist/database/errors.js
CHANGED
|
@@ -9,6 +9,10 @@ const definitions = {
|
|
|
9
9
|
message: "Invalid database request",
|
|
10
10
|
statusCode: 400
|
|
11
11
|
},
|
|
12
|
+
NOT_FOUND: {
|
|
13
|
+
message: "Database record not found",
|
|
14
|
+
statusCode: 404
|
|
15
|
+
},
|
|
12
16
|
CONFLICT: {
|
|
13
17
|
message: "Database conflict",
|
|
14
18
|
statusCode: 409
|
|
@@ -21,6 +25,10 @@ const definitions = {
|
|
|
21
25
|
message: "Database operation temporarily unavailable",
|
|
22
26
|
statusCode: 503
|
|
23
27
|
},
|
|
28
|
+
PAYLOAD_TOO_LARGE: {
|
|
29
|
+
message: "Database response is too large",
|
|
30
|
+
statusCode: 413
|
|
31
|
+
},
|
|
24
32
|
INTERNAL: {
|
|
25
33
|
message: "Database operation failed",
|
|
26
34
|
statusCode: 500
|
|
@@ -33,7 +41,9 @@ const definitions = {
|
|
|
33
41
|
const categoryByStatus = {
|
|
34
42
|
400: "INVALID_REQUEST",
|
|
35
43
|
403: "FORBIDDEN",
|
|
44
|
+
404: "NOT_FOUND",
|
|
36
45
|
409: "CONFLICT",
|
|
46
|
+
413: "PAYLOAD_TOO_LARGE",
|
|
37
47
|
503: "TRANSIENT"
|
|
38
48
|
};
|
|
39
49
|
/** AppKit-facing database failure with stable metadata and no driver details. */
|
|
@@ -41,11 +51,12 @@ var DatabasePluginError = class extends AppKitError {
|
|
|
41
51
|
code = "DATABASE_PLUGIN_ERROR";
|
|
42
52
|
isRetryable;
|
|
43
53
|
statusCode;
|
|
44
|
-
constructor(category, phase, runtimeMessage) {
|
|
54
|
+
constructor(category, phase, runtimeMessage, details) {
|
|
45
55
|
const definition = definitions[category];
|
|
46
56
|
super(phase === "runtime" && runtimeMessage ? runtimeMessage : definition.message, { clientMessage: definition.message });
|
|
47
57
|
this.category = category;
|
|
48
58
|
this.phase = phase;
|
|
59
|
+
this.details = details;
|
|
49
60
|
this.statusCode = definition.statusCode;
|
|
50
61
|
this.isRetryable = category === "TRANSIENT";
|
|
51
62
|
this.name = "DatabasePluginError";
|
|
@@ -55,10 +66,35 @@ var DatabasePluginError = class extends AppKitError {
|
|
|
55
66
|
function invalidDatabaseRequest(runtimeMessage) {
|
|
56
67
|
return new DatabasePluginError("INVALID_REQUEST", "runtime", runtimeMessage);
|
|
57
68
|
}
|
|
69
|
+
/** Refuse to publish a plugin whose configuration cannot be honored. */
|
|
70
|
+
function databaseSetupFailed() {
|
|
71
|
+
return new DatabasePluginError("SETUP_FAILED", "setup");
|
|
72
|
+
}
|
|
73
|
+
/** Reject untrusted request input, naming the field but never its value. */
|
|
74
|
+
function invalidDatabaseInput(path, message) {
|
|
75
|
+
return new DatabasePluginError("INVALID_REQUEST", "read", void 0, [{
|
|
76
|
+
path,
|
|
77
|
+
message
|
|
78
|
+
}]);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Name an unknown failure without its payload. A driver error carries the SQL
|
|
82
|
+
* text and its bound row values (for example a `DrizzleQueryError` retains
|
|
83
|
+
* `query` and `params`), so only constructor names walk into the log.
|
|
84
|
+
*/
|
|
85
|
+
function describeUnclassifiedError(error) {
|
|
86
|
+
const names = [];
|
|
87
|
+
let current = error;
|
|
88
|
+
for (let depth = 0; depth < 5 && current instanceof Error; depth++) {
|
|
89
|
+
names.push(current.name);
|
|
90
|
+
current = current.cause;
|
|
91
|
+
}
|
|
92
|
+
return names.length > 0 ? names.join(" <- ") : typeof error;
|
|
93
|
+
}
|
|
58
94
|
/** Add operation context without retaining an unknown error's details. */
|
|
59
95
|
function classifyDatabaseError(error, phase) {
|
|
60
96
|
if (error instanceof DatabasePluginError) return error.phase === phase ? error : new DatabasePluginError(error.category, phase);
|
|
61
|
-
logger.error("Unclassified database error during %s
|
|
97
|
+
logger.error("Unclassified database error during %s (%s)", phase, describeUnclassifiedError(error));
|
|
62
98
|
return new DatabasePluginError("INTERNAL", phase);
|
|
63
99
|
}
|
|
64
100
|
/** Restore the safe database category carried through `Plugin.execute()`. */
|
|
@@ -67,5 +103,5 @@ function databaseErrorFromStatus(status, phase) {
|
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
//#endregion
|
|
70
|
-
export { DatabasePluginError, classifyDatabaseError, databaseErrorFromStatus, invalidDatabaseRequest };
|
|
106
|
+
export { DatabasePluginError, classifyDatabaseError, databaseErrorFromStatus, databaseSetupFailed, invalidDatabaseInput, invalidDatabaseRequest };
|
|
71
107
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../src/database/errors.ts"],"sourcesContent":["import { AppKitError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"database\");\n\nexport type DatabaseErrorCategory =\n | \"INVALID_REQUEST\"\n | \"CONFLICT\"\n | \"FORBIDDEN\"\n | \"TRANSIENT\"\n | \"INTERNAL\"\n | \"SETUP_FAILED\";\n\ntype DatabaseErrorPhase =\n | \"setup\"\n | \"shutdown\"\n | \"read\"\n | \"write\"\n | \"transaction\"\n | \"runtime\";\n\nconst definitions: Record<\n DatabaseErrorCategory,\n { readonly message: string; readonly statusCode: number }\n> = {\n INVALID_REQUEST: { message: \"Invalid database request\", statusCode: 400 },\n CONFLICT: { message: \"Database conflict\", statusCode: 409 },\n FORBIDDEN: { message: \"Database operation forbidden\", statusCode: 403 },\n TRANSIENT: {\n message: \"Database operation temporarily unavailable\",\n statusCode: 503,\n },\n INTERNAL: { message: \"Database operation failed\", statusCode: 500 },\n SETUP_FAILED: { message: \"Database setup failed\", statusCode: 500 },\n};\n\nconst categoryByStatus: Readonly<Record<number, DatabaseErrorCategory>> = {\n 400: \"INVALID_REQUEST\",\n 403: \"FORBIDDEN\",\n 409: \"CONFLICT\",\n 503: \"TRANSIENT\",\n};\n\n/** AppKit-facing database failure with stable metadata and no driver details. */\nexport class DatabasePluginError extends AppKitError {\n readonly code = \"DATABASE_PLUGIN_ERROR\";\n readonly isRetryable: boolean;\n readonly statusCode: number;\n\n constructor(\n readonly category: DatabaseErrorCategory,\n readonly phase: DatabaseErrorPhase,\n runtimeMessage?: string,\n ) {\n const definition = definitions[category];\n // Plugin boundaries replace runtime diagnostics with the stable message.\n super(\n phase === \"runtime\" && runtimeMessage\n ? runtimeMessage\n : definition.message,\n {\n clientMessage: definition.message,\n },\n );\n this.statusCode = definition.statusCode;\n this.isRetryable = category === \"TRANSIENT\";\n this.name = \"DatabasePluginError\";\n }\n}\n\n/** Keep runtime diagnostics internal until a plugin boundary classifies them. */\nexport function invalidDatabaseRequest(\n runtimeMessage?: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"runtime\", runtimeMessage);\n}\n\n/** Add operation context without retaining an unknown error's details. */\nexport function classifyDatabaseError(\n error: unknown,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n if (error instanceof DatabasePluginError) {\n return error.phase === phase\n ? error\n : new DatabasePluginError(error.category, phase);\n }\n logger.error(\"Unclassified database error during %s
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/database/errors.ts"],"sourcesContent":["import { AppKitError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"database\");\n\nexport type DatabaseErrorCategory =\n | \"INVALID_REQUEST\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"FORBIDDEN\"\n | \"TRANSIENT\"\n | \"PAYLOAD_TOO_LARGE\"\n | \"INTERNAL\"\n | \"SETUP_FAILED\";\n\n/** Which request field a rejection concerns; it never carries caller values. */\nexport interface DatabaseErrorDetail {\n readonly path: readonly string[];\n readonly message: string;\n}\n\ntype DatabaseErrorPhase =\n | \"setup\"\n | \"shutdown\"\n | \"read\"\n | \"write\"\n | \"transaction\"\n | \"runtime\";\n\nconst definitions: Record<\n DatabaseErrorCategory,\n { readonly message: string; readonly statusCode: number }\n> = {\n INVALID_REQUEST: { message: \"Invalid database request\", statusCode: 400 },\n NOT_FOUND: { message: \"Database record not found\", statusCode: 404 },\n CONFLICT: { message: \"Database conflict\", statusCode: 409 },\n FORBIDDEN: { message: \"Database operation forbidden\", statusCode: 403 },\n TRANSIENT: {\n message: \"Database operation temporarily unavailable\",\n statusCode: 503,\n },\n PAYLOAD_TOO_LARGE: {\n message: \"Database response is too large\",\n statusCode: 413,\n },\n INTERNAL: { message: \"Database operation failed\", statusCode: 500 },\n SETUP_FAILED: { message: \"Database setup failed\", statusCode: 500 },\n};\n\nconst categoryByStatus: Readonly<Record<number, DatabaseErrorCategory>> = {\n 400: \"INVALID_REQUEST\",\n 403: \"FORBIDDEN\",\n 404: \"NOT_FOUND\",\n 409: \"CONFLICT\",\n 413: \"PAYLOAD_TOO_LARGE\",\n 503: \"TRANSIENT\",\n};\n\n/** AppKit-facing database failure with stable metadata and no driver details. */\nexport class DatabasePluginError extends AppKitError {\n readonly code = \"DATABASE_PLUGIN_ERROR\";\n readonly isRetryable: boolean;\n readonly statusCode: number;\n\n constructor(\n readonly category: DatabaseErrorCategory,\n readonly phase: DatabaseErrorPhase,\n runtimeMessage?: string,\n readonly details?: readonly DatabaseErrorDetail[],\n ) {\n const definition = definitions[category];\n // Plugin boundaries replace runtime diagnostics with the stable message.\n super(\n phase === \"runtime\" && runtimeMessage\n ? runtimeMessage\n : definition.message,\n {\n clientMessage: definition.message,\n },\n );\n this.statusCode = definition.statusCode;\n this.isRetryable = category === \"TRANSIENT\";\n this.name = \"DatabasePluginError\";\n }\n}\n\n/** Keep runtime diagnostics internal until a plugin boundary classifies them. */\nexport function invalidDatabaseRequest(\n runtimeMessage?: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"runtime\", runtimeMessage);\n}\n\n/** Refuse to publish a plugin whose configuration cannot be honored. */\nexport function databaseSetupFailed(): DatabasePluginError {\n return new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n}\n\n/** Reject untrusted request input, naming the field but never its value. */\nexport function invalidDatabaseInput(\n path: readonly string[],\n message: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"read\", undefined, [\n { path, message },\n ]);\n}\n\n/**\n * Name an unknown failure without its payload. A driver error carries the SQL\n * text and its bound row values (for example a `DrizzleQueryError` retains\n * `query` and `params`), so only constructor names walk into the log.\n */\nfunction describeUnclassifiedError(error: unknown): string {\n const names: string[] = [];\n let current: unknown = error;\n for (let depth = 0; depth < 5 && current instanceof Error; depth++) {\n names.push(current.name);\n current = current.cause;\n }\n return names.length > 0 ? names.join(\" <- \") : typeof error;\n}\n\n/** Add operation context without retaining an unknown error's details. */\nexport function classifyDatabaseError(\n error: unknown,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n if (error instanceof DatabasePluginError) {\n return error.phase === phase\n ? error\n : new DatabasePluginError(error.category, phase);\n }\n logger.error(\n \"Unclassified database error during %s (%s)\",\n phase,\n describeUnclassifiedError(error),\n );\n return new DatabasePluginError(\"INTERNAL\", phase);\n}\n\n/** Restore the safe database category carried through `Plugin.execute()`. */\nexport function databaseErrorFromStatus(\n status: number,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n const category = categoryByStatus[status] ?? \"INTERNAL\";\n return new DatabasePluginError(category, phase);\n}\n"],"mappings":";;;;;AAGA,MAAM,SAAS,aAAa,WAAW;AA0BvC,MAAM,cAGF;CACF,iBAAiB;EAAE,SAAS;EAA4B,YAAY;EAAK;CACzE,WAAW;EAAE,SAAS;EAA6B,YAAY;EAAK;CACpE,UAAU;EAAE,SAAS;EAAqB,YAAY;EAAK;CAC3D,WAAW;EAAE,SAAS;EAAgC,YAAY;EAAK;CACvE,WAAW;EACT,SAAS;EACT,YAAY;EACb;CACD,mBAAmB;EACjB,SAAS;EACT,YAAY;EACb;CACD,UAAU;EAAE,SAAS;EAA6B,YAAY;EAAK;CACnE,cAAc;EAAE,SAAS;EAAyB,YAAY;EAAK;CACpE;AAED,MAAM,mBAAoE;CACxE,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;AAGD,IAAa,sBAAb,cAAyC,YAAY;CACnD,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CAET,YACE,AAAS,UACT,AAAS,OACT,gBACA,AAAS,SACT;EACA,MAAM,aAAa,YAAY;AAE/B,QACE,UAAU,aAAa,iBACnB,iBACA,WAAW,SACf,EACE,eAAe,WAAW,SAC3B,CACF;EAdQ;EACA;EAEA;AAYT,OAAK,aAAa,WAAW;AAC7B,OAAK,cAAc,aAAa;AAChC,OAAK,OAAO;;;;AAKhB,SAAgB,uBACd,gBACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,WAAW,eAAe;;;AAI9E,SAAgB,sBAA2C;AACzD,QAAO,IAAI,oBAAoB,gBAAgB,QAAQ;;;AAIzD,SAAgB,qBACd,MACA,SACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,QAAQ,QAAW,CACnE;EAAE;EAAM;EAAS,CAClB,CAAC;;;;;;;AAQJ,SAAS,0BAA0B,OAAwB;CACzD,MAAM,QAAkB,EAAE;CAC1B,IAAI,UAAmB;AACvB,MAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,mBAAmB,OAAO,SAAS;AAClE,QAAM,KAAK,QAAQ,KAAK;AACxB,YAAU,QAAQ;;AAEpB,QAAO,MAAM,SAAS,IAAI,MAAM,KAAK,OAAO,GAAG,OAAO;;;AAIxD,SAAgB,sBACd,OACA,OACqB;AACrB,KAAI,iBAAiB,oBACnB,QAAO,MAAM,UAAU,QACnB,QACA,IAAI,oBAAoB,MAAM,UAAU,MAAM;AAEpD,QAAO,MACL,8CACA,OACA,0BAA0B,MAAM,CACjC;AACD,QAAO,IAAI,oBAAoB,YAAY,MAAM;;;AAInD,SAAgB,wBACd,QACA,OACqB;AAErB,QAAO,IAAI,oBADM,iBAAiB,WAAW,YACJ,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-path.js","names":[],"sources":["../../../src/database/runtime/data-path.ts"],"sourcesContent":["import {\n DEFAULT_LIMIT,\n type FilterOperator,\n type IdValue,\n MAX_LIMIT,\n MAX_OFFSET,\n type OrderDirection,\n} from \"../contract\";\nimport { invalidDatabaseRequest } from \"../errors\";\nimport type { AppKitTable, ColumnMeta } from \"../schema-builder\";\n\nexport type { IdValue, OrderDirection };\nexport type ScalarValue = string | number | bigint | boolean | null;\n/** Operators for one column; array operands are reserved for `in`. */\nexport type FilterOps = Partial<\n Record<FilterOperator, ScalarValue | readonly ScalarValue[]>\n>;\nexport type WhereValue = ScalarValue | readonly ScalarValue[] | FilterOps;\n/** Direct-column predicates with explicit `and` and `or` predicate groups. */\nexport type WhereClause = Readonly<\n Record<string, WhereValue | readonly WhereClause[]>\n>;\n\nexport type OrderSpec = Readonly<Record<string, OrderDirection>>;\n\nexport interface IncludeOptions {\n readonly select?: readonly string[];\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly limit?: number;\n}\n\n/** Selection and bounds for one declared relation edge. */\nexport type IncludeSpec = Readonly<Record<string, boolean | IncludeOptions>>;\n\n/** A bounded root read; adapters apply defaults and validate explicit bounds. */\nexport interface QuerySpec {\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly select?: readonly string[];\n readonly include?: IncludeSpec;\n readonly limit?: number;\n readonly offset?: number;\n}\n\nexport type Row = Record<string, unknown>;\n\n/** Combine predicates without making callers understand the wire shape. */\nexport function andWhere(\n existing: WhereClause | undefined,\n next: WhereClause,\n): WhereClause {\n return existing === undefined ? next : { and: [existing, next] };\n}\n\n/** Internal AppKit execution port; field names are schema-owned identifiers. */\nexport interface DataPath {\n /** Read a bounded collection from one finalized table. */\n select(table: AppKitTable, spec: QuerySpec): Promise<Row[]>;\n /** Read by the sole primary key while preserving supported query state. */\n findOne(\n table: AppKitTable,\n id: IdValue,\n spec?: Pick<QuerySpec, \"where\" | \"select\" | \"include\">,\n ): Promise<Row | null>;\n count(table: AppKitTable, where?: WhereClause): Promise<number>;\n /** Return exactly one inserted row; zero or many is an invariant failure. */\n insert(table: AppKitTable, values: Row): Promise<Row>;\n /** Return null for zero updated rows and reject more than one. */\n update(table: AppKitTable, id: IdValue, values: Row): Promise<Row | null>;\n /** Return exactly one row for a validated primary-key or unique conflict. */\n upsert(table: AppKitTable, values: Row, onConflict: string): Promise<Row>;\n /** Return false for zero deleted rows, true for one, and reject many. */\n delete(table: AppKitTable, id: IdValue): Promise<boolean>;\n /** Execute tagged SQL whose interpolations are parameter values, not SQL. */\n raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]>;\n /** Run the callback with one transaction-bound DataPath. */\n transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T>;\n}\n\n/** Validate an explicit root or relation row limit. */\nexport function validateLimit(limit: number): number {\n if (!Number.isInteger(limit) || limit < 0 || limit > MAX_LIMIT) {\n throw invalidDatabaseRequest(\n `limit must be an integer between 0 and ${MAX_LIMIT}`,\n );\n }\n return limit;\n}\n\n/** Apply the conservative collection default when no limit is supplied. */\nexport function limitOrDefault(limit?: number): number {\n return limit === undefined ? DEFAULT_LIMIT : validateLimit(limit);\n}\n\n/** Validate an explicit root or relation row offset. */\nexport function validateOffset(offset: number): number {\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > MAX_OFFSET) {\n throw invalidDatabaseRequest(\n `offset must be an integer between 0 and ${MAX_OFFSET}`,\n );\n }\n return offset;\n}\n\n/** Resolve the sole primary key required by keyed operations. */\nexport function primaryKeyMeta(table: AppKitTable): ColumnMeta {\n const primaryKeys = Object.values(table.$columns).filter(\n (column) => column.primaryKey,\n );\n if (primaryKeys.length !== 1) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" has no primary key`);\n }\n return primaryKeys[0];\n}\n\n/** Resolve an upsert target that PostgreSQL can use for conflict detection. */\nexport function conflictTargetMeta(\n table: AppKitTable,\n columnName: string,\n): ColumnMeta {\n const column = table.$columns[columnName];\n if (!column || (!column.primaryKey && !column.unique)) {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${columnName}\" is not a conflict target`,\n );\n }\n return column;\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"data-path.js","names":[],"sources":["../../../src/database/runtime/data-path.ts"],"sourcesContent":["import {\n DEFAULT_LIMIT,\n type FilterOperator,\n type IdValue,\n MAX_LIMIT,\n MAX_OFFSET,\n type OrderDirection,\n} from \"../contract\";\nimport { invalidDatabaseRequest } from \"../errors\";\nimport type { AppKitTable, ColumnMeta } from \"../schema-builder\";\n\nexport type { IdValue, OrderDirection };\nexport type ScalarValue = string | number | bigint | boolean | null;\n/** Operators for one column; array operands are reserved for `in`. */\nexport type FilterOps = Partial<\n Record<FilterOperator, ScalarValue | readonly ScalarValue[]>\n>;\nexport type WhereValue = ScalarValue | readonly ScalarValue[] | FilterOps;\n/** Direct-column predicates with explicit `and` and `or` predicate groups. */\nexport type WhereClause = Readonly<\n Record<string, WhereValue | readonly WhereClause[]>\n>;\n\nexport type OrderSpec = Readonly<Record<string, OrderDirection>>;\n\nexport interface IncludeOptions {\n readonly select?: readonly string[];\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly limit?: number;\n readonly include?: IncludeSpec;\n}\n\n/** Selection and bounds for one declared relation edge. */\nexport type IncludeSpec = Readonly<Record<string, boolean | IncludeOptions>>;\n\n/** A bounded root read; adapters apply defaults and validate explicit bounds. */\nexport interface QuerySpec {\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly select?: readonly string[];\n readonly include?: IncludeSpec;\n readonly limit?: number;\n readonly offset?: number;\n}\n\nexport type Row = Record<string, unknown>;\n\n/** Combine predicates without making callers understand the wire shape. */\nexport function andWhere(\n existing: WhereClause | undefined,\n next: WhereClause,\n): WhereClause {\n return existing === undefined ? next : { and: [existing, next] };\n}\n\n/** Internal AppKit execution port; field names are schema-owned identifiers. */\nexport interface DataPath {\n /** Read a bounded collection from one finalized table. */\n select(table: AppKitTable, spec: QuerySpec): Promise<Row[]>;\n /** Read by the sole primary key while preserving supported query state. */\n findOne(\n table: AppKitTable,\n id: IdValue,\n spec?: Pick<QuerySpec, \"where\" | \"select\" | \"include\">,\n ): Promise<Row | null>;\n count(table: AppKitTable, where?: WhereClause): Promise<number>;\n /** Return exactly one inserted row; zero or many is an invariant failure. */\n insert(table: AppKitTable, values: Row): Promise<Row>;\n /** Return null for zero updated rows and reject more than one. */\n update(table: AppKitTable, id: IdValue, values: Row): Promise<Row | null>;\n /** Return exactly one row for a validated primary-key or unique conflict. */\n upsert(table: AppKitTable, values: Row, onConflict: string): Promise<Row>;\n /** Return false for zero deleted rows, true for one, and reject many. */\n delete(table: AppKitTable, id: IdValue): Promise<boolean>;\n /** Execute tagged SQL whose interpolations are parameter values, not SQL. */\n raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]>;\n /** Run the callback with one transaction-bound DataPath. */\n transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T>;\n}\n\n/** Validate an explicit root or relation row limit. */\nexport function validateLimit(limit: number): number {\n if (!Number.isInteger(limit) || limit < 0 || limit > MAX_LIMIT) {\n throw invalidDatabaseRequest(\n `limit must be an integer between 0 and ${MAX_LIMIT}`,\n );\n }\n return limit;\n}\n\n/** Apply the conservative collection default when no limit is supplied. */\nexport function limitOrDefault(limit?: number): number {\n return limit === undefined ? DEFAULT_LIMIT : validateLimit(limit);\n}\n\n/** Validate an explicit root or relation row offset. */\nexport function validateOffset(offset: number): number {\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > MAX_OFFSET) {\n throw invalidDatabaseRequest(\n `offset must be an integer between 0 and ${MAX_OFFSET}`,\n );\n }\n return offset;\n}\n\n/** Resolve the sole primary key required by keyed operations. */\nexport function primaryKeyMeta(table: AppKitTable): ColumnMeta {\n const primaryKeys = Object.values(table.$columns).filter(\n (column) => column.primaryKey,\n );\n if (primaryKeys.length !== 1) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" has no primary key`);\n }\n return primaryKeys[0];\n}\n\n/** Resolve an upsert target that PostgreSQL can use for conflict detection. */\nexport function conflictTargetMeta(\n table: AppKitTable,\n columnName: string,\n): ColumnMeta {\n const column = table.$columns[columnName];\n if (!column || (!column.primaryKey && !column.unique)) {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${columnName}\" is not a conflict target`,\n );\n }\n return column;\n}\n"],"mappings":";;;;;;AAiDA,SAAgB,SACd,UACA,MACa;AACb,QAAO,aAAa,SAAY,OAAO,EAAE,KAAK,CAAC,UAAU,KAAK,EAAE;;;AAgClE,SAAgB,cAAc,OAAuB;AACnD,KAAI,CAAC,OAAO,UAAU,MAAM,IAAI,QAAQ,KAAK,QAAQ,UACnD,OAAM,uBACJ,0CAA0C,YAC3C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAwB;AACrD,QAAO,UAAU,SAAY,gBAAgB,cAAc,MAAM;;;AAInE,SAAgB,eAAe,QAAwB;AACrD,KAAI,CAAC,OAAO,cAAc,OAAO,IAAI,SAAS,KAAK,SAAS,WAC1D,OAAM,uBACJ,2CAA2C,aAC5C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAgC;CAC7D,MAAM,cAAc,OAAO,OAAO,MAAM,SAAS,CAAC,QAC/C,WAAW,OAAO,WACpB;AACD,KAAI,YAAY,WAAW,EACzB,OAAM,uBAAuB,UAAU,MAAM,MAAM,sBAAsB;AAE3E,QAAO,YAAY;;;AAIrB,SAAgB,mBACd,OACA,YACY;CACZ,MAAM,SAAS,MAAM,SAAS;AAC9B,KAAI,CAAC,UAAW,CAAC,OAAO,cAAc,CAAC,OAAO,OAC5C,OAAM,uBACJ,WAAW,MAAM,MAAM,GAAG,WAAW,4BACtC;AAEH,QAAO"}
|
|
@@ -65,7 +65,7 @@ function sqlStateOf(error) {
|
|
|
65
65
|
/** Classify SQLSTATE without retaining the driver error or its properties. */
|
|
66
66
|
function classifyDriverError(error) {
|
|
67
67
|
const code = sqlStateOf(error);
|
|
68
|
-
const category = code === "40001" || code === "40P01" ? "TRANSIENT" : code === "42501" ? "FORBIDDEN" : code?.startsWith("23") ? "CONFLICT" : "INTERNAL";
|
|
68
|
+
const category = code === "40001" || code === "40P01" || code === "57014" ? "TRANSIENT" : code === "42501" ? "FORBIDDEN" : code?.startsWith("23") ? "CONFLICT" : "INTERNAL";
|
|
69
69
|
logger.error("Database driver error classified as %s (SQLSTATE %s)", category, code ?? "unknown");
|
|
70
70
|
return new DatabasePluginError(category, "runtime");
|
|
71
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-data-path.js","names":[],"sources":["../../../../src/database/runtime/engine/drizzle-data-path.ts"],"sourcesContent":["import { and, eq, isSQLWrapper, type SQL, sql } from \"drizzle-orm\";\nimport { drizzle, type NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport type { Pool } from \"pg\";\n\nimport { createLogger } from \"../../../logging/logger\";\nimport {\n type DatabaseErrorCategory,\n DatabasePluginError,\n invalidDatabaseRequest,\n} from \"../../errors\";\nimport type { AppKitTable, ColumnMeta, Schema } from \"../../schema-builder\";\nimport { buildEngineRelations } from \"../../schema-builder/engine/relations\";\nimport { columnValueSchema } from \"../../schema-builder/validators\";\nimport {\n conflictTargetMeta,\n type DataPath,\n type IdValue,\n limitOrDefault,\n primaryKeyMeta,\n type Row,\n validateOffset,\n type WhereClause,\n} from \"../data-path\";\nimport {\n columnOf,\n type ColumnAccess,\n defaultColumns,\n publicColumnNames,\n returningColumns,\n selectToColumns,\n translateInclude,\n translateOrder,\n translateWhere,\n} from \"./translate\";\n\nconst logger = createLogger(\"database\");\n\n/** Concrete Drizzle seam shared by the adapter and its focused tests. */\nexport type DrizzleDb = NodePgDatabase<Record<string, never>>;\n\nexport interface DrizzleDataPathOptions {\n /** Explicit capability for trusted server code that must read private data. */\n readonly columnAccess?: ColumnAccess;\n}\n\n/** Bind finalized AppKit metadata to a relational Drizzle database. */\nexport function createDrizzleDb(pool: Pool, schema: Schema): DrizzleDb {\n const completeSchema: Record<string, unknown> = Object.assign(\n Object.create(null),\n schema.$engine,\n buildEngineRelations(schema.$tables),\n );\n return drizzle(pool, { schema: completeSchema }) as unknown as DrizzleDb;\n}\n\ninterface RelationalQueryBuilder {\n findMany(config: Record<string, unknown>): Promise<Row[]>;\n findFirst(config: Record<string, unknown>): Promise<Row | undefined>;\n}\n\n/** Reject same-name or forged tables by requiring finalized object identity. */\nfunction assertRegisteredTable(schema: Schema, table: AppKitTable): void {\n if (schema.$tables[table.$name] !== table) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" is not registered`);\n }\n}\n\nfunction relationalQueryBuilder(\n db: DrizzleDb,\n schema: Schema,\n table: AppKitTable,\n): RelationalQueryBuilder {\n assertRegisteredTable(schema, table);\n const query = (db.query as unknown as Record<string, RelationalQueryBuilder>)[\n table.$name\n ];\n if (!query) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" is not registered`);\n }\n return query;\n}\n\nfunction selectedColumns(\n table: AppKitTable,\n select?: readonly string[],\n access: ColumnAccess = \"public\",\n): Record<string, true> {\n return select === undefined\n ? defaultColumns(table)\n : selectToColumns(table, select, access);\n}\n\ntype MutationKind = \"insert\" | \"update\";\n\ninterface MutationSchema {\n safeParse(\n value: unknown,\n ):\n | { readonly success: true; readonly data: unknown }\n | { readonly success: false };\n}\n\n/** Apply the finalized write schema again at the persistence boundary. */\nfunction mutationValues(\n table: AppKitTable,\n values: Row,\n kind: MutationKind,\n): Row {\n if (values === null || typeof values !== \"object\" || Array.isArray(values)) {\n throw invalidDatabaseRequest(\"Database mutation values must be an object\");\n }\n for (const [key, value] of Object.entries(values)) {\n if (!Object.hasOwn(table.$columns, key)) {\n throw invalidDatabaseRequest(`Unknown column \"${table.$name}.${key}\"`);\n }\n if (isSQLWrapper(value)) {\n throw invalidDatabaseRequest(\n \"Database mutation values cannot contain SQL\",\n );\n }\n }\n const schema = (\n kind === \"insert\" ? table.$insertSchema : table.$updateSchema\n ) as MutationSchema;\n const result = schema.safeParse(values);\n if (!result.success) {\n throw invalidDatabaseRequest(`Invalid database ${kind} values`);\n }\n return result.data as Row;\n}\n\n/** Never change row identity or the conflict key during the update half. */\nfunction upsertUpdateValues(\n table: AppKitTable,\n parameters: Row,\n conflictTarget: string,\n): Row {\n const updates: Row = {};\n for (const [key, value] of Object.entries(parameters)) {\n const column = table.$columns[key];\n if (\n column.primaryKey ||\n column.serverGenerated ||\n column.columnName === conflictTarget\n ) {\n continue;\n }\n updates[key] = value;\n }\n if (Object.keys(updates).length > 0) return updates;\n\n // A safe self-assignment preserves the exactly-one-row upsert contract.\n return { [conflictTarget]: columnOf(table, conflictTarget) };\n}\n\n// Drizzle wraps driver failures in DrizzleQueryError, so the SQLSTATE sits on a\n// nested `cause` rather than the thrown error. Walk a bounded chain to find it.\nconst MAX_CAUSE_DEPTH = 5;\n\nfunction sqlStateOf(error: unknown): string | undefined {\n let current = error;\n for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {\n if (!current || typeof current !== \"object\") return undefined;\n try {\n const candidate = Reflect.get(current, \"code\");\n // SQLSTATE is always a five-character alphanumeric class code.\n if (typeof candidate === \"string\" && /^[0-9A-Z]{5}$/.test(candidate)) {\n return candidate;\n }\n current = Reflect.get(current, \"cause\");\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/** Classify SQLSTATE without retaining the driver error or its properties. */\nfunction classifyDriverError(error: unknown): DatabasePluginError {\n const code = sqlStateOf(error);\n const category: DatabaseErrorCategory =\n code === \"40001\" || code === \"40P01\"\n ? \"TRANSIENT\"\n : code === \"42501\"\n ? \"FORBIDDEN\"\n : code?.startsWith(\"23\")\n ? \"CONFLICT\"\n : \"INTERNAL\";\n logger.error(\n \"Database driver error classified as %s (SQLSTATE %s)\",\n category,\n code ?? \"unknown\",\n );\n return new DatabasePluginError(category, \"runtime\");\n}\n\nasync function runDatabaseOperation<T>(\n operation: () => Promise<T>,\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof DatabasePluginError) throw error;\n throw classifyDriverError(error);\n }\n}\n\n/** Apply the adapter's column capability before rows reach cardinality checks. */\nfunction mutationRows(\n table: AppKitTable,\n rows: Row[],\n access: ColumnAccess,\n): Row[] {\n if (access === \"trusted\") return rows;\n const names = publicColumnNames(table);\n return rows.map((row) => {\n const projected: Row = {};\n for (const name of names) {\n if (Object.hasOwn(row, name)) projected[name] = row[name];\n }\n return projected;\n });\n}\n\n/** Resolve and validate the sole key once before a keyed operation executes. */\nfunction validatedPrimaryKey(\n table: AppKitTable,\n id: unknown,\n): { readonly meta: ColumnMeta; readonly value: IdValue } {\n const meta = primaryKeyMeta(table);\n const result = columnValueSchema(meta).safeParse(id);\n if (!result.success) {\n throw invalidDatabaseRequest(\n `Invalid primary-key value for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n return { meta, value: result.data as IdValue };\n}\n\n// Enforce the single-row DataPath contract before results reach callers.\nfunction expectExactlyOne(rows: Row[]): Row {\n if (rows.length !== 1) {\n throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n }\n return rows[0];\n}\n\nfunction expectZeroOrOne(rows: Row[]): Row | null {\n if (rows.length > 1) {\n throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n }\n return rows[0] ?? null;\n}\n\n/** Adapt a Drizzle database to AppKit's internal execution port. */\nexport function createDrizzleDataPath(\n db: DrizzleDb,\n schema: Schema,\n options: DrizzleDataPathOptions = {},\n): DataPath {\n const columnAccess = options.columnAccess ?? \"public\";\n const pgTable = (table: AppKitTable): PgTable => {\n assertRegisteredTable(schema, table);\n return table.$engine as unknown as PgTable;\n };\n const whereSql = (\n table: AppKitTable,\n where?: WhereClause,\n ): SQL | undefined =>\n where === undefined\n ? undefined\n : translateWhere(table, where, columnAccess);\n\n return {\n async select(table, spec) {\n return runDatabaseOperation(() =>\n relationalQueryBuilder(db, schema, table).findMany({\n where: whereSql(table, spec.where),\n orderBy:\n spec.order === undefined\n ? undefined\n : translateOrder(table, spec.order, columnAccess),\n columns: selectedColumns(table, spec.select, columnAccess),\n with:\n spec.include === undefined\n ? undefined\n : translateInclude(table, schema, spec.include, columnAccess),\n limit: limitOrDefault(spec.limit),\n offset:\n spec.offset === undefined ? undefined : validateOffset(spec.offset),\n }),\n );\n },\n\n async findOne(table, id, spec) {\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const primaryKeyPredicate = eq(\n columnOf(table, primaryKey.columnName),\n validatedId,\n );\n const additionalPredicate =\n spec?.where === undefined\n ? undefined\n : translateWhere(table, spec.where, columnAccess);\n const row = await runDatabaseOperation(() =>\n relationalQueryBuilder(db, schema, table).findFirst({\n where:\n additionalPredicate === undefined\n ? primaryKeyPredicate\n : and(primaryKeyPredicate, additionalPredicate),\n columns: selectedColumns(table, spec?.select, columnAccess),\n with:\n spec?.include === undefined\n ? undefined\n : translateInclude(table, schema, spec.include, columnAccess),\n }),\n );\n return row ?? null;\n },\n\n async count(table, where) {\n return runDatabaseOperation(() =>\n db.$count(pgTable(table), whereSql(table, where)),\n );\n },\n\n async insert(table, values) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"insert\");\n const rows = await runDatabaseOperation(() =>\n db\n .insert(engineTable)\n .values(parameters)\n .returning(returningColumns(table, columnAccess)),\n );\n return expectExactlyOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async update(table, id, values) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"update\");\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const rows = await runDatabaseOperation(() =>\n db\n .update(engineTable)\n .set(parameters)\n .where(eq(columnOf(table, primaryKey.columnName), validatedId))\n .returning(returningColumns(table, columnAccess)),\n );\n return expectZeroOrOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async upsert(table, values, onConflict) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"insert\");\n const target = conflictTargetMeta(table, onConflict);\n const updates = upsertUpdateValues(table, parameters, target.columnName);\n const rows = await runDatabaseOperation(() =>\n db\n .insert(engineTable)\n .values(parameters)\n .onConflictDoUpdate({\n target: columnOf(table, target.columnName),\n set: updates,\n })\n .returning(returningColumns(table, columnAccess)),\n );\n return expectExactlyOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async delete(table, id) {\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const rows = await runDatabaseOperation(() =>\n db\n .delete(pgTable(table))\n .where(eq(columnOf(table, primaryKey.columnName), validatedId))\n .returning({ id: columnOf(table, primaryKey.columnName) }),\n );\n return expectZeroOrOne(rows as Row[]) !== null;\n },\n\n async raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]> {\n // SQL wrappers carry structure; tagged interpolations may carry values only.\n if (values.some((value) => isSQLWrapper(value))) {\n throw invalidDatabaseRequest(\n \"Tagged SQL interpolations must be parameter values\",\n );\n }\n const result = await runDatabaseOperation(() =>\n db.execute(sql(strings, ...values.map((value) => sql.param(value)))),\n );\n return ((result as { rows?: unknown }).rows ?? result) as T[];\n },\n\n async transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T> {\n let callbackFailed = false;\n let callbackError: unknown;\n try {\n return await db.transaction(async (transaction) => {\n try {\n return await callback(\n createDrizzleDataPath(\n transaction as unknown as DrizzleDb,\n schema,\n { columnAccess },\n ),\n );\n } catch (error) {\n callbackFailed = true;\n callbackError = error;\n throw error;\n }\n });\n } catch (error) {\n // Callback errors are application-owned; sanitize only tx lifecycle errors.\n if (\n callbackFailed &&\n (error === callbackError ||\n callbackError instanceof DatabasePluginError)\n ) {\n throw callbackError;\n }\n if (error instanceof DatabasePluginError) throw error;\n throw classifyDriverError(error);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drizzle-data-path.js","names":[],"sources":["../../../../src/database/runtime/engine/drizzle-data-path.ts"],"sourcesContent":["import { and, eq, isSQLWrapper, type SQL, sql } from \"drizzle-orm\";\nimport { drizzle, type NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport type { Pool } from \"pg\";\n\nimport { createLogger } from \"../../../logging/logger\";\nimport {\n type DatabaseErrorCategory,\n DatabasePluginError,\n invalidDatabaseRequest,\n} from \"../../errors\";\nimport type { AppKitTable, ColumnMeta, Schema } from \"../../schema-builder\";\nimport { buildEngineRelations } from \"../../schema-builder/engine/relations\";\nimport { columnValueSchema } from \"../../schema-builder/validators\";\nimport {\n conflictTargetMeta,\n type DataPath,\n type IdValue,\n limitOrDefault,\n primaryKeyMeta,\n type Row,\n validateOffset,\n type WhereClause,\n} from \"../data-path\";\nimport {\n columnOf,\n type ColumnAccess,\n defaultColumns,\n publicColumnNames,\n returningColumns,\n selectToColumns,\n translateInclude,\n translateOrder,\n translateWhere,\n} from \"./translate\";\n\nconst logger = createLogger(\"database\");\n\n/** Concrete Drizzle seam shared by the adapter and its focused tests. */\nexport type DrizzleDb = NodePgDatabase<Record<string, never>>;\n\nexport interface DrizzleDataPathOptions {\n /** Explicit capability for trusted server code that must read private data. */\n readonly columnAccess?: ColumnAccess;\n}\n\n/** Bind finalized AppKit metadata to a relational Drizzle database. */\nexport function createDrizzleDb(pool: Pool, schema: Schema): DrizzleDb {\n const completeSchema: Record<string, unknown> = Object.assign(\n Object.create(null),\n schema.$engine,\n buildEngineRelations(schema.$tables),\n );\n return drizzle(pool, { schema: completeSchema }) as unknown as DrizzleDb;\n}\n\ninterface RelationalQueryBuilder {\n findMany(config: Record<string, unknown>): Promise<Row[]>;\n findFirst(config: Record<string, unknown>): Promise<Row | undefined>;\n}\n\n/** Reject same-name or forged tables by requiring finalized object identity. */\nfunction assertRegisteredTable(schema: Schema, table: AppKitTable): void {\n if (schema.$tables[table.$name] !== table) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" is not registered`);\n }\n}\n\nfunction relationalQueryBuilder(\n db: DrizzleDb,\n schema: Schema,\n table: AppKitTable,\n): RelationalQueryBuilder {\n assertRegisteredTable(schema, table);\n const query = (db.query as unknown as Record<string, RelationalQueryBuilder>)[\n table.$name\n ];\n if (!query) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" is not registered`);\n }\n return query;\n}\n\nfunction selectedColumns(\n table: AppKitTable,\n select?: readonly string[],\n access: ColumnAccess = \"public\",\n): Record<string, true> {\n return select === undefined\n ? defaultColumns(table)\n : selectToColumns(table, select, access);\n}\n\ntype MutationKind = \"insert\" | \"update\";\n\ninterface MutationSchema {\n safeParse(\n value: unknown,\n ):\n | { readonly success: true; readonly data: unknown }\n | { readonly success: false };\n}\n\n/** Apply the finalized write schema again at the persistence boundary. */\nfunction mutationValues(\n table: AppKitTable,\n values: Row,\n kind: MutationKind,\n): Row {\n if (values === null || typeof values !== \"object\" || Array.isArray(values)) {\n throw invalidDatabaseRequest(\"Database mutation values must be an object\");\n }\n for (const [key, value] of Object.entries(values)) {\n if (!Object.hasOwn(table.$columns, key)) {\n throw invalidDatabaseRequest(`Unknown column \"${table.$name}.${key}\"`);\n }\n if (isSQLWrapper(value)) {\n throw invalidDatabaseRequest(\n \"Database mutation values cannot contain SQL\",\n );\n }\n }\n const schema = (\n kind === \"insert\" ? table.$insertSchema : table.$updateSchema\n ) as MutationSchema;\n const result = schema.safeParse(values);\n if (!result.success) {\n throw invalidDatabaseRequest(`Invalid database ${kind} values`);\n }\n return result.data as Row;\n}\n\n/** Never change row identity or the conflict key during the update half. */\nfunction upsertUpdateValues(\n table: AppKitTable,\n parameters: Row,\n conflictTarget: string,\n): Row {\n const updates: Row = {};\n for (const [key, value] of Object.entries(parameters)) {\n const column = table.$columns[key];\n if (\n column.primaryKey ||\n column.serverGenerated ||\n column.columnName === conflictTarget\n ) {\n continue;\n }\n updates[key] = value;\n }\n if (Object.keys(updates).length > 0) return updates;\n\n // A safe self-assignment preserves the exactly-one-row upsert contract.\n return { [conflictTarget]: columnOf(table, conflictTarget) };\n}\n\n// Drizzle wraps driver failures in DrizzleQueryError, so the SQLSTATE sits on a\n// nested `cause` rather than the thrown error. Walk a bounded chain to find it.\nconst MAX_CAUSE_DEPTH = 5;\n\nfunction sqlStateOf(error: unknown): string | undefined {\n let current = error;\n for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {\n if (!current || typeof current !== \"object\") return undefined;\n try {\n const candidate = Reflect.get(current, \"code\");\n // SQLSTATE is always a five-character alphanumeric class code.\n if (typeof candidate === \"string\" && /^[0-9A-Z]{5}$/.test(candidate)) {\n return candidate;\n }\n current = Reflect.get(current, \"cause\");\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/** Classify SQLSTATE without retaining the driver error or its properties. */\nfunction classifyDriverError(error: unknown): DatabasePluginError {\n const code = sqlStateOf(error);\n const category: DatabaseErrorCategory =\n code === \"40001\" || code === \"40P01\" || code === \"57014\"\n ? \"TRANSIENT\"\n : code === \"42501\"\n ? \"FORBIDDEN\"\n : code?.startsWith(\"23\")\n ? \"CONFLICT\"\n : \"INTERNAL\";\n logger.error(\n \"Database driver error classified as %s (SQLSTATE %s)\",\n category,\n code ?? \"unknown\",\n );\n return new DatabasePluginError(category, \"runtime\");\n}\n\nasync function runDatabaseOperation<T>(\n operation: () => Promise<T>,\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof DatabasePluginError) throw error;\n throw classifyDriverError(error);\n }\n}\n\n/** Apply the adapter's column capability before rows reach cardinality checks. */\nfunction mutationRows(\n table: AppKitTable,\n rows: Row[],\n access: ColumnAccess,\n): Row[] {\n if (access === \"trusted\") return rows;\n const names = publicColumnNames(table);\n return rows.map((row) => {\n const projected: Row = {};\n for (const name of names) {\n if (Object.hasOwn(row, name)) projected[name] = row[name];\n }\n return projected;\n });\n}\n\n/** Resolve and validate the sole key once before a keyed operation executes. */\nfunction validatedPrimaryKey(\n table: AppKitTable,\n id: unknown,\n): { readonly meta: ColumnMeta; readonly value: IdValue } {\n const meta = primaryKeyMeta(table);\n const result = columnValueSchema(meta).safeParse(id);\n if (!result.success) {\n throw invalidDatabaseRequest(\n `Invalid primary-key value for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n return { meta, value: result.data as IdValue };\n}\n\n// Enforce the single-row DataPath contract before results reach callers.\nfunction expectExactlyOne(rows: Row[]): Row {\n if (rows.length !== 1) {\n throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n }\n return rows[0];\n}\n\nfunction expectZeroOrOne(rows: Row[]): Row | null {\n if (rows.length > 1) {\n throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n }\n return rows[0] ?? null;\n}\n\n/** Adapt a Drizzle database to AppKit's internal execution port. */\nexport function createDrizzleDataPath(\n db: DrizzleDb,\n schema: Schema,\n options: DrizzleDataPathOptions = {},\n): DataPath {\n const columnAccess = options.columnAccess ?? \"public\";\n const pgTable = (table: AppKitTable): PgTable => {\n assertRegisteredTable(schema, table);\n return table.$engine as unknown as PgTable;\n };\n const whereSql = (\n table: AppKitTable,\n where?: WhereClause,\n ): SQL | undefined =>\n where === undefined\n ? undefined\n : translateWhere(table, where, columnAccess);\n\n return {\n async select(table, spec) {\n return runDatabaseOperation(() =>\n relationalQueryBuilder(db, schema, table).findMany({\n where: whereSql(table, spec.where),\n orderBy:\n spec.order === undefined\n ? undefined\n : translateOrder(table, spec.order, columnAccess),\n columns: selectedColumns(table, spec.select, columnAccess),\n with:\n spec.include === undefined\n ? undefined\n : translateInclude(table, schema, spec.include, columnAccess),\n limit: limitOrDefault(spec.limit),\n offset:\n spec.offset === undefined ? undefined : validateOffset(spec.offset),\n }),\n );\n },\n\n async findOne(table, id, spec) {\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const primaryKeyPredicate = eq(\n columnOf(table, primaryKey.columnName),\n validatedId,\n );\n const additionalPredicate =\n spec?.where === undefined\n ? undefined\n : translateWhere(table, spec.where, columnAccess);\n const row = await runDatabaseOperation(() =>\n relationalQueryBuilder(db, schema, table).findFirst({\n where:\n additionalPredicate === undefined\n ? primaryKeyPredicate\n : and(primaryKeyPredicate, additionalPredicate),\n columns: selectedColumns(table, spec?.select, columnAccess),\n with:\n spec?.include === undefined\n ? undefined\n : translateInclude(table, schema, spec.include, columnAccess),\n }),\n );\n return row ?? null;\n },\n\n async count(table, where) {\n return runDatabaseOperation(() =>\n db.$count(pgTable(table), whereSql(table, where)),\n );\n },\n\n async insert(table, values) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"insert\");\n const rows = await runDatabaseOperation(() =>\n db\n .insert(engineTable)\n .values(parameters)\n .returning(returningColumns(table, columnAccess)),\n );\n return expectExactlyOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async update(table, id, values) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"update\");\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const rows = await runDatabaseOperation(() =>\n db\n .update(engineTable)\n .set(parameters)\n .where(eq(columnOf(table, primaryKey.columnName), validatedId))\n .returning(returningColumns(table, columnAccess)),\n );\n return expectZeroOrOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async upsert(table, values, onConflict) {\n const engineTable = pgTable(table);\n const parameters = mutationValues(table, values, \"insert\");\n const target = conflictTargetMeta(table, onConflict);\n const updates = upsertUpdateValues(table, parameters, target.columnName);\n const rows = await runDatabaseOperation(() =>\n db\n .insert(engineTable)\n .values(parameters)\n .onConflictDoUpdate({\n target: columnOf(table, target.columnName),\n set: updates,\n })\n .returning(returningColumns(table, columnAccess)),\n );\n return expectExactlyOne(mutationRows(table, rows as Row[], columnAccess));\n },\n\n async delete(table, id) {\n const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(\n table,\n id,\n );\n const rows = await runDatabaseOperation(() =>\n db\n .delete(pgTable(table))\n .where(eq(columnOf(table, primaryKey.columnName), validatedId))\n .returning({ id: columnOf(table, primaryKey.columnName) }),\n );\n return expectZeroOrOne(rows as Row[]) !== null;\n },\n\n async raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]> {\n // SQL wrappers carry structure; tagged interpolations may carry values only.\n if (values.some((value) => isSQLWrapper(value))) {\n throw invalidDatabaseRequest(\n \"Tagged SQL interpolations must be parameter values\",\n );\n }\n const result = await runDatabaseOperation(() =>\n db.execute(sql(strings, ...values.map((value) => sql.param(value)))),\n );\n return ((result as { rows?: unknown }).rows ?? result) as T[];\n },\n\n async transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T> {\n let callbackFailed = false;\n let callbackError: unknown;\n try {\n return await db.transaction(async (transaction) => {\n try {\n return await callback(\n createDrizzleDataPath(\n transaction as unknown as DrizzleDb,\n schema,\n { columnAccess },\n ),\n );\n } catch (error) {\n callbackFailed = true;\n callbackError = error;\n throw error;\n }\n });\n } catch (error) {\n // Callback errors are application-owned; sanitize only tx lifecycle errors.\n if (\n callbackFailed &&\n (error === callbackError ||\n callbackError instanceof DatabasePluginError)\n ) {\n throw callbackError;\n }\n if (error instanceof DatabasePluginError) throw error;\n throw classifyDriverError(error);\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;AAoCA,MAAM,SAAS,aAAa,WAAW;;AAWvC,SAAgB,gBAAgB,MAAY,QAA2B;AAMrE,QAAO,QAAQ,MAAM,EAAE,QALyB,OAAO,OACrD,OAAO,OAAO,KAAK,EACnB,OAAO,SACP,qBAAqB,OAAO,QAAQ,CACrC,EAC8C,CAAC;;;AASlD,SAAS,sBAAsB,QAAgB,OAA0B;AACvE,KAAI,OAAO,QAAQ,MAAM,WAAW,MAClC,OAAM,uBAAuB,UAAU,MAAM,MAAM,qBAAqB;;AAI5E,SAAS,uBACP,IACA,QACA,OACwB;AACxB,uBAAsB,QAAQ,MAAM;CACpC,MAAM,QAAS,GAAG,MAChB,MAAM;AAER,KAAI,CAAC,MACH,OAAM,uBAAuB,UAAU,MAAM,MAAM,qBAAqB;AAE1E,QAAO;;AAGT,SAAS,gBACP,OACA,QACA,SAAuB,UACD;AACtB,QAAO,WAAW,SACd,eAAe,MAAM,GACrB,gBAAgB,OAAO,QAAQ,OAAO;;;AAc5C,SAAS,eACP,OACA,QACA,MACK;AACL,KAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CACxE,OAAM,uBAAuB,6CAA6C;AAE5E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;AACjD,MAAI,CAAC,OAAO,OAAO,MAAM,UAAU,IAAI,CACrC,OAAM,uBAAuB,mBAAmB,MAAM,MAAM,GAAG,IAAI,GAAG;AAExE,MAAI,aAAa,MAAM,CACrB,OAAM,uBACJ,8CACD;;CAML,MAAM,UAFJ,SAAS,WAAW,MAAM,gBAAgB,MAAM,eAE5B,UAAU,OAAO;AACvC,KAAI,CAAC,OAAO,QACV,OAAM,uBAAuB,oBAAoB,KAAK,SAAS;AAEjE,QAAO,OAAO;;;AAIhB,SAAS,mBACP,OACA,YACA,gBACK;CACL,MAAM,UAAe,EAAE;AACvB,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;EACrD,MAAM,SAAS,MAAM,SAAS;AAC9B,MACE,OAAO,cACP,OAAO,mBACP,OAAO,eAAe,eAEtB;AAEF,UAAQ,OAAO;;AAEjB,KAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EAAG,QAAO;AAG5C,QAAO,GAAG,iBAAiB,SAAS,OAAO,eAAe,EAAE;;AAK9D,MAAM,kBAAkB;AAExB,SAAS,WAAW,OAAoC;CACtD,IAAI,UAAU;AACd,MAAK,IAAI,QAAQ,GAAG,QAAQ,iBAAiB,SAAS;AACpD,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,MAAI;GACF,MAAM,YAAY,QAAQ,IAAI,SAAS,OAAO;AAE9C,OAAI,OAAO,cAAc,YAAY,gBAAgB,KAAK,UAAU,CAClE,QAAO;AAET,aAAU,QAAQ,IAAI,SAAS,QAAQ;UACjC;AACN;;;;;AAON,SAAS,oBAAoB,OAAqC;CAChE,MAAM,OAAO,WAAW,MAAM;CAC9B,MAAM,WACJ,SAAS,WAAW,SAAS,WAAW,SAAS,UAC7C,cACA,SAAS,UACP,cACA,MAAM,WAAW,KAAK,GACpB,aACA;AACV,QAAO,MACL,wDACA,UACA,QAAQ,UACT;AACD,QAAO,IAAI,oBAAoB,UAAU,UAAU;;AAGrD,eAAe,qBACb,WACY;AACZ,KAAI;AACF,SAAO,MAAM,WAAW;UACjB,OAAO;AACd,MAAI,iBAAiB,oBAAqB,OAAM;AAChD,QAAM,oBAAoB,MAAM;;;;AAKpC,SAAS,aACP,OACA,MACA,QACO;AACP,KAAI,WAAW,UAAW,QAAO;CACjC,MAAM,QAAQ,kBAAkB,MAAM;AACtC,QAAO,KAAK,KAAK,QAAQ;EACvB,MAAM,YAAiB,EAAE;AACzB,OAAK,MAAM,QAAQ,MACjB,KAAI,OAAO,OAAO,KAAK,KAAK,CAAE,WAAU,QAAQ,IAAI;AAEtD,SAAO;GACP;;;AAIJ,SAAS,oBACP,OACA,IACwD;CACxD,MAAM,OAAO,eAAe,MAAM;CAClC,MAAM,SAAS,kBAAkB,KAAK,CAAC,UAAU,GAAG;AACpD,KAAI,CAAC,OAAO,QACV,OAAM,uBACJ,kCAAkC,MAAM,MAAM,GAAG,KAAK,WAAW,GAClE;AAEH,QAAO;EAAE;EAAM,OAAO,OAAO;EAAiB;;AAIhD,SAAS,iBAAiB,MAAkB;AAC1C,KAAI,KAAK,WAAW,EAClB,OAAM,IAAI,oBAAoB,YAAY,UAAU;AAEtD,QAAO,KAAK;;AAGd,SAAS,gBAAgB,MAAyB;AAChD,KAAI,KAAK,SAAS,EAChB,OAAM,IAAI,oBAAoB,YAAY,UAAU;AAEtD,QAAO,KAAK,MAAM;;;AAIpB,SAAgB,sBACd,IACA,QACA,UAAkC,EAAE,EAC1B;CACV,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,WAAW,UAAgC;AAC/C,wBAAsB,QAAQ,MAAM;AACpC,SAAO,MAAM;;CAEf,MAAM,YACJ,OACA,UAEA,UAAU,SACN,SACA,eAAe,OAAO,OAAO,aAAa;AAEhD,QAAO;EACL,MAAM,OAAO,OAAO,MAAM;AACxB,UAAO,2BACL,uBAAuB,IAAI,QAAQ,MAAM,CAAC,SAAS;IACjD,OAAO,SAAS,OAAO,KAAK,MAAM;IAClC,SACE,KAAK,UAAU,SACX,SACA,eAAe,OAAO,KAAK,OAAO,aAAa;IACrD,SAAS,gBAAgB,OAAO,KAAK,QAAQ,aAAa;IAC1D,MACE,KAAK,YAAY,SACb,SACA,iBAAiB,OAAO,QAAQ,KAAK,SAAS,aAAa;IACjE,OAAO,eAAe,KAAK,MAAM;IACjC,QACE,KAAK,WAAW,SAAY,SAAY,eAAe,KAAK,OAAO;IACtE,CAAC,CACH;;EAGH,MAAM,QAAQ,OAAO,IAAI,MAAM;GAC7B,MAAM,EAAE,MAAM,YAAY,OAAO,gBAAgB,oBAC/C,OACA,GACD;GACD,MAAM,sBAAsB,GAC1B,SAAS,OAAO,WAAW,WAAW,EACtC,YACD;GACD,MAAM,sBACJ,MAAM,UAAU,SACZ,SACA,eAAe,OAAO,KAAK,OAAO,aAAa;AAcrD,UAbY,MAAM,2BAChB,uBAAuB,IAAI,QAAQ,MAAM,CAAC,UAAU;IAClD,OACE,wBAAwB,SACpB,sBACA,IAAI,qBAAqB,oBAAoB;IACnD,SAAS,gBAAgB,OAAO,MAAM,QAAQ,aAAa;IAC3D,MACE,MAAM,YAAY,SACd,SACA,iBAAiB,OAAO,QAAQ,KAAK,SAAS,aAAa;IAClE,CAAC,CACH,IACa;;EAGhB,MAAM,MAAM,OAAO,OAAO;AACxB,UAAO,2BACL,GAAG,OAAO,QAAQ,MAAM,EAAE,SAAS,OAAO,MAAM,CAAC,CAClD;;EAGH,MAAM,OAAO,OAAO,QAAQ;GAC1B,MAAM,cAAc,QAAQ,MAAM;GAClC,MAAM,aAAa,eAAe,OAAO,QAAQ,SAAS;AAO1D,UAAO,iBAAiB,aAAa,OANxB,MAAM,2BACjB,GACG,OAAO,YAAY,CACnB,OAAO,WAAW,CAClB,UAAU,iBAAiB,OAAO,aAAa,CAAC,CACpD,EAC0D,aAAa,CAAC;;EAG3E,MAAM,OAAO,OAAO,IAAI,QAAQ;GAC9B,MAAM,cAAc,QAAQ,MAAM;GAClC,MAAM,aAAa,eAAe,OAAO,QAAQ,SAAS;GAC1D,MAAM,EAAE,MAAM,YAAY,OAAO,gBAAgB,oBAC/C,OACA,GACD;AAQD,UAAO,gBAAgB,aAAa,OAPvB,MAAM,2BACjB,GACG,OAAO,YAAY,CACnB,IAAI,WAAW,CACf,MAAM,GAAG,SAAS,OAAO,WAAW,WAAW,EAAE,YAAY,CAAC,CAC9D,UAAU,iBAAiB,OAAO,aAAa,CAAC,CACpD,EACyD,aAAa,CAAC;;EAG1E,MAAM,OAAO,OAAO,QAAQ,YAAY;GACtC,MAAM,cAAc,QAAQ,MAAM;GAClC,MAAM,aAAa,eAAe,OAAO,QAAQ,SAAS;GAC1D,MAAM,SAAS,mBAAmB,OAAO,WAAW;GACpD,MAAM,UAAU,mBAAmB,OAAO,YAAY,OAAO,WAAW;AAWxE,UAAO,iBAAiB,aAAa,OAVxB,MAAM,2BACjB,GACG,OAAO,YAAY,CACnB,OAAO,WAAW,CAClB,mBAAmB;IAClB,QAAQ,SAAS,OAAO,OAAO,WAAW;IAC1C,KAAK;IACN,CAAC,CACD,UAAU,iBAAiB,OAAO,aAAa,CAAC,CACpD,EAC0D,aAAa,CAAC;;EAG3E,MAAM,OAAO,OAAO,IAAI;GACtB,MAAM,EAAE,MAAM,YAAY,OAAO,gBAAgB,oBAC/C,OACA,GACD;AAOD,UAAO,gBANM,MAAM,2BACjB,GACG,OAAO,QAAQ,MAAM,CAAC,CACtB,MAAM,GAAG,SAAS,OAAO,WAAW,WAAW,EAAE,YAAY,CAAC,CAC9D,UAAU,EAAE,IAAI,SAAS,OAAO,WAAW,WAAW,EAAE,CAAC,CAC7D,CACoC,KAAK;;EAG5C,MAAM,IACJ,SACA,GAAG,QACW;AAEd,OAAI,OAAO,MAAM,UAAU,aAAa,MAAM,CAAC,CAC7C,OAAM,uBACJ,qDACD;GAEH,MAAM,SAAS,MAAM,2BACnB,GAAG,QAAQ,IAAI,SAAS,GAAG,OAAO,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC,CAAC,CAAC,CACrE;AACD,UAAS,OAA8B,QAAQ;;EAGjD,MAAM,YAAe,UAAoD;GACvE,IAAI,iBAAiB;GACrB,IAAI;AACJ,OAAI;AACF,WAAO,MAAM,GAAG,YAAY,OAAO,gBAAgB;AACjD,SAAI;AACF,aAAO,MAAM,SACX,sBACE,aACA,QACA,EAAE,cAAc,CACjB,CACF;cACM,OAAO;AACd,uBAAiB;AACjB,sBAAgB;AAChB,YAAM;;MAER;YACK,OAAO;AAEd,QACE,mBACC,UAAU,iBACT,yBAAyB,qBAE3B,OAAM;AAER,QAAI,iBAAiB,oBAAqB,OAAM;AAChD,UAAM,oBAAoB,MAAM;;;EAGrC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { filterOperatorsForKind } from "../../schema-builder/types.js";
|
|
2
2
|
import { columnValueSchema } from "../../schema-builder/validators.js";
|
|
3
3
|
import { invalidDatabaseRequest } from "../../errors.js";
|
|
4
|
-
import { DEFAULT_LIMIT, IN_CAP, MAX_INCLUDES, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "../../contract/wire.js";
|
|
4
|
+
import { DEFAULT_LIMIT, IN_CAP, MAX_INCLUDES, MAX_INCLUDE_DEPTH, MAX_INCLUDE_NODES, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "../../contract/wire.js";
|
|
5
5
|
import "../../contract/index.js";
|
|
6
6
|
import { validateLimit } from "../data-path.js";
|
|
7
7
|
import { and, asc, desc, eq, gt, gte, ilike, inArray, isNull, like, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
@@ -141,8 +141,12 @@ function tableByName(schema, name) {
|
|
|
141
141
|
if (!table) throw invalidDatabaseRequest(`Unknown table "${name}"`);
|
|
142
142
|
return table;
|
|
143
143
|
}
|
|
144
|
-
/** Translate
|
|
144
|
+
/** Translate relation edges into Drizzle's relational `with` config. */
|
|
145
145
|
function translateInclude(table, schema, include, access = "public") {
|
|
146
|
+
return translateIncludeTree(table, schema, include, access, 1, { nodes: 0 });
|
|
147
|
+
}
|
|
148
|
+
function translateIncludeTree(table, schema, include, access, depth, budget) {
|
|
149
|
+
if (depth > MAX_INCLUDE_DEPTH) throw invalidDatabaseRequest(`include exceeds the ${MAX_INCLUDE_DEPTH}-edge depth limit`);
|
|
146
150
|
const entries = Object.entries(include);
|
|
147
151
|
if (entries.length > MAX_INCLUDES) throw invalidDatabaseRequest(`include exceeds the ${MAX_INCLUDES}-relation limit`);
|
|
148
152
|
const config = {};
|
|
@@ -150,6 +154,8 @@ function translateInclude(table, schema, include, access = "public") {
|
|
|
150
154
|
const relation = table.$relations.find((candidate) => candidate.name === relationName);
|
|
151
155
|
if (!relation) throw invalidDatabaseRequest(`Unknown relation "${table.$name}.${relationName}"`);
|
|
152
156
|
if (rawOptions === false) continue;
|
|
157
|
+
budget.nodes += 1;
|
|
158
|
+
if (budget.nodes > MAX_INCLUDE_NODES) throw invalidDatabaseRequest(`include exceeds the ${MAX_INCLUDE_NODES}-node limit`);
|
|
153
159
|
const target = tableByName(schema, relation.targetTable);
|
|
154
160
|
if (rawOptions === true) {
|
|
155
161
|
config[relationName] = {
|
|
@@ -166,6 +172,7 @@ function translateInclude(table, schema, include, access = "public") {
|
|
|
166
172
|
if (relation.cardinality !== "toMany") throw invalidDatabaseRequest("Only to-many relations accept a limit");
|
|
167
173
|
relationConfig.limit = validateLimit(options.limit);
|
|
168
174
|
} else if (relation.cardinality === "toMany") relationConfig.limit = DEFAULT_LIMIT;
|
|
175
|
+
if (options.include !== void 0) relationConfig.with = translateIncludeTree(target, schema, options.include, access, depth + 1, budget);
|
|
169
176
|
config[relationName] = relationConfig;
|
|
170
177
|
}
|
|
171
178
|
return config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../../../src/database/runtime/engine/translate.ts"],"sourcesContent":["import {\n and,\n asc,\n desc,\n eq,\n gt,\n gte,\n ilike,\n inArray,\n isNull,\n like,\n lt,\n lte,\n ne,\n or,\n type SQL,\n sql,\n} from \"drizzle-orm\";\nimport type { AnyPgColumn } from \"drizzle-orm/pg-core\";\n\nimport {\n DEFAULT_LIMIT,\n type FilterOperator,\n IN_CAP,\n isFilterOperator,\n MAX_INCLUDES,\n MAX_WHERE_CONDITIONS,\n MAX_WHERE_DEPTH,\n MAX_WHERE_GROUP_ITEMS,\n} from \"../../contract\";\nimport { invalidDatabaseRequest } from \"../../errors\";\nimport type { AppKitTable, ColumnMeta, Schema } from \"../../schema-builder\";\nimport { filterOperatorsForKind } from \"../../schema-builder/types\";\nimport { columnValueSchema } from \"../../schema-builder/validators\";\nimport {\n type FilterOps,\n type IncludeOptions,\n type IncludeSpec,\n type OrderSpec,\n validateLimit,\n type WhereClause,\n} from \"../data-path\";\n\n/** Read policy selected by the trusted adapter constructor, never by query input. */\nexport type ColumnAccess = \"public\" | \"trusted\";\n\nfunction columnMetaOf(table: AppKitTable, key: string): ColumnMeta {\n const column = table.$columns[key];\n if (!column) {\n throw invalidDatabaseRequest(`Unknown column \"${table.$name}.${key}\"`);\n }\n return column;\n}\n\nfunction readableColumnMeta(\n table: AppKitTable,\n key: string,\n access: ColumnAccess,\n): ColumnMeta {\n const column = columnMetaOf(table, key);\n if (column.isPrivate && access !== \"trusted\") {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${key}\" is not readable`,\n );\n }\n return column;\n}\n\n/** Resolve SQL identifiers only through columns finalized by the schema builder. */\nexport function columnOf(table: AppKitTable, key: string): AnyPgColumn {\n return columnMetaOf(table, key).engineColumn as unknown as AnyPgColumn;\n}\n\n/** Column names that default reads and mutation returns may expose. */\nexport function publicColumnNames(table: AppKitTable): string[] {\n return Object.values(table.$columns)\n .filter((column) => !column.isPrivate)\n .map((column) => column.columnName);\n}\n\n/** Default reads select all finalized columns except private application data. */\nexport function defaultColumns(table: AppKitTable): Record<string, true> {\n const columns: Record<string, true> = {};\n for (const name of publicColumnNames(table)) columns[name] = true;\n return columns;\n}\n\n/** Drizzle `.returning()` fields allowed by the adapter's read capability. */\nexport function returningColumns(\n table: AppKitTable,\n access: ColumnAccess = \"public\",\n): Record<string, AnyPgColumn> {\n const columns: Record<string, AnyPgColumn> = {};\n const names =\n access === \"trusted\"\n ? Object.values(table.$columns).map((column) => column.columnName)\n : publicColumnNames(table);\n for (const name of names) {\n columns[name] = columnOf(table, name);\n }\n return columns;\n}\n\nfunction supportsOperator(meta: ColumnMeta, operator: FilterOperator): boolean {\n if (operator === \"is\") {\n return !meta.notNull && meta.kind !== \"json\" && meta.kind !== \"unknown\";\n }\n return filterOperatorsForKind(meta.kind).includes(operator);\n}\n\nfunction assertColumnValue(\n table: AppKitTable,\n meta: ColumnMeta,\n operator: FilterOperator,\n value: unknown,\n): void {\n if (!columnValueSchema(meta).safeParse(value).success) {\n throw invalidDatabaseRequest(\n `Invalid ${operator} operand for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n}\n\nfunction inList(\n table: AppKitTable,\n meta: ColumnMeta,\n column: AnyPgColumn,\n value: unknown,\n): SQL {\n if (!Array.isArray(value)) {\n throw invalidDatabaseRequest('The \"in\" operator requires an array');\n }\n if (value.length > IN_CAP) {\n throw invalidDatabaseRequest(`in list exceeds the ${IN_CAP}-value limit`);\n }\n for (const item of value) {\n if (item === null) {\n throw invalidDatabaseRequest('The \"in\" operator does not accept null');\n }\n assertColumnValue(table, meta, \"in\", item);\n }\n return value.length === 0 ? sql.raw(\"false\") : inArray(column, value);\n}\n\nfunction translateOperator(\n table: AppKitTable,\n meta: ColumnMeta,\n column: AnyPgColumn,\n operator: FilterOperator,\n value: unknown,\n): SQL {\n if (!supportsOperator(meta, operator)) {\n throw invalidDatabaseRequest(\n `Operator \"${operator}\" is not supported for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n if (operator === \"is\") {\n if (value !== null) {\n throw invalidDatabaseRequest('The \"is\" operator accepts only null');\n }\n return isNull(column);\n }\n if (operator === \"in\") return inList(table, meta, column, value);\n\n assertColumnValue(table, meta, operator, value);\n switch (operator) {\n case \"eq\":\n return eq(column, value);\n case \"neq\":\n return ne(column, value);\n case \"gt\":\n return gt(column, value);\n case \"gte\":\n return gte(column, value);\n case \"lt\":\n return lt(column, value);\n case \"lte\":\n return lte(column, value);\n case \"like\":\n return like(column, value as string);\n case \"ilike\":\n return ilike(column, value as string);\n default:\n throw invalidDatabaseRequest(`Unsupported filter operator \"${operator}\"`);\n }\n}\n\ninterface WhereBudget {\n conditions: number;\n}\n\nfunction chargeWhereCondition(budget: WhereBudget): void {\n budget.conditions += 1;\n if (budget.conditions > MAX_WHERE_CONDITIONS) {\n throw invalidDatabaseRequest(\n `where exceeds the ${MAX_WHERE_CONDITIONS}-condition limit`,\n );\n }\n}\n\nfunction translateWhereNode(\n table: AppKitTable,\n clause: WhereClause,\n access: ColumnAccess,\n depth: number,\n budget: WhereBudget,\n): SQL | undefined {\n if (depth > MAX_WHERE_DEPTH) {\n throw invalidDatabaseRequest(\n `where exceeds the ${MAX_WHERE_DEPTH}-level depth limit`,\n );\n }\n if (clause === null || typeof clause !== \"object\" || Array.isArray(clause)) {\n throw invalidDatabaseRequest(\"where must be an object\");\n }\n const conditions: SQL[] = [];\n for (const [key, value] of Object.entries(clause)) {\n if (key === \"and\" || key === \"or\") {\n if (\n !Array.isArray(value) ||\n value.length === 0 ||\n value.length > MAX_WHERE_GROUP_ITEMS\n ) {\n if (Array.isArray(value) && value.length > MAX_WHERE_GROUP_ITEMS) {\n throw invalidDatabaseRequest(\n `${key} exceeds the ${MAX_WHERE_GROUP_ITEMS}-predicate limit`,\n );\n }\n throw invalidDatabaseRequest(\n `${key} requires a non-empty predicate array`,\n );\n }\n const groups = value.map((group) => {\n const translated = translateWhereNode(\n table,\n group as WhereClause,\n access,\n depth + 1,\n budget,\n );\n if (!translated) {\n throw invalidDatabaseRequest(`${key} predicates cannot be empty`);\n }\n return translated;\n });\n const combined = key === \"and\" ? and(...groups) : or(...groups);\n if (combined) {\n conditions.push(combined);\n }\n continue;\n }\n\n const meta = readableColumnMeta(table, key, access);\n const column = meta.engineColumn as unknown as AnyPgColumn;\n if (Array.isArray(value)) {\n chargeWhereCondition(budget);\n conditions.push(translateOperator(table, meta, column, \"in\", value));\n } else if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value)\n ) {\n const operators = Object.entries(value as FilterOps);\n if (operators.length === 0) {\n throw invalidDatabaseRequest(\n `Filter for \"${table.$name}.${key}\" cannot be empty`,\n );\n }\n for (const [operator, operand] of operators) {\n chargeWhereCondition(budget);\n if (!isFilterOperator(operator)) {\n throw invalidDatabaseRequest(`Unknown filter operator \"${operator}\"`);\n }\n conditions.push(\n translateOperator(table, meta, column, operator, operand),\n );\n }\n } else {\n chargeWhereCondition(budget);\n conditions.push(translateOperator(table, meta, column, \"eq\", value));\n }\n }\n return conditions.length > 0 ? and(...conditions) : undefined;\n}\n\n/** Translate one bounded direct-column predicate tree. */\nexport function translateWhere(\n table: AppKitTable,\n clause: WhereClause,\n access: ColumnAccess = \"public\",\n): SQL | undefined {\n return translateWhereNode(table, clause, access, 1, { conditions: 0 });\n}\n\nexport function translateOrder(\n table: AppKitTable,\n order: OrderSpec,\n access: ColumnAccess = \"public\",\n): SQL[] {\n return Object.entries(order).map(([key, direction]) => {\n if (direction !== \"asc\" && direction !== \"desc\") {\n throw invalidDatabaseRequest(`Unknown order direction \"${direction}\"`);\n }\n const column = readableColumnMeta(table, key, access)\n .engineColumn as unknown as AnyPgColumn;\n return direction === \"desc\" ? desc(column) : asc(column);\n });\n}\n\nexport function selectToColumns(\n table: AppKitTable,\n select: readonly string[],\n access: ColumnAccess = \"public\",\n): Record<string, true> {\n const columns: Record<string, true> = {};\n for (const key of select) {\n readableColumnMeta(table, key, access);\n columns[key] = true;\n }\n return columns;\n}\n\nfunction tableByName(schema: Schema, name: string): AppKitTable {\n const table = schema.$tables[name];\n if (!table) throw invalidDatabaseRequest(`Unknown table \"${name}\"`);\n return table;\n}\n\n/** Translate one relation edge into Drizzle's relational `with` config. */\nexport function translateInclude(\n table: AppKitTable,\n schema: Schema,\n include: IncludeSpec,\n access: ColumnAccess = \"public\",\n): Record<string, unknown> {\n const entries = Object.entries(include);\n if (entries.length > MAX_INCLUDES) {\n throw invalidDatabaseRequest(\n `include exceeds the ${MAX_INCLUDES}-relation limit`,\n );\n }\n\n const config: Record<string, unknown> = {};\n for (const [relationName, rawOptions] of entries) {\n const relation = table.$relations.find(\n (candidate) => candidate.name === relationName,\n );\n if (!relation) {\n throw invalidDatabaseRequest(\n `Unknown relation \"${table.$name}.${relationName}\"`,\n );\n }\n if (rawOptions === false) continue;\n\n const target = tableByName(schema, relation.targetTable);\n if (rawOptions === true) {\n config[relationName] = {\n columns: defaultColumns(target),\n ...(relation.cardinality === \"toMany\" ? { limit: DEFAULT_LIMIT } : {}),\n };\n continue;\n }\n\n const options = rawOptions as IncludeOptions;\n const relationConfig: Record<string, unknown> = {\n columns:\n options.select === undefined\n ? defaultColumns(target)\n : selectToColumns(target, options.select, access),\n };\n if (options.where !== undefined) {\n relationConfig.where = translateWhere(target, options.where, access);\n }\n if (options.order !== undefined) {\n relationConfig.orderBy = translateOrder(target, options.order, access);\n }\n if (options.limit !== undefined) {\n if (relation.cardinality !== \"toMany\") {\n throw invalidDatabaseRequest(\"Only to-many relations accept a limit\");\n }\n relationConfig.limit = validateLimit(options.limit);\n } else if (relation.cardinality === \"toMany\") {\n relationConfig.limit = DEFAULT_LIMIT;\n }\n config[relationName] = relationConfig;\n }\n return config;\n}\n"],"mappings":";;;;;;;;;AA8CA,SAAS,aAAa,OAAoB,KAAyB;CACjE,MAAM,SAAS,MAAM,SAAS;AAC9B,KAAI,CAAC,OACH,OAAM,uBAAuB,mBAAmB,MAAM,MAAM,GAAG,IAAI,GAAG;AAExE,QAAO;;AAGT,SAAS,mBACP,OACA,KACA,QACY;CACZ,MAAM,SAAS,aAAa,OAAO,IAAI;AACvC,KAAI,OAAO,aAAa,WAAW,UACjC,OAAM,uBACJ,WAAW,MAAM,MAAM,GAAG,IAAI,mBAC/B;AAEH,QAAO;;;AAIT,SAAgB,SAAS,OAAoB,KAA0B;AACrE,QAAO,aAAa,OAAO,IAAI,CAAC;;;AAIlC,SAAgB,kBAAkB,OAA8B;AAC9D,QAAO,OAAO,OAAO,MAAM,SAAS,CACjC,QAAQ,WAAW,CAAC,OAAO,UAAU,CACrC,KAAK,WAAW,OAAO,WAAW;;;AAIvC,SAAgB,eAAe,OAA0C;CACvE,MAAM,UAAgC,EAAE;AACxC,MAAK,MAAM,QAAQ,kBAAkB,MAAM,CAAE,SAAQ,QAAQ;AAC7D,QAAO;;;AAIT,SAAgB,iBACd,OACA,SAAuB,UACM;CAC7B,MAAM,UAAuC,EAAE;CAC/C,MAAM,QACJ,WAAW,YACP,OAAO,OAAO,MAAM,SAAS,CAAC,KAAK,WAAW,OAAO,WAAW,GAChE,kBAAkB,MAAM;AAC9B,MAAK,MAAM,QAAQ,MACjB,SAAQ,QAAQ,SAAS,OAAO,KAAK;AAEvC,QAAO;;AAGT,SAAS,iBAAiB,MAAkB,UAAmC;AAC7E,KAAI,aAAa,KACf,QAAO,CAAC,KAAK,WAAW,KAAK,SAAS,UAAU,KAAK,SAAS;AAEhE,QAAO,uBAAuB,KAAK,KAAK,CAAC,SAAS,SAAS;;AAG7D,SAAS,kBACP,OACA,MACA,UACA,OACM;AACN,KAAI,CAAC,kBAAkB,KAAK,CAAC,UAAU,MAAM,CAAC,QAC5C,OAAM,uBACJ,WAAW,SAAS,gBAAgB,MAAM,MAAM,GAAG,KAAK,WAAW,GACpE;;AAIL,SAAS,OACP,OACA,MACA,QACA,OACK;AACL,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB,OAAM,uBAAuB,wCAAsC;AAErE,KAAI,MAAM,SAAS,OACjB,OAAM,uBAAuB,uBAAuB,OAAO,cAAc;AAE3E,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,SAAS,KACX,OAAM,uBAAuB,2CAAyC;AAExE,oBAAkB,OAAO,MAAM,MAAM,KAAK;;AAE5C,QAAO,MAAM,WAAW,IAAI,IAAI,IAAI,QAAQ,GAAG,QAAQ,QAAQ,MAAM;;AAGvE,SAAS,kBACP,OACA,MACA,QACA,UACA,OACK;AACL,KAAI,CAAC,iBAAiB,MAAM,SAAS,CACnC,OAAM,uBACJ,aAAa,SAAS,0BAA0B,MAAM,MAAM,GAAG,KAAK,WAAW,GAChF;AAEH,KAAI,aAAa,MAAM;AACrB,MAAI,UAAU,KACZ,OAAM,uBAAuB,wCAAsC;AAErE,SAAO,OAAO,OAAO;;AAEvB,KAAI,aAAa,KAAM,QAAO,OAAO,OAAO,MAAM,QAAQ,MAAM;AAEhE,mBAAkB,OAAO,MAAM,UAAU,MAAM;AAC/C,SAAQ,UAAR;EACE,KAAK,KACH,QAAO,GAAG,QAAQ,MAAM;EAC1B,KAAK,MACH,QAAO,GAAG,QAAQ,MAAM;EAC1B,KAAK,KACH,QAAO,GAAG,QAAQ,MAAM;EAC1B,KAAK,MACH,QAAO,IAAI,QAAQ,MAAM;EAC3B,KAAK,KACH,QAAO,GAAG,QAAQ,MAAM;EAC1B,KAAK,MACH,QAAO,IAAI,QAAQ,MAAM;EAC3B,KAAK,OACH,QAAO,KAAK,QAAQ,MAAgB;EACtC,KAAK,QACH,QAAO,MAAM,QAAQ,MAAgB;EACvC,QACE,OAAM,uBAAuB,gCAAgC,SAAS,GAAG;;;AAQ/E,SAAS,qBAAqB,QAA2B;AACvD,QAAO,cAAc;AACrB,KAAI,OAAO,aAAa,qBACtB,OAAM,uBACJ,qBAAqB,qBAAqB,kBAC3C;;AAIL,SAAS,mBACP,OACA,QACA,QACA,OACA,QACiB;AACjB,KAAI,QAAQ,gBACV,OAAM,uBACJ,qBAAqB,gBAAgB,oBACtC;AAEH,KAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CACxE,OAAM,uBAAuB,0BAA0B;CAEzD,MAAM,aAAoB,EAAE;AAC5B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;AACjD,MAAI,QAAQ,SAAS,QAAQ,MAAM;AACjC,OACE,CAAC,MAAM,QAAQ,MAAM,IACrB,MAAM,WAAW,KACjB,MAAM,SAAS,uBACf;AACA,QAAI,MAAM,QAAQ,MAAM,IAAI,MAAM,SAAS,sBACzC,OAAM,uBACJ,GAAG,IAAI,eAAe,sBAAsB,kBAC7C;AAEH,UAAM,uBACJ,GAAG,IAAI,uCACR;;GAEH,MAAM,SAAS,MAAM,KAAK,UAAU;IAClC,MAAM,aAAa,mBACjB,OACA,OACA,QACA,QAAQ,GACR,OACD;AACD,QAAI,CAAC,WACH,OAAM,uBAAuB,GAAG,IAAI,6BAA6B;AAEnE,WAAO;KACP;GACF,MAAM,WAAW,QAAQ,QAAQ,IAAI,GAAG,OAAO,GAAG,GAAG,GAAG,OAAO;AAC/D,OAAI,SACF,YAAW,KAAK,SAAS;AAE3B;;EAGF,MAAM,OAAO,mBAAmB,OAAO,KAAK,OAAO;EACnD,MAAM,SAAS,KAAK;AACpB,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,wBAAqB,OAAO;AAC5B,cAAW,KAAK,kBAAkB,OAAO,MAAM,QAAQ,MAAM,MAAM,CAAC;aAEpE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,MAAM,EACrB;GACA,MAAM,YAAY,OAAO,QAAQ,MAAmB;AACpD,OAAI,UAAU,WAAW,EACvB,OAAM,uBACJ,eAAe,MAAM,MAAM,GAAG,IAAI,mBACnC;AAEH,QAAK,MAAM,CAAC,UAAU,YAAY,WAAW;AAC3C,yBAAqB,OAAO;AAC5B,QAAI,CAAC,iBAAiB,SAAS,CAC7B,OAAM,uBAAuB,4BAA4B,SAAS,GAAG;AAEvE,eAAW,KACT,kBAAkB,OAAO,MAAM,QAAQ,UAAU,QAAQ,CAC1D;;SAEE;AACL,wBAAqB,OAAO;AAC5B,cAAW,KAAK,kBAAkB,OAAO,MAAM,QAAQ,MAAM,MAAM,CAAC;;;AAGxE,QAAO,WAAW,SAAS,IAAI,IAAI,GAAG,WAAW,GAAG;;;AAItD,SAAgB,eACd,OACA,QACA,SAAuB,UACN;AACjB,QAAO,mBAAmB,OAAO,QAAQ,QAAQ,GAAG,EAAE,YAAY,GAAG,CAAC;;AAGxE,SAAgB,eACd,OACA,OACA,SAAuB,UAChB;AACP,QAAO,OAAO,QAAQ,MAAM,CAAC,KAAK,CAAC,KAAK,eAAe;AACrD,MAAI,cAAc,SAAS,cAAc,OACvC,OAAM,uBAAuB,4BAA4B,UAAU,GAAG;EAExE,MAAM,SAAS,mBAAmB,OAAO,KAAK,OAAO,CAClD;AACH,SAAO,cAAc,SAAS,KAAK,OAAO,GAAG,IAAI,OAAO;GACxD;;AAGJ,SAAgB,gBACd,OACA,QACA,SAAuB,UACD;CACtB,MAAM,UAAgC,EAAE;AACxC,MAAK,MAAM,OAAO,QAAQ;AACxB,qBAAmB,OAAO,KAAK,OAAO;AACtC,UAAQ,OAAO;;AAEjB,QAAO;;AAGT,SAAS,YAAY,QAAgB,MAA2B;CAC9D,MAAM,QAAQ,OAAO,QAAQ;AAC7B,KAAI,CAAC,MAAO,OAAM,uBAAuB,kBAAkB,KAAK,GAAG;AACnE,QAAO;;;AAIT,SAAgB,iBACd,OACA,QACA,SACA,SAAuB,UACE;CACzB,MAAM,UAAU,OAAO,QAAQ,QAAQ;AACvC,KAAI,QAAQ,SAAS,aACnB,OAAM,uBACJ,uBAAuB,aAAa,iBACrC;CAGH,MAAM,SAAkC,EAAE;AAC1C,MAAK,MAAM,CAAC,cAAc,eAAe,SAAS;EAChD,MAAM,WAAW,MAAM,WAAW,MAC/B,cAAc,UAAU,SAAS,aACnC;AACD,MAAI,CAAC,SACH,OAAM,uBACJ,qBAAqB,MAAM,MAAM,GAAG,aAAa,GAClD;AAEH,MAAI,eAAe,MAAO;EAE1B,MAAM,SAAS,YAAY,QAAQ,SAAS,YAAY;AACxD,MAAI,eAAe,MAAM;AACvB,UAAO,gBAAgB;IACrB,SAAS,eAAe,OAAO;IAC/B,GAAI,SAAS,gBAAgB,WAAW,EAAE,OAAO,eAAe,GAAG,EAAE;IACtE;AACD;;EAGF,MAAM,UAAU;EAChB,MAAM,iBAA0C,EAC9C,SACE,QAAQ,WAAW,SACf,eAAe,OAAO,GACtB,gBAAgB,QAAQ,QAAQ,QAAQ,OAAO,EACtD;AACD,MAAI,QAAQ,UAAU,OACpB,gBAAe,QAAQ,eAAe,QAAQ,QAAQ,OAAO,OAAO;AAEtE,MAAI,QAAQ,UAAU,OACpB,gBAAe,UAAU,eAAe,QAAQ,QAAQ,OAAO,OAAO;AAExE,MAAI,QAAQ,UAAU,QAAW;AAC/B,OAAI,SAAS,gBAAgB,SAC3B,OAAM,uBAAuB,wCAAwC;AAEvE,kBAAe,QAAQ,cAAc,QAAQ,MAAM;aAC1C,SAAS,gBAAgB,SAClC,gBAAe,QAAQ;AAEzB,SAAO,gBAAgB;;AAEzB,QAAO"}
|
|
1
|
+
{"version":3,"file":"translate.js","names":[],"sources":["../../../../src/database/runtime/engine/translate.ts"],"sourcesContent":["import {\n and,\n asc,\n desc,\n eq,\n gt,\n gte,\n ilike,\n inArray,\n isNull,\n like,\n lt,\n lte,\n ne,\n or,\n type SQL,\n sql,\n} from \"drizzle-orm\";\nimport type { AnyPgColumn } from \"drizzle-orm/pg-core\";\n\nimport {\n DEFAULT_LIMIT,\n type FilterOperator,\n IN_CAP,\n isFilterOperator,\n MAX_INCLUDE_DEPTH,\n MAX_INCLUDE_NODES,\n MAX_INCLUDES,\n MAX_WHERE_CONDITIONS,\n MAX_WHERE_DEPTH,\n MAX_WHERE_GROUP_ITEMS,\n} from \"../../contract\";\nimport { invalidDatabaseRequest } from \"../../errors\";\nimport type { AppKitTable, ColumnMeta, Schema } from \"../../schema-builder\";\nimport { filterOperatorsForKind } from \"../../schema-builder/types\";\nimport { columnValueSchema } from \"../../schema-builder/validators\";\nimport {\n type FilterOps,\n type IncludeOptions,\n type IncludeSpec,\n type OrderSpec,\n validateLimit,\n type WhereClause,\n} from \"../data-path\";\n\n/** Read policy selected by the trusted adapter constructor, never by query input. */\nexport type ColumnAccess = \"public\" | \"trusted\";\n\nfunction columnMetaOf(table: AppKitTable, key: string): ColumnMeta {\n const column = table.$columns[key];\n if (!column) {\n throw invalidDatabaseRequest(`Unknown column \"${table.$name}.${key}\"`);\n }\n return column;\n}\n\nfunction readableColumnMeta(\n table: AppKitTable,\n key: string,\n access: ColumnAccess,\n): ColumnMeta {\n const column = columnMetaOf(table, key);\n if (column.isPrivate && access !== \"trusted\") {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${key}\" is not readable`,\n );\n }\n return column;\n}\n\n/** Resolve SQL identifiers only through columns finalized by the schema builder. */\nexport function columnOf(table: AppKitTable, key: string): AnyPgColumn {\n return columnMetaOf(table, key).engineColumn as unknown as AnyPgColumn;\n}\n\n/** Column names that default reads and mutation returns may expose. */\nexport function publicColumnNames(table: AppKitTable): string[] {\n return Object.values(table.$columns)\n .filter((column) => !column.isPrivate)\n .map((column) => column.columnName);\n}\n\n/** Default reads select all finalized columns except private application data. */\nexport function defaultColumns(table: AppKitTable): Record<string, true> {\n const columns: Record<string, true> = {};\n for (const name of publicColumnNames(table)) columns[name] = true;\n return columns;\n}\n\n/** Drizzle `.returning()` fields allowed by the adapter's read capability. */\nexport function returningColumns(\n table: AppKitTable,\n access: ColumnAccess = \"public\",\n): Record<string, AnyPgColumn> {\n const columns: Record<string, AnyPgColumn> = {};\n const names =\n access === \"trusted\"\n ? Object.values(table.$columns).map((column) => column.columnName)\n : publicColumnNames(table);\n for (const name of names) {\n columns[name] = columnOf(table, name);\n }\n return columns;\n}\n\nfunction supportsOperator(meta: ColumnMeta, operator: FilterOperator): boolean {\n if (operator === \"is\") {\n return !meta.notNull && meta.kind !== \"json\" && meta.kind !== \"unknown\";\n }\n return filterOperatorsForKind(meta.kind).includes(operator);\n}\n\nfunction assertColumnValue(\n table: AppKitTable,\n meta: ColumnMeta,\n operator: FilterOperator,\n value: unknown,\n): void {\n if (!columnValueSchema(meta).safeParse(value).success) {\n throw invalidDatabaseRequest(\n `Invalid ${operator} operand for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n}\n\nfunction inList(\n table: AppKitTable,\n meta: ColumnMeta,\n column: AnyPgColumn,\n value: unknown,\n): SQL {\n if (!Array.isArray(value)) {\n throw invalidDatabaseRequest('The \"in\" operator requires an array');\n }\n if (value.length > IN_CAP) {\n throw invalidDatabaseRequest(`in list exceeds the ${IN_CAP}-value limit`);\n }\n for (const item of value) {\n if (item === null) {\n throw invalidDatabaseRequest('The \"in\" operator does not accept null');\n }\n assertColumnValue(table, meta, \"in\", item);\n }\n return value.length === 0 ? sql.raw(\"false\") : inArray(column, value);\n}\n\nfunction translateOperator(\n table: AppKitTable,\n meta: ColumnMeta,\n column: AnyPgColumn,\n operator: FilterOperator,\n value: unknown,\n): SQL {\n if (!supportsOperator(meta, operator)) {\n throw invalidDatabaseRequest(\n `Operator \"${operator}\" is not supported for \"${table.$name}.${meta.columnName}\"`,\n );\n }\n if (operator === \"is\") {\n if (value !== null) {\n throw invalidDatabaseRequest('The \"is\" operator accepts only null');\n }\n return isNull(column);\n }\n if (operator === \"in\") return inList(table, meta, column, value);\n\n assertColumnValue(table, meta, operator, value);\n switch (operator) {\n case \"eq\":\n return eq(column, value);\n case \"neq\":\n return ne(column, value);\n case \"gt\":\n return gt(column, value);\n case \"gte\":\n return gte(column, value);\n case \"lt\":\n return lt(column, value);\n case \"lte\":\n return lte(column, value);\n case \"like\":\n return like(column, value as string);\n case \"ilike\":\n return ilike(column, value as string);\n default:\n throw invalidDatabaseRequest(`Unsupported filter operator \"${operator}\"`);\n }\n}\n\ninterface WhereBudget {\n conditions: number;\n}\n\nfunction chargeWhereCondition(budget: WhereBudget): void {\n budget.conditions += 1;\n if (budget.conditions > MAX_WHERE_CONDITIONS) {\n throw invalidDatabaseRequest(\n `where exceeds the ${MAX_WHERE_CONDITIONS}-condition limit`,\n );\n }\n}\n\nfunction translateWhereNode(\n table: AppKitTable,\n clause: WhereClause,\n access: ColumnAccess,\n depth: number,\n budget: WhereBudget,\n): SQL | undefined {\n if (depth > MAX_WHERE_DEPTH) {\n throw invalidDatabaseRequest(\n `where exceeds the ${MAX_WHERE_DEPTH}-level depth limit`,\n );\n }\n if (clause === null || typeof clause !== \"object\" || Array.isArray(clause)) {\n throw invalidDatabaseRequest(\"where must be an object\");\n }\n const conditions: SQL[] = [];\n for (const [key, value] of Object.entries(clause)) {\n if (key === \"and\" || key === \"or\") {\n if (\n !Array.isArray(value) ||\n value.length === 0 ||\n value.length > MAX_WHERE_GROUP_ITEMS\n ) {\n if (Array.isArray(value) && value.length > MAX_WHERE_GROUP_ITEMS) {\n throw invalidDatabaseRequest(\n `${key} exceeds the ${MAX_WHERE_GROUP_ITEMS}-predicate limit`,\n );\n }\n throw invalidDatabaseRequest(\n `${key} requires a non-empty predicate array`,\n );\n }\n const groups = value.map((group) => {\n const translated = translateWhereNode(\n table,\n group as WhereClause,\n access,\n depth + 1,\n budget,\n );\n if (!translated) {\n throw invalidDatabaseRequest(`${key} predicates cannot be empty`);\n }\n return translated;\n });\n const combined = key === \"and\" ? and(...groups) : or(...groups);\n if (combined) {\n conditions.push(combined);\n }\n continue;\n }\n\n const meta = readableColumnMeta(table, key, access);\n const column = meta.engineColumn as unknown as AnyPgColumn;\n if (Array.isArray(value)) {\n chargeWhereCondition(budget);\n conditions.push(translateOperator(table, meta, column, \"in\", value));\n } else if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value)\n ) {\n const operators = Object.entries(value as FilterOps);\n if (operators.length === 0) {\n throw invalidDatabaseRequest(\n `Filter for \"${table.$name}.${key}\" cannot be empty`,\n );\n }\n for (const [operator, operand] of operators) {\n chargeWhereCondition(budget);\n if (!isFilterOperator(operator)) {\n throw invalidDatabaseRequest(`Unknown filter operator \"${operator}\"`);\n }\n conditions.push(\n translateOperator(table, meta, column, operator, operand),\n );\n }\n } else {\n chargeWhereCondition(budget);\n conditions.push(translateOperator(table, meta, column, \"eq\", value));\n }\n }\n return conditions.length > 0 ? and(...conditions) : undefined;\n}\n\n/** Translate one bounded direct-column predicate tree. */\nexport function translateWhere(\n table: AppKitTable,\n clause: WhereClause,\n access: ColumnAccess = \"public\",\n): SQL | undefined {\n return translateWhereNode(table, clause, access, 1, { conditions: 0 });\n}\n\nexport function translateOrder(\n table: AppKitTable,\n order: OrderSpec,\n access: ColumnAccess = \"public\",\n): SQL[] {\n return Object.entries(order).map(([key, direction]) => {\n if (direction !== \"asc\" && direction !== \"desc\") {\n throw invalidDatabaseRequest(`Unknown order direction \"${direction}\"`);\n }\n const column = readableColumnMeta(table, key, access)\n .engineColumn as unknown as AnyPgColumn;\n return direction === \"desc\" ? desc(column) : asc(column);\n });\n}\n\nexport function selectToColumns(\n table: AppKitTable,\n select: readonly string[],\n access: ColumnAccess = \"public\",\n): Record<string, true> {\n const columns: Record<string, true> = {};\n for (const key of select) {\n readableColumnMeta(table, key, access);\n columns[key] = true;\n }\n return columns;\n}\n\nfunction tableByName(schema: Schema, name: string): AppKitTable {\n const table = schema.$tables[name];\n if (!table) throw invalidDatabaseRequest(`Unknown table \"${name}\"`);\n return table;\n}\n\n/** Translate relation edges into Drizzle's relational `with` config. */\nexport function translateInclude(\n table: AppKitTable,\n schema: Schema,\n include: IncludeSpec,\n access: ColumnAccess = \"public\",\n): Record<string, unknown> {\n return translateIncludeTree(table, schema, include, access, 1, { nodes: 0 });\n}\n\nfunction translateIncludeTree(\n table: AppKitTable,\n schema: Schema,\n include: IncludeSpec,\n access: ColumnAccess,\n depth: number,\n budget: { nodes: number },\n): Record<string, unknown> {\n if (depth > MAX_INCLUDE_DEPTH) {\n throw invalidDatabaseRequest(\n `include exceeds the ${MAX_INCLUDE_DEPTH}-edge depth limit`,\n );\n }\n const entries = Object.entries(include);\n if (entries.length > MAX_INCLUDES) {\n throw invalidDatabaseRequest(\n `include exceeds the ${MAX_INCLUDES}-relation limit`,\n );\n }\n\n const config: Record<string, unknown> = {};\n for (const [relationName, rawOptions] of entries) {\n const relation = table.$relations.find(\n (candidate) => candidate.name === relationName,\n );\n if (!relation) {\n throw invalidDatabaseRequest(\n `Unknown relation \"${table.$name}.${relationName}\"`,\n );\n }\n if (rawOptions === false) continue;\n\n budget.nodes += 1;\n if (budget.nodes > MAX_INCLUDE_NODES) {\n throw invalidDatabaseRequest(\n `include exceeds the ${MAX_INCLUDE_NODES}-node limit`,\n );\n }\n\n const target = tableByName(schema, relation.targetTable);\n if (rawOptions === true) {\n config[relationName] = {\n columns: defaultColumns(target),\n ...(relation.cardinality === \"toMany\" ? { limit: DEFAULT_LIMIT } : {}),\n };\n continue;\n }\n\n const options = rawOptions as IncludeOptions;\n const relationConfig: Record<string, unknown> = {\n columns:\n options.select === undefined\n ? defaultColumns(target)\n : selectToColumns(target, options.select, access),\n };\n if (options.where !== undefined) {\n relationConfig.where = translateWhere(target, options.where, access);\n }\n if (options.order !== undefined) {\n relationConfig.orderBy = translateOrder(target, options.order, access);\n }\n if (options.limit !== undefined) {\n if (relation.cardinality !== \"toMany\") {\n throw invalidDatabaseRequest(\"Only to-many relations accept a limit\");\n }\n relationConfig.limit = validateLimit(options.limit);\n } else if (relation.cardinality === \"toMany\") {\n relationConfig.limit = DEFAULT_LIMIT;\n }\n if (options.include !== undefined) {\n relationConfig.with = translateIncludeTree(\n target,\n schema,\n options.include,\n access,\n depth + 1,\n budget,\n );\n }\n config[relationName] = relationConfig;\n }\n return config;\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,11 @@ interface SchemaBuilderContext {
|
|
|
6
6
|
table<C extends Record<string, ColumnBuilder>>(name: string, columns: C): TableHandle<C>;
|
|
7
7
|
enum(name: string, values: readonly string[]): ColumnBuilder;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Compile one declared schema. The returned type keeps the table names the
|
|
11
|
+
* builder returned, so `crudRoutes` and `hooks` can name only real tables.
|
|
12
|
+
*/
|
|
13
|
+
declare function defineSchema<TTables extends Record<string, AppKitTable>>(builder: (context: SchemaBuilderContext) => TTables, options?: DefineSchemaOptions): Schema<Extract<keyof TTables, string>>;
|
|
10
14
|
//#endregion
|
|
11
15
|
export { defineSchema };
|
|
12
16
|
//# sourceMappingURL=define-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-schema.d.ts","names":[],"sources":["../../../src/database/schema-builder/define-schema.ts"],"mappings":";;;;UAoCiB,oBAAA;EACf,KAAA,WAAgB,MAAA,SAAe,aAAA,GAC7B,IAAA,UACA,OAAA,EAAS,CAAA,GACR,WAAA,CAAY,CAAA;EACf,IAAA,CAAK,IAAA,UAAc,MAAA,sBAA4B,aAAA;AAAA
|
|
1
|
+
{"version":3,"file":"define-schema.d.ts","names":[],"sources":["../../../src/database/schema-builder/define-schema.ts"],"mappings":";;;;UAoCiB,oBAAA;EACf,KAAA,WAAgB,MAAA,SAAe,aAAA,GAC7B,IAAA,UACA,OAAA,EAAS,CAAA,GACR,WAAA,CAAY,CAAA;EACf,IAAA,CAAK,IAAA,UAAc,MAAA,sBAA4B,aAAA;AAAA;;;;;iBAgTjC,YAAA,iBAA6B,MAAA,SAAe,WAAA,EAAA,CAC1D,OAAA,GAAU,OAAA,EAAS,oBAAA,KAAyB,OAAA,EAC5C,OAAA,GAAU,mBAAA,GACT,MAAA,CAAO,OAAA,OAAc,OAAA"}
|
|
@@ -184,6 +184,10 @@ function publishSchema(raw, candidates, schemaName) {
|
|
|
184
184
|
function assertFinalizedSchema(value) {
|
|
185
185
|
if (value === null || typeof value !== "object" || value[FINALIZED_SCHEMA] !== true) throw new TypeError("Expected a finalized AppKit database schema");
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Compile one declared schema. The returned type keeps the table names the
|
|
189
|
+
* builder returned, so `crudRoutes` and `hooks` can name only real tables.
|
|
190
|
+
*/
|
|
187
191
|
function defineSchema(builder, options) {
|
|
188
192
|
const schemaName = options?.schemaName ?? "public";
|
|
189
193
|
if (!schemaName) throw new SchemaBuildError("Schema name cannot be empty");
|