@restforgejs/platform 4.1.1 → 4.2.8

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 (178) 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/endpoint/create.js +42 -3
  6. package/generators/cli/schema/apply.js +525 -0
  7. package/generators/cli/schema/diff.js +321 -0
  8. package/generators/cli/schema/generate-ddl.js +7 -10
  9. package/generators/cli/schema/init.js +95 -172
  10. package/generators/cli/schema/migrate.js +10 -16
  11. package/generators/cli/schema/models.js +8 -12
  12. package/generators/cli/schema/template.js +222 -0
  13. package/generators/cli/schema/validate.js +8 -12
  14. package/generators/cli-entry.js +17 -2
  15. package/generators/lib/dbschema-kit/apply-engine.js +582 -0
  16. package/generators/lib/dbschema-kit/diff-engine.js +703 -0
  17. package/generators/lib/dbschema-kit/diff-reporter.js +272 -0
  18. package/generators/lib/dbschema-kit/emitters/alter-table.js +275 -0
  19. package/generators/lib/payload/endpoint-schema-validator.js +171 -0
  20. package/generators/lib/payload/payload-runner.js +137 -220
  21. package/generators/lib/payload/schema-diff.js +277 -0
  22. package/generators/lib/utils/audit-columns.js +181 -0
  23. package/generators/lib/utils/cli-output.js +17 -0
  24. package/generators/lib/utils/database-introspector.js +16 -13
  25. package/integrity-manifest.json +8 -8
  26. package/package.json +1 -1
  27. package/scripts/verify-integrity.js +1 -1
  28. package/server.js +1 -1
  29. package/src/components/handlers/adjust_handler.js +1 -1
  30. package/src/components/handlers/audit_handler.js +1 -1
  31. package/src/components/handlers/delete_handler.js +1 -1
  32. package/src/components/handlers/export_handler.js +1 -1
  33. package/src/components/handlers/import_handler.js +1 -1
  34. package/src/components/handlers/insert_handler.js +1 -1
  35. package/src/components/handlers/update_handler.js +1 -1
  36. package/src/components/handlers/upload_handler.js +1 -1
  37. package/src/components/handlers/workflow_handler.js +1 -1
  38. package/src/components/integrations/webhook.js +1 -1
  39. package/src/consumers/baseConsumer.js +1 -1
  40. package/src/consumers/declarativeMapper.js +1 -1
  41. package/src/consumers/handlers/apiHandler.js +1 -1
  42. package/src/consumers/handlers/consoleHandler.js +1 -1
  43. package/src/consumers/handlers/databaseHandler.js +1 -1
  44. package/src/consumers/handlers/index.js +1 -1
  45. package/src/consumers/handlers/kafkaHandler.js +1 -1
  46. package/src/consumers/index.js +1 -1
  47. package/src/consumers/messageTransformer.js +1 -1
  48. package/src/consumers/validator.js +1 -1
  49. package/src/core/db/dialect/base-dialect.js +1 -1
  50. package/src/core/db/dialect/index.js +1 -1
  51. package/src/core/db/dialect/mysql-dialect.js +1 -1
  52. package/src/core/db/dialect/oracle-dialect.js +1 -1
  53. package/src/core/db/dialect/postgres-dialect.js +1 -1
  54. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  55. package/src/core/db/flatten-helper.js +1 -1
  56. package/src/core/db/query-builder-error.js +1 -1
  57. package/src/core/db/query-builder.js +1 -1
  58. package/src/core/db/relation-helper.js +1 -1
  59. package/src/core/handlers/delete_handler.js +1 -1
  60. package/src/core/handlers/insert_handler.js +1 -1
  61. package/src/core/handlers/update_handler.js +1 -1
  62. package/src/core/models/base-model.js +1 -1
  63. package/src/core/utils/cache-manager.js +1 -1
  64. package/src/core/utils/component-engine.js +1 -1
  65. package/src/core/utils/context-builder.js +1 -1
  66. package/src/core/utils/datetime-formatter.js +1 -1
  67. package/src/core/utils/datetime-parser.js +1 -1
  68. package/src/core/utils/db.js +1 -1
  69. package/src/core/utils/logger.js +1 -1
  70. package/src/core/utils/payload-loader.js +1 -1
  71. package/src/core/utils/security-checks.js +1 -1
  72. package/src/middleware/body-options.js +1 -1
  73. package/src/middleware/cors.js +1 -1
  74. package/src/middleware/idempotency.js +1 -1
  75. package/src/middleware/rate-limiter.js +1 -1
  76. package/src/middleware/request-logger.js +1 -1
  77. package/src/middleware/security-headers.js +1 -1
  78. package/src/models/base-model-mysql.js +1 -1
  79. package/src/models/base-model-oracle.js +1 -1
  80. package/src/models/base-model-sqlite.js +1 -1
  81. package/src/models/base-model.js +1 -1
  82. package/src/pro/caching/redis-client.js +1 -1
  83. package/src/pro/caching/redis-helper.js +1 -1
  84. package/src/pro/consumers/baseConsumer.js +1 -1
  85. package/src/pro/consumers/declarativeMapper.js +1 -1
  86. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  87. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  88. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  89. package/src/pro/consumers/handlers/index.js +1 -1
  90. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  91. package/src/pro/consumers/index.js +1 -1
  92. package/src/pro/consumers/messageTransformer.js +1 -1
  93. package/src/pro/consumers/validator.js +1 -1
  94. package/src/pro/database/base-model-mysql.js +1 -1
  95. package/src/pro/database/base-model-oracle.js +1 -1
  96. package/src/pro/database/base-model-sqlite.js +1 -1
  97. package/src/pro/database/db-mysql.js +1 -1
  98. package/src/pro/database/db-oracle.js +1 -1
  99. package/src/pro/database/db-sqlite.js +1 -1
  100. package/src/pro/excel/excel-generator.js +1 -1
  101. package/src/pro/excel/excel-parser.js +1 -1
  102. package/src/pro/excel/export-service.js +1 -1
  103. package/src/pro/excel/export_handler.js +1 -1
  104. package/src/pro/excel/import-service.js +1 -1
  105. package/src/pro/excel/import-validator.js +1 -1
  106. package/src/pro/excel/import_handler.js +1 -1
  107. package/src/pro/excel/upsert-builder.js +1 -1
  108. package/src/pro/idgen/idgen-routes.js +1 -1
  109. package/src/pro/integrations/lookup-resolver.js +1 -1
  110. package/src/pro/integrations/upload-handler-v2.js +1 -1
  111. package/src/pro/integrations/upload-handler.js +1 -1
  112. package/src/pro/integrations/webhook.js +1 -1
  113. package/src/pro/locking/lock-routes.js +1 -1
  114. package/src/pro/locking/resource-lock-manager.js +1 -1
  115. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  116. package/src/pro/messaging/kafkaService.js +1 -1
  117. package/src/pro/messaging/messagehubService.js +1 -1
  118. package/src/pro/messaging/rabbitmqService.js +1 -1
  119. package/src/pro/scheduler/job-manager.js +1 -1
  120. package/src/pro/scheduler/job-routes.js +1 -1
  121. package/src/pro/scheduler/job-validator.js +1 -1
  122. package/src/pro/storage/base-storage-provider.js +1 -1
  123. package/src/pro/storage/file-metadata-helper.js +1 -1
  124. package/src/pro/storage/index.js +1 -1
  125. package/src/pro/storage/local-storage-provider.js +1 -1
  126. package/src/pro/storage/s3-storage-provider.js +1 -1
  127. package/src/pro/storage/upload-cleanup-job.js +1 -1
  128. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  129. package/src/pro/storage/upload-pending-tracker.js +1 -1
  130. package/src/pro/websocket/broadcast-helper.js +1 -1
  131. package/src/pro/websocket/index.js +1 -1
  132. package/src/pro/websocket/livesync-server.js +1 -1
  133. package/src/pro/websocket/ws-broadcaster.js +1 -1
  134. package/src/services/export-service.js +1 -1
  135. package/src/services/import-service.js +1 -1
  136. package/src/services/kafkaConsumerService.js +1 -1
  137. package/src/services/kafkaService.js +1 -1
  138. package/src/services/messagehubService.js +1 -1
  139. package/src/services/rabbitmqService.js +1 -1
  140. package/src/utils/cache-invalidation-registry.js +1 -1
  141. package/src/utils/cache-manager.js +1 -1
  142. package/src/utils/component-engine.js +1 -1
  143. package/src/utils/config-extractor.js +1 -1
  144. package/src/utils/consumerLogger.js +1 -1
  145. package/src/utils/context-builder.js +1 -1
  146. package/src/utils/dashboard-helpers.js +1 -1
  147. package/src/utils/dateHelper.js +1 -1
  148. package/src/utils/datetime-formatter.js +1 -1
  149. package/src/utils/datetime-parser.js +1 -1
  150. package/src/utils/db-bootstrap.js +1 -1
  151. package/src/utils/db-mysql.js +1 -1
  152. package/src/utils/db-oracle.js +1 -1
  153. package/src/utils/db-sqlite.js +1 -1
  154. package/src/utils/db.js +1 -1
  155. package/src/utils/demo-generator.js +1 -1
  156. package/src/utils/excel-generator.js +1 -1
  157. package/src/utils/excel-parser.js +1 -1
  158. package/src/utils/file-watcher.js +1 -1
  159. package/src/utils/id-generator.js +1 -1
  160. package/src/utils/idempotency-manager.js +1 -1
  161. package/src/utils/import-validator.js +1 -1
  162. package/src/utils/license-client.js +1 -1
  163. package/src/utils/lock-manager.js +1 -1
  164. package/src/utils/logger.js +1 -1
  165. package/src/utils/lookup-resolver.js +1 -1
  166. package/src/utils/payload-loader.js +1 -1
  167. package/src/utils/processor-response.js +1 -1
  168. package/src/utils/rabbitmq.js +1 -1
  169. package/src/utils/redis-client.js +1 -1
  170. package/src/utils/redis-helper.js +1 -1
  171. package/src/utils/request-scope.js +1 -1
  172. package/src/utils/security-checks.js +1 -1
  173. package/src/utils/service-resolver.js +1 -1
  174. package/src/utils/shutdown-coordinator.js +1 -1
  175. package/src/utils/trusted-keys.js +1 -1
  176. package/src/utils/upload-handler.js +1 -1
  177. package/src/utils/upsert-builder.js +1 -1
  178. package/src/utils/workflow-hook-executor.js +1 -1
@@ -0,0 +1,277 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Schema Diff - Shared payload-vs-database comparison
5
+ *
6
+ * Module ini ekstraksi logic dari `SchemaValidator` di payload-runner.js untuk
7
+ * dipakai bersama oleh `endpoint create` (validasi pre-codegen) tanpa mengikat
8
+ * caller ke seluruh ergonomi `payload diff` command (mis. summary console output
9
+ * yang fokus ke batch validation).
10
+ *
11
+ * Audit-column-awareness:
12
+ * Default behavior `runtime` adalah inject 4 kolom audit (`created_at`,
13
+ * `created_by`, `updated_at`, `updated_by`) ke INSERT/UPDATE saat
14
+ * `payload.auditColumns` tidak di-set `false`/`null`. Fungsi ini meng-compute
15
+ * effective field list (eksplisit + audit) dan men-flag kolom audit yang
16
+ * *required* tapi tidak ada di database secara eksplisit, sehingga error
17
+ * runtime "column does not exist" dapat di-detect saat generate-time.
18
+ *
19
+ * @module lib/payload/schema-diff
20
+ */
21
+
22
+ const { resolveEffectiveFieldList, resolveAuditColumnNames, DEFAULT_AUDIT_COLUMNS } = require('../utils/audit-columns');
23
+
24
+ const DEFAULT_EXCLUDED_COLUMNS = DEFAULT_AUDIT_COLUMNS;
25
+
26
+ /**
27
+ * Normalisasi tipe dari fieldValidation payload ke kategori yang sama dengan
28
+ * `normalizeDatabaseType`. Dipakai untuk men-detect type drift antara payload
29
+ * dan database.
30
+ *
31
+ * @param {Object} validation - Entry dari payload.fieldValidation
32
+ * @returns {string|null}
33
+ */
34
+ function normalizePayloadValidationType(validation) {
35
+ if (!validation) return null;
36
+ const t = (validation.type || '').toLowerCase();
37
+ switch (t) {
38
+ case 'uuid': return 'uuid';
39
+ case 'integer': return 'integer';
40
+ case 'number': return 'numeric';
41
+ case 'boolean': return 'boolean';
42
+ case 'date': return 'date';
43
+ case 'datetime': return 'timestamp';
44
+ case 'string': return 'varchar';
45
+ case 'json':
46
+ case 'array': return 'json';
47
+ default: return t || null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Normalisasi tipe data database ke kategori umum untuk perbandingan
53
+ * (mirror dari SchemaValidator.normalizeType di payload-runner.js).
54
+ *
55
+ * @param {string} dataType
56
+ * @param {string} udtName
57
+ * @param {Object} col
58
+ * @returns {string}
59
+ */
60
+ function normalizeDatabaseType(dataType, udtName, col) {
61
+ const dt = (dataType || '').toLowerCase();
62
+ const udt = (udtName || '').toLowerCase();
63
+
64
+ if (dt === 'uuid' || udt === 'uuid') return 'uuid';
65
+ if (dt === 'boolean' || udt === 'bool') return 'boolean';
66
+
67
+ if (['integer', 'bigint', 'smallint', 'serial', 'bigserial', 'smallserial',
68
+ 'int', 'tinyint', 'mediumint'].includes(dt) ||
69
+ ['int4', 'int8', 'int2', 'serial4', 'serial8', 'serial2'].includes(udt)) {
70
+ return 'integer';
71
+ }
72
+
73
+ if (['numeric', 'decimal', 'real', 'double precision', 'float', 'double'].includes(dt) ||
74
+ ['numeric', 'float4', 'float8'].includes(udt)) {
75
+ return 'numeric';
76
+ }
77
+
78
+ if (['character varying', 'varchar', 'varchar2', 'nvarchar2', 'nvarchar'].includes(dt)) return 'varchar';
79
+ if (['text', 'clob', 'nclob', 'longtext', 'mediumtext', 'tinytext'].includes(dt)) return 'text';
80
+ if (['char', 'character', 'nchar'].includes(dt)) return 'char';
81
+
82
+ if (dt === 'date') return 'date';
83
+ if (dt === 'datetime') return 'timestamp';
84
+ if (dt.startsWith('timestamp')) return 'timestamp';
85
+ if (dt.startsWith('time')) return 'time';
86
+
87
+ if (['json', 'jsonb'].includes(dt) || ['json', 'jsonb'].includes(udt)) return 'json';
88
+
89
+ return dt || 'unknown';
90
+ }
91
+
92
+ /**
93
+ * Bandingkan satu payload dengan schema database aktual.
94
+ *
95
+ * Mode strict ini audit-column-aware:
96
+ * - tidak men-skip kolom audit default dari sisi "added" (kolom audit yang
97
+ * di-required oleh payload tapi tidak ada di database dilaporkan sebagai
98
+ * `auditMissing`).
99
+ * - compute removed = field di payload (eksplisit) yang tidak ada di database
100
+ * - compute typeChanges = field yang ada di payload + database tapi tipenya
101
+ * beda (pakai `fieldValidation` di payload sebagai source of truth)
102
+ *
103
+ * Dipakai bersama oleh endpoint create, payload validate, payload diff, dan
104
+ * payload sync sebagai source of truth tunggal untuk drift detection.
105
+ *
106
+ * @param {Object} payload - Processed payload object
107
+ * @param {Object} db - DatabaseIntrospector terkoneksi
108
+ * @returns {Promise<{
109
+ * tableName: string,
110
+ * status: 'ok' | 'drift' | 'error',
111
+ * removed: Array<{ column: string, source: 'payload' | 'audit' }>,
112
+ * added: Array<{ column: string, type: string, nullable: boolean }>,
113
+ * typeChanges: Array<{ column: string, payloadType: string, databaseType: string }>,
114
+ * auditMissing: string[],
115
+ * summary: string
116
+ * }>}
117
+ */
118
+ async function compareSchemaStrict(payload, db) {
119
+ const tableName = payload.tableName;
120
+ const result = {
121
+ tableName,
122
+ status: 'ok',
123
+ removed: [],
124
+ added: [],
125
+ typeChanges: [],
126
+ auditMissing: [],
127
+ summary: ''
128
+ };
129
+
130
+ const dbColumns = await db.getDetailedColumnInfo(tableName);
131
+ if (!Array.isArray(dbColumns) || dbColumns.length === 0) {
132
+ result.status = 'error';
133
+ result.summary = `Table "${tableName}" not found in database`;
134
+ return result;
135
+ }
136
+
137
+ const dbColumnSet = new Set(dbColumns.map(c => c.column_name));
138
+ const dbColumnList = dbColumns.map(c => c.column_name);
139
+ const dbColumnMap = {};
140
+ for (const col of dbColumns) {
141
+ dbColumnMap[col.column_name] = col;
142
+ }
143
+
144
+ const { explicit, audit } = resolveEffectiveFieldList(payload);
145
+
146
+ const explicitSet = new Set(explicit);
147
+ for (const field of explicit) {
148
+ if (!dbColumnSet.has(field)) {
149
+ result.removed.push({ column: field, source: 'payload' });
150
+ }
151
+ }
152
+
153
+ for (const auditCol of audit) {
154
+ if (!dbColumnSet.has(auditCol)) {
155
+ result.auditMissing.push(auditCol);
156
+ }
157
+ }
158
+
159
+ for (const dbCol of dbColumns) {
160
+ const name = dbCol.column_name;
161
+ if (explicitSet.has(name)) continue;
162
+ if (DEFAULT_EXCLUDED_COLUMNS.includes(name)) continue;
163
+ if (audit.includes(name)) continue;
164
+ result.added.push({
165
+ column: name,
166
+ type: normalizeDatabaseType(dbCol.data_type, dbCol.udt_name, dbCol),
167
+ nullable: dbCol.is_nullable === 'YES'
168
+ });
169
+ }
170
+
171
+ // Type drift: hanya untuk field yang ada di kedua sisi DAN punya entry
172
+ // di payload.fieldValidation. Tanpa fieldValidation, type drift tidak bisa
173
+ // di-detect (payload tidak men-claim tipe spesifik).
174
+ const validationMap = {};
175
+ if (Array.isArray(payload.fieldValidation)) {
176
+ for (const fv of payload.fieldValidation) {
177
+ if (fv && fv.name) validationMap[fv.name] = fv;
178
+ }
179
+ }
180
+ for (const field of explicit) {
181
+ const dbCol = dbColumnMap[field];
182
+ if (!dbCol) continue;
183
+ const validation = validationMap[field];
184
+ if (!validation) continue;
185
+
186
+ const dbType = normalizeDatabaseType(dbCol.data_type, dbCol.udt_name, dbCol);
187
+ const payloadType = normalizePayloadValidationType(validation);
188
+ if (!payloadType) continue;
189
+
190
+ if (dbType !== payloadType) {
191
+ result.typeChanges.push({
192
+ column: field,
193
+ payloadType,
194
+ databaseType: dbType
195
+ });
196
+ }
197
+ }
198
+
199
+ const driftCount = result.removed.length + result.added.length
200
+ + result.auditMissing.length + result.typeChanges.length;
201
+ if (driftCount > 0) {
202
+ result.status = 'drift';
203
+ const parts = [];
204
+ if (result.removed.length > 0) parts.push(`${result.removed.length} payload field(s) missing from database`);
205
+ if (result.typeChanges.length > 0) parts.push(`${result.typeChanges.length} type change(s)`);
206
+ if (result.added.length > 0) parts.push(`${result.added.length} new database column(s) not in payload`);
207
+ if (result.auditMissing.length > 0) parts.push(`${result.auditMissing.length} audit column(s) required but missing`);
208
+ result.summary = parts.join(', ');
209
+ } else {
210
+ result.summary = 'Schema is in sync';
211
+ }
212
+
213
+ result.totalColumnsChecked = dbColumnList.length;
214
+ return result;
215
+ }
216
+
217
+ /**
218
+ * Format drift result ke output console untuk endpoint create.
219
+ * Format mengikuti spec phase-01-implementation.md:
220
+ * [-] kolom_a (in payload, not in database)
221
+ * [+] kolom_b (in database, not in payload)
222
+ * [+] created_at, ... (required by auditColumns=true, not in database)
223
+ *
224
+ * @param {Object} comparison - Hasil compareSchemaStrict()
225
+ * @param {Object} options
226
+ * @param {string} options.payloadFileName - Nama file payload (untuk resolution message)
227
+ * @param {string} options.tableName - Nama table
228
+ * @returns {string[]} Lines untuk di-print
229
+ */
230
+ function formatDriftReport(comparison, options = {}) {
231
+ const lines = [];
232
+ const payloadFile = options.payloadFileName || comparison.tableName;
233
+ const tableName = comparison.tableName;
234
+
235
+ lines.push(' [BLOCKED] Payload-database drift detected:');
236
+
237
+ for (const item of comparison.removed) {
238
+ lines.push(` [-] ${item.column.padEnd(12)} (in payload, not in database)`);
239
+ }
240
+
241
+ if (Array.isArray(comparison.typeChanges)) {
242
+ for (const item of comparison.typeChanges) {
243
+ lines.push(` [~] ${item.column.padEnd(12)} (type: ${item.payloadType} -> ${item.databaseType})`);
244
+ }
245
+ }
246
+
247
+ for (const item of comparison.added) {
248
+ lines.push(` [+] ${item.column.padEnd(12)} (in database, not in payload)`);
249
+ }
250
+
251
+ if (comparison.auditMissing.length > 0) {
252
+ const cols = comparison.auditMissing.join(', ');
253
+ lines.push(` [+] ${cols}`);
254
+ lines.push(' (required by auditColumns=true, not in database)');
255
+ }
256
+
257
+ lines.push('');
258
+ lines.push(' Resolution:');
259
+ lines.push(` 1. Run: npx restforge payload sync --table=${tableName} --config=<ENV>`);
260
+ if (comparison.auditMissing.length > 0) {
261
+ lines.push(` 2. Add audit columns to schema OR set "auditColumns": false in payload/${payloadFile}`);
262
+ lines.push(' 3. Re-run endpoint create after resolving');
263
+ } else {
264
+ lines.push(' 2. Tambah kolom missing ke schema untuk match dengan payload');
265
+ lines.push(' 3. Re-run endpoint create after resolving');
266
+ }
267
+
268
+ return lines;
269
+ }
270
+
271
+ module.exports = {
272
+ DEFAULT_EXCLUDED_COLUMNS,
273
+ normalizeDatabaseType,
274
+ normalizePayloadValidationType,
275
+ compareSchemaStrict,
276
+ formatDriftReport
277
+ };
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Audit Columns Helper
5
+ *
6
+ * Resolve nama kolom audit yang efektif berdasarkan konfigurasi payload.
7
+ * Konvensi default mengikuti BaseModel runtime di `src/models/base-model.js`:
8
+ * createdAt -> created_at
9
+ * createdBy -> created_by
10
+ * updatedAt -> updated_at
11
+ * updatedBy -> updated_by
12
+ *
13
+ * Payload dapat override behavior:
14
+ * - `auditColumns: false` (atau null) -> disable audit columns
15
+ * - `auditColumns: { createdAt, createdBy, updatedAt, updatedBy }` -> custom mapping
16
+ * - tidak di-set / true -> pakai default 4 nama standar
17
+ *
18
+ * Helper ini dipakai oleh:
19
+ * - schema-diff: compute effective field list (fieldName ∪ audit columns)
20
+ * - endpoint create: validation pre-codegen
21
+ *
22
+ * @module lib/utils/audit-columns
23
+ */
24
+
25
+ const DEFAULT_AUDIT_COLUMN_MAP = {
26
+ createdAt: 'created_at',
27
+ createdBy: 'created_by',
28
+ updatedAt: 'updated_at',
29
+ updatedBy: 'updated_by'
30
+ };
31
+
32
+ const DEFAULT_AUDIT_COLUMNS = Object.values(DEFAULT_AUDIT_COLUMN_MAP);
33
+
34
+ /**
35
+ * Cek apakah audit columns aktif untuk payload ini.
36
+ *
37
+ * @param {Object} payload - Processed payload object
38
+ * @returns {boolean} true jika audit columns akan di-inject runtime
39
+ */
40
+ function isAuditColumnsEnabled(payload) {
41
+ if (!payload) return false;
42
+ if (!Object.prototype.hasOwnProperty.call(payload, 'auditColumns')) return true;
43
+ const value = payload.auditColumns;
44
+ if (value === false || value === null) return false;
45
+ return true;
46
+ }
47
+
48
+ /**
49
+ * Resolve nama kolom audit (sisi database) yang akan di-inject runtime.
50
+ * Mengembalikan empty array bila audit columns di-disable.
51
+ *
52
+ * @param {Object} payload - Processed payload object
53
+ * @returns {string[]} Array nama kolom database (snake_case)
54
+ */
55
+ function resolveAuditColumnNames(payload) {
56
+ if (!isAuditColumnsEnabled(payload)) return [];
57
+
58
+ const value = payload && payload.auditColumns;
59
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
60
+ const out = [];
61
+ for (const key of Object.keys(DEFAULT_AUDIT_COLUMN_MAP)) {
62
+ const mapped = value[key];
63
+ if (typeof mapped === 'string' && mapped.length > 0) {
64
+ out.push(mapped);
65
+ } else {
66
+ out.push(DEFAULT_AUDIT_COLUMN_MAP[key]);
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+
72
+ return DEFAULT_AUDIT_COLUMNS.slice();
73
+ }
74
+
75
+ /**
76
+ * Resolve effective field list yang dipakai generator codegen:
77
+ * gabungan fieldName eksplisit di payload + audit columns yang akan
78
+ * di-inject runtime.
79
+ *
80
+ * Dipakai oleh schema validation untuk men-detect drift yang berasal
81
+ * dari implicit audit columns (kasus umum: auditColumns aktif tapi
82
+ * tabel database tidak punya kolom audit).
83
+ *
84
+ * @param {Object} payload - Processed payload object
85
+ * @returns {{ fields: string[], audit: string[], explicit: string[] }}
86
+ */
87
+ function resolveEffectiveFieldList(payload) {
88
+ const explicit = Array.isArray(payload && payload.fieldName)
89
+ ? payload.fieldName.slice()
90
+ : [];
91
+ const audit = resolveAuditColumnNames(payload);
92
+
93
+ const merged = [];
94
+ const seen = new Set();
95
+ for (const f of explicit) {
96
+ if (!seen.has(f)) {
97
+ seen.add(f);
98
+ merged.push(f);
99
+ }
100
+ }
101
+ for (const f of audit) {
102
+ if (!seen.has(f)) {
103
+ seen.add(f);
104
+ merged.push(f);
105
+ }
106
+ }
107
+
108
+ return {
109
+ fields: merged,
110
+ audit,
111
+ explicit
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Deteksi alignment kolom audit standar antara payload generator dan tabel
117
+ * database aktual. Helper ini dipakai oleh sync flow (payload-runner) dan
118
+ * initial generation untuk men-set `auditColumns: false` bila kolom audit
119
+ * tidak tersedia di database.
120
+ *
121
+ * Konvensi: kolom audit standar mengikuti DEFAULT_AUDIT_COLUMNS (snake_case).
122
+ * Match dilakukan exact lowercase; bila tabel pakai naming convention lain
123
+ * (mis. `CreatedAt`), audit detection akan miss.
124
+ *
125
+ * @param {string[]} dbColumns - Daftar nama kolom dari tabel database
126
+ * @returns {{
127
+ * all: boolean, // semua 4 kolom audit standar ada di database
128
+ * partial: boolean, // sebagian (1-3) kolom audit ada
129
+ * none: boolean, // tidak satupun kolom audit ada
130
+ * present: string[], // kolom audit yang ada di database
131
+ * missing: string[] // kolom audit yang tidak ada di database
132
+ * }}
133
+ */
134
+ function detectAuditAlignment(dbColumns) {
135
+ const columns = Array.isArray(dbColumns) ? dbColumns : [];
136
+ const present = DEFAULT_AUDIT_COLUMNS.filter(col => columns.includes(col));
137
+ const missing = DEFAULT_AUDIT_COLUMNS.filter(col => !columns.includes(col));
138
+ return {
139
+ all: present.length === DEFAULT_AUDIT_COLUMNS.length,
140
+ partial: present.length > 0 && present.length < DEFAULT_AUDIT_COLUMNS.length,
141
+ none: present.length === 0,
142
+ present,
143
+ missing
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Cek apakah konfigurasi `auditColumns` di payload memerlukan resolusi
149
+ * berdasarkan alignment dengan database. Dipakai sync flow untuk men-detect
150
+ * "audit-only drift" yang tidak ter-detect oleh comparator schema reguler.
151
+ *
152
+ * Aturan resolusi:
153
+ * - `auditColumns: false`/`null` -> no-op (preserved)
154
+ * - `auditColumns: { ... }` (object form) -> no auto-override (manual)
155
+ * - `auditColumns: true` atau tidak di-set (default true) -> butuh resolusi
156
+ * bila database tidak punya keempat kolom audit standar (none atau partial)
157
+ *
158
+ * @param {Object} payload - Processed payload object
159
+ * @param {ReturnType<typeof detectAuditAlignment>} auditAlignment
160
+ * @returns {boolean} true bila sync wajib regenerate untuk men-set false
161
+ */
162
+ function needsAuditColumnResolution(payload, auditAlignment) {
163
+ if (!payload || !auditAlignment) return false;
164
+ const hasField = Object.prototype.hasOwnProperty.call(payload, 'auditColumns');
165
+ const value = hasField ? payload.auditColumns : undefined;
166
+
167
+ if (value === false || value === null) return false;
168
+ if (value && typeof value === 'object' && !Array.isArray(value)) return false;
169
+
170
+ return !auditAlignment.all;
171
+ }
172
+
173
+ module.exports = {
174
+ DEFAULT_AUDIT_COLUMN_MAP,
175
+ DEFAULT_AUDIT_COLUMNS,
176
+ isAuditColumnsEnabled,
177
+ resolveAuditColumnNames,
178
+ resolveEffectiveFieldList,
179
+ detectAuditAlignment,
180
+ needsAuditColumnResolution
181
+ };
@@ -67,6 +67,9 @@ class CliOutput {
67
67
  ? `${data.config.database} (force overwrite)`
68
68
  : data.config.database;
69
69
  out(` Database ${dbLine}`);
70
+ if (data.config.config) {
71
+ out(` Config ${data.config.config}`);
72
+ }
70
73
  out('');
71
74
 
72
75
  if (data.payload) {
@@ -79,6 +82,20 @@ class CliOutput {
79
82
  out('');
80
83
  }
81
84
 
85
+ if (data.schemaValidation) {
86
+ out('Schema Validation:');
87
+ if (data.schemaValidation.status === 'ok') {
88
+ const n = data.schemaValidation.columnsChecked || 0;
89
+ out(` Status OK (${n} column${n === 1 ? '' : 's'} match database)`);
90
+ } else if (data.schemaValidation.status === 'skipped') {
91
+ out(' Status SKIPPED (--skip-schema-check)');
92
+ if (data.schemaValidation.reason) {
93
+ out(` Reason ${data.schemaValidation.reason}`);
94
+ }
95
+ }
96
+ out('');
97
+ }
98
+
82
99
  if (data.archive && data.archive.count > 0) {
83
100
  out('Archive (force):');
84
101
  out(` Generation ${data.archive.generation} - ${data.archive.count} file(s) archived`);
@@ -242,22 +242,25 @@ class DatabaseIntrospector {
242
242
  }
243
243
 
244
244
  /**
245
- * Close database connection
245
+ * Close database connection. Idempotent — panggilan kedua jadi no-op
246
+ * setelah `this.pool` di-null-kan. Ini mencegah pesan "Database connection
247
+ * closed" muncul dua kali ketika caller punya cleanup ganda (mis. cleanup
248
+ * eksplisit + cleanup di catch block).
246
249
  */
247
250
  async close() {
248
- if (this.pool) {
249
- try {
250
- if (this.dbType === 'oracle') {
251
- await this.pool.close(0);
252
- } else {
253
- await this.pool.end();
254
- }
255
- } catch (e) {
256
- // ignore close errors
257
- }
258
- if (!this.quiet) {
259
- console.log('Database connection closed');
251
+ if (!this.pool) return;
252
+ try {
253
+ if (this.dbType === 'oracle') {
254
+ await this.pool.close(0);
255
+ } else {
256
+ await this.pool.end();
260
257
  }
258
+ } catch (e) {
259
+ // ignore close errors
260
+ }
261
+ this.pool = null;
262
+ if (!this.quiet) {
263
+ console.log('Database connection closed');
261
264
  }
262
265
  }
263
266
 
@@ -1,15 +1,15 @@
1
1
  {
2
- "version": "4.1.1",
3
- "generated": "2026-05-19T01:38:06.824Z",
2
+ "version": "4.2.8",
3
+ "generated": "2026-05-21T01:20:53.402Z",
4
4
  "generator": "restforge-build-system",
5
5
  "algorithm": "sha256",
6
6
  "files": {
7
- "server.js": "2bf1e25e7af33bb70dd0f3d5f3f970ed2f5d227c6c5534b4dfe5cb294d59b2f5",
8
- "src/utils/license-client.js": "976511ab72f356595bf1399a50480d340b556f9bce4b71058b90b10d6bd8e793",
9
- "src/utils/trusted-keys.js": "faf93514b43a83650c950c05aca6e42ed64c19fbeb7e3c84e93c509de84bd2c0",
10
- "cli/consumer.js": "e2a5055652cee82d32b6873e21469bc07784a490d125ec7e64171ba06ad31397",
11
- "src/utils/security-checks.js": "1748b39715ff5c1047a730d1e62a22151b38eedc0f48bbd33b1ab4f0ff592d34",
12
- "scripts/verify-integrity.js": "3e891f9baa234362ebcee8d1048fe7bac3221a026c2918cf0e154e5ea3444c87"
7
+ "server.js": "91f5255aacad771c0ff9370c6c76be4a081fa6f264adf1fb5bc77a1f585ddce9",
8
+ "src/utils/license-client.js": "2f9cfbc101e87c9e179a36d7c7e1b8dc2fd78999331efb3752bcb66dafb2364e",
9
+ "src/utils/trusted-keys.js": "2a02c10de6ca639ace51417ac93c74feb00db51a55ebf98f18a831eefef60516",
10
+ "cli/consumer.js": "2815eeba5613515349853107034f3763347a218d1a0ec3f484456a5fb4674143",
11
+ "src/utils/security-checks.js": "04aca0bfcb331befd114e454e0fdeff37a201874394afe0cdba79c9969aed776",
12
+ "scripts/verify-integrity.js": "2f4d06552da2d9204565c3473cde414c24ed6aef326f177acf30ba0ee7d243b8"
13
13
  },
14
14
  "stats": {
15
15
  "totalFiles": 6,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@restforgejs/platform",
3
- "version": "4.1.1",
3
+ "version": "4.2.8",
4
4
  "description": "RESTForge Platform — Schema-driven backend framework and code generator for full-stack Node.js applications. Generates production backend APIs with multi-database support (PostgreSQL, MySQL, Oracle). A platform builder and backend runtime, not an API testing or client tool.",
5
5
  "main": "server.js",
6
6
  "bin": {