@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
@@ -0,0 +1,703 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Diff Engine — compare dua IR (Intermediate Representation) dari dbschema-kit
5
+ * dan hasilkan structured delta. Operasi bersifat semantic-level, bukan
6
+ * text-level: nama field, tipe ter-normalisasi, length, nullability, primary
7
+ * key, index, unique constraint, dan foreign key di-compare langsung.
8
+ *
9
+ * Input IR diharapkan kompatibel dengan output `defineModel()` (SDF) maupun
10
+ * `mapTableMetaToIR()` (database introspection). Diff engine tidak melakukan
11
+ * I/O dan tidak memodifikasi input.
12
+ *
13
+ * @module lib/dbschema-kit/diff-engine
14
+ */
15
+
16
+ // ─────────────────────────────────────────────────────────────
17
+ // Type normalization (alias mapping per dialect)
18
+ // ─────────────────────────────────────────────────────────────
19
+
20
+ // Mapping kanonikal dari semua alias type (SDF + DB) ke representasi
21
+ // internal yang dipakai saat compare. Mapping ini intentionally konservatif:
22
+ // hanya alias yang umum di-emit oleh dialect driver yang di-cover.
23
+ const TYPE_ALIAS_MAP = {
24
+ // String varieties
25
+ 'string': 'string',
26
+ 'varchar': 'string',
27
+ 'character varying': 'string',
28
+ 'character': 'string',
29
+ 'char': 'string',
30
+ 'nvarchar': 'string',
31
+ 'nchar': 'string',
32
+ // Text
33
+ 'text': 'text',
34
+ 'clob': 'text',
35
+ 'nclob': 'text',
36
+ 'mediumtext': 'text',
37
+ 'longtext': 'text',
38
+ // Integer
39
+ 'integer': 'integer',
40
+ 'int': 'integer',
41
+ 'int4': 'integer',
42
+ 'int2': 'integer',
43
+ 'smallint': 'integer',
44
+ 'mediumint': 'integer',
45
+ // Bigint
46
+ 'bigint': 'bigint',
47
+ 'int8': 'bigint',
48
+ // Decimal / numeric
49
+ 'decimal': 'decimal',
50
+ 'numeric': 'decimal',
51
+ 'number': 'decimal',
52
+ // Boolean
53
+ 'boolean': 'boolean',
54
+ 'bool': 'boolean',
55
+ 'tinyint(1)': 'boolean',
56
+ // Date / time
57
+ 'date': 'date',
58
+ 'timestamp': 'timestamp',
59
+ 'timestamp without time zone': 'timestamp',
60
+ 'timestamp with time zone': 'timestamp',
61
+ 'timestamptz': 'timestamp',
62
+ 'datetime': 'timestamp',
63
+ // UUID
64
+ 'uuid': 'uuid',
65
+ // JSON
66
+ 'json': 'json',
67
+ 'jsonb': 'json'
68
+ };
69
+
70
+ function normalizeType(rawType) {
71
+ if (rawType === undefined || rawType === null) return null;
72
+ const key = String(rawType).toLowerCase().trim();
73
+ if (TYPE_ALIAS_MAP[key]) return TYPE_ALIAS_MAP[key];
74
+ return key;
75
+ }
76
+
77
+ // ─────────────────────────────────────────────────────────────
78
+ // Helpers
79
+ // ─────────────────────────────────────────────────────────────
80
+
81
+ function isPlainObject(v) {
82
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
83
+ }
84
+
85
+ function arrayEquals(a, b) {
86
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
87
+ if (a.length !== b.length) return false;
88
+ for (let i = 0; i < a.length; i++) {
89
+ if (a[i] !== b[i]) return false;
90
+ }
91
+ return true;
92
+ }
93
+
94
+ function sortedCopy(arr) {
95
+ return Array.isArray(arr) ? arr.slice().sort() : [];
96
+ }
97
+
98
+ function isNullable(field) {
99
+ // notnull=true berarti tidak nullable; notnull=undefined berarti default nullable.
100
+ return field && field.notnull === true ? false : true;
101
+ }
102
+
103
+ function resolvePrimaryKey(ir) {
104
+ // IR convention: primaryKey array hanya terisi untuk composite PK.
105
+ // Single PK ditandai via fields[name].pk = true.
106
+ if (!ir || !isPlainObject(ir)) return [];
107
+ if (Array.isArray(ir.primaryKey) && ir.primaryKey.length > 0) {
108
+ return ir.primaryKey.slice();
109
+ }
110
+ const out = [];
111
+ const fields = isPlainObject(ir.fields) ? ir.fields : {};
112
+ for (const [name, field] of Object.entries(fields)) {
113
+ if (field && field.pk === true) out.push(name);
114
+ }
115
+ return out;
116
+ }
117
+
118
+ // ─────────────────────────────────────────────────────────────
119
+ // Field comparison
120
+ // ─────────────────────────────────────────────────────────────
121
+
122
+ function buildFieldSummary(name, field) {
123
+ const out = { name };
124
+ if (field) {
125
+ if (field.type !== undefined) out.type = field.type;
126
+ if (field.length !== undefined) out.length = field.length;
127
+ if (field.precision !== undefined) out.precision = field.precision;
128
+ if (field.scale !== undefined) out.scale = field.scale;
129
+ out.nullable = isNullable(field);
130
+ } else {
131
+ out.nullable = true;
132
+ }
133
+ return out;
134
+ }
135
+
136
+ function compareFieldDetails(sdfField, dbField) {
137
+ const reasons = [];
138
+
139
+ const sdfType = normalizeType(sdfField && sdfField.type);
140
+ const dbType = normalizeType(dbField && dbField.type);
141
+ if (sdfType !== dbType) {
142
+ reasons.push(`type: SDF='${sdfField && sdfField.type}' vs DB='${dbField && dbField.type}'`);
143
+ }
144
+
145
+ // Length: relevan hanya untuk type=string. Untuk type lain (text/integer/dll.)
146
+ // length umumnya tidak digunakan dan diabaikan agar tidak false-positive.
147
+ if (sdfType === 'string' && dbType === 'string') {
148
+ const sdfLen = sdfField && sdfField.length;
149
+ const dbLen = dbField && dbField.length;
150
+ if (sdfLen !== undefined && dbLen !== undefined && Number(sdfLen) !== Number(dbLen)) {
151
+ reasons.push(`length: SDF=${sdfLen} vs DB=${dbLen}`);
152
+ }
153
+ }
154
+
155
+ // Precision / scale untuk decimal.
156
+ if (sdfType === 'decimal' && dbType === 'decimal') {
157
+ const sdfP = sdfField && sdfField.precision;
158
+ const dbP = dbField && dbField.precision;
159
+ if (sdfP !== undefined && dbP !== undefined && Number(sdfP) !== Number(dbP)) {
160
+ reasons.push(`precision: SDF=${sdfP} vs DB=${dbP}`);
161
+ }
162
+ const sdfS = sdfField && sdfField.scale;
163
+ const dbS = dbField && dbField.scale;
164
+ if (sdfS !== undefined && dbS !== undefined && Number(sdfS) !== Number(dbS)) {
165
+ reasons.push(`scale: SDF=${sdfS} vs DB=${dbS}`);
166
+ }
167
+ }
168
+
169
+ const sdfNullable = isNullable(sdfField);
170
+ const dbNullable = isNullable(dbField);
171
+ if (sdfNullable !== dbNullable) {
172
+ reasons.push(`nullable: SDF=${sdfNullable} vs DB=${dbNullable}`);
173
+ }
174
+
175
+ const defaultReason = compareDefaultValue(sdfField, dbField);
176
+ if (defaultReason) reasons.push(defaultReason);
177
+
178
+ return reasons;
179
+ }
180
+
181
+ // ─────────────────────────────────────────────────────────────
182
+ // Default value comparison
183
+ // ─────────────────────────────────────────────────────────────
184
+
185
+ // Kanonikal payload dari struct default { kind, value, name, args } untuk
186
+ // strict compare. Output adalah string yang stable (deterministic) sehingga
187
+ // equality check cukup dengan ===.
188
+ function canonicalDefaultPayload(def) {
189
+ if (!def || typeof def !== 'object') return '';
190
+ if (def.kind === 'literal') {
191
+ // JSON.stringify menjaga distingsi tipe (string vs number vs boolean).
192
+ return JSON.stringify(def.value);
193
+ }
194
+ if (def.kind === 'function') {
195
+ const name = String(def.name || '').toLowerCase();
196
+ const args = Array.isArray(def.args) ? def.args : [];
197
+ return `${name}(${args.join(',')})`;
198
+ }
199
+ if (def.kind === 'constant') {
200
+ return String(def.value == null ? '' : def.value).toLowerCase();
201
+ }
202
+ return '';
203
+ }
204
+
205
+ // Weak normalization untuk _defaultComment dan fallback string compare.
206
+ // Whitespace di-collapse, lowercase, lalu trim agar variasi cosmetic tidak
207
+ // memicu false-positive.
208
+ function normalizeWeakDefault(str) {
209
+ return String(str == null ? '' : str).toLowerCase().replace(/\s+/g, ' ').trim();
210
+ }
211
+
212
+ function describeDefault(def, comment) {
213
+ if (def && typeof def === 'object') {
214
+ return canonicalDefaultPayload(def);
215
+ }
216
+ if (comment) return `(comment) ${normalizeWeakDefault(comment)}`;
217
+ return '(none)';
218
+ }
219
+
220
+ function compareDefaultValue(sdfField, dbField) {
221
+ const sdfDef = sdfField && sdfField.default;
222
+ const dbDef = dbField && dbField.default;
223
+ const sdfComment = sdfField && sdfField._defaultComment;
224
+ const dbComment = dbField && dbField._defaultComment;
225
+
226
+ const sdfHasStruct = sdfDef !== undefined && sdfDef !== null;
227
+ const dbHasStruct = dbDef !== undefined && dbDef !== null;
228
+ const sdfHasComment = !!sdfComment;
229
+ const dbHasComment = !!dbComment;
230
+
231
+ // Case 1: keduanya tidak punya default sama sekali → match.
232
+ if (!sdfHasStruct && !dbHasStruct && !sdfHasComment && !dbHasComment) {
233
+ return null;
234
+ }
235
+
236
+ // Case 2: salah satu sisi punya signal default (struct atau comment),
237
+ // sisi lain kosong total → drift.
238
+ const sdfHasAny = sdfHasStruct || sdfHasComment;
239
+ const dbHasAny = dbHasStruct || dbHasComment;
240
+ if (sdfHasAny !== dbHasAny) {
241
+ return `default: SDF=${describeDefault(sdfDef, sdfComment)} vs DB=${describeDefault(dbDef, dbComment)}`;
242
+ }
243
+
244
+ // Case 3: keduanya punya struktur kanonikal → strict compare kind + value.
245
+ if (sdfHasStruct && dbHasStruct) {
246
+ if (sdfDef.kind !== dbDef.kind) {
247
+ return `default kind: SDF='${sdfDef.kind}' vs DB='${dbDef.kind}'`;
248
+ }
249
+ const sdfPayload = canonicalDefaultPayload(sdfDef);
250
+ const dbPayload = canonicalDefaultPayload(dbDef);
251
+ if (sdfPayload !== dbPayload) {
252
+ return `default: SDF=${sdfPayload} vs DB=${dbPayload}`;
253
+ }
254
+ return null;
255
+ }
256
+
257
+ // Case 4: minimal salah satu sisi hanya punya _defaultComment (atau struktur
258
+ // di satu sisi, comment di sisi lain). Fallback ke weak compare.
259
+ const sdfStr = normalizeWeakDefault(sdfHasStruct ? canonicalDefaultPayload(sdfDef) : sdfComment);
260
+ const dbStr = normalizeWeakDefault(dbHasStruct ? canonicalDefaultPayload(dbDef) : dbComment);
261
+ if (sdfStr !== dbStr) {
262
+ return `default (weak): SDF='${sdfStr}' vs DB='${dbStr}'`;
263
+ }
264
+ return null;
265
+ }
266
+
267
+ function diffFields(sdfFields, dbFields) {
268
+ const sdf = isPlainObject(sdfFields) ? sdfFields : {};
269
+ const db = isPlainObject(dbFields) ? dbFields : {};
270
+
271
+ const sdfNames = Object.keys(sdf);
272
+ const dbNames = Object.keys(db);
273
+
274
+ const onlyInSdf = [];
275
+ const onlyInDb = [];
276
+ const mismatched = [];
277
+
278
+ for (const name of sdfNames) {
279
+ if (!(name in db)) {
280
+ onlyInSdf.push(buildFieldSummary(name, sdf[name]));
281
+ }
282
+ }
283
+
284
+ for (const name of dbNames) {
285
+ if (!(name in sdf)) {
286
+ onlyInDb.push(buildFieldSummary(name, db[name]));
287
+ }
288
+ }
289
+
290
+ for (const name of sdfNames) {
291
+ if (!(name in db)) continue;
292
+ const reasons = compareFieldDetails(sdf[name], db[name]);
293
+ if (reasons.length > 0) {
294
+ mismatched.push({
295
+ name,
296
+ sdf: buildFieldSummary(name, sdf[name]),
297
+ db: buildFieldSummary(name, db[name]),
298
+ reasons
299
+ });
300
+ }
301
+ }
302
+
303
+ return { onlyInSdf, onlyInDb, mismatched };
304
+ }
305
+
306
+ // ─────────────────────────────────────────────────────────────
307
+ // Primary key comparison
308
+ // ─────────────────────────────────────────────────────────────
309
+
310
+ function diffPrimaryKey(sdfIR, dbIR) {
311
+ const sdf = resolvePrimaryKey(sdfIR);
312
+ const db = resolvePrimaryKey(dbIR);
313
+ // PK match jika kolom sama persis (order-sensitive: PK order matters di SQL).
314
+ const match = arrayEquals(sdf, db);
315
+ return { sdf, db, match };
316
+ }
317
+
318
+ // ─────────────────────────────────────────────────────────────
319
+ // Collection diff (indexes, uniques, foreign keys)
320
+ // ─────────────────────────────────────────────────────────────
321
+
322
+ // Build map dari kolom set kanonikal. Bila item punya nama dan unik, name
323
+ // digunakan sebagai key; selain itu dipakai gabungan kolom yang ter-sort agar
324
+ // stable order columns tidak dianggap berbeda.
325
+ function buildItemMap(items, getKey) {
326
+ const map = new Map();
327
+ if (!Array.isArray(items)) return map;
328
+ for (const item of items) {
329
+ if (!item) continue;
330
+ const key = getKey(item);
331
+ if (!key) continue;
332
+ if (!map.has(key)) map.set(key, item);
333
+ }
334
+ return map;
335
+ }
336
+
337
+ function columnsKey(item) {
338
+ if (!item || !Array.isArray(item.columns)) return null;
339
+ return item.columns.slice().sort().join('|');
340
+ }
341
+
342
+ function indexKey(item) {
343
+ return columnsKey(item);
344
+ }
345
+
346
+ function diffIndexes(sdfIndexes, dbIndexes) {
347
+ const sdfMap = buildItemMap(sdfIndexes, indexKey);
348
+ const dbMap = buildItemMap(dbIndexes, indexKey);
349
+
350
+ const onlyInSdf = [];
351
+ const onlyInDb = [];
352
+ const mismatched = [];
353
+
354
+ for (const [key, item] of sdfMap.entries()) {
355
+ if (!dbMap.has(key)) {
356
+ onlyInSdf.push({ columns: Array.isArray(item.columns) ? item.columns.slice() : [] });
357
+ }
358
+ }
359
+ for (const [key, item] of dbMap.entries()) {
360
+ if (!sdfMap.has(key)) {
361
+ onlyInDb.push({ columns: Array.isArray(item.columns) ? item.columns.slice() : [] });
362
+ }
363
+ }
364
+ // Mismatch: same column-set tapi order berbeda. MVP-nya kita treat
365
+ // matching column-set sebagai match dan tidak menandai order sebagai drift,
366
+ // karena index column order biasanya ditentukan oleh dialect optimizer.
367
+ // (Kebijakan ini dapat di-revisi fase berikutnya.)
368
+
369
+ return { onlyInSdf, onlyInDb, mismatched };
370
+ }
371
+
372
+ function diffUniques(sdfUniques, dbUniques) {
373
+ // Strategi sama dengan index: match by column-set kanonikal.
374
+ const sdfMap = buildItemMap(sdfUniques, columnsKey);
375
+ const dbMap = buildItemMap(dbUniques, columnsKey);
376
+
377
+ const onlyInSdf = [];
378
+ const onlyInDb = [];
379
+ const mismatched = [];
380
+
381
+ for (const [key, item] of sdfMap.entries()) {
382
+ if (!dbMap.has(key)) {
383
+ onlyInSdf.push({ columns: Array.isArray(item.columns) ? item.columns.slice() : [] });
384
+ }
385
+ }
386
+ for (const [key, item] of dbMap.entries()) {
387
+ if (!sdfMap.has(key)) {
388
+ onlyInDb.push({ columns: Array.isArray(item.columns) ? item.columns.slice() : [] });
389
+ }
390
+ }
391
+
392
+ return { onlyInSdf, onlyInDb, mismatched };
393
+ }
394
+
395
+ // Foreign key normalization: ambil triplet (localKey, target, references) plus
396
+ // onDelete/onUpdate canonical. introspect-mapper.mapReferentialAction() sudah
397
+ // menormalisasi raw DB action ke canonical (cascade/setNull/restrict), dan SDF
398
+ // menyimpan canonical lewat ir-builder.normalizeRelation(). Triplet dipakai
399
+ // untuk FK matching; action di-compare terpisah di compareFkActions().
400
+ function extractForeignKeys(ir) {
401
+ const out = [];
402
+ if (!ir || !isPlainObject(ir)) return out;
403
+ const relations = isPlainObject(ir.relations) ? ir.relations : {};
404
+ for (const [name, rel] of Object.entries(relations)) {
405
+ if (!rel || rel.type !== 'belongsTo') continue;
406
+ if (!rel.localKey || !rel.target) continue;
407
+ out.push({
408
+ name,
409
+ localKey: rel.localKey,
410
+ target: stripSchemaPrefix(rel.target),
411
+ references: rel.references || null,
412
+ onDelete: rel.onDelete,
413
+ onUpdate: rel.onUpdate
414
+ });
415
+ }
416
+ return out;
417
+ }
418
+
419
+ function stripSchemaPrefix(name) {
420
+ if (typeof name !== 'string') return name;
421
+ const dot = name.indexOf('.');
422
+ if (dot === -1) return name;
423
+ return name.slice(dot + 1);
424
+ }
425
+
426
+ function fkKey(item) {
427
+ if (!item) return null;
428
+ return `${item.localKey}->${item.target}.${item.references || ''}`;
429
+ }
430
+
431
+ // SQL standard: FK tanpa ON DELETE/UPDATE eksplisit berperilaku seperti NO ACTION
432
+ // (alias dari RESTRICT di kebanyakan dialect dan di canonical map kita).
433
+ // Normalisasi ini wajib agar SDF yang skip declare action tidak dianggap drift
434
+ // terhadap DB yang punya RESTRICT eksplisit.
435
+ function normalizeFkAction(value) {
436
+ if (value === undefined || value === null || value === '') {
437
+ return 'restrict';
438
+ }
439
+ return String(value).toLowerCase();
440
+ }
441
+
442
+ function compareFkActions(sdfFk, dbFk) {
443
+ const reasons = [];
444
+ const sdfOnDelete = normalizeFkAction(sdfFk && sdfFk.onDelete);
445
+ const dbOnDelete = normalizeFkAction(dbFk && dbFk.onDelete);
446
+ if (sdfOnDelete !== dbOnDelete) {
447
+ reasons.push(`onDelete: SDF='${sdfOnDelete}' vs DB='${dbOnDelete}'`);
448
+ }
449
+ const sdfOnUpdate = normalizeFkAction(sdfFk && sdfFk.onUpdate);
450
+ const dbOnUpdate = normalizeFkAction(dbFk && dbFk.onUpdate);
451
+ if (sdfOnUpdate !== dbOnUpdate) {
452
+ reasons.push(`onUpdate: SDF='${sdfOnUpdate}' vs DB='${dbOnUpdate}'`);
453
+ }
454
+ return reasons;
455
+ }
456
+
457
+ function formatFkEntry(fk) {
458
+ return {
459
+ localKey: fk.localKey,
460
+ target: fk.target,
461
+ references: fk.references,
462
+ onDelete: normalizeFkAction(fk.onDelete),
463
+ onUpdate: normalizeFkAction(fk.onUpdate)
464
+ };
465
+ }
466
+
467
+ function formatFkActions(fk) {
468
+ return {
469
+ onDelete: normalizeFkAction(fk.onDelete),
470
+ onUpdate: normalizeFkAction(fk.onUpdate)
471
+ };
472
+ }
473
+
474
+ function diffForeignKeys(sdfIR, dbIR) {
475
+ const sdf = extractForeignKeys(sdfIR);
476
+ const db = extractForeignKeys(dbIR);
477
+
478
+ const sdfMap = new Map();
479
+ for (const fk of sdf) sdfMap.set(fkKey(fk), fk);
480
+ const dbMap = new Map();
481
+ for (const fk of db) dbMap.set(fkKey(fk), fk);
482
+
483
+ const onlyInSdf = [];
484
+ const onlyInDb = [];
485
+ const mismatched = [];
486
+
487
+ for (const [key, sdfFk] of sdfMap.entries()) {
488
+ if (!dbMap.has(key)) {
489
+ onlyInSdf.push(formatFkEntry(sdfFk));
490
+ continue;
491
+ }
492
+ const dbFk = dbMap.get(key);
493
+ const reasons = compareFkActions(sdfFk, dbFk);
494
+ if (reasons.length > 0) {
495
+ mismatched.push({
496
+ localKey: sdfFk.localKey,
497
+ target: sdfFk.target,
498
+ references: sdfFk.references,
499
+ sdf: formatFkActions(sdfFk),
500
+ db: formatFkActions(dbFk),
501
+ reasons
502
+ });
503
+ }
504
+ }
505
+ for (const [key, dbFk] of dbMap.entries()) {
506
+ if (!sdfMap.has(key)) {
507
+ onlyInDb.push(formatFkEntry(dbFk));
508
+ }
509
+ }
510
+
511
+ return { onlyInSdf, onlyInDb, mismatched };
512
+ }
513
+
514
+ // ─────────────────────────────────────────────────────────────
515
+ // Check constraint comparison (set-based match-by-content)
516
+ // ─────────────────────────────────────────────────────────────
517
+
518
+ // Hash kanonikal untuk check item:
519
+ // - Parsed: ${field}:${op}:${valueKey} (IN-list value di-sort agar order
520
+ // tidak signifikan).
521
+ // - Unparsable: 'unparsable:' + normalized expression string.
522
+ function checkKey(c) {
523
+ if (!c) return null;
524
+ if (c._unparsable) {
525
+ return `unparsable:${normalizeWeakDefault(c.expression)}`;
526
+ }
527
+ if (typeof c.field !== 'string' || typeof c.op !== 'string') return null;
528
+ let valueKey;
529
+ if (Array.isArray(c.value)) {
530
+ valueKey = c.value
531
+ .slice()
532
+ .map((v) => JSON.stringify(v))
533
+ .sort()
534
+ .join(',');
535
+ } else {
536
+ valueKey = JSON.stringify(c.value);
537
+ }
538
+ return `${c.field}:${c.op}:${valueKey}`;
539
+ }
540
+
541
+ function formatCheck(c) {
542
+ if (!c) return null;
543
+ if (c._unparsable) return { expression: c.expression, _unparsable: true };
544
+ const out = { field: c.field, op: c.op, value: c.value };
545
+ if (c.name) out.name = c.name;
546
+ return out;
547
+ }
548
+
549
+ function diffChecks(sdfChecks, dbChecks) {
550
+ const sdfMap = new Map();
551
+ if (Array.isArray(sdfChecks)) {
552
+ for (const c of sdfChecks) {
553
+ const k = checkKey(c);
554
+ if (k && !sdfMap.has(k)) sdfMap.set(k, c);
555
+ }
556
+ }
557
+ const dbMap = new Map();
558
+ if (Array.isArray(dbChecks)) {
559
+ for (const c of dbChecks) {
560
+ const k = checkKey(c);
561
+ if (k && !dbMap.has(k)) dbMap.set(k, c);
562
+ }
563
+ }
564
+
565
+ const onlyInSdf = [];
566
+ const onlyInDb = [];
567
+ const mismatched = [];
568
+
569
+ for (const [k, c] of sdfMap.entries()) {
570
+ if (!dbMap.has(k)) onlyInSdf.push(formatCheck(c));
571
+ }
572
+ for (const [k, c] of dbMap.entries()) {
573
+ if (!sdfMap.has(k)) onlyInDb.push(formatCheck(c));
574
+ }
575
+
576
+ return { onlyInSdf, onlyInDb, mismatched };
577
+ }
578
+
579
+ // ─────────────────────────────────────────────────────────────
580
+ // Top-level API
581
+ // ─────────────────────────────────────────────────────────────
582
+
583
+ function hasAnyDrift(delta) {
584
+ if (!delta) return false;
585
+ if (delta.fields.onlyInSdf.length > 0) return true;
586
+ if (delta.fields.onlyInDb.length > 0) return true;
587
+ if (delta.fields.mismatched.length > 0) return true;
588
+ if (!delta.primaryKey.match) return true;
589
+ if (delta.indexes.onlyInSdf.length > 0) return true;
590
+ if (delta.indexes.onlyInDb.length > 0) return true;
591
+ if (delta.indexes.mismatched.length > 0) return true;
592
+ if (delta.uniques.onlyInSdf.length > 0) return true;
593
+ if (delta.uniques.onlyInDb.length > 0) return true;
594
+ if (delta.uniques.mismatched.length > 0) return true;
595
+ if (delta.foreignKeys.onlyInSdf.length > 0) return true;
596
+ if (delta.foreignKeys.onlyInDb.length > 0) return true;
597
+ if (delta.foreignKeys.mismatched.length > 0) return true;
598
+ if (delta.checks && delta.checks.onlyInSdf.length > 0) return true;
599
+ if (delta.checks && delta.checks.onlyInDb.length > 0) return true;
600
+ if (delta.checks && delta.checks.mismatched.length > 0) return true;
601
+ return false;
602
+ }
603
+
604
+ function diffModel(sdfIR, dbIR) {
605
+ if (!isPlainObject(sdfIR)) {
606
+ throw new Error('diffModel: sdfIR must be a plain object IR');
607
+ }
608
+ if (!isPlainObject(dbIR)) {
609
+ throw new Error('diffModel: dbIR must be a plain object IR');
610
+ }
611
+
612
+ const tableName = sdfIR.tableName || dbIR.tableName || null;
613
+
614
+ const delta = {
615
+ tableName,
616
+ hasDrift: false,
617
+ fields: diffFields(sdfIR.fields, dbIR.fields),
618
+ primaryKey: diffPrimaryKey(sdfIR, dbIR),
619
+ indexes: diffIndexes(sdfIR.indexes, dbIR.indexes),
620
+ uniques: diffUniques(sdfIR.uniques, dbIR.uniques),
621
+ foreignKeys: diffForeignKeys(sdfIR, dbIR),
622
+ checks: diffChecks(sdfIR.checks, dbIR.checks)
623
+ };
624
+ delta.hasDrift = hasAnyDrift(delta);
625
+
626
+ return delta;
627
+ }
628
+
629
+ function diffModels(sdfModels, dbModels) {
630
+ const sdfMap = toMap(sdfModels);
631
+ const dbMap = toMap(dbModels);
632
+
633
+ const results = [];
634
+ const visited = new Set();
635
+
636
+ for (const [key, sdfIR] of sdfMap.entries()) {
637
+ visited.add(key);
638
+ const dbIR = dbMap.get(key) || emptyIR(sdfIR.tableName || key);
639
+ results.push(diffModel(sdfIR, dbIR));
640
+ }
641
+ for (const [key, dbIR] of dbMap.entries()) {
642
+ if (visited.has(key)) continue;
643
+ results.push(diffModel(emptyIR(dbIR.tableName || key), dbIR));
644
+ }
645
+
646
+ return results;
647
+ }
648
+
649
+ function emptyIR(tableName) {
650
+ return {
651
+ tableName: tableName || '',
652
+ schemaName: null,
653
+ qualifiedName: tableName || '',
654
+ fields: {},
655
+ primaryKey: [],
656
+ indexes: [],
657
+ uniques: [],
658
+ relations: {},
659
+ checks: []
660
+ };
661
+ }
662
+
663
+ function toMap(input) {
664
+ if (input instanceof Map) return input;
665
+ const map = new Map();
666
+ if (Array.isArray(input)) {
667
+ for (const ir of input) {
668
+ if (ir && (ir.qualifiedName || ir.tableName)) {
669
+ map.set(ir.qualifiedName || ir.tableName, ir);
670
+ }
671
+ }
672
+ return map;
673
+ }
674
+ if (isPlainObject(input)) {
675
+ for (const [key, ir] of Object.entries(input)) {
676
+ map.set(key, ir);
677
+ }
678
+ return map;
679
+ }
680
+ return map;
681
+ }
682
+
683
+ module.exports = {
684
+ diffModel,
685
+ diffModels,
686
+ normalizeType,
687
+ // exported for unit test transparency
688
+ _internal: {
689
+ resolvePrimaryKey,
690
+ diffFields,
691
+ diffPrimaryKey,
692
+ diffIndexes,
693
+ diffUniques,
694
+ diffForeignKeys,
695
+ extractForeignKeys,
696
+ diffChecks,
697
+ compareDefaultValue,
698
+ canonicalDefaultPayload,
699
+ normalizeWeakDefault,
700
+ normalizeFkAction,
701
+ compareFkActions
702
+ }
703
+ };