@prisma-next/family-sql 0.0.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/README.md +90 -0
- package/dist/exports/control-adapter.d.ts +44 -0
- package/dist/exports/control-adapter.js +1 -0
- package/dist/exports/control-adapter.js.map +1 -0
- package/dist/exports/control.d.ts +148 -0
- package/dist/exports/control.js +1439 -0
- package/dist/exports/control.js.map +1 -0
- package/dist/exports/index-Bi3Sr19r.d.ts +28 -0
- package/dist/exports/runtime.d.ts +296 -0
- package/dist/exports/runtime.js +71 -0
- package/dist/exports/runtime.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,1439 @@
|
|
|
1
|
+
// src/core/descriptor.ts
|
|
2
|
+
import { sqlTargetFamilyHook as sqlTargetFamilyHook2 } from "@prisma-next/sql-contract-emitter";
|
|
3
|
+
|
|
4
|
+
// src/core/instance.ts
|
|
5
|
+
import { emit } from "@prisma-next/core-control-plane/emission";
|
|
6
|
+
import { sqlTargetFamilyHook } from "@prisma-next/sql-contract-emitter";
|
|
7
|
+
import { validateContract } from "@prisma-next/sql-contract-ts/contract";
|
|
8
|
+
import {
|
|
9
|
+
ensureSchemaStatement,
|
|
10
|
+
ensureTableStatement,
|
|
11
|
+
writeContractMarker
|
|
12
|
+
} from "@prisma-next/sql-runtime";
|
|
13
|
+
|
|
14
|
+
// ../../framework/core-operations/src/index.ts
|
|
15
|
+
var OperationRegistryImpl = class {
|
|
16
|
+
operations = /* @__PURE__ */ new Map();
|
|
17
|
+
register(op) {
|
|
18
|
+
const existing = this.operations.get(op.forTypeId) ?? [];
|
|
19
|
+
const duplicate = existing.find((existingOp) => existingOp.method === op.method);
|
|
20
|
+
if (duplicate) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Operation method "${op.method}" already registered for typeId "${op.forTypeId}"`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
existing.push(op);
|
|
26
|
+
this.operations.set(op.forTypeId, existing);
|
|
27
|
+
}
|
|
28
|
+
byType(typeId) {
|
|
29
|
+
return this.operations.get(typeId) ?? [];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
function createOperationRegistry() {
|
|
33
|
+
return new OperationRegistryImpl();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/core/assembly.ts
|
|
37
|
+
function assembleOperationRegistry(descriptors, convertOperationManifest2) {
|
|
38
|
+
const registry = createOperationRegistry();
|
|
39
|
+
for (const descriptor of descriptors) {
|
|
40
|
+
const operations = descriptor.manifest.operations ?? [];
|
|
41
|
+
for (const operationManifest of operations) {
|
|
42
|
+
const signature = convertOperationManifest2(operationManifest);
|
|
43
|
+
registry.register(signature);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return registry;
|
|
47
|
+
}
|
|
48
|
+
function extractCodecTypeImports(descriptors) {
|
|
49
|
+
const imports = [];
|
|
50
|
+
for (const descriptor of descriptors) {
|
|
51
|
+
const codecTypes = descriptor.manifest.types?.codecTypes;
|
|
52
|
+
if (codecTypes?.import) {
|
|
53
|
+
imports.push(codecTypes.import);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return imports;
|
|
57
|
+
}
|
|
58
|
+
function extractOperationTypeImports(descriptors) {
|
|
59
|
+
const imports = [];
|
|
60
|
+
for (const descriptor of descriptors) {
|
|
61
|
+
const operationTypes = descriptor.manifest.types?.operationTypes;
|
|
62
|
+
if (operationTypes?.import) {
|
|
63
|
+
imports.push(operationTypes.import);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return imports;
|
|
67
|
+
}
|
|
68
|
+
function extractExtensionIds(adapter, target, extensions) {
|
|
69
|
+
const ids = [];
|
|
70
|
+
const seen = /* @__PURE__ */ new Set();
|
|
71
|
+
if (!seen.has(adapter.id)) {
|
|
72
|
+
ids.push(adapter.id);
|
|
73
|
+
seen.add(adapter.id);
|
|
74
|
+
}
|
|
75
|
+
if (!seen.has(target.id)) {
|
|
76
|
+
ids.push(target.id);
|
|
77
|
+
seen.add(target.id);
|
|
78
|
+
}
|
|
79
|
+
for (const ext of extensions) {
|
|
80
|
+
if (!seen.has(ext.id)) {
|
|
81
|
+
ids.push(ext.id);
|
|
82
|
+
seen.add(ext.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return ids;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/verify.ts
|
|
89
|
+
import { type } from "arktype";
|
|
90
|
+
var MetaSchema = type({ "[string]": "unknown" });
|
|
91
|
+
function parseMeta(meta) {
|
|
92
|
+
if (meta === null || meta === void 0) {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
let parsed;
|
|
96
|
+
if (typeof meta === "string") {
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(meta);
|
|
99
|
+
} catch {
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
parsed = meta;
|
|
104
|
+
}
|
|
105
|
+
const result = MetaSchema(parsed);
|
|
106
|
+
if (result instanceof type.errors) {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
var ContractMarkerRowSchema = type({
|
|
112
|
+
core_hash: "string",
|
|
113
|
+
profile_hash: "string",
|
|
114
|
+
"contract_json?": "unknown | null",
|
|
115
|
+
"canonical_version?": "number | null",
|
|
116
|
+
"updated_at?": "Date | string",
|
|
117
|
+
"app_tag?": "string | null",
|
|
118
|
+
"meta?": "unknown | null"
|
|
119
|
+
});
|
|
120
|
+
function parseContractMarkerRow(row) {
|
|
121
|
+
const result = ContractMarkerRowSchema(row);
|
|
122
|
+
if (result instanceof type.errors) {
|
|
123
|
+
const messages = result.map((p) => p.message).join("; ");
|
|
124
|
+
throw new Error(`Invalid contract marker row: ${messages}`);
|
|
125
|
+
}
|
|
126
|
+
const validatedRow = result;
|
|
127
|
+
const updatedAt = validatedRow.updated_at ? validatedRow.updated_at instanceof Date ? validatedRow.updated_at : new Date(validatedRow.updated_at) : /* @__PURE__ */ new Date();
|
|
128
|
+
return {
|
|
129
|
+
coreHash: validatedRow.core_hash,
|
|
130
|
+
profileHash: validatedRow.profile_hash,
|
|
131
|
+
contractJson: validatedRow.contract_json ?? null,
|
|
132
|
+
canonicalVersion: validatedRow.canonical_version ?? null,
|
|
133
|
+
updatedAt,
|
|
134
|
+
appTag: validatedRow.app_tag ?? null,
|
|
135
|
+
meta: parseMeta(validatedRow.meta)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function readMarkerSql() {
|
|
139
|
+
return {
|
|
140
|
+
sql: `select
|
|
141
|
+
core_hash,
|
|
142
|
+
profile_hash,
|
|
143
|
+
contract_json,
|
|
144
|
+
canonical_version,
|
|
145
|
+
updated_at,
|
|
146
|
+
app_tag,
|
|
147
|
+
meta
|
|
148
|
+
from prisma_contract.marker
|
|
149
|
+
where id = $1`,
|
|
150
|
+
params: [1]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function readMarker(driver) {
|
|
154
|
+
const markerStatement = readMarkerSql();
|
|
155
|
+
const queryResult = await driver.query(markerStatement.sql, markerStatement.params);
|
|
156
|
+
if (queryResult.rows.length === 0) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const markerRow = queryResult.rows[0];
|
|
160
|
+
if (!markerRow) {
|
|
161
|
+
throw new Error("Database query returned unexpected result structure");
|
|
162
|
+
}
|
|
163
|
+
return parseContractMarkerRow(markerRow);
|
|
164
|
+
}
|
|
165
|
+
function collectSupportedCodecTypeIds(descriptors) {
|
|
166
|
+
void descriptors;
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/instance.ts
|
|
171
|
+
function convertOperationManifest(manifest) {
|
|
172
|
+
return {
|
|
173
|
+
forTypeId: manifest.for,
|
|
174
|
+
method: manifest.method,
|
|
175
|
+
args: manifest.args.map((arg) => {
|
|
176
|
+
if (arg.kind === "typeId") {
|
|
177
|
+
if (!arg.type) {
|
|
178
|
+
throw new Error("typeId arg must have type property");
|
|
179
|
+
}
|
|
180
|
+
return { kind: "typeId", type: arg.type };
|
|
181
|
+
}
|
|
182
|
+
if (arg.kind === "param") {
|
|
183
|
+
return { kind: "param" };
|
|
184
|
+
}
|
|
185
|
+
if (arg.kind === "literal") {
|
|
186
|
+
return { kind: "literal" };
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`Invalid arg kind: ${arg.kind}`);
|
|
189
|
+
}),
|
|
190
|
+
returns: (() => {
|
|
191
|
+
if (manifest.returns.kind === "typeId") {
|
|
192
|
+
return { kind: "typeId", type: manifest.returns.type };
|
|
193
|
+
}
|
|
194
|
+
if (manifest.returns.kind === "builtin") {
|
|
195
|
+
return {
|
|
196
|
+
kind: "builtin",
|
|
197
|
+
type: manifest.returns.type
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`Invalid return kind: ${manifest.returns.kind}`);
|
|
201
|
+
})(),
|
|
202
|
+
lowering: {
|
|
203
|
+
targetFamily: "sql",
|
|
204
|
+
strategy: manifest.lowering.strategy,
|
|
205
|
+
template: manifest.lowering.template
|
|
206
|
+
},
|
|
207
|
+
...manifest.capabilities ? { capabilities: manifest.capabilities } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function extractCodecTypeIdsFromContract(contract) {
|
|
211
|
+
const typeIds = /* @__PURE__ */ new Set();
|
|
212
|
+
if (typeof contract === "object" && contract !== null && "storage" in contract && typeof contract.storage === "object" && contract.storage !== null && "tables" in contract.storage) {
|
|
213
|
+
const storage = contract.storage;
|
|
214
|
+
if (storage.tables && typeof storage.tables === "object") {
|
|
215
|
+
for (const table of Object.values(storage.tables)) {
|
|
216
|
+
if (typeof table === "object" && table !== null && "columns" in table && typeof table.columns === "object" && table.columns !== null) {
|
|
217
|
+
const columns = table.columns;
|
|
218
|
+
for (const column of Object.values(columns)) {
|
|
219
|
+
if (column && typeof column === "object" && "type" in column && typeof column.type === "string") {
|
|
220
|
+
typeIds.add(column.type);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return Array.from(typeIds).sort();
|
|
228
|
+
}
|
|
229
|
+
function arraysEqual(a, b) {
|
|
230
|
+
if (a.length !== b.length) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
for (let i = 0; i < a.length; i++) {
|
|
234
|
+
if (a[i] !== b[i]) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
function comparePrimaryKey(contractPK, schemaPK, tableName, issues) {
|
|
241
|
+
if (!schemaPK) {
|
|
242
|
+
issues.push({
|
|
243
|
+
kind: "primary_key_mismatch",
|
|
244
|
+
table: tableName,
|
|
245
|
+
expected: contractPK.columns.join(", "),
|
|
246
|
+
message: `Table "${tableName}" is missing primary key`
|
|
247
|
+
});
|
|
248
|
+
return "fail";
|
|
249
|
+
}
|
|
250
|
+
if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
|
|
251
|
+
issues.push({
|
|
252
|
+
kind: "primary_key_mismatch",
|
|
253
|
+
table: tableName,
|
|
254
|
+
expected: contractPK.columns.join(", "),
|
|
255
|
+
actual: schemaPK.columns.join(", "),
|
|
256
|
+
message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
|
|
257
|
+
});
|
|
258
|
+
return "fail";
|
|
259
|
+
}
|
|
260
|
+
if (contractPK.name && schemaPK.name && contractPK.name !== schemaPK.name) {
|
|
261
|
+
issues.push({
|
|
262
|
+
kind: "primary_key_mismatch",
|
|
263
|
+
table: tableName,
|
|
264
|
+
indexOrConstraint: contractPK.name,
|
|
265
|
+
expected: contractPK.name,
|
|
266
|
+
actual: schemaPK.name,
|
|
267
|
+
message: `Table "${tableName}" has primary key name mismatch: expected "${contractPK.name}", got "${schemaPK.name}"`
|
|
268
|
+
});
|
|
269
|
+
return "fail";
|
|
270
|
+
}
|
|
271
|
+
return "pass";
|
|
272
|
+
}
|
|
273
|
+
function compareForeignKeys(contractFKs, schemaFKs, tableName, tablePath, issues, strict) {
|
|
274
|
+
const nodes = [];
|
|
275
|
+
for (const contractFK of contractFKs) {
|
|
276
|
+
const fkPath = `${tablePath}.foreignKeys[${contractFK.columns.join(",")}]`;
|
|
277
|
+
const matchingFK = schemaFKs.find((fk) => {
|
|
278
|
+
return arraysEqual(fk.columns, contractFK.columns) && fk.referencedTable === contractFK.references.table && arraysEqual(fk.referencedColumns, contractFK.references.columns);
|
|
279
|
+
});
|
|
280
|
+
if (!matchingFK) {
|
|
281
|
+
issues.push({
|
|
282
|
+
kind: "foreign_key_mismatch",
|
|
283
|
+
table: tableName,
|
|
284
|
+
expected: `${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`,
|
|
285
|
+
message: `Table "${tableName}" is missing foreign key: ${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`
|
|
286
|
+
});
|
|
287
|
+
nodes.push({
|
|
288
|
+
status: "fail",
|
|
289
|
+
kind: "foreignKey",
|
|
290
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
291
|
+
contractPath: fkPath,
|
|
292
|
+
code: "foreign_key_mismatch",
|
|
293
|
+
message: "Foreign key missing",
|
|
294
|
+
expected: contractFK,
|
|
295
|
+
actual: void 0,
|
|
296
|
+
children: []
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
if (contractFK.name && matchingFK.name && contractFK.name !== matchingFK.name) {
|
|
300
|
+
issues.push({
|
|
301
|
+
kind: "foreign_key_mismatch",
|
|
302
|
+
table: tableName,
|
|
303
|
+
indexOrConstraint: contractFK.name,
|
|
304
|
+
expected: contractFK.name,
|
|
305
|
+
actual: matchingFK.name,
|
|
306
|
+
message: `Table "${tableName}" has foreign key name mismatch: expected "${contractFK.name}", got "${matchingFK.name}"`
|
|
307
|
+
});
|
|
308
|
+
nodes.push({
|
|
309
|
+
status: "fail",
|
|
310
|
+
kind: "foreignKey",
|
|
311
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
312
|
+
contractPath: fkPath,
|
|
313
|
+
code: "foreign_key_mismatch",
|
|
314
|
+
message: "Foreign key name mismatch",
|
|
315
|
+
expected: contractFK.name,
|
|
316
|
+
actual: matchingFK.name,
|
|
317
|
+
children: []
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
nodes.push({
|
|
321
|
+
status: "pass",
|
|
322
|
+
kind: "foreignKey",
|
|
323
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
324
|
+
contractPath: fkPath,
|
|
325
|
+
code: "",
|
|
326
|
+
message: "",
|
|
327
|
+
expected: void 0,
|
|
328
|
+
actual: void 0,
|
|
329
|
+
children: []
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (strict) {
|
|
335
|
+
for (const schemaFK of schemaFKs) {
|
|
336
|
+
const matchingFK = contractFKs.find((fk) => {
|
|
337
|
+
return arraysEqual(fk.columns, schemaFK.columns) && fk.references.table === schemaFK.referencedTable && arraysEqual(fk.references.columns, schemaFK.referencedColumns);
|
|
338
|
+
});
|
|
339
|
+
if (!matchingFK) {
|
|
340
|
+
issues.push({
|
|
341
|
+
kind: "foreign_key_mismatch",
|
|
342
|
+
table: tableName,
|
|
343
|
+
message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
|
|
344
|
+
});
|
|
345
|
+
nodes.push({
|
|
346
|
+
status: "fail",
|
|
347
|
+
kind: "foreignKey",
|
|
348
|
+
name: `foreignKey(${schemaFK.columns.join(", ")})`,
|
|
349
|
+
contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
|
|
350
|
+
code: "extra_foreign_key",
|
|
351
|
+
message: "Extra foreign key found",
|
|
352
|
+
expected: void 0,
|
|
353
|
+
actual: schemaFK,
|
|
354
|
+
children: []
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return nodes;
|
|
360
|
+
}
|
|
361
|
+
function compareUniqueConstraints(contractUniques, schemaUniques, tableName, tablePath, issues, strict) {
|
|
362
|
+
const nodes = [];
|
|
363
|
+
for (const contractUnique of contractUniques) {
|
|
364
|
+
const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
|
|
365
|
+
const matchingUnique = schemaUniques.find(
|
|
366
|
+
(u) => arraysEqual(u.columns, contractUnique.columns)
|
|
367
|
+
);
|
|
368
|
+
if (!matchingUnique) {
|
|
369
|
+
issues.push({
|
|
370
|
+
kind: "unique_constraint_mismatch",
|
|
371
|
+
table: tableName,
|
|
372
|
+
expected: contractUnique.columns.join(", "),
|
|
373
|
+
message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
|
|
374
|
+
});
|
|
375
|
+
nodes.push({
|
|
376
|
+
status: "fail",
|
|
377
|
+
kind: "unique",
|
|
378
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
379
|
+
contractPath: uniquePath,
|
|
380
|
+
code: "unique_constraint_mismatch",
|
|
381
|
+
message: "Unique constraint missing",
|
|
382
|
+
expected: contractUnique,
|
|
383
|
+
actual: void 0,
|
|
384
|
+
children: []
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
if (contractUnique.name && matchingUnique.name && contractUnique.name !== matchingUnique.name) {
|
|
388
|
+
issues.push({
|
|
389
|
+
kind: "unique_constraint_mismatch",
|
|
390
|
+
table: tableName,
|
|
391
|
+
indexOrConstraint: contractUnique.name,
|
|
392
|
+
expected: contractUnique.name,
|
|
393
|
+
actual: matchingUnique.name,
|
|
394
|
+
message: `Table "${tableName}" has unique constraint name mismatch: expected "${contractUnique.name}", got "${matchingUnique.name}"`
|
|
395
|
+
});
|
|
396
|
+
nodes.push({
|
|
397
|
+
status: "fail",
|
|
398
|
+
kind: "unique",
|
|
399
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
400
|
+
contractPath: uniquePath,
|
|
401
|
+
code: "unique_constraint_mismatch",
|
|
402
|
+
message: "Unique constraint name mismatch",
|
|
403
|
+
expected: contractUnique.name,
|
|
404
|
+
actual: matchingUnique.name,
|
|
405
|
+
children: []
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
nodes.push({
|
|
409
|
+
status: "pass",
|
|
410
|
+
kind: "unique",
|
|
411
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
412
|
+
contractPath: uniquePath,
|
|
413
|
+
code: "",
|
|
414
|
+
message: "",
|
|
415
|
+
expected: void 0,
|
|
416
|
+
actual: void 0,
|
|
417
|
+
children: []
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (strict) {
|
|
423
|
+
for (const schemaUnique of schemaUniques) {
|
|
424
|
+
const matchingUnique = contractUniques.find(
|
|
425
|
+
(u) => arraysEqual(u.columns, schemaUnique.columns)
|
|
426
|
+
);
|
|
427
|
+
if (!matchingUnique) {
|
|
428
|
+
issues.push({
|
|
429
|
+
kind: "unique_constraint_mismatch",
|
|
430
|
+
table: tableName,
|
|
431
|
+
message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
|
|
432
|
+
});
|
|
433
|
+
nodes.push({
|
|
434
|
+
status: "fail",
|
|
435
|
+
kind: "unique",
|
|
436
|
+
name: `unique(${schemaUnique.columns.join(", ")})`,
|
|
437
|
+
contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
|
|
438
|
+
code: "extra_unique_constraint",
|
|
439
|
+
message: "Extra unique constraint found",
|
|
440
|
+
expected: void 0,
|
|
441
|
+
actual: schemaUnique,
|
|
442
|
+
children: []
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return nodes;
|
|
448
|
+
}
|
|
449
|
+
function compareIndexes(contractIndexes, schemaIndexes, tableName, tablePath, issues, strict) {
|
|
450
|
+
const nodes = [];
|
|
451
|
+
for (const contractIndex of contractIndexes) {
|
|
452
|
+
const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
|
|
453
|
+
const matchingIndex = schemaIndexes.find(
|
|
454
|
+
(idx) => arraysEqual(idx.columns, contractIndex.columns) && idx.unique === false
|
|
455
|
+
);
|
|
456
|
+
if (!matchingIndex) {
|
|
457
|
+
issues.push({
|
|
458
|
+
kind: "index_mismatch",
|
|
459
|
+
table: tableName,
|
|
460
|
+
expected: contractIndex.columns.join(", "),
|
|
461
|
+
message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
|
|
462
|
+
});
|
|
463
|
+
nodes.push({
|
|
464
|
+
status: "fail",
|
|
465
|
+
kind: "index",
|
|
466
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
467
|
+
contractPath: indexPath,
|
|
468
|
+
code: "index_mismatch",
|
|
469
|
+
message: "Index missing",
|
|
470
|
+
expected: contractIndex,
|
|
471
|
+
actual: void 0,
|
|
472
|
+
children: []
|
|
473
|
+
});
|
|
474
|
+
} else {
|
|
475
|
+
if (contractIndex.name && matchingIndex.name && contractIndex.name !== matchingIndex.name) {
|
|
476
|
+
issues.push({
|
|
477
|
+
kind: "index_mismatch",
|
|
478
|
+
table: tableName,
|
|
479
|
+
indexOrConstraint: contractIndex.name,
|
|
480
|
+
expected: contractIndex.name,
|
|
481
|
+
actual: matchingIndex.name,
|
|
482
|
+
message: `Table "${tableName}" has index name mismatch: expected "${contractIndex.name}", got "${matchingIndex.name}"`
|
|
483
|
+
});
|
|
484
|
+
nodes.push({
|
|
485
|
+
status: "fail",
|
|
486
|
+
kind: "index",
|
|
487
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
488
|
+
contractPath: indexPath,
|
|
489
|
+
code: "index_mismatch",
|
|
490
|
+
message: "Index name mismatch",
|
|
491
|
+
expected: contractIndex.name,
|
|
492
|
+
actual: matchingIndex.name,
|
|
493
|
+
children: []
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
nodes.push({
|
|
497
|
+
status: "pass",
|
|
498
|
+
kind: "index",
|
|
499
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
500
|
+
contractPath: indexPath,
|
|
501
|
+
code: "",
|
|
502
|
+
message: "",
|
|
503
|
+
expected: void 0,
|
|
504
|
+
actual: void 0,
|
|
505
|
+
children: []
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (strict) {
|
|
511
|
+
for (const schemaIndex of schemaIndexes) {
|
|
512
|
+
if (schemaIndex.unique) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const matchingIndex = contractIndexes.find(
|
|
516
|
+
(idx) => arraysEqual(idx.columns, schemaIndex.columns)
|
|
517
|
+
);
|
|
518
|
+
if (!matchingIndex) {
|
|
519
|
+
issues.push({
|
|
520
|
+
kind: "index_mismatch",
|
|
521
|
+
table: tableName,
|
|
522
|
+
message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
|
|
523
|
+
});
|
|
524
|
+
nodes.push({
|
|
525
|
+
status: "fail",
|
|
526
|
+
kind: "index",
|
|
527
|
+
name: `index(${schemaIndex.columns.join(", ")})`,
|
|
528
|
+
contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
|
|
529
|
+
code: "extra_index",
|
|
530
|
+
message: "Extra index found",
|
|
531
|
+
expected: void 0,
|
|
532
|
+
actual: schemaIndex,
|
|
533
|
+
children: []
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return nodes;
|
|
539
|
+
}
|
|
540
|
+
function compareExtensions(contractExtensions, schemaExtensions, contractTarget, issues, _strict) {
|
|
541
|
+
const nodes = [];
|
|
542
|
+
if (!contractExtensions) {
|
|
543
|
+
return nodes;
|
|
544
|
+
}
|
|
545
|
+
const contractExtensionNames = Object.keys(contractExtensions).filter(
|
|
546
|
+
(name) => name !== contractTarget
|
|
547
|
+
);
|
|
548
|
+
for (const extName of contractExtensionNames) {
|
|
549
|
+
const extPath = `extensions.${extName}`;
|
|
550
|
+
const normalizedExtName = extName.toLowerCase().replace(/^pg/, "");
|
|
551
|
+
const matchingExt = schemaExtensions.find((e) => {
|
|
552
|
+
const normalizedE = e.toLowerCase();
|
|
553
|
+
if (normalizedE === normalizedExtName || normalizedE === extName.toLowerCase()) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
if (normalizedE.includes(normalizedExtName) || normalizedExtName.includes(normalizedE)) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
});
|
|
561
|
+
const extensionLabels = {
|
|
562
|
+
pg: "database is postgres",
|
|
563
|
+
pgvector: "vector extension is enabled",
|
|
564
|
+
vector: "vector extension is enabled"
|
|
565
|
+
};
|
|
566
|
+
const extensionLabel = extensionLabels[extName] ?? `extension "${extName}" is enabled`;
|
|
567
|
+
if (!matchingExt) {
|
|
568
|
+
issues.push({
|
|
569
|
+
kind: "extension_missing",
|
|
570
|
+
table: "",
|
|
571
|
+
message: `Extension "${extName}" is missing from database`
|
|
572
|
+
});
|
|
573
|
+
nodes.push({
|
|
574
|
+
status: "fail",
|
|
575
|
+
kind: "extension",
|
|
576
|
+
name: extensionLabel,
|
|
577
|
+
contractPath: extPath,
|
|
578
|
+
code: "extension_missing",
|
|
579
|
+
message: `Extension "${extName}" is missing`,
|
|
580
|
+
expected: void 0,
|
|
581
|
+
actual: void 0,
|
|
582
|
+
children: []
|
|
583
|
+
});
|
|
584
|
+
} else {
|
|
585
|
+
nodes.push({
|
|
586
|
+
status: "pass",
|
|
587
|
+
kind: "extension",
|
|
588
|
+
name: extensionLabel,
|
|
589
|
+
contractPath: extPath,
|
|
590
|
+
code: "",
|
|
591
|
+
message: "",
|
|
592
|
+
expected: void 0,
|
|
593
|
+
actual: void 0,
|
|
594
|
+
children: []
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return nodes;
|
|
599
|
+
}
|
|
600
|
+
function computeCounts(node) {
|
|
601
|
+
let pass = 0;
|
|
602
|
+
let warn = 0;
|
|
603
|
+
let fail = 0;
|
|
604
|
+
function traverse(n) {
|
|
605
|
+
if (n.status === "pass") {
|
|
606
|
+
pass++;
|
|
607
|
+
} else if (n.status === "warn") {
|
|
608
|
+
warn++;
|
|
609
|
+
} else if (n.status === "fail") {
|
|
610
|
+
fail++;
|
|
611
|
+
}
|
|
612
|
+
if (n.children) {
|
|
613
|
+
for (const child of n.children) {
|
|
614
|
+
traverse(child);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
traverse(node);
|
|
619
|
+
return {
|
|
620
|
+
pass,
|
|
621
|
+
warn,
|
|
622
|
+
fail,
|
|
623
|
+
totalNodes: pass + warn + fail
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function createVerifyResult(options) {
|
|
627
|
+
const contract = {
|
|
628
|
+
coreHash: options.contractCoreHash
|
|
629
|
+
};
|
|
630
|
+
if (options.contractProfileHash) {
|
|
631
|
+
contract.profileHash = options.contractProfileHash;
|
|
632
|
+
}
|
|
633
|
+
const target = {
|
|
634
|
+
expected: options.expectedTargetId
|
|
635
|
+
};
|
|
636
|
+
if (options.actualTargetId) {
|
|
637
|
+
target.actual = options.actualTargetId;
|
|
638
|
+
}
|
|
639
|
+
const meta = {
|
|
640
|
+
contractPath: options.contractPath
|
|
641
|
+
};
|
|
642
|
+
if (options.configPath) {
|
|
643
|
+
meta.configPath = options.configPath;
|
|
644
|
+
}
|
|
645
|
+
const result = {
|
|
646
|
+
ok: options.ok,
|
|
647
|
+
summary: options.summary,
|
|
648
|
+
contract,
|
|
649
|
+
target,
|
|
650
|
+
meta,
|
|
651
|
+
timings: {
|
|
652
|
+
total: options.totalTime
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
if (options.code) {
|
|
656
|
+
result.code = options.code;
|
|
657
|
+
}
|
|
658
|
+
if (options.marker) {
|
|
659
|
+
result.marker = {
|
|
660
|
+
coreHash: options.marker.coreHash,
|
|
661
|
+
profileHash: options.marker.profileHash
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (options.missingCodecs) {
|
|
665
|
+
result.missingCodecs = options.missingCodecs;
|
|
666
|
+
}
|
|
667
|
+
if (options.codecCoverageSkipped) {
|
|
668
|
+
result.codecCoverageSkipped = options.codecCoverageSkipped;
|
|
669
|
+
}
|
|
670
|
+
return result;
|
|
671
|
+
}
|
|
672
|
+
function buildSqlTypeMetadataRegistry(options) {
|
|
673
|
+
const { target, adapter, extensions } = options;
|
|
674
|
+
const registry = /* @__PURE__ */ new Map();
|
|
675
|
+
const targetId = adapter.targetId;
|
|
676
|
+
const descriptors = [target, adapter, ...extensions];
|
|
677
|
+
for (const descriptor of descriptors) {
|
|
678
|
+
const manifest = descriptor.manifest;
|
|
679
|
+
const storageTypes = manifest.types?.storage;
|
|
680
|
+
if (!storageTypes) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
for (const storageType of storageTypes) {
|
|
684
|
+
if (storageType.familyId === "sql" && storageType.targetId === targetId) {
|
|
685
|
+
registry.set(storageType.typeId, {
|
|
686
|
+
typeId: storageType.typeId,
|
|
687
|
+
familyId: "sql",
|
|
688
|
+
targetId: storageType.targetId,
|
|
689
|
+
...storageType.nativeType !== void 0 ? { nativeType: storageType.nativeType } : {}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return registry;
|
|
695
|
+
}
|
|
696
|
+
function createSqlFamilyInstance(options) {
|
|
697
|
+
const { target, adapter, extensions } = options;
|
|
698
|
+
const descriptors = [target, adapter, ...extensions];
|
|
699
|
+
const operationRegistry = assembleOperationRegistry(descriptors, convertOperationManifest);
|
|
700
|
+
const codecTypeImports = extractCodecTypeImports(descriptors);
|
|
701
|
+
const operationTypeImports = extractOperationTypeImports(descriptors);
|
|
702
|
+
const extensionIds = extractExtensionIds(adapter, target, extensions);
|
|
703
|
+
const typeMetadataRegistry = buildSqlTypeMetadataRegistry({ target, adapter, extensions });
|
|
704
|
+
function stripMappings(contract) {
|
|
705
|
+
if (typeof contract === "object" && contract !== null && "mappings" in contract) {
|
|
706
|
+
const { mappings: _mappings, ...contractIR } = contract;
|
|
707
|
+
return contractIR;
|
|
708
|
+
}
|
|
709
|
+
return contract;
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
familyId: "sql",
|
|
713
|
+
operationRegistry,
|
|
714
|
+
codecTypeImports,
|
|
715
|
+
operationTypeImports,
|
|
716
|
+
extensionIds,
|
|
717
|
+
typeMetadataRegistry,
|
|
718
|
+
validateContractIR(contractJson) {
|
|
719
|
+
const validated = validateContract(contractJson);
|
|
720
|
+
const { mappings: _mappings, ...contractIR } = validated;
|
|
721
|
+
return contractIR;
|
|
722
|
+
},
|
|
723
|
+
async verify(verifyOptions) {
|
|
724
|
+
const { driver, contractIR, expectedTargetId, contractPath, configPath } = verifyOptions;
|
|
725
|
+
const startTime = Date.now();
|
|
726
|
+
if (typeof contractIR !== "object" || contractIR === null || !("coreHash" in contractIR) || !("target" in contractIR) || typeof contractIR.coreHash !== "string" || typeof contractIR.target !== "string") {
|
|
727
|
+
throw new Error("Contract is missing required fields: coreHash or target");
|
|
728
|
+
}
|
|
729
|
+
const contractCoreHash = contractIR.coreHash;
|
|
730
|
+
const contractProfileHash = "profileHash" in contractIR && typeof contractIR.profileHash === "string" ? contractIR.profileHash : void 0;
|
|
731
|
+
const contractTarget = contractIR.target;
|
|
732
|
+
const marker = await readMarker(driver);
|
|
733
|
+
let missingCodecs;
|
|
734
|
+
let codecCoverageSkipped = false;
|
|
735
|
+
const supportedTypeIds = collectSupportedCodecTypeIds([
|
|
736
|
+
adapter,
|
|
737
|
+
target,
|
|
738
|
+
...extensions
|
|
739
|
+
]);
|
|
740
|
+
if (supportedTypeIds.length === 0) {
|
|
741
|
+
codecCoverageSkipped = true;
|
|
742
|
+
} else {
|
|
743
|
+
const supportedSet = new Set(supportedTypeIds);
|
|
744
|
+
const usedTypeIds = extractCodecTypeIdsFromContract(contractIR);
|
|
745
|
+
const missing = usedTypeIds.filter((id) => !supportedSet.has(id));
|
|
746
|
+
if (missing.length > 0) {
|
|
747
|
+
missingCodecs = missing;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (!marker) {
|
|
751
|
+
const totalTime2 = Date.now() - startTime;
|
|
752
|
+
return createVerifyResult({
|
|
753
|
+
ok: false,
|
|
754
|
+
code: "PN-RTM-3001",
|
|
755
|
+
summary: "Marker missing",
|
|
756
|
+
contractCoreHash,
|
|
757
|
+
expectedTargetId,
|
|
758
|
+
contractPath,
|
|
759
|
+
totalTime: totalTime2,
|
|
760
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
761
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
762
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
763
|
+
...configPath ? { configPath } : {}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
if (contractTarget !== expectedTargetId) {
|
|
767
|
+
const totalTime2 = Date.now() - startTime;
|
|
768
|
+
return createVerifyResult({
|
|
769
|
+
ok: false,
|
|
770
|
+
code: "PN-RTM-3003",
|
|
771
|
+
summary: "Target mismatch",
|
|
772
|
+
contractCoreHash,
|
|
773
|
+
marker,
|
|
774
|
+
expectedTargetId,
|
|
775
|
+
actualTargetId: contractTarget,
|
|
776
|
+
contractPath,
|
|
777
|
+
totalTime: totalTime2,
|
|
778
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
779
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
780
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
781
|
+
...configPath ? { configPath } : {}
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
if (marker.coreHash !== contractCoreHash) {
|
|
785
|
+
const totalTime2 = Date.now() - startTime;
|
|
786
|
+
return createVerifyResult({
|
|
787
|
+
ok: false,
|
|
788
|
+
code: "PN-RTM-3002",
|
|
789
|
+
summary: "Hash mismatch",
|
|
790
|
+
contractCoreHash,
|
|
791
|
+
marker,
|
|
792
|
+
expectedTargetId,
|
|
793
|
+
contractPath,
|
|
794
|
+
totalTime: totalTime2,
|
|
795
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
796
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
797
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
798
|
+
...configPath ? { configPath } : {}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (contractProfileHash && marker.profileHash !== contractProfileHash) {
|
|
802
|
+
const totalTime2 = Date.now() - startTime;
|
|
803
|
+
return createVerifyResult({
|
|
804
|
+
ok: false,
|
|
805
|
+
code: "PN-RTM-3002",
|
|
806
|
+
summary: "Hash mismatch",
|
|
807
|
+
contractCoreHash,
|
|
808
|
+
contractProfileHash,
|
|
809
|
+
marker,
|
|
810
|
+
expectedTargetId,
|
|
811
|
+
contractPath,
|
|
812
|
+
totalTime: totalTime2,
|
|
813
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
814
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
815
|
+
...configPath ? { configPath } : {}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
const totalTime = Date.now() - startTime;
|
|
819
|
+
return createVerifyResult({
|
|
820
|
+
ok: true,
|
|
821
|
+
summary: "Database matches contract",
|
|
822
|
+
contractCoreHash,
|
|
823
|
+
marker,
|
|
824
|
+
expectedTargetId,
|
|
825
|
+
contractPath,
|
|
826
|
+
totalTime,
|
|
827
|
+
...contractProfileHash ? { contractProfileHash } : {},
|
|
828
|
+
...missingCodecs ? { missingCodecs } : {},
|
|
829
|
+
...codecCoverageSkipped ? { codecCoverageSkipped } : {},
|
|
830
|
+
...configPath ? { configPath } : {}
|
|
831
|
+
});
|
|
832
|
+
},
|
|
833
|
+
async schemaVerify(options2) {
|
|
834
|
+
const { driver, contractIR, strict, contractPath, configPath } = options2;
|
|
835
|
+
const startTime = Date.now();
|
|
836
|
+
const contract = validateContract(contractIR);
|
|
837
|
+
const contractCoreHash = contract.coreHash;
|
|
838
|
+
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : void 0;
|
|
839
|
+
const contractTarget = contract.target;
|
|
840
|
+
const controlAdapter = adapter.create();
|
|
841
|
+
const schemaIR = await controlAdapter.introspect(
|
|
842
|
+
driver,
|
|
843
|
+
contractIR
|
|
844
|
+
);
|
|
845
|
+
const issues = [];
|
|
846
|
+
const rootChildren = [];
|
|
847
|
+
const contractTables = contract.storage.tables;
|
|
848
|
+
const schemaTables = schemaIR.tables;
|
|
849
|
+
for (const [tableName, contractTable] of Object.entries(contractTables)) {
|
|
850
|
+
const schemaTable = schemaTables[tableName];
|
|
851
|
+
const tablePath = `storage.tables.${tableName}`;
|
|
852
|
+
if (!schemaTable) {
|
|
853
|
+
issues.push({
|
|
854
|
+
kind: "missing_table",
|
|
855
|
+
table: tableName,
|
|
856
|
+
message: `Table "${tableName}" is missing from database`
|
|
857
|
+
});
|
|
858
|
+
rootChildren.push({
|
|
859
|
+
status: "fail",
|
|
860
|
+
kind: "table",
|
|
861
|
+
name: `table ${tableName}`,
|
|
862
|
+
contractPath: tablePath,
|
|
863
|
+
code: "missing_table",
|
|
864
|
+
message: `Table "${tableName}" is missing`,
|
|
865
|
+
expected: void 0,
|
|
866
|
+
actual: void 0,
|
|
867
|
+
children: []
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
const tableChildren = [];
|
|
872
|
+
const columnNodes = [];
|
|
873
|
+
for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
|
|
874
|
+
const schemaColumn = schemaTable.columns[columnName];
|
|
875
|
+
const columnPath = `${tablePath}.columns.${columnName}`;
|
|
876
|
+
if (!schemaColumn) {
|
|
877
|
+
issues.push({
|
|
878
|
+
kind: "missing_column",
|
|
879
|
+
table: tableName,
|
|
880
|
+
column: columnName,
|
|
881
|
+
message: `Column "${tableName}"."${columnName}" is missing from database`
|
|
882
|
+
});
|
|
883
|
+
columnNodes.push({
|
|
884
|
+
status: "fail",
|
|
885
|
+
kind: "column",
|
|
886
|
+
name: `${columnName}: missing`,
|
|
887
|
+
contractPath: columnPath,
|
|
888
|
+
code: "missing_column",
|
|
889
|
+
message: `Column "${columnName}" is missing`,
|
|
890
|
+
expected: void 0,
|
|
891
|
+
actual: void 0,
|
|
892
|
+
children: []
|
|
893
|
+
});
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const columnChildren = [];
|
|
897
|
+
let columnStatus = "pass";
|
|
898
|
+
const typeMetadata = typeMetadataRegistry.get(contractColumn.type);
|
|
899
|
+
const contractNativeType = typeMetadata?.nativeType;
|
|
900
|
+
const schemaNativeType = schemaColumn.nativeType;
|
|
901
|
+
if (!contractNativeType) {
|
|
902
|
+
columnChildren.push({
|
|
903
|
+
status: "warn",
|
|
904
|
+
kind: "type",
|
|
905
|
+
name: "type",
|
|
906
|
+
contractPath: `${columnPath}.type`,
|
|
907
|
+
code: "type_metadata_missing",
|
|
908
|
+
message: `Contract type "${contractColumn.type}" has no nativeType metadata - native type comparison skipped`,
|
|
909
|
+
expected: { typeId: contractColumn.type },
|
|
910
|
+
actual: schemaColumn.typeId || schemaNativeType || "unknown",
|
|
911
|
+
children: []
|
|
912
|
+
});
|
|
913
|
+
} else if (!schemaNativeType) {
|
|
914
|
+
issues.push({
|
|
915
|
+
kind: "type_mismatch",
|
|
916
|
+
table: tableName,
|
|
917
|
+
column: columnName,
|
|
918
|
+
expected: contractNativeType,
|
|
919
|
+
actual: schemaColumn.typeId || "unknown",
|
|
920
|
+
message: `Column "${tableName}"."${columnName}" has type mismatch: schema column has no nativeType`
|
|
921
|
+
});
|
|
922
|
+
columnChildren.push({
|
|
923
|
+
status: "fail",
|
|
924
|
+
kind: "type",
|
|
925
|
+
name: "type",
|
|
926
|
+
contractPath: `${columnPath}.type`,
|
|
927
|
+
code: "type_mismatch",
|
|
928
|
+
message: "Schema column has no nativeType",
|
|
929
|
+
expected: contractNativeType,
|
|
930
|
+
actual: schemaColumn.typeId || "unknown",
|
|
931
|
+
children: []
|
|
932
|
+
});
|
|
933
|
+
columnStatus = "fail";
|
|
934
|
+
} else if (contractNativeType !== schemaNativeType) {
|
|
935
|
+
issues.push({
|
|
936
|
+
kind: "type_mismatch",
|
|
937
|
+
table: tableName,
|
|
938
|
+
column: columnName,
|
|
939
|
+
expected: contractNativeType,
|
|
940
|
+
actual: schemaNativeType,
|
|
941
|
+
message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
|
|
942
|
+
});
|
|
943
|
+
columnChildren.push({
|
|
944
|
+
status: "fail",
|
|
945
|
+
kind: "type",
|
|
946
|
+
name: "type",
|
|
947
|
+
contractPath: `${columnPath}.type`,
|
|
948
|
+
code: "type_mismatch",
|
|
949
|
+
message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
|
|
950
|
+
expected: contractNativeType,
|
|
951
|
+
actual: schemaNativeType,
|
|
952
|
+
children: []
|
|
953
|
+
});
|
|
954
|
+
columnStatus = "fail";
|
|
955
|
+
}
|
|
956
|
+
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
957
|
+
issues.push({
|
|
958
|
+
kind: "nullability_mismatch",
|
|
959
|
+
table: tableName,
|
|
960
|
+
column: columnName,
|
|
961
|
+
expected: String(contractColumn.nullable),
|
|
962
|
+
actual: String(schemaColumn.nullable),
|
|
963
|
+
message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
|
|
964
|
+
});
|
|
965
|
+
columnChildren.push({
|
|
966
|
+
status: "fail",
|
|
967
|
+
kind: "nullability",
|
|
968
|
+
name: "nullability",
|
|
969
|
+
contractPath: `${columnPath}.nullable`,
|
|
970
|
+
code: "nullability_mismatch",
|
|
971
|
+
message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
|
|
972
|
+
expected: contractColumn.nullable,
|
|
973
|
+
actual: schemaColumn.nullable,
|
|
974
|
+
children: []
|
|
975
|
+
});
|
|
976
|
+
columnStatus = "fail";
|
|
977
|
+
}
|
|
978
|
+
const computedColumnStatus = columnChildren.some((c) => c.status === "fail") ? "fail" : columnChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
979
|
+
const finalColumnStatus = columnChildren.length > 0 ? computedColumnStatus : columnStatus;
|
|
980
|
+
const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
|
|
981
|
+
const columnTypeDisplay = contractNativeType ? `${contractColumn.type} \u2192 ${contractNativeType}` : contractColumn.type;
|
|
982
|
+
const failureMessages = columnChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
|
|
983
|
+
const columnMessage = finalColumnStatus === "fail" && failureMessages.length > 0 ? failureMessages.join("; ") : "";
|
|
984
|
+
const columnCode = finalColumnStatus === "fail" && columnChildren.length > 0 && columnChildren[0] ? columnChildren[0].code : finalColumnStatus === "warn" && columnChildren.length > 0 && columnChildren[0] ? columnChildren[0].code : "";
|
|
985
|
+
columnNodes.push({
|
|
986
|
+
status: finalColumnStatus,
|
|
987
|
+
kind: "column",
|
|
988
|
+
name: `${columnName}: ${columnTypeDisplay} (${nullableText})`,
|
|
989
|
+
contractPath: columnPath,
|
|
990
|
+
code: columnCode,
|
|
991
|
+
message: columnMessage,
|
|
992
|
+
expected: void 0,
|
|
993
|
+
actual: void 0,
|
|
994
|
+
children: columnChildren
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
if (columnNodes.length > 0) {
|
|
998
|
+
const columnsStatus = columnNodes.some((c) => c.status === "fail") ? "fail" : columnNodes.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
999
|
+
tableChildren.push({
|
|
1000
|
+
status: columnsStatus,
|
|
1001
|
+
kind: "columns",
|
|
1002
|
+
name: "columns",
|
|
1003
|
+
contractPath: `${tablePath}.columns`,
|
|
1004
|
+
code: "",
|
|
1005
|
+
message: "",
|
|
1006
|
+
expected: void 0,
|
|
1007
|
+
actual: void 0,
|
|
1008
|
+
children: columnNodes
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
if (strict) {
|
|
1012
|
+
for (const [columnName, schemaColumn] of Object.entries(schemaTable.columns)) {
|
|
1013
|
+
if (!contractTable.columns[columnName]) {
|
|
1014
|
+
issues.push({
|
|
1015
|
+
kind: "missing_column",
|
|
1016
|
+
table: tableName,
|
|
1017
|
+
column: columnName,
|
|
1018
|
+
message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
|
|
1019
|
+
});
|
|
1020
|
+
columnNodes.push({
|
|
1021
|
+
status: "fail",
|
|
1022
|
+
kind: "column",
|
|
1023
|
+
name: `${columnName}: extra`,
|
|
1024
|
+
contractPath: `${tablePath}.columns.${columnName}`,
|
|
1025
|
+
code: "extra_column",
|
|
1026
|
+
message: `Extra column "${columnName}" found`,
|
|
1027
|
+
expected: void 0,
|
|
1028
|
+
actual: schemaColumn.typeId,
|
|
1029
|
+
children: []
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (contractTable.primaryKey) {
|
|
1035
|
+
const pkStatus = comparePrimaryKey(
|
|
1036
|
+
contractTable.primaryKey,
|
|
1037
|
+
schemaTable.primaryKey,
|
|
1038
|
+
tableName,
|
|
1039
|
+
issues
|
|
1040
|
+
);
|
|
1041
|
+
if (pkStatus === "fail") {
|
|
1042
|
+
tableChildren.push({
|
|
1043
|
+
status: "fail",
|
|
1044
|
+
kind: "primaryKey",
|
|
1045
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
1046
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
1047
|
+
code: "primary_key_mismatch",
|
|
1048
|
+
message: "Primary key mismatch",
|
|
1049
|
+
expected: contractTable.primaryKey,
|
|
1050
|
+
actual: schemaTable.primaryKey,
|
|
1051
|
+
children: []
|
|
1052
|
+
});
|
|
1053
|
+
} else {
|
|
1054
|
+
tableChildren.push({
|
|
1055
|
+
status: "pass",
|
|
1056
|
+
kind: "primaryKey",
|
|
1057
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
1058
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
1059
|
+
code: "",
|
|
1060
|
+
message: "",
|
|
1061
|
+
expected: void 0,
|
|
1062
|
+
actual: void 0,
|
|
1063
|
+
children: []
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
} else if (schemaTable.primaryKey && strict) {
|
|
1067
|
+
issues.push({
|
|
1068
|
+
kind: "primary_key_mismatch",
|
|
1069
|
+
table: tableName,
|
|
1070
|
+
message: "Extra primary key found in database (not in contract)"
|
|
1071
|
+
});
|
|
1072
|
+
tableChildren.push({
|
|
1073
|
+
status: "fail",
|
|
1074
|
+
kind: "primaryKey",
|
|
1075
|
+
name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
|
|
1076
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
1077
|
+
code: "extra_primary_key",
|
|
1078
|
+
message: "Extra primary key found",
|
|
1079
|
+
expected: void 0,
|
|
1080
|
+
actual: schemaTable.primaryKey,
|
|
1081
|
+
children: []
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
const fkStatuses = compareForeignKeys(
|
|
1085
|
+
contractTable.foreignKeys,
|
|
1086
|
+
schemaTable.foreignKeys,
|
|
1087
|
+
tableName,
|
|
1088
|
+
tablePath,
|
|
1089
|
+
issues,
|
|
1090
|
+
strict
|
|
1091
|
+
);
|
|
1092
|
+
tableChildren.push(...fkStatuses);
|
|
1093
|
+
const uniqueStatuses = compareUniqueConstraints(
|
|
1094
|
+
contractTable.uniques,
|
|
1095
|
+
schemaTable.uniques,
|
|
1096
|
+
tableName,
|
|
1097
|
+
tablePath,
|
|
1098
|
+
issues,
|
|
1099
|
+
strict
|
|
1100
|
+
);
|
|
1101
|
+
tableChildren.push(...uniqueStatuses);
|
|
1102
|
+
const indexStatuses = compareIndexes(
|
|
1103
|
+
contractTable.indexes,
|
|
1104
|
+
schemaTable.indexes,
|
|
1105
|
+
tableName,
|
|
1106
|
+
tablePath,
|
|
1107
|
+
issues,
|
|
1108
|
+
strict
|
|
1109
|
+
);
|
|
1110
|
+
tableChildren.push(...indexStatuses);
|
|
1111
|
+
const tableStatus = tableChildren.some((c) => c.status === "fail") ? "fail" : tableChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
1112
|
+
const tableFailureMessages = tableChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
|
|
1113
|
+
const tableMessage = tableStatus === "fail" && tableFailureMessages.length > 0 ? `${tableFailureMessages.length} issue${tableFailureMessages.length === 1 ? "" : "s"}` : "";
|
|
1114
|
+
const tableCode = tableStatus === "fail" && tableChildren.length > 0 && tableChildren[0] ? tableChildren[0].code : "";
|
|
1115
|
+
rootChildren.push({
|
|
1116
|
+
status: tableStatus,
|
|
1117
|
+
kind: "table",
|
|
1118
|
+
name: `table ${tableName}`,
|
|
1119
|
+
contractPath: tablePath,
|
|
1120
|
+
code: tableCode,
|
|
1121
|
+
message: tableMessage,
|
|
1122
|
+
expected: void 0,
|
|
1123
|
+
actual: void 0,
|
|
1124
|
+
children: tableChildren
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
if (strict) {
|
|
1128
|
+
for (const tableName of Object.keys(schemaTables)) {
|
|
1129
|
+
if (!contractTables[tableName]) {
|
|
1130
|
+
issues.push({
|
|
1131
|
+
kind: "missing_table",
|
|
1132
|
+
table: tableName,
|
|
1133
|
+
message: `Extra table "${tableName}" found in database (not in contract)`
|
|
1134
|
+
});
|
|
1135
|
+
rootChildren.push({
|
|
1136
|
+
status: "fail",
|
|
1137
|
+
kind: "table",
|
|
1138
|
+
name: `table ${tableName}`,
|
|
1139
|
+
contractPath: `storage.tables.${tableName}`,
|
|
1140
|
+
code: "extra_table",
|
|
1141
|
+
message: `Extra table "${tableName}" found`,
|
|
1142
|
+
expected: void 0,
|
|
1143
|
+
actual: void 0,
|
|
1144
|
+
children: []
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const extensionStatuses = compareExtensions(
|
|
1150
|
+
contract.extensions,
|
|
1151
|
+
schemaIR.extensions,
|
|
1152
|
+
contractTarget,
|
|
1153
|
+
issues,
|
|
1154
|
+
strict
|
|
1155
|
+
);
|
|
1156
|
+
rootChildren.push(...extensionStatuses);
|
|
1157
|
+
const rootStatus = rootChildren.some((c) => c.status === "fail") ? "fail" : rootChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
1158
|
+
const root = {
|
|
1159
|
+
status: rootStatus,
|
|
1160
|
+
kind: "contract",
|
|
1161
|
+
name: "contract",
|
|
1162
|
+
contractPath: "",
|
|
1163
|
+
code: "",
|
|
1164
|
+
message: "",
|
|
1165
|
+
expected: void 0,
|
|
1166
|
+
actual: void 0,
|
|
1167
|
+
children: rootChildren
|
|
1168
|
+
};
|
|
1169
|
+
const counts = computeCounts(root);
|
|
1170
|
+
const ok = counts.fail === 0;
|
|
1171
|
+
const code = ok ? void 0 : "PN-SCHEMA-0001";
|
|
1172
|
+
const summary = ok ? "Database schema satisfies contract" : `Database schema does not satisfy contract (${counts.fail} failure${counts.fail === 1 ? "" : "s"})`;
|
|
1173
|
+
const totalTime = Date.now() - startTime;
|
|
1174
|
+
return {
|
|
1175
|
+
ok,
|
|
1176
|
+
...code ? { code } : {},
|
|
1177
|
+
summary,
|
|
1178
|
+
contract: {
|
|
1179
|
+
coreHash: contractCoreHash,
|
|
1180
|
+
...contractProfileHash ? { profileHash: contractProfileHash } : {}
|
|
1181
|
+
},
|
|
1182
|
+
target: {
|
|
1183
|
+
expected: contractTarget,
|
|
1184
|
+
actual: contractTarget
|
|
1185
|
+
},
|
|
1186
|
+
schema: {
|
|
1187
|
+
issues,
|
|
1188
|
+
root,
|
|
1189
|
+
counts
|
|
1190
|
+
},
|
|
1191
|
+
meta: {
|
|
1192
|
+
contractPath,
|
|
1193
|
+
strict,
|
|
1194
|
+
...configPath ? { configPath } : {}
|
|
1195
|
+
},
|
|
1196
|
+
timings: {
|
|
1197
|
+
total: totalTime
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
},
|
|
1201
|
+
async sign(options2) {
|
|
1202
|
+
const { driver, contractIR, contractPath, configPath } = options2;
|
|
1203
|
+
const startTime = Date.now();
|
|
1204
|
+
const contract = validateContract(contractIR);
|
|
1205
|
+
const contractCoreHash = contract.coreHash;
|
|
1206
|
+
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : contractCoreHash;
|
|
1207
|
+
const contractTarget = contract.target;
|
|
1208
|
+
await driver.query(ensureSchemaStatement.sql, ensureSchemaStatement.params);
|
|
1209
|
+
await driver.query(ensureTableStatement.sql, ensureTableStatement.params);
|
|
1210
|
+
const existingMarker = await readMarker(driver);
|
|
1211
|
+
let markerCreated = false;
|
|
1212
|
+
let markerUpdated = false;
|
|
1213
|
+
let previousHashes;
|
|
1214
|
+
if (!existingMarker) {
|
|
1215
|
+
const write = writeContractMarker({
|
|
1216
|
+
coreHash: contractCoreHash,
|
|
1217
|
+
profileHash: contractProfileHash,
|
|
1218
|
+
contractJson: contractIR,
|
|
1219
|
+
canonicalVersion: 1
|
|
1220
|
+
});
|
|
1221
|
+
await driver.query(write.insert.sql, write.insert.params);
|
|
1222
|
+
markerCreated = true;
|
|
1223
|
+
} else {
|
|
1224
|
+
const existingCoreHash = existingMarker.coreHash;
|
|
1225
|
+
const existingProfileHash = existingMarker.profileHash;
|
|
1226
|
+
const coreHashMatches = existingCoreHash === contractCoreHash;
|
|
1227
|
+
const profileHashMatches = existingProfileHash === contractProfileHash;
|
|
1228
|
+
if (!coreHashMatches || !profileHashMatches) {
|
|
1229
|
+
previousHashes = {
|
|
1230
|
+
coreHash: existingCoreHash,
|
|
1231
|
+
profileHash: existingProfileHash
|
|
1232
|
+
};
|
|
1233
|
+
const write = writeContractMarker({
|
|
1234
|
+
coreHash: contractCoreHash,
|
|
1235
|
+
profileHash: contractProfileHash,
|
|
1236
|
+
contractJson: contractIR,
|
|
1237
|
+
canonicalVersion: existingMarker.canonicalVersion ?? 1
|
|
1238
|
+
});
|
|
1239
|
+
await driver.query(write.update.sql, write.update.params);
|
|
1240
|
+
markerUpdated = true;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
let summary;
|
|
1244
|
+
if (markerCreated) {
|
|
1245
|
+
summary = "Database signed (marker created)";
|
|
1246
|
+
} else if (markerUpdated) {
|
|
1247
|
+
summary = `Database signed (marker updated from ${previousHashes?.coreHash ?? "unknown"})`;
|
|
1248
|
+
} else {
|
|
1249
|
+
summary = "Database already signed with this contract";
|
|
1250
|
+
}
|
|
1251
|
+
const totalTime = Date.now() - startTime;
|
|
1252
|
+
return {
|
|
1253
|
+
ok: true,
|
|
1254
|
+
summary,
|
|
1255
|
+
contract: {
|
|
1256
|
+
coreHash: contractCoreHash,
|
|
1257
|
+
profileHash: contractProfileHash
|
|
1258
|
+
},
|
|
1259
|
+
target: {
|
|
1260
|
+
expected: contractTarget,
|
|
1261
|
+
actual: contractTarget
|
|
1262
|
+
},
|
|
1263
|
+
marker: {
|
|
1264
|
+
created: markerCreated,
|
|
1265
|
+
updated: markerUpdated,
|
|
1266
|
+
...previousHashes ? { previous: previousHashes } : {}
|
|
1267
|
+
},
|
|
1268
|
+
meta: {
|
|
1269
|
+
contractPath,
|
|
1270
|
+
...configPath ? { configPath } : {}
|
|
1271
|
+
},
|
|
1272
|
+
timings: {
|
|
1273
|
+
total: totalTime
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
},
|
|
1277
|
+
async introspect(options2) {
|
|
1278
|
+
const { driver, contractIR } = options2;
|
|
1279
|
+
const controlAdapter = adapter.create();
|
|
1280
|
+
return controlAdapter.introspect(driver, contractIR);
|
|
1281
|
+
},
|
|
1282
|
+
toSchemaView(schema) {
|
|
1283
|
+
const rootLabel = "contract";
|
|
1284
|
+
const tableNodes = Object.entries(schema.tables).map(
|
|
1285
|
+
([tableName, table]) => {
|
|
1286
|
+
const children = [];
|
|
1287
|
+
const columnNodes = [];
|
|
1288
|
+
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
1289
|
+
const nullableText = column.nullable ? "(nullable)" : "(not nullable)";
|
|
1290
|
+
const typeDisplay = column.nativeType ?? column.typeId;
|
|
1291
|
+
const label = `${columnName}: ${typeDisplay} ${nullableText}`;
|
|
1292
|
+
columnNodes.push({
|
|
1293
|
+
kind: "field",
|
|
1294
|
+
id: `column-${tableName}-${columnName}`,
|
|
1295
|
+
label,
|
|
1296
|
+
meta: {
|
|
1297
|
+
typeId: column.typeId,
|
|
1298
|
+
nullable: column.nullable,
|
|
1299
|
+
...column.nativeType ? { nativeType: column.nativeType } : {}
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
if (columnNodes.length > 0) {
|
|
1304
|
+
children.push({
|
|
1305
|
+
kind: "collection",
|
|
1306
|
+
id: `columns-${tableName}`,
|
|
1307
|
+
label: "columns",
|
|
1308
|
+
children: columnNodes
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
if (table.primaryKey) {
|
|
1312
|
+
const pkColumns = table.primaryKey.columns.join(", ");
|
|
1313
|
+
children.push({
|
|
1314
|
+
kind: "index",
|
|
1315
|
+
id: `primary-key-${tableName}`,
|
|
1316
|
+
label: `primary key: ${pkColumns}`,
|
|
1317
|
+
meta: {
|
|
1318
|
+
columns: table.primaryKey.columns,
|
|
1319
|
+
...table.primaryKey.name ? { name: table.primaryKey.name } : {}
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
for (const unique of table.uniques) {
|
|
1324
|
+
const name = unique.name ?? `${tableName}_${unique.columns.join("_")}_unique`;
|
|
1325
|
+
const label = `unique ${name}`;
|
|
1326
|
+
children.push({
|
|
1327
|
+
kind: "index",
|
|
1328
|
+
id: `unique-${tableName}-${name}`,
|
|
1329
|
+
label,
|
|
1330
|
+
meta: {
|
|
1331
|
+
columns: unique.columns,
|
|
1332
|
+
unique: true
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
for (const index of table.indexes) {
|
|
1337
|
+
const name = index.name ?? `${tableName}_${index.columns.join("_")}_idx`;
|
|
1338
|
+
const label = index.unique ? `unique index ${name}` : `index ${name}`;
|
|
1339
|
+
children.push({
|
|
1340
|
+
kind: "index",
|
|
1341
|
+
id: `index-${tableName}-${name}`,
|
|
1342
|
+
label,
|
|
1343
|
+
meta: {
|
|
1344
|
+
columns: index.columns,
|
|
1345
|
+
unique: index.unique
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
const tableMeta = {};
|
|
1350
|
+
if (table.primaryKey) {
|
|
1351
|
+
tableMeta["primaryKey"] = table.primaryKey.columns;
|
|
1352
|
+
if (table.primaryKey.name) {
|
|
1353
|
+
tableMeta["primaryKeyName"] = table.primaryKey.name;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (table.foreignKeys.length > 0) {
|
|
1357
|
+
tableMeta["foreignKeys"] = table.foreignKeys.map((fk) => ({
|
|
1358
|
+
columns: fk.columns,
|
|
1359
|
+
referencedTable: fk.referencedTable,
|
|
1360
|
+
referencedColumns: fk.referencedColumns,
|
|
1361
|
+
...fk.name ? { name: fk.name } : {}
|
|
1362
|
+
}));
|
|
1363
|
+
}
|
|
1364
|
+
const node = {
|
|
1365
|
+
kind: "entity",
|
|
1366
|
+
id: `table-${tableName}`,
|
|
1367
|
+
label: `table ${tableName}`,
|
|
1368
|
+
...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
|
|
1369
|
+
...children.length > 0 ? { children } : {}
|
|
1370
|
+
};
|
|
1371
|
+
return node;
|
|
1372
|
+
}
|
|
1373
|
+
);
|
|
1374
|
+
const extensionNodes = schema.extensions.map((extName) => ({
|
|
1375
|
+
kind: "extension",
|
|
1376
|
+
id: `extension-${extName}`,
|
|
1377
|
+
label: `${extName} extension is enabled`
|
|
1378
|
+
}));
|
|
1379
|
+
const rootChildren = [...tableNodes, ...extensionNodes];
|
|
1380
|
+
const rootNode = {
|
|
1381
|
+
kind: "root",
|
|
1382
|
+
id: "sql-schema",
|
|
1383
|
+
label: rootLabel,
|
|
1384
|
+
...rootChildren.length > 0 ? { children: rootChildren } : {}
|
|
1385
|
+
};
|
|
1386
|
+
return {
|
|
1387
|
+
root: rootNode
|
|
1388
|
+
};
|
|
1389
|
+
},
|
|
1390
|
+
async emitContract({ contractIR }) {
|
|
1391
|
+
const contractWithoutMappings = stripMappings(contractIR);
|
|
1392
|
+
const validatedIR = this.validateContractIR(contractWithoutMappings);
|
|
1393
|
+
const result = await emit(
|
|
1394
|
+
validatedIR,
|
|
1395
|
+
{
|
|
1396
|
+
outputDir: "",
|
|
1397
|
+
operationRegistry,
|
|
1398
|
+
codecTypeImports,
|
|
1399
|
+
operationTypeImports,
|
|
1400
|
+
extensionIds
|
|
1401
|
+
},
|
|
1402
|
+
sqlTargetFamilyHook
|
|
1403
|
+
);
|
|
1404
|
+
return {
|
|
1405
|
+
contractJson: result.contractJson,
|
|
1406
|
+
contractDts: result.contractDts,
|
|
1407
|
+
coreHash: result.coreHash,
|
|
1408
|
+
profileHash: result.profileHash
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/core/descriptor.ts
|
|
1415
|
+
var sqlFamilyManifest = {
|
|
1416
|
+
id: "sql",
|
|
1417
|
+
version: "0.0.1"
|
|
1418
|
+
};
|
|
1419
|
+
var SqlFamilyDescriptor = class {
|
|
1420
|
+
kind = "family";
|
|
1421
|
+
id = "sql";
|
|
1422
|
+
familyId = "sql";
|
|
1423
|
+
manifest = sqlFamilyManifest;
|
|
1424
|
+
hook = sqlTargetFamilyHook2;
|
|
1425
|
+
create(options) {
|
|
1426
|
+
return createSqlFamilyInstance({
|
|
1427
|
+
target: options.target,
|
|
1428
|
+
adapter: options.adapter,
|
|
1429
|
+
extensions: options.extensions
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
// src/exports/control.ts
|
|
1435
|
+
var control_default = new SqlFamilyDescriptor();
|
|
1436
|
+
export {
|
|
1437
|
+
control_default as default
|
|
1438
|
+
};
|
|
1439
|
+
//# sourceMappingURL=control.js.map
|