sqlew 3.6.10 → 3.7.0

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 (370) hide show
  1. package/CHANGELOG.md +318 -0
  2. package/README.md +54 -39
  3. package/assets/config.example.toml +93 -0
  4. package/assets/kanban-visualizer.png +0 -0
  5. package/assets/sample-agents/sqlew-architect.md +32 -13
  6. package/assets/sample-agents/sqlew-researcher.md +70 -17
  7. package/assets/sample-agents/sqlew-scrum-master.md +60 -25
  8. package/assets/schema.sql +2 -2
  9. package/dist/adapters/auth/auth-factory.d.ts +86 -0
  10. package/dist/adapters/auth/auth-factory.d.ts.map +1 -0
  11. package/dist/adapters/auth/auth-factory.js +103 -0
  12. package/dist/adapters/auth/auth-factory.js.map +1 -0
  13. package/dist/adapters/auth/auth-types.d.ts +30 -0
  14. package/dist/adapters/auth/auth-types.d.ts.map +1 -0
  15. package/dist/adapters/auth/auth-types.js +30 -0
  16. package/dist/adapters/auth/auth-types.js.map +1 -0
  17. package/dist/adapters/auth/base-auth-provider.d.ts +327 -0
  18. package/dist/adapters/auth/base-auth-provider.d.ts.map +1 -0
  19. package/dist/adapters/auth/base-auth-provider.js +111 -0
  20. package/dist/adapters/auth/base-auth-provider.js.map +1 -0
  21. package/dist/adapters/auth/direct-auth-provider.d.ts +356 -0
  22. package/dist/adapters/auth/direct-auth-provider.d.ts.map +1 -0
  23. package/dist/adapters/auth/direct-auth-provider.js +406 -0
  24. package/dist/adapters/auth/direct-auth-provider.js.map +1 -0
  25. package/dist/adapters/base-adapter.d.ts +638 -0
  26. package/dist/adapters/base-adapter.d.ts.map +1 -0
  27. package/dist/adapters/base-adapter.js +557 -0
  28. package/dist/adapters/base-adapter.js.map +1 -0
  29. package/dist/adapters/index.d.ts +13 -2
  30. package/dist/adapters/index.d.ts.map +1 -1
  31. package/dist/adapters/index.js +27 -5
  32. package/dist/adapters/index.js.map +1 -1
  33. package/dist/adapters/mysql-adapter.d.ts +547 -6
  34. package/dist/adapters/mysql-adapter.d.ts.map +1 -1
  35. package/dist/adapters/mysql-adapter.js +651 -32
  36. package/dist/adapters/mysql-adapter.js.map +1 -1
  37. package/dist/adapters/postgresql-adapter.d.ts +15 -4
  38. package/dist/adapters/postgresql-adapter.d.ts.map +1 -1
  39. package/dist/adapters/postgresql-adapter.js +19 -2
  40. package/dist/adapters/postgresql-adapter.js.map +1 -1
  41. package/dist/adapters/sqlite-adapter.d.ts +35 -5
  42. package/dist/adapters/sqlite-adapter.d.ts.map +1 -1
  43. package/dist/adapters/sqlite-adapter.js +57 -18
  44. package/dist/adapters/sqlite-adapter.js.map +1 -1
  45. package/dist/cli/db-dump.d.ts +32 -0
  46. package/dist/cli/db-dump.d.ts.map +1 -0
  47. package/dist/cli/db-dump.js +409 -0
  48. package/dist/cli/db-dump.js.map +1 -0
  49. package/dist/cli.js +24 -14
  50. package/dist/cli.js.map +1 -1
  51. package/dist/config/knex/bootstrap/20251025020452_create_master_tables.d.ts.map +1 -0
  52. package/dist/{migrations → config}/knex/bootstrap/20251025020452_create_master_tables.js +7 -2
  53. package/dist/config/knex/bootstrap/20251025020452_create_master_tables.js.map +1 -0
  54. package/dist/config/knex/bootstrap/20251025021152_create_transaction_tables.d.ts.map +1 -0
  55. package/dist/{migrations → config}/knex/bootstrap/20251025021152_create_transaction_tables.js +49 -50
  56. package/dist/config/knex/bootstrap/20251025021152_create_transaction_tables.js.map +1 -0
  57. package/dist/config/knex/bootstrap/20251025021351_create_indexes.d.ts.map +1 -0
  58. package/dist/config/knex/bootstrap/20251025021351_create_indexes.js.map +1 -0
  59. package/dist/config/knex/bootstrap/20251025021416_seed_master_data.d.ts.map +1 -0
  60. package/dist/{migrations → config}/knex/bootstrap/20251025021416_seed_master_data.js +11 -6
  61. package/dist/config/knex/bootstrap/20251025021416_seed_master_data.js.map +1 -0
  62. package/dist/config/knex/bootstrap/20251025070349_create_views.d.ts.map +1 -0
  63. package/dist/{migrations → config}/knex/bootstrap/20251025070349_create_views.js +66 -14
  64. package/dist/config/knex/bootstrap/20251025070349_create_views.js.map +1 -0
  65. package/dist/config/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.d.ts.map +1 -0
  66. package/dist/config/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.js +22 -0
  67. package/dist/config/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.js.map +1 -0
  68. package/dist/config/knex/enhancements/20251025082220_fix_task_dependencies_columns.d.ts.map +1 -0
  69. package/dist/config/knex/enhancements/20251025082220_fix_task_dependencies_columns.js.map +1 -0
  70. package/dist/config/knex/enhancements/20251025090000_create_help_system_tables.d.ts.map +1 -0
  71. package/dist/{migrations → config}/knex/enhancements/20251025090000_create_help_system_tables.js +6 -0
  72. package/dist/config/knex/enhancements/20251025090000_create_help_system_tables.js.map +1 -0
  73. package/dist/config/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.d.ts.map +1 -0
  74. package/dist/{migrations → config}/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.js +6 -0
  75. package/dist/config/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.js.map +1 -0
  76. package/dist/config/knex/enhancements/20251025100000_seed_help_metadata.d.ts.map +1 -0
  77. package/dist/{migrations → config}/knex/enhancements/20251025100000_seed_help_metadata.js +6 -0
  78. package/dist/config/knex/enhancements/20251025100000_seed_help_metadata.js.map +1 -0
  79. package/dist/config/knex/enhancements/20251025100100_seed_remaining_use_cases.d.ts.map +1 -0
  80. package/dist/config/knex/enhancements/20251025100100_seed_remaining_use_cases.js.map +1 -0
  81. package/dist/config/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.d.ts.map +1 -0
  82. package/dist/{migrations → config}/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.js +7 -0
  83. package/dist/config/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.js.map +1 -0
  84. package/dist/config/knex/enhancements/20251027000000_add_agent_reuse_system.d.ts.map +1 -0
  85. package/dist/config/knex/enhancements/20251027000000_add_agent_reuse_system.js +62 -0
  86. package/dist/config/knex/enhancements/20251027000000_add_agent_reuse_system.js.map +1 -0
  87. package/dist/config/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.d.ts.map +1 -0
  88. package/dist/config/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.js.map +1 -0
  89. package/dist/config/knex/enhancements/20251027020000_update_agent_reusability.d.ts.map +1 -0
  90. package/dist/{migrations → config}/knex/enhancements/20251027020000_update_agent_reusability.js +6 -0
  91. package/dist/config/knex/enhancements/20251027020000_update_agent_reusability.js.map +1 -0
  92. package/dist/config/knex/enhancements/20251028000000_simplify_agent_system.d.ts.map +1 -0
  93. package/dist/{migrations → config}/knex/enhancements/20251028000000_simplify_agent_system.js +6 -0
  94. package/dist/config/knex/enhancements/20251028000000_simplify_agent_system.js.map +1 -0
  95. package/dist/config/knex/enhancements/20251031000000_drop_orphaned_message_view.d.ts +13 -0
  96. package/dist/config/knex/enhancements/20251031000000_drop_orphaned_message_view.d.ts.map +1 -0
  97. package/dist/config/knex/enhancements/20251031000000_drop_orphaned_message_view.js +48 -0
  98. package/dist/config/knex/enhancements/20251031000000_drop_orphaned_message_view.js.map +1 -0
  99. package/dist/config/knex/enhancements/20251104000003_rename_constraints_created_by_to_agent_id.d.ts +24 -0
  100. package/dist/config/knex/enhancements/20251104000003_rename_constraints_created_by_to_agent_id.d.ts.map +1 -0
  101. package/dist/config/knex/enhancements/20251104000003_rename_constraints_created_by_to_agent_id.js +189 -0
  102. package/dist/config/knex/enhancements/20251104000003_rename_constraints_created_by_to_agent_id.js.map +1 -0
  103. package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.d.ts +16 -0
  104. package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.d.ts.map +1 -0
  105. package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.js +65 -0
  106. package/dist/config/knex/enhancements/20251105000000_add_token_usage_table.js.map +1 -0
  107. package/dist/config/knex/enhancements/20251105000001_rename_decision_context_decided_by_to_agent_id.d.ts +23 -0
  108. package/dist/config/knex/enhancements/20251105000001_rename_decision_context_decided_by_to_agent_id.d.ts.map +1 -0
  109. package/dist/config/knex/enhancements/20251105000001_rename_decision_context_decided_by_to_agent_id.js +118 -0
  110. package/dist/config/knex/enhancements/20251105000001_rename_decision_context_decided_by_to_agent_id.js.map +1 -0
  111. package/dist/config/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.d.ts.map +1 -0
  112. package/dist/config/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.js.map +1 -0
  113. package/dist/config/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.d.ts.map +1 -0
  114. package/dist/config/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.js.map +1 -0
  115. package/dist/config/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.d.ts.map +1 -0
  116. package/dist/config/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.js.map +1 -0
  117. package/dist/config/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.d.ts.map +1 -0
  118. package/dist/config/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.js.map +1 -0
  119. package/dist/config/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.d.ts.map +1 -0
  120. package/dist/config/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.js.map +1 -0
  121. package/dist/config/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.d.ts.map +1 -0
  122. package/dist/config/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.js.map +1 -0
  123. package/dist/config/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.d.ts.map +1 -0
  124. package/dist/config/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.js.map +1 -0
  125. package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.d.ts +49 -0
  126. package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.d.ts.map +1 -0
  127. package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.js +864 -0
  128. package/dist/config/knex/upgrades/20251104000000_add_multi_project_v3_7_0.js.map +1 -0
  129. package/dist/config/loader.d.ts +19 -1
  130. package/dist/config/loader.d.ts.map +1 -1
  131. package/dist/config/loader.js +149 -4
  132. package/dist/config/loader.js.map +1 -1
  133. package/dist/config/types.d.ts +261 -2
  134. package/dist/config/types.d.ts.map +1 -1
  135. package/dist/config/types.js.map +1 -1
  136. package/dist/config/writer.d.ts +65 -0
  137. package/dist/config/writer.d.ts.map +1 -0
  138. package/dist/config/writer.js +139 -0
  139. package/dist/config/writer.js.map +1 -0
  140. package/dist/database.d.ts +11 -2
  141. package/dist/database.d.ts.map +1 -1
  142. package/dist/database.js +62 -6
  143. package/dist/database.js.map +1 -1
  144. package/dist/index.js +165 -35
  145. package/dist/index.js.map +1 -1
  146. package/dist/knexfile.d.ts.map +1 -1
  147. package/dist/knexfile.js +88 -12
  148. package/dist/knexfile.js.map +1 -1
  149. package/dist/tests/all-features.test.js +15 -3
  150. package/dist/tests/all-features.test.js.map +1 -1
  151. package/dist/tests/config-loader.test.d.ts +6 -0
  152. package/dist/tests/config-loader.test.d.ts.map +1 -0
  153. package/dist/tests/config-loader.test.js +201 -0
  154. package/dist/tests/config-loader.test.js.map +1 -0
  155. package/dist/tests/connection-manager-integration.test.d.ts +2 -0
  156. package/dist/tests/connection-manager-integration.test.d.ts.map +1 -0
  157. package/dist/tests/connection-manager-integration.test.js +431 -0
  158. package/dist/tests/connection-manager-integration.test.js.map +1 -0
  159. package/dist/tests/connection-manager.test.d.ts +2 -0
  160. package/dist/tests/connection-manager.test.d.ts.map +1 -0
  161. package/dist/tests/connection-manager.test.js +361 -0
  162. package/dist/tests/connection-manager.test.js.map +1 -0
  163. package/dist/tests/dump-import.test.d.ts +15 -0
  164. package/dist/tests/dump-import.test.d.ts.map +1 -0
  165. package/dist/tests/dump-import.test.js +430 -0
  166. package/dist/tests/dump-import.test.js.map +1 -0
  167. package/dist/tests/migration-idempotency.test.d.ts +2 -0
  168. package/dist/tests/migration-idempotency.test.d.ts.map +1 -0
  169. package/dist/tests/migration-idempotency.test.js +330 -0
  170. package/dist/tests/migration-idempotency.test.js.map +1 -0
  171. package/dist/tests/migration-upgrade-paths.test.d.ts +2 -0
  172. package/dist/tests/migration-upgrade-paths.test.d.ts.map +1 -0
  173. package/dist/tests/migration-upgrade-paths.test.js +248 -0
  174. package/dist/tests/migration-upgrade-paths.test.js.map +1 -0
  175. package/dist/tests/multi-project-migration.test.d.ts +17 -0
  176. package/dist/tests/multi-project-migration.test.d.ts.map +1 -0
  177. package/dist/tests/multi-project-migration.test.js +399 -0
  178. package/dist/tests/multi-project-migration.test.js.map +1 -0
  179. package/dist/tests/multi-project.test.d.ts +5 -0
  180. package/dist/tests/multi-project.test.d.ts.map +1 -0
  181. package/dist/tests/multi-project.test.js +238 -0
  182. package/dist/tests/multi-project.test.js.map +1 -0
  183. package/dist/tests/schema-migration.test.d.ts +8 -0
  184. package/dist/tests/schema-migration.test.d.ts.map +1 -0
  185. package/dist/tests/schema-migration.test.js +108 -0
  186. package/dist/tests/schema-migration.test.js.map +1 -0
  187. package/dist/tests/sql-dump-converters.test.d.ts +7 -0
  188. package/dist/tests/sql-dump-converters.test.d.ts.map +1 -0
  189. package/dist/tests/sql-dump-converters.test.js +314 -0
  190. package/dist/tests/sql-dump-converters.test.js.map +1 -0
  191. package/dist/tests/sql-dump-cross-database.test.d.ts +21 -0
  192. package/dist/tests/sql-dump-cross-database.test.d.ts.map +1 -0
  193. package/dist/tests/sql-dump-cross-database.test.js +314 -0
  194. package/dist/tests/sql-dump-cross-database.test.js.map +1 -0
  195. package/dist/tests/sql-dump-default-conversions.test.d.ts +8 -0
  196. package/dist/tests/sql-dump-default-conversions.test.d.ts.map +1 -0
  197. package/dist/tests/sql-dump-default-conversions.test.js +141 -0
  198. package/dist/tests/sql-dump-default-conversions.test.js.map +1 -0
  199. package/dist/tests/sql-dump-fk-constraints.test.d.ts +13 -0
  200. package/dist/tests/sql-dump-fk-constraints.test.d.ts.map +1 -0
  201. package/dist/tests/sql-dump-fk-constraints.test.js +381 -0
  202. package/dist/tests/sql-dump-fk-constraints.test.js.map +1 -0
  203. package/dist/tests/sql-dump-indexes.test.d.ts +12 -0
  204. package/dist/tests/sql-dump-indexes.test.d.ts.map +1 -0
  205. package/dist/tests/sql-dump-indexes.test.js +269 -0
  206. package/dist/tests/sql-dump-indexes.test.js.map +1 -0
  207. package/dist/tests/sql-dump-integration.test.d.ts +16 -0
  208. package/dist/tests/sql-dump-integration.test.d.ts.map +1 -0
  209. package/dist/tests/sql-dump-integration.test.js +342 -0
  210. package/dist/tests/sql-dump-integration.test.js.map +1 -0
  211. package/dist/tests/sql-dump-table-ordering.test.d.ts +8 -0
  212. package/dist/tests/sql-dump-table-ordering.test.d.ts.map +1 -0
  213. package/dist/tests/sql-dump-table-ordering.test.js +253 -0
  214. package/dist/tests/sql-dump-table-ordering.test.js.map +1 -0
  215. package/dist/tests/tasks.link-file-backward-compat.test.js +11 -1
  216. package/dist/tests/tasks.link-file-backward-compat.test.js.map +1 -1
  217. package/dist/tests/tasks.watch-files-action.test.js +11 -1
  218. package/dist/tests/tasks.watch-files-action.test.js.map +1 -1
  219. package/dist/tests/type-conversion.test.d.ts +8 -0
  220. package/dist/tests/type-conversion.test.d.ts.map +1 -0
  221. package/dist/tests/type-conversion.test.js +312 -0
  222. package/dist/tests/type-conversion.test.js.map +1 -0
  223. package/dist/tests/utils/test-helpers.d.ts +93 -0
  224. package/dist/tests/utils/test-helpers.d.ts.map +1 -0
  225. package/dist/tests/utils/test-helpers.js +407 -0
  226. package/dist/tests/utils/test-helpers.js.map +1 -0
  227. package/dist/tools/config.d.ts +58 -0
  228. package/dist/tools/config.d.ts.map +1 -0
  229. package/dist/tools/config.js +281 -0
  230. package/dist/tools/config.js.map +1 -0
  231. package/dist/tools/constraints.d.ts.map +1 -1
  232. package/dist/tools/constraints.js +138 -122
  233. package/dist/tools/constraints.js.map +1 -1
  234. package/dist/tools/context.d.ts.map +1 -1
  235. package/dist/tools/context.js +216 -109
  236. package/dist/tools/context.js.map +1 -1
  237. package/dist/tools/files.d.ts.map +1 -1
  238. package/dist/tools/files.js +123 -102
  239. package/dist/tools/files.js.map +1 -1
  240. package/dist/tools/tasks.d.ts.map +1 -1
  241. package/dist/tools/tasks.js +581 -518
  242. package/dist/tools/tasks.js.map +1 -1
  243. package/dist/tools/utils.d.ts +5 -0
  244. package/dist/tools/utils.d.ts.map +1 -1
  245. package/dist/tools/utils.js +176 -122
  246. package/dist/tools/utils.js.map +1 -1
  247. package/dist/types.d.ts +9 -26
  248. package/dist/types.d.ts.map +1 -1
  249. package/dist/utils/cleanup.d.ts +3 -0
  250. package/dist/utils/cleanup.d.ts.map +1 -1
  251. package/dist/utils/cleanup.js +14 -2
  252. package/dist/utils/cleanup.js.map +1 -1
  253. package/dist/utils/connection-manager.d.ts +59 -0
  254. package/dist/utils/connection-manager.d.ts.map +1 -0
  255. package/dist/utils/connection-manager.js +178 -0
  256. package/dist/utils/connection-manager.js.map +1 -0
  257. package/dist/utils/debug-logger.d.ts +8 -4
  258. package/dist/utils/debug-logger.d.ts.map +1 -1
  259. package/dist/utils/debug-logger.js +27 -7
  260. package/dist/utils/debug-logger.js.map +1 -1
  261. package/dist/utils/error-handler.d.ts +2 -2
  262. package/dist/utils/error-handler.d.ts.map +1 -1
  263. package/dist/utils/error-handler.js +10 -7
  264. package/dist/utils/error-handler.js.map +1 -1
  265. package/dist/utils/parameter-validator.d.ts.map +1 -1
  266. package/dist/utils/parameter-validator.js +36 -15
  267. package/dist/utils/parameter-validator.js.map +1 -1
  268. package/dist/utils/project-context.d.ts +111 -0
  269. package/dist/utils/project-context.d.ts.map +1 -0
  270. package/dist/utils/project-context.js +187 -0
  271. package/dist/utils/project-context.js.map +1 -0
  272. package/dist/utils/sql-dump-converters.d.ts +188 -0
  273. package/dist/utils/sql-dump-converters.d.ts.map +1 -0
  274. package/dist/utils/sql-dump-converters.js +311 -0
  275. package/dist/utils/sql-dump-converters.js.map +1 -0
  276. package/dist/utils/sql-dump.d.ts +102 -0
  277. package/dist/utils/sql-dump.d.ts.map +1 -0
  278. package/dist/utils/sql-dump.js +1550 -0
  279. package/dist/utils/sql-dump.js.map +1 -0
  280. package/dist/utils/vcs-adapter.d.ts +42 -0
  281. package/dist/utils/vcs-adapter.d.ts.map +1 -1
  282. package/dist/utils/vcs-adapter.js +154 -0
  283. package/dist/utils/vcs-adapter.js.map +1 -1
  284. package/docs/BASEADAPTER_IMPLEMENTATION.md +399 -0
  285. package/docs/DATABASE_AUTH.md +445 -0
  286. package/docs/DATABASE_MIGRATION.md +247 -0
  287. package/docs/MULTI_PROJECT_ARCHITECTURE.md +497 -0
  288. package/package.json +12 -4
  289. package/dist/migrations/knex/bootstrap/20251025020452_create_master_tables.d.ts.map +0 -1
  290. package/dist/migrations/knex/bootstrap/20251025020452_create_master_tables.js.map +0 -1
  291. package/dist/migrations/knex/bootstrap/20251025021152_create_transaction_tables.d.ts.map +0 -1
  292. package/dist/migrations/knex/bootstrap/20251025021152_create_transaction_tables.js.map +0 -1
  293. package/dist/migrations/knex/bootstrap/20251025021351_create_indexes.d.ts.map +0 -1
  294. package/dist/migrations/knex/bootstrap/20251025021351_create_indexes.js.map +0 -1
  295. package/dist/migrations/knex/bootstrap/20251025021416_seed_master_data.d.ts.map +0 -1
  296. package/dist/migrations/knex/bootstrap/20251025021416_seed_master_data.js.map +0 -1
  297. package/dist/migrations/knex/bootstrap/20251025070349_create_views.d.ts.map +0 -1
  298. package/dist/migrations/knex/bootstrap/20251025070349_create_views.js.map +0 -1
  299. package/dist/migrations/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.d.ts.map +0 -1
  300. package/dist/migrations/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.js +0 -15
  301. package/dist/migrations/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.js.map +0 -1
  302. package/dist/migrations/knex/enhancements/20251025082220_fix_task_dependencies_columns.d.ts.map +0 -1
  303. package/dist/migrations/knex/enhancements/20251025082220_fix_task_dependencies_columns.js.map +0 -1
  304. package/dist/migrations/knex/enhancements/20251025090000_create_help_system_tables.d.ts.map +0 -1
  305. package/dist/migrations/knex/enhancements/20251025090000_create_help_system_tables.js.map +0 -1
  306. package/dist/migrations/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.d.ts.map +0 -1
  307. package/dist/migrations/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.js.map +0 -1
  308. package/dist/migrations/knex/enhancements/20251025100000_seed_help_metadata.d.ts.map +0 -1
  309. package/dist/migrations/knex/enhancements/20251025100000_seed_help_metadata.js.map +0 -1
  310. package/dist/migrations/knex/enhancements/20251025100100_seed_remaining_use_cases.d.ts.map +0 -1
  311. package/dist/migrations/knex/enhancements/20251025100100_seed_remaining_use_cases.js.map +0 -1
  312. package/dist/migrations/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.d.ts.map +0 -1
  313. package/dist/migrations/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.js.map +0 -1
  314. package/dist/migrations/knex/enhancements/20251027000000_add_agent_reuse_system.d.ts.map +0 -1
  315. package/dist/migrations/knex/enhancements/20251027000000_add_agent_reuse_system.js +0 -34
  316. package/dist/migrations/knex/enhancements/20251027000000_add_agent_reuse_system.js.map +0 -1
  317. package/dist/migrations/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.d.ts.map +0 -1
  318. package/dist/migrations/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.js.map +0 -1
  319. package/dist/migrations/knex/enhancements/20251027020000_update_agent_reusability.d.ts.map +0 -1
  320. package/dist/migrations/knex/enhancements/20251027020000_update_agent_reusability.js.map +0 -1
  321. package/dist/migrations/knex/enhancements/20251028000000_simplify_agent_system.d.ts.map +0 -1
  322. package/dist/migrations/knex/enhancements/20251028000000_simplify_agent_system.js.map +0 -1
  323. package/dist/migrations/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.d.ts.map +0 -1
  324. package/dist/migrations/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.js.map +0 -1
  325. package/dist/migrations/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.d.ts.map +0 -1
  326. package/dist/migrations/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.js.map +0 -1
  327. package/dist/migrations/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.d.ts.map +0 -1
  328. package/dist/migrations/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.js.map +0 -1
  329. package/dist/migrations/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.d.ts.map +0 -1
  330. package/dist/migrations/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.js.map +0 -1
  331. package/dist/migrations/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.d.ts.map +0 -1
  332. package/dist/migrations/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.js.map +0 -1
  333. package/dist/migrations/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.d.ts.map +0 -1
  334. package/dist/migrations/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.js.map +0 -1
  335. package/dist/migrations/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.d.ts.map +0 -1
  336. package/dist/migrations/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.js.map +0 -1
  337. /package/dist/{migrations → config}/knex/bootstrap/20251025020452_create_master_tables.d.ts +0 -0
  338. /package/dist/{migrations → config}/knex/bootstrap/20251025021152_create_transaction_tables.d.ts +0 -0
  339. /package/dist/{migrations → config}/knex/bootstrap/20251025021351_create_indexes.d.ts +0 -0
  340. /package/dist/{migrations → config}/knex/bootstrap/20251025021351_create_indexes.js +0 -0
  341. /package/dist/{migrations → config}/knex/bootstrap/20251025021416_seed_master_data.d.ts +0 -0
  342. /package/dist/{migrations → config}/knex/bootstrap/20251025070349_create_views.d.ts +0 -0
  343. /package/dist/{migrations → config}/knex/enhancements/20251025081221_add_link_type_to_task_decision_links.d.ts +0 -0
  344. /package/dist/{migrations → config}/knex/enhancements/20251025082220_fix_task_dependencies_columns.d.ts +0 -0
  345. /package/dist/{migrations → config}/knex/enhancements/20251025082220_fix_task_dependencies_columns.js +0 -0
  346. /package/dist/{migrations → config}/knex/enhancements/20251025090000_create_help_system_tables.d.ts +0 -0
  347. /package/dist/{migrations → config}/knex/enhancements/20251025090100_seed_help_categories_and_use_cases.d.ts +0 -0
  348. /package/dist/{migrations → config}/knex/enhancements/20251025100000_seed_help_metadata.d.ts +0 -0
  349. /package/dist/{migrations → config}/knex/enhancements/20251025100100_seed_remaining_use_cases.d.ts +0 -0
  350. /package/dist/{migrations → config}/knex/enhancements/20251025100100_seed_remaining_use_cases.js +0 -0
  351. /package/dist/{migrations → config}/knex/enhancements/20251025120000_add_cascade_to_task_dependencies.d.ts +0 -0
  352. /package/dist/{migrations → config}/knex/enhancements/20251027000000_add_agent_reuse_system.d.ts +0 -0
  353. /package/dist/{migrations → config}/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.d.ts +0 -0
  354. /package/dist/{migrations → config}/knex/enhancements/20251027010000_add_task_constraint_to_decision_context.js +0 -0
  355. /package/dist/{migrations → config}/knex/enhancements/20251027020000_update_agent_reusability.d.ts +0 -0
  356. /package/dist/{migrations → config}/knex/enhancements/20251028000000_simplify_agent_system.d.ts +0 -0
  357. /package/dist/{migrations → config}/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.d.ts +0 -0
  358. /package/dist/{migrations → config}/knex/upgrades/20251024010000_upgrade_v1_0_to_v1_1.js +0 -0
  359. /package/dist/{migrations → config}/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.d.ts +0 -0
  360. /package/dist/{migrations → config}/knex/upgrades/20251024020000_upgrade_v2_0_to_v2_1.js +0 -0
  361. /package/dist/{migrations → config}/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.d.ts +0 -0
  362. /package/dist/{migrations → config}/knex/upgrades/20251024030000_upgrade_v2_1_to_v3_0.js +0 -0
  363. /package/dist/{migrations → config}/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.d.ts +0 -0
  364. /package/dist/{migrations → config}/knex/upgrades/20251024040000_upgrade_v3_0_to_v3_2.js +0 -0
  365. /package/dist/{migrations → config}/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.d.ts +0 -0
  366. /package/dist/{migrations → config}/knex/upgrades/20251024050000_upgrade_v3_2_0_to_v3_2_2.js +0 -0
  367. /package/dist/{migrations → config}/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.d.ts +0 -0
  368. /package/dist/{migrations → config}/knex/upgrades/20251024060000_upgrade_v3_4_to_v3_5.js +0 -0
  369. /package/dist/{migrations → config}/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.d.ts +0 -0
  370. /package/dist/{migrations → config}/knex/upgrades/20251024070000_upgrade_v3_5_to_v3_6.js +0 -0
@@ -0,0 +1,1550 @@
1
+ // sql-dump.ts - Utility functions for generating SQL dump files
2
+ import knex from 'knex';
3
+ import { SchemaInspector } from 'knex-schema-inspector';
4
+ import { convertIdentifierQuotes, convertTimestampFunctions, } from './sql-dump-converters.js';
5
+ import { debugLog } from './debug-logger.js';
6
+ /**
7
+ * Convert data type from source format to target format using metadata
8
+ */
9
+ function convertDataType(columnType, targetFormat, maxLength) {
10
+ const upperType = columnType.toUpperCase();
11
+ if (targetFormat === 'mysql') {
12
+ // MySQL-specific conversions
13
+ if (upperType.includes('SERIAL') || upperType.includes('BIGSERIAL')) {
14
+ return 'BIGINT AUTO_INCREMENT';
15
+ }
16
+ if (upperType.includes('TEXT')) {
17
+ return 'TEXT';
18
+ }
19
+ if (upperType.includes('VARCHAR')) {
20
+ // Use maxLength from metadata
21
+ const length = maxLength && maxLength <= 191 ? maxLength : 191;
22
+ return `VARCHAR(${length})`;
23
+ }
24
+ if (upperType.includes('TIMESTAMP') || upperType.includes('TIMESTAMPTZ')) {
25
+ return 'DATETIME';
26
+ }
27
+ if (upperType.includes('BOOLEAN') || upperType === 'BOOL') {
28
+ return 'TINYINT(1)';
29
+ }
30
+ if (upperType === 'INTEGER' || upperType === 'INT') {
31
+ return 'INT';
32
+ }
33
+ if (upperType.includes('BIGINT')) {
34
+ return 'BIGINT';
35
+ }
36
+ }
37
+ else if (targetFormat === 'postgresql') {
38
+ // PostgreSQL-specific conversions
39
+ if (upperType.includes('AUTOINCREMENT') || upperType.includes('AUTO_INCREMENT')) {
40
+ return 'SERIAL';
41
+ }
42
+ if (upperType.includes('DATETIME')) {
43
+ return 'TIMESTAMP';
44
+ }
45
+ if (upperType.includes('TINYINT') || upperType === 'BIT') {
46
+ return 'BOOLEAN';
47
+ }
48
+ if (upperType.includes('TEXT')) {
49
+ return 'TEXT';
50
+ }
51
+ if (upperType.includes('VARCHAR')) {
52
+ const length = maxLength || 255;
53
+ return `VARCHAR(${length})`;
54
+ }
55
+ }
56
+ else if (targetFormat === 'sqlite') {
57
+ // SQLite-specific conversions
58
+ if (upperType.includes('SERIAL') || upperType.includes('AUTO_INCREMENT') || upperType.includes('AUTOINCREMENT')) {
59
+ return 'INTEGER';
60
+ }
61
+ if (upperType.includes('VARCHAR') || upperType.includes('TEXT')) {
62
+ return 'TEXT';
63
+ }
64
+ if (upperType.includes('TINYINT') || upperType.includes('BOOLEAN')) {
65
+ return 'INTEGER';
66
+ }
67
+ if (upperType.includes('DATETIME') || upperType.includes('TIMESTAMP')) {
68
+ return 'INTEGER'; // SQLite stores as Unix timestamp
69
+ }
70
+ }
71
+ // Default: return as-is
72
+ return columnType;
73
+ }
74
+ /**
75
+ * Convert default value from SQLite functions to target format
76
+ * Handles: unixepoch() → UNIX_TIMESTAMP() / EXTRACT(epoch FROM NOW())
77
+ * strftime() → DATE_FORMAT() / TO_CHAR()
78
+ */
79
+ function convertDefaultValue(defaultValue, targetFormat) {
80
+ if (!defaultValue) {
81
+ return null;
82
+ }
83
+ const lower = defaultValue.toLowerCase().trim();
84
+ // SQLite unixepoch() conversions
85
+ if (lower.includes('unixepoch()') || lower === 'unixepoch()') {
86
+ if (targetFormat === 'mysql') {
87
+ return 'UNIX_TIMESTAMP()';
88
+ }
89
+ else if (targetFormat === 'postgresql') {
90
+ return 'EXTRACT(epoch FROM NOW())::INTEGER';
91
+ }
92
+ return null; // Remove for SQLite
93
+ }
94
+ // SQLite strftime('%s', 'now') - Unix timestamp (INTEGER)
95
+ // Must check BEFORE generic strftime to handle this specific case
96
+ if (lower.includes("strftime('%s'") || lower.includes('strftime("%s"')) {
97
+ if (targetFormat === 'mysql') {
98
+ return 'UNIX_TIMESTAMP()';
99
+ }
100
+ else if (targetFormat === 'postgresql') {
101
+ return 'EXTRACT(epoch FROM NOW())::INTEGER';
102
+ }
103
+ return null;
104
+ }
105
+ // SQLite strftime conversions (datetime formats)
106
+ if (lower.includes('strftime')) {
107
+ if (targetFormat === 'mysql') {
108
+ // strftime('%Y-%m-%d %H:%M:%S', 'now') → NOW()
109
+ return 'NOW()';
110
+ }
111
+ else if (targetFormat === 'postgresql') {
112
+ return 'NOW()';
113
+ }
114
+ return null;
115
+ }
116
+ // Remove parentheses for simple values
117
+ let cleanValue = defaultValue;
118
+ if (lower.startsWith('(') && lower.endsWith(')')) {
119
+ cleanValue = defaultValue.substring(1, defaultValue.length - 1);
120
+ }
121
+ // For numeric defaults, normalize floating point values
122
+ // Remove trailing .0 (e.g., 1.0 → 1) to avoid type mismatches
123
+ const trimmed = cleanValue.trim();
124
+ if (/^\d+\.0+$/.test(trimmed)) {
125
+ return trimmed.replace(/\.0+$/, '');
126
+ }
127
+ // Quote string literals if not already quoted and not a number
128
+ if (!/^['"]/.test(trimmed) && !/^-?\d+(\.\d+)?$/.test(trimmed) && !/^(true|false|null|current_timestamp|now\(\)|unix_timestamp\(\))$/i.test(trimmed)) {
129
+ return `'${trimmed}'`;
130
+ }
131
+ return trimmed;
132
+ }
133
+ /**
134
+ * Quote identifier (table or column name) for target database
135
+ */
136
+ export function quoteIdentifier(name, format) {
137
+ switch (format) {
138
+ case 'mysql':
139
+ return `\`${name}\``;
140
+ case 'postgresql':
141
+ case 'sqlite':
142
+ return `"${name}"`;
143
+ default:
144
+ return `"${name}"`;
145
+ }
146
+ }
147
+ /**
148
+ * Build column definition from Column metadata
149
+ */
150
+ function buildColumnDefinition(col, targetFormat) {
151
+ const quotedName = quoteIdentifier(col.name, targetFormat);
152
+ let dataType = convertDataType(col.data_type, targetFormat, col.max_length);
153
+ // MySQL: TEXT columns cannot be used in UNIQUE/PRIMARY KEY constraints without prefix length
154
+ // Convert TEXT to VARCHAR(191) for utf8mb4 compatibility (768 bytes ÷ 4 bytes/char = 191)
155
+ if (targetFormat === 'mysql' && dataType.toUpperCase() === 'TEXT') {
156
+ if (col.is_unique || col.is_primary_key || col.foreign_key_table || col.in_composite_unique) {
157
+ dataType = 'VARCHAR(191)';
158
+ }
159
+ }
160
+ let def = `${quotedName} ${dataType}`;
161
+ // Handle NOT NULL constraint
162
+ if (col.is_nullable === false) {
163
+ def += ' NOT NULL';
164
+ }
165
+ // Handle DEFAULT value
166
+ if (col.default_value !== null && col.default_value !== undefined) {
167
+ let convertedDefault = convertDefaultValue(String(col.default_value), targetFormat);
168
+ if (convertedDefault !== null && convertedDefault !== '') {
169
+ // MySQL restrictions for DEFAULT values
170
+ const isTextColumn = dataType.toUpperCase().includes('TEXT') || dataType.toUpperCase().includes('BLOB');
171
+ const isFunctionCall = convertedDefault.includes('(') && convertedDefault.includes(')');
172
+ const isIntegerColumn = dataType.toUpperCase().includes('INT');
173
+ const isBooleanColumn = dataType.toUpperCase().includes('BOOLEAN');
174
+ // PostgreSQL: Convert integer defaults to boolean for BOOLEAN columns
175
+ if (targetFormat === 'postgresql' && isBooleanColumn && /^[01]$/.test(convertedDefault)) {
176
+ convertedDefault = convertedDefault === '1' ? 'TRUE' : 'FALSE';
177
+ }
178
+ if (targetFormat === 'mysql') {
179
+ // MySQL doesn't allow DEFAULT on TEXT/BLOB columns
180
+ if (isTextColumn) {
181
+ // Skip DEFAULT - application must handle at runtime
182
+ }
183
+ // MySQL DOES support certain function calls as DEFAULT for INTEGER columns
184
+ // (e.g., UNIX_TIMESTAMP(), CURRENT_TIMESTAMP)
185
+ // Only skip if conversion returned null (meaning function not supported)
186
+ else {
187
+ def += ` DEFAULT ${convertedDefault}`;
188
+ }
189
+ }
190
+ else {
191
+ def += ` DEFAULT ${convertedDefault}`;
192
+ }
193
+ }
194
+ }
195
+ // Handle AUTO_INCREMENT for MySQL
196
+ if (targetFormat === 'mysql' && col.is_generated && col.generation_expression === null) {
197
+ if (!def.includes('AUTO_INCREMENT')) {
198
+ def += ' AUTO_INCREMENT';
199
+ }
200
+ }
201
+ // Handle UNIQUE constraint (skip if already PRIMARY KEY)
202
+ if (col.is_unique && !col.is_primary_key) {
203
+ def += ' UNIQUE';
204
+ }
205
+ return def;
206
+ }
207
+ /**
208
+ * Build FOREIGN KEY definition from ForeignKey metadata
209
+ */
210
+ function buildForeignKeyDefinition(fk, targetFormat) {
211
+ const quotedColumn = quoteIdentifier(fk.column, targetFormat);
212
+ const quotedForeignTable = quoteIdentifier(fk.foreign_key_table, targetFormat);
213
+ const quotedForeignColumn = quoteIdentifier(fk.foreign_key_column, targetFormat);
214
+ let fkDef = `FOREIGN KEY (${quotedColumn}) REFERENCES ${quotedForeignTable}(${quotedForeignColumn})`;
215
+ // Add ON DELETE clause
216
+ if (fk.on_delete && fk.on_delete !== 'NO ACTION') {
217
+ fkDef += ` ON DELETE ${fk.on_delete}`;
218
+ }
219
+ // Add ON UPDATE clause
220
+ if (fk.on_update && fk.on_update !== 'NO ACTION') {
221
+ fkDef += ` ON UPDATE ${fk.on_update}`;
222
+ }
223
+ return fkDef;
224
+ }
225
+ /**
226
+ * Enforce NOT NULL constraints on PRIMARY KEY columns
227
+ * MySQL and PostgreSQL require all PRIMARY KEY columns to be NOT NULL
228
+ */
229
+ function enforceNotNullOnPrimaryKey(createSql, pkColumns) {
230
+ if (pkColumns.length === 0) {
231
+ return createSql;
232
+ }
233
+ // Split the CREATE TABLE statement to process column definitions
234
+ const lines = createSql.split('\n');
235
+ const processedLines = lines.map((line) => {
236
+ // Check if this line defines one of the PRIMARY KEY columns
237
+ for (const pkCol of pkColumns) {
238
+ // Match column definition (handle different quote styles)
239
+ const colPattern = new RegExp(`^\\s*[\`"']?${pkCol}[\`"']?\\s+`, 'i');
240
+ if (colPattern.test(line)) {
241
+ // Check if NOT NULL is already present
242
+ if (!/NOT\s+NULL/i.test(line)) {
243
+ // Find position to insert NOT NULL (before DEFAULT, FOREIGN KEY, CHECK, or comma/closing paren)
244
+ const insertBeforePatterns = [
245
+ /\s+DEFAULT/i,
246
+ /\s+FOREIGN\s+KEY/i,
247
+ /\s+CHECK/i,
248
+ /,\s*$/,
249
+ /\)\s*$/,
250
+ ];
251
+ let insertPos = line.length;
252
+ for (const pattern of insertBeforePatterns) {
253
+ const match = line.match(pattern);
254
+ if (match && match.index !== undefined) {
255
+ insertPos = Math.min(insertPos, match.index);
256
+ }
257
+ }
258
+ // Insert NOT NULL
259
+ return line.slice(0, insertPos) + ' NOT NULL' + line.slice(insertPos);
260
+ }
261
+ }
262
+ }
263
+ return line;
264
+ });
265
+ return processedLines.join('\n');
266
+ }
267
+ /**
268
+ * Get primary key columns for a table
269
+ */
270
+ export async function getPrimaryKeyColumns(knex, table) {
271
+ const client = knex.client.config.client;
272
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
273
+ // SQLite: Use PRAGMA table_info
274
+ const result = await knex.raw(`PRAGMA table_info(${table})`);
275
+ return result
276
+ .filter((col) => col.pk > 0)
277
+ .sort((a, b) => a.pk - b.pk)
278
+ .map((col) => col.name);
279
+ }
280
+ else if (client === 'mysql' || client === 'mysql2') {
281
+ // MySQL: Query information_schema
282
+ const result = await knex.raw(`
283
+ SELECT COLUMN_NAME
284
+ FROM information_schema.KEY_COLUMN_USAGE
285
+ WHERE TABLE_SCHEMA = DATABASE()
286
+ AND TABLE_NAME = ?
287
+ AND CONSTRAINT_NAME = 'PRIMARY'
288
+ ORDER BY ORDINAL_POSITION
289
+ `, [table]);
290
+ return result[0].map((row) => row.COLUMN_NAME);
291
+ }
292
+ else if (client === 'pg') {
293
+ // PostgreSQL: Query information_schema
294
+ const result = await knex.raw(`
295
+ SELECT a.attname AS column_name
296
+ FROM pg_index i
297
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
298
+ WHERE i.indrelid = ?::regclass
299
+ AND i.indisprimary
300
+ ORDER BY a.attnum
301
+ `, [table]);
302
+ return result.rows.map((row) => row.column_name);
303
+ }
304
+ throw new Error(`Unsupported database client: ${client}`);
305
+ }
306
+ /**
307
+ * Get CREATE TABLE statement for a table using knex-schema-inspector
308
+ * Replaces regex-based SQL conversion with metadata-driven approach
309
+ */
310
+ export async function getCreateTableStatement(knex, table, targetFormat) {
311
+ const client = knex.client.config.client;
312
+ // Initialize schema inspector (database-agnostic)
313
+ const inspector = SchemaInspector(knex);
314
+ // Get column metadata
315
+ const columns = await inspector.columnInfo(table);
316
+ if (columns.length === 0) {
317
+ throw new Error(`Table ${table} not found or has no columns`);
318
+ }
319
+ // Fix: knex-schema-inspector doesn't detect composite PRIMARY KEYs and UNIQUE constraints from SQLite properly
320
+ // Manually detect them using PRAGMA index_list
321
+ const compositeUniqueConstraints = []; // Track composite UNIQUE constraints
322
+ let compositePrimaryKey = null; // Track composite PRIMARY KEY
323
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
324
+ const indexResult = await knex.raw(`PRAGMA index_list(${table})`);
325
+ // Knex raw() returns an array directly for SQLite
326
+ const indexes = Array.isArray(indexResult) ? indexResult : [];
327
+ for (const index of indexes) {
328
+ // Check for PRIMARY KEY index
329
+ if (index.origin === 'pk' && index.unique === 1) {
330
+ const indexInfoResult = await knex.raw(`PRAGMA index_info(${index.name})`);
331
+ const indexInfo = Array.isArray(indexInfoResult) ? indexInfoResult : [];
332
+ const columnNames = indexInfo.map((idxCol) => idxCol.name);
333
+ if (columnNames.length > 1) {
334
+ // Composite PRIMARY KEY detected
335
+ compositePrimaryKey = columnNames;
336
+ debugLog('DEBUG', `Found composite PRIMARY KEY on ${table}(${columnNames.join(', ')}) from ${index.name}`);
337
+ }
338
+ }
339
+ // Check if this is a UNIQUE index (skip PRIMARY KEY indexes)
340
+ else if (index.unique === 1 && index.origin !== 'pk') {
341
+ // Get columns in this index
342
+ const indexInfoResult = await knex.raw(`PRAGMA index_info(${index.name})`);
343
+ const indexInfo = Array.isArray(indexInfoResult) ? indexInfoResult : [];
344
+ const columnNames = indexInfo.map((idxCol) => idxCol.name);
345
+ if (columnNames.length === 1) {
346
+ // Single-column UNIQUE - mark column as unique
347
+ const col = columns.find(c => c.name === columnNames[0]);
348
+ if (col && !col.is_primary_key) {
349
+ col.is_unique = true;
350
+ debugLog('DEBUG', `Marked ${table}.${col.name} as UNIQUE (single-column from ${index.name})`);
351
+ }
352
+ }
353
+ else if (columnNames.length > 1) {
354
+ // Composite UNIQUE - add to table-level constraints
355
+ compositeUniqueConstraints.push(columnNames);
356
+ debugLog('DEBUG', `Found composite UNIQUE on ${table}(${columnNames.join(', ')}) from ${index.name}`);
357
+ // For MySQL: Convert TEXT to VARCHAR(191) for columns in composite UNIQUE
358
+ if (targetFormat === 'mysql') {
359
+ for (const colName of columnNames) {
360
+ const col = columns.find(c => c.name === colName);
361
+ if (col && !col.is_primary_key) {
362
+ // Mark as part of composite unique (will be converted to VARCHAR later)
363
+ col.in_composite_unique = true;
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ }
371
+ // Build column definitions using buildColumnDefinition()
372
+ const columnDefs = columns.map(col => buildColumnDefinition(col, targetFormat));
373
+ // Add PRIMARY KEY constraint (with MySQL prefix length handling)
374
+ // Use composite PRIMARY KEY if detected, otherwise fall back to column metadata
375
+ const pkColumns = compositePrimaryKey || columns.filter(col => col.is_primary_key).map(col => col.name);
376
+ if (pkColumns.length > 0) {
377
+ // For MySQL: Apply (191) prefix to TEXT/long VARCHAR columns
378
+ if (targetFormat === 'mysql') {
379
+ const processedPkCols = pkColumns.map((colName) => {
380
+ const col = columns.find(c => c.name === colName);
381
+ if (col && (col.data_type.toUpperCase() === 'TEXT' ||
382
+ (col.data_type.toUpperCase().includes('VARCHAR') && col.max_length && col.max_length > 191))) {
383
+ return `${quoteIdentifier(colName, targetFormat)}(191)`;
384
+ }
385
+ return quoteIdentifier(colName, targetFormat);
386
+ }).join(', ');
387
+ columnDefs.push(`PRIMARY KEY (${processedPkCols})`);
388
+ }
389
+ else {
390
+ const quotedPkColumns = pkColumns.map(col => quoteIdentifier(col, targetFormat));
391
+ columnDefs.push(`PRIMARY KEY (${quotedPkColumns.join(', ')})`);
392
+ }
393
+ }
394
+ // Add FOREIGN KEY constraints using buildForeignKeyDefinition()
395
+ const foreignKeys = await inspector.foreignKeys(table);
396
+ for (const fk of foreignKeys) {
397
+ columnDefs.push(buildForeignKeyDefinition(fk, targetFormat));
398
+ }
399
+ // Add composite UNIQUE constraints (from SQLite multi-column UNIQUE indexes)
400
+ for (const uniqueCols of compositeUniqueConstraints) {
401
+ const quotedCols = uniqueCols.map(col => quoteIdentifier(col, targetFormat)).join(', ');
402
+ columnDefs.push(`UNIQUE (${quotedCols})`);
403
+ }
404
+ // Build CREATE TABLE statement with IF NOT EXISTS for idempotency
405
+ const quotedTable = quoteIdentifier(table, targetFormat);
406
+ const createSql = `CREATE TABLE IF NOT EXISTS ${quotedTable} (\n ${columnDefs.join(',\n ')}\n)`;
407
+ // Add database-specific table options
408
+ if (targetFormat === 'mysql') {
409
+ return createSql + ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;';
410
+ }
411
+ return createSql + ';';
412
+ }
413
+ /**
414
+ * Get all table names from the database (excluding system tables)
415
+ */
416
+ export async function getAllTables(knex, includeKnexTables = false) {
417
+ const client = knex.client.config.client;
418
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
419
+ const knexFilter = includeKnexTables ? '' : "AND name NOT LIKE 'knex_%'";
420
+ const result = await knex.raw(`
421
+ SELECT name FROM sqlite_master
422
+ WHERE type='table'
423
+ AND name NOT LIKE 'sqlite_%'
424
+ ${knexFilter}
425
+ ORDER BY name
426
+ `);
427
+ return result.map((row) => row.name);
428
+ }
429
+ else if (client === 'mysql' || client === 'mysql2') {
430
+ const result = await knex.raw('SHOW TABLES');
431
+ const tableKey = Object.keys(result[0][0])[0];
432
+ const tables = result[0].map((row) => row[tableKey]);
433
+ return includeKnexTables ? tables : tables.filter((t) => !t.startsWith('knex_'));
434
+ }
435
+ else if (client === 'pg') {
436
+ const knexFilter = includeKnexTables ? '' : "AND tablename NOT LIKE 'knex_%'";
437
+ const result = await knex.raw(`
438
+ SELECT tablename FROM pg_tables
439
+ WHERE schemaname = 'public'
440
+ ${knexFilter}
441
+ ORDER BY tablename
442
+ `);
443
+ return result.rows.map((row) => row.tablename);
444
+ }
445
+ throw new Error(`Unsupported database client: ${client}`);
446
+ }
447
+ /**
448
+ * Get all view names from the database
449
+ */
450
+ export async function getAllViews(knex) {
451
+ const client = knex.client.config.client;
452
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
453
+ const result = await knex.raw(`
454
+ SELECT name FROM sqlite_master
455
+ WHERE type='view'
456
+ ORDER BY name
457
+ `);
458
+ return result.map((row) => row.name);
459
+ }
460
+ else if (client === 'mysql' || client === 'mysql2') {
461
+ const result = await knex.raw(`
462
+ SELECT TABLE_NAME as name
463
+ FROM information_schema.VIEWS
464
+ WHERE TABLE_SCHEMA = DATABASE()
465
+ ORDER BY TABLE_NAME
466
+ `);
467
+ return result[0].map((row) => row.name);
468
+ }
469
+ else if (client === 'pg') {
470
+ const result = await knex.raw(`
471
+ SELECT viewname as name
472
+ FROM pg_views
473
+ WHERE schemaname = 'public'
474
+ ORDER BY viewname
475
+ `);
476
+ return result.rows.map((row) => row.name);
477
+ }
478
+ throw new Error(`Unsupported database client: ${client}`);
479
+ }
480
+ /**
481
+ * Get all indexes for a table
482
+ */
483
+ export async function getAllIndexes(knex, table) {
484
+ const client = knex.client.config.client;
485
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
486
+ const result = await knex.raw(`
487
+ SELECT name FROM sqlite_master
488
+ WHERE type='index'
489
+ AND tbl_name=?
490
+ AND sql IS NOT NULL
491
+ ORDER BY name
492
+ `, [table]);
493
+ return result.map((row) => row.name);
494
+ }
495
+ else if (client === 'mysql' || client === 'mysql2') {
496
+ const result = await knex.raw(`
497
+ SHOW INDEXES FROM ?? WHERE Key_name != 'PRIMARY'
498
+ `, [table]);
499
+ // Group by index name (indexes can span multiple columns)
500
+ const indexNames = new Set();
501
+ for (const row of result[0]) {
502
+ indexNames.add(row.Key_name);
503
+ }
504
+ return Array.from(indexNames).sort();
505
+ }
506
+ else if (client === 'pg') {
507
+ const result = await knex.raw(`
508
+ SELECT indexname
509
+ FROM pg_indexes
510
+ WHERE schemaname = 'public'
511
+ AND tablename = ?
512
+ AND indexname NOT LIKE '%_pkey'
513
+ ORDER BY indexname
514
+ `, [table]);
515
+ return result.rows.map((row) => row.indexname);
516
+ }
517
+ // For other database clients
518
+ return [];
519
+ }
520
+ /**
521
+ * Get CREATE INDEX statement for an index
522
+ */
523
+ export async function getCreateIndexStatement(knex, indexName, targetFormat) {
524
+ const client = knex.client.config.client;
525
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
526
+ const result = await knex.raw(`
527
+ SELECT sql, tbl_name FROM sqlite_master
528
+ WHERE type='index' AND name=?
529
+ `, [indexName]);
530
+ if (result.length === 0 || !result[0].sql) {
531
+ throw new Error(`Index ${indexName} not found`);
532
+ }
533
+ let createSql = result[0].sql;
534
+ const tableName = result[0].tbl_name;
535
+ // Convert to target format if needed
536
+ if (targetFormat === 'mysql') {
537
+ createSql = convertIdentifierQuotes(createSql, 'mysql');
538
+ // MySQL has a 3072 byte limit for index keys (with utf8mb4, that's ~768 chars)
539
+ // Add prefix length (191 chars) to VARCHAR columns longer than 191 to stay under limit
540
+ // Match: CREATE [UNIQUE] INDEX name ON table (col1, col2, ...)
541
+ const match = createSql.match(/\((.*?)\)(?:\s|$)/);
542
+ if (match) {
543
+ const columns = match[1];
544
+ // Get column info for the table
545
+ const columnInfo = await knex(tableName).columnInfo();
546
+ // Process each column in the index
547
+ const processedColumns = columns.split(',').map((col) => {
548
+ // Remove quotes/backticks and DESC/ASC keywords
549
+ let colSpec = col.trim().replace(/[`"]/g, '');
550
+ const colName = colSpec.replace(/\s+(DESC|ASC)$/i, '').trim();
551
+ const info = columnInfo[colName];
552
+ // If VARCHAR/TEXT longer than 191 chars, add prefix length
553
+ if (info && info.type) {
554
+ const type = info.type.toLowerCase();
555
+ if (type.includes('varchar') || type.includes('text')) {
556
+ // Check maxLength property first (SQLite returns this separately)
557
+ const maxLength = info.maxLength;
558
+ if (maxLength && parseInt(maxLength) > 191) {
559
+ return `\`${colName}\`(191)`;
560
+ }
561
+ // Also try extracting length from VARCHAR(n) in type string
562
+ const lengthMatch = type.match(/varchar\((\d+)\)/);
563
+ if (lengthMatch && parseInt(lengthMatch[1]) > 191) {
564
+ return `\`${colName}\`(191)`;
565
+ }
566
+ else if (type.includes('text')) {
567
+ // TEXT columns have no fixed length, always add prefix
568
+ return `\`${colName}\`(191)`;
569
+ }
570
+ }
571
+ }
572
+ return `\`${colName}\``;
573
+ }).join(', ');
574
+ createSql = createSql.replace(/\((.*?)\)(?:\s|$)/, `(${processedColumns})`);
575
+ }
576
+ }
577
+ else if (targetFormat === 'postgresql') {
578
+ createSql = convertIdentifierQuotes(createSql, 'postgresql');
579
+ }
580
+ return createSql + ';';
581
+ }
582
+ else if (client === 'mysql' || client === 'mysql2') {
583
+ // For MySQL, we need to find which table the index belongs to
584
+ // First, get all tables and search for the index
585
+ const tablesResult = await knex.raw('SHOW TABLES');
586
+ const tableKey = Object.keys(tablesResult[0][0])[0];
587
+ const tables = tablesResult[0].map((row) => row[tableKey]);
588
+ let indexInfo = null;
589
+ let tableName = '';
590
+ // Search for the index in all tables
591
+ for (const table of tables) {
592
+ const result = await knex.raw(`SHOW INDEXES FROM ?? WHERE Key_name = ?`, [table, indexName]);
593
+ if (result[0].length > 0) {
594
+ indexInfo = result[0];
595
+ tableName = table;
596
+ break;
597
+ }
598
+ }
599
+ if (!indexInfo || indexInfo.length === 0) {
600
+ throw new Error(`Index ${indexName} not found`);
601
+ }
602
+ const isUnique = indexInfo[0].Non_unique === 0;
603
+ // Get column info for prefix length handling
604
+ const columnInfo = await knex(tableName).columnInfo();
605
+ // Build column list with proper prefix lengths
606
+ const columns = indexInfo.map((row) => {
607
+ const colName = row.Column_name;
608
+ const colMeta = columnInfo[colName];
609
+ // Handle prefix length for long VARCHAR/TEXT columns
610
+ if (colMeta && colMeta.type) {
611
+ const type = colMeta.type.toLowerCase();
612
+ if (type.includes('varchar') || type.includes('text')) {
613
+ const maxLength = colMeta.maxLength;
614
+ // Extract length from type string like "varchar(255)"
615
+ const lengthMatch = type.match(/varchar\((\d+)\)/);
616
+ const typeLength = lengthMatch ? parseInt(lengthMatch[1]) : null;
617
+ if ((maxLength && parseInt(maxLength) > 191) || (typeLength && typeLength > 191) || type.includes('text')) {
618
+ return `\`${colName}\`(191)`;
619
+ }
620
+ }
621
+ }
622
+ return `\`${colName}\``;
623
+ }).join(', ');
624
+ const uniqueStr = isUnique ? 'UNIQUE ' : '';
625
+ let createSql = `CREATE ${uniqueStr}INDEX \`${indexName}\` ON \`${tableName}\` (${columns})`;
626
+ // Apply cross-database conversion
627
+ if (targetFormat === 'postgresql') {
628
+ createSql = convertIdentifierQuotes(createSql, 'postgresql');
629
+ }
630
+ else if (targetFormat === 'sqlite') {
631
+ createSql = convertIdentifierQuotes(createSql, 'sqlite');
632
+ // Remove prefix lengths for SQLite (not supported)
633
+ createSql = createSql.replace(/\(191\)/g, '');
634
+ }
635
+ return createSql + ';';
636
+ }
637
+ else if (client === 'pg') {
638
+ // Get index definition from PostgreSQL
639
+ const result = await knex.raw(`
640
+ SELECT indexdef
641
+ FROM pg_indexes
642
+ WHERE schemaname = 'public' AND indexname = ?
643
+ `, [indexName]);
644
+ if (result.rows.length === 0) {
645
+ throw new Error(`Index ${indexName} not found`);
646
+ }
647
+ let createSql = result.rows[0].indexdef;
648
+ // Apply cross-database conversion
649
+ if (targetFormat === 'mysql') {
650
+ createSql = convertIdentifierQuotes(createSql, 'mysql');
651
+ // Handle prefix length for MySQL
652
+ // Extract table name from the CREATE INDEX statement
653
+ const tableMatch = createSql.match(/ON\s+(["`]?\w+["`]?)/i);
654
+ if (tableMatch) {
655
+ const tableName = tableMatch[1].replace(/["`]/g, '');
656
+ const columnInfo = await knex(tableName).columnInfo();
657
+ // Find column list in the index definition
658
+ const colMatch = createSql.match(/\((.*?)\)(?:\s|$)/);
659
+ if (colMatch) {
660
+ const columns = colMatch[1];
661
+ const processedColumns = columns.split(',').map((col) => {
662
+ const colName = col.trim().replace(/["`]/g, '').replace(/\s+(DESC|ASC)$/i, '').trim();
663
+ const info = columnInfo[colName];
664
+ if (info && info.type) {
665
+ const type = info.type.toLowerCase();
666
+ if (type.includes('varchar') || type.includes('text')) {
667
+ const maxLength = info.maxLength;
668
+ const lengthMatch = type.match(/varchar\((\d+)\)/);
669
+ const typeLength = lengthMatch ? parseInt(lengthMatch[1]) : null;
670
+ if ((maxLength && parseInt(maxLength) > 191) || (typeLength && typeLength > 191) || type.includes('text')) {
671
+ return `\`${colName}\`(191)`;
672
+ }
673
+ }
674
+ }
675
+ return `\`${colName}\``;
676
+ }).join(', ');
677
+ createSql = createSql.replace(/\((.*?)\)(?:\s|$)/, `(${processedColumns})`);
678
+ }
679
+ }
680
+ }
681
+ else if (targetFormat === 'sqlite') {
682
+ createSql = convertIdentifierQuotes(createSql, 'sqlite');
683
+ }
684
+ return createSql + ';';
685
+ }
686
+ throw new Error(`Unsupported database client: ${client}`);
687
+ }
688
+ /**
689
+ * Get CREATE VIEW statement for a view
690
+ */
691
+ export async function getCreateViewStatement(knex, viewName, targetFormat) {
692
+ const client = knex.client.config.client;
693
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
694
+ // SQLite: Get from sqlite_master
695
+ const result = await knex.raw(`
696
+ SELECT sql FROM sqlite_master
697
+ WHERE type='view' AND name=?
698
+ `, [viewName]);
699
+ if (result.length === 0 || !result[0].sql) {
700
+ throw new Error(`View ${viewName} not found`);
701
+ }
702
+ let createSql = result[0].sql;
703
+ // Convert SQLite syntax to target format if needed
704
+ if (targetFormat === 'mysql') {
705
+ // Convert to MySQL syntax using shared converters
706
+ createSql = convertIdentifierQuotes(createSql, 'mysql');
707
+ createSql = convertTimestampFunctions(createSql, 'mysql');
708
+ }
709
+ else if (targetFormat === 'postgresql') {
710
+ // Convert to PostgreSQL syntax using shared converters
711
+ createSql = convertIdentifierQuotes(createSql, 'postgresql');
712
+ createSql = convertTimestampFunctions(createSql, 'postgresql');
713
+ // Convert GROUP_CONCAT(col, sep) → string_agg(col, sep)
714
+ createSql = createSql.replace(/GROUP_CONCAT\s*\(/gi, 'string_agg(');
715
+ // Cast integer comparisons to be type-safe: column = 1 → column::integer = 1
716
+ // This works for both boolean columns (TRUE::integer = 1) and integer enum columns
717
+ createSql = createSql.replace(/(\w+)\s*=\s*([01])\b/g, '$1::integer = $2');
718
+ }
719
+ return createSql + ';';
720
+ }
721
+ else if (client === 'mysql' || client === 'mysql2') {
722
+ // MySQL: Use SHOW CREATE VIEW
723
+ const result = await knex.raw(`SHOW CREATE VIEW ??`, [viewName]);
724
+ let createSql = result[0][0]['Create View'];
725
+ if (targetFormat === 'sqlite') {
726
+ // Convert MySQL to SQLite using shared converters
727
+ createSql = convertIdentifierQuotes(createSql, 'sqlite');
728
+ createSql = convertTimestampFunctions(createSql, 'sqlite');
729
+ return createSql + ';';
730
+ }
731
+ else if (targetFormat === 'postgresql') {
732
+ // Convert MySQL to PostgreSQL using shared converters
733
+ createSql = convertIdentifierQuotes(createSql, 'postgresql');
734
+ createSql = convertTimestampFunctions(createSql, 'postgresql');
735
+ return createSql + ';';
736
+ }
737
+ return createSql + ';';
738
+ }
739
+ else if (client === 'pg') {
740
+ // PostgreSQL: Get from pg_views
741
+ const result = await knex.raw(`
742
+ SELECT definition
743
+ FROM pg_views
744
+ WHERE schemaname = 'public' AND viewname = ?
745
+ `, [viewName]);
746
+ if (result.rows.length === 0) {
747
+ throw new Error(`View ${viewName} not found`);
748
+ }
749
+ let createSql = `CREATE VIEW "${viewName}" AS ${result.rows[0].definition}`;
750
+ if (targetFormat === 'mysql') {
751
+ createSql = convertIdentifierQuotes(createSql, 'mysql');
752
+ return createSql + ';';
753
+ }
754
+ else if (targetFormat === 'sqlite') {
755
+ createSql = convertTimestampFunctions(createSql, 'sqlite');
756
+ return createSql + ';';
757
+ }
758
+ return createSql + ';';
759
+ }
760
+ throw new Error(`Unsupported database client: ${client}`);
761
+ }
762
+ /**
763
+ * Convert backtick-quoted identifiers to double-quoted identifiers
764
+ * Only replaces backticks that are identifier quotes, not those inside string literals
765
+ */
766
+ function convertBackticksToDoubleQuotes(sql) {
767
+ // Match backtick-quoted identifiers: `identifier`
768
+ // Only matches word characters, dots, hyphens, and underscores (valid identifier chars)
769
+ return sql.replace(/`([a-zA-Z0-9_\.\-]+)`/g, '"$1"');
770
+ }
771
+ /**
772
+ * Format a value for SQL insertion
773
+ */
774
+ export function formatValue(value, format, table, column, columnType) {
775
+ // Handle NULL
776
+ if (value === null || value === undefined) {
777
+ return 'NULL';
778
+ }
779
+ // Special case: knex_migrations.migration_time
780
+ // Convert Unix timestamp (milliseconds) to datetime/timestamp string
781
+ if (table === 'knex_migrations' && column === 'migration_time' && typeof value === 'number') {
782
+ if (format === 'mysql') {
783
+ const date = new Date(value);
784
+ const year = date.getFullYear();
785
+ const month = String(date.getMonth() + 1).padStart(2, '0');
786
+ const day = String(date.getDate()).padStart(2, '0');
787
+ const hours = String(date.getHours()).padStart(2, '0');
788
+ const minutes = String(date.getMinutes()).padStart(2, '0');
789
+ const seconds = String(date.getSeconds()).padStart(2, '0');
790
+ return `'${year}-${month}-${day} ${hours}:${minutes}:${seconds}'`;
791
+ }
792
+ else if (format === 'postgresql') {
793
+ // PostgreSQL: Use to_timestamp() function
794
+ return `to_timestamp(${value / 1000})`; // Convert milliseconds to seconds
795
+ }
796
+ }
797
+ // Handle numbers
798
+ if (typeof value === 'number') {
799
+ // Special case: PostgreSQL boolean columns stored as 0/1 in SQLite
800
+ if (format === 'postgresql' && columnType === 'boolean') {
801
+ return value === 1 ? 'TRUE' : 'FALSE';
802
+ }
803
+ return String(value);
804
+ }
805
+ // Handle booleans
806
+ if (typeof value === 'boolean') {
807
+ if (format === 'postgresql') {
808
+ return value ? 'TRUE' : 'FALSE';
809
+ }
810
+ // MySQL and SQLite use 0/1
811
+ return value ? '1' : '0';
812
+ }
813
+ // Handle Buffer (binary data)
814
+ if (Buffer.isBuffer(value)) {
815
+ if (format === 'postgresql') {
816
+ // PostgreSQL bytea hex format
817
+ return `'\\x${value.toString('hex')}'::bytea`;
818
+ }
819
+ // MySQL and SQLite hex format
820
+ return `X'${value.toString('hex')}'`;
821
+ }
822
+ // Handle strings
823
+ if (typeof value === 'string') {
824
+ // Escape single quotes by doubling them
825
+ const escaped = value.replace(/'/g, "''");
826
+ // Also escape backslashes for MySQL
827
+ const finalEscaped = format === 'mysql' ? escaped.replace(/\\/g, '\\\\') : escaped;
828
+ return `'${finalEscaped}'`;
829
+ }
830
+ // Handle objects/arrays (JSON)
831
+ if (typeof value === 'object') {
832
+ const jsonStr = JSON.stringify(value).replace(/'/g, "''");
833
+ return `'${jsonStr}'`;
834
+ }
835
+ // Fallback
836
+ return 'NULL';
837
+ }
838
+ /**
839
+ * Convert value with type-aware conversion for cross-database migration
840
+ * Uses Knex columnInfo() metadata for accurate type detection
841
+ *
842
+ * @internal - Exported for testing only
843
+ */
844
+ export function convertValueWithType(value, columnName, columnInfo, // From knex(table).columnInfo()
845
+ sourceFormat, targetFormat) {
846
+ // Handle NULL
847
+ if (value === null || value === undefined) {
848
+ return 'NULL';
849
+ }
850
+ const colMeta = columnInfo.get(columnName);
851
+ if (!colMeta) {
852
+ // Fallback to basic formatValue
853
+ return formatValue(value, targetFormat);
854
+ }
855
+ const colType = (colMeta.type || '').toLowerCase();
856
+ // Boolean conversion - enhanced detection
857
+ // Knex columnInfo types: 'boolean' (PostgreSQL), 'tinyint' (MySQL), 'integer' (SQLite boolean stored as 0/1)
858
+ const isBooleanColumn = colType.includes('bool') ||
859
+ colType === 'tinyint' ||
860
+ colType === 'bit' ||
861
+ colMeta.type === 'boolean' ||
862
+ // Additional heuristic: maxLength === 1 for tinyint(1) in MySQL
863
+ (colType === 'integer' && colMeta.maxLength === 1);
864
+ if (isBooleanColumn) {
865
+ // Normalize value to boolean
866
+ const boolValue = Boolean(value);
867
+ if (targetFormat === 'postgresql') {
868
+ return boolValue ? 'TRUE' : 'FALSE';
869
+ }
870
+ // SQLite and MySQL use 0/1
871
+ return boolValue ? '1' : '0';
872
+ }
873
+ // Timestamp/DateTime conversion - enhanced with columnInfo metadata
874
+ const isTimestampColumn = colType.includes('timestamp') ||
875
+ colType.includes('datetime') ||
876
+ colType.includes('date') ||
877
+ colType === 'time';
878
+ if (isTimestampColumn) {
879
+ if (typeof value === 'number') {
880
+ // Unix timestamp - check if milliseconds or seconds based on magnitude
881
+ const timestamp = value > 10000000000 ? value : value * 1000;
882
+ const date = new Date(timestamp);
883
+ // ISO 8601 format: YYYY-MM-DD HH:MM:SS
884
+ const year = date.getUTCFullYear();
885
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
886
+ const day = String(date.getUTCDate()).padStart(2, '0');
887
+ const hours = String(date.getUTCHours()).padStart(2, '0');
888
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
889
+ const seconds = String(date.getUTCSeconds()).padStart(2, '0');
890
+ const isoString = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
891
+ if (targetFormat === 'postgresql') {
892
+ return `'${isoString}'::timestamp`;
893
+ }
894
+ else if (targetFormat === 'mysql') {
895
+ return `'${isoString}'`;
896
+ }
897
+ return `'${isoString}'`;
898
+ }
899
+ else if (typeof value === 'string') {
900
+ // Already formatted string - ensure proper escaping
901
+ const escaped = value.replace(/'/g, "''");
902
+ if (targetFormat === 'postgresql') {
903
+ return `'${escaped}'::timestamp`;
904
+ }
905
+ return `'${escaped}'`;
906
+ }
907
+ else if (value instanceof Date) {
908
+ // Date object
909
+ const year = value.getUTCFullYear();
910
+ const month = String(value.getUTCMonth() + 1).padStart(2, '0');
911
+ const day = String(value.getUTCDate()).padStart(2, '0');
912
+ const hours = String(value.getUTCHours()).padStart(2, '0');
913
+ const minutes = String(value.getUTCMinutes()).padStart(2, '0');
914
+ const seconds = String(value.getUTCSeconds()).padStart(2, '0');
915
+ const isoString = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
916
+ if (targetFormat === 'postgresql') {
917
+ return `'${isoString}'::timestamp`;
918
+ }
919
+ return `'${isoString}'`;
920
+ }
921
+ }
922
+ // Binary/Buffer handling - enhanced with proper encoding
923
+ const isBinaryColumn = colType.includes('blob') ||
924
+ colType.includes('bytea') ||
925
+ colType.includes('binary') ||
926
+ colType.includes('varbinary');
927
+ if (Buffer.isBuffer(value) || isBinaryColumn) {
928
+ const bufferValue = Buffer.isBuffer(value) ? value : Buffer.from(value);
929
+ const hexString = bufferValue.toString('hex');
930
+ if (targetFormat === 'postgresql') {
931
+ // PostgreSQL bytea hex format: '\x...'::bytea
932
+ return `'\\x${hexString}'::bytea`;
933
+ }
934
+ else if (targetFormat === 'mysql') {
935
+ // MySQL binary hex format: X'...' or 0x...
936
+ return `X'${hexString}'`;
937
+ }
938
+ // SQLite hex format
939
+ return `X'${hexString}'`;
940
+ }
941
+ // JSON handling - enhanced with proper type casting
942
+ const isJsonColumn = colType.includes('json') ||
943
+ colType === 'jsonb';
944
+ if (isJsonColumn) {
945
+ let jsonStr;
946
+ if (typeof value === 'string') {
947
+ // Already stringified by Knex - validate and escape
948
+ try {
949
+ JSON.parse(value); // Validate
950
+ jsonStr = value.replace(/'/g, "''");
951
+ }
952
+ catch {
953
+ // Invalid JSON string - treat as regular string
954
+ jsonStr = JSON.stringify(value).replace(/'/g, "''");
955
+ }
956
+ }
957
+ else if (typeof value === 'object') {
958
+ // Object that needs stringification
959
+ jsonStr = JSON.stringify(value).replace(/'/g, "''");
960
+ }
961
+ else {
962
+ // Primitive value - stringify
963
+ jsonStr = JSON.stringify(value).replace(/'/g, "''");
964
+ }
965
+ if (targetFormat === 'postgresql') {
966
+ // Use JSONB for better performance
967
+ return `'${jsonStr}'::jsonb`;
968
+ }
969
+ else if (targetFormat === 'mysql') {
970
+ // MySQL 5.7+ JSON type
971
+ return `'${jsonStr}'`;
972
+ }
973
+ // SQLite stores JSON as TEXT
974
+ return `'${jsonStr}'`;
975
+ }
976
+ // PostgreSQL Arrays - enhanced detection
977
+ const isArrayColumn = colType.includes('array') || colType.includes('[]');
978
+ if ((isArrayColumn || Array.isArray(value)) && targetFormat === 'postgresql') {
979
+ if (Array.isArray(value)) {
980
+ // Convert array elements recursively
981
+ const arrayStr = value
982
+ .map(v => {
983
+ if (v === null || v === undefined)
984
+ return 'NULL';
985
+ if (typeof v === 'string') {
986
+ const escaped = v.replace(/'/g, "''").replace(/\\/g, '\\\\');
987
+ return `'${escaped}'`;
988
+ }
989
+ if (typeof v === 'number')
990
+ return String(v);
991
+ if (typeof v === 'boolean')
992
+ return v ? 'TRUE' : 'FALSE';
993
+ // Objects - stringify
994
+ return `'${JSON.stringify(v).replace(/'/g, "''")}'`;
995
+ })
996
+ .join(',');
997
+ return `ARRAY[${arrayStr}]`;
998
+ }
999
+ else if (typeof value === 'string') {
1000
+ // Already formatted array string - pass through
1001
+ return value;
1002
+ }
1003
+ }
1004
+ // PostgreSQL Enum types
1005
+ const isEnumColumn = colType === 'enum' || colType.includes('user-defined');
1006
+ if (isEnumColumn && targetFormat === 'postgresql') {
1007
+ // Enum values must be quoted strings
1008
+ const escaped = String(value).replace(/'/g, "''");
1009
+ return `'${escaped}'`;
1010
+ }
1011
+ // Text columns with object values (fallback)
1012
+ if (colType === 'text' && typeof value === 'object' && !Buffer.isBuffer(value)) {
1013
+ const jsonStr = JSON.stringify(value).replace(/'/g, "''");
1014
+ return `'${jsonStr}'`;
1015
+ }
1016
+ // Numeric types - ensure no quotes
1017
+ const isNumericColumn = colType.includes('int') ||
1018
+ colType.includes('decimal') ||
1019
+ colType.includes('numeric') ||
1020
+ colType.includes('real') ||
1021
+ colType.includes('float') ||
1022
+ colType.includes('double');
1023
+ if (isNumericColumn && typeof value === 'number') {
1024
+ return String(value);
1025
+ }
1026
+ // Fallback to basic formatValue
1027
+ return formatValue(value, targetFormat);
1028
+ }
1029
+ /**
1030
+ * Extract column names from INSERT SQL statement
1031
+ * E.g., 'insert into "m_agents" ("id", "name", "is_reusable") values ...' => ["id", "name", "is_reusable"]
1032
+ */
1033
+ function extractColumnNamesFromInsertSql(sql) {
1034
+ // Match: insert into "table" ("col1", "col2", "col3") values
1035
+ // Or: insert into `table` (`col1`, `col2`) values
1036
+ // Or: INSERT INTO table (col1, col2) VALUES
1037
+ const match = sql.match(/insert\s+(?:ignore\s+)?into\s+[`"]?\w+[`"]?\s*\((.*?)\)\s*values/i);
1038
+ if (!match) {
1039
+ return [];
1040
+ }
1041
+ const columnsPart = match[1];
1042
+ // Split by comma and extract column names (removing quotes and whitespace)
1043
+ return columnsPart
1044
+ .split(',')
1045
+ .map(col => col.trim().replace(/[`"]/g, ''));
1046
+ }
1047
+ /**
1048
+ * Embed bindings from Knex parameterized query into plain SQL
1049
+ * with optional type-aware conversion for cross-database migrations
1050
+ */
1051
+ function embedBindings(sql, bindings, format, columnTypes, columnNames, columnInfo // NEW: columnInfo from knex(table).columnInfo()
1052
+ ) {
1053
+ // Detect source format (for now, assume SQLite as source)
1054
+ // TODO: Make this configurable or detect from the knex instance
1055
+ const sourceFormat = 'sqlite';
1056
+ if (format === 'postgresql') {
1057
+ // PostgreSQL: $1, $2, ... (replace in reverse order to avoid $10 matching $1)
1058
+ let result = sql;
1059
+ for (let i = bindings.length; i >= 1; i--) {
1060
+ const placeholder = `$${i}`;
1061
+ // For multi-row inserts, column names repeat: $1-$5 map to cols 0-4, $6-$10 map to cols 0-4, etc.
1062
+ const columnIndex = columnNames && columnNames.length > 0 ? (i - 1) % columnNames.length : i - 1;
1063
+ const columnName = columnNames?.[columnIndex] || '';
1064
+ // Use new convertValueWithType with columnInfo if available
1065
+ const value = columnInfo
1066
+ ? convertValueWithType(bindings[i - 1], columnName, columnInfo, sourceFormat, format)
1067
+ : formatValue(bindings[i - 1], format);
1068
+ result = result.replace(new RegExp(`\\${placeholder}\\b`, 'g'), value);
1069
+ }
1070
+ return result;
1071
+ }
1072
+ else {
1073
+ // MySQL/SQLite: ? placeholders
1074
+ let result = sql;
1075
+ let bindingIndex = 0;
1076
+ result = result.replace(/\?/g, () => {
1077
+ if (bindingIndex >= bindings.length) {
1078
+ throw new Error(`Not enough bindings: ${bindings.length} provided, more needed`);
1079
+ }
1080
+ // For multi-row inserts, column names repeat
1081
+ const columnIndex = columnNames && columnNames.length > 0 ? bindingIndex % columnNames.length : bindingIndex;
1082
+ const columnName = columnNames?.[columnIndex] || '';
1083
+ // Use new convertValueWithType with columnInfo if available
1084
+ const value = columnInfo
1085
+ ? convertValueWithType(bindings[bindingIndex], columnName, columnInfo, sourceFormat, format)
1086
+ : formatValue(bindings[bindingIndex], format);
1087
+ bindingIndex++;
1088
+ return value;
1089
+ });
1090
+ return result;
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Create a throwaway Knex instance for SQL generation
1095
+ */
1096
+ function createKnexForFormat(format) {
1097
+ const client = format === 'mysql' ? 'mysql2' : format === 'postgresql' ? 'pg' : 'better-sqlite3';
1098
+ return knex({
1099
+ client,
1100
+ connection: client === 'better-sqlite3' ? { filename: ':memory:' } : {},
1101
+ useNullAsDefault: client === 'better-sqlite3',
1102
+ });
1103
+ }
1104
+ /**
1105
+ * Generate a bulk INSERT statement for a table with conflict resolution
1106
+ *
1107
+ * REFACTORED: Uses Knex query builder instead of manual string construction
1108
+ */
1109
+ export function generateBulkInsert(table, rows, format, options = {}) {
1110
+ if (rows.length === 0) {
1111
+ return [];
1112
+ }
1113
+ const { chunkSize = 100, conflictMode = 'error', primaryKeys = [], columnTypes, columnInfo, } = options;
1114
+ const statements = [];
1115
+ const targetKnex = createKnexForFormat(format);
1116
+ try {
1117
+ // Split into chunks to avoid too-large statements
1118
+ for (let i = 0; i < rows.length; i += chunkSize) {
1119
+ const chunk = rows.slice(i, i + chunkSize);
1120
+ // Use Knex to generate parameterized INSERT
1121
+ let builder = targetKnex(table).insert(chunk);
1122
+ // Handle conflict modes with Knex-specific methods
1123
+ if (conflictMode === 'ignore') {
1124
+ if (format === 'mysql') {
1125
+ // MySQL: INSERT IGNORE is handled via raw SQL modification
1126
+ const { sql, bindings } = builder.toSQL().toNative();
1127
+ const ignoreSql = sql.replace(/^insert into/i, 'INSERT IGNORE INTO');
1128
+ const columnNames = extractColumnNamesFromInsertSql(ignoreSql);
1129
+ const embedded = embedBindings(ignoreSql, bindings, format, columnTypes, columnNames, columnInfo);
1130
+ statements.push(embedded + ';');
1131
+ continue;
1132
+ }
1133
+ else if (format === 'postgresql') {
1134
+ // PostgreSQL: ON CONFLICT DO NOTHING
1135
+ // Note: Knex's onConflict() requires specifying columns, so we use raw SQL
1136
+ const { sql, bindings } = builder.toSQL().toNative();
1137
+ const conflictSql = sql + ' ON CONFLICT DO NOTHING';
1138
+ const columnNames = extractColumnNamesFromInsertSql(conflictSql);
1139
+ const embedded = embedBindings(conflictSql, bindings, format, columnTypes, columnNames, columnInfo);
1140
+ statements.push(embedded + ';');
1141
+ continue;
1142
+ }
1143
+ else {
1144
+ // SQLite: INSERT OR IGNORE
1145
+ const { sql, bindings } = builder.toSQL().toNative();
1146
+ const ignoreSql = sql.replace(/^insert into/i, 'INSERT OR IGNORE INTO');
1147
+ const columnNames = extractColumnNamesFromInsertSql(ignoreSql);
1148
+ const embedded = embedBindings(ignoreSql, bindings, format, columnTypes, columnNames, columnInfo);
1149
+ statements.push(embedded + ';');
1150
+ continue;
1151
+ }
1152
+ }
1153
+ else if (conflictMode === 'replace') {
1154
+ // REPLACE mode requires primary keys
1155
+ if (primaryKeys.length === 0) {
1156
+ throw new Error(`Cannot use 'replace' mode for table ${table}: no primary key found`);
1157
+ }
1158
+ const { sql, bindings } = builder.toSQL().toNative();
1159
+ const columns = Object.keys(chunk[0]);
1160
+ const nonPkColumns = columns.filter(col => !primaryKeys.includes(col));
1161
+ const columnNames = extractColumnNamesFromInsertSql(sql);
1162
+ if (format === 'mysql') {
1163
+ // MySQL: ON DUPLICATE KEY UPDATE
1164
+ const updateClauses = nonPkColumns.map(col => `${quoteIdentifier(col, format)} = VALUES(${quoteIdentifier(col, format)})`);
1165
+ const finalSql = `${sql}\nON DUPLICATE KEY UPDATE\n ${updateClauses.join(',\n ')}`;
1166
+ const embedded = embedBindings(finalSql, bindings, format, columnTypes, columnNames, columnInfo);
1167
+ statements.push(embedded + ';');
1168
+ }
1169
+ else if (format === 'postgresql') {
1170
+ // PostgreSQL: ON CONFLICT DO UPDATE
1171
+ const quotedPks = primaryKeys.map(pk => quoteIdentifier(pk, format));
1172
+ const updateClauses = nonPkColumns.map(col => `${quoteIdentifier(col, format)} = EXCLUDED.${quoteIdentifier(col, format)}`);
1173
+ const finalSql = `${sql}\nON CONFLICT (${quotedPks.join(', ')}) DO UPDATE SET\n ${updateClauses.join(',\n ')}`;
1174
+ const embedded = embedBindings(finalSql, bindings, format, columnTypes, columnNames, columnInfo);
1175
+ statements.push(embedded + ';');
1176
+ }
1177
+ else {
1178
+ // SQLite: ON CONFLICT DO UPDATE
1179
+ const quotedPks = primaryKeys.map(pk => quoteIdentifier(pk, format));
1180
+ const updateClauses = nonPkColumns.map(col => `${quoteIdentifier(col, format)} = excluded.${quoteIdentifier(col, format)}`);
1181
+ const finalSql = `${sql}\nON CONFLICT (${quotedPks.join(', ')}) DO UPDATE SET\n ${updateClauses.join(',\n ')}`;
1182
+ const embedded = embedBindings(finalSql, bindings, format, columnTypes, columnNames, columnInfo);
1183
+ statements.push(embedded + ';');
1184
+ }
1185
+ }
1186
+ else {
1187
+ // ERROR mode: Standard INSERT
1188
+ const { sql, bindings } = builder.toSQL().toNative();
1189
+ const columnNames = extractColumnNamesFromInsertSql(sql);
1190
+ const embedded = embedBindings(sql, bindings, format, columnTypes, columnNames, columnInfo);
1191
+ statements.push(embedded + ';');
1192
+ }
1193
+ }
1194
+ return statements;
1195
+ }
1196
+ finally {
1197
+ // Clean up Knex instance
1198
+ targetKnex.destroy();
1199
+ }
1200
+ }
1201
+ /**
1202
+ * Generate header comments for SQL dump
1203
+ */
1204
+ export function generateHeader(format) {
1205
+ const timestamp = new Date().toISOString();
1206
+ return `-- SQL Dump generated by sqlew
1207
+ -- Date: ${timestamp}
1208
+ -- Target: ${format.toUpperCase()}
1209
+ --
1210
+ -- This dump is wrapped in a transaction.
1211
+ -- On error, all changes will be rolled back automatically.
1212
+ --
1213
+ -- Usage (empty database):
1214
+ -- ${format === 'mysql' ? 'mysql mydb < dump.sql' : format === 'postgresql' ? 'psql -d mydb -f dump.sql' : 'sqlite3 mydb.db < dump.sql'}
1215
+
1216
+ `;
1217
+ }
1218
+ /**
1219
+ * Generate foreign key disable/enable statements
1220
+ */
1221
+ export function generateForeignKeyControls(format, enable) {
1222
+ if (format === 'mysql') {
1223
+ return enable
1224
+ ? 'SET FOREIGN_KEY_CHECKS=1;'
1225
+ : 'SET FOREIGN_KEY_CHECKS=0;';
1226
+ }
1227
+ else if (format === 'postgresql') {
1228
+ return enable
1229
+ ? 'SET session_replication_role = DEFAULT;'
1230
+ : 'SET session_replication_role = replica;';
1231
+ }
1232
+ else {
1233
+ // SQLite
1234
+ return enable
1235
+ ? 'PRAGMA foreign_keys = ON;'
1236
+ : 'PRAGMA foreign_keys = OFF;';
1237
+ }
1238
+ }
1239
+ /**
1240
+ * Generate transaction control statements
1241
+ */
1242
+ export function generateTransactionControl(format, isStart) {
1243
+ if (isStart) {
1244
+ return format === 'mysql' ? 'START TRANSACTION;'
1245
+ : format === 'postgresql' ? 'BEGIN;'
1246
+ : 'BEGIN TRANSACTION;';
1247
+ }
1248
+ else {
1249
+ return 'COMMIT;';
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Generate sequence reset statements for PostgreSQL
1254
+ */
1255
+ export async function generateSequenceResets(knex, tables) {
1256
+ const statements = [];
1257
+ for (const table of tables) {
1258
+ try {
1259
+ // Check if table has an id column with a sequence
1260
+ const result = await knex.raw(`
1261
+ SELECT column_name, column_default
1262
+ FROM information_schema.columns
1263
+ WHERE table_name = ?
1264
+ AND column_default LIKE 'nextval%'
1265
+ `, [table]);
1266
+ if (result.rows.length > 0) {
1267
+ const columnName = result.rows[0].column_name;
1268
+ const sequenceName = `${table}_${columnName}_seq`;
1269
+ statements.push(`SELECT setval('${sequenceName}', COALESCE((SELECT MAX(${columnName}) FROM "${table}"), 1), true);`);
1270
+ }
1271
+ }
1272
+ catch (err) {
1273
+ // Ignore errors for tables without sequences
1274
+ }
1275
+ }
1276
+ return statements;
1277
+ }
1278
+ /**
1279
+ * Get foreign key dependencies for tables
1280
+ * Returns a map of table -> array of tables it depends on
1281
+ *
1282
+ * @internal Exported for testing purposes
1283
+ */
1284
+ export async function getTableDependencies(knex, tables) {
1285
+ const dependencies = new Map();
1286
+ const client = knex.client.config.client;
1287
+ for (const table of tables) {
1288
+ dependencies.set(table, []);
1289
+ }
1290
+ for (const table of tables) {
1291
+ try {
1292
+ if (client === 'better-sqlite3' || client === 'sqlite3') {
1293
+ // SQLite: Use PRAGMA foreign_key_list() for reliable FK detection
1294
+ // This catches both inline REFERENCES and explicit FOREIGN KEY syntax
1295
+ const result = await knex.raw(`PRAGMA foreign_key_list(${table})`);
1296
+ const fkList = Array.isArray(result) ? result : [];
1297
+ for (const fk of fkList) {
1298
+ const referencedTable = fk.table;
1299
+ if (tables.includes(referencedTable) && referencedTable !== table) {
1300
+ dependencies.get(table).push(referencedTable);
1301
+ }
1302
+ }
1303
+ }
1304
+ else if (client === 'mysql' || client === 'mysql2') {
1305
+ // MySQL: Use information_schema
1306
+ const result = await knex.raw(`
1307
+ SELECT REFERENCED_TABLE_NAME
1308
+ FROM information_schema.KEY_COLUMN_USAGE
1309
+ WHERE TABLE_SCHEMA = DATABASE()
1310
+ AND TABLE_NAME = ?
1311
+ AND REFERENCED_TABLE_NAME IS NOT NULL
1312
+ `, [table]);
1313
+ for (const row of result[0]) {
1314
+ const referencedTable = row.REFERENCED_TABLE_NAME;
1315
+ if (tables.includes(referencedTable) && referencedTable !== table) {
1316
+ dependencies.get(table).push(referencedTable);
1317
+ }
1318
+ }
1319
+ }
1320
+ else if (client === 'pg') {
1321
+ // PostgreSQL: Use information_schema
1322
+ const result = await knex.raw(`
1323
+ SELECT DISTINCT ccu.table_name AS referenced_table
1324
+ FROM information_schema.table_constraints AS tc
1325
+ JOIN information_schema.key_column_usage AS kcu
1326
+ ON tc.constraint_name = kcu.constraint_name
1327
+ AND tc.table_schema = kcu.table_schema
1328
+ JOIN information_schema.constraint_column_usage AS ccu
1329
+ ON ccu.constraint_name = tc.constraint_name
1330
+ AND ccu.table_schema = tc.table_schema
1331
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1332
+ AND tc.table_name = ?
1333
+ `, [table]);
1334
+ for (const row of result.rows) {
1335
+ const referencedTable = row.referenced_table;
1336
+ if (tables.includes(referencedTable) && referencedTable !== table) {
1337
+ dependencies.get(table).push(referencedTable);
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+ catch (err) {
1343
+ // Ignore errors - table might not have foreign keys
1344
+ }
1345
+ }
1346
+ return dependencies;
1347
+ }
1348
+ /**
1349
+ * Topologically sort tables by foreign key dependencies
1350
+ * Returns tables in order where parent tables come before child tables
1351
+ *
1352
+ * @internal Exported for testing purposes
1353
+ */
1354
+ export function topologicalSort(tables, dependencies) {
1355
+ const sorted = [];
1356
+ const visited = new Set();
1357
+ const visiting = new Set();
1358
+ function visit(table) {
1359
+ if (visited.has(table))
1360
+ return;
1361
+ if (visiting.has(table)) {
1362
+ // Circular dependency detected - log warning and continue
1363
+ debugLog('WARN', `Circular foreign key dependency detected involving table: ${table}`);
1364
+ return;
1365
+ }
1366
+ visiting.add(table);
1367
+ const deps = dependencies.get(table) || [];
1368
+ for (const dep of deps) {
1369
+ visit(dep);
1370
+ }
1371
+ visiting.delete(table);
1372
+ visited.add(table);
1373
+ sorted.push(table);
1374
+ }
1375
+ for (const table of tables) {
1376
+ visit(table);
1377
+ }
1378
+ return sorted;
1379
+ }
1380
+ /**
1381
+ * Main function to generate complete SQL dump
1382
+ */
1383
+ export async function generateSqlDump(knex, format, options = {}) {
1384
+ const { tables: requestedTables, includeHeader = true, includeSchema = true, // Default to TRUE - include schema
1385
+ chunkSize = 100, conflictMode = 'error' } = options;
1386
+ const statements = [];
1387
+ // Add header
1388
+ if (includeHeader) {
1389
+ statements.push(generateHeader(format));
1390
+ }
1391
+ // Disable foreign key checks
1392
+ statements.push(generateForeignKeyControls(format, false));
1393
+ statements.push('');
1394
+ // Start transaction
1395
+ statements.push(generateTransactionControl(format, true));
1396
+ statements.push('');
1397
+ // Get tables to dump
1398
+ // Include knex_migrations table when schema is included for complete migration state
1399
+ const allTables = await getAllTables(knex, includeSchema);
1400
+ const tablesToDump = requestedTables
1401
+ ? allTables.filter(t => requestedTables.includes(t))
1402
+ : allTables;
1403
+ // Sort tables by foreign key dependencies for PostgreSQL compatibility
1404
+ // Parent tables must be created before child tables
1405
+ const dependencies = await getTableDependencies(knex, tablesToDump);
1406
+ const sortedTables = topologicalSort(tablesToDump, dependencies);
1407
+ // Generate CREATE TABLE statements if schema is included
1408
+ if (includeSchema) {
1409
+ statements.push('-- ============================================');
1410
+ statements.push('-- Schema (CREATE TABLE statements)');
1411
+ statements.push('-- ============================================');
1412
+ statements.push('');
1413
+ for (const table of sortedTables) {
1414
+ try {
1415
+ const createSql = await getCreateTableStatement(knex, table, format);
1416
+ statements.push(`-- Table: ${table}`);
1417
+ statements.push(createSql);
1418
+ statements.push('');
1419
+ }
1420
+ catch (err) {
1421
+ console.warn(`Warning: Could not get CREATE TABLE for ${table}:`, err);
1422
+ statements.push(`-- Warning: Could not create table ${table}`);
1423
+ statements.push('');
1424
+ }
1425
+ }
1426
+ // Generate CREATE VIEW statements
1427
+ try {
1428
+ const views = await getAllViews(knex);
1429
+ if (views.length > 0) {
1430
+ statements.push('-- ============================================');
1431
+ statements.push('-- Views');
1432
+ statements.push('-- ============================================');
1433
+ statements.push('');
1434
+ for (const view of views) {
1435
+ try {
1436
+ const createViewSql = await getCreateViewStatement(knex, view, format);
1437
+ statements.push(`-- View: ${view}`);
1438
+ statements.push(createViewSql);
1439
+ statements.push('');
1440
+ }
1441
+ catch (err) {
1442
+ console.warn(`Warning: Could not get CREATE VIEW for ${view}:`, err);
1443
+ statements.push(`-- Warning: Could not create view ${view}`);
1444
+ statements.push('');
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ catch (err) {
1450
+ console.warn('Warning: Could not retrieve views:', err);
1451
+ }
1452
+ // Generate CREATE INDEX statements
1453
+ try {
1454
+ const indexStatements = [];
1455
+ for (const table of sortedTables) {
1456
+ const indexes = await getAllIndexes(knex, table);
1457
+ if (indexes.length > 0) {
1458
+ for (const indexName of indexes) {
1459
+ try {
1460
+ const createIndexSql = await getCreateIndexStatement(knex, indexName, format);
1461
+ indexStatements.push(`-- Index: ${indexName} on ${table}`);
1462
+ indexStatements.push(createIndexSql);
1463
+ indexStatements.push('');
1464
+ }
1465
+ catch (err) {
1466
+ console.warn(`Warning: Could not get CREATE INDEX for ${indexName}:`, err);
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ if (indexStatements.length > 0) {
1472
+ statements.push('-- ============================================');
1473
+ statements.push('-- Indexes');
1474
+ statements.push('-- ============================================');
1475
+ statements.push('');
1476
+ statements.push(...indexStatements);
1477
+ }
1478
+ }
1479
+ catch (err) {
1480
+ console.warn('Warning: Could not retrieve indexes:', err);
1481
+ }
1482
+ statements.push('-- ============================================');
1483
+ statements.push('-- Data (INSERT statements)');
1484
+ statements.push('-- ============================================');
1485
+ statements.push('');
1486
+ }
1487
+ // Generate INSERT statements for each table (in dependency order)
1488
+ // Skip data insertion if chunkSize is 0 (schema-only mode)
1489
+ if (chunkSize > 0) {
1490
+ for (const table of sortedTables) {
1491
+ statements.push(`-- Data for table: ${table}`);
1492
+ const rows = await knex(table).select('*');
1493
+ if (rows.length === 0) {
1494
+ statements.push(`-- No data in table ${table}`);
1495
+ statements.push('');
1496
+ continue;
1497
+ }
1498
+ // Get column types for cross-database type conversion
1499
+ // Query from source database to understand original types (e.g., INTEGER for booleans in SQLite)
1500
+ let columnTypes = new Map();
1501
+ let columnInfoMap = new Map();
1502
+ try {
1503
+ const columnInfo = await knex(table).columnInfo();
1504
+ for (const [col, info] of Object.entries(columnInfo)) {
1505
+ columnTypes.set(col, info.type);
1506
+ columnInfoMap.set(col, info); // Store full column metadata
1507
+ }
1508
+ }
1509
+ catch (err) {
1510
+ console.warn(`Warning: Could not get column types for table ${table}:`, err);
1511
+ }
1512
+ // Get primary keys if using replace mode
1513
+ let primaryKeys = [];
1514
+ if (conflictMode === 'replace') {
1515
+ try {
1516
+ primaryKeys = await getPrimaryKeyColumns(knex, table);
1517
+ if (primaryKeys.length > 0) {
1518
+ statements.push(`-- Primary key(s): ${primaryKeys.join(', ')}`);
1519
+ }
1520
+ }
1521
+ catch (err) {
1522
+ console.warn(`Warning: Could not detect primary key for table ${table}:`, err);
1523
+ }
1524
+ }
1525
+ const inserts = generateBulkInsert(table, rows, format, {
1526
+ chunkSize,
1527
+ conflictMode,
1528
+ primaryKeys,
1529
+ columnTypes,
1530
+ columnInfo: columnInfoMap // Pass full columnInfo for type-aware conversion
1531
+ });
1532
+ statements.push(...inserts);
1533
+ statements.push('');
1534
+ }
1535
+ } // End of if (chunkSize > 0)
1536
+ // Reset sequences for PostgreSQL
1537
+ if (format === 'postgresql') {
1538
+ statements.push('-- Reset sequences');
1539
+ const sequenceResets = await generateSequenceResets(knex, tablesToDump);
1540
+ statements.push(...sequenceResets);
1541
+ statements.push('');
1542
+ }
1543
+ // Commit transaction
1544
+ statements.push(generateTransactionControl(format, false));
1545
+ statements.push('');
1546
+ // Re-enable foreign key checks
1547
+ statements.push(generateForeignKeyControls(format, true));
1548
+ return statements.join('\n');
1549
+ }
1550
+ //# sourceMappingURL=sql-dump.js.map