@prisma-next/family-sql 0.14.0-dev.20 → 0.14.0-dev.22
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/control.d.mts.map +1 -1
- package/dist/control.mjs +11 -7
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +8 -5
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
- package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
- package/dist/types-BxsFkHYv.d.mts.map +1 -1
- package/dist/verify-sql-schema-B8uEjw2s.d.mts.map +1 -1
- package/dist/{verify-sql-schema-BzMyoiiV.mjs → verify-sql-schema-CEY7b78t.mjs} +7 -7
- package/dist/verify-sql-schema-CEY7b78t.mjs.map +1 -0
- package/package.json +21 -22
- package/src/core/control-instance.ts +17 -3
- package/src/core/ir/sql-contract-serializer-base.ts +23 -55
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/migrations/contract-to-schema-ir.ts +5 -4
- package/src/core/migrations/field-event-planner.ts +8 -3
- package/src/core/schema-verify/verify-sql-schema.ts +3 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/verify-sql-schema-BzMyoiiV.mjs.map +0 -1
- package/src/exports/test-utils.ts +0 -9
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/family-sql",
|
|
3
|
-
"version": "0.14.0-dev.
|
|
3
|
+
"version": "0.14.0-dev.22",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "SQL family descriptor for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.14.0-dev.
|
|
10
|
-
"@prisma-next/emitter": "0.14.0-dev.
|
|
11
|
-
"@prisma-next/framework-components": "0.14.0-dev.
|
|
12
|
-
"@prisma-next/migration-tools": "0.14.0-dev.
|
|
13
|
-
"@prisma-next/operations": "0.14.0-dev.
|
|
14
|
-
"@prisma-next/sql-contract": "0.14.0-dev.
|
|
15
|
-
"@prisma-next/sql-contract-emitter": "0.14.0-dev.
|
|
16
|
-
"@prisma-next/sql-contract-ts": "0.14.0-dev.
|
|
17
|
-
"@prisma-next/sql-operations": "0.14.0-dev.
|
|
18
|
-
"@prisma-next/sql-relational-core": "0.14.0-dev.
|
|
19
|
-
"@prisma-next/sql-runtime": "0.14.0-dev.
|
|
20
|
-
"@prisma-next/sql-schema-ir": "0.14.0-dev.
|
|
21
|
-
"@prisma-next/utils": "0.14.0-dev.
|
|
9
|
+
"@prisma-next/contract": "0.14.0-dev.22",
|
|
10
|
+
"@prisma-next/emitter": "0.14.0-dev.22",
|
|
11
|
+
"@prisma-next/framework-components": "0.14.0-dev.22",
|
|
12
|
+
"@prisma-next/migration-tools": "0.14.0-dev.22",
|
|
13
|
+
"@prisma-next/operations": "0.14.0-dev.22",
|
|
14
|
+
"@prisma-next/sql-contract": "0.14.0-dev.22",
|
|
15
|
+
"@prisma-next/sql-contract-emitter": "0.14.0-dev.22",
|
|
16
|
+
"@prisma-next/sql-contract-ts": "0.14.0-dev.22",
|
|
17
|
+
"@prisma-next/sql-operations": "0.14.0-dev.22",
|
|
18
|
+
"@prisma-next/sql-relational-core": "0.14.0-dev.22",
|
|
19
|
+
"@prisma-next/sql-runtime": "0.14.0-dev.22",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.14.0-dev.22",
|
|
21
|
+
"@prisma-next/utils": "0.14.0-dev.22",
|
|
22
22
|
"arktype": "^2.2.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@prisma-next/driver-postgres": "0.14.0-dev.
|
|
26
|
-
"@prisma-next/psl-parser": "0.14.0-dev.
|
|
27
|
-
"@prisma-next/psl-printer": "0.14.0-dev.
|
|
28
|
-
"@prisma-next/sql-contract-psl": "0.14.0-dev.
|
|
29
|
-
"@prisma-next/test-utils": "0.14.0-dev.
|
|
30
|
-
"@prisma-next/tsconfig": "0.14.0-dev.
|
|
31
|
-
"@prisma-next/tsdown": "0.14.0-dev.
|
|
25
|
+
"@prisma-next/driver-postgres": "0.14.0-dev.22",
|
|
26
|
+
"@prisma-next/psl-parser": "0.14.0-dev.22",
|
|
27
|
+
"@prisma-next/psl-printer": "0.14.0-dev.22",
|
|
28
|
+
"@prisma-next/sql-contract-psl": "0.14.0-dev.22",
|
|
29
|
+
"@prisma-next/test-utils": "0.14.0-dev.22",
|
|
30
|
+
"@prisma-next/tsconfig": "0.14.0-dev.22",
|
|
31
|
+
"@prisma-next/tsdown": "0.14.0-dev.22",
|
|
32
32
|
"tsdown": "0.22.1",
|
|
33
33
|
"typescript": "5.9.3",
|
|
34
34
|
"vitest": "4.1.8"
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
"./pack": "./dist/pack.mjs",
|
|
54
54
|
"./runtime": "./dist/runtime.mjs",
|
|
55
55
|
"./schema-verify": "./dist/schema-verify.mjs",
|
|
56
|
-
"./test-utils": "./dist/test-utils.mjs",
|
|
57
56
|
"./verify": "./dist/verify.mjs",
|
|
58
57
|
"./package.json": "./package.json"
|
|
59
58
|
},
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
VERIFY_CODE_TARGET_MISMATCH,
|
|
33
33
|
} from '@prisma-next/framework-components/control';
|
|
34
34
|
import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
|
|
35
|
+
import { isPlainRecord } from '@prisma-next/framework-components/ir';
|
|
35
36
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
36
37
|
import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
|
|
37
38
|
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
@@ -44,6 +45,7 @@ import type {
|
|
|
44
45
|
} from '@prisma-next/sql-relational-core/ast';
|
|
45
46
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
46
47
|
import type { SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
48
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
47
49
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
48
50
|
import type { SqlControlAdapter } from './control-adapter';
|
|
49
51
|
import { SqlContractSerializer } from './ir/sql-contract-serializer';
|
|
@@ -527,12 +529,24 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
527
529
|
|
|
528
530
|
const targetSerializer = (
|
|
529
531
|
target as unknown as {
|
|
530
|
-
contractSerializer?: {
|
|
532
|
+
contractSerializer?: {
|
|
533
|
+
deserializeContract(json: unknown): Contract<SqlStorage>;
|
|
534
|
+
serializeContract(contract: Contract<SqlStorage>): unknown;
|
|
535
|
+
};
|
|
531
536
|
}
|
|
532
537
|
).contractSerializer;
|
|
533
|
-
const deserializeWithTargetSerializer = (
|
|
538
|
+
const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
|
|
534
539
|
const serializer = targetSerializer ?? new SqlContractSerializer();
|
|
535
|
-
|
|
540
|
+
const json =
|
|
541
|
+
targetSerializer !== undefined && !isPlainRecord(contractOrJson)
|
|
542
|
+
? targetSerializer.serializeContract(
|
|
543
|
+
blindCast<
|
|
544
|
+
Contract<SqlStorage>,
|
|
545
|
+
'isPlainRecord returned false, so contractOrJson is a class instance, not raw JSON'
|
|
546
|
+
>(contractOrJson),
|
|
547
|
+
)
|
|
548
|
+
: contractOrJson;
|
|
549
|
+
return serializer.deserializeContract(json) as Contract<SqlStorage>;
|
|
536
550
|
};
|
|
537
551
|
|
|
538
552
|
return {
|
|
@@ -6,18 +6,15 @@ import {
|
|
|
6
6
|
type AnyEntityKindDescriptor,
|
|
7
7
|
hydrateNamespaceEntities,
|
|
8
8
|
type Namespace,
|
|
9
|
-
NamespaceBase,
|
|
10
|
-
UNBOUND_NAMESPACE_ID,
|
|
11
9
|
} from '@prisma-next/framework-components/ir';
|
|
12
10
|
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
13
11
|
import { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';
|
|
14
12
|
import {
|
|
15
|
-
|
|
16
|
-
type
|
|
13
|
+
isMaterializedSqlNamespace,
|
|
14
|
+
type SqlNamespaceInput,
|
|
17
15
|
SqlStorage,
|
|
18
16
|
type SqlStorageInput,
|
|
19
17
|
type SqlStorageTypeEntry,
|
|
20
|
-
SqlUnboundNamespace,
|
|
21
18
|
} from '@prisma-next/sql-contract/types';
|
|
22
19
|
import {
|
|
23
20
|
createSqlContractSchema,
|
|
@@ -120,77 +117,48 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
120
117
|
'structural',
|
|
121
118
|
);
|
|
122
119
|
}
|
|
123
|
-
const hydratedNamespaces = this.hydrateSqlNamespaceMap(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// deserialized JSON (e.g. buildMixedPolyContract) working by providing a slot to
|
|
130
|
-
// write into. Once runtime-qualification routes table lookups by namespace, this
|
|
131
|
-
// shim should be removed.
|
|
132
|
-
//
|
|
133
|
-
// TML-2916: the shim only fires when the target's default namespace IS unbound
|
|
134
|
-
// (SQLite, Mongo). On Postgres (`defaultNamespaceId === 'public'`) injecting an
|
|
135
|
-
// empty `__unbound__` slot violates ADR 223 — un-namespaced PG models belong in
|
|
136
|
-
// `public`, not `__unbound__`.
|
|
137
|
-
const withInjectedUnbound =
|
|
138
|
-
this.defaultNamespaceId === UNBOUND_NAMESPACE_ID
|
|
139
|
-
? {
|
|
140
|
-
...hydratedNamespaces,
|
|
141
|
-
[UNBOUND_NAMESPACE_ID]:
|
|
142
|
-
hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance,
|
|
143
|
-
}
|
|
144
|
-
: hydratedNamespaces;
|
|
120
|
+
const hydratedNamespaces = this.hydrateSqlNamespaceMap(
|
|
121
|
+
blindCast<
|
|
122
|
+
Readonly<Record<string, Record<string, unknown>>>,
|
|
123
|
+
'parseSqlContractStructure validated raw JSON; namespace entries are plain objects, not SqlNamespace instances.'
|
|
124
|
+
>(rawNamespaces),
|
|
125
|
+
);
|
|
145
126
|
|
|
146
127
|
return {
|
|
147
128
|
...validated,
|
|
148
129
|
storage: new SqlStorage({
|
|
149
130
|
storageHash: validated.storage.storageHash,
|
|
150
131
|
...ifDefined('types', hydratedTypes),
|
|
151
|
-
// Cast narrows the result of hydrateSqlNamespaceMap from the wider
|
|
152
|
-
// framework `Namespace` to the SQL-family `SqlNamespace`.
|
|
153
132
|
namespaces: blindCast<
|
|
154
133
|
SqlStorageInput['namespaces'],
|
|
155
|
-
'hydrateSqlNamespaceMap builds each namespace through the
|
|
156
|
-
>(
|
|
134
|
+
'hydrateSqlNamespaceMap builds each namespace through the target serializer override, so every value is a SqlNamespace; the framework return type only promises the base Namespace.'
|
|
135
|
+
>(hydratedNamespaces),
|
|
157
136
|
}),
|
|
158
137
|
};
|
|
159
138
|
}
|
|
160
139
|
|
|
161
|
-
protected abstract get defaultNamespaceId(): string;
|
|
162
|
-
|
|
163
140
|
protected hydrateSqlNamespaceMap(
|
|
164
|
-
namespaces: Readonly<Record<string,
|
|
141
|
+
namespaces: Readonly<Record<string, Record<string, unknown>>>,
|
|
165
142
|
): Readonly<Record<string, Namespace>> {
|
|
166
143
|
return Object.fromEntries(
|
|
167
144
|
Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {
|
|
168
|
-
// Raw entries passed structural validation; hydrate materialises family IR class instances.
|
|
169
145
|
const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
'hydrateSqlNamespaceEntry returns SqlNamespaceTablesInput when raw is not a NamespaceBase'
|
|
177
|
-
>(namespaceHydrated),
|
|
178
|
-
);
|
|
179
|
-
return [nsId, namespaceMaterialised];
|
|
146
|
+
if (!isMaterializedSqlNamespace(namespaceHydrated)) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Target serializer bug: hydrateSqlNamespaceEntry for namespace "${nsId}" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return [nsId, namespaceHydrated];
|
|
180
152
|
}),
|
|
181
153
|
);
|
|
182
154
|
}
|
|
183
155
|
|
|
184
156
|
protected hydrateSqlNamespaceEntry(
|
|
185
157
|
nsId: string,
|
|
186
|
-
raw:
|
|
187
|
-
): Namespace |
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
const rawRecord = isPlainRecord(raw) ? raw : {};
|
|
192
|
-
const id = typeof rawRecord['id'] === 'string' ? rawRecord['id'] : nsId;
|
|
193
|
-
const parsed = NamespaceRawSchema({ ...rawRecord, id });
|
|
158
|
+
raw: Record<string, unknown>,
|
|
159
|
+
): Namespace | SqlNamespaceInput {
|
|
160
|
+
const id = typeof raw['id'] === 'string' ? raw['id'] : nsId;
|
|
161
|
+
const parsed = NamespaceRawSchema({ ...raw, id });
|
|
194
162
|
if (parsed instanceof type.errors) {
|
|
195
163
|
const messages = parsed.map((p: { message: string }) => p.message).join('; ');
|
|
196
164
|
throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');
|
|
@@ -211,8 +179,8 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
211
179
|
}
|
|
212
180
|
|
|
213
181
|
return blindCast<
|
|
214
|
-
|
|
215
|
-
'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the
|
|
182
|
+
SqlNamespaceInput,
|
|
183
|
+
'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the SqlNamespaceInput the target createNamespace consumes.'
|
|
216
184
|
>({
|
|
217
185
|
id,
|
|
218
186
|
entries: entriesOutput,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
-
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
3
2
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
4
3
|
import { SqlContractSerializerBase } from './sql-contract-serializer-base';
|
|
5
4
|
|
|
@@ -11,15 +10,14 @@ import { SqlContractSerializerBase } from './sql-contract-serializer-base';
|
|
|
11
10
|
* codec-typed envelope wire a target-specific subclass with a populated
|
|
12
11
|
* registry (see Postgres). Family-level call sites instantiate this
|
|
13
12
|
* default directly when no target serializer is supplied.
|
|
13
|
+
*
|
|
14
|
+
* Because this serializer has no target concretion, deserialization of
|
|
15
|
+
* contracts that include namespace entries from JSON will throw unless
|
|
16
|
+
* the caller provides pre-hydrated `NamespaceBase` instances. Production
|
|
17
|
+
* paths always supply a target-specific serializer.
|
|
14
18
|
*/
|
|
15
19
|
export class SqlContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {
|
|
16
20
|
constructor() {
|
|
17
21
|
super(new Map());
|
|
18
22
|
}
|
|
19
|
-
|
|
20
|
-
// Family-level fallback when no target descriptor is wired in. Preserves the
|
|
21
|
-
// pre-TML-2916 compatibility-shim behaviour for the unbound slot.
|
|
22
|
-
protected override get defaultNamespaceId(): string {
|
|
23
|
-
return UNBOUND_NAMESPACE_ID;
|
|
24
|
-
}
|
|
25
23
|
}
|
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
type CheckConstraint,
|
|
5
5
|
type ForeignKey,
|
|
6
6
|
type Index,
|
|
7
|
+
isStorageTable,
|
|
7
8
|
isStorageTypeInstance,
|
|
8
9
|
type SqlStorage,
|
|
9
10
|
type StorageColumn,
|
|
10
|
-
StorageTable,
|
|
11
|
+
type StorageTable,
|
|
11
12
|
type StorageTypeInstance,
|
|
12
13
|
type UniqueConstraint,
|
|
13
14
|
} from '@prisma-next/sql-contract/types';
|
|
@@ -304,7 +305,7 @@ export function detectDestructiveChanges(
|
|
|
304
305
|
|
|
305
306
|
for (const tableName of Object.keys(fromTables)) {
|
|
306
307
|
const toTableRaw = toNs?.entries.table?.[tableName];
|
|
307
|
-
if (!(toTableRaw
|
|
308
|
+
if (!isStorageTable(toTableRaw)) {
|
|
308
309
|
conflicts.push({
|
|
309
310
|
kind: 'tableRemoved',
|
|
310
311
|
summary: `Table "${tableName}" was removed`,
|
|
@@ -314,7 +315,7 @@ export function detectDestructiveChanges(
|
|
|
314
315
|
const toTable = toTableRaw;
|
|
315
316
|
|
|
316
317
|
const fromTableRaw = fromTables[tableName];
|
|
317
|
-
if (!(fromTableRaw
|
|
318
|
+
if (!isStorageTable(fromTableRaw)) continue;
|
|
318
319
|
const fromTable = fromTableRaw;
|
|
319
320
|
|
|
320
321
|
for (const columnName of Object.keys(fromTable.columns)) {
|
|
@@ -378,7 +379,7 @@ export function contractToSchemaIR(
|
|
|
378
379
|
const tables: Record<string, SqlTableIR> = {};
|
|
379
380
|
for (const ns of Object.values(storage.namespaces)) {
|
|
380
381
|
for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
381
|
-
if (!(tableDefRaw
|
|
382
|
+
if (!isStorageTable(tableDefRaw)) {
|
|
382
383
|
throw new Error(
|
|
383
384
|
`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`,
|
|
384
385
|
);
|
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
|
|
25
25
|
import type { Contract } from '@prisma-next/contract/types';
|
|
26
26
|
import type { OpFactoryCall } from '@prisma-next/framework-components/control';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
isStorageTable,
|
|
29
|
+
type SqlStorage,
|
|
30
|
+
type StorageColumn,
|
|
31
|
+
type StorageTable,
|
|
32
|
+
} from '@prisma-next/sql-contract/types';
|
|
28
33
|
import type { CodecControlHooks, FieldEvent, FieldEventContext } from './types';
|
|
29
34
|
|
|
30
35
|
export interface PlanFieldEventOperationsOptions {
|
|
@@ -88,8 +93,8 @@ export function planFieldEventOperations(
|
|
|
88
93
|
for (const tableName of tableNames) {
|
|
89
94
|
const priorTableRaw = priorTables?.[tableName];
|
|
90
95
|
const newTableRaw = newTables?.[tableName];
|
|
91
|
-
const priorTable = priorTableRaw
|
|
92
|
-
const newTable = newTableRaw
|
|
96
|
+
const priorTable = isStorageTable(priorTableRaw) ? priorTableRaw : undefined;
|
|
97
|
+
const newTable = isStorageTable(newTableRaw) ? newTableRaw : undefined;
|
|
93
98
|
const fieldNames = unionSorted(
|
|
94
99
|
priorTable ? Object.keys(priorTable.columns) : [],
|
|
95
100
|
newTable ? Object.keys(newTable.columns) : [],
|
|
@@ -18,10 +18,11 @@ import type {
|
|
|
18
18
|
} from '@prisma-next/framework-components/control';
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
|
+
isStorageTable,
|
|
21
22
|
isStorageTypeInstance,
|
|
22
23
|
type SqlStorage,
|
|
23
24
|
type StorageColumn,
|
|
24
|
-
StorageTable,
|
|
25
|
+
type StorageTable,
|
|
25
26
|
type StorageTypeInstance,
|
|
26
27
|
} from '@prisma-next/sql-contract/types';
|
|
27
28
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
@@ -298,7 +299,7 @@ function verifySchemaTables(options: {
|
|
|
298
299
|
const ns = contract.storage.namespaces[namespaceId];
|
|
299
300
|
if (!ns) continue;
|
|
300
301
|
for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
301
|
-
if (!(contractTableRaw
|
|
302
|
+
if (!isStorageTable(contractTableRaw)) {
|
|
302
303
|
throw new Error(
|
|
303
304
|
`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`,
|
|
304
305
|
);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sql-contract-serializer-BR2vC7Z-.mjs","names":[],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts"],"sourcesContent":["import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';\nimport { isPlainRecord } from '@prisma-next/contract/is-plain-record';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ContractSerializer } from '@prisma-next/framework-components/control';\nimport {\n type AnyEntityKindDescriptor,\n hydrateNamespaceEntities,\n type Namespace,\n NamespaceBase,\n UNBOUND_NAMESPACE_ID,\n} from '@prisma-next/framework-components/ir';\nimport { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';\nimport { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';\nimport {\n buildSqlNamespace,\n type SqlNamespaceTablesInput,\n SqlStorage,\n type SqlStorageInput,\n type SqlStorageTypeEntry,\n SqlUnboundNamespace,\n} from '@prisma-next/sql-contract/types';\nimport {\n createSqlContractSchema,\n validateSqlContractFully,\n} from '@prisma-next/sql-contract/validators';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport { type Type, type } from 'arktype';\n\nconst NamespaceRawSchema = type({\n id: 'string',\n 'kind?': 'string',\n entries: type({\n '+': 'ignore',\n }),\n});\n\nexport type SqlEntityHydrationFactory = (entry: unknown) => unknown;\n\n/**\n * SQL family `ContractSerializer` abstract base. Carries the SQL-shared\n * deserialization pipeline:\n *\n * 1. `parseSqlContractStructure` validates the on-disk JSON envelope\n * against the SQL contract arktype schema (`validateSqlContractFully`)\n * and returns the validated flat-data shape.\n * 2. `hydrateSqlStorage` walks the validated `storage` subtree and\n * constructs the family-shared SQL Contract IR class hierarchy\n * (`SqlStorage` -> `StorageTable` -> `StorageColumn` / `PrimaryKey`\n * / …). The rest of the contract envelope is JSON-clean primitive\n * data and passes through unchanged.\n * 3. `constructTargetContract` is the target-specific extension hook;\n * defaults to identity. Targets that need to attach target-only\n * fields (e.g. target-specific derived storage fields) override it.\n *\n * Default `serializeContract` is identity over the contract — concrete\n * SQL targets ship JSON-clean class instances, so the contract value\n * can be stringified directly. The non-enumerable family-level `kind`\n * discriminator on `SqlNode` instances stays out of the persisted\n * envelope automatically. Targets that need to canonicalize on the way\n * out (key ordering, dropping computed-only fields) override\n * `serializeContract` directly.\n */\nexport abstract class SqlContractSerializerBase<TContract extends Contract<SqlStorage>>\n implements ContractSerializer<TContract>\n{\n private readonly contractSchema: Type<unknown> | undefined;\n private readonly entryKinds: ReadonlyMap<string, AnyEntityKindDescriptor>;\n\n constructor(\n protected readonly entityHydrationRegistry: ReadonlyMap<\n string,\n SqlEntityHydrationFactory\n > = new Map(),\n packEntityKinds: readonly AnyEntityKindDescriptor[] = [],\n ) {\n this.entryKinds = composeSqlEntityKinds(packEntityKinds);\n this.contractSchema =\n packEntityKinds.length > 0 ? createSqlContractSchema(this.entryKinds) : undefined;\n }\n\n deserializeContract<T extends TContract = TContract>(json: unknown): T {\n const validated = this.parseSqlContractStructure(json);\n const hydrated = this.hydrateSqlStorage(validated);\n return this.constructTargetContract(hydrated) as T;\n }\n\n serializeContract(contract: TContract): JsonObject {\n return contract as unknown as JsonObject;\n }\n\n shouldPreserveEmpty = sqlContractCanonicalizationHooks.shouldPreserveEmpty;\n\n sortStorage = sqlContractCanonicalizationHooks.sortStorage;\n\n protected parseSqlContractStructure(json: unknown): Contract<SqlStorage> {\n return validateSqlContractFully<Contract<SqlStorage>>(\n json,\n this.contractSchema !== undefined ? { contractSchema: this.contractSchema } : undefined,\n );\n }\n\n protected hydrateSqlStorage(validated: Contract<SqlStorage>): Contract<SqlStorage> {\n const types = validated.storage.types;\n const hydratedTypes =\n types !== undefined\n ? Object.fromEntries(\n Object.entries(types).map(([name, entry]) => [\n name,\n this.hydrateStorageTypeEntry(entry),\n ]),\n )\n : undefined;\n\n const rawNamespaces = validated.storage.namespaces;\n if (rawNamespaces === undefined) {\n throw new ContractValidationError(\n 'Contract storage.namespaces is required after structural validation',\n 'structural',\n );\n }\n const hydratedNamespaces = this.hydrateSqlNamespaceMap(rawNamespaces);\n // Compatibility shim: production code that addresses `__unbound__` for table\n // metadata lookups (collection-contract, query-plan-mutations, model-accessor,\n // query-plan-meta, where-binding) uses optional chaining and tolerates absence,\n // but runtime-qualification (TML-2605) has not yet landed cross-namespace table\n // routing. Injecting the empty singleton here keeps helpers that augment the\n // deserialized JSON (e.g. buildMixedPolyContract) working by providing a slot to\n // write into. Once runtime-qualification routes table lookups by namespace, this\n // shim should be removed.\n //\n // TML-2916: the shim only fires when the target's default namespace IS unbound\n // (SQLite, Mongo). On Postgres (`defaultNamespaceId === 'public'`) injecting an\n // empty `__unbound__` slot violates ADR 223 — un-namespaced PG models belong in\n // `public`, not `__unbound__`.\n const withInjectedUnbound =\n this.defaultNamespaceId === UNBOUND_NAMESPACE_ID\n ? {\n ...hydratedNamespaces,\n [UNBOUND_NAMESPACE_ID]:\n hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance,\n }\n : hydratedNamespaces;\n\n return {\n ...validated,\n storage: new SqlStorage({\n storageHash: validated.storage.storageHash,\n ...ifDefined('types', hydratedTypes),\n // Cast narrows the result of hydrateSqlNamespaceMap from the wider\n // framework `Namespace` to the SQL-family `SqlNamespace`.\n namespaces: blindCast<\n SqlStorageInput['namespaces'],\n 'hydrateSqlNamespaceMap builds each namespace through the SQL family concretions (SqlBoundNamespace / target schema), so every value is a SqlNamespace; the framework return type only promises the base Namespace.'\n >(withInjectedUnbound),\n }),\n };\n }\n\n protected abstract get defaultNamespaceId(): string;\n\n protected hydrateSqlNamespaceMap(\n namespaces: Readonly<Record<string, Namespace | Record<string, unknown>>>,\n ): Readonly<Record<string, Namespace>> {\n return Object.fromEntries(\n Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {\n // Raw entries passed structural validation; hydrate materialises family IR class instances.\n const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);\n const namespaceMaterialised =\n namespaceHydrated instanceof NamespaceBase\n ? namespaceHydrated\n : buildSqlNamespace(\n blindCast<\n SqlNamespaceTablesInput,\n 'hydrateSqlNamespaceEntry returns SqlNamespaceTablesInput when raw is not a NamespaceBase'\n >(namespaceHydrated),\n );\n return [nsId, namespaceMaterialised];\n }),\n );\n }\n\n protected hydrateSqlNamespaceEntry(\n nsId: string,\n raw: Namespace | Record<string, unknown>,\n ): Namespace | SqlNamespaceTablesInput {\n if (raw instanceof NamespaceBase) {\n return raw;\n }\n const rawRecord = isPlainRecord(raw) ? raw : {};\n const id = typeof rawRecord['id'] === 'string' ? rawRecord['id'] : nsId;\n const parsed = NamespaceRawSchema({ ...rawRecord, id });\n if (parsed instanceof type.errors) {\n const messages = parsed.map((p: { message: string }) => p.message).join('; ');\n throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');\n }\n const entriesRaw = parsed.entries;\n const rawEntriesMap = isPlainRecord(entriesRaw) ? entriesRaw : {};\n\n const entriesInput: Record<string, Readonly<Record<string, unknown>>> = {};\n for (const [key, innerMap] of Object.entries(rawEntriesMap)) {\n entriesInput[key] = isPlainRecord(innerMap) ? innerMap : Object.freeze({});\n }\n\n const entriesOutput = hydrateNamespaceEntities(entriesInput, this.entryKinds, 'fail', id);\n\n // Always ensure a 'table' key is present (may be empty).\n if (!Object.hasOwn(entriesOutput, 'table')) {\n entriesOutput['table'] = {};\n }\n\n return blindCast<\n SqlNamespaceTablesInput,\n 'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the SqlNamespaceTablesInput the family createNamespace consumes.'\n >({\n id,\n entries: entriesOutput,\n });\n }\n\n protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {\n if (typeof entry !== 'object' || entry === null) {\n return entry;\n }\n const kind = (entry as { kind?: unknown }).kind;\n if (typeof kind !== 'string') {\n return entry;\n }\n const factory = this.entityHydrationRegistry.get(kind);\n if (factory === undefined) {\n return entry;\n }\n return blindCast<\n SqlStorageTypeEntry,\n 'entity registry factory returns SqlStorageTypeEntry for storage.types entries'\n >(factory(entry));\n }\n\n protected constructTargetContract(hydrated: Contract<SqlStorage>): TContract {\n return hydrated as TContract;\n }\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport { SqlContractSerializerBase } from './sql-contract-serializer-base';\n\n/**\n * Default SQL family `ContractSerializer` concretion. Inherits the\n * full SQL-shared deserialization pipeline (structural validation +\n * IR-class hydration) without pack-registered `storage.types`\n * hydration factories — targets that emit polymorphic JSON outside the\n * codec-typed envelope wire a target-specific subclass with a populated\n * registry (see Postgres). Family-level call sites instantiate this\n * default directly when no target serializer is supplied.\n */\nexport class SqlContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {\n constructor() {\n super(new Map());\n }\n\n // Family-level fallback when no target descriptor is wired in. Preserves the\n // pre-TML-2916 compatibility-shim behaviour for the unbound slot.\n protected override get defaultNamespaceId(): string {\n return UNBOUND_NAMESPACE_ID;\n }\n}\n"],"mappings":"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"}
|
package/dist/test-utils.d.mts
DELETED
package/dist/test-utils.mjs
DELETED