@restforgejs/platform 5.1.7 → 5.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/catalog/dbschema.js +2 -1
- package/generators/cli/endpoint/list.js +264 -0
- package/generators/cli/fast-track.js +395 -37
- package/generators/cli/payload/generate.js +10 -2
- package/generators/cli/processor/create.js +7 -7
- package/generators/cli/processor/list.js +229 -0
- 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/dashboard-generator.js +5 -5
- package/generators/lib/generators/processor-validation-generator.js +16 -16
- package/generators/lib/payload/payload-runner.js +774 -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/check-install.js +8 -8
- 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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract: processor list
|
|
5
|
+
*
|
|
6
|
+
* Menampilkan daftar processor dari workbench metadata di working directory
|
|
7
|
+
* (`metadata/<project>-workbench.json` map `processors`, output dari
|
|
8
|
+
* `processor create`). File backup (`.backup.` / `.corrupt.`) di-skip.
|
|
9
|
+
*
|
|
10
|
+
* Kolom:
|
|
11
|
+
* - Processor Name : key di `workbench.processors`
|
|
12
|
+
* - Payload : nama file `payload/<processor>.json` bila ada, `-` bila tidak
|
|
13
|
+
* - Processor : daftar function berformat `nama (METHOD)`; di-wrap
|
|
14
|
+
* maksimal 4 function per baris, tanpa pemotongan
|
|
15
|
+
*
|
|
16
|
+
* Output default berupa table human-readable (`--format=text`). Output JSON
|
|
17
|
+
* (untuk konsumsi AI agent / mcp-server) tersedia via `--format=json`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const ConfigReader = require('../../lib/config/config-reader');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Baca semua entry processor dari file workbench metadata di working directory.
|
|
26
|
+
* Read-only: file yang corrupt di-skip dengan warning, tidak ditulis ulang.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} workingDir
|
|
29
|
+
* @returns {{ project: string, name: string, payload: string|null, processor: { name: string, method: string }[] }[]}
|
|
30
|
+
*/
|
|
31
|
+
function readProcessorEntries(workingDir) {
|
|
32
|
+
const metadataDir = path.join(workingDir, 'metadata');
|
|
33
|
+
if (!fs.existsSync(metadataDir)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const files = fs.readdirSync(metadataDir).filter(name =>
|
|
38
|
+
name.endsWith('-workbench.json') &&
|
|
39
|
+
!name.includes('.backup.') &&
|
|
40
|
+
!name.includes('.corrupt.')
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const entries = [];
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
let metadata;
|
|
47
|
+
try {
|
|
48
|
+
metadata = JSON.parse(fs.readFileSync(path.join(metadataDir, file), 'utf8'));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`Warning: skipping unreadable metadata file: ${file} (${error.message})`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!metadata || typeof metadata.processors !== 'object' || metadata.processors === null) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const project = metadata.projectName || path.basename(file, '-workbench.json');
|
|
59
|
+
|
|
60
|
+
for (const [processorName, processorData] of Object.entries(metadata.processors)) {
|
|
61
|
+
const payloadFile = `${processorName}.json`;
|
|
62
|
+
const hasPayload = fs.existsSync(path.join(workingDir, 'payload', payloadFile));
|
|
63
|
+
|
|
64
|
+
const procs = processorData && Array.isArray(processorData.processor)
|
|
65
|
+
? processorData.processor.map(proc => ({
|
|
66
|
+
name: proc.name,
|
|
67
|
+
method: proc.method
|
|
68
|
+
}))
|
|
69
|
+
: [];
|
|
70
|
+
|
|
71
|
+
entries.push({
|
|
72
|
+
project,
|
|
73
|
+
name: processorName,
|
|
74
|
+
payload: hasPayload ? payloadFile : null,
|
|
75
|
+
processor: procs
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
81
|
+
return entries;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Konversi pattern glob sederhana (`*` = karakter apa pun) menjadi RegExp
|
|
86
|
+
* case-insensitive yang match keseluruhan nama. Tanpa wildcard berarti
|
|
87
|
+
* exact match.
|
|
88
|
+
* @param {string} pattern
|
|
89
|
+
* @returns {RegExp}
|
|
90
|
+
*/
|
|
91
|
+
function patternToRegExp(pattern) {
|
|
92
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
93
|
+
return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$', 'i');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Potong string dengan suffix '..' bila melebihi lebar kolom (pola `project list`).
|
|
98
|
+
* @param {string} value
|
|
99
|
+
* @param {number} width
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
function truncate(value, width) {
|
|
103
|
+
return value.length > width - 2
|
|
104
|
+
? value.substring(0, width - 4) + '..'
|
|
105
|
+
: value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Render daftar processor ke format table human-readable.
|
|
110
|
+
* @param {{ name: string, payload: string|null, processor: { name: string, method: string }[] }[]} entries
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
113
|
+
function renderHumanTable(entries) {
|
|
114
|
+
const lines = [];
|
|
115
|
+
|
|
116
|
+
const colName = 24;
|
|
117
|
+
const colPayload = 28;
|
|
118
|
+
const colProcessor = 44;
|
|
119
|
+
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push('Registered Processors:');
|
|
122
|
+
lines.push('');
|
|
123
|
+
lines.push(
|
|
124
|
+
'Processor Name'.padEnd(colName) +
|
|
125
|
+
'Payload'.padEnd(colPayload) +
|
|
126
|
+
'Processor'.padEnd(colProcessor)
|
|
127
|
+
);
|
|
128
|
+
lines.push('-'.repeat(colName + colPayload + colProcessor));
|
|
129
|
+
|
|
130
|
+
const PROCS_PER_LINE = 4;
|
|
131
|
+
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
// Wrap function per 4 item; baris pertama menyatu dengan kolom lain,
|
|
134
|
+
// baris lanjutan di-indent sejajar kolom Processor.
|
|
135
|
+
const labels = entry.processor.map(proc =>
|
|
136
|
+
proc.method ? `${proc.name} (${proc.method})` : proc.name
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const procLines = [];
|
|
140
|
+
if (labels.length === 0) {
|
|
141
|
+
procLines.push('-');
|
|
142
|
+
} else {
|
|
143
|
+
for (let i = 0; i < labels.length; i += PROCS_PER_LINE) {
|
|
144
|
+
const chunk = labels.slice(i, i + PROCS_PER_LINE).join(', ');
|
|
145
|
+
const isLast = i + PROCS_PER_LINE >= labels.length;
|
|
146
|
+
procLines.push(isLast ? chunk : chunk + ',');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
lines.push(
|
|
151
|
+
truncate(entry.name, colName).padEnd(colName) +
|
|
152
|
+
truncate(entry.payload || '-', colPayload).padEnd(colPayload) +
|
|
153
|
+
procLines[0]
|
|
154
|
+
);
|
|
155
|
+
for (const continuation of procLines.slice(1)) {
|
|
156
|
+
lines.push(' '.repeat(colName + colPayload) + continuation);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push(`Total: ${entries.length} processor(s)`);
|
|
162
|
+
lines.push('');
|
|
163
|
+
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
resource: 'processor',
|
|
169
|
+
verb: 'list',
|
|
170
|
+
description: 'Menampilkan daftar processor dari workbench metadata di working directory',
|
|
171
|
+
category: 'management',
|
|
172
|
+
flags: {
|
|
173
|
+
format: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
required: false,
|
|
176
|
+
default: 'text',
|
|
177
|
+
description: 'Format output: `text` (table human-readable) atau `json` (untuk AI agent / mcp-server)'
|
|
178
|
+
},
|
|
179
|
+
name: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
required: false,
|
|
182
|
+
default: null,
|
|
183
|
+
description: 'Filter nama processor, case-insensitive. Wildcard `*` = karakter apa pun (mis. `*order*`); tanpa wildcard berarti exact match'
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
examples: [
|
|
187
|
+
'npx restforge processor list',
|
|
188
|
+
'npx restforge processor list --format=json',
|
|
189
|
+
'npx restforge processor list --name=*order*'
|
|
190
|
+
],
|
|
191
|
+
async handler(args) {
|
|
192
|
+
const workingDir = ConfigReader.getWorkingDirectory();
|
|
193
|
+
const allEntries = readProcessorEntries(workingDir);
|
|
194
|
+
const format = (args.format || 'text').toLowerCase();
|
|
195
|
+
|
|
196
|
+
const entries = args.name
|
|
197
|
+
? allEntries.filter(e => patternToRegExp(args.name).test(e.name))
|
|
198
|
+
: allEntries;
|
|
199
|
+
|
|
200
|
+
if (format === 'json') {
|
|
201
|
+
const summary = {
|
|
202
|
+
totalProcessors: entries.length,
|
|
203
|
+
projects: [...new Set(entries.map(e => e.project))].sort()
|
|
204
|
+
};
|
|
205
|
+
if (args.name) {
|
|
206
|
+
summary.namePattern = args.name;
|
|
207
|
+
}
|
|
208
|
+
const output = {
|
|
209
|
+
schemaVersion: '1.0',
|
|
210
|
+
source: 'processor-list',
|
|
211
|
+
summary,
|
|
212
|
+
processors: entries
|
|
213
|
+
};
|
|
214
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (allEntries.length === 0) {
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('No processors found in this project.');
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('Use "npx restforge processor create" to create a new processor.');
|
|
223
|
+
console.log('');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
process.stdout.write(renderHumanTable(entries) + '\n');
|
|
228
|
+
}
|
|
229
|
+
};
|
|
@@ -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,
|