@restforgejs/platform 4.3.8 → 5.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.
Files changed (193) hide show
  1. package/bin/sdf-tools.exe +0 -0
  2. package/build-info.json +2 -2
  3. package/cli/consumer-deploy.js +1 -1
  4. package/cli/consumer.js +1 -1
  5. package/generators/cli/init.js +4 -104
  6. package/generators/cli/payload/migrate.js +96 -96
  7. package/generators/cli/schema/list.js +82 -18
  8. package/generators/cli/schema/migrate.js +23 -3
  9. package/generators/lib/dbschema-kit/apply-engine.js +211 -46
  10. package/generators/lib/dbschema-kit/diff-engine.js +715 -703
  11. package/generators/lib/dbschema-kit/emitters/alter-table.js +96 -2
  12. package/generators/lib/dbschema-kit/introspect-mapper.js +9 -0
  13. package/generators/lib/migrate/backend-payload-migrator.js +221 -221
  14. package/generators/lib/migrate/field-type-resolver.js +325 -319
  15. package/generators/lib/migrate/label-generator.js +38 -38
  16. package/generators/lib/migrate/migrate-runner.js +244 -38
  17. package/generators/lib/migrate/naming.js +52 -43
  18. package/generators/lib/migrate/sql-parser.js +124 -124
  19. package/generators/lib/templates/dashboard-catalog.js +1 -1
  20. package/generators/lib/templates/db-connection-env.js +1 -1
  21. package/generators/lib/templates/dbschema-catalog.js +1 -1
  22. package/generators/lib/templates/field-validation-catalog.js +1 -1
  23. package/generators/lib/templates/mysql-template.js +1 -1
  24. package/generators/lib/templates/oracle-template.js +1 -1
  25. package/generators/lib/templates/postgres-template.js +1 -1
  26. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  27. package/generators/lib/templates/sqlite-template.js +1 -1
  28. package/integrity-manifest.json +18 -18
  29. package/node_modules/brace-expansion/index.js +1 -1
  30. package/node_modules/brace-expansion/package.json +1 -1
  31. package/node_modules/dayjs/CHANGELOG.md +7 -0
  32. package/node_modules/dayjs/README.md +12 -10
  33. package/node_modules/dayjs/dayjs.min.js +1 -1
  34. package/node_modules/dayjs/esm/constant.js +1 -1
  35. package/node_modules/dayjs/esm/plugin/duration/index.js +5 -4
  36. package/node_modules/dayjs/locale.json +1 -1
  37. package/node_modules/dayjs/package.json +2 -2
  38. package/node_modules/dayjs/plugin/duration.js +1 -1
  39. package/node_modules/tmp/lib/tmp.js +37 -7
  40. package/node_modules/tmp/package.json +4 -16
  41. package/package.json +1 -1
  42. package/scripts/verify-integrity.js +1 -1
  43. package/server.js +1 -1
  44. package/src/components/handlers/adjust_handler.js +1 -1
  45. package/src/components/handlers/audit_handler.js +1 -1
  46. package/src/components/handlers/delete_handler.js +1 -1
  47. package/src/components/handlers/export_handler.js +1 -1
  48. package/src/components/handlers/import_handler.js +1 -1
  49. package/src/components/handlers/insert_handler.js +1 -1
  50. package/src/components/handlers/update_handler.js +1 -1
  51. package/src/components/handlers/upload_handler.js +1 -1
  52. package/src/components/handlers/workflow_handler.js +1 -1
  53. package/src/components/integrations/webhook.js +1 -1
  54. package/src/consumers/baseConsumer.js +1 -1
  55. package/src/consumers/declarativeMapper.js +1 -1
  56. package/src/consumers/handlers/apiHandler.js +1 -1
  57. package/src/consumers/handlers/consoleHandler.js +1 -1
  58. package/src/consumers/handlers/databaseHandler.js +1 -1
  59. package/src/consumers/handlers/index.js +1 -1
  60. package/src/consumers/handlers/kafkaHandler.js +1 -1
  61. package/src/consumers/index.js +1 -1
  62. package/src/consumers/messageTransformer.js +1 -1
  63. package/src/consumers/validator.js +1 -1
  64. package/src/core/db/dialect/base-dialect.js +1 -1
  65. package/src/core/db/dialect/index.js +1 -1
  66. package/src/core/db/dialect/mysql-dialect.js +1 -1
  67. package/src/core/db/dialect/oracle-dialect.js +1 -1
  68. package/src/core/db/dialect/postgres-dialect.js +1 -1
  69. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  70. package/src/core/db/flatten-helper.js +1 -1
  71. package/src/core/db/query-builder-error.js +1 -1
  72. package/src/core/db/query-builder.js +1 -1
  73. package/src/core/db/relation-helper.js +1 -1
  74. package/src/core/handlers/delete_handler.js +1 -1
  75. package/src/core/handlers/insert_handler.js +1 -1
  76. package/src/core/handlers/update_handler.js +1 -1
  77. package/src/core/models/base-model.js +1 -1
  78. package/src/core/utils/cache-manager.js +1 -1
  79. package/src/core/utils/component-engine.js +1 -1
  80. package/src/core/utils/context-builder.js +1 -1
  81. package/src/core/utils/datetime-formatter.js +1 -1
  82. package/src/core/utils/datetime-parser.js +1 -1
  83. package/src/core/utils/db.js +1 -1
  84. package/src/core/utils/logger.js +1 -1
  85. package/src/core/utils/payload-loader.js +1 -1
  86. package/src/core/utils/security-checks.js +1 -1
  87. package/src/middleware/body-options.js +1 -1
  88. package/src/middleware/cors.js +1 -1
  89. package/src/middleware/idempotency.js +1 -1
  90. package/src/middleware/rate-limiter.js +1 -1
  91. package/src/middleware/request-logger.js +1 -1
  92. package/src/middleware/security-headers.js +1 -1
  93. package/src/models/base-model-mysql.js +1 -1
  94. package/src/models/base-model-oracle.js +1 -1
  95. package/src/models/base-model-sqlite.js +1 -1
  96. package/src/models/base-model.js +1 -1
  97. package/src/pro/caching/redis-client.js +1 -1
  98. package/src/pro/caching/redis-helper.js +1 -1
  99. package/src/pro/consumers/baseConsumer.js +1 -1
  100. package/src/pro/consumers/declarativeMapper.js +1 -1
  101. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  102. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  103. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  104. package/src/pro/consumers/handlers/index.js +1 -1
  105. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  106. package/src/pro/consumers/index.js +1 -1
  107. package/src/pro/consumers/messageTransformer.js +1 -1
  108. package/src/pro/consumers/validator.js +1 -1
  109. package/src/pro/database/base-model-mysql.js +1 -1
  110. package/src/pro/database/base-model-oracle.js +1 -1
  111. package/src/pro/database/base-model-sqlite.js +1 -1
  112. package/src/pro/database/db-mysql.js +1 -1
  113. package/src/pro/database/db-oracle.js +1 -1
  114. package/src/pro/database/db-sqlite.js +1 -1
  115. package/src/pro/excel/excel-generator.js +1 -1
  116. package/src/pro/excel/excel-parser.js +1 -1
  117. package/src/pro/excel/export-service.js +1 -1
  118. package/src/pro/excel/export_handler.js +1 -1
  119. package/src/pro/excel/import-service.js +1 -1
  120. package/src/pro/excel/import-validator.js +1 -1
  121. package/src/pro/excel/import_handler.js +1 -1
  122. package/src/pro/excel/upsert-builder.js +1 -1
  123. package/src/pro/idgen/idgen-routes.js +1 -1
  124. package/src/pro/integrations/lookup-resolver.js +1 -1
  125. package/src/pro/integrations/upload-handler-v2.js +1 -1
  126. package/src/pro/integrations/upload-handler.js +1 -1
  127. package/src/pro/integrations/webhook.js +1 -1
  128. package/src/pro/locking/lock-routes.js +1 -1
  129. package/src/pro/locking/resource-lock-manager.js +1 -1
  130. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  131. package/src/pro/messaging/kafkaService.js +1 -1
  132. package/src/pro/messaging/messagehubService.js +1 -1
  133. package/src/pro/messaging/rabbitmqService.js +1 -1
  134. package/src/pro/scheduler/job-manager.js +1 -1
  135. package/src/pro/scheduler/job-routes.js +1 -1
  136. package/src/pro/scheduler/job-validator.js +1 -1
  137. package/src/pro/storage/base-storage-provider.js +1 -1
  138. package/src/pro/storage/file-metadata-helper.js +1 -1
  139. package/src/pro/storage/index.js +1 -1
  140. package/src/pro/storage/local-storage-provider.js +1 -1
  141. package/src/pro/storage/s3-storage-provider.js +1 -1
  142. package/src/pro/storage/upload-cleanup-job.js +1 -1
  143. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  144. package/src/pro/storage/upload-pending-tracker.js +1 -1
  145. package/src/pro/websocket/broadcast-helper.js +1 -1
  146. package/src/pro/websocket/index.js +1 -1
  147. package/src/pro/websocket/livesync-server.js +1 -1
  148. package/src/pro/websocket/ws-broadcaster.js +1 -1
  149. package/src/services/export-service.js +1 -1
  150. package/src/services/import-service.js +1 -1
  151. package/src/services/kafkaConsumerService.js +1 -1
  152. package/src/services/kafkaService.js +1 -1
  153. package/src/services/messagehubService.js +1 -1
  154. package/src/services/rabbitmqService.js +1 -1
  155. package/src/utils/cache-invalidation-registry.js +1 -1
  156. package/src/utils/cache-manager.js +1 -1
  157. package/src/utils/component-engine.js +1 -1
  158. package/src/utils/config-extractor.js +1 -1
  159. package/src/utils/consumerLogger.js +1 -1
  160. package/src/utils/context-builder.js +1 -1
  161. package/src/utils/dashboard-helpers.js +1 -1
  162. package/src/utils/dateHelper.js +1 -1
  163. package/src/utils/datetime-formatter.js +1 -1
  164. package/src/utils/datetime-parser.js +1 -1
  165. package/src/utils/db-bootstrap.js +1 -1
  166. package/src/utils/db-mysql.js +1 -1
  167. package/src/utils/db-oracle.js +1 -1
  168. package/src/utils/db-sqlite.js +1 -1
  169. package/src/utils/db.js +1 -1
  170. package/src/utils/demo-generator.js +1 -1
  171. package/src/utils/excel-generator.js +1 -1
  172. package/src/utils/excel-parser.js +1 -1
  173. package/src/utils/file-watcher.js +1 -1
  174. package/src/utils/id-generator.js +1 -1
  175. package/src/utils/idempotency-manager.js +1 -1
  176. package/src/utils/import-validator.js +1 -1
  177. package/src/utils/license-client.js +1 -1
  178. package/src/utils/lock-manager.js +1 -1
  179. package/src/utils/logger.js +1 -1
  180. package/src/utils/lookup-resolver.js +1 -1
  181. package/src/utils/payload-loader.js +1 -1
  182. package/src/utils/processor-response.js +1 -1
  183. package/src/utils/rabbitmq.js +1 -1
  184. package/src/utils/redis-client.js +1 -1
  185. package/src/utils/redis-helper.js +1 -1
  186. package/src/utils/request-scope.js +1 -1
  187. package/src/utils/security-checks.js +1 -1
  188. package/src/utils/service-resolver.js +1 -1
  189. package/src/utils/shutdown-coordinator.js +1 -1
  190. package/src/utils/trusted-keys.js +1 -1
  191. package/src/utils/upload-handler.js +1 -1
  192. package/src/utils/upsert-builder.js +1 -1
  193. package/src/utils/workflow-hook-executor.js +1 -1
@@ -5,34 +5,40 @@
5
5
  * statement incremental. Komplemen `ddl-generator.js` yang menghasilkan full
6
6
  * CREATE TABLE.
7
7
  *
8
- * Operasi yang di-handle (MVP):
9
- * - ADD COLUMN (additive, default)
10
- * - DROP COLUMN (destruktif, butuh allowDrop)
11
- * - MODIFY COLUMN (length / nullable, butuh allowModify)
12
- * - CREATE INDEX (additive)
13
- * - DROP INDEX (destruktif, butuh allowDrop)
14
- * - ADD CONSTRAINT UNIQUE (additive)
15
- * - DROP CONSTRAINT UNIQUE (destruktif, butuh allowDrop)
8
+ * Operasi yang di-handle:
9
+ * - ADD COLUMN (additive, default)
10
+ * - DROP COLUMN (destruktif, butuh allowDrop)
11
+ * - MODIFY COLUMN (length / nullable, butuh allowModify)
12
+ * - CREATE INDEX (additive)
13
+ * - DROP INDEX (destruktif, butuh allowDrop)
14
+ * - ADD CONSTRAINT UNIQUE (additive)
15
+ * - DROP CONSTRAINT UNIQUE (destruktif, butuh allowDrop)
16
+ * - ADD FOREIGN KEY (additive, default — kecuali sqlite)
17
+ * - DROP FOREIGN KEY (destruktif, butuh allowDrop — kecuali sqlite)
18
+ * - REPLACE FOREIGN KEY action (destruktif: DROP+ADD, butuh allowModify — kecuali sqlite)
16
19
  *
17
20
  * Operasi yang TIDAK di-handle (deferred):
18
21
  * - ALTER COLUMN type change (butuh data conversion strategy per dialect)
19
22
  * - PK changes (butuh rebuild table)
20
- * - FK changes (butuh cross-model validation + dialect syntax kompleks)
21
23
  * - DEFAULT value changes (defer ke fase berikutnya)
22
24
  * - CHECK constraint changes
25
+ * - FK changes pada sqlite (butuh rebuild table)
23
26
  *
24
- * SQLite tidak mendukung ALTER COLUMN / DROP COLUMN tanpa rebuild table.
25
- * Untuk dialect ini, MODIFY dan DROP COLUMN otomatis di-skip dengan reason
26
- * 'sqlite limitation' walaupun flag opt-in aktif.
27
+ * SQLite tidak mendukung ALTER COLUMN / DROP COLUMN / ALTER ADD CONSTRAINT FK
28
+ * tanpa rebuild table. Untuk dialect ini, MODIFY/DROP COLUMN dan semua perubahan
29
+ * FK otomatis di-skip dengan reason 'sqlite limitation' walaupun flag opt-in
30
+ * aktif.
27
31
  *
28
32
  * Output dipesan dengan urutan aman untuk dependency:
29
- * 1. ADD COLUMN (no dependencies)
30
- * 2. CREATE INDEX (depend on column existence)
31
- * 3. ADD CONSTRAINT UNIQUE (depend on column existence)
32
- * 4. ALTER/MODIFY COLUMN (modify after additive done)
33
- * 5. DROP CONSTRAINT (before drop column to avoid dependency error)
34
- * 6. DROP INDEX (before drop column)
35
- * 7. DROP COLUMN (last)
33
+ * 1. ADD COLUMN (no dependencies)
34
+ * 2. CREATE INDEX (depend on column existence)
35
+ * 3. ADD CONSTRAINT UNIQUE (depend on column existence)
36
+ * 4. ADD CONSTRAINT FOREIGN KEY (depend on column existence di kedua sisi)
37
+ * 5. ALTER/MODIFY COLUMN (modify after additive done)
38
+ * 6. DROP CONSTRAINT FK (release FK sebelum drop unique/index/column)
39
+ * 7. DROP CONSTRAINT UNIQUE (before drop column to avoid dependency error)
40
+ * 8. DROP INDEX (before drop column)
41
+ * 9. DROP COLUMN (last)
36
42
  *
37
43
  * @module lib/dbschema-kit/apply-engine
38
44
  */
@@ -45,7 +51,9 @@ const {
45
51
  emitCreateIndex,
46
52
  emitDropIndex,
47
53
  emitAddUnique,
48
- emitDropUnique
54
+ emitDropUnique,
55
+ emitAddForeignKey,
56
+ emitDropForeignKey
49
57
  } = require('./emitters/alter-table');
50
58
 
51
59
  const VALID_DIALECTS = ['postgres', 'mysql', 'oracle', 'sqlite'];
@@ -142,7 +150,10 @@ function buildBuckets() {
142
150
  addColumns: [],
143
151
  createIndexes: [],
144
152
  addUniques: [],
153
+ addForeignKeys: [],
145
154
  modifyColumns: [],
155
+ dropForeignKeys: [],
156
+ replaceForeignKeysAdd: [],
146
157
  dropConstraints: [],
147
158
  dropIndexes: [],
148
159
  dropColumns: []
@@ -154,7 +165,10 @@ function flattenBuckets(buckets) {
154
165
  ...buckets.addColumns,
155
166
  ...buckets.createIndexes,
156
167
  ...buckets.addUniques,
168
+ ...buckets.addForeignKeys,
157
169
  ...buckets.modifyColumns,
170
+ ...buckets.dropForeignKeys,
171
+ ...buckets.replaceForeignKeysAdd,
158
172
  ...buckets.dropConstraints,
159
173
  ...buckets.dropIndexes,
160
174
  ...buckets.dropColumns
@@ -412,51 +426,201 @@ function processUniques(delta, tableIR, dialect, options, buckets, skipped, summ
412
426
  }
413
427
  }
414
428
 
415
- function noteDeferredSections(delta, skipped) {
416
- // PK changes — deferred dari MVP karena butuh rebuild table.
417
- if (delta.primaryKey && delta.primaryKey.match === false) {
418
- const sdf = Array.isArray(delta.primaryKey.sdf) ? delta.primaryKey.sdf : [];
419
- const db = Array.isArray(delta.primaryKey.db) ? delta.primaryKey.db : [];
420
- skipped.push({
421
- table: delta.tableName,
422
- kind: 'primary-key',
423
- target: `pk(${sdf.join(',') || '∅'} vs ${db.join(',') || '∅'})`,
424
- reason: 'deferred',
425
- description: `Primary key changes for ${delta.tableName} deferred from MVP (requires table rebuild)`
426
- });
427
- }
429
+ // ─────────────────────────────────────────────────────────────
430
+ // FK delta processing
431
+ // ─────────────────────────────────────────────────────────────
428
432
 
429
- if (delta.foreignKeys) {
430
- const fkOnlySdf = Array.isArray(delta.foreignKeys.onlyInSdf) ? delta.foreignKeys.onlyInSdf : [];
431
- const fkOnlyDb = Array.isArray(delta.foreignKeys.onlyInDb) ? delta.foreignKeys.onlyInDb : [];
432
- const fkMismatch = Array.isArray(delta.foreignKeys.mismatched) ? delta.foreignKeys.mismatched : [];
433
+ // FK delta entries dari diff-engine memuat triplet (localKey, target, references)
434
+ // plus optional `name` (relName dari relations map) dan `dbName` (untuk mismatched).
435
+ // Apply path hanya menjalankan validator opsional, jadi kita fallback ke localKey
436
+ // untuk derive constraint name bila relName tidak tersedia.
437
+ function pickRelName(fk) {
438
+ if (fk && typeof fk.name === 'string' && fk.name !== '') return fk.name;
439
+ if (fk && typeof fk.localKey === 'string' && fk.localKey !== '') return fk.localKey;
440
+ return null;
441
+ }
442
+
443
+ function fkSkipTarget(fk) {
444
+ const local = fk && fk.localKey ? fk.localKey : '?';
445
+ const target = fk && fk.target ? fk.target : '?';
446
+ const ref = fk && fk.references ? fk.references : '?';
447
+ return `${local} -> ${target}.${ref}`;
448
+ }
449
+
450
+ function processForeignKeys(delta, tableIR, dialect, options, buckets, skipped, summary) {
451
+ if (!delta.foreignKeys) return;
452
+
453
+ const fkOnlySdf = Array.isArray(delta.foreignKeys.onlyInSdf) ? delta.foreignKeys.onlyInSdf : [];
454
+ const fkOnlyDb = Array.isArray(delta.foreignKeys.onlyInDb) ? delta.foreignKeys.onlyInDb : [];
455
+ const fkMismatch = Array.isArray(delta.foreignKeys.mismatched) ? delta.foreignKeys.mismatched : [];
456
+
457
+ // SQLite: semua perubahan FK butuh table rebuild — defer dengan reason akurat
458
+ if (dialect.name === 'sqlite') {
433
459
  for (const fk of fkOnlySdf) {
434
460
  skipped.push({
435
461
  table: delta.tableName,
436
462
  kind: 'foreign-key',
437
- target: `${fk.localKey} -> ${fk.target}.${fk.references}`,
438
- reason: 'deferred',
439
- description: `Foreign key changes deferred from MVP (additive ${delta.tableName}.${fk.localKey})`
463
+ target: fkSkipTarget(fk),
464
+ reason: 'sqlite limitation',
465
+ description: `SQLite does not support ALTER TABLE ADD CONSTRAINT FOREIGN KEY (${delta.tableName}.${fk.localKey})`
440
466
  });
441
467
  }
442
468
  for (const fk of fkOnlyDb) {
443
469
  skipped.push({
444
470
  table: delta.tableName,
445
471
  kind: 'foreign-key',
446
- target: `${fk.localKey} -> ${fk.target}.${fk.references}`,
447
- reason: 'deferred',
448
- description: `Foreign key changes deferred from MVP (drop ${delta.tableName}.${fk.localKey})`
472
+ target: fkSkipTarget(fk),
473
+ reason: 'sqlite limitation',
474
+ description: `SQLite does not support ALTER TABLE DROP CONSTRAINT FOREIGN KEY (${delta.tableName}.${fk.localKey})`
449
475
  });
450
476
  }
451
477
  for (const fk of fkMismatch) {
452
478
  skipped.push({
453
479
  table: delta.tableName,
454
480
  kind: 'foreign-key',
455
- target: `${fk.localKey} -> ${fk.target}.${fk.references}`,
456
- reason: 'deferred',
457
- description: `Foreign key action change deferred from MVP (${delta.tableName}.${fk.localKey})`
481
+ target: fkSkipTarget(fk),
482
+ reason: 'sqlite limitation',
483
+ description: `SQLite does not support ALTER TABLE for FK action change (${delta.tableName}.${fk.localKey})`
484
+ });
485
+ }
486
+ return;
487
+ }
488
+
489
+ // FK additive (onlyInSdf) — non-destruktif, emit langsung tanpa flag opt-in
490
+ for (const fk of fkOnlySdf) {
491
+ const relName = pickRelName(fk);
492
+ if (!relName) {
493
+ skipped.push({
494
+ table: delta.tableName,
495
+ kind: 'foreign-key',
496
+ target: fkSkipTarget(fk),
497
+ reason: 'emit-error',
498
+ description: `Foreign key for ${delta.tableName} missing both name and localKey`
458
499
  });
500
+ continue;
459
501
  }
502
+ try {
503
+ buckets.addForeignKeys.push(emitAddForeignKey(tableIR, relName, fk, dialect));
504
+ summary.totalAdditions++;
505
+ } catch (err) {
506
+ skipped.push({
507
+ table: delta.tableName,
508
+ kind: 'foreign-key',
509
+ target: fkSkipTarget(fk),
510
+ reason: 'emit-error',
511
+ description: err && err.message ? err.message : String(err)
512
+ });
513
+ }
514
+ }
515
+
516
+ // FK drop (onlyInDb) — destruktif, butuh --allow-drop
517
+ for (const fk of fkOnlyDb) {
518
+ const relName = pickRelName(fk);
519
+ if (!relName) {
520
+ skipped.push({
521
+ table: delta.tableName,
522
+ kind: 'foreign-key',
523
+ target: fkSkipTarget(fk),
524
+ reason: 'emit-error',
525
+ description: `Foreign key for ${delta.tableName} missing both name and localKey`
526
+ });
527
+ continue;
528
+ }
529
+ if (!options.allowDrop) {
530
+ skipped.push({
531
+ table: delta.tableName,
532
+ kind: 'foreign-key',
533
+ target: fkSkipTarget(fk),
534
+ reason: 'requires --allow-drop',
535
+ description: `Drop foreign key on ${delta.tableName}.${fk.localKey} requires --allow-drop`
536
+ });
537
+ continue;
538
+ }
539
+ try {
540
+ // DROP memakai conname aktual dari introspeksi (fk.dbConstraintName) agar
541
+ // statement menargetkan constraint yang benar-benar ada di DB. Bila conname
542
+ // tidak tersedia (driver kustom), emitter fallback ke derivasi nama lama
543
+ // (generateConstraintName) — perilaku legacy, tanpa entry skipped/warning.
544
+ buckets.dropForeignKeys.push(
545
+ emitDropForeignKey(tableIR, relName, dialect, { name: fk.dbConstraintName })
546
+ );
547
+ summary.totalDeletions++;
548
+ } catch (err) {
549
+ skipped.push({
550
+ table: delta.tableName,
551
+ kind: 'foreign-key',
552
+ target: fkSkipTarget(fk),
553
+ reason: 'emit-error',
554
+ description: err && err.message ? err.message : String(err)
555
+ });
556
+ }
557
+ }
558
+
559
+ // FK mismatch (action change) — destruktif (DROP + ADD), butuh --allow-modify
560
+ for (const fk of fkMismatch) {
561
+ const relName = pickRelName(fk);
562
+ if (!relName) {
563
+ skipped.push({
564
+ table: delta.tableName,
565
+ kind: 'foreign-key',
566
+ target: fkSkipTarget(fk),
567
+ reason: 'emit-error',
568
+ description: `Foreign key for ${delta.tableName} missing both name and localKey`
569
+ });
570
+ continue;
571
+ }
572
+ if (!options.allowModify) {
573
+ skipped.push({
574
+ table: delta.tableName,
575
+ kind: 'foreign-key',
576
+ target: fkSkipTarget(fk),
577
+ reason: 'requires --allow-modify',
578
+ description: `Foreign key action change on ${delta.tableName}.${fk.localKey} requires --allow-modify`
579
+ });
580
+ continue;
581
+ }
582
+ try {
583
+ const sdfActions = fk.sdf || {};
584
+ const addRel = {
585
+ localKey: fk.localKey,
586
+ target: fk.target,
587
+ references: fk.references,
588
+ onDelete: sdfActions.onDelete,
589
+ onUpdate: sdfActions.onUpdate
590
+ };
591
+ // DROP memakai conname aktual dari DB (fk.dbConstraintName, fallback derive
592
+ // bila absen); ADD memakai nama SDF-derive (relName) konsisten dengan
593
+ // create-table. ADD masuk replaceForeignKeysAdd bucket supaya terbit SETELAH
594
+ // semua DROP FK, mencegah konflik bila nama lama dan baru kebetulan sama.
595
+ buckets.dropForeignKeys.push(
596
+ emitDropForeignKey(tableIR, relName, dialect, { name: fk.dbConstraintName })
597
+ );
598
+ buckets.replaceForeignKeysAdd.push(emitAddForeignKey(tableIR, relName, addRel, dialect));
599
+ summary.totalModifications++;
600
+ } catch (err) {
601
+ skipped.push({
602
+ table: delta.tableName,
603
+ kind: 'foreign-key',
604
+ target: fkSkipTarget(fk),
605
+ reason: 'emit-error',
606
+ description: err && err.message ? err.message : String(err)
607
+ });
608
+ }
609
+ }
610
+ }
611
+
612
+ function noteDeferredSections(delta, skipped) {
613
+ // PK changes — deferred dari MVP karena butuh rebuild table.
614
+ if (delta.primaryKey && delta.primaryKey.match === false) {
615
+ const sdf = Array.isArray(delta.primaryKey.sdf) ? delta.primaryKey.sdf : [];
616
+ const db = Array.isArray(delta.primaryKey.db) ? delta.primaryKey.db : [];
617
+ skipped.push({
618
+ table: delta.tableName,
619
+ kind: 'primary-key',
620
+ target: `pk(${sdf.join(',') || '∅'} vs ${db.join(',') || '∅'})`,
621
+ reason: 'deferred',
622
+ description: `Primary key changes for ${delta.tableName} deferred from MVP (requires table rebuild)`
623
+ });
460
624
  }
461
625
 
462
626
  if (delta.checks) {
@@ -548,6 +712,7 @@ function generateAlterStatements(deltas, options) {
548
712
  processFieldsMismatched(delta, tableIR, dialect, localOptions, buckets, skipped, sdfModels, summary);
549
713
  processIndexes(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
550
714
  processUniques(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
715
+ processForeignKeys(delta, tableIR, dialect, localOptions, buckets, skipped, summary);
551
716
  noteDeferredSections(delta, skipped);
552
717
 
553
718
  const flat = flattenBuckets(buckets);