@warlock.js/core 4.10.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/esm/application/application.d.mts +19 -0
  3. package/esm/application/application.d.mts.map +1 -1
  4. package/esm/application/application.mjs +25 -0
  5. package/esm/application/application.mjs.map +1 -1
  6. package/esm/application/boot-signal.d.mts +77 -0
  7. package/esm/application/boot-signal.d.mts.map +1 -0
  8. package/esm/application/boot-signal.mjs +64 -0
  9. package/esm/application/boot-signal.mjs.map +1 -0
  10. package/esm/application/index.d.mts +1 -0
  11. package/esm/application/index.mjs +1 -0
  12. package/esm/bootstrap.mjs +2 -2
  13. package/esm/bootstrap.mjs.map +1 -1
  14. package/esm/cli/cli-commands.manager.mjs +31 -8
  15. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  16. package/esm/cli/cli-commands.utils.mjs +57 -1
  17. package/esm/cli/cli-commands.utils.mjs.map +1 -1
  18. package/esm/cli/commands/add.command.mjs +4 -2
  19. package/esm/cli/commands/add.command.mjs.map +1 -1
  20. package/esm/cli/commands/generate/generate.command.mjs +42 -21
  21. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  22. package/esm/cli/commands/generate/generators/module.generator.mjs +1 -1
  23. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  24. package/esm/cli/commands/migrate.command.mjs +6 -2
  25. package/esm/cli/commands/migrate.command.mjs.map +1 -1
  26. package/esm/cli/commands/start-production.command.mjs +4 -25
  27. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  28. package/esm/cli/parse-cli-args.mjs +86 -9
  29. package/esm/cli/parse-cli-args.mjs.map +1 -1
  30. package/esm/cli/types.d.mts +6 -1
  31. package/esm/cli/types.d.mts.map +1 -1
  32. package/esm/config/config-getter.mjs +5 -5
  33. package/esm/config/config-getter.mjs.map +1 -1
  34. package/esm/config/config-loader.mjs +2 -2
  35. package/esm/config/config-loader.mjs.map +1 -1
  36. package/esm/config/config-setter.d.mts +27 -0
  37. package/esm/config/config-setter.d.mts.map +1 -0
  38. package/esm/config/config-setter.mjs +10 -0
  39. package/esm/config/config-setter.mjs.map +1 -0
  40. package/esm/config/index.mjs +1 -0
  41. package/esm/connectors/access-connector.mjs +2 -2
  42. package/esm/connectors/access-connector.mjs.map +1 -1
  43. package/esm/connectors/ai-connector.mjs +2 -2
  44. package/esm/connectors/ai-connector.mjs.map +1 -1
  45. package/esm/connectors/cache-connector.mjs +2 -2
  46. package/esm/connectors/cache-connector.mjs.map +1 -1
  47. package/esm/connectors/database-connector.mjs +2 -2
  48. package/esm/connectors/database-connector.mjs.map +1 -1
  49. package/esm/connectors/herald-connector.mjs +2 -2
  50. package/esm/connectors/herald-connector.mjs.map +1 -1
  51. package/esm/connectors/http-connector.d.mts.map +1 -1
  52. package/esm/connectors/http-connector.mjs +8 -7
  53. package/esm/connectors/http-connector.mjs.map +1 -1
  54. package/esm/connectors/logger-connector.mjs +2 -2
  55. package/esm/connectors/logger-connector.mjs.map +1 -1
  56. package/esm/connectors/mail-connector.mjs +2 -2
  57. package/esm/connectors/mail-connector.mjs.map +1 -1
  58. package/esm/connectors/notifications-connector.mjs +2 -2
  59. package/esm/connectors/notifications-connector.mjs.map +1 -1
  60. package/esm/connectors/socket-connector.mjs +3 -3
  61. package/esm/connectors/socket-connector.mjs.map +1 -1
  62. package/esm/connectors/storage.connector.d.mts +11 -5
  63. package/esm/connectors/storage.connector.d.mts.map +1 -1
  64. package/esm/connectors/storage.connector.mjs +11 -5
  65. package/esm/connectors/storage.connector.mjs.map +1 -1
  66. package/esm/database/migrate-action.mjs +53 -1
  67. package/esm/database/migrate-action.mjs.map +1 -1
  68. package/esm/database/pending-exit-code.mjs +39 -0
  69. package/esm/database/pending-exit-code.mjs.map +1 -0
  70. package/esm/database/resolve-pending-migrations.mjs +49 -0
  71. package/esm/database/resolve-pending-migrations.mjs.map +1 -0
  72. package/esm/dev-server/loader/load-hook.mjs +1 -1
  73. package/esm/generations/stubs.mjs +1 -1
  74. package/esm/generations/stubs.mjs.map +1 -1
  75. package/esm/http/config.mjs +2 -2
  76. package/esm/http/config.mjs.map +1 -1
  77. package/esm/http/createHttpApplication.mjs +2 -2
  78. package/esm/http/createHttpApplication.mjs.map +1 -1
  79. package/esm/http/index.d.mts +1 -0
  80. package/esm/http/index.mjs +1 -0
  81. package/esm/http/middleware/idempotency.middleware.mjs +5 -5
  82. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  83. package/esm/http/middleware/inject-request-context.mjs +2 -2
  84. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  85. package/esm/http/middleware/maintenance.middleware.mjs +4 -4
  86. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  87. package/esm/http/plugins.mjs +9 -9
  88. package/esm/http/plugins.mjs.map +1 -1
  89. package/esm/http/port-preflight.d.mts +34 -0
  90. package/esm/http/port-preflight.d.mts.map +1 -0
  91. package/esm/http/port-preflight.mjs +66 -0
  92. package/esm/http/port-preflight.mjs.map +1 -0
  93. package/esm/http/response.mjs +5 -5
  94. package/esm/http/response.mjs.map +1 -1
  95. package/esm/http/server.mjs +4 -4
  96. package/esm/http/server.mjs.map +1 -1
  97. package/esm/image/image.d.mts +3 -2
  98. package/esm/image/image.d.mts.map +1 -1
  99. package/esm/image/image.mjs +63 -17
  100. package/esm/image/image.mjs.map +1 -1
  101. package/esm/index.d.mts +6 -2
  102. package/esm/index.mjs +5 -1
  103. package/esm/mail/mailer-pool.d.mts.map +1 -1
  104. package/esm/mail/mailer-pool.mjs.map +1 -1
  105. package/esm/production/assert-generated-imports.mjs +94 -0
  106. package/esm/production/assert-generated-imports.mjs.map +1 -0
  107. package/esm/production/production-builder.mjs +29 -4
  108. package/esm/production/production-builder.mjs.map +1 -1
  109. package/esm/production/production-supervisor.mjs +108 -0
  110. package/esm/production/production-supervisor.mjs.map +1 -0
  111. package/esm/react/index.d.mts +4 -0
  112. package/esm/react/index.d.mts.map +1 -1
  113. package/esm/react/index.mjs +88 -14
  114. package/esm/react/index.mjs.map +1 -1
  115. package/esm/storage/storage.d.mts +16 -0
  116. package/esm/storage/storage.d.mts.map +1 -1
  117. package/esm/storage/storage.mjs +24 -1
  118. package/esm/storage/storage.mjs.map +1 -1
  119. package/esm/tests/index.d.mts +2 -1
  120. package/esm/tests/index.mjs +1 -0
  121. package/esm/tests/start-http-development-server.d.mts +15 -2
  122. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  123. package/esm/tests/start-http-development-server.mjs +43 -16
  124. package/esm/tests/start-http-development-server.mjs.map +1 -1
  125. package/esm/tests/test-helpers.d.mts.map +1 -1
  126. package/esm/tests/test-helpers.mjs +2 -1
  127. package/esm/tests/test-helpers.mjs.map +1 -1
  128. package/esm/tests/test-server-port-channel.d.mts +27 -0
  129. package/esm/tests/test-server-port-channel.d.mts.map +1 -0
  130. package/esm/tests/test-server-port-channel.mjs +32 -0
  131. package/esm/tests/test-server-port-channel.mjs.map +1 -0
  132. package/esm/utils/load-environment.mjs +59 -0
  133. package/esm/utils/load-environment.mjs.map +1 -0
  134. package/esm/utils/paths.mjs +2 -2
  135. package/esm/utils/paths.mjs.map +1 -1
  136. package/esm/validation/validateAll.mjs +2 -2
  137. package/esm/validation/validateAll.mjs.map +1 -1
  138. package/llms-full.txt +90 -3
  139. package/llms.txt +1 -1
  140. package/package.json +13 -13
  141. package/skills/process-image/SKILL.md +3 -1
  142. package/skills/run-app/SKILL.md +63 -0
  143. package/skills/test-http/SKILL.md +27 -3
  144. package/skills/write-cli-command/SKILL.md +19 -1
@@ -9,7 +9,7 @@ import { health } from "../http/health.mjs";
9
9
  import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
10
10
  import { BaseConnector } from "./base-connector.mjs";
11
11
  import { devLogError } from "../dev-server/dev-logger.mjs";
12
- import baseConfig from "@mongez/config";
12
+ import config from "@mongez/config";
13
13
  import { log } from "@warlock.js/logger";
14
14
  import { colors } from "@mongez/copper";
15
15
 
@@ -45,14 +45,14 @@ var HttpConnector = class extends BaseConnector {
45
45
  * `start()` so it reads the router after app code has registered.
46
46
  */
47
47
  async boot() {
48
- const httpConfig = baseConfig.get("http");
48
+ const httpConfig = config.get("http");
49
49
  if (!httpConfig) return;
50
50
  const port = httpConfig.port;
51
51
  log.info(`http`, "connection", `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`);
52
52
  this.http = startHttpServer(httpConfig.serverOptions);
53
53
  container.set("http.server", this.http);
54
54
  await registerHttpPlugins(this.http);
55
- setBaseUrl(baseConfig.get("app.baseUrl"));
55
+ setBaseUrl(config.get("app.baseUrl"));
56
56
  this.registerHealthRoutes();
57
57
  }
58
58
  /**
@@ -64,7 +64,7 @@ var HttpConnector = class extends BaseConnector {
64
64
  */
65
65
  registerHealthRoutes() {
66
66
  if (!this.http) return;
67
- const healthConfig = baseConfig.get("http.health");
67
+ const healthConfig = config.get("http.health");
68
68
  if (healthConfig?.enabled === false) return;
69
69
  const livenessPath = healthConfig?.path ?? "/health";
70
70
  const readinessPath = healthConfig?.readinessPath ?? "/ready";
@@ -83,7 +83,7 @@ var HttpConnector = class extends BaseConnector {
83
83
  * app code without losing routes.
84
84
  */
85
85
  async start() {
86
- const httpConfig = baseConfig.get("http");
86
+ const httpConfig = config.get("http");
87
87
  if (!httpConfig || !this.http) return;
88
88
  if (Application.runtimeStrategy === "development") router.scanDevServer(this.http);
89
89
  else router.scan(this.http);
@@ -94,7 +94,8 @@ var HttpConnector = class extends BaseConnector {
94
94
  port: httpConfig.port,
95
95
  host: httpConfig.host || "localhost"
96
96
  });
97
- const baseUrl = baseConfig.get("app.baseUrl");
97
+ Application.setServedPort(httpConfig.port);
98
+ const baseUrl = config.get("app.baseUrl");
98
99
  log.success(`http`, "connection", `Server ready at ${baseUrl}`);
99
100
  } catch (error) {
100
101
  devLogError("Error while starting http server", error);
@@ -121,7 +122,7 @@ var HttpConnector = class extends BaseConnector {
121
122
  if (!this.active) return;
122
123
  const server = getHttpServer();
123
124
  if (server) {
124
- const timeout = baseConfig.get("http.gracefulShutdown.timeout", DEFAULT_SHUTDOWN_TIMEOUT);
125
+ const timeout = config.get("http.gracefulShutdown.timeout", DEFAULT_SHUTDOWN_TIMEOUT);
125
126
  if (!await closeServerWithTimeout(server, timeout)) log.warn("http", "shutdown", `In-flight requests did not drain within ${timeout}ms; closing anyway`);
126
127
  }
127
128
  this.active = false;
@@ -1 +1 @@
1
- {"version":3,"file":"http-connector.mjs","names":["config"],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { Application } from \"../application\";\r\nimport { devLogError } from \"../dev-server/dev-logger\";\r\nimport { health } from \"../http/health\";\r\nimport { registerHttpPlugins } from \"../http/plugins\";\r\nimport { closeServerWithTimeout, FastifyInstance, getHttpServer, startHttpServer } from \"../http/server\";\r\nimport { router } from \"../router/router\";\r\nimport { Environment } from \"../utils\";\r\nimport { setBaseUrl } from \"../utils/urls\";\r\nimport { container } from \"./../container\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\nfunction environmentColor(environment: Environment) {\r\n switch (environment) {\r\n case \"development\":\r\n return colors.magentaBright(environment);\r\n case \"test\":\r\n return colors.yellowBright(environment);\r\n case \"production\":\r\n return colors.greenBright(environment);\r\n default:\r\n return colors.white(environment);\r\n }\r\n}\r\n\r\n/**\r\n * Default time to wait for in-flight requests to drain on shutdown before the\r\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\n/**\r\n * HTTP Connector\r\n * Manages HTTP server (Fastify) lifecycle\r\n */\r\nexport class HttpConnector extends BaseConnector {\r\n public readonly name = \"http\";\r\n public readonly priority = ConnectorPriority.HTTP;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\r\n\r\n /**\r\n * Files that trigger HTTP server restart\r\n * Note: routes.ts changes will be handled by HMR with wildcard routing\r\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\r\n */\r\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\r\n\r\n /**\r\n * Fastify Server instance\r\n */\r\n protected http?: FastifyInstance;\r\n\r\n /**\r\n * Boot the connector — construction only (create Fastify, register\r\n * plugins, populate container). Route scanning is deferred to\r\n * `start()` so it reads the router after app code has registered.\r\n */\r\n public async boot() {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig) return;\r\n\r\n const port = httpConfig.port;\r\n log.info(\r\n `http`,\r\n \"connection\",\r\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\r\n );\r\n\r\n this.http = startHttpServer(httpConfig.serverOptions);\r\n\r\n container.set(\"http.server\", this.http);\r\n\r\n await registerHttpPlugins(this.http);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n // update base url\r\n setBaseUrl(baseUrl);\r\n\r\n // Liveness/readiness endpoints — registered on Fastify before start() scans\r\n // the app router, so they exist by the time the server listens.\r\n this.registerHealthRoutes();\r\n }\r\n\r\n /**\r\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\r\n * endpoints directly on the Fastify instance — infra routes, kept off the app\r\n * router so they're immune to HMR and never collide with route scanning. Opt\r\n * out via `http.health.enabled = false`; rename via `http.health.path` /\r\n * `http.health.readinessPath`.\r\n */\r\n private registerHealthRoutes(): void {\r\n if (!this.http) {\r\n return;\r\n }\r\n\r\n const healthConfig = config.get(\"http.health\");\r\n\r\n if (healthConfig?.enabled === false) {\r\n return;\r\n }\r\n\r\n const livenessPath = healthConfig?.path ?? \"/health\";\r\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\r\n\r\n this.http.get(livenessPath, async (_request: any, reply: any) => {\r\n const result = health.liveness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n\r\n this.http.get(readinessPath, async (_request: any, reply: any) => {\r\n const result = await health.readiness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n }\r\n\r\n /**\r\n * Initialize HTTP server — bind app-registered routes to Fastify\r\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\r\n * app code without losing routes.\r\n */\r\n public async start(): Promise<void> {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig || !this.http) return;\r\n\r\n if (Application.runtimeStrategy === \"development\") {\r\n router.scanDevServer(this.http);\r\n } else {\r\n router.scan(this.http);\r\n }\r\n\r\n // Surface the route table as a readiness signal + a boot log, so an empty\r\n // or partial route surface (e.g. a route module that failed to register) is\r\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\r\n // keyed by name, so re-running on restart is idempotent.\r\n health.addRoutesRegisteredCheck(() => router.routeCount());\r\n log.info(\"http\", \"routes\", `${router.routeCount()} route(s) registered`);\r\n\r\n try {\r\n // We can use the url of the server\r\n await this.http.listen({\r\n port: httpConfig.port,\r\n host: httpConfig.host || \"localhost\",\r\n });\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n log.success(`http`, \"connection\", `Server ready at ${baseUrl}`);\r\n } catch (error) {\r\n devLogError(\"Error while starting http server\", error);\r\n\r\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\r\n // devLogError above is the dev-server console UI and bypasses the logger\r\n // pipeline, so also route it through `log.fatal` to reach Sentry/file,\r\n // then `await log.flush()` to drain buffered/async channels before exit.\r\n await log.fatal(\"http\", \"connection\", error);\r\n await log.flush();\r\n\r\n process.exit(1); // stop the process, exit with error\r\n }\r\n\r\n this.active = true;\r\n }\r\n\r\n /**\r\n * Restart — needs a fresh Fastify instance since `start()` now\r\n * re-runs `router.scan()`, and re-scanning the same Fastify would\r\n * register duplicate route handlers.\r\n */\r\n public async restart(): Promise<void> {\r\n await this.shutdown();\r\n await this.boot();\r\n await this.start();\r\n }\r\n\r\n /**\r\n * Shutdown HTTP server\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n const server = getHttpServer();\r\n\r\n if (server) {\r\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n const drained = await closeServerWithTimeout(server, timeout);\r\n\r\n if (!drained) {\r\n log.warn(\r\n \"http\",\r\n \"shutdown\",\r\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\r\n );\r\n }\r\n }\r\n\r\n this.active = false;\r\n }\r\n\r\n /**\r\n * Override shouldRestart to handle routes.ts specially\r\n * routes.ts changes should NOT restart the server (use HMR instead)\r\n * Now receives config file paths directly from layer-executor\r\n */\r\n public shouldRestart(changedFiles: string[]): boolean {\r\n // Only restart for config changes, not routes\r\n return changedFiles.some((file) => {\r\n const relativePath = file.replace(/\\\\/g, \"/\");\r\n return this.watchedFiles.includes(relativePath);\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { Application } from \"../application\";\r\nimport { devLogError } from \"../dev-server/dev-logger\";\r\nimport { health } from \"../http/health\";\r\nimport { registerHttpPlugins } from \"../http/plugins\";\r\nimport { closeServerWithTimeout, FastifyInstance, getHttpServer, startHttpServer } from \"../http/server\";\r\nimport { router } from \"../router/router\";\r\nimport { Environment } from \"../utils\";\r\nimport { setBaseUrl } from \"../utils/urls\";\r\nimport { container } from \"./../container\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\nfunction environmentColor(environment: Environment) {\r\n switch (environment) {\r\n case \"development\":\r\n return colors.magentaBright(environment);\r\n case \"test\":\r\n return colors.yellowBright(environment);\r\n case \"production\":\r\n return colors.greenBright(environment);\r\n default:\r\n return colors.white(environment);\r\n }\r\n}\r\n\r\n/**\r\n * Default time to wait for in-flight requests to drain on shutdown before the\r\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\n/**\r\n * HTTP Connector\r\n * Manages HTTP server (Fastify) lifecycle\r\n */\r\nexport class HttpConnector extends BaseConnector {\r\n public readonly name = \"http\";\r\n public readonly priority = ConnectorPriority.HTTP;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\r\n\r\n /**\r\n * Files that trigger HTTP server restart\r\n * Note: routes.ts changes will be handled by HMR with wildcard routing\r\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\r\n */\r\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\r\n\r\n /**\r\n * Fastify Server instance\r\n */\r\n protected http?: FastifyInstance;\r\n\r\n /**\r\n * Boot the connector — construction only (create Fastify, register\r\n * plugins, populate container). Route scanning is deferred to\r\n * `start()` so it reads the router after app code has registered.\r\n */\r\n public async boot() {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig) return;\r\n\r\n const port = httpConfig.port;\r\n log.info(\r\n `http`,\r\n \"connection\",\r\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\r\n );\r\n\r\n this.http = startHttpServer(httpConfig.serverOptions);\r\n\r\n container.set(\"http.server\", this.http);\r\n\r\n await registerHttpPlugins(this.http);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n // update base url\r\n setBaseUrl(baseUrl);\r\n\r\n // Liveness/readiness endpoints — registered on Fastify before start() scans\r\n // the app router, so they exist by the time the server listens.\r\n this.registerHealthRoutes();\r\n }\r\n\r\n /**\r\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\r\n * endpoints directly on the Fastify instance — infra routes, kept off the app\r\n * router so they're immune to HMR and never collide with route scanning. Opt\r\n * out via `http.health.enabled = false`; rename via `http.health.path` /\r\n * `http.health.readinessPath`.\r\n */\r\n private registerHealthRoutes(): void {\r\n if (!this.http) {\r\n return;\r\n }\r\n\r\n const healthConfig = config.get(\"http.health\");\r\n\r\n if (healthConfig?.enabled === false) {\r\n return;\r\n }\r\n\r\n const livenessPath = healthConfig?.path ?? \"/health\";\r\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\r\n\r\n this.http.get(livenessPath, async (_request: any, reply: any) => {\r\n const result = health.liveness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n\r\n this.http.get(readinessPath, async (_request: any, reply: any) => {\r\n const result = await health.readiness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n }\r\n\r\n /**\r\n * Initialize HTTP server — bind app-registered routes to Fastify\r\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\r\n * app code without losing routes.\r\n */\r\n public async start(): Promise<void> {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig || !this.http) return;\r\n\r\n if (Application.runtimeStrategy === \"development\") {\r\n router.scanDevServer(this.http);\r\n } else {\r\n router.scan(this.http);\r\n }\r\n\r\n // Surface the route table as a readiness signal + a boot log, so an empty\r\n // or partial route surface (e.g. a route module that failed to register) is\r\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\r\n // keyed by name, so re-running on restart is idempotent.\r\n health.addRoutesRegisteredCheck(() => router.routeCount());\r\n log.info(\"http\", \"routes\", `${router.routeCount()} route(s) registered`);\r\n\r\n try {\r\n // We can use the url of the server\r\n await this.http.listen({\r\n port: httpConfig.port,\r\n host: httpConfig.host || \"localhost\",\r\n });\r\n\r\n Application.setServedPort(httpConfig.port);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n log.success(`http`, \"connection\", `Server ready at ${baseUrl}`);\r\n } catch (error) {\r\n devLogError(\"Error while starting http server\", error);\r\n\r\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\r\n // devLogError above is the dev-server console UI and bypasses the logger\r\n // pipeline, so also route it through `log.fatal` to reach Sentry/file,\r\n // then `await log.flush()` to drain buffered/async channels before exit.\r\n await log.fatal(\"http\", \"connection\", error);\r\n await log.flush();\r\n\r\n process.exit(1); // stop the process, exit with error\r\n }\r\n\r\n this.active = true;\r\n }\r\n\r\n /**\r\n * Restart — needs a fresh Fastify instance since `start()` now\r\n * re-runs `router.scan()`, and re-scanning the same Fastify would\r\n * register duplicate route handlers.\r\n */\r\n public async restart(): Promise<void> {\r\n await this.shutdown();\r\n await this.boot();\r\n await this.start();\r\n }\r\n\r\n /**\r\n * Shutdown HTTP server\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n const server = getHttpServer();\r\n\r\n if (server) {\r\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n const drained = await closeServerWithTimeout(server, timeout);\r\n\r\n if (!drained) {\r\n log.warn(\r\n \"http\",\r\n \"shutdown\",\r\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\r\n );\r\n }\r\n }\r\n\r\n this.active = false;\r\n }\r\n\r\n /**\r\n * Override shouldRestart to handle routes.ts specially\r\n * routes.ts changes should NOT restart the server (use HMR instead)\r\n * Now receives config file paths directly from layer-executor\r\n */\r\n public shouldRestart(changedFiles: string[]): boolean {\r\n // Only restart for config changes, not routes\r\n return changedFiles.some((file) => {\r\n const relativePath = file.replace(/\\\\/g, \"/\");\r\n return this.watchedFiles.includes(relativePath);\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
2
2
  import { BaseConnector } from "./base-connector.mjs";
3
3
  import { setLogConfigurations } from "../logger/logger.mjs";
4
4
  import "../logger/index.mjs";
5
- import baseConfig from "@mongez/config";
5
+ import config from "@mongez/config";
6
6
  import { log } from "@warlock.js/logger";
7
7
 
8
8
  //#region ../core/src/connectors/logger-connector.ts
@@ -22,7 +22,7 @@ var LoggerConnector = class extends BaseConnector {
22
22
  * Initialize logger configurations
23
23
  */
24
24
  async start() {
25
- const logConfig = baseConfig.get("log");
25
+ const logConfig = config.get("log");
26
26
  if (!logConfig) return;
27
27
  try {
28
28
  setLogConfigurations(logConfig);
@@ -1 +1 @@
1
- {"version":3,"file":"logger-connector.mjs","names":["config"],"sources":["../../../../../../../core/src/connectors/logger-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { setLogConfigurations } from \"../logger\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Logger Connector\r\n * Manages logger lifecycle and ensures synchronous flushing on termination\r\n */\r\nexport class LoggerConnector extends BaseConnector {\r\n public readonly name = \"logger\";\r\n public readonly priority = ConnectorPriority.LOGGER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger logger restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/log.ts\", \"src/config/log.tsx\"];\r\n\r\n /**\r\n * Initialize logger configurations\r\n */\r\n public async start(): Promise<void> {\r\n const logConfig = config.get(\"log\");\r\n\r\n if (!logConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setLogConfigurations(logConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize logger:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown logger and flush messages synchronously\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n log.flushSync();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to flush logger:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAUA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,qBAAqB,oBAAoB;;;;;CAK5E,MAAa,QAAuB;EAClC,MAAM,YAAYA,WAAO,IAAI,KAAK;EAElC,IAAI,CAAC,WACH;EAGF,IAAI;GACF,qBAAqB,SAAS;GAC9B,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,IAAI,UAAU;GACd,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,KAAK;GAC9C,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"logger-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/logger-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { setLogConfigurations } from \"../logger\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Logger Connector\r\n * Manages logger lifecycle and ensures synchronous flushing on termination\r\n */\r\nexport class LoggerConnector extends BaseConnector {\r\n public readonly name = \"logger\";\r\n public readonly priority = ConnectorPriority.LOGGER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger logger restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/log.ts\", \"src/config/log.tsx\"];\r\n\r\n /**\r\n * Initialize logger configurations\r\n */\r\n public async start(): Promise<void> {\r\n const logConfig = config.get(\"log\");\r\n\r\n if (!logConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setLogConfigurations(logConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize logger:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown logger and flush messages synchronously\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n log.flushSync();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to flush logger:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAUA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,qBAAqB,oBAAoB;;;;;CAK5E,MAAa,QAAuB;EAClC,MAAM,YAAY,OAAO,IAAI,KAAK;EAElC,IAAI,CAAC,WACH;EAGF,IAAI;GACF,qBAAqB,SAAS;GAC9B,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,IAAI,UAAU;GACd,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,KAAK;GAC9C,MAAM;EACR;CACF;AACF"}
@@ -3,7 +3,7 @@ import { BaseConnector } from "./base-connector.mjs";
3
3
  import { setMailConfigurations } from "../mail/config.mjs";
4
4
  import { closeAllMailers } from "../mail/mailer-pool.mjs";
5
5
  import "../mail/index.mjs";
6
- import baseConfig from "@mongez/config";
6
+ import config from "@mongez/config";
7
7
 
8
8
  //#region ../core/src/connectors/mail-connector.ts
9
9
  /**
@@ -26,7 +26,7 @@ var MailerConnector = class extends BaseConnector {
26
26
  * Initialize mailer configurations
27
27
  */
28
28
  async start() {
29
- const mailConfig = baseConfig.get("mail");
29
+ const mailConfig = config.get("mail");
30
30
  if (!mailConfig) return;
31
31
  try {
32
32
  setMailConfigurations(mailConfig);
@@ -1 +1 @@
1
- {"version":3,"file":"mail-connector.mjs","names":["config"],"sources":["../../../../../../../core/src/connectors/mail-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { closeAllMailers, setMailConfigurations } from \"../mail\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Mailer Connector\r\n * Manages mailer lifecycle and ensures graceful pool shutdown\r\n */\r\nexport class MailerConnector extends BaseConnector {\r\n public readonly name = \"mailer\";\r\n public readonly priority = ConnectorPriority.MAILER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger mailer restart\r\n */\r\n protected readonly watchedFiles = [\".env\", \"src/config/mail.ts\", \"src/config/mail.tsx\"];\r\n\r\n /**\r\n * Initialize mailer configurations\r\n */\r\n public async start(): Promise<void> {\r\n const mailConfig = config.get(\"mail\");\r\n\r\n if (!mailConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setMailConfigurations(mailConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize mailer:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown mailer pool\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n closeAllMailers();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to close all mailers:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW;GAAC;GAAQ;GAAsB;EAAqB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,aAAaA,WAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YACH;EAGF,IAAI;GACF,sBAAsB,UAAU;GAChC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,gBAAgB;GAChB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"mail-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/mail-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { closeAllMailers, setMailConfigurations } from \"../mail\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Mailer Connector\r\n * Manages mailer lifecycle and ensures graceful pool shutdown\r\n */\r\nexport class MailerConnector extends BaseConnector {\r\n public readonly name = \"mailer\";\r\n public readonly priority = ConnectorPriority.MAILER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger mailer restart\r\n */\r\n protected readonly watchedFiles = [\".env\", \"src/config/mail.ts\", \"src/config/mail.tsx\"];\r\n\r\n /**\r\n * Initialize mailer configurations\r\n */\r\n public async start(): Promise<void> {\r\n const mailConfig = config.get(\"mail\");\r\n\r\n if (!mailConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setMailConfigurations(mailConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize mailer:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown mailer pool\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n closeAllMailers();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to close all mailers:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW;GAAC;GAAQ;GAAsB;EAAqB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YACH;EAGF,IAAI;GACF,sBAAsB,UAAU;GAChC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,gBAAgB;GAChB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
2
2
  import { BaseConnector } from "./base-connector.mjs";
3
- import baseConfig from "@mongez/config";
3
+ import config from "@mongez/config";
4
4
  import { log } from "@warlock.js/logger";
5
5
 
6
6
  //#region ../core/src/connectors/notifications-connector.ts
@@ -28,7 +28,7 @@ var NotificationsConnector = class extends BaseConnector {
28
28
  * Register the notifications configuration with the package.
29
29
  */
30
30
  async start() {
31
- const notificationsConfig = baseConfig.get("notifications");
31
+ const notificationsConfig = config.get("notifications");
32
32
  if (!notificationsConfig) return;
33
33
  const { setNotificationConfig } = await import("@warlock.js/notifications");
34
34
  setNotificationConfig(notificationsConfig);
@@ -1 +1 @@
1
- {"version":3,"file":"notifications-connector.mjs","names":["config"],"sources":["../../../../../../../core/src/connectors/notifications-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Notifications Connector\n *\n * Wires `@warlock.js/notifications` from `src/config/notifications.ts` at boot:\n * reads the exported config object and hands it to the package's\n * `setNotificationConfig`. Config files stay declarative — they export the\n * config, the connector performs the side-effect.\n *\n * The package is lazy-imported (only when a notifications config is present),\n * so core carries no hard dependency on it — the same pattern as the herald\n * connector with `@warlock.js/herald`.\n */\nexport class NotificationsConnector extends BaseConnector {\n public readonly name = \"notifications\";\n public readonly priority = ConnectorPriority.NOTIFICATIONS;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger a notifications restart\n */\n protected readonly watchedFiles = [\"src/config/notifications.ts\"];\n\n /**\n * Register the notifications configuration with the package.\n */\n public async start(): Promise<void> {\n const notificationsConfig = config.get(\"notifications\");\n\n if (!notificationsConfig) {\n return;\n }\n\n const { setNotificationConfig } = await import(\"@warlock.js/notifications\");\n\n setNotificationConfig(notificationsConfig);\n this.active = true;\n\n log.info(\"notifications\", \"configured\", \"Notifications wired from config/notifications.ts\");\n }\n\n /**\n * Nothing to tear down — the package holds no external connection of its own\n * (the optional queue worker + its broker own their lifecycle). Clear the\n * active flag so a dev restart re-registers from the (possibly changed) config.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,IAAa,yBAAb,cAA4C,cAAc;;;cACjC;;;sBAOW,CAAC,6BAA6B;;;;;CAKhE,MAAa,QAAuB;EAClC,MAAM,sBAAsBA,WAAO,IAAI,eAAe;EAEtD,IAAI,CAAC,qBACH;EAGF,MAAM,EAAE,0BAA0B,MAAM,OAAO;EAE/C,sBAAsB,mBAAmB;EACzC,KAAK,SAAS;EAEd,IAAI,KAAK,iBAAiB,cAAc,kDAAkD;CAC5F;;;;;;CAOA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,KAAK,SAAS;CAChB;AACF"}
1
+ {"version":3,"file":"notifications-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/notifications-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Notifications Connector\n *\n * Wires `@warlock.js/notifications` from `src/config/notifications.ts` at boot:\n * reads the exported config object and hands it to the package's\n * `setNotificationConfig`. Config files stay declarative — they export the\n * config, the connector performs the side-effect.\n *\n * The package is lazy-imported (only when a notifications config is present),\n * so core carries no hard dependency on it — the same pattern as the herald\n * connector with `@warlock.js/herald`.\n */\nexport class NotificationsConnector extends BaseConnector {\n public readonly name = \"notifications\";\n public readonly priority = ConnectorPriority.NOTIFICATIONS;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger a notifications restart\n */\n protected readonly watchedFiles = [\"src/config/notifications.ts\"];\n\n /**\n * Register the notifications configuration with the package.\n */\n public async start(): Promise<void> {\n const notificationsConfig = config.get(\"notifications\");\n\n if (!notificationsConfig) {\n return;\n }\n\n const { setNotificationConfig } = await import(\"@warlock.js/notifications\");\n\n setNotificationConfig(notificationsConfig);\n this.active = true;\n\n log.info(\"notifications\", \"configured\", \"Notifications wired from config/notifications.ts\");\n }\n\n /**\n * Nothing to tear down — the package holds no external connection of its own\n * (the optional queue worker + its broker own their lifecycle). Clear the\n * active flag so a dev restart re-registers from the (possibly changed) config.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,IAAa,yBAAb,cAA4C,cAAc;;;cACjC;;;sBAOW,CAAC,6BAA6B;;;;;CAKhE,MAAa,QAAuB;EAClC,MAAM,sBAAsB,OAAO,IAAI,eAAe;EAEtD,IAAI,CAAC,qBACH;EAGF,MAAM,EAAE,0BAA0B,MAAM,OAAO;EAE/C,sBAAsB,mBAAmB;EACzC,KAAK,SAAS;EAEd,IAAI,KAAK,iBAAiB,cAAc,kDAAkD;CAC5F;;;;;;CAOA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,KAAK,SAAS;CAChB;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { container } from "../container/index.mjs";
2
2
  import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
3
3
  import { BaseConnector } from "./base-connector.mjs";
4
- import baseConfig from "@mongez/config";
4
+ import config from "@mongez/config";
5
5
  import { log } from "@warlock.js/logger";
6
6
  import { createServer } from "http";
7
7
  import { createServer as createServer$1 } from "https";
@@ -40,7 +40,7 @@ var SocketConnector = class extends BaseConnector {
40
40
  * Boot the connector
41
41
  */
42
42
  async boot() {
43
- const socketConfig = baseConfig.get("socket");
43
+ const socketConfig = config.get("socket");
44
44
  if (!socketConfig) return;
45
45
  let SocketServer;
46
46
  try {
@@ -66,7 +66,7 @@ var SocketConnector = class extends BaseConnector {
66
66
  * Initialize Socket server
67
67
  */
68
68
  async start() {
69
- if (!baseConfig.get("socket") || !this.socket) return;
69
+ if (!config.get("socket") || !this.socket) return;
70
70
  log.success("socket", "connection", "Established Socket.IO server");
71
71
  this.active = true;
72
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.mjs","names":["config","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, socketConfig.options);\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,eAAeA,WAAO,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,MAAMC,eAAkB,IAAIC,aAAiB;GACnE,OAAO,OAAO,aAAa,IAAI;GAC/B,KAAK,gBAAgB;EACvB;EAEA,UAAU,IAAI,oBAAoB,MAAM;EAExC,KAAK,SAAS,IAAI,aAAa,QAAQ,aAAa,OAAO;EAE3D,UAAU,IAAI,UAAU,KAAK,MAAM;CACrC;;;;CAKA,MAAa,QAAuB;EAKlC,IAAI,CAJiBF,WAAO,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, 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, socketConfig.options);\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,aAAa,OAAO;EAE3D,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"}
@@ -15,14 +15,20 @@ declare class StorageConnector extends BaseConnector {
15
15
  */
16
16
  protected readonly watchedFiles: string[];
17
17
  /**
18
- * Initialize cache connection
18
+ * Initialize storage.
19
19
  *
20
20
  * Unlike its siblings (database, cache, herald), this connector does NOT
21
21
  * early-return when `config.get("storage")` is absent. Storage is
22
- * intentionally always-on: `storage.init()` falls back to the built-in
23
- * `local` driver (see `storageConfig("default", "local")`), so file storage
24
- * works out of the box even when a project ships no `src/config/storage.ts`.
25
- * A config-presence guard here would break that default local storage.
22
+ * intentionally always-on: `storage.init()` registers a built-in `local`
23
+ * driver rooted at `uploadsPath()` before reading configured drivers, so file
24
+ * storage works out of the box even when a project ships no
25
+ * `src/config/storage.ts`. A config-presence guard here would break that.
26
+ *
27
+ * That fallback is real as of 4.11.0. Until then this comment described it
28
+ * and nothing implemented it, so every app without `src/config/storage.ts`
29
+ * died at boot with `Storage driver "local" is not configured` — a decision
30
+ * to skip the guard, taken on the authority of behaviour nobody had written.
31
+ * See `Storage.registerBuiltInLocalDriver`.
26
32
  */
27
33
  start(): Promise<void>;
28
34
  /**
@@ -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;EA2BL;;;EAAA,mBAtBN,YAAA;EARiB;;;;;;;;;;EAoBvB,KAAA,IAAS,OAAA;EAUT;;;EAAA,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,IAAS,OAAA;;;;EAUT,QAAA,IAAY,OAAA;AAAA"}
@@ -18,14 +18,20 @@ var StorageConnector = class extends BaseConnector {
18
18
  this.watchedFiles = ["src/config/storage.ts", "src/config/storage.tsx"];
19
19
  }
20
20
  /**
21
- * Initialize cache connection
21
+ * Initialize storage.
22
22
  *
23
23
  * Unlike its siblings (database, cache, herald), this connector does NOT
24
24
  * early-return when `config.get("storage")` is absent. Storage is
25
- * intentionally always-on: `storage.init()` falls back to the built-in
26
- * `local` driver (see `storageConfig("default", "local")`), so file storage
27
- * works out of the box even when a project ships no `src/config/storage.ts`.
28
- * A config-presence guard here would break that default local storage.
25
+ * intentionally always-on: `storage.init()` registers a built-in `local`
26
+ * driver rooted at `uploadsPath()` before reading configured drivers, so file
27
+ * storage works out of the box even when a project ships no
28
+ * `src/config/storage.ts`. A config-presence guard here would break that.
29
+ *
30
+ * That fallback is real as of 4.11.0. Until then this comment described it
31
+ * and nothing implemented it, so every app without `src/config/storage.ts`
32
+ * died at boot with `Storage driver "local" is not configured` — a decision
33
+ * to skip the guard, taken on the authority of behaviour nobody had written.
34
+ * See `Storage.registerBuiltInLocalDriver`.
29
35
  */
30
36
  async start() {
31
37
  await loadS3();
@@ -1 +1 @@
1
- {"version":3,"file":"storage.connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"sourcesContent":["import { storage } from \"../storage\";\nimport { loadS3 } from \"../storage/drivers/cloud-driver\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class StorageConnector extends BaseConnector {\n public readonly name = \"storage\";\n public readonly priority = ConnectorPriority.STORAGE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/storage.ts\", \"src/config/storage.tsx\"];\n\n /**\n * Initialize cache connection\n *\n * Unlike its siblings (database, cache, herald), this connector does NOT\n * early-return when `config.get(\"storage\")` is absent. Storage is\n * intentionally always-on: `storage.init()` falls back to the built-in\n * `local` driver (see `storageConfig(\"default\", \"local\")`), so file storage\n * works out of the box even when a project ships no `src/config/storage.ts`.\n * A config-presence guard here would break that default local storage.\n */\n public async start(): Promise<void> {\n await loadS3();\n await storage.init();\n\n this.active = true;\n }\n\n /**\n * Shutdown cache connection\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n storage.reset();\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;AASA,IAAa,mBAAb,cAAsC,cAAc;;;cAC3B;;;sBAOW,CAAC,yBAAyB,wBAAwB;;;;;;;;;;;;CAYpF,MAAa,QAAuB;EAClC,MAAM,OAAO;EACb,MAAM,QAAQ,KAAK;EAEnB,KAAK,SAAS;CAChB;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,QAAQ,MAAM;EAEd,KAAK,SAAS;CAChB;AACF"}
1
+ {"version":3,"file":"storage.connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"sourcesContent":["import { storage } from \"../storage\";\nimport { loadS3 } from \"../storage/drivers/cloud-driver\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class StorageConnector extends BaseConnector {\n public readonly name = \"storage\";\n public readonly priority = ConnectorPriority.STORAGE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/storage.ts\", \"src/config/storage.tsx\"];\n\n /**\n * Initialize storage.\n *\n * Unlike its siblings (database, cache, herald), this connector does NOT\n * early-return when `config.get(\"storage\")` is absent. Storage is\n * intentionally always-on: `storage.init()` registers a built-in `local`\n * driver rooted at `uploadsPath()` before reading configured drivers, so file\n * storage works out of the box even when a project ships no\n * `src/config/storage.ts`. A config-presence guard here would break that.\n *\n * That fallback is real as of 4.11.0. Until then this comment described it\n * and nothing implemented it, so every app without `src/config/storage.ts`\n * died at boot with `Storage driver \"local\" is not configured` — a decision\n * to skip the guard, taken on the authority of behaviour nobody had written.\n * See `Storage.registerBuiltInLocalDriver`.\n */\n public async start(): Promise<void> {\n await loadS3();\n await storage.init();\n\n this.active = true;\n }\n\n /**\n * Shutdown cache connection\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n storage.reset();\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;AASA,IAAa,mBAAb,cAAsC,cAAc;;;cAC3B;;;sBAOW,CAAC,yBAAyB,wBAAwB;;;;;;;;;;;;;;;;;;CAkBpF,MAAa,QAAuB;EAClC,MAAM,OAAO;EACb,MAAM,QAAQ,KAAK;EAEnB,KAAK,SAAS;CAChB;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,QAAQ,MAAM;EAEd,KAAK,SAAS;CAChB;AACF"}
@@ -4,13 +4,30 @@ import { Path } from "../dev-server/path.mjs";
4
4
  import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
5
5
  import { getFilesFromDirectory } from "../dev-server/utils.mjs";
6
6
  import { filesOrchestrator } from "../dev-server/files-orchestrator.mjs";
7
+ import { PENDING_EXIT_CODE, exitCodeFor } from "./pending-exit-code.mjs";
8
+ import { resolvePendingMigrations } from "./resolve-pending-migrations.mjs";
7
9
  import { exportMigrationsSQL, freshMigrate, listExecutedMigrations, migrationRunner, rollbackMigrations, runMigrations } from "@warlock.js/cascade";
8
10
  import path from "path";
9
11
  import { colors } from "@mongez/copper";
10
12
  import dayjs from "dayjs";
11
13
 
12
14
  //#region ../core/src/database/migrate-action.ts
15
+ /**
16
+ * `--list` — print the migration state: what has run, then what will run next.
17
+ *
18
+ * The executed section is printed FIRST and unconditionally, before anything
19
+ * touches disk or config. That ordering is the contract: reading migrations
20
+ * from the table cannot fail because of a broken migration file, and `--list`
21
+ * is the command an operator reaches for *during* an incident. The pending
22
+ * section is best-effort on top of an answer that is already on screen.
23
+ *
24
+ * Always exits 0. It is a report; `--pending` is the gate.
25
+ */
13
26
  async function listMigrationsAction() {
27
+ await printExecutedMigrations();
28
+ printPendingMigrations(await resolvePendingMigrations(loadAllMigrations));
29
+ }
30
+ async function printExecutedMigrations() {
14
31
  const createdMigrations = await listExecutedMigrations();
15
32
  console.log(`\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\n`);
16
33
  if (createdMigrations.length === 0) {
@@ -26,6 +43,40 @@ async function listMigrationsAction() {
26
43
  console.log("");
27
44
  }
28
45
  }
46
+ /**
47
+ * Print the pending section shared by `--list` and `--pending`.
48
+ *
49
+ * An unavailable result prints an explicit line and NEVER a count. "0 pending"
50
+ * over a tree we failed to read is the one output this whole feature exists to
51
+ * prevent.
52
+ */
53
+ function printPendingMigrations(result) {
54
+ if (result.type === "unavailable") {
55
+ console.log(`${colors.yellowBright("Pending: unavailable")} — ${result.reason}\n` + colors.gray(" The executed list above is still accurate.\n"));
56
+ return;
57
+ }
58
+ const { migrations } = result;
59
+ console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\n`);
60
+ if (migrations.length === 0) {
61
+ console.log(colors.gray(" Nothing pending — the database is up to date.\n"));
62
+ return;
63
+ }
64
+ migrations.forEach((migration, index) => {
65
+ console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);
66
+ });
67
+ console.log("");
68
+ }
69
+ /**
70
+ * `--pending` — the scriptable half. Same computation as `--list`'s pending
71
+ * section, different exit contract: see {@link PENDING_EXIT_CODE}.
72
+ */
73
+ async function pendingMigrationsAction() {
74
+ const result = await resolvePendingMigrations(loadAllMigrations);
75
+ printPendingMigrations(result);
76
+ const exitCode = exitCodeFor(result);
77
+ if (exitCode === PENDING_EXIT_CODE.unavailable) console.error(colors.redBright("Could not determine the pending set — exiting 2 rather than reporting 0."));
78
+ process.exit(exitCode);
79
+ }
29
80
  async function allMigrationsFilesAction() {
30
81
  const files = (await migrationFiles()).map((path) => Path.toRelative(path));
31
82
  console.log(`Total Migration Files: ${colors.green(files.length)}`);
@@ -37,8 +88,9 @@ async function allMigrationsFilesAction() {
37
88
  * If rollback is provided, then run the migration runner against all files in reverse order
38
89
  */
39
90
  async function migrateAction(options) {
40
- const { fresh, path, rollback, all, list, sql, pendingOnly, compact } = options.options;
91
+ const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;
41
92
  if (list) return await listMigrationsAction();
93
+ if (pending) return await pendingMigrationsAction();
42
94
  if (all) return await allMigrationsFilesAction();
43
95
  if (path) await loadMigrationFile(Path.toAbsolute(path));
44
96
  else await loadAllMigrations();
@@ -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 \"../cli/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../dev-server/path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\n\r\nasync function listMigrationsAction() {\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\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, sql, pendingOnly, compact } = options.options;\r\n\r\n if (list) {\r\n return await listMigrationsAction();\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,\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 \"../cli/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../dev-server/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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"}
@@ -0,0 +1,39 @@
1
+ //#region ../core/src/database/pending-exit-code.ts
2
+ /**
3
+ * Exit codes for `warlock migrate --pending`.
4
+ *
5
+ * `--pending` is a GATE, not a report: it is written into `migrate --pending &&
6
+ * deploy` and into CI steps asserting nothing is outstanding. So it must
7
+ * distinguish three outcomes, not two.
8
+ *
9
+ * Two codes would fold `pending` and `unavailable` into a single non-zero, and
10
+ * a script could no longer tell "three migrations are waiting" from "I could
11
+ * not work out what is waiting". Those demand opposite responses — the first is
12
+ * "run them", the second is "stop and get a human".
13
+ *
14
+ * `warlock migrate --list` is the report half and always exits `0`, whatever
15
+ * the pending section says.
16
+ */
17
+ const PENDING_EXIT_CODE = {
18
+ /** Computed, and nothing is pending. */
19
+ clear: 0,
20
+ /** Computed, and at least one migration is pending. */
21
+ pending: 1,
22
+ /** Could not be computed. Never conflate this with `clear`. */
23
+ unavailable: 2
24
+ };
25
+ /**
26
+ * Map a resolution outcome onto the exit code a gate should report.
27
+ *
28
+ * @example
29
+ * exitCodeFor({ type: "resolved", migrations: [] }); // 0
30
+ * exitCodeFor({ type: "unavailable", reason: "…" }); // 2
31
+ */
32
+ function exitCodeFor(result) {
33
+ if (result.type === "unavailable") return PENDING_EXIT_CODE.unavailable;
34
+ return result.migrations.length > 0 ? PENDING_EXIT_CODE.pending : PENDING_EXIT_CODE.clear;
35
+ }
36
+
37
+ //#endregion
38
+ export { PENDING_EXIT_CODE, exitCodeFor };
39
+ //# sourceMappingURL=pending-exit-code.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-exit-code.mjs","names":[],"sources":["../../../../../../../core/src/database/pending-exit-code.ts"],"sourcesContent":["import type { PendingMigrationsResult } from \"./resolve-pending-migrations\";\n\n/**\n * Exit codes for `warlock migrate --pending`.\n *\n * `--pending` is a GATE, not a report: it is written into `migrate --pending &&\n * deploy` and into CI steps asserting nothing is outstanding. So it must\n * distinguish three outcomes, not two.\n *\n * Two codes would fold `pending` and `unavailable` into a single non-zero, and\n * a script could no longer tell \"three migrations are waiting\" from \"I could\n * not work out what is waiting\". Those demand opposite responses — the first is\n * \"run them\", the second is \"stop and get a human\".\n *\n * `warlock migrate --list` is the report half and always exits `0`, whatever\n * the pending section says.\n */\nexport const PENDING_EXIT_CODE = {\n /** Computed, and nothing is pending. */\n clear: 0,\n /** Computed, and at least one migration is pending. */\n pending: 1,\n /** Could not be computed. Never conflate this with `clear`. */\n unavailable: 2,\n} as const;\n\nexport type PendingExitCode = (typeof PENDING_EXIT_CODE)[keyof typeof PENDING_EXIT_CODE];\n\n/**\n * Map a resolution outcome onto the exit code a gate should report.\n *\n * @example\n * exitCodeFor({ type: \"resolved\", migrations: [] }); // 0\n * exitCodeFor({ type: \"unavailable\", reason: \"…\" }); // 2\n */\nexport function exitCodeFor(result: PendingMigrationsResult): PendingExitCode {\n if (result.type === \"unavailable\") {\n return PENDING_EXIT_CODE.unavailable;\n }\n\n return result.migrations.length > 0 ? PENDING_EXIT_CODE.pending : PENDING_EXIT_CODE.clear;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,oBAAoB;;CAE/B,OAAO;;CAEP,SAAS;;CAET,aAAa;AACf;;;;;;;;AAWA,SAAgB,YAAY,QAAkD;CAC5E,IAAI,OAAO,SAAS,eAClB,OAAO,kBAAkB;CAG3B,OAAO,OAAO,WAAW,SAAS,IAAI,kBAAkB,UAAU,kBAAkB;AACtF"}
@@ -0,0 +1,49 @@
1
+ import { listPendingMigrations } from "@warlock.js/cascade";
2
+
3
+ //#region ../core/src/database/resolve-pending-migrations.ts
4
+ /**
5
+ * Load the project's migrations, then compute which of them have not run yet.
6
+ *
7
+ * `loadMigrations` must be the SAME registration path a real `warlock migrate`
8
+ * takes. Skipping it does not fail — it silently yields an empty pending set,
9
+ * because the pending set is computed from the runner's registry.
10
+ *
11
+ * Loading executes project code (a migration file is imported, and a broken one
12
+ * throws), so every failure is converted into an `unavailable` result rather
13
+ * than propagating. Callers decide what an unknown means for them: a listing
14
+ * prints it and carries on, a gate refuses to pass.
15
+ *
16
+ * @example
17
+ * const result = await resolvePendingMigrations(loadAllMigrations);
18
+ *
19
+ * if (result.type === "unavailable") {
20
+ * console.error(`Pending: unavailable — ${result.reason}`);
21
+ * }
22
+ */
23
+ async function resolvePendingMigrations(loadMigrations) {
24
+ try {
25
+ await loadMigrations();
26
+ return {
27
+ type: "resolved",
28
+ migrations: await listPendingMigrations()
29
+ };
30
+ } catch (error) {
31
+ return {
32
+ type: "unavailable",
33
+ reason: describeFailure(error)
34
+ };
35
+ }
36
+ }
37
+ /**
38
+ * Reduce a thrown value to a single line an operator can act on. The message is
39
+ * the actionable part — a stack trace in the middle of a listing buries the
40
+ * executed section the reader still needs.
41
+ */
42
+ function describeFailure(error) {
43
+ if (error instanceof Error) return error.message;
44
+ return String(error);
45
+ }
46
+
47
+ //#endregion
48
+ export { resolvePendingMigrations };
49
+ //# sourceMappingURL=resolve-pending-migrations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-pending-migrations.mjs","names":[],"sources":["../../../../../../../core/src/database/resolve-pending-migrations.ts"],"sourcesContent":["import { listPendingMigrations, type PendingMigration } from \"@warlock.js/cascade\";\n\n/**\n * Outcome of trying to compute the pending migration set.\n *\n * The \"unavailable\" arm exists because the two failure modes are NOT the same\n * answer: a database with nothing pending and a tree we could not read both\n * produce an empty list, and only one of them means it is safe to proceed.\n * Collapsing them into `[]` is how a pre-flight check reports a confident\n * all-clear over an unknown.\n */\nexport type PendingMigrationsResult =\n | {\n readonly type: \"resolved\";\n readonly migrations: PendingMigration[];\n }\n | {\n readonly type: \"unavailable\";\n readonly reason: string;\n };\n\n/**\n * Load the project's migrations, then compute which of them have not run yet.\n *\n * `loadMigrations` must be the SAME registration path a real `warlock migrate`\n * takes. Skipping it does not fail — it silently yields an empty pending set,\n * because the pending set is computed from the runner's registry.\n *\n * Loading executes project code (a migration file is imported, and a broken one\n * throws), so every failure is converted into an `unavailable` result rather\n * than propagating. Callers decide what an unknown means for them: a listing\n * prints it and carries on, a gate refuses to pass.\n *\n * @example\n * const result = await resolvePendingMigrations(loadAllMigrations);\n *\n * if (result.type === \"unavailable\") {\n * console.error(`Pending: unavailable — ${result.reason}`);\n * }\n */\nexport async function resolvePendingMigrations(\n loadMigrations: () => Promise<void>,\n): Promise<PendingMigrationsResult> {\n try {\n await loadMigrations();\n\n const migrations = await listPendingMigrations();\n\n return { type: \"resolved\", migrations };\n } catch (error) {\n return { type: \"unavailable\", reason: describeFailure(error) };\n }\n}\n\n/**\n * Reduce a thrown value to a single line an operator can act on. The message is\n * the actionable part — a stack trace in the middle of a listing buries the\n * executed section the reader still needs.\n */\nfunction describeFailure(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,yBACpB,gBACkC;CAClC,IAAI;EACF,MAAM,eAAe;EAIrB,OAAO;GAAE,MAAM;GAAY,kBAFF,sBAAsB;EAET;CACxC,SAAS,OAAO;EACd,OAAO;GAAE,MAAM;GAAe,QAAQ,gBAAgB,KAAK;EAAE;CAC/D;AACF;;;;;;AAOA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,KAAK;AACrB"}
@@ -1,7 +1,7 @@
1
1
  import { TranspileCache, cacheKey } from "./transpile-cache.mjs";
2
2
  import { sourceSlug } from "./source-slug.mjs";
3
- import { transformSync } from "esbuild";
4
3
  import { readFileSync } from "node:fs";
4
+ import { transformSync } from "esbuild";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  //#region ../core/src/dev-server/loader/load-hook.ts
@@ -280,7 +280,7 @@ export default {
280
280
  },
281
281
  } as SocketOptions;
282
282
  `;
283
- const communicatorsConfigStub = `import { env } from "@mongez/dotenv";
283
+ const communicatorsConfigStub = `import { env } from "@warlock.js/core";
284
284
  import type { BrokerConfigurations, RabbitMQClientOptions } from "@warlock.js/herald";
285
285
 
286
286
  const heraldConfigurations: BrokerConfigurations<RabbitMQClientOptions> = {