@prisma-next/sql-contract 0.14.0 → 0.15.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 +70 -50
- package/dist/canonicalization-hooks.mjs.map +1 -1
- package/dist/contract-view.d.mts +61 -0
- package/dist/contract-view.d.mts.map +1 -0
- package/dist/contract-view.mjs +39 -0
- package/dist/contract-view.mjs.map +1 -0
- package/dist/entity-handle-lowering-hook.d.mts +91 -0
- package/dist/entity-handle-lowering-hook.d.mts.map +1 -0
- package/dist/entity-handle-lowering-hook.mjs +11 -0
- package/dist/entity-handle-lowering-hook.mjs.map +1 -0
- package/dist/entity-kinds-CVg6HzG2.mjs +130 -0
- package/dist/entity-kinds-CVg6HzG2.mjs.map +1 -0
- package/dist/entity-kinds.d.mts +2 -1
- package/dist/entity-kinds.d.mts.map +1 -1
- package/dist/entity-kinds.mjs +1 -1
- package/dist/factories.d.mts +7 -6
- package/dist/factories.d.mts.map +1 -1
- package/dist/factories.mjs +5 -11
- package/dist/factories.mjs.map +1 -1
- package/dist/{foreign-key-BATxB95l.d.mts → foreign-key-DuCoGJCf.d.mts} +12 -48
- package/dist/foreign-key-DuCoGJCf.d.mts.map +1 -0
- package/dist/foreign-key-materialization.d.mts +64 -0
- package/dist/foreign-key-materialization.d.mts.map +1 -0
- package/dist/foreign-key-materialization.mjs +67 -0
- package/dist/foreign-key-materialization.mjs.map +1 -0
- package/dist/index-type-validation.d.mts +1 -1
- package/dist/referential-action-sql.d.mts +1 -1
- package/dist/resolve-storage-table.d.mts +2 -2
- package/dist/sql-node-V214WXQD.d.mts +42 -0
- package/dist/sql-node-V214WXQD.d.mts.map +1 -0
- package/dist/{sql-storage-Dga0jwP2.d.mts → sql-storage-p-Wzt_MO.d.mts} +63 -20
- package/dist/sql-storage-p-Wzt_MO.d.mts.map +1 -0
- package/dist/{storage-value-set-WnYsIFM8.d.mts → storage-table-CE-Ns6wa.d.mts} +17 -90
- package/dist/storage-table-CE-Ns6wa.d.mts.map +1 -0
- package/dist/{entity-kinds-Cl36zL5j.mjs → storage-table-DBpYBmty.mjs} +26 -162
- package/dist/storage-table-DBpYBmty.mjs.map +1 -0
- package/dist/storage-value-set-3BKuha5q.mjs +38 -0
- package/dist/storage-value-set-3BKuha5q.mjs.map +1 -0
- package/dist/storage-value-set-D-jww77l.d.mts +41 -0
- package/dist/storage-value-set-D-jww77l.d.mts.map +1 -0
- package/dist/{types-B1N8w0I2.d.mts → types-3vOIrnvp.d.mts} +25 -55
- package/dist/types-3vOIrnvp.d.mts.map +1 -0
- package/dist/types.d.mts +14 -5
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +115 -3
- package/dist/types.mjs.map +1 -0
- package/dist/unique-constraint-C0De4sLU.d.mts +55 -0
- package/dist/unique-constraint-C0De4sLU.d.mts.map +1 -0
- package/dist/validators.d.mts +10 -7
- package/dist/validators.d.mts.map +1 -1
- package/dist/validators.mjs +111 -21
- package/dist/validators.mjs.map +1 -1
- package/dist/value-set-derivation-hook.d.mts +35 -0
- package/dist/value-set-derivation-hook.d.mts.map +1 -0
- package/dist/value-set-derivation-hook.mjs +22 -0
- package/dist/value-set-derivation-hook.mjs.map +1 -0
- package/package.json +15 -10
- package/src/canonicalization-hooks.ts +31 -3
- package/src/column-type-resolution.ts +26 -0
- package/src/contract-view.ts +98 -0
- package/src/entity-handle-lowering-hook.ts +104 -0
- package/src/exports/contract-view.ts +11 -0
- package/src/exports/entity-handle-lowering-hook.ts +10 -0
- package/src/exports/foreign-key-materialization.ts +8 -0
- package/src/exports/types.ts +17 -4
- package/src/exports/value-set-derivation-hook.ts +2 -0
- package/src/factories.ts +13 -7
- package/src/foreign-key-materialization.ts +107 -0
- package/src/ir/foreign-key.ts +9 -9
- package/src/ir/sql-storage.ts +81 -17
- package/src/ir/storage-column.ts +3 -0
- package/src/ir/storage-entry-schemas.ts +1 -2
- package/src/ir/storage-table.ts +21 -0
- package/src/ir/storage-value-set.ts +5 -0
- package/src/types.ts +51 -8
- package/src/validators.ts +215 -42
- package/src/value-set-derivation-hook.ts +47 -0
- package/dist/entity-kinds-Cl36zL5j.mjs.map +0 -1
- package/dist/foreign-key-BATxB95l.d.mts.map +0 -1
- package/dist/sql-storage-Dga0jwP2.d.mts.map +0 -1
- package/dist/storage-value-set-WnYsIFM8.d.mts.map +0 -1
- package/dist/types-B-eiQXff.mjs +0 -191
- package/dist/types-B-eiQXff.mjs.map +0 -1
- package/dist/types-B1N8w0I2.d.mts.map +0 -1
- package/src/ir/build-sql-namespace.ts +0 -110
- 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';
|
package/src/exports/types.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/src/factories.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { asNamespaceId, type ScalarFieldType } from '@prisma-next/contract/types';
|
|
2
2
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
3
3
|
import {
|
|
4
|
-
applyFkDefaults,
|
|
5
4
|
ForeignKey,
|
|
6
5
|
type ForeignKeyOptions,
|
|
7
6
|
Index,
|
|
@@ -14,8 +13,18 @@ import {
|
|
|
14
13
|
UniqueConstraint,
|
|
15
14
|
} from './types';
|
|
16
15
|
|
|
17
|
-
export function col(
|
|
18
|
-
|
|
16
|
+
export function col(
|
|
17
|
+
nativeType: string,
|
|
18
|
+
codecId: string,
|
|
19
|
+
nullable = false,
|
|
20
|
+
opts?: { readonly many?: boolean },
|
|
21
|
+
): StorageColumn {
|
|
22
|
+
return new StorageColumn({
|
|
23
|
+
nativeType,
|
|
24
|
+
codecId,
|
|
25
|
+
nullable,
|
|
26
|
+
...(opts?.many !== undefined && { many: opts.many }),
|
|
27
|
+
});
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export function pk(...columns: readonly string[]): PrimaryKey {
|
|
@@ -35,9 +44,8 @@ export function fk(
|
|
|
35
44
|
srcColumns: readonly string[],
|
|
36
45
|
targetTableName: string,
|
|
37
46
|
targetColumns: readonly string[],
|
|
38
|
-
opts?: ForeignKeyOptions & {
|
|
47
|
+
opts?: ForeignKeyOptions & { namespaceId?: string },
|
|
39
48
|
): ForeignKey {
|
|
40
|
-
const defaults = applyFkDefaults({ constraint: opts?.constraint, index: opts?.index });
|
|
41
49
|
const namespaceId = asNamespaceId(opts?.namespaceId ?? UNBOUND_NAMESPACE_ID);
|
|
42
50
|
return new ForeignKey({
|
|
43
51
|
source: { namespaceId, tableName: srcTableName, columns: srcColumns },
|
|
@@ -45,8 +53,6 @@ export function fk(
|
|
|
45
53
|
...(opts?.name !== undefined && { name: opts.name }),
|
|
46
54
|
...(opts?.onDelete !== undefined && { onDelete: opts.onDelete }),
|
|
47
55
|
...(opts?.onUpdate !== undefined && { onUpdate: opts.onUpdate }),
|
|
48
|
-
constraint: defaults.constraint,
|
|
49
|
-
index: defaults.index,
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
2
|
+
import type { ForeignKeyInput, ReferentialAction } from './ir/foreign-key';
|
|
3
|
+
import type { ForeignKeyReferenceInput } from './ir/foreign-key-reference';
|
|
4
|
+
import type { PrimaryKeyInput } from './ir/primary-key';
|
|
5
|
+
import type { IndexInput } from './ir/sql-index';
|
|
6
|
+
import type { UniqueConstraintInput } from './ir/unique-constraint';
|
|
7
|
+
|
|
8
|
+
export type BackingIndexCandidates = {
|
|
9
|
+
readonly indexes: readonly { readonly columns: readonly string[] }[];
|
|
10
|
+
readonly uniques: readonly { readonly columns: readonly string[] }[];
|
|
11
|
+
readonly primaryKey?: { readonly columns: readonly string[] } | undefined;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The column-list keys (`"colA,colB"`, order preserved) a table's own
|
|
16
|
+
* indexes, unique constraints, and primary key already back. A foreign key
|
|
17
|
+
* whose source columns join to one of these keys needs no separately
|
|
18
|
+
* derived backing index.
|
|
19
|
+
*
|
|
20
|
+
* Shared by {@link isBackedByColumnKeys}'s callers: `materializeForeignKeysAndIndexes`
|
|
21
|
+
* (deriving the discrete backing-index entities persisted at `contract emit`)
|
|
22
|
+
* and the postgres PSL inferrer (deciding whether an introspected relation
|
|
23
|
+
* needs an explicit `index: false`).
|
|
24
|
+
*/
|
|
25
|
+
export function backingIndexColumnKeys(table: BackingIndexCandidates): readonly string[] {
|
|
26
|
+
return [
|
|
27
|
+
...table.indexes.map((index) => index.columns.join(',')),
|
|
28
|
+
...table.uniques.map((unique) => unique.columns.join(',')),
|
|
29
|
+
...(table.primaryKey ? [table.primaryKey.columns.join(',')] : []),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether `columns`, in order, matches one of `backingKeys` (see
|
|
35
|
+
* {@link backingIndexColumnKeys}). Order-sensitive: `(a, b)` does not
|
|
36
|
+
* satisfy a backing index declared as `(b, a)`.
|
|
37
|
+
*/
|
|
38
|
+
export function isBackedByColumnKeys(
|
|
39
|
+
columns: readonly string[],
|
|
40
|
+
backingKeys: readonly string[],
|
|
41
|
+
): boolean {
|
|
42
|
+
return backingKeys.includes(columns.join(','));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A foreign key as authored, before FK1 materialization: the referential
|
|
47
|
+
* coordinates plus the `constraint`/`index` intent booleans that drive
|
|
48
|
+
* whether — and how — it survives into the persisted contract.
|
|
49
|
+
*/
|
|
50
|
+
export interface ForeignKeyAuthoringInput {
|
|
51
|
+
readonly source: ForeignKeyReferenceInput;
|
|
52
|
+
readonly target: ForeignKeyReferenceInput;
|
|
53
|
+
readonly name?: string;
|
|
54
|
+
readonly onDelete?: ReferentialAction;
|
|
55
|
+
readonly onUpdate?: ReferentialAction;
|
|
56
|
+
readonly constraint: boolean;
|
|
57
|
+
readonly index: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MaterializedTableConstraints {
|
|
61
|
+
readonly foreignKeys: readonly ForeignKeyInput[];
|
|
62
|
+
readonly indexes: readonly IndexInput[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Lowers a table's authored foreign keys into the discrete entities
|
|
67
|
+
* `contract.json` persists: a `constraint: false` FK contributes no
|
|
68
|
+
* `foreignKeys[]` entry, and an `index: true` FK whose columns aren't already
|
|
69
|
+
* backed by a declared index/unique/primary-key contributes a named
|
|
70
|
+
* `indexes[]` entry (`defaultIndexName`). Declared indexes always survive
|
|
71
|
+
* unchanged; a second FK sharing already-synthesized backing columns does not
|
|
72
|
+
* mint a duplicate index.
|
|
73
|
+
*/
|
|
74
|
+
export function materializeForeignKeysAndIndexes(
|
|
75
|
+
tableName: string,
|
|
76
|
+
foreignKeys: readonly ForeignKeyAuthoringInput[],
|
|
77
|
+
declaredIndexes: readonly IndexInput[],
|
|
78
|
+
uniques: readonly UniqueConstraintInput[],
|
|
79
|
+
primaryKey: PrimaryKeyInput | undefined,
|
|
80
|
+
): MaterializedTableConstraints {
|
|
81
|
+
const satisfiedIndexColumns = new Set(
|
|
82
|
+
backingIndexColumnKeys({ indexes: declaredIndexes, uniques, primaryKey }),
|
|
83
|
+
);
|
|
84
|
+
const synthesizedIndexes: IndexInput[] = [];
|
|
85
|
+
const materializedForeignKeys: ForeignKeyInput[] = [];
|
|
86
|
+
|
|
87
|
+
for (const { constraint, index, ...reference } of foreignKeys) {
|
|
88
|
+
if (constraint !== false) {
|
|
89
|
+
materializedForeignKeys.push(reference);
|
|
90
|
+
}
|
|
91
|
+
if (index !== false) {
|
|
92
|
+
const key = reference.source.columns.join(',');
|
|
93
|
+
if (!satisfiedIndexColumns.has(key)) {
|
|
94
|
+
synthesizedIndexes.push({
|
|
95
|
+
columns: reference.source.columns,
|
|
96
|
+
name: defaultIndexName(tableName, reference.source.columns),
|
|
97
|
+
});
|
|
98
|
+
satisfiedIndexColumns.add(key);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
foreignKeys: materializedForeignKeys,
|
|
105
|
+
indexes: [...declaredIndexes, ...synthesizedIndexes],
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/ir/foreign-key.ts
CHANGED
|
@@ -10,14 +10,18 @@ export interface ForeignKeyInput {
|
|
|
10
10
|
readonly name?: string;
|
|
11
11
|
readonly onDelete?: ReferentialAction;
|
|
12
12
|
readonly onUpdate?: ReferentialAction;
|
|
13
|
-
/** Whether to emit FK constraint DDL (ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY). */
|
|
14
|
-
readonly constraint: boolean;
|
|
15
|
-
/** Whether to emit a backing index for the FK columns. */
|
|
16
|
-
readonly index: boolean;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
|
-
* SQL Contract IR node for a table-level foreign-key declaration
|
|
16
|
+
* SQL Contract IR node for a table-level foreign-key declaration — the
|
|
17
|
+
* referential constraint only (source, target, `onDelete`/`onUpdate`).
|
|
18
|
+
*
|
|
19
|
+
* A persisted `foreignKeys[]` entry always denotes a real constraint: whether
|
|
20
|
+
* to emit the constraint at all, and whether to back it with an index, are
|
|
21
|
+
* authoring-time decisions (PSL `@relation(index:)`, TS `fk({ constraint,
|
|
22
|
+
* index })`) resolved once at `contract emit` — a `constraint: false` FK
|
|
23
|
+
* simply has no entry here, and a backing index (if any) is its own discrete,
|
|
24
|
+
* named entry in the table's `indexes[]`.
|
|
21
25
|
*
|
|
22
26
|
* Each FK carries explicit `source` and `target` {@link ForeignKeyReference}
|
|
23
27
|
* coordinates (namespace, table, columns). For single-namespace contracts the
|
|
@@ -31,8 +35,6 @@ export interface ForeignKeyInput {
|
|
|
31
35
|
export class ForeignKey extends SqlNode {
|
|
32
36
|
readonly source: ForeignKeyReference;
|
|
33
37
|
readonly target: ForeignKeyReference;
|
|
34
|
-
readonly constraint: boolean;
|
|
35
|
-
readonly index: boolean;
|
|
36
38
|
declare readonly name?: string;
|
|
37
39
|
declare readonly onDelete?: ReferentialAction;
|
|
38
40
|
declare readonly onUpdate?: ReferentialAction;
|
|
@@ -47,8 +49,6 @@ export class ForeignKey extends SqlNode {
|
|
|
47
49
|
input.target instanceof ForeignKeyReference
|
|
48
50
|
? input.target
|
|
49
51
|
: new ForeignKeyReference(input.target);
|
|
50
|
-
this.constraint = input.constraint;
|
|
51
|
-
this.index = input.index;
|
|
52
52
|
if (input.name !== undefined) this.name = input.name;
|
|
53
53
|
if (input.onDelete !== undefined) this.onDelete = input.onDelete;
|
|
54
54
|
if (input.onUpdate !== undefined) this.onUpdate = input.onUpdate;
|
package/src/ir/sql-storage.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { StorageHashBase } from '@prisma-next/contract/types';
|
|
2
|
-
import
|
|
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
|
|
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,
|
|
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
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
|
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
|
|
195
|
+
const rawKind = isPlainRecord(entry) ? entry['kind'] : undefined;
|
|
132
196
|
const kindDescription =
|
|
133
197
|
rawKind === undefined
|
|
134
198
|
? 'missing `kind` discriminator'
|
package/src/ir/storage-column.ts
CHANGED
|
@@ -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,
|
|
@@ -105,8 +106,6 @@ export const ForeignKeySchema = type.declare<ForeignKeyInput>().type({
|
|
|
105
106
|
'name?': 'string',
|
|
106
107
|
'onDelete?': ReferentialActionSchema,
|
|
107
108
|
'onUpdate?': ReferentialActionSchema,
|
|
108
|
-
constraint: 'boolean',
|
|
109
|
-
index: 'boolean',
|
|
110
109
|
});
|
|
111
110
|
|
|
112
111
|
export const CheckConstraintSchema = type({
|
package/src/ir/storage-table.ts
CHANGED
|
@@ -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
|
+
}
|