@prisma-next/sql-runtime 0.5.0-dev.7 → 0.5.0-dev.71

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 (48) hide show
  1. package/README.md +31 -22
  2. package/dist/exports-CXtbKm5q.mjs +1516 -0
  3. package/dist/exports-CXtbKm5q.mjs.map +1 -0
  4. package/dist/{index-yb51L_1h.d.mts → index-C4Dz0JKE.d.mts} +116 -45
  5. package/dist/index-C4Dz0JKE.d.mts.map +1 -0
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +2 -3
  8. package/dist/test/utils.d.mts +38 -33
  9. package/dist/test/utils.d.mts.map +1 -1
  10. package/dist/test/utils.mjs +107 -56
  11. package/dist/test/utils.mjs.map +1 -1
  12. package/package.json +17 -18
  13. package/src/codecs/alias-resolver.ts +34 -0
  14. package/src/codecs/decoding.ts +263 -176
  15. package/src/codecs/encoding.ts +151 -38
  16. package/src/codecs/validation.ts +4 -4
  17. package/src/content-hash.ts +44 -0
  18. package/src/exports/index.ts +13 -7
  19. package/src/fingerprint.ts +22 -0
  20. package/src/guardrails/raw.ts +165 -0
  21. package/src/lower-sql-plan.ts +3 -3
  22. package/src/marker.ts +75 -0
  23. package/src/middleware/before-compile-chain.ts +1 -0
  24. package/src/middleware/budgets.ts +36 -120
  25. package/src/middleware/lints.ts +3 -3
  26. package/src/middleware/sql-middleware.ts +6 -5
  27. package/src/runtime-spi.ts +44 -0
  28. package/src/sql-context.ts +315 -105
  29. package/src/sql-family-adapter.ts +3 -2
  30. package/src/sql-marker.ts +89 -51
  31. package/src/sql-runtime.ts +305 -144
  32. package/dist/exports-BQZSVXXt.mjs +0 -981
  33. package/dist/exports-BQZSVXXt.mjs.map +0 -1
  34. package/dist/index-yb51L_1h.d.mts.map +0 -1
  35. package/src/codecs/json-schema-validation.ts +0 -61
  36. package/test/async-iterable-result.test.ts +0 -141
  37. package/test/before-compile-chain.test.ts +0 -223
  38. package/test/budgets.test.ts +0 -431
  39. package/test/context.types.test-d.ts +0 -68
  40. package/test/execution-stack.test.ts +0 -161
  41. package/test/json-schema-validation.test.ts +0 -571
  42. package/test/lints.test.ts +0 -160
  43. package/test/mutation-default-generators.test.ts +0 -254
  44. package/test/parameterized-types.test.ts +0 -529
  45. package/test/sql-context.test.ts +0 -384
  46. package/test/sql-family-adapter.test.ts +0 -103
  47. package/test/sql-runtime.test.ts +0 -792
  48. package/test/utils.ts +0 -297
@@ -1,7 +1,10 @@
1
- import type { ExecutionPlan } from '@prisma-next/contract/types';
2
- import { type RuntimeErrorEnvelope, runtimeError } from '@prisma-next/framework-components/runtime';
3
- import type { AfterExecuteResult } from '@prisma-next/runtime-executor';
1
+ import {
2
+ type AfterExecuteResult,
3
+ type RuntimeErrorEnvelope,
4
+ runtimeError,
5
+ } from '@prisma-next/framework-components/runtime';
4
6
  import { isQueryAst, type SelectAst } from '@prisma-next/sql-relational-core/ast';
7
+ import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
5
8
  import type { SqlMiddleware, SqlMiddlewareContext } from './sql-middleware';
6
9
 
7
10
  export interface BudgetsOptions {
@@ -22,23 +25,31 @@ function hasAggregateWithoutGroupBy(ast: SelectAst): boolean {
22
25
  return ast.projection.some((item) => item.expr.kind === 'aggregate');
23
26
  }
24
27
 
28
+ function primaryTableFromAst(ast: SelectAst): string {
29
+ switch (ast.from.kind) {
30
+ case 'table-source':
31
+ return ast.from.name;
32
+ case 'derived-table-source':
33
+ return ast.from.alias;
34
+ // v8 ignore next 4
35
+ default:
36
+ throw new Error(
37
+ `Unsupported source kind: ${(ast.from satisfies never as { kind: string }).kind}`,
38
+ );
39
+ }
40
+ }
41
+
25
42
  function estimateRowsFromAst(
26
43
  ast: SelectAst,
27
44
  tableRows: Record<string, number>,
28
45
  defaultTableRows: number,
29
- refs: { tables?: readonly string[] } | undefined,
30
46
  hasAggregateWithoutGroup: boolean,
31
- ): number | null {
47
+ ): number {
32
48
  if (hasAggregateWithoutGroup) {
33
49
  return 1;
34
50
  }
35
51
 
36
- const table = refs?.tables?.[0];
37
- if (!table) {
38
- return null;
39
- }
40
-
41
- const tableEstimate = tableRows[table] ?? defaultTableRows;
52
+ const tableEstimate = tableRows[primaryTableFromAst(ast)] ?? defaultTableRows;
42
53
 
43
54
  if (ast.limit !== undefined) {
44
55
  return Math.min(ast.limit, tableEstimate);
@@ -47,30 +58,6 @@ function estimateRowsFromAst(
47
58
  return tableEstimate;
48
59
  }
49
60
 
50
- function estimateRowsFromHeuristics(
51
- plan: ExecutionPlan,
52
- tableRows: Record<string, number>,
53
- defaultTableRows: number,
54
- ): number | null {
55
- const table = plan.meta.refs?.tables?.[0];
56
- if (!table) {
57
- return null;
58
- }
59
-
60
- const tableEstimate = tableRows[table] ?? defaultTableRows;
61
-
62
- const limit = plan.meta.annotations?.['limit'];
63
- if (typeof limit === 'number') {
64
- return Math.min(limit, tableEstimate);
65
- }
66
-
67
- return tableEstimate;
68
- }
69
-
70
- function hasDetectableLimitFromHeuristics(plan: ExecutionPlan): boolean {
71
- return typeof plan.meta.annotations?.['limit'] === 'number';
72
- }
73
-
74
61
  function emitBudgetViolation(
75
62
  error: RuntimeErrorEnvelope,
76
63
  shouldBlock: boolean,
@@ -93,26 +80,21 @@ export function budgets(options?: BudgetsOptions): SqlMiddleware {
93
80
  const maxLatencyMs = options?.maxLatencyMs ?? 1_000;
94
81
  const rowSeverity = options?.severities?.rowCount ?? 'error';
95
82
 
96
- const observedRowsByPlan = new WeakMap<ExecutionPlan, { count: number }>();
83
+ const observedRowsByPlan = new WeakMap<SqlExecutionPlan, { count: number }>();
97
84
 
98
85
  return Object.freeze({
99
86
  name: 'budgets',
100
87
  familyId: 'sql' as const,
101
88
 
102
- async beforeExecute(plan: ExecutionPlan, ctx: SqlMiddlewareContext) {
89
+ async beforeExecute(plan: SqlExecutionPlan, ctx: SqlMiddlewareContext) {
103
90
  observedRowsByPlan.set(plan, { count: 0 });
104
91
 
105
- if (isQueryAst(plan.ast)) {
106
- if (plan.ast.kind === 'select') {
107
- return evaluateSelectAst(plan, plan.ast, ctx);
108
- }
109
- return;
92
+ if (isQueryAst(plan.ast) && plan.ast.kind === 'select') {
93
+ return evaluateSelectAst(plan.ast, ctx);
110
94
  }
111
-
112
- return evaluateWithHeuristics(plan, ctx);
113
95
  },
114
96
 
115
- async onRow(_row: Record<string, unknown>, plan: ExecutionPlan, _ctx: SqlMiddlewareContext) {
97
+ async onRow(_row: Record<string, unknown>, plan: SqlExecutionPlan, _ctx: SqlMiddlewareContext) {
116
98
  const state = observedRowsByPlan.get(plan);
117
99
  if (!state) return;
118
100
  state.count += 1;
@@ -126,7 +108,7 @@ export function budgets(options?: BudgetsOptions): SqlMiddleware {
126
108
  },
127
109
 
128
110
  async afterExecute(
129
- _plan: ExecutionPlan,
111
+ _plan: SqlExecutionPlan,
130
112
  result: AfterExecuteResult,
131
113
  ctx: SqlMiddlewareContext,
132
114
  ) {
@@ -145,44 +127,26 @@ export function budgets(options?: BudgetsOptions): SqlMiddleware {
145
127
  },
146
128
  });
147
129
 
148
- function evaluateSelectAst(plan: ExecutionPlan, ast: SelectAst, ctx: SqlMiddlewareContext) {
130
+ function evaluateSelectAst(ast: SelectAst, ctx: SqlMiddlewareContext) {
149
131
  const hasAggNoGroup = hasAggregateWithoutGroupBy(ast);
150
- const estimated = estimateRowsFromAst(
151
- ast,
152
- tableRows,
153
- defaultTableRows,
154
- plan.meta.refs,
155
- hasAggNoGroup,
156
- );
132
+ const estimated = estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggNoGroup);
157
133
  const isUnbounded = ast.limit === undefined && !hasAggNoGroup;
158
134
  const shouldBlock = rowSeverity === 'error' || ctx.mode === 'strict';
159
135
 
160
136
  if (isUnbounded) {
161
- if (estimated !== null && estimated >= maxRows) {
162
- emitBudgetViolation(
163
- runtimeError('BUDGET.ROWS_EXCEEDED', 'Unbounded SELECT query exceeds budget', {
164
- source: 'ast',
165
- estimatedRows: estimated,
166
- maxRows,
167
- }),
168
- shouldBlock,
169
- ctx,
170
- );
171
- return;
172
- }
173
-
137
+ const details =
138
+ estimated >= maxRows
139
+ ? { source: 'ast', estimatedRows: estimated, maxRows }
140
+ : { source: 'ast', maxRows };
174
141
  emitBudgetViolation(
175
- runtimeError('BUDGET.ROWS_EXCEEDED', 'Unbounded SELECT query exceeds budget', {
176
- source: 'ast',
177
- maxRows,
178
- }),
142
+ runtimeError('BUDGET.ROWS_EXCEEDED', 'Unbounded SELECT query exceeds budget', details),
179
143
  shouldBlock,
180
144
  ctx,
181
145
  );
182
146
  return;
183
147
  }
184
148
 
185
- if (estimated !== null && estimated > maxRows) {
149
+ if (estimated > maxRows) {
186
150
  emitBudgetViolation(
187
151
  runtimeError('BUDGET.ROWS_EXCEEDED', 'Estimated row count exceeds budget', {
188
152
  source: 'ast',
@@ -194,52 +158,4 @@ export function budgets(options?: BudgetsOptions): SqlMiddleware {
194
158
  );
195
159
  }
196
160
  }
197
-
198
- async function evaluateWithHeuristics(plan: ExecutionPlan, ctx: SqlMiddlewareContext) {
199
- const estimated = estimateRowsFromHeuristics(plan, tableRows, defaultTableRows);
200
- const isUnbounded = !hasDetectableLimitFromHeuristics(plan);
201
- const sqlUpper = plan.sql.trimStart().toUpperCase();
202
- const isSelect = sqlUpper.startsWith('SELECT');
203
- const shouldBlock = rowSeverity === 'error' || ctx.mode === 'strict';
204
-
205
- if (isSelect && isUnbounded) {
206
- if (estimated !== null && estimated >= maxRows) {
207
- emitBudgetViolation(
208
- runtimeError('BUDGET.ROWS_EXCEEDED', 'Unbounded SELECT query exceeds budget', {
209
- source: 'heuristic',
210
- estimatedRows: estimated,
211
- maxRows,
212
- }),
213
- shouldBlock,
214
- ctx,
215
- );
216
- return;
217
- }
218
-
219
- emitBudgetViolation(
220
- runtimeError('BUDGET.ROWS_EXCEEDED', 'Unbounded SELECT query exceeds budget', {
221
- source: 'heuristic',
222
- maxRows,
223
- }),
224
- shouldBlock,
225
- ctx,
226
- );
227
- return;
228
- }
229
-
230
- if (estimated !== null) {
231
- if (estimated > maxRows) {
232
- emitBudgetViolation(
233
- runtimeError('BUDGET.ROWS_EXCEEDED', 'Estimated row count exceeds budget', {
234
- source: 'heuristic',
235
- estimatedRows: estimated,
236
- maxRows,
237
- }),
238
- shouldBlock,
239
- ctx,
240
- );
241
- }
242
- return;
243
- }
244
- }
245
161
  }
@@ -1,12 +1,12 @@
1
- import type { ExecutionPlan } from '@prisma-next/contract/types';
2
1
  import { runtimeError } from '@prisma-next/framework-components/runtime';
3
- import { evaluateRawGuardrails } from '@prisma-next/runtime-executor';
4
2
  import {
5
3
  type AnyFromSource,
6
4
  type AnyQueryAst,
7
5
  isQueryAst,
8
6
  } from '@prisma-next/sql-relational-core/ast';
7
+ import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
9
8
  import { ifDefined } from '@prisma-next/utils/defined';
9
+ import { evaluateRawGuardrails } from '../guardrails/raw';
10
10
  import type { SqlMiddleware, SqlMiddlewareContext } from './sql-middleware';
11
11
 
12
12
  export interface LintsOptions {
@@ -145,7 +145,7 @@ export function lints(options?: LintsOptions): SqlMiddleware {
145
145
  name: 'lints',
146
146
  familyId: 'sql' as const,
147
147
 
148
- async beforeExecute(plan: ExecutionPlan, ctx: SqlMiddlewareContext) {
148
+ async beforeExecute(plan: SqlExecutionPlan, ctx: SqlMiddlewareContext) {
149
149
  if (isQueryAst(plan.ast)) {
150
150
  const findings = evaluateAstLints(plan.ast);
151
151
 
@@ -1,4 +1,4 @@
1
- import type { Contract, ExecutionPlan, PlanMeta } from '@prisma-next/contract/types';
1
+ import type { Contract, PlanMeta } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  AfterExecuteResult,
4
4
  RuntimeMiddleware,
@@ -6,6 +6,7 @@ import type {
6
6
  } from '@prisma-next/framework-components/runtime';
7
7
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
8
8
  import type { AnyQueryAst } from '@prisma-next/sql-relational-core/ast';
9
+ import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
9
10
 
10
11
  export interface SqlMiddlewareContext extends RuntimeMiddlewareContext {
11
12
  readonly contract: Contract<SqlStorage>;
@@ -20,7 +21,7 @@ export interface DraftPlan {
20
21
  readonly meta: PlanMeta;
21
22
  }
22
23
 
23
- export interface SqlMiddleware extends RuntimeMiddleware {
24
+ export interface SqlMiddleware extends RuntimeMiddleware<SqlExecutionPlan> {
24
25
  readonly familyId?: 'sql';
25
26
  /**
26
27
  * Rewrite the query AST before it is lowered to SQL. Middlewares run in
@@ -41,14 +42,14 @@ export interface SqlMiddleware extends RuntimeMiddleware {
41
42
  * See `docs/architecture docs/subsystems/4. Runtime & Middleware Framework.md`.
42
43
  */
43
44
  beforeCompile?(draft: DraftPlan, ctx: SqlMiddlewareContext): Promise<DraftPlan | undefined>;
44
- beforeExecute?(plan: ExecutionPlan, ctx: SqlMiddlewareContext): Promise<void>;
45
+ beforeExecute?(plan: SqlExecutionPlan, ctx: SqlMiddlewareContext): Promise<void>;
45
46
  onRow?(
46
47
  row: Record<string, unknown>,
47
- plan: ExecutionPlan,
48
+ plan: SqlExecutionPlan,
48
49
  ctx: SqlMiddlewareContext,
49
50
  ): Promise<void>;
50
51
  afterExecute?(
51
- plan: ExecutionPlan,
52
+ plan: SqlExecutionPlan,
52
53
  result: AfterExecuteResult,
53
54
  ctx: SqlMiddlewareContext,
54
55
  ): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import type { ExecutionPlan } from '@prisma-next/framework-components/runtime';
3
+ import type { MarkerStatement } from '@prisma-next/sql-relational-core/ast';
4
+
5
+ /**
6
+ * Reader of the SQL contract marker. SQL runtimes verify the database's
7
+ * `prisma_contract.marker` row against the runtime's contract by issuing
8
+ * this statement before executing user queries (when `verify` is enabled).
9
+ * Each adapter is responsible for any target-specific row decoding before
10
+ * delegating to the shared row schema.
11
+ */
12
+ export interface MarkerReader {
13
+ readMarkerStatement(): MarkerStatement;
14
+ parseMarkerRow(row: unknown): ContractMarkerRecord;
15
+ }
16
+
17
+ /**
18
+ * SQL family adapter SPI consumed by `SqlRuntime`. Encapsulates the
19
+ * runtime contract, marker reader, and plan validation logic so the
20
+ * runtime can be unit-tested without a concrete SQL adapter profile.
21
+ *
22
+ * Implemented by `SqlFamilyAdapter` for production and by mock classes
23
+ * in tests.
24
+ */
25
+ export interface RuntimeFamilyAdapter<TContract = unknown> {
26
+ readonly contract: TContract;
27
+ readonly markerReader: MarkerReader;
28
+ validatePlan(plan: ExecutionPlan, contract: TContract): void;
29
+ }
30
+
31
+ export interface RuntimeVerifyOptions {
32
+ readonly mode: 'onFirstUse' | 'startup' | 'always';
33
+ readonly requireMarker: boolean;
34
+ }
35
+
36
+ export type TelemetryOutcome = 'success' | 'runtime-error';
37
+
38
+ export interface RuntimeTelemetryEvent {
39
+ readonly lane: string;
40
+ readonly target: string;
41
+ readonly fingerprint: string;
42
+ readonly outcome: TelemetryOutcome;
43
+ readonly durationMs?: number;
44
+ }