@prisma-next/framework-components 0.5.0-dev.27 → 0.5.0-dev.29

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 (80) hide show
  1. package/README.md +31 -0
  2. package/dist/authoring.d.mts +1 -1
  3. package/dist/authoring.mjs +1 -1
  4. package/dist/authoring.mjs.map +1 -1
  5. package/dist/{codec-types-DQ1Agjom.d.mts → codec-types-DXv-ROhF.d.mts} +33 -7
  6. package/dist/codec-types-DXv-ROhF.d.mts.map +1 -0
  7. package/dist/codec.d.mts +2 -2
  8. package/dist/codec.mjs +1 -1
  9. package/dist/codec.mjs.map +1 -1
  10. package/dist/components.d.mts +1 -1
  11. package/dist/components.mjs +1 -1
  12. package/dist/control.d.mts +54 -13
  13. package/dist/control.d.mts.map +1 -1
  14. package/dist/control.mjs +11 -5
  15. package/dist/control.mjs.map +1 -1
  16. package/dist/{emission-types-BPAALJbF.d.mts → emission-types-D234HxUz.d.mts} +3 -3
  17. package/dist/emission-types-D234HxUz.d.mts.map +1 -0
  18. package/dist/emission.d.mts +2 -2
  19. package/dist/execution.d.mts +5 -5
  20. package/dist/execution.d.mts.map +1 -1
  21. package/dist/execution.mjs +3 -3
  22. package/dist/execution.mjs.map +1 -1
  23. package/dist/{framework-authoring-D1-JZ37B.d.mts → framework-authoring-BdrFDx4x.d.mts} +2 -2
  24. package/dist/framework-authoring-BdrFDx4x.d.mts.map +1 -0
  25. package/dist/{framework-components-C8ZhSwXe.mjs → framework-components-BsWST1Rn.mjs} +2 -2
  26. package/dist/framework-components-BsWST1Rn.mjs.map +1 -0
  27. package/dist/{framework-components-DFZMi2h7.d.mts → framework-components-Buvf7mnC.d.mts} +6 -6
  28. package/dist/framework-components-Buvf7mnC.d.mts.map +1 -0
  29. package/dist/psl-ast-9X5rwo98.d.mts +159 -0
  30. package/dist/psl-ast-9X5rwo98.d.mts.map +1 -0
  31. package/dist/psl-ast.d.mts +2 -0
  32. package/dist/psl-ast.mjs +1 -0
  33. package/dist/runtime.d.mts +110 -25
  34. package/dist/runtime.d.mts.map +1 -1
  35. package/dist/runtime.mjs +103 -8
  36. package/dist/runtime.mjs.map +1 -1
  37. package/dist/{types-import-spec-C4sc7wbb.d.mts → types-import-spec-D-O6GotH.d.mts} +2 -2
  38. package/dist/types-import-spec-D-O6GotH.d.mts.map +1 -0
  39. package/package.json +5 -4
  40. package/src/control/control-capabilities.ts +71 -0
  41. package/src/{control-descriptors.ts → control/control-descriptors.ts} +7 -7
  42. package/src/{control-instances.ts → control/control-instances.ts} +6 -6
  43. package/src/{control-migration-types.ts → control/control-migration-types.ts} +1 -1
  44. package/src/control/control-operation-preview.ts +23 -0
  45. package/src/{control-stack.ts → control/control-stack.ts} +13 -13
  46. package/src/{emission-types.ts → control/emission-types.ts} +1 -1
  47. package/src/control/psl-ast.ts +193 -0
  48. package/src/{execution-descriptors.ts → execution/execution-descriptors.ts} +7 -7
  49. package/src/{execution-instances.ts → execution/execution-instances.ts} +1 -1
  50. package/src/{execution-requirements.ts → execution/execution-requirements.ts} +1 -1
  51. package/src/execution/race-against-abort.ts +85 -0
  52. package/src/{runtime-core.ts → execution/runtime-core.ts} +27 -3
  53. package/src/{runtime-error.ts → execution/runtime-error.ts} +28 -0
  54. package/src/{runtime-middleware.ts → execution/runtime-middleware.ts} +17 -1
  55. package/src/exports/authoring.ts +2 -2
  56. package/src/exports/codec.ts +2 -2
  57. package/src/exports/components.ts +2 -2
  58. package/src/exports/control.ts +26 -12
  59. package/src/exports/emission.ts +2 -2
  60. package/src/exports/execution.ts +5 -5
  61. package/src/exports/psl-ast.ts +1 -0
  62. package/src/exports/runtime.ts +16 -9
  63. package/src/{codec-types.ts → shared/codec-types.ts} +31 -4
  64. package/dist/codec-types-DQ1Agjom.d.mts.map +0 -1
  65. package/dist/emission-types-BPAALJbF.d.mts.map +0 -1
  66. package/dist/framework-authoring-D1-JZ37B.d.mts.map +0 -1
  67. package/dist/framework-components-C8ZhSwXe.mjs.map +0 -1
  68. package/dist/framework-components-DFZMi2h7.d.mts.map +0 -1
  69. package/dist/types-import-spec-C4sc7wbb.d.mts.map +0 -1
  70. package/src/control-capabilities.ts +0 -34
  71. /package/src/{control-result-types.ts → control/control-result-types.ts} +0 -0
  72. /package/src/{control-schema-view.ts → control/control-schema-view.ts} +0 -0
  73. /package/src/{async-iterable-result.ts → execution/async-iterable-result.ts} +0 -0
  74. /package/src/{execution-stack.ts → execution/execution-stack.ts} +0 -0
  75. /package/src/{query-plan.ts → execution/query-plan.ts} +0 -0
  76. /package/src/{run-with-middleware.ts → execution/run-with-middleware.ts} +0 -0
  77. /package/src/{framework-authoring.ts → shared/framework-authoring.ts} +0 -0
  78. /package/src/{framework-components.ts → shared/framework-components.ts} +0 -0
  79. /package/src/{mutation-default-types.ts → shared/mutation-default-types.ts} +0 -0
  80. /package/src/{types-import-spec.ts → shared/types-import-spec.ts} +0 -0
@@ -1,9 +1,4 @@
1
1
  import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
2
- import type {
3
- SignDatabaseResult,
4
- VerifyDatabaseResult,
5
- VerifyDatabaseSchemaResult,
6
- } from './control-result-types';
7
2
  import type {
8
3
  AdapterInstance,
9
4
  DriverInstance,
@@ -11,7 +6,12 @@ import type {
11
6
  FamilyInstance,
12
7
  TargetBoundComponentDescriptor,
13
8
  TargetInstance,
14
- } from './framework-components';
9
+ } from '../shared/framework-components';
10
+ import type {
11
+ SignDatabaseResult,
12
+ VerifyDatabaseResult,
13
+ VerifyDatabaseSchemaResult,
14
+ } from './control-result-types';
15
15
 
16
16
  export interface ControlFamilyInstance<TFamilyId extends string, TSchemaIR>
17
17
  extends FamilyInstance<TFamilyId> {
@@ -11,8 +11,8 @@
11
11
 
12
12
  import type { Contract } from '@prisma-next/contract/types';
13
13
  import type { Result } from '@prisma-next/utils/result';
14
+ import type { TargetBoundComponentDescriptor } from '../shared/framework-components';
14
15
  import type { ControlDriverInstance, ControlFamilyInstance } from './control-instances';
15
- import type { TargetBoundComponentDescriptor } from './framework-components';
16
16
 
17
17
  // ============================================================================
18
18
  // Operation Classes and Policy
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Family-agnostic textual preview of a migration plan, used by the CLI to
3
+ * render a "DDL preview" section for `db init` / `db update` / `migration plan`
4
+ * / `migration show`. Each statement carries a free-form `language` tag so
5
+ * formatters can suffix `;` for SQL but render Mongo shell lines verbatim.
6
+ *
7
+ * Producers are family-specific: SQL emits `language: 'sql'` (existing DDL
8
+ * extraction); Mongo emits `language: 'mongodb-shell'` via the
9
+ * `MongoDdlCommandFormatter` visitor.
10
+ *
11
+ * The capability `OperationPreviewCapable` (declared in
12
+ * `./control-capabilities`) is how a family announces it can produce these.
13
+ */
14
+
15
+ export interface OperationPreviewStatement {
16
+ readonly text: string;
17
+ /** Dialect identifier, e.g. `'sql'`, `'mongodb-shell'`. Free-form by design (OQ-3). */
18
+ readonly language: string;
19
+ }
20
+
21
+ export interface OperationPreview {
22
+ readonly statements: readonly OperationPreviewStatement[];
23
+ }
@@ -1,25 +1,25 @@
1
- import type { CodecLookup } from './codec-types';
2
- import type {
3
- ControlAdapterDescriptor,
4
- ControlDriverDescriptor,
5
- ControlExtensionDescriptor,
6
- ControlFamilyDescriptor,
7
- ControlTargetDescriptor,
8
- } from './control-descriptors';
1
+ import type { CodecLookup } from '../shared/codec-types';
9
2
  import type {
10
3
  AuthoringContributions,
11
4
  AuthoringFieldNamespace,
12
5
  AuthoringFieldPresetDescriptor,
13
6
  AuthoringTypeConstructorDescriptor,
14
7
  AuthoringTypeNamespace,
15
- } from './framework-authoring';
16
- import type { ComponentMetadata } from './framework-components';
8
+ } from '../shared/framework-authoring';
9
+ import type { ComponentMetadata } from '../shared/framework-components';
17
10
  import type {
18
11
  ControlMutationDefaultEntry,
19
12
  ControlMutationDefaults,
20
13
  MutationDefaultGeneratorDescriptor,
21
- } from './mutation-default-types';
22
- import type { TypesImportSpec } from './types-import-spec';
14
+ } from '../shared/mutation-default-types';
15
+ import type { TypesImportSpec } from '../shared/types-import-spec';
16
+ import type {
17
+ ControlAdapterDescriptor,
18
+ ControlDriverDescriptor,
19
+ ControlExtensionDescriptor,
20
+ ControlFamilyDescriptor,
21
+ ControlTargetDescriptor,
22
+ } from './control-descriptors';
23
23
 
24
24
  export interface AssembledAuthoringContributions {
25
25
  readonly field: AuthoringFieldNamespace;
@@ -328,7 +328,7 @@ export function assembleControlMutationDefaults(
328
328
  export function extractCodecLookup(
329
329
  descriptors: ReadonlyArray<Pick<ComponentMetadata & { id?: string }, 'types' | 'id'>>,
330
330
  ): CodecLookup {
331
- const byId = new Map<string, import('./codec-types').Codec>();
331
+ const byId = new Map<string, import('../shared/codec-types').Codec>();
332
332
  const owners = new Map<string, string>();
333
333
  for (const descriptor of descriptors) {
334
334
  const codecInstances = descriptor.types?.codecTypes?.codecInstances;
@@ -1,5 +1,5 @@
1
1
  import type { Contract, ContractModel } from '@prisma-next/contract/types';
2
- import type { TypesImportSpec } from './types-import-spec';
2
+ import type { TypesImportSpec } from '../shared/types-import-spec';
3
3
 
4
4
  export interface GenerateContractTypesOptions {
5
5
  readonly queryOperationTypeImports?: ReadonlyArray<TypesImportSpec>;
@@ -0,0 +1,193 @@
1
+ export interface PslPosition {
2
+ readonly offset: number;
3
+ readonly line: number;
4
+ readonly column: number;
5
+ }
6
+
7
+ export interface PslSpan {
8
+ readonly start: PslPosition;
9
+ readonly end: PslPosition;
10
+ }
11
+
12
+ export type PslDiagnosticCode =
13
+ | 'PSL_UNTERMINATED_BLOCK'
14
+ | 'PSL_UNSUPPORTED_TOP_LEVEL_BLOCK'
15
+ | 'PSL_INVALID_ATTRIBUTE_SYNTAX'
16
+ | 'PSL_INVALID_MODEL_MEMBER'
17
+ | 'PSL_UNSUPPORTED_MODEL_ATTRIBUTE'
18
+ | 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE'
19
+ | 'PSL_INVALID_RELATION_ATTRIBUTE'
20
+ | 'PSL_INVALID_REFERENTIAL_ACTION'
21
+ | 'PSL_INVALID_DEFAULT_VALUE'
22
+ | 'PSL_INVALID_ENUM_MEMBER'
23
+ | 'PSL_INVALID_TYPES_MEMBER';
24
+
25
+ export interface PslDiagnostic {
26
+ readonly code: PslDiagnosticCode;
27
+ readonly message: string;
28
+ readonly sourceId: string;
29
+ readonly span: PslSpan;
30
+ }
31
+
32
+ export interface PslDefaultFunctionValue {
33
+ readonly kind: 'function';
34
+ readonly name: 'autoincrement' | 'now';
35
+ }
36
+
37
+ export interface PslDefaultLiteralValue {
38
+ readonly kind: 'literal';
39
+ readonly value: string | number | boolean;
40
+ }
41
+
42
+ export type PslDefaultValue = PslDefaultFunctionValue | PslDefaultLiteralValue;
43
+
44
+ export type PslAttributeTarget = 'field' | 'model' | 'enum' | 'namedType';
45
+
46
+ export interface PslAttributePositionalArgument {
47
+ readonly kind: 'positional';
48
+ readonly value: string;
49
+ readonly span: PslSpan;
50
+ }
51
+
52
+ export interface PslAttributeNamedArgument {
53
+ readonly kind: 'named';
54
+ readonly name: string;
55
+ readonly value: string;
56
+ readonly span: PslSpan;
57
+ }
58
+
59
+ export type PslAttributeArgument = PslAttributePositionalArgument | PslAttributeNamedArgument;
60
+
61
+ export interface PslTypeConstructorCall {
62
+ readonly kind: 'typeConstructor';
63
+ readonly path: readonly string[];
64
+ readonly args: readonly PslAttributeArgument[];
65
+ readonly span: PslSpan;
66
+ }
67
+
68
+ export interface PslAttribute {
69
+ readonly kind: 'attribute';
70
+ readonly target: PslAttributeTarget;
71
+ readonly name: string;
72
+ readonly args: readonly PslAttributeArgument[];
73
+ readonly span: PslSpan;
74
+ }
75
+
76
+ export type PslReferentialAction = string;
77
+
78
+ export type PslFieldAttribute = PslAttribute;
79
+
80
+ export interface PslField {
81
+ readonly kind: 'field';
82
+ readonly name: string;
83
+ readonly typeName: string;
84
+ readonly typeConstructor?: PslTypeConstructorCall;
85
+ readonly optional: boolean;
86
+ readonly list: boolean;
87
+ readonly typeRef?: string;
88
+ readonly attributes: readonly PslFieldAttribute[];
89
+ readonly span: PslSpan;
90
+ }
91
+
92
+ export interface PslUniqueConstraint {
93
+ readonly kind: 'unique';
94
+ readonly fields: readonly string[];
95
+ readonly span: PslSpan;
96
+ }
97
+
98
+ export interface PslIndexConstraint {
99
+ readonly kind: 'index';
100
+ readonly fields: readonly string[];
101
+ readonly span: PslSpan;
102
+ }
103
+
104
+ export type PslModelAttribute = PslAttribute;
105
+
106
+ export interface PslModel {
107
+ readonly kind: 'model';
108
+ readonly name: string;
109
+ readonly fields: readonly PslField[];
110
+ readonly attributes: readonly PslModelAttribute[];
111
+ readonly span: PslSpan;
112
+ /**
113
+ * Optional leading comment line emitted above the `model` keyword by the
114
+ * printer. Producers (e.g. `sqlSchemaIrToPslAst`) attach introspection
115
+ * advisories such as "// WARNING: This table has no primary key in the
116
+ * database" here. The parser leaves this field unset; round-tripping a
117
+ * parsed schema does not re-attach comments.
118
+ */
119
+ readonly comment?: string;
120
+ }
121
+
122
+ export interface PslEnumValue {
123
+ readonly kind: 'enumValue';
124
+ readonly name: string;
125
+ /**
126
+ * Optional storage label for the enum member, captured from a trailing
127
+ * `@map("...")` attribute on the member line. The parser populates this
128
+ * when the source PSL carries an explicit `@map`. Producers (e.g.
129
+ * `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`
130
+ * automatically when normalisation would change the printed member name
131
+ * (so an enum value `'in-progress'` becomes `inProgress @map("in-progress")`
132
+ * in PSL, preserving the round-trip).
133
+ */
134
+ readonly mapName?: string;
135
+ readonly span: PslSpan;
136
+ }
137
+
138
+ export interface PslEnum {
139
+ readonly kind: 'enum';
140
+ readonly name: string;
141
+ readonly values: readonly PslEnumValue[];
142
+ readonly attributes: readonly PslAttribute[];
143
+ readonly span: PslSpan;
144
+ }
145
+
146
+ export interface PslCompositeType {
147
+ readonly kind: 'compositeType';
148
+ readonly name: string;
149
+ readonly fields: readonly PslField[];
150
+ readonly attributes: readonly PslAttribute[];
151
+ readonly span: PslSpan;
152
+ }
153
+
154
+ export interface PslNamedTypeDeclaration {
155
+ readonly kind: 'namedType';
156
+ readonly name: string;
157
+ /**
158
+ * Parser invariant: exactly one of `baseType` and `typeConstructor` is set.
159
+ * Expressing this as a discriminated union trips TypeScript narrowing when
160
+ * the declaration flows through helpers that accept the full union.
161
+ */
162
+ readonly baseType?: string;
163
+ readonly typeConstructor?: PslTypeConstructorCall;
164
+ readonly attributes: readonly PslAttribute[];
165
+ readonly span: PslSpan;
166
+ }
167
+
168
+ export interface PslTypesBlock {
169
+ readonly kind: 'types';
170
+ readonly declarations: readonly PslNamedTypeDeclaration[];
171
+ readonly span: PslSpan;
172
+ }
173
+
174
+ export interface PslDocumentAst {
175
+ readonly kind: 'document';
176
+ readonly sourceId: string;
177
+ readonly models: readonly PslModel[];
178
+ readonly enums: readonly PslEnum[];
179
+ readonly compositeTypes: readonly PslCompositeType[];
180
+ readonly types?: PslTypesBlock;
181
+ readonly span: PslSpan;
182
+ }
183
+
184
+ export interface ParsePslDocumentInput {
185
+ readonly schema: string;
186
+ readonly sourceId: string;
187
+ }
188
+
189
+ export interface ParsePslDocumentResult {
190
+ readonly ast: PslDocumentAst;
191
+ readonly diagnostics: readonly PslDiagnostic[];
192
+ readonly ok: boolean;
193
+ }
@@ -1,3 +1,10 @@
1
+ import type {
2
+ AdapterDescriptor,
3
+ DriverDescriptor,
4
+ ExtensionDescriptor,
5
+ FamilyDescriptor,
6
+ TargetDescriptor,
7
+ } from '../shared/framework-components';
1
8
  import type {
2
9
  RuntimeAdapterInstance,
3
10
  RuntimeDriverInstance,
@@ -6,13 +13,6 @@ import type {
6
13
  RuntimeTargetInstance,
7
14
  } from './execution-instances';
8
15
  import type { ExecutionStack } from './execution-stack';
9
- import type {
10
- AdapterDescriptor,
11
- DriverDescriptor,
12
- ExtensionDescriptor,
13
- FamilyDescriptor,
14
- TargetDescriptor,
15
- } from './framework-components';
16
16
 
17
17
  export interface RuntimeFamilyDescriptor<
18
18
  TFamilyId extends string,
@@ -4,7 +4,7 @@ import type {
4
4
  ExtensionInstance,
5
5
  FamilyInstance,
6
6
  TargetInstance,
7
- } from './framework-components';
7
+ } from '../shared/framework-components';
8
8
 
9
9
  export interface RuntimeFamilyInstance<TFamilyId extends string>
10
10
  extends FamilyInstance<TFamilyId> {}
@@ -1,10 +1,10 @@
1
+ import { checkContractComponentRequirements } from '../shared/framework-components';
1
2
  import type {
2
3
  RuntimeAdapterDescriptor,
3
4
  RuntimeExtensionDescriptor,
4
5
  RuntimeFamilyDescriptor,
5
6
  RuntimeTargetDescriptor,
6
7
  } from './execution-descriptors';
7
- import { checkContractComponentRequirements } from './framework-components';
8
8
 
9
9
  export function assertRuntimeContractRequirementsSatisfied<
10
10
  TFamilyId extends string,
@@ -0,0 +1,85 @@
1
+ import type { CodecCallContext } from '../shared/codec-types';
2
+ import type { RuntimeAbortedPhase } from './runtime-error';
3
+ import { runtimeAborted } from './runtime-error';
4
+
5
+ /**
6
+ * Throw a phase-tagged `RUNTIME.ABORTED` envelope if the supplied
7
+ * codec-call context is already aborted at the precheck site. Centralises
8
+ * the `if (ctx.signal?.aborted) throw runtimeAborted(...)` pattern that
9
+ * every codec dispatch site repeats.
10
+ */
11
+ export function checkAborted(ctx: CodecCallContext, phase: RuntimeAbortedPhase): void {
12
+ if (ctx.signal?.aborted) {
13
+ throw runtimeAborted(phase, ctx.signal.reason);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Race a per-cell `Promise.all` (or any other in-flight work promise) against
19
+ * the supplied abort signal so the runtime returns `RUNTIME.ABORTED` promptly
20
+ * even when codec bodies ignore the signal. In-flight bodies that ignore the
21
+ * signal are abandoned and run to completion in the background — the
22
+ * cooperative-cancellation contract documented in ADR 204.
23
+ *
24
+ * Call sites still SHOULD pre-check `signal.aborted` and short-circuit with
25
+ * a phase-tagged `RUNTIME.ABORTED` envelope before invoking this helper —
26
+ * that path is the canonical "aborted at entry" surface and avoids
27
+ * scheduling the work promise. As a defensive belt-and-braces, this helper
28
+ * also handles the already-aborted case internally: `AbortSignal` does not
29
+ * replay past abort events to listeners registered after the abort, so we
30
+ * inspect `signal.aborted` synchronously and reject with the sentinel
31
+ * before installing the listener. The rejection is still attributed to the
32
+ * abort path via the sentinel-identity check.
33
+ *
34
+ * Distinguishing the rejection source is load-bearing for AC-ERR4
35
+ * (`RUNTIME.ENCODE_FAILED` / `RUNTIME.DECODE_FAILED` pass through unchanged).
36
+ * The semantically equivalent `abortable(signal)` helper in
37
+ * `@prisma-next/utils` rejects with `signal.reason ?? new DOMException(...)`,
38
+ * which is not stably distinguishable from a codec-thrown error by identity
39
+ * alone (a fresh fallback DOMException is allocated per call). We instead
40
+ * track abort attribution with a unique sentinel: only the `onAbort` listener
41
+ * installed here ever rejects with the sentinel, so an `error === sentinel`
42
+ * identity check after the race is unambiguous.
43
+ *
44
+ * Lives in `framework-components` (rather than the SQL family, where it
45
+ * originated in m2) so every family runtime that needs cooperative
46
+ * cancellation around a codec-dispatch `Promise.all` (SQL encode + decode
47
+ * today, Mongo encode in m3) shares the same attribution logic.
48
+ */
49
+ export async function raceAgainstAbort<T>(
50
+ work: Promise<T>,
51
+ signal: AbortSignal | undefined,
52
+ phase: RuntimeAbortedPhase,
53
+ ): Promise<T> {
54
+ if (signal === undefined) {
55
+ return await work;
56
+ }
57
+ const sentinel: { reason: unknown } = { reason: undefined };
58
+ let onAbort: (() => void) | undefined;
59
+
60
+ const abortPromise = new Promise<never>((_, reject) => {
61
+ if (signal.aborted) {
62
+ sentinel.reason = signal.reason;
63
+ reject(sentinel);
64
+ return;
65
+ }
66
+ onAbort = () => {
67
+ sentinel.reason = signal.reason;
68
+ reject(sentinel);
69
+ };
70
+ signal.addEventListener('abort', onAbort, { once: true });
71
+ });
72
+
73
+ try {
74
+ return await Promise.race([work, abortPromise]);
75
+ } catch (error) {
76
+ if (error === sentinel) {
77
+ throw runtimeAborted(phase, sentinel.reason);
78
+ }
79
+ throw error;
80
+ } finally {
81
+ if (onAbort) {
82
+ signal.removeEventListener('abort', onAbort);
83
+ }
84
+ }
85
+ }
@@ -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>(
@@ -5,6 +5,22 @@ export interface RuntimeErrorEnvelope extends Error {
5
5
  readonly details?: Record<string, unknown>;
6
6
  }
7
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
+ */
19
+ export const RUNTIME_ABORTED = 'RUNTIME.ABORTED' as const;
20
+
21
+ /** Discriminator placed in `details.phase` of a `RUNTIME.ABORTED` envelope. */
22
+ export type RuntimeAbortedPhase = 'encode' | 'decode' | 'stream';
23
+
8
24
  /**
9
25
  * Type guard for the runtime-error envelope produced by `runtimeError`.
10
26
  *
@@ -53,3 +69,15 @@ function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
53
69
  return 'RUNTIME';
54
70
  }
55
71
  }
72
+
73
+ /**
74
+ * Construct a `RUNTIME.ABORTED` envelope. Phase distinguishes where the
75
+ * abort was observed (encode / decode / stream); cause carries `signal.reason`
76
+ * verbatim from the platform — native abort produces a `DOMException`,
77
+ * explicit `controller.abort(reason)` produces whatever the caller passed.
78
+ * No synthesis happens here.
79
+ */
80
+ export function runtimeAborted(phase: RuntimeAbortedPhase, cause?: unknown): RuntimeErrorEnvelope {
81
+ const envelope = runtimeError(RUNTIME_ABORTED, `Operation aborted during ${phase}`, { phase });
82
+ return Object.assign(envelope, { cause });
83
+ }
@@ -43,6 +43,19 @@ export interface RuntimeMiddleware<TPlan extends QueryPlan = QueryPlan> {
43
43
  ): Promise<void>;
44
44
  }
45
45
 
46
+ /**
47
+ * Optional per-`execute` options accepted by every family runtime.
48
+ *
49
+ * `signal` is the per-query cancellation signal. The runtime threads the
50
+ * signal through to every codec call for the query and uses it to short-
51
+ * circuit the row stream with `RUNTIME.ABORTED` when the caller aborts.
52
+ * Omitting the option (or passing `undefined`) preserves today's behavior
53
+ * bit-for-bit.
54
+ */
55
+ export interface RuntimeExecuteOptions {
56
+ readonly signal?: AbortSignal;
57
+ }
58
+
46
59
  /**
47
60
  * Cross-family SPI for any runtime that can execute plans and be shut down.
48
61
  * Each family runtime (SQL, Mongo) satisfies this interface — SQL nominally,
@@ -52,7 +65,10 @@ export interface RuntimeMiddleware<TPlan extends QueryPlan = QueryPlan> {
52
65
  * plan, mirroring how `QueryPlan<Row>` carries a phantom `_row?: Row`.
53
66
  */
54
67
  export interface RuntimeExecutor<TPlan extends QueryPlan> {
55
- execute<Row>(plan: TPlan & { readonly _row?: Row }): AsyncIterableResult<Row>;
68
+ execute<Row>(
69
+ plan: TPlan & { readonly _row?: Row },
70
+ options?: RuntimeExecuteOptions,
71
+ ): AsyncIterableResult<Row>;
56
72
  close(): Promise<void>;
57
73
  }
58
74
 
@@ -10,7 +10,7 @@ export type {
10
10
  AuthoringTemplateValue,
11
11
  AuthoringTypeConstructorDescriptor,
12
12
  AuthoringTypeNamespace,
13
- } from '../framework-authoring';
13
+ } from '../shared/framework-authoring';
14
14
  export {
15
15
  instantiateAuthoringFieldPreset,
16
16
  instantiateAuthoringTypeConstructor,
@@ -19,4 +19,4 @@ export {
19
19
  isAuthoringTypeConstructorDescriptor,
20
20
  resolveAuthoringTemplateValue,
21
21
  validateAuthoringHelperArguments,
22
- } from '../framework-authoring';
22
+ } from '../shared/framework-authoring';
@@ -1,2 +1,2 @@
1
- export type { Codec, CodecLookup, CodecTrait } from '../codec-types';
2
- export { emptyCodecLookup } from '../codec-types';
1
+ export type { Codec, CodecCallContext, CodecLookup, CodecTrait } from '../shared/codec-types';
2
+ export { emptyCodecLookup } from '../shared/codec-types';
@@ -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';