@prisma-next/sql-runtime 0.12.0 → 0.13.0-dev.2

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.
@@ -30,19 +30,10 @@ import { canonicalizeJson } from '@prisma-next/framework-components/utils';
30
30
  import {
31
31
  isPostgresEnumStorageEntry,
32
32
  type SqlStorage,
33
- type StorageTable,
34
33
  type StorageTypeInstance,
35
34
  } from '@prisma-next/sql-contract/types';
36
35
  import { blindCast } from '@prisma-next/utils/casts';
37
36
 
38
- // Framework `Namespace.tables` is widened to `Record<string, object>` so
39
- // emitted `contract.d.ts` table literals satisfy it structurally. At
40
- // runtime, every `SqlStorage` namespace holds `StorageTable` instances —
41
- // the constructor enforces it. Narrowing per-iteration with a single-step
42
- // cast (not `as unknown as`) keeps Pattern C walks readable without
43
- // weakening the substrate type.
44
- type SqlNamespaceTables = Readonly<Record<string, StorageTable>>;
45
-
46
37
  function documentScopedCodecTypes(
47
38
  contract: Contract<SqlStorage>,
48
39
  ): Record<string, StorageTypeInstance> | undefined {
@@ -344,7 +335,7 @@ function collectTypeRefSites(
344
335
  ): Map<string, Array<{ readonly table: string; readonly column: string }>> {
345
336
  const sites = new Map<string, Array<{ readonly table: string; readonly column: string }>>();
346
337
  for (const ns of Object.values(storage.namespaces)) {
347
- for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) {
338
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
348
339
  for (const [columnName, column] of Object.entries(table.columns)) {
349
340
  if (typeof column.typeRef !== 'string') continue;
350
341
  const list = sites.get(column.typeRef);
@@ -387,7 +378,9 @@ function initializeTypeHelpers(
387
378
  continue;
388
379
  }
389
380
 
390
- const validatedParams = validateTypeParams(typeParams, descriptor, {
381
+ // `typeParams` may be absent on the canonical empty form. Forward `{}`
382
+ // to the descriptor's paramsSchema so it can probe its own optionality.
383
+ const validatedParams = validateTypeParams(typeParams ?? {}, descriptor, {
391
384
  typeName,
392
385
  });
393
386
 
@@ -404,7 +397,7 @@ function validateColumnTypeParams(
404
397
  codecDescriptors: Map<string, RuntimeParameterizedCodecDescriptor>,
405
398
  ): void {
406
399
  for (const ns of Object.values(storage.namespaces)) {
407
- for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) {
400
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
408
401
  for (const [columnName, column] of Object.entries(table.columns)) {
409
402
  if (column.typeParams) {
410
403
  const descriptor = codecDescriptors.get(column.codecId);
@@ -432,10 +425,10 @@ function assertColumnCodecIntegrity(
432
425
  storage: SqlStorage,
433
426
  codecDescriptors: CodecDescriptorRegistry,
434
427
  ): void {
435
- for (const ns of Object.values(storage.namespaces)) {
436
- for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) {
428
+ for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
429
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
437
430
  for (const columnName of Object.keys(table.columns)) {
438
- const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
431
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
439
432
  if (!ref) continue;
440
433
 
441
434
  const descriptor = codecDescriptors.descriptorFor(ref.codecId);
@@ -480,7 +473,22 @@ function assertColumnCodecIntegrity(
480
473
  }
481
474
  }
482
475
 
483
- if (!descriptor.isParameterized && ref.typeParams !== undefined) {
476
+ // An object-typed field's empty state is canonical at every boundary
477
+ // that compares them: `typeParams: {}` and missing `typeParams` are
478
+ // equivalent — both mean "no parameters were supplied". A
479
+ // non-parameterized codec only conflicts with typeParams that carry
480
+ // at least one key. The PSL interpreter emits `typeParams: {}` for
481
+ // `@db.X` named types whose body has no parameters; treating that as
482
+ // a mismatch would reject every such alias against `pg/text@1`
483
+ // (e.g. the supabase extension's `Uuid` type).
484
+ const refTypeParams = ref.typeParams;
485
+ const refHasTypeParamKeys =
486
+ refTypeParams !== undefined &&
487
+ refTypeParams !== null &&
488
+ typeof refTypeParams === 'object' &&
489
+ !Array.isArray(refTypeParams) &&
490
+ Object.keys(refTypeParams).length > 0;
491
+ if (!descriptor.isParameterized && refHasTypeParamKeys) {
484
492
  throw runtimeError(
485
493
  'RUNTIME.CODEC_PARAMETERIZATION_MISMATCH',
486
494
  `Column '${tableName}.${columnName}' supplies typeParams to non-parameterized codec '${ref.codecId}'. Remove the typeParams or switch to a parameterized codec id.`,
@@ -512,7 +520,7 @@ function assertColumnCodecIntegrity(
512
520
  *
513
521
  * Contract integrity is enforced upstream by {@link assertColumnCodecIntegrity}: every column must reference a registered `codecId` whose `descriptor.isParameterized` flag matches the presence of `typeParams` (via `codecRefForColumn`). The pre-population walk and `forColumn` therefore make no defensive checks — malformed columns fail fast at `createExecutionContext` construction with `RUNTIME.CODEC_DESCRIPTOR_MISSING` or `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH` rather than being silently skipped here.
514
522
  *
515
- * `forColumn(t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
523
+ * `forColumn(ns, t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(ns, t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
516
524
  */
517
525
  function buildContractCodecRegistry(
518
526
  contract: Contract<SqlStorage>,
@@ -526,15 +534,24 @@ function buildContractCodecRegistry(
526
534
  const typeRefSites = collectTypeRefSites(contract.storage);
527
535
  for (const [typeName, typeInstance] of Object.entries(documentScopedCodecTypes(contract) ?? {})) {
528
536
  const enumView = readEnumViewIfApplicable(typeInstance);
537
+ const instanceTypeParams = enumView
538
+ ? enumView.typeParams
539
+ : (typeInstance as StorageTypeInstance).typeParams;
540
+ const hasParamKeys =
541
+ instanceTypeParams !== undefined && Object.keys(instanceTypeParams).length > 0;
529
542
  const ref: CodecRef = enumView
530
- ? {
531
- codecId: enumView.codecId,
532
- typeParams: enumView.typeParams as unknown as JsonValue,
533
- }
534
- : {
535
- codecId: (typeInstance as StorageTypeInstance).codecId,
536
- typeParams: (typeInstance as StorageTypeInstance).typeParams as JsonValue,
537
- };
543
+ ? hasParamKeys
544
+ ? {
545
+ codecId: enumView.codecId,
546
+ typeParams: instanceTypeParams as unknown as JsonValue,
547
+ }
548
+ : { codecId: enumView.codecId }
549
+ : hasParamKeys
550
+ ? {
551
+ codecId: (typeInstance as StorageTypeInstance).codecId,
552
+ typeParams: instanceTypeParams as JsonValue,
553
+ }
554
+ : { codecId: (typeInstance as StorageTypeInstance).codecId };
538
555
  const key = refKeyOf(ref);
539
556
  const sites = typeRefSites.get(typeName) ?? [];
540
557
  const existing = usedAtByKey.get(key);
@@ -547,11 +564,11 @@ function buildContractCodecRegistry(
547
564
  }
548
565
  }
549
566
 
550
- for (const ns of Object.values(contract.storage.namespaces)) {
551
- for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) {
567
+ for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) {
568
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
552
569
  for (const [columnName, column] of Object.entries(table.columns)) {
553
570
  if (column.typeRef !== undefined) continue;
554
- const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
571
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
555
572
  if (!ref) continue;
556
573
  const key = refKeyOf(ref);
557
574
  const site = { table: tableName, column: columnName };
@@ -579,10 +596,10 @@ function buildContractCodecRegistry(
579
596
  };
580
597
  });
581
598
 
582
- for (const ns of Object.values(contract.storage.namespaces)) {
583
- for (const [tableName, table] of Object.entries(ns.tables as SqlNamespaceTables)) {
599
+ for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) {
600
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
584
601
  for (const columnName of Object.keys(table.columns)) {
585
- const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
602
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, tableName, columnName);
586
603
  if (!ref) continue;
587
604
  resolver.forCodecRef(ref);
588
605
  }
@@ -590,8 +607,8 @@ function buildContractCodecRegistry(
590
607
  }
591
608
 
592
609
  const registry: ContractCodecRegistry = {
593
- forColumn(table, column) {
594
- const ref = codecDescriptors.codecRefForColumn(table, column);
610
+ forColumn(namespaceId, table, column) {
611
+ const ref = codecDescriptors.codecRefForColumn(namespaceId, table, column);
595
612
  return ref ? resolver.forCodecRef(ref) : undefined;
596
613
  },
597
614
  forCodecRef(ref) {
@@ -761,6 +761,21 @@ export async function withTransaction<R>(
761
761
  const transaction = await connection.transaction();
762
762
 
763
763
  let invalidated = false;
764
+
765
+ async function* guardedStream<Row>(
766
+ inner: AsyncIterable<Row>,
767
+ ): AsyncGenerator<Row, void, unknown> {
768
+ if (invalidated) {
769
+ throw transactionClosedError();
770
+ }
771
+ for await (const row of inner) {
772
+ yield row;
773
+ if (invalidated) {
774
+ throw transactionClosedError();
775
+ }
776
+ }
777
+ }
778
+
764
779
  const txContext: TransactionContext = {
765
780
  get invalidated() {
766
781
  return invalidated;
@@ -772,16 +787,7 @@ export async function withTransaction<R>(
772
787
  if (invalidated) {
773
788
  throw transactionClosedError();
774
789
  }
775
- const inner = transaction.execute(plan, options);
776
- const guarded = async function* (): AsyncGenerator<Row, void, unknown> {
777
- for await (const row of inner) {
778
- if (invalidated) {
779
- throw transactionClosedError();
780
- }
781
- yield row;
782
- }
783
- };
784
- return new AsyncIterableResult(guarded());
790
+ return new AsyncIterableResult(guardedStream(transaction.execute(plan, options)));
785
791
  },
786
792
  executePrepared<Params, Row>(
787
793
  ps: PreparedStatement<Params, Row>,
@@ -791,16 +797,9 @@ export async function withTransaction<R>(
791
797
  if (invalidated) {
792
798
  throw transactionClosedError();
793
799
  }
794
- const inner = transaction.executePrepared(ps, params, options);
795
- const guarded = async function* (): AsyncGenerator<Row, void, unknown> {
796
- for await (const row of inner) {
797
- if (invalidated) {
798
- throw transactionClosedError();
799
- }
800
- yield row;
801
- }
802
- };
803
- return new AsyncIterableResult(guarded());
800
+ return new AsyncIterableResult(
801
+ guardedStream(transaction.executePrepared(ps, params, options)),
802
+ );
804
803
  },
805
804
  };
806
805