@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
@@ -1,272 +1,293 @@
1
- 'use strict';
2
-
3
- /**
4
- * Diff Reporter — format delta dari diff-engine menjadi output human-readable
5
- * plain text atau JSON object. Reporter tidak melakukan I/O sendiri; caller
6
- * yang memutuskan untuk write ke stdout atau file.
7
- *
8
- * Exit code semantic:
9
- * 0 — no drift
10
- * 1 — drift detected
11
- * 2 — error (di-set oleh caller, bukan oleh reporter ini)
12
- *
13
- * @module lib/dbschema-kit/diff-reporter
14
- */
15
-
16
- // ─────────────────────────────────────────────────────────────
17
- // Format helpers
18
- // ─────────────────────────────────────────────────────────────
19
-
20
- function formatFieldSignature(f) {
21
- if (!f) return '(unknown)';
22
- const parts = [];
23
- if (f.type !== undefined) {
24
- if (f.type === 'string' && f.length !== undefined) {
25
- parts.push(`${f.type}(${f.length})`);
26
- } else if (f.type === 'decimal' && f.precision !== undefined && f.scale !== undefined) {
27
- parts.push(`${f.type}(${f.precision},${f.scale})`);
28
- } else {
29
- parts.push(String(f.type));
30
- }
31
- }
32
- parts.push(f.nullable === false ? 'NOT NULL' : 'NULL');
33
- return parts.join(' ');
34
- }
35
-
36
- function formatFieldLine(f) {
37
- return ` - ${f.name} (${formatFieldSignature(f)})`;
38
- }
39
-
40
- function formatColumns(cols) {
41
- if (!Array.isArray(cols) || cols.length === 0) return '(empty)';
42
- return cols.join(', ');
43
- }
44
-
45
- function formatFkLine(fk) {
46
- if (!fk) return ' - (invalid)';
47
- const refCol = fk.references ? `.${fk.references}` : '';
48
- const actions = [];
49
- if (fk.onDelete && fk.onDelete !== 'restrict') {
50
- actions.push(`ON DELETE ${String(fk.onDelete).toUpperCase()}`);
51
- }
52
- if (fk.onUpdate && fk.onUpdate !== 'restrict') {
53
- actions.push(`ON UPDATE ${String(fk.onUpdate).toUpperCase()}`);
54
- }
55
- const actionStr = actions.length > 0 ? ` [${actions.join(', ')}]` : '';
56
- return ` - ${fk.localKey} -> ${fk.target}${refCol}${actionStr}`;
57
- }
58
-
59
- function formatFkMismatchedLine(fk) {
60
- if (!fk) return ' - (invalid)';
61
- const refCol = fk.references ? `.${fk.references}` : '';
62
- const lines = [` - ${fk.localKey} -> ${fk.target}${refCol}`];
63
- if (Array.isArray(fk.reasons)) {
64
- for (const r of fk.reasons) {
65
- lines.push(` ${r}`);
66
- }
67
- }
68
- return lines.join('\n');
69
- }
70
-
71
- function formatCheckValue(value) {
72
- if (Array.isArray(value)) {
73
- const items = value.map((v) => (typeof v === 'string' ? `'${v}'` : String(v)));
74
- return `[${items.join(', ')}]`;
75
- }
76
- if (typeof value === 'string') return `'${value}'`;
77
- if (value === null || value === undefined) return 'null';
78
- return String(value);
79
- }
80
-
81
- function formatCheckLine(c) {
82
- if (!c) return ' - (invalid)';
83
- if (c._unparsable) return ` - (unparsable) ${c.expression}`;
84
- return ` - ${c.field}: ${c.op} ${formatCheckValue(c.value)}`;
85
- }
86
-
87
- // ─────────────────────────────────────────────────────────────
88
- // Section builders (human format)
89
- // ─────────────────────────────────────────────────────────────
90
-
91
- function buildFieldsSection(fields) {
92
- const lines = ['Fields:'];
93
-
94
- lines.push(' + Only in SDF:');
95
- if (fields.onlyInSdf.length === 0) {
96
- lines.push(' (none)');
97
- } else {
98
- for (const f of fields.onlyInSdf) lines.push(formatFieldLine(f));
99
- }
100
-
101
- lines.push(' - Only in DB:');
102
- if (fields.onlyInDb.length === 0) {
103
- lines.push(' (none)');
104
- } else {
105
- for (const f of fields.onlyInDb) lines.push(formatFieldLine(f));
106
- }
107
-
108
- lines.push(' ~ Mismatched:');
109
- if (fields.mismatched.length === 0) {
110
- lines.push(' (none)');
111
- } else {
112
- for (const m of fields.mismatched) {
113
- lines.push(` - ${m.name}`);
114
- for (const r of m.reasons) {
115
- lines.push(` ${r}`);
116
- }
117
- }
118
- }
119
-
120
- return lines;
121
- }
122
-
123
- function buildPrimaryKeySection(pk) {
124
- const lines = ['Primary Key:'];
125
- if (pk.match) {
126
- const cols = pk.sdf.length === 0 ? '(none)' : pk.sdf.join(', ');
127
- lines.push(` Match: ${cols}`);
128
- } else {
129
- lines.push(` Mismatch:`);
130
- lines.push(` SDF: ${formatColumns(pk.sdf)}`);
131
- lines.push(` DB: ${formatColumns(pk.db)}`);
132
- }
133
- return lines;
134
- }
135
-
136
- function buildCollectionSection(label, coll, formatter, mismatchedFormatter) {
137
- const isEmpty = coll.onlyInSdf.length === 0 && coll.onlyInDb.length === 0 && coll.mismatched.length === 0;
138
- if (isEmpty) {
139
- return [`${label}: (no drift)`];
140
- }
141
-
142
- const lines = [`${label}:`];
143
- lines.push(' + Only in SDF:');
144
- if (coll.onlyInSdf.length === 0) {
145
- lines.push(' (none)');
146
- } else {
147
- for (const it of coll.onlyInSdf) lines.push(formatter(it));
148
- }
149
- lines.push(' - Only in DB:');
150
- if (coll.onlyInDb.length === 0) {
151
- lines.push(' (none)');
152
- } else {
153
- for (const it of coll.onlyInDb) lines.push(formatter(it));
154
- }
155
- lines.push(' ~ Mismatched:');
156
- if (coll.mismatched.length === 0) {
157
- lines.push(' (none)');
158
- } else {
159
- const useMismatch = typeof mismatchedFormatter === 'function' ? mismatchedFormatter : formatter;
160
- for (const it of coll.mismatched) lines.push(useMismatch(it));
161
- }
162
- return lines;
163
- }
164
-
165
- function formatColumnItem(item) {
166
- const cols = Array.isArray(item.columns) ? item.columns.join(', ') : '(empty)';
167
- return ` - ${cols}`;
168
- }
169
-
170
- function buildTableReport(delta, idx, total) {
171
- const lines = [];
172
- const banner = `Drift Report: ${delta.tableName} (${idx}/${total} table compared)`;
173
- lines.push(banner);
174
- lines.push('='.repeat(Math.max(banner.length, 30)));
175
- lines.push('');
176
-
177
- if (!delta.hasDrift) {
178
- lines.push('No drift detected. SDF and database are in sync.');
179
- return lines;
180
- }
181
-
182
- lines.push(...buildFieldsSection(delta.fields));
183
- lines.push('');
184
- lines.push(...buildPrimaryKeySection(delta.primaryKey));
185
- lines.push('');
186
- lines.push(...buildCollectionSection('Indexes', delta.indexes, formatColumnItem));
187
- lines.push('');
188
- lines.push(...buildCollectionSection('Uniques', delta.uniques, formatColumnItem));
189
- lines.push('');
190
- lines.push(...buildCollectionSection('Foreign Keys', delta.foreignKeys, formatFkLine, formatFkMismatchedLine));
191
- if (delta.checks) {
192
- lines.push('');
193
- lines.push(...buildCollectionSection('Checks', delta.checks, formatCheckLine));
194
- }
195
-
196
- return lines;
197
- }
198
-
199
- // ─────────────────────────────────────────────────────────────
200
- // Public API: human / json / exit code
201
- // ─────────────────────────────────────────────────────────────
202
-
203
- function reportHuman(deltas) {
204
- const arr = Array.isArray(deltas) ? deltas : [];
205
- const total = arr.length;
206
- const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
207
- const tablesClean = total - tablesWithDrift;
208
-
209
- const lines = [];
210
- if (total === 0) {
211
- lines.push('No tables compared. Schema set is empty.');
212
- lines.push('Exit code: 0');
213
- return lines.join('\n');
214
- }
215
-
216
- for (let i = 0; i < arr.length; i++) {
217
- const delta = arr[i];
218
- lines.push(...buildTableReport(delta, i + 1, total));
219
- if (i < arr.length - 1) {
220
- lines.push('');
221
- lines.push('');
222
- }
223
- }
224
-
225
- lines.push('');
226
- lines.push(`Summary: ${tablesWithDrift} table${tablesWithDrift === 1 ? '' : 's'} with drift, ${tablesClean} clean`);
227
- const exitCode = tablesWithDrift > 0 ? 1 : 0;
228
- lines.push(`Exit code: ${exitCode}`);
229
-
230
- return lines.join('\n');
231
- }
232
-
233
- function reportJson(deltas) {
234
- const arr = Array.isArray(deltas) ? deltas : [];
235
- const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
236
- const tablesClean = arr.length - tablesWithDrift;
237
- const exitCode = tablesWithDrift > 0 ? 1 : 0;
238
-
239
- return {
240
- version: '1.0',
241
- summary: {
242
- totalTables: arr.length,
243
- tablesWithDrift,
244
- tablesClean,
245
- exitCode
246
- },
247
- tables: arr.map((d) => {
248
- const out = {
249
- tableName: d.tableName,
250
- hasDrift: d.hasDrift,
251
- fields: d.fields,
252
- primaryKey: d.primaryKey,
253
- indexes: d.indexes,
254
- uniques: d.uniques,
255
- foreignKeys: d.foreignKeys
256
- };
257
- if (d.checks) out.checks = d.checks;
258
- return out;
259
- })
260
- };
261
- }
262
-
263
- function getExitCode(deltas) {
264
- const arr = Array.isArray(deltas) ? deltas : [];
265
- return arr.some((d) => d && d.hasDrift) ? 1 : 0;
266
- }
267
-
268
- module.exports = {
269
- reportHuman,
270
- reportJson,
271
- getExitCode
272
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Diff Reporter — format delta dari diff-engine menjadi output human-readable
5
+ * plain text atau JSON object. Reporter tidak melakukan I/O sendiri; caller
6
+ * yang memutuskan untuk write ke stdout atau file.
7
+ *
8
+ * Exit code semantic:
9
+ * 0 — no drift
10
+ * 1 — drift detected
11
+ * 2 — error (di-set oleh caller, bukan oleh reporter ini)
12
+ *
13
+ * @module lib/dbschema-kit/diff-reporter
14
+ */
15
+
16
+ // ─────────────────────────────────────────────────────────────
17
+ // Format helpers
18
+ // ─────────────────────────────────────────────────────────────
19
+
20
+ function formatFieldSignature(f) {
21
+ if (!f) return '(unknown)';
22
+ const parts = [];
23
+ if (f.type !== undefined) {
24
+ if (f.type === 'string' && f.length !== undefined) {
25
+ parts.push(`${f.type}(${f.length})`);
26
+ } else if (f.type === 'decimal' && f.precision !== undefined && f.scale !== undefined) {
27
+ parts.push(`${f.type}(${f.precision},${f.scale})`);
28
+ } else {
29
+ parts.push(String(f.type));
30
+ }
31
+ }
32
+ parts.push(f.nullable === false ? 'NOT NULL' : 'NULL');
33
+ return parts.join(' ');
34
+ }
35
+
36
+ function formatFieldLine(f) {
37
+ return ` - ${f.name} (${formatFieldSignature(f)})`;
38
+ }
39
+
40
+ function formatColumns(cols) {
41
+ if (!Array.isArray(cols) || cols.length === 0) return '(empty)';
42
+ return cols.join(', ');
43
+ }
44
+
45
+ function formatFkLine(fk) {
46
+ if (!fk) return ' - (invalid)';
47
+ const refCol = fk.references ? `.${fk.references}` : '';
48
+ const actions = [];
49
+ if (fk.onDelete && fk.onDelete !== 'restrict') {
50
+ actions.push(`ON DELETE ${String(fk.onDelete).toUpperCase()}`);
51
+ }
52
+ if (fk.onUpdate && fk.onUpdate !== 'restrict') {
53
+ actions.push(`ON UPDATE ${String(fk.onUpdate).toUpperCase()}`);
54
+ }
55
+ const actionStr = actions.length > 0 ? ` [${actions.join(', ')}]` : '';
56
+ return ` - ${fk.localKey} -> ${fk.target}${refCol}${actionStr}`;
57
+ }
58
+
59
+ function formatFkMismatchedLine(fk) {
60
+ if (!fk) return ' - (invalid)';
61
+ const refCol = fk.references ? `.${fk.references}` : '';
62
+ const lines = [` - ${fk.localKey} -> ${fk.target}${refCol}`];
63
+ if (Array.isArray(fk.reasons)) {
64
+ for (const r of fk.reasons) {
65
+ lines.push(` ${r}`);
66
+ }
67
+ }
68
+ return lines.join('\n');
69
+ }
70
+
71
+ function formatCheckValue(value) {
72
+ if (Array.isArray(value)) {
73
+ const items = value.map((v) => (typeof v === 'string' ? `'${v}'` : String(v)));
74
+ return `[${items.join(', ')}]`;
75
+ }
76
+ if (typeof value === 'string') return `'${value}'`;
77
+ if (value === null || value === undefined) return 'null';
78
+ return String(value);
79
+ }
80
+
81
+ function formatCheckLine(c) {
82
+ if (!c) return ' - (invalid)';
83
+ if (c._unparsable) return ` - (unparsable) ${c.expression}`;
84
+ return ` - ${c.field}: ${c.op} ${formatCheckValue(c.value)}`;
85
+ }
86
+
87
+ // Soft-delete drift section (R6/R8 reverse, Detection-only). Hanya dirender bila
88
+ // status soft-delete TIDAK cocok antara SDF dan DB.
89
+ function buildSoftDeleteSection(sd) {
90
+ const lines = ['Soft-Delete:'];
91
+ if (sd.sdf && !sd.db) {
92
+ lines.push(' + SDF declares softDelete.enabled=true, but the database is missing the');
93
+ lines.push(' consistency CHECK constraint (or the soft-delete columns/types are incomplete).');
94
+ lines.push(' Detection-only: retrofit via `schema migrate --drop` (recreate) or add the');
95
+ lines.push(' CHECK manually. RESTForge does not auto-add it (no ALTER ADD CHECK).');
96
+ } else if (!sd.sdf && sd.db) {
97
+ lines.push(' - Database has the soft-delete consistency CHECK, but the SDF does not declare');
98
+ lines.push(' softDelete.enabled=true.');
99
+ }
100
+ return lines;
101
+ }
102
+
103
+ // ─────────────────────────────────────────────────────────────
104
+ // Section builders (human format)
105
+ // ─────────────────────────────────────────────────────────────
106
+
107
+ function buildFieldsSection(fields) {
108
+ const lines = ['Fields:'];
109
+
110
+ lines.push(' + Only in SDF:');
111
+ if (fields.onlyInSdf.length === 0) {
112
+ lines.push(' (none)');
113
+ } else {
114
+ for (const f of fields.onlyInSdf) lines.push(formatFieldLine(f));
115
+ }
116
+
117
+ lines.push(' - Only in DB:');
118
+ if (fields.onlyInDb.length === 0) {
119
+ lines.push(' (none)');
120
+ } else {
121
+ for (const f of fields.onlyInDb) lines.push(formatFieldLine(f));
122
+ }
123
+
124
+ lines.push(' ~ Mismatched:');
125
+ if (fields.mismatched.length === 0) {
126
+ lines.push(' (none)');
127
+ } else {
128
+ for (const m of fields.mismatched) {
129
+ lines.push(` - ${m.name}`);
130
+ for (const r of m.reasons) {
131
+ lines.push(` ${r}`);
132
+ }
133
+ }
134
+ }
135
+
136
+ return lines;
137
+ }
138
+
139
+ function buildPrimaryKeySection(pk) {
140
+ const lines = ['Primary Key:'];
141
+ if (pk.match) {
142
+ const cols = pk.sdf.length === 0 ? '(none)' : pk.sdf.join(', ');
143
+ lines.push(` Match: ${cols}`);
144
+ } else {
145
+ lines.push(` Mismatch:`);
146
+ lines.push(` SDF: ${formatColumns(pk.sdf)}`);
147
+ lines.push(` DB: ${formatColumns(pk.db)}`);
148
+ }
149
+ return lines;
150
+ }
151
+
152
+ function buildCollectionSection(label, coll, formatter, mismatchedFormatter) {
153
+ const isEmpty = coll.onlyInSdf.length === 0 && coll.onlyInDb.length === 0 && coll.mismatched.length === 0;
154
+ if (isEmpty) {
155
+ return [`${label}: (no drift)`];
156
+ }
157
+
158
+ const lines = [`${label}:`];
159
+ lines.push(' + Only in SDF:');
160
+ if (coll.onlyInSdf.length === 0) {
161
+ lines.push(' (none)');
162
+ } else {
163
+ for (const it of coll.onlyInSdf) lines.push(formatter(it));
164
+ }
165
+ lines.push(' - Only in DB:');
166
+ if (coll.onlyInDb.length === 0) {
167
+ lines.push(' (none)');
168
+ } else {
169
+ for (const it of coll.onlyInDb) lines.push(formatter(it));
170
+ }
171
+ lines.push(' ~ Mismatched:');
172
+ if (coll.mismatched.length === 0) {
173
+ lines.push(' (none)');
174
+ } else {
175
+ const useMismatch = typeof mismatchedFormatter === 'function' ? mismatchedFormatter : formatter;
176
+ for (const it of coll.mismatched) lines.push(useMismatch(it));
177
+ }
178
+ return lines;
179
+ }
180
+
181
+ function formatColumnItem(item) {
182
+ const cols = Array.isArray(item.columns) ? item.columns.join(', ') : '(empty)';
183
+ return ` - ${cols}`;
184
+ }
185
+
186
+ function buildTableReport(delta, idx, total) {
187
+ const lines = [];
188
+ const banner = `Drift Report: ${delta.tableName} (${idx}/${total} table compared)`;
189
+ lines.push(banner);
190
+ lines.push('='.repeat(Math.max(banner.length, 30)));
191
+ lines.push('');
192
+
193
+ if (!delta.hasDrift) {
194
+ lines.push('No drift detected. SDF and database are in sync.');
195
+ return lines;
196
+ }
197
+
198
+ lines.push(...buildFieldsSection(delta.fields));
199
+ lines.push('');
200
+ lines.push(...buildPrimaryKeySection(delta.primaryKey));
201
+ lines.push('');
202
+ lines.push(...buildCollectionSection('Indexes', delta.indexes, formatColumnItem));
203
+ lines.push('');
204
+ lines.push(...buildCollectionSection('Uniques', delta.uniques, formatColumnItem));
205
+ lines.push('');
206
+ lines.push(...buildCollectionSection('Foreign Keys', delta.foreignKeys, formatFkLine, formatFkMismatchedLine));
207
+ if (delta.checks) {
208
+ lines.push('');
209
+ lines.push(...buildCollectionSection('Checks', delta.checks, formatCheckLine));
210
+ }
211
+ if (delta.softDelete && delta.softDelete.match === false) {
212
+ lines.push('');
213
+ lines.push(...buildSoftDeleteSection(delta.softDelete));
214
+ }
215
+
216
+ return lines;
217
+ }
218
+
219
+ // ─────────────────────────────────────────────────────────────
220
+ // Public API: human / json / exit code
221
+ // ─────────────────────────────────────────────────────────────
222
+
223
+ function reportHuman(deltas) {
224
+ const arr = Array.isArray(deltas) ? deltas : [];
225
+ const total = arr.length;
226
+ const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
227
+ const tablesClean = total - tablesWithDrift;
228
+
229
+ const lines = [];
230
+ if (total === 0) {
231
+ lines.push('No tables compared. Schema set is empty.');
232
+ lines.push('Exit code: 0');
233
+ return lines.join('\n');
234
+ }
235
+
236
+ for (let i = 0; i < arr.length; i++) {
237
+ const delta = arr[i];
238
+ lines.push(...buildTableReport(delta, i + 1, total));
239
+ if (i < arr.length - 1) {
240
+ lines.push('');
241
+ lines.push('');
242
+ }
243
+ }
244
+
245
+ lines.push('');
246
+ lines.push(`Summary: ${tablesWithDrift} table${tablesWithDrift === 1 ? '' : 's'} with drift, ${tablesClean} clean`);
247
+ const exitCode = tablesWithDrift > 0 ? 1 : 0;
248
+ lines.push(`Exit code: ${exitCode}`);
249
+
250
+ return lines.join('\n');
251
+ }
252
+
253
+ function reportJson(deltas) {
254
+ const arr = Array.isArray(deltas) ? deltas : [];
255
+ const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
256
+ const tablesClean = arr.length - tablesWithDrift;
257
+ const exitCode = tablesWithDrift > 0 ? 1 : 0;
258
+
259
+ return {
260
+ version: '1.0',
261
+ summary: {
262
+ totalTables: arr.length,
263
+ tablesWithDrift,
264
+ tablesClean,
265
+ exitCode
266
+ },
267
+ tables: arr.map((d) => {
268
+ const out = {
269
+ tableName: d.tableName,
270
+ hasDrift: d.hasDrift,
271
+ fields: d.fields,
272
+ primaryKey: d.primaryKey,
273
+ indexes: d.indexes,
274
+ uniques: d.uniques,
275
+ foreignKeys: d.foreignKeys
276
+ };
277
+ if (d.checks) out.checks = d.checks;
278
+ if (d.softDelete) out.softDelete = d.softDelete;
279
+ return out;
280
+ })
281
+ };
282
+ }
283
+
284
+ function getExitCode(deltas) {
285
+ const arr = Array.isArray(deltas) ? deltas : [];
286
+ return arr.some((d) => d && d.hasDrift) ? 1 : 0;
287
+ }
288
+
289
+ module.exports = {
290
+ reportHuman,
291
+ reportJson,
292
+ getExitCode
293
+ };
@@ -1,6 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const { generateConstraintName, deriveCompositeShortName } = require('../naming');
4
+ const {
5
+ IS_DELETED_COLUMN,
6
+ isSoftDeleteEnabled,
7
+ assertSoftDeletePostgresOnly
8
+ } = require('../soft-delete-constants');
4
9
 
5
10
  function collectIndexes(ir, maxLength) {
6
11
  const { tableName, fields, primaryKey, indexes, uniques } = ir;
@@ -55,13 +60,30 @@ function collectIndexes(ir, maxLength) {
55
60
  }
56
61
 
57
62
  function emitCreateIndexes(ir, dialect) {
63
+ // Guard Fase 1: soft-delete hanya didukung PostgreSQL (lihat soft-delete-constants).
64
+ assertSoftDeletePostgresOnly(ir, dialect);
65
+
58
66
  const collected = collectIndexes(ir, dialect.maxIdentifierLength);
59
67
  const tableIdentifier = dialect.formatTableIdentifier(ir);
68
+
69
+ // R10/R11: pada tabel soft-delete, index NON-UNIQUE dimaterialisasi sebagai partial
70
+ // index pada dialect yang mendukungnya (PostgreSQL: `WHERE is_deleted = FALSE`),
71
+ // sehingga lookup active subset lebih cepat dan index lebih kecil. Semua index yang
72
+ // dihasilkan collectIndexes memang sudah non-unique (PK/UNIQUE implicit di-skip),
73
+ // jadi UNIQUE TIDAK tersentuh di sini (uniqueness tetap penuh, R11 poin 2). Dialect-
74
+ // awareness lewat flag `supportsPartialIndex` (analog `nativeBoolean`), bukan hardcode
75
+ // nama dialect. Di Fase 1 guard di atas sudah membatasi soft-delete ke PostgreSQL;
76
+ // flag adalah seam untuk fase multi-database (MySQL/Oracle/SQLite → plain index).
77
+ const usePartial = isSoftDeleteEnabled(ir) && dialect.supportsPartialIndex === true;
78
+ const partialPredicate = usePartial
79
+ ? ` WHERE ${dialect.quoteIdentifier(IS_DELETED_COLUMN)} = FALSE`
80
+ : '';
81
+
60
82
  return collected.map((entry) => {
61
83
  const cols = entry.columns.map((c) => dialect.quoteIdentifier(c)).join(', ');
62
84
  // Index name TIDAK di-qualified dengan schema: PG/MySQL letakkan index di schema
63
85
  // tabelnya secara otomatis. Hanya target `ON <schema>.<table>` yang qualified.
64
- return `CREATE INDEX ${dialect.quoteIdentifier(entry.name)} ON ${tableIdentifier} (${cols})`;
86
+ return `CREATE INDEX ${dialect.quoteIdentifier(entry.name)} ON ${tableIdentifier} (${cols})${partialPredicate}`;
65
87
  });
66
88
  }
67
89