@restforgejs/platform 4.1.1 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/SECURITY.md +83 -4
  2. package/bin/sdf-tools.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/dashboard/create.js +4 -1
  7. package/generators/cli/endpoint/create.js +43 -4
  8. package/generators/cli/key/generate.js +2 -1
  9. package/generators/cli/key/revoke.js +2 -1
  10. package/generators/cli/payload/diff.js +3 -2
  11. package/generators/cli/payload/generate.js +3 -2
  12. package/generators/cli/payload/sync.js +3 -2
  13. package/generators/cli/payload/validate.js +3 -2
  14. package/generators/cli/processor/create.js +14 -3
  15. package/generators/cli/project/delete.js +2 -1
  16. package/generators/cli/query/validate.js +3 -2
  17. package/generators/cli/schema/apply.js +526 -0
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +322 -0
  20. package/generators/cli/schema/generate-ddl.js +7 -10
  21. package/generators/cli/schema/init.js +95 -172
  22. package/generators/cli/schema/introspect.js +3 -2
  23. package/generators/cli/schema/list.js +3 -2
  24. package/generators/cli/schema/migrate.js +13 -18
  25. package/generators/cli/schema/models.js +8 -12
  26. package/generators/cli/schema/template.js +222 -0
  27. package/generators/cli/schema/validate.js +8 -12
  28. package/generators/cli-entry.js +17 -2
  29. package/generators/lib/dbschema-kit/apply-engine.js +582 -0
  30. package/generators/lib/dbschema-kit/diff-engine.js +703 -0
  31. package/generators/lib/dbschema-kit/diff-reporter.js +272 -0
  32. package/generators/lib/dbschema-kit/emitters/alter-table.js +275 -0
  33. package/generators/lib/migration/audit-table-runner.js +213 -215
  34. package/generators/lib/payload/endpoint-schema-validator.js +171 -0
  35. package/generators/lib/payload/payload-runner.js +137 -220
  36. package/generators/lib/payload/schema-diff.js +277 -0
  37. package/generators/lib/templates/dashboard-catalog.js +1 -437
  38. package/generators/lib/templates/db-connection-env.js +1 -212
  39. package/generators/lib/templates/dbschema-catalog.js +1 -489
  40. package/generators/lib/templates/field-validation-catalog.js +1 -531
  41. package/generators/lib/templates/mysql-template.js +1 -3863
  42. package/generators/lib/templates/oracle-template.js +1 -3915
  43. package/generators/lib/templates/postgres-template.js +1 -5838
  44. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  45. package/generators/lib/templates/sqlite-template.js +1 -3440
  46. package/generators/lib/utils/audit-columns.js +181 -0
  47. package/generators/lib/utils/cli-output.js +17 -0
  48. package/generators/lib/utils/database-introspector.js +16 -13
  49. package/generators/lib/utils/env-manager.js +6 -0
  50. package/generators/lib/utils/path-validator.js +71 -0
  51. package/generators/lib/validators/payload-validator.js +1 -2
  52. package/integrity-manifest.json +28 -10
  53. package/package.json +11 -3
  54. package/scripts/verify-integrity.js +1 -1
  55. package/server.js +1 -1
  56. package/src/components/handlers/adjust_handler.js +1 -1
  57. package/src/components/handlers/audit_handler.js +1 -1
  58. package/src/components/handlers/delete_handler.js +1 -1
  59. package/src/components/handlers/export_handler.js +1 -1
  60. package/src/components/handlers/import_handler.js +1 -1
  61. package/src/components/handlers/insert_handler.js +1 -1
  62. package/src/components/handlers/update_handler.js +1 -1
  63. package/src/components/handlers/upload_handler.js +1 -1
  64. package/src/components/handlers/workflow_handler.js +1 -1
  65. package/src/components/integrations/webhook.js +1 -1
  66. package/src/consumers/baseConsumer.js +1 -1
  67. package/src/consumers/declarativeMapper.js +1 -1
  68. package/src/consumers/handlers/apiHandler.js +1 -1
  69. package/src/consumers/handlers/consoleHandler.js +1 -1
  70. package/src/consumers/handlers/databaseHandler.js +1 -1
  71. package/src/consumers/handlers/index.js +1 -1
  72. package/src/consumers/handlers/kafkaHandler.js +1 -1
  73. package/src/consumers/index.js +1 -1
  74. package/src/consumers/messageTransformer.js +1 -1
  75. package/src/consumers/validator.js +1 -1
  76. package/src/core/db/dialect/base-dialect.js +1 -1
  77. package/src/core/db/dialect/index.js +1 -1
  78. package/src/core/db/dialect/mysql-dialect.js +1 -1
  79. package/src/core/db/dialect/oracle-dialect.js +1 -1
  80. package/src/core/db/dialect/postgres-dialect.js +1 -1
  81. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  82. package/src/core/db/flatten-helper.js +1 -1
  83. package/src/core/db/query-builder-error.js +1 -1
  84. package/src/core/db/query-builder.js +1 -1
  85. package/src/core/db/relation-helper.js +1 -1
  86. package/src/core/handlers/delete_handler.js +1 -1
  87. package/src/core/handlers/insert_handler.js +1 -1
  88. package/src/core/handlers/update_handler.js +1 -1
  89. package/src/core/models/base-model.js +1 -1
  90. package/src/core/utils/cache-manager.js +1 -1
  91. package/src/core/utils/component-engine.js +1 -1
  92. package/src/core/utils/context-builder.js +1 -1
  93. package/src/core/utils/datetime-formatter.js +1 -1
  94. package/src/core/utils/datetime-parser.js +1 -1
  95. package/src/core/utils/db.js +1 -1
  96. package/src/core/utils/logger.js +1 -1
  97. package/src/core/utils/payload-loader.js +1 -1
  98. package/src/core/utils/security-checks.js +1 -1
  99. package/src/middleware/body-options.js +1 -1
  100. package/src/middleware/cors.js +1 -1
  101. package/src/middleware/idempotency.js +1 -1
  102. package/src/middleware/rate-limiter.js +1 -1
  103. package/src/middleware/request-logger.js +1 -1
  104. package/src/middleware/security-headers.js +1 -1
  105. package/src/models/base-model-mysql.js +1 -1
  106. package/src/models/base-model-oracle.js +1 -1
  107. package/src/models/base-model-sqlite.js +1 -1
  108. package/src/models/base-model.js +1 -1
  109. package/src/pro/caching/redis-client.js +1 -1
  110. package/src/pro/caching/redis-helper.js +1 -1
  111. package/src/pro/consumers/baseConsumer.js +1 -1
  112. package/src/pro/consumers/declarativeMapper.js +1 -1
  113. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  114. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  115. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  116. package/src/pro/consumers/handlers/index.js +1 -1
  117. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  118. package/src/pro/consumers/index.js +1 -1
  119. package/src/pro/consumers/messageTransformer.js +1 -1
  120. package/src/pro/consumers/validator.js +1 -1
  121. package/src/pro/database/base-model-mysql.js +1 -1
  122. package/src/pro/database/base-model-oracle.js +1 -1
  123. package/src/pro/database/base-model-sqlite.js +1 -1
  124. package/src/pro/database/db-mysql.js +1 -1
  125. package/src/pro/database/db-oracle.js +1 -1
  126. package/src/pro/database/db-sqlite.js +1 -1
  127. package/src/pro/excel/excel-generator.js +1 -1
  128. package/src/pro/excel/excel-parser.js +1 -1
  129. package/src/pro/excel/export-service.js +1 -1
  130. package/src/pro/excel/export_handler.js +1 -1
  131. package/src/pro/excel/import-service.js +1 -1
  132. package/src/pro/excel/import-validator.js +1 -1
  133. package/src/pro/excel/import_handler.js +1 -1
  134. package/src/pro/excel/upsert-builder.js +1 -1
  135. package/src/pro/idgen/idgen-routes.js +1 -1
  136. package/src/pro/integrations/lookup-resolver.js +1 -1
  137. package/src/pro/integrations/upload-handler-v2.js +1 -1
  138. package/src/pro/integrations/upload-handler.js +1 -1
  139. package/src/pro/integrations/webhook.js +1 -1
  140. package/src/pro/locking/lock-routes.js +1 -1
  141. package/src/pro/locking/resource-lock-manager.js +1 -1
  142. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  143. package/src/pro/messaging/kafkaService.js +1 -1
  144. package/src/pro/messaging/messagehubService.js +1 -1
  145. package/src/pro/messaging/rabbitmqService.js +1 -1
  146. package/src/pro/scheduler/job-manager.js +1 -1
  147. package/src/pro/scheduler/job-routes.js +1 -1
  148. package/src/pro/scheduler/job-validator.js +1 -1
  149. package/src/pro/storage/base-storage-provider.js +1 -1
  150. package/src/pro/storage/file-metadata-helper.js +1 -1
  151. package/src/pro/storage/index.js +1 -1
  152. package/src/pro/storage/local-storage-provider.js +1 -1
  153. package/src/pro/storage/s3-storage-provider.js +1 -1
  154. package/src/pro/storage/upload-cleanup-job.js +1 -1
  155. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  156. package/src/pro/storage/upload-pending-tracker.js +1 -1
  157. package/src/pro/websocket/broadcast-helper.js +1 -1
  158. package/src/pro/websocket/index.js +1 -1
  159. package/src/pro/websocket/livesync-server.js +1 -1
  160. package/src/pro/websocket/ws-broadcaster.js +1 -1
  161. package/src/services/export-service.js +1 -1
  162. package/src/services/import-service.js +1 -1
  163. package/src/services/kafkaConsumerService.js +1 -1
  164. package/src/services/kafkaService.js +1 -1
  165. package/src/services/messagehubService.js +1 -1
  166. package/src/services/rabbitmqService.js +1 -1
  167. package/src/utils/cache-invalidation-registry.js +1 -1
  168. package/src/utils/cache-manager.js +1 -1
  169. package/src/utils/component-engine.js +1 -1
  170. package/src/utils/config-extractor.js +1 -1
  171. package/src/utils/consumerLogger.js +1 -1
  172. package/src/utils/context-builder.js +1 -1
  173. package/src/utils/dashboard-helpers.js +1 -1
  174. package/src/utils/dateHelper.js +1 -1
  175. package/src/utils/datetime-formatter.js +1 -1
  176. package/src/utils/datetime-parser.js +1 -1
  177. package/src/utils/db-bootstrap.js +1 -1
  178. package/src/utils/db-mysql.js +1 -1
  179. package/src/utils/db-oracle.js +1 -1
  180. package/src/utils/db-sqlite.js +1 -1
  181. package/src/utils/db.js +1 -1
  182. package/src/utils/demo-generator.js +1 -1
  183. package/src/utils/excel-generator.js +1 -1
  184. package/src/utils/excel-parser.js +1 -1
  185. package/src/utils/file-watcher.js +1 -1
  186. package/src/utils/id-generator.js +1 -1
  187. package/src/utils/idempotency-manager.js +1 -1
  188. package/src/utils/import-validator.js +1 -1
  189. package/src/utils/license-client.js +1 -1
  190. package/src/utils/lock-manager.js +1 -1
  191. package/src/utils/logger.js +1 -1
  192. package/src/utils/lookup-resolver.js +1 -1
  193. package/src/utils/payload-loader.js +1 -1
  194. package/src/utils/processor-response.js +1 -1
  195. package/src/utils/rabbitmq.js +1 -1
  196. package/src/utils/redis-client.js +1 -1
  197. package/src/utils/redis-helper.js +1 -1
  198. package/src/utils/request-scope.js +1 -1
  199. package/src/utils/security-checks.js +1 -1
  200. package/src/utils/service-resolver.js +1 -1
  201. package/src/utils/shutdown-coordinator.js +1 -1
  202. package/src/utils/trusted-keys.js +1 -1
  203. package/src/utils/upload-handler.js +1 -1
  204. package/src/utils/upsert-builder.js +1 -1
  205. package/src/utils/workflow-hook-executor.js +1 -1
  206. package/generators/metadata/global.json +0 -58
  207. package/generators/metadata/test-mysql-workbench.json +0 -118
  208. package/generators/metadata/test-mysql.json +0 -56
  209. package/generators/metadata/test-oracle-workbench.json +0 -118
  210. package/generators/metadata/test-oracle.json +0 -56
  211. package/generators/metadata/test-pg-workbench.json +0 -118
  212. package/generators/metadata/test-pg.json +0 -56
  213. package/generators/scripts/obfuscate-source.js +0 -356
  214. package/generators/scripts/validate-catalog.js +0 -430
  215. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  216. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  217. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  218. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  219. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  220. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  221. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  222. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  223. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  224. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  225. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  226. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  227. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  228. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  229. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  230. package/generators/tests/integration-tahap4b/README.md +0 -145
  231. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  232. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  233. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  234. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  235. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  236. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  237. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  238. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  239. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  240. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  241. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  242. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  243. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  244. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  245. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  246. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  247. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  248. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  249. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  250. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  251. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  252. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  253. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  254. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  255. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  256. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  257. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  258. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  259. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  260. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  261. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  262. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  263. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  264. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  265. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  266. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  267. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  268. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  269. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  270. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  271. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  299. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  300. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  301. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  302. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  303. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  304. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  305. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  306. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  307. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  308. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  309. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  310. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  311. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  312. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  313. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  314. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  315. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  316. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  317. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  318. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  319. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  320. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  321. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  322. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  323. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  324. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  325. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  326. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  327. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  328. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  329. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  330. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  331. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  332. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  333. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  334. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  335. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  336. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  337. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  338. package/scripts/generate-integrity-manifest.js +0 -124
  339. package/scripts/snapshot-cli-contracts.js +0 -194
  340. package/scripts/verify-publish.js +0 -56
@@ -1,1173 +0,0 @@
1
- const express = require('express');
2
- const router = express.Router();
3
- const itemModel = require('../../models/mini-inventory/item');
4
-
5
-
6
- // Component Engine untuk event lifecycle (optional)
7
- let componentEngine = null;
8
- let ContextBuilder = null;
9
- try {
10
- // Hanya load component engine jika payload memiliki components
11
- const hasComponents = undefined && Array.isArray(undefined) && undefined.length > 0;
12
-
13
- if (hasComponents) {
14
- componentEngine = require('restforgejs/src/utils/component-engine').componentEngine;
15
- ContextBuilder = require('restforgejs/src/utils/context-builder');
16
-
17
- // Load component configuration dari payload yang sedang digunakan
18
- const componentConfig = {
19
- tableName: 'core.item',
20
- fieldName: ["item_id","item_code","item_name","description","uom","unit_price","weight","is_active","created_at","created_by","updated_at","updated_by"],
21
- exportQuery: null,
22
- columnFormats: null,
23
- fieldLabels: null,
24
- components: undefined,
25
- importConfig: null,
26
- adjustConfig: null,
27
- workflowConfig: null,
28
- uploadConfig: null,
29
- requestScope: null
30
- };
31
-
32
- componentEngine.loadConfigurationFromObject(componentConfig).then(result => {
33
- if (result.success) {
34
- console.log(`Component configuration loaded for mini-inventory/item: ${result.componentsLoaded} components`);
35
- }
36
- }).catch(err => {
37
- console.error(`Failed to load component configuration for mini-inventory/item:`, err.message);
38
- });
39
- } else {
40
- console.log(`No components defined in payload for mini-inventory/item, running without events`);
41
- }
42
- } catch (e) {
43
- if (hasComponents) {
44
- // Components dikonfigurasi tapi engine gagal load — ini error, bukan optional
45
- console.error(`CRITICAL: Component engine failed to load for mini-inventory/item but components are configured:`, e.message);
46
- throw e;
47
- }
48
- // Jika tidak ada components, silent skip wajar
49
- console.log(`No component engine required for mini-inventory/item, running without events`);
50
- }
51
-
52
- /**
53
- * Item Submodule - Auto-generated on 2026-04-25 07:34:33
54
- *
55
- * Endpoints untuk item dengan actions: datatables, create, update, delete, lookup, read
56
- * Table: core.item
57
- * Fields: 12 fields
58
- * Database: PostgreSQL
59
- */
60
-
61
- // CORS ditangani di level app oleh cors middleware (lihat konfigurasi CORS_ENABLED dan CORS_ORIGINS di .env)
62
-
63
-
64
-
65
- // Middleware untuk validasi payload pada metode POST
66
- router.use((req, res, next) => {
67
- if (req.method === 'POST') {
68
- try {
69
- // Skip validation untuk import routes (menggunakan multipart/form-data, bukan JSON)
70
- if (req.path.startsWith('/import-')) {
71
- return next();
72
- }
73
-
74
- // Validasi umum untuk payload
75
- if (!req.body || Object.keys(req.body).length === 0) {
76
- return res.status(400).json({
77
- success: false,
78
- error: 'Missing payload',
79
- message: 'Payload cannot be empty',
80
- timestamp: new Date().toISOString(),
81
- endpoint: '/api/mini-inventory/item' + req.path
82
- });
83
- }
84
-
85
- // Validasi spesifik untuk endpoint tertentu
86
- const endpoint = req.path.substring(1); // menghapus / di awal
87
-
88
- // Endpoint get membutuhkan where dalam format {key, value} atau [{key, value}] (1 elemen)
89
- if (endpoint === 'first') {
90
- // Normalize: array 1 elemen -> object
91
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
92
- req.body.where = req.body.where[0];
93
- }
94
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
95
- return res.status(400).json({
96
- success: false,
97
- error: 'Invalid payload',
98
- message: 'Where must be a single condition {key, value}',
99
- example: {
100
- "where": { "key": "field_name", "value": "field_value" },
101
- "select": ["field1", "field2"]
102
- },
103
- timestamp: new Date().toISOString()
104
- });
105
- }
106
- if (req.body.where.conditions || req.body.where.logic) {
107
- return res.status(400).json({
108
- success: false,
109
- error: 'Invalid payload',
110
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
111
- example: {
112
- "where": { "key": "field_name", "value": "field_value" }
113
- },
114
- timestamp: new Date().toISOString()
115
- });
116
- }
117
- }
118
-
119
- // Endpoint delete membutuhkan where
120
- if (endpoint === 'delete' && (!req.body.where)) {
121
- return res.status(400).json({
122
- success: false,
123
- error: 'Invalid payload',
124
- message: 'DELETE payload must include a where property',
125
- example: {
126
- "where": [{ "key": "id", "value": "your-id-value" }]
127
- },
128
- timestamp: new Date().toISOString()
129
- });
130
- }
131
-
132
- // Endpoint add membutuhkan data yang valid
133
- if (endpoint === 'add') {
134
- // Filter field yang memiliki autoGenerate dari required check
135
- const autoGenerateFields = [];
136
-
137
- const requiredFields = ['item_id']
138
- .filter(field => !autoGenerateFields.includes(field));
139
-
140
- const missingFields = requiredFields.filter(field => !req.body[field] || req.body[field] === '');
141
-
142
- if (missingFields.length > 0) {
143
- return res.status(400).json({
144
- success: false,
145
- error: 'Missing required fields',
146
- message: `Required field(s): ${missingFields.join(', ')}`,
147
- timestamp: new Date().toISOString()
148
- });
149
- }
150
- }
151
-
152
- // Endpoint update membutuhkan primary key
153
- if (endpoint === 'update') {
154
- const primaryKey = 'id';
155
- if (!req.body[primaryKey]) {
156
- return res.status(400).json({
157
- success: false,
158
- error: 'Missing required field',
159
- message: `Primary key (${primaryKey}) is required for update`,
160
- timestamp: new Date().toISOString()
161
- });
162
- }
163
- }
164
-
165
- } catch (error) {
166
- console.error(`Error validating payload for ${req.path}:`, error);
167
- return res.status(400).json({
168
- success: false,
169
- error: 'Invalid payload',
170
- message: 'Invalid payload',
171
- details: error.message,
172
- timestamp: new Date().toISOString()
173
- });
174
- }
175
- }
176
- next();
177
- });
178
-
179
-
180
- // POST /api/mini-inventory/item/datatables - Data untuk DataTables
181
- router.post('/datatables', async (req, res) => {
182
- try {
183
- // req.log.debug('Request body item/datatables:', JSON.stringify(req.body, null, 2));
184
-
185
- // Extract parameters dari request
186
- const options = {
187
- searchValue: req.body.search?.value || req.body.searchValue || req.body.search_value || '',
188
- searchBy: req.body.searchBy || req.body.search_by || 'all',
189
- perPage: parseInt(req.body.length || req.body.pagination?.perpage || 10, 10),
190
- start: parseInt(req.body.start || 0, 10),
191
- draw: req.body.draw || '1'
192
- };
193
-
194
- // Handle sort_columns
195
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
196
- options.sort_columns = req.body.sort_columns.map(item => ({
197
- column: item.column,
198
- direction: (item.direction || 'ASC').toUpperCase()
199
- }));
200
- }
201
-
202
- // Pass format DataTables parameters jika ada (fallback)
203
- if (req.body['order[0][column]'] !== undefined) {
204
- options['order[0][column]'] = req.body['order[0][column]'];
205
- }
206
- if (req.body['order[0][dir]'] !== undefined) {
207
- options['order[0][dir]'] = req.body['order[0][dir]'];
208
- }
209
-
210
- // Proses filter dalam format filters object
211
- if (req.body.filters && typeof req.body.filters === 'object') {
212
- // Filter out values that should be ignored ("all", "-", "", null, undefined)
213
- const filteredFilters = {};
214
- Object.keys(req.body.filters).forEach(key => {
215
- const value = req.body.filters[key];
216
- // Ignore filter if value is "all", "-", empty string, null, or undefined
217
- if (value !== "all" && value !== "-" && value !== "" && value !== null && value !== undefined) {
218
- filteredFilters[key] = value;
219
- }
220
- });
221
-
222
- // Only set filters if there are valid filters remaining
223
- if (Object.keys(filteredFilters).length > 0) {
224
- options.filters = filteredFilters;
225
- console.log('Applied filters (ignoring "all", "-", empty values):', JSON.stringify(filteredFilters, null, 2));
226
- } else {
227
- console.log('All filters ignored due to "all", "-", or empty values');
228
- }
229
- }
230
-
231
- // Proses parameter where dengan format advanced conditions
232
- if (req.body.where && typeof req.body.where === 'object') {
233
- // Validasi format where (mendukung format array legacy dan format object baru)
234
- if (Array.isArray(req.body.where) || (req.body.where.conditions && Array.isArray(req.body.where.conditions))) {
235
- options.where = req.body.where;
236
- console.log('Applied where conditions:', JSON.stringify(req.body.where, null, 2));
237
- } else {
238
- console.log('Invalid where format, ignoring where parameter');
239
- }
240
- }
241
-
242
- // Validasi dan sanitasi parameters
243
- if (options.perPage > 1000) {
244
- options.perPage = 1000; // Limit untuk mencegah overload
245
- }
246
- if (options.perPage < 1) {
247
- options.perPage = 10;
248
- }
249
-
250
- // Gunakan model untuk mendapatkan data
251
- const result = await itemModel.getDatatables(options);
252
-
253
- // Menambahkan nomor baris untuk DataTables jika diperlukan
254
- if (result.data && Array.isArray(result.data)) {
255
- result.data = result.data.map((item, index) => ({
256
- ...item,
257
- rownumerator: options.start + index + 1
258
- }));
259
- }
260
-
261
- // Add metadata untuk debugging (development only)
262
- if (process.env.NODE_ENV === 'development') {
263
- result._metadata = {
264
- endpoint: 'item',
265
- options: options,
266
- timestamp: new Date().toISOString()
267
- };
268
- }
269
-
270
- return res.json(result);
271
- } catch (error) {
272
- console.error('Error in item datatables:', error);
273
- const statusCode = error.statusCode || 500;
274
- return res.status(statusCode).json({
275
- success: false,
276
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
277
- message: statusCode === 400 ? error.message : 'An error occurred while fetching item data',
278
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
279
- timestamp: new Date().toISOString()
280
- });
281
- }
282
- });
283
-
284
-
285
- // POST /api/mini-inventory/item/create - Menambahkan data item baru
286
- router.post('/create', async (req, res) => {
287
- try {
288
- // req.log.debug('Request body item/create:', JSON.stringify(req.body, null, 2));
289
-
290
- // Validasi payload
291
- if (!req.body || Object.keys(req.body).length === 0) {
292
- return res.status(400).json({
293
- success: false,
294
- error: 'Invalid payload',
295
- message: 'Payload cannot be empty',
296
- timestamp: new Date().toISOString()
297
- });
298
- }
299
-
300
- // Get correlation ID from header (optional)
301
- const correlationId = req.headers['x-correlation-id'] || null;
302
-
303
- // Validasi data dengan model jika tersedia
304
- if (typeof itemModel.validateData === 'function') {
305
- const validation = await itemModel.validateData(req.body, 'insert');
306
- if (!validation.isValid) {
307
- return res.status(400).json({
308
- success: false,
309
- error: 'Validation failed',
310
- message: 'Invalid data',
311
- errors: validation.errors,
312
- timestamp: new Date().toISOString()
313
- });
314
- }
315
-
316
- // Gunakan sanitized data
317
- req.body = { ...req.body, ...validation.sanitizedData };
318
- }
319
-
320
- // Event lifecycle variables
321
- let oldData = null;
322
- let newData = null;
323
- const requestData = req.body;
324
- let responseData = null;
325
-
326
- // === Integrated Transaction dengan Event Lifecycle ===
327
- // onBefore + main operation + onAfter dieksekusi dalam satu transaction scope
328
- // di dalam model.executeTransactionWithEvents()
329
- if (componentEngine && ContextBuilder) {
330
- // Gunakan integrated transaction model dengan event lifecycle
331
- try {
332
- const eventContext = {
333
- componentEngine: componentEngine,
334
- ContextBuilder: ContextBuilder,
335
- tableName: 'core.item',
336
- additionalContext: {
337
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
338
- options: req.bodyOptions || {},
339
- requestId: req.id || null
340
- }
341
- };
342
-
343
- responseData = await itemModel.addData(req.body, eventContext);
344
- newData = responseData;
345
-
346
- console.log('[INTEGRATED TRANSACTION] INSERT completed successfully with events');
347
- } catch (error) {
348
- console.error('[INTEGRATED TRANSACTION] INSERT failed:', error.message);
349
- throw error;
350
- }
351
- } else {
352
- // Fallback: gunakan mode lama tanpa events (tetap propagasi requestId untuk Live Sync)
353
- try {
354
- responseData = await itemModel.addData(req.body, { additionalContext: { requestId: req.id || null } });
355
- newData = responseData;
356
- console.log('[FALLBACK] INSERT completed without events');
357
- } catch (error) {
358
- console.error('[FALLBACK] INSERT failed:', error.message);
359
- throw error;
360
- }
361
- }
362
-
363
- // Log successful operation
364
- console.log(`item data added successfully: ${responseData.id || 'new record'}`);
365
-
366
- // Kirim response
367
- return res.status(201).json({
368
- success: true,
369
- message: 'item data successfully added',
370
- data: responseData,
371
- timestamp: new Date().toISOString()
372
- });
373
- } catch (error) {
374
- console.error('Error saat menambahkan data item:', error);
375
-
376
- // Handle specific error types
377
- if (error.code === '23505') { // PostgreSQL unique violation
378
- return res.status(409).json({
379
- success: false,
380
- error: 'Duplicate entry',
381
- message: 'A record with this value already exists',
382
- timestamp: new Date().toISOString()
383
- });
384
- }
385
-
386
- if (error.code === '23503') { // PostgreSQL foreign key violation
387
- return res.status(400).json({
388
- success: false,
389
- error: 'Foreign key constraint',
390
- message: 'Referenced data not found',
391
- timestamp: new Date().toISOString()
392
- });
393
- }
394
-
395
- return res.status(500).json({
396
- success: false,
397
- error: 'Internal Server Error',
398
- message: 'An error occurred while adding item data',
399
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
400
- timestamp: new Date().toISOString()
401
- });
402
- }
403
- });
404
-
405
-
406
- // POST /api/mini-inventory/item/update - Mengupdate data item
407
- router.post('/update', async (req, res) => {
408
- try {
409
- // req.log.debug('Request body item/update:', JSON.stringify(req.body, null, 2));
410
-
411
- // Validasi payload
412
- if (!req.body || Object.keys(req.body).length === 0) {
413
- return res.status(400).json({
414
- success: false,
415
- error: 'Invalid payload',
416
- message: 'Payload cannot be empty',
417
- timestamp: new Date().toISOString()
418
- });
419
- }
420
-
421
- // Get correlation ID from header (optional)
422
- const correlationId = req.headers['x-correlation-id'] || null;
423
-
424
- // Validasi primary key
425
- const primaryKey = 'id';
426
- if (!req.body[primaryKey]) {
427
- return res.status(400).json({
428
- success: false,
429
- error: 'Missing required field',
430
- message: `Primary key (${primaryKey}) is required for update`,
431
- timestamp: new Date().toISOString()
432
- });
433
- }
434
-
435
- // Cek apakah primary key adalah UUID yang valid (jika primary key adalah 'id')
436
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
437
- const isValidUUID = primaryKey === 'id' && uuidRegex.test(req.body[primaryKey]);
438
-
439
- // Validasi data dengan model jika tersedia
440
- if (typeof itemModel.validateData === 'function') {
441
- const validation = await itemModel.validateData(req.body, 'update');
442
- if (!validation.isValid) {
443
- return res.status(400).json({
444
- success: false,
445
- error: 'Validation failed',
446
- message: 'Invalid data',
447
- errors: validation.errors,
448
- timestamp: new Date().toISOString()
449
- });
450
- }
451
-
452
- // Gunakan sanitized data
453
- req.body = { ...req.body, ...validation.sanitizedData };
454
- }
455
-
456
- // Event lifecycle variables
457
- let oldData = null;
458
- let newData = null;
459
- const requestData = req.body;
460
- let responseData = null;
461
-
462
- // === Integrated Transaction dengan Event Lifecycle ===
463
- // oldData fetch + onBefore + main operation + onAfter dieksekusi dalam satu transaction scope
464
- // di dalam model.executeTransactionWithEvents()
465
- if (componentEngine && ContextBuilder) {
466
- // Gunakan integrated transaction model dengan event lifecycle
467
- try {
468
- const eventContext = {
469
- componentEngine: componentEngine,
470
- ContextBuilder: ContextBuilder,
471
- tableName: 'core.item',
472
- additionalContext: {
473
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
474
- options: req.bodyOptions || {},
475
- requestId: req.id || null
476
- }
477
- };
478
-
479
- responseData = await itemModel.updateData(req.body, eventContext);
480
- newData = responseData;
481
-
482
- console.log('[INTEGRATED TRANSACTION] UPDATE completed successfully with events');
483
- } catch (error) {
484
- console.error('[INTEGRATED TRANSACTION] UPDATE failed:', error.message);
485
- throw error;
486
- }
487
- } else {
488
- // Fallback: gunakan mode lama tanpa events (tetap propagasi requestId untuk Live Sync)
489
- try {
490
- responseData = await itemModel.updateData(req.body, { additionalContext: { requestId: req.id || null } });
491
- newData = responseData;
492
- console.log('[FALLBACK] UPDATE completed without events');
493
- } catch (error) {
494
- console.error('[FALLBACK] UPDATE failed:', error.message);
495
- throw error;
496
- }
497
- }
498
-
499
- // Log successful operation
500
- console.log(`item data updated successfully: ${primaryKey}=${req.body[primaryKey]}`);
501
-
502
- // Kirim response
503
- return res.status(200).json({
504
- success: true,
505
- message: 'item data successfully updated',
506
- data: responseData,
507
- timestamp: new Date().toISOString()
508
- });
509
- } catch (error) {
510
- console.error('Error saat mengupdate data item:', error);
511
-
512
- // Handle khusus untuk error "Data tidak ditemukan"
513
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
514
- return res.status(404).json({
515
- success: false,
516
- error: 'Data not found',
517
- message: 'item data not found',
518
- timestamp: new Date().toISOString()
519
- });
520
- }
521
-
522
- // Handle specific database errors
523
- if (error.code === '23505') { // PostgreSQL unique violation
524
- return res.status(409).json({
525
- success: false,
526
- error: 'Duplicate entry',
527
- message: 'A record with this value already exists',
528
- timestamp: new Date().toISOString()
529
- });
530
- }
531
-
532
- return res.status(500).json({
533
- success: false,
534
- error: 'Internal Server Error',
535
- message: 'An error occurred while updating item data',
536
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
537
- timestamp: new Date().toISOString()
538
- });
539
- }
540
- });
541
-
542
-
543
- // POST /api/mini-inventory/item/delete - Menghapus data item
544
- router.post('/delete', async (req, res) => {
545
- try {
546
- // req.log.debug('Request body item/delete:', JSON.stringify(req.body, null, 2));
547
-
548
- // Validasi request body
549
- if (!req.body || Object.keys(req.body).length === 0) {
550
- return res.status(400).json({
551
- success: false,
552
- error: 'Invalid payload',
553
- message: 'Payload cannot be empty',
554
- timestamp: new Date().toISOString()
555
- });
556
- }
557
-
558
- // Get correlation ID from header (optional)
559
- const correlationId = req.headers['x-correlation-id'] || null;
560
-
561
- if (!req.body.where) {
562
- return res.status(400).json({
563
- success: false,
564
- error: 'Missing required field',
565
- message: 'Invalid request format: where parameter is required',
566
- example: {
567
- "where": [{ "key": "id", "value": "your-id-value" }]
568
- },
569
- timestamp: new Date().toISOString()
570
- });
571
- }
572
-
573
- // Validasi format where
574
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
575
- return res.status(400).json({
576
- success: false,
577
- error: 'Invalid where format',
578
- message: 'Invalid where format',
579
- example: {
580
- "where": [
581
- { "key": "id", "value": "your-id-value" }
582
- ]
583
- },
584
- timestamp: new Date().toISOString()
585
- });
586
- }
587
-
588
- // === EVENT LIFECYCLE: onBefore DELETE ===
589
- let oldData = null;
590
- let newData = null;
591
- const requestData = req.body;
592
- let responseData = null;
593
-
594
- // Cek apakah data exist sebelum delete dan ambil old data untuk event lifecycle
595
- // Menggunakan SELECT * dari tabel utama (tanpa explicit select) karena fieldName
596
- // bisa mengandung kolom dari JOIN (mis. city_name) yang tidak ada di tabel utama
597
- if (req.body.where && Array.isArray(req.body.where) && req.body.where.length > 0) {
598
- const firstCondition = req.body.where[0];
599
- try {
600
- const existingData = await itemModel.getData({
601
- where: [{ key: firstCondition.key, value: firstCondition.value }]
602
- });
603
-
604
- if (!existingData.success || !existingData.data || existingData.data.length === 0) {
605
- return res.status(404).json({
606
- success: false,
607
- error: 'Data not found',
608
- message: 'item data not found',
609
- timestamp: new Date().toISOString()
610
- });
611
- }
612
-
613
- // Simpan data untuk event lifecycle dan debug
614
- oldData = existingData.data[0];
615
-
616
- } catch (checkError) {
617
- return res.status(500).json({
618
- success: false,
619
- error: 'Verification Failed',
620
- message: 'Could not verify data existence before delete',
621
- details: process.env.NODE_ENV === 'development' ? checkError.message : undefined,
622
- timestamp: new Date().toISOString()
623
- });
624
- }
625
- }
626
-
627
- // === Integrated Transaction dengan Event Lifecycle ===
628
- // onBefore + main operation + onAfter dieksekusi dalam satu transaction scope
629
- // di dalam model.executeTransactionWithEvents()
630
- if (componentEngine && ContextBuilder) {
631
- // Gunakan integrated transaction model dengan event lifecycle
632
- try {
633
- const eventContext = {
634
- componentEngine: componentEngine,
635
- ContextBuilder: ContextBuilder,
636
- tableName: 'core.item',
637
- additionalContext: {
638
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
639
- options: req.bodyOptions || {},
640
- requestId: req.id || null
641
- }
642
- };
643
-
644
- responseData = await itemModel.deleteData(req.body, eventContext);
645
-
646
- console.log('[INTEGRATED TRANSACTION] DELETE completed successfully with events');
647
- } catch (error) {
648
- console.error('[INTEGRATED TRANSACTION] DELETE failed:', error.message);
649
- throw error;
650
- }
651
- } else {
652
- // Fallback: gunakan mode lama tanpa events (tetap propagasi requestId untuk Live Sync)
653
- try {
654
- responseData = await itemModel.deleteData(req.body, { additionalContext: { requestId: req.id || null } });
655
- console.log('[FALLBACK] DELETE completed without events');
656
- } catch (error) {
657
- console.error('[FALLBACK] DELETE failed:', error.message);
658
- throw error;
659
- }
660
- }
661
-
662
- // Log successful operation
663
- console.log(`item data deleted successfully`);
664
-
665
- return res.json({
666
- ...responseData,
667
- timestamp: new Date().toISOString()
668
- });
669
- } catch (error) {
670
- console.error('Error saat menghapus data item:', error);
671
-
672
- // Handle foreign key constraint errors
673
- if (error.code === '23503') { // PostgreSQL foreign key violation
674
- return res.status(409).json({
675
- success: false,
676
- error: 'Foreign key constraint',
677
- message: 'Cannot delete: record is still referenced by other data',
678
- timestamp: new Date().toISOString()
679
- });
680
- }
681
-
682
- return res.status(500).json({
683
- success: false,
684
- error: 'Internal Server Error',
685
- message: 'An error occurred while deleting item data',
686
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
687
- timestamp: new Date().toISOString()
688
- });
689
- }
690
- });
691
-
692
-
693
- // GET /api/mini-inventory/item/lookup - Pencarian item untuk dropdown (dynamic)
694
- router.get('/lookup', async (req, res) => {
695
- try {
696
- // Validasi X-Request-Mode header
697
- const requestMode = req.headers['x-request-mode'];
698
- console.log(`X-Request-Mode header: ${requestMode}`);
699
-
700
- if (requestMode !== 'dynamic') {
701
- return res.status(400).json({
702
- success: false,
703
- error: 'Invalid Request Mode',
704
- message: 'X-Request-Mode header must be set to dynamic',
705
- details: 'Contoh penggunaan: Tambahkan header X-Request-Mode: dynamic',
706
- timestamp: new Date().toISOString()
707
- });
708
- }
709
-
710
- // Ambil parameter search (default case-insensitive)
711
- let search = req.query.search || '';
712
- if (Array.isArray(search)) {
713
- search = search[0] || '';
714
- }
715
-
716
- // Ambil parameter tambahan untuk filtering
717
- const companyId = req.query.company_id || req.query.companyId;
718
- const extraFilters = {};
719
-
720
- // Collect additional filter parameters
721
- if (companyId) {
722
- extraFilters.company_id = companyId;
723
- }
724
-
725
- // Validasi search parameter
726
- if (search.length > 100) {
727
- return res.status(400).json({
728
- success: false,
729
- error: 'Invalid search parameter',
730
- message: 'Search parameter terlalu panjang (max 100 karakter)',
731
- timestamp: new Date().toISOString()
732
- });
733
- }
734
-
735
- console.log(`item lookup request: search=${search}, filters=`, extraFilters);
736
-
737
- // Gunakan model untuk lookup data dengan filters tambahan
738
- const list = await itemModel.getLookupDataDynamic(search, extraFilters);
739
-
740
- console.log(`Lookup results: ${list.length} records found`);
741
- return res.json({
742
- success: true,
743
- count: list.length,
744
- data: list,
745
- search: search,
746
- timestamp: new Date().toISOString()
747
- });
748
- } catch (error) {
749
- console.error('Error in item lookup:', error);
750
- return res.status(500).json({
751
- success: false,
752
- error: 'Internal Server Error',
753
- message: 'An error occurred while looking up item data',
754
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
755
- timestamp: new Date().toISOString()
756
- });
757
- }
758
- });
759
-
760
- // POST /api/mini-inventory/item/lookup - Mendapatkan data item untuk lookup dengan filtering
761
- router.post('/lookup', async (req, res) => {
762
- try {
763
- // Validasi X-Request-Mode header
764
- const requestMode = req.headers['x-request-mode'];
765
- console.log(`X-Request-Mode header for POST lookup: ${requestMode}`);
766
-
767
- if (requestMode !== 'static') {
768
- return res.status(400).json({
769
- success: false,
770
- error: 'Invalid Request Mode',
771
- message: 'X-Request-Mode header must be set to static for POST method',
772
- details: 'Contoh penggunaan: Tambahkan header X-Request-Mode: static',
773
- timestamp: new Date().toISOString()
774
- });
775
- }
776
-
777
- // Validasi payload
778
- if (!req.body) {
779
- // Support untuk fieldNameLookup config
780
- const lookupConfig = null;
781
- const defaultSelect = lookupConfig && lookupConfig.hasCustomText
782
- ? ["id", lookupConfig.textField]
783
- : ["id", "item_id"];
784
-
785
- return res.status(400).json({
786
- success: false,
787
- error: 'Invalid Payload',
788
- message: 'Payload cannot be empty',
789
- example: {
790
- "selected_tag": "optional-id-if-needed",
791
- "where": [{ "key": "item_id", "value": "AP2506-01" }],
792
- "select": defaultSelect
793
- },
794
- example_sql_expression: {
795
- "where": [{ "key": "company_id", "value": "your-company-id" }],
796
- "select": ["id", "item_id||' - '||code as display_text"]
797
- },
798
- example_custom_lookup: lookupConfig ? {
799
- "where": [{ "key": "company_id", "value": "your-company-id" }],
800
- "select": [lookupConfig.idField, lookupConfig.textField],
801
- "note": "Using fieldNameLookup configuration"
802
- } : null,
803
- example_advanced: {
804
- "where": {
805
- "logic": "OR",
806
- "conditions": [
807
- { "key": "item_id", "operator": "like", "value": "%Portal%" },
808
- { "key": "item_id", "operator": "like", "value": "%system%", "sensitive": false }
809
- ]
810
- },
811
- "select": ["id", "item_code", "item_name"]
812
- },
813
- example_with_ordering: {
814
- "select": ["id", "item_id"],
815
- "sort_columns": [
816
- {
817
- "column": "item_code",
818
- "direction": "ASC"
819
- },
820
- {
821
- "column": "item_id",
822
- "direction": "ASC"
823
- }
824
- ]
825
- },
826
- timestamp: new Date().toISOString()
827
- });
828
- }
829
-
830
- // req.log.debug('Request body item/lookup:', JSON.stringify(req.body, null, 2));
831
-
832
- // Cek apakah ada where clause (format baru) atau selected_tag (format lama)
833
- if (req.body.where) {
834
- // Format baru dengan where clause
835
- console.log('Using new format with where clause');
836
-
837
- // Validasi format where
838
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
839
- return res.status(400).json({
840
- success: false,
841
- error: 'Invalid where format',
842
- message: 'Invalid where format',
843
- example: {
844
- "where": [
845
- { "key": "item_id", "value": "AP2506-01" }
846
- ],
847
- "select": ["id", "item_id"]
848
- },
849
- example_advanced: {
850
- "where": {
851
- "logic": "OR",
852
- "conditions": [
853
- { "key": "item_id", "operator": "like", "value": "%Portal%" },
854
- { "key": "item_id", "operator": "like", "value": "%system%", "sensitive": false }
855
- ]
856
- },
857
- "select": ["id", "item_code", "item_name"]
858
- },
859
- timestamp: new Date().toISOString()
860
- });
861
- }
862
-
863
- // Validasi select fields jika ada (support SQL expressions)
864
- if (req.body.select && Array.isArray(req.body.select)) {
865
- const validFields = ["item_id","item_code","item_name","description","uom","unit_price","weight","is_active","created_at","created_by","updated_at","updated_by"];
866
- const invalidFields = [];
867
-
868
- // Check setiap field dalam select
869
- for (const field of req.body.select) {
870
- // Skip validasi untuk field 'id'
871
- if (field.toLowerCase() === 'id') {
872
- continue;
873
- }
874
-
875
- // Skip validasi jika ada SQL expression dengan AS (alias)
876
- if (/\s+as\s+\w+$/i.test(field)) {
877
- continue;
878
- }
879
-
880
- // Skip validasi jika ada SQL operators/functions (||, CONCAT, dll)
881
- if (/\|\||CONCAT|COALESCE|CASE|WHEN/i.test(field)) {
882
- continue;
883
- }
884
-
885
- // Validasi normal field name
886
- if (!validFields.includes(field)) {
887
- invalidFields.push(field);
888
- }
889
- }
890
-
891
- if (invalidFields.length > 0) {
892
- return res.status(400).json({
893
- success: false,
894
- error: 'Invalid select fields',
895
- message: `Invalid field(s): ${invalidFields.join(', ')}`,
896
- validFields: validFields,
897
- sqlExpressionNote: 'SQL expressions dengan operator || atau AS alias diperbolehkan',
898
- timestamp: new Date().toISOString()
899
- });
900
- }
901
- }
902
-
903
- // Gunakan method getLookupDataWithFilter
904
- const list = await itemModel.getLookupDataWithFilter(req.body);
905
-
906
- console.log(`Lookup with filter results: ${list.length} records found`);
907
- return res.json({
908
- success: true,
909
- count: list.length,
910
- data: list,
911
- query: req.body,
912
- timestamp: new Date().toISOString()
913
- });
914
-
915
- } else {
916
- // Format lama atau request tanpa WHERE clause tetapi dengan SELECT
917
- console.log('Using legacy format with selected_tag or select-only request');
918
-
919
- const selectedTag = req.body.selected_tag || '';
920
- console.log(`item static lookup request with selected_tag: ${selectedTag}`);
921
-
922
- // Jika ada field select atau sort_columns, gunakan getLookupDataWithFilter dengan body yang dimodifikasi
923
- if ((req.body.select && Array.isArray(req.body.select)) || (req.body.sort_columns && Array.isArray(req.body.sort_columns))) {
924
- console.log('Found select fields or sort_columns, using getLookupDataWithFilter');
925
-
926
- // Buat body baru dengan where kosong untuk mengambil semua data
927
- const modifiedBody = {
928
- ...req.body,
929
- where: [] // Empty where untuk mengambil semua data
930
- };
931
-
932
- const list = await itemModel.getLookupDataWithFilter(modifiedBody);
933
-
934
- console.log(`Static lookup with select/order results: ${list.length} records found`);
935
- return res.json({
936
- success: true,
937
- count: list.length,
938
- data: list,
939
- selectedTag: selectedTag,
940
- query: modifiedBody,
941
- timestamp: new Date().toISOString()
942
- });
943
- } else {
944
- // Gunakan model untuk mendapatkan data dengan method lama
945
- const list = await itemModel.getStaticLookupData(selectedTag);
946
-
947
- console.log(`Static lookup results: ${list.length} records found`);
948
- return res.json({
949
- success: true,
950
- count: list.length,
951
- data: list,
952
- selectedTag: selectedTag,
953
- timestamp: new Date().toISOString()
954
- });
955
- }
956
- }
957
-
958
- } catch (error) {
959
- console.error('Error in item static lookup:', error);
960
- return res.status(500).json({
961
- success: false,
962
- error: 'Internal Server Error',
963
- message: 'An error occurred while fetching item data',
964
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
965
- timestamp: new Date().toISOString()
966
- });
967
- }
968
- });
969
-
970
-
971
- // POST /api/mini-inventory/item/read - Manual pagination endpoint
972
- router.post('/read', async (req, res) => {
973
- try {
974
- // req.log.debug('Request body item/read:', JSON.stringify(req.body, null, 2));
975
-
976
- // Validasi request body
977
- if (!req.body || Object.keys(req.body).length === 0) {
978
- return res.status(400).json({
979
- success: false,
980
- error: 'Invalid payload',
981
- message: 'Payload cannot be empty',
982
- timestamp: new Date().toISOString()
983
- });
984
- }
985
-
986
- // Deteksi mode: paginasi (page dikirim) atau non-paginasi (page tidak dikirim)
987
- const paginate = req.body.page !== undefined;
988
- const page = paginate ? parseInt(req.body.page, 10) : null;
989
- const perPage = paginate ? parseInt(req.body.per_page || 10, 10) : null;
990
- const limit = !paginate ? Math.min(Math.max(parseInt(req.body.limit || 1000, 10), 1), 5000) : null;
991
- const searchValue = req.body.search_value || '';
992
- const searchBy = req.body.search_by || 'item_id';
993
-
994
- // Parse sort_columns
995
- let sort_columns = [];
996
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
997
- sort_columns = req.body.sort_columns.map(item => ({
998
- column: item.column,
999
- direction: (item.direction || 'ASC').toUpperCase()
1000
- }));
1001
- }
1002
-
1003
- // Proses parameter where dengan format advanced conditions
1004
- let where = null;
1005
- if (req.body.where && typeof req.body.where === 'object') {
1006
- if (Array.isArray(req.body.where) || (req.body.where.conditions && Array.isArray(req.body.where.conditions))) {
1007
- where = req.body.where;
1008
- }
1009
- }
1010
-
1011
- // Proses parameter select untuk kolom selektif
1012
- const validFields = ["item_id","item_code","item_name","description","uom","unit_price","weight","is_active","created_at","created_by","updated_at","updated_by"];
1013
- let select = null;
1014
- if (req.body.select && Array.isArray(req.body.select)) {
1015
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
1016
- if (invalidFields.length > 0) {
1017
- return res.status(400).json({
1018
- success: false,
1019
- error: 'Invalid select fields',
1020
- message: `Invalid field(s): ${invalidFields.join(', ')}`,
1021
- validFields: validFields,
1022
- timestamp: new Date().toISOString()
1023
- });
1024
- }
1025
- select = req.body.select;
1026
- }
1027
-
1028
- // Validasi parameter paginasi (hanya jika mode paginasi)
1029
- if (paginate) {
1030
- if (page < 1) {
1031
- return res.status(400).json({
1032
- success: false,
1033
- message: 'Validation failed',
1034
- errors: {
1035
- page: ['Page must be greater than 0']
1036
- }
1037
- });
1038
- }
1039
-
1040
- if (perPage < 1 || perPage > 100) {
1041
- return res.status(400).json({
1042
- success: false,
1043
- message: 'Validation failed',
1044
- errors: {
1045
- per_page: ['Per page must be between 1 and 100']
1046
- }
1047
- });
1048
- }
1049
- }
1050
-
1051
- if (searchValue.length > 255) {
1052
- return res.status(400).json({
1053
- success: false,
1054
- message: 'Validation failed',
1055
- errors: {
1056
- search_value: ['Search value must not exceed 255 characters']
1057
- }
1058
- });
1059
- }
1060
-
1061
- // Validasi kolom searching
1062
- if (!validFields.includes(searchBy)) {
1063
- return res.status(400).json({
1064
- success: false,
1065
- message: 'Validation failed',
1066
- errors: {
1067
- search_by: [`Invalid search field. Valid fields: ${validFields.join(', ')}`]
1068
- }
1069
- });
1070
- }
1071
-
1072
- // Build options untuk model
1073
- const options = {
1074
- searchValue: searchValue,
1075
- searchBy: searchBy,
1076
- sort_columns: sort_columns,
1077
- where: where,
1078
- select: select
1079
- };
1080
-
1081
- if (paginate) {
1082
- options.page = page;
1083
- options.perPage = perPage;
1084
- options.offset = (page - 1) * perPage;
1085
- } else {
1086
- options.limit = limit;
1087
- }
1088
-
1089
- // Gunakan model untuk mendapatkan data list
1090
- const result = await itemModel.getList(options);
1091
-
1092
- // Format response berdasarkan mode
1093
- if (paginate) {
1094
- const totalPages = Math.ceil(result.totalRecords / perPage);
1095
- const hasNext = page < totalPages;
1096
- const hasPrevious = page > 1;
1097
-
1098
- return res.json({
1099
- success: true,
1100
- data: result.data || [],
1101
- count: result.data ? result.data.length : 0,
1102
- pagination: {
1103
- current_page: page,
1104
- per_page: perPage,
1105
- total_records: result.totalRecords || 0,
1106
- total_pages: totalPages,
1107
- has_next: hasNext,
1108
- has_previous: hasPrevious
1109
- }
1110
- });
1111
- } else {
1112
- return res.json({
1113
- success: true,
1114
- data: result.data || [],
1115
- count: result.data ? result.data.length : 0
1116
- });
1117
- }
1118
-
1119
- } catch (error) {
1120
- console.error('Error in item list:', error);
1121
- const statusCode = error.statusCode || 500;
1122
- return res.status(statusCode).json({
1123
- success: false,
1124
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
1125
- message: statusCode === 400 ? error.message : 'An error occurred while fetching item list data',
1126
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
1127
- timestamp: new Date().toISOString()
1128
- });
1129
- }
1130
- });
1131
-
1132
-
1133
-
1134
- // Endpoint info — self-documenting API
1135
- router.get('/info', async (req, res) => {
1136
- try {
1137
- const actions = {"datatables":true,"read":true,"first":false,"create":true,"update":true,"delete":true,"lookup":true,"export":false,"import":false,"workflow":false,"info":true};
1138
-
1139
- const modelInfo = await itemModel.getModelInfo(actions);
1140
-
1141
- res.json({
1142
- success: true,
1143
- endpoint: 'item',
1144
- module: 'mini-inventory',
1145
- table: modelInfo.table,
1146
- fields: modelInfo.fields,
1147
- querySources: modelInfo.querySources,
1148
- actions: actions,
1149
- databaseType: 'postgres',
1150
- generated: '2026-04-25 07:34:33',
1151
- timestamp: new Date().toISOString()
1152
- });
1153
- } catch (error) {
1154
- console.error('Error in item info:', error);
1155
- res.status(500).json({
1156
- success: false,
1157
- error: 'Internal Server Error',
1158
- message: 'An error occurred while fetching endpoint information',
1159
- timestamp: new Date().toISOString()
1160
- });
1161
- }
1162
- });
1163
-
1164
- // Health check endpoint
1165
- router.get('/health', (req, res) => {
1166
- res.json({
1167
- status: 'ok',
1168
- endpoint: 'item',
1169
- timestamp: new Date().toISOString()
1170
- });
1171
- });
1172
-
1173
- module.exports = router;