@databricks/appkit 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CLAUDE.md +63 -0
  2. package/NOTICE.md +3 -2
  3. package/dist/appkit/package.js +1 -1
  4. package/dist/beta.d.ts +18 -3
  5. package/dist/beta.js +14 -1
  6. package/dist/cli/commands/agent/eval.js +120 -0
  7. package/dist/cli/commands/agent/eval.js.map +1 -0
  8. package/dist/cli/commands/agent/index.js +18 -0
  9. package/dist/cli/commands/agent/index.js.map +1 -0
  10. package/dist/cli/index.js +2 -0
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/connectors/index.js +2 -0
  13. package/dist/connectors/mlflow/auth.d.ts +28 -0
  14. package/dist/connectors/mlflow/auth.d.ts.map +1 -0
  15. package/dist/connectors/mlflow/auth.js +70 -0
  16. package/dist/connectors/mlflow/auth.js.map +1 -0
  17. package/dist/connectors/mlflow/client.d.ts +51 -0
  18. package/dist/connectors/mlflow/client.d.ts.map +1 -0
  19. package/dist/connectors/mlflow/client.js +93 -0
  20. package/dist/connectors/mlflow/client.js.map +1 -0
  21. package/dist/connectors/mlflow/index.d.ts +2 -0
  22. package/dist/database/errors.js +15 -5
  23. package/dist/database/errors.js.map +1 -1
  24. package/dist/database/runtime/data-path.d.ts +7 -0
  25. package/dist/database/runtime/data-path.d.ts.map +1 -0
  26. package/dist/database/runtime/data-path.js.map +1 -1
  27. package/dist/database/runtime/engine/drizzle-data-path.js +7 -5
  28. package/dist/database/runtime/engine/drizzle-data-path.js.map +1 -1
  29. package/dist/database/schema-builder/define-schema.d.ts +1 -1
  30. package/dist/database/schema-builder/define-schema.js +1 -1
  31. package/dist/database/schema-builder/define-schema.js.map +1 -1
  32. package/dist/errors/database-validation.d.ts +23 -0
  33. package/dist/errors/database-validation.d.ts.map +1 -0
  34. package/dist/errors/database-validation.js +24 -0
  35. package/dist/errors/database-validation.js.map +1 -0
  36. package/dist/errors/index.js +1 -0
  37. package/dist/evals/dataset.d.ts +36 -0
  38. package/dist/evals/dataset.d.ts.map +1 -0
  39. package/dist/evals/dataset.js +36 -0
  40. package/dist/evals/dataset.js.map +1 -0
  41. package/dist/evals/define-eval.d.ts +26 -0
  42. package/dist/evals/define-eval.d.ts.map +1 -0
  43. package/dist/evals/define-eval.js +28 -0
  44. package/dist/evals/define-eval.js.map +1 -0
  45. package/dist/evals/discover.d.ts +20 -0
  46. package/dist/evals/discover.d.ts.map +1 -0
  47. package/dist/evals/discover.js +49 -0
  48. package/dist/evals/discover.js.map +1 -0
  49. package/dist/evals/http-driver.d.ts +33 -0
  50. package/dist/evals/http-driver.d.ts.map +1 -0
  51. package/dist/evals/http-driver.js +123 -0
  52. package/dist/evals/http-driver.js.map +1 -0
  53. package/dist/evals/index.d.ts +14 -0
  54. package/dist/evals/index.js +14 -0
  55. package/dist/evals/judge.d.ts +27 -0
  56. package/dist/evals/judge.d.ts.map +1 -0
  57. package/dist/evals/judge.js +77 -0
  58. package/dist/evals/judge.js.map +1 -0
  59. package/dist/evals/matchers.d.ts +12 -0
  60. package/dist/evals/matchers.d.ts.map +1 -0
  61. package/dist/evals/matchers.js +26 -0
  62. package/dist/evals/matchers.js.map +1 -0
  63. package/dist/evals/mlflow-report.d.ts +37 -0
  64. package/dist/evals/mlflow-report.d.ts.map +1 -0
  65. package/dist/evals/mlflow-report.js +161 -0
  66. package/dist/evals/mlflow-report.js.map +1 -0
  67. package/dist/evals/mlflow-run.d.ts +13 -0
  68. package/dist/evals/mlflow-run.d.ts.map +1 -0
  69. package/dist/evals/mlflow-run.js +101 -0
  70. package/dist/evals/mlflow-run.js.map +1 -0
  71. package/dist/evals/pool.js +24 -0
  72. package/dist/evals/pool.js.map +1 -0
  73. package/dist/evals/report.d.ts +25 -0
  74. package/dist/evals/report.d.ts.map +1 -0
  75. package/dist/evals/report.js +57 -0
  76. package/dist/evals/report.js.map +1 -0
  77. package/dist/evals/run-eval.d.ts +23 -0
  78. package/dist/evals/run-eval.d.ts.map +1 -0
  79. package/dist/evals/run-eval.js +152 -0
  80. package/dist/evals/run-eval.js.map +1 -0
  81. package/dist/evals/run-evals.d.ts +94 -0
  82. package/dist/evals/run-evals.d.ts.map +1 -0
  83. package/dist/evals/run-evals.js +257 -0
  84. package/dist/evals/run-evals.js.map +1 -0
  85. package/dist/evals/types.d.ts +163 -0
  86. package/dist/evals/types.d.ts.map +1 -0
  87. package/dist/index.d.ts +2 -1
  88. package/dist/index.js +2 -1
  89. package/dist/plugin/plugin.d.ts.map +1 -1
  90. package/dist/plugin/plugin.js +1 -1
  91. package/dist/plugin/plugin.js.map +1 -1
  92. package/dist/plugins/agents/agents.js +1 -1
  93. package/dist/plugins/database/crud/contract.js +17 -8
  94. package/dist/plugins/database/crud/contract.js.map +1 -1
  95. package/dist/plugins/database/crud/exposure.js +63 -22
  96. package/dist/plugins/database/crud/exposure.js.map +1 -1
  97. package/dist/plugins/database/crud/request.js +50 -0
  98. package/dist/plugins/database/crud/request.js.map +1 -0
  99. package/dist/plugins/database/crud/response.js +77 -0
  100. package/dist/plugins/database/crud/response.js.map +1 -0
  101. package/dist/plugins/database/crud/routes.js +71 -52
  102. package/dist/plugins/database/crud/routes.js.map +1 -1
  103. package/dist/plugins/database/database.d.ts +6 -4
  104. package/dist/plugins/database/database.d.ts.map +1 -1
  105. package/dist/plugins/database/database.js +46 -16
  106. package/dist/plugins/database/database.js.map +1 -1
  107. package/dist/plugins/database/defaults.js +5 -1
  108. package/dist/plugins/database/defaults.js.map +1 -1
  109. package/dist/plugins/database/entity-client.js +143 -10
  110. package/dist/plugins/database/entity-client.js.map +1 -1
  111. package/dist/plugins/database/entity-types.d.ts +1 -1
  112. package/dist/plugins/database/hooks.d.ts +38 -0
  113. package/dist/plugins/database/hooks.d.ts.map +1 -0
  114. package/dist/plugins/database/index.d.ts +3 -2
  115. package/dist/plugins/database/lifecycle.js +67 -28
  116. package/dist/plugins/database/lifecycle.js.map +1 -1
  117. package/dist/plugins/database/scope.js +58 -0
  118. package/dist/plugins/database/scope.js.map +1 -0
  119. package/dist/plugins/database/types.d.ts +40 -12
  120. package/dist/plugins/database/types.d.ts.map +1 -1
  121. package/docs/api/appkit/Class.AppKitError.md +1 -0
  122. package/docs/api/appkit/Class.DatabaseValidationError.md +191 -0
  123. package/docs/api/appkit/Class.MlflowClient.md +103 -0
  124. package/docs/api/appkit/Function.buildAssessments.md +16 -0
  125. package/docs/api/appkit/Function.configureJudge.md +18 -0
  126. package/docs/api/appkit/Function.createHttpDriver.md +18 -0
  127. package/docs/api/appkit/Function.defineEval.md +35 -0
  128. package/docs/api/appkit/Function.defineSchema.md +1 -1
  129. package/docs/api/appkit/Function.discoverEvalFiles.md +18 -0
  130. package/docs/api/appkit/Function.equals.md +18 -0
  131. package/docs/api/appkit/Function.evalGlyph.md +18 -0
  132. package/docs/api/appkit/Function.formatEvalDetail.md +18 -0
  133. package/docs/api/appkit/Function.formatEvalHeadline.md +18 -0
  134. package/docs/api/appkit/Function.formatEvalResults.md +18 -0
  135. package/docs/api/appkit/Function.formatSummaryLine.md +18 -0
  136. package/docs/api/appkit/Function.includes.md +18 -0
  137. package/docs/api/appkit/Function.isJudgeConfigured.md +10 -0
  138. package/docs/api/appkit/Function.matches.md +18 -0
  139. package/docs/api/appkit/Function.normalizeHost.md +18 -0
  140. package/docs/api/appkit/Function.readEvalDataset.md +21 -0
  141. package/docs/api/appkit/Function.reportToMlflow.md +23 -0
  142. package/docs/api/appkit/Function.resolveDatabricksAuth.md +16 -0
  143. package/docs/api/appkit/Function.resolveWorkspaceClient.md +18 -0
  144. package/docs/api/appkit/Function.runEval.md +19 -0
  145. package/docs/api/appkit/Function.runEvalsInDir.md +18 -0
  146. package/docs/api/appkit/Function.summarize.md +16 -0
  147. package/docs/api/appkit/Interface.AssertionHandle.md +54 -0
  148. package/docs/api/appkit/Interface.AssertionResult.md +48 -0
  149. package/docs/api/appkit/Interface.Assessment.md +83 -0
  150. package/docs/api/appkit/Interface.CustomJudgeSpec.md +30 -0
  151. package/docs/api/appkit/Interface.DatabaseValidationIssue.md +21 -0
  152. package/docs/api/appkit/Interface.DatabricksAuth.md +21 -0
  153. package/docs/api/appkit/Interface.DatasetRow.md +21 -0
  154. package/docs/api/appkit/Interface.DiscoveredEval.md +36 -0
  155. package/docs/api/appkit/Interface.DriveResult.md +58 -0
  156. package/docs/api/appkit/Interface.EntityMutationHooks.md +173 -0
  157. package/docs/api/appkit/Interface.EvalDefinition.md +74 -0
  158. package/docs/api/appkit/Interface.EvalDriver.md +37 -0
  159. package/docs/api/appkit/Interface.EvalResult.md +83 -0
  160. package/docs/api/appkit/Interface.EvalRunSummary.md +46 -0
  161. package/docs/api/appkit/Interface.EvalSummary.md +48 -0
  162. package/docs/api/appkit/Interface.HookApp.md +12 -0
  163. package/docs/api/appkit/Interface.HookContext.md +21 -0
  164. package/docs/api/appkit/Interface.HttpDriverOptions.md +67 -0
  165. package/docs/api/appkit/Interface.JudgeConfig.md +34 -0
  166. package/docs/api/appkit/Interface.JudgeScore.md +21 -0
  167. package/docs/api/appkit/Interface.MatchResult.md +34 -0
  168. package/docs/api/appkit/Interface.PostResult.md +30 -0
  169. package/docs/api/appkit/Interface.ReadEvalDatasetOptions.md +34 -0
  170. package/docs/api/appkit/Interface.ReadSerializerContext.md +21 -0
  171. package/docs/api/appkit/Interface.ReportOutcome.md +53 -0
  172. package/docs/api/appkit/Interface.ResolveDatabricksAuthOptions.md +34 -0
  173. package/docs/api/appkit/Interface.RunEvalOptions.md +45 -0
  174. package/docs/api/appkit/Interface.RunEvalsOptions.md +214 -0
  175. package/docs/api/appkit/Interface.TestContext.md +245 -0
  176. package/docs/api/appkit/TypeAlias.DatabaseApiConfig.md +53 -0
  177. package/docs/api/appkit/TypeAlias.DatabaseApiWriteOperation.md +8 -0
  178. package/docs/api/appkit/TypeAlias.DatabaseApiWritesConfig.md +49 -0
  179. package/docs/api/appkit/TypeAlias.DatabaseExports.md +3 -3
  180. package/docs/api/appkit/TypeAlias.EntityHooks.md +25 -0
  181. package/docs/api/appkit/TypeAlias.EvalProgress.md +26 -0
  182. package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +16 -5
  183. package/docs/api/appkit/TypeAlias.Matcher.md +18 -0
  184. package/docs/api/appkit/TypeAlias.ReadSerializer.md +19 -0
  185. package/docs/api/appkit/TypeAlias.Severity.md +8 -0
  186. package/docs/api/appkit/TypeAlias.TransactionClient.md +19 -0
  187. package/docs/api/appkit.md +157 -95
  188. package/docs/plugins/database.md +144 -0
  189. package/llms.txt +63 -0
  190. package/package.json +3 -2
  191. package/sbom.cdx.json +1 -1
@@ -0,0 +1,70 @@
1
+ import { createWorkspaceClient } from "../../shared/src/workspace-client/factory.js";
2
+
3
+ //#region src/connectors/mlflow/auth.ts
4
+ /**
5
+ * Resolve `{host, token}` for the eval runner the same way the rest of AppKit
6
+ * authenticates: construct a Databricks `WorkspaceClient` and let its config
7
+ * mint (and later refresh) an OAuth bearer from the CLI profile — no hand-set
8
+ * PAT required. An explicit host/token still wins (PAT or CI env), so the SDK
9
+ * is only consulted for whatever isn't supplied.
10
+ *
11
+ * Returns `undefined` when neither an explicit token nor a resolvable profile
12
+ * yields a bearer, so the caller can treat auth as simply unavailable.
13
+ */
14
+ /** Pull the bearer out of an `Authorization: Bearer <token>` header. */
15
+ function extractBearer(headers) {
16
+ return headers.get("authorization")?.replace(/^Bearer\s+/i, "");
17
+ }
18
+ /**
19
+ * Resolve `{host, token}` via the SDK: construct a `WorkspaceClient`, let it
20
+ * mint/refresh an OAuth bearer from the profile (or reuse a PAT), and fall back
21
+ * to any explicit host/token the caller supplied. Returns `undefined` when
22
+ * either is missing or the SDK can't resolve credentials.
23
+ */
24
+ async function resolveViaSdk(options) {
25
+ try {
26
+ const client = resolveWorkspaceClient(options);
27
+ if (!client) return void 0;
28
+ const headers = new Headers();
29
+ await client.config.authenticate(headers);
30
+ const token = options.token ?? extractBearer(headers);
31
+ const host = options.host ?? (await client.config.getHost()).toString().replace(/\/+$/, "");
32
+ if (!token || !host) return void 0;
33
+ return {
34
+ host,
35
+ token
36
+ };
37
+ } catch {
38
+ return;
39
+ }
40
+ }
41
+ async function resolveDatabricksAuth(options = {}) {
42
+ if (options.host && options.token) return {
43
+ host: options.host,
44
+ token: options.token
45
+ };
46
+ return resolveViaSdk(options);
47
+ }
48
+ /**
49
+ * Construct a Databricks `WorkspaceClient` for the eval runner — the object the
50
+ * SDK-backed connectors (e.g. `SQLWarehouseConnector`) take. An explicit
51
+ * host+token builds a PAT client; otherwise the profile (or ambient config) is
52
+ * used and the SDK resolves credentials, minting OAuth as needed. Returns
53
+ * `undefined` if construction throws (missing/invalid config).
54
+ */
55
+ function resolveWorkspaceClient(options = {}) {
56
+ try {
57
+ if (options.host && options.token) return createWorkspaceClient({
58
+ host: options.host,
59
+ token: options.token,
60
+ authType: "pat"
61
+ });
62
+ return createWorkspaceClient(options.profile ? { profile: options.profile } : {});
63
+ } catch {
64
+ return;
65
+ }
66
+ }
67
+
68
+ //#endregion
69
+ export { resolveDatabricksAuth, resolveWorkspaceClient };
70
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../../src/connectors/mlflow/auth.ts"],"sourcesContent":["import {\n createWorkspaceClient,\n type WorkspaceClient,\n} from \"../../workspace-client\";\n\n/** Resolved Databricks host + bearer token for the eval runner's REST calls. */\nexport interface DatabricksAuth {\n host: string;\n token: string;\n}\n\nexport interface ResolveDatabricksAuthOptions {\n /** `~/.databrickscfg` profile to authenticate with (e.g. `dogfood`). */\n profile?: string;\n /** Explicit host; wins over the profile/SDK-resolved host when set. */\n host?: string;\n /** Explicit bearer token; when set, no OAuth is minted (PAT/CI path). */\n token?: string;\n}\n\n/**\n * Resolve `{host, token}` for the eval runner the same way the rest of AppKit\n * authenticates: construct a Databricks `WorkspaceClient` and let its config\n * mint (and later refresh) an OAuth bearer from the CLI profile — no hand-set\n * PAT required. An explicit host/token still wins (PAT or CI env), so the SDK\n * is only consulted for whatever isn't supplied.\n *\n * Returns `undefined` when neither an explicit token nor a resolvable profile\n * yields a bearer, so the caller can treat auth as simply unavailable.\n */\n/** Pull the bearer out of an `Authorization: Bearer <token>` header. */\nfunction extractBearer(headers: Headers): string | undefined {\n return headers.get(\"authorization\")?.replace(/^Bearer\\s+/i, \"\");\n}\n\n/**\n * Resolve `{host, token}` via the SDK: construct a `WorkspaceClient`, let it\n * mint/refresh an OAuth bearer from the profile (or reuse a PAT), and fall back\n * to any explicit host/token the caller supplied. Returns `undefined` when\n * either is missing or the SDK can't resolve credentials.\n */\nasync function resolveViaSdk(\n options: ResolveDatabricksAuthOptions,\n): Promise<DatabricksAuth | undefined> {\n try {\n const client = resolveWorkspaceClient(options);\n if (!client) return undefined;\n const headers = new Headers();\n // Mints the OAuth access token (or reuses a PAT from the profile) and adds\n // an `Authorization: Bearer <token>` header — the same call the connectors\n // use before each request.\n await client.config.authenticate(headers);\n const token = options.token ?? extractBearer(headers);\n const host =\n options.host ??\n (await client.config.getHost()).toString().replace(/\\/+$/, \"\");\n if (!token || !host) return undefined;\n return { host, token };\n } catch {\n return undefined;\n }\n}\n\nexport async function resolveDatabricksAuth(\n options: ResolveDatabricksAuthOptions = {},\n): Promise<DatabricksAuth | undefined> {\n // Fully explicit — no need to touch the SDK.\n if (options.host && options.token) {\n return { host: options.host, token: options.token };\n }\n return resolveViaSdk(options);\n}\n\n/**\n * Construct a Databricks `WorkspaceClient` for the eval runner — the object the\n * SDK-backed connectors (e.g. `SQLWarehouseConnector`) take. An explicit\n * host+token builds a PAT client; otherwise the profile (or ambient config) is\n * used and the SDK resolves credentials, minting OAuth as needed. Returns\n * `undefined` if construction throws (missing/invalid config).\n */\nexport function resolveWorkspaceClient(\n options: ResolveDatabricksAuthOptions = {},\n): WorkspaceClient | undefined {\n try {\n if (options.host && options.token) {\n return createWorkspaceClient({\n host: options.host,\n token: options.token,\n authType: \"pat\",\n });\n }\n return createWorkspaceClient(\n options.profile ? { profile: options.profile } : {},\n );\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA+BA,SAAS,cAAc,SAAsC;AAC3D,QAAO,QAAQ,IAAI,gBAAgB,EAAE,QAAQ,eAAe,GAAG;;;;;;;;AASjE,eAAe,cACb,SACqC;AACrC,KAAI;EACF,MAAM,SAAS,uBAAuB,QAAQ;AAC9C,MAAI,CAAC,OAAQ,QAAO;EACpB,MAAM,UAAU,IAAI,SAAS;AAI7B,QAAM,OAAO,OAAO,aAAa,QAAQ;EACzC,MAAM,QAAQ,QAAQ,SAAS,cAAc,QAAQ;EACrD,MAAM,OACJ,QAAQ,SACP,MAAM,OAAO,OAAO,SAAS,EAAE,UAAU,CAAC,QAAQ,QAAQ,GAAG;AAChE,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO;AAC5B,SAAO;GAAE;GAAM;GAAO;SAChB;AACN;;;AAIJ,eAAsB,sBACpB,UAAwC,EAAE,EACL;AAErC,KAAI,QAAQ,QAAQ,QAAQ,MAC1B,QAAO;EAAE,MAAM,QAAQ;EAAM,OAAO,QAAQ;EAAO;AAErD,QAAO,cAAc,QAAQ;;;;;;;;;AAU/B,SAAgB,uBACd,UAAwC,EAAE,EACb;AAC7B,KAAI;AACF,MAAI,QAAQ,QAAQ,QAAQ,MAC1B,QAAO,sBAAsB;GAC3B,MAAM,QAAQ;GACd,OAAO,QAAQ;GACf,UAAU;GACX,CAAC;AAEJ,SAAO,sBACL,QAAQ,UAAU,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE,CACpD;SACK;AACN"}
@@ -0,0 +1,51 @@
1
+ //#region src/connectors/mlflow/client.d.ts
2
+ /** Shared client for talking to the Databricks/MLflow REST API. */
3
+ /** Ensure the host has a scheme (Databricks env often lacks `https://`). */
4
+ declare function normalizeHost(raw: string): string;
5
+ /** Structured result for a best-effort POST that must not throw. */
6
+ interface PostResult {
7
+ ok: boolean;
8
+ status?: number;
9
+ error?: string;
10
+ }
11
+ /**
12
+ * A thin client over the Databricks workspace REST API, owning the host + bearer
13
+ * token so callers (eval-run creation, assessment writes, the judge's serving
14
+ * endpoint) don't each re-derive URLs or re-attach auth. The host is normalized
15
+ * once at construction.
16
+ */
17
+ declare class MlflowClient {
18
+ /** Normalized workspace base URL (scheme guaranteed, no trailing slash). */
19
+ readonly baseUrl: string;
20
+ private readonly token;
21
+ constructor(host: string, token: string);
22
+ private headers;
23
+ /**
24
+ * POST JSON to an MLflow REST endpoint. Returns the parsed JSON body, or
25
+ * throws with the status + response text so callers can surface a precise
26
+ * error. Use for calls whose failure should abort (e.g. `runs/create`).
27
+ *
28
+ * The thrown message embeds up to 500 chars of the upstream response body to
29
+ * aid debugging. That is fine for the dev-facing eval CLI, but do NOT relay
30
+ * it into an end-user HTTP response if this client is reused in a request
31
+ * handler — the body can carry workspace-internal detail.
32
+ */
33
+ post<T = unknown>(path: string, body: unknown): Promise<T>;
34
+ /**
35
+ * POST JSON without throwing: returns `{ ok, status, error }` so best-effort
36
+ * writes (e.g. per-trace assessments) can be collected and reported without
37
+ * aborting the run.
38
+ *
39
+ * `error` embeds up to 500 chars of the upstream body — same caveat as
40
+ * {@link post}: fine to log for the dev CLI, don't relay it to end users.
41
+ */
42
+ postResult(path: string, body: unknown): Promise<PostResult>;
43
+ /**
44
+ * OpenAI-compatible base URL for Databricks Model Serving, used as the judge's
45
+ * `OPENAI_BASE_URL`. Same workspace host + token as the MLflow REST calls.
46
+ */
47
+ servingEndpointsUrl(): string;
48
+ }
49
+ //#endregion
50
+ export { MlflowClient, PostResult, normalizeHost };
51
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../../src/connectors/mlflow/client.ts"],"mappings":";;AAGA;AAAA,iBAAgB,aAAA,CAAc,GAAA;;UAMb,UAAA;EACf,EAAA;EACA,MAAA;EACA,KAAA;AAAA;;;;;;;cASW,YAAA;EAAY;EAAA,SAEd,OAAA;EAAA,iBACQ,KAAA;cAEL,IAAA,UAAc,KAAA;EAAA,QAKlB,OAAA;EAuCuC;;;;;;;;;;EAtBzC,IAAA,aAAA,CAAkB,IAAA,UAAc,IAAA,YAAgB,OAAA,CAAQ,CAAA;EAAtC;;;;;;;;EAsBlB,UAAA,CAAW,IAAA,UAAc,IAAA,YAAgB,OAAA,CAAQ,UAAA;EAwBvD;;;;EAAA,mBAAA,CAAA;AAAA"}
@@ -0,0 +1,93 @@
1
+ //#region src/connectors/mlflow/client.ts
2
+ /** Shared client for talking to the Databricks/MLflow REST API. */
3
+ /** Ensure the host has a scheme (Databricks env often lacks `https://`). */
4
+ function normalizeHost(raw) {
5
+ const h = raw.trim().replace(/\/+$/, "");
6
+ return /^https?:\/\//i.test(h) ? h : `https://${h}`;
7
+ }
8
+ /**
9
+ * A thin client over the Databricks workspace REST API, owning the host + bearer
10
+ * token so callers (eval-run creation, assessment writes, the judge's serving
11
+ * endpoint) don't each re-derive URLs or re-attach auth. The host is normalized
12
+ * once at construction.
13
+ */
14
+ var MlflowClient = class {
15
+ /** Normalized workspace base URL (scheme guaranteed, no trailing slash). */
16
+ baseUrl;
17
+ token;
18
+ constructor(host, token) {
19
+ this.baseUrl = normalizeHost(host);
20
+ this.token = token;
21
+ }
22
+ headers() {
23
+ return {
24
+ "content-type": "application/json",
25
+ authorization: `Bearer ${this.token}`
26
+ };
27
+ }
28
+ /**
29
+ * POST JSON to an MLflow REST endpoint. Returns the parsed JSON body, or
30
+ * throws with the status + response text so callers can surface a precise
31
+ * error. Use for calls whose failure should abort (e.g. `runs/create`).
32
+ *
33
+ * The thrown message embeds up to 500 chars of the upstream response body to
34
+ * aid debugging. That is fine for the dev-facing eval CLI, but do NOT relay
35
+ * it into an end-user HTTP response if this client is reused in a request
36
+ * handler — the body can carry workspace-internal detail.
37
+ */
38
+ async post(path, body) {
39
+ const res = await fetch(`${this.baseUrl}${path}`, {
40
+ method: "POST",
41
+ headers: this.headers(),
42
+ body: JSON.stringify(body)
43
+ });
44
+ if (!res.ok) {
45
+ const text = await res.text().catch(() => "");
46
+ throw new Error(`${path} -> ${res.status} ${text.slice(0, 500)}`);
47
+ }
48
+ const text = await res.text();
49
+ return text ? JSON.parse(text) : {};
50
+ }
51
+ /**
52
+ * POST JSON without throwing: returns `{ ok, status, error }` so best-effort
53
+ * writes (e.g. per-trace assessments) can be collected and reported without
54
+ * aborting the run.
55
+ *
56
+ * `error` embeds up to 500 chars of the upstream body — same caveat as
57
+ * {@link post}: fine to log for the dev CLI, don't relay it to end users.
58
+ */
59
+ async postResult(path, body) {
60
+ try {
61
+ const res = await fetch(`${this.baseUrl}${path}`, {
62
+ method: "POST",
63
+ headers: this.headers(),
64
+ body: JSON.stringify(body)
65
+ });
66
+ if (!res.ok) {
67
+ const text = await res.text().catch(() => "");
68
+ return {
69
+ ok: false,
70
+ status: res.status,
71
+ error: text.slice(0, 500)
72
+ };
73
+ }
74
+ return { ok: true };
75
+ } catch (err) {
76
+ return {
77
+ ok: false,
78
+ error: err instanceof Error ? err.message : String(err)
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * OpenAI-compatible base URL for Databricks Model Serving, used as the judge's
84
+ * `OPENAI_BASE_URL`. Same workspace host + token as the MLflow REST calls.
85
+ */
86
+ servingEndpointsUrl() {
87
+ return `${this.baseUrl}/serving-endpoints`;
88
+ }
89
+ };
90
+
91
+ //#endregion
92
+ export { MlflowClient, normalizeHost };
93
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/mlflow/client.ts"],"sourcesContent":["/** Shared client for talking to the Databricks/MLflow REST API. */\n\n/** Ensure the host has a scheme (Databricks env often lacks `https://`). */\nexport function normalizeHost(raw: string): string {\n const h = raw.trim().replace(/\\/+$/, \"\");\n return /^https?:\\/\\//i.test(h) ? h : `https://${h}`;\n}\n\n/** Structured result for a best-effort POST that must not throw. */\nexport interface PostResult {\n ok: boolean;\n status?: number;\n error?: string;\n}\n\n/**\n * A thin client over the Databricks workspace REST API, owning the host + bearer\n * token so callers (eval-run creation, assessment writes, the judge's serving\n * endpoint) don't each re-derive URLs or re-attach auth. The host is normalized\n * once at construction.\n */\nexport class MlflowClient {\n /** Normalized workspace base URL (scheme guaranteed, no trailing slash). */\n readonly baseUrl: string;\n private readonly token: string;\n\n constructor(host: string, token: string) {\n this.baseUrl = normalizeHost(host);\n this.token = token;\n }\n\n private headers(): Record<string, string> {\n return {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${this.token}`,\n };\n }\n\n /**\n * POST JSON to an MLflow REST endpoint. Returns the parsed JSON body, or\n * throws with the status + response text so callers can surface a precise\n * error. Use for calls whose failure should abort (e.g. `runs/create`).\n *\n * The thrown message embeds up to 500 chars of the upstream response body to\n * aid debugging. That is fine for the dev-facing eval CLI, but do NOT relay\n * it into an end-user HTTP response if this client is reused in a request\n * handler — the body can carry workspace-internal detail.\n */\n async post<T = unknown>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`${path} -> ${res.status} ${text.slice(0, 500)}`);\n }\n const text = await res.text();\n return (text ? JSON.parse(text) : {}) as T;\n }\n\n /**\n * POST JSON without throwing: returns `{ ok, status, error }` so best-effort\n * writes (e.g. per-trace assessments) can be collected and reported without\n * aborting the run.\n *\n * `error` embeds up to 500 chars of the upstream body — same caveat as\n * {@link post}: fine to log for the dev CLI, don't relay it to end users.\n */\n async postResult(path: string, body: unknown): Promise<PostResult> {\n try {\n const res = await fetch(`${this.baseUrl}${path}`, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n return { ok: false, status: res.status, error: text.slice(0, 500) };\n }\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * OpenAI-compatible base URL for Databricks Model Serving, used as the judge's\n * `OPENAI_BASE_URL`. Same workspace host + token as the MLflow REST calls.\n */\n servingEndpointsUrl(): string {\n return `${this.baseUrl}/serving-endpoints`;\n }\n}\n"],"mappings":";;;AAGA,SAAgB,cAAc,KAAqB;CACjD,MAAM,IAAI,IAAI,MAAM,CAAC,QAAQ,QAAQ,GAAG;AACxC,QAAO,gBAAgB,KAAK,EAAE,GAAG,IAAI,WAAW;;;;;;;;AAgBlD,IAAa,eAAb,MAA0B;;CAExB,AAAS;CACT,AAAiB;CAEjB,YAAY,MAAc,OAAe;AACvC,OAAK,UAAU,cAAc,KAAK;AAClC,OAAK,QAAQ;;CAGf,AAAQ,UAAkC;AACxC,SAAO;GACL,gBAAgB;GAChB,eAAe,UAAU,KAAK;GAC/B;;;;;;;;;;;;CAaH,MAAM,KAAkB,MAAc,MAA2B;EAC/D,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,QAAQ;GAChD,QAAQ;GACR,SAAS,KAAK,SAAS;GACvB,MAAM,KAAK,UAAU,KAAK;GAC3B,CAAC;AACF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,YAAY,GAAG;AAC7C,SAAM,IAAI,MAAM,GAAG,KAAK,MAAM,IAAI,OAAO,GAAG,KAAK,MAAM,GAAG,IAAI,GAAG;;EAEnE,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,SAAQ,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE;;;;;;;;;;CAWtC,MAAM,WAAW,MAAc,MAAoC;AACjE,MAAI;GACF,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,QAAQ;IAChD,QAAQ;IACR,SAAS,KAAK,SAAS;IACvB,MAAM,KAAK,UAAU,KAAK;IAC3B,CAAC;AACF,OAAI,CAAC,IAAI,IAAI;IACX,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,YAAY,GAAG;AAC7C,WAAO;KAAE,IAAI;KAAO,QAAQ,IAAI;KAAQ,OAAO,KAAK,MAAM,GAAG,IAAI;KAAE;;AAErE,UAAO,EAAE,IAAI,MAAM;WACZ,KAAK;AACZ,UAAO;IACL,IAAI;IACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACxD;;;;;;;CAQL,sBAA8B;AAC5B,SAAO,GAAG,KAAK,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import { DatabricksAuth, ResolveDatabricksAuthOptions, resolveDatabricksAuth, resolveWorkspaceClient } from "./auth.js";
2
+ import { MlflowClient, PostResult, normalizeHost } from "./client.js";
@@ -9,6 +9,10 @@ const definitions = {
9
9
  message: "Invalid database request",
10
10
  statusCode: 400
11
11
  },
12
+ VALIDATION_FAILED: {
13
+ message: "Database request failed validation",
14
+ statusCode: 422
15
+ },
12
16
  NOT_FOUND: {
13
17
  message: "Database record not found",
14
18
  statusCode: 404
@@ -25,6 +29,10 @@ const definitions = {
25
29
  message: "Database operation temporarily unavailable",
26
30
  statusCode: 503
27
31
  },
32
+ UNSUPPORTED_MEDIA_TYPE: {
33
+ message: "Database request body must be JSON",
34
+ statusCode: 415
35
+ },
28
36
  PAYLOAD_TOO_LARGE: {
29
37
  message: "Database response is too large",
30
38
  statusCode: 413
@@ -44,6 +52,8 @@ const categoryByStatus = {
44
52
  404: "NOT_FOUND",
45
53
  409: "CONFLICT",
46
54
  413: "PAYLOAD_TOO_LARGE",
55
+ 415: "UNSUPPORTED_MEDIA_TYPE",
56
+ 422: "VALIDATION_FAILED",
47
57
  503: "TRANSIENT"
48
58
  };
49
59
  /** AppKit-facing database failure with stable metadata and no driver details. */
@@ -51,9 +61,9 @@ var DatabasePluginError = class extends AppKitError {
51
61
  code = "DATABASE_PLUGIN_ERROR";
52
62
  isRetryable;
53
63
  statusCode;
54
- constructor(category, phase, runtimeMessage, details) {
64
+ constructor(category, phase, diagnosticMessage, details) {
55
65
  const definition = definitions[category];
56
- super(phase === "runtime" && runtimeMessage ? runtimeMessage : definition.message, { clientMessage: definition.message });
66
+ super((phase === "runtime" || phase === "setup") && diagnosticMessage ? diagnosticMessage : definition.message, { clientMessage: definition.message });
57
67
  this.category = category;
58
68
  this.phase = phase;
59
69
  this.details = details;
@@ -67,8 +77,8 @@ function invalidDatabaseRequest(runtimeMessage) {
67
77
  return new DatabasePluginError("INVALID_REQUEST", "runtime", runtimeMessage);
68
78
  }
69
79
  /** Refuse to publish a plugin whose configuration cannot be honored. */
70
- function databaseSetupFailed() {
71
- return new DatabasePluginError("SETUP_FAILED", "setup");
80
+ function databaseSetupFailed(reason) {
81
+ return new DatabasePluginError("SETUP_FAILED", "setup", reason ? `Database setup failed: ${reason}` : void 0);
72
82
  }
73
83
  /** Reject untrusted request input, naming the field but never its value. */
74
84
  function invalidDatabaseInput(path, message) {
@@ -93,7 +103,7 @@ function describeUnclassifiedError(error) {
93
103
  }
94
104
  /** Add operation context without retaining an unknown error's details. */
95
105
  function classifyDatabaseError(error, phase) {
96
- if (error instanceof DatabasePluginError) return error.phase === phase ? error : new DatabasePluginError(error.category, phase);
106
+ if (error instanceof DatabasePluginError) return error.phase === phase ? error : new DatabasePluginError(error.category, phase, void 0, error.details);
97
107
  logger.error("Unclassified database error during %s (%s)", phase, describeUnclassifiedError(error));
98
108
  return new DatabasePluginError("INTERNAL", phase);
99
109
  }
@@ -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 | \"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
+ {"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 | \"VALIDATION_FAILED\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"FORBIDDEN\"\n | \"TRANSIENT\"\n | \"UNSUPPORTED_MEDIA_TYPE\"\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 VALIDATION_FAILED: {\n message: \"Database request failed validation\",\n statusCode: 422,\n },\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 UNSUPPORTED_MEDIA_TYPE: {\n message: \"Database request body must be JSON\",\n statusCode: 415,\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 415: \"UNSUPPORTED_MEDIA_TYPE\",\n 422: \"VALIDATION_FAILED\",\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 diagnosticMessage?: string,\n readonly details?: readonly DatabaseErrorDetail[],\n ) {\n const definition = definitions[category];\n // Setup and runtime diagnostics stay server-side. Request boundaries and\n // clientMessage always use the stable message.\n super(\n (phase === \"runtime\" || phase === \"setup\") && diagnosticMessage\n ? diagnosticMessage\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(reason?: string): DatabasePluginError {\n return new DatabasePluginError(\n \"SETUP_FAILED\",\n \"setup\",\n reason ? `Database setup failed: ${reason}` : undefined,\n );\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 // Details name request fields only, so they survive a change of phase.\n return error.phase === phase\n ? error\n : new DatabasePluginError(\n error.category,\n phase,\n undefined,\n error.details,\n );\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;AA4BvC,MAAM,cAGF;CACF,iBAAiB;EAAE,SAAS;EAA4B,YAAY;EAAK;CACzE,mBAAmB;EACjB,SAAS;EACT,YAAY;EACb;CACD,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,wBAAwB;EACtB,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;CACL,KAAK;CACL,KAAK;CACN;;AAGD,IAAa,sBAAb,cAAyC,YAAY;CACnD,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CAET,YACE,AAAS,UACT,AAAS,OACT,mBACA,AAAS,SACT;EACA,MAAM,aAAa,YAAY;AAG/B,SACG,UAAU,aAAa,UAAU,YAAY,oBAC1C,oBACA,WAAW,SACf,EACE,eAAe,WAAW,SAC3B,CACF;EAfQ;EACA;EAEA;AAaT,OAAK,aAAa,WAAW;AAC7B,OAAK,cAAc,aAAa;AAChC,OAAK,OAAO;;;;AAKhB,SAAgB,uBACd,gBACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,WAAW,eAAe;;;AAI9E,SAAgB,oBAAoB,QAAsC;AACxE,QAAO,IAAI,oBACT,gBACA,SACA,SAAS,0BAA0B,WAAW,OAC/C;;;AAIH,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,oBAEnB,QAAO,MAAM,UAAU,QACnB,QACA,IAAI,oBACF,MAAM,UACN,OACA,QACA,MAAM,QACP;AAEP,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"}
@@ -0,0 +1,7 @@
1
+ import { IdValue, OrderDirection } from "../contract/wire.js";
2
+
3
+ //#region src/database/runtime/data-path.d.ts
4
+ type Row = Record<string, unknown>;
5
+ //#endregion
6
+ export { Row };
7
+ //# sourceMappingURL=data-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-path.d.ts","names":[],"sources":["../../../src/database/runtime/data-path.ts"],"mappings":";;;KA8CY,GAAA,GAAM,MAAA"}
@@ -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 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"}
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(\n table: AppKitTable,\n id: IdValue,\n values: Row,\n where?: WhereClause,\n ): 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(\n table: AppKitTable,\n id: IdValue,\n where?: WhereClause,\n ): 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;;;AAyClE,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"}
@@ -2,7 +2,7 @@ import { createLogger } from "../../../logging/logger.js";
2
2
  import { columnValueSchema } from "../../schema-builder/validators.js";
3
3
  import { DatabasePluginError, invalidDatabaseRequest } from "../../errors.js";
4
4
  import { buildEngineRelations } from "../../schema-builder/engine/relations.js";
5
- import { conflictTargetMeta, limitOrDefault, primaryKeyMeta, validateOffset } from "../data-path.js";
5
+ import { andWhere, conflictTargetMeta, limitOrDefault, primaryKeyMeta, validateOffset } from "../data-path.js";
6
6
  import { columnOf, defaultColumns, publicColumnNames, returningColumns, selectToColumns, translateInclude, translateOrder, translateWhere } from "./translate.js";
7
7
  import { and, eq, isSQLWrapper, sql } from "drizzle-orm";
8
8
  import { drizzle } from "drizzle-orm/node-postgres";
@@ -113,6 +113,8 @@ function createDrizzleDataPath(db, schema, options = {}) {
113
113
  return table.$engine;
114
114
  };
115
115
  const whereSql = (table, where) => where === void 0 ? void 0 : translateWhere(table, where, columnAccess);
116
+ /** Narrow by the primary key while preserving an accumulated predicate. */
117
+ const keyedWhere = (table, primaryKey, id, where) => where === void 0 ? eq(columnOf(table, primaryKey.columnName), id) : translateWhere(table, andWhere({ [primaryKey.columnName]: { eq: id } }, where), columnAccess);
116
118
  return {
117
119
  async select(table, spec) {
118
120
  return runDatabaseOperation(() => relationalQueryBuilder(db, schema, table).findMany({
@@ -142,11 +144,11 @@ function createDrizzleDataPath(db, schema, options = {}) {
142
144
  const parameters = mutationValues(table, values, "insert");
143
145
  return expectExactlyOne(mutationRows(table, await runDatabaseOperation(() => db.insert(engineTable).values(parameters).returning(returningColumns(table, columnAccess))), columnAccess));
144
146
  },
145
- async update(table, id, values) {
147
+ async update(table, id, values, where) {
146
148
  const engineTable = pgTable(table);
147
149
  const parameters = mutationValues(table, values, "update");
148
150
  const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(table, id);
149
- return expectZeroOrOne(mutationRows(table, await runDatabaseOperation(() => db.update(engineTable).set(parameters).where(eq(columnOf(table, primaryKey.columnName), validatedId)).returning(returningColumns(table, columnAccess))), columnAccess));
151
+ return expectZeroOrOne(mutationRows(table, await runDatabaseOperation(() => db.update(engineTable).set(parameters).where(keyedWhere(table, primaryKey, validatedId, where)).returning(returningColumns(table, columnAccess))), columnAccess));
150
152
  },
151
153
  async upsert(table, values, onConflict) {
152
154
  const engineTable = pgTable(table);
@@ -158,9 +160,9 @@ function createDrizzleDataPath(db, schema, options = {}) {
158
160
  set: updates
159
161
  }).returning(returningColumns(table, columnAccess))), columnAccess));
160
162
  },
161
- async delete(table, id) {
163
+ async delete(table, id, where) {
162
164
  const { meta: primaryKey, value: validatedId } = validatedPrimaryKey(table, id);
163
- return expectZeroOrOne(await runDatabaseOperation(() => db.delete(pgTable(table)).where(eq(columnOf(table, primaryKey.columnName), validatedId)).returning({ id: columnOf(table, primaryKey.columnName) }))) !== null;
165
+ return expectZeroOrOne(await runDatabaseOperation(() => db.delete(pgTable(table)).where(keyedWhere(table, primaryKey, validatedId, where)).returning({ id: columnOf(table, primaryKey.columnName) }))) !== null;
164
166
  },
165
167
  async raw(strings, ...values) {
166
168
  if (values.some((value) => isSQLWrapper(value))) throw invalidDatabaseRequest("Tagged SQL interpolations must be parameter values");
@@ -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\" || 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":"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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 andWhere,\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 /** Narrow by the primary key while preserving an accumulated predicate. */\n const keyedWhere = (\n table: AppKitTable,\n primaryKey: ColumnMeta,\n id: IdValue,\n where?: WhereClause,\n ): SQL | undefined =>\n where === undefined\n ? eq(columnOf(table, primaryKey.columnName), id)\n : translateWhere(\n table,\n andWhere({ [primaryKey.columnName]: { eq: id } }, where),\n columnAccess,\n );\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, where) {\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(keyedWhere(table, primaryKey, validatedId, where))\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, where) {\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(keyedWhere(table, primaryKey, validatedId, where))\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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"}
@@ -8,7 +8,7 @@ interface SchemaBuilderContext {
8
8
  }
9
9
  /**
10
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.
11
+ * builder returned, so `api.tables` and `hooks` can name only real tables.
12
12
  */
13
13
  declare function defineSchema<TTables extends Record<string, AppKitTable>>(builder: (context: SchemaBuilderContext) => TTables, options?: DefineSchemaOptions): Schema<Extract<keyof TTables, string>>;
14
14
  //#endregion
@@ -186,7 +186,7 @@ function assertFinalizedSchema(value) {
186
186
  }
187
187
  /**
188
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.
189
+ * builder returned, so `api.tables` and `hooks` can name only real tables.
190
190
  */
191
191
  function defineSchema(builder, options) {
192
192
  const schemaName = options?.schemaName ?? "public";