@prisma-next/sql-contract 0.12.0 → 0.13.0-dev.1
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/canonicalization-hooks.d.mts.map +1 -1
- package/dist/canonicalization-hooks.mjs +10 -11
- package/dist/canonicalization-hooks.mjs.map +1 -1
- package/dist/factories.d.mts +4 -2
- package/dist/factories.d.mts.map +1 -1
- package/dist/factories.mjs +3 -2
- package/dist/factories.mjs.map +1 -1
- package/dist/foreign-key-BATxB95l.d.mts +121 -0
- package/dist/foreign-key-BATxB95l.d.mts.map +1 -0
- package/dist/index-type-validation.d.mts +2 -2
- package/dist/index-type-validation.mjs +1 -1
- package/dist/index-type-validation.mjs.map +1 -1
- package/dist/{index-types-B1cf5N0F.d.mts → index-types-Czsyu7Iw.d.mts} +1 -1
- package/dist/{index-types-B1cf5N0F.d.mts.map → index-types-Czsyu7Iw.d.mts.map} +1 -1
- package/dist/index-types.d.mts +1 -1
- package/dist/pack-types.d.mts +1 -1
- package/dist/referential-action-sql.d.mts +12 -0
- package/dist/referential-action-sql.d.mts.map +1 -0
- package/dist/referential-action-sql.mjs +17 -0
- package/dist/referential-action-sql.mjs.map +1 -0
- package/dist/resolve-storage-table.d.mts +20 -0
- package/dist/resolve-storage-table.d.mts.map +1 -0
- package/dist/resolve-storage-table.mjs +42 -0
- package/dist/resolve-storage-table.mjs.map +1 -0
- package/dist/sql-storage-CXf9xjAL.d.mts +314 -0
- package/dist/sql-storage-CXf9xjAL.d.mts.map +1 -0
- package/dist/types-DEnWD3xB.d.mts +208 -0
- package/dist/types-DEnWD3xB.d.mts.map +1 -0
- package/dist/{types-DPkj4y3_.mjs → types-DqhaAjCH.mjs} +109 -28
- package/dist/types-DqhaAjCH.mjs.map +1 -0
- package/dist/types.d.mts +4 -2
- package/dist/types.mjs +2 -2
- package/dist/validators.d.mts +51 -14
- package/dist/validators.d.mts.map +1 -1
- package/dist/validators.mjs +116 -32
- package/dist/validators.mjs.map +1 -1
- package/package.json +11 -9
- package/src/canonicalization-hooks.ts +5 -6
- package/src/exports/referential-action-sql.ts +1 -0
- package/src/exports/resolve-storage-table.ts +1 -0
- package/src/exports/types.ts +6 -0
- package/src/factories.ts +2 -1
- package/src/index-type-validation.ts +1 -1
- package/src/ir/build-sql-namespace.ts +33 -19
- package/src/ir/check-constraint.ts +42 -0
- package/src/ir/foreign-key-reference.ts +23 -0
- package/src/ir/postgres-enum-storage-entry.ts +2 -0
- package/src/ir/sql-storage.ts +53 -50
- package/src/ir/sql-unbound-namespace.ts +11 -4
- package/src/ir/storage-column.ts +7 -1
- package/src/ir/storage-table.ts +10 -0
- package/src/ir/storage-type-instance.ts +5 -3
- package/src/ir/storage-value-set.ts +42 -0
- package/src/referential-action-sql.ts +14 -0
- package/src/resolve-storage-table.ts +61 -0
- package/src/types.ts +13 -0
- package/src/validators.ts +156 -46
- package/dist/types-ChlHcJCu.d.mts +0 -508
- package/dist/types-ChlHcJCu.d.mts.map +0 -1
- package/dist/types-DPkj4y3_.mjs.map +0 -1
package/src/exports/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type {
|
|
2
|
+
CheckConstraintInput,
|
|
2
3
|
CodecTypesOf,
|
|
3
4
|
ContractWithTypeMaps,
|
|
4
5
|
ExtractCodecTypes,
|
|
@@ -21,6 +22,7 @@ export type {
|
|
|
21
22
|
QueryOperationTypesOf,
|
|
22
23
|
ReferentialAction,
|
|
23
24
|
ResolveCodecTypes,
|
|
25
|
+
SqlControlDriverInstance,
|
|
24
26
|
SqlModelFieldStorage,
|
|
25
27
|
SqlModelStorage,
|
|
26
28
|
SqlNamespaceTablesInput,
|
|
@@ -31,6 +33,7 @@ export type {
|
|
|
31
33
|
StorageTableInput,
|
|
32
34
|
StorageTypeInstance,
|
|
33
35
|
StorageTypeInstanceInput,
|
|
36
|
+
StorageValueSetInput,
|
|
34
37
|
TypeMaps,
|
|
35
38
|
TypeMapsPhantomKey,
|
|
36
39
|
UniqueConstraintInput,
|
|
@@ -39,6 +42,7 @@ export {
|
|
|
39
42
|
applyFkDefaults,
|
|
40
43
|
buildSqlNamespace,
|
|
41
44
|
buildSqlNamespaceMap,
|
|
45
|
+
CheckConstraint,
|
|
42
46
|
CODEC_INSTANCE_KIND,
|
|
43
47
|
DEFAULT_FK_CONSTRAINT,
|
|
44
48
|
DEFAULT_FK_INDEX,
|
|
@@ -54,6 +58,8 @@ export {
|
|
|
54
58
|
SqlUnboundNamespace,
|
|
55
59
|
StorageColumn,
|
|
56
60
|
StorageTable,
|
|
61
|
+
StorageValueSet,
|
|
62
|
+
storageTableAt,
|
|
57
63
|
toStorageTypeInstance,
|
|
58
64
|
UniqueConstraint,
|
|
59
65
|
} from '../types';
|
package/src/factories.ts
CHANGED
|
@@ -72,12 +72,13 @@ export function model(
|
|
|
72
72
|
tableName: string,
|
|
73
73
|
fields: Record<string, SqlModelFieldStorage>,
|
|
74
74
|
relations: Record<string, unknown> = {},
|
|
75
|
+
namespaceId: string = UNBOUND_NAMESPACE_ID,
|
|
75
76
|
): {
|
|
76
77
|
storage: SqlModelStorage;
|
|
77
78
|
fields: Record<string, { readonly nullable: boolean; readonly type: ScalarFieldType }>;
|
|
78
79
|
relations: Record<string, unknown>;
|
|
79
80
|
} {
|
|
80
|
-
const storage: SqlModelStorage = { table: tableName, fields };
|
|
81
|
+
const storage: SqlModelStorage = { table: tableName, namespaceId, fields };
|
|
81
82
|
const domainFields = Object.fromEntries(
|
|
82
83
|
Object.entries(fields).map(([name, field]) => [
|
|
83
84
|
name,
|
|
@@ -9,7 +9,7 @@ export function validateIndexTypes(
|
|
|
9
9
|
indexTypeRegistry: IndexTypeRegistry,
|
|
10
10
|
): void {
|
|
11
11
|
for (const [namespaceId, ns] of Object.entries(contract.storage.namespaces)) {
|
|
12
|
-
for (const [tableName, rawTable] of Object.entries(ns.
|
|
12
|
+
for (const [tableName, rawTable] of Object.entries(ns.entries.table)) {
|
|
13
13
|
const table = rawTable as StorageTable;
|
|
14
14
|
for (const index of table.indexes) {
|
|
15
15
|
if (index.type === undefined && index.options !== undefined) {
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
UNBOUND_NAMESPACE_ID,
|
|
6
6
|
} from '@prisma-next/framework-components/ir';
|
|
7
7
|
import { blindCast, castAs } from '@prisma-next/utils/casts';
|
|
8
|
-
import type { PostgresEnumStorageEntry } from './postgres-enum-storage-entry';
|
|
9
8
|
import type { SqlNamespace, SqlNamespaceTablesInput } from './sql-storage';
|
|
10
9
|
import { SqlUnboundNamespace } from './sql-unbound-namespace';
|
|
11
10
|
import { StorageTable } from './storage-table';
|
|
11
|
+
import { StorageValueSet } from './storage-value-set';
|
|
12
12
|
|
|
13
13
|
const SQL_NAMESPACE_KIND = 'sql-namespace' as const;
|
|
14
14
|
|
|
@@ -25,15 +25,18 @@ function isMaterializedSqlNamespace(ns: Namespace | SqlNamespaceTablesInput): ns
|
|
|
25
25
|
|
|
26
26
|
class SqlBoundNamespace extends NamespaceBase {
|
|
27
27
|
declare readonly kind: string;
|
|
28
|
-
declare readonly enum?: Readonly<Record<string, PostgresEnumStorageEntry>>;
|
|
29
28
|
|
|
30
29
|
readonly id: string;
|
|
31
|
-
readonly
|
|
30
|
+
readonly entries: Readonly<{
|
|
31
|
+
readonly table: Readonly<Record<string, StorageTable>>;
|
|
32
|
+
readonly valueSet?: Readonly<Record<string, StorageValueSet>>;
|
|
33
|
+
}>;
|
|
32
34
|
|
|
33
35
|
static fromTablesInput(input: SqlNamespaceTablesInput): SqlNamespace {
|
|
34
|
-
const tableCount = Object.keys(input.
|
|
35
|
-
const
|
|
36
|
-
|
|
36
|
+
const tableCount = Object.keys(input.entries.table).length;
|
|
37
|
+
const hasValueSets =
|
|
38
|
+
input.entries.valueSet !== undefined && Object.keys(input.entries.valueSet).length > 0;
|
|
39
|
+
if (input.id === UNBOUND_NAMESPACE_ID && tableCount === 0 && !hasValueSets) {
|
|
37
40
|
return castAs<SqlNamespace>(SqlUnboundNamespace.instance);
|
|
38
41
|
}
|
|
39
42
|
return castAs<SqlNamespace>(new SqlBoundNamespace(input));
|
|
@@ -42,21 +45,20 @@ class SqlBoundNamespace extends NamespaceBase {
|
|
|
42
45
|
private constructor(input: SqlNamespaceTablesInput) {
|
|
43
46
|
super();
|
|
44
47
|
this.id = input.id;
|
|
45
|
-
|
|
48
|
+
const table = Object.freeze(
|
|
46
49
|
Object.fromEntries(
|
|
47
|
-
Object.entries(input.
|
|
48
|
-
name,
|
|
49
|
-
t instanceof StorageTable ? t : new StorageTable(t),
|
|
50
|
-
]),
|
|
50
|
+
Object.entries(input.entries.table).map(([k, v]) => [k, new StorageTable(v)]),
|
|
51
51
|
),
|
|
52
52
|
);
|
|
53
|
-
if (input.
|
|
54
|
-
Object.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
53
|
+
if (input.entries.valueSet !== undefined) {
|
|
54
|
+
const valueSet = Object.freeze(
|
|
55
|
+
Object.fromEntries(
|
|
56
|
+
Object.entries(input.entries.valueSet).map(([k, v]) => [k, new StorageValueSet(v)]),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
this.entries = Object.freeze({ table, valueSet });
|
|
60
|
+
} else {
|
|
61
|
+
this.entries = Object.freeze({ table });
|
|
60
62
|
}
|
|
61
63
|
Object.defineProperty(this, 'kind', {
|
|
62
64
|
value: SQL_NAMESPACE_KIND,
|
|
@@ -66,6 +68,13 @@ class SqlBoundNamespace extends NamespaceBase {
|
|
|
66
68
|
});
|
|
67
69
|
freezeNode(this);
|
|
68
70
|
}
|
|
71
|
+
|
|
72
|
+
qualifyTable(tableName: string): string {
|
|
73
|
+
if (this.id === UNBOUND_NAMESPACE_ID) {
|
|
74
|
+
return `"${tableName}"`;
|
|
75
|
+
}
|
|
76
|
+
return `"${this.id}"."${tableName}"`;
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
export function buildSqlNamespace(input: SqlNamespaceTablesInput): SqlNamespace {
|
|
@@ -83,7 +92,12 @@ export function buildSqlNamespaceMap(
|
|
|
83
92
|
SqlNamespace,
|
|
84
93
|
'a materialised SQL-family namespace entry in a namespace map is a SqlNamespace'
|
|
85
94
|
>(ns)
|
|
86
|
-
: SqlBoundNamespace.fromTablesInput(
|
|
95
|
+
: SqlBoundNamespace.fromTablesInput(
|
|
96
|
+
blindCast<
|
|
97
|
+
SqlNamespaceTablesInput,
|
|
98
|
+
'non-materialized SQL namespace map entry is a SqlNamespaceTablesInput'
|
|
99
|
+
>(ns),
|
|
100
|
+
),
|
|
87
101
|
]),
|
|
88
102
|
);
|
|
89
103
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ValueSetRef } from '@prisma-next/contract/types';
|
|
2
|
+
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
3
|
+
import { SqlNode } from './sql-node';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hydration / construction input shape for {@link CheckConstraint}.
|
|
7
|
+
* Mirrors the on-disk storage JSON envelope so the serializer hydration
|
|
8
|
+
* walker can hand a validated literal straight to `new`.
|
|
9
|
+
*/
|
|
10
|
+
export interface CheckConstraintInput {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly column: string;
|
|
13
|
+
readonly valueSet: ValueSetRef;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SQL Contract IR node for a table-level check constraint that restricts
|
|
18
|
+
* a column to the permitted values of a value-set.
|
|
19
|
+
*
|
|
20
|
+
* The constraint is **structured** (names a column and a value-set
|
|
21
|
+
* reference), not a raw SQL expression. Each target renders its own DDL
|
|
22
|
+
* from the structured form, keeping the contract target-agnostic.
|
|
23
|
+
*
|
|
24
|
+
* Construction is idempotent: passing an existing `CheckConstraint`
|
|
25
|
+
* instance as input produces a new instance with identical fields.
|
|
26
|
+
* The constructor does not use `instanceof` for input discrimination —
|
|
27
|
+
* it reads plain named properties, which is sufficient since
|
|
28
|
+
* `CheckConstraintInput` is a structural type.
|
|
29
|
+
*/
|
|
30
|
+
export class CheckConstraint extends SqlNode {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly column: string;
|
|
33
|
+
readonly valueSet: ValueSetRef;
|
|
34
|
+
|
|
35
|
+
constructor(input: CheckConstraintInput) {
|
|
36
|
+
super();
|
|
37
|
+
this.name = input.name;
|
|
38
|
+
this.column = input.column;
|
|
39
|
+
this.valueSet = input.valueSet;
|
|
40
|
+
freezeNode(this);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -2,16 +2,37 @@ import { asNamespaceId, type NamespaceId } from '@prisma-next/contract/types';
|
|
|
2
2
|
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
3
3
|
import { SqlNode } from './sql-node';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Input for a foreign-key reference (one side of a foreign-key declaration).
|
|
7
|
+
*
|
|
8
|
+
* When `spaceId` is absent the reference is local — the referenced table lives
|
|
9
|
+
* in the same contract-space. When `spaceId` is present the reference is
|
|
10
|
+
* cross-space — the referenced table lives in a different contract-space
|
|
11
|
+
* identified by `spaceId`.
|
|
12
|
+
*
|
|
13
|
+
* Presence-based discrimination keeps local FK JSON byte-identical to
|
|
14
|
+
* contracts authored before cross-space support was added.
|
|
15
|
+
*/
|
|
5
16
|
export interface ForeignKeyReferenceInput {
|
|
6
17
|
readonly namespaceId: string;
|
|
7
18
|
readonly tableName: string;
|
|
8
19
|
readonly columns: readonly string[];
|
|
20
|
+
readonly spaceId?: string;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* SQL Contract IR node for one side (source or target) of a foreign-key
|
|
13
25
|
* declaration. Carries the full coordinate: namespace, table, and columns.
|
|
14
26
|
*
|
|
27
|
+
* Cross-space discrimination is based on `spaceId` presence: absent means
|
|
28
|
+
* local (same contract-space); present means cross-space (the referenced
|
|
29
|
+
* table lives in the contract-space identified by `spaceId`).
|
|
30
|
+
*
|
|
31
|
+
* For local references `spaceId` is absent from JSON, keeping the serialized
|
|
32
|
+
* shape byte-identical to contracts authored before cross-space support was
|
|
33
|
+
* added. For cross-space references `spaceId` appears in JSON so round-trips
|
|
34
|
+
* are lossless.
|
|
35
|
+
*
|
|
15
36
|
* Use `UNBOUND_NAMESPACE_ID` from `@prisma-next/framework-components/ir`
|
|
16
37
|
* as the sentinel `namespaceId` for single-namespace (unbound) references.
|
|
17
38
|
*/
|
|
@@ -19,12 +40,14 @@ export class ForeignKeyReference extends SqlNode {
|
|
|
19
40
|
readonly namespaceId: NamespaceId;
|
|
20
41
|
readonly tableName: string;
|
|
21
42
|
readonly columns: readonly string[];
|
|
43
|
+
declare readonly spaceId?: string;
|
|
22
44
|
|
|
23
45
|
constructor(input: ForeignKeyReferenceInput) {
|
|
24
46
|
super();
|
|
25
47
|
this.namespaceId = asNamespaceId(input.namespaceId);
|
|
26
48
|
this.tableName = input.tableName;
|
|
27
49
|
this.columns = input.columns;
|
|
50
|
+
if (input.spaceId !== undefined) this.spaceId = input.spaceId;
|
|
28
51
|
freezeNode(this);
|
|
29
52
|
}
|
|
30
53
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
1
2
|
import type { StorageType } from '@prisma-next/framework-components/ir';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -42,6 +43,7 @@ export interface PostgresEnumStorageEntry extends StorageType {
|
|
|
42
43
|
* present on raw JSON envelopes).
|
|
43
44
|
*/
|
|
44
45
|
readonly codecId: string;
|
|
46
|
+
readonly control?: ControlPolicy;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
package/src/ir/sql-storage.ts
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
import type { StorageHashBase } from '@prisma-next/contract/types';
|
|
2
2
|
import { freezeNode, type Namespace, type Storage } from '@prisma-next/framework-components/ir';
|
|
3
|
-
import {
|
|
4
|
-
isPostgresEnumStorageEntry,
|
|
5
|
-
type PostgresEnumStorageEntry,
|
|
6
|
-
} from './postgres-enum-storage-entry';
|
|
7
3
|
import { SqlNode } from './sql-node';
|
|
8
4
|
import type { StorageTable, StorageTableInput } from './storage-table';
|
|
9
5
|
import {
|
|
10
6
|
isStorageTypeInstance,
|
|
11
7
|
type StorageTypeInstance,
|
|
12
8
|
type StorageTypeInstanceInput,
|
|
9
|
+
toStorageTypeInstance,
|
|
13
10
|
} from './storage-type-instance';
|
|
11
|
+
import type { StorageValueSet, StorageValueSetInput } from './storage-value-set';
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* Polymorphic value type for document-scoped `SqlStorage.types` entries
|
|
17
|
-
* (codec aliases / parameterised native type registrations).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* (codec aliases / parameterised native type registrations).
|
|
16
|
+
*
|
|
17
|
+
* Postgres native enum registrations live under the postgres-specific
|
|
18
|
+
* `entries.type` slot on `PostgresSchema` (target layer), not here.
|
|
20
19
|
*/
|
|
21
|
-
export type SqlStorageTypeEntry =
|
|
22
|
-
| StorageTypeInstance
|
|
23
|
-
| StorageTypeInstanceInput
|
|
24
|
-
| PostgresEnumStorageEntry;
|
|
20
|
+
export type SqlStorageTypeEntry = StorageTypeInstance | StorageTypeInstanceInput;
|
|
25
21
|
|
|
26
22
|
export interface SqlNamespaceTablesInput {
|
|
27
23
|
readonly id: string;
|
|
28
|
-
readonly
|
|
29
|
-
|
|
24
|
+
readonly entries: {
|
|
25
|
+
readonly table: Record<string, StorageTable | StorageTableInput>;
|
|
26
|
+
readonly valueSet?: Record<string, StorageValueSet | StorageValueSetInput>;
|
|
27
|
+
};
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export interface SqlStorageInput<THash extends string = string> {
|
|
@@ -51,30 +49,35 @@ export interface SqlStorageInput<THash extends string = string> {
|
|
|
51
49
|
*
|
|
52
50
|
* The constructor normalises optional `types` into class instances.
|
|
53
51
|
* `types` is polymorphic per Decision 18 Option B: codec-triple inputs
|
|
54
|
-
* are stamped with `kind: 'codec-instance'`;
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* responsibility (so the family base does not import target-specific
|
|
59
|
-
* subclasses).
|
|
52
|
+
* are stamped with `kind: 'codec-instance'`; hydration of raw JSON
|
|
53
|
+
* class-instance entries (carrying their narrower `kind` literal) is
|
|
54
|
+
* the per-target serializer's responsibility (so the family base does
|
|
55
|
+
* not import target-specific subclasses).
|
|
60
56
|
*/
|
|
61
|
-
// SQL concretions
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// runtime `kind` discriminator on `StorageTable`) structurally satisfy
|
|
68
|
-
// the slot without a class-instance check.
|
|
57
|
+
// SQL concretions store `StorageTable` values under `entries.table`.
|
|
58
|
+
// Mongo namespaces carry `entries.collection` instead. The wider
|
|
59
|
+
// `Record<string, object>` on `StorageTable` is only there so emitted
|
|
60
|
+
// `contract.d.ts` table literals (which lack the runtime `kind`
|
|
61
|
+
// discriminator on `StorageTable`) structurally satisfy the slot without
|
|
62
|
+
// a class-instance check.
|
|
69
63
|
export type SqlNamespace = Namespace & {
|
|
70
|
-
readonly
|
|
71
|
-
|
|
64
|
+
readonly entries: Readonly<{
|
|
65
|
+
readonly table: Readonly<Record<string, StorageTable>>;
|
|
66
|
+
readonly valueSet?: Readonly<Record<string, StorageValueSet>>;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Render a dialect-qualified table reference for runtime SQL emission.
|
|
70
|
+
* Present on materialised target concretions (`PostgresSchema`,
|
|
71
|
+
* `SqliteDatabase`, …) and family placeholders; omitted on emitted
|
|
72
|
+
* contract structural namespace literals (methods are not serialised).
|
|
73
|
+
*/
|
|
74
|
+
qualifyTable?(tableName: string): string;
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
export class SqlStorage<THash extends string = string> extends SqlNode implements Storage {
|
|
75
78
|
readonly storageHash: StorageHashBase<THash>;
|
|
76
79
|
readonly namespaces: Readonly<Record<string, SqlNamespace>>;
|
|
77
|
-
declare readonly types?: Readonly<Record<string, StorageTypeInstance
|
|
80
|
+
declare readonly types?: Readonly<Record<string, StorageTypeInstance>>;
|
|
78
81
|
|
|
79
82
|
constructor(input: SqlStorageInput<THash>) {
|
|
80
83
|
super();
|
|
@@ -91,11 +94,18 @@ export class SqlStorage<THash extends string = string> extends SqlNode implement
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
export function storageTableAt(
|
|
98
|
+
storage: SqlStorage,
|
|
99
|
+
namespaceId: string,
|
|
100
|
+
tableName: string,
|
|
101
|
+
): StorageTable | undefined {
|
|
102
|
+
return storage.namespaces[namespaceId]?.entries.table[tableName];
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
/**
|
|
95
106
|
* Strict polymorphic-slot dispatch for `SqlStorage.types` entries.
|
|
96
|
-
* Every entry must carry a
|
|
97
|
-
*
|
|
98
|
-
* `'postgres-enum'` (target-specific IR class). Untagged or
|
|
107
|
+
* Every entry must carry a `kind: 'codec-instance'` discriminator or
|
|
108
|
+
* be an already-constructed `StorageTypeInstance`. Untagged or
|
|
99
109
|
* unrecognised inputs throw a diagnostic naming the entry and its
|
|
100
110
|
* `kind`, so format drift surfaces loudly at the deserializer
|
|
101
111
|
* boundary instead of slipping past the seam and corrupting
|
|
@@ -108,24 +118,17 @@ export class SqlStorage<THash extends string = string> extends SqlNode implement
|
|
|
108
118
|
* arktype schema rejects untagged entries earlier, so this throw
|
|
109
119
|
* only fires for in-memory authoring bugs.
|
|
110
120
|
*/
|
|
111
|
-
function normaliseTypeEntry(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
// way IR class instances enter the slot.
|
|
120
|
-
if (entry instanceof SqlNode) {
|
|
121
|
+
function normaliseTypeEntry(name: string, entry: SqlStorageTypeEntry): StorageTypeInstance {
|
|
122
|
+
if (isStorageTypeInstance(entry)) {
|
|
123
|
+
// Normalise on-disk objects that omit `typeParams` (the canonical on-disk
|
|
124
|
+
// form strips empty typeParams to keep JSON compact). The in-memory invariant
|
|
125
|
+
// is always `typeParams: {}` when empty — never `undefined`. Only create a
|
|
126
|
+
// new object when necessary to preserve identity-equality for callers that
|
|
127
|
+
// hold a reference to an already-correct in-memory entry.
|
|
128
|
+
if ('typeParams' in entry) {
|
|
121
129
|
return entry;
|
|
122
130
|
}
|
|
123
|
-
|
|
124
|
-
`Encountered raw postgres-enum JSON in storage.types[${JSON.stringify(name)}] without serializer hydration; use a target ContractSerializer that registers the matching entity-type factory.`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
if (isStorageTypeInstance(entry)) {
|
|
128
|
-
return entry;
|
|
131
|
+
return toStorageTypeInstance(entry);
|
|
129
132
|
}
|
|
130
133
|
const rawKind = (entry as { kind?: unknown }).kind;
|
|
131
134
|
const kindDescription =
|
|
@@ -133,6 +136,6 @@ function normaliseTypeEntry(
|
|
|
133
136
|
? 'missing `kind` discriminator'
|
|
134
137
|
: `unrecognised \`kind\` discriminator ${JSON.stringify(rawKind)}`;
|
|
135
138
|
throw new Error(
|
|
136
|
-
`storage.types[${JSON.stringify(name)}] has ${kindDescription}; expected ${JSON.stringify('codec-instance')}
|
|
139
|
+
`storage.types[${JSON.stringify(name)}] has ${kindDescription}; expected ${JSON.stringify('codec-instance')}. Untagged codec triples should be wrapped with toStorageTypeInstance(...) before construction.`,
|
|
137
140
|
);
|
|
138
141
|
}
|
|
@@ -19,9 +19,10 @@ import type { StorageTable } from './storage-table';
|
|
|
19
19
|
* envelope and runtime walk are honest at every layer.
|
|
20
20
|
*
|
|
21
21
|
* The `kind` discriminator is installed as a non-enumerable own property
|
|
22
|
-
* so the JSON envelope reads `{ "id": "__unbound__" }`
|
|
23
|
-
* with the family-level non-enumerable `kind` on `SqlNode`
|
|
24
|
-
* to the minimum data the framework `Namespace` interface
|
|
22
|
+
* so the JSON envelope reads `{ "id": "__unbound__", "entries": { … } }`
|
|
23
|
+
* — symmetric with the family-level non-enumerable `kind` on `SqlNode`
|
|
24
|
+
* and bounded to the minimum data the framework `Namespace` interface
|
|
25
|
+
* promises.
|
|
25
26
|
*
|
|
26
27
|
* **Freeze-trap warning.** The leaf constructor calls
|
|
27
28
|
* `freezeNode(this)` after installing `kind`. The leaf-class shape
|
|
@@ -39,7 +40,9 @@ export class SqlUnboundNamespace extends NamespaceBase {
|
|
|
39
40
|
static readonly instance: SqlUnboundNamespace = new SqlUnboundNamespace();
|
|
40
41
|
|
|
41
42
|
readonly id = UNBOUND_NAMESPACE_ID;
|
|
42
|
-
readonly
|
|
43
|
+
readonly entries: Readonly<{
|
|
44
|
+
readonly table: Readonly<Record<string, StorageTable>>;
|
|
45
|
+
}> = Object.freeze({ table: Object.freeze({}) });
|
|
43
46
|
declare readonly kind: string;
|
|
44
47
|
|
|
45
48
|
private constructor() {
|
|
@@ -52,4 +55,8 @@ export class SqlUnboundNamespace extends NamespaceBase {
|
|
|
52
55
|
});
|
|
53
56
|
freezeNode(this);
|
|
54
57
|
}
|
|
58
|
+
|
|
59
|
+
qualifyTable(tableName: string): string {
|
|
60
|
+
return `"${tableName}"`;
|
|
61
|
+
}
|
|
55
62
|
}
|
package/src/ir/storage-column.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColumnDefault } from '@prisma-next/contract/types';
|
|
1
|
+
import type { ColumnDefault, ControlPolicy, ValueSetRef } from '@prisma-next/contract/types';
|
|
2
2
|
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
3
3
|
import { SqlNode } from './sql-node';
|
|
4
4
|
|
|
@@ -19,6 +19,8 @@ export interface StorageColumnInput {
|
|
|
19
19
|
readonly typeParams?: Record<string, unknown>;
|
|
20
20
|
readonly typeRef?: string;
|
|
21
21
|
readonly default?: ColumnDefault;
|
|
22
|
+
readonly control?: ControlPolicy;
|
|
23
|
+
readonly valueSet?: ValueSetRef;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -41,6 +43,8 @@ export class StorageColumn extends SqlNode {
|
|
|
41
43
|
declare readonly typeParams?: Record<string, unknown>;
|
|
42
44
|
declare readonly typeRef?: string;
|
|
43
45
|
declare readonly default?: ColumnDefault;
|
|
46
|
+
declare readonly control?: ControlPolicy;
|
|
47
|
+
declare readonly valueSet?: ValueSetRef;
|
|
44
48
|
|
|
45
49
|
constructor(input: StorageColumnInput) {
|
|
46
50
|
super();
|
|
@@ -50,6 +54,8 @@ export class StorageColumn extends SqlNode {
|
|
|
50
54
|
if (input.typeParams !== undefined) this.typeParams = input.typeParams;
|
|
51
55
|
if (input.typeRef !== undefined) this.typeRef = input.typeRef;
|
|
52
56
|
if (input.default !== undefined) this.default = input.default;
|
|
57
|
+
if (input.control !== undefined) this.control = input.control;
|
|
58
|
+
if (input.valueSet !== undefined) this.valueSet = input.valueSet;
|
|
53
59
|
freezeNode(this);
|
|
54
60
|
}
|
|
55
61
|
}
|
package/src/ir/storage-table.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
1
2
|
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
3
|
+
import { CheckConstraint, type CheckConstraintInput } from './check-constraint';
|
|
2
4
|
import { ForeignKey, type ForeignKeyInput } from './foreign-key';
|
|
3
5
|
import { PrimaryKey, type PrimaryKeyInput } from './primary-key';
|
|
4
6
|
import { Index, type IndexInput } from './sql-index';
|
|
@@ -12,6 +14,8 @@ export interface StorageTableInput {
|
|
|
12
14
|
readonly uniques: ReadonlyArray<UniqueConstraint | UniqueConstraintInput>;
|
|
13
15
|
readonly indexes: ReadonlyArray<Index | IndexInput>;
|
|
14
16
|
readonly foreignKeys: ReadonlyArray<ForeignKey | ForeignKeyInput>;
|
|
17
|
+
readonly control?: ControlPolicy;
|
|
18
|
+
readonly checks?: ReadonlyArray<CheckConstraint | CheckConstraintInput>;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -32,6 +36,8 @@ export class StorageTable extends SqlNode {
|
|
|
32
36
|
readonly indexes: ReadonlyArray<Index>;
|
|
33
37
|
readonly foreignKeys: ReadonlyArray<ForeignKey>;
|
|
34
38
|
declare readonly primaryKey?: PrimaryKey;
|
|
39
|
+
declare readonly control?: ControlPolicy;
|
|
40
|
+
declare readonly checks?: ReadonlyArray<CheckConstraint>;
|
|
35
41
|
|
|
36
42
|
constructor(input: StorageTableInput) {
|
|
37
43
|
super();
|
|
@@ -56,6 +62,10 @@ export class StorageTable extends SqlNode {
|
|
|
56
62
|
this.foreignKeys = Object.freeze(
|
|
57
63
|
input.foreignKeys.map((fk) => (fk instanceof ForeignKey ? fk : new ForeignKey(fk))),
|
|
58
64
|
);
|
|
65
|
+
if (input.control !== undefined) this.control = input.control;
|
|
66
|
+
if (input.checks !== undefined && input.checks.length > 0) {
|
|
67
|
+
this.checks = Object.freeze(input.checks.map((cc) => new CheckConstraint(cc)));
|
|
68
|
+
}
|
|
59
69
|
freezeNode(this);
|
|
60
70
|
}
|
|
61
71
|
}
|
|
@@ -28,24 +28,26 @@ export interface StorageTypeInstance extends StorageType {
|
|
|
28
28
|
* Construction-time input for a codec-triple entry. Symmetric with the
|
|
29
29
|
* structural runtime shape minus the `kind` discriminator — callers may
|
|
30
30
|
* omit `kind`; the helper {@link toStorageTypeInstance} stamps it on.
|
|
31
|
+
* `typeParams` may be omitted on input; the constructor normalises a
|
|
32
|
+
* missing value to `{}` so the in-memory shape is always present.
|
|
31
33
|
*/
|
|
32
34
|
export interface StorageTypeInstanceInput {
|
|
33
35
|
readonly codecId: string;
|
|
34
36
|
readonly nativeType: string;
|
|
35
|
-
readonly typeParams
|
|
37
|
+
readonly typeParams?: Record<string, unknown>;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Stamp the codec-instance `kind` discriminator on a caller-supplied
|
|
40
42
|
* codec triple. Idempotent: input that already carries the discriminator
|
|
41
|
-
* passes through unchanged.
|
|
43
|
+
* passes through unchanged. Missing `typeParams` is normalised to `{}`.
|
|
42
44
|
*/
|
|
43
45
|
export function toStorageTypeInstance(input: StorageTypeInstanceInput): StorageTypeInstance {
|
|
44
46
|
return {
|
|
45
47
|
kind: CODEC_INSTANCE_KIND,
|
|
46
48
|
codecId: input.codecId,
|
|
47
49
|
nativeType: input.nativeType,
|
|
48
|
-
typeParams: input.typeParams,
|
|
50
|
+
typeParams: input.typeParams ?? {},
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
2
|
+
import { SqlNode } from './sql-node';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hydration / construction input shape for {@link StorageValueSet}.
|
|
6
|
+
* Mirrors the on-disk storage JSON envelope so the serializer hydration
|
|
7
|
+
* walker can hand a validated literal straight to `new`.
|
|
8
|
+
*/
|
|
9
|
+
export interface StorageValueSetInput {
|
|
10
|
+
readonly kind: 'value-set';
|
|
11
|
+
/** Ordered permitted values, codec-encoded. Declaration order is preserved. */
|
|
12
|
+
readonly values: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* SQL Contract IR node for a value-set entry in a namespace's `valueSet`
|
|
17
|
+
* map (`SqlNamespace.entries.valueSet`).
|
|
18
|
+
*
|
|
19
|
+
* A value-set records the ordered set of permitted codec-encoded values for
|
|
20
|
+
* an enum-like column restriction. It does not carry a `codecId` — the
|
|
21
|
+
* column that references it already holds the codec; the value-set holds
|
|
22
|
+
* only the permitted values.
|
|
23
|
+
*
|
|
24
|
+
* The node's `kind` is enumerable (`'value-set'`) so the JSON envelope
|
|
25
|
+
* carries the discriminator and the serializer hydration walker can
|
|
26
|
+
* dispatch on it. This follows the per-leaf enumerable-kind convention
|
|
27
|
+
* established in the SQL-node comment (future polymorphic dispatch on
|
|
28
|
+
* namespace entries needs the discriminator in JSON).
|
|
29
|
+
*
|
|
30
|
+
* The entry's name is not on the class — value-sets are keyed by name in
|
|
31
|
+
* the parent namespace's `valueSet: Record<string, StorageValueSet>` map.
|
|
32
|
+
*/
|
|
33
|
+
export class StorageValueSet extends SqlNode {
|
|
34
|
+
override readonly kind = 'value-set' as const;
|
|
35
|
+
readonly values: readonly string[];
|
|
36
|
+
|
|
37
|
+
constructor(input: StorageValueSetInput) {
|
|
38
|
+
super();
|
|
39
|
+
this.values = Object.freeze([...input.values]);
|
|
40
|
+
freezeNode(this);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReferentialAction } from './ir/foreign-key';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps each `ReferentialAction` value to the SQL keyword used in ON DELETE /
|
|
5
|
+
* ON UPDATE clauses. Shared across the migration planner and adapter DDL
|
|
6
|
+
* renderers — single source of truth for the action → SQL mapping.
|
|
7
|
+
*/
|
|
8
|
+
export const REFERENTIAL_ACTION_SQL: Record<ReferentialAction, string> = {
|
|
9
|
+
noAction: 'NO ACTION',
|
|
10
|
+
restrict: 'RESTRICT',
|
|
11
|
+
cascade: 'CASCADE',
|
|
12
|
+
setNull: 'SET NULL',
|
|
13
|
+
setDefault: 'SET DEFAULT',
|
|
14
|
+
};
|