@warlock.js/core 5.1.0 → 5.2.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 (356) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/esm/application/application.d.mts.map +1 -1
  3. package/esm/application/application.mjs.map +1 -1
  4. package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
  5. package/esm/benchmark/benchmark.d.mts.map +1 -1
  6. package/esm/benchmark/channels/console.channel.mjs.map +1 -1
  7. package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
  8. package/esm/benchmark/profiler.d.mts.map +1 -1
  9. package/esm/benchmark/profiler.mjs.map +1 -1
  10. package/esm/bootstrap/setup.d.mts.map +1 -1
  11. package/esm/bootstrap/setup.mjs.map +1 -1
  12. package/esm/bootstrap.d.mts.map +1 -1
  13. package/esm/cache/database-cache-driver.d.mts.map +1 -1
  14. package/esm/cache/database-cache-driver.mjs.map +1 -1
  15. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  16. package/esm/cli/cli-commands.utils.mjs +16 -10
  17. package/esm/cli/cli-commands.utils.mjs.map +1 -1
  18. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  19. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
  20. package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
  21. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  22. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  23. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
  24. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  25. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
  26. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  27. package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
  28. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  29. package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
  30. package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
  31. package/esm/cli/commands/routes/diff-page-routes.mjs +84 -0
  32. package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -0
  33. package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
  34. package/esm/cli/commands/routes/route-row.mjs.map +1 -1
  35. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
  36. package/esm/cli/commands/routes/routes-diff.action.mjs +84 -0
  37. package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -0
  38. package/esm/cli/commands/routes.command.mjs +15 -1
  39. package/esm/cli/commands/routes.command.mjs.map +1 -1
  40. package/esm/cli/commands/start-production.command.mjs +8 -1
  41. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  42. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  43. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  44. package/esm/cli/commands-loader.mjs.map +1 -1
  45. package/esm/cli/framework-cli-commands.mjs +2 -1
  46. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  47. package/esm/cli/start.mjs.map +1 -1
  48. package/esm/cli/string-similarity.mjs.map +1 -1
  49. package/esm/commands/cli-command.mjs.map +1 -1
  50. package/esm/config/config-loader.d.mts +1 -1
  51. package/esm/config/config-loader.d.mts.map +1 -1
  52. package/esm/config/config-loader.mjs.map +1 -1
  53. package/esm/config/config-setter.d.mts.map +1 -1
  54. package/esm/config/config-special-handlers.d.mts +1 -1
  55. package/esm/config/config-special-handlers.mjs.map +1 -1
  56. package/esm/config/load-config-files.mjs.map +1 -1
  57. package/esm/connectors/access-connector.d.mts.map +1 -1
  58. package/esm/connectors/ai-connector.d.mts.map +1 -1
  59. package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
  60. package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
  61. package/esm/connectors/base-connector.d.mts.map +1 -1
  62. package/esm/connectors/base-connector.mjs.map +1 -1
  63. package/esm/connectors/cache-connector.d.mts.map +1 -1
  64. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  65. package/esm/connectors/connectors-manager.mjs.map +1 -1
  66. package/esm/connectors/database-connector.d.mts.map +1 -1
  67. package/esm/connectors/describe-server-address.mjs +20 -3
  68. package/esm/connectors/describe-server-address.mjs.map +1 -1
  69. package/esm/connectors/herald-connector.d.mts.map +1 -1
  70. package/esm/connectors/http-connector.d.mts.map +1 -1
  71. package/esm/connectors/http-connector.mjs +5 -1
  72. package/esm/connectors/http-connector.mjs.map +1 -1
  73. package/esm/connectors/logger-connector.d.mts.map +1 -1
  74. package/esm/connectors/mail-connector.d.mts.map +1 -1
  75. package/esm/connectors/notifications-connector.d.mts.map +1 -1
  76. package/esm/connectors/register-configured-connectors.mjs.map +1 -1
  77. package/esm/connectors/socket-connector.d.mts.map +1 -1
  78. package/esm/connectors/socket-connector.mjs.map +1 -1
  79. package/esm/connectors/storage.connector.d.mts.map +1 -1
  80. package/esm/container/index.d.mts.map +1 -1
  81. package/esm/database/create-database-action.mjs.map +1 -1
  82. package/esm/database/drop-tables-action.mjs.map +1 -1
  83. package/esm/database/migrate-action.mjs.map +1 -1
  84. package/esm/database/models/database-log/database-log.mjs.map +1 -1
  85. package/esm/database/seed-command-action.mjs.map +1 -1
  86. package/esm/database/seeds/seed-records-table-migration.mjs +1 -1
  87. package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
  88. package/esm/database/seeds/seeder.mjs.map +1 -1
  89. package/esm/database/seeds/seeders.manager.mjs.map +1 -1
  90. package/esm/database/seeds/seeds-table-migration.mjs +1 -1
  91. package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
  92. package/esm/database/utils.mjs.map +1 -1
  93. package/esm/dev-server/check-for-updates.mjs.map +1 -1
  94. package/esm/dev-server/dependency-graph.d.mts.map +1 -1
  95. package/esm/dev-server/dependency-graph.mjs.map +1 -1
  96. package/esm/dev-server/dev-logger.mjs.map +1 -1
  97. package/esm/dev-server/development-server.mjs.map +1 -1
  98. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  99. package/esm/dev-server/file-manager.d.mts.map +1 -1
  100. package/esm/dev-server/file-manager.mjs.map +1 -1
  101. package/esm/dev-server/file-operations.d.mts.map +1 -1
  102. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  103. package/esm/dev-server/files-watcher.mjs.map +1 -1
  104. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
  105. package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
  106. package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
  107. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  108. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  109. package/esm/dev-server/layer-executor.mjs +10 -3
  110. package/esm/dev-server/layer-executor.mjs.map +1 -1
  111. package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
  112. package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
  113. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  114. package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
  115. package/esm/dev-server/loader/source-slug.mjs.map +1 -1
  116. package/esm/dev-server/loader/transpile-cache.mjs +12 -3
  117. package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
  118. package/esm/dev-server/loader/version-registry.mjs.map +1 -1
  119. package/esm/dev-server/manifest-manager.d.mts.map +1 -1
  120. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  121. package/esm/dev-server/module-loader.mjs.map +1 -1
  122. package/esm/dev-server/package-json-manager.mjs.map +1 -1
  123. package/esm/dev-server/parse-imports.mjs.map +1 -1
  124. package/esm/dev-server/ready-block.mjs.map +1 -1
  125. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  126. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  127. package/esm/dev-server/supervisor.mjs +9 -6
  128. package/esm/dev-server/supervisor.mjs.map +1 -1
  129. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  130. package/esm/dev-server/type-generator.mjs.map +1 -1
  131. package/esm/dev-server/utils.mjs.map +1 -1
  132. package/esm/encryption/encrypt.mjs.map +1 -1
  133. package/esm/encryption/hash.mjs.map +1 -1
  134. package/esm/encryption/password.mjs.map +1 -1
  135. package/esm/generations/add-command.action.mjs +11 -4
  136. package/esm/generations/add-command.action.mjs.map +1 -1
  137. package/esm/generations/features/access.feature.mjs +2 -1
  138. package/esm/generations/features/access.feature.mjs.map +1 -1
  139. package/esm/generations/features/ai-anthropic.feature.mjs +3 -1
  140. package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
  141. package/esm/generations/features/ai-bedrock.feature.mjs +3 -1
  142. package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
  143. package/esm/generations/features/ai-google.feature.mjs +3 -1
  144. package/esm/generations/features/ai-google.feature.mjs.map +1 -1
  145. package/esm/generations/features/ai-ollama.feature.mjs +3 -1
  146. package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
  147. package/esm/generations/features/ai-openai.feature.mjs +3 -1
  148. package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
  149. package/esm/generations/features/ai-panoptic.feature.mjs +2 -1
  150. package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
  151. package/esm/generations/features/ai-tools.feature.mjs +2 -1
  152. package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
  153. package/esm/generations/features/ai-workspace.feature.mjs +2 -1
  154. package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
  155. package/esm/generations/features/ai.feature.mjs +2 -1
  156. package/esm/generations/features/ai.feature.mjs.map +1 -1
  157. package/esm/generations/features/herald.feature.mjs +2 -1
  158. package/esm/generations/features/herald.feature.mjs.map +1 -1
  159. package/esm/generations/features/notifications.feature.mjs +3 -2
  160. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  161. package/esm/generations/features/scheduler.feature.mjs +3 -1
  162. package/esm/generations/features/scheduler.feature.mjs.map +1 -1
  163. package/esm/generations/features/shadcn.feature.mjs.map +1 -1
  164. package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
  165. package/esm/generations/features/tailwind.feature.mjs.map +1 -1
  166. package/esm/generations/features/types.mjs +14 -0
  167. package/esm/generations/features/types.mjs.map +1 -0
  168. package/esm/generations/features/web.feature.mjs +10 -2
  169. package/esm/generations/features/web.feature.mjs.map +1 -1
  170. package/esm/generations/stubs.mjs +169 -28
  171. package/esm/generations/stubs.mjs.map +1 -1
  172. package/esm/http/boot-port-preflight.d.mts +46 -0
  173. package/esm/http/boot-port-preflight.d.mts.map +1 -0
  174. package/esm/http/boot-port-preflight.mjs +87 -0
  175. package/esm/http/boot-port-preflight.mjs.map +1 -0
  176. package/esm/http/context/request-context.d.mts.map +1 -1
  177. package/esm/http/context/request-context.mjs.map +1 -1
  178. package/esm/http/context/request-memo.d.mts.map +1 -1
  179. package/esm/http/context/request-memo.mjs.map +1 -1
  180. package/esm/http/createHttpApplication.d.mts.map +1 -1
  181. package/esm/http/createHttpApplication.mjs.map +1 -1
  182. package/esm/http/health.d.mts.map +1 -1
  183. package/esm/http/index.d.mts +1 -0
  184. package/esm/http/index.mjs +1 -0
  185. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  186. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  187. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  188. package/esm/http/middleware/ip-filter.middleware.d.mts +1 -1
  189. package/esm/http/middleware/ip-filter.middleware.mjs +1 -1
  190. package/esm/http/middleware/ip-filter.middleware.mjs.map +1 -1
  191. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  192. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  193. package/esm/http/port-preflight.d.mts +8 -0
  194. package/esm/http/port-preflight.d.mts.map +1 -1
  195. package/esm/http/port-preflight.mjs +8 -1
  196. package/esm/http/port-preflight.mjs.map +1 -1
  197. package/esm/http/ready-report.d.mts.map +1 -1
  198. package/esm/http/request-controller.d.mts.map +1 -1
  199. package/esm/http/request.d.mts +14 -2
  200. package/esm/http/request.d.mts.map +1 -1
  201. package/esm/http/request.mjs +16 -2
  202. package/esm/http/request.mjs.map +1 -1
  203. package/esm/http/response.d.mts +3 -3
  204. package/esm/http/response.d.mts.map +1 -1
  205. package/esm/http/response.mjs.map +1 -1
  206. package/esm/http/server.d.mts.map +1 -1
  207. package/esm/http/server.mjs +26 -2
  208. package/esm/http/server.mjs.map +1 -1
  209. package/esm/http/types.d.mts +1 -3
  210. package/esm/http/types.d.mts.map +1 -1
  211. package/esm/http/uploaded-file.d.mts.map +1 -1
  212. package/esm/http/uploaded-file.mjs.map +1 -1
  213. package/esm/http/uploads-config.d.mts.map +1 -1
  214. package/esm/image/image.d.mts.map +1 -1
  215. package/esm/image/image.mjs.map +1 -1
  216. package/esm/index.d.mts +6 -1
  217. package/esm/index.mjs +6 -1
  218. package/esm/logger/fatal-visibility.d.mts +25 -0
  219. package/esm/logger/fatal-visibility.d.mts.map +1 -0
  220. package/esm/logger/fatal-visibility.mjs +31 -0
  221. package/esm/logger/fatal-visibility.mjs.map +1 -0
  222. package/esm/logger/index.d.mts +1 -0
  223. package/esm/logger/index.mjs +1 -0
  224. package/esm/logger/logger.mjs.map +1 -1
  225. package/esm/mail/config.d.mts.map +1 -1
  226. package/esm/mail/events.d.mts.map +1 -1
  227. package/esm/mail/mail.d.mts.map +1 -1
  228. package/esm/mail/mail.mjs.map +1 -1
  229. package/esm/mail/mailer-pool.d.mts.map +1 -1
  230. package/esm/mail/mailer-pool.mjs +2 -0
  231. package/esm/mail/mailer-pool.mjs.map +1 -1
  232. package/esm/mail/send-mail.mjs.map +1 -1
  233. package/esm/mail/test-mailbox.d.mts.map +1 -1
  234. package/esm/mail/test-mailbox.mjs.map +1 -1
  235. package/esm/production/assert-dist-ready-to-start.mjs +24 -0
  236. package/esm/production/assert-dist-ready-to-start.mjs.map +1 -0
  237. package/esm/production/assert-generated-imports.mjs.map +1 -1
  238. package/esm/production/build-app-production.mjs.map +1 -1
  239. package/esm/production/dist-build-manifest.mjs +47 -0
  240. package/esm/production/dist-build-manifest.mjs.map +1 -0
  241. package/esm/production/production-builder.mjs +68 -13
  242. package/esm/production/production-builder.mjs.map +1 -1
  243. package/esm/production/production-supervisor.mjs +21 -9
  244. package/esm/production/production-supervisor.mjs.map +1 -1
  245. package/esm/production/promote-dist.mjs +115 -0
  246. package/esm/production/promote-dist.mjs.map +1 -0
  247. package/esm/production/resolve-build-config.mjs.map +1 -1
  248. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
  249. package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
  250. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
  251. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  252. package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
  253. package/esm/repositories/repository.manager.d.mts.map +1 -1
  254. package/esm/repositories/repository.manager.mjs.map +1 -1
  255. package/esm/resource/register-resource.d.mts.map +1 -1
  256. package/esm/resource/resource-field-builder.d.mts.map +1 -1
  257. package/esm/resource/resource-field-builder.mjs.map +1 -1
  258. package/esm/resource/resource.d.mts.map +1 -1
  259. package/esm/resource/resource.mjs.map +1 -1
  260. package/esm/restful/restful.d.mts.map +1 -1
  261. package/esm/restful/restful.mjs.map +1 -1
  262. package/esm/router/index.d.mts +3 -0
  263. package/esm/router/index.mjs +3 -0
  264. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  265. package/esm/router/normalize-request-path.d.mts +14 -0
  266. package/esm/router/normalize-request-path.d.mts.map +1 -0
  267. package/esm/router/normalize-request-path.mjs +18 -0
  268. package/esm/router/normalize-request-path.mjs.map +1 -0
  269. package/esm/router/normalize-route-path.d.mts +28 -0
  270. package/esm/router/normalize-route-path.d.mts.map +1 -0
  271. package/esm/router/normalize-route-path.mjs +33 -0
  272. package/esm/router/normalize-route-path.mjs.map +1 -0
  273. package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
  274. package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
  275. package/esm/router/route-name-method-suffix.d.mts +33 -0
  276. package/esm/router/route-name-method-suffix.d.mts.map +1 -0
  277. package/esm/router/route-name-method-suffix.mjs +39 -0
  278. package/esm/router/route-name-method-suffix.mjs.map +1 -0
  279. package/esm/router/route-registry.d.mts.map +1 -1
  280. package/esm/router/route-registry.mjs +3 -2
  281. package/esm/router/route-registry.mjs.map +1 -1
  282. package/esm/router/router.d.mts +14 -0
  283. package/esm/router/router.d.mts.map +1 -1
  284. package/esm/router/router.mjs +50 -11
  285. package/esm/router/router.mjs.map +1 -1
  286. package/esm/socket/utils.d.mts.map +1 -1
  287. package/esm/storage/config.d.mts.map +1 -1
  288. package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
  289. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  290. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  291. package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
  292. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  293. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  294. package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
  295. package/esm/storage/scoped-storage.d.mts.map +1 -1
  296. package/esm/storage/scoped-storage.mjs.map +1 -1
  297. package/esm/storage/storage-file.d.mts.map +1 -1
  298. package/esm/storage/storage-file.mjs.map +1 -1
  299. package/esm/storage/storage.d.mts.map +1 -1
  300. package/esm/storage/types.d.mts +14 -0
  301. package/esm/storage/types.d.mts.map +1 -1
  302. package/esm/storage/utils/safe-fetch.mjs.map +1 -1
  303. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  304. package/esm/tests/start-http-development-server.mjs +2 -1
  305. package/esm/tests/start-http-development-server.mjs.map +1 -1
  306. package/esm/tests/test-connectors-selection.mjs.map +1 -1
  307. package/esm/tests/test-helpers.d.mts.map +1 -1
  308. package/esm/tests/test-setup-timeout.mjs.map +1 -1
  309. package/esm/tests/vitest-setup.d.mts.map +1 -1
  310. package/esm/tests/vitest-setup.mjs.map +1 -1
  311. package/esm/updater/update-warlock-packages.mjs.map +1 -1
  312. package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
  313. package/esm/use-cases/use-case-events.d.mts.map +1 -1
  314. package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
  315. package/esm/use-cases/use-case.d.mts.map +1 -1
  316. package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
  317. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  318. package/esm/utils/database-log.d.mts.map +1 -1
  319. package/esm/utils/database-log.mjs.map +1 -1
  320. package/esm/utils/environment.d.mts.map +1 -1
  321. package/esm/utils/framework-vesion.mjs.map +1 -1
  322. package/esm/utils/get-localized.mjs.map +1 -1
  323. package/esm/utils/load-environment.mjs.map +1 -1
  324. package/esm/utils/normalized-path.d.mts.map +1 -1
  325. package/esm/utils/paths.d.mts.map +1 -1
  326. package/esm/utils/promise-all-object.d.mts.map +1 -1
  327. package/esm/utils/promise-all-object.mjs.map +1 -1
  328. package/esm/utils/sluggable.mjs.map +1 -1
  329. package/esm/utils/version-compare.mjs.map +1 -1
  330. package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
  331. package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
  332. package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
  333. package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
  334. package/esm/validation/validators/file-validator.mjs.map +1 -1
  335. package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
  336. package/esm/warlock-config/default-configurations.mjs.map +1 -1
  337. package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
  338. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  339. package/llms-full.txt +205 -90
  340. package/llms.txt +5 -5
  341. package/package.json +12 -12
  342. package/skills/add-connector/SKILL.md +35 -28
  343. package/skills/create-controller/SKILL.md +1 -1
  344. package/skills/create-module/SKILL.md +12 -12
  345. package/skills/hash-password/SKILL.md +4 -4
  346. package/skills/run-app/SKILL.md +77 -16
  347. package/skills/send-mail/SKILL.md +2 -2
  348. package/skills/use-middleware/SKILL.md +13 -4
  349. package/skills/use-model-transformers/SKILL.md +2 -2
  350. package/skills/use-repository/SKILL.md +1 -1
  351. package/skills/validate-input/SKILL.md +1 -1
  352. package/skills/warlock-conventions/SKILL.md +1 -1
  353. package/skills/warlock-doctor/SKILL.md +3 -3
  354. package/skills/warlock-routes/SKILL.md +47 -9
  355. package/skills/write-cli-command/SKILL.md +1 -1
  356. package/skills/write-seeder/SKILL.md +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"register-configured-connectors.mjs","names":[],"sources":["../../../../../../../core/src/connectors/register-configured-connectors.ts"],"sourcesContent":["/**\n * Registration half of the `connectors` config key.\n *\n * ONE key drives both halves of a connector's life: `warlock build` reads the\n * same array to drain each connector's build contribution, and this function\n * registers that array with the connectors manager so the runtime boots it.\n * Reading them from one place is the whole point — a bundle \"built for\" web\n * that \"boots without\" web is a green build with a missing client bundle, and\n * the failure is silent. A second key would let the two drift; this cannot.\n *\n * Two callers, two ways in:\n *\n * - The generated production entry PASSES the array explicitly. Inside a\n * bundled artifact nothing ever loads `warlock.config.ts` — the build\n * imports it statically so esbuild bakes it into the bundle, then hands the\n * array straight here. No config manager, no runtime file read. It also\n * passes `expectedNames` — the list the build resolved — so a config that\n * resolves differently at startup is refused instead of booted.\n * - The dev preloader OMITS the array and lets this read the already-resolved\n * warlock config, which dev has loaded by then. It also omits\n * `expectedNames`, so dev gets NO drift check — deliberately. Drift is a\n * relationship between what a build baked and what a runtime registers, and\n * dev has no build: there is nothing to drift from, and inventing a second\n * source of truth to compare against would create the disagreement this\n * check exists to prevent. The absence of a check on this path is the\n * design, not an omission — do not harmonise it with the production path.\n * A conditional config that works in dev and is refused at production boot\n * is this working as intended, not a gap.\n *\n * Idempotent either way: a connector whose name an EARLIER CALL in the same\n * process already registered is skipped rather than stacked, since a duplicate\n * would boot and shut down twice. A built-in's name is not that case — it is\n * reserved, and an array claiming one is refused rather than skipped.\n */\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { assertNoReservedConnectorNames } from \"./assert-no-reserved-connector-names\";\nimport { assertUniqueConnectorNames } from \"./assert-unique-connector-names\";\nimport { connectorsManager } from \"./connectors-manager\";\nimport type { Connector } from \"./types\";\n\nexport type RegisterConfiguredConnectorsOptions = {\n /**\n * The connector names, in order, that the build resolved. The generated\n * production entry bakes this in; every other caller omits it.\n */\n expectedNames?: string[];\n};\n\nexport function registerConfiguredConnectors(\n connectors?: Connector[],\n options: RegisterConfiguredConnectorsOptions = {},\n): void {\n const registering = connectors ?? configuredConnectors();\n\n // First, and before the drift check: a name listed twice is registered once\n // here but was drained twice by the build, and the two lists the drift check\n // compares are identical in that case — so it would wave the array through\n // and the asymmetry would survive into the boot. Reported as the duplicate it\n // is, rather than as whatever the next check happens to make of it.\n assertUniqueConnectorNames(registering);\n\n // Then the names that were taken before this array was ever read. The skip\n // below treats an already-held name as idempotency, which is right for a name\n // an EARLIER CALL registered — the same array arriving twice in one process —\n // and wrong for a built-in: nothing the app configured put that name there, so\n // the skip would drop a connector the app asked for and the build already\n // prepared artifacts for. Same call the build makes, so neither half can be\n // reached with an array the other refuses.\n assertNoReservedConnectorNames(registering);\n\n // Before anything is registered: a half-registered manager is worse than a\n // refused boot, because the phases would start on top of it.\n if (options.expectedNames) {\n assertNoConnectorDrift(options.expectedNames, registering);\n }\n\n for (const connector of registering) {\n if (connectorsManager.has(connector.name)) continue;\n\n connectorsManager.register(connector);\n }\n}\n\n/**\n * Refuses a boot whose connector list is not the one the build was made from.\n *\n * `connectors` in warlock.config is a normal expression, so it may read the\n * environment — the build resolves it once, and the artifact re-evaluates the\n * same source at startup. Nothing forces the two results to agree, and when\n * they do not the failure is silent: the bundle carries what a connector\n * contributed at build time while the runtime never boots it, or boots one the\n * build never prepared for.\n *\n * Order counts as a difference. The build drains connector contributions\n * SEQUENTIALLY in config-array order — each one writing files and merging\n * esbuild options on top of what the previous ones left — so the same names in\n * another order describe an artifact produced by a drain sequence this build\n * never ran. Start order is NOT the reason: connectors start in PRIORITY order,\n * and the array's order only breaks ties between equal priorities.\n *\n * Names only — this compares WHICH connectors are present, never how any of\n * them is configured, so it cannot catch a connector pointed at the wrong\n * database. Saying so in the message is the point: a check that reads as\n * broader than it is buys false confidence.\n */\nfunction assertNoConnectorDrift(expectedNames: string[], connectors: Connector[]): void {\n const actualNames = connectors.map((connector) => connector.name);\n\n const matches =\n expectedNames.length === actualNames.length &&\n expectedNames.every((name, index) => name === actualNames[index]);\n\n if (matches) return;\n\n throw new Error(\n \"The connector list this server booted with does not match the list its build was made from.\\n\" +\n ` expected (resolved when the app was built): ${formatNames(expectedNames)}\\n` +\n ` actual (handed over at boot): ${formatNames(actualNames)}\\n` +\n \"This compares the set and order of connectors only, and says nothing about how any connector is configured. \" +\n \"It usually means `connectors` in warlock.config resolves differently here than it did at build time — \" +\n \"rebuild the app in the same configuration environment this server runs in, \" +\n \"or make the array resolve to the same connectors in both.\",\n );\n}\n\nfunction formatNames(names: string[]): string {\n return `[${names.map((name) => JSON.stringify(name)).join(\", \")}]`;\n}\n\n/**\n * The connectors the resolved warlock config declares.\n *\n * Reached only when the caller omitted the array. An unloaded config here is\n * not \"nothing to register\" — it is a caller asking this function to read a\n * source that does not exist yet, so it throws instead of returning empty.\n * Returning empty is how the production entry once registered NOTHING while\n * reporting success: the bundle booted none of the connectors it was built\n * for, and no line of output said so.\n */\nfunction configuredConnectors(): Connector[] {\n if (!warlockConfigManager.isLoaded) {\n throw new Error(\n \"registerConfiguredConnectors: warlock config is not loaded; pass the connectors array explicitly or load the config first\",\n );\n }\n\n return warlockConfigManager.get(\"connectors\") ?? [];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,6BACd,YACA,UAA+C,CAAC,GAC1C;CACN,MAAM,cAAc,cAAc,qBAAqB;CAOvD,2BAA2B,WAAW;CAStC,+BAA+B,WAAW;CAI1C,IAAI,QAAQ,eACV,uBAAuB,QAAQ,eAAe,WAAW;CAG3D,KAAK,MAAM,aAAa,aAAa;EACnC,IAAI,kBAAkB,IAAI,UAAU,IAAI,GAAG;EAE3C,kBAAkB,SAAS,SAAS;CACtC;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,uBAAuB,eAAyB,YAA+B;CACtF,MAAM,cAAc,WAAW,KAAK,cAAc,UAAU,IAAI;CAMhE,IAHE,cAAc,WAAW,YAAY,UACrC,cAAc,OAAO,MAAM,UAAU,SAAS,YAAY,MAAM,GAErD;CAEb,MAAM,IAAI,MACR;gDACmD,YAAY,aAAa,EAAE,oCACzC,YAAY,WAAW,EAAE,2VAKhE;AACF;AAEA,SAAS,YAAY,OAAyB;CAC5C,OAAO,IAAI,MAAM,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE;AAClE;;;;;;;;;;;AAYA,SAAS,uBAAoC;CAC3C,IAAI,CAAC,qBAAqB,UACxB,MAAM,IAAI,MACR,2HACF;CAGF,OAAO,qBAAqB,IAAI,YAAY,KAAK,CAAC;AACpD"}
1
+ {"version":3,"file":"register-configured-connectors.mjs","names":[],"sources":["../../../../../../../core/src/connectors/register-configured-connectors.ts"],"sourcesContent":["/**\n * Registration half of the `connectors` config key.\n *\n * ONE key drives both halves of a connector's life: `warlock build` reads the\n * same array to drain each connector's build contribution, and this function\n * registers that array with the connectors manager so the runtime boots it.\n * Reading them from one place is the whole point — a bundle \"built for\" web\n * that \"boots without\" web is a green build with a missing client bundle, and\n * the failure is silent. A second key would let the two drift; this cannot.\n *\n * Two callers, two ways in:\n *\n * - The generated production entry PASSES the array explicitly. Inside a\n * bundled artifact nothing ever loads `warlock.config.ts` — the build\n * imports it statically so esbuild bakes it into the bundle, then hands the\n * array straight here. No config manager, no runtime file read. It also\n * passes `expectedNames` — the list the build resolved — so a config that\n * resolves differently at startup is refused instead of booted.\n * - The dev preloader OMITS the array and lets this read the already-resolved\n * warlock config, which dev has loaded by then. It also omits\n * `expectedNames`, so dev gets NO drift check — deliberately. Drift is a\n * relationship between what a build baked and what a runtime registers, and\n * dev has no build: there is nothing to drift from, and inventing a second\n * source of truth to compare against would create the disagreement this\n * check exists to prevent. The absence of a check on this path is the\n * design, not an omission — do not harmonise it with the production path.\n * A conditional config that works in dev and is refused at production boot\n * is this working as intended, not a gap.\n *\n * Idempotent either way: a connector whose name an EARLIER CALL in the same\n * process already registered is skipped rather than stacked, since a duplicate\n * would boot and shut down twice. A built-in's name is not that case — it is\n * reserved, and an array claiming one is refused rather than skipped.\n */\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { assertNoReservedConnectorNames } from \"./assert-no-reserved-connector-names\";\nimport { assertUniqueConnectorNames } from \"./assert-unique-connector-names\";\nimport { connectorsManager } from \"./connectors-manager\";\nimport type { Connector } from \"./types\";\n\nexport type RegisterConfiguredConnectorsOptions = {\n /**\n * The connector names, in order, that the build resolved. The generated\n * production entry bakes this in; every other caller omits it.\n */\n expectedNames?: string[];\n};\n\nexport function registerConfiguredConnectors(\n connectors?: Connector[],\n options: RegisterConfiguredConnectorsOptions = {},\n): void {\n const registering = connectors ?? configuredConnectors();\n\n // First, and before the drift check: a name listed twice is registered once\n // here but was drained twice by the build, and the two lists the drift check\n // compares are identical in that case — so it would wave the array through\n // and the asymmetry would survive into the boot. Reported as the duplicate it\n // is, rather than as whatever the next check happens to make of it.\n assertUniqueConnectorNames(registering);\n\n // Then the names that were taken before this array was ever read. The skip\n // below treats an already-held name as idempotency, which is right for a name\n // an EARLIER CALL registered — the same array arriving twice in one process —\n // and wrong for a built-in: nothing the app configured put that name there, so\n // the skip would drop a connector the app asked for and the build already\n // prepared artifacts for. Same call the build makes, so neither half can be\n // reached with an array the other refuses.\n assertNoReservedConnectorNames(registering);\n\n // Before anything is registered: a half-registered manager is worse than a\n // refused boot, because the phases would start on top of it.\n if (options.expectedNames) {\n assertNoConnectorDrift(options.expectedNames, registering);\n }\n\n for (const connector of registering) {\n if (connectorsManager.has(connector.name)) continue;\n\n connectorsManager.register(connector);\n }\n}\n\n/**\n * Refuses a boot whose connector list is not the one the build was made from.\n *\n * `connectors` in warlock.config is a normal expression, so it may read the\n * environment — the build resolves it once, and the artifact re-evaluates the\n * same source at startup. Nothing forces the two results to agree, and when\n * they do not the failure is silent: the bundle carries what a connector\n * contributed at build time while the runtime never boots it, or boots one the\n * build never prepared for.\n *\n * Order counts as a difference. The build drains connector contributions\n * SEQUENTIALLY in config-array order — each one writing files and merging\n * esbuild options on top of what the previous ones left — so the same names in\n * another order describe an artifact produced by a drain sequence this build\n * never ran. Start order is NOT the reason: connectors start in PRIORITY order,\n * and the array's order only breaks ties between equal priorities.\n *\n * Names only — this compares WHICH connectors are present, never how any of\n * them is configured, so it cannot catch a connector pointed at the wrong\n * database. Saying so in the message is the point: a check that reads as\n * broader than it is buys false confidence.\n */\nfunction assertNoConnectorDrift(expectedNames: string[], connectors: Connector[]): void {\n const actualNames = connectors.map((connector) => connector.name);\n\n const matches =\n expectedNames.length === actualNames.length &&\n expectedNames.every((name, index) => name === actualNames[index]);\n\n if (matches) return;\n\n throw new Error(\n \"The connector list this server booted with does not match the list its build was made from.\\n\" +\n ` expected (resolved when the app was built): ${formatNames(expectedNames)}\\n` +\n ` actual (handed over at boot): ${formatNames(actualNames)}\\n` +\n \"This compares the set and order of connectors only, and says nothing about how any connector is configured. \" +\n \"It usually means `connectors` in warlock.config resolves differently here than it did at build time — \" +\n \"rebuild the app in the same configuration environment this server runs in, \" +\n \"or make the array resolve to the same connectors in both.\",\n );\n}\n\nfunction formatNames(names: string[]): string {\n return `[${names.map((name) => JSON.stringify(name)).join(\", \")}]`;\n}\n\n/**\n * The connectors the resolved warlock config declares.\n *\n * Reached only when the caller omitted the array. An unloaded config here is\n * not \"nothing to register\" — it is a caller asking this function to read a\n * source that does not exist yet, so it throws instead of returning empty.\n * Returning empty is how the production entry once registered NOTHING while\n * reporting success: the bundle booted none of the connectors it was built\n * for, and no line of output said so.\n */\nfunction configuredConnectors(): Connector[] {\n if (!warlockConfigManager.isLoaded) {\n throw new Error(\n \"registerConfiguredConnectors: warlock config is not loaded; pass the connectors array explicitly or load the config first\",\n );\n }\n\n return warlockConfigManager.get(\"connectors\") ?? [];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,6BACd,YACA,UAA+C,CAAC,GAC1C;CACN,MAAM,cAAc,cAAc,qBAAqB;CAOvD,2BAA2B,WAAW;CAStC,+BAA+B,WAAW;CAI1C,IAAI,QAAQ,eACV,uBAAuB,QAAQ,eAAe,WAAW;CAG3D,KAAK,MAAM,aAAa,aAAa;EACnC,IAAI,kBAAkB,IAAI,UAAU,IAAI,GAAG;EAE3C,kBAAkB,SAAS,SAAS;CACtC;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,uBAAuB,eAAyB,YAA+B;CACtF,MAAM,cAAc,WAAW,KAAK,cAAc,UAAU,IAAI;CAMhE,IAHE,cAAc,WAAW,YAAY,UACrC,cAAc,OAAO,MAAM,UAAU,SAAS,YAAY,MAAM,GAErD;CAEb,MAAM,IAAI,MACR;gDACmD,YAAY,aAAa,EAAE,oCACzC,YAAY,WAAW,EAAE,2VAKhE;AACF;AAEA,SAAS,YAAY,OAAyB;CAC5C,OAAO,IAAI,MAAM,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;AAClE;;;;;;;;;;;AAYA,SAAS,uBAAoC;CAC3C,IAAI,CAAC,qBAAqB,UACxB,MAAM,IAAI,MACR,2HACF;CAGF,OAAO,qBAAqB,IAAI,YAAY,KAAK,CAAC;AACpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;AA8BA;;cAAa,eAAA,SAAwB,aAAA;EAAA,SACnB,IAAA,EAAM,aAAA;EAAA,SACN,QAAA,GAAQ,iBAAA,CAAA,MAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EASX;;;;;EAAA,mBAFA,YAAA;EAAA,UAET,MAAA,GAAS,MAAA;EAZgB;;;;;;;;EAAA,UAsBzB,aAAA;EAZS;;;EAiBN,IAAA,IAAI,OAAA;EAAJ;;;EAuDA,KAAA,IAAS,OAAA;EAqBT;;;AAAmB;;;;;;EAAnB,QAAA,IAAY,OAAA;AAAA"}
1
+ {"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;AA8BA;;cAAa,eAAA,SAAwB,aAAA;EAAA,SACnB,IAAA,EAAM,aAAA;EAAA,SACN,QAAA,GAAQ,iBAAA,CAAA,MAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EASX;;;;;EAAA,mBAFA,YAAA;EAAA,UAET,MAAA,GAAS,MAAA;EAZgB;;;;;;;;EAAA,UAsBzB,aAAA;EAZS;;;EAiBN,IAAA,CAAA,GAAI,OAAA;EAAJ;;;EAuDA,KAAA,CAAA,GAAS,OAAA;EAqBT;;;AAAmB;;;;;;EAAnB,QAAA,CAAA,GAAY,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.mjs","names":["createHttpsServer","createHttpServer"],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server;\n if (container.has(\"http.server\")) {\n const fastify = container.get(\"http.server\");\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl ? createHttpsServer() : createHttpServer();\n server.listen(socketConfig.port);\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n if (container.has(\"socket\")) {\n const socket = container.get(\"socket\");\n // socket.io's close() takes a callback — await it so shutdown doesn't\n // report done while sockets are still draining.\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n }\n\n if (this.ownsRawServer && container.has(\"socket.rawServer\")) {\n const server = container.get(\"socket.rawServer\");\n await new Promise<void>((resolve, reject) => {\n server.close((error?: Error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,8BAA8B;;;;;;;;;;;EAWlC,KAAK;;;;;AAMP,IAAa,kBAAb,cAAqC,cAAc;;;cACX;;;sBASJ,CAAC,sBAAsB;uBAY/B;;;;;CAK1B,MAAa,OAAO;EAClB,MAAM,eAAe,OAAO,IAAI,QAAQ;EAExC,IAAI,CAAC,cAAc;EAInB,IAAI;EACJ,IAAI;GACF,CAAC,CAAE,QAAQ,gBAAiB,MAAM,OAAO;EAC3C,QAAQ;GACN,MAAM,IAAI,MAAM,2BAA2B;EAC7C;EAEA,IAAI,KAAK,UAAU,cAAc,2BAA2B;EAK5D,IAAI;EACJ,IAAI,UAAU,IAAI,aAAa,GAAG;GAEhC,SADgB,UAAU,IAAI,aACf,CAAC,CAAC;GACjB,KAAK,gBAAgB;EACvB,OAAO;GACL,SAAS,aAAa,MAAMA,eAAkB,IAAIC,aAAiB;GACnE,OAAO,OAAO,aAAa,IAAI;GAC/B,KAAK,gBAAgB;EACvB;EAEA,UAAU,IAAI,oBAAoB,MAAM;EAExC,KAAK,SAAS,IAAI,aAAa,QAAQ;GAarC,gBAAgB;GAChB,GAAG,aAAa;EAClB,CAAC;EAED,UAAU,IAAI,UAAU,KAAK,MAAM;CACrC;;;;CAKA,MAAa,QAAuB;EAKlC,IAAI,CAJiB,OAAO,IAAI,QAIhB,KAAK,CAAC,KAAK,QAAQ;EAEnC,IAAI,QAAQ,UAAU,cAAc,8BAA8B;EAElE,KAAK,SAAS;CAChB;;;;;;;;;;CAWA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI,UAAU,IAAI,QAAQ,GAAG;GAC3B,MAAM,SAAS,UAAU,IAAI,QAAQ;GAGrC,MAAM,IAAI,SAAe,YAAY;IACnC,OAAO,YAAY,QAAQ,CAAC;GAC9B,CAAC;EACH;EAEA,IAAI,KAAK,iBAAiB,UAAU,IAAI,kBAAkB,GAAG;GAC3D,MAAM,SAAS,UAAU,IAAI,kBAAkB;GAC/C,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,OAAO,OAAO,UAAkB;KAC9B,IAAI,OACF,OAAO,KAAK;UAEZ,QAAQ;IAEZ,CAAC;GACH,CAAC;EACH;EAEA,KAAK,SAAS;CAChB;AACF"}
1
+ {"version":3,"file":"socket-connector.mjs","names":["createHttpsServer","createHttpServer"],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, type ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server;\n if (container.has(\"http.server\")) {\n const fastify = container.get(\"http.server\");\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl ? createHttpsServer() : createHttpServer();\n server.listen(socketConfig.port);\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n if (container.has(\"socket\")) {\n const socket = container.get(\"socket\");\n // socket.io's close() takes a callback — await it so shutdown doesn't\n // report done while sockets are still draining.\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n }\n\n if (this.ownsRawServer && container.has(\"socket.rawServer\")) {\n const server = container.get(\"socket.rawServer\");\n await new Promise<void>((resolve, reject) => {\n server.close((error?: Error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,8BAA8B;;;;;;;;;;;EAWlC,KAAK;;;;;AAMP,IAAa,kBAAb,cAAqC,cAAc;;;cACX;;;sBASJ,CAAC,sBAAsB;uBAY/B;;;;;CAK1B,MAAa,OAAO;EAClB,MAAM,eAAe,OAAO,IAAI,QAAQ;EAExC,IAAI,CAAC,cAAc;EAInB,IAAI;EACJ,IAAI;GACF,CAAC,CAAE,QAAQ,gBAAiB,MAAM,OAAO;EAC3C,QAAQ;GACN,MAAM,IAAI,MAAM,2BAA2B;EAC7C;EAEA,IAAI,KAAK,UAAU,cAAc,2BAA2B;EAK5D,IAAI;EACJ,IAAI,UAAU,IAAI,aAAa,GAAG;GAEhC,SADgB,UAAU,IAAI,aACf,EAAE;GACjB,KAAK,gBAAgB;EACvB,OAAO;GACL,SAAS,aAAa,MAAMA,eAAkB,IAAIC,aAAiB;GACnE,OAAO,OAAO,aAAa,IAAI;GAC/B,KAAK,gBAAgB;EACvB;EAEA,UAAU,IAAI,oBAAoB,MAAM;EAExC,KAAK,SAAS,IAAI,aAAa,QAAQ;GAarC,gBAAgB;GAChB,GAAG,aAAa;EAClB,CAAC;EAED,UAAU,IAAI,UAAU,KAAK,MAAM;CACrC;;;;CAKA,MAAa,QAAuB;EAKlC,IAAI,CAJiB,OAAO,IAAI,QAIhB,KAAK,CAAC,KAAK,QAAQ;EAEnC,IAAI,QAAQ,UAAU,cAAc,8BAA8B;EAElE,KAAK,SAAS;CAChB;;;;;;;;;;CAWA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI,UAAU,IAAI,QAAQ,GAAG;GAC3B,MAAM,SAAS,UAAU,IAAI,QAAQ;GAGrC,MAAM,IAAI,SAAe,YAAY;IACnC,OAAO,YAAY,QAAQ,CAAC;GAC9B,CAAC;EACH;EAEA,IAAI,KAAK,iBAAiB,UAAU,IAAI,kBAAkB,GAAG;GAC3D,MAAM,SAAS,UAAU,IAAI,kBAAkB;GAC/C,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,OAAO,OAAO,UAAkB;KAC9B,IAAI,OACF,OAAO,KAAK;UAEZ,QAAQ;IAEZ,CAAC;GACH,CAAC;EACH;EAEA,KAAK,SAAS;CAChB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage.connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"mappings":";;;;;;AASA;;cAAa,gBAAA,SAAyB,aAAA;EAAA,SACpB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,OAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,KAAA;EAiCL;;;EAAA,mBA5BN,YAAA;EARiB;;;;;;;;;;;;;;AAoCJ;;EAVnB,KAAA,IAAS,OAAA;;;;EAUT,QAAA,IAAY,OAAA;AAAA"}
1
+ {"version":3,"file":"storage.connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"mappings":";;;;;;AASA;;cAAa,gBAAA,SAAyB,aAAA;EAAA,SACpB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,OAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,KAAA;EAiCL;;;EAAA,mBA5BN,YAAA;EARiB;;;;;;;;;;;;;;AAoCJ;;EAVnB,KAAA,CAAA,GAAS,OAAA;;;;EAUT,QAAA,CAAA,GAAY,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../core/src/container/index.ts"],"mappings":";;;;;;;;;KAUY,cAAA;EACV,MAAA,EAAQ,MAAA;EACR,aAAA,EAAe,eAAA;EACf,cAAA;EACA,MAAA,EAAQ,MAAA;EACR,iBAAA,EAAmB,UAAA;AAAA;AAAA,KAGhB,aAAA,SAAsB,cAAc;AAAA,cAEnC,SAAA;EATJ;;;EAaO,GAAA,iBAAoB,cAAA,EAAgB,GAAA,EAAK,CAAA,EAAG,KAAA,EAAO,cAAA,CAAe,CAAA;EAClE,GAAA,UAAa,GAAA,UAAa,KAAA,EAAO,CAAA;EAXxC;;;EAmBO,GAAA,iBAAoB,cAAA,EAAgB,GAAA,EAAK,CAAA,GAAI,cAAA,CAAe,CAAA;EAC5D,GAAA,UAAa,GAAA,WAAc,CAAA;EAnBL;AAC7B;;EA0BO,GAAA,CAAI,GAAA,EAAK,aAAA;EAxBuB;AAAA;AAAA;EA+BhC,MAAA,CAAO,GAAA;AAAA;AAAA,cAKH,SAAA,EAAS,SAAkB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../core/src/container/index.ts"],"mappings":";;;;;;;;;KAUY,cAAA;EACV,MAAA,EAAQ,MAAA;EACR,aAAA,EAAe,eAAA;EACf,cAAA;EACA,MAAA,EAAQ,MAAA;EACR,iBAAA,EAAmB,UAAA;AAAA;AAAA,KAGhB,aAAA,SAAsB,cAAc;AAAA,cAEnC,SAAA;EATJ;;;EAaO,GAAA,iBAAoB,cAAA,CAAA,CAAgB,GAAA,EAAK,CAAA,EAAG,KAAA,EAAO,cAAA,CAAe,CAAA;EAClE,GAAA,SAAA,CAAa,GAAA,UAAa,KAAA,EAAO,CAAA;EAXxC;;;EAmBO,GAAA,iBAAoB,cAAA,CAAA,CAAgB,GAAA,EAAK,CAAA,GAAI,cAAA,CAAe,CAAA;EAC5D,GAAA,SAAA,CAAa,GAAA,WAAc,CAAA;EAnBL;AAC7B;;EA0BO,GAAA,CAAI,GAAA,EAAK,aAAA;EAxBuB;AAAA;AAAA;EA+BhC,MAAA,CAAO,GAAA;AAAA;AAAA,cAKH,SAAA,EAAS,SAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { createDatabase } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { config } from \"../config\";\r\n\r\nexport async function createDatabaseAction(command: CommandActionData) {\r\n const name = command.args[0];\r\n const { connection = \"default\" } = command.options;\r\n\r\n const connectionName =\r\n connection === \"default\" || connection === true ? undefined : String(connection);\r\n\r\n let databaseName = name;\r\n\r\n if (!databaseName) {\r\n if (connection === \"default\") {\r\n databaseName = config.get(\"database\")?.database;\r\n }\r\n }\r\n\r\n if (!databaseName) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n \"Database name is required. Please provide a name or configure it in the database config.\",\r\n );\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"create\", `Creating database ${colors.cyan(databaseName)}...`);\r\n\r\n try {\r\n const { created } = await createDatabase(databaseName, { connection: connectionName });\r\n\r\n if (created) {\r\n log.success(\r\n \"database\",\r\n \"create\",\r\n `Database ${colors.green(databaseName)} created successfully.`,\r\n );\r\n } else {\r\n log.warn(\"database\", \"create\", `Database ${colors.yellow(databaseName)} already exists.`);\r\n }\r\n } catch (error: any) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n `Failed to create database ${colors.red(databaseName)}: ${error.message}`,\r\n );\r\n }\r\n}\r\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB,SAA4B;CACrE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,MAAM,iBACJ,eAAe,aAAa,eAAe,OAAO,SAAY,OAAO,UAAU;CAEjF,IAAI,eAAe;CAEnB,IAAI,CAAC,cACH;MAAI,eAAe,WACjB,eAAe,OAAO,IAAI,UAAU,CAAC,EAAE;CACzC;CAGF,IAAI,CAAC,cAAc;EACjB,IAAI,MACF,YACA,UACA,0FACF;EACA;CACF;CAEA,IAAI,KAAK,YAAY,UAAU,qBAAqB,OAAO,KAAK,YAAY,EAAE,IAAI;CAElF,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,eAAe,cAAc,EAAE,YAAY,eAAe,CAAC;EAErF,IAAI,SACF,IAAI,QACF,YACA,UACA,YAAY,OAAO,MAAM,YAAY,EAAE,uBACzC;OAEA,IAAI,KAAK,YAAY,UAAU,YAAY,OAAO,OAAO,YAAY,EAAE,iBAAiB;CAE5F,SAAS,OAAY;EACnB,IAAI,MACF,YACA,UACA,6BAA6B,OAAO,IAAI,YAAY,EAAE,IAAI,MAAM,SAClE;CACF;AACF"}
1
+ {"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { createDatabase } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { config } from \"../config\";\r\n\r\nexport async function createDatabaseAction(command: CommandActionData) {\r\n const name = command.args[0];\r\n const { connection = \"default\" } = command.options;\r\n\r\n const connectionName =\r\n connection === \"default\" || connection === true ? undefined : String(connection);\r\n\r\n let databaseName = name;\r\n\r\n if (!databaseName) {\r\n if (connection === \"default\") {\r\n databaseName = config.get(\"database\")?.database;\r\n }\r\n }\r\n\r\n if (!databaseName) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n \"Database name is required. Please provide a name or configure it in the database config.\",\r\n );\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"create\", `Creating database ${colors.cyan(databaseName)}...`);\r\n\r\n try {\r\n const { created } = await createDatabase(databaseName, { connection: connectionName });\r\n\r\n if (created) {\r\n log.success(\r\n \"database\",\r\n \"create\",\r\n `Database ${colors.green(databaseName)} created successfully.`,\r\n );\r\n } else {\r\n log.warn(\"database\", \"create\", `Database ${colors.yellow(databaseName)} already exists.`);\r\n }\r\n } catch (error: any) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n `Failed to create database ${colors.red(databaseName)}: ${error.message}`,\r\n );\r\n }\r\n}\r\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB,SAA4B;CACrE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,MAAM,iBACJ,eAAe,aAAa,eAAe,OAAO,SAAY,OAAO,UAAU;CAEjF,IAAI,eAAe;CAEnB,IAAI,CAAC,cACH;MAAI,eAAe,WACjB,eAAe,OAAO,IAAI,UAAU,GAAG;CACzC;CAGF,IAAI,CAAC,cAAc;EACjB,IAAI,MACF,YACA,UACA,0FACF;EACA;CACF;CAEA,IAAI,KAAK,YAAY,UAAU,qBAAqB,OAAO,KAAK,YAAY,EAAE,IAAI;CAElF,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,eAAe,cAAc,EAAE,YAAY,eAAe,CAAC;EAErF,IAAI,SACF,IAAI,QACF,YACA,UACA,YAAY,OAAO,MAAM,YAAY,EAAE,uBACzC;OAEA,IAAI,KAAK,YAAY,UAAU,YAAY,OAAO,OAAO,YAAY,EAAE,iBAAiB;CAE5F,SAAS,OAAY;EACnB,IAAI,MACF,YACA,UACA,6BAA6B,OAAO,IAAI,YAAY,EAAE,IAAI,MAAM,SAClE;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"drop-tables-action.mjs","names":[],"sources":["../../../../../../../core/src/database/drop-tables-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { dataSourceRegistry, dropAllTables } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { confirm } from \"../cli/commands/generate/utils/prompt\";\r\nimport { CommandActionData } from \"../commands/types\";\r\n\r\nexport async function dropTablesAction(command: CommandActionData) {\r\n const { force } = command.options;\r\n\r\n if (force) {\r\n const { dropped } = await dropAllTables();\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n return;\r\n }\r\n\r\n // Preview phase — we keep direct driver access so we can show per-table\r\n // row counts before asking for confirmation. The actual drop still goes\r\n // through the Cascade Operations API.\r\n const driver = dataSourceRegistry.get().driver;\r\n const tables = await driver.blueprint.listTables();\r\n\r\n if (tables.length === 0) {\r\n log.warn(\"database\", \"drop\", \"No tables found in the database.\");\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"drop\", `Found ${colors.yellowBright(tables.length)} tables:`);\r\n\r\n for (const table of tables) {\r\n const count = await driver.queryBuilder(table).count();\r\n console.log(` - ${colors.cyan(table)}: ${colors.green(count)} rows`);\r\n }\r\n\r\n const confirmed = await confirm(\r\n `Are you sure you want to drop all ${colors.red(tables.length)} tables?`,\r\n );\r\n\r\n if (!confirmed) {\r\n log.info(\"database\", \"drop\", \"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n const { dropped } = await dropAllTables();\r\n\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n}\r\n"],"mappings":";;;;;;AAMA,eAAsB,iBAAiB,SAA4B;CACjE,MAAM,EAAE,UAAU,QAAQ;CAE1B,IAAI,OAAO;EACT,MAAM,EAAE,YAAY,MAAM,cAAc;EACxC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;EACA;CACF;CAKA,MAAM,SAAS,mBAAmB,IAAI,CAAC,CAAC;CACxC,MAAM,SAAS,MAAM,OAAO,UAAU,WAAW;CAEjD,IAAI,OAAO,WAAW,GAAG;EACvB,IAAI,KAAK,YAAY,QAAQ,kCAAkC;EAC/D;CACF;CAEA,IAAI,KAAK,YAAY,QAAQ,SAAS,OAAO,aAAa,OAAO,MAAM,EAAE,SAAS;CAElF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,MAAM,OAAO,aAAa,KAAK,CAAC,CAAC,MAAM;EACrD,QAAQ,IAAI,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,OAAO,MAAM,KAAK,EAAE,MAAM;CACtE;CAMA,IAAI,CAAC,MAJmB,QACtB,qCAAqC,OAAO,IAAI,OAAO,MAAM,EAAE,SACjE,GAEgB;EACd,IAAI,KAAK,YAAY,QAAQ,sBAAsB;EACnD;CACF;CAEA,MAAM,EAAE,YAAY,MAAM,cAAc;CAExC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;AACF"}
1
+ {"version":3,"file":"drop-tables-action.mjs","names":[],"sources":["../../../../../../../core/src/database/drop-tables-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { dataSourceRegistry, dropAllTables } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { confirm } from \"../cli/commands/generate/utils/prompt\";\r\nimport { CommandActionData } from \"../commands/types\";\r\n\r\nexport async function dropTablesAction(command: CommandActionData) {\r\n const { force } = command.options;\r\n\r\n if (force) {\r\n const { dropped } = await dropAllTables();\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n return;\r\n }\r\n\r\n // Preview phase — we keep direct driver access so we can show per-table\r\n // row counts before asking for confirmation. The actual drop still goes\r\n // through the Cascade Operations API.\r\n const driver = dataSourceRegistry.get().driver;\r\n const tables = await driver.blueprint.listTables();\r\n\r\n if (tables.length === 0) {\r\n log.warn(\"database\", \"drop\", \"No tables found in the database.\");\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"drop\", `Found ${colors.yellowBright(tables.length)} tables:`);\r\n\r\n for (const table of tables) {\r\n const count = await driver.queryBuilder(table).count();\r\n console.log(` - ${colors.cyan(table)}: ${colors.green(count)} rows`);\r\n }\r\n\r\n const confirmed = await confirm(\r\n `Are you sure you want to drop all ${colors.red(tables.length)} tables?`,\r\n );\r\n\r\n if (!confirmed) {\r\n log.info(\"database\", \"drop\", \"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n const { dropped } = await dropAllTables();\r\n\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n}\r\n"],"mappings":";;;;;;AAMA,eAAsB,iBAAiB,SAA4B;CACjE,MAAM,EAAE,UAAU,QAAQ;CAE1B,IAAI,OAAO;EACT,MAAM,EAAE,YAAY,MAAM,cAAc;EACxC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;EACA;CACF;CAKA,MAAM,SAAS,mBAAmB,IAAI,EAAE;CACxC,MAAM,SAAS,MAAM,OAAO,UAAU,WAAW;CAEjD,IAAI,OAAO,WAAW,GAAG;EACvB,IAAI,KAAK,YAAY,QAAQ,kCAAkC;EAC/D;CACF;CAEA,IAAI,KAAK,YAAY,QAAQ,SAAS,OAAO,aAAa,OAAO,MAAM,EAAE,SAAS;CAElF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,MAAM,OAAO,aAAa,KAAK,EAAE,MAAM;EACrD,QAAQ,IAAI,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,OAAO,MAAM,KAAK,EAAE,MAAM;CACtE;CAMA,IAAI,CAAC,MAJmB,QACtB,qCAAqC,OAAO,IAAI,OAAO,MAAM,EAAE,SACjE,GAEgB;EACd,IAAI,KAAK,YAAY,QAAQ,sBAAsB;EACnD;CACF;CAEA,MAAM,EAAE,YAAY,MAAM,cAAc;CAExC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n exportMigrationsSQL,\r\n freshMigrate,\r\n listExecutedMigrations,\r\n Migration,\r\n migrationRunner,\r\n rollbackMigrations,\r\n runMigrations,\r\n} from \"@warlock.js/cascade\";\r\nimport dayjs from \"dayjs\";\r\nimport path from \"path\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { exitCodeFor, PENDING_EXIT_CODE } from \"./pending-exit-code\";\r\nimport {\r\n resolvePendingMigrations,\r\n type PendingMigrationsResult,\r\n} from \"./resolve-pending-migrations\";\r\n\r\n/**\r\n * `--list` — print the migration state: what has run, then what will run next.\r\n *\r\n * The executed section is printed FIRST and unconditionally, before anything\r\n * touches disk or config. That ordering is the contract: reading migrations\r\n * from the table cannot fail because of a broken migration file, and `--list`\r\n * is the command an operator reaches for *during* an incident. The pending\r\n * section is best-effort on top of an answer that is already on screen.\r\n *\r\n * Always exits 0. It is a report; `--pending` is the gate.\r\n */\r\nasync function listMigrationsAction() {\r\n await printExecutedMigrations();\r\n\r\n const pending = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(pending);\r\n}\r\n\r\nasync function printExecutedMigrations() {\r\n const createdMigrations = await listExecutedMigrations();\r\n\r\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\r\n\r\n if (createdMigrations.length === 0) {\r\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\r\n return;\r\n }\r\n\r\n // Display each migration as a block\r\n for (const migration of createdMigrations) {\r\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\r\n const createdAt = migration.createdAt\r\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\r\n : null;\r\n\r\n // Migration name with checkmark icon\r\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\r\n\r\n // Executed date\r\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\r\n\r\n // Created date (if available)\r\n if (createdAt) {\r\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\r\n }\r\n\r\n console.log(\"\"); // Empty line between migrations\r\n }\r\n}\r\n\r\n/**\r\n * Print the pending section shared by `--list` and `--pending`.\r\n *\r\n * An unavailable result prints an explicit line and NEVER a count. \"0 pending\"\r\n * over a tree we failed to read is the one output this whole feature exists to\r\n * prevent.\r\n */\r\nfunction printPendingMigrations(result: PendingMigrationsResult) {\r\n if (result.type === \"unavailable\") {\r\n console.log(\r\n `${colors.yellowBright(\"Pending: unavailable\")} — ${result.reason}\\n` +\r\n colors.gray(\" The executed list above is still accurate.\\n\"),\r\n );\r\n return;\r\n }\r\n\r\n const { migrations } = result;\r\n\r\n console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\\n`);\r\n\r\n if (migrations.length === 0) {\r\n console.log(colors.gray(\" Nothing pending — the database is up to date.\\n\"));\r\n return;\r\n }\r\n\r\n // Numbered because the ORDER is the answer: this is what the next\r\n // `warlock migrate` will do, in the sequence it will do it.\r\n migrations.forEach((migration, index) => {\r\n console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);\r\n });\r\n\r\n console.log(\"\");\r\n}\r\n\r\n/**\r\n * `--pending` — the scriptable half. Same computation as `--list`'s pending\r\n * section, different exit contract: see {@link PENDING_EXIT_CODE}.\r\n */\r\nasync function pendingMigrationsAction() {\r\n const result = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(result);\r\n\r\n const exitCode = exitCodeFor(result);\r\n\r\n if (exitCode === PENDING_EXIT_CODE.unavailable) {\r\n console.error(\r\n colors.redBright(\"Could not determine the pending set — exiting 2 rather than reporting 0.\"),\r\n );\r\n }\r\n\r\n process.exit(exitCode);\r\n}\r\n\r\nasync function allMigrationsFilesAction() {\r\n // get all available migration files in the project\r\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\r\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\r\n\r\n for (const file of files) {\r\n console.log(colors.yellowBright(file));\r\n }\r\n}\r\n\r\n/**\r\n * If path is provided, then run the migration runner against that file only\r\n * If fresh is provided, then rollback all migrations and run all migrations\r\n * If rollback is provided, then run the migration runner against all files in reverse order\r\n */\r\nexport async function migrateAction(options: CommandActionData) {\r\n const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;\r\n\r\n if (list) {\r\n return await listMigrationsAction();\r\n }\r\n\r\n if (pending) {\r\n return await pendingMigrationsAction();\r\n }\r\n\r\n if (all) {\r\n return await allMigrationsFilesAction();\r\n }\r\n\r\n if (path) {\r\n await loadMigrationFile(Path.toAbsolute(path as string));\r\n } else {\r\n await loadAllMigrations();\r\n }\r\n\r\n if (fresh && rollback) {\r\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\r\n process.exit(1);\r\n }\r\n\r\n if (rollback) {\r\n await rollbackMigrations({ all: true });\r\n return;\r\n }\r\n\r\n if (sql) {\r\n await exportMigrationsSQL({\r\n pendingOnly: pendingOnly as boolean,\r\n compact: compact as boolean,\r\n });\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await freshMigrate();\r\n return;\r\n }\r\n\r\n await runMigrations();\r\n}\r\n\r\nasync function loadMigrationFile(absPath: string) {\r\n const relativePath = Path.toRelative(absPath);\r\n\r\n const loadedModule = await filesOrchestrator.load<{ default: typeof Migration }>(relativePath);\r\n\r\n if (!loadedModule?.default) {\r\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\r\n }\r\n\r\n const MigrationClass = loadedModule.default;\r\n\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = path\r\n .basename(absPath)\r\n .split(\".\")[0]\r\n .replace(\"-migration\", \"\")\r\n .replace(\"_migration\", \"\");\r\n }\r\n\r\n // Extract createdAt timestamp from filename if not already set\r\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\r\n if (!MigrationClass.createdAt) {\r\n const filename = path.basename(absPath);\r\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\r\n if (timestampMatch) {\r\n MigrationClass.createdAt = timestampMatch[1];\r\n }\r\n }\r\n\r\n migrationRunner.register(MigrationClass);\r\n}\r\n\r\n/**\r\n *\r\n * @returns List of absolute paths to migration files\r\n */\r\nasync function migrationFiles() {\r\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\r\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\r\n srcPath(\"app\"),\r\n \"*/migrations/*\",\r\n );\r\n\r\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles];\r\n\r\n return migrations;\r\n}\r\n\r\nasync function loadAllMigrations() {\r\n // Load config-registered migrations (from packages like @warlock.js/auth)\r\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\r\n\r\n for (const MigrationClass of configMigrations) {\r\n // Use class name as migration name if not set\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = MigrationClass.name;\r\n }\r\n migrationRunner.register(MigrationClass);\r\n }\r\n\r\n // Always load file-based migrations from src/app, regardless of config\r\n const migrations = await migrationFiles();\r\n for (const migrationFile of migrations) {\r\n await loadMigrationFile(migrationFile);\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n exportMigrationsSQL,\r\n freshMigrate,\r\n listExecutedMigrations,\r\n Migration,\n type MigrationConstructor,\r\n migrationRunner,\r\n rollbackMigrations,\r\n runMigrations,\r\n} from \"@warlock.js/cascade\";\r\nimport dayjs from \"dayjs\";\r\nimport path from \"path\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { exitCodeFor, PENDING_EXIT_CODE } from \"./pending-exit-code\";\r\nimport {\r\n resolvePendingMigrations,\r\n type PendingMigrationsResult,\r\n} from \"./resolve-pending-migrations\";\r\n\r\n/**\r\n * `--list` — print the migration state: what has run, then what will run next.\r\n *\r\n * The executed section is printed FIRST and unconditionally, before anything\r\n * touches disk or config. That ordering is the contract: reading migrations\r\n * from the table cannot fail because of a broken migration file, and `--list`\r\n * is the command an operator reaches for *during* an incident. The pending\r\n * section is best-effort on top of an answer that is already on screen.\r\n *\r\n * Always exits 0. It is a report; `--pending` is the gate.\r\n */\r\nasync function listMigrationsAction() {\r\n await printExecutedMigrations();\r\n\r\n const pending = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(pending);\r\n}\r\n\r\nasync function printExecutedMigrations() {\r\n const createdMigrations = await listExecutedMigrations();\r\n\r\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\r\n\r\n if (createdMigrations.length === 0) {\r\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\r\n return;\r\n }\r\n\r\n // Display each migration as a block\r\n for (const migration of createdMigrations) {\r\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\r\n const createdAt = migration.createdAt\r\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\r\n : null;\r\n\r\n // Migration name with checkmark icon\r\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\r\n\r\n // Executed date\r\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\r\n\r\n // Created date (if available)\r\n if (createdAt) {\r\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\r\n }\r\n\r\n console.log(\"\"); // Empty line between migrations\r\n }\r\n}\r\n\r\n/**\r\n * Print the pending section shared by `--list` and `--pending`.\r\n *\r\n * An unavailable result prints an explicit line and NEVER a count. \"0 pending\"\r\n * over a tree we failed to read is the one output this whole feature exists to\r\n * prevent.\r\n */\r\nfunction printPendingMigrations(result: PendingMigrationsResult) {\r\n if (result.type === \"unavailable\") {\r\n console.log(\r\n `${colors.yellowBright(\"Pending: unavailable\")} — ${result.reason}\\n` +\r\n colors.gray(\" The executed list above is still accurate.\\n\"),\r\n );\r\n return;\r\n }\r\n\r\n const { migrations } = result;\r\n\r\n console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\\n`);\r\n\r\n if (migrations.length === 0) {\r\n console.log(colors.gray(\" Nothing pending — the database is up to date.\\n\"));\r\n return;\r\n }\r\n\r\n // Numbered because the ORDER is the answer: this is what the next\r\n // `warlock migrate` will do, in the sequence it will do it.\r\n migrations.forEach((migration, index) => {\r\n console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);\r\n });\r\n\r\n console.log(\"\");\r\n}\r\n\r\n/**\r\n * `--pending` — the scriptable half. Same computation as `--list`'s pending\r\n * section, different exit contract: see {@link PENDING_EXIT_CODE}.\r\n */\r\nasync function pendingMigrationsAction() {\r\n const result = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(result);\r\n\r\n const exitCode = exitCodeFor(result);\r\n\r\n if (exitCode === PENDING_EXIT_CODE.unavailable) {\r\n console.error(\r\n colors.redBright(\"Could not determine the pending set — exiting 2 rather than reporting 0.\"),\r\n );\r\n }\r\n\r\n process.exit(exitCode);\r\n}\r\n\r\nasync function allMigrationsFilesAction() {\r\n // get all available migration files in the project\r\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\r\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\r\n\r\n for (const file of files) {\r\n console.log(colors.yellowBright(file));\r\n }\r\n}\r\n\r\n/**\r\n * If path is provided, then run the migration runner against that file only\r\n * If fresh is provided, then rollback all migrations and run all migrations\r\n * If rollback is provided, then run the migration runner against all files in reverse order\r\n */\r\nexport async function migrateAction(options: CommandActionData) {\r\n const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;\r\n\r\n if (list) {\r\n return await listMigrationsAction();\r\n }\r\n\r\n if (pending) {\r\n return await pendingMigrationsAction();\r\n }\r\n\r\n if (all) {\r\n return await allMigrationsFilesAction();\r\n }\r\n\r\n if (path) {\r\n await loadMigrationFile(Path.toAbsolute(path as string));\r\n } else {\r\n await loadAllMigrations();\r\n }\r\n\r\n if (fresh && rollback) {\r\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\r\n process.exit(1);\r\n }\r\n\r\n if (rollback) {\r\n await rollbackMigrations({ all: true });\r\n return;\r\n }\r\n\r\n if (sql) {\r\n await exportMigrationsSQL({\r\n pendingOnly: pendingOnly as boolean,\r\n compact: compact as boolean,\r\n });\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await freshMigrate();\r\n return;\r\n }\r\n\r\n await runMigrations();\r\n}\r\n\r\nasync function loadMigrationFile(absPath: string) {\r\n const relativePath = Path.toRelative(absPath);\r\n\r\n // MigrationConstructor, not `typeof Migration`: a migration file default-exports a\r\n // CONCRETE subclass, while Migration itself is abstract and so cannot satisfy a\r\n // `new () => ...` parameter. The annotation described the base class rather than\r\n // what is actually loaded, and that is what migrationRunner.register() rejected.\r\n const loadedModule = await filesOrchestrator.load<{ default: MigrationConstructor }>(relativePath);\r\n\r\n if (!loadedModule?.default) {\r\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\r\n }\r\n\r\n const MigrationClass = loadedModule.default;\r\n\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = path\r\n .basename(absPath)\r\n .split(\".\")[0]\r\n .replace(\"-migration\", \"\")\r\n .replace(\"_migration\", \"\");\r\n }\r\n\r\n // Extract createdAt timestamp from filename if not already set\r\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\r\n if (!MigrationClass.createdAt) {\r\n const filename = path.basename(absPath);\r\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\r\n if (timestampMatch) {\r\n MigrationClass.createdAt = timestampMatch[1];\r\n }\r\n }\r\n\r\n migrationRunner.register(MigrationClass);\r\n}\r\n\r\n/**\r\n *\r\n * @returns List of absolute paths to migration files\r\n */\r\nasync function migrationFiles() {\r\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\r\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\r\n srcPath(\"app\"),\r\n \"*/migrations/*\",\r\n );\r\n\r\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles];\r\n\r\n return migrations;\r\n}\r\n\r\nasync function loadAllMigrations() {\r\n // Load config-registered migrations (from packages like @warlock.js/auth)\r\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\r\n\r\n for (const MigrationClass of configMigrations) {\r\n // Use class name as migration name if not set\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = MigrationClass.name;\r\n }\r\n migrationRunner.register(MigrationClass);\r\n }\r\n\r\n // Always load file-based migrations from src/app, regardless of config\r\n const migrations = await migrationFiles();\r\n for (const migrationFile of migrations) {\r\n await loadMigrationFile(migrationFile);\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"database-log.mjs","names":[],"sources":["../../../../../../../../../core/src/database/models/database-log/database-log.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { Infer, v } from \"@warlock.js/seal\";\r\n\r\nconst schema = v.object({\r\n module: v.string(),\r\n action: v.string(),\r\n message: v.string(),\r\n trace: v.record(v.any()),\r\n level: v.string(),\r\n});\r\n\r\ntype LogSchema = Infer.Output<typeof schema>;\r\n\r\nexport class DatabaseLogModel extends Model<LogSchema> {\r\n /**\r\n * Table name\r\n */\r\n public static table = \"logs\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public static schema = schema;\r\n}\r\n"],"mappings":";;;;AAGA,MAAM,SAAS,EAAE,OAAO;CACtB,QAAQ,EAAE,OAAO;CACjB,QAAQ,EAAE,OAAO;CACjB,SAAS,EAAE,OAAO;CAClB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CACvB,OAAO,EAAE,OAAO;AAClB,CAAC;AAID,IAAa,mBAAb,cAAsC,MAAiB;;eAI/B;;;gBAKC;;AACzB"}
1
+ {"version":3,"file":"database-log.mjs","names":[],"sources":["../../../../../../../../../core/src/database/models/database-log/database-log.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\nconst schema = v.object({\r\n module: v.string(),\r\n action: v.string(),\r\n message: v.string(),\r\n trace: v.record(v.any()),\r\n level: v.string(),\r\n});\r\n\r\ntype LogSchema = Infer.Output<typeof schema>;\r\n\r\nexport class DatabaseLogModel extends Model<LogSchema> {\r\n /**\r\n * Table name\r\n */\r\n public static table = \"logs\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public static schema = schema;\r\n}\r\n"],"mappings":";;;;AAGA,MAAM,SAAS,EAAE,OAAO;CACtB,QAAQ,EAAE,OAAO;CACjB,QAAQ,EAAE,OAAO;CACjB,SAAS,EAAE,OAAO;CAClB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CACvB,OAAO,EAAE,OAAO;AAClB,CAAC;AAID,IAAa,mBAAb,cAAsC,MAAiB;;eAI/B;;;gBAKC;;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"seed-command-action.mjs","names":["runInTransaction"],"sources":["../../../../../../../core/src/database/seed-command-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n DataSource,\r\n dataSourceRegistry,\r\n transaction as runInTransaction,\r\n} from \"@warlock.js/cascade\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { Seeder } from \"./seeds/seeder\";\r\nimport { SeedersManager } from \"./seeds/seeders.manager\";\r\nimport { SeedClock, SeedRecordRef } from \"./seeds/types\";\r\nimport { seedRecordsTableName, seedsTableName } from \"./seeds/utils\";\r\n\r\n/**\r\n * Programmatic-only overrides for {@link seedCommandAction}.\r\n *\r\n * Not derived from CLI flags (the CLI passes only {@link CommandActionData}),\r\n * so a back-fill script or a test can inject behaviour the command line can't\r\n * express.\r\n */\r\nexport type SeedCommandOverrides = {\r\n /**\r\n * Clock the run reads for the `now` handed to each seeder and for the\r\n * seeds-log metadata timestamps. Defaults to `() => new Date()`. Forwarded to\r\n * {@link SeedersManager}'s `clock` option.\r\n */\r\n clock?: SeedClock;\r\n};\r\n\r\nasync function clearAllTables(datasource: DataSource) {\r\n const tables = await datasource.driver.blueprint.listTables();\r\n\r\n for (const table of tables) {\r\n await datasource.driver.truncateTable(table, { cascade: true });\r\n }\r\n}\r\n\r\ntype SeedRecordRow = SeedRecordRef & { id: number };\r\n\r\n/**\r\n * Undo seeded data by deleting the records a seeder tracked via `track`.\r\n *\r\n * Reads `seed_records` (all seeders, or a single `seederName`), then deletes\r\n * the referenced records in REVERSE seed-order and, within each seed, in\r\n * reverse insertion-order. Insertion id is strictly increasing across the\r\n * whole run, so deleting by descending `id` satisfies both at once. The whole\r\n * operation runs in a single transaction, and the matching seeds-log rows are\r\n * reset afterwards so `once: true` seeds re-run on the next `warlock seed`.\r\n *\r\n * @returns the number of tracked records deleted.\r\n */\r\nexport async function dropSeedRecords(\r\n datasource: DataSource,\r\n seederName?: string,\r\n): Promise<number> {\r\n const driver = datasource.driver;\r\n\r\n if (!(await driver.blueprint.tableExists(seedRecordsTableName))) {\r\n return 0;\r\n }\r\n\r\n const query = driver.queryBuilder<SeedRecordRow>(seedRecordsTableName);\r\n\r\n if (seederName) {\r\n query.where(\"seeder\", seederName);\r\n }\r\n\r\n const rows = await query.get();\r\n\r\n if (rows.length === 0) {\r\n return 0;\r\n }\r\n\r\n // Descending insertion id == reverse seed-order + reverse within-seed order.\r\n const ordered = [...rows].sort((a, b) => b.id - a.id);\r\n\r\n await runInTransaction(async () => {\r\n for (const row of ordered) {\r\n await driver.deleteMany(row.table, { id: row.recordId });\r\n }\r\n\r\n // Clear the tracked refs we just acted on.\r\n if (seederName) {\r\n await driver.deleteMany(seedRecordsTableName, { seeder: seederName });\r\n } else {\r\n await driver.deleteMany(seedRecordsTableName, {});\r\n }\r\n\r\n // Reset the seeds-log rows so `once: true` seeds re-run.\r\n const seederNames = seederName\r\n ? [seederName]\r\n : [...new Set(ordered.map((row) => row.seeder))];\r\n\r\n for (const name of seederNames) {\r\n await driver.deleteMany(seedsTableName, { name });\r\n }\r\n });\r\n\r\n return ordered.length;\r\n}\r\n\r\n/**\r\n * Run database seeds.\r\n *\r\n * @example\r\n * ```ts\r\n * import { seedCommandAction } from \"@warlock.js/core\";\r\n *\r\n * await seedCommandAction({\r\n * command: \"seed\",\r\n * options: { fresh: true, list: false }\r\n * });\r\n * ```\r\n *\r\n * @param overrides Programmatic-only overrides (e.g. an injected `clock`); the\r\n * CLI never passes this, so the command-line signature stays unchanged.\r\n */\r\nexport async function seedCommandAction(\r\n options: CommandActionData,\r\n overrides?: SeedCommandOverrides,\r\n) {\r\n const { path, fresh, transaction, list, drop } = options.options;\r\n\r\n const datasource = dataSourceRegistry.get();\r\n\r\n if (drop) {\r\n // `--drop` (boolean) undoes every tracked record; `--drop=<name>` scopes to\r\n // a single seeder.\r\n const seederName = typeof drop === \"string\" ? drop : undefined;\r\n\r\n const deleted = await dropSeedRecords(datasource, seederName);\r\n\r\n const scope = seederName ? colors.cyan(seederName) : \"all seeders\";\r\n console.log(\r\n `🗑️ Dropped ${colors.redBright(deleted)} tracked record(s) for ${scope}; matching seeds-log entries reset.`,\r\n );\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await clearAllTables(datasource);\r\n }\r\n\r\n if (list) {\r\n const seedFiles = await listSeedsFiles();\r\n\r\n if (seedFiles.length === 0) {\r\n console.log(\"No seeds found.\");\r\n return;\r\n }\r\n\r\n const seedersManager = new SeedersManager();\r\n\r\n seedersManager.register(...seedFiles);\r\n\r\n const seeds = seedersManager.sort().seeders.map((seed) => {\r\n return {\r\n name: seed.name,\r\n order: seed.order,\r\n enabled: seed.enabled,\r\n };\r\n });\r\n\r\n console.table(seeds);\r\n\r\n console.log(\r\n `Total Seeds: ${colors.blueBright(seeds.length)}, enabled: ${colors.greenBright(seeds.filter((seed) => seed.enabled !== false).length)}, disabled: ${colors.redBright(seeds.filter((seed) => seed.enabled === false).length)}`,\r\n );\r\n return;\r\n }\r\n\r\n const seeds = path\r\n ? [await loadSeedFile(Path.toAbsolute(path as string))]\r\n : await listSeedsFiles();\r\n\r\n const seedersManager = new SeedersManager({ clock: overrides?.clock });\r\n\r\n seedersManager.register(...seeds);\r\n\r\n await seedersManager.run(transaction as boolean);\r\n}\r\n\r\nasync function listSeedsFiles() {\r\n const seedsFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/seeds/*.ts\");\r\n\r\n const seeds = [];\r\n\r\n for (const seedFile of seedsFiles) {\r\n const seed = await loadSeedFile(seedFile);\r\n seeds.push(seed);\r\n }\r\n\r\n return seeds;\r\n}\r\n\r\nasync function loadSeedFile(absPath: string): Promise<Seeder> {\r\n const relativePath = Path.toRelative(absPath);\r\n const seedImport = await filesOrchestrator.load<{ default: Seeder }>(relativePath);\r\n\r\n if (!seedImport || !seedImport.default) {\r\n throw new Error(`Seeder file ${relativePath} does not export a default seeder.`);\r\n }\r\n\r\n return seedImport.default;\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"seed-command-action.mjs","names":["runInTransaction"],"sources":["../../../../../../../core/src/database/seed-command-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n DataSource,\r\n dataSourceRegistry,\r\n transaction as runInTransaction,\r\n} from \"@warlock.js/cascade\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { Seeder } from \"./seeds/seeder\";\r\nimport { SeedersManager } from \"./seeds/seeders.manager\";\r\nimport { SeedClock, SeedRecordRef } from \"./seeds/types\";\r\nimport { seedRecordsTableName, seedsTableName } from \"./seeds/utils\";\r\n\r\n/**\r\n * Programmatic-only overrides for {@link seedCommandAction}.\r\n *\r\n * Not derived from CLI flags (the CLI passes only {@link CommandActionData}),\r\n * so a back-fill script or a test can inject behaviour the command line can't\r\n * express.\r\n */\r\nexport type SeedCommandOverrides = {\r\n /**\r\n * Clock the run reads for the `now` handed to each seeder and for the\r\n * seeds-log metadata timestamps. Defaults to `() => new Date()`. Forwarded to\r\n * {@link SeedersManager}'s `clock` option.\r\n */\r\n clock?: SeedClock;\r\n};\r\n\r\nasync function clearAllTables(datasource: DataSource) {\r\n const tables = await datasource.driver.blueprint.listTables();\r\n\r\n for (const table of tables) {\r\n await datasource.driver.truncateTable(table, { cascade: true });\r\n }\r\n}\r\n\r\ntype SeedRecordRow = SeedRecordRef & { id: number };\r\n\r\n/**\r\n * Undo seeded data by deleting the records a seeder tracked via `track`.\r\n *\r\n * Reads `seed_records` (all seeders, or a single `seederName`), then deletes\r\n * the referenced records in REVERSE seed-order and, within each seed, in\r\n * reverse insertion-order. Insertion id is strictly increasing across the\r\n * whole run, so deleting by descending `id` satisfies both at once. The whole\r\n * operation runs in a single transaction, and the matching seeds-log rows are\r\n * reset afterwards so `once: true` seeds re-run on the next `warlock seed`.\r\n *\r\n * @returns the number of tracked records deleted.\r\n */\r\nexport async function dropSeedRecords(\r\n datasource: DataSource,\r\n seederName?: string,\r\n): Promise<number> {\r\n const driver = datasource.driver;\r\n\r\n if (!(await driver.blueprint.tableExists(seedRecordsTableName))) {\r\n return 0;\r\n }\r\n\r\n const query = driver.queryBuilder<SeedRecordRow>(seedRecordsTableName);\r\n\r\n if (seederName) {\r\n query.where(\"seeder\", seederName);\r\n }\r\n\r\n const rows = await query.get();\r\n\r\n if (rows.length === 0) {\r\n return 0;\r\n }\r\n\r\n // Descending insertion id == reverse seed-order + reverse within-seed order.\r\n const ordered = [...rows].sort((a, b) => b.id - a.id);\r\n\r\n await runInTransaction(async () => {\r\n for (const row of ordered) {\r\n await driver.deleteMany(row.table, { id: row.recordId });\r\n }\r\n\r\n // Clear the tracked refs we just acted on.\r\n if (seederName) {\r\n await driver.deleteMany(seedRecordsTableName, { seeder: seederName });\r\n } else {\r\n await driver.deleteMany(seedRecordsTableName, {});\r\n }\r\n\r\n // Reset the seeds-log rows so `once: true` seeds re-run.\r\n const seederNames = seederName\r\n ? [seederName]\r\n : [...new Set(ordered.map((row) => row.seeder))];\r\n\r\n for (const name of seederNames) {\r\n await driver.deleteMany(seedsTableName, { name });\r\n }\r\n });\r\n\r\n return ordered.length;\r\n}\r\n\r\n/**\r\n * Run database seeds.\r\n *\r\n * @example\r\n * ```ts\r\n * import { seedCommandAction } from \"@warlock.js/core\";\r\n *\r\n * await seedCommandAction({\r\n * command: \"seed\",\r\n * options: { fresh: true, list: false }\r\n * });\r\n * ```\r\n *\r\n * @param overrides Programmatic-only overrides (e.g. an injected `clock`); the\r\n * CLI never passes this, so the command-line signature stays unchanged.\r\n */\r\nexport async function seedCommandAction(\r\n options: CommandActionData,\r\n overrides?: SeedCommandOverrides,\r\n) {\r\n const { path, fresh, transaction, list, drop } = options.options;\r\n\r\n const datasource = dataSourceRegistry.get();\r\n\r\n if (drop) {\r\n // `--drop` (boolean) undoes every tracked record; `--drop=<name>` scopes to\r\n // a single seeder.\r\n const seederName = typeof drop === \"string\" ? drop : undefined;\r\n\r\n const deleted = await dropSeedRecords(datasource, seederName);\r\n\r\n const scope = seederName ? colors.cyan(seederName) : \"all seeders\";\r\n console.log(\r\n `🗑️ Dropped ${colors.redBright(deleted)} tracked record(s) for ${scope}; matching seeds-log entries reset.`,\r\n );\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await clearAllTables(datasource);\r\n }\r\n\r\n if (list) {\r\n const seedFiles = await listSeedsFiles();\r\n\r\n if (seedFiles.length === 0) {\r\n console.log(\"No seeds found.\");\r\n return;\r\n }\r\n\r\n const seedersManager = new SeedersManager();\r\n\r\n seedersManager.register(...seedFiles);\r\n\r\n const seeds = seedersManager.sort().seeders.map((seed) => {\r\n return {\r\n name: seed.name,\r\n order: seed.order,\r\n enabled: seed.enabled,\r\n };\r\n });\r\n\r\n console.table(seeds);\r\n\r\n console.log(\r\n `Total Seeds: ${colors.blueBright(seeds.length)}, enabled: ${colors.greenBright(seeds.filter((seed) => seed.enabled !== false).length)}, disabled: ${colors.redBright(seeds.filter((seed) => seed.enabled === false).length)}`,\r\n );\r\n return;\r\n }\r\n\r\n const seeds = path\r\n ? [await loadSeedFile(Path.toAbsolute(path as string))]\r\n : await listSeedsFiles();\r\n\r\n const seedersManager = new SeedersManager({ clock: overrides?.clock });\r\n\r\n seedersManager.register(...seeds);\r\n\r\n await seedersManager.run(transaction as boolean);\r\n}\r\n\r\nasync function listSeedsFiles() {\r\n const seedsFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/seeds/*.ts\");\r\n\r\n const seeds = [];\r\n\r\n for (const seedFile of seedsFiles) {\r\n const seed = await loadSeedFile(seedFile);\r\n seeds.push(seed);\r\n }\r\n\r\n return seeds;\r\n}\r\n\r\nasync function loadSeedFile(absPath: string): Promise<Seeder> {\r\n const relativePath = Path.toRelative(absPath);\r\n const seedImport = await filesOrchestrator.load<{ default: Seeder }>(relativePath);\r\n\r\n if (!seedImport || !seedImport.default) {\r\n throw new Error(`Seeder file ${relativePath} does not export a default seeder.`);\r\n }\r\n\r\n return seedImport.default;\r\n}\r\n"],"mappings":"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"}
@@ -27,7 +27,7 @@ var SeedRecordsTableMigration = class extends Migration {
27
27
  this.dateTime("runAt").useCurrent();
28
28
  }
29
29
  down() {
30
- this.dropTableIfExists(seedRecordsTableName);
30
+ this.dropTableIfExists();
31
31
  }
32
32
  };
33
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"seed-records-table-migration.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seed-records-table-migration.ts"],"sourcesContent":["import { Migration } from \"@warlock.js/cascade\";\nimport { seedRecordsTableName } from \"./utils\";\n\n/**\n * Creates the `seed_records` table that backs `warlock seed --drop`.\n *\n * Each row is a reference to a single record a seeder created, captured via the\n * `track` helper inside the same transaction the seed ran in. `--drop` reads\n * these refs to delete exactly (and only) the records a seed produced, then\n * resets the matching seeds-log row so a `once: true` seed can re-run.\n */\nexport class SeedRecordsTableMigration extends Migration {\n public static migrationName = \"seed-records-table-migration\";\n public table = seedRecordsTableName;\n\n public up() {\n this.createTableIfNotExists();\n\n this.id();\n this.text(\"seeder\");\n this.text(\"table\");\n this.text(\"recordId\");\n this.dateTime(\"runAt\").useCurrent();\n }\n\n public down() {\n this.dropTableIfExists(seedRecordsTableName);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,4BAAb,cAA+C,UAAU;;;eAExC;;;uBADe;;CAG9B,AAAO,KAAK;EACV,KAAK,uBAAuB;EAE5B,KAAK,GAAG;EACR,KAAK,KAAK,QAAQ;EAClB,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,UAAU;EACpB,KAAK,SAAS,OAAO,CAAC,CAAC,WAAW;CACpC;CAEA,AAAO,OAAO;EACZ,KAAK,kBAAkB,oBAAoB;CAC7C;AACF"}
1
+ {"version":3,"file":"seed-records-table-migration.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seed-records-table-migration.ts"],"sourcesContent":["import { Migration } from \"@warlock.js/cascade\";\nimport { seedRecordsTableName } from \"./utils\";\n\n/**\n * Creates the `seed_records` table that backs `warlock seed --drop`.\n *\n * Each row is a reference to a single record a seeder created, captured via the\n * `track` helper inside the same transaction the seed ran in. `--drop` reads\n * these refs to delete exactly (and only) the records a seed produced, then\n * resets the matching seeds-log row so a `once: true` seed can re-run.\n */\nexport class SeedRecordsTableMigration extends Migration {\n public static migrationName = \"seed-records-table-migration\";\n public table = seedRecordsTableName;\n\n public up() {\n this.createTableIfNotExists();\n\n this.id();\n this.text(\"seeder\");\n this.text(\"table\");\n this.text(\"recordId\");\n this.dateTime(\"runAt\").useCurrent();\n }\n\n public down() {\n // No argument: dropTableIfExists() operates on `this.table`, set as a class\n // field above. The name passed here was silently ignored at runtime.\n this.dropTableIfExists();\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAa,4BAAb,cAA+C,UAAU;;;eAExC;;;uBADe;;CAG9B,AAAO,KAAK;EACV,KAAK,uBAAuB;EAE5B,KAAK,GAAG;EACR,KAAK,KAAK,QAAQ;EAClB,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,UAAU;EACpB,KAAK,SAAS,OAAO,EAAE,WAAW;CACpC;CAEA,AAAO,OAAO;EAGZ,KAAK,kBAAkB;CACzB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"seeder.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeder.ts"],"sourcesContent":["import { SeedContext, SeedResult } from \"./types\";\n\nexport type Seeder = {\n /**\n * Seed name\n */\n name: string;\n /**\n * Whether to enable it\n */\n enabled?: boolean;\n /**\n * Seed description\n */\n description?: string;\n /**\n * List of dependent seeds to run first\n */\n dependsOn?: string[];\n /**\n * Whether to run it once\n */\n once?: boolean;\n /**\n * Seed execution order\n */\n order?: number;\n /**\n * Batch size.\n *\n * Surfaced to `run()` as {@link SeedContext.batchSize} (defaulting to this\n * value, or `0` when unset) so a seed can forward it to\n * `Model.createMany(rows, { batchSize })`.\n */\n batchSize?: number;\n /**\n * Run seed.\n *\n * Receives a {@link SeedContext} exposing:\n * - `track` — registers the records this seed creates so they can be undone\n * by `warlock seed --drop`. Auto-derives `recordsCreated` from the track\n * count; an explicitly returned {@link SeedResult} is an optional fallback.\n * - `now` — the injectable clock to read instead of an ambient `new Date()` /\n * `dayjs()`, so historical seeds and the seeds-log share one overridable\n * clock.\n * - `batchSize` — the suggested bulk-insert batch size (from this seeder's\n * `batchSize`, default `0`).\n *\n * The `ctx` parameter is optional at the call site — an existing zero-arg\n * `run()` keeps working unchanged.\n */\n run(ctx: SeedContext): Promise<SeedResult | void>;\n};\n\n/**\n * Create a new seeder\n */\nexport function seeder(seeder: Seeder) {\n return seeder;\n}\n"],"mappings":";;;;AAyDA,SAAgB,OAAO,QAAgB;CACrC,OAAO;AACT"}
1
+ {"version":3,"file":"seeder.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeder.ts"],"sourcesContent":["import { type SeedContext, type SeedResult } from \"./types\";\n\nexport type Seeder = {\n /**\n * Seed name\n */\n name: string;\n /**\n * Whether to enable it\n */\n enabled?: boolean;\n /**\n * Seed description\n */\n description?: string;\n /**\n * List of dependent seeds to run first\n */\n dependsOn?: string[];\n /**\n * Whether to run it once\n */\n once?: boolean;\n /**\n * Seed execution order\n */\n order?: number;\n /**\n * Batch size.\n *\n * Surfaced to `run()` as {@link SeedContext.batchSize} (defaulting to this\n * value, or `0` when unset) so a seed can forward it to\n * `Model.createMany(rows, { batchSize })`.\n */\n batchSize?: number;\n /**\n * Run seed.\n *\n * Receives a {@link SeedContext} exposing:\n * - `track` — registers the records this seed creates so they can be undone\n * by `warlock seed --drop`. Auto-derives `recordsCreated` from the track\n * count; an explicitly returned {@link SeedResult} is an optional fallback.\n * - `now` — the injectable clock to read instead of an ambient `new Date()` /\n * `dayjs()`, so historical seeds and the seeds-log share one overridable\n * clock.\n * - `batchSize` — the suggested bulk-insert batch size (from this seeder's\n * `batchSize`, default `0`).\n *\n * The `ctx` parameter is optional at the call site — an existing zero-arg\n * `run()` keeps working unchanged.\n */\n run(ctx: SeedContext): Promise<SeedResult | void>;\n};\n\n/**\n * Create a new seeder\n */\nexport function seeder(seeder: Seeder) {\n return seeder;\n}\n"],"mappings":";;;;AAyDA,SAAgB,OAAO,QAAgB;CACrC,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"seeders.manager.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeders.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n type DataSource,\n type DriverContract,\n DatabaseWriterValidationError,\n dataSourceRegistry,\n migrationRunner,\n transaction,\n} from \"@warlock.js/cascade\";\nimport { Seeder } from \"./seeder\";\nimport { SeederDependencyCycleError, UnknownSeederDependencyError } from \"./seeder.errors\";\nimport { SeedRecordsTableMigration } from \"./seed-records-table-migration\";\nimport { SeedsTableMigration } from \"./seeds-table-migration\";\nimport {\n SeedClock,\n SeederMetadata,\n SeedRecordRef,\n SeedResult,\n Track,\n TrackableModel,\n} from \"./types\";\nimport { seedRecordsTableName, seedsTableName } from \"./utils\";\n\nexport type SeedersManagerOptions = {\n datasource?: DataSource;\n /**\n * Clock the manager reads for the `now` it hands each seeder AND for its own\n * metadata timestamps (`createdAt` / `firstRunAt` / `lastRunAt` / a tracked\n * ref's `runAt`). Defaults to `() => new Date()`. Override it to make a\n * historical back-fill or a test deterministic — every timestamp a run\n * produces then comes from this one clock.\n */\n clock?: SeedClock;\n};\n\nexport class SeedersManager {\n public seeders: Seeder[] = [];\n\n protected datasource?: DataSource;\n\n /**\n * The single clock every timestamp in a run reads from. Defaults to\n * `() => new Date()`; overridable via {@link SeedersManagerOptions.clock}.\n */\n protected readonly now: SeedClock;\n\n public constructor(protected options?: SeedersManagerOptions) {\n this.datasource = options?.datasource ?? dataSourceRegistry.get();\n this.now = options?.clock ?? (() => new Date());\n }\n\n /**\n * Register new seeder\n */\n public register(...seeders: Seeder[]) {\n this.seeders.push(...seeders);\n return this;\n }\n\n /**\n * Initialize before running\n */\n protected async init() {\n if (!(await this.driver.blueprint.tableExists(seedsTableName))) {\n await migrationRunner.run(SeedsTableMigration);\n }\n\n if (!(await this.driver.blueprint.tableExists(seedRecordsTableName))) {\n await migrationRunner.run(SeedRecordsTableMigration);\n }\n }\n\n /**\n * Run seeders\n */\n public async run(withTransaction = true) {\n await this.init();\n\n this.prepareSeeders();\n\n console.log(`🌱 Running ${this.seeders.length} seeder(s)...\\n`);\n\n let successCount = 0;\n let skippedCount = 0;\n let failedCount = 0;\n\n for (const seeder of this.seeders) {\n // Check if already executed\n if (seeder.once && (await this.seederIsExecutedBefore(seeder))) {\n console.log(`⏭️ Skipping ${colors.yellow(seeder.name)} (already executed)`);\n skippedCount++;\n continue;\n }\n\n try {\n console.log(`🔄 Running ${colors.green(seeder.name)}...`);\n const startTime = Date.now();\n\n const refs: SeedRecordRef[] = [];\n const track = this.createTracker(seeder, refs);\n\n const runSeeder = async () => {\n // Keep LAST-RUN refs only: drop the prior refs for this seeder before\n // writing the new ones, so `--drop` undo stays bounded.\n await this.driver.deleteMany(seedRecordsTableName, { seeder: seeder.name });\n\n const result = await seeder.run({\n track,\n now: this.now,\n batchSize: seeder.batchSize ?? 0,\n });\n\n if (refs.length > 0) {\n await this.driver.insertMany(\n seedRecordsTableName,\n refs.map((ref) => ({ ...ref })),\n );\n }\n\n return result;\n };\n\n const result = withTransaction ? await transaction(runSeeder) : await runSeeder();\n\n const duration = Date.now() - startTime;\n\n // Auto-derive recordsCreated from the track count; a returned\n // SeedResult is an optional fallback when nothing was tracked.\n const recordsCreated = refs.length > 0 ? refs.length : (result?.recordsCreated ?? 0);\n\n await this.storeSeedsResults(seeder, { recordsCreated });\n\n console.log(\n `✅ ${colors.green(seeder.name)} completed (${duration}ms, ${recordsCreated} records)\\n`,\n );\n successCount++;\n } catch (error: any) {\n const err = error as Error;\n console.error(`❌ ${colors.red(seeder.name)} failed:`, err.message);\n console.log(err);\n failedCount++;\n\n if (error instanceof DatabaseWriterValidationError) {\n console.log(error.errors);\n }\n\n // Re-throw to stop execution (or continue to next seed based on your preference)\n throw error;\n }\n }\n\n // Summary\n console.log(\"\\n\" + \"=\".repeat(50));\n console.log(`✅ Success: ${successCount}`);\n\n if (skippedCount > 0) console.log(`⏭️ Skipped: ${skippedCount}`);\n\n if (failedCount > 0) console.log(`❌ Failed: ${failedCount}`);\n\n console.log(\"=\".repeat(50));\n }\n\n /**\n * Build a `track` helper bound to the given seeder. Tracked refs are pushed\n * into `refs` and persisted (within the run transaction) by the caller.\n *\n * Every overload returns its first argument so it can be chained inline.\n */\n protected createTracker(seeder: Seeder, refs: SeedRecordRef[]): Track {\n const push = (table: string, recordId: number | string) => {\n refs.push({ seeder: seeder.name, table, recordId, runAt: this.now() });\n };\n\n function track<T extends TrackableModel>(model: T): T;\n function track<T extends TrackableModel>(models: T[]): T[];\n function track(table: string, id: number | string): string;\n function track(\n arg: TrackableModel | TrackableModel[] | string,\n id?: number | string,\n ): TrackableModel | TrackableModel[] | string {\n if (typeof arg === \"string\") {\n push(arg, id as number | string);\n\n return arg;\n }\n\n if (Array.isArray(arg)) {\n for (const model of arg) {\n push(model.getTableName(), model.id);\n }\n\n return arg;\n }\n\n push(arg.getTableName(), arg.id);\n\n return arg;\n }\n\n return track;\n }\n\n /**\n * Sort seeds\n */\n public sort() {\n this.seeders = this.seeders.sort((a, b) => {\n const orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n const orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n\n return this;\n }\n\n /**\n * Prepare seeders to order by the seeder order, then resolve `dependsOn`.\n *\n * Dependencies are honored via a topological sort layered over the existing\n * numeric `order` tie-break: among seeders whose dependencies are already\n * satisfied, the lowest `order` (then registration order) runs first. Throws\n * {@link UnknownSeederDependencyError} for a missing dependency and\n * {@link SeederDependencyCycleError} for a cycle.\n */\n public prepareSeeders() {\n this.seeders = this.seeders.filter((seeder) => seeder.enabled !== false);\n\n this.sort();\n\n this.seeders = this.resolveDependencies(this.seeders);\n\n return this;\n }\n\n /**\n * Topologically sort `seeders` by their `dependsOn` names. The input is\n * assumed pre-sorted by `order` (see {@link sort}); that ordering is used as\n * the tie-break so dependency-free siblings keep their `order` sequence.\n */\n protected resolveDependencies(seeders: Seeder[]): Seeder[] {\n const byName = new Map<string, Seeder>();\n\n for (const seeder of seeders) {\n byName.set(seeder.name, seeder);\n }\n\n // Validate dependencies up-front so an unknown name is a clear error rather\n // than a silently dropped edge.\n for (const seeder of seeders) {\n for (const dependency of seeder.dependsOn ?? []) {\n if (!byName.has(dependency)) {\n throw new UnknownSeederDependencyError(seeder.name, dependency);\n }\n }\n }\n\n const sorted: Seeder[] = [];\n const state = new Map<string, \"visiting\" | \"visited\">();\n\n const visit = (seeder: Seeder, path: string[]) => {\n const status = state.get(seeder.name);\n\n if (status === \"visited\") {\n return;\n }\n\n if (status === \"visiting\") {\n const cycleStart = path.indexOf(seeder.name);\n const cycle = path.slice(cycleStart >= 0 ? cycleStart : 0).concat(seeder.name);\n\n throw new SeederDependencyCycleError(cycle);\n }\n\n state.set(seeder.name, \"visiting\");\n\n for (const dependency of seeder.dependsOn ?? []) {\n // Presence already validated above; the `!` is safe.\n visit(byName.get(dependency)!, [...path, seeder.name]);\n }\n\n state.set(seeder.name, \"visited\");\n sorted.push(seeder);\n };\n\n // Iterate in the incoming (order-sorted, registration-stable) sequence so\n // dependency-free seeders preserve their `order` tie-break.\n for (const seeder of seeders) {\n visit(seeder, []);\n }\n\n return sorted;\n }\n\n /**\n * Store seed results in db\n */\n public async storeSeedsResults(seeder: Seeder, result: SeedResult) {\n const oldResult = await this.getMetadata(seeder);\n\n // log with an emoji icon at beginning of text no of total craeted records\n console.log(`📊 Total records created: ${result.recordsCreated}`);\n\n // Read the single overridable clock once so every timestamp in this write\n // is identical (and pinned in a deterministic / historical run).\n const now = this.now();\n\n if (oldResult) {\n // Update existing record - use query builder with WHERE clause\n await this.driver\n .queryBuilder(seedsTableName)\n .where(\"name\", seeder.name)\n .update({\n runCount: oldResult.runCount + 1,\n lastRunAt: now,\n totalRecordsCreated: oldResult.totalRecordsCreated + result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n\n // now display total created records so far\n console.log(\n `📊 Total records created so far: ${oldResult.totalRecordsCreated + result.recordsCreated}`,\n );\n } else {\n // Insert new record\n await this.driver.insert(seedsTableName, {\n name: seeder.name,\n createdAt: now,\n firstRunAt: now,\n lastRunAt: now,\n runCount: 1,\n totalRecordsCreated: result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n }\n }\n\n /**\n * Get seed info from database\n */\n protected getMetadata(seeder: Seeder): Promise<SeederMetadata> {\n const driver = this.datasource.driver;\n\n return driver.queryBuilder(seedsTableName).where(\"name\", seeder.name).first();\n }\n\n /**\n * Get driver instance\n */\n protected get driver(): DriverContract {\n return this.datasource.driver;\n }\n\n /**\n * Check if seeder has been executed before\n */\n protected async seederIsExecutedBefore(seeder: Seeder) {\n return !!(await this.getMetadata(seeder));\n }\n}\n"],"mappings":";;;;;;;;AAmCA,IAAa,iBAAb,MAA4B;CAW1B,AAAO,YAAY,AAAU,SAAiC;EAAjC;iBAVF,CAAC;EAW1B,KAAK,aAAa,SAAS,cAAc,mBAAmB,IAAI;EAChE,KAAK,MAAM,SAAS,gCAAgB,IAAI,KAAK;CAC/C;;;;CAKA,AAAO,SAAS,GAAG,SAAmB;EACpC,KAAK,QAAQ,KAAK,GAAG,OAAO;EAC5B,OAAO;CACT;;;;CAKA,MAAgB,OAAO;EACrB,IAAI,CAAE,MAAM,KAAK,OAAO,UAAU,oBAA0B,GAC1D,MAAM,gBAAgB,IAAI,mBAAmB;EAG/C,IAAI,CAAE,MAAM,KAAK,OAAO,UAAU,2BAAgC,GAChE,MAAM,gBAAgB,IAAI,yBAAyB;CAEvD;;;;CAKA,MAAa,IAAI,kBAAkB,MAAM;EACvC,MAAM,KAAK,KAAK;EAEhB,KAAK,eAAe;EAEpB,QAAQ,IAAI,cAAc,KAAK,QAAQ,OAAO,gBAAgB;EAE9D,IAAI,eAAe;EACnB,IAAI,eAAe;EACnB,IAAI,cAAc;EAElB,KAAK,MAAM,UAAU,KAAK,SAAS;GAEjC,IAAI,OAAO,QAAS,MAAM,KAAK,uBAAuB,MAAM,GAAI;IAC9D,QAAQ,IAAI,gBAAgB,OAAO,OAAO,OAAO,IAAI,EAAE,oBAAoB;IAC3E;IACA;GACF;GAEA,IAAI;IACF,QAAQ,IAAI,cAAc,OAAO,MAAM,OAAO,IAAI,EAAE,IAAI;IACxD,MAAM,YAAY,KAAK,IAAI;IAE3B,MAAM,OAAwB,CAAC;IAC/B,MAAM,QAAQ,KAAK,cAAc,QAAQ,IAAI;IAE7C,MAAM,YAAY,YAAY;KAG5B,MAAM,KAAK,OAAO,WAAW,sBAAsB,EAAE,QAAQ,OAAO,KAAK,CAAC;KAE1E,MAAM,SAAS,MAAM,OAAO,IAAI;MAC9B;MACA,KAAK,KAAK;MACV,WAAW,OAAO,aAAa;KACjC,CAAC;KAED,IAAI,KAAK,SAAS,GAChB,MAAM,KAAK,OAAO,WAChB,sBACA,KAAK,KAAK,SAAS,EAAE,GAAG,IAAI,EAAE,CAChC;KAGF,OAAO;IACT;IAEA,MAAM,SAAS,kBAAkB,MAAM,YAAY,SAAS,IAAI,MAAM,UAAU;IAEhF,MAAM,WAAW,KAAK,IAAI,IAAI;IAI9B,MAAM,iBAAiB,KAAK,SAAS,IAAI,KAAK,SAAU,QAAQ,kBAAkB;IAElF,MAAM,KAAK,kBAAkB,QAAQ,EAAE,eAAe,CAAC;IAEvD,QAAQ,IACN,KAAK,OAAO,MAAM,OAAO,IAAI,EAAE,cAAc,SAAS,MAAM,eAAe,YAC7E;IACA;GACF,SAAS,OAAY;IACnB,MAAM,MAAM;IACZ,QAAQ,MAAM,KAAK,OAAO,IAAI,OAAO,IAAI,EAAE,WAAW,IAAI,OAAO;IACjE,QAAQ,IAAI,GAAG;IACf;IAEA,IAAI,iBAAiB,+BACnB,QAAQ,IAAI,MAAM,MAAM;IAI1B,MAAM;GACR;EACF;EAGA,QAAQ,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;EACjC,QAAQ,IAAI,cAAc,cAAc;EAExC,IAAI,eAAe,GAAG,QAAQ,IAAI,gBAAgB,cAAc;EAEhE,IAAI,cAAc,GAAG,QAAQ,IAAI,aAAa,aAAa;EAE3D,QAAQ,IAAI,IAAI,OAAO,EAAE,CAAC;CAC5B;;;;;;;CAQA,AAAU,cAAc,QAAgB,MAA8B;EACpE,MAAM,QAAQ,OAAe,aAA8B;GACzD,KAAK,KAAK;IAAE,QAAQ,OAAO;IAAM;IAAO;IAAU,OAAO,KAAK,IAAI;GAAE,CAAC;EACvE;EAKA,SAAS,MACP,KACA,IAC4C;GAC5C,IAAI,OAAO,QAAQ,UAAU;IAC3B,KAAK,KAAK,EAAqB;IAE/B,OAAO;GACT;GAEA,IAAI,MAAM,QAAQ,GAAG,GAAG;IACtB,KAAK,MAAM,SAAS,KAClB,KAAK,MAAM,aAAa,GAAG,MAAM,EAAE;IAGrC,OAAO;GACT;GAEA,KAAK,IAAI,aAAa,GAAG,IAAI,EAAE;GAE/B,OAAO;EACT;EAEA,OAAO;CACT;;;;CAKA,AAAO,OAAO;EACZ,KAAK,UAAU,KAAK,QAAQ,MAAM,GAAG,MAAM;GAGzC,QAFe,EAAE,SAAS,OAAO,qBAClB,EAAE,SAAS,OAAO;EAEnC,CAAC;EAED,OAAO;CACT;;;;;;;;;;CAWA,AAAO,iBAAiB;EACtB,KAAK,UAAU,KAAK,QAAQ,QAAQ,WAAW,OAAO,YAAY,KAAK;EAEvE,KAAK,KAAK;EAEV,KAAK,UAAU,KAAK,oBAAoB,KAAK,OAAO;EAEpD,OAAO;CACT;;;;;;CAOA,AAAU,oBAAoB,SAA6B;EACzD,MAAM,yBAAS,IAAI,IAAoB;EAEvC,KAAK,MAAM,UAAU,SACnB,OAAO,IAAI,OAAO,MAAM,MAAM;EAKhC,KAAK,MAAM,UAAU,SACnB,KAAK,MAAM,cAAc,OAAO,aAAa,CAAC,GAC5C,IAAI,CAAC,OAAO,IAAI,UAAU,GACxB,MAAM,IAAI,6BAA6B,OAAO,MAAM,UAAU;EAKpE,MAAM,SAAmB,CAAC;EAC1B,MAAM,wBAAQ,IAAI,IAAoC;EAEtD,MAAM,SAAS,QAAgB,SAAmB;GAChD,MAAM,SAAS,MAAM,IAAI,OAAO,IAAI;GAEpC,IAAI,WAAW,WACb;GAGF,IAAI,WAAW,YAAY;IACzB,MAAM,aAAa,KAAK,QAAQ,OAAO,IAAI;IAG3C,MAAM,IAAI,2BAFI,KAAK,MAAM,cAAc,IAAI,aAAa,CAAC,CAAC,CAAC,OAAO,OAAO,IAEhC,CAAC;GAC5C;GAEA,MAAM,IAAI,OAAO,MAAM,UAAU;GAEjC,KAAK,MAAM,cAAc,OAAO,aAAa,CAAC,GAE5C,MAAM,OAAO,IAAI,UAAU,GAAI,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC;GAGvD,MAAM,IAAI,OAAO,MAAM,SAAS;GAChC,OAAO,KAAK,MAAM;EACpB;EAIA,KAAK,MAAM,UAAU,SACnB,MAAM,QAAQ,CAAC,CAAC;EAGlB,OAAO;CACT;;;;CAKA,MAAa,kBAAkB,QAAgB,QAAoB;EACjE,MAAM,YAAY,MAAM,KAAK,YAAY,MAAM;EAG/C,QAAQ,IAAI,6BAA6B,OAAO,gBAAgB;EAIhE,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,WAAW;GAEb,MAAM,KAAK,OACR,aAAa,cAAc,CAAC,CAC5B,MAAM,QAAQ,OAAO,IAAI,CAAC,CAC1B,OAAO;IACN,UAAU,UAAU,WAAW;IAC/B,WAAW;IACX,qBAAqB,UAAU,sBAAsB,OAAO;IAC5D,uBAAuB,OAAO;GAChC,CAAC;GAGH,QAAQ,IACN,oCAAoC,UAAU,sBAAsB,OAAO,gBAC7E;EACF,OAEE,MAAM,KAAK,OAAO,OAAO,gBAAgB;GACvC,MAAM,OAAO;GACb,WAAW;GACX,YAAY;GACZ,WAAW;GACX,UAAU;GACV,qBAAqB,OAAO;GAC5B,uBAAuB,OAAO;EAChC,CAAC;CAEL;;;;CAKA,AAAU,YAAY,QAAyC;EAG7D,OAFe,KAAK,WAAW,OAEjB,aAAa,cAAc,CAAC,CAAC,MAAM,QAAQ,OAAO,IAAI,CAAC,CAAC,MAAM;CAC9E;;;;CAKA,IAAc,SAAyB;EACrC,OAAO,KAAK,WAAW;CACzB;;;;CAKA,MAAgB,uBAAuB,QAAgB;EACrD,OAAO,CAAC,CAAE,MAAM,KAAK,YAAY,MAAM;CACzC;AACF"}
1
+ {"version":3,"file":"seeders.manager.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeders.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n type DataSource,\n type DriverContract,\n DatabaseWriterValidationError,\n dataSourceRegistry,\n migrationRunner,\n transaction,\n} from \"@warlock.js/cascade\";\nimport { Seeder } from \"./seeder\";\nimport { SeederDependencyCycleError, UnknownSeederDependencyError } from \"./seeder.errors\";\nimport { SeedRecordsTableMigration } from \"./seed-records-table-migration\";\nimport { SeedsTableMigration } from \"./seeds-table-migration\";\nimport {\n SeedClock,\n SeederMetadata,\n SeedRecordRef,\n SeedResult,\n Track,\n TrackableModel,\n} from \"./types\";\nimport { seedRecordsTableName, seedsTableName } from \"./utils\";\n\nexport type SeedersManagerOptions = {\n datasource?: DataSource;\n /**\n * Clock the manager reads for the `now` it hands each seeder AND for its own\n * metadata timestamps (`createdAt` / `firstRunAt` / `lastRunAt` / a tracked\n * ref's `runAt`). Defaults to `() => new Date()`. Override it to make a\n * historical back-fill or a test deterministic — every timestamp a run\n * produces then comes from this one clock.\n */\n clock?: SeedClock;\n};\n\nexport class SeedersManager {\n public seeders: Seeder[] = [];\n\n // Not optional: the constructor assigns unconditionally, falling back to\n // dataSourceRegistry.get(), which itself returns a DataSource rather than\n // undefined. The `?` here described the OPTIONS field, not this one, and made\n // every read of `this.datasource.driver` a type error. The options field stays\n // optional, because that input genuinely is.\n protected datasource: DataSource;\n\n /**\n * The single clock every timestamp in a run reads from. Defaults to\n * `() => new Date()`; overridable via {@link SeedersManagerOptions.clock}.\n */\n protected readonly now: SeedClock;\n\n public constructor(protected options?: SeedersManagerOptions) {\n this.datasource = options?.datasource ?? dataSourceRegistry.get();\n this.now = options?.clock ?? (() => new Date());\n }\n\n /**\n * Register new seeder\n */\n public register(...seeders: Seeder[]) {\n this.seeders.push(...seeders);\n return this;\n }\n\n /**\n * Initialize before running\n */\n protected async init() {\n if (!(await this.driver.blueprint.tableExists(seedsTableName))) {\n await migrationRunner.run(SeedsTableMigration);\n }\n\n if (!(await this.driver.blueprint.tableExists(seedRecordsTableName))) {\n await migrationRunner.run(SeedRecordsTableMigration);\n }\n }\n\n /**\n * Run seeders\n */\n public async run(withTransaction = true) {\n await this.init();\n\n this.prepareSeeders();\n\n console.log(`🌱 Running ${this.seeders.length} seeder(s)...\\n`);\n\n let successCount = 0;\n let skippedCount = 0;\n let failedCount = 0;\n\n for (const seeder of this.seeders) {\n // Check if already executed\n if (seeder.once && (await this.seederIsExecutedBefore(seeder))) {\n console.log(`⏭️ Skipping ${colors.yellow(seeder.name)} (already executed)`);\n skippedCount++;\n continue;\n }\n\n try {\n console.log(`🔄 Running ${colors.green(seeder.name)}...`);\n const startTime = Date.now();\n\n const refs: SeedRecordRef[] = [];\n const track = this.createTracker(seeder, refs);\n\n const runSeeder = async () => {\n // Keep LAST-RUN refs only: drop the prior refs for this seeder before\n // writing the new ones, so `--drop` undo stays bounded.\n await this.driver.deleteMany(seedRecordsTableName, { seeder: seeder.name });\n\n const result = await seeder.run({\n track,\n now: this.now,\n batchSize: seeder.batchSize ?? 0,\n });\n\n if (refs.length > 0) {\n await this.driver.insertMany(\n seedRecordsTableName,\n refs.map((ref) => ({ ...ref })),\n );\n }\n\n return result;\n };\n\n const result = withTransaction ? await transaction(runSeeder) : await runSeeder();\n\n const duration = Date.now() - startTime;\n\n // Auto-derive recordsCreated from the track count; a returned\n // SeedResult is an optional fallback when nothing was tracked.\n const recordsCreated = refs.length > 0 ? refs.length : (result?.recordsCreated ?? 0);\n\n await this.storeSeedsResults(seeder, { recordsCreated });\n\n console.log(\n `✅ ${colors.green(seeder.name)} completed (${duration}ms, ${recordsCreated} records)\\n`,\n );\n successCount++;\n } catch (error: any) {\n const err = error as Error;\n console.error(`❌ ${colors.red(seeder.name)} failed:`, err.message);\n console.log(err);\n failedCount++;\n\n if (error instanceof DatabaseWriterValidationError) {\n console.log(error.errors);\n }\n\n // Re-throw to stop execution (or continue to next seed based on your preference)\n throw error;\n }\n }\n\n // Summary\n console.log(\"\\n\" + \"=\".repeat(50));\n console.log(`✅ Success: ${successCount}`);\n\n if (skippedCount > 0) console.log(`⏭️ Skipped: ${skippedCount}`);\n\n if (failedCount > 0) console.log(`❌ Failed: ${failedCount}`);\n\n console.log(\"=\".repeat(50));\n }\n\n /**\n * Build a `track` helper bound to the given seeder. Tracked refs are pushed\n * into `refs` and persisted (within the run transaction) by the caller.\n *\n * Every overload returns its first argument so it can be chained inline.\n */\n protected createTracker(seeder: Seeder, refs: SeedRecordRef[]): Track {\n const push = (table: string, recordId: number | string) => {\n refs.push({ seeder: seeder.name, table, recordId, runAt: this.now() });\n };\n\n function track<T extends TrackableModel>(model: T): T;\n function track<T extends TrackableModel>(models: T[]): T[];\n function track(table: string, id: number | string): string;\n function track(\n arg: TrackableModel | TrackableModel[] | string,\n id?: number | string,\n ): TrackableModel | TrackableModel[] | string {\n if (typeof arg === \"string\") {\n push(arg, id as number | string);\n\n return arg;\n }\n\n if (Array.isArray(arg)) {\n for (const model of arg) {\n push(model.getTableName(), model.id);\n }\n\n return arg;\n }\n\n push(arg.getTableName(), arg.id);\n\n return arg;\n }\n\n return track;\n }\n\n /**\n * Sort seeds\n */\n public sort() {\n this.seeders = this.seeders.sort((a, b) => {\n const orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n const orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n\n return this;\n }\n\n /**\n * Prepare seeders to order by the seeder order, then resolve `dependsOn`.\n *\n * Dependencies are honored via a topological sort layered over the existing\n * numeric `order` tie-break: among seeders whose dependencies are already\n * satisfied, the lowest `order` (then registration order) runs first. Throws\n * {@link UnknownSeederDependencyError} for a missing dependency and\n * {@link SeederDependencyCycleError} for a cycle.\n */\n public prepareSeeders() {\n this.seeders = this.seeders.filter((seeder) => seeder.enabled !== false);\n\n this.sort();\n\n this.seeders = this.resolveDependencies(this.seeders);\n\n return this;\n }\n\n /**\n * Topologically sort `seeders` by their `dependsOn` names. The input is\n * assumed pre-sorted by `order` (see {@link sort}); that ordering is used as\n * the tie-break so dependency-free siblings keep their `order` sequence.\n */\n protected resolveDependencies(seeders: Seeder[]): Seeder[] {\n const byName = new Map<string, Seeder>();\n\n for (const seeder of seeders) {\n byName.set(seeder.name, seeder);\n }\n\n // Validate dependencies up-front so an unknown name is a clear error rather\n // than a silently dropped edge.\n for (const seeder of seeders) {\n for (const dependency of seeder.dependsOn ?? []) {\n if (!byName.has(dependency)) {\n throw new UnknownSeederDependencyError(seeder.name, dependency);\n }\n }\n }\n\n const sorted: Seeder[] = [];\n const state = new Map<string, \"visiting\" | \"visited\">();\n\n const visit = (seeder: Seeder, path: string[]) => {\n const status = state.get(seeder.name);\n\n if (status === \"visited\") {\n return;\n }\n\n if (status === \"visiting\") {\n const cycleStart = path.indexOf(seeder.name);\n const cycle = path.slice(cycleStart >= 0 ? cycleStart : 0).concat(seeder.name);\n\n throw new SeederDependencyCycleError(cycle);\n }\n\n state.set(seeder.name, \"visiting\");\n\n for (const dependency of seeder.dependsOn ?? []) {\n // Presence already validated above; the `!` is safe.\n visit(byName.get(dependency)!, [...path, seeder.name]);\n }\n\n state.set(seeder.name, \"visited\");\n sorted.push(seeder);\n };\n\n // Iterate in the incoming (order-sorted, registration-stable) sequence so\n // dependency-free seeders preserve their `order` tie-break.\n for (const seeder of seeders) {\n visit(seeder, []);\n }\n\n return sorted;\n }\n\n /**\n * Store seed results in db\n */\n public async storeSeedsResults(seeder: Seeder, result: SeedResult) {\n const oldResult = await this.getMetadata(seeder);\n\n // log with an emoji icon at beginning of text no of total craeted records\n console.log(`📊 Total records created: ${result.recordsCreated}`);\n\n // Read the single overridable clock once so every timestamp in this write\n // is identical (and pinned in a deterministic / historical run).\n const now = this.now();\n\n if (oldResult) {\n // Update existing record - use query builder with WHERE clause\n await this.driver\n .queryBuilder(seedsTableName)\n .where(\"name\", seeder.name)\n .update({\n runCount: oldResult.runCount + 1,\n lastRunAt: now,\n totalRecordsCreated: oldResult.totalRecordsCreated + result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n\n // now display total created records so far\n console.log(\n `📊 Total records created so far: ${oldResult.totalRecordsCreated + result.recordsCreated}`,\n );\n } else {\n // Insert new record\n await this.driver.insert(seedsTableName, {\n name: seeder.name,\n createdAt: now,\n firstRunAt: now,\n lastRunAt: now,\n runCount: 1,\n totalRecordsCreated: result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n }\n }\n\n /**\n * Get seed info from database\n */\n // Returns null when the seeder has never run. first() has always resolved to\n // SeederMetadata | null; annotating it as non-null pushed that lie to every\n // caller, each of which already branches on the absent case.\n protected getMetadata(seeder: Seeder): Promise<SeederMetadata | null> {\n const driver = this.datasource.driver;\n\n return driver.queryBuilder(seedsTableName).where(\"name\", seeder.name).first();\n }\n\n /**\n * Get driver instance\n */\n protected get driver(): DriverContract {\n return this.datasource.driver;\n }\n\n /**\n * Check if seeder has been executed before\n */\n protected async seederIsExecutedBefore(seeder: Seeder) {\n return !!(await this.getMetadata(seeder));\n }\n}\n"],"mappings":";;;;;;;;AAmCA,IAAa,iBAAb,MAA4B;CAgB1B,AAAO,YAAY,AAAU,SAAiC;EAAjC;iBAfF,CAAC;EAgB1B,KAAK,aAAa,SAAS,cAAc,mBAAmB,IAAI;EAChE,KAAK,MAAM,SAAS,gCAAgB,IAAI,KAAK;CAC/C;;;;CAKA,AAAO,SAAS,GAAG,SAAmB;EACpC,KAAK,QAAQ,KAAK,GAAG,OAAO;EAC5B,OAAO;CACT;;;;CAKA,MAAgB,OAAO;EACrB,IAAI,CAAE,MAAM,KAAK,OAAO,UAAU,oBAA0B,GAC1D,MAAM,gBAAgB,IAAI,mBAAmB;EAG/C,IAAI,CAAE,MAAM,KAAK,OAAO,UAAU,2BAAgC,GAChE,MAAM,gBAAgB,IAAI,yBAAyB;CAEvD;;;;CAKA,MAAa,IAAI,kBAAkB,MAAM;EACvC,MAAM,KAAK,KAAK;EAEhB,KAAK,eAAe;EAEpB,QAAQ,IAAI,cAAc,KAAK,QAAQ,OAAO,gBAAgB;EAE9D,IAAI,eAAe;EACnB,IAAI,eAAe;EACnB,IAAI,cAAc;EAElB,KAAK,MAAM,UAAU,KAAK,SAAS;GAEjC,IAAI,OAAO,QAAS,MAAM,KAAK,uBAAuB,MAAM,GAAI;IAC9D,QAAQ,IAAI,gBAAgB,OAAO,OAAO,OAAO,IAAI,EAAE,oBAAoB;IAC3E;IACA;GACF;GAEA,IAAI;IACF,QAAQ,IAAI,cAAc,OAAO,MAAM,OAAO,IAAI,EAAE,IAAI;IACxD,MAAM,YAAY,KAAK,IAAI;IAE3B,MAAM,OAAwB,CAAC;IAC/B,MAAM,QAAQ,KAAK,cAAc,QAAQ,IAAI;IAE7C,MAAM,YAAY,YAAY;KAG5B,MAAM,KAAK,OAAO,WAAW,sBAAsB,EAAE,QAAQ,OAAO,KAAK,CAAC;KAE1E,MAAM,SAAS,MAAM,OAAO,IAAI;MAC9B;MACA,KAAK,KAAK;MACV,WAAW,OAAO,aAAa;KACjC,CAAC;KAED,IAAI,KAAK,SAAS,GAChB,MAAM,KAAK,OAAO,WAChB,sBACA,KAAK,KAAK,SAAS,EAAE,GAAG,IAAI,EAAE,CAChC;KAGF,OAAO;IACT;IAEA,MAAM,SAAS,kBAAkB,MAAM,YAAY,SAAS,IAAI,MAAM,UAAU;IAEhF,MAAM,WAAW,KAAK,IAAI,IAAI;IAI9B,MAAM,iBAAiB,KAAK,SAAS,IAAI,KAAK,SAAU,QAAQ,kBAAkB;IAElF,MAAM,KAAK,kBAAkB,QAAQ,EAAE,eAAe,CAAC;IAEvD,QAAQ,IACN,KAAK,OAAO,MAAM,OAAO,IAAI,EAAE,cAAc,SAAS,MAAM,eAAe,YAC7E;IACA;GACF,SAAS,OAAY;IACnB,MAAM,MAAM;IACZ,QAAQ,MAAM,KAAK,OAAO,IAAI,OAAO,IAAI,EAAE,WAAW,IAAI,OAAO;IACjE,QAAQ,IAAI,GAAG;IACf;IAEA,IAAI,iBAAiB,+BACnB,QAAQ,IAAI,MAAM,MAAM;IAI1B,MAAM;GACR;EACF;EAGA,QAAQ,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;EACjC,QAAQ,IAAI,cAAc,cAAc;EAExC,IAAI,eAAe,GAAG,QAAQ,IAAI,gBAAgB,cAAc;EAEhE,IAAI,cAAc,GAAG,QAAQ,IAAI,aAAa,aAAa;EAE3D,QAAQ,IAAI,IAAI,OAAO,EAAE,CAAC;CAC5B;;;;;;;CAQA,AAAU,cAAc,QAAgB,MAA8B;EACpE,MAAM,QAAQ,OAAe,aAA8B;GACzD,KAAK,KAAK;IAAE,QAAQ,OAAO;IAAM;IAAO;IAAU,OAAO,KAAK,IAAI;GAAE,CAAC;EACvE;EAKA,SAAS,MACP,KACA,IAC4C;GAC5C,IAAI,OAAO,QAAQ,UAAU;IAC3B,KAAK,KAAK,EAAqB;IAE/B,OAAO;GACT;GAEA,IAAI,MAAM,QAAQ,GAAG,GAAG;IACtB,KAAK,MAAM,SAAS,KAClB,KAAK,MAAM,aAAa,GAAG,MAAM,EAAE;IAGrC,OAAO;GACT;GAEA,KAAK,IAAI,aAAa,GAAG,IAAI,EAAE;GAE/B,OAAO;EACT;EAEA,OAAO;CACT;;;;CAKA,AAAO,OAAO;EACZ,KAAK,UAAU,KAAK,QAAQ,MAAM,GAAG,MAAM;GAGzC,QAFe,EAAE,SAAS,OAAO,qBAClB,EAAE,SAAS,OAAO;EAEnC,CAAC;EAED,OAAO;CACT;;;;;;;;;;CAWA,AAAO,iBAAiB;EACtB,KAAK,UAAU,KAAK,QAAQ,QAAQ,WAAW,OAAO,YAAY,KAAK;EAEvE,KAAK,KAAK;EAEV,KAAK,UAAU,KAAK,oBAAoB,KAAK,OAAO;EAEpD,OAAO;CACT;;;;;;CAOA,AAAU,oBAAoB,SAA6B;EACzD,MAAM,yBAAS,IAAI,IAAoB;EAEvC,KAAK,MAAM,UAAU,SACnB,OAAO,IAAI,OAAO,MAAM,MAAM;EAKhC,KAAK,MAAM,UAAU,SACnB,KAAK,MAAM,cAAc,OAAO,aAAa,CAAC,GAC5C,IAAI,CAAC,OAAO,IAAI,UAAU,GACxB,MAAM,IAAI,6BAA6B,OAAO,MAAM,UAAU;EAKpE,MAAM,SAAmB,CAAC;EAC1B,MAAM,wBAAQ,IAAI,IAAoC;EAEtD,MAAM,SAAS,QAAgB,SAAmB;GAChD,MAAM,SAAS,MAAM,IAAI,OAAO,IAAI;GAEpC,IAAI,WAAW,WACb;GAGF,IAAI,WAAW,YAAY;IACzB,MAAM,aAAa,KAAK,QAAQ,OAAO,IAAI;IAG3C,MAAM,IAAI,2BAFI,KAAK,MAAM,cAAc,IAAI,aAAa,CAAC,EAAE,OAAO,OAAO,IAEhC,CAAC;GAC5C;GAEA,MAAM,IAAI,OAAO,MAAM,UAAU;GAEjC,KAAK,MAAM,cAAc,OAAO,aAAa,CAAC,GAE5C,MAAM,OAAO,IAAI,UAAU,GAAI,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC;GAGvD,MAAM,IAAI,OAAO,MAAM,SAAS;GAChC,OAAO,KAAK,MAAM;EACpB;EAIA,KAAK,MAAM,UAAU,SACnB,MAAM,QAAQ,CAAC,CAAC;EAGlB,OAAO;CACT;;;;CAKA,MAAa,kBAAkB,QAAgB,QAAoB;EACjE,MAAM,YAAY,MAAM,KAAK,YAAY,MAAM;EAG/C,QAAQ,IAAI,6BAA6B,OAAO,gBAAgB;EAIhE,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,WAAW;GAEb,MAAM,KAAK,OACR,aAAa,cAAc,EAC3B,MAAM,QAAQ,OAAO,IAAI,EACzB,OAAO;IACN,UAAU,UAAU,WAAW;IAC/B,WAAW;IACX,qBAAqB,UAAU,sBAAsB,OAAO;IAC5D,uBAAuB,OAAO;GAChC,CAAC;GAGH,QAAQ,IACN,oCAAoC,UAAU,sBAAsB,OAAO,gBAC7E;EACF,OAEE,MAAM,KAAK,OAAO,OAAO,gBAAgB;GACvC,MAAM,OAAO;GACb,WAAW;GACX,YAAY;GACZ,WAAW;GACX,UAAU;GACV,qBAAqB,OAAO;GAC5B,uBAAuB,OAAO;EAChC,CAAC;CAEL;;;;CAQA,AAAU,YAAY,QAAgD;EAGpE,OAFe,KAAK,WAAW,OAEjB,aAAa,cAAc,EAAE,MAAM,QAAQ,OAAO,IAAI,EAAE,MAAM;CAC9E;;;;CAKA,IAAc,SAAyB;EACrC,OAAO,KAAK,WAAW;CACzB;;;;CAKA,MAAgB,uBAAuB,QAAgB;EACrD,OAAO,CAAC,CAAE,MAAM,KAAK,YAAY,MAAM;CACzC;AACF"}
@@ -22,7 +22,7 @@ var SeedsTableMigration = class extends Migration {
22
22
  this.int("lastRunRecordsCreated").default(0);
23
23
  }
24
24
  down() {
25
- this.dropTableIfExists(seedsTableName);
25
+ this.dropTableIfExists();
26
26
  }
27
27
  };
28
28
 
@@ -1 +1 @@
1
- {"version":3,"file":"seeds-table-migration.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeds-table-migration.ts"],"sourcesContent":["import { Migration } from \"@warlock.js/cascade\";\nimport { seedsTableName } from \"./utils\";\n\nexport class SeedsTableMigration extends Migration {\n public static migrationName = \"seeds-table-migration\";\n public table = seedsTableName;\n\n public up() {\n this.createTableIfNotExists();\n\n this.id();\n this.text(\"name\").unique();\n this.int(\"runCount\").default(0);\n this.dateTime(\"createdAt\").useCurrent();\n this.dateTime(\"firstRunAt\").useCurrent();\n this.dateTime(\"lastRunAt\").useCurrent();\n this.int(\"totalRecordsCreated\").default(0);\n this.int(\"lastRunRecordsCreated\").default(0);\n }\n\n public down() {\n this.dropTableIfExists(seedsTableName);\n }\n}\n"],"mappings":";;;;AAGA,IAAa,sBAAb,cAAyC,UAAU;;;eAElC;;;uBADe;;CAG9B,AAAO,KAAK;EACV,KAAK,uBAAuB;EAE5B,KAAK,GAAG;EACR,KAAK,KAAK,MAAM,CAAC,CAAC,OAAO;EACzB,KAAK,IAAI,UAAU,CAAC,CAAC,QAAQ,CAAC;EAC9B,KAAK,SAAS,WAAW,CAAC,CAAC,WAAW;EACtC,KAAK,SAAS,YAAY,CAAC,CAAC,WAAW;EACvC,KAAK,SAAS,WAAW,CAAC,CAAC,WAAW;EACtC,KAAK,IAAI,qBAAqB,CAAC,CAAC,QAAQ,CAAC;EACzC,KAAK,IAAI,uBAAuB,CAAC,CAAC,QAAQ,CAAC;CAC7C;CAEA,AAAO,OAAO;EACZ,KAAK,kBAAkB,cAAc;CACvC;AACF"}
1
+ {"version":3,"file":"seeds-table-migration.mjs","names":[],"sources":["../../../../../../../../core/src/database/seeds/seeds-table-migration.ts"],"sourcesContent":["import { Migration } from \"@warlock.js/cascade\";\nimport { seedsTableName } from \"./utils\";\n\nexport class SeedsTableMigration extends Migration {\n public static migrationName = \"seeds-table-migration\";\n public table = seedsTableName;\n\n public up() {\n this.createTableIfNotExists();\n\n this.id();\n this.text(\"name\").unique();\n this.int(\"runCount\").default(0);\n this.dateTime(\"createdAt\").useCurrent();\n this.dateTime(\"firstRunAt\").useCurrent();\n this.dateTime(\"lastRunAt\").useCurrent();\n this.int(\"totalRecordsCreated\").default(0);\n this.int(\"lastRunRecordsCreated\").default(0);\n }\n\n public down() {\n // No argument: dropTableIfExists() operates on `this.table`, set as a class\n // field above. The name passed here was silently ignored at runtime.\n this.dropTableIfExists();\n }\n}\n"],"mappings":";;;;AAGA,IAAa,sBAAb,cAAyC,UAAU;;;eAElC;;;uBADe;;CAG9B,AAAO,KAAK;EACV,KAAK,uBAAuB;EAE5B,KAAK,GAAG;EACR,KAAK,KAAK,MAAM,EAAE,OAAO;EACzB,KAAK,IAAI,UAAU,EAAE,QAAQ,CAAC;EAC9B,KAAK,SAAS,WAAW,EAAE,WAAW;EACtC,KAAK,SAAS,YAAY,EAAE,WAAW;EACvC,KAAK,SAAS,WAAW,EAAE,WAAW;EACtC,KAAK,IAAI,qBAAqB,EAAE,QAAQ,CAAC;EACzC,KAAK,IAAI,uBAAuB,EAAE,QAAQ,CAAC;CAC7C;CAEA,AAAO,OAAO;EAGZ,KAAK,kBAAkB;CACzB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/database/utils.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { slugify } from \"@mongez/slug\";\nimport { authService } from \"@warlock.js/auth\";\nimport { Model, useModelTransformer } from \"@warlock.js/cascade\";\nimport { ComputedCallback, SchemaContext } from \"@warlock.js/seal\";\n\n/**\n * Hash password on saving if password changes\n */\nexport const useHashedPassword = () =>\n useModelTransformer(({ value, isChanged, isNew }) => {\n if (!value) return value;\n\n if (!isNew && !isChanged) return value;\n\n return authService.hashPassword(String(value));\n });\n\ntype ComputedCallbackModel = (\n data: any,\n model: Model,\n context: SchemaContext,\n) => any | Promise<any>;\n\n/**\n * Generate computed value based on other fields\n */\nexport function useComputedModel(callback: ComputedCallbackModel) {\n const computedCallback: ComputedCallback = (data, context) => {\n return callback(data, context.rootContext!.model, context);\n };\n\n return computedCallback;\n}\n\n/**\n * Generate slug based on a field on saving\n */\nexport function useComputedSlug(field = \"title\", scope: \"global\" | \"sibling\" = \"sibling\") {\n return useComputedModel((data, model, context) => {\n const value = scope === \"sibling\" ? data[field] : get(context.allValues, field);\n\n if (!value) return model.get(field);\n\n return slugify(value);\n });\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,0BACX,qBAAqB,EAAE,OAAO,WAAW,YAAY;CACnD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI,CAAC,SAAS,CAAC,WAAW,OAAO;CAEjC,OAAO,YAAY,aAAa,OAAO,KAAK,CAAC;AAC/C,CAAC;;;;AAWH,SAAgB,iBAAiB,UAAiC;CAChE,MAAM,oBAAsC,MAAM,YAAY;EAC5D,OAAO,SAAS,MAAM,QAAQ,YAAa,OAAO,OAAO;CAC3D;CAEA,OAAO;AACT;;;;AAKA,SAAgB,gBAAgB,QAAQ,SAAS,QAA8B,WAAW;CACxF,OAAO,kBAAkB,MAAM,OAAO,YAAY;EAChD,MAAM,QAAQ,UAAU,YAAY,KAAK,SAAS,IAAI,QAAQ,WAAW,KAAK;EAE9E,IAAI,CAAC,OAAO,OAAO,MAAM,IAAI,KAAK;EAElC,OAAO,QAAQ,KAAK;CACtB,CAAC;AACH"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/database/utils.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { slugify } from \"@mongez/slug\";\nimport { authService } from \"@warlock.js/auth\";\nimport { Model, useModelTransformer } from \"@warlock.js/cascade\";\nimport { type ComputedCallback, type SchemaContext } from \"@warlock.js/seal\";\n\n/**\n * Hash password on saving if password changes\n */\nexport const useHashedPassword = () =>\n useModelTransformer(({ value, isChanged, isNew }) => {\n if (!value) return value;\n\n if (!isNew && !isChanged) return value;\n\n return authService.hashPassword(String(value));\n });\n\ntype ComputedCallbackModel = (\n data: any,\n model: Model,\n context: SchemaContext,\n) => any | Promise<any>;\n\n/**\n * Generate computed value based on other fields\n */\nexport function useComputedModel(callback: ComputedCallbackModel) {\n const computedCallback: ComputedCallback = (data, context) => {\n return callback(data, context.rootContext!.model, context);\n };\n\n return computedCallback;\n}\n\n/**\n * Generate slug based on a field on saving\n */\nexport function useComputedSlug(field = \"title\", scope: \"global\" | \"sibling\" = \"sibling\") {\n return useComputedModel((data, model, context) => {\n const value = scope === \"sibling\" ? data[field] : get(context.allValues, field);\n\n if (!value) return model.get(field);\n\n return slugify(value);\n });\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,0BACX,qBAAqB,EAAE,OAAO,WAAW,YAAY;CACnD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI,CAAC,SAAS,CAAC,WAAW,OAAO;CAEjC,OAAO,YAAY,aAAa,OAAO,KAAK,CAAC;AAC/C,CAAC;;;;AAWH,SAAgB,iBAAiB,UAAiC;CAChE,MAAM,oBAAsC,MAAM,YAAY;EAC5D,OAAO,SAAS,MAAM,QAAQ,YAAa,OAAO,OAAO;CAC3D;CAEA,OAAO;AACT;;;;AAKA,SAAgB,gBAAgB,QAAQ,SAAS,QAA8B,WAAW;CACxF,OAAO,kBAAkB,MAAM,OAAO,YAAY;EAChD,MAAM,QAAQ,UAAU,YAAY,KAAK,SAAS,IAAI,QAAQ,WAAW,KAAK;EAE9E,IAAI,CAAC,OAAO,OAAO,MAAM,IAAI,KAAK;EAElC,OAAO,QAAQ,KAAK;CACtB,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"check-for-updates.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/check-for-updates.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { updateWarlockPackages } from \"../updater/update-warlock-packages\";\nimport { getWarlockVersion } from \"../utils/framework-vesion\";\nimport { fetchLatestVersion } from \"../utils/npm-registry\";\nimport { isNewerVersion } from \"../utils/version-compare\";\nimport { warlockConfigManager } from \"../warlock-config\";\nimport type { DevelopmentServer } from \"./development-server\";\nimport { restartDevServer } from \"./restart-dev-server\";\nimport { devServerShortcuts } from \"./shortcuts\";\nimport {\n clearCachedLatestVersion,\n readCachedLatestVersion,\n writeCachedLatestVersion,\n} from \"./update-check-cache\";\n\n/** The package whose version represents the whole (lockstep) family. */\nconst CORE_PACKAGE = \"@warlock.js/core\";\n\n/** Where developers can read what changed between releases. */\nconst CHANGELOG_URL = \"https://warlock.js.org/changelog/\";\n\n/** The key that applies the update without leaving the dev server. */\nconst UPDATE_SHORTCUT_KEY = \"u\";\n\n/**\n * Abort budget for the notice's registry lookup. Deliberately much shorter\n * than the `warlock update` command's: nobody is waiting on this answer, and\n * a network that hangs (captive portal, dead VPN, flaky DNS) must not leave a\n * pending request and a live timer sitting around behind a running dev server.\n */\nconst UPDATE_CHECK_TIMEOUT_MS = 5_000;\n\n/**\n * Check npm for a newer `@warlock.js/core` release and, if one exists, print\n * a short non-blocking notice to the terminal. Because the whole family is\n * released in lockstep, core's version stands in for every `@warlock.js/*`\n * package, so a single lookup is enough.\n *\n * When a `devServer` is passed and the terminal is interactive, the notice\n * also arms the `u` shortcut: one keypress updates every `@warlock.js/*`\n * dependency, installs, and restarts the server.\n *\n * Designed to be called fire-and-forget once the dev server is ready: it\n * never throws, never blocks startup, and stays silent unless there is a\n * genuinely newer version to report. Automatically skipped in CI, in\n * non-interactive (non-TTY) shells, and when `devServer.checkForUpdates` is\n * set to `false`.\n */\nexport async function checkForFrameworkUpdate(devServer?: DevelopmentServer): Promise<void> {\n try {\n if (!isUpdateCheckEnabled()) {\n return;\n }\n\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n\n if (devServerConfig?.checkForUpdates === false) {\n return;\n }\n\n const currentVersion = await getWarlockVersion();\n const latestVersion = await resolveLatestVersion();\n\n if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {\n return;\n }\n\n printUpdateNotice(currentVersion, latestVersion, offerUpdateShortcut(devServer));\n } catch {\n // An update check is a convenience — never let it disrupt the dev server.\n }\n}\n\n/**\n * The latest published core version, preferring a recent cached answer over a\n * network round-trip. Every `warlock dev` boot would otherwise hit npm, which\n * is a lot of traffic for a question whose answer changes a few times a month.\n */\nasync function resolveLatestVersion(): Promise<string | undefined> {\n const cached = await readCachedLatestVersion();\n\n if (cached) {\n return cached;\n }\n\n const latestVersion = await fetchLatestVersion(CORE_PACKAGE, UPDATE_CHECK_TIMEOUT_MS);\n\n // A failed lookup is deliberately NOT cached as \"no version\" — that would\n // silence the notice for a day over one flaky moment. Only real answers are\n // remembered; offline simply re-asks next boot.\n if (latestVersion) {\n await writeCachedLatestVersion(latestVersion);\n }\n\n return latestVersion;\n}\n\n/**\n * Whether an update check should run at all in the current environment.\n * Mirrors npm's own update-notifier conventions: stay quiet in CI, in\n * non-interactive shells, and when explicitly opted out via env.\n */\nfunction isUpdateCheckEnabled(): boolean {\n if (process.env.CI) {\n return false;\n }\n\n if (process.env.NO_UPDATE_NOTIFIER) {\n return false;\n }\n\n if (!process.stdout.isTTY) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Arm the `u` shortcut, if there is a server to restart and a terminal that\n * can deliver keypresses. Returns whether the notice may advertise it.\n */\nfunction offerUpdateShortcut(devServer?: DevelopmentServer): boolean {\n if (!devServer) {\n return false;\n }\n\n return devServerShortcuts.register({\n key: UPDATE_SHORTCUT_KEY,\n description: \"update @warlock.js packages and restart\",\n handler: () => updateAndRestart(devServer),\n });\n}\n\n/**\n * The `u` handler: rewrite the `@warlock.js/*` versions, install them, and\n * come back up on the new ones.\n *\n * The shortcut is dropped and the terminal released first, so the package\n * manager child owns stdin (and a second `u` can't stack a parallel install).\n * Releasing has to be explicit: the standing shortcut bar keeps other keys\n * registered, so dropping `u` alone would leave us in raw mode.\n *\n * The terminal is taken back — and `u` re-armed — only for the cases worth\n * retrying: an unreachable registry, or a restart that could not be started.\n */\nasync function updateAndRestart(devServer: DevelopmentServer): Promise<void> {\n devServerShortcuts.unregister(UPDATE_SHORTCUT_KEY);\n devServerShortcuts.release();\n\n console.log();\n\n const result = await updateWarlockPackages();\n\n if (result.outcome === \"updated\") {\n // The remembered answer described the version we just replaced.\n await clearCachedLatestVersion();\n\n const restarted = await restartDevServer(devServer);\n\n if (!restarted) {\n devServerShortcuts.resume();\n console.log(` ${colors.dim(\"Restart the dev server manually to load the new version.\")}`);\n }\n\n return;\n }\n\n devServerShortcuts.resume();\n\n if (result.outcome === \"registry-unreachable\") {\n printShortcutRetryHint();\n offerUpdateShortcut(devServer);\n return;\n }\n\n // Anything else — a failed install, or a notice that turned out to be stale —\n // leaves the shortcut off on purpose: package.json is already rewritten, so a\n // second `u` would just report \"up to date\" and hide the real problem.\n}\n\n/**\n * Print the \"update available\" notice in the dev-logger's visual style.\n *\n * @param canPressKey Whether the `u` shortcut is armed. When it is not (piped\n * stdin, CI, a supervisor), fall back to the command the\n * developer can run by hand.\n */\nfunction printUpdateNotice(\n currentVersion: string,\n latestVersion: string,\n canPressKey: boolean,\n): void {\n console.log();\n console.log(\n ` ${colors.yellow(\"⚡\")} ${colors.bold(\"A new version of Warlock.js is available\")} ` +\n `${colors.dim(currentVersion)} ${colors.dim(\"→\")} ${colors.greenBright(latestVersion)}`,\n );\n\n if (canPressKey) {\n console.log(\n ` ${colors.dim(\"Press\")} ${colors.bold(colors.cyan(UPDATE_SHORTCUT_KEY))} ` +\n `${colors.dim(\"to update all @warlock.js packages and restart\")}`,\n );\n } else {\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"npx warlock update\")} ` +\n `${colors.dim(\"to update all @warlock.js packages\")}`,\n );\n }\n\n console.log(` ${colors.dim(\"Changelog\")} ${colors.cyan(CHANGELOG_URL)}`);\n console.log();\n}\n\n/** Tell the developer the shortcut is still available after a failed try. */\nfunction printShortcutRetryHint(): void {\n console.log(\n ` ${colors.dim(\"Press\")} ${colors.bold(colors.cyan(UPDATE_SHORTCUT_KEY))} ` +\n `${colors.dim(\"to try again once you are back online\")}`,\n );\n console.log();\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAM,eAAe;;AAGrB,MAAM,gBAAgB;;AAGtB,MAAM,sBAAsB;;;;;;;AAQ5B,MAAM,0BAA0B;;;;;;;;;;;;;;;;;AAkBhC,eAAsB,wBAAwB,WAA8C;CAC1F,IAAI;EACF,IAAI,CAAC,qBAAqB,GACxB;EAKF,KAAI,MAF0B,qBAAqB,IAAI,WAAW,EAE/C,EAAE,oBAAoB,OACvC;EAGF,MAAM,iBAAiB,MAAM,kBAAkB;EAC/C,MAAM,gBAAgB,MAAM,qBAAqB;EAEjD,IAAI,CAAC,iBAAiB,CAAC,eAAe,eAAe,cAAc,GACjE;EAGF,kBAAkB,gBAAgB,eAAe,oBAAoB,SAAS,CAAC;CACjF,QAAQ,CAER;AACF;;;;;;AAOA,eAAe,uBAAoD;CACjE,MAAM,SAAS,MAAM,wBAAwB;CAE7C,IAAI,QACF,OAAO;CAGT,MAAM,gBAAgB,MAAM,mBAAmB,cAAc,uBAAuB;CAKpF,IAAI,eACF,MAAM,yBAAyB,aAAa;CAG9C,OAAO;AACT;;;;;;AAOA,SAAS,uBAAgC;CACvC,IAAI,QAAQ,IAAI,IACd,OAAO;CAGT,IAAI,QAAQ,IAAI,oBACd,OAAO;CAGT,IAAI,CAAC,QAAQ,OAAO,OAClB,OAAO;CAGT,OAAO;AACT;;;;;AAMA,SAAS,oBAAoB,WAAwC;CACnE,IAAI,CAAC,WACH,OAAO;CAGT,OAAO,mBAAmB,SAAS;EACjC,KAAK;EACL,aAAa;EACb,eAAe,iBAAiB,SAAS;CAC3C,CAAC;AACH;;;;;;;;;;;;;AAcA,eAAe,iBAAiB,WAA6C;CAC3E,mBAAmB,WAAW,mBAAmB;CACjD,mBAAmB,QAAQ;CAE3B,QAAQ,IAAI;CAEZ,MAAM,SAAS,MAAM,sBAAsB;CAE3C,IAAI,OAAO,YAAY,WAAW;EAEhC,MAAM,yBAAyB;EAI/B,IAAI,CAAC,MAFmB,iBAAiB,SAAS,GAElC;GACd,mBAAmB,OAAO;GAC1B,QAAQ,IAAI,QAAQ,OAAO,IAAI,0DAA0D,GAAG;EAC9F;EAEA;CACF;CAEA,mBAAmB,OAAO;CAE1B,IAAI,OAAO,YAAY,wBAAwB;EAC7C,uBAAuB;EACvB,oBAAoB,SAAS;EAC7B;CACF;AAKF;;;;;;;;AASA,SAAS,kBACP,gBACA,eACA,aACM;CACN,QAAQ,IAAI;CACZ,QAAQ,IACN,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,KAAK,0CAA0C,EAAE,IAC9E,OAAO,IAAI,cAAc,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,YAAY,aAAa,GACxF;CAEA,IAAI,aACF,QAAQ,IACN,QAAQ,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,KAAK,mBAAmB,CAAC,EAAE,GACxE,OAAO,IAAI,gDAAgD,GAClE;MAEA,QAAQ,IACN,QAAQ,OAAO,IAAI,KAAK,EAAE,GAAG,OAAO,KAAK,oBAAoB,EAAE,GAC1D,OAAO,IAAI,oCAAoC,GACtD;CAGF,QAAQ,IAAI,QAAQ,OAAO,IAAI,WAAW,EAAE,GAAG,OAAO,KAAK,aAAa,GAAG;CAC3E,QAAQ,IAAI;AACd;;AAGA,SAAS,yBAA+B;CACtC,QAAQ,IACN,QAAQ,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,KAAK,mBAAmB,CAAC,EAAE,GACxE,OAAO,IAAI,uCAAuC,GACzD;CACA,QAAQ,IAAI;AACd"}
1
+ {"version":3,"file":"check-for-updates.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/check-for-updates.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { updateWarlockPackages } from \"../updater/update-warlock-packages\";\nimport { getWarlockVersion } from \"../utils/framework-vesion\";\nimport { fetchLatestVersion } from \"../utils/npm-registry\";\nimport { isNewerVersion } from \"../utils/version-compare\";\nimport { warlockConfigManager } from \"../warlock-config\";\nimport type { DevelopmentServer } from \"./development-server\";\nimport { restartDevServer } from \"./restart-dev-server\";\nimport { devServerShortcuts } from \"./shortcuts\";\nimport {\n clearCachedLatestVersion,\n readCachedLatestVersion,\n writeCachedLatestVersion,\n} from \"./update-check-cache\";\n\n/** The package whose version represents the whole (lockstep) family. */\nconst CORE_PACKAGE = \"@warlock.js/core\";\n\n/** Where developers can read what changed between releases. */\nconst CHANGELOG_URL = \"https://warlock.js.org/changelog/\";\n\n/** The key that applies the update without leaving the dev server. */\nconst UPDATE_SHORTCUT_KEY = \"u\";\n\n/**\n * Abort budget for the notice's registry lookup. Deliberately much shorter\n * than the `warlock update` command's: nobody is waiting on this answer, and\n * a network that hangs (captive portal, dead VPN, flaky DNS) must not leave a\n * pending request and a live timer sitting around behind a running dev server.\n */\nconst UPDATE_CHECK_TIMEOUT_MS = 5_000;\n\n/**\n * Check npm for a newer `@warlock.js/core` release and, if one exists, print\n * a short non-blocking notice to the terminal. Because the whole family is\n * released in lockstep, core's version stands in for every `@warlock.js/*`\n * package, so a single lookup is enough.\n *\n * When a `devServer` is passed and the terminal is interactive, the notice\n * also arms the `u` shortcut: one keypress updates every `@warlock.js/*`\n * dependency, installs, and restarts the server.\n *\n * Designed to be called fire-and-forget once the dev server is ready: it\n * never throws, never blocks startup, and stays silent unless there is a\n * genuinely newer version to report. Automatically skipped in CI, in\n * non-interactive (non-TTY) shells, and when `devServer.checkForUpdates` is\n * set to `false`.\n */\nexport async function checkForFrameworkUpdate(devServer?: DevelopmentServer): Promise<void> {\n try {\n if (!isUpdateCheckEnabled()) {\n return;\n }\n\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n\n if (devServerConfig?.checkForUpdates === false) {\n return;\n }\n\n const currentVersion = await getWarlockVersion();\n const latestVersion = await resolveLatestVersion();\n\n if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {\n return;\n }\n\n printUpdateNotice(currentVersion, latestVersion, offerUpdateShortcut(devServer));\n } catch {\n // An update check is a convenience — never let it disrupt the dev server.\n }\n}\n\n/**\n * The latest published core version, preferring a recent cached answer over a\n * network round-trip. Every `warlock dev` boot would otherwise hit npm, which\n * is a lot of traffic for a question whose answer changes a few times a month.\n */\nasync function resolveLatestVersion(): Promise<string | undefined> {\n const cached = await readCachedLatestVersion();\n\n if (cached) {\n return cached;\n }\n\n const latestVersion = await fetchLatestVersion(CORE_PACKAGE, UPDATE_CHECK_TIMEOUT_MS);\n\n // A failed lookup is deliberately NOT cached as \"no version\" — that would\n // silence the notice for a day over one flaky moment. Only real answers are\n // remembered; offline simply re-asks next boot.\n if (latestVersion) {\n await writeCachedLatestVersion(latestVersion);\n }\n\n return latestVersion;\n}\n\n/**\n * Whether an update check should run at all in the current environment.\n * Mirrors npm's own update-notifier conventions: stay quiet in CI, in\n * non-interactive shells, and when explicitly opted out via env.\n */\nfunction isUpdateCheckEnabled(): boolean {\n if (process.env.CI) {\n return false;\n }\n\n if (process.env.NO_UPDATE_NOTIFIER) {\n return false;\n }\n\n if (!process.stdout.isTTY) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Arm the `u` shortcut, if there is a server to restart and a terminal that\n * can deliver keypresses. Returns whether the notice may advertise it.\n */\nfunction offerUpdateShortcut(devServer?: DevelopmentServer): boolean {\n if (!devServer) {\n return false;\n }\n\n return devServerShortcuts.register({\n key: UPDATE_SHORTCUT_KEY,\n description: \"update @warlock.js packages and restart\",\n handler: () => updateAndRestart(devServer),\n });\n}\n\n/**\n * The `u` handler: rewrite the `@warlock.js/*` versions, install them, and\n * come back up on the new ones.\n *\n * The shortcut is dropped and the terminal released first, so the package\n * manager child owns stdin (and a second `u` can't stack a parallel install).\n * Releasing has to be explicit: the standing shortcut bar keeps other keys\n * registered, so dropping `u` alone would leave us in raw mode.\n *\n * The terminal is taken back — and `u` re-armed — only for the cases worth\n * retrying: an unreachable registry, or a restart that could not be started.\n */\nasync function updateAndRestart(devServer: DevelopmentServer): Promise<void> {\n devServerShortcuts.unregister(UPDATE_SHORTCUT_KEY);\n devServerShortcuts.release();\n\n console.log();\n\n const result = await updateWarlockPackages();\n\n if (result.outcome === \"updated\") {\n // The remembered answer described the version we just replaced.\n await clearCachedLatestVersion();\n\n const restarted = await restartDevServer(devServer);\n\n if (!restarted) {\n devServerShortcuts.resume();\n console.log(` ${colors.dim(\"Restart the dev server manually to load the new version.\")}`);\n }\n\n return;\n }\n\n devServerShortcuts.resume();\n\n if (result.outcome === \"registry-unreachable\") {\n printShortcutRetryHint();\n offerUpdateShortcut(devServer);\n return;\n }\n\n // Anything else — a failed install, or a notice that turned out to be stale —\n // leaves the shortcut off on purpose: package.json is already rewritten, so a\n // second `u` would just report \"up to date\" and hide the real problem.\n}\n\n/**\n * Print the \"update available\" notice in the dev-logger's visual style.\n *\n * @param canPressKey Whether the `u` shortcut is armed. When it is not (piped\n * stdin, CI, a supervisor), fall back to the command the\n * developer can run by hand.\n */\nfunction printUpdateNotice(\n currentVersion: string,\n latestVersion: string,\n canPressKey: boolean,\n): void {\n console.log();\n console.log(\n ` ${colors.yellow(\"⚡\")} ${colors.bold(\"A new version of Warlock.js is available\")} ` +\n `${colors.dim(currentVersion)} ${colors.dim(\"→\")} ${colors.greenBright(latestVersion)}`,\n );\n\n if (canPressKey) {\n console.log(\n ` ${colors.dim(\"Press\")} ${colors.bold(colors.cyan(UPDATE_SHORTCUT_KEY))} ` +\n `${colors.dim(\"to update all @warlock.js packages and restart\")}`,\n );\n } else {\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"npx warlock update\")} ` +\n `${colors.dim(\"to update all @warlock.js packages\")}`,\n );\n }\n\n console.log(` ${colors.dim(\"Changelog\")} ${colors.cyan(CHANGELOG_URL)}`);\n console.log();\n}\n\n/** Tell the developer the shortcut is still available after a failed try. */\nfunction printShortcutRetryHint(): void {\n console.log(\n ` ${colors.dim(\"Press\")} ${colors.bold(colors.cyan(UPDATE_SHORTCUT_KEY))} ` +\n `${colors.dim(\"to try again once you are back online\")}`,\n );\n console.log();\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAM,eAAe;;AAGrB,MAAM,gBAAgB;;AAGtB,MAAM,sBAAsB;;;;;;;AAQ5B,MAAM,0BAA0B;;;;;;;;;;;;;;;;;AAkBhC,eAAsB,wBAAwB,WAA8C;CAC1F,IAAI;EACF,IAAI,CAAC,qBAAqB,GACxB;EAKF,KAAI,MAF0B,qBAAqB,IAAI,WAAW,IAE7C,oBAAoB,OACvC;EAGF,MAAM,iBAAiB,MAAM,kBAAkB;EAC/C,MAAM,gBAAgB,MAAM,qBAAqB;EAEjD,IAAI,CAAC,iBAAiB,CAAC,eAAe,eAAe,cAAc,GACjE;EAGF,kBAAkB,gBAAgB,eAAe,oBAAoB,SAAS,CAAC;CACjF,QAAQ,CAER;AACF;;;;;;AAOA,eAAe,uBAAoD;CACjE,MAAM,SAAS,MAAM,wBAAwB;CAE7C,IAAI,QACF,OAAO;CAGT,MAAM,gBAAgB,MAAM,mBAAmB,cAAc,uBAAuB;CAKpF,IAAI,eACF,MAAM,yBAAyB,aAAa;CAG9C,OAAO;AACT;;;;;;AAOA,SAAS,uBAAgC;CACvC,IAAI,QAAQ,IAAI,IACd,OAAO;CAGT,IAAI,QAAQ,IAAI,oBACd,OAAO;CAGT,IAAI,CAAC,QAAQ,OAAO,OAClB,OAAO;CAGT,OAAO;AACT;;;;;AAMA,SAAS,oBAAoB,WAAwC;CACnE,IAAI,CAAC,WACH,OAAO;CAGT,OAAO,mBAAmB,SAAS;EACjC,KAAK;EACL,aAAa;EACb,eAAe,iBAAiB,SAAS;CAC3C,CAAC;AACH;;;;;;;;;;;;;AAcA,eAAe,iBAAiB,WAA6C;CAC3E,mBAAmB,WAAW,mBAAmB;CACjD,mBAAmB,QAAQ;CAE3B,QAAQ,IAAI;CAEZ,MAAM,SAAS,MAAM,sBAAsB;CAE3C,IAAI,OAAO,YAAY,WAAW;EAEhC,MAAM,yBAAyB;EAI/B,IAAI,CAAC,MAFmB,iBAAiB,SAAS,GAElC;GACd,mBAAmB,OAAO;GAC1B,QAAQ,IAAI,QAAQ,OAAO,IAAI,0DAA0D,GAAG;EAC9F;EAEA;CACF;CAEA,mBAAmB,OAAO;CAE1B,IAAI,OAAO,YAAY,wBAAwB;EAC7C,uBAAuB;EACvB,oBAAoB,SAAS;EAC7B;CACF;AAKF;;;;;;;;AASA,SAAS,kBACP,gBACA,eACA,aACM;CACN,QAAQ,IAAI;CACZ,QAAQ,IACN,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,KAAK,0CAA0C,EAAE,IAC9E,OAAO,IAAI,cAAc,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,YAAY,aAAa,GACxF;CAEA,IAAI,aACF,QAAQ,IACN,QAAQ,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,KAAK,mBAAmB,CAAC,EAAE,GACxE,OAAO,IAAI,gDAAgD,GAClE;MAEA,QAAQ,IACN,QAAQ,OAAO,IAAI,KAAK,EAAE,GAAG,OAAO,KAAK,oBAAoB,EAAE,GAC1D,OAAO,IAAI,oCAAoC,GACtD;CAGF,QAAQ,IAAI,QAAQ,OAAO,IAAI,WAAW,EAAE,GAAG,OAAO,KAAK,aAAa,GAAG;CAC3E,QAAQ,IAAI;AACd;;AAGA,SAAS,yBAA+B;CACtC,QAAQ,IACN,QAAQ,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,KAAK,mBAAmB,CAAC,EAAE,GACxE,OAAO,IAAI,uCAAuC,GACzD;CACA,QAAQ,IAAI;AACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-graph.d.mts","names":[],"sources":["../../../../../../../core/src/dev-server/dependency-graph.ts"],"mappings":";;;;;AAYA;;;;;;;;cAAa,eAAA;EA0L4B;;;;;EAAA,QApL/B,YAAA;EAkBY;;;;;EAAA,QAXZ,UAAA;EAiD+C;;;;EAAA,QA3C/C,KAAA;EA8EU;;;EAzEX,KAAA,CAAM,KAAA,EAAO,GAAA,SAAY,WAAA;EA2G9B;;;;;;;;;EArEK,aAAA,CAAc,IAAA,UAAc,UAAA,UAAoB,UAAA;EA4HlC;;;;;EAlGd,gBAAA,CAAiB,IAAA,UAAc,UAAA;EAwM9B;;;;EA/LD,UAAA,CAAW,IAAA;;;;;;;;;;;;EAgCX,UAAA,CACL,IAAA,UACA,eAAA,EAAiB,GAAA,UACjB,oBAAA,GAAsB,GAAA;;;;;;EA+BjB,eAAA,CAAgB,IAAA,WAAe,GAAA;;;;;EAc/B,cAAA,CAAe,IAAA,UAAc,UAAA;;;;;;EAS7B,aAAA,CAAc,IAAA,WAAe,GAAA;;;;;;;;EAW7B,oBAAA,CAAqB,IAAA;;;;;;;;;;;;;EA+BrB,0BAAA;;;;;;;UA2CC,cAAA;;;;;UAiBA,iCAAA;;;;EAgGD,QAAA"}
1
+ {"version":3,"file":"dependency-graph.d.mts","names":[],"sources":["../../../../../../../core/src/dev-server/dependency-graph.ts"],"mappings":";;;;;AAYA;;;;;;;;cAAa,eAAA;EA0L4B;;;;;EAAA,QApL/B,YAAA;EAkBY;;;;;EAAA,QAXZ,UAAA;EAiD+C;;;;EAAA,QA3C/C,KAAA;EA8EU;;;EAzEX,KAAA,CAAM,KAAA,EAAO,GAAA,SAAY,WAAA;EA2G9B;;;;;;;;;EArEK,aAAA,CAAc,IAAA,UAAc,UAAA,UAAoB,UAAA;EA4HlC;;;;;EAlGd,gBAAA,CAAiB,IAAA,UAAc,UAAA;EAwM9B;;;;EA/LD,UAAA,CAAW,IAAA;;;;;;;;;;;;EAgCX,UAAA,CACL,IAAA,UACA,eAAA,EAAiB,GAAA,UACjB,oBAAA,GAAsB,GAAA;;;;;;EA+BjB,eAAA,CAAgB,IAAA,WAAe,GAAA;;;;;EAc/B,cAAA,CAAe,IAAA,UAAc,UAAA;;;;;;EAS7B,aAAA,CAAc,IAAA,WAAe,GAAA;;;;;;;;EAW7B,oBAAA,CAAqB,IAAA;;;;;;;;;;;;;EA+BrB,0BAAA,CAAA;;;;;;;UA2CC,cAAA;;;;;UAiBA,iCAAA;;;;EAgGD,QAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-graph.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dependency-graph.ts"],"sourcesContent":["import type { FileManager } from \"./file-manager\";\r\n\r\n/**\r\n * Dependency Graph\r\n * Tracks bidirectional relationships between files:\r\n * - dependencies: files that this file imports\r\n * - dependents: files that import this file\r\n *\r\n * Each forward edge also carries an `isTypeOnly` flag so cycle detection\r\n * can distinguish runtime cycles from ones formed purely by `import type`\r\n * edges — the latter are erased by TypeScript and have no runtime effect.\r\n */\r\nexport class DependencyGraph {\r\n /**\r\n * Map of file -> files it depends on (imports).\r\n * Key: relative file path.\r\n * Value: relative dependency path → `{ isTypeOnly }` for that edge.\r\n */\r\n private dependencies = new Map<string, Map<string, { isTypeOnly: boolean }>>();\r\n\r\n /**\r\n * Map of file -> files that depend on it (importers)\r\n * Key: relative file path\r\n * Value: Set of relative file paths that import this file\r\n */\r\n private dependents = new Map<string, Set<string>>();\r\n\r\n /**\r\n * Reference to the files map for accessing FileManager instances\r\n * Used to read per-file typeOnlyDependencies during graph build.\r\n */\r\n private files: Map<string, FileManager> | null = null;\r\n\r\n /**\r\n * Build dependency graph from FileManager map\r\n */\r\n public build(files: Map<string, FileManager>) {\r\n this.files = files;\r\n\r\n this.dependencies.clear();\r\n this.dependents.clear();\r\n\r\n for (const [relativePath, fileManager] of files) {\r\n if (!this.dependencies.has(relativePath)) {\r\n this.dependencies.set(relativePath, new Map());\r\n }\r\n\r\n if (!this.dependents.has(relativePath)) {\r\n this.dependents.set(relativePath, new Set());\r\n }\r\n\r\n for (const dependency of fileManager.dependencies) {\r\n const isTypeOnly = fileManager.typeOnlyDependencies.has(dependency);\r\n\r\n this.addDependency(relativePath, dependency, isTypeOnly);\r\n }\r\n }\r\n\r\n const cycles = this.detectCircularDependencies();\r\n\r\n if (cycles.length > 0) {\r\n this.displayCircularDependencyWarnings(cycles);\r\n }\r\n }\r\n\r\n /**\r\n * Add a dependency relationship.\r\n * If the edge already exists, the type-only flag is ANDed: any runtime\r\n * statement between the two files makes the edge runtime permanently.\r\n *\r\n * @param file The file that has the dependency\r\n * @param dependency The file being depended upon\r\n * @param isTypeOnly Whether every reference from `file` to `dependency` is type-only\r\n */\r\n public addDependency(file: string, dependency: string, isTypeOnly: boolean = false) {\r\n if (!this.dependencies.has(file)) {\r\n this.dependencies.set(file, new Map());\r\n }\r\n\r\n if (!this.dependents.has(dependency)) {\r\n this.dependents.set(dependency, new Set());\r\n }\r\n\r\n const edges = this.dependencies.get(file)!;\r\n const existing = edges.get(dependency);\r\n\r\n if (existing) {\r\n existing.isTypeOnly = existing.isTypeOnly && isTypeOnly;\r\n } else {\r\n edges.set(dependency, { isTypeOnly });\r\n }\r\n\r\n this.dependents.get(dependency)!.add(file);\r\n }\r\n\r\n /**\r\n * Remove a dependency relationship\r\n * @param file The file that has the dependency\r\n * @param dependency The file being depended upon\r\n */\r\n public removeDependency(file: string, dependency: string) {\r\n this.dependencies.get(file)?.delete(dependency);\r\n this.dependents.get(dependency)?.delete(file);\r\n }\r\n\r\n /**\r\n * Remove all relationships for a file\r\n * @param file The file to remove\r\n */\r\n public removeFile(file: string) {\r\n const deps = this.dependencies.get(file);\r\n\r\n if (deps) {\r\n for (const dependency of deps.keys()) {\r\n this.dependents.get(dependency)?.delete(file);\r\n }\r\n }\r\n\r\n const dependents = this.dependents.get(file);\r\n\r\n if (dependents) {\r\n for (const dependent of dependents) {\r\n this.dependencies.get(dependent)?.delete(file);\r\n }\r\n }\r\n\r\n this.dependencies.delete(file);\r\n this.dependents.delete(file);\r\n }\r\n\r\n /**\r\n * Update dependencies for a file.\r\n *\r\n * Accepts the updated `Set<string>` of dependency paths; when the caller\r\n * has per-edge type-only info, pass `typeOnlyDependencies` so the new\r\n * edges get the correct flag. Missing flag defaults to runtime.\r\n *\r\n * @param file The file to update\r\n * @param newDependencies New set of dependency paths\r\n * @param typeOnlyDependencies Subset of `newDependencies` whose edges are type-only\r\n */\r\n public updateFile(\r\n file: string,\r\n newDependencies: Set<string>,\r\n typeOnlyDependencies: Set<string> = new Set(),\r\n ) {\r\n const oldEdges = this.dependencies.get(file) ?? new Map<string, { isTypeOnly: boolean }>();\r\n\r\n for (const oldDep of oldEdges.keys()) {\r\n if (!newDependencies.has(oldDep)) {\r\n this.removeDependency(file, oldDep);\r\n }\r\n }\r\n\r\n for (const newDep of newDependencies) {\r\n const isTypeOnly = typeOnlyDependencies.has(newDep);\r\n const existing = oldEdges.get(newDep);\r\n\r\n if (!existing) {\r\n this.addDependency(file, newDep, isTypeOnly);\r\n\r\n continue;\r\n }\r\n\r\n if (existing.isTypeOnly !== isTypeOnly) {\r\n existing.isTypeOnly = isTypeOnly;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get files that this file depends on (imports)\r\n * @param file The file to check\r\n * @returns Set of files this file imports\r\n */\r\n public getDependencies(file: string): Set<string> {\r\n const edges = this.dependencies.get(file);\r\n\r\n if (!edges) {\r\n return new Set();\r\n }\r\n\r\n return new Set(edges.keys());\r\n }\r\n\r\n /**\r\n * Check whether the edge `file → dependency` is type-only.\r\n * Returns false if the edge does not exist.\r\n */\r\n public isEdgeTypeOnly(file: string, dependency: string): boolean {\r\n return this.dependencies.get(file)?.get(dependency)?.isTypeOnly === true;\r\n }\r\n\r\n /**\r\n * Get files that depend on this file (importers)\r\n * @param file The file to check\r\n * @returns Set of files that import this file\r\n */\r\n public getDependents(file: string): Set<string> {\r\n return this.dependents.get(file) || new Set();\r\n }\r\n\r\n /**\r\n * Get invalidation chain for a file\r\n * Returns all files that need to be reloaded when this file changes\r\n * Includes the file itself and all transitive dependents\r\n * @param file The file that changed\r\n * @returns Array of files to invalidate (in order)\r\n */\r\n public getInvalidationChain(file: string): string[] {\r\n const chain: string[] = [file];\r\n const visited = new Set([file]);\r\n\r\n const traverse = (current: string) => {\r\n const deps = this.getDependents(current);\r\n for (const dep of deps) {\r\n if (!visited.has(dep)) {\r\n visited.add(dep);\r\n chain.push(dep);\r\n traverse(dep); // Recursive traversal\r\n }\r\n }\r\n };\r\n\r\n traverse(file);\r\n return chain;\r\n }\r\n\r\n /**\r\n * Detect circular dependencies in the dependency graph.\r\n * Uses depth-first search to find cycles.\r\n *\r\n * Filtering rule (per-edge): a cycle is suppressed when **any** edge in\r\n * the chain is type-only. TypeScript erases type-only imports entirely,\r\n * so a single type-only edge breaks the cycle at runtime. Only warn when\r\n * every edge is a runtime binding — that's the cycle that actually loops\r\n * at load time.\r\n *\r\n * @returns Array of circular dependency chains (each chain is an array of file paths)\r\n */\r\n public detectCircularDependencies(): string[][] {\r\n const cycles: string[][] = [];\r\n const visited = new Set<string>();\r\n const recursionStack = new Set<string>();\r\n\r\n const dfs = (file: string, path: string[]): void => {\r\n visited.add(file);\r\n recursionStack.add(file);\r\n path.push(file);\r\n\r\n const edges = this.dependencies.get(file);\r\n\r\n if (edges) {\r\n for (const dep of edges.keys()) {\r\n if (!visited.has(dep)) {\r\n dfs(dep, [...path]);\r\n } else if (recursionStack.has(dep)) {\r\n const cycleStart = path.indexOf(dep);\r\n const cycle = [...path.slice(cycleStart), dep];\r\n\r\n cycles.push(cycle);\r\n }\r\n }\r\n }\r\n\r\n recursionStack.delete(file);\r\n };\r\n\r\n for (const file of this.dependencies.keys()) {\r\n if (!visited.has(file)) {\r\n dfs(file, []);\r\n }\r\n }\r\n\r\n return cycles.filter((cycle) => this.isRuntimeCycle(cycle));\r\n }\r\n\r\n /**\r\n * True iff every edge in the cycle is a runtime binding. The cycle array\r\n * ends with a duplicate of its start (`[A, B, C, A]`) so we walk the\r\n * consecutive pairs once — any type-only edge means TS erases the loop\r\n * and there is no runtime cycle to report.\r\n */\r\n private isRuntimeCycle(cycle: string[]): boolean {\r\n for (let index = 0; index < cycle.length - 1; index++) {\r\n const from = cycle[index];\r\n const to = cycle[index + 1];\r\n\r\n if (this.isEdgeTypeOnly(from, to)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Display circular dependency warnings in a formatted, user-friendly way\r\n * Shows each cycle with visual tree structure and helpful recommendations\r\n */\r\n private displayCircularDependencyWarnings(cycles: string[][]): void {\r\n const colors = {\r\n yellow: (text: string) => `\\x1b[33m${text}\\x1b[0m`,\r\n cyan: (text: string) => `\\x1b[36m${text}\\x1b[0m`,\r\n dim: (text: string) => `\\x1b[2m${text}\\x1b[0m`,\r\n bold: (text: string) => `\\x1b[1m${text}\\x1b[0m`,\r\n green: (text: string) => `\\x1b[32m${text}\\x1b[0m`,\r\n };\r\n\r\n console.log(\"\");\r\n console.log(colors.yellow(\"⚠️ Circular Dependencies Detected\"));\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(\"\");\r\n console.log(\r\n colors.dim(\r\n `Found ${colors.bold(cycles.length.toString())} circular dependency chain${cycles.length > 1 ? \"s\" : \"\"}`,\r\n ),\r\n );\r\n console.log(\"\");\r\n\r\n cycles.forEach((cycle, index) => {\r\n console.log(colors.cyan(` ${index + 1}. Cycle with ${cycle.length - 1} files:`));\r\n console.log(\"\");\r\n\r\n // Display the cycle chain\r\n cycle.forEach((file, fileIndex) => {\r\n const isLast = fileIndex === cycle.length - 1;\r\n const arrow = isLast ? colors.dim(\" └─→ \") : colors.dim(\" ├─→ \");\r\n const fileName = file.split(\"/\").pop() || file;\r\n const filePath = colors.dim(file.replace(fileName, \"\"));\r\n\r\n if (isLast) {\r\n // Last item is the same as first (completes the cycle)\r\n console.log(\r\n arrow + colors.yellow(`${filePath}${colors.bold(fileName)} (cycle completes)`),\r\n );\r\n } else {\r\n console.log(arrow + filePath + colors.bold(fileName));\r\n }\r\n });\r\n\r\n console.log(\"\");\r\n });\r\n\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(colors.yellow(\"💡 How to Fix:\"));\r\n console.log(\"\");\r\n\r\n const exampleCycle = cycles[0];\r\n const fileA = exampleCycle[0]?.split(\"/\").pop() || \"fileA.ts\";\r\n const fileB = exampleCycle[1]?.split(\"/\").pop() || \"fileB.ts\";\r\n const stripExt = (file: string) => file.replace(/\\.tsx?$/, \"\");\r\n\r\n const kind = classifyCycle(exampleCycle);\r\n\r\n if (kind === \"resource\" || kind === \"model\") {\r\n // Cascade's `lazy()` is purpose-built for circular relations between\r\n // resources and models — recommend it first, not as a footnote.\r\n console.log(colors.green(` ✨ Use lazy() for the back-reference`));\r\n console.log(\r\n colors.dim(\r\n ` ${kind === \"resource\" ? \"Resources\" : \"Models\"} that reference each other should declare`,\r\n ),\r\n );\r\n console.log(colors.dim(` one side with lazy() so the import resolves at access time:`));\r\n console.log(\"\");\r\n console.log(colors.dim(` import { lazy } from \"@warlock.js/cascade\";`));\r\n console.log(colors.dim(` // inside ${fileB}:`));\r\n console.log(colors.dim(` other: lazy(() => ${kind === \"resource\" ? \"OtherResource\" : \"OtherModel\"})`));\r\n console.log(\"\");\r\n } else {\r\n // Generic cycle — dynamic import is the most common reliable fix.\r\n console.log(colors.green(` ✨ Convert one edge to a dynamic import`));\r\n console.log(colors.dim(` In ${fileB}, change:`));\r\n console.log(colors.dim(` import { SomeClass } from \"./${stripExt(fileA)}\";`));\r\n console.log(colors.dim(` To:`));\r\n console.log(\r\n colors.dim(` const { SomeClass } = await import(\"./${stripExt(fileA)}\");`),\r\n );\r\n console.log(\"\");\r\n }\r\n\r\n console.log(colors.dim(\" Other options:\"));\r\n console.log(colors.dim(\" • Extract the shared symbols into a third file both can import.\"));\r\n console.log(colors.dim(\" • Pass the dependency as a constructor/function parameter instead.\"));\r\n console.log(\"\");\r\n\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(colors.dim(\" Circular dependencies can cause HMR issues and hard-to-debug\"));\r\n console.log(colors.dim(\" initialization order problems. Consider refactoring.\"));\r\n console.log(\"\");\r\n }\r\n\r\n /**\r\n * Get statistics about the dependency graph\r\n */\r\n public getStats() {\r\n let totalDependencies = 0;\r\n let maxDependencies = 0;\r\n let maxDependents = 0;\r\n let mostDependedFile = \"\";\r\n let mostDependingFile = \"\";\r\n\r\n for (const [file, deps] of this.dependencies) {\r\n totalDependencies += deps.size;\r\n if (deps.size > maxDependencies) {\r\n maxDependencies = deps.size;\r\n mostDependingFile = file;\r\n }\r\n }\r\n\r\n for (const [file, deps] of this.dependents) {\r\n if (deps.size > maxDependents) {\r\n maxDependents = deps.size;\r\n mostDependedFile = file;\r\n }\r\n }\r\n\r\n return {\r\n totalFiles: this.dependencies.size,\r\n totalDependencies,\r\n avgDependenciesPerFile: totalDependencies / this.dependencies.size || 0,\r\n maxDependencies,\r\n maxDependents,\r\n mostDependingFile,\r\n mostDependedFile,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Pick the most useful \"fix-it\" hint for a cycle by inspecting the file\r\n * extensions involved. Cascade resources/models have a first-class fix\r\n * (`lazy()`); generic cycles just want a dynamic import.\r\n */\r\nfunction classifyCycle(cycle: string[]): \"resource\" | \"model\" | \"generic\" {\r\n const files = cycle.slice(0, -1); // last entry repeats the first\r\n if (files.every(file => file.endsWith(\".resource.ts\") || file.endsWith(\".resource.tsx\"))) {\r\n return \"resource\";\r\n }\r\n if (files.every(file => file.endsWith(\".model.ts\") || file.endsWith(\".model.tsx\"))) {\r\n return \"model\";\r\n }\r\n return \"generic\";\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"dependency-graph.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dependency-graph.ts"],"sourcesContent":["import type { FileManager } from \"./file-manager\";\r\n\r\n/**\r\n * Dependency Graph\r\n * Tracks bidirectional relationships between files:\r\n * - dependencies: files that this file imports\r\n * - dependents: files that import this file\r\n *\r\n * Each forward edge also carries an `isTypeOnly` flag so cycle detection\r\n * can distinguish runtime cycles from ones formed purely by `import type`\r\n * edges — the latter are erased by TypeScript and have no runtime effect.\r\n */\r\nexport class DependencyGraph {\r\n /**\r\n * Map of file -> files it depends on (imports).\r\n * Key: relative file path.\r\n * Value: relative dependency path → `{ isTypeOnly }` for that edge.\r\n */\r\n private dependencies = new Map<string, Map<string, { isTypeOnly: boolean }>>();\r\n\r\n /**\r\n * Map of file -> files that depend on it (importers)\r\n * Key: relative file path\r\n * Value: Set of relative file paths that import this file\r\n */\r\n private dependents = new Map<string, Set<string>>();\r\n\r\n /**\r\n * Reference to the files map for accessing FileManager instances\r\n * Used to read per-file typeOnlyDependencies during graph build.\r\n */\r\n private files: Map<string, FileManager> | null = null;\r\n\r\n /**\r\n * Build dependency graph from FileManager map\r\n */\r\n public build(files: Map<string, FileManager>) {\r\n this.files = files;\r\n\r\n this.dependencies.clear();\r\n this.dependents.clear();\r\n\r\n for (const [relativePath, fileManager] of files) {\r\n if (!this.dependencies.has(relativePath)) {\r\n this.dependencies.set(relativePath, new Map());\r\n }\r\n\r\n if (!this.dependents.has(relativePath)) {\r\n this.dependents.set(relativePath, new Set());\r\n }\r\n\r\n for (const dependency of fileManager.dependencies) {\r\n const isTypeOnly = fileManager.typeOnlyDependencies.has(dependency);\r\n\r\n this.addDependency(relativePath, dependency, isTypeOnly);\r\n }\r\n }\r\n\r\n const cycles = this.detectCircularDependencies();\r\n\r\n if (cycles.length > 0) {\r\n this.displayCircularDependencyWarnings(cycles);\r\n }\r\n }\r\n\r\n /**\r\n * Add a dependency relationship.\r\n * If the edge already exists, the type-only flag is ANDed: any runtime\r\n * statement between the two files makes the edge runtime permanently.\r\n *\r\n * @param file The file that has the dependency\r\n * @param dependency The file being depended upon\r\n * @param isTypeOnly Whether every reference from `file` to `dependency` is type-only\r\n */\r\n public addDependency(file: string, dependency: string, isTypeOnly: boolean = false) {\r\n if (!this.dependencies.has(file)) {\r\n this.dependencies.set(file, new Map());\r\n }\r\n\r\n if (!this.dependents.has(dependency)) {\r\n this.dependents.set(dependency, new Set());\r\n }\r\n\r\n const edges = this.dependencies.get(file)!;\r\n const existing = edges.get(dependency);\r\n\r\n if (existing) {\r\n existing.isTypeOnly = existing.isTypeOnly && isTypeOnly;\r\n } else {\r\n edges.set(dependency, { isTypeOnly });\r\n }\r\n\r\n this.dependents.get(dependency)!.add(file);\r\n }\r\n\r\n /**\r\n * Remove a dependency relationship\r\n * @param file The file that has the dependency\r\n * @param dependency The file being depended upon\r\n */\r\n public removeDependency(file: string, dependency: string) {\r\n this.dependencies.get(file)?.delete(dependency);\r\n this.dependents.get(dependency)?.delete(file);\r\n }\r\n\r\n /**\r\n * Remove all relationships for a file\r\n * @param file The file to remove\r\n */\r\n public removeFile(file: string) {\r\n const deps = this.dependencies.get(file);\r\n\r\n if (deps) {\r\n for (const dependency of deps.keys()) {\r\n this.dependents.get(dependency)?.delete(file);\r\n }\r\n }\r\n\r\n const dependents = this.dependents.get(file);\r\n\r\n if (dependents) {\r\n for (const dependent of dependents) {\r\n this.dependencies.get(dependent)?.delete(file);\r\n }\r\n }\r\n\r\n this.dependencies.delete(file);\r\n this.dependents.delete(file);\r\n }\r\n\r\n /**\r\n * Update dependencies for a file.\r\n *\r\n * Accepts the updated `Set<string>` of dependency paths; when the caller\r\n * has per-edge type-only info, pass `typeOnlyDependencies` so the new\r\n * edges get the correct flag. Missing flag defaults to runtime.\r\n *\r\n * @param file The file to update\r\n * @param newDependencies New set of dependency paths\r\n * @param typeOnlyDependencies Subset of `newDependencies` whose edges are type-only\r\n */\r\n public updateFile(\r\n file: string,\r\n newDependencies: Set<string>,\r\n typeOnlyDependencies: Set<string> = new Set(),\r\n ) {\r\n const oldEdges = this.dependencies.get(file) ?? new Map<string, { isTypeOnly: boolean }>();\r\n\r\n for (const oldDep of oldEdges.keys()) {\r\n if (!newDependencies.has(oldDep)) {\r\n this.removeDependency(file, oldDep);\r\n }\r\n }\r\n\r\n for (const newDep of newDependencies) {\r\n const isTypeOnly = typeOnlyDependencies.has(newDep);\r\n const existing = oldEdges.get(newDep);\r\n\r\n if (!existing) {\r\n this.addDependency(file, newDep, isTypeOnly);\r\n\r\n continue;\r\n }\r\n\r\n if (existing.isTypeOnly !== isTypeOnly) {\r\n existing.isTypeOnly = isTypeOnly;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get files that this file depends on (imports)\r\n * @param file The file to check\r\n * @returns Set of files this file imports\r\n */\r\n public getDependencies(file: string): Set<string> {\r\n const edges = this.dependencies.get(file);\r\n\r\n if (!edges) {\r\n return new Set();\r\n }\r\n\r\n return new Set(edges.keys());\r\n }\r\n\r\n /**\r\n * Check whether the edge `file → dependency` is type-only.\r\n * Returns false if the edge does not exist.\r\n */\r\n public isEdgeTypeOnly(file: string, dependency: string): boolean {\r\n return this.dependencies.get(file)?.get(dependency)?.isTypeOnly === true;\r\n }\r\n\r\n /**\r\n * Get files that depend on this file (importers)\r\n * @param file The file to check\r\n * @returns Set of files that import this file\r\n */\r\n public getDependents(file: string): Set<string> {\r\n return this.dependents.get(file) || new Set();\r\n }\r\n\r\n /**\r\n * Get invalidation chain for a file\r\n * Returns all files that need to be reloaded when this file changes\r\n * Includes the file itself and all transitive dependents\r\n * @param file The file that changed\r\n * @returns Array of files to invalidate (in order)\r\n */\r\n public getInvalidationChain(file: string): string[] {\r\n const chain: string[] = [file];\r\n const visited = new Set([file]);\r\n\r\n const traverse = (current: string) => {\r\n const deps = this.getDependents(current);\r\n for (const dep of deps) {\r\n if (!visited.has(dep)) {\r\n visited.add(dep);\r\n chain.push(dep);\r\n traverse(dep); // Recursive traversal\r\n }\r\n }\r\n };\r\n\r\n traverse(file);\r\n return chain;\r\n }\r\n\r\n /**\r\n * Detect circular dependencies in the dependency graph.\r\n * Uses depth-first search to find cycles.\r\n *\r\n * Filtering rule (per-edge): a cycle is suppressed when **any** edge in\r\n * the chain is type-only. TypeScript erases type-only imports entirely,\r\n * so a single type-only edge breaks the cycle at runtime. Only warn when\r\n * every edge is a runtime binding — that's the cycle that actually loops\r\n * at load time.\r\n *\r\n * @returns Array of circular dependency chains (each chain is an array of file paths)\r\n */\r\n public detectCircularDependencies(): string[][] {\r\n const cycles: string[][] = [];\r\n const visited = new Set<string>();\r\n const recursionStack = new Set<string>();\r\n\r\n const dfs = (file: string, path: string[]): void => {\r\n visited.add(file);\r\n recursionStack.add(file);\r\n path.push(file);\r\n\r\n const edges = this.dependencies.get(file);\r\n\r\n if (edges) {\r\n for (const dep of edges.keys()) {\r\n if (!visited.has(dep)) {\r\n dfs(dep, [...path]);\r\n } else if (recursionStack.has(dep)) {\r\n const cycleStart = path.indexOf(dep);\r\n const cycle = [...path.slice(cycleStart), dep];\r\n\r\n cycles.push(cycle);\r\n }\r\n }\r\n }\r\n\r\n recursionStack.delete(file);\r\n };\r\n\r\n for (const file of this.dependencies.keys()) {\r\n if (!visited.has(file)) {\r\n dfs(file, []);\r\n }\r\n }\r\n\r\n return cycles.filter((cycle) => this.isRuntimeCycle(cycle));\r\n }\r\n\r\n /**\r\n * True iff every edge in the cycle is a runtime binding. The cycle array\r\n * ends with a duplicate of its start (`[A, B, C, A]`) so we walk the\r\n * consecutive pairs once — any type-only edge means TS erases the loop\r\n * and there is no runtime cycle to report.\r\n */\r\n private isRuntimeCycle(cycle: string[]): boolean {\r\n for (let index = 0; index < cycle.length - 1; index++) {\r\n const from = cycle[index];\r\n const to = cycle[index + 1];\r\n\r\n if (this.isEdgeTypeOnly(from, to)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Display circular dependency warnings in a formatted, user-friendly way\r\n * Shows each cycle with visual tree structure and helpful recommendations\r\n */\r\n private displayCircularDependencyWarnings(cycles: string[][]): void {\r\n const colors = {\r\n yellow: (text: string) => `\\x1b[33m${text}\\x1b[0m`,\r\n cyan: (text: string) => `\\x1b[36m${text}\\x1b[0m`,\r\n dim: (text: string) => `\\x1b[2m${text}\\x1b[0m`,\r\n bold: (text: string) => `\\x1b[1m${text}\\x1b[0m`,\r\n green: (text: string) => `\\x1b[32m${text}\\x1b[0m`,\r\n };\r\n\r\n console.log(\"\");\r\n console.log(colors.yellow(\"⚠️ Circular Dependencies Detected\"));\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(\"\");\r\n console.log(\r\n colors.dim(\r\n `Found ${colors.bold(cycles.length.toString())} circular dependency chain${cycles.length > 1 ? \"s\" : \"\"}`,\r\n ),\r\n );\r\n console.log(\"\");\r\n\r\n cycles.forEach((cycle, index) => {\r\n console.log(colors.cyan(` ${index + 1}. Cycle with ${cycle.length - 1} files:`));\r\n console.log(\"\");\r\n\r\n // Display the cycle chain\r\n cycle.forEach((file, fileIndex) => {\r\n const isLast = fileIndex === cycle.length - 1;\r\n const arrow = isLast ? colors.dim(\" └─→ \") : colors.dim(\" ├─→ \");\r\n const fileName = file.split(\"/\").pop() || file;\r\n const filePath = colors.dim(file.replace(fileName, \"\"));\r\n\r\n if (isLast) {\r\n // Last item is the same as first (completes the cycle)\r\n console.log(\r\n arrow + colors.yellow(`${filePath}${colors.bold(fileName)} (cycle completes)`),\r\n );\r\n } else {\r\n console.log(arrow + filePath + colors.bold(fileName));\r\n }\r\n });\r\n\r\n console.log(\"\");\r\n });\r\n\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(colors.yellow(\"💡 How to Fix:\"));\r\n console.log(\"\");\r\n\r\n const exampleCycle = cycles[0];\r\n const fileA = exampleCycle[0]?.split(\"/\").pop() || \"fileA.ts\";\r\n const fileB = exampleCycle[1]?.split(\"/\").pop() || \"fileB.ts\";\r\n const stripExt = (file: string) => file.replace(/\\.tsx?$/, \"\");\r\n\r\n const kind = classifyCycle(exampleCycle);\r\n\r\n if (kind === \"resource\" || kind === \"model\") {\r\n // Cascade's `lazy()` is purpose-built for circular relations between\r\n // resources and models — recommend it first, not as a footnote.\r\n console.log(colors.green(` ✨ Use lazy() for the back-reference`));\r\n console.log(\r\n colors.dim(\r\n ` ${kind === \"resource\" ? \"Resources\" : \"Models\"} that reference each other should declare`,\r\n ),\r\n );\r\n console.log(colors.dim(` one side with lazy() so the import resolves at access time:`));\r\n console.log(\"\");\r\n console.log(colors.dim(` import { lazy } from \"@warlock.js/cascade\";`));\r\n console.log(colors.dim(` // inside ${fileB}:`));\r\n console.log(colors.dim(` other: lazy(() => ${kind === \"resource\" ? \"OtherResource\" : \"OtherModel\"})`));\r\n console.log(\"\");\r\n } else {\r\n // Generic cycle — dynamic import is the most common reliable fix.\r\n console.log(colors.green(` ✨ Convert one edge to a dynamic import`));\r\n console.log(colors.dim(` In ${fileB}, change:`));\r\n console.log(colors.dim(` import { SomeClass } from \"./${stripExt(fileA)}\";`));\r\n console.log(colors.dim(` To:`));\r\n console.log(\r\n colors.dim(` const { SomeClass } = await import(\"./${stripExt(fileA)}\");`),\r\n );\r\n console.log(\"\");\r\n }\r\n\r\n console.log(colors.dim(\" Other options:\"));\r\n console.log(colors.dim(\" • Extract the shared symbols into a third file both can import.\"));\r\n console.log(colors.dim(\" • Pass the dependency as a constructor/function parameter instead.\"));\r\n console.log(\"\");\r\n\r\n console.log(colors.dim(\"━\".repeat(60)));\r\n console.log(colors.dim(\" Circular dependencies can cause HMR issues and hard-to-debug\"));\r\n console.log(colors.dim(\" initialization order problems. Consider refactoring.\"));\r\n console.log(\"\");\r\n }\r\n\r\n /**\r\n * Get statistics about the dependency graph\r\n */\r\n public getStats() {\r\n let totalDependencies = 0;\r\n let maxDependencies = 0;\r\n let maxDependents = 0;\r\n let mostDependedFile = \"\";\r\n let mostDependingFile = \"\";\r\n\r\n for (const [file, deps] of this.dependencies) {\r\n totalDependencies += deps.size;\r\n if (deps.size > maxDependencies) {\r\n maxDependencies = deps.size;\r\n mostDependingFile = file;\r\n }\r\n }\r\n\r\n for (const [file, deps] of this.dependents) {\r\n if (deps.size > maxDependents) {\r\n maxDependents = deps.size;\r\n mostDependedFile = file;\r\n }\r\n }\r\n\r\n return {\r\n totalFiles: this.dependencies.size,\r\n totalDependencies,\r\n avgDependenciesPerFile: totalDependencies / this.dependencies.size || 0,\r\n maxDependencies,\r\n maxDependents,\r\n mostDependingFile,\r\n mostDependedFile,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Pick the most useful \"fix-it\" hint for a cycle by inspecting the file\r\n * extensions involved. Cascade resources/models have a first-class fix\r\n * (`lazy()`); generic cycles just want a dynamic import.\r\n */\r\nfunction classifyCycle(cycle: string[]): \"resource\" | \"model\" | \"generic\" {\r\n const files = cycle.slice(0, -1); // last entry repeats the first\r\n if (files.every(file => file.endsWith(\".resource.ts\") || file.endsWith(\".resource.tsx\"))) {\r\n return \"resource\";\r\n }\r\n if (files.every(file => file.endsWith(\".model.ts\") || file.endsWith(\".model.tsx\"))) {\r\n return \"model\";\r\n }\r\n return \"generic\";\r\n}\r\n"],"mappings":"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"}