@restforgejs/platform 5.1.6 → 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.
- package/bin/restforge-hwinfo-linux +0 -0
- package/bin/restforge-hwinfo.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/fast-track.js +63 -43
- package/generators/cli/payload/generate.js +10 -2
- package/generators/cli/schema/apply.js +6 -1
- package/generators/cli/schema/diff.js +6 -1
- package/generators/cli/schema/introspect.js +32 -11
- package/generators/lib/data/db-executor.js +8 -8
- package/generators/lib/data/envelope.js +3 -3
- package/generators/lib/dbschema-kit/apply-engine.js +20 -0
- package/generators/lib/dbschema-kit/dialect/mysql.js +2 -0
- package/generators/lib/dbschema-kit/dialect/oracle.js +2 -0
- package/generators/lib/dbschema-kit/dialect/postgres.js +4 -0
- package/generators/lib/dbschema-kit/dialect/sqlite.js +5 -0
- package/generators/lib/dbschema-kit/diff-engine.js +22 -1
- package/generators/lib/dbschema-kit/diff-reporter.js +293 -272
- package/generators/lib/dbschema-kit/emitters/create-index.js +23 -1
- package/generators/lib/dbschema-kit/emitters/create-table.js +48 -0
- package/generators/lib/dbschema-kit/introspect-mapper.js +154 -2
- package/generators/lib/dbschema-kit/ir-builder.js +84 -1
- package/generators/lib/dbschema-kit/schema-printer.js +20 -0
- package/generators/lib/dbschema-kit/soft-delete-constants.js +111 -0
- package/generators/lib/dbschema-kit/validator/schema-validator.js +231 -0
- package/generators/lib/generators/processor-validation-generator.js +16 -16
- package/generators/lib/payload/payload-runner.js +711 -1
- package/generators/lib/payload/schema-diff.js +7 -0
- package/generators/lib/templates/dashboard-catalog.js +1 -1
- package/generators/lib/templates/db-connection-env.js +1 -1
- package/generators/lib/templates/dbschema-catalog.js +1 -1
- package/generators/lib/templates/field-validation-catalog.js +1 -1
- package/generators/lib/templates/mysql-template.js +1 -1
- package/generators/lib/templates/oracle-template.js +1 -1
- package/generators/lib/templates/postgres-template.js +1 -1
- package/generators/lib/templates/query-declarative-catalog.js +1 -1
- package/generators/lib/templates/sqlite-template.js +1 -1
- package/generators/lib/utils/database-introspector.js +48 -0
- package/generators/lib/utils/env-manager.js +4 -4
- package/generators/lib/utils/file-utils.js +6 -6
- package/generators/lib/utils/payload-processor.js +18 -2
- package/generators/lib/validators/argument-validator.js +2 -2
- package/generators/lib/validators/dashboard-validator.js +35 -1
- package/generators/lib/validators/payload-validator.js +460 -33
- package/integrity-manifest.json +20 -20
- package/package.json +2 -1
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/soft-delete-dashboard-guard.js +1 -0
- package/src/utils/sql-table-extractor.js +1 -0
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
- 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
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
lines
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
lines.push(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
lines
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
lines
|
|
188
|
-
|
|
189
|
-
lines.push(
|
|
190
|
-
lines.push(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
lines.push('
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|