@restforgejs/platform 4.1.1 → 4.3.1

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