@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,6 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { generateConstraintName, deriveCompositeShortName, shortenIdentifier } = require('../naming');
|
|
4
|
+
const {
|
|
5
|
+
IS_DELETED_COLUMN,
|
|
6
|
+
DELETED_AT_COLUMN,
|
|
7
|
+
DELETED_BY_COLUMN,
|
|
8
|
+
isSoftDeleteEnabled,
|
|
9
|
+
assertSoftDeletePostgresOnly,
|
|
10
|
+
softDeleteCheckName
|
|
11
|
+
} = require('../soft-delete-constants');
|
|
4
12
|
|
|
5
13
|
const COLUMN_INDENT = ' ';
|
|
6
14
|
|
|
@@ -17,6 +25,10 @@ const OP_TO_SQL = {
|
|
|
17
25
|
function emitCreateTable(ir, dialect) {
|
|
18
26
|
const { tableName, fields, primaryKey, uniques, relations, checks } = ir;
|
|
19
27
|
|
|
28
|
+
// Guard Fase 1: soft-delete hanya didukung PostgreSQL. Non-PG + softDelete → ERROR
|
|
29
|
+
// (jangan emit bentuk boolean VARCHAR salah diam-diam).
|
|
30
|
+
assertSoftDeletePostgresOnly(ir, dialect);
|
|
31
|
+
|
|
20
32
|
const lines = [];
|
|
21
33
|
|
|
22
34
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
@@ -56,9 +68,45 @@ function emitCreateTable(ir, dialect) {
|
|
|
56
68
|
lines.push(emitCheck(check, checkNames[i], dialect));
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
// CHECK konsistensi soft-delete (R8). Artifact IMPLISIT: tidak pernah ditulis di SDF,
|
|
72
|
+
// selalu diturunkan dari `softDelete.enabled` (pola sama dengan deriveBooleanChecks).
|
|
73
|
+
// Compound multi-field, jadi TIDAK lewat jalur emitCheck generik (single-field).
|
|
74
|
+
if (isSoftDeleteEnabled(ir)) {
|
|
75
|
+
lines.push(emitSoftDeleteCheck(tableName, dialect));
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
return `CREATE TABLE ${dialect.formatTableIdentifier(ir)} (\n${lines.join(',\n')}\n)`;
|
|
60
79
|
}
|
|
61
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Emit CHECK konsistensi tiga kolom soft-delete (R8). Bentuk PostgreSQL (Fase 1,
|
|
83
|
+
* boolean native):
|
|
84
|
+
*
|
|
85
|
+
* CONSTRAINT <name> CHECK (
|
|
86
|
+
* (is_deleted = TRUE AND deleted_at IS NOT NULL AND deleted_by IS NOT NULL)
|
|
87
|
+
* OR (is_deleted = FALSE AND deleted_at IS NULL AND deleted_by IS NULL)
|
|
88
|
+
* )
|
|
89
|
+
*
|
|
90
|
+
* Nama deterministik via softDeleteCheckName (sumber tunggal di soft-delete-constants,
|
|
91
|
+
* bukan auto-name DB) agar Phase 03 (diff/introspect) mencocokkan dengan derivasi nama
|
|
92
|
+
* yang sama. Identifier kolom lewat dialect.quoteIdentifier (PostgreSQL: tanpa quote
|
|
93
|
+
* untuk identifier snake_case).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} tableName - nama tabel tak-qualified (untuk nama constraint)
|
|
96
|
+
* @param {Object} dialect - dialect aktif (dijamin PostgreSQL oleh guard di pemanggil)
|
|
97
|
+
* @returns {string} satu baris constraint ber-indent siap join ke body CREATE TABLE
|
|
98
|
+
*/
|
|
99
|
+
function emitSoftDeleteCheck(tableName, dialect) {
|
|
100
|
+
const name = softDeleteCheckName(tableName, dialect.maxIdentifierLength);
|
|
101
|
+
const isDeleted = dialect.quoteIdentifier(IS_DELETED_COLUMN);
|
|
102
|
+
const deletedAt = dialect.quoteIdentifier(DELETED_AT_COLUMN);
|
|
103
|
+
const deletedBy = dialect.quoteIdentifier(DELETED_BY_COLUMN);
|
|
104
|
+
const expr =
|
|
105
|
+
`(${isDeleted} = TRUE AND ${deletedAt} IS NOT NULL AND ${deletedBy} IS NOT NULL)` +
|
|
106
|
+
` OR (${isDeleted} = FALSE AND ${deletedAt} IS NULL AND ${deletedBy} IS NULL)`;
|
|
107
|
+
return `${COLUMN_INDENT}CONSTRAINT ${dialect.quoteIdentifier(name)} CHECK (${expr})`;
|
|
108
|
+
}
|
|
109
|
+
|
|
62
110
|
/**
|
|
63
111
|
* Derive CHECK constraint penanda boolean untuk dialect yang menyimpan boolean
|
|
64
112
|
* sebagai VARCHAR (mysql/oracle/sqlite). Bentuk: `col IN ('true','false')`.
|
|
@@ -29,7 +29,28 @@
|
|
|
29
29
|
* @module lib/dbschema-kit/introspect-mapper
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const { DIALECTS } = require('./dialect');
|
|
33
|
+
const {
|
|
34
|
+
SOFT_DELETE_COLUMNS,
|
|
35
|
+
SOFT_DELETE_COLUMN_TYPES,
|
|
36
|
+
SOFT_DELETE_PHASE1_DIALECT,
|
|
37
|
+
softDeleteCheckName,
|
|
38
|
+
buildSoftDeleteCheckSql
|
|
39
|
+
} = require('./soft-delete-constants');
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Map raw DB metadata ke IR dbschema-kit.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} tableMeta - metadata tabel (lihat doc input shape di atas).
|
|
45
|
+
* @param {string} [dialect] - id dialect ('postgres' default).
|
|
46
|
+
* @param {Object} [options]
|
|
47
|
+
* @param {boolean} [options.strictSoftDelete=false] - bila true (jalur `introspect`),
|
|
48
|
+
* tabel dengan kolom soft-delete tidak lengkap / tipe salah / CHECK hilang melempar
|
|
49
|
+
* ERROR (R6). Bila false (jalur `diff`/`apply`), tabel tersebut TIDAK diakui sebagai
|
|
50
|
+
* soft-delete (softDelete tidak di-set) sehingga `diff` melaporkannya sebagai drift
|
|
51
|
+
* apa adanya (Detection-only), bukan crash.
|
|
52
|
+
*/
|
|
53
|
+
function mapTableMetaToIR(tableMeta, dialect, options) {
|
|
33
54
|
if (!tableMeta || typeof tableMeta !== 'object') {
|
|
34
55
|
throw new Error('mapTableMetaToIR: tableMeta is required');
|
|
35
56
|
}
|
|
@@ -37,6 +58,7 @@ function mapTableMetaToIR(tableMeta, dialect) {
|
|
|
37
58
|
throw new Error('mapTableMetaToIR: tableMeta.tableName must be a non-empty string');
|
|
38
59
|
}
|
|
39
60
|
|
|
61
|
+
const opts = options || {};
|
|
40
62
|
const dialectId = (dialect || 'postgres').toLowerCase();
|
|
41
63
|
const tableName = tableMeta.tableName;
|
|
42
64
|
const schemaName = (typeof tableMeta.schemaName === 'string' && tableMeta.schemaName !== '')
|
|
@@ -67,6 +89,19 @@ function mapTableMetaToIR(tableMeta, dialect) {
|
|
|
67
89
|
fields[primaryKey[0]].pk = true;
|
|
68
90
|
}
|
|
69
91
|
|
|
92
|
+
// ── Soft-delete detection (R6, reverse) ──────────────────
|
|
93
|
+
// Deteksi tabel soft-delete dari kolom + CHECK konsistensi (name-based). Hasil
|
|
94
|
+
// dipakai untuk: (a) mengecualikan CHECK soft-delete dari `checks` generik (R8
|
|
95
|
+
// poin 2: artifact implisit, tidak boleh muncul sebagai check manual / unparsable),
|
|
96
|
+
// dan (b) men-set `softDelete: { enabled: true }` pada IR bila valid. Strict mode
|
|
97
|
+
// (`introspect`) melempar ERROR untuk kondisi tidak valid (R6).
|
|
98
|
+
const softDeleteDetection = detectSoftDelete(tableName, fields, rawChecks, dialectId);
|
|
99
|
+
if (softDeleteDetection.state === 'invalid' && opts.strictSoftDelete === true) {
|
|
100
|
+
const err = new Error(buildSoftDeleteIntrospectError(tableName, softDeleteDetection, dialectId));
|
|
101
|
+
err.isSoftDeleteIntrospectError = true;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
|
|
70
105
|
// ── Uniques: split single vs composite ───────────────────
|
|
71
106
|
const compositeUniques = [];
|
|
72
107
|
for (const u of rawUniques) {
|
|
@@ -141,6 +176,13 @@ function mapTableMetaToIR(tableMeta, dialect) {
|
|
|
141
176
|
const checks = [];
|
|
142
177
|
for (const c of rawChecks) {
|
|
143
178
|
if (!c) continue;
|
|
179
|
+
// CHECK konsistensi soft-delete (R8) ditangani lewat blok `softDelete`, BUKAN
|
|
180
|
+
// jalur `checks` generik. Kecualikan by NAMA agar tidak muncul sebagai check
|
|
181
|
+
// manual / unparsable (false drift di diff, atau ter-emit ke SDF). Hanya
|
|
182
|
+
// dikecualikan bila tabel memang membawa kolom soft-delete (state != 'none').
|
|
183
|
+
if (softDeleteDetection.state !== 'none' && c.name && c.name === softDeleteDetection.checkName) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
144
186
|
if (typeof c.field === 'string' && typeof c.op === 'string') {
|
|
145
187
|
// Already parsed shape (test fixtures may pass this).
|
|
146
188
|
checks.push(Object.assign({}, c));
|
|
@@ -157,7 +199,7 @@ function mapTableMetaToIR(tableMeta, dialect) {
|
|
|
157
199
|
}
|
|
158
200
|
}
|
|
159
201
|
|
|
160
|
-
|
|
202
|
+
const ir = {
|
|
161
203
|
tableName,
|
|
162
204
|
schemaName,
|
|
163
205
|
qualifiedName,
|
|
@@ -173,6 +215,116 @@ function mapTableMetaToIR(tableMeta, dialect) {
|
|
|
173
215
|
autoGeneratedRelations: []
|
|
174
216
|
}
|
|
175
217
|
};
|
|
218
|
+
|
|
219
|
+
// R6/R8 poin 2: tabel soft-delete valid → emit blok `softDelete` minimal (hanya
|
|
220
|
+
// `enabled`). `reusable`/base-length adalah intent developer yang tidak tersimpan
|
|
221
|
+
// di DB, jadi tidak diturunkan introspect. Hanya di-attach saat valid agar tabel
|
|
222
|
+
// non-soft-delete tetap byte-identik (tidak ada key baru di IR).
|
|
223
|
+
if (softDeleteDetection.state === 'valid') {
|
|
224
|
+
ir.softDelete = { enabled: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return ir;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─────────────────────────────────────────────────────────────
|
|
231
|
+
// Soft-delete detection (R6, reverse)
|
|
232
|
+
// ─────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
function resolveMaxIdentifierLength(dialectId) {
|
|
235
|
+
const d = DIALECTS[dialectId];
|
|
236
|
+
return d && typeof d.maxIdentifierLength === 'number' ? d.maxIdentifierLength : 63;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Deteksi status soft-delete sebuah tabel dari `fields` (sudah ber-tipe logical) dan
|
|
241
|
+
* `rawChecks` ({ name, expression }). Strategi pengenalan CHECK adalah NAME-BASED:
|
|
242
|
+
* cocokkan nama CHECK terhadap `softDeleteCheckName(table, maxLen)` (derivasi identik
|
|
243
|
+
* dengan emitter forward Phase 02). Name-based dipilih karena (a) nama deterministik
|
|
244
|
+
* dijamin emitter, dan (b) klausa CHECK compound tidak parsable oleh parser single-field
|
|
245
|
+
* sehingga content-based akan rapuh. `maxLen` diambil dari dialect agar tabel bernama
|
|
246
|
+
* panjang yang ter-truncate tetap cocok (tidak false drift).
|
|
247
|
+
*
|
|
248
|
+
* Hanya PostgreSQL (Fase 1 PostgreSQL-only). Dialect lain → 'none' (tidak diakui).
|
|
249
|
+
*
|
|
250
|
+
* @returns {{state:'none'}|{state:'valid',checkName:string}|{state:'invalid',present,missing,typeMismatches,hasCheck,checkName}}
|
|
251
|
+
*/
|
|
252
|
+
function detectSoftDelete(tableName, fields, rawChecks, dialectId) {
|
|
253
|
+
if (dialectId !== SOFT_DELETE_PHASE1_DIALECT) {
|
|
254
|
+
return { state: 'none' };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const present = SOFT_DELETE_COLUMNS.filter((c) => !!fields[c]);
|
|
258
|
+
if (present.length === 0) {
|
|
259
|
+
return { state: 'none' };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const maxLen = resolveMaxIdentifierLength(dialectId);
|
|
263
|
+
const checkName = softDeleteCheckName(tableName, maxLen);
|
|
264
|
+
|
|
265
|
+
const missing = SOFT_DELETE_COLUMNS.filter((c) => !fields[c]);
|
|
266
|
+
const typeMismatches = [];
|
|
267
|
+
for (const c of SOFT_DELETE_COLUMNS) {
|
|
268
|
+
const f = fields[c];
|
|
269
|
+
if (f && f.type !== SOFT_DELETE_COLUMN_TYPES[c]) {
|
|
270
|
+
typeMismatches.push({ column: c, expected: SOFT_DELETE_COLUMN_TYPES[c], actual: f.type });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const hasCheck = Array.isArray(rawChecks)
|
|
274
|
+
&& rawChecks.some((c) => c && c.name === checkName);
|
|
275
|
+
|
|
276
|
+
if (missing.length === 0 && typeMismatches.length === 0 && hasCheck) {
|
|
277
|
+
return { state: 'valid', checkName };
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
state: 'invalid',
|
|
281
|
+
present: present.slice(),
|
|
282
|
+
missing,
|
|
283
|
+
typeMismatches,
|
|
284
|
+
hasCheck,
|
|
285
|
+
checkName
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Bangun pesan ERROR introspect (R6) sesuai kondisi yang gagal. Untuk CHECK hilang
|
|
291
|
+
* (kolom lengkap + tipe benar) menyertakan rekomendasi SQL native PostgreSQL (R8).
|
|
292
|
+
*/
|
|
293
|
+
function buildSoftDeleteIntrospectError(tableName, detection, dialectId) {
|
|
294
|
+
const maxLen = resolveMaxIdentifierLength(dialectId);
|
|
295
|
+
const lines = [`[restforge] ERROR: introspect blocked for table '${tableName}'`, ''];
|
|
296
|
+
|
|
297
|
+
if (detection.missing.length > 0) {
|
|
298
|
+
lines.push(' The table has some soft-delete columns, but the set is incomplete.');
|
|
299
|
+
lines.push(` Columns found : ${detection.present.join(', ')}`);
|
|
300
|
+
lines.push(` Columns missing : ${detection.missing.join(', ')}`);
|
|
301
|
+
lines.push('');
|
|
302
|
+
lines.push(' Soft-delete requires all three columns (is_deleted, deleted_at, deleted_by).');
|
|
303
|
+
lines.push(' Options: add the missing columns + the consistency CHECK, OR drop the soft-delete columns.');
|
|
304
|
+
} else if (detection.typeMismatches.length > 0) {
|
|
305
|
+
lines.push(' The soft-delete columns are complete, but their types do not match the contract (R5):');
|
|
306
|
+
for (const tm of detection.typeMismatches) {
|
|
307
|
+
lines.push(` - ${tm.column}: found '${tm.actual}', required '${tm.expected}'`);
|
|
308
|
+
}
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push(' Options: fix the column types to match the contract, OR drop the soft-delete columns.');
|
|
311
|
+
} else {
|
|
312
|
+
// Kolom lengkap + tipe benar, CHECK hilang → bentuk R8 + rekomendasi SQL.
|
|
313
|
+
lines.push(' The soft-delete columns are complete and correctly typed, but the consistency');
|
|
314
|
+
lines.push(' CHECK constraint was not found. RESTForge refuses to generate a soft-delete SDF');
|
|
315
|
+
lines.push(' without a data-level consistency guarantee.');
|
|
316
|
+
lines.push('');
|
|
317
|
+
lines.push(' Options:');
|
|
318
|
+
lines.push(' 1. Drop the is_deleted, deleted_at, deleted_by columns, OR');
|
|
319
|
+
lines.push(' 2. Add the following CHECK, then re-run introspect:');
|
|
320
|
+
lines.push('');
|
|
321
|
+
const sql = buildSoftDeleteCheckSql(tableName, maxLen);
|
|
322
|
+
for (const sqlLine of sql.split('\n')) {
|
|
323
|
+
lines.push(` ${sqlLine}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return lines.join('\n');
|
|
176
328
|
}
|
|
177
329
|
|
|
178
330
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -4,6 +4,12 @@ const { parseFieldShorthand } = require('./parser/shorthand-parser');
|
|
|
4
4
|
|
|
5
5
|
const VALID_CHECK_OPS = new Set(['in', 'gt', 'gte', 'lt', 'lte', 'eq', 'neq']);
|
|
6
6
|
|
|
7
|
+
// Soft-delete (Fase 1) — blok ini sengaja ketat (R1): key tak dikenal ditolak,
|
|
8
|
+
// bukan di-drop diam-diam. Whitelist khusus blok `softDelete`; tidak mengubah
|
|
9
|
+
// keleluasaan blok SDF lain (relations/checks/indexes tetap lenient).
|
|
10
|
+
const SOFT_DELETE_ALLOWED_KEYS = new Set(['enabled', 'reusable']);
|
|
11
|
+
const SOFT_DELETE_REUSABLE_ALLOWED_KEYS = new Set(['field', 'length']);
|
|
12
|
+
|
|
7
13
|
// Token boundary detection: autoUpdate must stand alone, not part of `default:autoUpdate` or `autoUpdated`
|
|
8
14
|
const AUTO_UPDATE_TOKEN_RE = /(?:^|\s)autoUpdate(?=\s|$)/;
|
|
9
15
|
const AUTO_UPDATE_TOKEN_RE_G = /(?:^|\s)autoUpdate(?=\s|$)/g;
|
|
@@ -44,8 +50,9 @@ function buildIR(tableName, options) {
|
|
|
44
50
|
const indexes = normalizeIndexes(options.indexes, indexFromFk);
|
|
45
51
|
const uniques = normalizeUniques(options.uniques);
|
|
46
52
|
const checks = normalizeChecks(options.checks);
|
|
53
|
+
const softDelete = normalizeSoftDelete(options.softDelete);
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
const ir = {
|
|
49
56
|
schemaName,
|
|
50
57
|
tableName,
|
|
51
58
|
qualifiedName,
|
|
@@ -61,6 +68,14 @@ function buildIR(tableName, options) {
|
|
|
61
68
|
autoGeneratedRelations
|
|
62
69
|
}
|
|
63
70
|
};
|
|
71
|
+
|
|
72
|
+
// Optional block: only attach when declared so SDF tanpa softDelete tetap byte-identik
|
|
73
|
+
// dengan IR baseline (backward-compat, R1 anti-magic — tidak menambah key diam-diam).
|
|
74
|
+
if (softDelete !== undefined) {
|
|
75
|
+
ir.softDelete = softDelete;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return ir;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
81
|
function validateInputs(tableName, options) {
|
|
@@ -94,6 +109,41 @@ function validateInputs(tableName, options) {
|
|
|
94
109
|
if (options.schema !== undefined && options.schema !== null && typeof options.schema !== 'string') {
|
|
95
110
|
throw new Error(`Table '${tableName}': options.schema must be a string or null`);
|
|
96
111
|
}
|
|
112
|
+
if (options.softDelete !== undefined) {
|
|
113
|
+
if (!isPlainObject(options.softDelete)) {
|
|
114
|
+
throw new Error(`Table '${tableName}': options.softDelete must be a plain object`);
|
|
115
|
+
}
|
|
116
|
+
if (typeof options.softDelete.enabled !== 'boolean') {
|
|
117
|
+
throw new Error(`Table '${tableName}': options.softDelete.enabled must be a boolean`);
|
|
118
|
+
}
|
|
119
|
+
// R1 (strict): tolak key tak dikenal di level blok `softDelete` (selain whitelist),
|
|
120
|
+
// alih-alih membiarkan normalizeSoftDelete membuangnya diam-diam.
|
|
121
|
+
for (const key of Object.keys(options.softDelete)) {
|
|
122
|
+
if (!SOFT_DELETE_ALLOWED_KEYS.has(key)) {
|
|
123
|
+
throw new Error(unknownSoftDeleteKeyError(tableName, key));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (options.softDelete.reusable !== undefined) {
|
|
127
|
+
if (!Array.isArray(options.softDelete.reusable)) {
|
|
128
|
+
throw new Error(`Table '${tableName}': options.softDelete.reusable must be an array`);
|
|
129
|
+
}
|
|
130
|
+
for (let i = 0; i < options.softDelete.reusable.length; i++) {
|
|
131
|
+
const entry = options.softDelete.reusable[i];
|
|
132
|
+
if (!isPlainObject(entry)) {
|
|
133
|
+
throw new Error(`Table '${tableName}': options.softDelete.reusable[${i}] must be a plain object`);
|
|
134
|
+
}
|
|
135
|
+
// R1 (strict): tiap entri reusable hanya boleh `field` dan `length`.
|
|
136
|
+
for (const key of Object.keys(entry)) {
|
|
137
|
+
if (!SOFT_DELETE_REUSABLE_ALLOWED_KEYS.has(key)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Table '${tableName}': options.softDelete.reusable[${i}] has unknown key '${key}'; ` +
|
|
140
|
+
`only 'field' and 'length' are allowed.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
97
147
|
}
|
|
98
148
|
|
|
99
149
|
function parseField(shorthand, fieldName, tableName) {
|
|
@@ -275,6 +325,39 @@ function normalizeChecks(input) {
|
|
|
275
325
|
return result;
|
|
276
326
|
}
|
|
277
327
|
|
|
328
|
+
// Pesan ERROR unknown-key di level blok `softDelete`. Untuk `visibility` diberi
|
|
329
|
+
// petunjuk khusus: itu config endpoint di RDF/payload (R13/R15), bukan SDF, supaya
|
|
330
|
+
// developer memindahkannya, bukan sekadar menghapus.
|
|
331
|
+
function unknownSoftDeleteKeyError(tableName, key) {
|
|
332
|
+
let msg =
|
|
333
|
+
`Table '${tableName}': options.softDelete has unknown key '${key}'; ` +
|
|
334
|
+
`only 'enabled' and 'reusable' are allowed.`;
|
|
335
|
+
if (key === 'visibility') {
|
|
336
|
+
msg +=
|
|
337
|
+
` 'visibility' is an endpoint configuration declared in the RDF/payload ` +
|
|
338
|
+
`(one of active_only/deleted_only/include_deleted, see R13/R15), not in the SDF; ` +
|
|
339
|
+
`move it to the payload's softDelete block instead of declaring it here.`;
|
|
340
|
+
}
|
|
341
|
+
return msg;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function normalizeSoftDelete(input) {
|
|
345
|
+
if (input === undefined) return undefined;
|
|
346
|
+
// validateInputs sudah menjamin: input plain object, enabled boolean,
|
|
347
|
+
// reusable (bila ada) array of object, DAN tidak ada key tak dikenal — baik di
|
|
348
|
+
// level blok (hanya enabled/reusable) maupun di tiap entri reusable (hanya
|
|
349
|
+
// field/length). Jadi key seperti `visibility` (R13/R15, milik RDF) sudah ditolak
|
|
350
|
+
// ERROR di validateInputs, bukan di-drop diam-diam di sini (R1, anti-magic).
|
|
351
|
+
// Di sini hanya normalisasi minimal: simpan enabled apa adanya, dan proyeksikan
|
|
352
|
+
// tiap entri reusable ke { field, length }. Validasi semantik (kolom ada, tipe,
|
|
353
|
+
// unique, panjang) ditegakkan di validateSoftDelete.
|
|
354
|
+
const out = { enabled: input.enabled };
|
|
355
|
+
if (input.reusable !== undefined) {
|
|
356
|
+
out.reusable = input.reusable.map((entry) => ({ field: entry.field, length: entry.length }));
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
|
|
278
361
|
function isPlainObject(v) {
|
|
279
362
|
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
280
363
|
}
|
|
@@ -56,6 +56,10 @@ function serialize(ir, options) {
|
|
|
56
56
|
sections.push(serializeRelationsSection(explicitRelations));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
if (ir.softDelete && ir.softDelete.enabled === true) {
|
|
60
|
+
sections.push(serializeSoftDeleteSection(ir.softDelete));
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
const header = [
|
|
60
64
|
"'use strict';",
|
|
61
65
|
'',
|
|
@@ -319,6 +323,22 @@ function formatLiteralValue(v) {
|
|
|
319
323
|
return String(v);
|
|
320
324
|
}
|
|
321
325
|
|
|
326
|
+
// ─────────────────────────────────────────────────────────────
|
|
327
|
+
// softDelete section (R6 reverse)
|
|
328
|
+
// ─────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
// Introspect hanya menurunkan `enabled` (R8 poin 2): `reusable`/base-length adalah
|
|
331
|
+
// intent developer yang tidak tersimpan di database, jadi tidak diturunkan. SDF hasil
|
|
332
|
+
// introspect cukup `softDelete: { enabled: true }` (re-load tetap valid: ketiga kolom
|
|
333
|
+
// ada + enabled true memenuhi invariant R3/R5).
|
|
334
|
+
function serializeSoftDeleteSection(softDelete) {
|
|
335
|
+
return [
|
|
336
|
+
' softDelete: {',
|
|
337
|
+
' enabled: true',
|
|
338
|
+
' }'
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
|
|
322
342
|
// ─────────────────────────────────────────────────────────────
|
|
323
343
|
// relations section
|
|
324
344
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { generateConstraintName } = require('./naming');
|
|
4
|
+
|
|
5
|
+
// Soft-delete (Fase 1) — sumber kebenaran tunggal untuk nama kolom soft-delete dan
|
|
6
|
+
// panjang suffix mutasi. Dipakai bersama oleh:
|
|
7
|
+
// - validator SDF (validator/schema-validator.js) untuk invariant R3/R5/R7/R9,
|
|
8
|
+
// - emitter DDL forward (emitters/create-table.js CHECK konsistensi R8,
|
|
9
|
+
// emitters/create-index.js partial index R10/R11), dan
|
|
10
|
+
// - jalur reverse Phase 03 (introspect-mapper.js deteksi R6, diff-engine no-false-drift)
|
|
11
|
+
// untuk mengenali CHECK soft-delete lewat nama deterministik yang sama.
|
|
12
|
+
// supaya tidak ada duplikat nama kolom atau angka suffix yang harus dijaga sinkron
|
|
13
|
+
// di banyak tempat.
|
|
14
|
+
//
|
|
15
|
+
// Catatan scope: konstanta whitelist input SDF (`SOFT_DELETE_ALLOWED_KEYS` dkk. di
|
|
16
|
+
// ir-builder.js) sengaja TIDAK dipindah ke sini. Itu domain parsing input (key apa
|
|
17
|
+
// yang boleh ditulis di blok `softDelete`), beda concern dari nama kolom fisik DDL.
|
|
18
|
+
|
|
19
|
+
// Tiga kolom soft-delete bernama tetap (R3) dengan kontrak tipe kaku (R5).
|
|
20
|
+
const IS_DELETED_COLUMN = 'is_deleted';
|
|
21
|
+
const DELETED_AT_COLUMN = 'deleted_at';
|
|
22
|
+
const DELETED_BY_COLUMN = 'deleted_by';
|
|
23
|
+
|
|
24
|
+
// Urutan array adalah urutan kanonik kolom: is_deleted, deleted_at, deleted_by.
|
|
25
|
+
const SOFT_DELETE_COLUMNS = [IS_DELETED_COLUMN, DELETED_AT_COLUMN, DELETED_BY_COLUMN];
|
|
26
|
+
|
|
27
|
+
// Kontrak tipe logical tiga kolom soft-delete (R5). Dipakai deteksi introspect
|
|
28
|
+
// (reverse, R6) untuk memverifikasi tipe kolom benar sebelum mengakui sebuah tabel
|
|
29
|
+
// sebagai soft-delete. Validator SDF (schema-validator.js) memegang kontrak yang sama
|
|
30
|
+
// di sisi forward; keduanya WAJIB tetap identik (R5 fixed).
|
|
31
|
+
const SOFT_DELETE_COLUMN_TYPES = {
|
|
32
|
+
[IS_DELETED_COLUMN]: 'boolean',
|
|
33
|
+
[DELETED_AT_COLUMN]: 'timestamp',
|
|
34
|
+
[DELETED_BY_COLUMN]: 'string'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Suffix mutasi reuse '{code}##{uuidv7}' = '##' (2) + uuidv7 (36) = 38 char (R7).
|
|
38
|
+
const SOFT_DELETE_SUFFIX_LENGTH = 38;
|
|
39
|
+
|
|
40
|
+
// Suffix nama CHECK konsistensi soft-delete (R8). HARUS identik dengan emitter forward
|
|
41
|
+
// Phase 02 (emitters/create-table.js memakai literal yang sama) agar reverse-matching
|
|
42
|
+
// nama-based di Phase 03 mengenali CHECK yang sama tanpa false drift. Konsistensi
|
|
43
|
+
// nama-derivation antara emit (forward) dan match (reverse) adalah kunci no-false-drift.
|
|
44
|
+
const SOFT_DELETE_CHECK_SUFFIX = 'soft_delete_consistency';
|
|
45
|
+
|
|
46
|
+
// Dialect yang didukung soft-delete pada Fase 1. CHECK konsistensi memakai boolean
|
|
47
|
+
// native (`= TRUE`/`= FALSE`) dan index non-unique memakai partial index; keduanya
|
|
48
|
+
// adalah bentuk PostgreSQL. Adaptasi VARCHAR 'true'/'false' dan plain index untuk
|
|
49
|
+
// dialect lain ditunda ke fase multi-database.
|
|
50
|
+
const SOFT_DELETE_PHASE1_DIALECT = 'postgres';
|
|
51
|
+
|
|
52
|
+
// Predikat tunggal kehadiran soft-delete aktif pada IR. Blok `softDelete` bersifat
|
|
53
|
+
// optional di IR (hanya di-attach bila dideklarasikan, lihat ir-builder), jadi guard
|
|
54
|
+
// `softDelete && softDelete.enabled === true` dipakai konsisten di semua emitter agar
|
|
55
|
+
// emisi soft-delete benar-benar guarded (backward-compat byte-identik baseline).
|
|
56
|
+
function isSoftDeleteEnabled(ir) {
|
|
57
|
+
return !!(ir && ir.softDelete && ir.softDelete.enabled === true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Guard Fase 1 PostgreSQL-only. Bila soft-delete aktif tetapi dialect bukan PostgreSQL,
|
|
61
|
+
// lempar ERROR jelas alih-alih meng-emit DDL salah diam-diam (bentuk VARCHAR boolean /
|
|
62
|
+
// plain index). Dipakai oleh emitter CHECK (create-table) dan partial index (create-index)
|
|
63
|
+
// supaya tiap emitter aman dipanggil mandiri (test maupun jalur generateDDL).
|
|
64
|
+
function assertSoftDeletePostgresOnly(ir, dialect) {
|
|
65
|
+
if (!isSoftDeleteEnabled(ir)) return;
|
|
66
|
+
if (dialect && dialect.name === SOFT_DELETE_PHASE1_DIALECT) return;
|
|
67
|
+
const label = (ir && (ir.qualifiedName || ir.tableName)) || '<unknown>';
|
|
68
|
+
const dialectName = dialect && dialect.name ? dialect.name : '<unknown>';
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Table '${label}': soft-delete is PostgreSQL-only in Phase 1, ` +
|
|
71
|
+
`but the active dialect is '${dialectName}'. ` +
|
|
72
|
+
`Remove the softDelete block or generate DDL for PostgreSQL.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Derivasi nama CHECK konsistensi soft-delete, identik dengan emitter forward Phase 02
|
|
77
|
+
// (emitters/create-table.js emitSoftDeleteCheck). `maxIdentifierLength` WAJIB sama dengan
|
|
78
|
+
// yang dipakai emitter (`dialect.maxIdentifierLength`, PostgreSQL = 63) agar tabel bernama
|
|
79
|
+
// panjang yang ter-truncate menghasilkan nama identik di kedua arah (tidak false drift).
|
|
80
|
+
function softDeleteCheckName(tableName, maxIdentifierLength) {
|
|
81
|
+
return generateConstraintName('chk', tableName, SOFT_DELETE_CHECK_SUFFIX, maxIdentifierLength);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Bangun rekomendasi SQL native PostgreSQL (R8) untuk menambahkan CHECK konsistensi
|
|
85
|
+
// soft-delete. Dipakai pesan ERROR introspect (R6) saat tiga kolom lengkap & bertipe
|
|
86
|
+
// benar tetapi CHECK hilang. Bentuk persis mengikuti plan master R8 (boolean native PG).
|
|
87
|
+
function buildSoftDeleteCheckSql(tableName, maxIdentifierLength) {
|
|
88
|
+
const name = softDeleteCheckName(tableName, maxIdentifierLength);
|
|
89
|
+
return (
|
|
90
|
+
`ALTER TABLE ${tableName} ADD CONSTRAINT ${name}\n` +
|
|
91
|
+
` CHECK (\n` +
|
|
92
|
+
` (${IS_DELETED_COLUMN} = TRUE AND ${DELETED_AT_COLUMN} IS NOT NULL AND ${DELETED_BY_COLUMN} IS NOT NULL)\n` +
|
|
93
|
+
` OR (${IS_DELETED_COLUMN} = FALSE AND ${DELETED_AT_COLUMN} IS NULL AND ${DELETED_BY_COLUMN} IS NULL)\n` +
|
|
94
|
+
` );`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
IS_DELETED_COLUMN,
|
|
100
|
+
DELETED_AT_COLUMN,
|
|
101
|
+
DELETED_BY_COLUMN,
|
|
102
|
+
SOFT_DELETE_COLUMNS,
|
|
103
|
+
SOFT_DELETE_COLUMN_TYPES,
|
|
104
|
+
SOFT_DELETE_SUFFIX_LENGTH,
|
|
105
|
+
SOFT_DELETE_CHECK_SUFFIX,
|
|
106
|
+
SOFT_DELETE_PHASE1_DIALECT,
|
|
107
|
+
isSoftDeleteEnabled,
|
|
108
|
+
assertSoftDeletePostgresOnly,
|
|
109
|
+
softDeleteCheckName,
|
|
110
|
+
buildSoftDeleteCheckSql
|
|
111
|
+
};
|