@restforgejs/platform 4.2.8 → 4.3.2

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