@prisma-next/sql-runtime 0.5.0-dev.60 → 0.5.0-dev.62
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-BcX9wp4z.mjs → exports-CZIUsCRE.mjs} +197 -320
- package/dist/exports-CZIUsCRE.mjs.map +1 -0
- package/dist/{index-DkthtnOX.d.mts → index-Ba3eysL3.d.mts} +19 -44
- package/dist/index-Ba3eysL3.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +31 -26
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +99 -55
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +11 -11
- package/src/codecs/alias-resolver.ts +34 -0
- package/src/codecs/decoding.ts +49 -83
- package/src/codecs/encoding.ts +44 -56
- package/src/codecs/validation.ts +3 -22
- package/src/middleware/budgets.ts +13 -27
- package/src/sql-context.ts +119 -268
- package/src/sql-runtime.ts +39 -101
- package/dist/exports-BcX9wp4z.mjs.map +0 -1
- package/dist/index-DkthtnOX.d.mts.map +0 -1
- package/src/codecs/json-schema-validation.ts +0 -61
package/src/sql-context.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Contract, ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
|
|
2
|
-
import type { CodecDescriptor } from '@prisma-next/framework-components/codec';
|
|
3
|
-
import { synthesizeNonParameterizedDescriptor } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import type { AnyCodecDescriptor, CodecDescriptor } from '@prisma-next/framework-components/codec';
|
|
4
3
|
import type { ComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
5
4
|
import { checkContractComponentRequirements } from '@prisma-next/framework-components/components';
|
|
6
5
|
import {
|
|
@@ -25,19 +24,16 @@ import type {
|
|
|
25
24
|
Adapter,
|
|
26
25
|
AnyQueryAst,
|
|
27
26
|
Codec,
|
|
28
|
-
CodecRegistry,
|
|
29
27
|
ContractCodecRegistry,
|
|
30
28
|
LoweredStatement,
|
|
31
29
|
SqlCodecInstanceContext,
|
|
32
30
|
SqlDriver,
|
|
33
31
|
} from '@prisma-next/sql-relational-core/ast';
|
|
34
|
-
import {
|
|
32
|
+
import { buildCodecDescriptorRegistry } from '@prisma-next/sql-relational-core/codec-descriptor-registry';
|
|
35
33
|
import type {
|
|
36
34
|
AppliedMutationDefault,
|
|
37
35
|
CodecDescriptorRegistry,
|
|
38
36
|
ExecutionContext,
|
|
39
|
-
JsonSchemaValidateFn,
|
|
40
|
-
JsonSchemaValidatorRegistry,
|
|
41
37
|
MutationDefaultsOptions,
|
|
42
38
|
TypeHelperRegistry,
|
|
43
39
|
} from '@prisma-next/sql-relational-core/query-lane-context';
|
|
@@ -45,20 +41,17 @@ import type {
|
|
|
45
41
|
/**
|
|
46
42
|
* Runtime parameterized codec descriptor.
|
|
47
43
|
*
|
|
48
|
-
* The unified `CodecDescriptor<P>` shape applied to parameterized codecs
|
|
49
|
-
* — `paramsSchema: StandardSchemaV1<P>` for JSON-boundary validation,
|
|
50
|
-
* `factory: (P) => (CodecInstanceContext) => Codec` for the curried higher-order codec.
|
|
51
|
-
* The factory is called once per `storage.types` instance (or once per
|
|
52
|
-
* inline-`typeParams` column); per-instance state lives in the closure.
|
|
44
|
+
* The unified `CodecDescriptor<P>` shape applied to parameterized codecs — `paramsSchema: StandardSchemaV1<P>` for JSON-boundary validation, `factory: (P) => (CodecInstanceContext) => Codec` for the curried higher-order codec. The factory is called once per `storage.types` instance (or once per inline-`typeParams` column); per-instance state lives in the closure.
|
|
53
45
|
*
|
|
54
46
|
* Codec-registry-unification spec § Decision.
|
|
55
47
|
*/
|
|
56
48
|
export type RuntimeParameterizedCodecDescriptor<P = Record<string, unknown>> = CodecDescriptor<P>;
|
|
57
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Contributor protocol for SQL components (target, adapter, extension pack). The unified `codecs:` slot returns the full {@link CodecDescriptor} list — non-parameterized and parameterized descriptors live side-by-side in the same array. The framework dispatches every codec id through the unified descriptor map without branching on parameterization.
|
|
52
|
+
*/
|
|
58
53
|
export interface SqlStaticContributions {
|
|
59
|
-
readonly codecs: () =>
|
|
60
|
-
// biome-ignore lint/suspicious/noExplicitAny: needed for covariance with concrete descriptor types
|
|
61
|
-
readonly parameterizedCodecs: () => ReadonlyArray<RuntimeParameterizedCodecDescriptor<any>>;
|
|
54
|
+
readonly codecs: () => ReadonlyArray<AnyCodecDescriptor>;
|
|
62
55
|
readonly queryOperations?: () => ReadonlyArray<SqlOperationDescriptor>;
|
|
63
56
|
readonly mutationDefaultGenerators?: () => ReadonlyArray<RuntimeMutationDefaultGenerator>;
|
|
64
57
|
}
|
|
@@ -66,16 +59,9 @@ export interface SqlStaticContributions {
|
|
|
66
59
|
/**
|
|
67
60
|
* Scope across which a generator's value is constant.
|
|
68
61
|
*
|
|
69
|
-
* - `'field'` — one value per defaulting site (one column, one row).
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* - `'row'` — one value across all defaulting sites of one row of one
|
|
73
|
-
* operation. Cache strategy: per-call cache keyed by `generatorId`.
|
|
74
|
-
* Right for correlation ids stamped into multiple columns of one row.
|
|
75
|
-
* - `'query'` — one value across all rows and columns of one ORM
|
|
76
|
-
* operation. Cache strategy: caller-provided cache keyed by
|
|
77
|
-
* `generatorId`. Right for `timestampNow` (a single timestamp per
|
|
78
|
-
* bulk insert/update).
|
|
62
|
+
* - `'field'` — one value per defaulting site (one column, one row). Cache strategy: no cache; call per defaulting site. Right for per-row identifiers (UUIDs, CUIDs, ULIDs, nanoid, ksuid).
|
|
63
|
+
* - `'row'` — one value across all defaulting sites of one row of one operation. Cache strategy: per-call cache keyed by `generatorId`. Right for correlation ids stamped into multiple columns of one row.
|
|
64
|
+
* - `'query'` — one value across all rows and columns of one ORM operation. Cache strategy: caller-provided cache keyed by `generatorId`. Right for `timestampNow` (a single timestamp per bulk insert/update).
|
|
79
65
|
*/
|
|
80
66
|
export type GeneratorStability = 'field' | 'row' | 'query';
|
|
81
67
|
|
|
@@ -83,10 +69,7 @@ export interface RuntimeMutationDefaultGenerator {
|
|
|
83
69
|
readonly id: string;
|
|
84
70
|
readonly generate: (params?: Record<string, unknown>) => unknown;
|
|
85
71
|
/**
|
|
86
|
-
* Scope across which the generator's value is constant. The framework
|
|
87
|
-
* derives the cache strategy from this declaration; generator authors
|
|
88
|
-
* never need to know about cache keys. See `GeneratorStability` for
|
|
89
|
-
* the per-value semantics.
|
|
72
|
+
* Scope across which the generator's value is constant. The framework derives the cache strategy from this declaration; generator authors never need to know about cache keys. See `GeneratorStability` for the per-value semantics.
|
|
90
73
|
*/
|
|
91
74
|
readonly stability: GeneratorStability;
|
|
92
75
|
}
|
|
@@ -149,10 +132,7 @@ export type SqlRuntimeAdapterInstance<TTargetId extends string = string> = Runti
|
|
|
149
132
|
Adapter<AnyQueryAst, Contract<SqlStorage>, LoweredStatement>;
|
|
150
133
|
|
|
151
134
|
/**
|
|
152
|
-
* NOTE: Binding type is intentionally erased to unknown at this shared runtime layer.
|
|
153
|
-
* Target clients (for example `postgres()`) validate and construct the concrete binding
|
|
154
|
-
* before calling `driver.connect(binding)`, which keeps runtime behavior safe today.
|
|
155
|
-
* A future follow-up can preserve TBinding through stack/context generics end-to-end.
|
|
135
|
+
* NOTE: Binding type is intentionally erased to unknown at this shared runtime layer. Target clients (for example `postgres()`) validate and construct the concrete binding before calling `driver.connect(binding)`, which keeps runtime behavior safe today. A future follow-up can preserve TBinding through stack/context generics end-to-end.
|
|
156
136
|
*/
|
|
157
137
|
export type SqlRuntimeDriverInstance<TTargetId extends string = string> = RuntimeDriverInstance<
|
|
158
138
|
'sql',
|
|
@@ -176,7 +156,7 @@ export function createSqlExecutionStack<TTargetId extends string>(options: {
|
|
|
176
156
|
});
|
|
177
157
|
}
|
|
178
158
|
|
|
179
|
-
export type { ExecutionContext,
|
|
159
|
+
export type { ExecutionContext, TypeHelperRegistry };
|
|
180
160
|
|
|
181
161
|
export function assertExecutionStackContractRequirements(
|
|
182
162
|
contract: Contract<SqlStorage>,
|
|
@@ -230,15 +210,15 @@ export function assertExecutionStackContractRequirements(
|
|
|
230
210
|
|
|
231
211
|
function validateTypeParams(
|
|
232
212
|
typeParams: Record<string, unknown>,
|
|
233
|
-
|
|
213
|
+
descriptor: RuntimeParameterizedCodecDescriptor,
|
|
234
214
|
context: { typeName?: string; tableName?: string; columnName?: string },
|
|
235
215
|
): Record<string, unknown> {
|
|
236
|
-
const result =
|
|
216
|
+
const result = descriptor.paramsSchema['~standard'].validate(typeParams);
|
|
237
217
|
if (result instanceof Promise) {
|
|
238
218
|
throw runtimeError(
|
|
239
219
|
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
240
|
-
`paramsSchema for codec '${
|
|
241
|
-
{ ...context, codecId:
|
|
220
|
+
`paramsSchema for codec '${descriptor.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`,
|
|
221
|
+
{ ...context, codecId: descriptor.codecId, typeParams },
|
|
242
222
|
);
|
|
243
223
|
}
|
|
244
224
|
if (result.issues) {
|
|
@@ -248,93 +228,49 @@ function validateTypeParams(
|
|
|
248
228
|
: `column '${context.tableName}.${context.columnName}'`;
|
|
249
229
|
throw runtimeError(
|
|
250
230
|
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
251
|
-
`Invalid typeParams for ${locationInfo} (codecId: ${
|
|
252
|
-
{ ...context, codecId:
|
|
231
|
+
`Invalid typeParams for ${locationInfo} (codecId: ${descriptor.codecId}): ${messages}`,
|
|
232
|
+
{ ...context, codecId: descriptor.codecId, typeParams },
|
|
253
233
|
);
|
|
254
234
|
}
|
|
255
235
|
return result.value as Record<string, unknown>;
|
|
256
236
|
}
|
|
257
237
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Collect every {@link CodecDescriptor} contributed by the SQL stack and partition into "parameterized" vs "non-parameterized" via the descriptor's own {@link CodecDescriptorImpl.isParameterized} getter. The getter is the canonical discriminator — a `paramsSchema` identity check would misroute any descriptor that doesn't reuse the exact `voidParamsSchema` singleton (e.g. a non-parameterized codec authoring its own no-op schema).
|
|
240
|
+
*
|
|
241
|
+
* The unified descriptor list collapses the legacy split (a separate slot used to register parameterized codecs) — every codec id resolves through the same map (codec-registry-unification spec § Decision).
|
|
242
|
+
*/
|
|
243
|
+
function collectCodecDescriptors(contributors: ReadonlyArray<SqlStaticContributions>): {
|
|
244
|
+
readonly all: ReadonlyArray<AnyCodecDescriptor>;
|
|
245
|
+
readonly parameterized: Map<string, RuntimeParameterizedCodecDescriptor>;
|
|
246
|
+
} {
|
|
247
|
+
const all: AnyCodecDescriptor[] = [];
|
|
248
|
+
const parameterized = new Map<string, RuntimeParameterizedCodecDescriptor>();
|
|
249
|
+
const seen = new Set<string>();
|
|
262
250
|
|
|
263
251
|
for (const contributor of contributors) {
|
|
264
|
-
for (const descriptor of contributor.
|
|
265
|
-
if (
|
|
252
|
+
for (const descriptor of contributor.codecs()) {
|
|
253
|
+
if (seen.has(descriptor.codecId)) {
|
|
266
254
|
throw runtimeError(
|
|
267
|
-
'RUNTIME.
|
|
268
|
-
`Duplicate
|
|
255
|
+
'RUNTIME.DUPLICATE_CODEC',
|
|
256
|
+
`Duplicate codec descriptor for codecId '${descriptor.codecId}'.`,
|
|
269
257
|
{ codecId: descriptor.codecId },
|
|
270
258
|
);
|
|
271
259
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* (which already ship as `CodecDescriptor`s) with synthesized descriptors
|
|
282
|
-
* for non-parameterized codecs registered through the legacy `codecs:`
|
|
283
|
-
* slot. Codec ids that ship a parameterized descriptor take precedence —
|
|
284
|
-
* even when the legacy registry registers a representative codec under
|
|
285
|
-
* the same id, the parameterized descriptor is the authoritative source.
|
|
286
|
-
*
|
|
287
|
-
* Codec-registry-unification spec § Decision: every codec resolves
|
|
288
|
-
* through one descriptor map; reads are non-branching.
|
|
289
|
-
*/
|
|
290
|
-
function buildCodecDescriptorRegistry(
|
|
291
|
-
codecRegistry: CodecRegistry,
|
|
292
|
-
parameterizedDescriptors: Map<string, RuntimeParameterizedCodecDescriptor>,
|
|
293
|
-
): CodecDescriptorRegistry {
|
|
294
|
-
type AnyDescriptor = CodecDescriptor<unknown>;
|
|
295
|
-
const byId = new Map<string, AnyDescriptor>();
|
|
296
|
-
const byTargetType = new Map<string, Array<AnyDescriptor>>();
|
|
297
|
-
|
|
298
|
-
function registerInIndices(descriptor: AnyDescriptor): void {
|
|
299
|
-
byId.set(descriptor.codecId, descriptor);
|
|
300
|
-
for (const targetType of descriptor.targetTypes) {
|
|
301
|
-
const list = byTargetType.get(targetType);
|
|
302
|
-
if (list) {
|
|
303
|
-
list.push(descriptor);
|
|
304
|
-
} else {
|
|
305
|
-
byTargetType.set(targetType, [descriptor]);
|
|
260
|
+
seen.add(descriptor.codecId);
|
|
261
|
+
all.push(descriptor);
|
|
262
|
+
|
|
263
|
+
if (descriptor.isParameterized) {
|
|
264
|
+
// Cast widens the descriptor's heterogeneous `P` to the runtime alias surface; consumers narrow per codec id at the dispatch site, where the descriptor's own `paramsSchema` validates JSON-sourced params before the factory ever sees them.
|
|
265
|
+
parameterized.set(
|
|
266
|
+
descriptor.codecId,
|
|
267
|
+
descriptor as unknown as RuntimeParameterizedCodecDescriptor,
|
|
268
|
+
);
|
|
306
269
|
}
|
|
307
270
|
}
|
|
308
271
|
}
|
|
309
272
|
|
|
310
|
-
|
|
311
|
-
// params shape. The public `CodecDescriptorRegistry` interface widens to
|
|
312
|
-
// `CodecDescriptor<unknown>` and consumers narrow per codec id at the
|
|
313
|
-
// call site (the descriptor's `paramsSchema` validates JSON-sourced
|
|
314
|
-
// params before the factory ever sees them, so the runtime narrow is
|
|
315
|
-
// safe). The cast at registration goes through `unknown` because
|
|
316
|
-
// `CodecDescriptor<P>` is invariant in `P` (the `factory` and
|
|
317
|
-
// `renderOutputType` slots use `P` contravariantly).
|
|
318
|
-
for (const descriptor of parameterizedDescriptors.values()) {
|
|
319
|
-
registerInIndices(descriptor as unknown as AnyDescriptor);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
for (const codec of codecRegistry.values()) {
|
|
323
|
-
if (byId.has(codec.id)) continue;
|
|
324
|
-
registerInIndices(synthesizeNonParameterizedDescriptor(codec) as unknown as AnyDescriptor);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
descriptorFor(codecId: string): AnyDescriptor | undefined {
|
|
329
|
-
return byId.get(codecId);
|
|
330
|
-
},
|
|
331
|
-
*values(): IterableIterator<AnyDescriptor> {
|
|
332
|
-
yield* byId.values();
|
|
333
|
-
},
|
|
334
|
-
byTargetType(targetType: string): readonly AnyDescriptor[] {
|
|
335
|
-
return byTargetType.get(targetType) ?? Object.freeze([]);
|
|
336
|
-
},
|
|
337
|
-
};
|
|
273
|
+
return { all, parameterized };
|
|
338
274
|
}
|
|
339
275
|
|
|
340
276
|
function collectTypeRefSites(
|
|
@@ -373,8 +309,7 @@ function initializeTypeHelpers(
|
|
|
373
309
|
const descriptor = codecDescriptors.get(typeInstance.codecId);
|
|
374
310
|
|
|
375
311
|
if (!descriptor) {
|
|
376
|
-
// No parameterized descriptor for this codec id — store the raw
|
|
377
|
-
// type instance for callers that need typeParams metadata.
|
|
312
|
+
// No parameterized descriptor for this codec id — store the raw type instance for callers that need typeParams metadata.
|
|
378
313
|
helpers[typeName] = typeInstance;
|
|
379
314
|
continue;
|
|
380
315
|
}
|
|
@@ -407,31 +342,6 @@ function validateColumnTypeParams(
|
|
|
407
342
|
}
|
|
408
343
|
}
|
|
409
344
|
|
|
410
|
-
/**
|
|
411
|
-
* View of a codec that exposes a per-instance JSON-schema `validate`
|
|
412
|
-
* function. Codecs declare this contract by including the
|
|
413
|
-
* `'json-validator'` `CodecTrait` in their `traits` array; the trait is
|
|
414
|
-
* the gate that lets `extractValidator` resolve from structurally-typed
|
|
415
|
-
* `unknown` to this typed view.
|
|
416
|
-
*/
|
|
417
|
-
type JsonValidatorCodec = {
|
|
418
|
-
readonly traits?: ReadonlyArray<unknown>;
|
|
419
|
-
readonly validate: JsonSchemaValidateFn;
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
function hasJsonValidatorTrait(candidate: unknown): candidate is JsonValidatorCodec {
|
|
423
|
-
if (candidate === null || typeof candidate !== 'object') return false;
|
|
424
|
-
const traits = (candidate as { readonly traits?: unknown }).traits;
|
|
425
|
-
if (!Array.isArray(traits)) return false;
|
|
426
|
-
if (!traits.includes('json-validator')) return false;
|
|
427
|
-
const validate = (candidate as { readonly validate?: unknown }).validate;
|
|
428
|
-
return typeof validate === 'function';
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function extractValidator(candidate: unknown): JsonSchemaValidateFn | undefined {
|
|
432
|
-
return hasJsonValidatorTrait(candidate) ? candidate.validate : undefined;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
345
|
function isResolvedCodec(candidate: unknown): candidate is Codec {
|
|
436
346
|
return (
|
|
437
347
|
candidate !== null &&
|
|
@@ -442,52 +352,59 @@ function isResolvedCodec(candidate: unknown): candidate is Codec {
|
|
|
442
352
|
}
|
|
443
353
|
|
|
444
354
|
/**
|
|
445
|
-
* Walk the contract's `storage.tables[].columns[]` and resolve each
|
|
446
|
-
* column to a `Codec` through the unified descriptor map. Per-instance
|
|
447
|
-
* behavior:
|
|
355
|
+
* Walk the contract's `storage.tables[].columns[]` and resolve each column to a `Codec` through the unified descriptor map. Per-instance behavior:
|
|
448
356
|
*
|
|
449
|
-
* - **typeRef columns**: reuse the resolved codec materialized once by
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* - **inline-typeParams columns**: call `descriptor.factory(typeParams)
|
|
453
|
-
* (ctx)` once per column (per-column anonymous instance).
|
|
454
|
-
* - **non-parameterized columns**: call `descriptor.factory()(ctx)`
|
|
455
|
-
* once. The synthesized descriptor's factory is constant — every call
|
|
456
|
-
* returns the same shared codec instance — so columns sharing a non-
|
|
457
|
-
* parameterized codec id share one resolved codec without explicit
|
|
458
|
-
* caching.
|
|
357
|
+
* - **typeRef columns**: reuse the resolved codec materialized once by `initializeTypeHelpers` for the `storage.types` entry. Multiple columns sharing one typeRef share one codec instance.
|
|
358
|
+
* - **inline-typeParams columns**: call `descriptor.factory(typeParams) (ctx)` once per column (per-column anonymous instance).
|
|
359
|
+
* - **non-parameterized columns**: call `descriptor.factory()(ctx)` once. The synthesized descriptor's factory is constant — every call returns the same shared codec instance — so columns sharing a non-parameterized codec id share one resolved codec without explicit caching.
|
|
459
360
|
*
|
|
460
|
-
*
|
|
461
|
-
* old `buildJsonSchemaValidatorRegistry` (per-column walk) used to do
|
|
462
|
-
* separately: one walk over all columns, one resolved codec per column,
|
|
463
|
-
* one trait-gated validator extraction per column. The result drives
|
|
464
|
-
* both the dispatch registry (`ContractCodecRegistry.forColumn`) and the
|
|
465
|
-
* validator registry.
|
|
466
|
-
*
|
|
467
|
-
* Codec-registry-unification spec § AC-4: every column resolves through
|
|
468
|
-
* one descriptor map without branching on parameterization.
|
|
361
|
+
* Codec-registry-unification spec § AC-4: every column resolves through one descriptor map without branching on parameterization. JSON-Schema validation, when required, lives inside the resolved codec's `decode` body (see `arktype-json`'s `ArktypeJsonCodecClass`); the framework no longer maintains a parallel validator registry.
|
|
469
362
|
*/
|
|
470
363
|
function buildContractCodecRegistry(
|
|
471
364
|
contract: Contract<SqlStorage>,
|
|
472
365
|
codecDescriptors: CodecDescriptorRegistry,
|
|
473
|
-
legacyCodecRegistry: CodecRegistry,
|
|
474
366
|
types: TypeHelperRegistry,
|
|
475
367
|
parameterizedDescriptors: Map<string, RuntimeParameterizedCodecDescriptor>,
|
|
476
|
-
): {
|
|
477
|
-
readonly registry: ContractCodecRegistry;
|
|
478
|
-
readonly jsonValidators: JsonSchemaValidatorRegistry | undefined;
|
|
479
|
-
} {
|
|
368
|
+
): ContractCodecRegistry {
|
|
480
369
|
const byColumn = new Map<string, Codec>();
|
|
481
370
|
const byCodecId = new Map<string, Codec>();
|
|
482
|
-
// Codec ids whose `byCodecId` entry is ambiguous — multiple distinct
|
|
483
|
-
//
|
|
484
|
-
// `Vector<1024>` and `Vector<1536>` both registering under
|
|
485
|
-
// `pg/vector@1`). The encode-side `forCodecId` fallback rejects these
|
|
486
|
-
// ids so a DSL-param without a column ref cannot silently bind to the
|
|
487
|
-
// wrong instance. Retires when AC-5's `ParamRef.refs` plumbing lands
|
|
488
|
-
// (TML-2357).
|
|
371
|
+
// Codec ids whose `byCodecId` entry is ambiguous — multiple distinct resolved instances landed under the same parameterized codec id (e.g. `Vector<1024>` and `Vector<1536>` both registering under `pg/vector@1`). The refs-less `forCodecId` fallback rejects these ids so a DSL-param without a column ref cannot silently bind to the wrong instance. The validator pass enforces refs on every parameterized `ParamRef`, so this
|
|
372
|
+
// branch is reachable only as a defensive guard for non-parameterized columns whose `byCodecId` entry is unique by construction.
|
|
489
373
|
const ambiguousCodecIds = new Set<string>();
|
|
490
|
-
|
|
374
|
+
|
|
375
|
+
// Pre-populate `byCodecId` with non-parameterized descriptor instances. Refs-less encode/decode call sites (computed projections without a column ref, transient builder ParamRefs) resolve through `forCodecId(id)` and need a representative instance for codec ids that no contract column declares. Non-parameterized descriptors' factories are constant — every call yields the same shared codec — so a single materialization
|
|
376
|
+
// is correct.
|
|
377
|
+
for (const descriptor of codecDescriptors.values()) {
|
|
378
|
+
if (descriptor.isParameterized) continue;
|
|
379
|
+
const ctx: SqlCodecInstanceContext = {
|
|
380
|
+
name: `<shared:${descriptor.codecId}>`,
|
|
381
|
+
usedAt: [],
|
|
382
|
+
};
|
|
383
|
+
const voidFactory = descriptor.factory as unknown as (
|
|
384
|
+
params: undefined,
|
|
385
|
+
) => (ctx: SqlCodecInstanceContext) => Codec;
|
|
386
|
+
byCodecId.set(descriptor.codecId, voidFactory(undefined)(ctx));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Representative instances for parameterized descriptors whose factory tolerates `factory(undefined)` (e.g. pgvector — the factory ignores its params and returns the same shared codec). Used as the last-resort fallback in `forCodecId` for refs-less call sites whose codec id has no contract column the walk could resolve through (e.g. `cosineSimilarity(col, [literal])` builds an inline `ParamRef` without column refs).
|
|
390
|
+
// Stored separately so column-bound walk results don't trip the ambiguity check, and consulted only when `byCodecId` has no column-bound entry. Descriptors whose factory needs real params (arktype-json) raise and are skipped — the per-column dispatch path materializes those lazily when refs are populated.
|
|
391
|
+
const parameterizedRepresentatives = new Map<string, Codec>();
|
|
392
|
+
for (const descriptor of codecDescriptors.values()) {
|
|
393
|
+
if (!descriptor.isParameterized) continue;
|
|
394
|
+
const ctx: SqlCodecInstanceContext = {
|
|
395
|
+
name: `<shared:${descriptor.codecId}>`,
|
|
396
|
+
usedAt: [],
|
|
397
|
+
};
|
|
398
|
+
// Call `factory` *as a method on the descriptor* — `descriptor.factory(undefined)` — rather than detaching it into a local. Several descriptors implement `factory` as a class method whose body returns an arrow that captures `this` (`return () => new SomeCodec(this);`), and detaching loses that binding so the codec ends up with an `undefined` descriptor and `codec.id` throws.
|
|
399
|
+
const factory = descriptor.factory.bind(descriptor) as unknown as (
|
|
400
|
+
params: unknown,
|
|
401
|
+
) => (ctx: SqlCodecInstanceContext) => Codec;
|
|
402
|
+
try {
|
|
403
|
+
parameterizedRepresentatives.set(descriptor.codecId, factory(undefined)(ctx));
|
|
404
|
+
} catch {
|
|
405
|
+
// Parameterized descriptor whose factory requires real params; refs-less fallback for this codec id is unavailable.
|
|
406
|
+
}
|
|
407
|
+
}
|
|
491
408
|
|
|
492
409
|
for (const [tableName, table] of Object.entries(contract.storage.tables)) {
|
|
493
410
|
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
@@ -500,10 +417,7 @@ function buildContractCodecRegistry(
|
|
|
500
417
|
const isParameterized = parameterizedDescriptors.has(column.codecId);
|
|
501
418
|
|
|
502
419
|
if (column.typeRef) {
|
|
503
|
-
// The named instance was already materialized once by
|
|
504
|
-
// `initializeTypeHelpers`; reuse it so multiple columns sharing
|
|
505
|
-
// the same typeRef share one codec instance (and any per-
|
|
506
|
-
// instance helper state on it).
|
|
420
|
+
// The named instance was already materialized once by `initializeTypeHelpers`; reuse it so multiple columns sharing the same typeRef share one codec instance (and any per-instance helper state on it).
|
|
507
421
|
const helper = types[column.typeRef];
|
|
508
422
|
if (isResolvedCodec(helper)) {
|
|
509
423
|
resolvedCodec = helper;
|
|
@@ -516,60 +430,30 @@ function buildContractCodecRegistry(
|
|
|
516
430
|
columnName,
|
|
517
431
|
});
|
|
518
432
|
const ctx: SqlCodecInstanceContext = {
|
|
519
|
-
name: `<
|
|
433
|
+
name: `<col:${tableName}.${columnName}>`,
|
|
520
434
|
usedAt: [{ table: tableName, column: columnName }],
|
|
521
435
|
};
|
|
522
436
|
resolvedCodec = parameterizedDescriptor.factory(validatedParams)(ctx);
|
|
523
437
|
}
|
|
524
438
|
} else if (!isParameterized) {
|
|
525
|
-
// Non-parameterized column
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// `CodecDescriptor<void>` whose factory ignores its params
|
|
537
|
-
// and ctx; the runtime's `void` value is `undefined`. The
|
|
538
|
-
// structural cast goes through `unknown` to satisfy the
|
|
539
|
-
// heterogeneous-`P` registry boundary (the factory's
|
|
540
|
-
// declared `P` is `any` here; the consumer narrows per
|
|
541
|
-
// codec id). The cast narrows the descriptor's
|
|
542
|
-
// family-agnostic `CodecInstanceContext` slot to the SQL
|
|
543
|
-
// `SqlCodecInstanceContext` we pass at this call site —
|
|
544
|
-
// function-argument contravariance makes the narrow safe
|
|
545
|
-
// (a callee that accepts the base will also accept the
|
|
546
|
-
// SQL extension). Per spec § Non-functional constraints.
|
|
547
|
-
const voidFactory = descriptor.factory as unknown as (
|
|
548
|
-
params: undefined,
|
|
549
|
-
) => (ctx: SqlCodecInstanceContext) => Codec;
|
|
550
|
-
cached = voidFactory(undefined)(ctx);
|
|
551
|
-
byCodecId.set(column.codecId, cached);
|
|
552
|
-
}
|
|
553
|
-
resolvedCodec = cached;
|
|
439
|
+
// Non-parameterized column: materialize a fresh codec instance per `forColumn(table, column)` entry with a column-specific `SqlCodecInstanceContext`. The pre-populated `byCodecId` representative (built with the synthetic `<shared:codecId>` context and empty `usedAt`) is reserved for `forCodecId()` refs-less fallbacks; reusing it for column-bound dispatch would erase per-column diagnostics for any descriptor whose factory reads `CodecInstanceContext`.
|
|
440
|
+
const ctx: SqlCodecInstanceContext = {
|
|
441
|
+
name: `<col:${tableName}.${columnName}>`,
|
|
442
|
+
usedAt: [{ table: tableName, column: columnName }],
|
|
443
|
+
};
|
|
444
|
+
// The descriptor's `P` is `void` for non-parameterized codecs; the runtime's `void` value is `undefined`. The cast narrows the descriptor's family-agnostic `CodecInstanceContext` slot to the SQL `SqlCodecInstanceContext` we pass at this call site — function-argument contravariance makes the narrow safe.
|
|
445
|
+
// `bind` preserves the `this`-on-descriptor invariant — several descriptors implement `factory` as a class method whose body returns an arrow that captures `this`; detaching loses the binding and produces a codec whose `descriptor` is `undefined`.
|
|
446
|
+
const voidFactory = descriptor.factory.bind(descriptor) as unknown as (
|
|
447
|
+
params: undefined,
|
|
448
|
+
) => (ctx: SqlCodecInstanceContext) => Codec;
|
|
449
|
+
resolvedCodec = voidFactory(undefined)(ctx);
|
|
554
450
|
}
|
|
555
|
-
// else: parameterized codec id with no typeRef and no typeParams
|
|
556
|
-
//
|
|
557
|
-
// ship a no-params column variant alongside a parameterized one
|
|
558
|
-
// (e.g. pgvector's `vectorColumn` vs. `vector(N)`). Leave
|
|
559
|
-
// `resolvedCodec` undefined; encode/decode for this column flows
|
|
560
|
-
// through `forCodecId` (the AC-5-deferred carve-out documented
|
|
561
|
-
// in `relational-core/src/ast/codec-types.ts`). The fallback
|
|
562
|
-
// works for these cases because their wire format is
|
|
563
|
-
// params-independent (vector formats `[v1,v2,...]` regardless
|
|
564
|
-
// of declared length).
|
|
451
|
+
// else: parameterized codec id with no typeRef and no typeParams — this is the legitimate "undimensioned" form for codecs that ship a no-params column variant alongside a parameterized one (e.g. pgvector's `vectorColumn` vs. `vector(N)`). Leave `resolvedCodec` undefined; encode/decode for this column flows through `forCodecId`. The fallback works for these cases because their wire format is params-independent (vector
|
|
452
|
+
// formats `[v1,v2,...]` regardless of declared length).
|
|
565
453
|
}
|
|
566
454
|
|
|
567
455
|
if (resolvedCodec) {
|
|
568
456
|
byColumn.set(columnKey, resolvedCodec);
|
|
569
|
-
const validate = extractValidator(resolvedCodec);
|
|
570
|
-
if (validate) {
|
|
571
|
-
validators.set(columnKey, validate);
|
|
572
|
-
}
|
|
573
457
|
const existing = byCodecId.get(column.codecId);
|
|
574
458
|
if (existing === undefined) {
|
|
575
459
|
byCodecId.set(column.codecId, resolvedCodec);
|
|
@@ -585,20 +469,10 @@ function buildContractCodecRegistry(
|
|
|
585
469
|
return byColumn.get(`${table}.${column}`);
|
|
586
470
|
},
|
|
587
471
|
forCodecId(codecId) {
|
|
588
|
-
// Codec-id-only fallback for sites
|
|
589
|
-
//
|
|
590
|
-
// the contract-walk-derived shared codec; fall back to the legacy
|
|
591
|
-
// `codecRegistry.get` for parameterized codec ids whose contracts
|
|
592
|
-
// don't have a typeRef/typeParams column the walk could resolve
|
|
593
|
-
// through. The legacy fallback retires once `ParamRef.refs` is
|
|
594
|
-
// threaded everywhere (TML-2357).
|
|
472
|
+
// Codec-id-only fallback for refs-less sites. The validator pass (`validateParamRefRefs`) enforces refs on every parameterized `ParamRef` before encode, so this path is only legitimately reachable for non-parameterized codec ids. The map is pre-populated with every non-parameterized descriptor's canonical instance and overlaid with column-resolved instances from the contract walk; parameterized codec ids without a
|
|
473
|
+
// typeRef/typeParams-bound column never reach this map.
|
|
595
474
|
//
|
|
596
|
-
// Reject ambiguous parameterized fallbacks: if the contract walk
|
|
597
|
-
// resolved more than one distinct codec instance under this id
|
|
598
|
-
// (e.g. multiple vector dimensions, multiple arktype-json
|
|
599
|
-
// schemas), the codec-id-keyed lookup cannot honor the call site
|
|
600
|
-
// — fail fast rather than bind to whichever instance happened to
|
|
601
|
-
// land first.
|
|
475
|
+
// Reject ambiguous parameterized fallbacks: if the contract walk resolved more than one distinct codec instance under this id (e.g. multiple vector dimensions, multiple arktype-json schemas), the codec-id-keyed lookup cannot honor the call site — fail fast rather than bind to whichever instance happened to land first.
|
|
602
476
|
if (ambiguousCodecIds.has(codecId)) {
|
|
603
477
|
throw runtimeError(
|
|
604
478
|
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
@@ -606,19 +480,11 @@ function buildContractCodecRegistry(
|
|
|
606
480
|
{ codecId },
|
|
607
481
|
);
|
|
608
482
|
}
|
|
609
|
-
return byCodecId.get(codecId) ??
|
|
483
|
+
return byCodecId.get(codecId) ?? parameterizedRepresentatives.get(codecId);
|
|
610
484
|
},
|
|
611
485
|
};
|
|
612
486
|
|
|
613
|
-
|
|
614
|
-
validators.size > 0
|
|
615
|
-
? {
|
|
616
|
-
get: (key: string) => validators.get(key),
|
|
617
|
-
size: validators.size,
|
|
618
|
-
}
|
|
619
|
-
: undefined;
|
|
620
|
-
|
|
621
|
-
return { registry, jsonValidators };
|
|
487
|
+
return registry;
|
|
622
488
|
}
|
|
623
489
|
|
|
624
490
|
function assertMutationDefaultGeneratorsAvailable(
|
|
@@ -714,8 +580,7 @@ function applyMutationDefaults(
|
|
|
714
580
|
|
|
715
581
|
const applied: AppliedMutationDefault[] = [];
|
|
716
582
|
const appliedColumns = new Set<string>();
|
|
717
|
-
// Fresh per-call cache for `stability: 'row'` generators — they share
|
|
718
|
-
// across columns of a single row but regenerate on the next call.
|
|
583
|
+
// Fresh per-call cache for `stability: 'row'` generators — they share across columns of a single row but regenerate on the next call.
|
|
719
584
|
const rowCache = new Map<string, unknown>();
|
|
720
585
|
|
|
721
586
|
for (const mutationDefault of defaults) {
|
|
@@ -729,8 +594,7 @@ function applyMutationDefaults(
|
|
|
729
594
|
continue;
|
|
730
595
|
}
|
|
731
596
|
|
|
732
|
-
// RD2: empty update payloads skip onUpdate defaults — no write means
|
|
733
|
-
// no `@updatedAt` advance.
|
|
597
|
+
// RD2: empty update payloads skip onUpdate defaults — no write means no `@updatedAt` advance.
|
|
734
598
|
if (isEmptyUpdate) {
|
|
735
599
|
continue;
|
|
736
600
|
}
|
|
@@ -803,19 +667,14 @@ export function createExecutionContext<
|
|
|
803
667
|
|
|
804
668
|
assertExecutionStackContractRequirements(contract, stack);
|
|
805
669
|
|
|
806
|
-
const codecRegistry = createCodecRegistry();
|
|
807
|
-
|
|
808
670
|
const contributors: Array<SqlStaticContributions & ComponentDescriptor<string>> = [
|
|
809
671
|
stack.target,
|
|
810
672
|
stack.adapter,
|
|
811
673
|
...stack.extensionPacks,
|
|
812
674
|
];
|
|
813
675
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
codecRegistry.register(c);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
676
|
+
const { all: allCodecDescriptors, parameterized: parameterizedCodecDescriptors } =
|
|
677
|
+
collectCodecDescriptors(contributors);
|
|
819
678
|
|
|
820
679
|
const queryOperationRegistry = createSqlOperationRegistry();
|
|
821
680
|
for (const contributor of contributors) {
|
|
@@ -824,11 +683,7 @@ export function createExecutionContext<
|
|
|
824
683
|
}
|
|
825
684
|
}
|
|
826
685
|
|
|
827
|
-
const
|
|
828
|
-
const codecDescriptors = buildCodecDescriptorRegistry(
|
|
829
|
-
codecRegistry,
|
|
830
|
-
parameterizedCodecDescriptors,
|
|
831
|
-
);
|
|
686
|
+
const codecDescriptors = buildCodecDescriptorRegistry(allCodecDescriptors);
|
|
832
687
|
const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
|
|
833
688
|
assertMutationDefaultGeneratorsAvailable(contract, mutationDefaultGeneratorRegistry);
|
|
834
689
|
|
|
@@ -838,23 +693,19 @@ export function createExecutionContext<
|
|
|
838
693
|
|
|
839
694
|
const types = initializeTypeHelpers(contract.storage, parameterizedCodecDescriptors);
|
|
840
695
|
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
parameterizedCodecDescriptors,
|
|
848
|
-
);
|
|
696
|
+
const contractCodecs = buildContractCodecRegistry(
|
|
697
|
+
contract,
|
|
698
|
+
codecDescriptors,
|
|
699
|
+
types,
|
|
700
|
+
parameterizedCodecDescriptors,
|
|
701
|
+
);
|
|
849
702
|
|
|
850
703
|
return {
|
|
851
704
|
contract,
|
|
852
|
-
codecs: codecRegistry,
|
|
853
705
|
contractCodecs,
|
|
854
706
|
codecDescriptors,
|
|
855
707
|
queryOperations: queryOperationRegistry,
|
|
856
708
|
types,
|
|
857
|
-
...(jsonSchemaValidators ? { jsonSchemaValidators } : {}),
|
|
858
709
|
applyMutationDefaults: (options) =>
|
|
859
710
|
applyMutationDefaults(contract, mutationDefaultGeneratorRegistry, options),
|
|
860
711
|
};
|