@prisma-next/family-sql 0.1.0-dev.2 → 0.1.0-dev.21

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.
@@ -0,0 +1,772 @@
1
+ // src/core/schema-verify/verify-helpers.ts
2
+ function arraysEqual(a, b) {
3
+ if (a.length !== b.length) {
4
+ return false;
5
+ }
6
+ for (let i = 0; i < a.length; i++) {
7
+ if (a[i] !== b[i]) {
8
+ return false;
9
+ }
10
+ }
11
+ return true;
12
+ }
13
+ function verifyPrimaryKey(contractPK, schemaPK, tableName, issues) {
14
+ if (!schemaPK) {
15
+ issues.push({
16
+ kind: "primary_key_mismatch",
17
+ table: tableName,
18
+ expected: contractPK.columns.join(", "),
19
+ message: `Table "${tableName}" is missing primary key`
20
+ });
21
+ return "fail";
22
+ }
23
+ if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
24
+ issues.push({
25
+ kind: "primary_key_mismatch",
26
+ table: tableName,
27
+ expected: contractPK.columns.join(", "),
28
+ actual: schemaPK.columns.join(", "),
29
+ message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
30
+ });
31
+ return "fail";
32
+ }
33
+ if (contractPK.name && schemaPK.name && contractPK.name !== schemaPK.name) {
34
+ issues.push({
35
+ kind: "primary_key_mismatch",
36
+ table: tableName,
37
+ indexOrConstraint: contractPK.name,
38
+ expected: contractPK.name,
39
+ actual: schemaPK.name,
40
+ message: `Table "${tableName}" has primary key name mismatch: expected "${contractPK.name}", got "${schemaPK.name}"`
41
+ });
42
+ return "fail";
43
+ }
44
+ return "pass";
45
+ }
46
+ function verifyForeignKeys(contractFKs, schemaFKs, tableName, tablePath, issues, strict) {
47
+ const nodes = [];
48
+ for (const contractFK of contractFKs) {
49
+ const fkPath = `${tablePath}.foreignKeys[${contractFK.columns.join(",")}]`;
50
+ const matchingFK = schemaFKs.find((fk) => {
51
+ return arraysEqual(fk.columns, contractFK.columns) && fk.referencedTable === contractFK.references.table && arraysEqual(fk.referencedColumns, contractFK.references.columns);
52
+ });
53
+ if (!matchingFK) {
54
+ issues.push({
55
+ kind: "foreign_key_mismatch",
56
+ table: tableName,
57
+ expected: `${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`,
58
+ message: `Table "${tableName}" is missing foreign key: ${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`
59
+ });
60
+ nodes.push({
61
+ status: "fail",
62
+ kind: "foreignKey",
63
+ name: `foreignKey(${contractFK.columns.join(", ")})`,
64
+ contractPath: fkPath,
65
+ code: "foreign_key_mismatch",
66
+ message: "Foreign key missing",
67
+ expected: contractFK,
68
+ actual: void 0,
69
+ children: []
70
+ });
71
+ } else {
72
+ if (contractFK.name && matchingFK.name && contractFK.name !== matchingFK.name) {
73
+ issues.push({
74
+ kind: "foreign_key_mismatch",
75
+ table: tableName,
76
+ indexOrConstraint: contractFK.name,
77
+ expected: contractFK.name,
78
+ actual: matchingFK.name,
79
+ message: `Table "${tableName}" has foreign key name mismatch: expected "${contractFK.name}", got "${matchingFK.name}"`
80
+ });
81
+ nodes.push({
82
+ status: "fail",
83
+ kind: "foreignKey",
84
+ name: `foreignKey(${contractFK.columns.join(", ")})`,
85
+ contractPath: fkPath,
86
+ code: "foreign_key_mismatch",
87
+ message: "Foreign key name mismatch",
88
+ expected: contractFK.name,
89
+ actual: matchingFK.name,
90
+ children: []
91
+ });
92
+ } else {
93
+ nodes.push({
94
+ status: "pass",
95
+ kind: "foreignKey",
96
+ name: `foreignKey(${contractFK.columns.join(", ")})`,
97
+ contractPath: fkPath,
98
+ code: "",
99
+ message: "",
100
+ expected: void 0,
101
+ actual: void 0,
102
+ children: []
103
+ });
104
+ }
105
+ }
106
+ }
107
+ if (strict) {
108
+ for (const schemaFK of schemaFKs) {
109
+ const matchingFK = contractFKs.find((fk) => {
110
+ return arraysEqual(fk.columns, schemaFK.columns) && fk.references.table === schemaFK.referencedTable && arraysEqual(fk.references.columns, schemaFK.referencedColumns);
111
+ });
112
+ if (!matchingFK) {
113
+ issues.push({
114
+ kind: "extra_foreign_key",
115
+ table: tableName,
116
+ message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
117
+ });
118
+ nodes.push({
119
+ status: "fail",
120
+ kind: "foreignKey",
121
+ name: `foreignKey(${schemaFK.columns.join(", ")})`,
122
+ contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
123
+ code: "extra_foreign_key",
124
+ message: "Extra foreign key found",
125
+ expected: void 0,
126
+ actual: schemaFK,
127
+ children: []
128
+ });
129
+ }
130
+ }
131
+ }
132
+ return nodes;
133
+ }
134
+ function verifyUniqueConstraints(contractUniques, schemaUniques, tableName, tablePath, issues, strict) {
135
+ const nodes = [];
136
+ for (const contractUnique of contractUniques) {
137
+ const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
138
+ const matchingUnique = schemaUniques.find(
139
+ (u) => arraysEqual(u.columns, contractUnique.columns)
140
+ );
141
+ if (!matchingUnique) {
142
+ issues.push({
143
+ kind: "unique_constraint_mismatch",
144
+ table: tableName,
145
+ expected: contractUnique.columns.join(", "),
146
+ message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
147
+ });
148
+ nodes.push({
149
+ status: "fail",
150
+ kind: "unique",
151
+ name: `unique(${contractUnique.columns.join(", ")})`,
152
+ contractPath: uniquePath,
153
+ code: "unique_constraint_mismatch",
154
+ message: "Unique constraint missing",
155
+ expected: contractUnique,
156
+ actual: void 0,
157
+ children: []
158
+ });
159
+ } else {
160
+ if (contractUnique.name && matchingUnique.name && contractUnique.name !== matchingUnique.name) {
161
+ issues.push({
162
+ kind: "unique_constraint_mismatch",
163
+ table: tableName,
164
+ indexOrConstraint: contractUnique.name,
165
+ expected: contractUnique.name,
166
+ actual: matchingUnique.name,
167
+ message: `Table "${tableName}" has unique constraint name mismatch: expected "${contractUnique.name}", got "${matchingUnique.name}"`
168
+ });
169
+ nodes.push({
170
+ status: "fail",
171
+ kind: "unique",
172
+ name: `unique(${contractUnique.columns.join(", ")})`,
173
+ contractPath: uniquePath,
174
+ code: "unique_constraint_mismatch",
175
+ message: "Unique constraint name mismatch",
176
+ expected: contractUnique.name,
177
+ actual: matchingUnique.name,
178
+ children: []
179
+ });
180
+ } else {
181
+ nodes.push({
182
+ status: "pass",
183
+ kind: "unique",
184
+ name: `unique(${contractUnique.columns.join(", ")})`,
185
+ contractPath: uniquePath,
186
+ code: "",
187
+ message: "",
188
+ expected: void 0,
189
+ actual: void 0,
190
+ children: []
191
+ });
192
+ }
193
+ }
194
+ }
195
+ if (strict) {
196
+ for (const schemaUnique of schemaUniques) {
197
+ const matchingUnique = contractUniques.find(
198
+ (u) => arraysEqual(u.columns, schemaUnique.columns)
199
+ );
200
+ if (!matchingUnique) {
201
+ issues.push({
202
+ kind: "extra_unique_constraint",
203
+ table: tableName,
204
+ message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
205
+ });
206
+ nodes.push({
207
+ status: "fail",
208
+ kind: "unique",
209
+ name: `unique(${schemaUnique.columns.join(", ")})`,
210
+ contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
211
+ code: "extra_unique_constraint",
212
+ message: "Extra unique constraint found",
213
+ expected: void 0,
214
+ actual: schemaUnique,
215
+ children: []
216
+ });
217
+ }
218
+ }
219
+ }
220
+ return nodes;
221
+ }
222
+ function verifyIndexes(contractIndexes, schemaIndexes, tableName, tablePath, issues, strict) {
223
+ const nodes = [];
224
+ for (const contractIndex of contractIndexes) {
225
+ const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
226
+ const matchingIndex = schemaIndexes.find(
227
+ (idx) => arraysEqual(idx.columns, contractIndex.columns) && idx.unique === false
228
+ );
229
+ if (!matchingIndex) {
230
+ issues.push({
231
+ kind: "index_mismatch",
232
+ table: tableName,
233
+ expected: contractIndex.columns.join(", "),
234
+ message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
235
+ });
236
+ nodes.push({
237
+ status: "fail",
238
+ kind: "index",
239
+ name: `index(${contractIndex.columns.join(", ")})`,
240
+ contractPath: indexPath,
241
+ code: "index_mismatch",
242
+ message: "Index missing",
243
+ expected: contractIndex,
244
+ actual: void 0,
245
+ children: []
246
+ });
247
+ } else {
248
+ if (contractIndex.name && matchingIndex.name && contractIndex.name !== matchingIndex.name) {
249
+ issues.push({
250
+ kind: "index_mismatch",
251
+ table: tableName,
252
+ indexOrConstraint: contractIndex.name,
253
+ expected: contractIndex.name,
254
+ actual: matchingIndex.name,
255
+ message: `Table "${tableName}" has index name mismatch: expected "${contractIndex.name}", got "${matchingIndex.name}"`
256
+ });
257
+ nodes.push({
258
+ status: "fail",
259
+ kind: "index",
260
+ name: `index(${contractIndex.columns.join(", ")})`,
261
+ contractPath: indexPath,
262
+ code: "index_mismatch",
263
+ message: "Index name mismatch",
264
+ expected: contractIndex.name,
265
+ actual: matchingIndex.name,
266
+ children: []
267
+ });
268
+ } else {
269
+ nodes.push({
270
+ status: "pass",
271
+ kind: "index",
272
+ name: `index(${contractIndex.columns.join(", ")})`,
273
+ contractPath: indexPath,
274
+ code: "",
275
+ message: "",
276
+ expected: void 0,
277
+ actual: void 0,
278
+ children: []
279
+ });
280
+ }
281
+ }
282
+ }
283
+ if (strict) {
284
+ for (const schemaIndex of schemaIndexes) {
285
+ if (schemaIndex.unique) {
286
+ continue;
287
+ }
288
+ const matchingIndex = contractIndexes.find(
289
+ (idx) => arraysEqual(idx.columns, schemaIndex.columns)
290
+ );
291
+ if (!matchingIndex) {
292
+ issues.push({
293
+ kind: "extra_index",
294
+ table: tableName,
295
+ message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
296
+ });
297
+ nodes.push({
298
+ status: "fail",
299
+ kind: "index",
300
+ name: `index(${schemaIndex.columns.join(", ")})`,
301
+ contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
302
+ code: "extra_index",
303
+ message: "Extra index found",
304
+ expected: void 0,
305
+ actual: schemaIndex,
306
+ children: []
307
+ });
308
+ }
309
+ }
310
+ }
311
+ return nodes;
312
+ }
313
+ function verifyDatabaseDependencies(dependencies, schema, issues) {
314
+ const nodes = [];
315
+ for (const dependency of dependencies) {
316
+ const depIssues = dependency.verifyDatabaseDependencyInstalled(schema);
317
+ const depPath = `dependencies.${dependency.id}`;
318
+ if (depIssues.length > 0) {
319
+ issues.push(...depIssues);
320
+ const issuesMessage = depIssues.map((i) => i.message).join("; ");
321
+ const nodeMessage = issuesMessage ? `${dependency.id}: ${issuesMessage}` : dependency.id;
322
+ nodes.push({
323
+ status: "fail",
324
+ kind: "databaseDependency",
325
+ name: dependency.label,
326
+ contractPath: depPath,
327
+ code: "dependency_missing",
328
+ message: nodeMessage,
329
+ expected: void 0,
330
+ actual: void 0,
331
+ children: []
332
+ });
333
+ } else {
334
+ nodes.push({
335
+ status: "pass",
336
+ kind: "databaseDependency",
337
+ name: dependency.label,
338
+ contractPath: depPath,
339
+ code: "",
340
+ message: "",
341
+ expected: void 0,
342
+ actual: void 0,
343
+ children: []
344
+ });
345
+ }
346
+ }
347
+ return nodes;
348
+ }
349
+ function computeCounts(node) {
350
+ let pass = 0;
351
+ let warn = 0;
352
+ let fail = 0;
353
+ function traverse(n) {
354
+ if (n.status === "pass") {
355
+ pass++;
356
+ } else if (n.status === "warn") {
357
+ warn++;
358
+ } else if (n.status === "fail") {
359
+ fail++;
360
+ }
361
+ if (n.children) {
362
+ for (const child of n.children) {
363
+ traverse(child);
364
+ }
365
+ }
366
+ }
367
+ traverse(node);
368
+ return {
369
+ pass,
370
+ warn,
371
+ fail,
372
+ totalNodes: pass + warn + fail
373
+ };
374
+ }
375
+
376
+ // src/core/schema-verify/verify-sql-schema.ts
377
+ import { ifDefined } from "@prisma-next/utils/defined";
378
+ function verifySqlSchema(options) {
379
+ const { contract, schema, strict, context, typeMetadataRegistry } = options;
380
+ const startTime = Date.now();
381
+ const contractCoreHash = contract.coreHash;
382
+ const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : void 0;
383
+ const contractTarget = contract.target;
384
+ const issues = [];
385
+ const rootChildren = [];
386
+ const contractTables = contract.storage.tables;
387
+ const schemaTables = schema.tables;
388
+ for (const [tableName, contractTable] of Object.entries(contractTables)) {
389
+ const schemaTable = schemaTables[tableName];
390
+ const tablePath = `storage.tables.${tableName}`;
391
+ if (!schemaTable) {
392
+ issues.push({
393
+ kind: "missing_table",
394
+ table: tableName,
395
+ message: `Table "${tableName}" is missing from database`
396
+ });
397
+ rootChildren.push({
398
+ status: "fail",
399
+ kind: "table",
400
+ name: `table ${tableName}`,
401
+ contractPath: tablePath,
402
+ code: "missing_table",
403
+ message: `Table "${tableName}" is missing`,
404
+ expected: void 0,
405
+ actual: void 0,
406
+ children: []
407
+ });
408
+ continue;
409
+ }
410
+ const tableChildren = [];
411
+ const columnNodes = [];
412
+ for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
413
+ const schemaColumn = schemaTable.columns[columnName];
414
+ const columnPath = `${tablePath}.columns.${columnName}`;
415
+ if (!schemaColumn) {
416
+ issues.push({
417
+ kind: "missing_column",
418
+ table: tableName,
419
+ column: columnName,
420
+ message: `Column "${tableName}"."${columnName}" is missing from database`
421
+ });
422
+ columnNodes.push({
423
+ status: "fail",
424
+ kind: "column",
425
+ name: `${columnName}: missing`,
426
+ contractPath: columnPath,
427
+ code: "missing_column",
428
+ message: `Column "${columnName}" is missing`,
429
+ expected: void 0,
430
+ actual: void 0,
431
+ children: []
432
+ });
433
+ continue;
434
+ }
435
+ const columnChildren = [];
436
+ let columnStatus = "pass";
437
+ const contractNativeType = contractColumn.nativeType;
438
+ const schemaNativeType = schemaColumn.nativeType;
439
+ if (contractNativeType !== schemaNativeType) {
440
+ issues.push({
441
+ kind: "type_mismatch",
442
+ table: tableName,
443
+ column: columnName,
444
+ expected: contractNativeType,
445
+ actual: schemaNativeType,
446
+ message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
447
+ });
448
+ columnChildren.push({
449
+ status: "fail",
450
+ kind: "type",
451
+ name: "type",
452
+ contractPath: `${columnPath}.nativeType`,
453
+ code: "type_mismatch",
454
+ message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
455
+ expected: contractNativeType,
456
+ actual: schemaNativeType,
457
+ children: []
458
+ });
459
+ columnStatus = "fail";
460
+ }
461
+ if (contractColumn.codecId) {
462
+ const typeMetadata = typeMetadataRegistry.get(contractColumn.codecId);
463
+ if (!typeMetadata) {
464
+ columnChildren.push({
465
+ status: "warn",
466
+ kind: "type",
467
+ name: "type_metadata_missing",
468
+ contractPath: `${columnPath}.codecId`,
469
+ code: "type_metadata_missing",
470
+ message: `codecId "${contractColumn.codecId}" not found in type metadata registry`,
471
+ expected: contractColumn.codecId,
472
+ actual: void 0,
473
+ children: []
474
+ });
475
+ } else if (typeMetadata.nativeType && typeMetadata.nativeType !== contractNativeType) {
476
+ columnChildren.push({
477
+ status: "warn",
478
+ kind: "type",
479
+ name: "type_consistency",
480
+ contractPath: `${columnPath}.codecId`,
481
+ code: "type_consistency_warning",
482
+ message: `codecId "${contractColumn.codecId}" maps to nativeType "${typeMetadata.nativeType}" in registry, but contract has "${contractNativeType}"`,
483
+ expected: typeMetadata.nativeType,
484
+ actual: contractNativeType,
485
+ children: []
486
+ });
487
+ }
488
+ }
489
+ if (contractColumn.nullable !== schemaColumn.nullable) {
490
+ issues.push({
491
+ kind: "nullability_mismatch",
492
+ table: tableName,
493
+ column: columnName,
494
+ expected: String(contractColumn.nullable),
495
+ actual: String(schemaColumn.nullable),
496
+ message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
497
+ });
498
+ columnChildren.push({
499
+ status: "fail",
500
+ kind: "nullability",
501
+ name: "nullability",
502
+ contractPath: `${columnPath}.nullable`,
503
+ code: "nullability_mismatch",
504
+ message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
505
+ expected: contractColumn.nullable,
506
+ actual: schemaColumn.nullable,
507
+ children: []
508
+ });
509
+ columnStatus = "fail";
510
+ }
511
+ const computedColumnStatus = columnChildren.some((c) => c.status === "fail") ? "fail" : columnChildren.some((c) => c.status === "warn") ? "warn" : "pass";
512
+ const finalColumnStatus = columnChildren.length > 0 ? computedColumnStatus : columnStatus;
513
+ const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
514
+ const columnTypeDisplay = contractColumn.codecId ? `${contractNativeType} (${contractColumn.codecId})` : contractNativeType;
515
+ const failureMessages = columnChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
516
+ const columnMessage = finalColumnStatus === "fail" && failureMessages.length > 0 ? failureMessages.join("; ") : "";
517
+ const columnCode = (finalColumnStatus === "fail" || finalColumnStatus === "warn") && columnChildren[0] ? columnChildren[0].code : "";
518
+ columnNodes.push({
519
+ status: finalColumnStatus,
520
+ kind: "column",
521
+ name: `${columnName}: ${columnTypeDisplay} (${nullableText})`,
522
+ contractPath: columnPath,
523
+ code: columnCode,
524
+ message: columnMessage,
525
+ expected: void 0,
526
+ actual: void 0,
527
+ children: columnChildren
528
+ });
529
+ }
530
+ if (columnNodes.length > 0) {
531
+ const columnsStatus = columnNodes.some((c) => c.status === "fail") ? "fail" : columnNodes.some((c) => c.status === "warn") ? "warn" : "pass";
532
+ tableChildren.push({
533
+ status: columnsStatus,
534
+ kind: "columns",
535
+ name: "columns",
536
+ contractPath: `${tablePath}.columns`,
537
+ code: "",
538
+ message: "",
539
+ expected: void 0,
540
+ actual: void 0,
541
+ children: columnNodes
542
+ });
543
+ }
544
+ if (strict) {
545
+ for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) {
546
+ if (!contractTable.columns[columnName]) {
547
+ issues.push({
548
+ kind: "extra_column",
549
+ table: tableName,
550
+ column: columnName,
551
+ message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
552
+ });
553
+ columnNodes.push({
554
+ status: "fail",
555
+ kind: "column",
556
+ name: `${columnName}: extra`,
557
+ contractPath: `${tablePath}.columns.${columnName}`,
558
+ code: "extra_column",
559
+ message: `Extra column "${columnName}" found`,
560
+ expected: void 0,
561
+ actual: nativeType,
562
+ children: []
563
+ });
564
+ }
565
+ }
566
+ }
567
+ if (contractTable.primaryKey) {
568
+ const pkStatus = verifyPrimaryKey(
569
+ contractTable.primaryKey,
570
+ schemaTable.primaryKey,
571
+ tableName,
572
+ issues
573
+ );
574
+ if (pkStatus === "fail") {
575
+ tableChildren.push({
576
+ status: "fail",
577
+ kind: "primaryKey",
578
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
579
+ contractPath: `${tablePath}.primaryKey`,
580
+ code: "primary_key_mismatch",
581
+ message: "Primary key mismatch",
582
+ expected: contractTable.primaryKey,
583
+ actual: schemaTable.primaryKey,
584
+ children: []
585
+ });
586
+ } else {
587
+ tableChildren.push({
588
+ status: "pass",
589
+ kind: "primaryKey",
590
+ name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
591
+ contractPath: `${tablePath}.primaryKey`,
592
+ code: "",
593
+ message: "",
594
+ expected: void 0,
595
+ actual: void 0,
596
+ children: []
597
+ });
598
+ }
599
+ } else if (schemaTable.primaryKey && strict) {
600
+ issues.push({
601
+ kind: "extra_primary_key",
602
+ table: tableName,
603
+ message: "Extra primary key found in database (not in contract)"
604
+ });
605
+ tableChildren.push({
606
+ status: "fail",
607
+ kind: "primaryKey",
608
+ name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
609
+ contractPath: `${tablePath}.primaryKey`,
610
+ code: "extra_primary_key",
611
+ message: "Extra primary key found",
612
+ expected: void 0,
613
+ actual: schemaTable.primaryKey,
614
+ children: []
615
+ });
616
+ }
617
+ const fkStatuses = verifyForeignKeys(
618
+ contractTable.foreignKeys,
619
+ schemaTable.foreignKeys,
620
+ tableName,
621
+ tablePath,
622
+ issues,
623
+ strict
624
+ );
625
+ tableChildren.push(...fkStatuses);
626
+ const uniqueStatuses = verifyUniqueConstraints(
627
+ contractTable.uniques,
628
+ schemaTable.uniques,
629
+ tableName,
630
+ tablePath,
631
+ issues,
632
+ strict
633
+ );
634
+ tableChildren.push(...uniqueStatuses);
635
+ const indexStatuses = verifyIndexes(
636
+ contractTable.indexes,
637
+ schemaTable.indexes,
638
+ tableName,
639
+ tablePath,
640
+ issues,
641
+ strict
642
+ );
643
+ tableChildren.push(...indexStatuses);
644
+ const tableStatus = tableChildren.some((c) => c.status === "fail") ? "fail" : tableChildren.some((c) => c.status === "warn") ? "warn" : "pass";
645
+ const tableFailureMessages = tableChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
646
+ const tableMessage = tableStatus === "fail" && tableFailureMessages.length > 0 ? `${tableFailureMessages.length} issue${tableFailureMessages.length === 1 ? "" : "s"}` : "";
647
+ const tableCode = tableStatus === "fail" && tableChildren.length > 0 && tableChildren[0] ? tableChildren[0].code : "";
648
+ rootChildren.push({
649
+ status: tableStatus,
650
+ kind: "table",
651
+ name: `table ${tableName}`,
652
+ contractPath: tablePath,
653
+ code: tableCode,
654
+ message: tableMessage,
655
+ expected: void 0,
656
+ actual: void 0,
657
+ children: tableChildren
658
+ });
659
+ }
660
+ if (strict) {
661
+ for (const tableName of Object.keys(schemaTables)) {
662
+ if (!contractTables[tableName]) {
663
+ issues.push({
664
+ kind: "extra_table",
665
+ table: tableName,
666
+ message: `Extra table "${tableName}" found in database (not in contract)`
667
+ });
668
+ rootChildren.push({
669
+ status: "fail",
670
+ kind: "table",
671
+ name: `table ${tableName}`,
672
+ contractPath: `storage.tables.${tableName}`,
673
+ code: "extra_table",
674
+ message: `Extra table "${tableName}" found`,
675
+ expected: void 0,
676
+ actual: void 0,
677
+ children: []
678
+ });
679
+ }
680
+ }
681
+ }
682
+ const contractExtensions = contract.extensions ?? {};
683
+ for (const extensionNamespace of Object.keys(contractExtensions)) {
684
+ const hasExtension = options.frameworkComponents.some(
685
+ (component) => component.kind === "extension" && component.id === extensionNamespace
686
+ );
687
+ if (!hasExtension) {
688
+ throw new Error(
689
+ `Extension '${extensionNamespace}' is declared in the contract but not found in framework components. This indicates a configuration mismatch - the contract was emitted with this extension, but it is not provided in the current configuration.`
690
+ );
691
+ }
692
+ }
693
+ const databaseDependencies = collectDependenciesFromFrameworkComponents(
694
+ options.frameworkComponents
695
+ );
696
+ const dependencyStatuses = verifyDatabaseDependencies(databaseDependencies, schema, issues);
697
+ rootChildren.push(...dependencyStatuses);
698
+ const rootStatus = rootChildren.some((c) => c.status === "fail") ? "fail" : rootChildren.some((c) => c.status === "warn") ? "warn" : "pass";
699
+ const root = {
700
+ status: rootStatus,
701
+ kind: "contract",
702
+ name: "contract",
703
+ contractPath: "",
704
+ code: "",
705
+ message: "",
706
+ expected: void 0,
707
+ actual: void 0,
708
+ children: rootChildren
709
+ };
710
+ const counts = computeCounts(root);
711
+ const ok = counts.fail === 0;
712
+ const code = ok ? void 0 : "PN-SCHEMA-0001";
713
+ const summary = ok ? "Database schema satisfies contract" : `Database schema does not satisfy contract (${counts.fail} failure${counts.fail === 1 ? "" : "s"})`;
714
+ const totalTime = Date.now() - startTime;
715
+ return {
716
+ ok,
717
+ ...ifDefined("code", code),
718
+ summary,
719
+ contract: {
720
+ coreHash: contractCoreHash,
721
+ ...ifDefined("profileHash", contractProfileHash)
722
+ },
723
+ target: {
724
+ expected: contractTarget,
725
+ actual: contractTarget
726
+ },
727
+ schema: {
728
+ issues,
729
+ root,
730
+ counts
731
+ },
732
+ meta: {
733
+ strict,
734
+ ...ifDefined("contractPath", context?.contractPath),
735
+ ...ifDefined("configPath", context?.configPath)
736
+ },
737
+ timings: {
738
+ total: totalTime
739
+ }
740
+ };
741
+ }
742
+ function hasDatabaseDependenciesInit(component) {
743
+ if (!("databaseDependencies" in component)) {
744
+ return false;
745
+ }
746
+ const dbDeps = component["databaseDependencies"];
747
+ if (dbDeps === void 0 || dbDeps === null || typeof dbDeps !== "object") {
748
+ return false;
749
+ }
750
+ const depsRecord = dbDeps;
751
+ const init = depsRecord["init"];
752
+ if (init === void 0 || !Array.isArray(init)) {
753
+ return false;
754
+ }
755
+ return true;
756
+ }
757
+ function collectDependenciesFromFrameworkComponents(components) {
758
+ const dependencies = [];
759
+ for (const component of components) {
760
+ if (hasDatabaseDependenciesInit(component)) {
761
+ dependencies.push(...component.databaseDependencies.init);
762
+ }
763
+ }
764
+ return dependencies;
765
+ }
766
+
767
+ export {
768
+ arraysEqual,
769
+ verifyDatabaseDependencies,
770
+ verifySqlSchema
771
+ };
772
+ //# sourceMappingURL=chunk-54XYY6SU.js.map