@restforgejs/platform 5.0.1 → 5.0.8

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.
Files changed (182) hide show
  1. package/build-info.json +2 -2
  2. package/cli/consumer-deploy.js +1 -1
  3. package/cli/consumer.js +1 -1
  4. package/generators/cli/dashboard/create.js +1 -1
  5. package/generators/cli/endpoint/create.js +1 -1
  6. package/generators/cli/processor/create.js +1 -1
  7. package/generators/lib/dbschema-kit/apply-executor.js +15 -1
  8. package/generators/lib/dbschema-kit/dialect/mysql.js +2 -0
  9. package/generators/lib/dbschema-kit/dialect/oracle.js +2 -0
  10. package/generators/lib/dbschema-kit/dialect/postgres.js +3 -0
  11. package/generators/lib/dbschema-kit/dialect/sqlite.js +2 -0
  12. package/generators/lib/dbschema-kit/emitters/alter-table.js +7 -0
  13. package/generators/lib/dbschema-kit/emitters/create-table.js +31 -3
  14. package/generators/lib/dbschema-kit/statement-modifier.js +12 -2
  15. package/generators/lib/migrate/migrate-runner.js +393 -393
  16. package/generators/lib/payload/payload-runner.js +64 -2
  17. package/generators/lib/payload/schema-diff.js +31 -1
  18. package/generators/lib/templates/dashboard-catalog.js +1 -1
  19. package/generators/lib/templates/db-connection-env.js +1 -1
  20. package/generators/lib/templates/dbschema-catalog.js +1 -1
  21. package/generators/lib/templates/field-validation-catalog.js +1 -1
  22. package/generators/lib/templates/mysql-template.js +1 -1
  23. package/generators/lib/templates/oracle-template.js +1 -1
  24. package/generators/lib/templates/postgres-template.js +1 -1
  25. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  26. package/generators/lib/templates/sqlite-template.js +1 -1
  27. package/generators/lib/utils/database-introspector.js +417 -6
  28. package/generators/lib/validators/argument-validator.js +2 -2
  29. package/integrity-manifest.json +18 -18
  30. package/package.json +4 -2
  31. package/scripts/verify-integrity.js +1 -1
  32. package/server.js +1 -1
  33. package/src/components/handlers/adjust_handler.js +1 -1
  34. package/src/components/handlers/audit_handler.js +1 -1
  35. package/src/components/handlers/delete_handler.js +1 -1
  36. package/src/components/handlers/export_handler.js +1 -1
  37. package/src/components/handlers/import_handler.js +1 -1
  38. package/src/components/handlers/insert_handler.js +1 -1
  39. package/src/components/handlers/update_handler.js +1 -1
  40. package/src/components/handlers/upload_handler.js +1 -1
  41. package/src/components/handlers/workflow_handler.js +1 -1
  42. package/src/components/integrations/webhook.js +1 -1
  43. package/src/consumers/baseConsumer.js +1 -1
  44. package/src/consumers/declarativeMapper.js +1 -1
  45. package/src/consumers/handlers/apiHandler.js +1 -1
  46. package/src/consumers/handlers/consoleHandler.js +1 -1
  47. package/src/consumers/handlers/databaseHandler.js +1 -1
  48. package/src/consumers/handlers/index.js +1 -1
  49. package/src/consumers/handlers/kafkaHandler.js +1 -1
  50. package/src/consumers/index.js +1 -1
  51. package/src/consumers/messageTransformer.js +1 -1
  52. package/src/consumers/validator.js +1 -1
  53. package/src/core/db/dialect/base-dialect.js +1 -1
  54. package/src/core/db/dialect/index.js +1 -1
  55. package/src/core/db/dialect/mysql-dialect.js +1 -1
  56. package/src/core/db/dialect/oracle-dialect.js +1 -1
  57. package/src/core/db/dialect/postgres-dialect.js +1 -1
  58. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  59. package/src/core/db/flatten-helper.js +1 -1
  60. package/src/core/db/query-builder-error.js +1 -1
  61. package/src/core/db/query-builder.js +1 -1
  62. package/src/core/db/relation-helper.js +1 -1
  63. package/src/core/handlers/delete_handler.js +1 -1
  64. package/src/core/handlers/insert_handler.js +1 -1
  65. package/src/core/handlers/update_handler.js +1 -1
  66. package/src/core/models/base-model.js +1 -1
  67. package/src/core/utils/cache-manager.js +1 -1
  68. package/src/core/utils/component-engine.js +1 -1
  69. package/src/core/utils/context-builder.js +1 -1
  70. package/src/core/utils/datetime-formatter.js +1 -1
  71. package/src/core/utils/datetime-parser.js +1 -1
  72. package/src/core/utils/db.js +1 -1
  73. package/src/core/utils/logger.js +1 -1
  74. package/src/core/utils/payload-loader.js +1 -1
  75. package/src/core/utils/security-checks.js +1 -1
  76. package/src/middleware/body-options.js +1 -1
  77. package/src/middleware/cors.js +1 -1
  78. package/src/middleware/idempotency.js +1 -1
  79. package/src/middleware/rate-limiter.js +1 -1
  80. package/src/middleware/request-logger.js +1 -1
  81. package/src/middleware/security-headers.js +1 -1
  82. package/src/models/base-model-mysql.js +1 -1
  83. package/src/models/base-model-oracle.js +1 -1
  84. package/src/models/base-model-sqlite.js +1 -1
  85. package/src/models/base-model.js +1 -1
  86. package/src/pro/caching/redis-client.js +1 -1
  87. package/src/pro/caching/redis-helper.js +1 -1
  88. package/src/pro/consumers/baseConsumer.js +1 -1
  89. package/src/pro/consumers/declarativeMapper.js +1 -1
  90. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  91. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  92. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  93. package/src/pro/consumers/handlers/index.js +1 -1
  94. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  95. package/src/pro/consumers/index.js +1 -1
  96. package/src/pro/consumers/messageTransformer.js +1 -1
  97. package/src/pro/consumers/validator.js +1 -1
  98. package/src/pro/database/base-model-mysql.js +1 -1
  99. package/src/pro/database/base-model-oracle.js +1 -1
  100. package/src/pro/database/base-model-sqlite.js +1 -1
  101. package/src/pro/database/db-mysql.js +1 -1
  102. package/src/pro/database/db-oracle.js +1 -1
  103. package/src/pro/database/db-sqlite.js +1 -1
  104. package/src/pro/excel/excel-generator.js +1 -1
  105. package/src/pro/excel/excel-parser.js +1 -1
  106. package/src/pro/excel/export-service.js +1 -1
  107. package/src/pro/excel/export_handler.js +1 -1
  108. package/src/pro/excel/import-service.js +1 -1
  109. package/src/pro/excel/import-validator.js +1 -1
  110. package/src/pro/excel/import_handler.js +1 -1
  111. package/src/pro/excel/upsert-builder.js +1 -1
  112. package/src/pro/idgen/idgen-routes.js +1 -1
  113. package/src/pro/integrations/lookup-resolver.js +1 -1
  114. package/src/pro/integrations/upload-handler-v2.js +1 -1
  115. package/src/pro/integrations/upload-handler.js +1 -1
  116. package/src/pro/integrations/webhook.js +1 -1
  117. package/src/pro/locking/lock-routes.js +1 -1
  118. package/src/pro/locking/resource-lock-manager.js +1 -1
  119. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  120. package/src/pro/messaging/kafkaService.js +1 -1
  121. package/src/pro/messaging/messagehubService.js +1 -1
  122. package/src/pro/messaging/rabbitmqService.js +1 -1
  123. package/src/pro/scheduler/job-manager.js +1 -1
  124. package/src/pro/scheduler/job-routes.js +1 -1
  125. package/src/pro/scheduler/job-validator.js +1 -1
  126. package/src/pro/storage/base-storage-provider.js +1 -1
  127. package/src/pro/storage/file-metadata-helper.js +1 -1
  128. package/src/pro/storage/index.js +1 -1
  129. package/src/pro/storage/local-storage-provider.js +1 -1
  130. package/src/pro/storage/s3-storage-provider.js +1 -1
  131. package/src/pro/storage/upload-cleanup-job.js +1 -1
  132. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  133. package/src/pro/storage/upload-pending-tracker.js +1 -1
  134. package/src/pro/websocket/broadcast-helper.js +1 -1
  135. package/src/pro/websocket/index.js +1 -1
  136. package/src/pro/websocket/livesync-server.js +1 -1
  137. package/src/pro/websocket/ws-broadcaster.js +1 -1
  138. package/src/services/export-service.js +1 -1
  139. package/src/services/import-service.js +1 -1
  140. package/src/services/kafkaConsumerService.js +1 -1
  141. package/src/services/kafkaService.js +1 -1
  142. package/src/services/messagehubService.js +1 -1
  143. package/src/services/rabbitmqService.js +1 -1
  144. package/src/utils/cache-invalidation-registry.js +1 -1
  145. package/src/utils/cache-manager.js +1 -1
  146. package/src/utils/component-engine.js +1 -1
  147. package/src/utils/config-extractor.js +1 -1
  148. package/src/utils/consumerLogger.js +1 -1
  149. package/src/utils/context-builder.js +1 -1
  150. package/src/utils/dashboard-helpers.js +1 -1
  151. package/src/utils/dateHelper.js +1 -1
  152. package/src/utils/datetime-formatter.js +1 -1
  153. package/src/utils/datetime-parser.js +1 -1
  154. package/src/utils/db-bootstrap.js +1 -1
  155. package/src/utils/db-mysql.js +1 -1
  156. package/src/utils/db-oracle.js +1 -1
  157. package/src/utils/db-sqlite.js +1 -1
  158. package/src/utils/db.js +1 -1
  159. package/src/utils/demo-generator.js +1 -1
  160. package/src/utils/excel-generator.js +1 -1
  161. package/src/utils/excel-parser.js +1 -1
  162. package/src/utils/file-watcher.js +1 -1
  163. package/src/utils/id-generator.js +1 -1
  164. package/src/utils/idempotency-manager.js +1 -1
  165. package/src/utils/import-validator.js +1 -1
  166. package/src/utils/license-client.js +1 -1
  167. package/src/utils/lock-manager.js +1 -1
  168. package/src/utils/logger.js +1 -1
  169. package/src/utils/lookup-resolver.js +1 -1
  170. package/src/utils/payload-loader.js +1 -1
  171. package/src/utils/processor-response.js +1 -1
  172. package/src/utils/rabbitmq.js +1 -1
  173. package/src/utils/redis-client.js +1 -1
  174. package/src/utils/redis-helper.js +1 -1
  175. package/src/utils/request-scope.js +1 -1
  176. package/src/utils/security-checks.js +1 -1
  177. package/src/utils/service-resolver.js +1 -1
  178. package/src/utils/shutdown-coordinator.js +1 -1
  179. package/src/utils/trusted-keys.js +1 -1
  180. package/src/utils/upload-handler.js +1 -1
  181. package/src/utils/upsert-builder.js +1 -1
  182. package/src/utils/workflow-hook-executor.js +1 -1
@@ -17,11 +17,12 @@ const path = require('path');
17
17
  let pg = null;
18
18
  let mysql2 = null;
19
19
  let oracledb = null;
20
+ let betterSqlite3 = null;
20
21
 
21
22
  /**
22
23
  * Lazy-load database driver berdasarkan DB_TYPE.
23
24
  *
24
- * @param {string} dbType - 'postgresql' | 'mysql' | 'oracle'
25
+ * @param {string} dbType - 'postgresql' | 'mysql' | 'oracle' | 'sqlite'
25
26
  * @returns {Object} Driver module
26
27
  */
27
28
  function loadDriver(dbType) {
@@ -47,11 +48,48 @@ function loadDriver(dbType) {
47
48
  }
48
49
  }
49
50
  return oracledb;
51
+ case 'sqlite':
52
+ if (!betterSqlite3) {
53
+ try { betterSqlite3 = require('better-sqlite3'); } catch (e) {
54
+ throw new Error('SQLite driver (better-sqlite3) not installed. Run: npm install better-sqlite3');
55
+ }
56
+ }
57
+ return betterSqlite3;
50
58
  default:
51
- throw new Error(`Unsupported DB_TYPE: ${dbType}. Supported: postgresql, mysql, oracle`);
59
+ throw new Error(`Unsupported DB_TYPE: ${dbType}. Supported: postgresql, mysql, oracle, sqlite`);
52
60
  }
53
61
  }
54
62
 
63
+ /**
64
+ * Ekstrak nama kolom yang membawa penanda boolean `<col> IN ('true','false')`
65
+ * dari teks CHECK constraint / DDL. Dipakai introspeksi untuk mengenali kembali
66
+ * kolom VARCHAR sebagai boolean pada dialect non-native (mysql/oracle/sqlite),
67
+ * penanda yang di-emit otomatis oleh dbschema-kit create-table/alter-table.
68
+ *
69
+ * Toleran terhadap variasi quoting identifier ("col" / `col` / [col]),
70
+ * urutan nilai ('true','false' atau 'false','true'), serta charset introducer
71
+ * MySQL (mis. _utf8mb4'true'). Nama kolom dikembalikan lowercase.
72
+ *
73
+ * @param {string} text - Teks CHECK clause atau CREATE TABLE DDL
74
+ * @returns {string[]} Daftar nama kolom boolean (lowercase)
75
+ */
76
+ function extractBooleanCheckColumns(text) {
77
+ const cols = [];
78
+ if (!text || typeof text !== 'string') return cols;
79
+ // `\\?` mentoleransi backslash-escaped quote yang dikembalikan MySQL pada
80
+ // CHECK_CLAUSE (mis. _utf8mb4\'true\'); SQLite/Oracle memakai 'true' polos.
81
+ const re = /["`\[]?([A-Za-z_][A-Za-z0-9_]*)["`\]]?\s+IN\s*\(\s*(?:_\w+)?\\?'(true|false)\\?'\s*,\s*(?:_\w+)?\\?'(true|false)\\?'\s*\)/gi;
82
+ let m;
83
+ while ((m = re.exec(text)) !== null) {
84
+ const a = m[2].toLowerCase();
85
+ const b = m[3].toLowerCase();
86
+ if ((a === 'true' && b === 'false') || (a === 'false' && b === 'true')) {
87
+ cols.push(m[1].toLowerCase());
88
+ }
89
+ }
90
+ return cols;
91
+ }
92
+
55
93
  // PostgreSQL FK action codes -> human-readable
56
94
  const PG_FK_ACTION = {
57
95
  a: 'NO ACTION',
@@ -142,12 +180,21 @@ class DatabaseIntrospector {
142
180
  this.dbType = 'mysql';
143
181
  } else if (dbTypeRaw === 'oracle' || dbTypeRaw === 'oracledb') {
144
182
  this.dbType = 'oracle';
183
+ } else if (dbTypeRaw === 'sqlite' || dbTypeRaw === 'sqlite3') {
184
+ this.dbType = 'sqlite';
145
185
  } else {
146
186
  this.dbType = dbTypeRaw;
147
187
  }
148
188
 
149
- // Validate required keys
150
- const requiredKeys = ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'];
189
+ // Validate required keys.
190
+ //
191
+ // SQLite adalah file-based: tidak ada host/port/user/password. Yang wajib
192
+ // hanya path ke file database, diterima via DB_FILE (konsisten dengan
193
+ // dbschema-kit connection.js) atau DB_NAME sebagai fallback. Server-based
194
+ // dialect (postgresql/mysql/oracle) tetap mewajibkan kredensial koneksi.
195
+ const requiredKeys = this.dbType === 'sqlite'
196
+ ? [] // dicek terpisah di bawah karena boleh DB_FILE ATAU DB_NAME
197
+ : ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'];
151
198
  const missingKeys = requiredKeys.filter(key => !config[key]);
152
199
 
153
200
  if (missingKeys.length > 0) {
@@ -155,9 +202,19 @@ class DatabaseIntrospector {
155
202
  return false;
156
203
  }
157
204
 
205
+ if (this.dbType === 'sqlite' && !config.DB_FILE && !config.DB_NAME) {
206
+ console.error('Error: Missing required config key for SQLite: DB_FILE (atau DB_NAME) berisi path file database');
207
+ return false;
208
+ }
209
+
158
210
  this.config = config;
159
211
  if (!this.quiet) {
160
- console.log(`Config loaded: [${this.dbType}] ${config.DB_NAME}@${config.DB_HOST}:${config.DB_PORT}`);
212
+ if (this.dbType === 'sqlite') {
213
+ // SQLite file-based: host:port tidak relevan, tampilkan path file saja.
214
+ console.log(`Config loaded: [sqlite] ${config.DB_FILE || config.DB_NAME}`);
215
+ } else {
216
+ console.log(`Config loaded: [${this.dbType}] ${config.DB_NAME}@${config.DB_HOST}:${config.DB_PORT}`);
217
+ }
161
218
  }
162
219
  return true;
163
220
  } catch (error) {
@@ -227,6 +284,19 @@ class DatabaseIntrospector {
227
284
  break;
228
285
  }
229
286
 
287
+ case 'sqlite': {
288
+ // better-sqlite3 bersifat synchronous: konstruktor langsung membuka
289
+ // file. Mode readonly + fileMustExist dipakai karena introspeksi
290
+ // bersifat read-only dan men-generate payload dari DB yang sudah ada;
291
+ // tanpa fileMustExist, file yang salah path akan dibuat kosong dan
292
+ // berujung error "table not found" yang membingungkan.
293
+ const Database = driver;
294
+ const filePath = this.config.DB_FILE || this.config.DB_NAME;
295
+ this.pool = new Database(filePath, { readonly: true, fileMustExist: true });
296
+ this.pool.prepare('SELECT 1').get();
297
+ break;
298
+ }
299
+
230
300
  default:
231
301
  throw new Error(`Unsupported DB_TYPE: ${this.dbType}`);
232
302
  }
@@ -252,6 +322,9 @@ class DatabaseIntrospector {
252
322
  try {
253
323
  if (this.dbType === 'oracle') {
254
324
  await this.pool.close(0);
325
+ } else if (this.dbType === 'sqlite') {
326
+ // better-sqlite3 Database.close() synchronous, tidak punya .end()
327
+ this.pool.close();
255
328
  } else {
256
329
  await this.pool.end();
257
330
  }
@@ -304,6 +377,13 @@ class DatabaseIntrospector {
304
377
  await conn.close();
305
378
  }
306
379
  }
380
+ case 'sqlite': {
381
+ // better-sqlite3 synchronous. Placeholder positional '?' kompatibel
382
+ // dengan SQL/PRAGMA table-valued function (mis. pragma_table_info(?)).
383
+ // .all() valid untuk statement yang mengembalikan row (SELECT/PRAGMA).
384
+ const stmt = this.pool.prepare(sql);
385
+ return stmt.all(...(params || []));
386
+ }
307
387
  default:
308
388
  return [];
309
389
  }
@@ -325,6 +405,9 @@ class DatabaseIntrospector {
325
405
  case 'postgresql': return { schema: 'public', table: tableName };
326
406
  case 'mysql': return { schema: this.config.DB_NAME, table: tableName };
327
407
  case 'oracle': return { schema: this.config.DB_USER.toUpperCase(), table: tableName };
408
+ // SQLite tidak mengenal schema (hanya database 'main'); schema diabaikan
409
+ // di seluruh query introspeksi sqlite (memakai sqlite_master / PRAGMA).
410
+ case 'sqlite': return { schema: 'main', table: tableName };
328
411
  default: return { schema: 'public', table: tableName };
329
412
  }
330
413
  }
@@ -361,7 +444,10 @@ class DatabaseIntrospector {
361
444
  ORDER BY ordinal_position LIMIT 1`,
362
445
  [schema, table]
363
446
  );
364
- return rows.length > 0 ? rows[0].column_name : null;
447
+ // MySQL 8 information_schema mengembalikan key UPPERCASE (COLUMN_NAME).
448
+ // Tanpa fallback ini PK tidak terdeteksi -> payload jatuh ke tebakan
449
+ // nama PK yang salah (konsisten dengan pola getColumns mysql).
450
+ return rows.length > 0 ? (rows[0].column_name || rows[0].COLUMN_NAME) : null;
365
451
  }
366
452
 
367
453
  case 'oracle': {
@@ -375,6 +461,17 @@ class DatabaseIntrospector {
375
461
  );
376
462
  return rows.length > 0 ? rows[0].column_name.toLowerCase() : null;
377
463
  }
464
+
465
+ case 'sqlite': {
466
+ // pragma_table_info.pk = 0 (bukan PK) atau urutan kolom dalam PK
467
+ // composite (1-based). Ambil kolom pertama (pk=1) konsisten dengan
468
+ // dialect lain yang mengembalikan satu kolom PK.
469
+ const rows = await this.query(
470
+ `SELECT name FROM pragma_table_info(?) WHERE pk > 0 ORDER BY pk`,
471
+ [table]
472
+ );
473
+ return rows.length > 0 ? rows[0].name : null;
474
+ }
378
475
  }
379
476
  return null;
380
477
  } catch (error) {
@@ -422,6 +519,16 @@ class DatabaseIntrospector {
422
519
  );
423
520
  return rows.length > 0;
424
521
  }
522
+
523
+ case 'sqlite': {
524
+ const rows = await this.query(
525
+ `SELECT 1 FROM sqlite_master
526
+ WHERE type IN ('table', 'view') AND name = ?
527
+ LIMIT 1`,
528
+ [table]
529
+ );
530
+ return rows.length > 0;
531
+ }
425
532
  }
426
533
  return false;
427
534
  } catch (error) {
@@ -471,6 +578,14 @@ class DatabaseIntrospector {
471
578
  );
472
579
  return rows.map(r => r.column_name.toLowerCase());
473
580
  }
581
+
582
+ case 'sqlite': {
583
+ const rows = await this.query(
584
+ `SELECT name FROM pragma_table_info(?) ORDER BY cid`,
585
+ [table]
586
+ );
587
+ return rows.map(r => r.name);
588
+ }
474
589
  }
475
590
  return [];
476
591
  } catch (error) {
@@ -528,6 +643,18 @@ class DatabaseIntrospector {
528
643
  rows.forEach(r => { types[r.column_name.toLowerCase()] = r.data_type.toLowerCase(); });
529
644
  break;
530
645
  }
646
+
647
+ case 'sqlite': {
648
+ const rows = await this.query(
649
+ `SELECT name, type FROM pragma_table_info(?) ORDER BY cid`,
650
+ [table]
651
+ );
652
+ // Declared type direfleksikan apa adanya (lowercased). generateDateTimeFields
653
+ // mencocokkan terhadap 'date'/'timestamp'/'time'/'datetime'; tipe DDL sqlite
654
+ // RESTForge memakai DATE/TIMESTAMP sehingga match deterministik.
655
+ rows.forEach(r => { types[r.name] = (r.type || '').toLowerCase(); });
656
+ break;
657
+ }
531
658
  }
532
659
 
533
660
  return types;
@@ -616,6 +743,53 @@ class DatabaseIntrospector {
616
743
  };
617
744
  });
618
745
  }
746
+
747
+ case 'sqlite': {
748
+ // Alias kolom "notnull" ke `nn`: NOTNULL adalah keyword SQLite sehingga
749
+ // alias unquoted `AS notnull` memicu syntax error.
750
+ const rows = await this.query(
751
+ `SELECT name, type, "notnull" AS nn, dflt_value, pk
752
+ FROM pragma_table_info(?) ORDER BY cid`,
753
+ [table]
754
+ );
755
+ // Normalisasi declared type sqlite -> format PostgreSQL-like agar
756
+ // generateFieldValidation (yang mencocokkan terhadap data_type seperti
757
+ // 'varchar'/'integer'/'decimal'/'date'/'timestamp') berperilaku identik
758
+ // lintas dialect. Tipe DDL sqlite RESTForge: VARCHAR(n), TEXT, INTEGER,
759
+ // BIGINT, DECIMAL(p,s), DATE, TIMESTAMP (boolean & uuid -> VARCHAR).
760
+ const STRING_BASES = ['varchar', 'character varying', 'char', 'character', 'nvarchar', 'nchar', 'text', 'clob'];
761
+ return rows.map(r => {
762
+ const declared = (r.type || '').trim().toLowerCase();
763
+ const paren = declared.match(/^([a-z0-9_ ]+?)\s*\(([^)]*)\)\s*$/);
764
+ const base = paren ? paren[1].trim() : declared;
765
+ const argParts = paren && paren[2].trim() !== ''
766
+ ? paren[2].split(',').map(s => parseInt(s.trim(), 10)).filter(n => !Number.isNaN(n))
767
+ : [];
768
+
769
+ let charMaxLen = null;
770
+ let numericPrecision = null;
771
+ let numericScale = null;
772
+ if (argParts.length > 0) {
773
+ if (STRING_BASES.includes(base)) {
774
+ charMaxLen = argParts[0];
775
+ } else {
776
+ numericPrecision = argParts[0];
777
+ numericScale = argParts.length > 1 ? argParts[1] : 0;
778
+ }
779
+ }
780
+
781
+ return {
782
+ column_name: r.name,
783
+ data_type: base,
784
+ udt_name: base,
785
+ column_default: r.dflt_value !== undefined && r.dflt_value !== null ? String(r.dflt_value) : null,
786
+ is_nullable: Number(r.nn) === 1 ? 'NO' : 'YES',
787
+ character_maximum_length: charMaxLen,
788
+ numeric_precision: numericPrecision,
789
+ numeric_scale: numericScale
790
+ };
791
+ });
792
+ }
619
793
  }
620
794
  return [];
621
795
  } catch (error) {
@@ -662,6 +836,14 @@ class DatabaseIntrospector {
662
836
  );
663
837
  return rows.map(r => r.column_name.toLowerCase());
664
838
  }
839
+
840
+ case 'sqlite': {
841
+ const rows = await this.query(
842
+ `SELECT name FROM pragma_table_info(?) WHERE "notnull" = 1`,
843
+ [table]
844
+ );
845
+ return rows.map(r => r.name);
846
+ }
665
847
  }
666
848
  return [];
667
849
  } catch (error) {
@@ -746,6 +928,45 @@ class DatabaseIntrospector {
746
928
  }
747
929
  return result;
748
930
  }
931
+
932
+ case 'sqlite': {
933
+ const result = [];
934
+
935
+ // PRIMARY KEY: dari pragma_table_info. Menangani INTEGER PRIMARY KEY
936
+ // (rowid alias) yang TIDAK muncul di pragma_index_list. Composite PK
937
+ // diurutkan berdasarkan kolom pk (1-based).
938
+ const pkRows = await this.query(
939
+ `SELECT name, pk FROM pragma_table_info(?) WHERE pk > 0 ORDER BY pk`,
940
+ [table]
941
+ );
942
+ if (pkRows.length > 0) {
943
+ result.push({
944
+ name: `pk_${table}`,
945
+ type: 'PRIMARY KEY',
946
+ columns: pkRows.map(r => r.name)
947
+ });
948
+ }
949
+
950
+ // UNIQUE: pragma_index_list origin = 'u' (UNIQUE constraint), bukan
951
+ // 'pk' (primary key, sudah ditangani) atau 'c' (CREATE INDEX manual).
952
+ const uniqueIdx = await this.query(
953
+ `SELECT name FROM pragma_index_list(?) WHERE origin = 'u'`,
954
+ [table]
955
+ );
956
+ for (const idx of uniqueIdx) {
957
+ const cols = await this.query(
958
+ `SELECT name FROM pragma_index_info(?) ORDER BY seqno`,
959
+ [idx.name]
960
+ );
961
+ result.push({
962
+ name: idx.name,
963
+ type: 'UNIQUE',
964
+ columns: cols.map(c => c.name)
965
+ });
966
+ }
967
+
968
+ return result;
969
+ }
749
970
  }
750
971
  return [];
751
972
  } catch (error) {
@@ -754,6 +975,78 @@ class DatabaseIntrospector {
754
975
  }
755
976
  }
756
977
 
978
+ /**
979
+ * Deteksi kolom boolean pada dialect varchar (mysql/oracle/sqlite) lewat
980
+ * penanda CHECK `<col> IN ('true','false')` yang di-emit dbschema-kit.
981
+ *
982
+ * PostgreSQL memakai tipe BOOLEAN native sehingga sudah terdeteksi di
983
+ * getDetailedColumnInfo (data_type 'boolean'); method ini mengembalikan []
984
+ * untuk postgresql. Degradasi anggun: bila katalog CHECK tidak tersedia
985
+ * (mis. MySQL < 8.0.16 atau Oracle < 12c tanpa search_condition_vc), error
986
+ * di-catch dan dikembalikan [] (kolom tetap diperlakukan string).
987
+ *
988
+ * @param {string} tableName - Nama table
989
+ * @returns {Promise<string[]>} Nama kolom boolean (lowercase)
990
+ */
991
+ async getBooleanColumns(tableName) {
992
+ if (!this.pool) return [];
993
+
994
+ try {
995
+ const { schema, table } = this.parseTableName(tableName);
996
+ let texts = [];
997
+
998
+ switch (this.dbType) {
999
+ case 'sqlite': {
1000
+ const rows = await this.query(
1001
+ `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,
1002
+ [table]
1003
+ );
1004
+ texts = rows.map(r => r.sql || '');
1005
+ break;
1006
+ }
1007
+
1008
+ case 'mysql': {
1009
+ const rows = await this.query(
1010
+ `SELECT cc.CHECK_CLAUSE AS clause
1011
+ FROM information_schema.CHECK_CONSTRAINTS cc
1012
+ JOIN information_schema.TABLE_CONSTRAINTS tc
1013
+ ON cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
1014
+ AND cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
1015
+ WHERE tc.TABLE_SCHEMA = ? AND tc.TABLE_NAME = ? AND tc.CONSTRAINT_TYPE = 'CHECK'`,
1016
+ [schema, table]
1017
+ );
1018
+ texts = rows.map(r => r.CHECK_CLAUSE || r.clause || '');
1019
+ break;
1020
+ }
1021
+
1022
+ case 'oracle': {
1023
+ // search_condition_vc (VARCHAR2) tersedia Oracle 12c+; lebih mudah
1024
+ // di-query daripada kolom LONG search_condition.
1025
+ const rows = await this.query(
1026
+ `SELECT search_condition_vc AS clause FROM user_constraints
1027
+ WHERE table_name = :1 AND constraint_type = 'C'`,
1028
+ [table.toUpperCase()]
1029
+ );
1030
+ texts = rows.map(r => r.clause || r.search_condition_vc || '');
1031
+ break;
1032
+ }
1033
+
1034
+ default:
1035
+ // postgresql: boolean native, tidak perlu penanda
1036
+ return [];
1037
+ }
1038
+
1039
+ const cols = new Set();
1040
+ for (const t of texts) {
1041
+ for (const c of extractBooleanCheckColumns(t)) cols.add(c);
1042
+ }
1043
+ return Array.from(cols);
1044
+ } catch (error) {
1045
+ console.warn(` Warning: Could not detect boolean columns: ${error.message}`);
1046
+ return [];
1047
+ }
1048
+ }
1049
+
757
1050
  /**
758
1051
  * List all tables (and views) in database.
759
1052
  *
@@ -909,6 +1202,25 @@ class DatabaseIntrospector {
909
1202
  break;
910
1203
  }
911
1204
 
1205
+ case 'sqlite': {
1206
+ // SQLite tidak punya schema; schemaFilter diabaikan. Tabel internal
1207
+ // (sqlite_*, mis. sqlite_sequence) di-skip kecuali includeSystem.
1208
+ // Legacy mode: BASE TABLE saja; modern mode: table + view.
1209
+ const typeList = modernMode ? "('table', 'view')" : "('table')";
1210
+ let sql = `SELECT name, type FROM sqlite_master WHERE type IN ${typeList}`;
1211
+ if (!includeSystem) {
1212
+ sql += " AND name NOT LIKE 'sqlite_%'";
1213
+ }
1214
+ sql += ' ORDER BY name';
1215
+ const rows = await this.query(sql, []);
1216
+ rawRows = rows.map(r => ({
1217
+ schema: 'main',
1218
+ name: r.name,
1219
+ type: r.type === 'view' ? 'view' : 'table'
1220
+ }));
1221
+ break;
1222
+ }
1223
+
912
1224
  default:
913
1225
  return [];
914
1226
  }
@@ -1073,6 +1385,39 @@ class DatabaseIntrospector {
1073
1385
  }
1074
1386
  return Object.values(grouped);
1075
1387
  }
1388
+
1389
+ case 'sqlite': {
1390
+ // pragma_foreign_key_list: kolom id (group FK composite), seq (urutan
1391
+ // kolom), table (tabel referensi), from/to (kolom lokal/referensi),
1392
+ // on_update/on_delete. SQLite tidak menamai FK constraint, jadi nama
1393
+ // disintesis fk_<table>_<id>. references.schema null (tanpa schema).
1394
+ const rows = await this.query(
1395
+ `SELECT id, seq, "table" AS ref_table, "from" AS col, "to" AS ref_col,
1396
+ on_update, on_delete
1397
+ FROM pragma_foreign_key_list(?) ORDER BY id, seq`,
1398
+ [table]
1399
+ );
1400
+ const grouped = {};
1401
+ for (const r of rows) {
1402
+ const id = r.id;
1403
+ if (!grouped[id]) {
1404
+ grouped[id] = {
1405
+ name: `fk_${table}_${id}`,
1406
+ columns: [],
1407
+ references: {
1408
+ schema: null,
1409
+ table: r.ref_table,
1410
+ columns: []
1411
+ },
1412
+ onDelete: r.on_delete || null,
1413
+ onUpdate: r.on_update || null
1414
+ };
1415
+ }
1416
+ grouped[id].columns.push(r.col);
1417
+ grouped[id].references.columns.push(r.ref_col);
1418
+ }
1419
+ return Object.values(grouped);
1420
+ }
1076
1421
  }
1077
1422
  return [];
1078
1423
  } catch (error) {
@@ -1178,6 +1523,30 @@ class DatabaseIntrospector {
1178
1523
  }
1179
1524
  return Object.values(grouped);
1180
1525
  }
1526
+
1527
+ case 'sqlite': {
1528
+ // pragma_index_list: name, unique (0/1), origin ('pk'|'u'|'c').
1529
+ // INTEGER PRIMARY KEY (rowid) tidak punya index terpisah sehingga
1530
+ // tidak muncul di sini, konsisten dengan realita storage sqlite.
1531
+ const idxRows = await this.query(
1532
+ `SELECT name, "unique" AS is_unique, origin FROM pragma_index_list(?) ORDER BY name`,
1533
+ [table]
1534
+ );
1535
+ const result = [];
1536
+ for (const idx of idxRows) {
1537
+ const cols = await this.query(
1538
+ `SELECT name FROM pragma_index_info(?) ORDER BY seqno`,
1539
+ [idx.name]
1540
+ );
1541
+ result.push({
1542
+ name: idx.name,
1543
+ columns: cols.map(c => c.name),
1544
+ unique: Number(idx.is_unique) === 1,
1545
+ primary: idx.origin === 'pk'
1546
+ });
1547
+ }
1548
+ return result;
1549
+ }
1181
1550
  }
1182
1551
  return [];
1183
1552
  } catch (error) {
@@ -1249,6 +1618,12 @@ class DatabaseIntrospector {
1249
1618
  const rows = await this.query(sql, []);
1250
1619
  return rows.map(r => String(r.username).toLowerCase());
1251
1620
  }
1621
+
1622
+ case 'sqlite': {
1623
+ // SQLite tidak mengenal konsep schema bernama (hanya database 'main'
1624
+ // dan optional 'temp'/attached). Tidak ada user schema untuk diiterasi.
1625
+ return [];
1626
+ }
1252
1627
  }
1253
1628
  return [];
1254
1629
  } catch (error) {
@@ -1336,6 +1711,25 @@ class DatabaseIntrospector {
1336
1711
  }
1337
1712
  }
1338
1713
 
1714
+ case 'sqlite': {
1715
+ // prepare() me-resolve metadata kolom tanpa mengeksekusi baris.
1716
+ // stmt.columns() mengembalikan deskriptor kolom untuk statement
1717
+ // yang mengembalikan data (SELECT).
1718
+ try {
1719
+ const stmt = this.pool.prepare(trimmed);
1720
+ const columns = (stmt.columns() || []).map(c => String(c.name).toLowerCase());
1721
+ return { ok: true, columns };
1722
+ } catch (err) {
1723
+ return {
1724
+ ok: false,
1725
+ error: {
1726
+ code: err.code || 'unknown',
1727
+ message: err.message
1728
+ }
1729
+ };
1730
+ }
1731
+ }
1732
+
1339
1733
  default:
1340
1734
  throw new Error(`Unsupported DB_TYPE: ${this.dbType}`);
1341
1735
  }
@@ -1398,6 +1792,23 @@ class DatabaseIntrospector {
1398
1792
  }
1399
1793
  }
1400
1794
 
1795
+ case 'sqlite': {
1796
+ // prepare() melakukan validasi sintaks + resolusi schema tanpa
1797
+ // mengeksekusi statement, ekuivalen dengan peran EXPLAIN di dialect lain.
1798
+ try {
1799
+ this.pool.prepare(sql);
1800
+ return { ok: true };
1801
+ } catch (err) {
1802
+ return {
1803
+ ok: false,
1804
+ error: {
1805
+ code: err.code || 'unknown',
1806
+ message: err.message
1807
+ }
1808
+ };
1809
+ }
1810
+ }
1811
+
1401
1812
  default:
1402
1813
  throw new Error(`Unsupported DB_TYPE: ${this.dbType}`);
1403
1814
  }
@@ -330,7 +330,7 @@ class ArgumentValidator {
330
330
  }
331
331
 
332
332
  const trimmed = databaseType.trim().toLowerCase();
333
- const validTypes = ['postgres', 'oracle', 'mysql'];
333
+ const validTypes = ['postgres', 'oracle', 'mysql', 'sqlite'];
334
334
 
335
335
  if (!validTypes.includes(trimmed)) {
336
336
  throw new Error(`Database type '${databaseType}' is invalid. Valid options: ${validTypes.join(', ')}`);
@@ -436,7 +436,7 @@ class ArgumentValidator {
436
436
  --endpoint=<name> Endpoint name to create (OPTIONAL)
437
437
  --payload=<file> Payload file name without .json (OPTIONAL)
438
438
 
439
- --database=<type> Database type: postgres|oracle|mysql (DEFAULT: postgres)
439
+ --database=<type> Database type: postgres|oracle|mysql|sqlite (DEFAULT: postgres)
440
440
  --force=<bool> Overwrite existing files (skip confirmation): true|false (DEFAULT: false)
441
441
  --create-examples=<bool> Create example files for testing: true|false (DEFAULT: true)
442
442
  --skip-sql-validation=<bool> Skip SQL keywords validation: true|false (DEFAULT: false)
@@ -1,27 +1,27 @@
1
1
  {
2
- "version": "5.0.1",
3
- "generated": "2026-05-31T03:19:51.440Z",
2
+ "version": "5.0.8",
3
+ "generated": "2026-06-01T03:35:49.528Z",
4
4
  "generator": "restforge-build-system",
5
5
  "algorithm": "sha256",
6
6
  "files": {
7
- "server.js": "2a00acf82b46545d92744aa738d6e0f5c18e589a8cbf39f82cbce56cda172a1a",
8
- "src/utils/license-client.js": "cb3485ce1b80693679e1befb1e3d1a05c52b2be9364b2fda6cf9605ed24257a4",
9
- "src/utils/trusted-keys.js": "c99a0521c5fb97326f928452265d77f4d4c9d05b0761a2b267315c8bbc416d80",
10
- "cli/consumer.js": "7fcee1e3c4b441e4730f3e2908bcaf832cd80c47140385951a6d2e7d0752a07d",
11
- "src/utils/security-checks.js": "dd5b60c2410339b0d1501ff2a31f32373568256a9b3510477927e2f4018c3adf",
12
- "scripts/verify-integrity.js": "e90f1ff83445a0c31fa27dbe883e3911c7fd8365627fb314d51ca391fa2ad695",
7
+ "server.js": "c2dad04d38ab4ab2b287276c84f58b30e26c9c0a5c257f6273ffb820b68c2cf2",
8
+ "src/utils/license-client.js": "ac2ce8875b21009f826d0afddcdae32e5307b123a05fe1b62eec0569843e9472",
9
+ "src/utils/trusted-keys.js": "01a7f3eb841fe8b4062bdc4a10899686d78af7580e10bba23f037021bf9bf905",
10
+ "cli/consumer.js": "a82465404d9da7aca1c0ae0caf820934b9e73d564225e2054d409fa5450fe2db",
11
+ "src/utils/security-checks.js": "e599e1b4c01ef857b5e99bee9d5aec9bd5e326262d04a1272c3a088cdcc9eceb",
12
+ "scripts/verify-integrity.js": "8fd989b49b31c776071b2eeba02831cbf1511d9ee05a906548acf0f6c3909da5",
13
13
  "bin/restforge-hwinfo-linux": "d9a0071b0acf58a020e738a29837de356f531a5a84d363a2adc50563e3874b8f",
14
14
  "bin/restforge-hwinfo-darwin": "d35b15d4e6d7bbdd8317490335ba9602ce5c218519898fa3eabe18cc9c5bb1b6",
15
15
  "bin/restforge-hwinfo.exe": "351fa4d33459d30de804ee78deb5eb977b77b688fe1b7b461cdaa214a82f145e",
16
- "generators/lib/templates/mysql-template.js": "393be6e36bbb0d7a90ea674985512156b4c7bd18a09cb0e0b488c82893cb5d31",
17
- "generators/lib/templates/oracle-template.js": "5899cbf1b5b6b38ec5c2fb23f677650a10c6700597a879214959aa8fad6567f4",
18
- "generators/lib/templates/postgres-template.js": "c862c0cba61720da1222657fa95fe18ffe6cc3e59e32022c9652389c6e51342e",
19
- "generators/lib/templates/sqlite-template.js": "33e597b3120bcc862d891a8c23a02dfaa8e31e512c56f24aaeb37c105dc43945",
20
- "generators/lib/templates/dashboard-catalog.js": "2eef46fb442bcd3325fd56758d22acdf88be8b1221e8b99a6b53804b91f10618",
21
- "generators/lib/templates/dbschema-catalog.js": "70d65baeb0211a29ce824b24a4b27b66b83d07785afb85f90ba8964be0b3cac2",
22
- "generators/lib/templates/field-validation-catalog.js": "de743c44c141e287df8a6fe9cca6bf3040de35dd744ecc9d5106c711e264a4b4",
23
- "generators/lib/templates/query-declarative-catalog.js": "e571ca7ae08944ff11bbbcfaa75c1558cdfa6be6fa8d2f919795dbe118a42db3",
24
- "generators/lib/templates/db-connection-env.js": "fbd9dc6caa4771f9e4ea4cfbee53f7d6e63937ba9fabad39da5e22ab55933467"
16
+ "generators/lib/templates/mysql-template.js": "cef2a73006f4981d2bc008c704e552b61f27cfb0150dc4c1c905d1a7819d7039",
17
+ "generators/lib/templates/oracle-template.js": "57a38d80aced2d735d033bd05a52df89d6238a47d1a0a84c8a4c2250746ff593",
18
+ "generators/lib/templates/postgres-template.js": "0f6bc325e5b41a11e7ce9766e58a020f2cd5b85b88b282778d059d81bf7aa601",
19
+ "generators/lib/templates/sqlite-template.js": "ef3658fb92047abc52c5acf2869ceca4298a3e22fdaf549c7daa806f6ec73aa9",
20
+ "generators/lib/templates/dashboard-catalog.js": "5ad9b475aa7ccd5c097abe4ecf2777a9036714dceb2984e74bf321adb2bdde98",
21
+ "generators/lib/templates/dbschema-catalog.js": "9e45b2219352f321fbb2b5f0a2ea14dc7e689d85ee6ab9c94c66275bda697b81",
22
+ "generators/lib/templates/field-validation-catalog.js": "5d77379d8f069204fdbe2d8700932fc76583cbf2431946413aca72b3dcfe1c93",
23
+ "generators/lib/templates/query-declarative-catalog.js": "d12158465f017aa41ee0eac6699a4a79321fdbb63ea984821e403f0d155e27ba",
24
+ "generators/lib/templates/db-connection-env.js": "3fb59cfdd6badd39543e3725bc163c6672c6f7dd78c69bf126b9b1bf8e5d54c5"
25
25
  },
26
26
  "stats": {
27
27
  "totalFiles": 18,
@@ -31,7 +31,7 @@
31
31
  "signature": {
32
32
  "algorithm": "ed25519",
33
33
  "keyId": "rfs-manifest-2026-05",
34
- "value": "1467eb2cf73a93bd5e2f28f4f9f3c5e4baf15215ea058bd5fee5da859608233b454208dcc472200d07a9cd347494f3fc9e6ad676234bc74feb1f9bdbdf5eaa05",
34
+ "value": "17b3022758981192df691f2b55aeed699e81dd0106ed15eeb445fefdc8392cd4ff7e5e1ab29725dc6592d2156545d0535e115059a5eba5cf8dd9464d710fa80e",
35
35
  "scope": "canonical-json(manifest-without-signature)"
36
36
  }
37
37
  }