@prisma-next/sql-runtime 0.5.0-dev.28 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":["collectAsync"],"sources":["../../test/utils.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport { coreHash, profileHash } from '@prisma-next/contract/types';\nimport {\n instantiateExecutionStack,\n type RuntimeDriverDescriptor,\n} from '@prisma-next/framework-components/execution';\nimport type { ResultType } from '@prisma-next/framework-components/runtime';\nimport { builtinGeneratorIds } from '@prisma-next/ids';\nimport { generateId } from '@prisma-next/ids/runtime';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { Adapter, LoweredStatement, SelectAst } from '@prisma-next/sql-relational-core/ast';\nimport { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';\nimport type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';\nimport { collectAsync, drainAsyncIterable } from '@prisma-next/test-utils';\nimport type { Client } from 'pg';\nimport type { SqlStatement } from '../src/exports';\nimport {\n createExecutionContext,\n type createRuntime,\n createSqlExecutionStack,\n ensureSchemaStatement,\n ensureTableStatement,\n parseContractMarkerRow,\n writeContractMarker,\n} from '../src/exports';\nimport type {\n ExecutionContext,\n SqlRuntimeAdapterDescriptor,\n SqlRuntimeAdapterInstance,\n SqlRuntimeDriverInstance,\n SqlRuntimeExtensionDescriptor,\n SqlRuntimeTargetDescriptor,\n} from '../src/sql-context';\n\nfunction createTestMutationDefaultGenerators() {\n return builtinGeneratorIds.map((id) => ({\n id,\n generate: (params?: Record<string, unknown>) => generateId(params ? { id, params } : { id }),\n }));\n}\n\n/**\n * Executes a plan and collects all results into an array.\n * This helper DRYs up the common pattern of executing plans in tests.\n * The return type is inferred from the plan's type parameter.\n */\nexport async function executePlanAndCollect<\n P extends SqlExecutionPlan<ResultType<P>> | SqlQueryPlan<ResultType<P>>,\n>(runtime: ReturnType<typeof createRuntime>, plan: P): Promise<ResultType<P>[]> {\n type Row = ResultType<P>;\n return collectAsync<Row>(runtime.execute<Row>(plan));\n}\n\n/**\n * Drains a plan execution, consuming all results without collecting them.\n * Useful for testing side effects without memory overhead.\n */\nexport async function drainPlanExecution(\n runtime: ReturnType<typeof createRuntime>,\n plan: SqlExecutionPlan | SqlQueryPlan<unknown>,\n): Promise<void> {\n return drainAsyncIterable(runtime.execute(plan));\n}\n\n/**\n * Executes a SQL statement on a database client.\n */\nexport async function executeStatement(client: Client, statement: SqlStatement): Promise<void> {\n if (statement.params.length > 0) {\n await client.query(statement.sql, [...statement.params]);\n return;\n }\n\n await client.query(statement.sql);\n}\n\n/**\n * Sets up database schema and data, then writes the contract marker.\n * This helper DRYs up the common pattern of database setup in tests.\n */\nexport async function setupTestDatabase(\n client: Client,\n contract: Contract<SqlStorage>,\n setupFn: (client: Client) => Promise<void>,\n): Promise<void> {\n await client.query('drop schema if exists prisma_contract cascade');\n await client.query('create schema if not exists public');\n\n await setupFn(client);\n\n await executeStatement(client, ensureSchemaStatement);\n await executeStatement(client, ensureTableStatement);\n const write = writeContractMarker({\n storageHash: contract.storage.storageHash,\n profileHash: contract.profileHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Writes a contract marker to the database.\n * This helper DRYs up the common pattern of writing contract markers in tests.\n */\nexport async function writeTestContractMarker(\n client: Client,\n contract: Contract<SqlStorage>,\n): Promise<void> {\n const write = writeContractMarker({\n storageHash: contract.storage.storageHash,\n profileHash: contract.profileHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Creates a test adapter descriptor from a raw adapter.\n * Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions\n * derived from the adapter's codec registry.\n */\nexport function createTestAdapterDescriptor(\n adapter: Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement>,\n): SqlRuntimeAdapterDescriptor<'postgres'> {\n const codecRegistry = adapter.profile.codecs();\n return {\n kind: 'adapter' as const,\n id: 'test-adapter',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => codecRegistry,\n parameterizedCodecs: () => [],\n mutationDefaultGenerators: createTestMutationDefaultGenerators,\n create(_stack): SqlRuntimeAdapterInstance<'postgres'> {\n return Object.assign({ familyId: 'sql' as const, targetId: 'postgres' as const }, adapter);\n },\n };\n}\n\n/**\n * Creates a test target descriptor with empty static contributions.\n */\nexport function createTestTargetDescriptor(): SqlRuntimeTargetDescriptor<'postgres'> {\n return {\n kind: 'target' as const,\n id: 'postgres',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => createCodecRegistry(),\n parameterizedCodecs: () => [],\n create() {\n return { familyId: 'sql' as const, targetId: 'postgres' as const };\n },\n };\n}\n\n/**\n * Creates an ExecutionContext for testing.\n * This helper DRYs up the common pattern of context creation in tests.\n *\n * Accepts a raw adapter and optional extension descriptors, wrapping the\n * adapter in a descriptor internally for descriptor-first context creation.\n */\nexport function createTestContext<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n adapter: Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement>,\n options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n },\n): ExecutionContext<TContract> {\n return createExecutionContext({\n contract,\n stack: {\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(adapter),\n extensionPacks: options?.extensionPacks ?? [],\n },\n });\n}\n\nexport function createTestStackInstance(options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n driver?: RuntimeDriverDescriptor<\n 'sql',\n 'postgres',\n unknown,\n SqlRuntimeDriverInstance<'postgres'>\n >;\n}) {\n const stack = createSqlExecutionStack({\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(createStubAdapter()),\n driver: options?.driver,\n extensionPacks: options?.extensionPacks ?? [],\n });\n\n return instantiateExecutionStack(stack);\n}\n\n/**\n * Creates a stub adapter for testing.\n * This helper DRYs up the common pattern of adapter creation in tests.\n *\n * The stub adapter includes simple codecs for common test types (pg/int4@1, pg/text@1, pg/timestamptz@1)\n * to enable type inference in tests without requiring the postgres adapter package.\n */\nexport function createStubAdapter(): Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement> {\n const codecRegistry = createCodecRegistry();\n\n // Register stub codecs for common test types\n // These match the codec IDs used in test contracts (pg/int4@1, pg/text@1, pg/timestamptz@1)\n // but don't require importing from the postgres adapter package\n codecRegistry.register(\n codec({\n typeId: 'pg/int4@1',\n targetTypes: ['int4'],\n encode: (value: number) => value,\n decode: (wire: number) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/text@1',\n targetTypes: ['text'],\n encode: (value: string) => value,\n decode: (wire: string) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/timestamptz@1',\n targetTypes: ['timestamptz'],\n encode: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),\n decode: (wire: string | Date) => (wire instanceof Date ? wire : new Date(wire)),\n // string | Date includes Date which is not assignable to JsonValue, so\n // the JSON round-trip pair must be supplied explicitly.\n encodeJson: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),\n decodeJson: (json) => {\n if (typeof json !== 'string') throw new Error('expected ISO date string');\n return new Date(json);\n },\n }),\n );\n\n return {\n profile: {\n id: 'stub-profile',\n target: 'postgres',\n capabilities: {},\n codecs() {\n return codecRegistry;\n },\n readMarkerStatement() {\n return {\n sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where id = $1',\n params: [1],\n };\n },\n parseMarkerRow: parseContractMarkerRow,\n },\n lower(ast: SelectAst, ctx: { contract: Contract<SqlStorage>; params?: readonly unknown[] }) {\n const sqlText = JSON.stringify(ast);\n return Object.freeze({ sql: sqlText, params: ctx.params ? [...ctx.params] : [] });\n },\n };\n}\n\nexport function createTestContract(\n contract: Partial<Omit<Contract<SqlStorage>, 'profileHash' | 'storage'>> & {\n storageHash?: string;\n profileHash?: string;\n storage?: Omit<SqlStorage, 'storageHash'>;\n },\n): Contract<SqlStorage> {\n const { execution, ...rest } = contract;\n const storageHashValue = coreHash(rest['storageHash'] ?? 'sha256:testcore');\n\n return {\n target: rest['target'] ?? 'postgres',\n targetFamily: rest['targetFamily'] ?? 'sql',\n storage: rest['storage']\n ? { ...rest['storage'], storageHash: storageHashValue }\n : { storageHash: storageHashValue, tables: {} },\n models: rest['models'] ?? {},\n roots: rest['roots'] ?? {},\n capabilities: rest['capabilities'] ?? {},\n extensionPacks: rest['extensionPacks'] ?? {},\n meta: rest['meta'] ?? {},\n ...(execution ? { execution } : {}),\n profileHash: profileHash(rest['profileHash'] ?? 'sha256:testprofile'),\n };\n}\n\n// Re-export generic utilities from test-utils\nexport {\n collectAsync,\n createDevDatabase,\n type DevDatabase,\n teardownTestDatabase,\n withClient,\n} from '@prisma-next/test-utils';\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.mjs","names":["collectAsync"],"sources":["../../test/utils.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport { coreHash, profileHash } from '@prisma-next/contract/types';\nimport {\n instantiateExecutionStack,\n type RuntimeDriverDescriptor,\n} from '@prisma-next/framework-components/execution';\nimport type { ResultType } from '@prisma-next/framework-components/runtime';\nimport { builtinGeneratorIds } from '@prisma-next/ids';\nimport { generateId } from '@prisma-next/ids/runtime';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { Adapter, LoweredStatement, SelectAst } from '@prisma-next/sql-relational-core/ast';\nimport { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';\nimport type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';\nimport { collectAsync, drainAsyncIterable } from '@prisma-next/test-utils';\nimport type { Client } from 'pg';\nimport type { SqlStatement } from '../src/exports';\nimport {\n createExecutionContext,\n type createRuntime,\n createSqlExecutionStack,\n ensureSchemaStatement,\n ensureTableStatement,\n parseContractMarkerRow,\n writeContractMarker,\n} from '../src/exports';\nimport type {\n ExecutionContext,\n SqlRuntimeAdapterDescriptor,\n SqlRuntimeAdapterInstance,\n SqlRuntimeDriverInstance,\n SqlRuntimeExtensionDescriptor,\n SqlRuntimeTargetDescriptor,\n} from '../src/sql-context';\n\nfunction createTestMutationDefaultGenerators() {\n return builtinGeneratorIds.map((id) => ({\n id,\n generate: (params?: Record<string, unknown>) => generateId(params ? { id, params } : { id }),\n }));\n}\n\n/**\n * Executes a plan and collects all results into an array.\n * This helper DRYs up the common pattern of executing plans in tests.\n * The return type is inferred from the plan's type parameter.\n */\nexport async function executePlanAndCollect<\n P extends SqlExecutionPlan<ResultType<P>> | SqlQueryPlan<ResultType<P>>,\n>(runtime: ReturnType<typeof createRuntime>, plan: P): Promise<ResultType<P>[]> {\n type Row = ResultType<P>;\n return collectAsync<Row>(runtime.execute<Row>(plan));\n}\n\n/**\n * Drains a plan execution, consuming all results without collecting them.\n * Useful for testing side effects without memory overhead.\n */\nexport async function drainPlanExecution(\n runtime: ReturnType<typeof createRuntime>,\n plan: SqlExecutionPlan | SqlQueryPlan<unknown>,\n): Promise<void> {\n return drainAsyncIterable(runtime.execute(plan));\n}\n\n/**\n * Executes a SQL statement on a database client.\n */\nexport async function executeStatement(client: Client, statement: SqlStatement): Promise<void> {\n if (statement.params.length > 0) {\n await client.query(statement.sql, [...statement.params]);\n return;\n }\n\n await client.query(statement.sql);\n}\n\n/**\n * Sets up database schema and data, then writes the contract marker.\n * This helper DRYs up the common pattern of database setup in tests.\n */\nexport async function setupTestDatabase(\n client: Client,\n contract: Contract<SqlStorage>,\n setupFn: (client: Client) => Promise<void>,\n): Promise<void> {\n await client.query('drop schema if exists prisma_contract cascade');\n await client.query('create schema if not exists public');\n\n await setupFn(client);\n\n await executeStatement(client, ensureSchemaStatement);\n await executeStatement(client, ensureTableStatement);\n const write = writeContractMarker({\n storageHash: contract.storage.storageHash,\n profileHash: contract.profileHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Writes a contract marker to the database.\n * This helper DRYs up the common pattern of writing contract markers in tests.\n */\nexport async function writeTestContractMarker(\n client: Client,\n contract: Contract<SqlStorage>,\n): Promise<void> {\n const write = writeContractMarker({\n storageHash: contract.storage.storageHash,\n profileHash: contract.profileHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Creates a test adapter descriptor from a raw adapter.\n * Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions\n * derived from the adapter's codec registry.\n */\nexport function createTestAdapterDescriptor(\n adapter: Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement>,\n): SqlRuntimeAdapterDescriptor<'postgres'> {\n const codecRegistry = adapter.profile.codecs();\n return {\n kind: 'adapter' as const,\n id: 'test-adapter',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => codecRegistry,\n parameterizedCodecs: () => [],\n mutationDefaultGenerators: createTestMutationDefaultGenerators,\n create(_stack): SqlRuntimeAdapterInstance<'postgres'> {\n return Object.assign({ familyId: 'sql' as const, targetId: 'postgres' as const }, adapter);\n },\n };\n}\n\n/**\n * Creates a test target descriptor with empty static contributions.\n */\nexport function createTestTargetDescriptor(): SqlRuntimeTargetDescriptor<'postgres'> {\n return {\n kind: 'target' as const,\n id: 'postgres',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => createCodecRegistry(),\n parameterizedCodecs: () => [],\n create() {\n return { familyId: 'sql' as const, targetId: 'postgres' as const };\n },\n };\n}\n\n/**\n * Creates an ExecutionContext for testing.\n * This helper DRYs up the common pattern of context creation in tests.\n *\n * Accepts a raw adapter and optional extension descriptors, wrapping the\n * adapter in a descriptor internally for descriptor-first context creation.\n */\nexport function createTestContext<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n adapter: Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement>,\n options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n },\n): ExecutionContext<TContract> {\n return createExecutionContext({\n contract,\n stack: {\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(adapter),\n extensionPacks: options?.extensionPacks ?? [],\n },\n });\n}\n\nexport function createTestStackInstance(options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n driver?: RuntimeDriverDescriptor<\n 'sql',\n 'postgres',\n unknown,\n SqlRuntimeDriverInstance<'postgres'>\n >;\n}) {\n const stack = createSqlExecutionStack({\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(createStubAdapter()),\n driver: options?.driver,\n extensionPacks: options?.extensionPacks ?? [],\n });\n\n return instantiateExecutionStack(stack);\n}\n\n/**\n * Creates a stub adapter for testing.\n * This helper DRYs up the common pattern of adapter creation in tests.\n *\n * The stub adapter includes simple codecs for common test types (pg/int4@1, pg/text@1, pg/timestamptz@1)\n * to enable type inference in tests without requiring the postgres adapter package.\n */\nexport function createStubAdapter(): Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement> {\n const codecRegistry = createCodecRegistry();\n\n // Register stub codecs for common test types\n // These match the codec IDs used in test contracts (pg/int4@1, pg/text@1, pg/timestamptz@1)\n // but don't require importing from the postgres adapter package\n codecRegistry.register(\n codec({\n typeId: 'pg/int4@1',\n targetTypes: ['int4'],\n encode: (value: number) => value,\n decode: (wire: number) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/text@1',\n targetTypes: ['text'],\n encode: (value: string) => value,\n decode: (wire: string) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/timestamptz@1',\n targetTypes: ['timestamptz'],\n encode: (value: Date) => value,\n decode: (wire: Date) => wire,\n // Date is not assignable to JsonValue, so the JSON round-trip pair\n // must be supplied explicitly.\n encodeJson: (value: Date) => value.toISOString(),\n decodeJson: (json) => {\n if (typeof json !== 'string') throw new Error('expected ISO date string');\n return new Date(json);\n },\n }),\n );\n\n return {\n profile: {\n id: 'stub-profile',\n target: 'postgres',\n capabilities: {},\n codecs() {\n return codecRegistry;\n },\n readMarkerStatement() {\n return {\n sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where id = $1',\n params: [1],\n };\n },\n parseMarkerRow: parseContractMarkerRow,\n },\n lower(ast: SelectAst, ctx: { contract: Contract<SqlStorage>; params?: readonly unknown[] }) {\n const sqlText = JSON.stringify(ast);\n return Object.freeze({ sql: sqlText, params: ctx.params ? [...ctx.params] : [] });\n },\n };\n}\n\nexport function createTestContract(\n contract: Partial<Omit<Contract<SqlStorage>, 'profileHash' | 'storage'>> & {\n storageHash?: string;\n profileHash?: string;\n storage?: Omit<SqlStorage, 'storageHash'>;\n },\n): Contract<SqlStorage> {\n const { execution, ...rest } = contract;\n const storageHashValue = coreHash(rest['storageHash'] ?? 'sha256:testcore');\n\n return {\n target: rest['target'] ?? 'postgres',\n targetFamily: rest['targetFamily'] ?? 'sql',\n storage: rest['storage']\n ? { ...rest['storage'], storageHash: storageHashValue }\n : { storageHash: storageHashValue, tables: {} },\n models: rest['models'] ?? {},\n roots: rest['roots'] ?? {},\n capabilities: rest['capabilities'] ?? {},\n extensionPacks: rest['extensionPacks'] ?? {},\n meta: rest['meta'] ?? {},\n ...(execution ? { execution } : {}),\n profileHash: profileHash(rest['profileHash'] ?? 'sha256:testprofile'),\n };\n}\n\n// Re-export generic utilities from test-utils\nexport {\n collectAsync,\n createDevDatabase,\n type DevDatabase,\n teardownTestDatabase,\n withClient,\n} from '@prisma-next/test-utils';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-runtime",
3
- "version": "0.5.0-dev.28",
3
+ "version": "0.5.0-dev.29",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "SQL runtime implementation for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.26",
9
- "@prisma-next/framework-components": "0.5.0-dev.28",
10
- "@prisma-next/contract": "0.5.0-dev.28",
11
- "@prisma-next/ids": "0.5.0-dev.28",
12
- "@prisma-next/utils": "0.5.0-dev.28",
13
- "@prisma-next/sql-contract": "0.5.0-dev.28",
14
- "@prisma-next/operations": "0.5.0-dev.28",
15
- "@prisma-next/sql-operations": "0.5.0-dev.28",
16
- "@prisma-next/sql-relational-core": "0.5.0-dev.28"
9
+ "@prisma-next/contract": "0.5.0-dev.29",
10
+ "@prisma-next/framework-components": "0.5.0-dev.29",
11
+ "@prisma-next/utils": "0.5.0-dev.29",
12
+ "@prisma-next/ids": "0.5.0-dev.29",
13
+ "@prisma-next/operations": "0.5.0-dev.29",
14
+ "@prisma-next/sql-contract": "0.5.0-dev.29",
15
+ "@prisma-next/sql-relational-core": "0.5.0-dev.29",
16
+ "@prisma-next/sql-operations": "0.5.0-dev.29"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/pg": "8.16.0",
@@ -21,8 +21,8 @@
21
21
  "tsdown": "0.18.4",
22
22
  "typescript": "5.9.3",
23
23
  "vitest": "4.0.17",
24
- "@prisma-next/test-utils": "0.0.1",
25
24
  "@prisma-next/tsdown": "0.0.0",
25
+ "@prisma-next/test-utils": "0.0.1",
26
26
  "@prisma-next/tsconfig": "0.0.0"
27
27
  },
28
28
  "files": [
@@ -1,9 +1,15 @@
1
- import { isRuntimeError, runtimeError } from '@prisma-next/framework-components/runtime';
1
+ import {
2
+ checkAborted,
3
+ isRuntimeError,
4
+ raceAgainstAbort,
5
+ runtimeError,
6
+ } from '@prisma-next/framework-components/runtime';
2
7
  import type {
3
8
  AnyQueryAst,
4
9
  Codec,
5
10
  CodecRegistry,
6
11
  ProjectionItem,
12
+ SqlCodecCallContext,
7
13
  } from '@prisma-next/sql-relational-core/ast';
8
14
  import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
9
15
  import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
@@ -157,27 +163,55 @@ function decodeIncludeAggregate(alias: string, wireValue: unknown): unknown {
157
163
  * Decodes a single field. Single-armed: every cell takes the same path —
158
164
  * `codec.decode → await → JSON-Schema validate → return plain value` — so
159
165
  * sync- and async-authored codecs are indistinguishable to callers.
166
+ *
167
+ * The row-level `rowCtx` is repackaged into a per-cell
168
+ * `SqlCodecCallContext` whose `column = { table, name }` is a structural
169
+ * projection of the per-cell `ColumnRef = { table, column }` resolved from
170
+ * the AST-backed `DecodeContext` (the same resolution `wrapDecodeFailure`
171
+ * uses for envelope construction — one resolution per cell, two consumers).
172
+ * Cells the runtime cannot resolve to a single underlying column (aggregate
173
+ * aliases, computed projections without a simple ref) get
174
+ * `column: undefined`, matching the spec contract that the runtime never
175
+ * silently defaults this field.
160
176
  */
161
177
  async function decodeField(
162
178
  alias: string,
163
179
  wireValue: unknown,
164
- ctx: DecodeContext,
180
+ decodeCtx: DecodeContext,
165
181
  jsonValidators: JsonSchemaValidatorRegistry | undefined,
182
+ rowCtx: SqlCodecCallContext,
166
183
  ): Promise<unknown> {
167
184
  if (wireValue === null) {
168
185
  return null;
169
186
  }
170
187
 
171
- const codec = ctx.codecs.get(alias);
188
+ const codec = decodeCtx.codecs.get(alias);
172
189
  if (!codec) {
173
190
  return wireValue;
174
191
  }
175
192
 
176
- const ref = ctx.columnRefs.get(alias);
193
+ const ref = decodeCtx.columnRefs.get(alias);
194
+
195
+ // Per-cell ctx: the cell-level `column` is a `SqlColumnRef = { table, name }`
196
+ // projection of the resolved `ColumnRef = { table, column }` (same
197
+ // resolution `wrapDecodeFailure` uses below — no double work). Cells the
198
+ // runtime cannot resolve (aggregate aliases, computed projections without
199
+ // a simple ref) drop the `column` field entirely — explicitly cleared so
200
+ // a previously-populated `rowCtx.column` cannot leak through to unrelated
201
+ // cells. Destructuring (rather than `column: undefined`) is required
202
+ // because `SqlCodecCallContext.column` is declared `column?: SqlColumnRef`
203
+ // under `exactOptionalPropertyTypes`.
204
+ let cellCtx: SqlCodecCallContext;
205
+ if (ref) {
206
+ cellCtx = { ...rowCtx, column: { table: ref.table, name: ref.column } };
207
+ } else {
208
+ const { column: _drop, ...rowCtxWithoutColumn } = rowCtx;
209
+ cellCtx = rowCtxWithoutColumn;
210
+ }
177
211
 
178
212
  let decoded: unknown;
179
213
  try {
180
- decoded = await codec.decode(wireValue);
214
+ decoded = await codec.decode(wireValue, cellCtx);
181
215
  } catch (error) {
182
216
  wrapDecodeFailure(error, alias, ref, codec, wireValue);
183
217
  }
@@ -200,23 +234,38 @@ async function decodeField(
200
234
  * Failures are wrapped in `RUNTIME.DECODE_FAILED` with `{ table, column,
201
235
  * codec }` (or `{ alias, codec }` when no column ref is resolvable) and the
202
236
  * original error attached on `cause`.
237
+ *
238
+ * When `rowCtx.signal` is provided:
239
+ *
240
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
241
+ * (`{ phase: 'decode' }`) before any `codec.decode` call is made.
242
+ * - **Mid-flight aborts** race the per-cell `Promise.all` against the
243
+ * signal so the runtime returns promptly even when codec bodies ignore
244
+ * it. In-flight bodies that ignore the signal complete in the
245
+ * background (cooperative cancellation).
246
+ * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass
247
+ * through unchanged (no double wrap).
203
248
  */
204
249
  export async function decodeRow(
205
250
  row: Record<string, unknown>,
206
251
  plan: SqlExecutionPlan,
207
252
  registry: CodecRegistry,
208
- jsonValidators?: JsonSchemaValidatorRegistry,
253
+ jsonValidators: JsonSchemaValidatorRegistry | undefined,
254
+ rowCtx: SqlCodecCallContext,
209
255
  ): Promise<Record<string, unknown>> {
210
- const ctx = buildDecodeContext(plan, registry);
256
+ checkAborted(rowCtx, 'decode');
257
+ const signal = rowCtx.signal;
258
+
259
+ const decodeCtx = buildDecodeContext(plan, registry);
211
260
 
212
- const aliases = ctx.aliases ?? Object.keys(row);
261
+ const aliases = decodeCtx.aliases ?? Object.keys(row);
213
262
 
214
- if (ctx.aliases !== undefined) {
215
- for (const alias of ctx.aliases) {
263
+ if (decodeCtx.aliases !== undefined) {
264
+ for (const alias of decodeCtx.aliases) {
216
265
  if (!Object.hasOwn(row, alias)) {
217
266
  throw runtimeError('RUNTIME.DECODE_FAILED', `Row missing projection alias "${alias}"`, {
218
267
  alias,
219
- expectedAliases: ctx.aliases,
268
+ expectedAliases: decodeCtx.aliases,
220
269
  presentKeys: Object.keys(row),
221
270
  });
222
271
  }
@@ -230,16 +279,16 @@ export async function decodeRow(
230
279
  const alias = aliases[i] as string;
231
280
  const wireValue = row[alias];
232
281
 
233
- if (ctx.includeAliases.has(alias)) {
282
+ if (decodeCtx.includeAliases.has(alias)) {
234
283
  includeIndices.push({ index: i, alias, value: wireValue });
235
284
  tasks.push(Promise.resolve(undefined));
236
285
  continue;
237
286
  }
238
287
 
239
- tasks.push(decodeField(alias, wireValue, ctx, jsonValidators));
288
+ tasks.push(decodeField(alias, wireValue, decodeCtx, jsonValidators, rowCtx));
240
289
  }
241
290
 
242
- const settled = await Promise.all(tasks);
291
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'decode');
243
292
 
244
293
  // Include aggregates are decoded synchronously after concurrent codec
245
294
  // dispatch settles, so any decode failures upstream propagate first.
@@ -1,8 +1,13 @@
1
- import { runtimeError } from '@prisma-next/framework-components/runtime';
1
+ import {
2
+ checkAborted,
3
+ raceAgainstAbort,
4
+ runtimeError,
5
+ } from '@prisma-next/framework-components/runtime';
2
6
  import {
3
7
  type Codec,
4
8
  type CodecRegistry,
5
9
  collectOrderedParamRefs,
10
+ type SqlCodecCallContext,
6
11
  } from '@prisma-next/sql-relational-core/ast';
7
12
  import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
8
13
 
@@ -40,18 +45,27 @@ function wrapEncodeFailure(
40
45
  * lifted to async by the codec() factory. Failures are wrapped in
41
46
  * `RUNTIME.ENCODE_FAILED` with `{ label, codec, paramIndex }` and the original
42
47
  * error attached on `cause`.
48
+ *
49
+ * `ctx` is forwarded verbatim to `codec.encode` so codec authors who opt
50
+ * into the `(value, ctx)` arity see the same `SqlCodecCallContext` the
51
+ * runtime built for the surrounding `runtime.execute()` call. The ctx is
52
+ * always present; its `signal` field may be `undefined`. Encode call
53
+ * sites do not populate `ctx.column` — encode-time column context is the
54
+ * middleware's domain.
43
55
  */
44
56
  export async function encodeParam(
45
57
  value: unknown,
46
58
  paramRef: { readonly codecId?: string; readonly name?: string },
47
59
  paramIndex: number,
48
60
  registry: CodecRegistry,
61
+ ctx: SqlCodecCallContext,
49
62
  ): Promise<unknown> {
50
63
  return encodeParamValue(
51
64
  value,
52
65
  { codecId: paramRef.codecId, name: paramRef.name },
53
66
  paramIndex,
54
67
  registry,
68
+ ctx,
55
69
  );
56
70
  }
57
71
 
@@ -60,6 +74,7 @@ async function encodeParamValue(
60
74
  metadata: ParamMetadata,
61
75
  paramIndex: number,
62
76
  registry: CodecRegistry,
77
+ ctx: SqlCodecCallContext,
63
78
  ): Promise<unknown> {
64
79
  if (value === null || value === undefined) {
65
80
  return null;
@@ -75,7 +90,7 @@ async function encodeParamValue(
75
90
  }
76
91
 
77
92
  try {
78
- return await codec.encode(value);
93
+ return await codec.encode(value, ctx);
79
94
  } catch (error) {
80
95
  wrapEncodeFailure(error, metadata, paramIndex, codec.id);
81
96
  }
@@ -85,11 +100,29 @@ async function encodeParamValue(
85
100
  * Encodes all parameters concurrently via `Promise.all`. Per parameter, sync-
86
101
  * and async-authored codecs share the same path: `codec.encode → await →
87
102
  * return`. Param-level failures are wrapped in `RUNTIME.ENCODE_FAILED`.
103
+ *
104
+ * When `ctx.signal` is provided:
105
+ *
106
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED`
107
+ * (`{ phase: 'encode' }`) before any `codec.encode` call is made — codecs
108
+ * can pin this with a per-call counter that stays at zero.
109
+ * - **Mid-flight abort** races the per-param `Promise.all` against
110
+ * `abortable(ctx.signal)`. The runtime returns `RUNTIME.ABORTED` promptly
111
+ * even if codec bodies ignore the signal; the in-flight bodies are
112
+ * abandoned and run to completion in the background (cooperative
113
+ * cancellation, see ADR 204).
114
+ * - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec
115
+ * body before the runtime observes the abort pass through unchanged
116
+ * (no double wrap).
88
117
  */
89
118
  export async function encodeParams(
90
119
  plan: SqlExecutionPlan,
91
120
  registry: CodecRegistry,
121
+ ctx: SqlCodecCallContext,
92
122
  ): Promise<readonly unknown[]> {
123
+ checkAborted(ctx, 'encode');
124
+ const signal = ctx.signal;
125
+
93
126
  if (plan.params.length === 0) {
94
127
  return plan.params;
95
128
  }
@@ -109,9 +142,9 @@ export async function encodeParams(
109
142
 
110
143
  const tasks: Promise<unknown>[] = new Array(paramCount);
111
144
  for (let i = 0; i < paramCount; i++) {
112
- tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, registry);
145
+ tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, registry, ctx);
113
146
  }
114
147
 
115
- const encoded = await Promise.all(tasks);
116
- return Object.freeze(encoded);
148
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'encode');
149
+ return Object.freeze(settled);
117
150
  }
@@ -5,8 +5,10 @@ import type {
5
5
  } from '@prisma-next/framework-components/execution';
6
6
  import {
7
7
  AsyncIterableResult,
8
+ checkAborted,
8
9
  checkMiddlewareCompatibility,
9
10
  RuntimeCore,
11
+ type RuntimeExecuteOptions,
10
12
  type RuntimeLog,
11
13
  runtimeError,
12
14
  runWithMiddleware,
@@ -17,6 +19,7 @@ import type {
17
19
  AnyQueryAst,
18
20
  CodecRegistry,
19
21
  LoweredStatement,
22
+ SqlCodecCallContext,
20
23
  SqlDriver,
21
24
  SqlQueryable,
22
25
  SqlTransaction,
@@ -187,12 +190,21 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
187
190
  * Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan` with
188
191
  * encoded parameters ready for the driver. This is the single point at
189
192
  * which params transition from app-layer values to driver wire-format.
193
+ *
194
+ * `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so per-query
195
+ * cancellation reaches every codec body during parameter encoding. The
196
+ * framework abstract typed this as `CodecCallContext`; the SQL family
197
+ * narrows it to the SQL-specific extension. SQL params do not populate
198
+ * `ctx.column` — encode-side column metadata is the middleware's domain.
190
199
  */
191
- protected override async lower(plan: SqlQueryPlan): Promise<SqlExecutionPlan> {
200
+ protected override async lower(
201
+ plan: SqlQueryPlan,
202
+ ctx: SqlCodecCallContext,
203
+ ): Promise<SqlExecutionPlan> {
192
204
  const lowered = lowerSqlPlan(this.adapter, this.contract, plan);
193
205
  return Object.freeze({
194
206
  ...lowered,
195
- params: await encodeParams(lowered, this.codecRegistry),
207
+ params: await encodeParams(lowered, this.codecRegistry, ctx),
196
208
  });
197
209
  }
198
210
 
@@ -231,24 +243,37 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
231
243
 
232
244
  override execute<Row>(
233
245
  plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
246
+ options?: RuntimeExecuteOptions,
234
247
  ): AsyncIterableResult<Row> {
235
- return this.executeAgainstQueryable<Row>(plan, this.driver);
248
+ return this.executeAgainstQueryable<Row>(plan, this.driver, options);
236
249
  }
237
250
 
238
251
  private executeAgainstQueryable<Row>(
239
252
  plan: SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>,
240
253
  queryable: SqlQueryable,
254
+ options?: RuntimeExecuteOptions,
241
255
  ): AsyncIterableResult<Row> {
242
256
  this.ensureCodecRegistryValidated();
243
257
 
244
258
  const self = this;
259
+ const signal = options?.signal;
260
+ // One ctx per execute() call — the same reference is shared by
261
+ // encodeParams (lower), decodeRow (per-row), and the stream loop's
262
+ // between-row checks. Per-cell ctx allocations inside decodeField add
263
+ // `column` for resolvable cells without re-wrapping the signal. The
264
+ // ctx object is always allocated; the `signal` field is only included
265
+ // when a signal was supplied (exactOptionalPropertyTypes).
266
+ const codecCtx: SqlCodecCallContext = signal === undefined ? {} : { signal };
267
+
245
268
  const generator = async function* (): AsyncGenerator<Row, void, unknown> {
269
+ checkAborted(codecCtx, 'stream');
270
+
246
271
  const exec: SqlExecutionPlan = isExecutionPlan(plan)
247
272
  ? Object.freeze({
248
273
  ...plan,
249
- params: await encodeParams(plan, self.codecRegistry),
274
+ params: await encodeParams(plan, self.codecRegistry, codecCtx),
250
275
  })
251
- : await self.lower(await self.runBeforeCompile(plan));
276
+ : await self.lower(await self.runBeforeCompile(plan), codecCtx);
252
277
 
253
278
  self.familyAdapter.validatePlan(exec, self.contract);
254
279
  self._telemetry = null;
@@ -280,14 +305,33 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
280
305
  }),
281
306
  );
282
307
 
283
- for await (const rawRow of stream) {
284
- const decodedRow = await decodeRow(
285
- rawRow,
286
- exec,
287
- self.codecRegistry,
288
- self.jsonSchemaValidators,
289
- );
290
- yield decodedRow as Row;
308
+ // Manually drive the driver's async iterator so the between-row
309
+ // abort check fires *before* requesting the next row. With a
310
+ // `for await...of` loop the runtime would await `iterator.next()`
311
+ // first, leaving a window where one extra row is pulled through
312
+ // the driver after the signal aborted.
313
+ const iterator = stream[Symbol.asyncIterator]();
314
+ try {
315
+ while (true) {
316
+ checkAborted(codecCtx, 'stream');
317
+ const next = await iterator.next();
318
+ if (next.done) {
319
+ break;
320
+ }
321
+ const decodedRow = await decodeRow(
322
+ next.value,
323
+ exec,
324
+ self.codecRegistry,
325
+ self.jsonSchemaValidators,
326
+ codecCtx,
327
+ );
328
+ yield decodedRow as Row;
329
+ }
330
+ } finally {
331
+ // Best-effort iterator cleanup so the driver can release its
332
+ // resources whether the stream finished normally, threw, or was
333
+ // abandoned by the consumer.
334
+ await iterator.return?.();
291
335
  }
292
336
 
293
337
  outcome = 'success';
@@ -321,8 +365,9 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
321
365
  },
322
366
  execute<Row>(
323
367
  plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
368
+ options?: RuntimeExecuteOptions,
324
369
  ): AsyncIterableResult<Row> {
325
- return self.executeAgainstQueryable<Row>(plan, driverConn);
370
+ return self.executeAgainstQueryable<Row>(plan, driverConn, options);
326
371
  },
327
372
  };
328
373
 
@@ -340,8 +385,9 @@ class SqlRuntimeImpl<TContract extends Contract<SqlStorage> = Contract<SqlStorag
340
385
  },
341
386
  execute<Row>(
342
387
  plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
388
+ options?: RuntimeExecuteOptions,
343
389
  ): AsyncIterableResult<Row> {
344
- return self.executeAgainstQueryable<Row>(plan, driverTx);
390
+ return self.executeAgainstQueryable<Row>(plan, driverTx, options);
345
391
  },
346
392
  };
347
393
  }
@@ -455,11 +501,12 @@ export async function withTransaction<R>(
455
501
  },
456
502
  execute<Row>(
457
503
  plan: (SqlExecutionPlan<unknown> | SqlQueryPlan<unknown>) & { readonly _row?: Row },
504
+ options?: RuntimeExecuteOptions,
458
505
  ): AsyncIterableResult<Row> {
459
506
  if (invalidated) {
460
507
  throw transactionClosedError();
461
508
  }
462
- const inner = transaction.execute(plan);
509
+ const inner = transaction.execute(plan, options);
463
510
  const guarded = async function* (): AsyncGenerator<Row, void, unknown> {
464
511
  for await (const row of inner) {
465
512
  if (invalidated) {