@prisma-next/sql-contract 0.9.0 → 0.10.0

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.
@@ -2,15 +2,16 @@ import type { StorageHashBase } from '@prisma-next/contract/types';
2
2
  import {
3
3
  freezeNode,
4
4
  type Namespace,
5
+ NamespaceBase,
5
6
  type Storage,
6
- UNSPECIFIED_NAMESPACE_ID,
7
+ UNBOUND_NAMESPACE_ID,
7
8
  } from '@prisma-next/framework-components/ir';
8
9
  import {
9
10
  isPostgresEnumStorageEntry,
10
11
  type PostgresEnumStorageEntry,
11
12
  } from './postgres-enum-storage-entry';
12
13
  import { SqlNode } from './sql-node';
13
- import { SqlUnspecifiedNamespace } from './sql-unspecified-namespace';
14
+ import { SqlUnboundNamespace } from './sql-unbound-namespace';
14
15
  import { StorageTable, type StorageTableInput } from './storage-table';
15
16
  import {
16
17
  isStorageTypeInstance,
@@ -19,50 +20,104 @@ import {
19
20
  } from './storage-type-instance';
20
21
 
21
22
  /**
22
- * Polymorphic value type for `SqlStorage.types` entries (Decision 18,
23
- * Option B). The slot's framework alphabet is `StorageType` — codec
24
- * triples (`StorageTypeInstance` with `kind: 'codec-instance'`) and
25
- * target-specific IR class instances structurally satisfying
26
- * `PostgresEnumStorageEntry` (with `kind: 'postgres-enum'`) are the
27
- * two variants the SQL family ships today. The construction side also
28
- * accepts {@link StorageTypeInstanceInput} so callers can pass raw
29
- * codec triples; the constructor stamps the discriminator.
23
+ * Polymorphic value type for document-scoped `SqlStorage.types` entries
24
+ * (codec aliases / parameterised native type registrations). Postgres
25
+ * native enum registrations live under
26
+ * `storage.namespaces[namespaceId].types` instead.
30
27
  */
31
28
  export type SqlStorageTypeEntry =
32
29
  | StorageTypeInstance
33
- | PostgresEnumStorageEntry
34
- | StorageTypeInstanceInput;
30
+ | StorageTypeInstanceInput
31
+ | PostgresEnumStorageEntry;
35
32
 
36
33
  const DEFAULT_NAMESPACES: Readonly<Record<string, Namespace>> = Object.freeze({
37
- [UNSPECIFIED_NAMESPACE_ID]: SqlUnspecifiedNamespace.instance,
34
+ [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance,
38
35
  });
39
36
 
37
+ export interface SqlNamespaceTablesInput {
38
+ readonly id: string;
39
+ readonly tables?: Record<string, StorageTable | StorageTableInput>;
40
+ readonly types?: Record<string, PostgresEnumStorageEntry>;
41
+ }
42
+
40
43
  export interface SqlStorageInput<THash extends string = string> {
41
44
  readonly storageHash: StorageHashBase<THash>;
42
- readonly tables: Record<string, StorageTable | StorageTableInput>;
43
45
  readonly types?: Record<string, SqlStorageTypeEntry>;
44
- readonly namespaces?: Readonly<Record<string, Namespace>>;
46
+ readonly namespaces?: Readonly<Record<string, Namespace | SqlNamespaceTablesInput>>;
47
+ }
48
+
49
+ class SqlNamespacePayload extends NamespaceBase {
50
+ declare readonly kind?: string;
51
+ declare readonly types?: Readonly<Record<string, PostgresEnumStorageEntry>>;
52
+
53
+ readonly id: string;
54
+ readonly tables: Readonly<Record<string, StorageTable>>;
55
+
56
+ constructor(input: SqlNamespaceTablesInput) {
57
+ super();
58
+ this.id = input.id;
59
+ this.tables = Object.freeze(
60
+ Object.fromEntries(
61
+ Object.entries(input.tables ?? {}).map(([name, t]) => [
62
+ name,
63
+ t instanceof StorageTable ? t : new StorageTable(t),
64
+ ]),
65
+ ),
66
+ );
67
+ if (input.types !== undefined && Object.keys(input.types).length > 0) {
68
+ Object.defineProperty(this, 'types', {
69
+ value: Object.freeze({ ...input.types }),
70
+ writable: false,
71
+ enumerable: true,
72
+ configurable: false,
73
+ });
74
+ }
75
+ Object.defineProperty(this, 'kind', {
76
+ value: 'sql-namespace',
77
+ writable: false,
78
+ enumerable: false,
79
+ configurable: true,
80
+ });
81
+ freezeNode(this);
82
+ }
83
+ }
84
+
85
+ function normaliseNamespaceEntry(
86
+ nsKey: string,
87
+ ns: Namespace | SqlNamespaceTablesInput,
88
+ ): Namespace {
89
+ if (ns instanceof NamespaceBase) {
90
+ return ns;
91
+ }
92
+ const input = ns as SqlNamespaceTablesInput; // JSON namespace payloads match SqlNamespaceTablesInput before SqlNamespacePayload materialises StorageTable instances.
93
+ const tableCount = Object.keys(input.tables ?? {}).length;
94
+ const typeCount = Object.keys(input.types ?? {}).length;
95
+ if (nsKey === UNBOUND_NAMESPACE_ID && tableCount === 0 && typeCount === 0) {
96
+ return SqlUnboundNamespace.instance;
97
+ }
98
+ return new SqlNamespacePayload(input);
45
99
  }
46
100
 
47
101
  /**
48
102
  * SQL Contract IR root node for the `storage` field.
49
103
  *
50
104
  * Single concrete family-shared class — both Postgres and SQLite
51
- * consume this same class today. Per-target storage subclasses are
105
+ * consume this class today. Per-target storage subclasses are
52
106
  * introduced when each target's namespace shape earns its
53
107
  * target-specific concretion (target-specific derived fields,
54
108
  * target-specific storage extensions).
55
109
  *
56
110
  * Honours the framework `Storage` interface: every SQL IR carries a
57
111
  * `namespaces` map keyed by namespace id. The default singleton
58
- * (`{ [UNSPECIFIED_NAMESPACE_ID]: SqlUnspecifiedNamespace.instance }`)
112
+ * (`{ [UNBOUND_NAMESPACE_ID]: SqlUnboundNamespace.instance }`)
59
113
  * binds every contract authored before per-target namespace concretions
60
- * land; per-target namespace classes (`PostgresSchema.unspecified`,
61
- * `SqliteUnspecifiedDatabase.instance`) earn their slots when each
114
+ * land; per-target namespace classes (`PostgresSchema.unbound`,
115
+ * `SqliteUnboundDatabase.instance`) earn their slots when each
62
116
  * target's namespace shape lands.
63
117
  *
64
- * The constructor normalises nested IR-class fields (`tables`, optional
65
- * `types`) into class instances so downstream walks see a uniform AST.
118
+ * The constructor normalises optional `types` into class instances and
119
+ * materialises plain namespace envelope objects into `Namespace` class
120
+ * instances so downstream walks see a uniform AST.
66
121
  * `types` is polymorphic per Decision 18 Option B: codec-triple inputs
67
122
  * are stamped with `kind: 'codec-instance'`; class-instance kinds
68
123
  * (e.g. Postgres-enum entries satisfying `PostgresEnumStorageEntry`)
@@ -71,31 +126,42 @@ export interface SqlStorageInput<THash extends string = string> {
71
126
  * responsibility (so the family base does not import target-specific
72
127
  * subclasses).
73
128
  */
129
+ // SQL concretions always store `StorageTable`-shaped values in `tables`.
130
+ // `tables` is a SQL-family idiom — the framework `Namespace` contract no
131
+ // longer mandates this field; Mongo namespaces carry `collections`
132
+ // instead. The `__unbound__` slot uses the same narrowing as every other
133
+ // SQL namespace; the wider `Record<string, object>` on `StorageTable` is
134
+ // only there so emitted `contract.d.ts` table literals (which lack the
135
+ // runtime `kind` discriminator on `StorageTable`) structurally satisfy
136
+ // the slot without a class-instance check.
137
+ export type SqlNamespace = Namespace & {
138
+ readonly tables: Readonly<Record<string, StorageTable>>;
139
+ readonly types?: Readonly<Record<string, PostgresEnumStorageEntry>>;
140
+ };
141
+
74
142
  export class SqlStorage<THash extends string = string> extends SqlNode implements Storage {
75
143
  readonly storageHash: StorageHashBase<THash>;
76
- readonly tables: Readonly<Record<string, StorageTable>>;
77
- readonly namespaces: Readonly<Record<string, Namespace>>;
78
- // SQL-family slot view: the two structural variants the family ships
79
- // today (codec triples + Postgres-enum structural entries). Each
80
- // variant extends the framework `StorageType` alphabet; the SQL
81
- // narrowing keeps cross-domain layering clean — SQL-family consumers
82
- // dispatch via `isStorageTypeInstance` / `isPostgresEnumStorageEntry`
83
- // type guards rather than importing the target's concrete IR class
84
- // (cross-domain rule: SQL may not import `target-*`).
144
+ readonly namespaces: Readonly<Record<string, SqlNamespace>> & {
145
+ readonly __unbound__: SqlNamespace;
146
+ };
85
147
  declare readonly types?: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
86
148
 
87
149
  constructor(input: SqlStorageInput<THash>) {
88
150
  super();
89
151
  this.storageHash = input.storageHash;
90
- this.tables = Object.freeze(
91
- Object.fromEntries(
92
- Object.entries(input.tables).map(([name, t]) => [
93
- name,
94
- t instanceof StorageTable ? t : new StorageTable(t),
95
- ]),
96
- ),
152
+ const inputNamespaces = input.namespaces ?? DEFAULT_NAMESPACES;
153
+ const normalised: Record<string, SqlNamespace> = Object.fromEntries(
154
+ Object.entries(inputNamespaces).map(([nsKey, ns]) => [
155
+ nsKey,
156
+ normaliseNamespaceEntry(nsKey, ns) as SqlNamespace,
157
+ ]),
97
158
  );
98
- this.namespaces = input.namespaces ?? DEFAULT_NAMESPACES;
159
+ if (!normalised[UNBOUND_NAMESPACE_ID]) {
160
+ normalised[UNBOUND_NAMESPACE_ID] = SqlUnboundNamespace.instance as SqlNamespace;
161
+ }
162
+ this.namespaces = Object.freeze(normalised) as Readonly<Record<string, SqlNamespace>> & {
163
+ readonly __unbound__: SqlNamespace;
164
+ };
99
165
  if (input.types !== undefined) {
100
166
  this.types = Object.freeze(
101
167
  Object.fromEntries(
@@ -108,9 +174,9 @@ export class SqlStorage<THash extends string = string> extends SqlNode implement
108
174
  }
109
175
 
110
176
  /**
111
- * Strict polymorphic-slot dispatch for `SqlStorage.types` entries
112
- * (TML-2536). Every entry must carry a recognised `kind` discriminator
113
- * — either `'codec-instance'` (codec triple, family-shared) or
177
+ * Strict polymorphic-slot dispatch for `SqlStorage.types` entries.
178
+ * Every entry must carry a recognised `kind` discriminator — either
179
+ * `'codec-instance'` (codec triple, family-shared) or
114
180
  * `'postgres-enum'` (target-specific IR class). Untagged or
115
181
  * unrecognised inputs throw a diagnostic naming the entry and its
116
182
  * `kind`, so format drift surfaces loudly at the deserializer
@@ -1,22 +1,25 @@
1
1
  import {
2
2
  freezeNode,
3
3
  NamespaceBase,
4
- UNSPECIFIED_NAMESPACE_ID,
4
+ UNBOUND_NAMESPACE_ID,
5
5
  } from '@prisma-next/framework-components/ir';
6
+ import type { StorageTable } from './storage-table';
6
7
 
7
8
  /**
8
- * Family-layer placeholder for the SQL unspecified-namespace singleton.
9
+ * Family-layer placeholder for the SQL unbound-namespace singleton
10
+ * the late-bound slot whose binding the target resolves at connection
11
+ * time rather than at authoring time.
9
12
  *
10
13
  * SQL contracts honour the framework `Storage.namespaces` invariant from
11
14
  * the moment they appear in the IR. Today `SqlStorage` is family-shared
12
15
  * (Postgres + SQLite consume the same class); a per-target namespace
13
- * concretion (`PostgresSchema.unspecified`, `SqliteUnspecifiedDatabase.instance`)
16
+ * concretion (`PostgresSchema.unbound`, `SqliteUnboundDatabase.instance`)
14
17
  * earns its existence when each target's namespace shape lands. Until
15
18
  * then the family ships a single placeholder singleton so the JSON
16
19
  * envelope and runtime walk are honest at every layer.
17
20
  *
18
21
  * The `kind` discriminator is installed as a non-enumerable own property
19
- * so the JSON envelope reads `{ "id": "__unspecified__" }` — symmetric
22
+ * so the JSON envelope reads `{ "id": "__unbound__" }` — symmetric
20
23
  * with the family-level non-enumerable `kind` on `SqlNode` and bounded
21
24
  * to the minimum data the framework `Namespace` interface promises.
22
25
  *
@@ -32,10 +35,11 @@ import {
32
35
  * fields safely, lift `freezeNode` to a leaf-class `seal()` hook each
33
36
  * leaf calls explicitly at the end of its own constructor.
34
37
  */
35
- export class SqlUnspecifiedNamespace extends NamespaceBase {
36
- static readonly instance: SqlUnspecifiedNamespace = new SqlUnspecifiedNamespace();
38
+ export class SqlUnboundNamespace extends NamespaceBase {
39
+ static readonly instance: SqlUnboundNamespace = new SqlUnboundNamespace();
37
40
 
38
- readonly id = UNSPECIFIED_NAMESPACE_ID;
41
+ readonly id = UNBOUND_NAMESPACE_ID;
42
+ readonly tables: Readonly<Record<string, StorageTable>> = Object.freeze({});
39
43
  declare readonly kind?: string;
40
44
 
41
45
  private constructor() {
@@ -15,7 +15,8 @@ export interface StorageTableInput {
15
15
  }
16
16
 
17
17
  /**
18
- * SQL Contract IR node for a single table entry in `SqlStorage.tables`.
18
+ * SQL Contract IR node for a single table entry in a namespace's
19
+ * `tables` map.
19
20
  *
20
21
  * The constructor normalises nested IR-class fields (columns, primary
21
22
  * key, uniques, indexes, foreign keys) into the appropriate class
@@ -23,10 +24,7 @@ export interface StorageTableInput {
23
24
  * the input was a JSON literal or an already-constructed class.
24
25
  *
25
26
  * The table's `name` is not on the class — tables are keyed by name in
26
- * the parent `SqlStorage.tables: Record<string, StorageTable>` map.
27
- * A future namespace-aware milestone will add a `namespaceId` field
28
- * when namespace-keyed storage lands; today's single-namespace shape
29
- * needs neither field.
27
+ * the parent namespace's `tables: Record<string, StorageTable>` map.
30
28
  */
31
29
  export class StorageTable extends SqlNode {
32
30
  readonly columns: Readonly<Record<string, StorageColumn>>;
package/src/types.ts CHANGED
@@ -7,9 +7,9 @@ export {
7
7
  type ReferentialAction,
8
8
  } from './ir/foreign-key';
9
9
  export {
10
- ForeignKeyReferences,
11
- type ForeignKeyReferencesInput,
12
- } from './ir/foreign-key-references';
10
+ ForeignKeyReference,
11
+ type ForeignKeyReferenceInput,
12
+ } from './ir/foreign-key-reference';
13
13
  export {
14
14
  isPostgresEnumStorageEntry,
15
15
  POSTGRES_ENUM_KIND,
@@ -19,11 +19,12 @@ export { PrimaryKey, type PrimaryKeyInput } from './ir/primary-key';
19
19
  export { Index, type IndexInput } from './ir/sql-index';
20
20
  export { SqlNode } from './ir/sql-node';
21
21
  export {
22
+ type SqlNamespaceTablesInput,
22
23
  SqlStorage,
23
24
  type SqlStorageInput,
24
25
  type SqlStorageTypeEntry,
25
26
  } from './ir/sql-storage';
26
- export { SqlUnspecifiedNamespace } from './ir/sql-unspecified-namespace';
27
+ export { SqlUnboundNamespace } from './ir/sql-unbound-namespace';
27
28
  export { StorageColumn, type StorageColumnInput } from './ir/storage-column';
28
29
  export { StorageTable, type StorageTableInput } from './ir/storage-table';
29
30
  export {