@prisma-next/family-sql 0.1.0-dev.3 → 0.1.0-dev.30
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/README.md +42 -0
- package/dist/exports/chunk-6P44BVZ4.js +580 -0
- package/dist/exports/chunk-6P44BVZ4.js.map +1 -0
- package/dist/exports/{chunk-3HYKCN35.js → chunk-C3GKWCKA.js} +16 -9
- package/dist/exports/chunk-C3GKWCKA.js.map +1 -0
- package/dist/exports/chunk-F252JMEU.js +772 -0
- package/dist/exports/chunk-F252JMEU.js.map +1 -0
- package/dist/exports/control-adapter.d.ts +1 -1
- package/dist/exports/control.d.ts +47 -117
- package/dist/exports/control.js +128 -1355
- package/dist/exports/control.js.map +1 -1
- package/dist/exports/instance-DiZi2k_2.d.ts +127 -0
- package/dist/exports/runtime.d.ts +16 -10
- package/dist/exports/runtime.js +17 -24
- package/dist/exports/runtime.js.map +1 -1
- package/dist/exports/schema-verify.d.ts +75 -0
- package/dist/exports/schema-verify.js +11 -0
- package/dist/exports/schema-verify.js.map +1 -0
- package/dist/exports/test-utils.d.ts +33 -0
- package/dist/exports/test-utils.js +17 -0
- package/dist/exports/test-utils.js.map +1 -0
- package/dist/exports/types-Bh7ftf0Q.d.ts +275 -0
- package/dist/exports/verify.d.ts +1 -1
- package/dist/exports/verify.js +1 -1
- package/package.json +31 -22
- package/dist/exports/chunk-3HYKCN35.js.map +0 -1
package/README.md
CHANGED
|
@@ -12,8 +12,10 @@ Provides the SQL family descriptor (`ControlFamilyDescriptor`) that includes:
|
|
|
12
12
|
|
|
13
13
|
- **Family Descriptor Export**: Exports the SQL `ControlFamilyDescriptor` for use in CLI configuration files
|
|
14
14
|
- **Family Instance Creation**: Creates `SqlFamilyInstance` objects that implement control-plane domain actions (`verify`, `schemaVerify`, `introspect`, `emitContract`, `validateContractIR`)
|
|
15
|
+
- **Planner & Runner SPI**: Owns the `MigrationPlanner` / `MigrationRunner` interfaces plus the `SqlControlTargetDescriptor` helper so targets can expose planners and runners (e.g., Postgres init planner/runner)
|
|
15
16
|
- **Family Hook Integration**: Integrates the SQL target family hook (`sqlTargetFamilyHook`) from `@prisma-next/sql-contract-emitter`
|
|
16
17
|
- **Control Plane Entry Point**: Serves as the control plane entry point for the SQL family, enabling the CLI to select the family hook and create family instances
|
|
18
|
+
- **Component Database Dependencies**: Consumes database dependencies declared by framework components (target/adapter/extensions). Callers pass the active `frameworkComponents` list into planning/execution/verification; SQL layers structurally narrow to components that declare `databaseDependencies` and use their pure verification hooks (no fuzzy matching against `contract.extensionPacks`).
|
|
17
19
|
|
|
18
20
|
## Usage
|
|
19
21
|
|
|
@@ -39,6 +41,33 @@ const familyInstance = sql.create({
|
|
|
39
41
|
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
40
42
|
const verifyResult = await familyInstance.verify({ driver, contractIR, ... });
|
|
41
43
|
const emitResult = await familyInstance.emitContract({ contractIR: rawContract }); // Handles stripping mappings and validation internally
|
|
44
|
+
|
|
45
|
+
// Targets that implement SqlControlTargetDescriptor can build planners
|
|
46
|
+
const planner = postgresTargetDescriptor.createPlanner(familyInstance);
|
|
47
|
+
// frameworkComponents should include the active target, adapter, and any extension descriptors so
|
|
48
|
+
// planner/runner can resolve database dependencies declared by those components.
|
|
49
|
+
const planResult = planner.plan({
|
|
50
|
+
contract: sqlContract,
|
|
51
|
+
schema,
|
|
52
|
+
policy,
|
|
53
|
+
frameworkComponents: [postgresTargetDescriptor, postgresAdapterDescriptor, pgVectorExtensionDescriptor],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Targets also provide runners for executing plans
|
|
57
|
+
const runner = postgresTargetDescriptor.createRunner(familyInstance);
|
|
58
|
+
const executeResult = await runner.execute({
|
|
59
|
+
plan: planResult.plan,
|
|
60
|
+
driver,
|
|
61
|
+
destinationContract: sqlContract,
|
|
62
|
+
frameworkComponents: [postgresTargetDescriptor, postgresAdapterDescriptor, pgVectorExtensionDescriptor],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// executeResult is a Result<MigrationRunnerSuccessValue, MigrationRunnerFailure>
|
|
66
|
+
if (executeResult.ok) {
|
|
67
|
+
console.log(`Executed ${executeResult.value.operationsExecuted} operations`);
|
|
68
|
+
} else {
|
|
69
|
+
console.error(`Migration failed: ${executeResult.failure.code} - ${executeResult.failure.summary}`);
|
|
70
|
+
}
|
|
42
71
|
```
|
|
43
72
|
|
|
44
73
|
## Architecture
|
|
@@ -68,6 +97,19 @@ The descriptor is "pure data + factory" - it only provides the hook and factory
|
|
|
68
97
|
- **`src/core/assembly.ts`**: Assembly helpers for building operation registries and extracting type imports from descriptors. Test utilities import `convertOperationManifest` from the same package via relative path.
|
|
69
98
|
- **`src/core/verify.ts`**: Verification helpers (`readMarker`, `collectSupportedCodecTypeIds`)
|
|
70
99
|
- **`src/core/control-adapter.ts`**: SQL control adapter interface (`SqlControlAdapter`) for control-plane operations
|
|
100
|
+
- **`src/core/migrations/`**: Migration IR helpers plus planner and runner SPI types (`MigrationPlanner`, `MigrationRunner`, `SqlControlTargetDescriptor`). Runners return `MigrationRunnerResult` which is a union of success/failure.
|
|
101
|
+
|
|
102
|
+
### Migration Runner Error Codes
|
|
103
|
+
|
|
104
|
+
The runner returns structured errors with the following codes:
|
|
105
|
+
|
|
106
|
+
- **`DESTINATION_CONTRACT_MISMATCH`**: Plan destination hash doesn't match provided contract hash
|
|
107
|
+
- **`MARKER_ORIGIN_MISMATCH`**: Existing marker doesn't match plan's expected origin
|
|
108
|
+
- **`POLICY_VIOLATION`**: Operation class is not allowed by the plan's policy
|
|
109
|
+
- **`PRECHECK_FAILED`**: Operation precheck returned false
|
|
110
|
+
- **`POSTCHECK_FAILED`**: Operation postcheck returned false after execution
|
|
111
|
+
- **`SCHEMA_VERIFY_FAILED`**: Resulting schema doesn't satisfy the destination contract
|
|
112
|
+
- **`EXECUTION_FAILED`**: SQL execution error during operation execution
|
|
71
113
|
- **`src/exports/control.ts`**: Control plane entry point (exports `SqlFamilyDescriptor` instance)
|
|
72
114
|
- **`src/exports/runtime.ts`**: Runtime entry point (placeholder for future functionality)
|
|
73
115
|
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectSupportedCodecTypeIds,
|
|
3
|
+
readMarker
|
|
4
|
+
} from "./chunk-C3GKWCKA.js";
|
|
5
|
+
import {
|
|
6
|
+
verifySqlSchema
|
|
7
|
+
} from "./chunk-F252JMEU.js";
|
|
8
|
+
|
|
9
|
+
// src/core/assembly.ts
|
|
10
|
+
import { createOperationRegistry } from "@prisma-next/operations";
|
|
11
|
+
function assembleOperationRegistry(descriptors, convertOperationManifest2) {
|
|
12
|
+
const registry = createOperationRegistry();
|
|
13
|
+
for (const descriptor of descriptors) {
|
|
14
|
+
const operations = descriptor.operations ?? [];
|
|
15
|
+
for (const operationManifest of operations) {
|
|
16
|
+
const signature = convertOperationManifest2(operationManifest);
|
|
17
|
+
registry.register(signature);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return registry;
|
|
21
|
+
}
|
|
22
|
+
function extractCodecTypeImports(descriptors) {
|
|
23
|
+
const imports = [];
|
|
24
|
+
for (const descriptor of descriptors) {
|
|
25
|
+
const types = descriptor.types;
|
|
26
|
+
const codecTypes = types?.codecTypes;
|
|
27
|
+
if (codecTypes?.import) {
|
|
28
|
+
imports.push(codecTypes.import);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return imports;
|
|
32
|
+
}
|
|
33
|
+
function extractOperationTypeImports(descriptors) {
|
|
34
|
+
const imports = [];
|
|
35
|
+
for (const descriptor of descriptors) {
|
|
36
|
+
const types = descriptor.types;
|
|
37
|
+
const operationTypes = types?.operationTypes;
|
|
38
|
+
if (operationTypes?.import) {
|
|
39
|
+
imports.push(operationTypes.import);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return imports;
|
|
43
|
+
}
|
|
44
|
+
function extractExtensionIds(adapter, target, extensions) {
|
|
45
|
+
const ids = [];
|
|
46
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47
|
+
if (!seen.has(adapter.id)) {
|
|
48
|
+
ids.push(adapter.id);
|
|
49
|
+
seen.add(adapter.id);
|
|
50
|
+
}
|
|
51
|
+
if (!seen.has(target.id)) {
|
|
52
|
+
ids.push(target.id);
|
|
53
|
+
seen.add(target.id);
|
|
54
|
+
}
|
|
55
|
+
for (const ext of extensions) {
|
|
56
|
+
if (!seen.has(ext.id)) {
|
|
57
|
+
ids.push(ext.id);
|
|
58
|
+
seen.add(ext.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return ids;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/core/instance.ts
|
|
65
|
+
import { emit } from "@prisma-next/core-control-plane/emission";
|
|
66
|
+
import { sqlTargetFamilyHook } from "@prisma-next/sql-contract-emitter";
|
|
67
|
+
import { validateContract } from "@prisma-next/sql-contract-ts/contract";
|
|
68
|
+
import {
|
|
69
|
+
ensureSchemaStatement,
|
|
70
|
+
ensureTableStatement,
|
|
71
|
+
writeContractMarker
|
|
72
|
+
} from "@prisma-next/sql-runtime";
|
|
73
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
74
|
+
function convertOperationManifest(manifest) {
|
|
75
|
+
return {
|
|
76
|
+
forTypeId: manifest.for,
|
|
77
|
+
method: manifest.method,
|
|
78
|
+
args: manifest.args.map((arg) => {
|
|
79
|
+
if (arg.kind === "typeId") {
|
|
80
|
+
if (!arg.type) {
|
|
81
|
+
throw new Error("typeId arg must have type property");
|
|
82
|
+
}
|
|
83
|
+
return { kind: "typeId", type: arg.type };
|
|
84
|
+
}
|
|
85
|
+
if (arg.kind === "param") {
|
|
86
|
+
return { kind: "param" };
|
|
87
|
+
}
|
|
88
|
+
if (arg.kind === "literal") {
|
|
89
|
+
return { kind: "literal" };
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Invalid arg kind: ${arg.kind}`);
|
|
92
|
+
}),
|
|
93
|
+
returns: (() => {
|
|
94
|
+
if (manifest.returns.kind === "typeId") {
|
|
95
|
+
return { kind: "typeId", type: manifest.returns.type };
|
|
96
|
+
}
|
|
97
|
+
if (manifest.returns.kind === "builtin") {
|
|
98
|
+
return {
|
|
99
|
+
kind: "builtin",
|
|
100
|
+
type: manifest.returns.type
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`Invalid return kind: ${manifest.returns.kind}`);
|
|
104
|
+
})(),
|
|
105
|
+
lowering: {
|
|
106
|
+
targetFamily: "sql",
|
|
107
|
+
strategy: manifest.lowering.strategy,
|
|
108
|
+
template: manifest.lowering.template
|
|
109
|
+
},
|
|
110
|
+
...manifest.capabilities ? { capabilities: manifest.capabilities } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function extractCodecTypeIdsFromContract(contract) {
|
|
114
|
+
const typeIds = /* @__PURE__ */ new Set();
|
|
115
|
+
if (typeof contract === "object" && contract !== null && "storage" in contract && typeof contract.storage === "object" && contract.storage !== null && "tables" in contract.storage) {
|
|
116
|
+
const storage = contract.storage;
|
|
117
|
+
if (storage.tables && typeof storage.tables === "object") {
|
|
118
|
+
for (const table of Object.values(storage.tables)) {
|
|
119
|
+
if (typeof table === "object" && table !== null && "columns" in table && typeof table.columns === "object" && table.columns !== null) {
|
|
120
|
+
const columns = table.columns;
|
|
121
|
+
for (const column of Object.values(columns)) {
|
|
122
|
+
if (column && typeof column === "object" && "codecId" in column && typeof column.codecId === "string") {
|
|
123
|
+
typeIds.add(column.codecId);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Array.from(typeIds).sort();
|
|
131
|
+
}
|
|
132
|
+
function createVerifyResult(options) {
|
|
133
|
+
const contract = {
|
|
134
|
+
coreHash: options.contractCoreHash
|
|
135
|
+
};
|
|
136
|
+
if (options.contractProfileHash) {
|
|
137
|
+
contract.profileHash = options.contractProfileHash;
|
|
138
|
+
}
|
|
139
|
+
const target = {
|
|
140
|
+
expected: options.expectedTargetId
|
|
141
|
+
};
|
|
142
|
+
if (options.actualTargetId) {
|
|
143
|
+
target.actual = options.actualTargetId;
|
|
144
|
+
}
|
|
145
|
+
const meta = {
|
|
146
|
+
contractPath: options.contractPath
|
|
147
|
+
};
|
|
148
|
+
if (options.configPath) {
|
|
149
|
+
meta.configPath = options.configPath;
|
|
150
|
+
}
|
|
151
|
+
const result = {
|
|
152
|
+
ok: options.ok,
|
|
153
|
+
summary: options.summary,
|
|
154
|
+
contract,
|
|
155
|
+
target,
|
|
156
|
+
meta,
|
|
157
|
+
timings: {
|
|
158
|
+
total: options.totalTime
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
if (options.code) {
|
|
162
|
+
result.code = options.code;
|
|
163
|
+
}
|
|
164
|
+
if (options.marker) {
|
|
165
|
+
result.marker = {
|
|
166
|
+
coreHash: options.marker.coreHash,
|
|
167
|
+
profileHash: options.marker.profileHash
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (options.missingCodecs) {
|
|
171
|
+
result.missingCodecs = options.missingCodecs;
|
|
172
|
+
}
|
|
173
|
+
if (options.codecCoverageSkipped) {
|
|
174
|
+
result.codecCoverageSkipped = options.codecCoverageSkipped;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function buildSqlTypeMetadataRegistry(options) {
|
|
179
|
+
const { target, adapter, extensionPacks: extensions } = options;
|
|
180
|
+
const registry = /* @__PURE__ */ new Map();
|
|
181
|
+
const targetId = adapter.targetId;
|
|
182
|
+
const descriptors = [target, adapter, ...extensions];
|
|
183
|
+
for (const descriptor of descriptors) {
|
|
184
|
+
const types = descriptor.types;
|
|
185
|
+
const storageTypes = types?.storage;
|
|
186
|
+
if (!storageTypes) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
for (const storageType of storageTypes) {
|
|
190
|
+
if (storageType.familyId === "sql" && storageType.targetId === targetId) {
|
|
191
|
+
registry.set(storageType.typeId, {
|
|
192
|
+
typeId: storageType.typeId,
|
|
193
|
+
familyId: "sql",
|
|
194
|
+
targetId: storageType.targetId,
|
|
195
|
+
...storageType.nativeType !== void 0 ? { nativeType: storageType.nativeType } : {}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return registry;
|
|
201
|
+
}
|
|
202
|
+
function createSqlFamilyInstance(options) {
|
|
203
|
+
const { target, adapter, extensionPacks: extensions = [] } = options;
|
|
204
|
+
const descriptors = [target, adapter, ...extensions];
|
|
205
|
+
const operationRegistry = assembleOperationRegistry(descriptors, convertOperationManifest);
|
|
206
|
+
const codecTypeImports = extractCodecTypeImports(descriptors);
|
|
207
|
+
const operationTypeImports = extractOperationTypeImports(descriptors);
|
|
208
|
+
const extensionIds = extractExtensionIds(adapter, target, extensions);
|
|
209
|
+
const typeMetadataRegistry = buildSqlTypeMetadataRegistry({
|
|
210
|
+
target,
|
|
211
|
+
adapter,
|
|
212
|
+
extensionPacks: extensions
|
|
213
|
+
});
|
|
214
|
+
function stripMappings(contract) {
|
|
215
|
+
if (typeof contract === "object" && contract !== null && "mappings" in contract) {
|
|
216
|
+
const { mappings: _mappings, ...contractIR } = contract;
|
|
217
|
+
return contractIR;
|
|
218
|
+
}
|
|
219
|
+
return contract;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
familyId: "sql",
|
|
223
|
+
operationRegistry,
|
|
224
|
+
codecTypeImports,
|
|
225
|
+
operationTypeImports,
|
|
226
|
+
extensionIds,
|
|
227
|
+
typeMetadataRegistry,
|
|
228
|
+
validateContractIR(contractJson) {
|
|
229
|
+
const validated = validateContract(contractJson);
|
|
230
|
+
const { mappings: _mappings, ...contractIR } = validated;
|
|
231
|
+
return contractIR;
|
|
232
|
+
},
|
|
233
|
+
async verify(verifyOptions) {
|
|
234
|
+
const { driver, contractIR, expectedTargetId, contractPath, configPath } = verifyOptions;
|
|
235
|
+
const startTime = Date.now();
|
|
236
|
+
if (typeof contractIR !== "object" || contractIR === null || !("coreHash" in contractIR) || !("target" in contractIR) || typeof contractIR.coreHash !== "string" || typeof contractIR.target !== "string") {
|
|
237
|
+
throw new Error("Contract is missing required fields: coreHash or target");
|
|
238
|
+
}
|
|
239
|
+
const contractCoreHash = contractIR.coreHash;
|
|
240
|
+
const contractProfileHash = "profileHash" in contractIR && typeof contractIR.profileHash === "string" ? contractIR.profileHash : void 0;
|
|
241
|
+
const contractTarget = contractIR.target;
|
|
242
|
+
const marker = await readMarker(driver);
|
|
243
|
+
let missingCodecs;
|
|
244
|
+
let codecCoverageSkipped = false;
|
|
245
|
+
const supportedTypeIds = collectSupportedCodecTypeIds([
|
|
246
|
+
adapter,
|
|
247
|
+
target,
|
|
248
|
+
...extensions
|
|
249
|
+
]);
|
|
250
|
+
if (supportedTypeIds.length === 0) {
|
|
251
|
+
codecCoverageSkipped = true;
|
|
252
|
+
} else {
|
|
253
|
+
const supportedSet = new Set(supportedTypeIds);
|
|
254
|
+
const usedTypeIds = extractCodecTypeIdsFromContract(contractIR);
|
|
255
|
+
const missing = usedTypeIds.filter((id) => !supportedSet.has(id));
|
|
256
|
+
if (missing.length > 0) {
|
|
257
|
+
missingCodecs = missing;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!marker) {
|
|
261
|
+
const totalTime2 = Date.now() - startTime;
|
|
262
|
+
return createVerifyResult({
|
|
263
|
+
ok: false,
|
|
264
|
+
code: "PN-RTM-3001",
|
|
265
|
+
summary: "Marker missing",
|
|
266
|
+
contractCoreHash,
|
|
267
|
+
expectedTargetId,
|
|
268
|
+
contractPath,
|
|
269
|
+
totalTime: totalTime2,
|
|
270
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
271
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
272
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
273
|
+
...configPath ? { configPath } : {}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (contractTarget !== expectedTargetId) {
|
|
277
|
+
const totalTime2 = Date.now() - startTime;
|
|
278
|
+
return createVerifyResult({
|
|
279
|
+
ok: false,
|
|
280
|
+
code: "PN-RTM-3003",
|
|
281
|
+
summary: "Target mismatch",
|
|
282
|
+
contractCoreHash,
|
|
283
|
+
marker,
|
|
284
|
+
expectedTargetId,
|
|
285
|
+
actualTargetId: contractTarget,
|
|
286
|
+
contractPath,
|
|
287
|
+
totalTime: totalTime2,
|
|
288
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
289
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
290
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
291
|
+
...configPath ? { configPath } : {}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (marker.coreHash !== contractCoreHash) {
|
|
295
|
+
const totalTime2 = Date.now() - startTime;
|
|
296
|
+
return createVerifyResult({
|
|
297
|
+
ok: false,
|
|
298
|
+
code: "PN-RTM-3002",
|
|
299
|
+
summary: "Hash mismatch",
|
|
300
|
+
contractCoreHash,
|
|
301
|
+
marker,
|
|
302
|
+
expectedTargetId,
|
|
303
|
+
contractPath,
|
|
304
|
+
totalTime: totalTime2,
|
|
305
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
306
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
307
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
308
|
+
...configPath ? { configPath } : {}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (contractProfileHash && marker.profileHash !== contractProfileHash) {
|
|
312
|
+
const totalTime2 = Date.now() - startTime;
|
|
313
|
+
return createVerifyResult({
|
|
314
|
+
ok: false,
|
|
315
|
+
code: "PN-RTM-3002",
|
|
316
|
+
summary: "Hash mismatch",
|
|
317
|
+
contractCoreHash,
|
|
318
|
+
contractProfileHash,
|
|
319
|
+
marker,
|
|
320
|
+
expectedTargetId,
|
|
321
|
+
contractPath,
|
|
322
|
+
totalTime: totalTime2,
|
|
323
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
324
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
325
|
+
...configPath ? { configPath } : {}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const totalTime = Date.now() - startTime;
|
|
329
|
+
return createVerifyResult({
|
|
330
|
+
ok: true,
|
|
331
|
+
summary: "Database matches contract",
|
|
332
|
+
contractCoreHash,
|
|
333
|
+
marker,
|
|
334
|
+
expectedTargetId,
|
|
335
|
+
contractPath,
|
|
336
|
+
totalTime,
|
|
337
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
338
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
339
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
340
|
+
...configPath ? { configPath } : {}
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
async schemaVerify(options2) {
|
|
344
|
+
const { driver, contractIR, strict, context, frameworkComponents } = options2;
|
|
345
|
+
const contract = validateContract(contractIR);
|
|
346
|
+
const controlAdapter = adapter.create();
|
|
347
|
+
const schemaIR = await controlAdapter.introspect(driver, contractIR);
|
|
348
|
+
return verifySqlSchema({
|
|
349
|
+
contract,
|
|
350
|
+
schema: schemaIR,
|
|
351
|
+
strict,
|
|
352
|
+
...ifDefined("context", context),
|
|
353
|
+
typeMetadataRegistry,
|
|
354
|
+
frameworkComponents
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
async sign(options2) {
|
|
358
|
+
const { driver, contractIR, contractPath, configPath } = options2;
|
|
359
|
+
const startTime = Date.now();
|
|
360
|
+
const contract = validateContract(contractIR);
|
|
361
|
+
const contractCoreHash = contract.coreHash;
|
|
362
|
+
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : contractCoreHash;
|
|
363
|
+
const contractTarget = contract.target;
|
|
364
|
+
await driver.query(ensureSchemaStatement.sql, ensureSchemaStatement.params);
|
|
365
|
+
await driver.query(ensureTableStatement.sql, ensureTableStatement.params);
|
|
366
|
+
const existingMarker = await readMarker(driver);
|
|
367
|
+
let markerCreated = false;
|
|
368
|
+
let markerUpdated = false;
|
|
369
|
+
let previousHashes;
|
|
370
|
+
if (!existingMarker) {
|
|
371
|
+
const write = writeContractMarker({
|
|
372
|
+
coreHash: contractCoreHash,
|
|
373
|
+
profileHash: contractProfileHash,
|
|
374
|
+
contractJson: contractIR,
|
|
375
|
+
canonicalVersion: 1
|
|
376
|
+
});
|
|
377
|
+
await driver.query(write.insert.sql, write.insert.params);
|
|
378
|
+
markerCreated = true;
|
|
379
|
+
} else {
|
|
380
|
+
const existingCoreHash = existingMarker.coreHash;
|
|
381
|
+
const existingProfileHash = existingMarker.profileHash;
|
|
382
|
+
const coreHashMatches = existingCoreHash === contractCoreHash;
|
|
383
|
+
const profileHashMatches = existingProfileHash === contractProfileHash;
|
|
384
|
+
if (!coreHashMatches || !profileHashMatches) {
|
|
385
|
+
previousHashes = {
|
|
386
|
+
coreHash: existingCoreHash,
|
|
387
|
+
profileHash: existingProfileHash
|
|
388
|
+
};
|
|
389
|
+
const write = writeContractMarker({
|
|
390
|
+
coreHash: contractCoreHash,
|
|
391
|
+
profileHash: contractProfileHash,
|
|
392
|
+
contractJson: contractIR,
|
|
393
|
+
canonicalVersion: existingMarker.canonicalVersion ?? 1
|
|
394
|
+
});
|
|
395
|
+
await driver.query(write.update.sql, write.update.params);
|
|
396
|
+
markerUpdated = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
let summary;
|
|
400
|
+
if (markerCreated) {
|
|
401
|
+
summary = "Database signed (marker created)";
|
|
402
|
+
} else if (markerUpdated) {
|
|
403
|
+
summary = `Database signed (marker updated from ${previousHashes?.coreHash ?? "unknown"})`;
|
|
404
|
+
} else {
|
|
405
|
+
summary = "Database already signed with this contract";
|
|
406
|
+
}
|
|
407
|
+
const totalTime = Date.now() - startTime;
|
|
408
|
+
return {
|
|
409
|
+
ok: true,
|
|
410
|
+
summary,
|
|
411
|
+
contract: {
|
|
412
|
+
coreHash: contractCoreHash,
|
|
413
|
+
profileHash: contractProfileHash
|
|
414
|
+
},
|
|
415
|
+
target: {
|
|
416
|
+
expected: contractTarget,
|
|
417
|
+
actual: contractTarget
|
|
418
|
+
},
|
|
419
|
+
marker: {
|
|
420
|
+
created: markerCreated,
|
|
421
|
+
updated: markerUpdated,
|
|
422
|
+
...previousHashes ? { previous: previousHashes } : {}
|
|
423
|
+
},
|
|
424
|
+
meta: {
|
|
425
|
+
contractPath,
|
|
426
|
+
...configPath ? { configPath } : {}
|
|
427
|
+
},
|
|
428
|
+
timings: {
|
|
429
|
+
total: totalTime
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
async readMarker(options2) {
|
|
434
|
+
return readMarker(options2.driver);
|
|
435
|
+
},
|
|
436
|
+
async introspect(options2) {
|
|
437
|
+
const { driver, contractIR } = options2;
|
|
438
|
+
const controlAdapter = adapter.create();
|
|
439
|
+
return controlAdapter.introspect(driver, contractIR);
|
|
440
|
+
},
|
|
441
|
+
toSchemaView(schema) {
|
|
442
|
+
const rootLabel = "contract";
|
|
443
|
+
const tableNodes = Object.entries(schema.tables).map(
|
|
444
|
+
([tableName, table]) => {
|
|
445
|
+
const children = [];
|
|
446
|
+
const columnNodes = [];
|
|
447
|
+
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
448
|
+
const nullableText = column.nullable ? "(nullable)" : "(not nullable)";
|
|
449
|
+
const typeDisplay = column.nativeType;
|
|
450
|
+
const label = `${columnName}: ${typeDisplay} ${nullableText}`;
|
|
451
|
+
columnNodes.push({
|
|
452
|
+
kind: "field",
|
|
453
|
+
id: `column-${tableName}-${columnName}`,
|
|
454
|
+
label,
|
|
455
|
+
meta: {
|
|
456
|
+
nativeType: column.nativeType,
|
|
457
|
+
nullable: column.nullable
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
if (columnNodes.length > 0) {
|
|
462
|
+
children.push({
|
|
463
|
+
kind: "collection",
|
|
464
|
+
id: `columns-${tableName}`,
|
|
465
|
+
label: "columns",
|
|
466
|
+
children: columnNodes
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
if (table.primaryKey) {
|
|
470
|
+
const pkColumns = table.primaryKey.columns.join(", ");
|
|
471
|
+
children.push({
|
|
472
|
+
kind: "index",
|
|
473
|
+
id: `primary-key-${tableName}`,
|
|
474
|
+
label: `primary key: ${pkColumns}`,
|
|
475
|
+
meta: {
|
|
476
|
+
columns: table.primaryKey.columns,
|
|
477
|
+
...table.primaryKey.name ? { name: table.primaryKey.name } : {}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
for (const unique of table.uniques) {
|
|
482
|
+
const name = unique.name ?? `${tableName}_${unique.columns.join("_")}_unique`;
|
|
483
|
+
const label = `unique ${name}`;
|
|
484
|
+
children.push({
|
|
485
|
+
kind: "index",
|
|
486
|
+
id: `unique-${tableName}-${name}`,
|
|
487
|
+
label,
|
|
488
|
+
meta: {
|
|
489
|
+
columns: unique.columns,
|
|
490
|
+
unique: true
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
for (const index of table.indexes) {
|
|
495
|
+
const name = index.name ?? `${tableName}_${index.columns.join("_")}_idx`;
|
|
496
|
+
const label = index.unique ? `unique index ${name}` : `index ${name}`;
|
|
497
|
+
children.push({
|
|
498
|
+
kind: "index",
|
|
499
|
+
id: `index-${tableName}-${name}`,
|
|
500
|
+
label,
|
|
501
|
+
meta: {
|
|
502
|
+
columns: index.columns,
|
|
503
|
+
unique: index.unique
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const tableMeta = {};
|
|
508
|
+
if (table.primaryKey) {
|
|
509
|
+
tableMeta["primaryKey"] = table.primaryKey.columns;
|
|
510
|
+
if (table.primaryKey.name) {
|
|
511
|
+
tableMeta["primaryKeyName"] = table.primaryKey.name;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (table.foreignKeys.length > 0) {
|
|
515
|
+
tableMeta["foreignKeys"] = table.foreignKeys.map((fk) => ({
|
|
516
|
+
columns: fk.columns,
|
|
517
|
+
referencedTable: fk.referencedTable,
|
|
518
|
+
referencedColumns: fk.referencedColumns,
|
|
519
|
+
...fk.name ? { name: fk.name } : {}
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
const node = {
|
|
523
|
+
kind: "entity",
|
|
524
|
+
id: `table-${tableName}`,
|
|
525
|
+
label: `table ${tableName}`,
|
|
526
|
+
...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
|
|
527
|
+
...children.length > 0 ? { children } : {}
|
|
528
|
+
};
|
|
529
|
+
return node;
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
const extensionNodes = schema.extensions.map((extName) => ({
|
|
533
|
+
kind: "extension",
|
|
534
|
+
id: `extension-${extName}`,
|
|
535
|
+
label: `${extName} extension is enabled`
|
|
536
|
+
}));
|
|
537
|
+
const rootChildren = [...tableNodes, ...extensionNodes];
|
|
538
|
+
const rootNode = {
|
|
539
|
+
kind: "root",
|
|
540
|
+
id: "sql-schema",
|
|
541
|
+
label: rootLabel,
|
|
542
|
+
...rootChildren.length > 0 ? { children: rootChildren } : {}
|
|
543
|
+
};
|
|
544
|
+
return {
|
|
545
|
+
root: rootNode
|
|
546
|
+
};
|
|
547
|
+
},
|
|
548
|
+
async emitContract({ contractIR }) {
|
|
549
|
+
const contractWithoutMappings = stripMappings(contractIR);
|
|
550
|
+
const validatedIR = this.validateContractIR(contractWithoutMappings);
|
|
551
|
+
const result = await emit(
|
|
552
|
+
validatedIR,
|
|
553
|
+
{
|
|
554
|
+
outputDir: "",
|
|
555
|
+
operationRegistry,
|
|
556
|
+
codecTypeImports,
|
|
557
|
+
operationTypeImports,
|
|
558
|
+
extensionIds
|
|
559
|
+
},
|
|
560
|
+
sqlTargetFamilyHook
|
|
561
|
+
);
|
|
562
|
+
return {
|
|
563
|
+
contractJson: result.contractJson,
|
|
564
|
+
contractDts: result.contractDts,
|
|
565
|
+
coreHash: result.coreHash,
|
|
566
|
+
profileHash: result.profileHash
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export {
|
|
573
|
+
assembleOperationRegistry,
|
|
574
|
+
extractCodecTypeImports,
|
|
575
|
+
extractOperationTypeImports,
|
|
576
|
+
extractExtensionIds,
|
|
577
|
+
convertOperationManifest,
|
|
578
|
+
createSqlFamilyInstance
|
|
579
|
+
};
|
|
580
|
+
//# sourceMappingURL=chunk-6P44BVZ4.js.map
|