@restforgejs/platform 4.2.8 → 4.3.2

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 (325) hide show
  1. package/SECURITY.md +83 -4
  2. package/bin/sdf-tools.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/dashboard/create.js +4 -1
  7. package/generators/cli/endpoint/create.js +1 -1
  8. package/generators/cli/key/generate.js +2 -1
  9. package/generators/cli/key/revoke.js +2 -1
  10. package/generators/cli/payload/diff.js +3 -2
  11. package/generators/cli/payload/generate.js +3 -2
  12. package/generators/cli/payload/sync.js +3 -2
  13. package/generators/cli/payload/validate.js +3 -2
  14. package/generators/cli/processor/create.js +14 -3
  15. package/generators/cli/project/delete.js +2 -1
  16. package/generators/cli/query/validate.js +3 -2
  17. package/generators/cli/schema/apply.js +3 -2
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +3 -2
  20. package/generators/cli/schema/introspect.js +3 -2
  21. package/generators/cli/schema/list.js +3 -2
  22. package/generators/cli/schema/migrate.js +3 -2
  23. package/generators/lib/migration/audit-table-runner.js +213 -215
  24. package/generators/lib/payload/payload-runner.js +1 -1
  25. package/generators/lib/templates/dashboard-catalog.js +1 -437
  26. package/generators/lib/templates/db-connection-env.js +1 -212
  27. package/generators/lib/templates/dbschema-catalog.js +1 -489
  28. package/generators/lib/templates/field-validation-catalog.js +1 -531
  29. package/generators/lib/templates/mysql-template.js +1 -3863
  30. package/generators/lib/templates/oracle-template.js +1 -3915
  31. package/generators/lib/templates/postgres-template.js +1 -5838
  32. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  33. package/generators/lib/templates/sqlite-template.js +1 -3440
  34. package/generators/lib/utils/env-manager.js +6 -0
  35. package/generators/lib/utils/path-validator.js +71 -0
  36. package/generators/lib/validators/payload-validator.js +1 -2
  37. package/integrity-manifest.json +28 -10
  38. package/package.json +11 -3
  39. package/scripts/verify-integrity.js +1 -1
  40. package/server.js +1 -1
  41. package/src/components/handlers/adjust_handler.js +1 -1
  42. package/src/components/handlers/audit_handler.js +1 -1
  43. package/src/components/handlers/delete_handler.js +1 -1
  44. package/src/components/handlers/export_handler.js +1 -1
  45. package/src/components/handlers/import_handler.js +1 -1
  46. package/src/components/handlers/insert_handler.js +1 -1
  47. package/src/components/handlers/update_handler.js +1 -1
  48. package/src/components/handlers/upload_handler.js +1 -1
  49. package/src/components/handlers/workflow_handler.js +1 -1
  50. package/src/components/integrations/webhook.js +1 -1
  51. package/src/consumers/baseConsumer.js +1 -1
  52. package/src/consumers/declarativeMapper.js +1 -1
  53. package/src/consumers/handlers/apiHandler.js +1 -1
  54. package/src/consumers/handlers/consoleHandler.js +1 -1
  55. package/src/consumers/handlers/databaseHandler.js +1 -1
  56. package/src/consumers/handlers/index.js +1 -1
  57. package/src/consumers/handlers/kafkaHandler.js +1 -1
  58. package/src/consumers/index.js +1 -1
  59. package/src/consumers/messageTransformer.js +1 -1
  60. package/src/consumers/validator.js +1 -1
  61. package/src/core/db/dialect/base-dialect.js +1 -1
  62. package/src/core/db/dialect/index.js +1 -1
  63. package/src/core/db/dialect/mysql-dialect.js +1 -1
  64. package/src/core/db/dialect/oracle-dialect.js +1 -1
  65. package/src/core/db/dialect/postgres-dialect.js +1 -1
  66. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  67. package/src/core/db/flatten-helper.js +1 -1
  68. package/src/core/db/query-builder-error.js +1 -1
  69. package/src/core/db/query-builder.js +1 -1
  70. package/src/core/db/relation-helper.js +1 -1
  71. package/src/core/handlers/delete_handler.js +1 -1
  72. package/src/core/handlers/insert_handler.js +1 -1
  73. package/src/core/handlers/update_handler.js +1 -1
  74. package/src/core/models/base-model.js +1 -1
  75. package/src/core/utils/cache-manager.js +1 -1
  76. package/src/core/utils/component-engine.js +1 -1
  77. package/src/core/utils/context-builder.js +1 -1
  78. package/src/core/utils/datetime-formatter.js +1 -1
  79. package/src/core/utils/datetime-parser.js +1 -1
  80. package/src/core/utils/db.js +1 -1
  81. package/src/core/utils/logger.js +1 -1
  82. package/src/core/utils/payload-loader.js +1 -1
  83. package/src/core/utils/security-checks.js +1 -1
  84. package/src/middleware/body-options.js +1 -1
  85. package/src/middleware/cors.js +1 -1
  86. package/src/middleware/idempotency.js +1 -1
  87. package/src/middleware/rate-limiter.js +1 -1
  88. package/src/middleware/request-logger.js +1 -1
  89. package/src/middleware/security-headers.js +1 -1
  90. package/src/models/base-model-mysql.js +1 -1
  91. package/src/models/base-model-oracle.js +1 -1
  92. package/src/models/base-model-sqlite.js +1 -1
  93. package/src/models/base-model.js +1 -1
  94. package/src/pro/caching/redis-client.js +1 -1
  95. package/src/pro/caching/redis-helper.js +1 -1
  96. package/src/pro/consumers/baseConsumer.js +1 -1
  97. package/src/pro/consumers/declarativeMapper.js +1 -1
  98. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  99. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  100. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  101. package/src/pro/consumers/handlers/index.js +1 -1
  102. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  103. package/src/pro/consumers/index.js +1 -1
  104. package/src/pro/consumers/messageTransformer.js +1 -1
  105. package/src/pro/consumers/validator.js +1 -1
  106. package/src/pro/database/base-model-mysql.js +1 -1
  107. package/src/pro/database/base-model-oracle.js +1 -1
  108. package/src/pro/database/base-model-sqlite.js +1 -1
  109. package/src/pro/database/db-mysql.js +1 -1
  110. package/src/pro/database/db-oracle.js +1 -1
  111. package/src/pro/database/db-sqlite.js +1 -1
  112. package/src/pro/excel/excel-generator.js +1 -1
  113. package/src/pro/excel/excel-parser.js +1 -1
  114. package/src/pro/excel/export-service.js +1 -1
  115. package/src/pro/excel/export_handler.js +1 -1
  116. package/src/pro/excel/import-service.js +1 -1
  117. package/src/pro/excel/import-validator.js +1 -1
  118. package/src/pro/excel/import_handler.js +1 -1
  119. package/src/pro/excel/upsert-builder.js +1 -1
  120. package/src/pro/idgen/idgen-routes.js +1 -1
  121. package/src/pro/integrations/lookup-resolver.js +1 -1
  122. package/src/pro/integrations/upload-handler-v2.js +1 -1
  123. package/src/pro/integrations/upload-handler.js +1 -1
  124. package/src/pro/integrations/webhook.js +1 -1
  125. package/src/pro/locking/lock-routes.js +1 -1
  126. package/src/pro/locking/resource-lock-manager.js +1 -1
  127. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  128. package/src/pro/messaging/kafkaService.js +1 -1
  129. package/src/pro/messaging/messagehubService.js +1 -1
  130. package/src/pro/messaging/rabbitmqService.js +1 -1
  131. package/src/pro/scheduler/job-manager.js +1 -1
  132. package/src/pro/scheduler/job-routes.js +1 -1
  133. package/src/pro/scheduler/job-validator.js +1 -1
  134. package/src/pro/storage/base-storage-provider.js +1 -1
  135. package/src/pro/storage/file-metadata-helper.js +1 -1
  136. package/src/pro/storage/index.js +1 -1
  137. package/src/pro/storage/local-storage-provider.js +1 -1
  138. package/src/pro/storage/s3-storage-provider.js +1 -1
  139. package/src/pro/storage/upload-cleanup-job.js +1 -1
  140. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  141. package/src/pro/storage/upload-pending-tracker.js +1 -1
  142. package/src/pro/websocket/broadcast-helper.js +1 -1
  143. package/src/pro/websocket/index.js +1 -1
  144. package/src/pro/websocket/livesync-server.js +1 -1
  145. package/src/pro/websocket/ws-broadcaster.js +1 -1
  146. package/src/services/export-service.js +1 -1
  147. package/src/services/import-service.js +1 -1
  148. package/src/services/kafkaConsumerService.js +1 -1
  149. package/src/services/kafkaService.js +1 -1
  150. package/src/services/messagehubService.js +1 -1
  151. package/src/services/rabbitmqService.js +1 -1
  152. package/src/utils/cache-invalidation-registry.js +1 -1
  153. package/src/utils/cache-manager.js +1 -1
  154. package/src/utils/component-engine.js +1 -1
  155. package/src/utils/config-extractor.js +1 -1
  156. package/src/utils/consumerLogger.js +1 -1
  157. package/src/utils/context-builder.js +1 -1
  158. package/src/utils/dashboard-helpers.js +1 -1
  159. package/src/utils/dateHelper.js +1 -1
  160. package/src/utils/datetime-formatter.js +1 -1
  161. package/src/utils/datetime-parser.js +1 -1
  162. package/src/utils/db-bootstrap.js +1 -1
  163. package/src/utils/db-mysql.js +1 -1
  164. package/src/utils/db-oracle.js +1 -1
  165. package/src/utils/db-sqlite.js +1 -1
  166. package/src/utils/db.js +1 -1
  167. package/src/utils/demo-generator.js +1 -1
  168. package/src/utils/excel-generator.js +1 -1
  169. package/src/utils/excel-parser.js +1 -1
  170. package/src/utils/file-watcher.js +1 -1
  171. package/src/utils/id-generator.js +1 -1
  172. package/src/utils/idempotency-manager.js +1 -1
  173. package/src/utils/import-validator.js +1 -1
  174. package/src/utils/license-client.js +1 -1
  175. package/src/utils/lock-manager.js +1 -1
  176. package/src/utils/logger.js +1 -1
  177. package/src/utils/lookup-resolver.js +1 -1
  178. package/src/utils/payload-loader.js +1 -1
  179. package/src/utils/processor-response.js +1 -1
  180. package/src/utils/rabbitmq.js +1 -1
  181. package/src/utils/redis-client.js +1 -1
  182. package/src/utils/redis-helper.js +1 -1
  183. package/src/utils/request-scope.js +1 -1
  184. package/src/utils/security-checks.js +1 -1
  185. package/src/utils/service-resolver.js +1 -1
  186. package/src/utils/shutdown-coordinator.js +1 -1
  187. package/src/utils/trusted-keys.js +1 -1
  188. package/src/utils/upload-handler.js +1 -1
  189. package/src/utils/upsert-builder.js +1 -1
  190. package/src/utils/workflow-hook-executor.js +1 -1
  191. package/generators/metadata/global.json +0 -58
  192. package/generators/metadata/test-mysql-workbench.json +0 -118
  193. package/generators/metadata/test-mysql.json +0 -56
  194. package/generators/metadata/test-oracle-workbench.json +0 -118
  195. package/generators/metadata/test-oracle.json +0 -56
  196. package/generators/metadata/test-pg-workbench.json +0 -118
  197. package/generators/metadata/test-pg.json +0 -56
  198. package/generators/scripts/obfuscate-source.js +0 -356
  199. package/generators/scripts/validate-catalog.js +0 -430
  200. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  201. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  202. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  203. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  204. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  205. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  206. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  207. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  208. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  209. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  210. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  211. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  212. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  213. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  214. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  215. package/generators/tests/integration-tahap4b/README.md +0 -145
  216. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  217. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  218. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  219. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  220. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  221. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  222. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  223. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  224. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  225. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  226. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  227. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  228. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  229. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  230. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  231. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  232. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  233. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  234. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  235. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  236. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  237. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  238. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  239. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  240. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  241. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  242. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  243. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  244. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  245. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  246. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  247. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  248. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  249. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  250. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  251. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  252. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  253. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  254. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  255. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  256. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  257. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  258. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  259. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  260. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  261. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  262. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  263. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  264. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  265. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  266. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  267. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  268. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  269. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  270. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  271. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  299. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  300. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  301. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  302. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  303. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  304. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  305. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  306. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  307. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  308. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  309. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  310. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  311. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  312. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  313. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  314. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  315. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  316. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  317. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  318. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  319. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  320. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  321. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  322. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  323. package/scripts/generate-integrity-manifest.js +0 -124
  324. package/scripts/snapshot-cli-contracts.js +0 -194
  325. package/scripts/verify-publish.js +0 -56
@@ -1,1002 +0,0 @@
1
- const BaseModel = require('restforgejs/src/models/base-model-oracle');
2
- const db = require('restforgejs/src/utils/db-oracle');
3
- const oracledb = require('oracledb');
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- /**
8
- * Item Model - Oracle Database
9
- * Generated: 2026-04-25T07:59:17.818Z
10
- *
11
- * Table: core.item
12
- * Primary Key: id
13
- * Fields: 12
14
- * Database: Oracle
15
- */
16
- class ItemModel extends BaseModel {
17
- /**
18
- * Constructor
19
- */
20
- constructor() {
21
- const validFields = [
22
- 'item_id', 'item_code', 'item_name', 'description', 'uom', 'unit_price', 'weight', 'is_active', 'created_at', 'created_by', 'updated_at', 'updated_by'
23
- ];
24
-
25
- const datatablesWhere = ["item_code","item_name","description","all"];
26
-
27
- super('core.item', validFields);
28
-
29
- // base-model-oracle hanya menerima 2 parameter, simpan datatablesWhere manual
30
- this.datatablesWhere = datatablesWhere;
31
-
32
- // Primary key configuration
33
- this.primaryKey = 'id';
34
-
35
- // Read/Write source configuration
36
- this.viewName = 'core.item';
37
- this.readSource = 'core.item';
38
- this.writeSource = 'core.item';
39
-
40
- // Flag untuk self-documenting API (endpoint /info)
41
- this.hasViewQuery = false;
42
- this.hasExportQuery = false;
43
-
44
- // Field validation configuration
45
- this.validationConfig = {}; // No field validation config
46
-
47
- // Model metadata
48
- this.modelMetadata = {
49
- endpointName: 'item',
50
- moduleName: 'mini-inventory',
51
- tableName: 'core.item',
52
- databaseType: 'oracle',
53
- primaryKey: 'id',
54
- fieldCount: 12,
55
- generated: '2026-04-25T07:59:17.818Z'
56
- };
57
-
58
- this.advancedQueryTemplates = this.loadAdvancedQueryTemplates();
59
- }
60
-
61
- /**
62
- * Load advanced query templates dari file
63
- */
64
- loadAdvancedQueryTemplates() {
65
- const templates = {};
66
-
67
- // No advanced queries defined
68
-
69
- return templates;
70
- }
71
-
72
- /**
73
- * Override getListQuery untuk Oracle syntax
74
- */
75
- getListQuery(options = {}) {
76
- let baseQuery = `
77
- select item_id, item_code, item_name, description, uom, unit_price, weight, is_active, created_at, created_by, updated_at, updated_by from core.item
78
- `.trim();
79
-
80
- // Convert PostgreSQL syntax to Oracle if needed
81
- baseQuery = this.convertToOracleSQL(baseQuery);
82
-
83
- return baseQuery;
84
- }
85
-
86
- /**
87
- * Override getReadQuery untuk endpoint /read
88
- * Prioritas: viewName → viewQuery → tableName (SELECT * FROM readSource)
89
- */
90
- getReadQuery(options = {}) {
91
- if (this.viewName && this.viewName !== this.table) {
92
- return 'SELECT * FROM ' + this.viewName;
93
- }
94
- return 'SELECT * FROM ' + this.readSource;
95
- }
96
-
97
- /**
98
- * Convert PostgreSQL SQL syntax to Oracle
99
- */
100
- convertToOracleSQL(sql) {
101
- sql = sql.replace(/\bILIKE\b/gi, 'LIKE');
102
- sql = sql.replace(/LIMIT\s+(\d+)\s+OFFSET\s+(\d+)/gi, (match, limit, offset) => {
103
- return `AND ROWNUM BETWEEN ${parseInt(offset) + 1} AND ${parseInt(offset) + parseInt(limit)}`;
104
- });
105
- sql = sql.replace(/NOW\(\)/gi, 'SYSDATE');
106
- sql = sql.replace(/CURRENT_DATE/gi, 'SYSDATE');
107
- return sql;
108
- }
109
-
110
- /**
111
- * Override getDatatables untuk Oracle dengan pagination yang tepat
112
- * Paritas fungsional dengan PostgreSQL getDatatables
113
- */
114
- async getDatatables(options) {
115
- try {
116
- // Check cache first
117
- const cachedResult = await this.getCachedDatatables(options);
118
- if (cachedResult) return cachedResult;
119
-
120
- const {
121
- searchValue = '',
122
- searchBy = 'all',
123
- perPage = 10,
124
- start = 0,
125
- sort_columns = [],
126
- filters = {},
127
- advancedFilters = []
128
- } = options;
129
-
130
- // Resolve sort columns dengan prioritas: sort_columns > order[0][column] > default
131
- let resolvedSortColumns = sort_columns;
132
-
133
- // Fallback: cek format DataTables bawaan (order[0][column] dan order[0][dir])
134
- if ((!resolvedSortColumns || resolvedSortColumns.length === 0) &&
135
- options['order[0][column]'] !== undefined && options['order[0][dir]'] !== undefined) {
136
- const columnIndex = parseInt(options['order[0][column]']);
137
- const direction = options['order[0][dir]'];
138
-
139
- if (columnIndex >= 0 && columnIndex < this.validFields.length) {
140
- resolvedSortColumns = [{ column: this.validFields[columnIndex], direction: direction.toUpperCase() }];
141
- }
142
- }
143
-
144
- const orderClause = this.buildSortColumnsClause(resolvedSortColumns);
145
-
146
- const baseQuery = this.getListQuery(options);
147
-
148
- // Build WHERE clause (parameterized)
149
- const searchResult = this.buildWhereClause(searchValue, searchBy);
150
- let whereClauseSql = searchResult.sql;
151
- let whereParams = [...searchResult.params];
152
-
153
- // Build filter clause
154
- const filterClause = this.buildObjectFilterClause(filters);
155
- if (filterClause) {
156
- if (whereClauseSql) {
157
- whereClauseSql = `${whereClauseSql} AND ${filterClause}`;
158
- } else {
159
- whereClauseSql = `WHERE ${filterClause}`;
160
- }
161
- }
162
-
163
- // Support WHERE conditions dari request body
164
- if (options.where) {
165
- try {
166
- const complexResult = this.buildComplexWhereClause(options.where, whereParams, whereParams.length + 1);
167
- if (whereClauseSql) {
168
- whereClauseSql = `${whereClauseSql} AND ${complexResult.sql}`;
169
- } else {
170
- whereClauseSql = `WHERE ${complexResult.sql}`;
171
- }
172
- whereParams = complexResult.params;
173
- } catch (e) {
174
- const error = new Error('Invalid where conditions: ' + e.message);
175
- error.statusCode = 400;
176
- throw error;
177
- }
178
- }
179
-
180
- // Advanced filters support
181
- if (advancedFilters && advancedFilters.length > 0) {
182
- const advResult = this.buildAdvancedFilterCondition(advancedFilters);
183
- if (advResult.sql) {
184
- if (whereClauseSql) {
185
- whereClauseSql = `${whereClauseSql} AND ${advResult.sql}`;
186
- } else {
187
- whereClauseSql = `WHERE ${advResult.sql}`;
188
- }
189
- whereParams.push(...advResult.params);
190
- }
191
- }
192
-
193
- // Check if query needs subquery wrapping (CTE or JOIN)
194
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
195
- const hasJoin = /\b(inner|left|right|cross|full)\s+join\b/i.test(baseQuery) || /\bjoin\b/i.test(baseQuery);
196
- const needsSubquery = isCteQuery || hasJoin;
197
-
198
- // Count total records
199
- const countTotalQuery = needsSubquery ?
200
- `SELECT COUNT(*) as TOTAL FROM (${baseQuery}) base_query` :
201
- 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a';
202
- const countTotalResult = await db.executeQuery(countTotalQuery);
203
- const totalRecords = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].TOTAL) : 0;
204
-
205
- // Count filtered records
206
- let filteredRecords = totalRecords;
207
- if (whereClauseSql) {
208
- const countFilteredQuery = needsSubquery ?
209
- `SELECT COUNT(*) as TOTAL FROM (${baseQuery}) base_query ${whereClauseSql}` :
210
- 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a ' + whereClauseSql;
211
- const countFilteredResult = await db.executeQuery(countFilteredQuery, whereParams.length > 0 ? whereParams : undefined);
212
- filteredRecords = countFilteredResult && countFilteredResult[0] ? parseInt(countFilteredResult[0].TOTAL) : 0;
213
- }
214
-
215
- // Oracle pagination using ROWNUM
216
- const endRow = start + perPage;
217
-
218
- const query = needsSubquery ?
219
- `SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (${baseQuery}) base_query ${whereClauseSql || ''} ${orderClause}) base_query WHERE ROWNUM <= ${endRow}) WHERE rnum > ${start}` :
220
- `SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (${baseQuery} ${whereClauseSql || ''} ${orderClause}) a WHERE ROWNUM <= ${endRow}) WHERE rnum > ${start}`;
221
-
222
- console.log('Final Query:', query);
223
- console.log('Query Parameters:', whereParams.length > 0 ? whereParams : []);
224
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
225
-
226
- // Format data: hapus RNUM, normalize ke lowercase, tambahkan ROWNUMERATOR
227
- const data = rawData ? rawData.map((row, index) => {
228
- const { RNUM, rnum, ...cleanRow } = row;
229
- const formatted = this.formatResponseData(cleanRow);
230
- return {
231
- ...formatted,
232
- ROWNUMERATOR: start + index + 1
233
- };
234
- }) : [];
235
-
236
- const result = {
237
- draw: parseInt(options.draw || '1', 10),
238
- recordsTotal: totalRecords,
239
- recordsFiltered: filteredRecords,
240
- data: data
241
- };
242
-
243
- // Cache result
244
- await this.setCachedDatatables(options, result);
245
-
246
- return result;
247
- } catch (error) {
248
- console.error('Error in getDatatables:', error);
249
- throw error;
250
- }
251
- }
252
-
253
- /**
254
- * Build WHERE clause untuk search (parameterized query)
255
- * @returns {Object} { sql: string, params: array }
256
- */
257
- buildWhereClause(searchValue, searchBy) {
258
- if (!searchValue || searchValue === '') {
259
- return { sql: '', params: [] };
260
- }
261
-
262
- const params = [];
263
- let paramIndex = 1;
264
- const searchPattern = `%${searchValue}%`;
265
-
266
- if (searchBy === 'all') {
267
- const searchableFields = this.datatablesWhere.filter(field => field !== 'all');
268
- if (searchableFields.length > 0) {
269
- const conditions = searchableFields.map(field => {
270
- params.push(searchPattern);
271
- return `UPPER(${field}) LIKE UPPER(:${paramIndex++})`;
272
- });
273
- return { sql: `WHERE (${conditions.join(' OR ')})`, params };
274
- }
275
- } else if (this.validFields.includes(searchBy)) {
276
- params.push(searchPattern);
277
- return { sql: `WHERE UPPER(${searchBy}) LIKE UPPER(:1)`, params };
278
- }
279
-
280
- return { sql: '', params: [] };
281
- }
282
-
283
- /**
284
- * Build filter clause dari object filters
285
- * @param {Object} filters - Filter object {column: value}
286
- * @returns {string} Filter conditions SQL (tanpa WHERE prefix)
287
- */
288
- buildObjectFilterClause(filters) {
289
- if (!filters || typeof filters !== 'object' || Object.keys(filters).length === 0) {
290
- return '';
291
- }
292
-
293
- const conditions = [];
294
- for (const [column, value] of Object.entries(filters)) {
295
- if (!this.validFields.includes(column)) continue;
296
- if (value === null || value === undefined || value === '' || value === 'all' || value === '-') continue;
297
-
298
- const escapedValue = value.toString().replace(/'/g, "''");
299
- conditions.push(`${column} = '${escapedValue}'`);
300
- }
301
-
302
- return conditions.length > 0 ? conditions.join(' AND ') : '';
303
- }
304
-
305
- /**
306
- * Get list data dengan pagination untuk Oracle
307
- */
308
- async getList(options) {
309
- try {
310
- // Check cache first
311
- const cachedResult = await this.getCachedList(options);
312
- if (cachedResult) {
313
- const { page: p = null, perPage: pp = 10, searchValue: sv = '', sort_columns: sc = [], where: w = null } = options;
314
- const scInfo = sc && sc.length > 0 ? sc.map(s => `${s.column}:${s.direction}`).join(',') : 'default';
315
- console.log(`[Cache] HIT for list - page:${p}, perPage:${pp}, sort:${scInfo}, search:${sv || 'none'}${w ? ', where:yes' : ''}`);
316
- return cachedResult;
317
- }
318
-
319
- const {
320
- page = null,
321
- perPage = 10,
322
- searchValue = '',
323
- searchBy = 'all',
324
- sort_columns = [],
325
- where = null,
326
- select = null,
327
- limit = 1000
328
- } = options;
329
-
330
- const paginate = page !== null;
331
- const scInfo = sort_columns && sort_columns.length > 0 ? sort_columns.map(s => `${s.column}:${s.direction}`).join(',') : 'default';
332
- const cacheInfo = `page:${page}, perPage:${perPage}, sort:${scInfo}, search:${searchValue || 'none'}${where ? ', where:yes' : ''}`;
333
-
334
- console.log(`[Cache] MISS for list - ${cacheInfo}`);
335
-
336
- // 1. Mendapatkan query dasar
337
- let baseQuery;
338
- if (select && Array.isArray(select) && select.length > 0) {
339
- const selectedValidColumns = select.filter(col => this.validFields.includes(col));
340
- if (selectedValidColumns.length > 0) {
341
- baseQuery = 'SELECT ' + selectedValidColumns.join(', ') + ' FROM ' + this.getTableSource('read') + ' a';
342
- } else {
343
- baseQuery = 'SELECT * FROM ' + this.getTableSource('read') + ' a';
344
- }
345
- } else {
346
- baseQuery = this.getReadQuery(options);
347
- }
348
-
349
- // Deteksi apakah query mengandung JOIN atau CTE (perlu subquery wrapping)
350
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
351
- const hasJoin = /\b(inner|left|right|cross|full)\s+join\b/i.test(baseQuery) || /\bjoin\b/i.test(baseQuery);
352
- const needsSubquery = isCteQuery || hasJoin;
353
-
354
- const searchResult = this.buildWhereClause(searchValue, searchBy);
355
- let whereClauseSql = searchResult.sql;
356
- let whereParams = [...searchResult.params];
357
- const orderClause = this.buildSortColumnsClause(sort_columns);
358
-
359
- // Support WHERE conditions dari request body
360
- if (where) {
361
- try {
362
- const complexResult = this.buildComplexWhereClause(where, whereParams, whereParams.length + 1);
363
- if (whereClauseSql) {
364
- whereClauseSql = `${whereClauseSql} AND ${complexResult.sql}`;
365
- } else {
366
- whereClauseSql = `WHERE ${complexResult.sql}`;
367
- }
368
- whereParams = complexResult.params;
369
- } catch (e) {
370
- const error = new Error('Invalid where conditions: ' + e.message);
371
- error.statusCode = 400;
372
- throw error;
373
- }
374
- }
375
-
376
- // Count total unfiltered records
377
- const countTotalQuery = needsSubquery
378
- ? 'SELECT COUNT(*) as TOTAL FROM (' + baseQuery + ') base_query'
379
- : 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a';
380
- const countTotalResult = await db.executeQuery(countTotalQuery);
381
- const totalUnfiltered = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].TOTAL) : 0;
382
-
383
- // Count filtered records
384
- let totalRecords = totalUnfiltered;
385
- if (whereClauseSql) {
386
- const countQuery = needsSubquery
387
- ? 'SELECT COUNT(*) as TOTAL FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '')
388
- : 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a ' + (whereClauseSql || '');
389
- const countResult = await db.executeQuery(countQuery, whereParams.length > 0 ? whereParams : undefined);
390
- totalRecords = countResult && countResult[0] ? parseInt(countResult[0].TOTAL) : 0;
391
- }
392
-
393
- // Build query berdasarkan mode paginasi (subquery wrapping untuk JOIN/CTE)
394
- let query;
395
- if (paginate) {
396
- // Oracle pagination using ROWNUM
397
- const offset = (page - 1) * perPage;
398
- const endRow = offset + perPage;
399
- query = needsSubquery
400
- ? 'SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '') + orderClause + ') base_query WHERE ROWNUM <= ' + endRow + ') WHERE rnum > ' + offset
401
- : 'SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (' + baseQuery + ' ' + (whereClauseSql || '') + orderClause + ') a WHERE ROWNUM <= ' + endRow + ') WHERE rnum > ' + offset;
402
- } else {
403
- // Non-paginasi dengan safety limit
404
- query = needsSubquery
405
- ? 'SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '') + orderClause + ') base_query WHERE ROWNUM <= ' + limit + ')'
406
- : 'SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (' + baseQuery + ' ' + (whereClauseSql || '') + orderClause + ') a WHERE ROWNUM <= ' + limit + ')';
407
- }
408
-
409
- console.log('List SQL Query:', query);
410
- console.log('List Query Parameters:', whereParams);
411
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
412
-
413
- const data = rawData ? rawData.map((row) => {
414
- const { RNUM, rnum, ...cleanRow } = row;
415
- return this.formatResponseData(cleanRow);
416
- }) : [];
417
-
418
- const result = {
419
- success: true,
420
- data: data
421
- };
422
-
423
- if (paginate) {
424
- const totalPages = Math.ceil(totalRecords / perPage);
425
- result.pagination = {
426
- current_page: page,
427
- per_page: perPage,
428
- total_records: totalRecords,
429
- total_pages: totalPages,
430
- has_next: page < totalPages,
431
- has_previous: page > 1
432
- };
433
- }
434
-
435
- // Cache result
436
- await this.setCachedList(options, result);
437
- console.log(`[Cache] SET for list - ${cacheInfo}`);
438
-
439
- return result;
440
- } catch (error) {
441
- console.error('Error in getList:', error);
442
- throw error;
443
- }
444
- }
445
-
446
- /**
447
- * Override getLookupData untuk Oracle (dynamic search)
448
- */
449
- async getLookupData(search) {
450
- try {
451
- const query = `SELECT * FROM (SELECT id, item_name FROM ${this.getTableSource('read')} WHERE UPPER(item_name) LIKE UPPER(:1) ORDER BY item_name) WHERE ROWNUM <= 100`;
452
- const params = [`%${search || ''}%`];
453
- const data = await db.executeQuery(query, params);
454
-
455
- const result = data.map(item => ({
456
- id: item.ID,
457
- text: item.ITEM_NAME
458
- }));
459
-
460
- return result;
461
- } catch (error) {
462
- console.error('Error in Oracle getLookupData:', error);
463
- throw error;
464
- }
465
- }
466
-
467
- /**
468
- * Dynamic lookup dengan extra filters untuk Oracle
469
- */
470
- async getLookupDataDynamic(search, extraFilters = {}) {
471
- try {
472
- let params = [];
473
- let paramIndex = 1;
474
- let whereConditions = [];
475
-
476
- if (search) {
477
- whereConditions.push(`UPPER(item_name) LIKE UPPER(:${paramIndex})`);
478
- params.push(`%${search}%`);
479
- paramIndex++;
480
- }
481
-
482
- // Add extra filters
483
- if (extraFilters && Object.keys(extraFilters).length > 0) {
484
- for (const [key, value] of Object.entries(extraFilters)) {
485
- if (this.validFields.includes(key) && value !== null && value !== undefined) {
486
- whereConditions.push(`${key} = :${paramIndex}`);
487
- params.push(value);
488
- paramIndex++;
489
- }
490
- }
491
- }
492
-
493
- const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
494
-
495
- const query = `SELECT * FROM (SELECT id, item_name FROM ${this.getTableSource('read')} ${whereClause} ORDER BY item_name) WHERE ROWNUM <= 100`;
496
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
497
-
498
- return data.map(item => ({
499
- id: item.ID,
500
- text: item.ITEM_NAME
501
- }));
502
- } catch (error) {
503
- console.error('Error in Oracle getLookupDataDynamic:', error);
504
- throw error;
505
- }
506
- }
507
-
508
- /**
509
- * Override getStaticLookupData untuk Oracle
510
- */
511
- async getStaticLookupData(selectedTag) {
512
- try {
513
- // Check cache first - cache tanpa selectedTag karena data sama
514
- const cacheOptions = { type: 'static' };
515
- const cachedResult = await this.getCachedLookup(cacheOptions, 'static');
516
- if (cachedResult) {
517
- // Apply selectedTag to cached result
518
- return cachedResult.map(item => {
519
- if (item.id === selectedTag) {
520
- return { ...item, selected: 'true' };
521
- }
522
- return { id: item.id, text: item.text };
523
- });
524
- }
525
-
526
- const query = `SELECT * FROM (SELECT id, item_name FROM ${this.getTableSource('read')} ORDER BY item_name) WHERE ROWNUM <= 1000`;
527
- const data = await db.executeQuery(query);
528
-
529
- // Cache result tanpa selected flag
530
- const cacheData = data.map(item => ({
531
- id: item.ID,
532
- text: item.ITEM_NAME
533
- }));
534
- await this.setCachedLookup(cacheOptions, cacheData, 'static');
535
-
536
- // Return dengan selected flag
537
- return data.map(item => {
538
- const row = {
539
- id: item.ID,
540
- text: item.ITEM_NAME
541
- };
542
- if (item.ID === selectedTag) {
543
- row.selected = 'true';
544
- }
545
- return row;
546
- });
547
- } catch (error) {
548
- console.error('Error in Oracle getStaticLookupData:', error);
549
- throw error;
550
- }
551
- }
552
-
553
- /**
554
- * Lookup dengan advanced filter support untuk Oracle
555
- */
556
- async getLookupDataWithFilter(options) {
557
- try {
558
- // Check cache first
559
- const cacheOptions = { ...options, type: 'filter' };
560
- const cachedResult = await this.getCachedLookup(cacheOptions, 'filter');
561
- if (cachedResult) return cachedResult;
562
-
563
- const selectColumns = options.select || ['id', 'item_name'];
564
- let params = [];
565
- let paramIndex = 1;
566
-
567
- // Validasi text fields
568
- const validTextFields = this.validFields.filter(field =>
569
- field.includes('name') || field.includes('nama') ||
570
- field.includes('code') || field.includes('kode') ||
571
- field.includes('text') || field.includes('title')
572
- );
573
-
574
- let selectClause = 'id';
575
- let textField = 'item_name';
576
- let aliasField = null;
577
-
578
- // Proses setiap column dalam select
579
- for (const column of selectColumns) {
580
- if (column.toLowerCase() === 'id') {
581
- continue; // primary key sudah ada
582
- }
583
-
584
- // Check jika ada SQL expression dengan alias (menggunakan AS)
585
- const aliasRegex = new RegExp('(.+)\\s+as\\s+(\\w+)$', 'i');
586
- const aliasMatch = column.match(aliasRegex);
587
- if (aliasMatch) {
588
- const expression = aliasMatch[1].trim();
589
- const alias = aliasMatch[2].trim();
590
- selectClause += `, ${expression} AS ${alias}`;
591
- textField = alias;
592
- aliasField = alias;
593
- break;
594
- }
595
-
596
- // Check jika simple field name
597
- if (this.validFields.includes(column) || validTextFields.includes(column)) {
598
- selectClause += `, ${column}`;
599
- textField = column;
600
- break;
601
- }
602
-
603
- // Computed column
604
- selectClause += `, ${column}`;
605
- textField = column;
606
- }
607
-
608
- let query = `SELECT ${selectClause} FROM ${this.getTableSource('read')} `;
609
-
610
- // Build WHERE clause jika ada
611
- if ((options.where && Array.isArray(options.where) && options.where.length > 0) ||
612
- (options.where && options.where.conditions && Array.isArray(options.where.conditions) && options.where.conditions.length > 0)) {
613
- try {
614
- const whereResult = this.buildComplexWhereClause(options.where, params, paramIndex);
615
- query += `WHERE ${whereResult.sql} `;
616
- params = whereResult.params;
617
- } catch (e) {
618
- const error = new Error('Invalid where conditions: ' + e.message);
619
- error.statusCode = 400;
620
- throw error;
621
- }
622
- }
623
-
624
- // Handle sort_columns
625
- if (options.sort_columns && Array.isArray(options.sort_columns) && options.sort_columns.length > 0) {
626
- const orderParts = options.sort_columns.map(item => {
627
- const column = item.column;
628
- const direction = (item.direction || 'ASC').toUpperCase();
629
- if (!column) return null;
630
- if (!this.validFields.includes(column)) return null;
631
- if (direction !== 'ASC' && direction !== 'DESC') return null;
632
- return `${column} ${direction}`;
633
- }).filter(Boolean);
634
-
635
- if (orderParts.length === 0) {
636
- const error = new Error('No valid sort columns provided');
637
- error.statusCode = 400;
638
- throw error;
639
- }
640
- query += `ORDER BY ${orderParts.join(', ')}`;
641
- } else {
642
- query += `ORDER BY ${aliasField || textField}`;
643
- }
644
-
645
- console.log('Oracle Lookup Filter Query:', query);
646
- console.log('Parameters:', params);
647
-
648
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
649
-
650
- const textFieldUpper = (aliasField || textField).toUpperCase();
651
- const result = data.map(item => ({
652
- id: item.ID,
653
- text: item[textFieldUpper] || item.ITEM_NAME || ''
654
- }));
655
-
656
- // Cache the result
657
- await this.setCachedLookup(cacheOptions, result, 'filter');
658
-
659
- return result;
660
- } catch (error) {
661
- console.error('Error in getLookupDataWithFilter:', error);
662
- throw error;
663
- }
664
- }
665
-
666
- /**
667
- * Build advanced filter conditions untuk Oracle
668
- * @param {Array} filters - Array of {column, type, value, value2}
669
- * @returns {Object} {sql, params}
670
- */
671
- buildAdvancedFilterCondition(filters) {
672
- if (!filters || !Array.isArray(filters) || filters.length === 0) {
673
- return { sql: '', params: [] };
674
- }
675
-
676
- const conditions = [];
677
- const params = [];
678
- let paramIndex = 100; // Start dari 100 agar tidak bentrok dengan param lain
679
-
680
- for (const filter of filters) {
681
- const { column, type, value, value2 } = filter;
682
-
683
- if (!column || !this.validFields.includes(column)) continue;
684
-
685
- switch (type) {
686
- case 'equals':
687
- conditions.push(`${column} = :${paramIndex}`);
688
- params.push(value);
689
- paramIndex++;
690
- break;
691
- case 'not_equals':
692
- conditions.push(`${column} <> :${paramIndex}`);
693
- params.push(value);
694
- paramIndex++;
695
- break;
696
- case 'contains':
697
- case 'like':
698
- conditions.push(`UPPER(${column}) LIKE UPPER(:${paramIndex})`);
699
- params.push(`%${value}%`);
700
- paramIndex++;
701
- break;
702
- case 'not_contains':
703
- case 'not_like':
704
- conditions.push(`UPPER(${column}) NOT LIKE UPPER(:${paramIndex})`);
705
- params.push(`%${value}%`);
706
- paramIndex++;
707
- break;
708
- case 'starts_with':
709
- conditions.push(`UPPER(${column}) LIKE UPPER(:${paramIndex})`);
710
- params.push(`${value}%`);
711
- paramIndex++;
712
- break;
713
- case 'ends_with':
714
- conditions.push(`UPPER(${column}) LIKE UPPER(:${paramIndex})`);
715
- params.push(`%${value}`);
716
- paramIndex++;
717
- break;
718
- case 'greater_than':
719
- conditions.push(`${column} > :${paramIndex}`);
720
- params.push(value);
721
- paramIndex++;
722
- break;
723
- case 'less_than':
724
- conditions.push(`${column} < :${paramIndex}`);
725
- params.push(value);
726
- paramIndex++;
727
- break;
728
- case 'greater_equal':
729
- conditions.push(`${column} >= :${paramIndex}`);
730
- params.push(value);
731
- paramIndex++;
732
- break;
733
- case 'less_equal':
734
- conditions.push(`${column} <= :${paramIndex}`);
735
- params.push(value);
736
- paramIndex++;
737
- break;
738
- case 'between':
739
- conditions.push(`${column} BETWEEN :${paramIndex} AND :${paramIndex + 1}`);
740
- params.push(value, value2);
741
- paramIndex += 2;
742
- break;
743
- case 'in':
744
- if (Array.isArray(value)) {
745
- const inPlaceholders = value.map((_, i) => `:${paramIndex + i}`).join(', ');
746
- conditions.push(`${column} IN (${inPlaceholders})`);
747
- params.push(...value);
748
- paramIndex += value.length;
749
- }
750
- break;
751
- case 'not_in':
752
- if (Array.isArray(value)) {
753
- const notInPlaceholders = value.map((_, i) => `:${paramIndex + i}`).join(', ');
754
- conditions.push(`${column} NOT IN (${notInPlaceholders})`);
755
- params.push(...value);
756
- paramIndex += value.length;
757
- }
758
- break;
759
- case 'is_null':
760
- conditions.push(`${column} IS NULL`);
761
- break;
762
- case 'is_not_null':
763
- conditions.push(`${column} IS NOT NULL`);
764
- break;
765
- case 'date_equals':
766
- conditions.push(`TRUNC(${column}) = TO_DATE(:${paramIndex}, 'YYYY-MM-DD')`);
767
- params.push(value);
768
- paramIndex++;
769
- break;
770
- case 'date_between':
771
- conditions.push(`TRUNC(${column}) BETWEEN TO_DATE(:${paramIndex}, 'YYYY-MM-DD') AND TO_DATE(:${paramIndex + 1}, 'YYYY-MM-DD')`);
772
- params.push(value, value2);
773
- paramIndex += 2;
774
- break;
775
- case 'date_after':
776
- conditions.push(`TRUNC(${column}) > TO_DATE(:${paramIndex}, 'YYYY-MM-DD')`);
777
- params.push(value);
778
- paramIndex++;
779
- break;
780
- case 'date_before':
781
- conditions.push(`TRUNC(${column}) < TO_DATE(:${paramIndex}, 'YYYY-MM-DD')`);
782
- params.push(value);
783
- paramIndex++;
784
- break;
785
- default:
786
- break;
787
- }
788
- }
789
-
790
- if (conditions.length === 0) {
791
- return { sql: '', params: [] };
792
- }
793
-
794
- return { sql: conditions.join(' AND '), params };
795
- }
796
-
797
- /**
798
- * Escape value untuk Oracle SQL (sanitization)
799
- */
800
- escapeValue(value) {
801
- if (value === null || value === undefined) return null;
802
- if (typeof value === 'number') return value;
803
- return String(value).replace(/'/g, "''");
804
- }
805
-
806
- /**
807
- * Validasi data sebelum insert/update
808
- */
809
- async validateData(data, operation = 'insert') {
810
- const result = {
811
- isValid: true,
812
- errors: [],
813
- warnings: [],
814
- sanitizedData: {}
815
- };
816
-
817
- try {
818
- const hasFieldValidation = this.validationConfig && Object.keys(this.validationConfig).length > 0;
819
-
820
- if (hasFieldValidation) {
821
- // Loop semua field yang ada di validationConfig
822
- for (const fieldName in this.validationConfig) {
823
- let value = data[fieldName];
824
- const config = this.validationConfig[fieldName];
825
- const constraints = config.constraints || {};
826
-
827
- // Auto-generate value jika autoGenerate dan nilai kosong.
828
- // String dan uuid diperlakukan sama: UUID v7 via uuid package
829
- // (konsisten lintas dialect; cocok dengan konvensi payload category.json
830
- // yang memakai type: "string" dengan constraint autoGenerate + primaryKey).
831
- if (operation === 'insert' && constraints.autoGenerate && (!value || value === '')) {
832
- if (config.type === 'uuid' || config.type === 'string') {
833
- value = require('uuid').v7();
834
- data[fieldName] = value;
835
- }
836
- }
837
-
838
- // Skip validation jika value kosong dan tidak required
839
- if (value === undefined || value === null || value === '') {
840
- if (constraints.required) {
841
- // Skip: autoGenerate atau primaryKey di insert
842
- if (operation === 'insert' && (constraints.autoGenerate || constraints.primaryKey)) {
843
- // OK — akan di-generate otomatis
844
- }
845
- // Skip: update partial — field tidak dikirim berarti tidak diubah
846
- else if (operation === 'update' && value === undefined) {
847
- // OK — field tidak sedang di-update
848
- }
849
- else {
850
- const message = constraints.requiredMessage || `Field '${fieldName}' is required`;
851
- result.errors.push(message);
852
- result.isValid = false;
853
- }
854
- }
855
- continue;
856
- }
857
-
858
- // String field: hash constraint support
859
- if (config.type === 'string' && typeof value === 'string') {
860
- let sanitized = value;
861
- const fieldErrors = [];
862
- const isHashField = constraints.hash === 'bcrypt';
863
-
864
- // Trim
865
- if (constraints.trim) {
866
- sanitized = sanitized.trim();
867
- }
868
-
869
- // Case transformation (skip jika hash field)
870
- if (!isHashField) {
871
- if (constraints.lowercase) {
872
- sanitized = sanitized.toLowerCase();
873
- } else if (constraints.uppercase) {
874
- sanitized = sanitized.toUpperCase();
875
- }
876
- }
877
-
878
- // Length validation (validasi plaintext sebelum hash)
879
- if (constraints.minLength && sanitized.length < constraints.minLength) {
880
- fieldErrors.push(constraints.minLengthMessage || `Field '${fieldName}' must be at least ${constraints.minLength} characters`);
881
- }
882
- if (constraints.maxLength && !isHashField && sanitized.length > constraints.maxLength) {
883
- fieldErrors.push(constraints.maxLengthMessage || `Field '${fieldName}' must not exceed ${constraints.maxLength} characters`);
884
- }
885
-
886
- // Pattern validation
887
- if (constraints.pattern) {
888
- const regex = new RegExp(constraints.pattern);
889
- if (!regex.test(sanitized)) {
890
- fieldErrors.push(constraints.patternMessage || `Field '${fieldName}' does not match required pattern`);
891
- }
892
- }
893
-
894
- // Format validation
895
- if (constraints.format === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(sanitized)) {
896
- fieldErrors.push(constraints.formatMessage || `Field '${fieldName}' has invalid email format`);
897
- }
898
-
899
- if (fieldErrors.length > 0) {
900
- result.isValid = false;
901
- result.errors.push(...fieldErrors);
902
- }
903
-
904
- // Hash transformation (setelah semua validation pass)
905
- if (isHashField && fieldErrors.length === 0) {
906
- const bcrypt = require('bcrypt');
907
- const cost = constraints.hashCost || 10;
908
- sanitized = await bcrypt.hash(sanitized, cost);
909
- }
910
-
911
- result.sanitizedData[fieldName] = sanitized;
912
- } else {
913
- // Non-string field: basic sanitization
914
- result.sanitizedData[fieldName] = value;
915
- }
916
- }
917
-
918
- // Validate field yang tidak ada di validationConfig (backward compatibility)
919
- for (const field of this.validFields) {
920
- if (!this.validationConfig[field] && data[field] !== undefined && data[field] !== null) {
921
- if (typeof data[field] === 'string') {
922
- result.sanitizedData[field] = data[field].trim().replace(/\0/g, '').substring(0, 4000);
923
- } else {
924
- result.sanitizedData[field] = data[field];
925
- }
926
- }
927
- }
928
- } else {
929
- // Fallback: Tidak ada fieldValidation - gunakan generic sanitization
930
- for (const field of this.validFields) {
931
- const value = data[field];
932
- if (value !== undefined && value !== null) {
933
- if (typeof value === 'string') {
934
- result.sanitizedData[field] = value.trim().replace(/\0/g, '').substring(0, 4000);
935
- } else {
936
- result.sanitizedData[field] = value;
937
- }
938
- }
939
- }
940
- }
941
- } catch (error) {
942
- result.errors.push(`Validation error: ${error.message}`);
943
- result.isValid = false;
944
- }
945
-
946
- return result;
947
- }
948
-
949
- /**
950
- * Get field mapping information
951
- */
952
- getFieldMapping() {
953
- return {
954
- allFields: this.validFields,
955
- primaryKey: this.primaryKey,
956
- textFields: this.validFields.filter(f => f.includes('name') || f.includes('nama') || f.includes('description')),
957
- dateFields: this.validFields.filter(f => f.includes('date') || f.includes('time')),
958
- numericFields: this.validFields.filter(f => f.includes('amount') || f.includes('price') || f.includes('count'))
959
- };
960
- }
961
-
962
- /**
963
- * Get Oracle connection info untuk health check
964
- */
965
- async getConnectionInfo() {
966
- try {
967
- const result = await db.executeQuery('SELECT 1 as TEST_CON FROM DUAL');
968
- if (result && result.length > 0) {
969
- return { connected: true, database: 'Oracle', retrievedAt: new Date().toISOString() };
970
- }
971
- return null;
972
- } catch (error) {
973
- return { connected: false, error: error.message, checkedAt: new Date().toISOString() };
974
- }
975
- }
976
-
977
- /**
978
- * Format response data untuk Oracle (field names uppercase)
979
- */
980
- formatResponseData(data) {
981
- if (!data) return null;
982
-
983
- const formatted = {};
984
- formatted.item_id = data.ITEM_ID;
985
- formatted.item_code = data.ITEM_CODE;
986
- formatted.item_name = data.ITEM_NAME;
987
- formatted.description = data.DESCRIPTION;
988
- formatted.uom = data.UOM;
989
- formatted.unit_price = data.UNIT_PRICE;
990
- formatted.weight = data.WEIGHT;
991
- formatted.is_active = data.IS_ACTIVE;
992
- formatted.created_at = data.CREATED_AT;
993
- formatted.created_by = data.CREATED_BY;
994
- formatted.updated_at = data.UPDATED_AT;
995
- formatted.updated_by = data.UPDATED_BY;
996
-
997
- return formatted;
998
- }
999
-
1000
- }
1001
-
1002
- module.exports = new ItemModel();