@restforgejs/platform 5.1.7 → 5.1.16

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 (201) hide show
  1. package/bin/restforge-hwinfo-linux +0 -0
  2. package/bin/restforge-hwinfo.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/payload/generate.js +10 -2
  7. package/generators/cli/schema/apply.js +6 -1
  8. package/generators/cli/schema/diff.js +6 -1
  9. package/generators/cli/schema/introspect.js +32 -11
  10. package/generators/lib/data/db-executor.js +8 -8
  11. package/generators/lib/data/envelope.js +3 -3
  12. package/generators/lib/dbschema-kit/apply-engine.js +20 -0
  13. package/generators/lib/dbschema-kit/dialect/mysql.js +2 -0
  14. package/generators/lib/dbschema-kit/dialect/oracle.js +2 -0
  15. package/generators/lib/dbschema-kit/dialect/postgres.js +4 -0
  16. package/generators/lib/dbschema-kit/dialect/sqlite.js +5 -0
  17. package/generators/lib/dbschema-kit/diff-engine.js +22 -1
  18. package/generators/lib/dbschema-kit/diff-reporter.js +293 -272
  19. package/generators/lib/dbschema-kit/emitters/create-index.js +23 -1
  20. package/generators/lib/dbschema-kit/emitters/create-table.js +48 -0
  21. package/generators/lib/dbschema-kit/introspect-mapper.js +154 -2
  22. package/generators/lib/dbschema-kit/ir-builder.js +84 -1
  23. package/generators/lib/dbschema-kit/schema-printer.js +20 -0
  24. package/generators/lib/dbschema-kit/soft-delete-constants.js +111 -0
  25. package/generators/lib/dbschema-kit/validator/schema-validator.js +231 -0
  26. package/generators/lib/generators/processor-validation-generator.js +16 -16
  27. package/generators/lib/payload/payload-runner.js +711 -1
  28. package/generators/lib/payload/schema-diff.js +7 -0
  29. package/generators/lib/templates/dashboard-catalog.js +1 -1
  30. package/generators/lib/templates/db-connection-env.js +1 -1
  31. package/generators/lib/templates/dbschema-catalog.js +1 -1
  32. package/generators/lib/templates/field-validation-catalog.js +1 -1
  33. package/generators/lib/templates/mysql-template.js +1 -1
  34. package/generators/lib/templates/oracle-template.js +1 -1
  35. package/generators/lib/templates/postgres-template.js +1 -1
  36. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  37. package/generators/lib/templates/sqlite-template.js +1 -1
  38. package/generators/lib/utils/database-introspector.js +48 -0
  39. package/generators/lib/utils/env-manager.js +4 -4
  40. package/generators/lib/utils/file-utils.js +6 -6
  41. package/generators/lib/utils/payload-processor.js +18 -2
  42. package/generators/lib/validators/argument-validator.js +2 -2
  43. package/generators/lib/validators/dashboard-validator.js +35 -1
  44. package/generators/lib/validators/payload-validator.js +460 -33
  45. package/integrity-manifest.json +20 -20
  46. package/package.json +2 -1
  47. package/scripts/verify-integrity.js +1 -1
  48. package/server.js +1 -1
  49. package/src/components/handlers/adjust_handler.js +1 -1
  50. package/src/components/handlers/audit_handler.js +1 -1
  51. package/src/components/handlers/delete_handler.js +1 -1
  52. package/src/components/handlers/export_handler.js +1 -1
  53. package/src/components/handlers/import_handler.js +1 -1
  54. package/src/components/handlers/insert_handler.js +1 -1
  55. package/src/components/handlers/update_handler.js +1 -1
  56. package/src/components/handlers/upload_handler.js +1 -1
  57. package/src/components/handlers/workflow_handler.js +1 -1
  58. package/src/components/integrations/webhook.js +1 -1
  59. package/src/consumers/baseConsumer.js +1 -1
  60. package/src/consumers/declarativeMapper.js +1 -1
  61. package/src/consumers/handlers/apiHandler.js +1 -1
  62. package/src/consumers/handlers/consoleHandler.js +1 -1
  63. package/src/consumers/handlers/databaseHandler.js +1 -1
  64. package/src/consumers/handlers/index.js +1 -1
  65. package/src/consumers/handlers/kafkaHandler.js +1 -1
  66. package/src/consumers/index.js +1 -1
  67. package/src/consumers/messageTransformer.js +1 -1
  68. package/src/consumers/validator.js +1 -1
  69. package/src/core/db/dialect/base-dialect.js +1 -1
  70. package/src/core/db/dialect/index.js +1 -1
  71. package/src/core/db/dialect/mysql-dialect.js +1 -1
  72. package/src/core/db/dialect/oracle-dialect.js +1 -1
  73. package/src/core/db/dialect/postgres-dialect.js +1 -1
  74. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  75. package/src/core/db/flatten-helper.js +1 -1
  76. package/src/core/db/query-builder-error.js +1 -1
  77. package/src/core/db/query-builder.js +1 -1
  78. package/src/core/db/relation-helper.js +1 -1
  79. package/src/core/handlers/delete_handler.js +1 -1
  80. package/src/core/handlers/insert_handler.js +1 -1
  81. package/src/core/handlers/update_handler.js +1 -1
  82. package/src/core/models/base-model.js +1 -1
  83. package/src/core/utils/cache-manager.js +1 -1
  84. package/src/core/utils/component-engine.js +1 -1
  85. package/src/core/utils/context-builder.js +1 -1
  86. package/src/core/utils/datetime-formatter.js +1 -1
  87. package/src/core/utils/datetime-parser.js +1 -1
  88. package/src/core/utils/db.js +1 -1
  89. package/src/core/utils/logger.js +1 -1
  90. package/src/core/utils/payload-loader.js +1 -1
  91. package/src/core/utils/security-checks.js +1 -1
  92. package/src/middleware/body-options.js +1 -1
  93. package/src/middleware/cors.js +1 -1
  94. package/src/middleware/idempotency.js +1 -1
  95. package/src/middleware/rate-limiter.js +1 -1
  96. package/src/middleware/request-logger.js +1 -1
  97. package/src/middleware/security-headers.js +1 -1
  98. package/src/models/base-model-mysql.js +1 -1
  99. package/src/models/base-model-oracle.js +1 -1
  100. package/src/models/base-model-sqlite.js +1 -1
  101. package/src/models/base-model.js +1 -1
  102. package/src/pro/caching/redis-client.js +1 -1
  103. package/src/pro/caching/redis-helper.js +1 -1
  104. package/src/pro/consumers/baseConsumer.js +1 -1
  105. package/src/pro/consumers/declarativeMapper.js +1 -1
  106. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  107. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  108. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  109. package/src/pro/consumers/handlers/index.js +1 -1
  110. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  111. package/src/pro/consumers/index.js +1 -1
  112. package/src/pro/consumers/messageTransformer.js +1 -1
  113. package/src/pro/consumers/validator.js +1 -1
  114. package/src/pro/database/base-model-mysql.js +1 -1
  115. package/src/pro/database/base-model-oracle.js +1 -1
  116. package/src/pro/database/base-model-sqlite.js +1 -1
  117. package/src/pro/database/db-mysql.js +1 -1
  118. package/src/pro/database/db-oracle.js +1 -1
  119. package/src/pro/database/db-sqlite.js +1 -1
  120. package/src/pro/excel/excel-generator.js +1 -1
  121. package/src/pro/excel/excel-parser.js +1 -1
  122. package/src/pro/excel/export-service.js +1 -1
  123. package/src/pro/excel/export_handler.js +1 -1
  124. package/src/pro/excel/import-service.js +1 -1
  125. package/src/pro/excel/import-validator.js +1 -1
  126. package/src/pro/excel/import_handler.js +1 -1
  127. package/src/pro/excel/upsert-builder.js +1 -1
  128. package/src/pro/idgen/idgen-routes.js +1 -1
  129. package/src/pro/integrations/lookup-resolver.js +1 -1
  130. package/src/pro/integrations/upload-handler-v2.js +1 -1
  131. package/src/pro/integrations/upload-handler.js +1 -1
  132. package/src/pro/integrations/webhook.js +1 -1
  133. package/src/pro/locking/lock-routes.js +1 -1
  134. package/src/pro/locking/resource-lock-manager.js +1 -1
  135. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  136. package/src/pro/messaging/kafkaService.js +1 -1
  137. package/src/pro/messaging/messagehubService.js +1 -1
  138. package/src/pro/messaging/rabbitmqService.js +1 -1
  139. package/src/pro/scheduler/job-manager.js +1 -1
  140. package/src/pro/scheduler/job-routes.js +1 -1
  141. package/src/pro/scheduler/job-validator.js +1 -1
  142. package/src/pro/storage/base-storage-provider.js +1 -1
  143. package/src/pro/storage/file-metadata-helper.js +1 -1
  144. package/src/pro/storage/index.js +1 -1
  145. package/src/pro/storage/local-storage-provider.js +1 -1
  146. package/src/pro/storage/s3-storage-provider.js +1 -1
  147. package/src/pro/storage/upload-cleanup-job.js +1 -1
  148. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  149. package/src/pro/storage/upload-pending-tracker.js +1 -1
  150. package/src/pro/websocket/broadcast-helper.js +1 -1
  151. package/src/pro/websocket/index.js +1 -1
  152. package/src/pro/websocket/livesync-server.js +1 -1
  153. package/src/pro/websocket/ws-broadcaster.js +1 -1
  154. package/src/services/export-service.js +1 -1
  155. package/src/services/import-service.js +1 -1
  156. package/src/services/kafkaConsumerService.js +1 -1
  157. package/src/services/kafkaService.js +1 -1
  158. package/src/services/messagehubService.js +1 -1
  159. package/src/services/rabbitmqService.js +1 -1
  160. package/src/utils/cache-invalidation-registry.js +1 -1
  161. package/src/utils/cache-manager.js +1 -1
  162. package/src/utils/component-engine.js +1 -1
  163. package/src/utils/config-extractor.js +1 -1
  164. package/src/utils/consumerLogger.js +1 -1
  165. package/src/utils/context-builder.js +1 -1
  166. package/src/utils/dashboard-helpers.js +1 -1
  167. package/src/utils/dateHelper.js +1 -1
  168. package/src/utils/datetime-formatter.js +1 -1
  169. package/src/utils/datetime-parser.js +1 -1
  170. package/src/utils/db-bootstrap.js +1 -1
  171. package/src/utils/db-mysql.js +1 -1
  172. package/src/utils/db-oracle.js +1 -1
  173. package/src/utils/db-sqlite.js +1 -1
  174. package/src/utils/db.js +1 -1
  175. package/src/utils/demo-generator.js +1 -1
  176. package/src/utils/excel-generator.js +1 -1
  177. package/src/utils/excel-parser.js +1 -1
  178. package/src/utils/file-watcher.js +1 -1
  179. package/src/utils/id-generator.js +1 -1
  180. package/src/utils/idempotency-manager.js +1 -1
  181. package/src/utils/import-validator.js +1 -1
  182. package/src/utils/license-client.js +1 -1
  183. package/src/utils/lock-manager.js +1 -1
  184. package/src/utils/logger.js +1 -1
  185. package/src/utils/lookup-resolver.js +1 -1
  186. package/src/utils/payload-loader.js +1 -1
  187. package/src/utils/processor-response.js +1 -1
  188. package/src/utils/rabbitmq.js +1 -1
  189. package/src/utils/redis-client.js +1 -1
  190. package/src/utils/redis-helper.js +1 -1
  191. package/src/utils/request-scope.js +1 -1
  192. package/src/utils/security-checks.js +1 -1
  193. package/src/utils/service-resolver.js +1 -1
  194. package/src/utils/shutdown-coordinator.js +1 -1
  195. package/src/utils/soft-delete-dashboard-guard.js +1 -0
  196. package/src/utils/sql-table-extractor.js +1 -0
  197. package/src/utils/trusted-keys.js +1 -1
  198. package/src/utils/upload-handler.js +1 -1
  199. package/src/utils/upsert-builder.js +1 -1
  200. package/src/utils/workflow-hook-executor.js +1 -1
  201. package/generators/lib/utils/sql-table-extractor.js +0 -83
@@ -80,6 +80,7 @@ async function collectTableMeta(introspector, target) {
80
80
  const constraints = await introspector.getConstraints(ref);
81
81
  const foreignKeys = await introspector.getForeignKeys(ref);
82
82
  const indexes = await introspector.getIndexes(ref);
83
+ const checks = await introspector.getCheckConstraints(ref);
83
84
 
84
85
  const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
85
86
  const uniqueConstraints = constraints
@@ -94,7 +95,11 @@ async function collectTableMeta(introspector, target) {
94
95
  uniques: uniqueConstraints,
95
96
  foreignKeys,
96
97
  indexes,
97
- checks: []
98
+ // introspect-mapper expects { name, expression }; getCheckConstraints
99
+ // returns { name, clause } (D5 wiring, Phase 03).
100
+ checks: Array.isArray(checks)
101
+ ? checks.map((c) => ({ name: c.name, expression: c.clause }))
102
+ : []
98
103
  };
99
104
  }
100
105
 
@@ -69,6 +69,7 @@ async function collectTableMeta(introspector, target) {
69
69
  const constraints = await introspector.getConstraints(ref);
70
70
  const foreignKeys = await introspector.getForeignKeys(ref);
71
71
  const indexes = await introspector.getIndexes(ref);
72
+ const checks = await introspector.getCheckConstraints(ref);
72
73
 
73
74
  const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
74
75
  const uniqueConstraints = constraints
@@ -83,7 +84,11 @@ async function collectTableMeta(introspector, target) {
83
84
  uniques: uniqueConstraints,
84
85
  foreignKeys,
85
86
  indexes,
86
- checks: []
87
+ // introspect-mapper expects { name, expression }; getCheckConstraints
88
+ // returns { name, clause } (D5 wiring, Phase 03).
89
+ checks: Array.isArray(checks)
90
+ ? checks.map((c) => ({ name: c.name, expression: c.clause }))
91
+ : []
87
92
  };
88
93
  }
89
94
 
@@ -146,6 +146,7 @@ async function collectTableMeta(introspector, target) {
146
146
  const constraints = await introspector.getConstraints(ref);
147
147
  const foreignKeys = await introspector.getForeignKeys(ref);
148
148
  const indexes = await introspector.getIndexes(ref);
149
+ const checks = await introspector.getCheckConstraints(ref);
149
150
 
150
151
  const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
151
152
  const uniqueConstraints = constraints
@@ -160,7 +161,11 @@ async function collectTableMeta(introspector, target) {
160
161
  uniques: uniqueConstraints,
161
162
  foreignKeys,
162
163
  indexes,
163
- checks: []
164
+ // introspect-mapper expects { name, expression }; getCheckConstraints
165
+ // returns { name, clause } (D5 wiring, Phase 03).
166
+ checks: Array.isArray(checks)
167
+ ? checks.map((c) => ({ name: c.name, expression: c.clause }))
168
+ : []
164
169
  };
165
170
  }
166
171
 
@@ -406,16 +411,32 @@ module.exports = {
406
411
  }
407
412
 
408
413
  const dialect = config.dialect;
409
- const emitted = result.tables.map((tableMeta) => {
410
- const ir = mapTableMetaToIR(tableMeta, dialect);
411
- const source = serialize(ir);
412
- return {
413
- tableName: ir.tableName,
414
- schemaName: ir.schemaName,
415
- qualifiedName: ir.qualifiedName,
416
- source
417
- };
418
- });
414
+ let emitted;
415
+ try {
416
+ emitted = result.tables.map((tableMeta) => {
417
+ // strictSoftDelete: introspect ERROR (R6) bila kolom soft-delete tidak
418
+ // lengkap / tipe salah / CHECK konsistensi hilang, dan tidak menghasilkan SDF.
419
+ const ir = mapTableMetaToIR(tableMeta, dialect, { strictSoftDelete: true });
420
+ const source = serialize(ir);
421
+ return {
422
+ tableName: ir.tableName,
423
+ schemaName: ir.schemaName,
424
+ qualifiedName: ir.qualifiedName,
425
+ source
426
+ };
427
+ });
428
+ } catch (err) {
429
+ if (err && err.isSoftDeleteIntrospectError) {
430
+ // Pesan multi-baris (R6/R8) sudah lengkap; print apa adanya lalu rethrow
431
+ // silent agar cli-entry tidak menambah prefix "Error: " yang merusak format.
432
+ console.error(err.message);
433
+ const e = new Error(err.message);
434
+ e.silent = true;
435
+ e.exitCode = 1;
436
+ throw e;
437
+ }
438
+ throw err;
439
+ }
419
440
 
420
441
  const schemaSet = new Set();
421
442
  for (const item of emitted) {
@@ -38,33 +38,33 @@ function loadDriver(dialect) {
38
38
  case 'postgresql':
39
39
  if (!pgDriver) {
40
40
  try { pgDriver = require('pg'); } catch (_e) {
41
- throw new Error('PostgreSQL driver (pg) tidak terpasang. Jalankan: npm install pg');
41
+ throw new Error('PostgreSQL driver (pg) is not installed. Run: npm install pg');
42
42
  }
43
43
  }
44
44
  return pgDriver;
45
45
  case 'mysql':
46
46
  if (!mysqlDriver) {
47
47
  try { mysqlDriver = require('mysql2/promise'); } catch (_e) {
48
- throw new Error('MySQL driver (mysql2) tidak terpasang. Jalankan: npm install mysql2');
48
+ throw new Error('MySQL driver (mysql2) is not installed. Run: npm install mysql2');
49
49
  }
50
50
  }
51
51
  return mysqlDriver;
52
52
  case 'oracle':
53
53
  if (!oracleDriver) {
54
54
  try { oracleDriver = require('oracledb'); } catch (_e) {
55
- throw new Error('Oracle driver (oracledb) tidak terpasang. Jalankan: npm install oracledb');
55
+ throw new Error('Oracle driver (oracledb) is not installed. Run: npm install oracledb');
56
56
  }
57
57
  }
58
58
  return oracleDriver;
59
59
  case 'sqlite':
60
60
  if (!sqliteDriver) {
61
61
  try { sqliteDriver = require('better-sqlite3'); } catch (_e) {
62
- throw new Error('SQLite driver (better-sqlite3) tidak terpasang. Jalankan: npm rebuild better-sqlite3');
62
+ throw new Error('SQLite driver (better-sqlite3) is not installed. Run: npm rebuild better-sqlite3');
63
63
  }
64
64
  }
65
65
  return sqliteDriver;
66
66
  default:
67
- throw new Error(`Dialect tidak didukung: ${dialect}. Didukung: postgresql, mysql, oracle, sqlite`);
67
+ throw new Error(`Unsupported dialect: ${dialect}. Supported: postgresql, mysql, oracle, sqlite`);
68
68
  }
69
69
  }
70
70
 
@@ -362,7 +362,7 @@ function createSqliteExecutor(config) {
362
362
  const Database = loadDriver('sqlite');
363
363
  const filePath = config.file || config.database;
364
364
  if (!filePath) {
365
- throw new Error('SQLite: path file database wajib ada di config (field "file" atau "database")');
365
+ throw new Error('SQLite: the database file path is required in config (field "file" or "database")');
366
366
  }
367
367
  let db = null;
368
368
 
@@ -419,7 +419,7 @@ function createSqliteExecutor(config) {
419
419
  */
420
420
  function createExecutor(config) {
421
421
  if (!config || typeof config !== 'object') {
422
- throw new Error('createExecutor: config wajib berupa object hasil loadConfig');
422
+ throw new Error('createExecutor: config must be an object produced by loadConfig');
423
423
  }
424
424
  const dialect = normalizeDialect(config.dialect);
425
425
  switch (dialect) {
@@ -428,7 +428,7 @@ function createExecutor(config) {
428
428
  case 'oracle': return createOracleExecutor(config);
429
429
  case 'sqlite': return createSqliteExecutor(config);
430
430
  default:
431
- throw new Error(`createExecutor: dialect tidak didukung '${config.dialect}'`);
431
+ throw new Error(`createExecutor: unsupported dialect '${config.dialect}'`);
432
432
  }
433
433
  }
434
434
 
@@ -55,7 +55,7 @@ function resolveSink(sink) {
55
55
  if (typeof sink.write === 'function') {
56
56
  return { write: (chunk) => sink.write(chunk), buffer: null };
57
57
  }
58
- throw new Error('EnvelopeWriter: sink harus berupa fungsi, object dengan method write(), atau undefined');
58
+ throw new Error('EnvelopeWriter: sink must be a function, an object with a write() method, or undefined');
59
59
  }
60
60
 
61
61
  /**
@@ -116,7 +116,7 @@ class EnvelopeWriter {
116
116
  */
117
117
  appendRows(rows) {
118
118
  if (this._state !== 'header') {
119
- throw new Error('EnvelopeWriter: appendRows() harus dipanggil setelah writeHeader() dan sebelum end()');
119
+ throw new Error('EnvelopeWriter: appendRows() must be called after writeHeader() and before end()');
120
120
  }
121
121
  if (!Array.isArray(rows)) {
122
122
  throw new Error('EnvelopeWriter: appendRows() membutuhkan array rows');
@@ -138,7 +138,7 @@ class EnvelopeWriter {
138
138
  throw new Error('EnvelopeWriter: end() dipanggil sebelum writeHeader()');
139
139
  }
140
140
  if (this._state === 'ended') {
141
- throw new Error('EnvelopeWriter: end() sudah dipanggil');
141
+ throw new Error('EnvelopeWriter: end() has already been called');
142
142
  }
143
143
  this._write(']}');
144
144
  this._state = 'ended';
@@ -645,6 +645,26 @@ function noteDeferredSections(delta, skipped) {
645
645
  });
646
646
  }
647
647
  }
648
+
649
+ // Soft-delete CHECK drift — Detection-only di Fase 1 (TIDAK ada emitter ALTER ADD
650
+ // CHECK). apply tidak meng-auto-retrofit; cukup laporkan + rekomendasi recreate/manual.
651
+ if (delta.softDelete && delta.softDelete.match === false) {
652
+ const sd = delta.softDelete;
653
+ const description = (sd.sdf && !sd.db)
654
+ ? `Soft-delete consistency CHECK missing in DB for '${delta.tableName}'. ` +
655
+ `Detection-only in Phase 1 (no ALTER ADD CHECK): retrofit via ` +
656
+ `'schema migrate --drop' (recreate) or add the CHECK manually.`
657
+ : `Soft-delete state mismatch for '${delta.tableName}' ` +
658
+ `(DB has the consistency CHECK, SDF does not declare softDelete.enabled). ` +
659
+ `Detection-only: reconcile SDF/DB manually.`;
660
+ skipped.push({
661
+ table: delta.tableName,
662
+ kind: 'soft-delete-check',
663
+ target: 'soft_delete_consistency',
664
+ reason: 'detection-only',
665
+ description
666
+ });
667
+ }
648
668
  }
649
669
 
650
670
  // ─────────────────────────────────────────────────────────────
@@ -84,6 +84,8 @@ module.exports = {
84
84
  maxIdentifierLength: 64,
85
85
  // Boolean disimpan sebagai VARCHAR(5) 'true'/'false'; bukan native.
86
86
  nativeBoolean: false,
87
+ // MySQL tidak punya partial index efisien; index soft-delete jadi plain (R11).
88
+ supportsPartialIndex: false,
87
89
  mapType,
88
90
  formatDefault,
89
91
  quoteIdentifier,
@@ -89,6 +89,8 @@ module.exports = {
89
89
  maxIdentifierLength: 128,
90
90
  // Boolean disimpan sebagai VARCHAR2(5) 'true'/'false'; bukan native.
91
91
  nativeBoolean: false,
92
+ // Oracle tidak punya partial index efisien; index soft-delete jadi plain (R11).
93
+ supportsPartialIndex: false,
92
94
  mapType,
93
95
  formatDefault,
94
96
  quoteIdentifier,
@@ -84,6 +84,10 @@ module.exports = {
84
84
  // PostgreSQL punya tipe BOOLEAN native; dialect lain menyimpan boolean
85
85
  // sebagai VARCHAR 'true'/'false' dan butuh CHECK sebagai penanda boolean.
86
86
  nativeBoolean: true,
87
+ // PostgreSQL mendukung partial index (`CREATE INDEX ... WHERE <predikat>`).
88
+ // Dipakai R10/R11: index non-unique pada tabel soft-delete jadi partial
89
+ // (`WHERE is_deleted = FALSE`).
90
+ supportsPartialIndex: true,
87
91
  mapType,
88
92
  formatDefault,
89
93
  quoteIdentifier,
@@ -87,6 +87,11 @@ module.exports = {
87
87
  maxIdentifierLength: 63,
88
88
  // Boolean disimpan sebagai VARCHAR(5) 'true'/'false'; bukan native.
89
89
  nativeBoolean: false,
90
+ // Flag ini menandai apakah index non-unique soft-delete dimaterialisasi sebagai
91
+ // partial index (R10/R11). Per plan master R11, SQLite masuk grup plain index
92
+ // (bersama MySQL/Oracle), jadi false. (Soft-delete Fase 1 juga PostgreSQL-only via
93
+ // guard di emitter, sehingga jalur ini belum aktif untuk SQLite.)
94
+ supportsPartialIndex: false,
90
95
  mapType,
91
96
  formatDefault,
92
97
  quoteIdentifier,
@@ -588,6 +588,24 @@ function diffChecks(sdfChecks, dbChecks) {
588
588
  return { onlyInSdf, onlyInDb, mismatched };
589
589
  }
590
590
 
591
+ // ─────────────────────────────────────────────────────────────
592
+ // Soft-delete comparison (R8 poin 2 / R6 reverse)
593
+ // ─────────────────────────────────────────────────────────────
594
+
595
+ // Bandingkan status soft-delete kedua IR. CHECK konsistensi soft-delete bersifat
596
+ // IMPLISIT (diturunkan dari `softDelete.enabled`, tidak pernah di-list di `checks`).
597
+ // introspect-mapper men-set `dbIR.softDelete = { enabled: true }` HANYA bila tabel DB
598
+ // valid (3 kolom + tipe benar + CHECK ada by-nama), dan mengecualikan CHECK soft-delete
599
+ // dari `checks` di kedua sisi. Maka:
600
+ // - enabled di kedua sisi → CHECK implisit expected di keduanya → tidak ada drift.
601
+ // - SDF enabled tetapi DB tidak (CHECK absen / kolom-tipe tak valid) → drift NYATA
602
+ // (Detection-only; dilaporkan apa adanya, bukan disembunyikan, bukan di-auto-fix).
603
+ function diffSoftDelete(sdfIR, dbIR) {
604
+ const sdf = !!(sdfIR && sdfIR.softDelete && sdfIR.softDelete.enabled === true);
605
+ const db = !!(dbIR && dbIR.softDelete && dbIR.softDelete.enabled === true);
606
+ return { sdf, db, match: sdf === db };
607
+ }
608
+
591
609
  // ─────────────────────────────────────────────────────────────
592
610
  // Top-level API
593
611
  // ─────────────────────────────────────────────────────────────
@@ -610,6 +628,7 @@ function hasAnyDrift(delta) {
610
628
  if (delta.checks && delta.checks.onlyInSdf.length > 0) return true;
611
629
  if (delta.checks && delta.checks.onlyInDb.length > 0) return true;
612
630
  if (delta.checks && delta.checks.mismatched.length > 0) return true;
631
+ if (delta.softDelete && delta.softDelete.match === false) return true;
613
632
  return false;
614
633
  }
615
634
 
@@ -631,7 +650,8 @@ function diffModel(sdfIR, dbIR) {
631
650
  indexes: diffIndexes(sdfIR.indexes, dbIR.indexes),
632
651
  uniques: diffUniques(sdfIR.uniques, dbIR.uniques),
633
652
  foreignKeys: diffForeignKeys(sdfIR, dbIR),
634
- checks: diffChecks(sdfIR.checks, dbIR.checks)
653
+ checks: diffChecks(sdfIR.checks, dbIR.checks),
654
+ softDelete: diffSoftDelete(sdfIR, dbIR)
635
655
  };
636
656
  delta.hasDrift = hasAnyDrift(delta);
637
657
 
@@ -706,6 +726,7 @@ module.exports = {
706
726
  diffForeignKeys,
707
727
  extractForeignKeys,
708
728
  diffChecks,
729
+ diffSoftDelete,
709
730
  compareDefaultValue,
710
731
  canonicalDefaultPayload,
711
732
  normalizeWeakDefault,