@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,895 +0,0 @@
1
- 'use strict';
2
-
3
- const test = require('node:test');
4
- const { before, after } = require('node:test');
5
- const assert = require('node:assert');
6
- const fs = require('fs');
7
- const os = require('os');
8
- const path = require('path');
9
-
10
- const DashboardGenerator = require('../../../lib/generators/dashboard-generator');
11
- const ConfigReader = require('../../../lib/config/config-reader');
12
-
13
- const SAMPLE_PAYLOAD_PATH = path.resolve(
14
- __dirname,
15
- '../../../../restforge/docs/architecture/dashboard-architecture/payload/dashboard-sales.json'
16
- );
17
-
18
- const SAMPLE_PAYLOAD_DIR = path.dirname(SAMPLE_PAYLOAD_PATH);
19
-
20
- function loadSamplePayload() {
21
- return JSON.parse(fs.readFileSync(SAMPLE_PAYLOAD_PATH, 'utf8'));
22
- }
23
-
24
- function makeTmpDir() {
25
- return fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-test-'));
26
- }
27
-
28
- /**
29
- * Override ConfigReader.getWorkingDirectory selama eksekusi callback.
30
- * Pendekatan ini menghindari mutasi process.cwd() global yang bisa
31
- * bocor antar test paralel.
32
- */
33
- function withWorkingDir(dir, fn) {
34
- const original = ConfigReader.getWorkingDirectory;
35
- ConfigReader.getWorkingDirectory = () => dir;
36
- try {
37
- return fn();
38
- } finally {
39
- ConfigReader.getWorkingDirectory = original;
40
- }
41
- }
42
-
43
- // Suppress noisy console output dari FileUtils.setupModuleDirectories
44
- // tanpa menyembunyikan error sebenarnya.
45
- let _originalLog;
46
- let _originalWarn;
47
- before(() => {
48
- _originalLog = console.log;
49
- _originalWarn = console.warn;
50
- console.log = () => {};
51
- console.warn = () => {};
52
- });
53
- after(() => {
54
- console.log = _originalLog;
55
- console.warn = _originalWarn;
56
- });
57
-
58
- // ===== collectSqlFileReferences =====
59
-
60
- test('collectSqlFileReferences: payload sample mengembalikan 4 file SQL relatif terhadap payload dir', () => {
61
- const payload = loadSamplePayload();
62
- const refs = DashboardGenerator.collectSqlFileReferences(payload.widgets);
63
- const sorted = [...refs].sort();
64
- const expected = [
65
- 'query/author-sales.sql',
66
- 'query/sales-statistics.sql',
67
- 'query/sales-this-months-points.sql',
68
- 'query/sales-this-months-value.sql'
69
- ].sort();
70
- assert.deepStrictEqual(sorted, expected);
71
- });
72
-
73
- // ===== createDashboardModule: output struktur =====
74
-
75
- test('createDashboardModule: file output dibuat di src/modules/{project}/dash-{name}.js', () => {
76
- const tmpDir = makeTmpDir();
77
- withWorkingDir(tmpDir, () => {
78
- const payload = loadSamplePayload();
79
- const result = DashboardGenerator.createDashboardModule(
80
- 'mini-inventory', 'dash-sales', payload,
81
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
82
- );
83
- const expected = path.join(
84
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
85
- );
86
- assert.strictEqual(result.filePath, expected);
87
- assert.strictEqual(result.generated, true);
88
- assert.strictEqual(fs.existsSync(expected), true);
89
- });
90
- });
91
-
92
- test('createDashboardModule: folder src/models/{project}/dash-{name}/query/ TIDAK dibuat (SQL embedded, no copy)', () => {
93
- const tmpDir = makeTmpDir();
94
- withWorkingDir(tmpDir, () => {
95
- const payload = loadSamplePayload();
96
- DashboardGenerator.createDashboardModule(
97
- 'mini-inventory', 'dash-sales', payload,
98
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
99
- );
100
- const dashboardQueryDir = path.join(
101
- tmpDir, 'src', 'models', 'mini-inventory', 'dash-sales', 'query'
102
- );
103
- assert.strictEqual(
104
- fs.existsSync(dashboardQueryDir),
105
- false,
106
- 'folder dashboard query tidak boleh terbuat (SQL sudah embedded di JS)'
107
- );
108
- });
109
- });
110
-
111
- test('createDashboardModule: output mengandung require express, dashboard-helpers, db', () => {
112
- const tmpDir = makeTmpDir();
113
- withWorkingDir(tmpDir, () => {
114
- const payload = loadSamplePayload();
115
- DashboardGenerator.createDashboardModule(
116
- 'mini-inventory', 'dash-sales', payload,
117
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
118
- );
119
- const filePath = path.join(
120
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
121
- );
122
- const content = fs.readFileSync(filePath, 'utf8');
123
- assert.ok(content.includes("require('express')"), 'harus require express');
124
- assert.ok(
125
- content.includes("require('restforgejs/src/utils/dashboard-helpers')"),
126
- 'harus require dashboard-helpers via npm package style restforgejs/src/utils/...'
127
- );
128
- assert.ok(
129
- content.includes("require('restforgejs/src/utils/db')"),
130
- 'harus require db utility via npm package style'
131
- );
132
- assert.ok(
133
- !content.includes("require('../../utils/"),
134
- 'tidak boleh emit relative path require ../../utils/... — anti-pattern di deployment'
135
- );
136
- });
137
- });
138
-
139
- test('createDashboardModule: output mengandung paramsContract constant', () => {
140
- const tmpDir = makeTmpDir();
141
- withWorkingDir(tmpDir, () => {
142
- const payload = loadSamplePayload();
143
- DashboardGenerator.createDashboardModule(
144
- 'mini-inventory', 'dash-sales', payload,
145
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
146
- );
147
- const filePath = path.join(
148
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
149
- );
150
- const content = fs.readFileSync(filePath, 'utf8');
151
- assert.match(content, /const paramsContract\s*=/);
152
- });
153
- });
154
-
155
- test('createDashboardModule: output mengandung allWidgetIds array literal dengan ID widget yang benar', () => {
156
- const tmpDir = makeTmpDir();
157
- withWorkingDir(tmpDir, () => {
158
- const payload = loadSamplePayload();
159
- DashboardGenerator.createDashboardModule(
160
- 'mini-inventory', 'dash-sales', payload,
161
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
162
- );
163
- const filePath = path.join(
164
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
165
- );
166
- const content = fs.readFileSync(filePath, 'utf8');
167
- assert.match(content, /const allWidgetIds\s*=\s*\[/);
168
- assert.ok(content.includes('"author_sales"'), 'allWidgetIds harus mengandung author_sales');
169
- assert.ok(content.includes('"sales_statistics"'), 'allWidgetIds harus mengandung sales_statistics');
170
- assert.ok(content.includes('"sales_this_months"'), 'allWidgetIds harus mengandung sales_this_months');
171
- });
172
- });
173
-
174
- test('createDashboardModule: output mengandung router.post handler /dashboard', () => {
175
- const tmpDir = makeTmpDir();
176
- withWorkingDir(tmpDir, () => {
177
- const payload = loadSamplePayload();
178
- DashboardGenerator.createDashboardModule(
179
- 'mini-inventory', 'dash-sales', payload,
180
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
181
- );
182
- const filePath = path.join(
183
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
184
- );
185
- const content = fs.readFileSync(filePath, 'utf8');
186
- assert.ok(
187
- content.includes("router.post('/dashboard', async (req, res) => {"),
188
- 'output harus emit router.post handler untuk /dashboard'
189
- );
190
- });
191
- });
192
-
193
- test('createDashboardModule: output mengandung post-processing loop lowercaseKeysDeep + stringifyNumericDeep sebelum res.json', () => {
194
- const tmpDir = makeTmpDir();
195
- withWorkingDir(tmpDir, () => {
196
- const payload = loadSamplePayload();
197
- DashboardGenerator.createDashboardModule(
198
- 'mini-inventory', 'dash-sales', payload,
199
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
200
- );
201
- const filePath = path.join(
202
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
203
- );
204
- const content = fs.readFileSync(filePath, 'utf8');
205
- assert.ok(
206
- content.includes('helpers.lowercaseKeysDeep'),
207
- 'output harus memanggil helpers.lowercaseKeysDeep untuk normalisasi key Oracle UPPERCASE'
208
- );
209
- assert.ok(
210
- content.includes('helpers.stringifyNumericDeep'),
211
- 'output harus memanggil helpers.stringifyNumericDeep untuk normalisasi numeric → string'
212
- );
213
- const lowerIdx = content.indexOf('helpers.lowercaseKeysDeep');
214
- const stringifyIdx = content.indexOf('helpers.stringifyNumericDeep');
215
- const resJsonIdx = content.indexOf('res.json(response)');
216
- assert.ok(
217
- lowerIdx < stringifyIdx,
218
- 'lowercaseKeysDeep harus dipanggil sebelum stringifyNumericDeep'
219
- );
220
- assert.ok(
221
- stringifyIdx < resJsonIdx,
222
- 'post-processing loop harus dipanggil sebelum res.json(response)'
223
- );
224
- assert.match(
225
- content,
226
- /const response\s*=\s*\{\s*success:\s*true,\s*data\s*\};/,
227
- 'response envelope dibangun ke variable response sebelum res.json'
228
- );
229
- });
230
- });
231
-
232
- test('createDashboardModule: output mengandung Promise.allSettled call untuk eksekusi paralel', () => {
233
- const tmpDir = makeTmpDir();
234
- withWorkingDir(tmpDir, () => {
235
- const payload = loadSamplePayload();
236
- DashboardGenerator.createDashboardModule(
237
- 'mini-inventory', 'dash-sales', payload,
238
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
239
- );
240
- const filePath = path.join(
241
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
242
- );
243
- const content = fs.readFileSync(filePath, 'utf8');
244
- assert.ok(content.includes('Promise.allSettled'), 'harus pakai Promise.allSettled');
245
- });
246
- });
247
-
248
- test('createDashboardModule: output mengandung module.exports = router', () => {
249
- const tmpDir = makeTmpDir();
250
- withWorkingDir(tmpDir, () => {
251
- const payload = loadSamplePayload();
252
- DashboardGenerator.createDashboardModule(
253
- 'mini-inventory', 'dash-sales', payload,
254
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
255
- );
256
- const filePath = path.join(
257
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
258
- );
259
- const content = fs.readFileSync(filePath, 'utf8');
260
- assert.ok(content.includes('module.exports = router'));
261
- });
262
- });
263
-
264
- test('createDashboardModule: payload tanpa params mengemit paramsContract = {}', () => {
265
- const tmpDir = makeTmpDir();
266
- withWorkingDir(tmpDir, () => {
267
- const payload = loadSamplePayload();
268
- assert.ok(!('params' in payload), 'sample payload tidak punya params');
269
- DashboardGenerator.createDashboardModule(
270
- 'mini-inventory', 'dash-sales', payload,
271
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
272
- );
273
- const filePath = path.join(
274
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
275
- );
276
- const content = fs.readFileSync(filePath, 'utf8');
277
- assert.match(content, /const paramsContract\s*=\s*\{\}/);
278
- });
279
- });
280
-
281
- // ===== SQL embedded =====
282
-
283
- test('createDashboardModule: SQL author-sales.sql ter-embed apa adanya di output (string match)', () => {
284
- const tmpDir = makeTmpDir();
285
- withWorkingDir(tmpDir, () => {
286
- const payload = loadSamplePayload();
287
- DashboardGenerator.createDashboardModule(
288
- 'mini-inventory', 'dash-sales', payload,
289
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
290
- );
291
- const filePath = path.join(
292
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
293
- );
294
- const content = fs.readFileSync(filePath, 'utf8');
295
- assert.ok(content.includes('country AS label'), 'fragment SQL author-sales harus muncul di output');
296
- assert.ok(content.includes('SUM(amount) AS value'), 'fragment SQL author-sales harus muncul di output');
297
- assert.ok(content.includes('GROUP BY country'), 'fragment SQL author-sales harus muncul di output');
298
- });
299
- });
300
-
301
- test('createDashboardModule: SQL semua 4 fixture file dashboard-sales ter-embed', () => {
302
- const tmpDir = makeTmpDir();
303
- withWorkingDir(tmpDir, () => {
304
- const payload = loadSamplePayload();
305
- DashboardGenerator.createDashboardModule(
306
- 'mini-inventory', 'dash-sales', payload,
307
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
308
- );
309
- const filePath = path.join(
310
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
311
- );
312
- const content = fs.readFileSync(filePath, 'utf8');
313
-
314
- // Marker khas tiap fixture
315
- assert.ok(content.includes('country AS label'), 'author-sales.sql harus muncul');
316
- assert.ok(content.includes('product_name AS label'), 'sales-statistics.sql harus muncul');
317
- assert.ok(
318
- content.includes("date_trunc('month', sale_date)"),
319
- 'sales-this-months-*.sql harus muncul'
320
- );
321
- assert.ok(
322
- content.includes("TO_CHAR(sale_date, 'YYYY-MM-DD') AS period"),
323
- 'sales-this-months-points.sql (varian points) harus muncul'
324
- );
325
- });
326
- });
327
-
328
- // ===== SoC lock + residue lock =====
329
-
330
- test('createDashboardModule: SoC lock — output tidak boleh emit field frontend (widgetType, __dashboardEmbed, title, subtitle)', () => {
331
- const tmpDir = makeTmpDir();
332
- withWorkingDir(tmpDir, () => {
333
- const payload = loadSamplePayload();
334
- DashboardGenerator.createDashboardModule(
335
- 'mini-inventory', 'dash-sales', payload,
336
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
337
- );
338
- const filePath = path.join(
339
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
340
- );
341
- const content = fs.readFileSync(filePath, 'utf8');
342
-
343
- assert.ok(!content.includes('widgetType'), 'tidak boleh emit widgetType');
344
- assert.ok(!content.includes('__dashboardEmbed'), 'tidak boleh emit __dashboardEmbed');
345
- assert.ok(!/['"]title['"]\s*:/.test(content), 'tidak boleh emit field title');
346
- assert.ok(!/['"]subtitle['"]\s*:/.test(content), 'tidak boleh emit field subtitle');
347
- });
348
- });
349
-
350
- test('createDashboardModule: residue lock — output tidak boleh mengandung pattern lama (widgetsConfig/queryDir/dashboardRunner/runDashboard/queryFile/queryFiles)', () => {
351
- const tmpDir = makeTmpDir();
352
- withWorkingDir(tmpDir, () => {
353
- const payload = loadSamplePayload();
354
- DashboardGenerator.createDashboardModule(
355
- 'mini-inventory', 'dash-sales', payload,
356
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
357
- );
358
- const filePath = path.join(
359
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
360
- );
361
- const content = fs.readFileSync(filePath, 'utf8');
362
-
363
- assert.ok(!content.includes('widgetsConfig'), 'residue widgetsConfig harus hilang');
364
- assert.ok(!content.includes('queryDir'), 'residue queryDir harus hilang');
365
- assert.ok(!content.includes('dashboardRunner'), 'residue dashboardRunner harus hilang');
366
- assert.ok(!content.includes('runDashboard'), 'residue runDashboard harus hilang');
367
- assert.ok(!content.includes('queryFile'), 'residue queryFile harus hilang');
368
- assert.ok(!content.includes('queryFiles'), 'residue queryFiles harus hilang');
369
- });
370
- });
371
-
372
- // ===== Inline SQL rejection =====
373
-
374
- test('createDashboardModule: widget dengan inline SQL (tidak pakai file: prefix) ditolak', () => {
375
- const tmpDir = makeTmpDir();
376
- withWorkingDir(tmpDir, () => {
377
- const payload = {
378
- widgets: [{ id: 'x', query: 'SELECT 1' }]
379
- };
380
- assert.throws(
381
- () => DashboardGenerator.createDashboardModule(
382
- 'mini-inventory', 'dash-sales', payload,
383
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
384
- ),
385
- /inline SQL is not supported/
386
- );
387
- });
388
- });
389
-
390
- // ===== Conflict handling =====
391
-
392
- test('createDashboardModule: file sudah ada tanpa force harus throw error', () => {
393
- const tmpDir = makeTmpDir();
394
- withWorkingDir(tmpDir, () => {
395
- const payload = loadSamplePayload();
396
- DashboardGenerator.createDashboardModule(
397
- 'mini-inventory', 'dash-sales', payload,
398
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
399
- );
400
- assert.throws(
401
- () => DashboardGenerator.createDashboardModule(
402
- 'mini-inventory', 'dash-sales', payload,
403
- { force: false, payloadDir: SAMPLE_PAYLOAD_DIR }
404
- ),
405
- /already exists/
406
- );
407
- });
408
- });
409
-
410
- test('createDashboardModule: file sudah ada dengan force=true harus archive lalu rewrite', () => {
411
- const tmpDir = makeTmpDir();
412
- withWorkingDir(tmpDir, () => {
413
- const payload = loadSamplePayload();
414
- DashboardGenerator.createDashboardModule(
415
- 'mini-inventory', 'dash-sales', payload,
416
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
417
- );
418
- const moduleDir = path.join(tmpDir, 'src', 'modules', 'mini-inventory');
419
- const liveFile = path.join(moduleDir, 'dash-sales.js');
420
- const before = fs.statSync(liveFile).mtimeMs;
421
-
422
- // Pastikan timestamp archive berbeda dengan menunggu sedikit
423
- const start = Date.now();
424
- while (Date.now() === start) { /* spin */ }
425
-
426
- const result = DashboardGenerator.createDashboardModule(
427
- 'mini-inventory', 'dash-sales', payload,
428
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
429
- );
430
-
431
- assert.ok(result.archivedPath, 'createDashboardModule harus return archivedPath bila override');
432
- assert.strictEqual(fs.existsSync(result.archivedPath), true);
433
-
434
- const files = fs.readdirSync(moduleDir);
435
- const archived = files.filter((f) => f.startsWith('dash-sales.js.archive.'));
436
- const live = files.filter((f) => f === 'dash-sales.js');
437
- assert.strictEqual(live.length, 1);
438
- assert.ok(archived.length >= 1, 'minimal satu archive file harus terbuat');
439
-
440
- const after = fs.statSync(liveFile).mtimeMs;
441
- assert.ok(after >= before, 'live file harus ditulis ulang');
442
- });
443
- });
444
-
445
- // ===== Signature & validation =====
446
-
447
- test('createDashboardModule: payloadDir wajib di options (throw bila tidak ada)', () => {
448
- const tmpDir = makeTmpDir();
449
- withWorkingDir(tmpDir, () => {
450
- const payload = loadSamplePayload();
451
- assert.throws(
452
- () => DashboardGenerator.createDashboardModule(
453
- 'mini-inventory', 'dash-sales', payload, { force: true }
454
- ),
455
- /payloadDir/
456
- );
457
- });
458
- });
459
-
460
- // ===== SQL escaping (optional defensive) =====
461
-
462
- test('createDashboardModule: SQL berisi backtick di-escape jadi \\` di template literal output', () => {
463
- const tmpDir = makeTmpDir();
464
- // Buat payloadDir custom dengan SQL fixture yang punya backtick
465
- const customPayloadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-fixture-'));
466
- fs.mkdirSync(path.join(customPayloadDir, 'query'));
467
- const sqlPath = path.join(customPayloadDir, 'query', 'tick.sql');
468
- fs.writeFileSync(sqlPath, 'SELECT `col` FROM tbl', 'utf8');
469
-
470
- withWorkingDir(tmpDir, () => {
471
- const payload = {
472
- widgets: [{ id: 'tick_widget', query: 'file:query/tick.sql' }]
473
- };
474
- DashboardGenerator.createDashboardModule(
475
- 'mini-inventory', 'dash-tick', payload,
476
- { force: true, payloadDir: customPayloadDir }
477
- );
478
- const filePath = path.join(
479
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-tick.js'
480
- );
481
- const content = fs.readFileSync(filePath, 'utf8');
482
- assert.ok(
483
- content.includes('SELECT \\`col\\` FROM tbl'),
484
- 'backtick di SQL harus di-escape jadi \\`'
485
- );
486
- });
487
- });
488
-
489
- test('createDashboardModule: SQL berisi ${...} di-escape jadi \\${ di template literal output', () => {
490
- const tmpDir = makeTmpDir();
491
- const customPayloadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dash-gen-fixture-'));
492
- fs.mkdirSync(path.join(customPayloadDir, 'query'));
493
- const sqlPath = path.join(customPayloadDir, 'query', 'dollar.sql');
494
- fs.writeFileSync(sqlPath, 'SELECT * FROM t WHERE name = \'${suspect}\'', 'utf8');
495
-
496
- withWorkingDir(tmpDir, () => {
497
- const payload = {
498
- widgets: [{ id: 'dollar_widget', query: 'file:query/dollar.sql' }]
499
- };
500
- DashboardGenerator.createDashboardModule(
501
- 'mini-inventory', 'dash-dollar', payload,
502
- { force: true, payloadDir: customPayloadDir }
503
- );
504
- const filePath = path.join(
505
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-dollar.js'
506
- );
507
- const content = fs.readFileSync(filePath, 'utf8');
508
- assert.ok(
509
- content.includes('\\${suspect}'),
510
- 'dollar-brace di SQL harus di-escape jadi \\${'
511
- );
512
- });
513
- });
514
-
515
- // ===== Multi-dialect: option database propagation =====
516
-
517
- test('createDashboardModule: database=postgres → emit require restforgejs/src/utils/db dan const dialect = postgres', () => {
518
- const tmpDir = makeTmpDir();
519
- withWorkingDir(tmpDir, () => {
520
- const payload = loadSamplePayload();
521
- DashboardGenerator.createDashboardModule(
522
- 'mini-inventory', 'dash-sales', payload,
523
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'postgres' }
524
- );
525
- const filePath = path.join(
526
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
527
- );
528
- const content = fs.readFileSync(filePath, 'utf8');
529
- assert.ok(
530
- content.includes("require('restforgejs/src/utils/db')"),
531
- 'output harus require restforgejs/src/utils/db untuk dialect postgres'
532
- );
533
- assert.ok(
534
- content.includes("const dialect = 'postgres';"),
535
- "output harus emit const dialect = 'postgres'"
536
- );
537
- });
538
- });
539
-
540
- test('createDashboardModule: database=mysql → emit require restforgejs/src/utils/db-mysql dan const dialect = mysql', () => {
541
- const tmpDir = makeTmpDir();
542
- withWorkingDir(tmpDir, () => {
543
- const payload = loadSamplePayload();
544
- DashboardGenerator.createDashboardModule(
545
- 'mini-inventory', 'dash-sales', payload,
546
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'mysql' }
547
- );
548
- const filePath = path.join(
549
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
550
- );
551
- const content = fs.readFileSync(filePath, 'utf8');
552
- assert.ok(
553
- content.includes("require('restforgejs/src/utils/db-mysql')"),
554
- 'output harus require restforgejs/src/utils/db-mysql untuk dialect mysql'
555
- );
556
- assert.ok(
557
- content.includes("const dialect = 'mysql';"),
558
- "output harus emit const dialect = 'mysql'"
559
- );
560
- assert.ok(
561
- !content.includes("require('restforgejs/src/utils/db')\n"),
562
- 'output tidak boleh require db postgres saat dialect mysql'
563
- );
564
- });
565
- });
566
-
567
- test('createDashboardModule: database=oracle → emit require restforgejs/src/utils/db-oracle dan const dialect = oracle', () => {
568
- const tmpDir = makeTmpDir();
569
- withWorkingDir(tmpDir, () => {
570
- const payload = loadSamplePayload();
571
- DashboardGenerator.createDashboardModule(
572
- 'mini-inventory', 'dash-sales', payload,
573
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'oracle' }
574
- );
575
- const filePath = path.join(
576
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
577
- );
578
- const content = fs.readFileSync(filePath, 'utf8');
579
- assert.ok(
580
- content.includes("require('restforgejs/src/utils/db-oracle')"),
581
- 'output harus require restforgejs/src/utils/db-oracle untuk dialect oracle'
582
- );
583
- assert.ok(
584
- content.includes("const dialect = 'oracle';"),
585
- "output harus emit const dialect = 'oracle'"
586
- );
587
- });
588
- });
589
-
590
- test('createDashboardModule: database=sqlite (tidak didukung) → throw error dengan pesan jelas', () => {
591
- const tmpDir = makeTmpDir();
592
- withWorkingDir(tmpDir, () => {
593
- const payload = loadSamplePayload();
594
- assert.throws(
595
- () => DashboardGenerator.createDashboardModule(
596
- 'mini-inventory', 'dash-sales', payload,
597
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'sqlite' }
598
- ),
599
- (err) =>
600
- err instanceof Error &&
601
- /sqlite/.test(err.message) &&
602
- /Supported: postgres, mysql, oracle/.test(err.message)
603
- );
604
- });
605
- });
606
-
607
- test('createDashboardModule: options tanpa database → default postgres (backward compat caller existing)', () => {
608
- const tmpDir = makeTmpDir();
609
- withWorkingDir(tmpDir, () => {
610
- const payload = loadSamplePayload();
611
- DashboardGenerator.createDashboardModule(
612
- 'mini-inventory', 'dash-sales', payload,
613
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR }
614
- );
615
- const filePath = path.join(
616
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
617
- );
618
- const content = fs.readFileSync(filePath, 'utf8');
619
- assert.ok(
620
- content.includes("require('restforgejs/src/utils/db')"),
621
- 'tanpa options.database, output default ke require db postgres'
622
- );
623
- assert.ok(
624
- content.includes("const dialect = 'postgres';"),
625
- "tanpa options.database, output default emit const dialect = 'postgres'"
626
- );
627
- });
628
- });
629
-
630
- test('createDashboardModule: generated handler memanggil substitutePlaceholders dengan 3 argument (dialect)', () => {
631
- const tmpDir = makeTmpDir();
632
- withWorkingDir(tmpDir, () => {
633
- const payload = loadSamplePayload();
634
- DashboardGenerator.createDashboardModule(
635
- 'mini-inventory', 'dash-sales', payload,
636
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, database: 'mysql' }
637
- );
638
- const filePath = path.join(
639
- tmpDir, 'src', 'modules', 'mini-inventory', 'dash-sales.js'
640
- );
641
- const content = fs.readFileSync(filePath, 'utf8');
642
- assert.match(
643
- content,
644
- /helpers\.substitutePlaceholders\(\s*`[\s\S]*?`,\s*effectiveParams,\s*dialect\s*\)/,
645
- 'panggilan substitutePlaceholders harus menyertakan dialect sebagai argument ketiga'
646
- );
647
- });
648
- });
649
-
650
- // ============================================================================
651
- // CACHE WRAPPING (Phase 3)
652
- // ============================================================================
653
-
654
- function generateAndRead(projectName, dashboardName, payload, opts = {}) {
655
- const tmpDir = makeTmpDir();
656
- let content;
657
- withWorkingDir(tmpDir, () => {
658
- DashboardGenerator.createDashboardModule(
659
- projectName, dashboardName, payload,
660
- { force: true, payloadDir: SAMPLE_PAYLOAD_DIR, ...opts }
661
- );
662
- const filePath = path.join(
663
- tmpDir, 'src', 'modules', projectName, `${dashboardName}.js`
664
- );
665
- content = fs.readFileSync(filePath, 'utf8');
666
- });
667
- return content;
668
- }
669
-
670
- // ----- Cache disabled (3 test) -----
671
-
672
- test('cache: payload tanpa cache block — output TIDAK mengandung cacheManager / cache check / cache set', () => {
673
- const payload = loadSamplePayload(); // sample tidak punya cache
674
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
675
-
676
- assert.ok(
677
- !content.includes('cacheManager'),
678
- 'tanpa cache.enabled, generator TIDAK boleh emit referensi cacheManager'
679
- );
680
- assert.ok(
681
- !content.includes('Cache check'),
682
- 'tanpa cache.enabled, generator TIDAK boleh emit comment "Cache check"'
683
- );
684
- assert.ok(
685
- !content.includes('Cache set'),
686
- 'tanpa cache.enabled, generator TIDAK boleh emit comment "Cache set"'
687
- );
688
- assert.ok(
689
- !content.includes("require('restforgejs/src/utils/cache-manager')"),
690
- 'tanpa cache.enabled, generator TIDAK boleh require cache-manager'
691
- );
692
- });
693
-
694
- test('cache: payload dengan cache.enabled=false — output TIDAK mengandung cache wrapping', () => {
695
- const payload = { ...loadSamplePayload(), cache: { enabled: false } };
696
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
697
-
698
- assert.ok(!content.includes('cacheManager'), 'cache.enabled=false harus skip cache wrapping');
699
- assert.ok(!content.includes('Cache check'), 'comment Cache check harus absen');
700
- assert.ok(!content.includes('Cache set'), 'comment Cache set harus absen');
701
- });
702
-
703
- test('cache: output non-cache valid sebagai Express router (smoke parse via new Function)', () => {
704
- const payload = loadSamplePayload();
705
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
706
-
707
- assert.doesNotThrow(
708
- () => new Function(content),
709
- 'output non-cache harus parse-able sebagai JavaScript valid'
710
- );
711
- });
712
-
713
- // ----- Cache enabled (8 test) -----
714
-
715
- test('cache: payload dengan cache.enabled=true — output MENGANDUNG require cache-manager', () => {
716
- const payload = {
717
- ...loadSamplePayload(),
718
- cache: { enabled: true, invalidates: ['sales'] }
719
- };
720
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
721
-
722
- assert.ok(
723
- content.includes("require('restforgejs/src/utils/cache-manager')"),
724
- 'cache.enabled=true harus emit require cache-manager via npm package style'
725
- );
726
- assert.ok(
727
- content.includes('const cacheManager = '),
728
- 'output harus declare const cacheManager'
729
- );
730
- });
731
-
732
- test('cache: output mengandung buildKey dengan module/endpoint/type=dashboard', () => {
733
- const payload = {
734
- ...loadSamplePayload(),
735
- cache: { enabled: true, invalidates: ['sales'] }
736
- };
737
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
738
-
739
- assert.ok(content.includes('cacheManager.buildKey({'), 'harus emit buildKey call');
740
- assert.ok(
741
- content.includes("module: 'mini-inventory'"),
742
- 'buildKey module field harus literal projectName'
743
- );
744
- assert.ok(
745
- content.includes("endpoint: 'dash-sales'"),
746
- 'buildKey endpoint field harus literal dashboardName'
747
- );
748
- assert.ok(
749
- content.includes("type: 'dashboard'"),
750
- 'buildKey type field harus literal "dashboard"'
751
- );
752
- });
753
-
754
- test('cache: output mengandung crypto.createHash MD5 + cabang isEmpty="all"', () => {
755
- const payload = {
756
- ...loadSamplePayload(),
757
- cache: { enabled: true, invalidates: ['sales'] }
758
- };
759
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
760
-
761
- assert.ok(
762
- content.includes("crypto.createHash('md5')"),
763
- 'identifier hash WAJIB pakai MD5'
764
- );
765
- assert.ok(
766
- content.includes(".substring(0, 8)"),
767
- 'hash harus dipotong 8 karakter'
768
- );
769
- assert.ok(
770
- content.includes("'all'"),
771
- 'output harus emit literal "all" untuk body kosong'
772
- );
773
- assert.match(
774
- content,
775
- /const isEmpty\s*=\s*Object\.keys\(body\)\.length\s*===\s*0/,
776
- 'isEmpty derivation dari Object.keys(req.body).length'
777
- );
778
- });
779
-
780
- test('cache: output mengandung cacheManager.set(cacheKey, response, <ttl>)', () => {
781
- const payload = {
782
- ...loadSamplePayload(),
783
- cache: { enabled: true, ttl: 600, invalidates: ['sales'] }
784
- };
785
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
786
-
787
- assert.ok(
788
- content.includes('cacheManager.set(cacheKey, response, 600)'),
789
- 'cache set harus pakai cacheKey + response + ttl literal'
790
- );
791
- assert.ok(
792
- content.includes('if (cacheKey) {'),
793
- 'cache set di-guard dengan if (cacheKey)'
794
- );
795
- });
796
-
797
- test('cache: TTL literal — cache.ttl=600 emit "600", tanpa ttl emit "null"', () => {
798
- // case 1: cache.ttl=600
799
- const withTtl = generateAndRead('mini-inventory', 'dash-sales', {
800
- ...loadSamplePayload(),
801
- cache: { enabled: true, ttl: 600, invalidates: ['sales'] }
802
- });
803
- assert.ok(
804
- withTtl.includes('cacheManager.set(cacheKey, response, 600)'),
805
- 'ttl=600 harus emit literal numeric 600'
806
- );
807
-
808
- // case 2: tanpa ttl
809
- const withoutTtl = generateAndRead('mini-inventory', 'dash-sales', {
810
- ...loadSamplePayload(),
811
- cache: { enabled: true, invalidates: ['sales'] }
812
- });
813
- assert.ok(
814
- withoutTtl.includes('cacheManager.set(cacheKey, response, null)'),
815
- 'tanpa ttl harus emit literal null (fallback ke env CACHE_TTL)'
816
- );
817
- });
818
-
819
- test('cache: cache check muncul sebelum Promise.allSettled, cache set setelah loop normalisasi', () => {
820
- const payload = {
821
- ...loadSamplePayload(),
822
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
823
- };
824
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
825
-
826
- const cacheCheckIdx = content.indexOf('// --- Cache check');
827
- const allSettledIdx = content.indexOf('Promise.allSettled');
828
- const stringifyIdx = content.indexOf('helpers.stringifyNumericDeep');
829
- const cacheSetIdx = content.indexOf('// --- Cache set');
830
- const resJsonIdx = content.indexOf('res.json(response)');
831
-
832
- assert.ok(cacheCheckIdx > 0, 'cache check block harus ada');
833
- assert.ok(cacheSetIdx > 0, 'cache set block harus ada');
834
- assert.ok(
835
- cacheCheckIdx < allSettledIdx,
836
- 'cache check WAJIB muncul sebelum Promise.allSettled'
837
- );
838
- assert.ok(
839
- stringifyIdx < cacheSetIdx,
840
- 'cache set WAJIB muncul setelah loop normalisasi (stringifyNumericDeep)'
841
- );
842
- assert.ok(
843
- cacheSetIdx < resJsonIdx,
844
- 'cache set WAJIB muncul sebelum res.json(response)'
845
- );
846
- });
847
-
848
- test('cache: generated code dengan cache wrapping tetap parse valid (smoke)', () => {
849
- const payload = {
850
- ...loadSamplePayload(),
851
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
852
- };
853
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
854
-
855
- assert.doesNotThrow(
856
- () => new Function(content),
857
- 'output dengan cache wrapping harus parse-able sebagai JavaScript valid'
858
- );
859
- });
860
-
861
- test('cache: cache check guard menggunakan getter cacheManager.enabled (bukan isEnabled())', () => {
862
- const payload = {
863
- ...loadSamplePayload(),
864
- cache: { enabled: true, invalidates: ['sales'] }
865
- };
866
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
867
-
868
- assert.ok(
869
- content.includes('if (cacheManager.enabled) {'),
870
- 'cache check WAJIB pakai getter cacheManager.enabled, bukan cacheManager.isEnabled()'
871
- );
872
- assert.ok(
873
- !content.includes('cacheManager.isEnabled()'),
874
- 'cache check TIDAK boleh pakai method isEnabled() — gunakan getter enabled'
875
- );
876
- });
877
-
878
- test('cache: cache hit branch menjalankan early return res.json(cached)', () => {
879
- const payload = {
880
- ...loadSamplePayload(),
881
- cache: { enabled: true, ttl: 300, invalidates: ['sales'] }
882
- };
883
- const content = generateAndRead('mini-inventory', 'dash-sales', payload);
884
-
885
- assert.match(
886
- content,
887
- /const cached\s*=\s*await cacheManager\.get\(cacheKey\)/,
888
- 'cache check harus await cacheManager.get(cacheKey)'
889
- );
890
- assert.match(
891
- content,
892
- /if \(cached\)\s*return res\.json\(cached\)/,
893
- 'cache hit harus early-return res.json(cached)'
894
- );
895
- });