@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
|
@@ -28,6 +28,11 @@ const http = require('node:http');
|
|
|
28
28
|
const readline = require('node:readline');
|
|
29
29
|
const { spawnSync } = require('node:child_process');
|
|
30
30
|
|
|
31
|
+
// Loader SDF yang SAMA dengan `schema migrate`. Bersifat rekursif sehingga
|
|
32
|
+
// struktur per-schema (mis. `schema/public/visitors.js`) ikut terbaca, dan
|
|
33
|
+
// mengembalikan IR model dengan relasi ter-normalisasi (sumber FK yang andal).
|
|
34
|
+
const { loadSchemaPath } = require('../lib/dbschema-kit/loader');
|
|
35
|
+
|
|
31
36
|
// ---------------------------------------------------------------------------
|
|
32
37
|
// Default konfigurasi (selaras dengan restforge-playbook/fast-track.mjs)
|
|
33
38
|
// ---------------------------------------------------------------------------
|
|
@@ -207,9 +212,11 @@ const FALLBACK_TABLES = [
|
|
|
207
212
|
];
|
|
208
213
|
|
|
209
214
|
/**
|
|
210
|
-
* Mengumpulkan nama tabel dari folder schema bila ada
|
|
215
|
+
* Mengumpulkan nama tabel dari folder schema bila ada (rekursif, termasuk
|
|
216
|
+
* subfolder per schema seperti `schema/public/*.js`); bila tidak, memakai
|
|
211
217
|
* daftar fallback yang dipadkan sampai 30 entri agar contoh kanonik (30 tabel)
|
|
212
|
-
* tetap dapat dirender.
|
|
218
|
+
* tetap dapat dirender. Nama yang dikumpulkan adalah kebab tabel agar count
|
|
219
|
+
* preview konsisten dengan file payload/endpoint yang akan dibuat.
|
|
213
220
|
*/
|
|
214
221
|
function collectTables(schemaDir) {
|
|
215
222
|
let names = [];
|
|
@@ -217,10 +224,7 @@ function collectTables(schemaDir) {
|
|
|
217
224
|
|
|
218
225
|
try {
|
|
219
226
|
if (schemaDir && fs.existsSync(schemaDir) && fs.statSync(schemaDir).isDirectory()) {
|
|
220
|
-
names =
|
|
221
|
-
.filter((f) => f.endsWith('.js') && !f.startsWith('_') && !f.endsWith('.test.js'))
|
|
222
|
-
.map((f) => f.slice(0, -3))
|
|
223
|
-
.sort();
|
|
227
|
+
names = loadModels(schemaDir).map((m) => m.kebab).sort();
|
|
224
228
|
if (names.length > 0) realSchema = true;
|
|
225
229
|
}
|
|
226
230
|
} catch (_err) {
|
|
@@ -497,48 +501,64 @@ function ensureEnv(ctx) {
|
|
|
497
501
|
const AUDIT_COLS = new Set(['created_at', 'created_by', 'updated_at', 'updated_by']);
|
|
498
502
|
|
|
499
503
|
/**
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
+
* Turunkan kebab dari NAMA TABEL (bukan nama file) dengan normalisasi
|
|
505
|
+
* `[._]` -> `-`, identik dengan penamaan file output `payload generate`
|
|
506
|
+
* (payload-runner: baseFilename = table.replace(/[._]/g, '-')). Dengan ini
|
|
507
|
+
* `endpoint create --payload=<kebab>.json` dan `payload migrate --name=<kebab>.json`
|
|
508
|
+
* menemukan file payload yang benar, baik untuk SDF bernama underscore (hasil
|
|
509
|
+
* `schema introspect`, mis. `visitor_categories.js`) maupun kebab.
|
|
510
|
+
*/
|
|
511
|
+
function tableToKebab(table) {
|
|
512
|
+
return table.replace(/[._]/g, '-');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Muat seluruh SDF di folder memakai loader dbschema-kit yang SAMA dengan
|
|
517
|
+
* `schema migrate`. Loader bersifat REKURSIF sehingga struktur per-schema
|
|
518
|
+
* (mis. `schema/public/visitors.js`) ikut terbaca; flat readdir lama hanya
|
|
519
|
+
* membaca top-level sehingga subfolder schema tidak dikenali.
|
|
520
|
+
*
|
|
521
|
+
* Mengembalikan descriptor per tabel (deterministik dari SDF, tanpa DB):
|
|
522
|
+
* { kebab, table, fks[{childCol,parentTable,parentCol}], displayCols }
|
|
523
|
+
*
|
|
524
|
+
* FK dibaca dari `relations` (belongsTo) pada IR model. IR menormalkan KEDUA
|
|
525
|
+
* gaya format: blok `relations:` eksplisit (gaya `schema introspect`) DAN inline
|
|
526
|
+
* `fk:<parent>.<col>` pada field (di-auto-promote ke relations oleh ir-builder).
|
|
527
|
+
* displayCols = kolom code/name di luar PK & kolom audit, dipakai sebagai kolom
|
|
528
|
+
* display saat tabel ini menjadi parent FK (untuk `payload sync --expand-fk`).
|
|
504
529
|
*/
|
|
505
|
-
function
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
530
|
+
function loadModels(schemaDir) {
|
|
531
|
+
const models = loadSchemaPath(schemaDir);
|
|
532
|
+
const entries = [];
|
|
533
|
+
for (const model of models.values()) {
|
|
534
|
+
const table = model.tableName;
|
|
535
|
+
const primaryKey = Array.isArray(model.primaryKey) ? model.primaryKey : [];
|
|
536
|
+
|
|
537
|
+
const fks = [];
|
|
538
|
+
for (const rel of Object.values(model.relations || {})) {
|
|
539
|
+
if (rel.type !== 'belongsTo' || !rel.localKey || !rel.target) continue;
|
|
540
|
+
fks.push({
|
|
541
|
+
childCol: rel.localKey,
|
|
542
|
+
parentTable: rel.target,
|
|
543
|
+
parentCol: rel.references || null
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const displayCols = Object.keys(model.fields || {}).filter((name) => {
|
|
548
|
+
const field = model.fields[name];
|
|
549
|
+
const isPk = (field && field.pk === true) || primaryKey.includes(name);
|
|
550
|
+
return !isPk && !AUDIT_COLS.has(name) && /(code|name)/i.test(name);
|
|
523
551
|
});
|
|
524
|
-
}
|
|
525
|
-
const pk = (fields.find((f) => f.isPk) || {}).name || null;
|
|
526
|
-
const displayCols = fields
|
|
527
|
-
.filter((f) => !f.isPk && !f.isAudit && /(code|name)/i.test(f.name))
|
|
528
|
-
.map((f) => f.name);
|
|
529
|
-
const fks = fields
|
|
530
|
-
.filter((f) => f.fk)
|
|
531
|
-
.map((f) => ({ childCol: f.name, parentTable: f.fk.parentTable, parentCol: f.fk.parentCol }));
|
|
532
552
|
|
|
533
|
-
|
|
553
|
+
entries.push({ kebab: tableToKebab(table), table, fks, displayCols });
|
|
554
|
+
}
|
|
555
|
+
entries.sort((a, b) => a.table.localeCompare(b.table));
|
|
556
|
+
return entries;
|
|
534
557
|
}
|
|
535
558
|
|
|
536
|
-
/** Bangun daftar model dari folder schema. */
|
|
559
|
+
/** Bangun daftar model dari folder schema (rekursif, via loader bersama). */
|
|
537
560
|
function buildTableEntries(schemaDir) {
|
|
538
|
-
|
|
539
|
-
.filter((f) => f.endsWith('.js') && !f.startsWith('_') && !f.endsWith('.test.js'))
|
|
540
|
-
.sort();
|
|
541
|
-
return files.map((f) => parseModel(path.join(schemaDir, f), f.slice(0, -3)));
|
|
561
|
+
return loadModels(schemaDir);
|
|
542
562
|
}
|
|
543
563
|
|
|
544
564
|
/**
|
|
@@ -946,5 +966,5 @@ module.exports = {
|
|
|
946
966
|
// terpisah tanpa menjalankan pipeline penuh. Pada penggunaan CLI normal env ini
|
|
947
967
|
// tidak di-set sehingga export tetap berupa contract murni.
|
|
948
968
|
if (process.env.FASTTRACK_TEST === '1') {
|
|
949
|
-
module.exports.__test = {
|
|
969
|
+
module.exports.__test = { loadModels, buildTableEntries, fkColumnsForEntry, tableToKebab };
|
|
950
970
|
}
|
|
@@ -36,18 +36,26 @@ module.exports = {
|
|
|
36
36
|
required: false,
|
|
37
37
|
default: null,
|
|
38
38
|
description: 'Output directory untuk file payload yang di-generate (default: payload/)'
|
|
39
|
+
},
|
|
40
|
+
'schema-path': {
|
|
41
|
+
type: 'string',
|
|
42
|
+
required: false,
|
|
43
|
+
default: 'schema',
|
|
44
|
+
description: 'Lokasi SDF (file atau folder) sumber metadata tabel (default: folder `schema`). WAJIB memuat tabel bila tabel memiliki kolom soft-delete (is_deleted/deleted_at/deleted_by): blok softDelete RDF diturunkan dari SDF (R12/R13). Diabaikan untuk tabel non-soft-delete.'
|
|
39
45
|
}
|
|
40
46
|
},
|
|
41
47
|
examples: [
|
|
42
48
|
'npx restforge payload generate --config=db.env --table=users',
|
|
43
|
-
'npx restforge payload generate --config=db.env --table=users --output=./my-payloads'
|
|
49
|
+
'npx restforge payload generate --config=db.env --table=users --output=./my-payloads',
|
|
50
|
+
'npx restforge payload generate --config=db.env --table=visitor_categories --schema-path=./schema/visitor-categories.js'
|
|
44
51
|
],
|
|
45
52
|
async handler(args) {
|
|
46
53
|
const generator = new PayloadGenerator();
|
|
47
54
|
await generator.run({
|
|
48
55
|
config: args.config,
|
|
49
56
|
table: args.table,
|
|
50
|
-
output: args.output || null
|
|
57
|
+
output: args.output || null,
|
|
58
|
+
'schema-path': args['schema-path']
|
|
51
59
|
});
|
|
52
60
|
}
|
|
53
61
|
};
|
|
@@ -80,6 +80,7 @@ async function collectTableMeta(introspector, target) {
|
|
|
80
80
|
const constraints = await introspector.getConstraints(ref);
|
|
81
81
|
const foreignKeys = await introspector.getForeignKeys(ref);
|
|
82
82
|
const indexes = await introspector.getIndexes(ref);
|
|
83
|
+
const checks = await introspector.getCheckConstraints(ref);
|
|
83
84
|
|
|
84
85
|
const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
|
|
85
86
|
const uniqueConstraints = constraints
|
|
@@ -94,7 +95,11 @@ async function collectTableMeta(introspector, target) {
|
|
|
94
95
|
uniques: uniqueConstraints,
|
|
95
96
|
foreignKeys,
|
|
96
97
|
indexes,
|
|
97
|
-
|
|
98
|
+
// introspect-mapper expects { name, expression }; getCheckConstraints
|
|
99
|
+
// returns { name, clause } (D5 wiring, Phase 03).
|
|
100
|
+
checks: Array.isArray(checks)
|
|
101
|
+
? checks.map((c) => ({ name: c.name, expression: c.clause }))
|
|
102
|
+
: []
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -69,6 +69,7 @@ async function collectTableMeta(introspector, target) {
|
|
|
69
69
|
const constraints = await introspector.getConstraints(ref);
|
|
70
70
|
const foreignKeys = await introspector.getForeignKeys(ref);
|
|
71
71
|
const indexes = await introspector.getIndexes(ref);
|
|
72
|
+
const checks = await introspector.getCheckConstraints(ref);
|
|
72
73
|
|
|
73
74
|
const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
|
|
74
75
|
const uniqueConstraints = constraints
|
|
@@ -83,7 +84,11 @@ async function collectTableMeta(introspector, target) {
|
|
|
83
84
|
uniques: uniqueConstraints,
|
|
84
85
|
foreignKeys,
|
|
85
86
|
indexes,
|
|
86
|
-
|
|
87
|
+
// introspect-mapper expects { name, expression }; getCheckConstraints
|
|
88
|
+
// returns { name, clause } (D5 wiring, Phase 03).
|
|
89
|
+
checks: Array.isArray(checks)
|
|
90
|
+
? checks.map((c) => ({ name: c.name, expression: c.clause }))
|
|
91
|
+
: []
|
|
87
92
|
};
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -146,6 +146,7 @@ async function collectTableMeta(introspector, target) {
|
|
|
146
146
|
const constraints = await introspector.getConstraints(ref);
|
|
147
147
|
const foreignKeys = await introspector.getForeignKeys(ref);
|
|
148
148
|
const indexes = await introspector.getIndexes(ref);
|
|
149
|
+
const checks = await introspector.getCheckConstraints(ref);
|
|
149
150
|
|
|
150
151
|
const pkConstraint = constraints.find((c) => c.type === 'PRIMARY KEY');
|
|
151
152
|
const uniqueConstraints = constraints
|
|
@@ -160,7 +161,11 @@ async function collectTableMeta(introspector, target) {
|
|
|
160
161
|
uniques: uniqueConstraints,
|
|
161
162
|
foreignKeys,
|
|
162
163
|
indexes,
|
|
163
|
-
|
|
164
|
+
// introspect-mapper expects { name, expression }; getCheckConstraints
|
|
165
|
+
// returns { name, clause } (D5 wiring, Phase 03).
|
|
166
|
+
checks: Array.isArray(checks)
|
|
167
|
+
? checks.map((c) => ({ name: c.name, expression: c.clause }))
|
|
168
|
+
: []
|
|
164
169
|
};
|
|
165
170
|
}
|
|
166
171
|
|
|
@@ -406,16 +411,32 @@ module.exports = {
|
|
|
406
411
|
}
|
|
407
412
|
|
|
408
413
|
const dialect = config.dialect;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
414
|
+
let emitted;
|
|
415
|
+
try {
|
|
416
|
+
emitted = result.tables.map((tableMeta) => {
|
|
417
|
+
// strictSoftDelete: introspect ERROR (R6) bila kolom soft-delete tidak
|
|
418
|
+
// lengkap / tipe salah / CHECK konsistensi hilang, dan tidak menghasilkan SDF.
|
|
419
|
+
const ir = mapTableMetaToIR(tableMeta, dialect, { strictSoftDelete: true });
|
|
420
|
+
const source = serialize(ir);
|
|
421
|
+
return {
|
|
422
|
+
tableName: ir.tableName,
|
|
423
|
+
schemaName: ir.schemaName,
|
|
424
|
+
qualifiedName: ir.qualifiedName,
|
|
425
|
+
source
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
} catch (err) {
|
|
429
|
+
if (err && err.isSoftDeleteIntrospectError) {
|
|
430
|
+
// Pesan multi-baris (R6/R8) sudah lengkap; print apa adanya lalu rethrow
|
|
431
|
+
// silent agar cli-entry tidak menambah prefix "Error: " yang merusak format.
|
|
432
|
+
console.error(err.message);
|
|
433
|
+
const e = new Error(err.message);
|
|
434
|
+
e.silent = true;
|
|
435
|
+
e.exitCode = 1;
|
|
436
|
+
throw e;
|
|
437
|
+
}
|
|
438
|
+
throw err;
|
|
439
|
+
}
|
|
419
440
|
|
|
420
441
|
const schemaSet = new Set();
|
|
421
442
|
for (const item of emitted) {
|
|
@@ -38,33 +38,33 @@ function loadDriver(dialect) {
|
|
|
38
38
|
case 'postgresql':
|
|
39
39
|
if (!pgDriver) {
|
|
40
40
|
try { pgDriver = require('pg'); } catch (_e) {
|
|
41
|
-
throw new Error('PostgreSQL driver (pg)
|
|
41
|
+
throw new Error('PostgreSQL driver (pg) is not installed. Run: npm install pg');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
return pgDriver;
|
|
45
45
|
case 'mysql':
|
|
46
46
|
if (!mysqlDriver) {
|
|
47
47
|
try { mysqlDriver = require('mysql2/promise'); } catch (_e) {
|
|
48
|
-
throw new Error('MySQL driver (mysql2)
|
|
48
|
+
throw new Error('MySQL driver (mysql2) is not installed. Run: npm install mysql2');
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
return mysqlDriver;
|
|
52
52
|
case 'oracle':
|
|
53
53
|
if (!oracleDriver) {
|
|
54
54
|
try { oracleDriver = require('oracledb'); } catch (_e) {
|
|
55
|
-
throw new Error('Oracle driver (oracledb)
|
|
55
|
+
throw new Error('Oracle driver (oracledb) is not installed. Run: npm install oracledb');
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
return oracleDriver;
|
|
59
59
|
case 'sqlite':
|
|
60
60
|
if (!sqliteDriver) {
|
|
61
61
|
try { sqliteDriver = require('better-sqlite3'); } catch (_e) {
|
|
62
|
-
throw new Error('SQLite driver (better-sqlite3)
|
|
62
|
+
throw new Error('SQLite driver (better-sqlite3) is not installed. Run: npm rebuild better-sqlite3');
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
return sqliteDriver;
|
|
66
66
|
default:
|
|
67
|
-
throw new Error(`
|
|
67
|
+
throw new Error(`Unsupported dialect: ${dialect}. Supported: postgresql, mysql, oracle, sqlite`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -362,7 +362,7 @@ function createSqliteExecutor(config) {
|
|
|
362
362
|
const Database = loadDriver('sqlite');
|
|
363
363
|
const filePath = config.file || config.database;
|
|
364
364
|
if (!filePath) {
|
|
365
|
-
throw new Error('SQLite:
|
|
365
|
+
throw new Error('SQLite: the database file path is required in config (field "file" or "database")');
|
|
366
366
|
}
|
|
367
367
|
let db = null;
|
|
368
368
|
|
|
@@ -419,7 +419,7 @@ function createSqliteExecutor(config) {
|
|
|
419
419
|
*/
|
|
420
420
|
function createExecutor(config) {
|
|
421
421
|
if (!config || typeof config !== 'object') {
|
|
422
|
-
throw new Error('createExecutor: config
|
|
422
|
+
throw new Error('createExecutor: config must be an object produced by loadConfig');
|
|
423
423
|
}
|
|
424
424
|
const dialect = normalizeDialect(config.dialect);
|
|
425
425
|
switch (dialect) {
|
|
@@ -428,7 +428,7 @@ function createExecutor(config) {
|
|
|
428
428
|
case 'oracle': return createOracleExecutor(config);
|
|
429
429
|
case 'sqlite': return createSqliteExecutor(config);
|
|
430
430
|
default:
|
|
431
|
-
throw new Error(`createExecutor: dialect
|
|
431
|
+
throw new Error(`createExecutor: unsupported dialect '${config.dialect}'`);
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
|
|
@@ -55,7 +55,7 @@ function resolveSink(sink) {
|
|
|
55
55
|
if (typeof sink.write === 'function') {
|
|
56
56
|
return { write: (chunk) => sink.write(chunk), buffer: null };
|
|
57
57
|
}
|
|
58
|
-
throw new Error('EnvelopeWriter: sink
|
|
58
|
+
throw new Error('EnvelopeWriter: sink must be a function, an object with a write() method, or undefined');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -116,7 +116,7 @@ class EnvelopeWriter {
|
|
|
116
116
|
*/
|
|
117
117
|
appendRows(rows) {
|
|
118
118
|
if (this._state !== 'header') {
|
|
119
|
-
throw new Error('EnvelopeWriter: appendRows()
|
|
119
|
+
throw new Error('EnvelopeWriter: appendRows() must be called after writeHeader() and before end()');
|
|
120
120
|
}
|
|
121
121
|
if (!Array.isArray(rows)) {
|
|
122
122
|
throw new Error('EnvelopeWriter: appendRows() membutuhkan array rows');
|
|
@@ -138,7 +138,7 @@ class EnvelopeWriter {
|
|
|
138
138
|
throw new Error('EnvelopeWriter: end() dipanggil sebelum writeHeader()');
|
|
139
139
|
}
|
|
140
140
|
if (this._state === 'ended') {
|
|
141
|
-
throw new Error('EnvelopeWriter: end()
|
|
141
|
+
throw new Error('EnvelopeWriter: end() has already been called');
|
|
142
142
|
}
|
|
143
143
|
this._write(']}');
|
|
144
144
|
this._state = 'ended';
|
|
@@ -645,6 +645,26 @@ function noteDeferredSections(delta, skipped) {
|
|
|
645
645
|
});
|
|
646
646
|
}
|
|
647
647
|
}
|
|
648
|
+
|
|
649
|
+
// Soft-delete CHECK drift — Detection-only di Fase 1 (TIDAK ada emitter ALTER ADD
|
|
650
|
+
// CHECK). apply tidak meng-auto-retrofit; cukup laporkan + rekomendasi recreate/manual.
|
|
651
|
+
if (delta.softDelete && delta.softDelete.match === false) {
|
|
652
|
+
const sd = delta.softDelete;
|
|
653
|
+
const description = (sd.sdf && !sd.db)
|
|
654
|
+
? `Soft-delete consistency CHECK missing in DB for '${delta.tableName}'. ` +
|
|
655
|
+
`Detection-only in Phase 1 (no ALTER ADD CHECK): retrofit via ` +
|
|
656
|
+
`'schema migrate --drop' (recreate) or add the CHECK manually.`
|
|
657
|
+
: `Soft-delete state mismatch for '${delta.tableName}' ` +
|
|
658
|
+
`(DB has the consistency CHECK, SDF does not declare softDelete.enabled). ` +
|
|
659
|
+
`Detection-only: reconcile SDF/DB manually.`;
|
|
660
|
+
skipped.push({
|
|
661
|
+
table: delta.tableName,
|
|
662
|
+
kind: 'soft-delete-check',
|
|
663
|
+
target: 'soft_delete_consistency',
|
|
664
|
+
reason: 'detection-only',
|
|
665
|
+
description
|
|
666
|
+
});
|
|
667
|
+
}
|
|
648
668
|
}
|
|
649
669
|
|
|
650
670
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -84,6 +84,8 @@ module.exports = {
|
|
|
84
84
|
maxIdentifierLength: 64,
|
|
85
85
|
// Boolean disimpan sebagai VARCHAR(5) 'true'/'false'; bukan native.
|
|
86
86
|
nativeBoolean: false,
|
|
87
|
+
// MySQL tidak punya partial index efisien; index soft-delete jadi plain (R11).
|
|
88
|
+
supportsPartialIndex: false,
|
|
87
89
|
mapType,
|
|
88
90
|
formatDefault,
|
|
89
91
|
quoteIdentifier,
|
|
@@ -89,6 +89,8 @@ module.exports = {
|
|
|
89
89
|
maxIdentifierLength: 128,
|
|
90
90
|
// Boolean disimpan sebagai VARCHAR2(5) 'true'/'false'; bukan native.
|
|
91
91
|
nativeBoolean: false,
|
|
92
|
+
// Oracle tidak punya partial index efisien; index soft-delete jadi plain (R11).
|
|
93
|
+
supportsPartialIndex: false,
|
|
92
94
|
mapType,
|
|
93
95
|
formatDefault,
|
|
94
96
|
quoteIdentifier,
|
|
@@ -84,6 +84,10 @@ module.exports = {
|
|
|
84
84
|
// PostgreSQL punya tipe BOOLEAN native; dialect lain menyimpan boolean
|
|
85
85
|
// sebagai VARCHAR 'true'/'false' dan butuh CHECK sebagai penanda boolean.
|
|
86
86
|
nativeBoolean: true,
|
|
87
|
+
// PostgreSQL mendukung partial index (`CREATE INDEX ... WHERE <predikat>`).
|
|
88
|
+
// Dipakai R10/R11: index non-unique pada tabel soft-delete jadi partial
|
|
89
|
+
// (`WHERE is_deleted = FALSE`).
|
|
90
|
+
supportsPartialIndex: true,
|
|
87
91
|
mapType,
|
|
88
92
|
formatDefault,
|
|
89
93
|
quoteIdentifier,
|
|
@@ -87,6 +87,11 @@ module.exports = {
|
|
|
87
87
|
maxIdentifierLength: 63,
|
|
88
88
|
// Boolean disimpan sebagai VARCHAR(5) 'true'/'false'; bukan native.
|
|
89
89
|
nativeBoolean: false,
|
|
90
|
+
// Flag ini menandai apakah index non-unique soft-delete dimaterialisasi sebagai
|
|
91
|
+
// partial index (R10/R11). Per plan master R11, SQLite masuk grup plain index
|
|
92
|
+
// (bersama MySQL/Oracle), jadi false. (Soft-delete Fase 1 juga PostgreSQL-only via
|
|
93
|
+
// guard di emitter, sehingga jalur ini belum aktif untuk SQLite.)
|
|
94
|
+
supportsPartialIndex: false,
|
|
90
95
|
mapType,
|
|
91
96
|
formatDefault,
|
|
92
97
|
quoteIdentifier,
|
|
@@ -588,6 +588,24 @@ function diffChecks(sdfChecks, dbChecks) {
|
|
|
588
588
|
return { onlyInSdf, onlyInDb, mismatched };
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
// ─────────────────────────────────────────────────────────────
|
|
592
|
+
// Soft-delete comparison (R8 poin 2 / R6 reverse)
|
|
593
|
+
// ─────────────────────────────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
// Bandingkan status soft-delete kedua IR. CHECK konsistensi soft-delete bersifat
|
|
596
|
+
// IMPLISIT (diturunkan dari `softDelete.enabled`, tidak pernah di-list di `checks`).
|
|
597
|
+
// introspect-mapper men-set `dbIR.softDelete = { enabled: true }` HANYA bila tabel DB
|
|
598
|
+
// valid (3 kolom + tipe benar + CHECK ada by-nama), dan mengecualikan CHECK soft-delete
|
|
599
|
+
// dari `checks` di kedua sisi. Maka:
|
|
600
|
+
// - enabled di kedua sisi → CHECK implisit expected di keduanya → tidak ada drift.
|
|
601
|
+
// - SDF enabled tetapi DB tidak (CHECK absen / kolom-tipe tak valid) → drift NYATA
|
|
602
|
+
// (Detection-only; dilaporkan apa adanya, bukan disembunyikan, bukan di-auto-fix).
|
|
603
|
+
function diffSoftDelete(sdfIR, dbIR) {
|
|
604
|
+
const sdf = !!(sdfIR && sdfIR.softDelete && sdfIR.softDelete.enabled === true);
|
|
605
|
+
const db = !!(dbIR && dbIR.softDelete && dbIR.softDelete.enabled === true);
|
|
606
|
+
return { sdf, db, match: sdf === db };
|
|
607
|
+
}
|
|
608
|
+
|
|
591
609
|
// ─────────────────────────────────────────────────────────────
|
|
592
610
|
// Top-level API
|
|
593
611
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -610,6 +628,7 @@ function hasAnyDrift(delta) {
|
|
|
610
628
|
if (delta.checks && delta.checks.onlyInSdf.length > 0) return true;
|
|
611
629
|
if (delta.checks && delta.checks.onlyInDb.length > 0) return true;
|
|
612
630
|
if (delta.checks && delta.checks.mismatched.length > 0) return true;
|
|
631
|
+
if (delta.softDelete && delta.softDelete.match === false) return true;
|
|
613
632
|
return false;
|
|
614
633
|
}
|
|
615
634
|
|
|
@@ -631,7 +650,8 @@ function diffModel(sdfIR, dbIR) {
|
|
|
631
650
|
indexes: diffIndexes(sdfIR.indexes, dbIR.indexes),
|
|
632
651
|
uniques: diffUniques(sdfIR.uniques, dbIR.uniques),
|
|
633
652
|
foreignKeys: diffForeignKeys(sdfIR, dbIR),
|
|
634
|
-
checks: diffChecks(sdfIR.checks, dbIR.checks)
|
|
653
|
+
checks: diffChecks(sdfIR.checks, dbIR.checks),
|
|
654
|
+
softDelete: diffSoftDelete(sdfIR, dbIR)
|
|
635
655
|
};
|
|
636
656
|
delta.hasDrift = hasAnyDrift(delta);
|
|
637
657
|
|
|
@@ -706,6 +726,7 @@ module.exports = {
|
|
|
706
726
|
diffForeignKeys,
|
|
707
727
|
extractForeignKeys,
|
|
708
728
|
diffChecks,
|
|
729
|
+
diffSoftDelete,
|
|
709
730
|
compareDefaultValue,
|
|
710
731
|
canonicalDefaultPayload,
|
|
711
732
|
normalizeWeakDefault,
|