@prisma-next/framework-components 0.5.0-dev.9 → 0.5.1

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 (95) hide show
  1. package/README.md +51 -4
  2. package/dist/authoring.d.mts +2 -2
  3. package/dist/authoring.mjs +2 -122
  4. package/dist/codec-m_-FAyQn.d.mts +168 -0
  5. package/dist/codec-m_-FAyQn.d.mts.map +1 -0
  6. package/dist/codec.d.mts +48 -2
  7. package/dist/codec.d.mts.map +1 -0
  8. package/dist/codec.mjs +67 -4
  9. package/dist/codec.mjs.map +1 -1
  10. package/dist/components.d.mts +1 -1
  11. package/dist/components.mjs +2 -3
  12. package/dist/control.d.mts +421 -78
  13. package/dist/control.d.mts.map +1 -1
  14. package/dist/control.mjs +83 -58
  15. package/dist/control.mjs.map +1 -1
  16. package/dist/emission-types-CMv_053d.d.mts +38 -0
  17. package/dist/emission-types-CMv_053d.d.mts.map +1 -0
  18. package/dist/emission.d.mts +2 -2
  19. package/dist/emission.mjs +1 -1
  20. package/dist/execution.d.mts +5 -5
  21. package/dist/execution.d.mts.map +1 -1
  22. package/dist/execution.mjs +4 -6
  23. package/dist/execution.mjs.map +1 -1
  24. package/dist/{framework-authoring-D1-JZ37B.d.mts → framework-authoring-DGIQbNPt.d.mts} +43 -12
  25. package/dist/framework-authoring-DGIQbNPt.d.mts.map +1 -0
  26. package/dist/framework-authoring-DxXcjyJX.mjs +209 -0
  27. package/dist/framework-authoring-DxXcjyJX.mjs.map +1 -0
  28. package/dist/{framework-components-DFZMi2h7.d.mts → framework-components-CHuBHXQN.d.mts} +45 -58
  29. package/dist/framework-components-CHuBHXQN.d.mts.map +1 -0
  30. package/dist/{framework-components-C8ZhSwXe.mjs → framework-components-FdqmlGUj.mjs} +3 -3
  31. package/dist/framework-components-FdqmlGUj.mjs.map +1 -0
  32. package/dist/psl-ast-Ckn_G-jv.d.mts +159 -0
  33. package/dist/psl-ast-Ckn_G-jv.d.mts.map +1 -0
  34. package/dist/psl-ast.d.mts +2 -0
  35. package/dist/psl-ast.mjs +1 -0
  36. package/dist/runtime.d.mts +273 -37
  37. package/dist/runtime.d.mts.map +1 -1
  38. package/dist/runtime.mjs +172 -30
  39. package/dist/runtime.mjs.map +1 -1
  40. package/dist/{types-import-spec-C4sc7wbb.d.mts → types-import-spec-BxI5cSQy.d.mts} +2 -2
  41. package/dist/types-import-spec-BxI5cSQy.d.mts.map +1 -0
  42. package/package.json +12 -8
  43. package/src/control/control-capabilities.ts +96 -0
  44. package/src/{control-descriptors.ts → control/control-descriptors.ts} +7 -7
  45. package/src/{control-instances.ts → control/control-instances.ts} +52 -6
  46. package/src/{control-migration-types.ts → control/control-migration-types.ts} +251 -63
  47. package/src/control/control-operation-preview.ts +23 -0
  48. package/src/{control-result-types.ts → control/control-result-types.ts} +0 -2
  49. package/src/control/control-spaces.ts +82 -0
  50. package/src/{control-stack.ts → control/control-stack.ts} +77 -111
  51. package/src/control/emission-types.ts +48 -0
  52. package/src/control/psl-ast.ts +193 -0
  53. package/src/{execution-descriptors.ts → execution/execution-descriptors.ts} +7 -7
  54. package/src/{execution-instances.ts → execution/execution-instances.ts} +1 -1
  55. package/src/{execution-requirements.ts → execution/execution-requirements.ts} +1 -1
  56. package/src/execution/race-against-abort.ts +89 -0
  57. package/src/execution/run-with-middleware.ts +153 -0
  58. package/src/{runtime-core.ts → execution/runtime-core.ts} +27 -3
  59. package/src/execution/runtime-error.ts +94 -0
  60. package/src/execution/runtime-middleware.ts +235 -0
  61. package/src/exports/authoring.ts +5 -2
  62. package/src/exports/codec.ts +27 -2
  63. package/src/exports/components.ts +2 -2
  64. package/src/exports/control.ts +41 -14
  65. package/src/exports/emission.ts +2 -2
  66. package/src/exports/execution.ts +5 -5
  67. package/src/exports/psl-ast.ts +1 -0
  68. package/src/exports/runtime.ts +18 -9
  69. package/src/shared/codec-descriptor.ts +87 -0
  70. package/src/shared/codec-types.ts +79 -0
  71. package/src/shared/codec.ts +80 -0
  72. package/src/shared/column-spec.ts +83 -0
  73. package/src/{framework-authoring.ts → shared/framework-authoring.ts} +210 -23
  74. package/src/{framework-components.ts → shared/framework-components.ts} +22 -49
  75. package/src/{mutation-default-types.ts → shared/mutation-default-types.ts} +22 -2
  76. package/dist/authoring.mjs.map +0 -1
  77. package/dist/codec-types-DQ1Agjom.d.mts +0 -58
  78. package/dist/codec-types-DQ1Agjom.d.mts.map +0 -1
  79. package/dist/emission-types-BPAALJbF.d.mts +0 -24
  80. package/dist/emission-types-BPAALJbF.d.mts.map +0 -1
  81. package/dist/framework-authoring-D1-JZ37B.d.mts.map +0 -1
  82. package/dist/framework-components-C8ZhSwXe.mjs.map +0 -1
  83. package/dist/framework-components-DFZMi2h7.d.mts.map +0 -1
  84. package/dist/types-import-spec-C4sc7wbb.d.mts.map +0 -1
  85. package/src/codec-types.ts +0 -64
  86. package/src/control-capabilities.ts +0 -34
  87. package/src/emission-types.ts +0 -28
  88. package/src/run-with-middleware.ts +0 -77
  89. package/src/runtime-error.ts +0 -55
  90. package/src/runtime-middleware.ts +0 -87
  91. /package/src/{control-schema-view.ts → control/control-schema-view.ts} +0 -0
  92. /package/src/{async-iterable-result.ts → execution/async-iterable-result.ts} +0 -0
  93. /package/src/{execution-stack.ts → execution/execution-stack.ts} +0 -0
  94. /package/src/{query-plan.ts → execution/query-plan.ts} +0 -0
  95. /package/src/{types-import-spec.ts → shared/types-import-spec.ts} +0 -0
@@ -0,0 +1,153 @@
1
+ import { AsyncIterableResult } from './async-iterable-result';
2
+ import type { ExecutionPlan } from './query-plan';
3
+ import { checkAborted, raceAgainstAbort } from './race-against-abort';
4
+ import type {
5
+ ParamRefMutator,
6
+ RuntimeMiddleware,
7
+ RuntimeMiddlewareContext,
8
+ } from './runtime-middleware';
9
+
10
+ /**
11
+ * Drives a single execution of `runDriver()` through the middleware lifecycle.
12
+ *
13
+ * Lifecycle, in order:
14
+ * 1. For each middleware in registration order: `intercept(exec, ctx)`. The
15
+ * first non-`undefined` result wins; subsequent middleware's `intercept`
16
+ * does not fire. On a hit, the runtime emits a `middleware.intercept`
17
+ * debug event naming the winning middleware, switches the row source to
18
+ * the intercepted rows, and proceeds with `source: 'middleware'`. On
19
+ * all-passthrough (every `intercept` returns `undefined` or is omitted),
20
+ * `source: 'driver'` is used and the row source is `runDriver()`.
21
+ * 2. If `source === 'driver'`: for each middleware in registration order,
22
+ * `beforeExecute(exec, ctx)`. Skipped on the intercepted hit path —
23
+ * `beforeExecute` semantically means "about to hit the driver".
24
+ * 3. Iterate the row source. On the driver path, for each row, for each
25
+ * middleware in registration order: `onRow(row, exec, ctx)`; then yield
26
+ * the row. On the intercepted hit path, `onRow` is skipped — intercepted
27
+ * rows did not originate from a driver row stream — but rows are still
28
+ * yielded to the consumer in order.
29
+ * 4. On successful completion: for each middleware in registration order:
30
+ * `afterExecute(exec, { rowCount, latencyMs, completed: true, source },
31
+ * ctx)`.
32
+ * 5. On any error thrown during steps 1–3: for each middleware in
33
+ * registration order: `afterExecute(exec, { rowCount, latencyMs,
34
+ * completed: false, source }, ctx)`. Errors thrown by `afterExecute`
35
+ * during the error path are swallowed so they do not mask the original
36
+ * error. The original error is then rethrown.
37
+ *
38
+ * The `source` field on `AfterExecuteResult` lets observers (telemetry,
39
+ * lints, budgets) distinguish driver-served from middleware-served
40
+ * executions without needing their own out-of-band signal.
41
+ *
42
+ * This helper is the single canonical implementation of the middleware
43
+ * orchestration loop; family runtimes should not reimplement it.
44
+ */
45
+ export function runWithMiddleware<
46
+ TExec extends ExecutionPlan,
47
+ Row,
48
+ TMutator extends ParamRefMutator = ParamRefMutator,
49
+ >(
50
+ exec: TExec,
51
+ middleware: ReadonlyArray<RuntimeMiddleware<TExec, TMutator>>,
52
+ ctx: RuntimeMiddlewareContext,
53
+ runDriver: () => AsyncIterable<Row>,
54
+ paramsMutator?: TMutator,
55
+ ): AsyncIterableResult<Row> {
56
+ const iterator = async function* (): AsyncGenerator<Row, void, unknown> {
57
+ const startedAt = Date.now();
58
+ let rowCount = 0;
59
+ let completed = false;
60
+ let source: 'driver' | 'middleware' = 'driver';
61
+ // Deferred so a winning interceptor can skip `runDriver()` entirely.
62
+ // For factories that lazily produce async generators this is a no-op,
63
+ // but factories that do eager work (e.g. acquiring a connection,
64
+ // sending a query) must not run on the intercepted hit path.
65
+ let rowSource: AsyncIterable<Row> | Iterable<Row> | undefined;
66
+
67
+ try {
68
+ for (const mw of middleware) {
69
+ if (!mw.intercept) {
70
+ continue;
71
+ }
72
+ // Mark the lifecycle as middleware-driven *before* awaiting the
73
+ // hook. If `intercept` throws, the catch block reports the failure
74
+ // as `source: 'middleware'` — the failure originated in the
75
+ // intercept chain, not in the driver. If `intercept` returns
76
+ // `undefined` (passthrough), we revert to `'driver'` and continue.
77
+ source = 'middleware';
78
+ const result = await mw.intercept(exec, ctx);
79
+ if (result === undefined) {
80
+ source = 'driver';
81
+ continue;
82
+ }
83
+ ctx.log.debug?.({ event: 'middleware.intercept', middleware: mw.name });
84
+ // The intercepted rows are typed as `Record<string, unknown>` at
85
+ // the SPI level; the consumer's `Row` type parameter is enforced by
86
+ // the caller (via the plan's phantom `_row`) the same way driver
87
+ // rows are. Cast through unknown to bridge the SPI shape to the
88
+ // caller-supplied Row.
89
+ rowSource = result.rows as unknown as AsyncIterable<Row> | Iterable<Row>;
90
+ break;
91
+ }
92
+
93
+ if (source === 'driver') {
94
+ for (const mw of middleware) {
95
+ if (mw.beforeExecute) {
96
+ checkAborted(ctx, 'beforeExecute');
97
+ // The framework only forwards the mutator the caller supplied; a
98
+ // pass-through `undefined` for non-mutating families is safe — the
99
+ // base `RuntimeMiddleware` declares the third parameter, and
100
+ // existing `(plan, ctx)` bodies that ignore it stay unchanged.
101
+ // The cast below is the single point at which the framework's
102
+ // generic mutator slot meets the (possibly absent) caller value;
103
+ // `runWithMiddleware` cannot synthesize a TMutator instance.
104
+ const work = mw.beforeExecute(exec, ctx, paramsMutator as TMutator);
105
+ if (work !== undefined) {
106
+ await raceAgainstAbort(Promise.resolve(work), ctx.signal, 'beforeExecute');
107
+ }
108
+ }
109
+ }
110
+ rowSource = runDriver();
111
+ }
112
+
113
+ // `rowSource` is always assigned by this point: either the intercepted
114
+ // rows (on a hit) or `runDriver()` (on the driver path).
115
+ for await (const row of rowSource as AsyncIterable<Row> | Iterable<Row>) {
116
+ if (source === 'driver') {
117
+ for (const mw of middleware) {
118
+ if (mw.onRow) {
119
+ await mw.onRow(row as Record<string, unknown>, exec, ctx);
120
+ }
121
+ }
122
+ }
123
+ rowCount++;
124
+ yield row;
125
+ }
126
+
127
+ completed = true;
128
+ } catch (error) {
129
+ const latencyMs = Date.now() - startedAt;
130
+ for (const mw of middleware) {
131
+ if (mw.afterExecute) {
132
+ try {
133
+ await mw.afterExecute(exec, { rowCount, latencyMs, completed, source }, ctx);
134
+ } catch {
135
+ // Swallow afterExecute errors during the error path so they do not
136
+ // mask the original error.
137
+ }
138
+ }
139
+ }
140
+
141
+ throw error;
142
+ }
143
+
144
+ const latencyMs = Date.now() - startedAt;
145
+ for (const mw of middleware) {
146
+ if (mw.afterExecute) {
147
+ await mw.afterExecute(exec, { rowCount, latencyMs, completed, source }, ctx);
148
+ }
149
+ }
150
+ };
151
+
152
+ return new AsyncIterableResult(iterator());
153
+ }
@@ -1,7 +1,10 @@
1
+ import type { CodecCallContext } from '../shared/codec-types';
1
2
  import { AsyncIterableResult } from './async-iterable-result';
2
3
  import type { ExecutionPlan, QueryPlan } from './query-plan';
4
+ import { checkAborted } from './race-against-abort';
3
5
  import { runWithMiddleware } from './run-with-middleware';
4
6
  import type {
7
+ RuntimeExecuteOptions,
5
8
  RuntimeExecutor,
6
9
  RuntimeMiddleware,
7
10
  RuntimeMiddlewareContext,
@@ -71,8 +74,16 @@ export abstract class RuntimeCore<
71
74
  * Lower a pre-lowering `TPlan` into the family's executable `TExec`.
72
75
  * Family-specific: SQL produces `{ sql, params, ast?, ... }`; Mongo
73
76
  * produces `{ command, ... }`.
77
+ *
78
+ * `ctx` carries per-query cancellation (and any future fields on
79
+ * `CodecCallContext`); concrete subclasses forward it to the
80
+ * encode-side codec dispatch site (e.g. SQL's `encodeParams` in m2,
81
+ * Mongo's `resolveValue` in m3). The runtime allocates one ctx per
82
+ * `execute()` call and threads the same reference everywhere; the
83
+ * `signal` field inside may be `undefined`, but the ctx object itself
84
+ * is always present.
74
85
  */
75
- protected abstract lower(plan: TPlan): TExec | Promise<TExec>;
86
+ protected abstract lower(plan: TPlan, ctx: CodecCallContext): TExec | Promise<TExec>;
76
87
 
77
88
  /**
78
89
  * Drive the underlying transport for a lowered `TExec`. Yields raw rows
@@ -88,12 +99,25 @@ export abstract class RuntimeCore<
88
99
 
89
100
  abstract close(): Promise<void>;
90
101
 
91
- execute<Row>(plan: TPlan & { readonly _row?: Row }): AsyncIterableResult<Row> {
102
+ execute<Row>(
103
+ plan: TPlan & { readonly _row?: Row },
104
+ options?: RuntimeExecuteOptions,
105
+ ): AsyncIterableResult<Row> {
92
106
  const self = this;
107
+ const signal = options?.signal;
108
+ // One ctx per execute() call. The ctx object is always allocated; the
109
+ // `signal` field is only included when a signal was supplied (required
110
+ // under exactOptionalPropertyTypes — `{ signal: undefined }` would not
111
+ // satisfy `signal?: AbortSignal`).
112
+ const codecCtx: CodecCallContext = signal === undefined ? {} : { signal };
93
113
 
94
114
  async function* generator(): AsyncGenerator<Row, void, unknown> {
115
+ // Pre-check the signal at entry so an already-aborted caller observes
116
+ // RUNTIME.ABORTED on the first `next()` without any work being done.
117
+ checkAborted(codecCtx, 'stream');
118
+
95
119
  const compiled = await self.runBeforeCompile(plan);
96
- const exec = await self.lower(compiled);
120
+ const exec = await self.lower(compiled, codecCtx);
97
121
  // The driver yields raw `Record<string, unknown>`; we cast to `Row` here.
98
122
  // The Row contract is enforced by the caller via `plan._row`.
99
123
  yield* runWithMiddleware<TExec, Row>(
@@ -0,0 +1,94 @@
1
+ export interface RuntimeErrorEnvelope extends Error {
2
+ readonly code: string;
3
+ readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';
4
+ readonly severity: 'error';
5
+ readonly details?: Record<string, unknown>;
6
+ }
7
+
8
+ /**
9
+ * Stable code emitted by the runtime when an in-flight `execute()`
10
+ * is cancelled via the per-query `AbortSignal`. The envelope's
11
+ * `details.phase` distinguishes where the abort was observed:
12
+ *
13
+ * - `'encode'` — abort fired during `encodeParams` (SQL) or
14
+ * `resolveValue` (Mongo).
15
+ * - `'decode'` — abort fired during `decodeRow` / `decodeField`.
16
+ * - `'stream'` — abort fired between rows or before any codec call
17
+ * (already-aborted at entry).
18
+ * - `'beforeExecute'` / `'afterExecute'` / `'onRow'` — abort fired
19
+ * on entry to or during the corresponding middleware phase
20
+ * (cooperative cancellation per the param-transform seam).
21
+ */
22
+ export const RUNTIME_ABORTED = 'RUNTIME.ABORTED' as const;
23
+
24
+ /** Discriminator placed in `details.phase` of a `RUNTIME.ABORTED` envelope. */
25
+ export type RuntimeAbortedPhase =
26
+ | 'encode'
27
+ | 'decode'
28
+ | 'stream'
29
+ | 'beforeExecute'
30
+ | 'afterExecute'
31
+ | 'onRow';
32
+
33
+ /**
34
+ * Type guard for the runtime-error envelope produced by `runtimeError`.
35
+ *
36
+ * Prefer this over duck-typing on `error.code` directly so consumers stay
37
+ * insulated from the envelope's internal shape.
38
+ */
39
+ export function isRuntimeError(error: unknown): error is RuntimeErrorEnvelope {
40
+ return (
41
+ error instanceof Error &&
42
+ 'code' in error &&
43
+ typeof (error as { code?: unknown }).code === 'string' &&
44
+ 'category' in error &&
45
+ 'severity' in error
46
+ );
47
+ }
48
+
49
+ export function runtimeError(
50
+ code: string,
51
+ message: string,
52
+ details?: Record<string, unknown>,
53
+ ): RuntimeErrorEnvelope {
54
+ const error = new Error(message) as RuntimeErrorEnvelope;
55
+ Object.defineProperty(error, 'name', {
56
+ value: 'RuntimeError',
57
+ configurable: true,
58
+ });
59
+
60
+ return Object.assign(error, {
61
+ code,
62
+ category: resolveCategory(code),
63
+ severity: 'error' as const,
64
+ message,
65
+ details,
66
+ });
67
+ }
68
+
69
+ function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
70
+ const prefix = code.split('.')[0] ?? 'RUNTIME';
71
+ switch (prefix) {
72
+ case 'PLAN':
73
+ case 'CONTRACT':
74
+ case 'LINT':
75
+ case 'BUDGET':
76
+ return prefix;
77
+ default:
78
+ return 'RUNTIME';
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Construct a `RUNTIME.ABORTED` envelope. Phase distinguishes where the
84
+ * abort was observed — codec call sites (`encode` / `decode` / `stream`)
85
+ * or middleware seams (`beforeExecute` / `afterExecute` / `onRow`), as
86
+ * enumerated on {@link RuntimeAbortedPhase}. Cause carries
87
+ * `signal.reason` verbatim from the platform — native abort produces a
88
+ * `DOMException`, explicit `controller.abort(reason)` produces whatever
89
+ * the caller passed. No synthesis happens here.
90
+ */
91
+ export function runtimeAborted(phase: RuntimeAbortedPhase, cause?: unknown): RuntimeErrorEnvelope {
92
+ const envelope = runtimeError(RUNTIME_ABORTED, `Operation aborted during ${phase}`, { phase });
93
+ return Object.assign(envelope, { cause });
94
+ }
@@ -0,0 +1,235 @@
1
+ import type { AsyncIterableResult } from './async-iterable-result';
2
+ import type { ExecutionPlan, QueryPlan } from './query-plan';
3
+ import { runtimeError } from './runtime-error';
4
+
5
+ export interface RuntimeLog {
6
+ info(event: unknown): void;
7
+ warn(event: unknown): void;
8
+ error(event: unknown): void;
9
+ debug?(event: unknown): void;
10
+ }
11
+
12
+ /**
13
+ * Per-execute context threaded through every middleware phase
14
+ * (`beforeExecute`, `onRow`, `afterExecute`). Allocated once per
15
+ * `runtime.execute()` call and shared by reference across all
16
+ * middleware in the chain.
17
+ *
18
+ * - `signal` carries the per-query `AbortSignal` -- the same
19
+ * reference that `runtime.execute(plan, { signal })` was invoked
20
+ * with, and the same reference threaded into the per-call
21
+ * `CodecCallContext` (ADR 207). Middleware that wraps a
22
+ * network-backed SDK forwards `ctx.signal` into that SDK to
23
+ * propagate caller cancellation; pure-CPU middleware ignores it.
24
+ *
25
+ * Symmetric plumbing across all middleware phases (rather than only
26
+ * `beforeExecute`) is a deliberate choice: a middleware that wraps a
27
+ * downstream observability hook or post-processor in `afterExecute` /
28
+ * `onRow` needs the same cancellation reach as its `beforeExecute`
29
+ * counterpart.
30
+ */
31
+ export interface RuntimeMiddlewareContext {
32
+ readonly contract: unknown;
33
+ readonly mode: 'strict' | 'permissive';
34
+ readonly now: () => number;
35
+ readonly log: RuntimeLog;
36
+ /**
37
+ * Returns a stable string identifying the (storage, statement, params)
38
+ * tuple of an execution. Two semantically equivalent executions return
39
+ * the same string. Used by middleware that need per-execution identity
40
+ * (caching, request coalescing).
41
+ *
42
+ * The family runtime owns the implementation:
43
+ * - SQL: `meta.storageHash` + `exec.sql` + `canonicalStringify(exec.params)`
44
+ * - Mongo: `meta.storageHash` + `canonicalStringify({ ...exec.command })`
45
+ *
46
+ * The method is `async` because the underlying digest helper
47
+ * (`hashContent`) uses the WebCrypto API, whose `crypto.subtle.digest`
48
+ * primitive is asynchronous by design.
49
+ *
50
+ * The returned string is intended to be consumed directly as a `Map` key
51
+ * — it is not (and should not be) further hashed by callers.
52
+ */
53
+ contentHash(exec: ExecutionPlan): Promise<string>;
54
+ /**
55
+ * Per-execute cancellation signal threaded through every middleware
56
+ * phase. Middleware that wraps async work or downstream cancellable
57
+ * primitives should observe this and abort early when the consumer
58
+ * cancels.
59
+ */
60
+ readonly signal?: AbortSignal;
61
+ }
62
+
63
+ export interface AfterExecuteResult {
64
+ readonly rowCount: number;
65
+ readonly latencyMs: number;
66
+ readonly completed: boolean;
67
+ /**
68
+ * Indicates where the rows observed during this execution came from.
69
+ *
70
+ * - `'driver'` — the default. Rows came from the underlying driver via
71
+ * `runDriver` / `runWithMiddleware`'s normal path.
72
+ * - `'middleware'` — a `RuntimeMiddleware.intercept` hook short-circuited
73
+ * execution and supplied the rows directly. The driver was not invoked.
74
+ *
75
+ * Observers (telemetry, lints, budgets) that need to distinguish between
76
+ * driver-served and middleware-served executions read this field.
77
+ * Observers that don't care can ignore it.
78
+ */
79
+ readonly source: 'driver' | 'middleware';
80
+ }
81
+
82
+ /**
83
+ * Result of a successful `RuntimeMiddleware.intercept` hook.
84
+ *
85
+ * Carries the rows that the middleware wishes to return in place of
86
+ * invoking the driver. The runtime iterates `rows` in order and yields
87
+ * each row to the consumer; `beforeExecute`, `runDriver`, and `onRow` are
88
+ * all skipped on the hit path. `afterExecute` still fires with
89
+ * `source: 'middleware'`.
90
+ *
91
+ * `rows` accepts both `Iterable` (arrays, sync generators) and
92
+ * `AsyncIterable` (async generators). `for await` natively handles both
93
+ * via `Symbol.asyncIterator` / `Symbol.iterator` fallback, so the
94
+ * orchestrator does not need to branch on the variant. Cached arrays in
95
+ * the cache middleware are the common case; streaming variants support
96
+ * future use cases like mock layers replaying recordings.
97
+ *
98
+ * Row shape is `Record<string, unknown>` — the same untyped shape
99
+ * `onRow` receives. The SQL runtime decodes intercepted rows through its
100
+ * normal codec pass, so interceptors cache and return raw (undecoded)
101
+ * rows.
102
+ */
103
+ export interface InterceptResult {
104
+ readonly rows: AsyncIterable<Record<string, unknown>> | Iterable<Record<string, unknown>>;
105
+ }
106
+
107
+ /**
108
+ * Marker interface for family-specific param-ref mutators threaded into
109
+ * `beforeExecute` as the third argument. The framework treats the mutator
110
+ * opaquely — it allocates and forwards the family's mutator instance so
111
+ * `runWithMiddleware` can stay family-agnostic. SQL extends this with
112
+ * `SqlParamRefMutator` (over `ParamRef`); Mongo extends with
113
+ * `MongoParamRefMutator` (over `MongoParamRef`).
114
+ *
115
+ * Extension authors target the family-specific mutator type, not this
116
+ * marker.
117
+ */
118
+ declare const PARAM_REF_MUTATOR_BRAND: unique symbol;
119
+ export type ParamRefMutator = { readonly [PARAM_REF_MUTATOR_BRAND]?: never };
120
+
121
+ /**
122
+ * Family-agnostic middleware SPI parameterized over the plan marker.
123
+ *
124
+ * `TPlan` defaults to the framework `QueryPlan` marker so a generic
125
+ * middleware (e.g. cross-family telemetry) can be authored without
126
+ * naming a family. Family-specific middleware (`SqlMiddleware`,
127
+ * `MongoMiddleware`) narrow `TPlan` to their concrete plan type.
128
+ *
129
+ * `TMutator` is the family-specific {@link ParamRefMutator} the runtime
130
+ * threads into `beforeExecute(plan, ctx, params)` as a third argument.
131
+ * Existing `(plan)` / `(plan, ctx)` middleware bodies continue to compile
132
+ * — TypeScript permits assigning a function with fewer parameters to a
133
+ * function-typed slot that declares more. The third arg is additive.
134
+ */
135
+ export interface RuntimeMiddleware<
136
+ TPlan extends QueryPlan = QueryPlan,
137
+ TMutator extends ParamRefMutator = ParamRefMutator,
138
+ > {
139
+ readonly name: string;
140
+ readonly familyId?: string;
141
+ readonly targetId?: string;
142
+ /**
143
+ * Optional short-circuit hook. Runs inside `runWithMiddleware`, after
144
+ * the orchestrator receives the lowered plan and before any
145
+ * `beforeExecute` hook fires. Middleware run in registration order; the
146
+ * first to return a non-`undefined` `InterceptResult` wins, and
147
+ * subsequent middleware's `intercept` does not fire.
148
+ *
149
+ * On a hit, `beforeExecute`, `runDriver`, and `onRow` are all skipped.
150
+ * `afterExecute` still fires with `source: 'middleware'`.
151
+ *
152
+ * Returning `undefined` (or omitting the hook entirely) signals
153
+ * passthrough — execution proceeds through the normal driver path.
154
+ *
155
+ * Errors thrown inside `intercept` are rethrown by `runWithMiddleware`
156
+ * as the original `Error` — no envelope is guaranteed at this layer.
157
+ * Before rethrowing, `afterExecute` fires with `completed: false` and
158
+ * `source: 'middleware'`. Errors thrown by `afterExecute` during the
159
+ * error path remain swallowed (existing semantics, unchanged).
160
+ *
161
+ * Used by middleware that need to short-circuit execution and supply
162
+ * rows directly: caching, mocks, rate limiting, circuit breaking.
163
+ */
164
+ intercept?(plan: TPlan, ctx: RuntimeMiddlewareContext): Promise<InterceptResult | undefined>;
165
+ beforeExecute?(
166
+ plan: TPlan,
167
+ ctx: RuntimeMiddlewareContext,
168
+ params?: TMutator,
169
+ ): void | Promise<void>;
170
+ onRow?(row: Record<string, unknown>, plan: TPlan, ctx: RuntimeMiddlewareContext): Promise<void>;
171
+ afterExecute?(
172
+ plan: TPlan,
173
+ result: AfterExecuteResult,
174
+ ctx: RuntimeMiddlewareContext,
175
+ ): Promise<void>;
176
+ }
177
+
178
+ /**
179
+ * Optional per-`execute` options accepted by every family runtime.
180
+ *
181
+ * `signal` is the per-query cancellation signal. The runtime threads the
182
+ * signal through to every codec call for the query and uses it to short-
183
+ * circuit the row stream with `RUNTIME.ABORTED` when the caller aborts.
184
+ * Omitting the option (or passing `undefined`) preserves today's behavior
185
+ * bit-for-bit.
186
+ */
187
+ export interface RuntimeExecuteOptions {
188
+ readonly signal?: AbortSignal;
189
+ }
190
+
191
+ /**
192
+ * Cross-family SPI for any runtime that can execute plans and be shut down.
193
+ * Each family runtime (SQL, Mongo) satisfies this interface — SQL nominally,
194
+ * Mongo structurally (due to its phantom Row parameter using a unique symbol).
195
+ *
196
+ * The `_row` intersection on `execute` connects the `Row` type parameter to the
197
+ * plan, mirroring how `QueryPlan<Row>` carries a phantom `_row?: Row`.
198
+ */
199
+ export interface RuntimeExecutor<TPlan extends QueryPlan> {
200
+ execute<Row>(
201
+ plan: TPlan & { readonly _row?: Row },
202
+ options?: RuntimeExecuteOptions,
203
+ ): AsyncIterableResult<Row>;
204
+ close(): Promise<void>;
205
+ }
206
+
207
+ export function checkMiddlewareCompatibility(
208
+ middleware: RuntimeMiddleware,
209
+ runtimeFamilyId: string,
210
+ runtimeTargetId: string,
211
+ ): void {
212
+ if (middleware.targetId !== undefined && middleware.familyId === undefined) {
213
+ throw runtimeError(
214
+ 'RUNTIME.MIDDLEWARE_INCOMPATIBLE',
215
+ `Middleware '${middleware.name}' specifies targetId '${middleware.targetId}' without familyId`,
216
+ { middleware: middleware.name, targetId: middleware.targetId },
217
+ );
218
+ }
219
+
220
+ if (middleware.familyId !== undefined && middleware.familyId !== runtimeFamilyId) {
221
+ throw runtimeError(
222
+ 'RUNTIME.MIDDLEWARE_FAMILY_MISMATCH',
223
+ `Middleware '${middleware.name}' requires family '${middleware.familyId}' but the runtime is configured for family '${runtimeFamilyId}'`,
224
+ { middleware: middleware.name, middlewareFamilyId: middleware.familyId, runtimeFamilyId },
225
+ );
226
+ }
227
+
228
+ if (middleware.targetId !== undefined && middleware.targetId !== runtimeTargetId) {
229
+ throw runtimeError(
230
+ 'RUNTIME.MIDDLEWARE_TARGET_MISMATCH',
231
+ `Middleware '${middleware.name}' requires target '${middleware.targetId}' but the runtime is configured for target '${runtimeTargetId}'`,
232
+ { middleware: middleware.name, middlewareTargetId: middleware.targetId, runtimeTargetId },
233
+ );
234
+ }
235
+ }
@@ -10,13 +10,16 @@ export type {
10
10
  AuthoringTemplateValue,
11
11
  AuthoringTypeConstructorDescriptor,
12
12
  AuthoringTypeNamespace,
13
- } from '../framework-authoring';
13
+ } from '../shared/framework-authoring';
14
14
  export {
15
+ assertNoCrossRegistryCollisions,
16
+ hasRegisteredFieldNamespace,
15
17
  instantiateAuthoringFieldPreset,
16
18
  instantiateAuthoringTypeConstructor,
17
19
  isAuthoringArgRef,
18
20
  isAuthoringFieldPresetDescriptor,
19
21
  isAuthoringTypeConstructorDescriptor,
22
+ mergeAuthoringNamespaces,
20
23
  resolveAuthoringTemplateValue,
21
24
  validateAuthoringHelperArguments,
22
- } from '../framework-authoring';
25
+ } from '../shared/framework-authoring';
@@ -1,2 +1,27 @@
1
- export type { Codec, CodecLookup, CodecTrait } from '../codec-types';
2
- export { emptyCodecLookup } from '../codec-types';
1
+ /**
2
+ * Codec model: interfaces (consumer surface) plus abstract `Impl` classes (codec-author surface) plus the column packager.
3
+ *
4
+ * Consumers depend on the interfaces: {@link Codec}, {@link CodecDescriptor}, {@link AnyCodecDescriptor}, {@link ColumnSpec}, {@link ColumnTypeDescriptor}.
5
+ *
6
+ * Codec authors `extend` the abstract bases: {@link CodecImpl} and {@link CodecDescriptorImpl}. They write a per-codec column helper that calls `descriptor.factory(...)` directly and tie the helper to its descriptor with `satisfies ColumnHelperFor<D>` (or `ColumnHelperForStrict<D>`).
7
+ */
8
+
9
+ export type { Codec } from '../shared/codec';
10
+ export { CodecImpl } from '../shared/codec';
11
+ export type { AnyCodecDescriptor, CodecDescriptor } from '../shared/codec-descriptor';
12
+ export { CodecDescriptorImpl } from '../shared/codec-descriptor';
13
+ export type {
14
+ CodecCallContext,
15
+ CodecInstanceContext,
16
+ CodecLookup,
17
+ CodecMeta,
18
+ CodecTrait,
19
+ } from '../shared/codec-types';
20
+ export { emptyCodecLookup, voidParamsSchema } from '../shared/codec-types';
21
+ export type {
22
+ ColumnHelperFor,
23
+ ColumnHelperForStrict,
24
+ ColumnSpec,
25
+ ColumnTypeDescriptor,
26
+ } from '../shared/column-spec';
27
+ export { column } from '../shared/column-spec';
@@ -20,5 +20,5 @@ export type {
20
20
  TargetDescriptor,
21
21
  TargetInstance,
22
22
  TargetPackRef,
23
- } from '../framework-components';
24
- export { checkContractComponentRequirements } from '../framework-components';
23
+ } from '../shared/framework-components';
24
+ export { checkContractComponentRequirements } from '../shared/framework-components';