@restforgejs/platform 5.1.7 → 5.1.20

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 (208) 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/catalog/dbschema.js +2 -1
  7. package/generators/cli/endpoint/list.js +264 -0
  8. package/generators/cli/fast-track.js +395 -37
  9. package/generators/cli/payload/generate.js +10 -2
  10. package/generators/cli/processor/create.js +7 -7
  11. package/generators/cli/processor/list.js +229 -0
  12. package/generators/cli/schema/apply.js +6 -1
  13. package/generators/cli/schema/diff.js +6 -1
  14. package/generators/cli/schema/introspect.js +32 -11
  15. package/generators/lib/data/db-executor.js +8 -8
  16. package/generators/lib/data/envelope.js +3 -3
  17. package/generators/lib/dbschema-kit/apply-engine.js +20 -0
  18. package/generators/lib/dbschema-kit/dialect/mysql.js +2 -0
  19. package/generators/lib/dbschema-kit/dialect/oracle.js +2 -0
  20. package/generators/lib/dbschema-kit/dialect/postgres.js +4 -0
  21. package/generators/lib/dbschema-kit/dialect/sqlite.js +5 -0
  22. package/generators/lib/dbschema-kit/diff-engine.js +22 -1
  23. package/generators/lib/dbschema-kit/diff-reporter.js +293 -272
  24. package/generators/lib/dbschema-kit/emitters/create-index.js +23 -1
  25. package/generators/lib/dbschema-kit/emitters/create-table.js +48 -0
  26. package/generators/lib/dbschema-kit/introspect-mapper.js +154 -2
  27. package/generators/lib/dbschema-kit/ir-builder.js +84 -1
  28. package/generators/lib/dbschema-kit/schema-printer.js +20 -0
  29. package/generators/lib/dbschema-kit/soft-delete-constants.js +111 -0
  30. package/generators/lib/dbschema-kit/validator/schema-validator.js +231 -0
  31. package/generators/lib/generators/dashboard-generator.js +5 -5
  32. package/generators/lib/generators/processor-validation-generator.js +16 -16
  33. package/generators/lib/payload/payload-runner.js +774 -1
  34. package/generators/lib/payload/schema-diff.js +7 -0
  35. package/generators/lib/templates/dashboard-catalog.js +1 -1
  36. package/generators/lib/templates/db-connection-env.js +1 -1
  37. package/generators/lib/templates/dbschema-catalog.js +1 -1
  38. package/generators/lib/templates/field-validation-catalog.js +1 -1
  39. package/generators/lib/templates/mysql-template.js +1 -1
  40. package/generators/lib/templates/oracle-template.js +1 -1
  41. package/generators/lib/templates/postgres-template.js +1 -1
  42. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  43. package/generators/lib/templates/sqlite-template.js +1 -1
  44. package/generators/lib/utils/database-introspector.js +48 -0
  45. package/generators/lib/utils/env-manager.js +4 -4
  46. package/generators/lib/utils/file-utils.js +6 -6
  47. package/generators/lib/utils/payload-processor.js +18 -2
  48. package/generators/lib/validators/argument-validator.js +2 -2
  49. package/generators/lib/validators/dashboard-validator.js +35 -1
  50. package/generators/lib/validators/payload-validator.js +460 -33
  51. package/integrity-manifest.json +20 -20
  52. package/package.json +2 -1
  53. package/scripts/check-install.js +8 -8
  54. package/scripts/verify-integrity.js +1 -1
  55. package/server.js +1 -1
  56. package/src/components/handlers/adjust_handler.js +1 -1
  57. package/src/components/handlers/audit_handler.js +1 -1
  58. package/src/components/handlers/delete_handler.js +1 -1
  59. package/src/components/handlers/export_handler.js +1 -1
  60. package/src/components/handlers/import_handler.js +1 -1
  61. package/src/components/handlers/insert_handler.js +1 -1
  62. package/src/components/handlers/update_handler.js +1 -1
  63. package/src/components/handlers/upload_handler.js +1 -1
  64. package/src/components/handlers/workflow_handler.js +1 -1
  65. package/src/components/integrations/webhook.js +1 -1
  66. package/src/consumers/baseConsumer.js +1 -1
  67. package/src/consumers/declarativeMapper.js +1 -1
  68. package/src/consumers/handlers/apiHandler.js +1 -1
  69. package/src/consumers/handlers/consoleHandler.js +1 -1
  70. package/src/consumers/handlers/databaseHandler.js +1 -1
  71. package/src/consumers/handlers/index.js +1 -1
  72. package/src/consumers/handlers/kafkaHandler.js +1 -1
  73. package/src/consumers/index.js +1 -1
  74. package/src/consumers/messageTransformer.js +1 -1
  75. package/src/consumers/validator.js +1 -1
  76. package/src/core/db/dialect/base-dialect.js +1 -1
  77. package/src/core/db/dialect/index.js +1 -1
  78. package/src/core/db/dialect/mysql-dialect.js +1 -1
  79. package/src/core/db/dialect/oracle-dialect.js +1 -1
  80. package/src/core/db/dialect/postgres-dialect.js +1 -1
  81. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  82. package/src/core/db/flatten-helper.js +1 -1
  83. package/src/core/db/query-builder-error.js +1 -1
  84. package/src/core/db/query-builder.js +1 -1
  85. package/src/core/db/relation-helper.js +1 -1
  86. package/src/core/handlers/delete_handler.js +1 -1
  87. package/src/core/handlers/insert_handler.js +1 -1
  88. package/src/core/handlers/update_handler.js +1 -1
  89. package/src/core/models/base-model.js +1 -1
  90. package/src/core/utils/cache-manager.js +1 -1
  91. package/src/core/utils/component-engine.js +1 -1
  92. package/src/core/utils/context-builder.js +1 -1
  93. package/src/core/utils/datetime-formatter.js +1 -1
  94. package/src/core/utils/datetime-parser.js +1 -1
  95. package/src/core/utils/db.js +1 -1
  96. package/src/core/utils/logger.js +1 -1
  97. package/src/core/utils/payload-loader.js +1 -1
  98. package/src/core/utils/security-checks.js +1 -1
  99. package/src/middleware/body-options.js +1 -1
  100. package/src/middleware/cors.js +1 -1
  101. package/src/middleware/idempotency.js +1 -1
  102. package/src/middleware/rate-limiter.js +1 -1
  103. package/src/middleware/request-logger.js +1 -1
  104. package/src/middleware/security-headers.js +1 -1
  105. package/src/models/base-model-mysql.js +1 -1
  106. package/src/models/base-model-oracle.js +1 -1
  107. package/src/models/base-model-sqlite.js +1 -1
  108. package/src/models/base-model.js +1 -1
  109. package/src/pro/caching/redis-client.js +1 -1
  110. package/src/pro/caching/redis-helper.js +1 -1
  111. package/src/pro/consumers/baseConsumer.js +1 -1
  112. package/src/pro/consumers/declarativeMapper.js +1 -1
  113. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  114. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  115. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  116. package/src/pro/consumers/handlers/index.js +1 -1
  117. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  118. package/src/pro/consumers/index.js +1 -1
  119. package/src/pro/consumers/messageTransformer.js +1 -1
  120. package/src/pro/consumers/validator.js +1 -1
  121. package/src/pro/database/base-model-mysql.js +1 -1
  122. package/src/pro/database/base-model-oracle.js +1 -1
  123. package/src/pro/database/base-model-sqlite.js +1 -1
  124. package/src/pro/database/db-mysql.js +1 -1
  125. package/src/pro/database/db-oracle.js +1 -1
  126. package/src/pro/database/db-sqlite.js +1 -1
  127. package/src/pro/excel/excel-generator.js +1 -1
  128. package/src/pro/excel/excel-parser.js +1 -1
  129. package/src/pro/excel/export-service.js +1 -1
  130. package/src/pro/excel/export_handler.js +1 -1
  131. package/src/pro/excel/import-service.js +1 -1
  132. package/src/pro/excel/import-validator.js +1 -1
  133. package/src/pro/excel/import_handler.js +1 -1
  134. package/src/pro/excel/upsert-builder.js +1 -1
  135. package/src/pro/idgen/idgen-routes.js +1 -1
  136. package/src/pro/integrations/lookup-resolver.js +1 -1
  137. package/src/pro/integrations/upload-handler-v2.js +1 -1
  138. package/src/pro/integrations/upload-handler.js +1 -1
  139. package/src/pro/integrations/webhook.js +1 -1
  140. package/src/pro/locking/lock-routes.js +1 -1
  141. package/src/pro/locking/resource-lock-manager.js +1 -1
  142. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  143. package/src/pro/messaging/kafkaService.js +1 -1
  144. package/src/pro/messaging/messagehubService.js +1 -1
  145. package/src/pro/messaging/rabbitmqService.js +1 -1
  146. package/src/pro/scheduler/job-manager.js +1 -1
  147. package/src/pro/scheduler/job-routes.js +1 -1
  148. package/src/pro/scheduler/job-validator.js +1 -1
  149. package/src/pro/storage/base-storage-provider.js +1 -1
  150. package/src/pro/storage/file-metadata-helper.js +1 -1
  151. package/src/pro/storage/index.js +1 -1
  152. package/src/pro/storage/local-storage-provider.js +1 -1
  153. package/src/pro/storage/s3-storage-provider.js +1 -1
  154. package/src/pro/storage/upload-cleanup-job.js +1 -1
  155. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  156. package/src/pro/storage/upload-pending-tracker.js +1 -1
  157. package/src/pro/websocket/broadcast-helper.js +1 -1
  158. package/src/pro/websocket/index.js +1 -1
  159. package/src/pro/websocket/livesync-server.js +1 -1
  160. package/src/pro/websocket/ws-broadcaster.js +1 -1
  161. package/src/services/export-service.js +1 -1
  162. package/src/services/import-service.js +1 -1
  163. package/src/services/kafkaConsumerService.js +1 -1
  164. package/src/services/kafkaService.js +1 -1
  165. package/src/services/messagehubService.js +1 -1
  166. package/src/services/rabbitmqService.js +1 -1
  167. package/src/utils/cache-invalidation-registry.js +1 -1
  168. package/src/utils/cache-manager.js +1 -1
  169. package/src/utils/component-engine.js +1 -1
  170. package/src/utils/config-extractor.js +1 -1
  171. package/src/utils/consumerLogger.js +1 -1
  172. package/src/utils/context-builder.js +1 -1
  173. package/src/utils/dashboard-helpers.js +1 -1
  174. package/src/utils/dateHelper.js +1 -1
  175. package/src/utils/datetime-formatter.js +1 -1
  176. package/src/utils/datetime-parser.js +1 -1
  177. package/src/utils/db-bootstrap.js +1 -1
  178. package/src/utils/db-mysql.js +1 -1
  179. package/src/utils/db-oracle.js +1 -1
  180. package/src/utils/db-sqlite.js +1 -1
  181. package/src/utils/db.js +1 -1
  182. package/src/utils/demo-generator.js +1 -1
  183. package/src/utils/excel-generator.js +1 -1
  184. package/src/utils/excel-parser.js +1 -1
  185. package/src/utils/file-watcher.js +1 -1
  186. package/src/utils/id-generator.js +1 -1
  187. package/src/utils/idempotency-manager.js +1 -1
  188. package/src/utils/import-validator.js +1 -1
  189. package/src/utils/license-client.js +1 -1
  190. package/src/utils/lock-manager.js +1 -1
  191. package/src/utils/logger.js +1 -1
  192. package/src/utils/lookup-resolver.js +1 -1
  193. package/src/utils/payload-loader.js +1 -1
  194. package/src/utils/processor-response.js +1 -1
  195. package/src/utils/rabbitmq.js +1 -1
  196. package/src/utils/redis-client.js +1 -1
  197. package/src/utils/redis-helper.js +1 -1
  198. package/src/utils/request-scope.js +1 -1
  199. package/src/utils/security-checks.js +1 -1
  200. package/src/utils/service-resolver.js +1 -1
  201. package/src/utils/shutdown-coordinator.js +1 -1
  202. package/src/utils/soft-delete-dashboard-guard.js +1 -0
  203. package/src/utils/sql-table-extractor.js +1 -0
  204. package/src/utils/trusted-keys.js +1 -1
  205. package/src/utils/upload-handler.js +1 -1
  206. package/src/utils/upsert-builder.js +1 -1
  207. package/src/utils/workflow-hook-executor.js +1 -1
  208. package/generators/lib/utils/sql-table-extractor.js +0 -83
@@ -975,6 +975,54 @@ class DatabaseIntrospector {
975
975
  }
976
976
  }
977
977
 
978
+ /**
979
+ * Get CHECK constraints for a table (PostgreSQL, Fase 1).
980
+ *
981
+ * Mengembalikan `{ name, clause }` per CHECK constraint, di mana `clause` adalah
982
+ * definisi lengkap dari `pg_get_constraintdef` (mis.
983
+ * `CHECK ((is_deleted = ...) OR (...))`). Cukup untuk:
984
+ * - mengenali CHECK konsistensi soft-delete lewat NAMA deterministik
985
+ * (`chk_<table>_soft_delete_consistency`, R8) di introspect-mapper (R6), dan
986
+ * - parse CHECK generik single-field (`age >= 0`, dll.) di introspect-mapper.
987
+ *
988
+ * Hanya PostgreSQL yang diimplementasikan: Fase 1 soft-delete adalah PostgreSQL-only,
989
+ * dan dialect lain (mysql/oracle) memakai penanda boolean `col IN ('true','false')`
990
+ * yang sudah ditangani jalur `getBooleanColumns` terpisah. Memasukkan penanda boolean
991
+ * itu ke `checks` justru akan menimbulkan false drift di diff dbschema-kit (sisi SDF
992
+ * tidak menyimpan penanda boolean di `checks`), maka non-PostgreSQL mengembalikan [].
993
+ *
994
+ * Degradasi anggun: bila katalog tidak tersedia, error di-catch dan dikembalikan []
995
+ * (mengikuti pola `getBooleanColumns`).
996
+ *
997
+ * @param {string} tableName - Nama table (boleh schema.tablename atau tablename)
998
+ * @returns {Promise<Array<{name:string, clause:string}>>}
999
+ */
1000
+ async getCheckConstraints(tableName) {
1001
+ if (!this.pool) return [];
1002
+
1003
+ // Fase 1: introspeksi CHECK hanya untuk PostgreSQL (lihat doc di atas).
1004
+ if (this.dbType !== 'postgresql') return [];
1005
+
1006
+ try {
1007
+ const { schema, table } = this.parseTableName(tableName);
1008
+ const rows = await this.query(
1009
+ `SELECT con.conname AS name,
1010
+ pg_get_constraintdef(con.oid) AS clause
1011
+ FROM pg_constraint con
1012
+ JOIN pg_class rel ON rel.oid = con.conrelid
1013
+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace
1014
+ WHERE con.contype = 'c'
1015
+ AND ns.nspname = $1 AND rel.relname = $2
1016
+ ORDER BY con.conname`,
1017
+ [schema, table]
1018
+ );
1019
+ return rows.map(r => ({ name: r.name, clause: r.clause }));
1020
+ } catch (error) {
1021
+ console.warn(` Warning: Could not fetch check constraints: ${error.message}`);
1022
+ return [];
1023
+ }
1024
+ }
1025
+
978
1026
  /**
979
1027
  * Deteksi kolom boolean pada dialect varchar (mysql/oracle/sqlite) lewat
980
1028
  * penanda CHECK `<col> IN ('true','false')` yang di-emit dbschema-kit.
@@ -133,7 +133,7 @@ function updateEnvKey(filePath, key, value, createIfNotExist = true) {
133
133
  if (fs.existsSync(filePath)) {
134
134
  envData = readEnvFile(filePath);
135
135
  } else if (!createIfNotExist) {
136
- throw new Error(`File ${filePath} tidak ditemukan`);
136
+ throw new Error(`File ${filePath} not found`);
137
137
  }
138
138
 
139
139
  // Update data
@@ -161,7 +161,7 @@ function updateEnvKey(filePath, key, value, createIfNotExist = true) {
161
161
  */
162
162
  function removeEnvKey(filePath, key, softDelete = true) {
163
163
  if (!fs.existsSync(filePath)) {
164
- throw new Error(`File ${filePath} tidak ditemukan`);
164
+ throw new Error(`File ${filePath} not found`);
165
165
  }
166
166
 
167
167
  const envData = readEnvFile(filePath);
@@ -252,7 +252,7 @@ function validateEnvFile(filePath) {
252
252
  if (!fs.existsSync(filePath)) {
253
253
  return {
254
254
  valid: false,
255
- error: `File ${filePath} tidak ditemukan`
255
+ error: `File ${filePath} not found`
256
256
  };
257
257
  }
258
258
 
@@ -294,7 +294,7 @@ function validateEnvFile(filePath) {
294
294
  */
295
295
  function backupEnvFile(filePath) {
296
296
  if (!fs.existsSync(filePath)) {
297
- throw new Error(`File ${filePath} tidak ditemukan`);
297
+ throw new Error(`File ${filePath} not found`);
298
298
  }
299
299
 
300
300
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -286,7 +286,7 @@ class FileUtils {
286
286
  */
287
287
  static copyFileWithValidation(fileInfo) {
288
288
  if (!fs.existsSync(fileInfo.source)) {
289
- throw new Error(`Source file tidak ditemukan: ${fileInfo.source}`);
289
+ throw new Error(`Source file not found: ${fileInfo.source}`);
290
290
  }
291
291
 
292
292
  console.log(`Copying SQL file: ${fileInfo.originalPath} -> ${path.basename(fileInfo.dest)}`);
@@ -297,14 +297,14 @@ class FileUtils {
297
297
 
298
298
  // Verifikasi copy result
299
299
  if (!fs.existsSync(fileInfo.dest)) {
300
- throw new Error('Destination file tidak berhasil dibuat');
300
+ throw new Error('Destination file was not created');
301
301
  }
302
302
 
303
303
  const sourceStats = fs.statSync(fileInfo.source);
304
304
  const destStats = fs.statSync(fileInfo.dest);
305
305
 
306
306
  if (destStats.size === 0) {
307
- throw new Error('Destination file kosong setelah copy');
307
+ throw new Error('Destination file is empty after copy');
308
308
  }
309
309
 
310
310
  if (sourceStats.size !== destStats.size) {
@@ -329,12 +329,12 @@ class FileUtils {
329
329
  static readFileWithErrorHandling(filePath, encoding = 'utf8') {
330
330
  try {
331
331
  if (!fs.existsSync(filePath)) {
332
- throw new Error(`File tidak ditemukan: ${filePath}`);
332
+ throw new Error(`File not found: ${filePath}`);
333
333
  }
334
334
 
335
335
  const stats = fs.statSync(filePath);
336
336
  if (stats.size === 0) {
337
- console.warn(`Warning: File kosong: ${filePath}`);
337
+ console.warn(`Warning: File is empty: ${filePath}`);
338
338
  return '';
339
339
  }
340
340
 
@@ -374,7 +374,7 @@ class FileUtils {
374
374
 
375
375
  // Verifikasi
376
376
  if (!fs.existsSync(filePath)) {
377
- throw new Error('File tidak berhasil dibuat setelah write operation');
377
+ throw new Error('File was not created after the write operation');
378
378
  }
379
379
 
380
380
  const stats = fs.statSync(filePath);
@@ -675,6 +675,14 @@ class PayloadProcessor {
675
675
  // Note: 'add' dan 'list' sudah di-convert ke 'create' dan 'read' oleh applyActionAliases()
676
676
  const validActions = ['datatables', 'create', 'update', 'delete', 'first', 'lookup', 'read', 'export', 'import', 'adjust', 'aggregate', 'createComposite', 'updateComposite', 'readComposite', 'workflow'];
677
677
 
678
+ // Opt-in actions (soft-delete R20): dikenali generator TAPI TIDAK diberi default-false.
679
+ // Hanya disertakan di output normalized bila dideklarasikan eksplisit di payload. Ini
680
+ // menjaga gate diff-zero: objek action untuk payload non-restore tetap byte-identik
681
+ // baseline (tidak ada key `restore` tambahan yang bisa bocor ke output ter-generate,
682
+ // mis. /info). Bila restore=true, key dipertahankan sehingga route `/restore` +
683
+ // `restoreData` ter-generate (validator R17 memaksa softDelete.enabled bila restore=true).
684
+ const optInActions = ['restore'];
685
+
678
686
  // Action aliases yang dikenali di payload tapi tidak perlu di-normalize lagi
679
687
  // (sudah di-handle oleh applyActionAliases sebelum sampai di sini)
680
688
  const knownAliases = Object.keys(this.ACTION_ALIASES);
@@ -690,9 +698,17 @@ class PayloadProcessor {
690
698
  }
691
699
  }
692
700
 
693
- // Warn about unknown actions (skip known aliases)
701
+ // Process opt-in actions: TANPA default-false (lihat catatan optInActions di atas).
702
+ // Hanya muncul di normalized bila key hadir di payload, menjaga diff-zero non-restore.
703
+ for (const action of optInActions) {
704
+ if (action in actions) {
705
+ normalized[action] = Boolean(actions[action]);
706
+ }
707
+ }
708
+
709
+ // Warn about unknown actions (skip known aliases + opt-in actions yang dikenali)
694
710
  for (const action of Object.keys(actions)) {
695
- if (!validActions.includes(action) && !knownAliases.includes(action)) {
711
+ if (!validActions.includes(action) && !optInActions.includes(action) && !knownAliases.includes(action)) {
696
712
  console.warn(`Unknown action '${action}' will be ignored`);
697
713
  }
698
714
  }
@@ -519,12 +519,12 @@ class ArgumentValidator {
519
519
 
520
520
  // Warning untuk force overwrite
521
521
  if (args.force) {
522
- summary.warnings.push('Mode force overwrite aktif - file yang sudah ada akan ditimpa tanpa konfirmasi');
522
+ summary.warnings.push('Force overwrite mode is active - existing files will be overwritten without confirmation');
523
523
  }
524
524
 
525
525
  // Warning untuk Oracle database
526
526
  if (args.database === 'oracle') {
527
- summary.warnings.push('Menggunakan Oracle database - pastikan utils/oracle.js tersedia');
527
+ summary.warnings.push('Using Oracle database - make sure utils/oracle.js is available');
528
528
  }
529
529
 
530
530
  return summary;
@@ -1,7 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const PayloadProcessor = require('../utils/payload-processor');
4
- const sqlTableExtractor = require('../utils/sql-table-extractor');
4
+ const sqlTableExtractor = require('../../../src/utils/sql-table-extractor');
5
+ const softDeleteDashboardGuard = require('../../../src/utils/soft-delete-dashboard-guard');
5
6
 
6
7
  const FORBIDDEN_FRONTEND_FIELDS = ['widgetType', 'layout', 'title', 'subtitle', 'color'];
7
8
  const ALLOWED_PARAM_TYPES = ['string', 'number', 'boolean', 'date'];
@@ -40,6 +41,8 @@ class DashboardValidator {
40
41
 
41
42
  this._validateWidgets(payload.widgets, declaredParams, payloadDir);
42
43
 
44
+ this._validateSoftDeleteGuard(payload, payloadDir);
45
+
43
46
  this._validateCacheBlock(payload.cache);
44
47
 
45
48
  if (
@@ -167,6 +170,37 @@ class DashboardValidator {
167
170
  }
168
171
  }
169
172
 
173
+ /**
174
+ * Checkpoint soft-delete (ERROR) untuk `dashboard create`.
175
+ *
176
+ * Bangun registry tabel soft-delete dari scan payloadDir, lalu cek tiap widget.
177
+ * Bila ada widget yang mereferensikan tabel soft-delete tanpa token `is_deleted`,
178
+ * lempar Error yang memblok `dashboard create`. Registry kosong (project tanpa
179
+ * tabel soft-delete) → no-op, sehingga perilaku project lama tidak berubah.
180
+ *
181
+ * Resolusi SQL di-delegasikan ke `_resolveSqlText` (sudah memverifikasi file:
182
+ * reference pada `_validateWidgets`, jadi di titik ini file dijamin ada).
183
+ */
184
+ static _validateSoftDeleteGuard(payload, payloadDir) {
185
+ const registry = softDeleteDashboardGuard.buildSoftDeleteRegistry(payloadDir);
186
+ if (registry.size === 0) return;
187
+
188
+ const violations = softDeleteDashboardGuard.checkDashboardViolations(
189
+ payload,
190
+ (sqlOrRef, widgetId, key) => this._resolveSqlText(
191
+ widgetId,
192
+ key === null ? 'query' : `queries.${key}`,
193
+ sqlOrRef,
194
+ payloadDir
195
+ ),
196
+ registry
197
+ );
198
+
199
+ if (violations.length > 0) {
200
+ throw new Error(softDeleteDashboardGuard.formatViolationMessage(violations[0]));
201
+ }
202
+ }
203
+
170
204
  static _resolveSqlText(widgetId, queryLabel, sqlOrRef, payloadDir) {
171
205
  if (!PayloadProcessor.isFileReference(sqlOrRef)) {
172
206
  return sqlOrRef;