@restforgejs/platform 5.1.0 → 5.1.6
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/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/data/pull.js +104 -0
- package/generators/cli/data/push.js +95 -0
- package/generators/cli/fast-track.js +12 -25
- package/generators/cli/init.js +347 -97
- package/generators/cli/schema/introspect.js +10 -10
- package/generators/lib/data/data-scope.js +138 -0
- package/generators/lib/data/db-executor.js +440 -0
- package/generators/lib/data/dialect-kit.js +56 -0
- package/generators/lib/data/envelope.js +236 -0
- package/generators/lib/data/pull-runner.js +414 -0
- package/generators/lib/data/push-runner.js +400 -0
- package/generators/lib/data/sdf-reader.js +132 -0
- package/generators/lib/data/table-order.js +126 -0
- package/generators/lib/data/value-codec.js +188 -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/integrity-manifest.json +18 -18
- package/package.json +1 -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/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
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* db-executor — Executor koneksi + transaksi tipis (read-write) per dialect.
|
|
5
|
+
*
|
|
6
|
+
* Membuka koneksi read-write dari config terparse (`dbschema-kit/connection.loadConfig`)
|
|
7
|
+
* dan mengekspos antarmuka kompatibel sebagai `dbUtils` untuk QueryBuilder
|
|
8
|
+
* (`executeQuery`, `executeTransaction`) plus `disconnect`.
|
|
9
|
+
*
|
|
10
|
+
* Berbeda dari `DatabaseIntrospector` (read-only), executor ini:
|
|
11
|
+
* - SQLite dibuka TANPA `readonly` (WAL + foreign_keys = ON).
|
|
12
|
+
* - Oracle memakai ALTER SESSION NLS agar string ISO-8601 ⇒ DATE/TIMESTAMP.
|
|
13
|
+
* - `toBindableParam` direplikasi (boolean→'true'/'false' untuk MySQL/Oracle/SQLite;
|
|
14
|
+
* SQLite juga undefined→null; PostgreSQL tidak dikoersi).
|
|
15
|
+
*
|
|
16
|
+
* Koneksi dibuka LAZY pada pemakaian pertama sehingga `createExecutor()` bersifat
|
|
17
|
+
* sinkron dan tidak menyentuh DB hingga query benar-benar dijalankan.
|
|
18
|
+
*
|
|
19
|
+
* JANGAN impor `src/utils/db*.js` (singleton env-coupled) — pola di sini direplikasi
|
|
20
|
+
* dari runtime + DatabaseIntrospector (Phase 00b E4). Driver dimuat lazy.
|
|
21
|
+
*
|
|
22
|
+
* @module generators/lib/data/db-executor
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Cache driver module-level (lazy require, pola database-introspector.loadDriver).
|
|
26
|
+
let pgDriver = null;
|
|
27
|
+
let mysqlDriver = null;
|
|
28
|
+
let oracleDriver = null;
|
|
29
|
+
let sqliteDriver = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lazy-load driver database berdasarkan dialect kanonik.
|
|
33
|
+
* @param {string} dialect - 'postgresql'|'mysql'|'oracle'|'sqlite'
|
|
34
|
+
* @returns {Object} Module driver
|
|
35
|
+
*/
|
|
36
|
+
function loadDriver(dialect) {
|
|
37
|
+
switch (dialect) {
|
|
38
|
+
case 'postgresql':
|
|
39
|
+
if (!pgDriver) {
|
|
40
|
+
try { pgDriver = require('pg'); } catch (_e) {
|
|
41
|
+
throw new Error('PostgreSQL driver (pg) tidak terpasang. Jalankan: npm install pg');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return pgDriver;
|
|
45
|
+
case 'mysql':
|
|
46
|
+
if (!mysqlDriver) {
|
|
47
|
+
try { mysqlDriver = require('mysql2/promise'); } catch (_e) {
|
|
48
|
+
throw new Error('MySQL driver (mysql2) tidak terpasang. Jalankan: npm install mysql2');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return mysqlDriver;
|
|
52
|
+
case 'oracle':
|
|
53
|
+
if (!oracleDriver) {
|
|
54
|
+
try { oracleDriver = require('oracledb'); } catch (_e) {
|
|
55
|
+
throw new Error('Oracle driver (oracledb) tidak terpasang. Jalankan: npm install oracledb');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return oracleDriver;
|
|
59
|
+
case 'sqlite':
|
|
60
|
+
if (!sqliteDriver) {
|
|
61
|
+
try { sqliteDriver = require('better-sqlite3'); } catch (_e) {
|
|
62
|
+
throw new Error('SQLite driver (better-sqlite3) tidak terpasang. Jalankan: npm rebuild better-sqlite3');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return sqliteDriver;
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Dialect tidak didukung: ${dialect}. Didukung: postgresql, mysql, oracle, sqlite`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalisasi nilai dialect dari config menjadi nama kanonik.
|
|
73
|
+
* connection.loadConfig memetakan postgresql→'postgres'; di sini dikembalikan
|
|
74
|
+
* 'postgresql' agar konsisten dengan nama dialect QueryBuilder.
|
|
75
|
+
* @param {string} dialect
|
|
76
|
+
* @returns {string} 'postgresql'|'mysql'|'oracle'|'sqlite'
|
|
77
|
+
*/
|
|
78
|
+
function normalizeDialect(dialect) {
|
|
79
|
+
const d = (dialect || '').toLowerCase();
|
|
80
|
+
if (d === 'postgres' || d === 'pg' || d === 'postgresql') return 'postgresql';
|
|
81
|
+
return d;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Koersi satu nilai bind ke tipe yang diterima driver target.
|
|
86
|
+
* Direplikasi dari runtime (db-mysql / db-oracle / db-sqlite). PostgreSQL tidak
|
|
87
|
+
* dikoersi karena driver pg menerima JS boolean native.
|
|
88
|
+
*
|
|
89
|
+
* @param {*} value
|
|
90
|
+
* @param {string} dialect - dialect kanonik
|
|
91
|
+
* @returns {*}
|
|
92
|
+
*/
|
|
93
|
+
function toBindableParam(value, dialect) {
|
|
94
|
+
if (dialect === 'postgresql') return value;
|
|
95
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
96
|
+
if (dialect === 'sqlite' && value === undefined) return null;
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Koersi array params.
|
|
102
|
+
* @param {Array} params
|
|
103
|
+
* @param {string} dialect
|
|
104
|
+
* @returns {Array}
|
|
105
|
+
*/
|
|
106
|
+
function toBindableParams(params, dialect) {
|
|
107
|
+
return Array.isArray(params) ? params.map((p) => toBindableParam(p, dialect)) : params;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Deteksi jenis query (untuk SQLite synchronous .all() vs .run()).
|
|
111
|
+
const RE_RETURNING = /\bRETURNING\b/i;
|
|
112
|
+
const RE_SELECTISH = /^\s*(SELECT|PRAGMA|EXPLAIN|WITH\s)/i;
|
|
113
|
+
|
|
114
|
+
// ─── PostgreSQL ───────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
// OID tipe tanggal/waktu PG yang dikembalikan sebagai STRING mentah (identitas),
|
|
117
|
+
// meniru `dateStrings: true` MySQL. Parser default `pg` mem-parse OID ini ke JS
|
|
118
|
+
// Date (zona lokal) sehingga `encodeValue` memakai `toISOString()` (UTC) dan pada
|
|
119
|
+
// mesin non-UTC menggeser nilai secara asimetris lintas push → round-trip gagal
|
|
120
|
+
// (`env2 ≠ env1`). Dengan string mentah, encodeValue masuk cabang string (slice /
|
|
121
|
+
// normalisasi separator) tanpa konversi zona → round-trip stabil seperti MySQL.
|
|
122
|
+
// 1082 = date, 1114 = timestamp, 1184 = timestamptz
|
|
123
|
+
const PG_DATETIME_OIDS = new Set([1082, 1114, 1184]);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Bangun objek `types` per-pool untuk opsi `new Pool({ types })`. Mengembalikan
|
|
127
|
+
* parser identitas `(v) => v` untuk OID date/timestamp/timestamptz, dan delegasi
|
|
128
|
+
* ke `pg.types.getTypeParser` default untuk OID lain.
|
|
129
|
+
*
|
|
130
|
+
* Per-pool (BUKAN `pg.types.setTypeParser` global) agar pemakaian `pg` lain di
|
|
131
|
+
* proses yang sama tidak terpengaruh.
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} driver - modul `require('pg')`
|
|
134
|
+
* @returns {{getTypeParser: Function}}
|
|
135
|
+
*/
|
|
136
|
+
function buildPgTypeParsers(driver) {
|
|
137
|
+
const identity = (v) => v;
|
|
138
|
+
return {
|
|
139
|
+
getTypeParser(oid, format) {
|
|
140
|
+
if (PG_DATETIME_OIDS.has(oid)) return identity;
|
|
141
|
+
return driver.types.getTypeParser(oid, format);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function createPgExecutor(config) {
|
|
147
|
+
const driver = loadDriver('postgresql');
|
|
148
|
+
let pool = null;
|
|
149
|
+
|
|
150
|
+
function ensurePool() {
|
|
151
|
+
if (!pool) {
|
|
152
|
+
pool = new driver.Pool({
|
|
153
|
+
host: config.host,
|
|
154
|
+
port: config.port,
|
|
155
|
+
user: config.user,
|
|
156
|
+
password: config.password,
|
|
157
|
+
database: config.database,
|
|
158
|
+
// Per-pool type parser: date/timestamp/timestamptz → string mentah.
|
|
159
|
+
types: buildPgTypeParsers(driver)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return pool;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function executeQuery(sql, params = []) {
|
|
166
|
+
const client = await ensurePool().connect();
|
|
167
|
+
try {
|
|
168
|
+
const result = await client.query(sql, toBindableParams(params, 'postgresql'));
|
|
169
|
+
return result.rows;
|
|
170
|
+
} finally {
|
|
171
|
+
client.release();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function executeTransaction(queries) {
|
|
176
|
+
const client = await ensurePool().connect();
|
|
177
|
+
try {
|
|
178
|
+
await client.query('BEGIN');
|
|
179
|
+
const results = [];
|
|
180
|
+
for (const q of queries) {
|
|
181
|
+
const result = await client.query(q.sql, toBindableParams(q.params || [], 'postgresql'));
|
|
182
|
+
results.push(result.rows);
|
|
183
|
+
}
|
|
184
|
+
await client.query('COMMIT');
|
|
185
|
+
return results;
|
|
186
|
+
} catch (err) {
|
|
187
|
+
try { await client.query('ROLLBACK'); } catch (_e) { /* swallow rollback error */ }
|
|
188
|
+
throw err;
|
|
189
|
+
} finally {
|
|
190
|
+
client.release();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function disconnect() {
|
|
195
|
+
if (pool) { await pool.end(); pool = null; }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { executeQuery, executeTransaction, disconnect };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── MySQL ────────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
function createMysqlExecutor(config) {
|
|
204
|
+
const driver = loadDriver('mysql');
|
|
205
|
+
let pool = null;
|
|
206
|
+
|
|
207
|
+
function ensurePool() {
|
|
208
|
+
if (!pool) {
|
|
209
|
+
pool = driver.createPool({
|
|
210
|
+
host: config.host,
|
|
211
|
+
port: config.port,
|
|
212
|
+
user: config.user,
|
|
213
|
+
password: config.password,
|
|
214
|
+
database: config.database,
|
|
215
|
+
waitForConnections: true,
|
|
216
|
+
connectionLimit: 10,
|
|
217
|
+
queueLimit: 0,
|
|
218
|
+
// Opsi penting untuk pull: bigint/decimal sebagai string (presisi),
|
|
219
|
+
// tanggal sebagai string, zona UTC.
|
|
220
|
+
dateStrings: true,
|
|
221
|
+
supportBigNumbers: true,
|
|
222
|
+
bigNumberStrings: true,
|
|
223
|
+
timezone: '+00:00',
|
|
224
|
+
charset: 'utf8mb4'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return pool;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function normalizeDml(rows) {
|
|
231
|
+
return {
|
|
232
|
+
rowsAffected: rows.affectedRows,
|
|
233
|
+
insertId: rows.insertId,
|
|
234
|
+
changedRows: rows.changedRows
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function executeQuery(sql, params = []) {
|
|
239
|
+
const conn = await ensurePool().getConnection();
|
|
240
|
+
try {
|
|
241
|
+
const [rows] = await conn.execute(sql, toBindableParams(params, 'mysql'));
|
|
242
|
+
return Array.isArray(rows) ? rows : normalizeDml(rows);
|
|
243
|
+
} finally {
|
|
244
|
+
conn.release();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function executeTransaction(queries) {
|
|
249
|
+
const conn = await ensurePool().getConnection();
|
|
250
|
+
try {
|
|
251
|
+
await conn.beginTransaction();
|
|
252
|
+
const results = [];
|
|
253
|
+
for (const q of queries) {
|
|
254
|
+
const params = q.params || [];
|
|
255
|
+
// execute() (prepared) untuk parameterized; query() untuk SQL tanpa param.
|
|
256
|
+
const [rows] = params.length > 0
|
|
257
|
+
? await conn.execute(q.sql, toBindableParams(params, 'mysql'))
|
|
258
|
+
: await conn.query(q.sql);
|
|
259
|
+
results.push(Array.isArray(rows) ? rows : normalizeDml(rows));
|
|
260
|
+
}
|
|
261
|
+
await conn.commit();
|
|
262
|
+
return results;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
try { await conn.rollback(); } catch (_e) { /* swallow rollback error */ }
|
|
265
|
+
throw err;
|
|
266
|
+
} finally {
|
|
267
|
+
conn.release();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function disconnect() {
|
|
272
|
+
if (pool) { await pool.end(); pool = null; }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { executeQuery, executeTransaction, disconnect };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── Oracle ─────────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
function createOracleExecutor(config) {
|
|
281
|
+
const driver = loadDriver('oracle');
|
|
282
|
+
let pool = null;
|
|
283
|
+
const serviceName = config.serviceName || config.database;
|
|
284
|
+
const connectString = `//${config.host}:${config.port}/${serviceName}`;
|
|
285
|
+
|
|
286
|
+
async function ensurePool() {
|
|
287
|
+
if (!pool) {
|
|
288
|
+
pool = await driver.createPool({
|
|
289
|
+
user: config.user,
|
|
290
|
+
password: config.password,
|
|
291
|
+
connectString,
|
|
292
|
+
poolMin: 1,
|
|
293
|
+
poolMax: 5,
|
|
294
|
+
poolTimeout: 60
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return pool;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function ensureNls(connection) {
|
|
301
|
+
if (connection._nlsReady) return;
|
|
302
|
+
await connection.execute(`ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'`);
|
|
303
|
+
await connection.execute(`ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS.FF3'`);
|
|
304
|
+
await connection.execute(`ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM'`);
|
|
305
|
+
connection._nlsReady = true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeDml(result) {
|
|
309
|
+
return { rowsAffected: result.rowsAffected, lastRowid: result.lastRowid };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function executeQuery(sql, params = []) {
|
|
313
|
+
const p = await ensurePool();
|
|
314
|
+
const connection = await p.getConnection();
|
|
315
|
+
try {
|
|
316
|
+
await ensureNls(connection);
|
|
317
|
+
const result = await connection.execute(
|
|
318
|
+
sql,
|
|
319
|
+
toBindableParams(params, 'oracle'),
|
|
320
|
+
{ outFormat: driver.OUT_FORMAT_OBJECT, autoCommit: true }
|
|
321
|
+
);
|
|
322
|
+
return result.rows ? result.rows : normalizeDml(result);
|
|
323
|
+
} finally {
|
|
324
|
+
await connection.close();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function executeTransaction(queries) {
|
|
329
|
+
const p = await ensurePool();
|
|
330
|
+
const connection = await p.getConnection();
|
|
331
|
+
try {
|
|
332
|
+
await ensureNls(connection);
|
|
333
|
+
const results = [];
|
|
334
|
+
for (const q of queries) {
|
|
335
|
+
const result = await connection.execute(
|
|
336
|
+
q.sql,
|
|
337
|
+
toBindableParams(q.params || [], 'oracle'),
|
|
338
|
+
{ autoCommit: false, outFormat: driver.OUT_FORMAT_OBJECT }
|
|
339
|
+
);
|
|
340
|
+
results.push(result.rows ? result.rows : normalizeDml(result));
|
|
341
|
+
}
|
|
342
|
+
await connection.commit();
|
|
343
|
+
return results;
|
|
344
|
+
} catch (err) {
|
|
345
|
+
try { await connection.rollback(); } catch (_e) { /* swallow rollback error */ }
|
|
346
|
+
throw err;
|
|
347
|
+
} finally {
|
|
348
|
+
await connection.close();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function disconnect() {
|
|
353
|
+
if (pool) { await pool.close(10); pool = null; }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return { executeQuery, executeTransaction, disconnect };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ─── SQLite ─────────────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
function createSqliteExecutor(config) {
|
|
362
|
+
const Database = loadDriver('sqlite');
|
|
363
|
+
const filePath = config.file || config.database;
|
|
364
|
+
if (!filePath) {
|
|
365
|
+
throw new Error('SQLite: path file database wajib ada di config (field "file" atau "database")');
|
|
366
|
+
}
|
|
367
|
+
let db = null;
|
|
368
|
+
|
|
369
|
+
function ensureDb() {
|
|
370
|
+
if (!db) {
|
|
371
|
+
// READ-WRITE (tanpa readonly) — berbeda dari DatabaseIntrospector.
|
|
372
|
+
db = new Database(filePath);
|
|
373
|
+
db.pragma('journal_mode = WAL');
|
|
374
|
+
db.pragma('foreign_keys = ON');
|
|
375
|
+
}
|
|
376
|
+
return db;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function runOne(database, sql, params) {
|
|
380
|
+
const stmt = database.prepare(sql);
|
|
381
|
+
const bound = toBindableParams(params, 'sqlite');
|
|
382
|
+
if (RE_SELECTISH.test(sql) || RE_RETURNING.test(sql)) {
|
|
383
|
+
return stmt.all(...bound);
|
|
384
|
+
}
|
|
385
|
+
const info = stmt.run(...bound);
|
|
386
|
+
return { rowsAffected: info.changes, insertId: info.lastInsertRowid };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function executeQuery(sql, params = []) {
|
|
390
|
+
return runOne(ensureDb(), sql, params);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function executeTransaction(queries) {
|
|
394
|
+
const database = ensureDb();
|
|
395
|
+
// Wrapper better-sqlite3: auto BEGIN/COMMIT, auto ROLLBACK saat throw.
|
|
396
|
+
const txn = database.transaction((queryList) => {
|
|
397
|
+
const results = [];
|
|
398
|
+
for (const q of queryList) {
|
|
399
|
+
results.push(runOne(database, q.sql, q.params || []));
|
|
400
|
+
}
|
|
401
|
+
return results;
|
|
402
|
+
});
|
|
403
|
+
return txn(queries);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function disconnect() {
|
|
407
|
+
if (db) { db.close(); db = null; }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return { executeQuery, executeTransaction, disconnect };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Buat executor read-write untuk dialect pada config.
|
|
415
|
+
*
|
|
416
|
+
* @param {Object} config - Hasil dbschema-kit/connection.loadConfig:
|
|
417
|
+
* { dialect, host, port, database, user, password, serviceName, file }
|
|
418
|
+
* @returns {{executeQuery: Function, executeTransaction: Function, disconnect: Function}}
|
|
419
|
+
*/
|
|
420
|
+
function createExecutor(config) {
|
|
421
|
+
if (!config || typeof config !== 'object') {
|
|
422
|
+
throw new Error('createExecutor: config wajib berupa object hasil loadConfig');
|
|
423
|
+
}
|
|
424
|
+
const dialect = normalizeDialect(config.dialect);
|
|
425
|
+
switch (dialect) {
|
|
426
|
+
case 'postgresql': return createPgExecutor(config);
|
|
427
|
+
case 'mysql': return createMysqlExecutor(config);
|
|
428
|
+
case 'oracle': return createOracleExecutor(config);
|
|
429
|
+
case 'sqlite': return createSqliteExecutor(config);
|
|
430
|
+
default:
|
|
431
|
+
throw new Error(`createExecutor: dialect tidak didukung '${config.dialect}'`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = {
|
|
436
|
+
createExecutor,
|
|
437
|
+
toBindableParam,
|
|
438
|
+
toBindableParams,
|
|
439
|
+
_internal: { loadDriver, normalizeDialect, buildPgTypeParsers, PG_DATETIME_OIDS }
|
|
440
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* dialect-kit — Wrapper tipis (read-only) atas modul murni `src/core/db/dialect`.
|
|
5
|
+
*
|
|
6
|
+
* Tujuan: satu titik impor agar boundary tier (generator → src/core/db) terlihat
|
|
7
|
+
* eksplisit. Modul `src/core/db/dialect` terbukti murni (Phase 00b E3): nol
|
|
8
|
+
* `process.env` / `new Pool` / side effect, aman diimpor read-only dari tier generator.
|
|
9
|
+
*
|
|
10
|
+
* JANGAN impor `src/utils/db*.js` (singleton env-coupled).
|
|
11
|
+
*
|
|
12
|
+
* Jalur impor: generators/lib/data/dialect-kit.js → ../../../src/core/db/dialect/
|
|
13
|
+
*
|
|
14
|
+
* @module generators/lib/data/dialect-kit
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { createDialect } = require('../../../src/core/db/dialect');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validasi identifier (nama tabel/kolom) via dialect.
|
|
21
|
+
* @param {Object} dialect - Instance dialect
|
|
22
|
+
* @param {string} name
|
|
23
|
+
* @returns {string} name yang sudah divalidasi
|
|
24
|
+
*/
|
|
25
|
+
function validateIdentifier(dialect, name) {
|
|
26
|
+
return dialect.validateIdentifier(name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Placeholder parameter ke-i (1-based) via dialect.
|
|
31
|
+
* @param {Object} dialect - Instance dialect
|
|
32
|
+
* @param {number} i - 1-based index
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function placeholder(dialect, i) {
|
|
36
|
+
return dialect.placeholder(i);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clause LIMIT/OFFSET (dialect-aware) via dialect.
|
|
41
|
+
* @param {Object} dialect - Instance dialect
|
|
42
|
+
* @param {number|null} limit
|
|
43
|
+
* @param {number|null} offset
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function limitOffsetClause(dialect, limit, offset) {
|
|
47
|
+
return dialect.limitOffsetClause(limit, offset);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
// Nama `createDialect` dipertahankan untuk paritas dengan src/core/db/dialect.
|
|
52
|
+
createDialect,
|
|
53
|
+
validateIdentifier,
|
|
54
|
+
placeholder,
|
|
55
|
+
limitOffsetClause
|
|
56
|
+
};
|