@prisma-next/family-sql 0.14.0 → 0.15.0-dev.10
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/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-BQgad8Zc.d.mts} +30 -8
- package/dist/control-adapter-BQgad8Zc.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +279 -34
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +414 -1005
- package/dist/control.mjs.map +1 -1
- package/dist/diff.d.mts +102 -0
- package/dist/diff.d.mts.map +1 -0
- package/dist/diff.mjs +14 -0
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +26 -9
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +2 -2
- package/dist/ir.mjs.map +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +119 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +366 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/schema-differ-DnoopSXm.d.mts +45 -0
- package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
- package/dist/schema-verify-3sOPsdn_.mjs +226 -0
- package/dist/schema-verify-3sOPsdn_.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-C75cfMSS.mjs} +46 -20
- package/dist/sql-contract-serializer-C75cfMSS.mjs.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-BPv_y7iS.d.mts} +36 -142
- package/dist/types-BPv_y7iS.d.mts.map +1 -0
- package/package.json +26 -26
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +11 -5
- package/src/core/control-instance.ts +212 -42
- package/src/core/control-target-descriptor.ts +98 -0
- package/src/core/diff/schema-verify.ts +324 -0
- package/src/core/diff/sql-schema-diff.ts +41 -0
- package/src/core/diff/verifier-disposition.ts +30 -0
- package/src/core/ir/sql-contract-serializer-base.ts +70 -56
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/ir/sql-schema-verifier-base.ts +5 -5
- package/src/core/migrations/contract-to-schema-ir.ts +200 -54
- package/src/core/migrations/control-policy.ts +66 -115
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/native-type-expander.ts +28 -0
- package/src/core/migrations/schema-differ.ts +41 -0
- package/src/core/migrations/types.ts +33 -37
- package/src/core/psl-contract-infer/name-transforms.ts +15 -0
- package/src/core/psl-contract-infer/printer-config.ts +14 -6
- package/src/core/psl-contract-infer/relation-inference.ts +30 -1
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +12 -2
- package/src/exports/diff.ts +25 -0
- package/src/exports/psl-infer.ts +40 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
- package/dist/schema-verify.d.mts +0 -39
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -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/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/core/schema-verify/control-verify-emit.ts +0 -46
- package/src/core/schema-verify/verifier-disposition.ts +0 -58
- package/src/core/schema-verify/verify-helpers.ts +0 -820
- package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
- package/src/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
|
@@ -11,10 +11,12 @@ import type {
|
|
|
11
11
|
ControlFamilyInstance,
|
|
12
12
|
ControlStack,
|
|
13
13
|
CoreSchemaView,
|
|
14
|
+
DiffSubjectGranularity,
|
|
14
15
|
MigrationPlanOperation,
|
|
15
16
|
OperationPreview,
|
|
16
17
|
OperationPreviewCapable,
|
|
17
18
|
PslContractInferCapable,
|
|
19
|
+
SchemaDiffIssue,
|
|
18
20
|
SchemaViewCapable,
|
|
19
21
|
SignDatabaseResult,
|
|
20
22
|
VerifyDatabaseResult,
|
|
@@ -28,6 +30,7 @@ import {
|
|
|
28
30
|
VERIFY_CODE_TARGET_MISMATCH,
|
|
29
31
|
} from '@prisma-next/framework-components/control';
|
|
30
32
|
import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
|
|
33
|
+
import { isPlainRecord } from '@prisma-next/framework-components/ir';
|
|
31
34
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
32
35
|
import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
|
|
33
36
|
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
@@ -39,17 +42,26 @@ import type {
|
|
|
39
42
|
SqlExecuteRequest,
|
|
40
43
|
} from '@prisma-next/sql-relational-core/ast';
|
|
41
44
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
42
|
-
import type {
|
|
45
|
+
import type { SqlSchemaIRNode, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
46
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
43
47
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
44
48
|
import type { SqlControlAdapter } from './control-adapter';
|
|
49
|
+
import type {
|
|
50
|
+
SqlControlTargetDescriptor,
|
|
51
|
+
SqlDescribedContractSpace,
|
|
52
|
+
} from './control-target-descriptor';
|
|
53
|
+
import {
|
|
54
|
+
classifyDiffEntityKind,
|
|
55
|
+
classifyDiffSubjectGranularity,
|
|
56
|
+
verifySqlSchemaByDiff,
|
|
57
|
+
} from './diff/schema-verify';
|
|
45
58
|
import { SqlContractSerializer } from './ir/sql-contract-serializer';
|
|
59
|
+
import type { SqlSchemaDiffFn } from './migrations/schema-differ';
|
|
46
60
|
import type {
|
|
47
61
|
SqlControlAdapterDescriptor,
|
|
48
62
|
SqlControlExtensionDescriptor,
|
|
49
63
|
} from './migrations/types';
|
|
50
64
|
import { sqlOperationsToPreview } from './operation-preview';
|
|
51
|
-
import { sqlSchemaIrToPslAst } from './psl-contract-infer/sql-schema-ir-to-psl-ast';
|
|
52
|
-
import { verifySqlSchema } from './schema-verify/verify-sql-schema';
|
|
53
65
|
import { collectSupportedCodecTypeIds } from './verify';
|
|
54
66
|
|
|
55
67
|
function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
|
|
@@ -186,9 +198,9 @@ interface SqlFamilyInstanceState {
|
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
export interface SqlControlFamilyInstance
|
|
189
|
-
extends ControlFamilyInstance<'sql',
|
|
190
|
-
SchemaViewCapable<
|
|
191
|
-
PslContractInferCapable<
|
|
201
|
+
extends ControlFamilyInstance<'sql', SqlSchemaIRNode>,
|
|
202
|
+
SchemaViewCapable<SqlSchemaIRNode>,
|
|
203
|
+
PslContractInferCapable<SqlSchemaIRNode>,
|
|
192
204
|
OperationPreviewCapable,
|
|
193
205
|
SqlFamilyInstanceState {
|
|
194
206
|
/**
|
|
@@ -210,22 +222,41 @@ export interface SqlControlFamilyInstance
|
|
|
210
222
|
}): Promise<VerifyDatabaseResult>;
|
|
211
223
|
|
|
212
224
|
/**
|
|
213
|
-
* Verify a contract against an already-introspected schema
|
|
225
|
+
* Verify a contract against an already-introspected schema.
|
|
214
226
|
*
|
|
215
227
|
* Callers that need to verify against the live database compose
|
|
216
228
|
* `introspect({ driver })` + `verifySchema({ contract, schema, ... })`.
|
|
217
|
-
* The aggregate verifier
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* as `extras`.
|
|
229
|
+
* The aggregate verifier hands in the full introspected schema and scopes
|
|
230
|
+
* the returned result to each member's contract space afterwards — so
|
|
231
|
+
* sibling-space tables never survive as `extras`.
|
|
221
232
|
*/
|
|
222
233
|
verifySchema(options: {
|
|
223
234
|
readonly contract: unknown;
|
|
224
|
-
readonly schema:
|
|
235
|
+
readonly schema: SqlSchemaIRNode;
|
|
225
236
|
readonly strict: boolean;
|
|
226
237
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
227
238
|
}): VerifyDatabaseSchemaResult;
|
|
228
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Classifies a diff issue's subject granularity on demand, resolved from
|
|
242
|
+
* its node's `nodeKind` via the target's classifier. Satisfies the
|
|
243
|
+
* {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
244
|
+
* capability that framework consumers spanning contract spaces (the
|
|
245
|
+
* migration aggregate's unclaimed-elements sweep) detect via
|
|
246
|
+
* `hasSchemaSubjectClassifier` and call instead of reading family/target
|
|
247
|
+
* node vocabulary. Nothing is stamped on the issue or the node.
|
|
248
|
+
*/
|
|
249
|
+
classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Classifies a diff issue's subject storage `entityKind` on demand,
|
|
253
|
+
* resolved from its node's `nodeKind` via the target's classifier —
|
|
254
|
+
* sibling of `classifySubjectGranularity` above, part of the same
|
|
255
|
+
* {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
256
|
+
* capability. Nothing is stamped on the issue or the node.
|
|
257
|
+
*/
|
|
258
|
+
classifyEntityKind(issue: SchemaDiffIssue): string | undefined;
|
|
259
|
+
|
|
229
260
|
sign(options: {
|
|
230
261
|
readonly driver: SqlControlDriverInstance<string>;
|
|
231
262
|
readonly contract: unknown;
|
|
@@ -236,9 +267,9 @@ export interface SqlControlFamilyInstance
|
|
|
236
267
|
introspect(options: {
|
|
237
268
|
readonly driver: SqlControlDriverInstance<string>;
|
|
238
269
|
readonly contract?: unknown;
|
|
239
|
-
}): Promise<
|
|
270
|
+
}): Promise<SqlSchemaIRNode>;
|
|
240
271
|
|
|
241
|
-
inferPslContract(schemaIR:
|
|
272
|
+
inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst;
|
|
242
273
|
|
|
243
274
|
lowerAst(
|
|
244
275
|
ast: AnyQueryAst | DdlNode,
|
|
@@ -289,13 +320,12 @@ export interface SqlControlFamilyInstance
|
|
|
289
320
|
readonly migrationName: string;
|
|
290
321
|
readonly migrationHash: string;
|
|
291
322
|
readonly operations: readonly unknown[];
|
|
323
|
+
readonly destinationContractJson?: unknown;
|
|
292
324
|
};
|
|
293
325
|
}): Promise<void>;
|
|
294
326
|
|
|
295
327
|
bootstrapControlTableQueries(): readonly DdlNode[];
|
|
296
328
|
|
|
297
|
-
bootstrapSignMarkerQueries(): readonly DdlNode[];
|
|
298
|
-
|
|
299
329
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
|
|
300
330
|
}
|
|
301
331
|
|
|
@@ -523,12 +553,66 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
523
553
|
|
|
524
554
|
const targetSerializer = (
|
|
525
555
|
target as unknown as {
|
|
526
|
-
contractSerializer?: {
|
|
556
|
+
contractSerializer?: {
|
|
557
|
+
deserializeContract(json: unknown): Contract<SqlStorage>;
|
|
558
|
+
serializeContract(contract: Contract<SqlStorage>): unknown;
|
|
559
|
+
};
|
|
527
560
|
}
|
|
528
561
|
).contractSerializer;
|
|
529
|
-
|
|
562
|
+
// Database→PSL inference is target logic (it owns the dialect type/default
|
|
563
|
+
// maps and walks its own schema tree), so it is read off the descriptor like
|
|
564
|
+
// `contractSerializer`. Absent for targets without `contract infer` (Mongo).
|
|
565
|
+
const targetInferPslContract = blindCast<
|
|
566
|
+
SqlControlTargetDescriptor<TTargetId, unknown>,
|
|
567
|
+
'reading the optional target-descriptor inferPslContract hook'
|
|
568
|
+
>(target).inferPslContract;
|
|
569
|
+
// The full-tree node diff the verify VERDICT derives from. Read lazily so
|
|
570
|
+
// construction-only stub descriptors (schema-view tests) keep working; the
|
|
571
|
+
// throw happens at verify time.
|
|
572
|
+
const diffSchema = blindCast<
|
|
573
|
+
{ readonly diffSchema?: SqlSchemaDiffFn },
|
|
574
|
+
'reading the target-descriptor diffSchema hook'
|
|
575
|
+
>(target).diffSchema;
|
|
576
|
+
// The target's nodeKind → granularity classifier, resolved on demand by the
|
|
577
|
+
// verdict and by the instance's `classifySubjectGranularity` method below —
|
|
578
|
+
// never stamped onto an issue. Read lazily for the same
|
|
579
|
+
// construction-only-stub reason as `diffSchema`; the throw happens at call
|
|
580
|
+
// time.
|
|
581
|
+
const targetGranularityOf = blindCast<
|
|
582
|
+
{ readonly classifySubjectGranularity?: (nodeKind: string) => DiffSubjectGranularity },
|
|
583
|
+
'reading the target-descriptor classifySubjectGranularity hook'
|
|
584
|
+
>(target).classifySubjectGranularity;
|
|
585
|
+
// The target's nodeKind → storage entityKind classifier — sibling of
|
|
586
|
+
// `targetGranularityOf` above, read the same lazy way for the same
|
|
587
|
+
// construction-only-stub reason.
|
|
588
|
+
const targetEntityKindOf = blindCast<
|
|
589
|
+
{ readonly classifyEntityKind?: (nodeKind: string) => string | undefined },
|
|
590
|
+
'reading the target-descriptor classifyEntityKind hook'
|
|
591
|
+
>(target).classifyEntityKind;
|
|
592
|
+
// `contract infer` needs each extension pack's already-assembled contract,
|
|
593
|
+
// carried as-is (no merging — that is the contract-spaces machinery's
|
|
594
|
+
// concern), paired with the `spaceId` its descriptor was registered under
|
|
595
|
+
// (neither the contract JSON nor `ContractSpace` self-declares it), so the
|
|
596
|
+
// target hook can omit elements those contracts describe and qualify a
|
|
597
|
+
// cross-space relation with the owning pack's space id.
|
|
598
|
+
const describedContracts: readonly SqlDescribedContractSpace[] = extensions.flatMap(
|
|
599
|
+
(extension) =>
|
|
600
|
+
extension.contractSpace
|
|
601
|
+
? [{ spaceId: extension.id, contract: extension.contractSpace.contractJson }]
|
|
602
|
+
: [],
|
|
603
|
+
);
|
|
604
|
+
const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
|
|
530
605
|
const serializer = targetSerializer ?? new SqlContractSerializer();
|
|
531
|
-
|
|
606
|
+
const json =
|
|
607
|
+
targetSerializer !== undefined && !isPlainRecord(contractOrJson)
|
|
608
|
+
? targetSerializer.serializeContract(
|
|
609
|
+
blindCast<
|
|
610
|
+
Contract<SqlStorage>,
|
|
611
|
+
'isPlainRecord returned false, so contractOrJson is a class instance, not raw JSON'
|
|
612
|
+
>(contractOrJson),
|
|
613
|
+
)
|
|
614
|
+
: contractOrJson;
|
|
615
|
+
return serializer.deserializeContract(json) as Contract<SqlStorage>;
|
|
532
616
|
};
|
|
533
617
|
|
|
534
618
|
return {
|
|
@@ -669,22 +753,67 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
669
753
|
|
|
670
754
|
verifySchema(options: {
|
|
671
755
|
readonly contract: unknown;
|
|
672
|
-
readonly schema:
|
|
756
|
+
readonly schema: SqlSchemaIRNode;
|
|
673
757
|
readonly strict: boolean;
|
|
674
758
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
675
759
|
}): VerifyDatabaseSchemaResult {
|
|
676
760
|
const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
|
|
677
|
-
|
|
678
|
-
|
|
761
|
+
if (!diffSchema) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (!targetGranularityOf) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
// THE VERDICT: the target's full-tree node diff, graded by the
|
|
772
|
+
// family's post-diff filters (strict gating + control-policy
|
|
773
|
+
// disposition), plus the codec verifyType hook findings. The result
|
|
774
|
+
// is issue-based — `ok` holds exactly when both issue lists are empty.
|
|
775
|
+
return verifySqlSchemaByDiff({
|
|
679
776
|
contract,
|
|
680
777
|
schema: options.schema,
|
|
681
778
|
strict: options.strict,
|
|
682
|
-
typeMetadataRegistry,
|
|
683
779
|
frameworkComponents: options.frameworkComponents,
|
|
684
|
-
|
|
685
|
-
|
|
780
|
+
diffSchema,
|
|
781
|
+
granularityOf: targetGranularityOf,
|
|
686
782
|
});
|
|
687
783
|
},
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Classifies a diff issue's subject granularity on demand, by resolving
|
|
787
|
+
* its node's `nodeKind` through the target's classifier — the
|
|
788
|
+
* {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
789
|
+
* capability. Framework consumers spanning contract spaces (the
|
|
790
|
+
* migration aggregate's unclaimed-elements sweep) detect and call this
|
|
791
|
+
* instead of reaching into the concrete schema-IR node, which they
|
|
792
|
+
* cannot read; nothing is stamped on the issue or the node.
|
|
793
|
+
*/
|
|
794
|
+
classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined {
|
|
795
|
+
if (!targetGranularityOf) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return classifyDiffSubjectGranularity(issue, targetGranularityOf);
|
|
801
|
+
},
|
|
802
|
+
/**
|
|
803
|
+
* Classifies a diff issue's subject storage `entityKind` on demand, by
|
|
804
|
+
* resolving its node's `nodeKind` through the target's classifier —
|
|
805
|
+
* the sibling of `classifySubjectGranularity` above, and part of the
|
|
806
|
+
* same {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
807
|
+
* capability.
|
|
808
|
+
*/
|
|
809
|
+
classifyEntityKind(issue: SchemaDiffIssue): string | undefined {
|
|
810
|
+
if (!targetEntityKindOf) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return classifyDiffEntityKind(issue, targetEntityKindOf);
|
|
816
|
+
},
|
|
688
817
|
async sign(options: {
|
|
689
818
|
readonly driver: SqlControlDriverInstance<string>;
|
|
690
819
|
readonly contract: unknown;
|
|
@@ -841,6 +970,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
841
970
|
readonly migrationName: string;
|
|
842
971
|
readonly migrationHash: string;
|
|
843
972
|
readonly operations: readonly unknown[];
|
|
973
|
+
readonly destinationContractJson?: unknown;
|
|
844
974
|
};
|
|
845
975
|
}): Promise<void> {
|
|
846
976
|
return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
|
|
@@ -848,12 +978,17 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
848
978
|
async introspect(options: {
|
|
849
979
|
readonly driver: SqlControlDriverInstance<string>;
|
|
850
980
|
readonly contract?: unknown;
|
|
851
|
-
}): Promise<
|
|
981
|
+
}): Promise<SqlSchemaIRNode> {
|
|
852
982
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
853
983
|
},
|
|
854
984
|
|
|
855
|
-
inferPslContract(schemaIR:
|
|
856
|
-
|
|
985
|
+
inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
|
|
986
|
+
if (!targetInferPslContract) {
|
|
987
|
+
throw new Error(
|
|
988
|
+
`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
return targetInferPslContract(schemaIR, describedContracts);
|
|
857
992
|
},
|
|
858
993
|
|
|
859
994
|
lowerAst(
|
|
@@ -867,17 +1002,52 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
867
1002
|
return getControlAdapter().bootstrapControlTableQueries();
|
|
868
1003
|
},
|
|
869
1004
|
|
|
870
|
-
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
871
|
-
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
872
|
-
},
|
|
873
|
-
|
|
874
1005
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
|
|
875
1006
|
return sqlOperationsToPreview(operations);
|
|
876
1007
|
},
|
|
877
1008
|
|
|
878
|
-
toSchemaView(schema:
|
|
879
|
-
|
|
880
|
-
|
|
1009
|
+
toSchemaView(schema: SqlSchemaIRNode): CoreSchemaView {
|
|
1010
|
+
// Walk the schema-IR tree's own structure (root → namespaces → tables)
|
|
1011
|
+
// into one flat list of table nodes. A root that exposes a `namespaces`
|
|
1012
|
+
// record (Postgres) contributes each namespace's tables; a flat root
|
|
1013
|
+
// (SQLite) is its own single namespace. The single-schema common case
|
|
1014
|
+
// renders the same table-level view as today — no synthetic namespace
|
|
1015
|
+
// level.
|
|
1016
|
+
const root = blindCast<
|
|
1017
|
+
{
|
|
1018
|
+
readonly namespaces?: Readonly<
|
|
1019
|
+
Record<string, { readonly tables: Record<string, SqlTableIR> }>
|
|
1020
|
+
>;
|
|
1021
|
+
readonly tables?: Record<string, SqlTableIR>;
|
|
1022
|
+
},
|
|
1023
|
+
'structural read of the schema-IR tree own namespaces/tables records'
|
|
1024
|
+
>(schema);
|
|
1025
|
+
// A multi-namespace root qualifies every table's display name with its
|
|
1026
|
+
// namespace key, so same-named tables in different namespaces (e.g.
|
|
1027
|
+
// public.thing and auth.thing) render distinct ids and labels. The
|
|
1028
|
+
// single-namespace case (and a flat SQLite root) keeps today's bare
|
|
1029
|
+
// names. Synthesized constraint/index fallback names keep the bare table
|
|
1030
|
+
// name — that is the name the database derives them from.
|
|
1031
|
+
const namespaceEntries: ReadonlyArray<[string | undefined, Record<string, SqlTableIR>]> =
|
|
1032
|
+
root.namespaces !== undefined
|
|
1033
|
+
? Object.entries(root.namespaces).map(
|
|
1034
|
+
([namespaceKey, namespace]): [string, Record<string, SqlTableIR>] => [
|
|
1035
|
+
namespaceKey,
|
|
1036
|
+
namespace.tables,
|
|
1037
|
+
],
|
|
1038
|
+
)
|
|
1039
|
+
: [[undefined, root.tables ?? {}]];
|
|
1040
|
+
const qualify = namespaceEntries.length > 1;
|
|
1041
|
+
const tableEntries: ReadonlyArray<[string, string, SqlTableIR]> = namespaceEntries.flatMap(
|
|
1042
|
+
([namespaceKey, tables]) =>
|
|
1043
|
+
Object.entries(tables).map(([tableName, table]): [string, string, SqlTableIR] => [
|
|
1044
|
+
qualify && namespaceKey !== undefined ? `${namespaceKey}.${tableName}` : tableName,
|
|
1045
|
+
tableName,
|
|
1046
|
+
table,
|
|
1047
|
+
]),
|
|
1048
|
+
);
|
|
1049
|
+
const tableNodes: readonly SchemaTreeNode[] = tableEntries.map(
|
|
1050
|
+
([displayName, tableName, table]: [string, string, SqlTableIR]) => {
|
|
881
1051
|
const children: SchemaTreeNode[] = [];
|
|
882
1052
|
|
|
883
1053
|
const columnNodes: SchemaTreeNode[] = [];
|
|
@@ -888,7 +1058,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
888
1058
|
columnNodes.push(
|
|
889
1059
|
new SchemaTreeNode({
|
|
890
1060
|
kind: 'field',
|
|
891
|
-
id: `column-${
|
|
1061
|
+
id: `column-${displayName}-${columnName}`,
|
|
892
1062
|
label,
|
|
893
1063
|
meta: {
|
|
894
1064
|
nativeType: column.nativeType,
|
|
@@ -903,7 +1073,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
903
1073
|
children.push(
|
|
904
1074
|
new SchemaTreeNode({
|
|
905
1075
|
kind: 'collection',
|
|
906
|
-
id: `columns-${
|
|
1076
|
+
id: `columns-${displayName}`,
|
|
907
1077
|
label: 'columns',
|
|
908
1078
|
children: columnNodes,
|
|
909
1079
|
}),
|
|
@@ -915,7 +1085,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
915
1085
|
children.push(
|
|
916
1086
|
new SchemaTreeNode({
|
|
917
1087
|
kind: 'index',
|
|
918
|
-
id: `primary-key-${
|
|
1088
|
+
id: `primary-key-${displayName}`,
|
|
919
1089
|
label: `primary key: ${pkColumns}`,
|
|
920
1090
|
meta: {
|
|
921
1091
|
columns: table.primaryKey.columns,
|
|
@@ -931,7 +1101,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
931
1101
|
children.push(
|
|
932
1102
|
new SchemaTreeNode({
|
|
933
1103
|
kind: 'index',
|
|
934
|
-
id: `unique-${
|
|
1104
|
+
id: `unique-${displayName}-${name}`,
|
|
935
1105
|
label,
|
|
936
1106
|
meta: {
|
|
937
1107
|
columns: unique.columns,
|
|
@@ -947,7 +1117,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
947
1117
|
children.push(
|
|
948
1118
|
new SchemaTreeNode({
|
|
949
1119
|
kind: 'index',
|
|
950
|
-
id: `index-${
|
|
1120
|
+
id: `index-${displayName}-${name}`,
|
|
951
1121
|
label,
|
|
952
1122
|
meta: {
|
|
953
1123
|
columns: index.columns,
|
|
@@ -975,8 +1145,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
975
1145
|
|
|
976
1146
|
return new SchemaTreeNode({
|
|
977
1147
|
kind: 'entity',
|
|
978
|
-
id: `table-${
|
|
979
|
-
label: `table ${
|
|
1148
|
+
id: `table-${displayName}`,
|
|
1149
|
+
label: `table ${displayName}`,
|
|
980
1150
|
...(Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {}),
|
|
981
1151
|
...(children.length > 0 ? { children } : {}),
|
|
982
1152
|
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type {
|
|
3
|
+
ContractSerializer,
|
|
4
|
+
DiffSubjectGranularity,
|
|
5
|
+
MigratableTargetDescriptor,
|
|
6
|
+
SchemaVerifier,
|
|
7
|
+
} from '@prisma-next/framework-components/control';
|
|
8
|
+
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
9
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
10
|
+
import type { SqlOperationDescriptors } from '@prisma-next/sql-operations';
|
|
11
|
+
import type { SqlSchemaIR, SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
|
|
12
|
+
import type { SqlControlAdapter } from './control-adapter';
|
|
13
|
+
import type { SqlControlFamilyInstance } from './control-instance';
|
|
14
|
+
import type { SqlSchemaDiffFn } from './migrations/schema-differ';
|
|
15
|
+
import type { SqlMigrationPlanner, SqlMigrationRunner } from './migrations/types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One stack extension pack's already-assembled contract, paired with the
|
|
19
|
+
* `spaceId` its extension descriptor was registered under. `contract infer`
|
|
20
|
+
* needs both: the contract to know which elements the pack describes and to
|
|
21
|
+
* resolve the domain model a cross-space foreign key targets, the `spaceId`
|
|
22
|
+
* to qualify the emitted relation type (`<spaceId>:<namespace>.<Model>`).
|
|
23
|
+
* Neither the contract JSON nor the framework's `ContractSpace` wrapper
|
|
24
|
+
* self-declares its owning space id — it is only known from the extension
|
|
25
|
+
* descriptor that carries the `ContractSpace`.
|
|
26
|
+
*/
|
|
27
|
+
export interface SqlDescribedContractSpace {
|
|
28
|
+
readonly spaceId: string;
|
|
29
|
+
readonly contract: Contract<SqlStorage>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SqlControlTargetDescriptor<
|
|
33
|
+
TTargetId extends string,
|
|
34
|
+
TTargetDetails,
|
|
35
|
+
TContract extends Contract<SqlStorage> = Contract<SqlStorage>,
|
|
36
|
+
> extends MigratableTargetDescriptor<'sql', TTargetId, SqlControlFamilyInstance> {
|
|
37
|
+
readonly queryOperations?: () => SqlOperationDescriptors;
|
|
38
|
+
/**
|
|
39
|
+
* JSON ⇄ class boundary for the SQL target's contract. The descriptor
|
|
40
|
+
* composes a concrete `SqlContractSerializerBase` subclass; the rest
|
|
41
|
+
* of the control stack reaches `descriptor.contractSerializer` rather
|
|
42
|
+
* than importing a per-target deserialization function.
|
|
43
|
+
*/
|
|
44
|
+
readonly contractSerializer: ContractSerializer<TContract>;
|
|
45
|
+
/**
|
|
46
|
+
* Per-target schema verifier walking the contract against
|
|
47
|
+
* `SqlSchemaIR`. The descriptor composes a concrete
|
|
48
|
+
* `SqlSchemaVerifierBase` subclass; the family-shared walk lives on
|
|
49
|
+
* the base, the target-specific dispatch on the subclass.
|
|
50
|
+
*/
|
|
51
|
+
readonly schemaVerifier: SchemaVerifier<TContract, SqlSchemaIR>;
|
|
52
|
+
/**
|
|
53
|
+
* Database→PSL inference for `contract infer`. Target logic (owns the dialect
|
|
54
|
+
* maps), so it lives on the descriptor. Optional: targets without `contract
|
|
55
|
+
* infer` (Mongo) omit it, and the family instance throws when it is absent.
|
|
56
|
+
* `describedContracts` carries the stack's extension packs' already-assembled
|
|
57
|
+
* contracts (each paired with its `spaceId`) so the inferrer can omit elements
|
|
58
|
+
* they already describe, and can qualify a cross-space relation with the
|
|
59
|
+
* owning pack's space id.
|
|
60
|
+
*/
|
|
61
|
+
readonly inferPslContract?: (
|
|
62
|
+
schema: SqlSchemaIRNode,
|
|
63
|
+
describedContracts?: readonly SqlDescribedContractSpace[],
|
|
64
|
+
) => PslDocumentAst;
|
|
65
|
+
/**
|
|
66
|
+
* The full-tree node diff the family verify verdict derives from —
|
|
67
|
+
* expected-tree derivation, pre-diff normalization, the generic differ,
|
|
68
|
+
* and ownership scoping, all target-side. The family applies strict
|
|
69
|
+
* gating + control-policy disposition over the returned issues; verify
|
|
70
|
+
* rejects when a surviving issue is a failure.
|
|
71
|
+
*/
|
|
72
|
+
readonly diffSchema: SqlSchemaDiffFn;
|
|
73
|
+
/**
|
|
74
|
+
* Classifies a diff-tree node's `nodeKind` into its framework-neutral
|
|
75
|
+
* {@link DiffSubjectGranularity} — the target owns the full node vocabulary
|
|
76
|
+
* that appears in its diff tree (its own kinds plus the relational kinds it
|
|
77
|
+
* delegates to), so it is the one place that can resolve this. The family
|
|
78
|
+
* verdict calls it inline per issue (never stamping the result); the
|
|
79
|
+
* framework aggregate's unclaimed-elements sweep reaches the same
|
|
80
|
+
* classifier via the family instance's `classifySubjectGranularity`
|
|
81
|
+
* capability.
|
|
82
|
+
*/
|
|
83
|
+
readonly classifySubjectGranularity: (nodeKind: string) => DiffSubjectGranularity;
|
|
84
|
+
/**
|
|
85
|
+
* Classifies a diff-tree node's `nodeKind` into its storage `entityKind` —
|
|
86
|
+
* the same vocabulary the contract storage's `entries` dictionary keys use
|
|
87
|
+
* (e.g. `'table'`). Sibling of `classifySubjectGranularity`, resolved the
|
|
88
|
+
* same way: the target owns the full node vocabulary, so it is the one
|
|
89
|
+
* place that can resolve this. `undefined` for a node kind with no
|
|
90
|
+
* storage entity of its own (a column, an index, …). The framework
|
|
91
|
+
* aggregate's unclaimed-elements sweep reaches this via the family
|
|
92
|
+
* instance's `classifyEntityKind` capability, so it never hardcodes a
|
|
93
|
+
* family entity kind.
|
|
94
|
+
*/
|
|
95
|
+
readonly classifyEntityKind: (nodeKind: string) => string | undefined;
|
|
96
|
+
createPlanner(adapter: SqlControlAdapter<TTargetId>): SqlMigrationPlanner<TTargetDetails>;
|
|
97
|
+
createRunner(family: SqlControlFamilyInstance): SqlMigrationRunner<TTargetDetails>;
|
|
98
|
+
}
|