@restforgejs/platform 4.2.8 → 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 (324) 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/templates/dashboard-catalog.js +1 -437
  25. package/generators/lib/templates/db-connection-env.js +1 -212
  26. package/generators/lib/templates/dbschema-catalog.js +1 -489
  27. package/generators/lib/templates/field-validation-catalog.js +1 -531
  28. package/generators/lib/templates/mysql-template.js +1 -3863
  29. package/generators/lib/templates/oracle-template.js +1 -3915
  30. package/generators/lib/templates/postgres-template.js +1 -5838
  31. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  32. package/generators/lib/templates/sqlite-template.js +1 -3440
  33. package/generators/lib/utils/env-manager.js +6 -0
  34. package/generators/lib/utils/path-validator.js +71 -0
  35. package/generators/lib/validators/payload-validator.js +1 -2
  36. package/integrity-manifest.json +28 -10
  37. package/package.json +11 -3
  38. package/scripts/verify-integrity.js +1 -1
  39. package/server.js +1 -1
  40. package/src/components/handlers/adjust_handler.js +1 -1
  41. package/src/components/handlers/audit_handler.js +1 -1
  42. package/src/components/handlers/delete_handler.js +1 -1
  43. package/src/components/handlers/export_handler.js +1 -1
  44. package/src/components/handlers/import_handler.js +1 -1
  45. package/src/components/handlers/insert_handler.js +1 -1
  46. package/src/components/handlers/update_handler.js +1 -1
  47. package/src/components/handlers/upload_handler.js +1 -1
  48. package/src/components/handlers/workflow_handler.js +1 -1
  49. package/src/components/integrations/webhook.js +1 -1
  50. package/src/consumers/baseConsumer.js +1 -1
  51. package/src/consumers/declarativeMapper.js +1 -1
  52. package/src/consumers/handlers/apiHandler.js +1 -1
  53. package/src/consumers/handlers/consoleHandler.js +1 -1
  54. package/src/consumers/handlers/databaseHandler.js +1 -1
  55. package/src/consumers/handlers/index.js +1 -1
  56. package/src/consumers/handlers/kafkaHandler.js +1 -1
  57. package/src/consumers/index.js +1 -1
  58. package/src/consumers/messageTransformer.js +1 -1
  59. package/src/consumers/validator.js +1 -1
  60. package/src/core/db/dialect/base-dialect.js +1 -1
  61. package/src/core/db/dialect/index.js +1 -1
  62. package/src/core/db/dialect/mysql-dialect.js +1 -1
  63. package/src/core/db/dialect/oracle-dialect.js +1 -1
  64. package/src/core/db/dialect/postgres-dialect.js +1 -1
  65. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  66. package/src/core/db/flatten-helper.js +1 -1
  67. package/src/core/db/query-builder-error.js +1 -1
  68. package/src/core/db/query-builder.js +1 -1
  69. package/src/core/db/relation-helper.js +1 -1
  70. package/src/core/handlers/delete_handler.js +1 -1
  71. package/src/core/handlers/insert_handler.js +1 -1
  72. package/src/core/handlers/update_handler.js +1 -1
  73. package/src/core/models/base-model.js +1 -1
  74. package/src/core/utils/cache-manager.js +1 -1
  75. package/src/core/utils/component-engine.js +1 -1
  76. package/src/core/utils/context-builder.js +1 -1
  77. package/src/core/utils/datetime-formatter.js +1 -1
  78. package/src/core/utils/datetime-parser.js +1 -1
  79. package/src/core/utils/db.js +1 -1
  80. package/src/core/utils/logger.js +1 -1
  81. package/src/core/utils/payload-loader.js +1 -1
  82. package/src/core/utils/security-checks.js +1 -1
  83. package/src/middleware/body-options.js +1 -1
  84. package/src/middleware/cors.js +1 -1
  85. package/src/middleware/idempotency.js +1 -1
  86. package/src/middleware/rate-limiter.js +1 -1
  87. package/src/middleware/request-logger.js +1 -1
  88. package/src/middleware/security-headers.js +1 -1
  89. package/src/models/base-model-mysql.js +1 -1
  90. package/src/models/base-model-oracle.js +1 -1
  91. package/src/models/base-model-sqlite.js +1 -1
  92. package/src/models/base-model.js +1 -1
  93. package/src/pro/caching/redis-client.js +1 -1
  94. package/src/pro/caching/redis-helper.js +1 -1
  95. package/src/pro/consumers/baseConsumer.js +1 -1
  96. package/src/pro/consumers/declarativeMapper.js +1 -1
  97. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  98. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  99. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  100. package/src/pro/consumers/handlers/index.js +1 -1
  101. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  102. package/src/pro/consumers/index.js +1 -1
  103. package/src/pro/consumers/messageTransformer.js +1 -1
  104. package/src/pro/consumers/validator.js +1 -1
  105. package/src/pro/database/base-model-mysql.js +1 -1
  106. package/src/pro/database/base-model-oracle.js +1 -1
  107. package/src/pro/database/base-model-sqlite.js +1 -1
  108. package/src/pro/database/db-mysql.js +1 -1
  109. package/src/pro/database/db-oracle.js +1 -1
  110. package/src/pro/database/db-sqlite.js +1 -1
  111. package/src/pro/excel/excel-generator.js +1 -1
  112. package/src/pro/excel/excel-parser.js +1 -1
  113. package/src/pro/excel/export-service.js +1 -1
  114. package/src/pro/excel/export_handler.js +1 -1
  115. package/src/pro/excel/import-service.js +1 -1
  116. package/src/pro/excel/import-validator.js +1 -1
  117. package/src/pro/excel/import_handler.js +1 -1
  118. package/src/pro/excel/upsert-builder.js +1 -1
  119. package/src/pro/idgen/idgen-routes.js +1 -1
  120. package/src/pro/integrations/lookup-resolver.js +1 -1
  121. package/src/pro/integrations/upload-handler-v2.js +1 -1
  122. package/src/pro/integrations/upload-handler.js +1 -1
  123. package/src/pro/integrations/webhook.js +1 -1
  124. package/src/pro/locking/lock-routes.js +1 -1
  125. package/src/pro/locking/resource-lock-manager.js +1 -1
  126. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  127. package/src/pro/messaging/kafkaService.js +1 -1
  128. package/src/pro/messaging/messagehubService.js +1 -1
  129. package/src/pro/messaging/rabbitmqService.js +1 -1
  130. package/src/pro/scheduler/job-manager.js +1 -1
  131. package/src/pro/scheduler/job-routes.js +1 -1
  132. package/src/pro/scheduler/job-validator.js +1 -1
  133. package/src/pro/storage/base-storage-provider.js +1 -1
  134. package/src/pro/storage/file-metadata-helper.js +1 -1
  135. package/src/pro/storage/index.js +1 -1
  136. package/src/pro/storage/local-storage-provider.js +1 -1
  137. package/src/pro/storage/s3-storage-provider.js +1 -1
  138. package/src/pro/storage/upload-cleanup-job.js +1 -1
  139. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  140. package/src/pro/storage/upload-pending-tracker.js +1 -1
  141. package/src/pro/websocket/broadcast-helper.js +1 -1
  142. package/src/pro/websocket/index.js +1 -1
  143. package/src/pro/websocket/livesync-server.js +1 -1
  144. package/src/pro/websocket/ws-broadcaster.js +1 -1
  145. package/src/services/export-service.js +1 -1
  146. package/src/services/import-service.js +1 -1
  147. package/src/services/kafkaConsumerService.js +1 -1
  148. package/src/services/kafkaService.js +1 -1
  149. package/src/services/messagehubService.js +1 -1
  150. package/src/services/rabbitmqService.js +1 -1
  151. package/src/utils/cache-invalidation-registry.js +1 -1
  152. package/src/utils/cache-manager.js +1 -1
  153. package/src/utils/component-engine.js +1 -1
  154. package/src/utils/config-extractor.js +1 -1
  155. package/src/utils/consumerLogger.js +1 -1
  156. package/src/utils/context-builder.js +1 -1
  157. package/src/utils/dashboard-helpers.js +1 -1
  158. package/src/utils/dateHelper.js +1 -1
  159. package/src/utils/datetime-formatter.js +1 -1
  160. package/src/utils/datetime-parser.js +1 -1
  161. package/src/utils/db-bootstrap.js +1 -1
  162. package/src/utils/db-mysql.js +1 -1
  163. package/src/utils/db-oracle.js +1 -1
  164. package/src/utils/db-sqlite.js +1 -1
  165. package/src/utils/db.js +1 -1
  166. package/src/utils/demo-generator.js +1 -1
  167. package/src/utils/excel-generator.js +1 -1
  168. package/src/utils/excel-parser.js +1 -1
  169. package/src/utils/file-watcher.js +1 -1
  170. package/src/utils/id-generator.js +1 -1
  171. package/src/utils/idempotency-manager.js +1 -1
  172. package/src/utils/import-validator.js +1 -1
  173. package/src/utils/license-client.js +1 -1
  174. package/src/utils/lock-manager.js +1 -1
  175. package/src/utils/logger.js +1 -1
  176. package/src/utils/lookup-resolver.js +1 -1
  177. package/src/utils/payload-loader.js +1 -1
  178. package/src/utils/processor-response.js +1 -1
  179. package/src/utils/rabbitmq.js +1 -1
  180. package/src/utils/redis-client.js +1 -1
  181. package/src/utils/redis-helper.js +1 -1
  182. package/src/utils/request-scope.js +1 -1
  183. package/src/utils/security-checks.js +1 -1
  184. package/src/utils/service-resolver.js +1 -1
  185. package/src/utils/shutdown-coordinator.js +1 -1
  186. package/src/utils/trusted-keys.js +1 -1
  187. package/src/utils/upload-handler.js +1 -1
  188. package/src/utils/upsert-builder.js +1 -1
  189. package/src/utils/workflow-hook-executor.js +1 -1
  190. package/generators/metadata/global.json +0 -58
  191. package/generators/metadata/test-mysql-workbench.json +0 -118
  192. package/generators/metadata/test-mysql.json +0 -56
  193. package/generators/metadata/test-oracle-workbench.json +0 -118
  194. package/generators/metadata/test-oracle.json +0 -56
  195. package/generators/metadata/test-pg-workbench.json +0 -118
  196. package/generators/metadata/test-pg.json +0 -56
  197. package/generators/scripts/obfuscate-source.js +0 -356
  198. package/generators/scripts/validate-catalog.js +0 -430
  199. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  200. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  201. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  202. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  203. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  204. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  205. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  206. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  207. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  208. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  209. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  210. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  211. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  212. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  213. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  214. package/generators/tests/integration-tahap4b/README.md +0 -145
  215. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  216. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  217. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  218. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  219. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  220. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  221. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  222. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  223. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  224. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  225. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  226. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  227. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  228. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  229. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  230. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  231. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  232. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  233. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  234. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  235. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  236. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  237. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  238. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  239. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  240. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  241. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  242. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  243. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  244. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  245. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  246. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  247. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  248. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  249. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  250. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  251. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  252. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  253. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  254. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  255. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  256. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  257. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  258. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  259. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  260. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  261. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  262. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  263. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  264. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  265. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  266. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  267. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  268. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  269. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  270. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  271. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  299. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  300. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  301. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  302. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  303. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  304. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  305. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  306. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  307. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  308. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  309. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  310. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  311. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  312. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  313. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  314. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  315. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  316. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  317. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  318. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  319. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  320. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  321. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  322. package/scripts/generate-integrity-manifest.js +0 -124
  323. package/scripts/snapshot-cli-contracts.js +0 -194
  324. 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();