@prisma-next/sql-contract 0.14.0-dev.9 → 0.14.0-dev.90

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.
Files changed (80) hide show
  1. package/dist/canonicalization-hooks.d.mts.map +1 -1
  2. package/dist/canonicalization-hooks.mjs +80 -50
  3. package/dist/canonicalization-hooks.mjs.map +1 -1
  4. package/dist/contract-view.d.mts +61 -0
  5. package/dist/contract-view.d.mts.map +1 -0
  6. package/dist/contract-view.mjs +39 -0
  7. package/dist/contract-view.mjs.map +1 -0
  8. package/dist/entity-handle-lowering-hook.d.mts +91 -0
  9. package/dist/entity-handle-lowering-hook.d.mts.map +1 -0
  10. package/dist/entity-handle-lowering-hook.mjs +11 -0
  11. package/dist/entity-handle-lowering-hook.mjs.map +1 -0
  12. package/dist/entity-kinds-BEPZdFBN.mjs +132 -0
  13. package/dist/entity-kinds-BEPZdFBN.mjs.map +1 -0
  14. package/dist/entity-kinds.d.mts +2 -1
  15. package/dist/entity-kinds.d.mts.map +1 -1
  16. package/dist/entity-kinds.mjs +1 -1
  17. package/dist/factories.d.mts +6 -4
  18. package/dist/factories.d.mts.map +1 -1
  19. package/dist/factories.mjs +5 -4
  20. package/dist/factories.mjs.map +1 -1
  21. package/dist/{foreign-key-BATxB95l.d.mts → foreign-key-DM1UTydh.d.mts} +3 -41
  22. package/dist/foreign-key-DM1UTydh.d.mts.map +1 -0
  23. package/dist/index-type-validation.d.mts +1 -1
  24. package/dist/referential-action-sql.d.mts +1 -1
  25. package/dist/resolve-storage-table.d.mts +2 -2
  26. package/dist/sql-node-V214WXQD.d.mts +42 -0
  27. package/dist/sql-node-V214WXQD.d.mts.map +1 -0
  28. package/dist/{sql-storage-Dga0jwP2.d.mts → sql-storage-Dt5ipcRl.d.mts} +63 -20
  29. package/dist/sql-storage-Dt5ipcRl.d.mts.map +1 -0
  30. package/dist/{storage-value-set-WnYsIFM8.d.mts → storage-table-Bj1ZN1P7.d.mts} +16 -39
  31. package/dist/storage-table-Bj1ZN1P7.d.mts.map +1 -0
  32. package/dist/{entity-kinds-Cl36zL5j.mjs → storage-table-y6R6pxyI.mjs} +17 -157
  33. package/dist/storage-table-y6R6pxyI.mjs.map +1 -0
  34. package/dist/storage-value-set-D-jww77l.d.mts +41 -0
  35. package/dist/storage-value-set-D-jww77l.d.mts.map +1 -0
  36. package/dist/storage-value-set-Tx3CFoS_.mjs +38 -0
  37. package/dist/storage-value-set-Tx3CFoS_.mjs.map +1 -0
  38. package/dist/types-Hkxybhdj.mjs +13 -0
  39. package/dist/types-Hkxybhdj.mjs.map +1 -0
  40. package/dist/{types-B1N8w0I2.d.mts → types-zBvpNCmg.d.mts} +25 -55
  41. package/dist/types-zBvpNCmg.d.mts.map +1 -0
  42. package/dist/types.d.mts +13 -5
  43. package/dist/types.d.mts.map +1 -0
  44. package/dist/types.mjs +106 -3
  45. package/dist/types.mjs.map +1 -0
  46. package/dist/validators.d.mts +10 -7
  47. package/dist/validators.d.mts.map +1 -1
  48. package/dist/validators.mjs +8 -18
  49. package/dist/validators.mjs.map +1 -1
  50. package/dist/value-set-derivation-hook.d.mts +35 -0
  51. package/dist/value-set-derivation-hook.d.mts.map +1 -0
  52. package/dist/value-set-derivation-hook.mjs +22 -0
  53. package/dist/value-set-derivation-hook.mjs.map +1 -0
  54. package/package.json +13 -10
  55. package/src/canonicalization-hooks.ts +31 -2
  56. package/src/column-type-resolution.ts +26 -0
  57. package/src/contract-view.ts +98 -0
  58. package/src/entity-handle-lowering-hook.ts +104 -0
  59. package/src/exports/contract-view.ts +11 -0
  60. package/src/exports/entity-handle-lowering-hook.ts +10 -0
  61. package/src/exports/types.ts +17 -4
  62. package/src/exports/value-set-derivation-hook.ts +2 -0
  63. package/src/factories.ts +12 -2
  64. package/src/ir/sql-storage.ts +81 -17
  65. package/src/ir/storage-column.ts +3 -0
  66. package/src/ir/storage-entry-schemas.ts +1 -0
  67. package/src/ir/storage-table.ts +21 -0
  68. package/src/ir/storage-value-set.ts +5 -0
  69. package/src/types.ts +51 -8
  70. package/src/validators.ts +12 -41
  71. package/src/value-set-derivation-hook.ts +47 -0
  72. package/dist/entity-kinds-Cl36zL5j.mjs.map +0 -1
  73. package/dist/foreign-key-BATxB95l.d.mts.map +0 -1
  74. package/dist/sql-storage-Dga0jwP2.d.mts.map +0 -1
  75. package/dist/storage-value-set-WnYsIFM8.d.mts.map +0 -1
  76. package/dist/types-B-eiQXff.mjs +0 -191
  77. package/dist/types-B-eiQXff.mjs.map +0 -1
  78. package/dist/types-B1N8w0I2.d.mts.map +0 -1
  79. package/src/ir/build-sql-namespace.ts +0 -110
  80. package/src/ir/sql-unbound-namespace.ts +0 -74
@@ -0,0 +1,104 @@
1
+ /**
2
+ * SQL-family extension to the pack authoring contributions: a pack whose
3
+ * `entityTypes` registers entity kinds may implement a batch lowering hook
4
+ * that turns author-declared pack-entity handles (the generic `entities`
5
+ * list on the TS contract input) into `entries` rows.
6
+ *
7
+ * The generic contract build (`contract-ts`) owns the kind-agnostic walk: it
8
+ * groups handles by the pack that registered each `entityKind`, resolves
9
+ * each handle's declared model refs to storage table coordinates using the
10
+ * same model→table maps the relation lowering uses, and calls the owning
11
+ * pack's hook once with all of its claimed handles — batch, so the hook's
12
+ * diagnostics can see sibling handles. Neither the walk nor these types name
13
+ * any specific entity kind.
14
+ *
15
+ * SQL-family concept — the resolved-ref shape is a table coordinate, so the
16
+ * hook lives here beside {@link ../value-set-derivation-hook} instead of on
17
+ * the framework contributions type.
18
+ */
19
+
20
+ /**
21
+ * An authored pack-entity handle: branded by the `entityKind` its owning
22
+ * pack registered, optionally declaring model refs under `refs` (actual
23
+ * model-handle objects, resolved by the generic walk before lowering).
24
+ */
25
+ export interface PackEntityHandle {
26
+ readonly entityKind: string;
27
+ readonly refs?: Readonly<Record<string, unknown>>;
28
+ }
29
+
30
+ /**
31
+ * A declared model ref after the generic walk resolved it: a storage table
32
+ * coordinate for a model of this contract, the coordinate plus `spaceId` for
33
+ * a cross-space (extensionModel) handle, or `unresolved` when the handle
34
+ * does not correspond to any model in the contract. `modelName` is carried
35
+ * for diagnostics where the handle declares one.
36
+ */
37
+ export type ResolvedEntityHandleRef =
38
+ | {
39
+ readonly kind: 'resolved';
40
+ readonly namespaceId: string;
41
+ readonly tableName: string;
42
+ readonly modelName?: string;
43
+ }
44
+ | {
45
+ readonly kind: 'cross-space';
46
+ readonly spaceId: string;
47
+ readonly namespaceId: string;
48
+ readonly tableName: string;
49
+ readonly modelName?: string;
50
+ }
51
+ | {
52
+ readonly kind: 'unresolved';
53
+ readonly modelName?: string;
54
+ };
55
+
56
+ /** One claimed handle paired with its resolved declared refs. */
57
+ export interface ResolvedPackEntityHandle {
58
+ readonly handle: PackEntityHandle;
59
+ readonly refs: Readonly<Record<string, ResolvedEntityHandleRef>>;
60
+ }
61
+
62
+ export interface EntityHandleLoweringInput {
63
+ /** Every handle in the `entities` list whose `entityKind` this pack registered, in authored order. */
64
+ readonly handles: readonly ResolvedPackEntityHandle[];
65
+ readonly defaultNamespaceId: string;
66
+ }
67
+
68
+ /** One lowered entity row: filed into `storage.namespaces[namespaceId].entries[entityKind][key]`. */
69
+ export interface LoweredPackEntity {
70
+ readonly namespaceId: string;
71
+ readonly entityKind: string;
72
+ readonly key: string;
73
+ readonly entity: unknown;
74
+ }
75
+
76
+ export interface SqlEntityHandleLoweringContribution {
77
+ /**
78
+ * Method syntax keeps the declaration bivariant, so a pack's concretely
79
+ * typed implementation stays structurally compatible (the same convention
80
+ * as `SqlValueSetDerivingEntityTypeOutput.deriveValueSet`).
81
+ */
82
+ lowerEntityHandles(input: EntityHandleLoweringInput): readonly LoweredPackEntity[];
83
+ }
84
+
85
+ /** Structural check for {@link SqlEntityHandleLoweringContribution}: no casts. */
86
+ export function providesEntityHandleLowering(
87
+ authoring: unknown,
88
+ ): authoring is SqlEntityHandleLoweringContribution {
89
+ if (typeof authoring !== 'object' || authoring === null || !('lowerEntityHandles' in authoring)) {
90
+ return false;
91
+ }
92
+ const { lowerEntityHandles } = authoring;
93
+ return typeof lowerEntityHandles === 'function';
94
+ }
95
+
96
+ /**
97
+ * PSL-side twin of {@link ResolvedEntityHandleRef}: the family interpreter
98
+ * resolves an extension block's descriptor-declared model ref parameters
99
+ * (`{ kind: 'ref', refKind: 'model' }`) to storage table names and annotates
100
+ * the block with this map (keyed by parameter name) before invoking the
101
+ * entity factory. An unresolved required ref is the interpreter's
102
+ * diagnostic; a factory never sees one.
103
+ */
104
+ export type ResolvedPslModelRefs = Readonly<Record<string, { readonly tableName: string }>>;
@@ -0,0 +1,11 @@
1
+ export type {
2
+ SqlBuiltinKind,
3
+ SqlSchemaQualifiedView,
4
+ SqlSingleNamespaceAccessors,
5
+ SqlSingleNamespaceView,
6
+ } from '../contract-view';
7
+ export {
8
+ buildSqlSchemaQualifiedView,
9
+ buildSqlSingleNamespaceView,
10
+ SQL_BUILTIN_KINDS,
11
+ } from '../contract-view';
@@ -0,0 +1,10 @@
1
+ export {
2
+ type EntityHandleLoweringInput,
3
+ type LoweredPackEntity,
4
+ type PackEntityHandle,
5
+ providesEntityHandleLowering,
6
+ type ResolvedEntityHandleRef,
7
+ type ResolvedPackEntityHandle,
8
+ type ResolvedPslModelRefs,
9
+ type SqlEntityHandleLoweringContribution,
10
+ } from '../entity-handle-lowering-hook';
@@ -1,3 +1,7 @@
1
+ export type {
2
+ StorageColumnMapAt,
3
+ StorageColumnTypeAcrossNamespaces,
4
+ } from '../column-type-resolution';
1
5
  export type {
2
6
  CheckConstraintInput,
3
7
  CodecTypesOf,
@@ -6,6 +10,8 @@ export type {
6
10
  ExtractFieldInputTypes,
7
11
  ExtractFieldOutputTypes,
8
12
  ExtractQueryOperationTypes,
13
+ ExtractStorageColumnInputTypes,
14
+ ExtractStorageColumnTypes,
9
15
  ExtractTypeMapsFromContract,
10
16
  FieldInputTypesOf,
11
17
  FieldOutputTypesOf,
@@ -13,6 +19,8 @@ export type {
13
19
  ForeignKeyOptions,
14
20
  ForeignKeyReferenceInput,
15
21
  IndexInput,
22
+ NamespacedFieldTypeMap,
23
+ NamespacedStorageColumnTypeMap,
16
24
  PrimaryKeyInput,
17
25
  QueryOperationReturn,
18
26
  QueryOperationSelfSpec,
@@ -21,16 +29,20 @@ export type {
21
29
  QueryOperationTypesOf,
22
30
  ReferentialAction,
23
31
  ResolveCodecTypes,
32
+ SqlAuthoringContributions,
24
33
  SqlControlDriverInstance,
25
34
  SqlModelFieldStorage,
26
35
  SqlModelStorage,
27
36
  SqlNamespace,
28
37
  SqlNamespaceEntries,
29
- SqlNamespaceTablesInput,
38
+ SqlNamespaceFactory,
39
+ SqlNamespaceInput,
30
40
  SqlQueryOperationTypes,
31
41
  SqlStorageInput,
32
42
  SqlStorageTypeEntry,
33
43
  StorageColumnInput,
44
+ StorageColumnInputTypesOf,
45
+ StorageColumnTypesOf,
34
46
  StorageTableInput,
35
47
  StorageTypeInstance,
36
48
  StorageTypeInstanceInput,
@@ -41,8 +53,6 @@ export type {
41
53
  } from '../types';
42
54
  export {
43
55
  applyFkDefaults,
44
- buildSqlNamespace,
45
- buildSqlNamespaceMap,
46
56
  CheckConstraint,
47
57
  CODEC_INSTANCE_KIND,
48
58
  DEFAULT_FK_CONSTRAINT,
@@ -50,11 +60,14 @@ export {
50
60
  ForeignKey,
51
61
  ForeignKeyReference,
52
62
  Index,
63
+ isMaterializedSqlNamespace,
64
+ isSqlAuthoringContributions,
53
65
  isStorageTypeInstance,
66
+ isStorageValueSet,
54
67
  PrimaryKey,
68
+ SqlNamespaceBase,
55
69
  SqlNode,
56
70
  SqlStorage,
57
- SqlUnboundNamespace,
58
71
  StorageColumn,
59
72
  StorageTable,
60
73
  StorageValueSet,
@@ -0,0 +1,2 @@
1
+ export type { SqlValueSetDerivingEntityTypeOutput } from '../value-set-derivation-hook';
2
+ export { deriveValueSetFromEntity, providesValueSetDerivation } from '../value-set-derivation-hook';
package/src/factories.ts CHANGED
@@ -14,8 +14,18 @@ import {
14
14
  UniqueConstraint,
15
15
  } from './types';
16
16
 
17
- export function col(nativeType: string, codecId: string, nullable = false): StorageColumn {
18
- return new StorageColumn({ nativeType, codecId, nullable });
17
+ export function col(
18
+ nativeType: string,
19
+ codecId: string,
20
+ nullable = false,
21
+ opts?: { readonly many?: boolean },
22
+ ): StorageColumn {
23
+ return new StorageColumn({
24
+ nativeType,
25
+ codecId,
26
+ nullable,
27
+ ...(opts?.many !== undefined && { many: opts.many }),
28
+ });
19
29
  }
20
30
 
21
31
  export function pk(...columns: readonly string[]): PrimaryKey {
@@ -1,5 +1,12 @@
1
1
  import type { StorageHashBase } from '@prisma-next/contract/types';
2
- import { freezeNode, type Namespace, type Storage } from '@prisma-next/framework-components/ir';
2
+ import type { AuthoringContributions } from '@prisma-next/framework-components/authoring';
3
+ import {
4
+ freezeNode,
5
+ isPlainRecord,
6
+ type Namespace,
7
+ NamespaceBase,
8
+ type Storage,
9
+ } from '@prisma-next/framework-components/ir';
3
10
  import { SqlNode } from './sql-node';
4
11
  import type { StorageTable } from './storage-table';
5
12
  import {
@@ -19,15 +26,45 @@ import type { StorageValueSet } from './storage-value-set';
19
26
  */
20
27
  export type SqlStorageTypeEntry = StorageTypeInstance | StorageTypeInstanceInput;
21
28
 
22
- export interface SqlNamespaceTablesInput {
29
+ export interface SqlNamespaceInput {
23
30
  readonly id: string;
24
31
  readonly entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
25
32
  }
26
33
 
34
+ /**
35
+ * Target-supplied factory that materializes a `Namespace` from a SQL
36
+ * `SqlNamespaceInput` (used to populate `SqlStorage.namespaces`).
37
+ */
38
+ export type SqlNamespaceFactory = (input: SqlNamespaceInput) => Namespace;
39
+
40
+ /**
41
+ * SQL-family extension of the framework `AuthoringContributions`. SQL target
42
+ * packs add a `createNamespace` factory so the PSL/TS authoring paths can
43
+ * materialize namespaces (and merge lowered extension-block entities) without
44
+ * each consumer re-specifying it. The factory is SQL-specific, so it lives here
45
+ * rather than on the framework `AuthoringContributions` base.
46
+ */
47
+ export interface SqlAuthoringContributions extends AuthoringContributions {
48
+ readonly createNamespace?: SqlNamespaceFactory;
49
+ }
50
+
51
+ /**
52
+ * Narrows framework `AuthoringContributions` to the SQL-family shape by testing
53
+ * for the SQL-specific `createNamespace` capability.
54
+ */
55
+ export function isSqlAuthoringContributions(
56
+ authoring: AuthoringContributions | undefined,
57
+ ): authoring is SqlAuthoringContributions {
58
+ if (authoring === undefined || !Object.hasOwn(authoring, 'createNamespace')) {
59
+ return false;
60
+ }
61
+ return typeof Reflect.get(authoring, 'createNamespace') === 'function';
62
+ }
63
+
27
64
  export interface SqlStorageInput<THash extends string = string> {
28
65
  readonly storageHash: StorageHashBase<THash>;
29
66
  readonly types?: Record<string, SqlStorageTypeEntry>;
30
- readonly namespaces: Readonly<Record<string, SqlNamespace>>;
67
+ readonly namespaces: Readonly<Record<string, SqlNamespaceBase>>;
31
68
  }
32
69
 
33
70
  /**
@@ -63,22 +100,49 @@ export type SqlNamespaceEntries = Readonly<Record<string, Readonly<Record<string
63
100
  };
64
101
 
65
102
  /**
66
- * SQL family namespace. `entries` is the open ADR 224 dictionary —
67
- * `entries[entityKind][entityName]` addresses any entity. Emitted
68
- * contract literals satisfy this structurally (no prototype getters
69
- * needed). For typed access to specific kinds, use the class getters
70
- * on the concretion or `ns.entries.table` / `ns.entries.valueSet` directly.
103
+ * Structural interface for SQL family namespaces. Generated `.d.ts` contract
104
+ * types satisfy this structurally (no prototype methods). The runtime
105
+ * abstract class `SqlNamespaceBase` extends this.
106
+ *
107
+ * `qualifyTable` and `isUnbound` are optional so JSON-shaped contract types
108
+ * (which carry no methods) are accepted where `SqlNamespace` is required.
109
+ * Hydrated `SqlNamespaceBase` instances always have both.
71
110
  */
72
- export type SqlNamespace = Namespace & {
111
+ export interface SqlNamespace {
112
+ readonly kind: string;
113
+ readonly id: string;
73
114
  readonly entries: SqlNamespaceEntries;
74
- /**
75
- * Render a dialect-qualified table reference for runtime SQL emission.
76
- * Present on materialised target concretions (`PostgresSchema`,
77
- * `SqliteDatabase`, …) and family placeholders; omitted on emitted
78
- * contract structural namespace literals (methods are not serialised).
79
- */
115
+ readonly isUnbound?: boolean;
80
116
  qualifyTable?(tableName: string): string;
81
- };
117
+ }
118
+
119
+ /**
120
+ * Abstract SQL family namespace base class. Target concretions (`PostgresSchema`,
121
+ * `SqliteDatabase`, …) extend this — it is never instantiated directly.
122
+ * `entries` is the open ADR 224 dictionary: `entries[entityKind][entityName]`
123
+ * addresses any entity.
124
+ */
125
+ export abstract class SqlNamespaceBase extends NamespaceBase implements SqlNamespace {
126
+ abstract override readonly id: string;
127
+ abstract override readonly entries: SqlNamespaceEntries;
128
+
129
+ abstract qualifyTable(tableName: string): string;
130
+ }
131
+
132
+ /**
133
+ * Realm-safe guard for hydrated `SqlNamespaceBase` concretions. Checks
134
+ * `qualifyTable` structurally instead of `instanceof NamespaceBase`, so it
135
+ * survives duplicate-module boundaries (e.g. dist e2e where the target and
136
+ * the family carry separate copies of `@prisma-next/framework-components`).
137
+ *
138
+ * Every concrete `SqlNamespaceBase` subclass (`PostgresSchema`, `SqliteDatabase`,
139
+ * `TestSqlNamespace`, …) implements `qualifyTable`. Raw `SqlNamespaceInput`
140
+ * objects (`{ id, entries }`) do not.
141
+ */
142
+ export function isMaterializedSqlNamespace(x: unknown): x is SqlNamespaceBase {
143
+ if (typeof x !== 'object' || x === null || !('qualifyTable' in x)) return false;
144
+ return typeof x.qualifyTable === 'function';
145
+ }
82
146
 
83
147
  export class SqlStorage<THash extends string = string> extends SqlNode implements Storage {
84
148
  readonly storageHash: StorageHashBase<THash>;
@@ -128,7 +192,7 @@ function normaliseTypeEntry(name: string, entry: SqlStorageTypeEntry): StorageTy
128
192
  }
129
193
  return toStorageTypeInstance(entry);
130
194
  }
131
- const rawKind = (entry as { kind?: unknown }).kind;
195
+ const rawKind = isPlainRecord(entry) ? entry['kind'] : undefined;
132
196
  const kindDescription =
133
197
  rawKind === undefined
134
198
  ? 'missing `kind` discriminator'
@@ -16,6 +16,7 @@ export interface StorageColumnInput {
16
16
  readonly nativeType: string;
17
17
  readonly codecId: string;
18
18
  readonly nullable: boolean;
19
+ readonly many?: boolean;
19
20
  readonly typeParams?: Record<string, unknown>;
20
21
  readonly typeRef?: string;
21
22
  readonly default?: ColumnDefault;
@@ -40,6 +41,7 @@ export class StorageColumn extends SqlNode {
40
41
  readonly nativeType: string;
41
42
  readonly codecId: string;
42
43
  readonly nullable: boolean;
44
+ declare readonly many?: boolean;
43
45
  declare readonly typeParams?: Record<string, unknown>;
44
46
  declare readonly typeRef?: string;
45
47
  declare readonly default?: ColumnDefault;
@@ -51,6 +53,7 @@ export class StorageColumn extends SqlNode {
51
53
  this.nativeType = input.nativeType;
52
54
  this.codecId = input.codecId;
53
55
  this.nullable = input.nullable;
56
+ if (input.many !== undefined) this.many = input.many;
54
57
  if (input.typeParams !== undefined) this.typeParams = input.typeParams;
55
58
  if (input.typeRef !== undefined) this.typeRef = input.typeRef;
56
59
  if (input.default !== undefined) this.default = input.default;
@@ -39,6 +39,7 @@ const StorageColumnSchema = type({
39
39
  nativeType: 'string',
40
40
  codecId: 'string',
41
41
  nullable: 'boolean',
42
+ 'many?': 'boolean',
42
43
  'typeParams?': 'Record<string, unknown>',
43
44
  'typeRef?': 'string',
44
45
  'default?': ColumnDefaultSchema,
@@ -68,4 +68,25 @@ export class StorageTable extends SqlNode {
68
68
  }
69
69
  freezeNode(this);
70
70
  }
71
+
72
+ /**
73
+ * Runtime guard that a namespace `table` entry is really a `StorageTable`.
74
+ * The compiler already types the entry as `StorageTable`, but a
75
+ * freshly-deserialized contract may carry plain JSON at that slot until
76
+ * hydration; this duck-types the structural shape. Accepts `undefined` so
77
+ * optional-chained entry lookups pass straight through.
78
+ */
79
+ static is(value: StorageTable | undefined): value is StorageTable {
80
+ if (typeof value !== 'object' || value === null) return false;
81
+ return 'columns' in value && 'uniques' in value && 'indexes' in value && 'foreignKeys' in value;
82
+ }
83
+
84
+ static assert(
85
+ value: StorageTable | undefined,
86
+ coordinate: string,
87
+ ): asserts value is StorageTable {
88
+ if (!StorageTable.is(value)) {
89
+ throw new Error(`Expected a StorageTable at ${coordinate}`);
90
+ }
91
+ }
71
92
  }
@@ -41,3 +41,8 @@ export class StorageValueSet extends SqlNode {
41
41
  freezeNode(this);
42
42
  }
43
43
  }
44
+
45
+ export function isStorageValueSet(value: unknown): value is StorageValueSet {
46
+ if (typeof value !== 'object' || value === null) return false;
47
+ return 'kind' in value && value.kind === 'valueSet' && 'values' in value;
48
+ }
package/src/types.ts CHANGED
@@ -10,7 +10,6 @@ export interface SqlControlDriverInstance<T extends string = string>
10
10
  ): Promise<{ readonly rows: Row[] }>;
11
11
  }
12
12
 
13
- export { buildSqlNamespace, buildSqlNamespaceMap } from './ir/build-sql-namespace';
14
13
  export { CheckConstraint, type CheckConstraintInput } from './ir/check-constraint';
15
14
  export {
16
15
  ForeignKey,
@@ -25,14 +24,18 @@ export { PrimaryKey, type PrimaryKeyInput } from './ir/primary-key';
25
24
  export { Index, type IndexInput } from './ir/sql-index';
26
25
  export { SqlNode } from './ir/sql-node';
27
26
  export {
27
+ isMaterializedSqlNamespace,
28
+ isSqlAuthoringContributions,
29
+ type SqlAuthoringContributions,
28
30
  type SqlNamespace,
31
+ SqlNamespaceBase,
29
32
  type SqlNamespaceEntries,
30
- type SqlNamespaceTablesInput,
33
+ type SqlNamespaceFactory,
34
+ type SqlNamespaceInput,
31
35
  SqlStorage,
32
36
  type SqlStorageInput,
33
37
  type SqlStorageTypeEntry,
34
38
  } from './ir/sql-storage';
35
- export { SqlUnboundNamespace } from './ir/sql-unbound-namespace';
36
39
  export { StorageColumn, type StorageColumnInput } from './ir/storage-column';
37
40
  export { StorageTable, type StorageTableInput } from './ir/storage-table';
38
41
  export {
@@ -42,7 +45,11 @@ export {
42
45
  type StorageTypeInstanceInput,
43
46
  toStorageTypeInstance,
44
47
  } from './ir/storage-type-instance';
45
- export { StorageValueSet, type StorageValueSetInput } from './ir/storage-value-set';
48
+ export {
49
+ isStorageValueSet,
50
+ StorageValueSet,
51
+ type StorageValueSetInput,
52
+ } from './ir/storage-value-set';
46
53
  export {
47
54
  UniqueConstraint,
48
55
  type UniqueConstraintInput,
@@ -83,16 +90,25 @@ export function applyFkDefaults(
83
90
  // Shared by the output and input field-type maps and their extractors.
84
91
  export type NamespacedFieldTypeMap = Record<string, Record<string, Record<string, unknown>>>;
85
92
 
93
+ export type NamespacedStorageColumnTypeMap = Record<
94
+ string,
95
+ Record<string, Record<string, unknown>>
96
+ >;
97
+
86
98
  export type TypeMaps<
87
99
  TCodecTypes extends Record<string, { output: unknown }> = Record<string, never>,
88
100
  TQueryOperationTypes extends Record<string, unknown> = Record<string, never>,
89
101
  TFieldOutputTypes extends NamespacedFieldTypeMap = Record<string, never>,
90
102
  TFieldInputTypes extends NamespacedFieldTypeMap = Record<string, never>,
103
+ TStorageColumnTypes extends NamespacedStorageColumnTypeMap = Record<string, never>,
104
+ TStorageColumnInputTypes extends NamespacedStorageColumnTypeMap = Record<string, never>,
91
105
  > = {
92
106
  readonly codecTypes: TCodecTypes;
93
107
  readonly queryOperationTypes: TQueryOperationTypes;
94
108
  readonly fieldOutputTypes: TFieldOutputTypes;
95
109
  readonly fieldInputTypes: TFieldInputTypes;
110
+ readonly storageColumnTypes: TStorageColumnTypes;
111
+ readonly storageColumnInputTypes: TStorageColumnInputTypes;
96
112
  };
97
113
 
98
114
  export type CodecTypesOf<T> = [T] extends [never]
@@ -107,12 +123,19 @@ export type CodecTypesOf<T> = [T] extends [never]
107
123
  * Dispatch hint identifying the first-argument target of an operation.
108
124
  *
109
125
  * Used by ORM column helpers to decide whether an operation is reachable on a
110
- * field. Either names a concrete codec identity or a set of capability traits
111
- * that the field's codec must carry.
126
+ * field. Names a concrete codec identity, a set of capability traits the
127
+ * field's codec must carry, or targets list-typed (`many`) fields. Element
128
+ * capability gating for list ops travels in `elementTraits`.
112
129
  */
113
130
  export type QueryOperationSelfSpec =
114
- | { readonly codecId: string; readonly traits?: never }
115
- | { readonly traits: readonly CodecTrait[]; readonly codecId?: never };
131
+ | { readonly codecId: string; readonly traits?: never; readonly many?: never }
132
+ | { readonly traits: readonly CodecTrait[]; readonly codecId?: never; readonly many?: never }
133
+ | {
134
+ readonly many: true;
135
+ readonly elementTraits?: readonly CodecTrait[];
136
+ readonly codecId?: never;
137
+ readonly traits?: never;
138
+ };
116
139
 
117
140
  /**
118
141
  * Structural shape an operation's impl must return: any value carrying a
@@ -172,10 +195,30 @@ export type FieldInputTypesOf<T> = [T] extends [never]
172
195
  : Record<string, never>
173
196
  : Record<string, never>;
174
197
 
198
+ export type StorageColumnTypesOf<T> = [T] extends [never]
199
+ ? Record<string, never>
200
+ : T extends { readonly storageColumnTypes: infer F }
201
+ ? F extends NamespacedStorageColumnTypeMap
202
+ ? F
203
+ : Record<string, never>
204
+ : Record<string, never>;
205
+
206
+ export type StorageColumnInputTypesOf<T> = [T] extends [never]
207
+ ? Record<string, never>
208
+ : T extends { readonly storageColumnInputTypes: infer F }
209
+ ? F extends NamespacedStorageColumnTypeMap
210
+ ? F
211
+ : Record<string, never>
212
+ : Record<string, never>;
213
+
175
214
  export type ExtractCodecTypes<T> = CodecTypesOf<ExtractTypeMapsFromContract<T>>;
176
215
  export type ExtractQueryOperationTypes<T> = QueryOperationTypesOf<ExtractTypeMapsFromContract<T>>;
177
216
  export type ExtractFieldOutputTypes<T> = FieldOutputTypesOf<ExtractTypeMapsFromContract<T>>;
178
217
  export type ExtractFieldInputTypes<T> = FieldInputTypesOf<ExtractTypeMapsFromContract<T>>;
218
+ export type ExtractStorageColumnTypes<T> = StorageColumnTypesOf<ExtractTypeMapsFromContract<T>>;
219
+ export type ExtractStorageColumnInputTypes<T> = StorageColumnInputTypesOf<
220
+ ExtractTypeMapsFromContract<T>
221
+ >;
179
222
 
180
223
  export type ResolveCodecTypes<TContract, TTypeMaps> = [TTypeMaps] extends [never]
181
224
  ? ExtractCodecTypes<TContract>
package/src/validators.ts CHANGED
@@ -10,13 +10,11 @@ import {
10
10
  type AnyEntityKindDescriptor,
11
11
  isPlainRecord,
12
12
  type Namespace,
13
- UNBOUND_NAMESPACE_ID,
14
13
  } from '@prisma-next/framework-components/ir';
15
14
  import { blindCast } from '@prisma-next/utils/casts';
16
15
  import { ifDefined } from '@prisma-next/utils/defined';
17
16
  import { type Type, type } from 'arktype';
18
17
  import { composeSqlEntityKinds } from './entity-kinds';
19
- import { buildSqlNamespaceMap } from './ir/build-sql-namespace';
20
18
 
21
19
  export {
22
20
  CheckConstraintSchema,
@@ -32,14 +30,12 @@ export {
32
30
  StorageValueSetSchema,
33
31
  } from './ir/storage-entry-schemas';
34
32
 
35
- import { SqlUnboundNamespace } from './ir/sql-unbound-namespace';
36
- import {
37
- type SqlModelStorage,
33
+ import type {
34
+ SqlModelStorage,
38
35
  SqlStorage,
39
- type SqlStorageInput,
40
- type StorageColumn,
41
- type StorageTable,
42
- type StorageTypeInstanceInput,
36
+ StorageColumn,
37
+ StorageTable,
38
+ StorageTypeInstanceInput,
43
39
  } from './types';
44
40
 
45
41
  const generatorKindSchema = type("'generator'");
@@ -193,9 +189,8 @@ export function createSqlStorageSchema(
193
189
  'types?': type({ '[string]': DocumentScopedStorageTypeSchema }),
194
190
  // `__unbound__` is NOT required here: cross-namespace contracts can
195
191
  // declare only named namespaces (see cross-namespace FK fixtures). The
196
- // `__unbound__` brand on `SqlStorageInput['namespaces']` is kept sound at
197
- // construction time by injecting the unbound singleton when absent
198
- // (see `validateStorage` / `hydrateSqlStorage`), not by structural require.
192
+ // unbound slot is injected when absent by `ensureUnboundNamespaceSlot`
193
+ // in `build-contract.ts`, not enforced here structurally.
199
194
  'namespaces?': type({ '[string]': namespaceEntry }),
200
195
  }) as Type<unknown>;
201
196
  }
@@ -369,45 +364,21 @@ export function createSqlContractSchema(
369
364
 
370
365
  const SqlContractSchema = createSqlContractSchema(DEFAULT_SQL_KINDS);
371
366
 
372
- // NOTE: StorageColumnSchema, StorageTableSchema, and StorageSchema use bare type()
373
- // instead of type.declare<T>().type() because the ColumnDefault union's value field
374
- // includes bigint | Date (runtime-only types after decoding) which cannot be expressed
375
- // in Arktype's JSON validation DSL. The `as SqlStorage` cast in validateStorage() bridges
376
- // the gap between the JSON-safe Arktype output and the runtime TypeScript type.
377
-
378
367
  /**
379
- * Validates the structural shape of SqlStorage using Arktype.
368
+ * Validates the structural shape of SqlStorage using Arktype. Pure
369
+ * structural check: namespace IR is never materialized here (that needs
370
+ * a target concretion via the serializer hydration path), so this throws
371
+ * on invalid input and constructs nothing.
380
372
  *
381
373
  * @param value - The storage value to validate
382
- * @returns The validated storage if structure is valid
383
374
  * @throws Error if the storage structure is invalid
384
375
  */
385
- export function validateStorage(value: unknown): SqlStorage {
376
+ export function validateStorage(value: unknown): void {
386
377
  const result = StorageSchema(value);
387
378
  if (result instanceof type.errors) {
388
379
  const messages = result.map((p: { message: string }) => p.message).join('; ');
389
380
  throw new Error(`Storage validation failed: ${messages}`);
390
381
  }
391
- // Arktype validates the JSON-safe envelope, but the `ColumnDefault`
392
- // union carries runtime-only `bigint | Date` that the validation DSL
393
- // can't express (see NOTE above), so bridge the validated shape to the
394
- // input type. Construction below re-materialises nested IR fields.
395
- const validated = blindCast<
396
- SqlStorageInput & { readonly namespaces?: SqlStorageInput['namespaces'] },
397
- 'arktype validated the JSON envelope but its output type is unknown (ColumnDefault carries runtime-only bigint|Date); bridge to the input shape'
398
- >(result);
399
- const namespaces = buildSqlNamespaceMap(validated.namespaces ?? {});
400
- // Compatibility shim: inject the empty unbound singleton when absent so that
401
- // production code paths which address __unbound__ for table metadata have a
402
- // slot to read or write into. The `SqlStorageInput['namespaces']` type no
403
- // longer requires __unbound__, so this is a runtime convenience, not a type
404
- // invariant.
405
- const unbound = namespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance;
406
- return new SqlStorage({
407
- storageHash: validated.storageHash,
408
- ...ifDefined('types', validated.types),
409
- namespaces: { ...namespaces, [UNBOUND_NAMESPACE_ID]: unbound },
410
- });
411
382
  }
412
383
 
413
384
  export function validateModel(value: unknown): unknown {