@prisma-next/sql-runtime 0.6.0-dev.1 → 0.6.0-dev.11

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/test-codec.ts","../../test/utils.ts"],"sourcesContent":["/**\n * Test-only helper that constructs a SQL-family `Codec` instance from author-side encode/decode functions. Replaces the legacy public `mkCodec()` factory (deleted under TML-2357); tests that need a stub codec for behavioural assertions instantiate one through this helper rather than going through `descriptor.factory(...)`.\n *\n * The body is identical in spirit to the retired `mkCodec`: promise-lift sync author functions onto the framework-required `Promise<…>` boundary, default `encodeJson`/`decodeJson` to identity when `TInput` is JSON-safe, fail loudly otherwise.\n */\nimport type { JsonValue } from '@prisma-next/contract/types';\nimport type { CodecTrait } from '@prisma-next/framework-components/codec';\nimport type { Codec, SqlCodecCallContext } from '@prisma-next/sql-relational-core/ast';\n\ntype JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]\n ? {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n }\n : {\n encodeJson: (value: TInput) => JsonValue;\n decodeJson: (json: JsonValue) => TInput;\n };\n\nexport function defineTestCodec<\n Id extends string,\n const TTraits extends readonly CodecTrait[] = readonly [],\n TWire = unknown,\n TInput = unknown,\n>(\n config: {\n typeId: Id;\n targetTypes?: readonly string[];\n encode: (value: TInput, ctx: SqlCodecCallContext) => TWire | Promise<TWire>;\n decode: (wire: TWire, ctx: SqlCodecCallContext) => TInput | Promise<TInput>;\n traits?: TTraits;\n } & JsonRoundTripConfig<TInput>,\n): Codec<Id, TTraits, TWire, TInput> {\n const identity = (v: unknown) => v;\n const userEncode = config.encode;\n const userDecode = config.decode;\n const widenedConfig = config as {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n };\n return {\n id: config.typeId,\n encode: (value, ctx) => {\n try {\n return Promise.resolve(userEncode(value, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n decode: (wire, ctx) => {\n try {\n return Promise.resolve(userDecode(wire, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n encodeJson: (widenedConfig.encodeJson ?? identity) as (value: TInput) => JsonValue,\n decodeJson: (widenedConfig.decodeJson ?? identity) as (json: JsonValue) => TInput,\n } as Codec<Id, TTraits, TWire, TInput>;\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { coreHash, profileHash } from '@prisma-next/contract/types';\nimport type {\n CodecDescriptor,\n CodecMeta,\n CodecTrait,\n} from '@prisma-next/framework-components/codec';\nimport { voidParamsSchema } from '@prisma-next/framework-components/codec';\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 {\n Adapter,\n Codec,\n ContractCodecRegistry,\n LoweredStatement,\n SelectAst,\n} 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 APP_SPACE_ID,\n createExecutionContext,\n type createRuntime,\n createSqlExecutionStack,\n ensureSchemaStatement,\n ensureTableStatement,\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';\nimport { defineTestCodec } from './test-codec';\n\nfunction createTestMutationDefaultGenerators() {\n return builtinGeneratorIds.map((id) => ({\n id,\n generate: (params?: Record<string, unknown>) => generateId(params ? { id, params } : { id }),\n stability: 'field' as const,\n }));\n}\n\n/**\n * Executes a plan and collects all results into an array. This helper DRYs up the common pattern of executing plans in tests. 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. 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. 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 space: APP_SPACE_ID,\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. 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 space: APP_SPACE_ID,\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. Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions derived from the adapter's codec registry.\n */\n/**\n * Build a {@link ContractCodecRegistry} from a codec array for tests that exercise `encodeParam(s)` / `decodeRow` in isolation. The production runtime builds `ContractCodecRegistry` from contract walk + descriptor list and never goes through this helper; tests use it to wire a hand-built codec set into the surface those functions consume in production.\n */\nexport function buildTestContractCodecs(\n codecs: ReadonlyArray<Codec<string>>,\n): ContractCodecRegistry {\n const byId = new Map<string, Codec<string>>();\n for (const codec of codecs) {\n byId.set(codec.id, codec);\n }\n return {\n forColumn: () => undefined,\n forCodecId: (codecId) => byId.get(codecId),\n };\n}\n\n/**\n * Synthesize `CodecDescriptor`s from a codec array of non-parameterized codec instances. Test-only: the production synthesis bridge was retired under TML-2357. Lets the existing `createTestAdapterDescriptor` pattern keep wrapping a stub `Adapter` (whose `__codecs` slot still exposes the codec set) into the descriptor-list shape that `SqlStaticContributions.codecs:` now expects. The `Codec` instances carry\n * `traits`/`targetTypes`/`meta` via the SQL family extension; the structural narrow reads those fields directly.\n */\nexport function descriptorsFromCodecs(\n codecs: ReadonlyArray<Codec<string>>,\n): ReadonlyArray<CodecDescriptor> {\n const descriptors: CodecDescriptor[] = [];\n for (const instance of codecs) {\n const legacy = instance as {\n readonly traits?: readonly CodecTrait[];\n readonly targetTypes?: readonly string[];\n readonly meta?: CodecMeta;\n };\n descriptors.push({\n codecId: instance.id,\n traits: legacy.traits ?? [],\n targetTypes: legacy.targetTypes ?? [],\n paramsSchema: voidParamsSchema,\n isParameterized: false,\n factory: () => () => instance,\n ...(legacy.meta !== undefined ? { meta: legacy.meta } : {}),\n });\n }\n return descriptors;\n}\n\nexport function createTestAdapterDescriptor(\n adapter: StubAdapter,\n): SqlRuntimeAdapterDescriptor<'postgres'> {\n const descriptors = descriptorsFromCodecs(adapter.__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: () => descriptors,\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: () => [],\n create() {\n return { familyId: 'sql' as const, targetId: 'postgres' as const };\n },\n };\n}\n\n/**\n * Creates an ExecutionContext for testing. This helper DRYs up the common pattern of context creation in tests.\n *\n * Accepts a raw adapter and optional extension descriptors, wrapping the adapter in a descriptor internally for descriptor-first context creation.\n */\nexport function createTestContext<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n adapter: StubAdapter,\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 * Stub-adapter type augments the public {@link Adapter} surface with a `__codecs` slot that exposes the test stub's runtime codec set to descriptor-shaping helpers (`createTestAdapterDescriptor`). Production adapters do not declare this slot — runtime codecs flow through the descriptor list from `SqlRuntimeAdapterDescriptor.codecs()` — so the augmentation is intentionally test-only.\n */\nexport type StubAdapter = Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement> & {\n readonly __codecs: ReadonlyArray<Codec<string>>;\n};\n\n/**\n * Creates a stub adapter for testing. 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) to enable type inference in tests without requiring the postgres adapter package.\n */\nexport function createStubAdapter(): StubAdapter {\n // Stub codecs for common test types — match the codec IDs used in test contracts (pg/int4@1, pg/text@1, pg/timestamptz@1) without importing from the postgres adapter package.\n const codecs: ReadonlyArray<Codec<string>> = [\n defineTestCodec({\n typeId: 'pg/int4@1',\n targetTypes: ['int4'],\n encode: (value: number) => value,\n decode: (wire: number) => wire,\n }),\n defineTestCodec({\n typeId: 'pg/text@1',\n targetTypes: ['text'],\n encode: (value: string) => value,\n decode: (wire: string) => wire,\n }),\n defineTestCodec({\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 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 __codecs: codecs,\n profile: {\n id: 'stub-profile',\n target: 'postgres',\n capabilities: {},\n readMarker: async () => ({ kind: 'absent' as const }),\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/test-codec.ts","../../test/utils.ts"],"sourcesContent":["/**\n * Test-only helper that constructs a SQL-family `Codec` instance from author-side encode/decode functions. Replaces the legacy public `mkCodec()` factory (deleted under TML-2357); tests that need a stub codec for behavioural assertions instantiate one through this helper rather than going through `descriptor.factory(...)`.\n *\n * The body is identical in spirit to the retired `mkCodec`: promise-lift sync author functions onto the framework-required `Promise<…>` boundary, default `encodeJson`/`decodeJson` to identity when `TInput` is JSON-safe, fail loudly otherwise.\n */\nimport type { JsonValue } from '@prisma-next/contract/types';\nimport type { CodecTrait } from '@prisma-next/framework-components/codec';\nimport type { Codec, SqlCodecCallContext } from '@prisma-next/sql-relational-core/ast';\n\ntype JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]\n ? {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n }\n : {\n encodeJson: (value: TInput) => JsonValue;\n decodeJson: (json: JsonValue) => TInput;\n };\n\nexport function defineTestCodec<\n Id extends string,\n const TTraits extends readonly CodecTrait[] = readonly [],\n TWire = unknown,\n TInput = unknown,\n>(\n config: {\n typeId: Id;\n targetTypes?: readonly string[];\n encode: (value: TInput, ctx: SqlCodecCallContext) => TWire | Promise<TWire>;\n decode: (wire: TWire, ctx: SqlCodecCallContext) => TInput | Promise<TInput>;\n traits?: TTraits;\n } & JsonRoundTripConfig<TInput>,\n): Codec<Id, TTraits, TWire, TInput> {\n const identity = (v: unknown) => v;\n const userEncode = config.encode;\n const userDecode = config.decode;\n const widenedConfig = config as {\n encodeJson?: (value: TInput) => JsonValue;\n decodeJson?: (json: JsonValue) => TInput;\n };\n return {\n id: config.typeId,\n encode: (value, ctx) => {\n try {\n return Promise.resolve(userEncode(value, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n decode: (wire, ctx) => {\n try {\n return Promise.resolve(userDecode(wire, ctx));\n } catch (error) {\n return Promise.reject(error);\n }\n },\n encodeJson: (widenedConfig.encodeJson ?? identity) as (value: TInput) => JsonValue,\n decodeJson: (widenedConfig.decodeJson ?? identity) as (json: JsonValue) => TInput,\n } as Codec<Id, TTraits, TWire, TInput>;\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { coreHash, profileHash } from '@prisma-next/contract/types';\nimport type {\n CodecDescriptor,\n CodecMeta,\n CodecTrait,\n} from '@prisma-next/framework-components/codec';\nimport {\n instantiateExecutionStack,\n type RuntimeDriverDescriptor,\n} from '@prisma-next/framework-components/execution';\nimport type { ResultType } from '@prisma-next/framework-components/runtime';\nimport { runtimeError } from '@prisma-next/framework-components/runtime';\nimport { canonicalizeJson } from '@prisma-next/framework-components/utils';\nimport { builtinGeneratorIds } from '@prisma-next/ids';\nimport { generateId } from '@prisma-next/ids/runtime';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type {\n Adapter,\n Codec,\n ContractCodecRegistry,\n LoweredStatement,\n SelectAst,\n} 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 APP_SPACE_ID,\n createExecutionContext,\n type createRuntime,\n createSqlExecutionStack,\n ensureSchemaStatement,\n ensureTableStatement,\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';\nimport { defineTestCodec } from './test-codec';\n\nfunction createTestMutationDefaultGenerators() {\n return builtinGeneratorIds.map((id) => ({\n id,\n generate: (params?: Record<string, unknown>) => generateId(params ? { id, params } : { id }),\n stability: 'field' as const,\n }));\n}\n\n/**\n * Executes a plan and collects all results into an array. This helper DRYs up the common pattern of executing plans in tests. 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. 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. 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 space: APP_SPACE_ID,\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. 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 space: APP_SPACE_ID,\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. Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions derived from the adapter's codec registry.\n */\n/**\n * Build a {@link ContractCodecRegistry} from a codec array for tests that exercise `encodeParam(s)` / `decodeRow` in isolation. The production runtime builds `ContractCodecRegistry` from contract walk + descriptor list and never goes through this helper; tests use it to wire a hand-built codec set into the surface those functions consume in production.\n */\nexport function buildTestContractCodecs(\n codecs: ReadonlyArray<Codec<string>>,\n): ContractCodecRegistry {\n const byId = new Map<string, Codec<string>>();\n for (const codec of codecs) {\n byId.set(codec.id, codec);\n }\n // Canonical-key cache: production `forCodecRef` memoizes per `(codecId, canonicalize(typeParams))`. Tests resolve by codecId, but key the cache on the canonical pair so callers passing distinct typeParams get distinct (still codec-id-templated) entries — and so this helper cannot silently coalesce them.\n const byCanonicalKey = new Map<string, Codec<string>>();\n return {\n forColumn: () => undefined,\n forCodecRef: (ref) => {\n const canonicalKey = canonicalizeJson({\n codecId: ref.codecId,\n ...(ref.typeParams !== undefined ? { typeParams: ref.typeParams } : {}),\n });\n const cached = byCanonicalKey.get(canonicalKey);\n if (cached) return cached;\n const template = byId.get(ref.codecId);\n if (!template) {\n throw runtimeError(\n 'RUNTIME.CODEC_DESCRIPTOR_MISSING',\n `Test ContractCodecRegistry has no codec for codecId '${ref.codecId}'.`,\n {\n codecId: ref.codecId,\n ...(ref.typeParams !== undefined ? { typeParams: ref.typeParams } : {}),\n },\n );\n }\n byCanonicalKey.set(canonicalKey, template);\n return template;\n },\n };\n}\n\n/**\n * Synthesize `CodecDescriptor`s from a codec array of non-parameterized codec instances. Test-only: the production synthesis bridge was retired under TML-2357. Lets the existing `createTestAdapterDescriptor` pattern keep wrapping a stub `Adapter` (whose `__codecs` slot still exposes the codec set) into the descriptor-list shape that `SqlStaticContributions.codecs:` now expects. The `Codec` instances carry\n * `traits`/`targetTypes`/`meta` via the SQL family extension; the structural narrow reads those fields directly.\n */\nexport function descriptorsFromCodecs(\n codecs: ReadonlyArray<Codec<string>>,\n): ReadonlyArray<CodecDescriptor> {\n // Permissive paramsSchema for synthesized test descriptors: accepts any\n // shape (incl. undefined) and passes it through. Stubs do not encode\n // parameterization, so marking them `isParameterized: true` with this\n // schema lets the runtime integrity check tolerate columns that legitimately\n // carry typeParams (e.g. `sql/char@1` length=36) without re-introducing\n // the legacy \"non-parameterized + typeParams\" silent skip.\n // Permissive schema for synthesized test descriptors. `validate()` always\n // succeeds and discards input, narrowed to `void` to match the\n // `paramsSchema: StandardSchemaV1<void, void>` slot on the descriptor.\n // The factory ignores typeParams, so typing the validated output as `void`\n // is honest about what the stub does with the value.\n const acceptAnyParamsSchema = {\n '~standard': {\n version: 1 as const,\n vendor: 'sql-runtime/test-utils',\n validate: (_value: unknown) => ({ value: undefined }),\n },\n };\n const descriptors: CodecDescriptor[] = [];\n for (const instance of codecs) {\n const legacy = instance as {\n readonly traits?: readonly CodecTrait[];\n readonly targetTypes?: readonly string[];\n readonly meta?: CodecMeta;\n };\n descriptors.push({\n codecId: instance.id,\n traits: legacy.traits ?? [],\n targetTypes: legacy.targetTypes ?? [],\n paramsSchema: acceptAnyParamsSchema,\n isParameterized: true,\n factory: () => () => instance,\n ...(legacy.meta !== undefined ? { meta: legacy.meta } : {}),\n });\n }\n return descriptors;\n}\n\nexport function createTestAdapterDescriptor(\n adapter: StubAdapter,\n): SqlRuntimeAdapterDescriptor<'postgres'> {\n const descriptors = descriptorsFromCodecs(adapter.__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: () => descriptors,\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: () => [],\n create() {\n return { familyId: 'sql' as const, targetId: 'postgres' as const };\n },\n };\n}\n\n/**\n * Creates an ExecutionContext for testing. This helper DRYs up the common pattern of context creation in tests.\n *\n * Accepts a raw adapter and optional extension descriptors, wrapping the adapter in a descriptor internally for descriptor-first context creation.\n */\nexport function createTestContext<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n adapter: StubAdapter,\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 * Stub-adapter type augments the public {@link Adapter} surface with a `__codecs` slot that exposes the test stub's runtime codec set to descriptor-shaping helpers (`createTestAdapterDescriptor`). Production adapters do not declare this slot — runtime codecs flow through the descriptor list from `SqlRuntimeAdapterDescriptor.codecs()` — so the augmentation is intentionally test-only.\n */\nexport type StubAdapter = Adapter<SelectAst, Contract<SqlStorage>, LoweredStatement> & {\n readonly __codecs: ReadonlyArray<Codec<string>>;\n};\n\n/**\n * Creates a stub adapter for testing. 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) to enable type inference in tests without requiring the postgres adapter package.\n */\nexport function createStubAdapter(): StubAdapter {\n // Stub codecs for codec IDs that test contracts may reference. The set must\n // be complete enough to satisfy `assertColumnCodecIntegrity` against any\n // emitted test contract; the encode/decode bodies are passthrough since\n // the stub adapter never executes against a real driver.\n // The encode/decode bodies pass through; widen TInput to a JSON-safe type\n // so `defineTestCodec` does not require explicit JSON round-trip helpers.\n const passthroughCodec = (typeId: string, targetType: string): Codec<string> =>\n defineTestCodec({\n typeId,\n targetTypes: [targetType],\n encode: (value: string | number | boolean | null) => value,\n decode: (wire: string | number | boolean | null) => wire,\n });\n const codecs: ReadonlyArray<Codec<string>> = [\n passthroughCodec('pg/bit@1', 'bit'),\n passthroughCodec('pg/bool@1', 'bool'),\n passthroughCodec('pg/bytea@1', 'bytea'),\n passthroughCodec('pg/float4@1', 'float4'),\n passthroughCodec('pg/float8@1', 'float8'),\n passthroughCodec('pg/int2@1', 'int2'),\n defineTestCodec({\n typeId: 'pg/int4@1',\n targetTypes: ['int4'],\n encode: (value: number) => value,\n decode: (wire: number) => wire,\n }),\n passthroughCodec('pg/int8@1', 'int8'),\n passthroughCodec('pg/interval@1', 'interval'),\n passthroughCodec('pg/json@1', 'json'),\n passthroughCodec('pg/jsonb@1', 'jsonb'),\n passthroughCodec('pg/numeric@1', 'numeric'),\n defineTestCodec({\n typeId: 'pg/text@1',\n targetTypes: ['text'],\n encode: (value: string) => value,\n decode: (wire: string) => wire,\n }),\n passthroughCodec('pg/time@1', 'time'),\n defineTestCodec({\n typeId: 'pg/timestamp@1',\n targetTypes: ['timestamp'],\n encode: (value: Date) => value,\n decode: (wire: Date) => wire,\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 defineTestCodec({\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 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 passthroughCodec('pg/timetz@1', 'timetz'),\n passthroughCodec('pg/varbit@1', 'varbit'),\n passthroughCodec('pg/uuid@1', 'uuid'),\n passthroughCodec('sql/char@1', 'char'),\n passthroughCodec('sql/varchar@1', 'varchar'),\n ];\n\n return {\n __codecs: codecs,\n profile: {\n id: 'stub-profile',\n target: 'postgres',\n capabilities: {},\n readMarker: async () => ({ kind: 'absent' as const }),\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,20 +1,20 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-runtime",
3
- "version": "0.6.0-dev.1",
3
+ "version": "0.6.0-dev.11",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL runtime implementation for Prisma Next",
8
8
  "dependencies": {
9
9
  "arktype": "^2.1.29",
10
- "@prisma-next/contract": "0.6.0-dev.1",
11
- "@prisma-next/framework-components": "0.6.0-dev.1",
12
- "@prisma-next/operations": "0.6.0-dev.1",
13
- "@prisma-next/utils": "0.6.0-dev.1",
14
- "@prisma-next/sql-contract": "0.6.0-dev.1",
15
- "@prisma-next/sql-operations": "0.6.0-dev.1",
16
- "@prisma-next/ids": "0.6.0-dev.1",
17
- "@prisma-next/sql-relational-core": "0.6.0-dev.1"
10
+ "@prisma-next/contract": "0.6.0-dev.11",
11
+ "@prisma-next/utils": "0.6.0-dev.11",
12
+ "@prisma-next/framework-components": "0.6.0-dev.11",
13
+ "@prisma-next/ids": "0.6.0-dev.11",
14
+ "@prisma-next/operations": "0.6.0-dev.11",
15
+ "@prisma-next/sql-contract": "0.6.0-dev.11",
16
+ "@prisma-next/sql-operations": "0.6.0-dev.11",
17
+ "@prisma-next/sql-relational-core": "0.6.0-dev.11"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/pg": "8.20.0",
@@ -22,9 +22,9 @@
22
22
  "tsdown": "0.22.0",
23
23
  "typescript": "5.9.3",
24
24
  "vitest": "4.1.5",
25
- "@prisma-next/tsconfig": "0.0.0",
25
+ "@prisma-next/test-utils": "0.0.1",
26
26
  "@prisma-next/tsdown": "0.0.0",
27
- "@prisma-next/test-utils": "0.0.1"
27
+ "@prisma-next/tsconfig": "0.0.0"
28
28
  },
29
29
  "files": [
30
30
  "dist",
@@ -0,0 +1,99 @@
1
+ import type { CodecRef } from '@prisma-next/framework-components/codec';
2
+ import { runtimeError } from '@prisma-next/framework-components/runtime';
3
+ import { canonicalizeJson } from '@prisma-next/framework-components/utils';
4
+ import type { Codec, SqlCodecInstanceContext } from '@prisma-next/sql-relational-core/ast';
5
+ import type { CodecDescriptorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
6
+
7
+ /**
8
+ * Per-`ExecutionContext` resolver that materialises the {@link Codec} for a {@link CodecRef} carried on an AST node.
9
+ *
10
+ * Wraps `descriptorFor(codecId).factory(typeParams)(ctx)` with a content-keyed cache: lookups are keyed by `${codecId}:${canonicalizeJson(typeParams)}`, so two refs with the same `codecId` and structurally equal `typeParams` (regardless of object key order) resolve to the same memoised codec instance. Non-parameterized codecs key as `${codecId}:undefined` and share one instance per resolver.
11
+ *
12
+ * AST-bound codec resolution dissolves the legacy column-aware dispatch path: every codec-bearing AST node carries the canonical `CodecRef` directly, so the resolver is the single dispatch shape encode and decode share. Refs the contract walk pre-populates hit on first call; refs the AST supplies (e.g. deserialised migration ops) populate the cache lazily.
13
+ */
14
+ export interface AstCodecResolver {
15
+ /**
16
+ * Resolve the {@link Codec} for the supplied {@link CodecRef}.
17
+ *
18
+ * Throws `RUNTIME.CODEC_DESCRIPTOR_MISSING` when no descriptor is registered for `ref.codecId`. Throws `RUNTIME.TYPE_PARAMS_INVALID` when the descriptor's `paramsSchema` rejects `ref.typeParams` (validated only on cache miss; subsequent lookups for the same canonical key skip validation).
19
+ */
20
+ forCodecRef(ref: CodecRef): Codec;
21
+ }
22
+
23
+ /**
24
+ * Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
25
+ *
26
+ * The instance-context factory lets callers control `name` / `usedAt` for refs the AST supplies (e.g. AST-embedded migration ops where the materialisation site is the AST node, not a contract column). The contract-walk pre-population path constructs its own contexts and invokes the resolver with those refs to seed the cache.
27
+ */
28
+ export function createAstCodecResolver(
29
+ descriptors: CodecDescriptorRegistry,
30
+ instanceContextFor: (ref: CodecRef) => SqlCodecInstanceContext,
31
+ ): AstCodecResolver {
32
+ const cache = new Map<string, Codec>();
33
+
34
+ return {
35
+ forCodecRef(ref: CodecRef): Codec {
36
+ const key = `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
37
+ const cached = cache.get(key);
38
+ if (cached) return cached;
39
+
40
+ const descriptor = descriptors.descriptorFor(ref.codecId);
41
+ if (!descriptor) {
42
+ throw runtimeError(
43
+ 'RUNTIME.CODEC_DESCRIPTOR_MISSING',
44
+ `No codec descriptor registered for codecId '${ref.codecId}'.`,
45
+ { codecId: ref.codecId },
46
+ );
47
+ }
48
+
49
+ // Parameterized codecs whose paramsSchema accepts `{}` (every field
50
+ // optional, e.g. `pg/timestamptz@1` precision) tolerate refs that omit
51
+ // `typeParams` entirely. Normalize `undefined` to `{}` at the validation
52
+ // boundary; the integrity check upstream already rejected refs whose
53
+ // schemas reject the empty object.
54
+ const validated = validateTypeParams(
55
+ descriptor.paramsSchema,
56
+ descriptor.isParameterized && ref.typeParams === undefined
57
+ ? { ...ref, typeParams: {} }
58
+ : ref,
59
+ );
60
+ const ctx = instanceContextFor(ref);
61
+ // The descriptor's `factory` is typed against its own `P`; the registry erases `P` to `unknown`, so callers narrow per codec id at the dispatch boundary. The descriptor's `paramsSchema` validates the input above before we forward it, so this narrow is safe by construction.
62
+ const codec = (
63
+ descriptor.factory as (params: unknown) => (ctx: SqlCodecInstanceContext) => Codec
64
+ )(validated)(ctx);
65
+
66
+ cache.set(key, codec);
67
+ return codec;
68
+ },
69
+ };
70
+ }
71
+
72
+ function validateTypeParams(
73
+ paramsSchema: { '~standard': { validate: (input: unknown) => unknown } },
74
+ ref: CodecRef,
75
+ ): unknown {
76
+ const result = paramsSchema['~standard'].validate(ref.typeParams) as
77
+ | { value: unknown }
78
+ | { issues: ReadonlyArray<{ message: string }> }
79
+ | Promise<unknown>;
80
+
81
+ if (result instanceof Promise) {
82
+ throw runtimeError(
83
+ 'RUNTIME.TYPE_PARAMS_INVALID',
84
+ `paramsSchema for codec '${ref.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`,
85
+ { codecId: ref.codecId, typeParams: ref.typeParams },
86
+ );
87
+ }
88
+
89
+ if ('issues' in result && result.issues) {
90
+ const messages = result.issues.map((issue) => issue.message).join('; ');
91
+ throw runtimeError(
92
+ 'RUNTIME.TYPE_PARAMS_INVALID',
93
+ `Invalid typeParams for codec '${ref.codecId}': ${messages}`,
94
+ { codecId: ref.codecId, typeParams: ref.typeParams },
95
+ );
96
+ }
97
+
98
+ return (result as { value: unknown }).value;
99
+ }
@@ -12,7 +12,6 @@ import type {
12
12
  SqlCodecCallContext,
13
13
  } from '@prisma-next/sql-relational-core/ast';
14
14
  import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
15
- import { makeAliasResolver } from './alias-resolver';
16
15
 
17
16
  type ColumnRef = { table: string; column: string };
18
17
 
@@ -42,36 +41,12 @@ function projectionListFromAst(ast: AnyQueryAst): ReadonlyArray<ProjectionItem>
42
41
  return ast.returning;
43
42
  }
44
43
 
45
- /**
46
- * Resolve the per-cell codec for a projection item.
47
- *
48
- * When a `(table, column)` ref is available — either implicit on a `column-ref` expression or carried explicitly via `item.refs` for column-bound non-`column-ref` projections — prefer `contractCodecs.forColumn(table, column)`: that returns the per-instance codec materialized from the descriptor's factory for that column, encoding any per-instance state (typeParams like vector length, schema validators, etc.).
49
- *
50
- * The wrong-instance risk for parameterized codecs is closed off structurally:
51
- *
52
- * 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`). 3. The non-ambiguous parameterized case stores the column-correct per-instance codec under `byCodecId`, so the fall-through still resolves to the right instance.
53
- *
54
- * The `forCodecId` fallback otherwise covers projections that are *not* column-bound (computed projections, raw SQL aliases) but still carry a `codecId` (ADR 205 stamps every `ProjectionItem` with the producer's codec id).
55
- *
56
- * Codec-registry-unification spec § AC-4 / AC-5.
57
- */
58
44
  function resolveProjectionCodec(
59
45
  item: ProjectionItem,
60
46
  contractCodecs: ContractCodecRegistry | undefined,
61
- aliasResolver: (alias: string) => string,
62
47
  ): Codec | undefined {
63
- if (contractCodecs) {
64
- if (item.expr.kind === 'column-ref') {
65
- const byColumn = contractCodecs.forColumn(aliasResolver(item.expr.table), item.expr.column);
66
- // Only honour `byColumn` when its codec id agrees with `item.codecId`. They can legitimately disagree when an `OperationExpr`-shaped projection carries a single inner column-ref but transforms the value's codec (e.g. `cosineDistance(col, x)` projects `pg/float8@1` while the inner column-ref points at a `pg/vector@1` column).
67
- if (byColumn && (item.codecId === undefined || byColumn.id === item.codecId)) return byColumn;
68
- } else if (item.refs) {
69
- const byColumn = contractCodecs.forColumn(aliasResolver(item.refs.table), item.refs.column);
70
- if (byColumn && (item.codecId === undefined || byColumn.id === item.codecId)) return byColumn;
71
- }
72
- }
73
- if (item.codecId) {
74
- return contractCodecs?.forCodecId(item.codecId);
48
+ if (item.codec && contractCodecs) {
49
+ return contractCodecs.forCodecRef(item.codec);
75
50
  }
76
51
  return undefined;
77
52
  }
@@ -103,26 +78,20 @@ function buildDecodeContext(
103
78
  const codecs = new Map<string, Codec>();
104
79
  const columnRefs = new Map<string, ColumnRef>();
105
80
  const includeAliases = new Set<string>();
106
- const aliasResolver = makeAliasResolver(plan.ast);
107
81
 
108
82
  for (const item of projection) {
109
83
  aliases.push(item.alias);
110
84
 
111
- const codec = resolveProjectionCodec(item, contractCodecs, aliasResolver);
85
+ const codec = resolveProjectionCodec(item, contractCodecs);
112
86
  if (codec) {
113
87
  codecs.set(item.alias, codec);
114
88
  }
115
89
 
116
90
  if (item.expr.kind === 'column-ref') {
117
91
  columnRefs.set(item.alias, {
118
- table: aliasResolver(item.expr.table),
92
+ table: item.expr.table,
119
93
  column: item.expr.column,
120
94
  });
121
- } else if (item.refs) {
122
- columnRefs.set(item.alias, {
123
- table: aliasResolver(item.refs.table),
124
- column: item.refs.column,
125
- });
126
95
  } else if (item.expr.kind === 'subquery' || item.expr.kind === 'json-array-agg') {
127
96
  includeAliases.add(item.alias);
128
97
  }
@@ -225,8 +194,6 @@ async function decodeField(
225
194
 
226
195
  const ref = decodeCtx.columnRefs.get(alias);
227
196
 
228
- // Per-cell ctx: the cell-level `column` is a `SqlColumnRef = { table, name }` projection of the resolved `ColumnRef = { table, column }` (same resolution `wrapDecodeFailure` uses below — no double work). Cells the runtime cannot resolve (aggregate aliases, computed projections without a simple ref) drop the `column` field entirely — explicitly cleared so a previously-populated `rowCtx.column` cannot leak through to
229
- // unrelated cells. Destructuring (rather than `column: undefined`) is required because `SqlCodecCallContext.column` is declared `column?: SqlColumnRef` under `exactOptionalPropertyTypes`.
230
197
  let cellCtx: SqlCodecCallContext;
231
198
  if (ref) {
232
199
  cellCtx = { ...rowCtx, column: { table: ref.table, name: ref.column } };
@@ -238,7 +205,6 @@ async function decodeField(
238
205
  try {
239
206
  return await codec.decode(wireValue, cellCtx);
240
207
  } catch (error) {
241
- // Codec-authored runtime envelopes (e.g. `RUNTIME.DECODE_FAILED` thrown from inside the codec body, or `RUNTIME.ABORTED` raised via `CodecCallContext.signal` per ADR 207) carry their own per-codec context — wrapping them again would erase that context and coerce the abort intent into a generic decode failure. Pass them through unchanged; only foreign errors get the `wrapDecodeFailure` envelope.
242
208
  if (isRuntimeError(error)) {
243
209
  throw error;
244
210
  }
@@ -298,7 +264,6 @@ export async function decodeRow(
298
264
 
299
265
  const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'decode');
300
266
 
301
- // Include aggregates are decoded synchronously after concurrent codec dispatch settles, so any decode failures upstream propagate first.
302
267
  for (const entry of includeIndices) {
303
268
  settled[entry.index] = decodeIncludeAggregate(entry.alias, entry.value);
304
269
  }
@@ -1,58 +1,36 @@
1
1
  import {
2
2
  checkAborted,
3
+ isRuntimeError,
3
4
  raceAgainstAbort,
4
5
  runtimeError,
5
6
  } from '@prisma-next/framework-components/runtime';
6
7
  import {
7
8
  type Codec,
9
+ type CodecRef,
8
10
  type ContractCodecRegistry,
9
11
  collectOrderedParamRefs,
10
- type ParamRefBindingRefs,
11
12
  type SqlCodecCallContext,
12
13
  } from '@prisma-next/sql-relational-core/ast';
13
14
  import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
14
- import { makeAliasResolver } from './alias-resolver';
15
15
 
16
16
  interface ParamMetadata {
17
- readonly codecId: string | undefined;
17
+ readonly codec: CodecRef | undefined;
18
18
  readonly name: string | undefined;
19
- readonly refs: ParamRefBindingRefs | undefined;
20
19
  }
21
20
 
22
21
  const NO_METADATA: ParamMetadata = Object.freeze({
23
- codecId: undefined,
22
+ codec: undefined,
24
23
  name: undefined,
25
- refs: undefined,
26
24
  });
27
25
 
28
- /**
29
- * Resolve the codec for an outgoing param.
30
- *
31
- * Column-aware dispatch: when `metadata.refs` is populated by a column-bound construction site, prefer `contractCodecs.forColumn(refs.table, refs.column)` — that returns the per-instance codec the contract walk materialized for the `(table, column)` pair, encoding the column's typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
32
- *
33
- * On a column-lookup miss the resolver falls through to `forCodecId`. The wrong-instance risk for parameterized codecs is closed off structurally:
34
- *
35
- * 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent from this pre-population. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`) — if the contract walk resolved more than one distinct instance under a single parameterized id, the call throws rather than binding to
36
- * whichever landed first. 3. For the non-ambiguous parameterized case (a single column with that id), `byCodecId` stores the column-correct per-instance codec, so the fall-through still resolves to the right instance.
37
- *
38
- * Refs-less fallback: ParamRefs constructed outside a column-bound site (literals, transient builder state) carry a non-parameterized `codecId` whose dispatch is ambiguity-free. The validator pass (`validateParamRefRefs`) already enforced refs on every parameterized ParamRef before encode runs.
39
- */
40
26
  function resolveParamCodec(
41
27
  metadata: ParamMetadata,
42
28
  contractCodecs: ContractCodecRegistry | undefined,
43
- aliasResolver: (alias: string) => string,
44
29
  ): Codec | undefined {
45
- if (!metadata.codecId) return undefined;
46
- if (metadata.refs && contractCodecs) {
47
- const byColumn = contractCodecs.forColumn(
48
- aliasResolver(metadata.refs.table),
49
- metadata.refs.column,
50
- );
51
- // Only honour `byColumn` when its codec id agrees with the `ParamRef`'s declared `codecId`. They can legitimately disagree when a heuristic (e.g. the ORM's `refsFromLeft`) lifts column refs out of an `OperationExpr` that changed the codec id — e.g. `cosineDistance(p.embedding, x).lt(1)` carries `refs={post,embedding}` (a vector column) but the comparison side's codec is `pg/float8@1`. Trusting `byColumn` blindly would dispatch the float
52
- // literal through the vector codec.
53
- if (byColumn && byColumn.id === metadata.codecId) return byColumn;
30
+ if (metadata.codec && contractCodecs) {
31
+ return contractCodecs.forCodecRef(metadata.codec);
54
32
  }
55
- return contractCodecs?.forCodecId(metadata.codecId);
33
+ return undefined;
56
34
  }
57
35
 
58
36
  function paramLabel(metadata: ParamMetadata, paramIndex: number): string {
@@ -84,9 +62,8 @@ function wrapEncodeFailure(
84
62
  export async function encodeParam(
85
63
  value: unknown,
86
64
  paramRef: {
87
- readonly codecId?: string;
65
+ readonly codec?: CodecRef;
88
66
  readonly name?: string;
89
- readonly refs?: ParamRefBindingRefs;
90
67
  },
91
68
  paramIndex: number,
92
69
  ctx: SqlCodecCallContext,
@@ -94,11 +71,10 @@ export async function encodeParam(
94
71
  ): Promise<unknown> {
95
72
  return encodeParamValue(
96
73
  value,
97
- { codecId: paramRef.codecId, name: paramRef.name, refs: paramRef.refs },
74
+ { codec: paramRef.codec, name: paramRef.name },
98
75
  paramIndex,
99
76
  ctx,
100
77
  contractCodecs,
101
- (alias) => alias,
102
78
  );
103
79
  }
104
80
 
@@ -108,13 +84,12 @@ async function encodeParamValue(
108
84
  paramIndex: number,
109
85
  ctx: SqlCodecCallContext,
110
86
  contractCodecs: ContractCodecRegistry | undefined,
111
- aliasResolver: (alias: string) => string,
112
87
  ): Promise<unknown> {
113
88
  if (value === null || value === undefined) {
114
89
  return null;
115
90
  }
116
91
 
117
- const codec = resolveParamCodec(metadata, contractCodecs, aliasResolver);
92
+ const codec = resolveParamCodec(metadata, contractCodecs);
118
93
  if (!codec) {
119
94
  return value;
120
95
  }
@@ -122,6 +97,14 @@ async function encodeParamValue(
122
97
  try {
123
98
  return await codec.encode(value, ctx);
124
99
  } catch (error) {
100
+ // Any `runtimeError`-built envelope is stable by construction — let
101
+ // it pass through unchanged. This covers codec-authored
102
+ // `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` (per-library JSON-with-
103
+ // schema codecs validate inside `encode` per ADR 208 § Case J),
104
+ // codec-authored `RUNTIME.ENCODE_FAILED` (no double wrap), and any
105
+ // future stable code thrown from a codec body. Symmetric with the
106
+ // decode-side guard.
107
+ if (isRuntimeError(error)) throw error;
125
108
  wrapEncodeFailure(error, metadata, paramIndex, codec.id);
126
109
  }
127
110
  }
@@ -155,23 +138,14 @@ export async function encodeParams(
155
138
  for (let i = 0; i < paramCount && i < refs.length; i++) {
156
139
  const ref = refs[i];
157
140
  if (ref) {
158
- metadata[i] = { codecId: ref.codecId, name: ref.name, refs: ref.refs };
141
+ metadata[i] = { codec: ref.codec, name: ref.name };
159
142
  }
160
143
  }
161
144
  }
162
145
 
163
- const aliasResolver = makeAliasResolver(plan.ast);
164
-
165
146
  const tasks: Promise<unknown>[] = new Array(paramCount);
166
147
  for (let i = 0; i < paramCount; i++) {
167
- tasks[i] = encodeParamValue(
168
- plan.params[i],
169
- metadata[i] ?? NO_METADATA,
170
- i,
171
- ctx,
172
- contractCodecs,
173
- aliasResolver,
174
- );
148
+ tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, ctx, contractCodecs);
175
149
  }
176
150
 
177
151
  const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'encode');
@@ -45,17 +45,29 @@ export interface SqlMiddleware<TCodecMap extends Record<string, unknown> = Recor
45
45
  */
46
46
  beforeCompile?(draft: DraftPlan, ctx: SqlMiddlewareContext): Promise<DraftPlan | undefined>;
47
47
  /**
48
- * Mutate `ParamRef.value` slots before encode runs. The third `params`
49
- * argument is a {@link SqlParamRefMutator} scoped to value slots only —
50
- * SQL strings, projections, and `ParamRef` membership are not mutable.
51
- * Existing `(plan)` and `(plan, ctx)` middleware bodies that ignore the
52
- * additional argument continue to compile and run unchanged.
48
+ * Mutate `ParamRef.value` slots before encode runs. The third
49
+ * `params` argument is a {@link SqlParamRefMutator} scoped to value
50
+ * slots only — SQL strings, projections, and `ParamRef` membership
51
+ * are not mutable. Existing `(plan)` and `(plan, ctx)` middleware
52
+ * bodies that ignore the additional argument continue to compile
53
+ * and run unchanged.
53
54
  *
54
- * `ctx.signal` carries the per-query `AbortSignal` (ADR 207); middleware
55
- * that wraps a network SDK forwards `ctx.signal` to that SDK.
56
- * Cooperative cancellation: a body that ignores the signal still
57
- * surfaces `RUNTIME.ABORTED { phase: 'beforeExecute' }` promptly via
58
- * the runtime's race against the signal.
55
+ * Lifecycle position:
56
+ * `beforeCompile lowerSqlPlan beforeExecute encodeParams intercept driver`.
57
+ *
58
+ * The plan handed in is the SQL execution plan after lowering but
59
+ * *before* parameter encoding: `plan.params[i]` is the user-domain
60
+ * value the mutator's `entries()` iterator surfaces (e.g. an
61
+ * `EncryptedEnvelopeBase` for a cipherstash-codec'd column, or a
62
+ * plain JS value for non-codec'd ParamRefs). Mutations applied via
63
+ * `params.replaceValue` / `replaceValues` are visible to encode,
64
+ * which then renders the mutated value through the column's codec.
65
+ *
66
+ * `ctx.signal` carries the per-query `AbortSignal` (ADR 207);
67
+ * middleware that wraps a network SDK forwards `ctx.signal` to
68
+ * that SDK. Cooperative cancellation: a body that ignores the
69
+ * signal still surfaces `RUNTIME.ABORTED { phase: 'beforeExecute' }`
70
+ * promptly via the runtime's race against the signal.
59
71
  */
60
72
  beforeExecute?(
61
73
  plan: SqlExecutionPlan,