@prisma-next/sql-runtime 0.3.0-dev.4 → 0.3.0-dev.5

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 (40) hide show
  1. package/dist/src/codecs/decoding.d.ts +4 -0
  2. package/dist/src/codecs/decoding.d.ts.map +1 -0
  3. package/dist/src/codecs/encoding.d.ts +5 -0
  4. package/dist/src/codecs/encoding.d.ts.map +1 -0
  5. package/dist/src/codecs/validation.d.ts +6 -0
  6. package/dist/src/codecs/validation.d.ts.map +1 -0
  7. package/dist/src/exports/index.d.ts +11 -0
  8. package/dist/src/exports/index.d.ts.map +1 -0
  9. package/dist/src/index.d.ts +2 -0
  10. package/dist/src/index.d.ts.map +1 -0
  11. package/dist/src/lower-sql-plan.d.ts +15 -0
  12. package/dist/src/lower-sql-plan.d.ts.map +1 -0
  13. package/dist/src/sql-context.d.ts +65 -0
  14. package/dist/src/sql-context.d.ts.map +1 -0
  15. package/dist/src/sql-family-adapter.d.ts +10 -0
  16. package/dist/src/sql-family-adapter.d.ts.map +1 -0
  17. package/dist/src/sql-marker.d.ts +22 -0
  18. package/dist/src/sql-marker.d.ts.map +1 -0
  19. package/dist/src/sql-runtime.d.ts +25 -0
  20. package/dist/src/sql-runtime.d.ts.map +1 -0
  21. package/dist/test/utils.d.ts +18 -23
  22. package/dist/test/utils.d.ts.map +1 -0
  23. package/package.json +13 -11
  24. package/src/codecs/decoding.ts +140 -0
  25. package/src/codecs/encoding.ts +76 -0
  26. package/src/codecs/validation.ts +67 -0
  27. package/src/exports/index.ts +38 -0
  28. package/src/index.ts +1 -0
  29. package/src/lower-sql-plan.ts +32 -0
  30. package/src/sql-context.ts +156 -0
  31. package/src/sql-family-adapter.ts +43 -0
  32. package/src/sql-marker.ts +105 -0
  33. package/src/sql-runtime.ts +166 -0
  34. package/test/async-iterable-result.test.ts +136 -0
  35. package/test/sql-context.test.ts +217 -0
  36. package/test/sql-family-adapter.test.ts +86 -0
  37. package/test/sql-runtime.test.ts +155 -0
  38. package/test/utils.ts +262 -0
  39. package/dist/index.d.ts +0 -29
  40. package/dist/sql-runtime-DgEbg2OP.d.ts +0 -109
@@ -0,0 +1,67 @@
1
+ import { runtimeError } from '@prisma-next/runtime-executor';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type { CodecRegistry } from '@prisma-next/sql-relational-core/ast';
4
+
5
+ export function extractCodecIds(contract: SqlContract<SqlStorage>): Set<string> {
6
+ const codecIds = new Set<string>();
7
+
8
+ for (const table of Object.values(contract.storage.tables)) {
9
+ for (const column of Object.values(table.columns)) {
10
+ const codecId = column.codecId;
11
+ codecIds.add(codecId);
12
+ }
13
+ }
14
+
15
+ return codecIds;
16
+ }
17
+
18
+ function extractCodecIdsFromColumns(contract: SqlContract<SqlStorage>): Map<string, string> {
19
+ const codecIds = new Map<string, string>();
20
+
21
+ for (const [tableName, table] of Object.entries(contract.storage.tables)) {
22
+ for (const [columnName, column] of Object.entries(table.columns)) {
23
+ const codecId = column.codecId;
24
+ const key = `${tableName}.${columnName}`;
25
+ codecIds.set(key, codecId);
26
+ }
27
+ }
28
+
29
+ return codecIds;
30
+ }
31
+
32
+ export function validateContractCodecMappings(
33
+ registry: CodecRegistry,
34
+ contract: SqlContract<SqlStorage>,
35
+ ): void {
36
+ const codecIds = extractCodecIdsFromColumns(contract);
37
+ const invalidCodecs: Array<{ table: string; column: string; codecId: string }> = [];
38
+
39
+ for (const [key, codecId] of codecIds.entries()) {
40
+ if (!registry.has(codecId)) {
41
+ const parts = key.split('.');
42
+ const table = parts[0] ?? '';
43
+ const column = parts[1] ?? '';
44
+ invalidCodecs.push({ table, column, codecId });
45
+ }
46
+ }
47
+
48
+ if (invalidCodecs.length > 0) {
49
+ const details: Record<string, unknown> = {
50
+ contractTarget: contract.target,
51
+ invalidCodecs,
52
+ };
53
+
54
+ throw runtimeError(
55
+ 'RUNTIME.CODEC_MISSING',
56
+ `Missing codec implementations for column codecIds: ${invalidCodecs.map((c) => `${c.table}.${c.column} (${c.codecId})`).join(', ')}`,
57
+ details,
58
+ );
59
+ }
60
+ }
61
+
62
+ export function validateCodecRegistryCompleteness(
63
+ registry: CodecRegistry,
64
+ contract: SqlContract<SqlStorage>,
65
+ ): void {
66
+ validateContractCodecMappings(registry, contract);
67
+ }
@@ -0,0 +1,38 @@
1
+ export type {
2
+ AfterExecuteResult,
3
+ BudgetsOptions,
4
+ LintsOptions,
5
+ Log,
6
+ Plugin,
7
+ PluginContext,
8
+ } from '@prisma-next/runtime-executor';
9
+ export { budgets, lints } from '@prisma-next/runtime-executor';
10
+ export {
11
+ extractCodecIds,
12
+ validateCodecRegistryCompleteness,
13
+ validateContractCodecMappings,
14
+ } from '../codecs/validation';
15
+ export { lowerSqlPlan } from '../lower-sql-plan';
16
+ export type {
17
+ CreateRuntimeContextOptions,
18
+ RuntimeContext,
19
+ SqlRuntimeAdapterInstance,
20
+ SqlRuntimeExtensionDescriptor,
21
+ SqlRuntimeExtensionInstance,
22
+ } from '../sql-context';
23
+ export { createRuntimeContext } from '../sql-context';
24
+ export type { SqlStatement } from '../sql-marker';
25
+ export {
26
+ ensureSchemaStatement,
27
+ ensureTableStatement,
28
+ readContractMarker,
29
+ writeContractMarker,
30
+ } from '../sql-marker';
31
+ export type {
32
+ Runtime,
33
+ RuntimeOptions,
34
+ RuntimeTelemetryEvent,
35
+ RuntimeVerifyOptions,
36
+ TelemetryOutcome,
37
+ } from '../sql-runtime';
38
+ export { createRuntime } from '../sql-runtime';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './exports';
@@ -0,0 +1,32 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
3
+ import type { RuntimeContext } from './sql-context';
4
+
5
+ /**
6
+ * Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
7
+ *
8
+ * This function is responsible for converting a lane-produced SqlQueryPlan (which contains
9
+ * AST and params but no SQL) into a fully executable Plan (which includes SQL string).
10
+ *
11
+ * @param context - Runtime context containing the adapter
12
+ * @param queryPlan - SQL query plan from a lane (contains AST, params, meta, but no SQL)
13
+ * @returns Fully executable Plan with SQL string
14
+ */
15
+ export function lowerSqlPlan<Row>(
16
+ context: RuntimeContext,
17
+ queryPlan: SqlQueryPlan<Row>,
18
+ ): ExecutionPlan<Row> {
19
+ const lowered = context.adapter.lower(queryPlan.ast, {
20
+ contract: context.contract,
21
+ params: queryPlan.params,
22
+ });
23
+
24
+ const body = lowered.body;
25
+
26
+ return Object.freeze({
27
+ sql: body.sql,
28
+ params: body.params ?? queryPlan.params,
29
+ ast: queryPlan.ast,
30
+ meta: queryPlan.meta,
31
+ });
32
+ }
@@ -0,0 +1,156 @@
1
+ import type {
2
+ RuntimeAdapterDescriptor,
3
+ RuntimeAdapterInstance,
4
+ RuntimeExtensionDescriptor,
5
+ RuntimeExtensionInstance,
6
+ RuntimeTargetDescriptor,
7
+ } from '@prisma-next/core-execution-plane/types';
8
+ import { createOperationRegistry } from '@prisma-next/operations';
9
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
10
+ import type { SqlOperationSignature } from '@prisma-next/sql-operations';
11
+ import type {
12
+ Adapter,
13
+ CodecRegistry,
14
+ LoweredStatement,
15
+ QueryAst,
16
+ } from '@prisma-next/sql-relational-core/ast';
17
+ import { createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
18
+ import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
19
+
20
+ // ============================================================================
21
+ // SQL Runtime Extension Types
22
+ // ============================================================================
23
+
24
+ /**
25
+ * SQL runtime extension instance.
26
+ * Extends the framework RuntimeExtensionInstance with SQL-specific hooks
27
+ * for contributing codecs and operations to the runtime context.
28
+ *
29
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
30
+ */
31
+ export interface SqlRuntimeExtensionInstance<TTargetId extends string>
32
+ extends RuntimeExtensionInstance<'sql', TTargetId> {
33
+ /** Returns codecs to register in the runtime context. */
34
+ codecs?(): CodecRegistry;
35
+ /** Returns operations to register in the runtime context. */
36
+ operations?(): ReadonlyArray<SqlOperationSignature>;
37
+ }
38
+
39
+ /**
40
+ * SQL runtime extension descriptor.
41
+ * Extends the framework RuntimeExtensionDescriptor with SQL-specific instance type.
42
+ *
43
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
44
+ */
45
+ export interface SqlRuntimeExtensionDescriptor<TTargetId extends string>
46
+ extends RuntimeExtensionDescriptor<'sql', TTargetId, SqlRuntimeExtensionInstance<TTargetId>> {
47
+ create(): SqlRuntimeExtensionInstance<TTargetId>;
48
+ }
49
+
50
+ // ============================================================================
51
+ // SQL Runtime Adapter Instance
52
+ // ============================================================================
53
+
54
+ /**
55
+ * SQL runtime adapter instance interface.
56
+ * Combines RuntimeAdapterInstance identity with SQL Adapter behavior.
57
+ * The instance IS an Adapter (via intersection), not HAS an adapter property.
58
+ *
59
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
60
+ */
61
+ export type SqlRuntimeAdapterInstance<TTargetId extends string = string> = RuntimeAdapterInstance<
62
+ 'sql',
63
+ TTargetId
64
+ > &
65
+ Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>;
66
+
67
+ // ============================================================================
68
+ // SQL Runtime Context
69
+ // ============================================================================
70
+
71
+ export interface RuntimeContext<TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>>
72
+ extends QueryLaneContext<TContract> {
73
+ readonly adapter:
74
+ | Adapter<QueryAst, TContract, LoweredStatement>
75
+ | Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>;
76
+ }
77
+
78
+ /**
79
+ * Descriptor-first options for creating a SQL runtime context.
80
+ * Takes the same framework composition as control-plane: target, adapter, extensionPacks.
81
+ *
82
+ * @template TContract - The SQL contract type
83
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
84
+ */
85
+ export interface CreateRuntimeContextOptions<
86
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
87
+ TTargetId extends string = string,
88
+ > {
89
+ readonly contract: TContract;
90
+ readonly target: RuntimeTargetDescriptor<'sql', TTargetId>;
91
+ readonly adapter: RuntimeAdapterDescriptor<
92
+ 'sql',
93
+ TTargetId,
94
+ SqlRuntimeAdapterInstance<TTargetId>
95
+ >;
96
+ readonly extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<TTargetId>>;
97
+ }
98
+
99
+ /**
100
+ * Creates a SQL runtime context from descriptor-first composition.
101
+ *
102
+ * The context includes:
103
+ * - The validated contract
104
+ * - The adapter instance (created from descriptor)
105
+ * - Codec registry (populated from adapter + extension instances)
106
+ * - Operation registry (populated from extension instances)
107
+ *
108
+ * @param options - Descriptor-first composition options
109
+ * @returns RuntimeContext with registries wired from all components
110
+ */
111
+ export function createRuntimeContext<
112
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
113
+ TTargetId extends string = string,
114
+ >(options: CreateRuntimeContextOptions<TContract, TTargetId>): RuntimeContext<TContract> {
115
+ const { contract, adapter: adapterDescriptor, extensionPacks } = options;
116
+
117
+ // Create adapter instance from descriptor
118
+ // The adapter instance IS an Adapter (via intersection)
119
+ const adapterInstance = adapterDescriptor.create();
120
+
121
+ // Create registries
122
+ const codecRegistry = createCodecRegistry();
123
+ const operationRegistry = createOperationRegistry();
124
+
125
+ // Register adapter codecs (adapter instance has profile.codecs())
126
+ const adapterCodecs = adapterInstance.profile.codecs();
127
+ for (const codec of adapterCodecs.values()) {
128
+ codecRegistry.register(codec);
129
+ }
130
+
131
+ // Create extension instances and register their codecs/operations
132
+ for (const extDescriptor of extensionPacks ?? []) {
133
+ const extInstance = extDescriptor.create();
134
+
135
+ const extCodecs = extInstance.codecs?.();
136
+ if (extCodecs) {
137
+ for (const codec of extCodecs.values()) {
138
+ codecRegistry.register(codec);
139
+ }
140
+ }
141
+
142
+ const extOperations = extInstance.operations?.();
143
+ if (extOperations) {
144
+ for (const operation of extOperations) {
145
+ operationRegistry.register(operation);
146
+ }
147
+ }
148
+ }
149
+
150
+ return {
151
+ contract,
152
+ adapter: adapterInstance,
153
+ operations: operationRegistry,
154
+ codecs: codecRegistry,
155
+ };
156
+ }
@@ -0,0 +1,43 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type {
3
+ MarkerReader,
4
+ MarkerStatement,
5
+ RuntimeFamilyAdapter,
6
+ } from '@prisma-next/runtime-executor';
7
+ import { runtimeError } from '@prisma-next/runtime-executor';
8
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
9
+ import { readContractMarker } from './sql-marker';
10
+
11
+ class SqlMarkerReader implements MarkerReader {
12
+ readMarkerStatement(): MarkerStatement {
13
+ return readContractMarker();
14
+ }
15
+ }
16
+
17
+ export class SqlFamilyAdapter<TContract extends SqlContract<SqlStorage>>
18
+ implements RuntimeFamilyAdapter<TContract>
19
+ {
20
+ readonly contract: TContract;
21
+ readonly markerReader: MarkerReader;
22
+
23
+ constructor(contract: TContract) {
24
+ this.contract = contract;
25
+ this.markerReader = new SqlMarkerReader();
26
+ }
27
+
28
+ validatePlan(plan: ExecutionPlan, contract: TContract): void {
29
+ if (plan.meta.target !== contract.target) {
30
+ throw runtimeError('PLAN.TARGET_MISMATCH', 'Plan target does not match runtime target', {
31
+ planTarget: plan.meta.target,
32
+ runtimeTarget: contract.target,
33
+ });
34
+ }
35
+
36
+ if (plan.meta.coreHash !== contract.coreHash) {
37
+ throw runtimeError('PLAN.HASH_MISMATCH', 'Plan core hash does not match runtime contract', {
38
+ planCoreHash: plan.meta.coreHash,
39
+ runtimeCoreHash: contract.coreHash,
40
+ });
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,105 @@
1
+ import type { MarkerStatement } from '@prisma-next/runtime-executor';
2
+
3
+ export interface SqlStatement {
4
+ readonly sql: string;
5
+ readonly params: readonly unknown[];
6
+ }
7
+
8
+ export interface WriteMarkerInput {
9
+ readonly coreHash: string;
10
+ readonly profileHash: string;
11
+ readonly contractJson?: unknown;
12
+ readonly canonicalVersion?: number;
13
+ readonly appTag?: string;
14
+ readonly meta?: Record<string, unknown>;
15
+ }
16
+
17
+ export const ensureSchemaStatement: SqlStatement = {
18
+ sql: 'create schema if not exists prisma_contract',
19
+ params: [],
20
+ };
21
+
22
+ export const ensureTableStatement: SqlStatement = {
23
+ sql: `create table if not exists prisma_contract.marker (
24
+ id smallint primary key default 1,
25
+ core_hash text not null,
26
+ profile_hash text not null,
27
+ contract_json jsonb,
28
+ canonical_version int,
29
+ updated_at timestamptz not null default now(),
30
+ app_tag text,
31
+ meta jsonb not null default '{}'
32
+ )`,
33
+ params: [],
34
+ };
35
+
36
+ export function readContractMarker(): MarkerStatement {
37
+ return {
38
+ sql: `select
39
+ core_hash,
40
+ profile_hash,
41
+ contract_json,
42
+ canonical_version,
43
+ updated_at,
44
+ app_tag,
45
+ meta
46
+ from prisma_contract.marker
47
+ where id = $1`,
48
+ params: [1],
49
+ };
50
+ }
51
+
52
+ export interface WriteContractMarkerStatements {
53
+ readonly insert: SqlStatement;
54
+ readonly update: SqlStatement;
55
+ }
56
+
57
+ export function writeContractMarker(input: WriteMarkerInput): WriteContractMarkerStatements {
58
+ const baseParams: readonly unknown[] = [
59
+ 1,
60
+ input.coreHash,
61
+ input.profileHash,
62
+ input.contractJson ?? null,
63
+ input.canonicalVersion ?? null,
64
+ input.appTag ?? null,
65
+ JSON.stringify(input.meta ?? {}),
66
+ ];
67
+
68
+ const insert: SqlStatement = {
69
+ sql: `insert into prisma_contract.marker (
70
+ id,
71
+ core_hash,
72
+ profile_hash,
73
+ contract_json,
74
+ canonical_version,
75
+ updated_at,
76
+ app_tag,
77
+ meta
78
+ ) values (
79
+ $1,
80
+ $2,
81
+ $3,
82
+ $4::jsonb,
83
+ $5,
84
+ now(),
85
+ $6,
86
+ $7::jsonb
87
+ )`,
88
+ params: baseParams,
89
+ };
90
+
91
+ const update: SqlStatement = {
92
+ sql: `update prisma_contract.marker set
93
+ core_hash = $2,
94
+ profile_hash = $3,
95
+ contract_json = $4::jsonb,
96
+ canonical_version = $5,
97
+ updated_at = now(),
98
+ app_tag = $6,
99
+ meta = $7::jsonb
100
+ where id = $1`,
101
+ params: baseParams,
102
+ };
103
+
104
+ return { insert, update };
105
+ }
@@ -0,0 +1,166 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type { OperationRegistry } from '@prisma-next/operations';
3
+ import type {
4
+ Log,
5
+ Plugin,
6
+ RuntimeCore,
7
+ RuntimeCoreOptions,
8
+ RuntimeTelemetryEvent,
9
+ RuntimeVerifyOptions,
10
+ TelemetryOutcome,
11
+ } from '@prisma-next/runtime-executor';
12
+ import { AsyncIterableResult, createRuntimeCore } from '@prisma-next/runtime-executor';
13
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
14
+ import type {
15
+ Adapter,
16
+ CodecRegistry,
17
+ LoweredStatement,
18
+ SelectAst,
19
+ SqlDriver,
20
+ } from '@prisma-next/sql-relational-core/ast';
21
+ import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
22
+ import { decodeRow } from './codecs/decoding';
23
+ import { encodeParams } from './codecs/encoding';
24
+ import { validateCodecRegistryCompleteness } from './codecs/validation';
25
+ import { lowerSqlPlan } from './lower-sql-plan';
26
+ import type { RuntimeContext } from './sql-context';
27
+ import { SqlFamilyAdapter } from './sql-family-adapter';
28
+
29
+ export interface RuntimeOptions<
30
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
31
+ > {
32
+ readonly driver: SqlDriver;
33
+ readonly verify: RuntimeVerifyOptions;
34
+ readonly context: RuntimeContext<TContract>;
35
+ readonly plugins?: readonly Plugin<
36
+ TContract,
37
+ Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
38
+ SqlDriver
39
+ >[];
40
+ readonly mode?: 'strict' | 'permissive';
41
+ readonly log?: Log;
42
+ }
43
+
44
+ export interface Runtime {
45
+ execute<Row = Record<string, unknown>>(
46
+ plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
47
+ ): AsyncIterableResult<Row>;
48
+ telemetry(): RuntimeTelemetryEvent | null;
49
+ close(): Promise<void>;
50
+ operations(): OperationRegistry;
51
+ }
52
+
53
+ export type { RuntimeTelemetryEvent, RuntimeVerifyOptions, TelemetryOutcome };
54
+
55
+ class SqlRuntimeImpl<TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>>
56
+ implements Runtime
57
+ {
58
+ private readonly core: RuntimeCore<
59
+ TContract,
60
+ Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
61
+ SqlDriver
62
+ >;
63
+ private readonly contract: TContract;
64
+ private readonly context: RuntimeContext<TContract>;
65
+ private readonly codecRegistry: CodecRegistry;
66
+ private codecRegistryValidated: boolean;
67
+
68
+ constructor(options: RuntimeOptions<TContract>) {
69
+ const { context, driver, verify, plugins, mode, log } = options;
70
+ this.contract = context.contract;
71
+ this.context = context;
72
+ this.codecRegistry = context.codecs;
73
+ this.codecRegistryValidated = false;
74
+
75
+ const familyAdapter = new SqlFamilyAdapter(context.contract);
76
+
77
+ const coreOptions: RuntimeCoreOptions<
78
+ TContract,
79
+ Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
80
+ SqlDriver
81
+ > = {
82
+ familyAdapter,
83
+ driver,
84
+ verify,
85
+ plugins: plugins as readonly Plugin<
86
+ TContract,
87
+ Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
88
+ SqlDriver
89
+ >[],
90
+ ...(mode !== undefined ? { mode } : {}),
91
+ ...(log !== undefined ? { log } : {}),
92
+ operationRegistry: context.operations,
93
+ };
94
+
95
+ this.core = createRuntimeCore(coreOptions);
96
+
97
+ if (verify.mode === 'startup') {
98
+ validateCodecRegistryCompleteness(this.codecRegistry, context.contract);
99
+ this.codecRegistryValidated = true;
100
+ }
101
+ }
102
+
103
+ private ensureCodecRegistryValidated(contract: SqlContract<SqlStorage>): void {
104
+ if (!this.codecRegistryValidated) {
105
+ validateCodecRegistryCompleteness(this.codecRegistry, contract);
106
+ this.codecRegistryValidated = true;
107
+ }
108
+ }
109
+
110
+ execute<Row = Record<string, unknown>>(
111
+ plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
112
+ ): AsyncIterableResult<Row> {
113
+ this.ensureCodecRegistryValidated(this.contract);
114
+
115
+ // Check if plan is SqlQueryPlan (has ast but no sql)
116
+ const isSqlQueryPlan = (p: ExecutionPlan<Row> | SqlQueryPlan<Row>): p is SqlQueryPlan<Row> => {
117
+ return 'ast' in p && !('sql' in p);
118
+ };
119
+
120
+ // Lower SqlQueryPlan to Plan if needed
121
+ const executablePlan: ExecutionPlan<Row> = isSqlQueryPlan(plan)
122
+ ? lowerSqlPlan(this.context, plan)
123
+ : plan;
124
+
125
+ const iterator = async function* (
126
+ self: SqlRuntimeImpl<TContract>,
127
+ ): AsyncGenerator<Row, void, unknown> {
128
+ const encodedParams = encodeParams(executablePlan, self.codecRegistry);
129
+ const planWithEncodedParams: ExecutionPlan<Row> = {
130
+ ...executablePlan,
131
+ params: encodedParams,
132
+ };
133
+
134
+ const coreIterator = self.core.execute(planWithEncodedParams);
135
+
136
+ for await (const rawRow of coreIterator) {
137
+ const decodedRow = decodeRow(
138
+ rawRow as Record<string, unknown>,
139
+ executablePlan,
140
+ self.codecRegistry,
141
+ );
142
+ yield decodedRow as Row;
143
+ }
144
+ };
145
+
146
+ return new AsyncIterableResult(iterator(this));
147
+ }
148
+
149
+ telemetry(): RuntimeTelemetryEvent | null {
150
+ return this.core.telemetry();
151
+ }
152
+
153
+ operations(): OperationRegistry {
154
+ return this.core.operations();
155
+ }
156
+
157
+ close(): Promise<void> {
158
+ return this.core.close();
159
+ }
160
+ }
161
+
162
+ export function createRuntime<TContract extends SqlContract<SqlStorage>>(
163
+ options: RuntimeOptions<TContract>,
164
+ ): Runtime {
165
+ return new SqlRuntimeImpl(options);
166
+ }