@prisma-next/sql-runtime 0.6.0-dev.5 → 0.6.0-dev.7
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-BSTHn_rH.mjs → exports-BeaTZIiJ.mjs} +171 -174
- package/dist/exports-BeaTZIiJ.mjs.map +1 -0
- package/dist/{index-CTCvZOWI.d.mts → index-qGOr0ruJ.d.mts} +2 -2
- package/dist/index-qGOr0ruJ.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +2 -2
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +61 -6
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +11 -11
- package/src/codecs/ast-codec-resolver.ts +99 -0
- package/src/codecs/decoding.ts +4 -39
- package/src/codecs/encoding.ts +20 -46
- package/src/sql-context.ts +157 -124
- package/src/sql-runtime.ts +0 -5
- package/dist/exports-BSTHn_rH.mjs.map +0 -1
- package/dist/index-CTCvZOWI.d.mts.map +0 -1
- package/src/codecs/alias-resolver.ts +0 -37
package/dist/test/utils.mjs.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.6.0-dev.7",
|
|
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/
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/sql-operations": "0.6.0-dev.
|
|
16
|
-
"@prisma-next/
|
|
17
|
-
"@prisma-next/
|
|
10
|
+
"@prisma-next/utils": "0.6.0-dev.7",
|
|
11
|
+
"@prisma-next/framework-components": "0.6.0-dev.7",
|
|
12
|
+
"@prisma-next/contract": "0.6.0-dev.7",
|
|
13
|
+
"@prisma-next/operations": "0.6.0-dev.7",
|
|
14
|
+
"@prisma-next/sql-contract": "0.6.0-dev.7",
|
|
15
|
+
"@prisma-next/sql-operations": "0.6.0-dev.7",
|
|
16
|
+
"@prisma-next/ids": "0.6.0-dev.7",
|
|
17
|
+
"@prisma-next/sql-relational-core": "0.6.0-dev.7"
|
|
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/test-utils": "0.0.1",
|
|
25
26
|
"@prisma-next/tsconfig": "0.0.0",
|
|
26
|
-
"@prisma-next/tsdown": "0.0.0"
|
|
27
|
-
"@prisma-next/test-utils": "0.0.1"
|
|
27
|
+
"@prisma-next/tsdown": "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
|
+
}
|
package/src/codecs/decoding.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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:
|
|
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
|
}
|
package/src/codecs/encoding.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 (
|
|
46
|
-
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
|
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] = {
|
|
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');
|