@prisma-next/sql-runtime 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.
- package/dist/{exports-CwCgOv6w.mjs → exports-Cq_9ZrU4.mjs} +106 -37
- package/dist/exports-Cq_9ZrU4.mjs.map +1 -0
- package/dist/index-Df2GsLSH.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test/utils.mjs +4 -4
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +11 -11
- package/src/codecs/decoding.ts +63 -14
- package/src/codecs/encoding.ts +38 -5
- package/src/sql-runtime.ts +63 -16
- package/dist/exports-CwCgOv6w.mjs.map +0 -1
package/dist/test/utils.mjs.map
CHANGED
|
@@ -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.
|
|
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/contract": "0.5.0-dev.
|
|
10
|
-
"@prisma-next/
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/sql-
|
|
16
|
-
"@prisma-next/sql-
|
|
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,9 +21,9 @@
|
|
|
21
21
|
"tsdown": "0.18.4",
|
|
22
22
|
"typescript": "5.9.3",
|
|
23
23
|
"vitest": "4.0.17",
|
|
24
|
+
"@prisma-next/tsdown": "0.0.0",
|
|
24
25
|
"@prisma-next/test-utils": "0.0.1",
|
|
25
|
-
"@prisma-next/tsconfig": "0.0.0"
|
|
26
|
-
"@prisma-next/tsdown": "0.0.0"
|
|
26
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
package/src/codecs/decoding.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 =
|
|
188
|
+
const codec = decodeCtx.codecs.get(alias);
|
|
172
189
|
if (!codec) {
|
|
173
190
|
return wireValue;
|
|
174
191
|
}
|
|
175
192
|
|
|
176
|
-
const ref =
|
|
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
|
|
253
|
+
jsonValidators: JsonSchemaValidatorRegistry | undefined,
|
|
254
|
+
rowCtx: SqlCodecCallContext,
|
|
209
255
|
): Promise<Record<string, unknown>> {
|
|
210
|
-
|
|
256
|
+
checkAborted(rowCtx, 'decode');
|
|
257
|
+
const signal = rowCtx.signal;
|
|
258
|
+
|
|
259
|
+
const decodeCtx = buildDecodeContext(plan, registry);
|
|
211
260
|
|
|
212
|
-
const aliases =
|
|
261
|
+
const aliases = decodeCtx.aliases ?? Object.keys(row);
|
|
213
262
|
|
|
214
|
-
if (
|
|
215
|
-
for (const alias of
|
|
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:
|
|
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 (
|
|
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,
|
|
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.
|
package/src/codecs/encoding.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
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
|
|
116
|
-
return Object.freeze(
|
|
148
|
+
const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'encode');
|
|
149
|
+
return Object.freeze(settled);
|
|
117
150
|
}
|
package/src/sql-runtime.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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) {
|