@warlock.js/core 5.4.0 → 5.6.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.
- package/CHANGELOG.md +21 -0
- package/esm/application/app.d.mts.map +1 -1
- package/esm/application/app.mjs +7 -1
- package/esm/application/app.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
- package/esm/cli/commands/routes/route-row.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/config/locale-configuration.mjs +11 -1
- package/esm/config/locale-configuration.mjs.map +1 -1
- package/esm/container/container-instance-registry.mjs +40 -0
- package/esm/container/container-instance-registry.mjs.map +1 -0
- package/esm/container/index.d.mts +11 -0
- package/esm/container/index.d.mts.map +1 -1
- package/esm/container/index.mjs +11 -0
- package/esm/container/index.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.d.mts.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/loader/build-transpile-init.mjs.map +1 -1
- package/esm/dev-server/loader/load-hook.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/types.d.mts.map +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/errors/container-key-missing-error.mjs +28 -0
- package/esm/errors/container-key-missing-error.mjs.map +1 -0
- package/esm/errors/unknown-locale-error.mjs +28 -0
- package/esm/errors/unknown-locale-error.mjs.map +1 -0
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
- package/esm/generations/features/ai-google.feature.mjs.map +1 -1
- package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
- package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
- package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
- package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
- package/esm/generations/features/ai.feature.mjs.map +1 -1
- package/esm/generations/features/herald.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/scheduler.feature.mjs.map +1 -1
- package/esm/generations/features/shared/patch-tsconfig-include.mjs.map +1 -1
- package/esm/generations/features/shared/resolve-contact-scaffold.mjs +45 -0
- package/esm/generations/features/shared/resolve-contact-scaffold.mjs.map +1 -0
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/types.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +14 -4
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/http/context/request-memo.mjs.map +1 -1
- package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.d.mts.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/port-preflight.d.mts.map +1 -1
- package/esm/http/port-preflight.mjs.map +1 -1
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +2 -2
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +38 -0
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +47 -0
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/types.d.mts.map +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/production/build-contributions.mjs.map +1 -1
- package/esm/production/bundle-framework-dependencies.mjs +152 -0
- package/esm/production/bundle-framework-dependencies.mjs.map +1 -0
- package/esm/production/production-builder.mjs +30 -3
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/tsconfig-path-aliases.mjs +99 -0
- package/esm/production/tsconfig-path-aliases.mjs.map +1 -0
- package/esm/react/index.d.mts.map +1 -1
- package/esm/react/index.mjs.map +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/repositories/contracts/types.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +1 -2
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/storage/types.d.mts.map +1 -1
- package/esm/storage/utils/contain-path.mjs.map +1 -1
- package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-lifecycle-state.mjs.map +1 -1
- package/esm/tests/test-setup-timeout.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/app-log.mjs.map +1 -1
- package/esm/utils/queue.mjs.map +1 -1
- package/esm/utils/sleep.mjs.map +1 -1
- package/esm/validation/plugins/database-plugin.mjs.map +1 -1
- package/llms-full.txt +60 -60
- package/llms.txt +3 -3
- package/package.json +12 -12
- package/skills/create-controller/SKILL.md +1 -1
- package/skills/create-module/SKILL.md +11 -11
- package/skills/hash-password/SKILL.md +4 -4
- package/skills/run-app/SKILL.md +16 -16
- package/skills/send-mail/SKILL.md +2 -2
- package/skills/use-model-transformers/SKILL.md +2 -2
- package/skills/use-repository/SKILL.md +1 -1
- package/skills/validate-input/SKILL.md +1 -1
- package/skills/warlock-doctor/SKILL.md +3 -3
- package/skills/warlock-routes/SKILL.md +10 -10
- package/skills/write-cli-command/SKILL.md +1 -1
- package/skills/write-seeder/SKILL.md +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-http-development-server.mjs","names":[],"sources":["../../../../../../../core/src/tests/start-http-development-server.ts"],"sourcesContent":["/**\n * HTTP Test Server\n *\n * Starts a minimal HTTP server for testing.\n * Unlike the full DevelopmentServer, this:\n * - Does NOT watch files\n * - Does NOT do HMR\n * - Only starts connectors needed for HTTP requests\n *\n * Used in Vitest's globalSetup to start server once for all test workers.\n */\nimport config from \"@mongez/config\";\nimport { Application } from \"../application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { assertPortIsAvailable } from \"../http/port-preflight\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { publishTestServerPort, withdrawTestServerPort } from \"./test-server-port-channel\";\n\nlet isServerRunning = false;\n\nexport type StartHttpTestServerOptions = {\n /**\n * Port the test server binds to, overriding `http.port`.\n *\n * Needed because the server cannot be moved from outside: this function\n * bootstraps the app itself, and that bootstrap re-reads `.env`.\n */\n port?: number;\n};\n\n/**\n * Apply the caller's port and preflight the bind.\n *\n * Called at the last moment before the HTTP connector binds, and that timing is\n * the fix: `bootstrap()` re-reads `.env` through `loadEnv()` (dotenv overrides\n * by default), and `src/config/http.ts` then resolves its port from that store,\n * so anything the caller set earlier is gone by the time the port is read.\n * Writing into `config` here is also the only channel that works — assigning\n * `process.env.HTTP_PORT` has no effect, since `env()` reads dotenv's own store\n * and never falls back to `process.env`.\n */\nasync function applyTestServerPort(port?: number): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) {\n if (port !== undefined) {\n throw new Error(\n `startHttpTestServer({ port: ${port} }) was given a port, but this app has no \\`http\\` config, so no HTTP server is started. Add \\`src/config/http.ts\\` first.`,\n );\n }\n\n return;\n }\n\n if (port !== undefined) {\n config.set(\"http.port\", port);\n }\n\n const resolvedPort = config.get(\"http.port\");\n\n // No configured port at all is fine: Fastify falls back to its own default and\n // the workers resolve the same one from their own config, so both ends agree\n // without the channel.\n //\n // A non-numeric port is NOT fine, and must fail loudly rather than skip the\n // preflight silently. `env()` coerces a value only when it round-trips exactly\n // (`String(Number(v)) === v`), so `HTTP_PORT=3999` and `HTTP_PORT=0` arrive as\n // numbers, but `03999`, `+3999`, `1e3` and anything with stray whitespace stay\n // strings. Letting those through used to reach `HttpConnector.start()`'s\n // `listen({ port })` unchanged — no preflight, no published port, and every\n // test worker resolving a URL from a channel nothing ever wrote to.\n if (typeof resolvedPort !== \"number\") {\n throw new Error(\n `startHttpTestServer() cannot use \\`http.port\\` value ${JSON.stringify(resolvedPort)} — it did not round-trip through Number() (env() only coerces a value when \\`String(Number(v)) === v\\`, so \"03999\", \"+3999\", \"1e3\", and values with stray whitespace stay strings). Fix \\`HTTP_PORT\\` in .env, or pass an explicit numeric port: startHttpTestServer({ port: 3999 }).`,\n );\n }\n\n // `0` is the OS's \"pick a free one for me\" idiom, and the test server cannot\n // support it. Preflighting 0 would bind some unrelated ephemeral port and pass\n // without proving anything, and there is nothing meaningful to publish: the\n // port Fastify ends up binding is never recorded — `HttpConnector.start()`\n // stores the port it ASKED for, not the one it got. Accepting 0 silently is\n // what left `getTestServerUrl()` resolving to `http://host:0` through its own\n // config fallback, so every worker request went to a port nothing listens on.\n if (resolvedPort <= 0) {\n throw new Error(\n `startHttpTestServer() cannot run on port ${resolvedPort}. Pass an explicit port — e.g. startHttpTestServer({ port: 3999 }) — or set \\`http.port\\` in your config. Test workers are separate processes that resolve the server's URL from the port published at startup, and an OS-assigned port is not knowable to them.`,\n );\n }\n\n await assertPortIsAvailable(resolvedPort, config.get(\"http.host\") || \"localhost\");\n\n publishTestServerPort(resolvedPort);\n}\n\n/**\n * Start the HTTP test server (minimal - no file watching)\n * Call this in Vitest's globalSetup\n *\n * @example\n * // run this suite on its own port, whatever `.env` says\n * await startHttpTestServer({ port: 3999 });\n */\nexport async function startHttpTestServer(\n options: StartHttpTestServerOptions = {},\n): Promise<void> {\n if (isServerRunning) {\n console.log(\"[test-server] Server already running, skipping start\");\n return;\n }\n\n console.log(\"[test-server] Starting HTTP test server...\");\n\n try {\n // Set environment\n Application.setRuntimeStrategy(\"development\");\n Application.setEnvironment(\"test\");\n\n // Bootstrap (env, etc.)\n await warlockConfigManager.load();\n await bootstrap();\n\n // Initialize file orchestrator (but don't watch)\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n\n // Load config files\n await loadConfigFiles(true);\n\n // Early-phase connectors (database, cache, logger, storage, mailer,\n // herald) must start BEFORE app modules load: a module's `main.ts` boot\n // side-effect can query the DB at import time, so the data source has to\n // be registered first. This mirrors the dev/prod boot order (see\n // `cli-commands.manager`, `production-builder`, and `DevelopmentServer`).\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\n\n // Load application modules (their boot side-effects now see a live DB).\n await filesOrchestrator.moduleLoader.loadAll();\n\n await applyTestServerPort(options.port);\n\n // A rejecting validator (Application.onValidateBoot) must abort boot\n // before late-phase connectors bind a port — same slot as\n // `DevelopmentServer.start()` and the generated production `app.ts`.\n await Application.runStartupValidators();\n\n // Late-phase connectors (http, socket) bind after app code has\n // registered its routes and listeners.\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\n\n isServerRunning = true;\n } catch (error) {\n // This function owns the connectors it starts, so a failure part-way\n // through has to leave nothing behind. Without this, `isServerRunning` was\n // still `false` — it is only set on the last line — so `globalTeardown`'s\n // `stopHttpTestServer()` reported \"No server to stop\" and walked away from\n // live early-phase connectors and a published port that outlived them.\n await unwindPartialStartup();\n\n // Rethrown unchanged: the startup failure is the one the caller needs to\n // read. A cleanup failure that replaced it would hide the actual cause.\n throw error;\n }\n}\n\n/**\n * Best-effort teardown of whatever a failed {@link startHttpTestServer} managed\n * to start.\n *\n * Every step is independent and none may throw: this runs while an error is\n * already in flight, and the caller is about to rethrow it.\n */\nasync function unwindPartialStartup(): Promise<void> {\n try {\n await connectorsManager.shutdown();\n } catch (shutdownError) {\n // Reported, not thrown — it is secondary to the startup error being\n // rethrown, and losing that one to this would be the worse outcome.\n console.error(\"[test-server] Cleanup after a failed start did not complete:\", shutdownError);\n }\n\n withdrawTestServerPort();\n isServerRunning = false;\n}\n\n/**\n * Stop the HTTP test server\n * Call this in Vitest's globalTeardown\n */\nexport async function stopHttpTestServer(): Promise<void> {\n if (!isServerRunning) {\n console.log(\"[test-server] No server to stop\");\n return;\n }\n\n try {\n await connectorsManager.shutdown();\n\n console.log(\"[test-server] HTTP test server stopped\");\n } catch (error) {\n // Surfaced, never swallowed — cleaning up is not the same as the shutdown\n // having succeeded, and a suite whose teardown failed should say so.\n console.error(\"[test-server] Error stopping HTTP server:\", error);\n\n throw error;\n } finally {\n // In a `finally` rather than after the `await`: when `shutdown()` threw,\n // the published port survived the server it pointed at, so the next run in\n // the same process inherited a stale one and sent every request to it.\n withdrawTestServerPort();\n\n isServerRunning = false;\n }\n}\n\n/**\n * Check if test server is running\n */\nexport function isTestServerRunning(): boolean {\n return isServerRunning;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAI,kBAAkB;;;;;;;;;;;;AAuBtB,eAAe,oBAAoB,MAA8B;CAG/D,IAAI,CAFe,OAAO,IAAI,MAEhB,GAAG;EACf,IAAI,SAAS,QACX,MAAM,IAAI,MACR,+BAA+B,KAAK,2HACtC;EAGF;CACF;CAEA,IAAI,SAAS,QACX,OAAO,IAAI,aAAa,IAAI;CAG9B,MAAM,eAAe,OAAO,IAAI,WAAW;CAa3C,IAAI,OAAO,iBAAiB,UAC1B,MAAM,IAAI,MACR,wDAAwD,KAAK,UAAU,YAAY,EAAE,sRACvF;CAUF,IAAI,gBAAgB,GAClB,MAAM,IAAI,MACR,4CAA4C,aAAa,iQAC3D;CAGF,MAAM,sBAAsB,cAAc,OAAO,IAAI,WAAW,KAAK,WAAW;CAEhF,sBAAsB,YAAY;AACpC;;;;;;;;;AAUA,eAAsB,oBACpB,UAAsC,CAAC,GACxB;CACf,IAAI,iBAAiB;EACnB,QAAQ,IAAI,sDAAsD;EAClE;CACF;CAEA,QAAQ,IAAI,4CAA4C;CAExD,IAAI;EAEF,YAAY,mBAAmB,aAAa;EAC5C,YAAY,eAAe,MAAM;EAGjC,MAAM,qBAAqB,KAAK;EAChC,MAAM,UAAU;EAGhB,MAAM,kBAAkB,KAAK;EAC7B,MAAM,kBAAkB,cAAc;EAGtC,MAAM,gBAAgB,IAAI;EAO1B,MAAM,kBAAkB,kBAAwC;EAGhE,MAAM,kBAAkB,aAAa,QAAQ;EAE7C,MAAM,oBAAoB,QAAQ,IAAI;EAKtC,MAAM,YAAY,qBAAqB;EAIvC,MAAM,kBAAkB,iBAAuC;EAE/D,kBAAkB;CACpB,SAAS,OAAO;EAMd,MAAM,qBAAqB;EAI3B,MAAM;CACR;AACF;;;;;;;;AASA,eAAe,uBAAsC;CACnD,IAAI;EACF,MAAM,kBAAkB,SAAS;CACnC,SAAS,eAAe;EAGtB,QAAQ,MAAM,gEAAgE,aAAa;CAC7F;CAEA,uBAAuB;CACvB,kBAAkB;AACpB;;;;;AAMA,eAAsB,qBAAoC;CACxD,IAAI,CAAC,iBAAiB;EACpB,QAAQ,IAAI,iCAAiC;EAC7C;CACF;CAEA,IAAI;EACF,MAAM,kBAAkB,SAAS;EAEjC,QAAQ,IAAI,wCAAwC;CACtD,SAAS,OAAO;EAGd,QAAQ,MAAM,6CAA6C,KAAK;EAEhE,MAAM;CACR,UAAU;EAIR,uBAAuB;EAEvB,kBAAkB;CACpB;AACF;;;;AAKA,SAAgB,sBAA+B;CAC7C,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"start-http-development-server.mjs","names":[],"sources":["../../../../../../../core/src/tests/start-http-development-server.ts"],"sourcesContent":["/**\n * HTTP Test Server\n *\n * Starts a minimal HTTP server for testing.\n * Unlike the full DevelopmentServer, this:\n * - Does NOT watch files\n * - Does NOT do HMR\n * - Only starts connectors needed for HTTP requests\n *\n * Used in Vitest's globalSetup to start server once for all test workers.\n */\nimport config from \"@mongez/config\";\nimport { Application } from \"../application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { assertPortIsAvailable } from \"../http/port-preflight\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { publishTestServerPort, withdrawTestServerPort } from \"./test-server-port-channel\";\n\nlet isServerRunning = false;\n\nexport type StartHttpTestServerOptions = {\n /**\n * Port the test server binds to, overriding `http.port`.\n *\n * Needed because the server cannot be moved from outside: this function\n * bootstraps the app itself, and that bootstrap re-reads `.env`.\n */\n port?: number;\n};\n\n/**\n * Apply the caller's port and preflight the bind.\n *\n * Called at the last moment before the HTTP connector binds, and that timing is\n * the fix: `bootstrap()` re-reads `.env` through `loadEnv()` (dotenv overrides\n * by default), and `src/config/http.ts` then resolves its port from that store,\n * so anything the caller set earlier is gone by the time the port is read.\n * Writing into `config` here is also the only channel that works — assigning\n * `process.env.HTTP_PORT` has no effect, since `env()` reads dotenv's own store\n * and never falls back to `process.env`.\n */\nasync function applyTestServerPort(port?: number): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) {\n if (port !== undefined) {\n throw new Error(\n `startHttpTestServer({ port: ${port} }) was given a port, but this app has no \\`http\\` config, so no HTTP server is started. Add \\`src/config/http.ts\\` first.`,\n );\n }\n\n return;\n }\n\n if (port !== undefined) {\n config.set(\"http.port\", port);\n }\n\n const resolvedPort = config.get(\"http.port\");\n\n // No configured port at all is fine: Fastify falls back to its own default and\n // the workers resolve the same one from their own config, so both ends agree\n // without the channel.\n //\n // A non-numeric port is NOT fine, and must fail loudly rather than skip the\n // preflight silently. `env()` coerces a value only when it round-trips exactly\n // (`String(Number(v)) === v`), so `HTTP_PORT=3999` and `HTTP_PORT=0` arrive as\n // numbers, but `03999`, `+3999`, `1e3` and anything with stray whitespace stay\n // strings. Letting those through used to reach `HttpConnector.start()`'s\n // `listen({ port })` unchanged — no preflight, no published port, and every\n // test worker resolving a URL from a channel nothing ever wrote to.\n if (typeof resolvedPort !== \"number\") {\n throw new Error(\n `startHttpTestServer() cannot use \\`http.port\\` value ${JSON.stringify(resolvedPort)} — it did not round-trip through Number() (env() only coerces a value when \\`String(Number(v)) === v\\`, so \"03999\", \"+3999\", \"1e3\", and values with stray whitespace stay strings). Fix \\`HTTP_PORT\\` in .env, or pass an explicit numeric port: startHttpTestServer({ port: 3999 }).`,\n );\n }\n\n // `0` is the OS's \"pick a free one for me\" idiom, and the test server cannot\n // support it. Preflighting 0 would bind some unrelated ephemeral port and pass\n // without proving anything, and there is nothing meaningful to publish: the\n // port Fastify ends up binding is never recorded — `HttpConnector.start()`\n // stores the port it ASKED for, not the one it got. Accepting 0 silently is\n // what left `getTestServerUrl()` resolving to `http://host:0` through its own\n // config fallback, so every worker request went to a port nothing listens on.\n if (resolvedPort <= 0) {\n throw new Error(\n `startHttpTestServer() cannot run on port ${resolvedPort}. Pass an explicit port — e.g. startHttpTestServer({ port: 3999 }) — or set \\`http.port\\` in your config. Test workers are separate processes that resolve the server's URL from the port published at startup, and an OS-assigned port is not knowable to them.`,\n );\n }\n\n await assertPortIsAvailable(resolvedPort, config.get(\"http.host\") || \"localhost\");\n\n publishTestServerPort(resolvedPort);\n}\n\n/**\n * Start the HTTP test server (minimal - no file watching)\n * Call this in Vitest's globalSetup\n *\n * @example\n * // run this suite on its own port, whatever `.env` says\n * await startHttpTestServer({ port: 3999 });\n */\nexport async function startHttpTestServer(options: StartHttpTestServerOptions = {}): Promise<void> {\n if (isServerRunning) {\n console.log(\"[test-server] Server already running, skipping start\");\n return;\n }\n\n console.log(\"[test-server] Starting HTTP test server...\");\n\n try {\n // Set environment\n Application.setRuntimeStrategy(\"development\");\n Application.setEnvironment(\"test\");\n\n // Bootstrap (env, etc.)\n await warlockConfigManager.load();\n await bootstrap();\n\n // Initialize file orchestrator (but don't watch)\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n\n // Load config files\n await loadConfigFiles(true);\n\n // Early-phase connectors (database, cache, logger, storage, mailer,\n // herald) must start BEFORE app modules load: a module's `main.ts` boot\n // side-effect can query the DB at import time, so the data source has to\n // be registered first. This mirrors the dev/prod boot order (see\n // `cli-commands.manager`, `production-builder`, and `DevelopmentServer`).\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\n\n // Load application modules (their boot side-effects now see a live DB).\n await filesOrchestrator.moduleLoader.loadAll();\n\n await applyTestServerPort(options.port);\n\n // A rejecting validator (Application.onValidateBoot) must abort boot\n // before late-phase connectors bind a port — same slot as\n // `DevelopmentServer.start()` and the generated production `app.ts`.\n await Application.runStartupValidators();\n\n // Late-phase connectors (http, socket) bind after app code has\n // registered its routes and listeners.\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\n\n isServerRunning = true;\n } catch (error) {\n // This function owns the connectors it starts, so a failure part-way\n // through has to leave nothing behind. Without this, `isServerRunning` was\n // still `false` — it is only set on the last line — so `globalTeardown`'s\n // `stopHttpTestServer()` reported \"No server to stop\" and walked away from\n // live early-phase connectors and a published port that outlived them.\n await unwindPartialStartup();\n\n // Rethrown unchanged: the startup failure is the one the caller needs to\n // read. A cleanup failure that replaced it would hide the actual cause.\n throw error;\n }\n}\n\n/**\n * Best-effort teardown of whatever a failed {@link startHttpTestServer} managed\n * to start.\n *\n * Every step is independent and none may throw: this runs while an error is\n * already in flight, and the caller is about to rethrow it.\n */\nasync function unwindPartialStartup(): Promise<void> {\n try {\n await connectorsManager.shutdown();\n } catch (shutdownError) {\n // Reported, not thrown — it is secondary to the startup error being\n // rethrown, and losing that one to this would be the worse outcome.\n console.error(\"[test-server] Cleanup after a failed start did not complete:\", shutdownError);\n }\n\n withdrawTestServerPort();\n isServerRunning = false;\n}\n\n/**\n * Stop the HTTP test server\n * Call this in Vitest's globalTeardown\n */\nexport async function stopHttpTestServer(): Promise<void> {\n if (!isServerRunning) {\n console.log(\"[test-server] No server to stop\");\n return;\n }\n\n try {\n await connectorsManager.shutdown();\n\n console.log(\"[test-server] HTTP test server stopped\");\n } catch (error) {\n // Surfaced, never swallowed — cleaning up is not the same as the shutdown\n // having succeeded, and a suite whose teardown failed should say so.\n console.error(\"[test-server] Error stopping HTTP server:\", error);\n\n throw error;\n } finally {\n // In a `finally` rather than after the `await`: when `shutdown()` threw,\n // the published port survived the server it pointed at, so the next run in\n // the same process inherited a stale one and sent every request to it.\n withdrawTestServerPort();\n\n isServerRunning = false;\n }\n}\n\n/**\n * Check if test server is running\n */\nexport function isTestServerRunning(): boolean {\n return isServerRunning;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAI,kBAAkB;;;;;;;;;;;;AAuBtB,eAAe,oBAAoB,MAA8B;CAG/D,IAAI,CAFe,OAAO,IAAI,MAEhB,GAAG;EACf,IAAI,SAAS,QACX,MAAM,IAAI,MACR,+BAA+B,KAAK,2HACtC;EAGF;CACF;CAEA,IAAI,SAAS,QACX,OAAO,IAAI,aAAa,IAAI;CAG9B,MAAM,eAAe,OAAO,IAAI,WAAW;CAa3C,IAAI,OAAO,iBAAiB,UAC1B,MAAM,IAAI,MACR,wDAAwD,KAAK,UAAU,YAAY,EAAE,sRACvF;CAUF,IAAI,gBAAgB,GAClB,MAAM,IAAI,MACR,4CAA4C,aAAa,iQAC3D;CAGF,MAAM,sBAAsB,cAAc,OAAO,IAAI,WAAW,KAAK,WAAW;CAEhF,sBAAsB,YAAY;AACpC;;;;;;;;;AAUA,eAAsB,oBAAoB,UAAsC,CAAC,GAAkB;CACjG,IAAI,iBAAiB;EACnB,QAAQ,IAAI,sDAAsD;EAClE;CACF;CAEA,QAAQ,IAAI,4CAA4C;CAExD,IAAI;EAEF,YAAY,mBAAmB,aAAa;EAC5C,YAAY,eAAe,MAAM;EAGjC,MAAM,qBAAqB,KAAK;EAChC,MAAM,UAAU;EAGhB,MAAM,kBAAkB,KAAK;EAC7B,MAAM,kBAAkB,cAAc;EAGtC,MAAM,gBAAgB,IAAI;EAO1B,MAAM,kBAAkB,kBAAwC;EAGhE,MAAM,kBAAkB,aAAa,QAAQ;EAE7C,MAAM,oBAAoB,QAAQ,IAAI;EAKtC,MAAM,YAAY,qBAAqB;EAIvC,MAAM,kBAAkB,iBAAuC;EAE/D,kBAAkB;CACpB,SAAS,OAAO;EAMd,MAAM,qBAAqB;EAI3B,MAAM;CACR;AACF;;;;;;;;AASA,eAAe,uBAAsC;CACnD,IAAI;EACF,MAAM,kBAAkB,SAAS;CACnC,SAAS,eAAe;EAGtB,QAAQ,MAAM,gEAAgE,aAAa;CAC7F;CAEA,uBAAuB;CACvB,kBAAkB;AACpB;;;;;AAMA,eAAsB,qBAAoC;CACxD,IAAI,CAAC,iBAAiB;EACpB,QAAQ,IAAI,iCAAiC;EAC7C;CACF;CAEA,IAAI;EACF,MAAM,kBAAkB,SAAS;EAEjC,QAAQ,IAAI,wCAAwC;CACtD,SAAS,OAAO;EAGd,QAAQ,MAAM,6CAA6C,KAAK;EAEhE,MAAM;CACR,UAAU;EAIR,uBAAuB;EAEvB,kBAAkB;CACpB;AACF;;;;AAKA,SAAgB,sBAA+B;CAC7C,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-lifecycle-state.mjs","names":[],"sources":["../../../../../../../core/src/tests/test-lifecycle-state.ts"],"sourcesContent":["/**\n * Lifecycle bookkeeping for the worker test runtime.\n *\n * Owns one thing: WHERE the state lives. `setupTest` / `teardownTest` own what\n * the transitions mean.\n *\n * The storage rule is the defect this module exists to fix. A module-scoped\n * `let` is rebuilt for every test file — measured across `forks|threads` ×\n * `isolate true|false` in `proofs/2026-08-12-nova-vitest-setupfiles-lifetime.md`\n * — while the worker itself, and every socket, pool and timer it holds, can\n * survive that rebuild. State that resets precisely where live resources do not\n * is not a guard; it is a lie about what is running.\n *\n * `globalThis` is the right scope for it: one per process for a fork worker, one\n * per thread for a thread worker, never shared between distinct workers — which\n * is exactly the boundary the resources themselves live inside.\n */\nimport type {
|
|
1
|
+
{"version":3,"file":"test-lifecycle-state.mjs","names":[],"sources":["../../../../../../../core/src/tests/test-lifecycle-state.ts"],"sourcesContent":["/**\n * Lifecycle bookkeeping for the worker test runtime.\n *\n * Owns one thing: WHERE the state lives. `setupTest` / `teardownTest` own what\n * the transitions mean.\n *\n * The storage rule is the defect this module exists to fix. A module-scoped\n * `let` is rebuilt for every test file — measured across `forks|threads` ×\n * `isolate true|false` in `proofs/2026-08-12-nova-vitest-setupfiles-lifetime.md`\n * — while the worker itself, and every socket, pool and timer it holds, can\n * survive that rebuild. State that resets precisely where live resources do not\n * is not a guard; it is a lie about what is running.\n *\n * `globalThis` is the right scope for it: one per process for a fork worker, one\n * per thread for a thread worker, never shared between distinct workers — which\n * is exactly the boundary the resources themselves live inside.\n */\nimport type { RequestedTestConnectors, TestConnectorsSelection } from \"./test-connectors-selection\";\nimport type { TestTimeoutScheduler } from \"./test-setup-timeout\";\n\n/**\n * The transitions the lifecycle serializes through.\n *\n * ```\n * idle -> starting -> ready -> stopping -> idle\n * \\-> poisoned\n * ```\n *\n * `poisoned` is reached when the shutdown layer reports a failure: the runtime\n * is neither up nor provably down, so the next `setupTest` must refuse rather\n * than stack a second runtime on top of leaked resources.\n *\n * @internal No state API is public.\n */\nexport type TestLifecycleState = \"idle\" | \"starting\" | \"ready\" | \"stopping\" | \"poisoned\";\n\n/**\n * The single in-flight setup attempt that concurrent `setupTest` callers share.\n *\n * @internal\n */\nexport type TestSetupAttempt = {\n /**\n * What the caller that opened the attempt asked for, before config was read.\n */\n readonly requested: RequestedTestConnectors;\n\n /**\n * Settles once the attempt has consulted project config, and therefore doubles\n * as the \"config is readable now\" barrier a later caller needs before it can\n * resolve its own config-derived selection and compare the two. Rejects with\n * the startup error when the attempt fails before reaching that point.\n */\n readonly effectiveSelection: Promise<TestConnectorsSelection>;\n\n /**\n * The attempt itself. Resolves when the runtime is ready, rejects with the\n * original startup error.\n */\n readonly completion: Promise<void>;\n};\n\n/**\n * @internal\n */\nexport type TestLifecycleRegistry = {\n state: TestLifecycleState;\n activeSelection?: TestConnectorsSelection;\n setupAttempt?: TestSetupAttempt;\n teardownAttempt?: Promise<void>;\n\n /**\n * How the setup bound is scheduled. Specs replace it to prove the guard in\n * milliseconds rather than outliving a real two-minute timeout; production\n * leaves it unset and gets {@link scheduleRealTimeout}.\n *\n * It lives here, on the runtime-context registry, for the same reason the\n * state does: a spec that could only reach a module-level slot would be\n * relying on the very rebuild this module exists to survive.\n */\n scheduleTimeout?: TestTimeoutScheduler;\n\n /**\n * Overrides `tests.setupTimeout` and the default. Internal, for specs — the\n * public surface stays the `setupTest` / `teardownTest` pair.\n */\n setupTimeoutOverride?: number;\n};\n\n/**\n * Where the registry hangs off the runtime context.\n *\n * @internal Exported for specs, which must be able to reach the same slot the\n * implementation uses — a spec that could only reset module state would be\n * testing the very assumption this module rejects. Deliberately not re-exported\n * from `src/tests/index.ts`.\n */\nexport const TEST_LIFECYCLE_REGISTRY_KEY = Symbol.for(\"@warlock.js/core:tests:lifecycle\");\n\ntype TestLifecycleHost = typeof globalThis & {\n [TEST_LIFECYCLE_REGISTRY_KEY]?: TestLifecycleRegistry;\n};\n\n/**\n * Read the runtime context's lifecycle registry, creating it on first use.\n *\n * @internal\n */\nexport function getTestLifecycleRegistry(): TestLifecycleRegistry {\n const host = globalThis as TestLifecycleHost;\n const registry = host[TEST_LIFECYCLE_REGISTRY_KEY];\n\n if (registry) {\n return registry;\n }\n\n const freshRegistry: TestLifecycleRegistry = { state: \"idle\" };\n\n host[TEST_LIFECYCLE_REGISTRY_KEY] = freshRegistry;\n\n return freshRegistry;\n}\n"],"mappings":";;;;;;;;;AAiGA,MAAa,8BAA8B,OAAO,IAAI,kCAAkC;;;;;;AAWxF,SAAgB,2BAAkD;CAChE,MAAM,OAAO;CACb,MAAM,WAAW,KAAK;CAEtB,IAAI,UACF,OAAO;CAGT,MAAM,gBAAuC,EAAE,OAAO,OAAO;CAE7D,KAAK,+BAA+B;CAEpC,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-setup-timeout.mjs","names":[],"sources":["../../../../../../../core/src/tests/test-setup-timeout.ts"],"sourcesContent":["/**\n * The setup bound for the worker test lifecycle.\n *\n * Owns one decision: how long a `setupTest` attempt may run before the\n * lifecycle gives up on it. `vitest-setup.ts` owns what giving up MEANS.\n *\n * This is a HANG GUARD, not a startup-performance deadline. A cold database, a\n * container that has just been started, a first-run migration — all of those are\n * legitimately slow and none of them are a stuck lifecycle. The default is\n * therefore generous on purpose: it exists so that a setup which will never\n * finish fails with a sentence a caller can act on, instead of stranding the\n * lifecycle in `starting` where `teardownTest`'s wait-then-re-enter path spins\n * until the worker dies of heap exhaustion.\n */\nimport type { GenericObject } from \"@mongez/reinforcements\";\nimport { config } from \"../config\";\n\n/**\n * Two minutes. Chosen to sit far above any healthy bootstrap and far below the\n * point where a human stops watching — a stuck worker should announce itself\n * while someone is still looking at the terminal.\n */\nexport const DEFAULT_TEST_SETUP_TIMEOUT = 120_000;\n\n/**\n * A scheduled expiry the lifecycle can cancel when the attempt settles first.\n *\n * @internal\n */\nexport type TestTimeoutHandle = {\n cancel: () => void;\n};\n\n/**\n * How the lifecycle schedules its expiry.\n *\n * Injectable so a spec can prove the bound in milliseconds instead of outliving\n * a real one — see {@link TestLifecycleRegistry.scheduleTimeout}. Deliberately\n * internal: the public API stays the `setupTest` / `teardownTest` pair.\n *\n * @internal\n */\nexport type TestTimeoutScheduler = (
|
|
1
|
+
{"version":3,"file":"test-setup-timeout.mjs","names":[],"sources":["../../../../../../../core/src/tests/test-setup-timeout.ts"],"sourcesContent":["/**\n * The setup bound for the worker test lifecycle.\n *\n * Owns one decision: how long a `setupTest` attempt may run before the\n * lifecycle gives up on it. `vitest-setup.ts` owns what giving up MEANS.\n *\n * This is a HANG GUARD, not a startup-performance deadline. A cold database, a\n * container that has just been started, a first-run migration — all of those are\n * legitimately slow and none of them are a stuck lifecycle. The default is\n * therefore generous on purpose: it exists so that a setup which will never\n * finish fails with a sentence a caller can act on, instead of stranding the\n * lifecycle in `starting` where `teardownTest`'s wait-then-re-enter path spins\n * until the worker dies of heap exhaustion.\n */\nimport type { GenericObject } from \"@mongez/reinforcements\";\nimport { config } from \"../config\";\n\n/**\n * Two minutes. Chosen to sit far above any healthy bootstrap and far below the\n * point where a human stops watching — a stuck worker should announce itself\n * while someone is still looking at the terminal.\n */\nexport const DEFAULT_TEST_SETUP_TIMEOUT = 120_000;\n\n/**\n * A scheduled expiry the lifecycle can cancel when the attempt settles first.\n *\n * @internal\n */\nexport type TestTimeoutHandle = {\n cancel: () => void;\n};\n\n/**\n * How the lifecycle schedules its expiry.\n *\n * Injectable so a spec can prove the bound in milliseconds instead of outliving\n * a real one — see {@link TestLifecycleRegistry.scheduleTimeout}. Deliberately\n * internal: the public API stays the `setupTest` / `teardownTest` pair.\n *\n * @internal\n */\nexport type TestTimeoutScheduler = (timeout: number, onExpiry: () => void) => TestTimeoutHandle;\n\n/**\n * The real scheduler.\n *\n * `unref` matters: a pending hang guard must never be the reason a worker stays\n * alive after its tests have finished.\n *\n * @internal\n */\nexport const scheduleRealTimeout: TestTimeoutScheduler = (timeout, onExpiry) => {\n const timer = setTimeout(onExpiry, timeout);\n\n timer.unref?.();\n\n return { cancel: () => clearTimeout(timer) };\n};\n\n/**\n * Read `tests.setupTimeout`, or `undefined` when the project has not set one.\n *\n * Only meaningful once config files are loaded — before that, every project\n * looks like a project without a `tests` config.\n *\n * @throws when the key is present but is not a positive finite number of\n * milliseconds. Falling back to the default would silently erase a project's\n * configuration, which is the same defect the connector precedence rules out.\n */\nexport function readConfiguredSetupTimeout(): number | undefined {\n // `config.get` resolves an absent key to its default, and ITS default is\n // `null` — not `{}` — so the explicit `{}` is what keeps this from throwing\n // \"Cannot read properties of null\" on a project with no `src/config/tests.ts`.\n const testsConfig = config.get<GenericObject>(\"tests\", {});\n const configured = testsConfig?.setupTimeout as unknown;\n\n if (configured === undefined) {\n return undefined;\n }\n\n if (typeof configured !== \"number\" || !Number.isFinite(configured) || configured <= 0) {\n throw new Error(\n `tests.setupTimeout must be a positive number of milliseconds, but it is ${JSON.stringify(configured)}. Remove the key to use the default of ${DEFAULT_TEST_SETUP_TIMEOUT}ms.`,\n );\n }\n\n return configured;\n}\n\n/**\n * The message a caller sees when the bound expires.\n *\n * Names three things, because a hang guard that only says \"timed out\" sends the\n * reader to the framework's source: what state the lifecycle is stuck in, what\n * bound it exceeded, and how to raise that bound.\n */\nexport function describeExpiredSetup(timeout: number): string {\n return (\n `setupTest() did not finish within ${timeout}ms and is stuck in the \"starting\" state. ` +\n \"The lifecycle is now poisoned: whatever that attempt had already started is not known to be \" +\n \"closed, so later setupTest() calls refuse until the Vitest worker is recycled. \" +\n `If your cold start is legitimately slower than this, raise the bound with \\`tests.setupTimeout\\` ` +\n `in \\`src/config/tests.ts\\` — milliseconds, default ${DEFAULT_TEST_SETUP_TIMEOUT}.`\n );\n}\n"],"mappings":";;;;;;;;;AAsBA,MAAa,6BAA6B;;;;;;;;;AA8B1C,MAAa,uBAA6C,SAAS,aAAa;CAC9E,MAAM,QAAQ,WAAW,UAAU,OAAO;CAE1C,MAAM,QAAQ;CAEd,OAAO,EAAE,cAAc,aAAa,KAAK,EAAE;AAC7C;;;;;;;;;;;AAYA,SAAgB,6BAAiD;CAK/D,MAAM,aADc,OAAO,IAAmB,SAAS,CAAC,CAC3B,CAAC,EAAE;CAEhC,IAAI,eAAe,QACjB;CAGF,IAAI,OAAO,eAAe,YAAY,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,GAClF,MAAM,IAAI,MACR,2EAA2E,KAAK,UAAU,UAAU,EAAE,yCAAyC,2BAA2B,IAC5K;CAGF,OAAO;AACT;;;;;;;;AASA,SAAgB,qBAAqB,SAAyB;CAC5D,OACE,qCAAqC,QAAQ,0WAIS,2BAA2B;AAErF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-setup.d.mts","names":[],"sources":["../../../../../../../core/src/tests/vitest-setup.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"vitest-setup.d.mts","names":[],"sources":["../../../../../../../core/src/tests/vitest-setup.ts"],"mappings":";;;KAiDY,gBAAA;EAAA;;;;AAS0B;AAOtC;;;EAPE,UAAA,GAAa,uBAAuB;AAAA;;;;AAQF;cADvB,kBAAA,SAA2B,KAAK;cACxB,OAAA;AAAA;;;;;;AAqC+C;AAgDpE;;;;AAA6C;;;;;;;;;;;;;;;;;iBAhDvB,SAAA,CAAU,OAAA,GAAU,gBAAA,GAAmB,OAAO;;;;;;;;;;;;;;;;;;iBAgD9C,YAAA,IAAgB,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-setup.mjs","names":[],"sources":["../../../../../../../core/src/tests/vitest-setup.ts"],"sourcesContent":["/**\r\n * Worker test lifecycle — `setupTest` / `teardownTest`.\r\n *\r\n * Implements `contracts/2026-08-12-test-worker-lifecycle.md`. The pair is\r\n * context-scoped, not \"once per worker\": Vitest runs `setupFiles` before EVERY\r\n * test file and rebuilds the module registry each time, so the harness that\r\n * calls `setupTest` owns pairing it with `teardownTest` in the same runtime\r\n * context.\r\n *\r\n * Ownership, stated once because teardown is wider than it looks:\r\n *\r\n * - the runtime assumes exclusive ownership of the process-local\r\n * `connectorsManager` for its lifetime;\r\n * - it owns the framework bootstrap, the application shutdown hooks, and the\r\n * connectors it starts — including the `connectors: false` case, where\r\n * bootstrap still registers hooks;\r\n * - teardown is MANAGER-WIDE. `connectorsManager.shutdown()` has no selective\r\n * ownership handle, so a lifecycle that started two connectors still closes\r\n * every connector the manager holds. Mixing `setupTest` with manual connector\r\n * startup in the same process is unsupported for exactly that reason.\r\n * - HTTP global setup stays separately owned by `startHttpTestServer` /\r\n * `stopHttpTestServer`.\r\n */\r\nimport { Application } from \"../application/application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport {\r\n describeConnectorsSelection,\r\n isSameConnectorsSelection,\r\n normalizeConnectorNames,\r\n readRequestedConnectors,\r\n resolveEffectiveConnectors,\r\n} from \"./test-connectors-selection\";\r\nimport type {\r\n RequestedTestConnectors,\r\n TestConnectorsSelection,\r\n} from \"./test-connectors-selection\";\r\nimport { getTestLifecycleRegistry } from \"./test-lifecycle-state\";\r\nimport type { TestLifecycleRegistry, TestSetupAttempt } from \"./test-lifecycle-state\";\r\nimport {\r\n DEFAULT_TEST_SETUP_TIMEOUT,\r\n describeExpiredSetup,\r\n readConfiguredSetupTimeout,\r\n scheduleRealTimeout,\r\n} from \"./test-setup-timeout\";\r\nimport type { TestTimeoutHandle } from \"./test-setup-timeout\";\r\n\r\nexport type { TestConnectorsSelection } from \"./test-connectors-selection\";\r\n\r\nexport type TestSetupOptions = {\r\n /**\r\n * Which connectors to start.\r\n *\r\n * Precedence is `explicit non-undefined option > tests.connectors config >\r\n * true`. Omitting the property, or passing `undefined`, lets project config\r\n * apply — so an optional variable that happens to be `undefined` cannot erase\r\n * it.\r\n */\r\n connectors?: TestConnectorsSelection;\r\n};\r\n\r\n/**\r\n * Raised by the lifecycle itself, never by the runtime it manages — a conflict\r\n * between two calls, or a refusal to reuse a runtime that did not close.\r\n */\r\nexport class TestLifecycleError extends Error {\r\n public constructor(message: string) {\r\n super(message);\r\n\r\n this.name = \"TestLifecycleError\";\r\n }\r\n}\r\n\r\nconst POISONED_MESSAGE =\r\n \"setupTest() refuses to start a test runtime: an earlier teardownTest() reported a shutdown failure, so the connectors, ports and pools it owned are not known to be closed. Restart the Vitest worker before setting up again, or call teardownTest() to retry the shutdown — the lifecycle only returns to idle on a fully successful retry.\";\r\n\r\n/**\r\n * Bootstrap the test runtime and start the selected connectors.\r\n *\r\n * ⚠ Runs once per TEST FILE, not once per worker: `setupFiles` is executed for\r\n * every file and its module registry is rebuilt with it, measured across both\r\n * pools with isolation on and off. The lifecycle state lives on the runtime\r\n * context rather than in this module precisely so that a rebuild cannot hide a\r\n * runtime that is still up.\r\n *\r\n * Repeated calls:\r\n *\r\n * - concurrent calls with the same effective selection share one startup;\r\n * - while ready, the same effective selection is a no-op;\r\n * - while starting or ready, a DIFFERENT effective selection rejects and leaves\r\n * the live runtime untouched;\r\n * - a failed setup unwinds and returns to idle, so a clean retry is allowed;\r\n * - after a successful `teardownTest`, a later setup may use a different\r\n * selection.\r\n *\r\n * @example\r\n * // let project config decide, falling back to the default set\r\n * await setupTest();\r\n *\r\n * @example\r\n * // bootstrap only — no connectors, whatever config says\r\n * await setupTest({ connectors: false });\r\n */\r\nexport async function setupTest(options?: TestSetupOptions): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n const requested = readRequestedConnectors(options?.connectors);\r\n\r\n if (registry.state === \"poisoned\") {\r\n throw new TestLifecycleError(POISONED_MESSAGE);\r\n }\r\n\r\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\r\n await settled(registry.teardownAttempt);\r\n\r\n return setupTest(options);\r\n }\r\n\r\n if (registry.state === \"ready\") {\r\n assertReadySelectionMatches(registry.activeSelection, requested);\r\n\r\n return;\r\n }\r\n\r\n if (registry.state === \"starting\" && registry.setupAttempt) {\r\n const attempt = registry.setupAttempt;\r\n\r\n await assertPendingSetupMatches(attempt, requested);\r\n\r\n return attempt.completion;\r\n }\r\n\r\n return startTestRuntime(requested);\r\n}\r\n\r\n/**\r\n * Shut the test runtime down and release the lifecycle.\r\n *\r\n * - idle is a no-op;\r\n * - concurrent calls share one attempt;\r\n * - a call made while setup is still running waits for that attempt to settle,\r\n * then closes the runtime if it succeeded;\r\n * - the local \"ready\" state is always cleared, including when shutdown rejects;\r\n * - a shutdown rejection is surfaced, never swallowed, and poisons the\r\n * lifecycle — a reported close failure is not proof that anything closed, so\r\n * later `setupTest` calls refuse until the worker is restarted or a retry\r\n * fully succeeds.\r\n *\r\n * ⚠ Individual connector shutdown failures are caught and logged inside\r\n * `connectorsManager.shutdown()`, so they never reach this function and never\r\n * poison anything. This lifecycle can only surface what that layer reports.\r\n */\r\nexport async function teardownTest(): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\r\n return registry.teardownAttempt;\r\n }\r\n\r\n if (registry.state === \"starting\" && registry.setupAttempt) {\r\n // A runtime that is still opening cannot be closed halfway. Wait for the\r\n // attempt to settle: a failed one has already unwound itself and left the\r\n // lifecycle idle, which the re-entry below then reads as \"nothing to do\".\r\n await settled(registry.setupAttempt.completion);\r\n\r\n return teardownTest();\r\n }\r\n\r\n if (registry.state === \"idle\") {\r\n return;\r\n }\r\n\r\n registry.state = \"stopping\";\r\n\r\n // Deferred by a microtask so the registry is fully published before the\r\n // attempt's first line runs — a synchronous throw would otherwise reach the\r\n // `finally` below, and reset the state, before this function had recorded\r\n // that a teardown was in flight at all.\r\n const attempt = Promise.resolve().then(() => runTestRuntimeShutdown());\r\n\r\n registry.teardownAttempt = attempt;\r\n\r\n return attempt;\r\n}\r\n\r\n/**\r\n * The setup attempt's hang guard, as the lifecycle consumes it.\r\n *\r\n * The bound's POLICY — the default, the config key, the message — lives in\r\n * `test-setup-timeout.ts`. What expiry MEANS lives here.\r\n *\r\n * @internal\r\n */\r\ntype SetupBound = {\r\n /**\r\n * Rejects with the expiry error. Never resolves: the attempt is the only side\r\n * of the race that can succeed.\r\n */\r\n readonly expiry: Promise<never>;\r\n\r\n /**\r\n * Re-aim the bound at `next` ms measured from when the ATTEMPT STARTED, once\r\n * config has made `tests.setupTimeout` readable.\r\n */\r\n rearm(next: number): void;\r\n\r\n /** The race is over — cancel whatever timer is still pending. */\r\n settle(): void;\r\n\r\n /** Whether the bound, rather than the attempt, won the race. */\r\n hasExpired(): boolean;\r\n};\r\n\r\n/**\r\n * Arm the hang guard for one setup attempt.\r\n *\r\n * Armed at the default BEFORE the attempt runs, because `tests.setupTimeout` is\r\n * not readable until the attempt has loaded config — and loading config is\r\n * itself one of the steps that can hang. `rearm` then narrows or widens it the\r\n * moment config arrives.\r\n */\r\nfunction armSetupBound(registry: TestLifecycleRegistry): SetupBound {\r\n const schedule = registry.scheduleTimeout ?? scheduleRealTimeout;\r\n const armedAt = Date.now();\r\n\r\n let handle: TestTimeoutHandle | undefined;\r\n let expired = false;\r\n let closed = false;\r\n let expire: (error: unknown) => void = () => undefined;\r\n\r\n const expiry = new Promise<never>((_resolve, reject) => {\r\n expire = reject;\r\n });\r\n\r\n // Only `awaitSetupWithinBound` observes this, and only until the attempt wins\r\n // — after that nobody is listening, and an unobserved rejection surfaces as an\r\n // unhandled rejection that fails an unrelated test file. Same reason as the\r\n // `effectiveSelection` barrier below.\r\n expiry.catch(() => undefined);\r\n\r\n const expireNow = (bound: number): void => {\r\n if (closed) {\r\n return;\r\n }\r\n\r\n closed = true;\r\n expired = true;\r\n\r\n handle?.cancel();\r\n handle = undefined;\r\n\r\n expire(new TestLifecycleError(describeExpiredSetup(bound)));\r\n };\r\n\r\n const armFor = (bound: number, delay: number): void => {\r\n if (closed) {\r\n return;\r\n }\r\n\r\n handle?.cancel();\r\n\r\n // Already overdue. A bound the attempt has ALREADY exceeded must fire now,\r\n // not be rescheduled for time that was spent before it was known.\r\n if (delay <= 0) {\r\n expireNow(bound);\r\n\r\n return;\r\n }\r\n\r\n handle = schedule(delay, () => expireNow(bound));\r\n };\r\n\r\n const initialBound = registry.setupTimeoutOverride ?? DEFAULT_TEST_SETUP_TIMEOUT;\r\n\r\n armFor(initialBound, initialBound);\r\n\r\n return {\r\n expiry,\r\n\r\n // Measured from `armedAt`, never from now: adding a configured bound to the\r\n // time already spent would make the real bound default-plus-configured, and\r\n // a project that lowered the key would get a LONGER guard than the default.\r\n rearm: (next) => armFor(next, next - (Date.now() - armedAt)),\r\n\r\n settle: () => {\r\n closed = true;\r\n\r\n handle?.cancel();\r\n handle = undefined;\r\n },\r\n\r\n hasExpired: () => expired,\r\n };\r\n}\r\n\r\n/**\r\n * Race one setup attempt against its bound.\r\n *\r\n * The losing attempt is NOT cancellable — nothing here can interrupt a bootstrap\r\n * stuck in a socket connect — so when the bound wins, the lifecycle is poisoned\r\n * and every later state write from that abandoned attempt is dropped. That\r\n * dropping happens in `runTestRuntimeStartup`, guarded on `hasExpired()`;\r\n * without it the still-running attempt would quietly overwrite `poisoned` with\r\n * `ready` or `idle` and hand the next caller a runtime nobody owns.\r\n */\r\nasync function awaitSetupWithinBound(\r\n attempt: Promise<void>,\r\n bound: SetupBound,\r\n registry: TestLifecycleRegistry,\r\n): Promise<void> {\r\n try {\r\n await Promise.race([attempt, bound.expiry]);\r\n } catch (error) {\r\n if (bound.hasExpired()) {\r\n registry.state = \"poisoned\";\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n }\r\n\r\n throw error;\r\n } finally {\r\n bound.settle();\r\n }\r\n}\r\n\r\n/**\r\n * Open a new setup attempt and publish it before it runs.\r\n */\r\nfunction startTestRuntime(requested: RequestedTestConnectors): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n let publishEffectiveSelection: (selection: TestConnectorsSelection) => void = () => undefined;\r\n let failEffectiveSelection: (error: unknown) => void = () => undefined;\r\n\r\n const effectiveSelection = new Promise<TestConnectorsSelection>((resolve, reject) => {\r\n publishEffectiveSelection = resolve;\r\n failEffectiveSelection = reject;\r\n });\r\n\r\n // Only the mixed explicit/config comparison ever awaits this barrier, so its\r\n // rejection is usually unobserved — and an unobserved rejection would surface\r\n // as an unhandled rejection and fail an unrelated test file.\r\n effectiveSelection.catch(() => undefined);\r\n\r\n const bound = armSetupBound(registry);\r\n\r\n const attempt = Promise.resolve().then(() =>\r\n runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound),\r\n );\r\n\r\n // The attempt outlives a bound that beat it, and by then `completion` has\r\n // already rejected with the expiry error — so the attempt's own late rejection\r\n // has no observer. Same unhandled-rejection hazard as above.\r\n attempt.catch(() => undefined);\r\n\r\n // `teardownTest` waits on this same promise, which is how a teardown requested\r\n // during setup inherits the bound. There is deliberately no second timer and\r\n // no separate teardown deadline.\r\n const completion = awaitSetupWithinBound(attempt, bound, registry);\r\n\r\n registry.state = \"starting\";\r\n registry.setupAttempt = { requested, effectiveSelection, completion };\r\n\r\n return completion;\r\n}\r\n\r\n/**\r\n * The startup attempt itself.\r\n *\r\n * On failure: best-effort unwind, reset bookkeeping, rethrow the ORIGINAL error\r\n * unchanged. A cleanup failure is a secondary diagnostic and never replaces the\r\n * cause the caller needs to read.\r\n */\r\nasync function runTestRuntimeStartup(\r\n requested: RequestedTestConnectors,\r\n publishEffectiveSelection: (selection: TestConnectorsSelection) => void,\r\n failEffectiveSelection: (error: unknown) => void,\r\n bound: SetupBound,\r\n): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n try {\r\n Application.setEnvironment(\"test\");\r\n\r\n await warlockConfigManager.load();\r\n await bootstrap();\r\n\r\n await filesOrchestrator.init();\r\n await loadConfigFiles(true);\r\n\r\n // The first point at which `tests.setupTimeout` is readable at all. An\r\n // invalid value throws from here and takes the normal failure path — falling\r\n // back to the default would silently erase what the project configured.\r\n const configuredTimeout = readConfiguredSetupTimeout();\r\n\r\n if (configuredTimeout !== undefined) {\r\n bound.rearm(configuredTimeout);\r\n }\r\n\r\n // Resolved here and not at call time: the config layer is only readable once\r\n // the four steps above have run.\r\n const effectiveSelection = resolveEffectiveConnectors(requested);\r\n\r\n // Every state write below is conditional on this attempt still being the one\r\n // the lifecycle is waiting for. Once the bound has expired the lifecycle is\r\n // poisoned and this attempt is abandoned — it may still be running, but it\r\n // no longer speaks for the runtime.\r\n if (!bound.hasExpired()) {\r\n registry.activeSelection = effectiveSelection;\r\n }\r\n\r\n publishEffectiveSelection(effectiveSelection);\r\n\r\n await startSelectedConnectors(effectiveSelection);\r\n\r\n if (!bound.hasExpired()) {\r\n registry.state = \"ready\";\r\n registry.setupAttempt = undefined;\r\n }\r\n } catch (error) {\r\n failEffectiveSelection(error);\r\n\r\n await unwindPartialStartup();\r\n\r\n if (!bound.hasExpired()) {\r\n registry.state = \"idle\";\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n }\r\n\r\n console.error(\"[vitest-setup] Failed to setup test environment:\", error);\r\n\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * `false` means none at all. Everything else starts something: an array starts\r\n * exactly those, and `true` starts the default set — all but http, which is the\r\n * global setup's job and shared across every worker.\r\n */\r\nasync function startSelectedConnectors(selection: TestConnectorsSelection): Promise<void> {\r\n if (selection === false) {\r\n return;\r\n }\r\n\r\n if (Array.isArray(selection)) {\r\n await connectorsManager.start(normalizeConnectorNames(selection));\r\n\r\n return;\r\n }\r\n\r\n await connectorsManager.startWithout([\"http\"]);\r\n}\r\n\r\n/**\r\n * Best-effort teardown of whatever a failed startup managed to start.\r\n *\r\n * Runs while an error is already in flight, so no step here may throw and every\r\n * step is attempted independently.\r\n */\r\nasync function unwindPartialStartup(): Promise<void> {\r\n // Two steps rather than one: `connectorsManager.shutdown()` runs the\r\n // application hooks itself, but it runs them FIRST and does not isolate their\r\n // rejection — so a hook that rejects would take the connector teardown down\r\n // with it and leave every connector up. Both calls are idempotent, so running\r\n // the hooks on their own first costs nothing and means neither failure can\r\n // skip the other step.\r\n await attemptCleanupStep(\"application shutdown hooks\", () => Application.runShutdownHooks());\r\n await attemptCleanupStep(\"connectors shutdown\", () => connectorsManager.shutdown());\r\n}\r\n\r\n/**\r\n * Run one cleanup step, isolating its failure so the next step still runs.\r\n */\r\nasync function attemptCleanupStep(step: string, run: () => Promise<void>): Promise<void> {\r\n try {\r\n await run();\r\n } catch (error) {\r\n // Reported, not thrown — it is secondary to the startup error about to be\r\n // rethrown, and losing that one to this would be the worse outcome.\r\n console.error(`[vitest-setup] Cleanup after a failed setup did not complete (${step}):`, error);\r\n }\r\n}\r\n\r\n/**\r\n * The teardown attempt itself.\r\n */\r\nasync function runTestRuntimeShutdown(): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n let shutdownSucceeded = false;\r\n\r\n try {\r\n // Manager-wide, and deliberately called whatever the selection was: with\r\n // `connectors: false` nothing was started, but bootstrap still registered\r\n // application shutdown hooks, and the manager runs those first (see\r\n // `connectors-manager.ts` — `Application.runShutdownHooks()` is its first\r\n // line). Skipping the call to \"match\" an empty selection would leak them.\r\n await connectorsManager.shutdown();\r\n\r\n shutdownSucceeded = true;\r\n } finally {\r\n // In a `finally`, not after the `await`: a rejected shutdown must not leave\r\n // the lifecycle claiming a runtime it no longer owns. Poisoned rather than\r\n // idle, because a cleared flag does not prove that ports, sockets, pools or\r\n // timers closed.\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n registry.teardownAttempt = undefined;\r\n registry.state = shutdownSucceeded ? \"idle\" : \"poisoned\";\r\n }\r\n}\r\n\r\n/**\r\n * Reject when a ready runtime was set up with a different selection.\r\n */\r\nfunction assertReadySelectionMatches(\r\n activeSelection: TestConnectorsSelection | undefined,\r\n requested: RequestedTestConnectors,\r\n): void {\r\n if (activeSelection === undefined) {\r\n return;\r\n }\r\n\r\n // Config is loaded by the time anything is ready, so the config-derived layer\r\n // is resolvable right here.\r\n const requestedSelection = resolveEffectiveConnectors(requested);\r\n\r\n assertSelectionsMatch(activeSelection, requestedSelection);\r\n}\r\n\r\n/**\r\n * Reject when an in-flight setup attempt is starting a different selection.\r\n */\r\nasync function assertPendingSetupMatches(\r\n attempt: TestSetupAttempt,\r\n requested: RequestedTestConnectors,\r\n): Promise<void> {\r\n const pending = attempt.requested;\r\n\r\n // Two calls that both defer to project config resolve to the same thing by\r\n // construction — knowable without waiting for config to load.\r\n if (!pending.isExplicit && !requested.isExplicit) {\r\n return;\r\n }\r\n\r\n if (pending.isExplicit && requested.isExplicit) {\r\n assertSelectionsMatch(pending.selection, requested.selection);\r\n\r\n return;\r\n }\r\n\r\n // Mixed: one side is config-derived and config is not readable until the\r\n // running attempt has loaded it, so wait for that barrier first.\r\n const activeSelection = await attempt.effectiveSelection;\r\n const requestedSelection = resolveEffectiveConnectors(requested);\r\n\r\n assertSelectionsMatch(activeSelection, requestedSelection);\r\n}\r\n\r\n/**\r\n * Name both selections: a caller looking at this message has to be able to tell\r\n * which call to change without reading the framework's source.\r\n */\r\nfunction assertSelectionsMatch(\r\n activeSelection: TestConnectorsSelection,\r\n requestedSelection: TestConnectorsSelection,\r\n): void {\r\n if (isSameConnectorsSelection(activeSelection, requestedSelection)) {\r\n return;\r\n }\r\n\r\n throw new TestLifecycleError(\r\n `setupTest() is already using ${describeConnectorsSelection(activeSelection)}, and this call asked for ${describeConnectorsSelection(requestedSelection)}. One test runtime serves one connector selection — call teardownTest() before setting up a different one.`,\r\n );\r\n}\r\n\r\n/**\r\n * Await an attempt for its state transition only, not its result.\r\n */\r\nfunction settled(attempt: Promise<void>): Promise<void> {\r\n return attempt.catch(() => undefined);\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EAEb,KAAK,OAAO;CACd;AACF;AAEA,MAAM,mBACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BF,eAAsB,UAAU,SAA2C;CACzE,MAAM,WAAW,yBAAyB;CAC1C,MAAM,YAAY,wBAAwB,SAAS,UAAU;CAE7D,IAAI,SAAS,UAAU,YACrB,MAAM,IAAI,mBAAmB,gBAAgB;CAG/C,IAAI,SAAS,UAAU,cAAc,SAAS,iBAAiB;EAC7D,MAAM,QAAQ,SAAS,eAAe;EAEtC,OAAO,UAAU,OAAO;CAC1B;CAEA,IAAI,SAAS,UAAU,SAAS;EAC9B,4BAA4B,SAAS,iBAAiB,SAAS;EAE/D;CACF;CAEA,IAAI,SAAS,UAAU,cAAc,SAAS,cAAc;EAC1D,MAAM,UAAU,SAAS;EAEzB,MAAM,0BAA0B,SAAS,SAAS;EAElD,OAAO,QAAQ;CACjB;CAEA,OAAO,iBAAiB,SAAS;AACnC;;;;;;;;;;;;;;;;;;AAmBA,eAAsB,eAA8B;CAClD,MAAM,WAAW,yBAAyB;CAE1C,IAAI,SAAS,UAAU,cAAc,SAAS,iBAC5C,OAAO,SAAS;CAGlB,IAAI,SAAS,UAAU,cAAc,SAAS,cAAc;EAI1D,MAAM,QAAQ,SAAS,aAAa,UAAU;EAE9C,OAAO,aAAa;CACtB;CAEA,IAAI,SAAS,UAAU,QACrB;CAGF,SAAS,QAAQ;CAMjB,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,WAAW,uBAAuB,CAAC;CAErE,SAAS,kBAAkB;CAE3B,OAAO;AACT;;;;;;;;;AAsCA,SAAS,cAAc,UAA6C;CAClE,MAAM,WAAW,SAAS,mBAAmB;CAC7C,MAAM,UAAU,KAAK,IAAI;CAEzB,IAAI;CACJ,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,eAAyC;CAE7C,MAAM,SAAS,IAAI,SAAgB,UAAU,WAAW;EACtD,SAAS;CACX,CAAC;CAMD,OAAO,YAAY,MAAS;CAE5B,MAAM,aAAa,UAAwB;EACzC,IAAI,QACF;EAGF,SAAS;EACT,UAAU;EAEV,QAAQ,OAAO;EACf,SAAS;EAET,OAAO,IAAI,mBAAmB,qBAAqB,KAAK,CAAC,CAAC;CAC5D;CAEA,MAAM,UAAU,OAAe,UAAwB;EACrD,IAAI,QACF;EAGF,QAAQ,OAAO;EAIf,IAAI,SAAS,GAAG;GACd,UAAU,KAAK;GAEf;EACF;EAEA,SAAS,SAAS,aAAa,UAAU,KAAK,CAAC;CACjD;CAEA,MAAM,eAAe,SAAS;CAE9B,OAAO,cAAc,YAAY;CAEjC,OAAO;EACL;EAKA,QAAQ,SAAS,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ;EAE3D,cAAc;GACZ,SAAS;GAET,QAAQ,OAAO;GACf,SAAS;EACX;EAEA,kBAAkB;CACpB;AACF;;;;;;;;;;;AAYA,eAAe,sBACb,SACA,OACA,UACe;CACf,IAAI;EACF,MAAM,QAAQ,KAAK,CAAC,SAAS,MAAM,MAAM,CAAC;CAC5C,SAAS,OAAO;EACd,IAAI,MAAM,WAAW,GAAG;GACtB,SAAS,QAAQ;GACjB,SAAS,kBAAkB;GAC3B,SAAS,eAAe;EAC1B;EAEA,MAAM;CACR,UAAU;EACR,MAAM,OAAO;CACf;AACF;;;;AAKA,SAAS,iBAAiB,WAAmD;CAC3E,MAAM,WAAW,yBAAyB;CAE1C,IAAI,kCAAgF;CACpF,IAAI,+BAAyD;CAE7D,MAAM,qBAAqB,IAAI,SAAkC,SAAS,WAAW;EACnF,4BAA4B;EAC5B,yBAAyB;CAC3B,CAAC;CAKD,mBAAmB,YAAY,MAAS;CAExC,MAAM,QAAQ,cAAc,QAAQ;CAEpC,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,WAChC,sBAAsB,WAAW,2BAA2B,wBAAwB,KAAK,CAC3F;CAKA,QAAQ,YAAY,MAAS;CAK7B,MAAM,aAAa,sBAAsB,SAAS,OAAO,QAAQ;CAEjE,SAAS,QAAQ;CACjB,SAAS,eAAe;EAAE;EAAW;EAAoB;CAAW;CAEpE,OAAO;AACT;;;;;;;;AASA,eAAe,sBACb,WACA,2BACA,wBACA,OACe;CACf,MAAM,WAAW,yBAAyB;CAE1C,IAAI;EACF,YAAY,eAAe,MAAM;EAEjC,MAAM,qBAAqB,KAAK;EAChC,MAAM,UAAU;EAEhB,MAAM,kBAAkB,KAAK;EAC7B,MAAM,gBAAgB,IAAI;EAK1B,MAAM,oBAAoB,2BAA2B;EAErD,IAAI,sBAAsB,QACxB,MAAM,MAAM,iBAAiB;EAK/B,MAAM,qBAAqB,2BAA2B,SAAS;EAM/D,IAAI,CAAC,MAAM,WAAW,GACpB,SAAS,kBAAkB;EAG7B,0BAA0B,kBAAkB;EAE5C,MAAM,wBAAwB,kBAAkB;EAEhD,IAAI,CAAC,MAAM,WAAW,GAAG;GACvB,SAAS,QAAQ;GACjB,SAAS,eAAe;EAC1B;CACF,SAAS,OAAO;EACd,uBAAuB,KAAK;EAE5B,MAAM,qBAAqB;EAE3B,IAAI,CAAC,MAAM,WAAW,GAAG;GACvB,SAAS,QAAQ;GACjB,SAAS,kBAAkB;GAC3B,SAAS,eAAe;EAC1B;EAEA,QAAQ,MAAM,oDAAoD,KAAK;EAEvE,MAAM;CACR;AACF;;;;;;AAOA,eAAe,wBAAwB,WAAmD;CACxF,IAAI,cAAc,OAChB;CAGF,IAAI,MAAM,QAAQ,SAAS,GAAG;EAC5B,MAAM,kBAAkB,MAAM,wBAAwB,SAAS,CAAC;EAEhE;CACF;CAEA,MAAM,kBAAkB,aAAa,CAAC,MAAM,CAAC;AAC/C;;;;;;;AAQA,eAAe,uBAAsC;CAOnD,MAAM,mBAAmB,oCAAoC,YAAY,iBAAiB,CAAC;CAC3F,MAAM,mBAAmB,6BAA6B,kBAAkB,SAAS,CAAC;AACpF;;;;AAKA,eAAe,mBAAmB,MAAc,KAAyC;CACvF,IAAI;EACF,MAAM,IAAI;CACZ,SAAS,OAAO;EAGd,QAAQ,MAAM,iEAAiE,KAAK,KAAK,KAAK;CAChG;AACF;;;;AAKA,eAAe,yBAAwC;CACrD,MAAM,WAAW,yBAAyB;CAC1C,IAAI,oBAAoB;CAExB,IAAI;EAMF,MAAM,kBAAkB,SAAS;EAEjC,oBAAoB;CACtB,UAAU;EAKR,SAAS,kBAAkB;EAC3B,SAAS,eAAe;EACxB,SAAS,kBAAkB;EAC3B,SAAS,QAAQ,oBAAoB,SAAS;CAChD;AACF;;;;AAKA,SAAS,4BACP,iBACA,WACM;CACN,IAAI,oBAAoB,QACtB;CAOF,sBAAsB,iBAFK,2BAA2B,SAEE,CAAC;AAC3D;;;;AAKA,eAAe,0BACb,SACA,WACe;CACf,MAAM,UAAU,QAAQ;CAIxB,IAAI,CAAC,QAAQ,cAAc,CAAC,UAAU,YACpC;CAGF,IAAI,QAAQ,cAAc,UAAU,YAAY;EAC9C,sBAAsB,QAAQ,WAAW,UAAU,SAAS;EAE5D;CACF;CAOA,sBAAsB,MAHQ,QAAQ,oBACX,2BAA2B,SAEE,CAAC;AAC3D;;;;;AAMA,SAAS,sBACP,iBACA,oBACM;CACN,IAAI,0BAA0B,iBAAiB,kBAAkB,GAC/D;CAGF,MAAM,IAAI,mBACR,gCAAgC,4BAA4B,eAAe,EAAE,4BAA4B,4BAA4B,kBAAkB,EAAE,2GAC3J;AACF;;;;AAKA,SAAS,QAAQ,SAAuC;CACtD,OAAO,QAAQ,YAAY,MAAS;AACtC"}
|
|
1
|
+
{"version":3,"file":"vitest-setup.mjs","names":[],"sources":["../../../../../../../core/src/tests/vitest-setup.ts"],"sourcesContent":["/**\r\n * Worker test lifecycle — `setupTest` / `teardownTest`.\r\n *\r\n * Implements `contracts/2026-08-12-test-worker-lifecycle.md`. The pair is\r\n * context-scoped, not \"once per worker\": Vitest runs `setupFiles` before EVERY\r\n * test file and rebuilds the module registry each time, so the harness that\r\n * calls `setupTest` owns pairing it with `teardownTest` in the same runtime\r\n * context.\r\n *\r\n * Ownership, stated once because teardown is wider than it looks:\r\n *\r\n * - the runtime assumes exclusive ownership of the process-local\r\n * `connectorsManager` for its lifetime;\r\n * - it owns the framework bootstrap, the application shutdown hooks, and the\r\n * connectors it starts — including the `connectors: false` case, where\r\n * bootstrap still registers hooks;\r\n * - teardown is MANAGER-WIDE. `connectorsManager.shutdown()` has no selective\r\n * ownership handle, so a lifecycle that started two connectors still closes\r\n * every connector the manager holds. Mixing `setupTest` with manual connector\r\n * startup in the same process is unsupported for exactly that reason.\r\n * - HTTP global setup stays separately owned by `startHttpTestServer` /\r\n * `stopHttpTestServer`.\r\n */\r\nimport { Application } from \"../application/application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport {\r\n describeConnectorsSelection,\r\n isSameConnectorsSelection,\r\n normalizeConnectorNames,\r\n readRequestedConnectors,\r\n resolveEffectiveConnectors,\r\n} from \"./test-connectors-selection\";\r\nimport type { RequestedTestConnectors, TestConnectorsSelection } from \"./test-connectors-selection\";\r\nimport { getTestLifecycleRegistry } from \"./test-lifecycle-state\";\r\nimport type { TestLifecycleRegistry, TestSetupAttempt } from \"./test-lifecycle-state\";\r\nimport {\r\n DEFAULT_TEST_SETUP_TIMEOUT,\r\n describeExpiredSetup,\r\n readConfiguredSetupTimeout,\r\n scheduleRealTimeout,\r\n} from \"./test-setup-timeout\";\r\nimport type { TestTimeoutHandle } from \"./test-setup-timeout\";\r\n\r\nexport type { TestConnectorsSelection } from \"./test-connectors-selection\";\r\n\r\nexport type TestSetupOptions = {\r\n /**\r\n * Which connectors to start.\r\n *\r\n * Precedence is `explicit non-undefined option > tests.connectors config >\r\n * true`. Omitting the property, or passing `undefined`, lets project config\r\n * apply — so an optional variable that happens to be `undefined` cannot erase\r\n * it.\r\n */\r\n connectors?: TestConnectorsSelection;\r\n};\r\n\r\n/**\r\n * Raised by the lifecycle itself, never by the runtime it manages — a conflict\r\n * between two calls, or a refusal to reuse a runtime that did not close.\r\n */\r\nexport class TestLifecycleError extends Error {\r\n public constructor(message: string) {\r\n super(message);\r\n\r\n this.name = \"TestLifecycleError\";\r\n }\r\n}\r\n\r\nconst POISONED_MESSAGE =\r\n \"setupTest() refuses to start a test runtime: an earlier teardownTest() reported a shutdown failure, so the connectors, ports and pools it owned are not known to be closed. Restart the Vitest worker before setting up again, or call teardownTest() to retry the shutdown — the lifecycle only returns to idle on a fully successful retry.\";\r\n\r\n/**\r\n * Bootstrap the test runtime and start the selected connectors.\r\n *\r\n * ⚠ Runs once per TEST FILE, not once per worker: `setupFiles` is executed for\r\n * every file and its module registry is rebuilt with it, measured across both\r\n * pools with isolation on and off. The lifecycle state lives on the runtime\r\n * context rather than in this module precisely so that a rebuild cannot hide a\r\n * runtime that is still up.\r\n *\r\n * Repeated calls:\r\n *\r\n * - concurrent calls with the same effective selection share one startup;\r\n * - while ready, the same effective selection is a no-op;\r\n * - while starting or ready, a DIFFERENT effective selection rejects and leaves\r\n * the live runtime untouched;\r\n * - a failed setup unwinds and returns to idle, so a clean retry is allowed;\r\n * - after a successful `teardownTest`, a later setup may use a different\r\n * selection.\r\n *\r\n * @example\r\n * // let project config decide, falling back to the default set\r\n * await setupTest();\r\n *\r\n * @example\r\n * // bootstrap only — no connectors, whatever config says\r\n * await setupTest({ connectors: false });\r\n */\r\nexport async function setupTest(options?: TestSetupOptions): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n const requested = readRequestedConnectors(options?.connectors);\r\n\r\n if (registry.state === \"poisoned\") {\r\n throw new TestLifecycleError(POISONED_MESSAGE);\r\n }\r\n\r\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\r\n await settled(registry.teardownAttempt);\r\n\r\n return setupTest(options);\r\n }\r\n\r\n if (registry.state === \"ready\") {\r\n assertReadySelectionMatches(registry.activeSelection, requested);\r\n\r\n return;\r\n }\r\n\r\n if (registry.state === \"starting\" && registry.setupAttempt) {\r\n const attempt = registry.setupAttempt;\r\n\r\n await assertPendingSetupMatches(attempt, requested);\r\n\r\n return attempt.completion;\r\n }\r\n\r\n return startTestRuntime(requested);\r\n}\r\n\r\n/**\r\n * Shut the test runtime down and release the lifecycle.\r\n *\r\n * - idle is a no-op;\r\n * - concurrent calls share one attempt;\r\n * - a call made while setup is still running waits for that attempt to settle,\r\n * then closes the runtime if it succeeded;\r\n * - the local \"ready\" state is always cleared, including when shutdown rejects;\r\n * - a shutdown rejection is surfaced, never swallowed, and poisons the\r\n * lifecycle — a reported close failure is not proof that anything closed, so\r\n * later `setupTest` calls refuse until the worker is restarted or a retry\r\n * fully succeeds.\r\n *\r\n * ⚠ Individual connector shutdown failures are caught and logged inside\r\n * `connectorsManager.shutdown()`, so they never reach this function and never\r\n * poison anything. This lifecycle can only surface what that layer reports.\r\n */\r\nexport async function teardownTest(): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\r\n return registry.teardownAttempt;\r\n }\r\n\r\n if (registry.state === \"starting\" && registry.setupAttempt) {\r\n // A runtime that is still opening cannot be closed halfway. Wait for the\r\n // attempt to settle: a failed one has already unwound itself and left the\r\n // lifecycle idle, which the re-entry below then reads as \"nothing to do\".\r\n await settled(registry.setupAttempt.completion);\r\n\r\n return teardownTest();\r\n }\r\n\r\n if (registry.state === \"idle\") {\r\n return;\r\n }\r\n\r\n registry.state = \"stopping\";\r\n\r\n // Deferred by a microtask so the registry is fully published before the\r\n // attempt's first line runs — a synchronous throw would otherwise reach the\r\n // `finally` below, and reset the state, before this function had recorded\r\n // that a teardown was in flight at all.\r\n const attempt = Promise.resolve().then(() => runTestRuntimeShutdown());\r\n\r\n registry.teardownAttempt = attempt;\r\n\r\n return attempt;\r\n}\r\n\r\n/**\r\n * The setup attempt's hang guard, as the lifecycle consumes it.\r\n *\r\n * The bound's POLICY — the default, the config key, the message — lives in\r\n * `test-setup-timeout.ts`. What expiry MEANS lives here.\r\n *\r\n * @internal\r\n */\r\ntype SetupBound = {\r\n /**\r\n * Rejects with the expiry error. Never resolves: the attempt is the only side\r\n * of the race that can succeed.\r\n */\r\n readonly expiry: Promise<never>;\r\n\r\n /**\r\n * Re-aim the bound at `next` ms measured from when the ATTEMPT STARTED, once\r\n * config has made `tests.setupTimeout` readable.\r\n */\r\n rearm(next: number): void;\r\n\r\n /** The race is over — cancel whatever timer is still pending. */\r\n settle(): void;\r\n\r\n /** Whether the bound, rather than the attempt, won the race. */\r\n hasExpired(): boolean;\r\n};\r\n\r\n/**\r\n * Arm the hang guard for one setup attempt.\r\n *\r\n * Armed at the default BEFORE the attempt runs, because `tests.setupTimeout` is\r\n * not readable until the attempt has loaded config — and loading config is\r\n * itself one of the steps that can hang. `rearm` then narrows or widens it the\r\n * moment config arrives.\r\n */\r\nfunction armSetupBound(registry: TestLifecycleRegistry): SetupBound {\r\n const schedule = registry.scheduleTimeout ?? scheduleRealTimeout;\r\n const armedAt = Date.now();\r\n\r\n let handle: TestTimeoutHandle | undefined;\r\n let expired = false;\r\n let closed = false;\r\n let expire: (error: unknown) => void = () => undefined;\r\n\r\n const expiry = new Promise<never>((_resolve, reject) => {\r\n expire = reject;\r\n });\r\n\r\n // Only `awaitSetupWithinBound` observes this, and only until the attempt wins\r\n // — after that nobody is listening, and an unobserved rejection surfaces as an\r\n // unhandled rejection that fails an unrelated test file. Same reason as the\r\n // `effectiveSelection` barrier below.\r\n expiry.catch(() => undefined);\r\n\r\n const expireNow = (bound: number): void => {\r\n if (closed) {\r\n return;\r\n }\r\n\r\n closed = true;\r\n expired = true;\r\n\r\n handle?.cancel();\r\n handle = undefined;\r\n\r\n expire(new TestLifecycleError(describeExpiredSetup(bound)));\r\n };\r\n\r\n const armFor = (bound: number, delay: number): void => {\r\n if (closed) {\r\n return;\r\n }\r\n\r\n handle?.cancel();\r\n\r\n // Already overdue. A bound the attempt has ALREADY exceeded must fire now,\r\n // not be rescheduled for time that was spent before it was known.\r\n if (delay <= 0) {\r\n expireNow(bound);\r\n\r\n return;\r\n }\r\n\r\n handle = schedule(delay, () => expireNow(bound));\r\n };\r\n\r\n const initialBound = registry.setupTimeoutOverride ?? DEFAULT_TEST_SETUP_TIMEOUT;\r\n\r\n armFor(initialBound, initialBound);\r\n\r\n return {\r\n expiry,\r\n\r\n // Measured from `armedAt`, never from now: adding a configured bound to the\r\n // time already spent would make the real bound default-plus-configured, and\r\n // a project that lowered the key would get a LONGER guard than the default.\r\n rearm: (next) => armFor(next, next - (Date.now() - armedAt)),\r\n\r\n settle: () => {\r\n closed = true;\r\n\r\n handle?.cancel();\r\n handle = undefined;\r\n },\r\n\r\n hasExpired: () => expired,\r\n };\r\n}\r\n\r\n/**\r\n * Race one setup attempt against its bound.\r\n *\r\n * The losing attempt is NOT cancellable — nothing here can interrupt a bootstrap\r\n * stuck in a socket connect — so when the bound wins, the lifecycle is poisoned\r\n * and every later state write from that abandoned attempt is dropped. That\r\n * dropping happens in `runTestRuntimeStartup`, guarded on `hasExpired()`;\r\n * without it the still-running attempt would quietly overwrite `poisoned` with\r\n * `ready` or `idle` and hand the next caller a runtime nobody owns.\r\n */\r\nasync function awaitSetupWithinBound(\r\n attempt: Promise<void>,\r\n bound: SetupBound,\r\n registry: TestLifecycleRegistry,\r\n): Promise<void> {\r\n try {\r\n await Promise.race([attempt, bound.expiry]);\r\n } catch (error) {\r\n if (bound.hasExpired()) {\r\n registry.state = \"poisoned\";\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n }\r\n\r\n throw error;\r\n } finally {\r\n bound.settle();\r\n }\r\n}\r\n\r\n/**\r\n * Open a new setup attempt and publish it before it runs.\r\n */\r\nfunction startTestRuntime(requested: RequestedTestConnectors): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n let publishEffectiveSelection: (selection: TestConnectorsSelection) => void = () => undefined;\r\n let failEffectiveSelection: (error: unknown) => void = () => undefined;\r\n\r\n const effectiveSelection = new Promise<TestConnectorsSelection>((resolve, reject) => {\r\n publishEffectiveSelection = resolve;\r\n failEffectiveSelection = reject;\r\n });\r\n\r\n // Only the mixed explicit/config comparison ever awaits this barrier, so its\r\n // rejection is usually unobserved — and an unobserved rejection would surface\r\n // as an unhandled rejection and fail an unrelated test file.\r\n effectiveSelection.catch(() => undefined);\r\n\r\n const bound = armSetupBound(registry);\r\n\r\n const attempt = Promise.resolve().then(() =>\r\n runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound),\r\n );\r\n\r\n // The attempt outlives a bound that beat it, and by then `completion` has\r\n // already rejected with the expiry error — so the attempt's own late rejection\r\n // has no observer. Same unhandled-rejection hazard as above.\r\n attempt.catch(() => undefined);\r\n\r\n // `teardownTest` waits on this same promise, which is how a teardown requested\r\n // during setup inherits the bound. There is deliberately no second timer and\r\n // no separate teardown deadline.\r\n const completion = awaitSetupWithinBound(attempt, bound, registry);\r\n\r\n registry.state = \"starting\";\r\n registry.setupAttempt = { requested, effectiveSelection, completion };\r\n\r\n return completion;\r\n}\r\n\r\n/**\r\n * The startup attempt itself.\r\n *\r\n * On failure: best-effort unwind, reset bookkeeping, rethrow the ORIGINAL error\r\n * unchanged. A cleanup failure is a secondary diagnostic and never replaces the\r\n * cause the caller needs to read.\r\n */\r\nasync function runTestRuntimeStartup(\r\n requested: RequestedTestConnectors,\r\n publishEffectiveSelection: (selection: TestConnectorsSelection) => void,\r\n failEffectiveSelection: (error: unknown) => void,\r\n bound: SetupBound,\r\n): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n\r\n try {\r\n Application.setEnvironment(\"test\");\r\n\r\n await warlockConfigManager.load();\r\n await bootstrap();\r\n\r\n await filesOrchestrator.init();\r\n await loadConfigFiles(true);\r\n\r\n // The first point at which `tests.setupTimeout` is readable at all. An\r\n // invalid value throws from here and takes the normal failure path — falling\r\n // back to the default would silently erase what the project configured.\r\n const configuredTimeout = readConfiguredSetupTimeout();\r\n\r\n if (configuredTimeout !== undefined) {\r\n bound.rearm(configuredTimeout);\r\n }\r\n\r\n // Resolved here and not at call time: the config layer is only readable once\r\n // the four steps above have run.\r\n const effectiveSelection = resolveEffectiveConnectors(requested);\r\n\r\n // Every state write below is conditional on this attempt still being the one\r\n // the lifecycle is waiting for. Once the bound has expired the lifecycle is\r\n // poisoned and this attempt is abandoned — it may still be running, but it\r\n // no longer speaks for the runtime.\r\n if (!bound.hasExpired()) {\r\n registry.activeSelection = effectiveSelection;\r\n }\r\n\r\n publishEffectiveSelection(effectiveSelection);\r\n\r\n await startSelectedConnectors(effectiveSelection);\r\n\r\n if (!bound.hasExpired()) {\r\n registry.state = \"ready\";\r\n registry.setupAttempt = undefined;\r\n }\r\n } catch (error) {\r\n failEffectiveSelection(error);\r\n\r\n await unwindPartialStartup();\r\n\r\n if (!bound.hasExpired()) {\r\n registry.state = \"idle\";\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n }\r\n\r\n console.error(\"[vitest-setup] Failed to setup test environment:\", error);\r\n\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * `false` means none at all. Everything else starts something: an array starts\r\n * exactly those, and `true` starts the default set — all but http, which is the\r\n * global setup's job and shared across every worker.\r\n */\r\nasync function startSelectedConnectors(selection: TestConnectorsSelection): Promise<void> {\r\n if (selection === false) {\r\n return;\r\n }\r\n\r\n if (Array.isArray(selection)) {\r\n await connectorsManager.start(normalizeConnectorNames(selection));\r\n\r\n return;\r\n }\r\n\r\n await connectorsManager.startWithout([\"http\"]);\r\n}\r\n\r\n/**\r\n * Best-effort teardown of whatever a failed startup managed to start.\r\n *\r\n * Runs while an error is already in flight, so no step here may throw and every\r\n * step is attempted independently.\r\n */\r\nasync function unwindPartialStartup(): Promise<void> {\r\n // Two steps rather than one: `connectorsManager.shutdown()` runs the\r\n // application hooks itself, but it runs them FIRST and does not isolate their\r\n // rejection — so a hook that rejects would take the connector teardown down\r\n // with it and leave every connector up. Both calls are idempotent, so running\r\n // the hooks on their own first costs nothing and means neither failure can\r\n // skip the other step.\r\n await attemptCleanupStep(\"application shutdown hooks\", () => Application.runShutdownHooks());\r\n await attemptCleanupStep(\"connectors shutdown\", () => connectorsManager.shutdown());\r\n}\r\n\r\n/**\r\n * Run one cleanup step, isolating its failure so the next step still runs.\r\n */\r\nasync function attemptCleanupStep(step: string, run: () => Promise<void>): Promise<void> {\r\n try {\r\n await run();\r\n } catch (error) {\r\n // Reported, not thrown — it is secondary to the startup error about to be\r\n // rethrown, and losing that one to this would be the worse outcome.\r\n console.error(`[vitest-setup] Cleanup after a failed setup did not complete (${step}):`, error);\r\n }\r\n}\r\n\r\n/**\r\n * The teardown attempt itself.\r\n */\r\nasync function runTestRuntimeShutdown(): Promise<void> {\r\n const registry = getTestLifecycleRegistry();\r\n let shutdownSucceeded = false;\r\n\r\n try {\r\n // Manager-wide, and deliberately called whatever the selection was: with\r\n // `connectors: false` nothing was started, but bootstrap still registered\r\n // application shutdown hooks, and the manager runs those first (see\r\n // `connectors-manager.ts` — `Application.runShutdownHooks()` is its first\r\n // line). Skipping the call to \"match\" an empty selection would leak them.\r\n await connectorsManager.shutdown();\r\n\r\n shutdownSucceeded = true;\r\n } finally {\r\n // In a `finally`, not after the `await`: a rejected shutdown must not leave\r\n // the lifecycle claiming a runtime it no longer owns. Poisoned rather than\r\n // idle, because a cleared flag does not prove that ports, sockets, pools or\r\n // timers closed.\r\n registry.activeSelection = undefined;\r\n registry.setupAttempt = undefined;\r\n registry.teardownAttempt = undefined;\r\n registry.state = shutdownSucceeded ? \"idle\" : \"poisoned\";\r\n }\r\n}\r\n\r\n/**\r\n * Reject when a ready runtime was set up with a different selection.\r\n */\r\nfunction assertReadySelectionMatches(\r\n activeSelection: TestConnectorsSelection | undefined,\r\n requested: RequestedTestConnectors,\r\n): void {\r\n if (activeSelection === undefined) {\r\n return;\r\n }\r\n\r\n // Config is loaded by the time anything is ready, so the config-derived layer\r\n // is resolvable right here.\r\n const requestedSelection = resolveEffectiveConnectors(requested);\r\n\r\n assertSelectionsMatch(activeSelection, requestedSelection);\r\n}\r\n\r\n/**\r\n * Reject when an in-flight setup attempt is starting a different selection.\r\n */\r\nasync function assertPendingSetupMatches(\r\n attempt: TestSetupAttempt,\r\n requested: RequestedTestConnectors,\r\n): Promise<void> {\r\n const pending = attempt.requested;\r\n\r\n // Two calls that both defer to project config resolve to the same thing by\r\n // construction — knowable without waiting for config to load.\r\n if (!pending.isExplicit && !requested.isExplicit) {\r\n return;\r\n }\r\n\r\n if (pending.isExplicit && requested.isExplicit) {\r\n assertSelectionsMatch(pending.selection, requested.selection);\r\n\r\n return;\r\n }\r\n\r\n // Mixed: one side is config-derived and config is not readable until the\r\n // running attempt has loaded it, so wait for that barrier first.\r\n const activeSelection = await attempt.effectiveSelection;\r\n const requestedSelection = resolveEffectiveConnectors(requested);\r\n\r\n assertSelectionsMatch(activeSelection, requestedSelection);\r\n}\r\n\r\n/**\r\n * Name both selections: a caller looking at this message has to be able to tell\r\n * which call to change without reading the framework's source.\r\n */\r\nfunction assertSelectionsMatch(\r\n activeSelection: TestConnectorsSelection,\r\n requestedSelection: TestConnectorsSelection,\r\n): void {\r\n if (isSameConnectorsSelection(activeSelection, requestedSelection)) {\r\n return;\r\n }\r\n\r\n throw new TestLifecycleError(\r\n `setupTest() is already using ${describeConnectorsSelection(activeSelection)}, and this call asked for ${describeConnectorsSelection(requestedSelection)}. One test runtime serves one connector selection — call teardownTest() before setting up a different one.`,\r\n );\r\n}\r\n\r\n/**\r\n * Await an attempt for its state transition only, not its result.\r\n */\r\nfunction settled(attempt: Promise<void>): Promise<void> {\r\n return attempt.catch(() => undefined);\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EAEb,KAAK,OAAO;CACd;AACF;AAEA,MAAM,mBACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BF,eAAsB,UAAU,SAA2C;CACzE,MAAM,WAAW,yBAAyB;CAC1C,MAAM,YAAY,wBAAwB,SAAS,UAAU;CAE7D,IAAI,SAAS,UAAU,YACrB,MAAM,IAAI,mBAAmB,gBAAgB;CAG/C,IAAI,SAAS,UAAU,cAAc,SAAS,iBAAiB;EAC7D,MAAM,QAAQ,SAAS,eAAe;EAEtC,OAAO,UAAU,OAAO;CAC1B;CAEA,IAAI,SAAS,UAAU,SAAS;EAC9B,4BAA4B,SAAS,iBAAiB,SAAS;EAE/D;CACF;CAEA,IAAI,SAAS,UAAU,cAAc,SAAS,cAAc;EAC1D,MAAM,UAAU,SAAS;EAEzB,MAAM,0BAA0B,SAAS,SAAS;EAElD,OAAO,QAAQ;CACjB;CAEA,OAAO,iBAAiB,SAAS;AACnC;;;;;;;;;;;;;;;;;;AAmBA,eAAsB,eAA8B;CAClD,MAAM,WAAW,yBAAyB;CAE1C,IAAI,SAAS,UAAU,cAAc,SAAS,iBAC5C,OAAO,SAAS;CAGlB,IAAI,SAAS,UAAU,cAAc,SAAS,cAAc;EAI1D,MAAM,QAAQ,SAAS,aAAa,UAAU;EAE9C,OAAO,aAAa;CACtB;CAEA,IAAI,SAAS,UAAU,QACrB;CAGF,SAAS,QAAQ;CAMjB,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,WAAW,uBAAuB,CAAC;CAErE,SAAS,kBAAkB;CAE3B,OAAO;AACT;;;;;;;;;AAsCA,SAAS,cAAc,UAA6C;CAClE,MAAM,WAAW,SAAS,mBAAmB;CAC7C,MAAM,UAAU,KAAK,IAAI;CAEzB,IAAI;CACJ,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,eAAyC;CAE7C,MAAM,SAAS,IAAI,SAAgB,UAAU,WAAW;EACtD,SAAS;CACX,CAAC;CAMD,OAAO,YAAY,MAAS;CAE5B,MAAM,aAAa,UAAwB;EACzC,IAAI,QACF;EAGF,SAAS;EACT,UAAU;EAEV,QAAQ,OAAO;EACf,SAAS;EAET,OAAO,IAAI,mBAAmB,qBAAqB,KAAK,CAAC,CAAC;CAC5D;CAEA,MAAM,UAAU,OAAe,UAAwB;EACrD,IAAI,QACF;EAGF,QAAQ,OAAO;EAIf,IAAI,SAAS,GAAG;GACd,UAAU,KAAK;GAEf;EACF;EAEA,SAAS,SAAS,aAAa,UAAU,KAAK,CAAC;CACjD;CAEA,MAAM,eAAe,SAAS;CAE9B,OAAO,cAAc,YAAY;CAEjC,OAAO;EACL;EAKA,QAAQ,SAAS,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ;EAE3D,cAAc;GACZ,SAAS;GAET,QAAQ,OAAO;GACf,SAAS;EACX;EAEA,kBAAkB;CACpB;AACF;;;;;;;;;;;AAYA,eAAe,sBACb,SACA,OACA,UACe;CACf,IAAI;EACF,MAAM,QAAQ,KAAK,CAAC,SAAS,MAAM,MAAM,CAAC;CAC5C,SAAS,OAAO;EACd,IAAI,MAAM,WAAW,GAAG;GACtB,SAAS,QAAQ;GACjB,SAAS,kBAAkB;GAC3B,SAAS,eAAe;EAC1B;EAEA,MAAM;CACR,UAAU;EACR,MAAM,OAAO;CACf;AACF;;;;AAKA,SAAS,iBAAiB,WAAmD;CAC3E,MAAM,WAAW,yBAAyB;CAE1C,IAAI,kCAAgF;CACpF,IAAI,+BAAyD;CAE7D,MAAM,qBAAqB,IAAI,SAAkC,SAAS,WAAW;EACnF,4BAA4B;EAC5B,yBAAyB;CAC3B,CAAC;CAKD,mBAAmB,YAAY,MAAS;CAExC,MAAM,QAAQ,cAAc,QAAQ;CAEpC,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,WAChC,sBAAsB,WAAW,2BAA2B,wBAAwB,KAAK,CAC3F;CAKA,QAAQ,YAAY,MAAS;CAK7B,MAAM,aAAa,sBAAsB,SAAS,OAAO,QAAQ;CAEjE,SAAS,QAAQ;CACjB,SAAS,eAAe;EAAE;EAAW;EAAoB;CAAW;CAEpE,OAAO;AACT;;;;;;;;AASA,eAAe,sBACb,WACA,2BACA,wBACA,OACe;CACf,MAAM,WAAW,yBAAyB;CAE1C,IAAI;EACF,YAAY,eAAe,MAAM;EAEjC,MAAM,qBAAqB,KAAK;EAChC,MAAM,UAAU;EAEhB,MAAM,kBAAkB,KAAK;EAC7B,MAAM,gBAAgB,IAAI;EAK1B,MAAM,oBAAoB,2BAA2B;EAErD,IAAI,sBAAsB,QACxB,MAAM,MAAM,iBAAiB;EAK/B,MAAM,qBAAqB,2BAA2B,SAAS;EAM/D,IAAI,CAAC,MAAM,WAAW,GACpB,SAAS,kBAAkB;EAG7B,0BAA0B,kBAAkB;EAE5C,MAAM,wBAAwB,kBAAkB;EAEhD,IAAI,CAAC,MAAM,WAAW,GAAG;GACvB,SAAS,QAAQ;GACjB,SAAS,eAAe;EAC1B;CACF,SAAS,OAAO;EACd,uBAAuB,KAAK;EAE5B,MAAM,qBAAqB;EAE3B,IAAI,CAAC,MAAM,WAAW,GAAG;GACvB,SAAS,QAAQ;GACjB,SAAS,kBAAkB;GAC3B,SAAS,eAAe;EAC1B;EAEA,QAAQ,MAAM,oDAAoD,KAAK;EAEvE,MAAM;CACR;AACF;;;;;;AAOA,eAAe,wBAAwB,WAAmD;CACxF,IAAI,cAAc,OAChB;CAGF,IAAI,MAAM,QAAQ,SAAS,GAAG;EAC5B,MAAM,kBAAkB,MAAM,wBAAwB,SAAS,CAAC;EAEhE;CACF;CAEA,MAAM,kBAAkB,aAAa,CAAC,MAAM,CAAC;AAC/C;;;;;;;AAQA,eAAe,uBAAsC;CAOnD,MAAM,mBAAmB,oCAAoC,YAAY,iBAAiB,CAAC;CAC3F,MAAM,mBAAmB,6BAA6B,kBAAkB,SAAS,CAAC;AACpF;;;;AAKA,eAAe,mBAAmB,MAAc,KAAyC;CACvF,IAAI;EACF,MAAM,IAAI;CACZ,SAAS,OAAO;EAGd,QAAQ,MAAM,iEAAiE,KAAK,KAAK,KAAK;CAChG;AACF;;;;AAKA,eAAe,yBAAwC;CACrD,MAAM,WAAW,yBAAyB;CAC1C,IAAI,oBAAoB;CAExB,IAAI;EAMF,MAAM,kBAAkB,SAAS;EAEjC,oBAAoB;CACtB,UAAU;EAKR,SAAS,kBAAkB;EAC3B,SAAS,eAAe;EACxB,SAAS,kBAAkB;EAC3B,SAAS,QAAQ,oBAAoB,SAAS;CAChD;AACF;;;;AAKA,SAAS,4BACP,iBACA,WACM;CACN,IAAI,oBAAoB,QACtB;CAOF,sBAAsB,iBAFK,2BAA2B,SAEE,CAAC;AAC3D;;;;AAKA,eAAe,0BACb,SACA,WACe;CACf,MAAM,UAAU,QAAQ;CAIxB,IAAI,CAAC,QAAQ,cAAc,CAAC,UAAU,YACpC;CAGF,IAAI,QAAQ,cAAc,UAAU,YAAY;EAC9C,sBAAsB,QAAQ,WAAW,UAAU,SAAS;EAE5D;CACF;CAOA,sBAAsB,MAHQ,QAAQ,oBACX,2BAA2B,SAEE,CAAC;AAC3D;;;;;AAMA,SAAS,sBACP,iBACA,oBACM;CACN,IAAI,0BAA0B,iBAAiB,kBAAkB,GAC/D;CAGF,MAAM,IAAI,mBACR,gCAAgC,4BAA4B,eAAe,EAAE,4BAA4B,4BAA4B,kBAAkB,EAAE,2GAC3J;AACF;;;;AAKA,SAAS,QAAQ,SAAuC;CACtD,OAAO,QAAQ,YAAY,MAAS;AACtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cases-registry.d.mts","names":[],"sources":["../../../../../../../core/src/use-cases/use-cases-registry.ts"],"mappings":";;;;;AAaA;iBAAgB,gBAAA,gBACd,IAAA,UACA,OAAA,EAAS,iBAAA,CAAkB,MAAA,EAAQ,KAAA;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-cases-registry.d.mts","names":[],"sources":["../../../../../../../core/src/use-cases/use-cases-registry.ts"],"mappings":";;;;;AAaA;iBAAgB,gBAAA,gBACd,IAAA,UACA,OAAA,EAAS,iBAAA,CAAkB,MAAA,EAAQ,KAAA;;;;;;;;iBAgBrB,kBAAA,CAAmB,IAAY;;;;;;;AAhBJ;iBA+B3B,UAAA,gBAA0B,IAAA,WACJ,iBAAA,CAAkB,MAAA,EAAQ,KAAA;;;;iBAMhD,WAAA,IAAW,GAAA,SAAA,iBAAA;AAP3B;;;AAAA,iBAcgB,2BAAA,CAA4B,IAAY;;;;iBAexC,0BAAA,CAA2B,IAAY;;;;;;iBA2BjC,iBAAA,CAAkB,IAAA,WAAe,OAAO,CAAC,aAAA;;AAvDM;AAMrE;;iBA+EsB,iBAAA,CAAkB,IAAA,UAAc,MAAA,EAAQ,aAAA,QAAkB,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cases-registry.mjs","names":[],"sources":["../../../../../../../core/src/use-cases/use-cases-registry.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport { config } from \"../config\";\nimport { type RegisteredUseCase, type UseCaseConfigurations, type UseCaseResult } from \"./types\";\n\n/**\n * Store registered use cases\n */\nconst useCaseRegister = new Map<string, RegisteredUseCase>();\n\n/**\n * Register a use case\n */\nexport function $registerUseCase<Output, Input>(\n name: string,\n useCase: RegisteredUseCase<Output, Input>,\n) {\n if (useCaseRegister.has(name) && process.env.NODE_ENV !== \"production\") {\n log.warn(\
|
|
1
|
+
{"version":3,"file":"use-cases-registry.mjs","names":[],"sources":["../../../../../../../core/src/use-cases/use-cases-registry.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport { config } from \"../config\";\nimport { type RegisteredUseCase, type UseCaseConfigurations, type UseCaseResult } from \"./types\";\n\n/**\n * Store registered use cases\n */\nconst useCaseRegister = new Map<string, RegisteredUseCase>();\n\n/**\n * Register a use case\n */\nexport function $registerUseCase<Output, Input>(\n name: string,\n useCase: RegisteredUseCase<Output, Input>,\n) {\n if (useCaseRegister.has(name) && process.env.NODE_ENV !== \"production\") {\n log.warn(\"use-cases\", \"registering\", `Use case \"${name}\" is already registered. Overwriting.`);\n }\n\n useCaseRegister.set(name, useCase);\n}\n\n/**\n * Unregister a use case.\n *\n * Dropping the history namespace is best-effort: `$cleanup` is synchronous, so the\n * async cache removal is fire-and-forget with a `.catch` guard — a rejecting cache\n * driver must never turn cleanup into an unhandled rejection.\n */\nexport function $unregisterUseCase(name: string) {\n useCaseRegister.delete(name);\n\n Promise.resolve(cache.removeNamespace(`use-case:history:${name}`)).catch((error) => {\n log.error(\"use-cases\", name, \"history namespace removal failed\", { error });\n });\n}\n\n/**\n * Get a use case.\n *\n * Returns the `RegisteredUseCase` shape — including the runtime `calls` counters\n * the registry actually stores — so consumers can read call stats without an\n * ad-hoc cast.\n */\nexport function getUseCase<Output, Input>(name: string) {\n return useCaseRegister.get(name) as RegisteredUseCase<Output, Input> | undefined;\n}\n\n/**\n * Get all use cases\n */\nexport function getUseCases() {\n return useCaseRegister;\n}\n\n/**\n * Increase use case success calls\n */\nexport function increaseUseCaseSuccessCalls(name: string): number {\n const useCase = useCaseRegister.get(name);\n if (useCase) {\n useCase.calls.success++;\n useCase.calls.total++;\n\n return useCase.calls.success;\n }\n\n return 0;\n}\n\n/**\n * Increase use case failed calls\n */\nexport function increaseUseCaseFailedCalls(name: string): number {\n const useCase = useCaseRegister.get(name);\n if (useCase) {\n useCase.calls.failed++;\n useCase.calls.total++;\n\n return useCase.calls.failed;\n }\n\n return 0;\n}\n\n/**\n * Resolve the history cache TTL (seconds) from config.\n * `false` means \"use the cache driver's default\", represented as `undefined`.\n */\nfunction resolveHistoryTtl(useCaseConfig?: UseCaseConfigurations): number | undefined {\n const ttlConfig = useCaseConfig?.history?.ttl ?? 3600; // 1 hour default\n\n return ttlConfig === false ? undefined : ttlConfig;\n}\n\n/**\n * Get use case history from cache.\n *\n * Prunes ids whose entries have expired so the list can't accumulate dead refs.\n */\nexport async function getUseCaseHistory(name: string): Promise<UseCaseResult<any>[]> {\n const listKey = `use-case:history:${name}:list`;\n const ids = (await cache.get<string[]>(listKey)) || [];\n\n const entries = await Promise.all(\n ids.map(async (id: string) => ({\n id,\n result: await cache.get<UseCaseResult<any>>(`use-case:history:${name}:${id}`),\n })),\n );\n\n const liveEntries = entries.filter((entry) => Boolean(entry.result));\n\n if (liveEntries.length !== ids.length) {\n const useCaseConfig = config.get<UseCaseConfigurations>(\"use-cases\");\n\n await cache.set(\n listKey,\n liveEntries.map((entry) => entry.id),\n resolveHistoryTtl(useCaseConfig),\n );\n }\n\n return liveEntries.map((entry) => entry.result) as UseCaseResult<any>[];\n}\n\n/**\n * Add use case history to cache, capping the per-use-case list so it can't grow\n * unbounded — oldest entries (and their ids) are evicted past MAX_HISTORY_ENTRIES.\n */\nexport async function addUseCaseHistory(name: string, result: UseCaseResult<any>) {\n const useCaseConfig = config.get<UseCaseConfigurations>(\"use-cases\");\n\n if (useCaseConfig?.history?.enabled === false) return;\n\n const ttl = resolveHistoryTtl(useCaseConfig);\n const maxEntries = useCaseConfig?.history?.maxEntries ?? 100;\n const key = `use-case:history:${name}:${result.id}`;\n const listKey = `use-case:history:${name}:list`;\n\n await cache.set(key, result, ttl);\n\n const list = (await cache.get<string[]>(listKey)) || [];\n\n list.push(result.id);\n\n while (list.length > maxEntries) {\n const evictedId = list.shift();\n\n if (evictedId) {\n await cache.remove(`use-case:history:${name}:${evictedId}`);\n }\n }\n\n await cache.set(listKey, list, ttl);\n}\n"],"mappings":";;;;;;;;;AAQA,MAAM,kCAAkB,IAAI,IAA+B;;;;AAK3D,SAAgB,iBACd,MACA,SACA;CACA,IAAI,gBAAgB,IAAI,IAAI,KAAK,QAAQ,IAAI,aAAa,cACxD,IAAI,KAAK,aAAa,eAAe,aAAa,KAAK,sCAAsC;CAG/F,gBAAgB,IAAI,MAAM,OAAO;AACnC;;;;;;;;AASA,SAAgB,mBAAmB,MAAc;CAC/C,gBAAgB,OAAO,IAAI;CAE3B,QAAQ,QAAQ,MAAM,gBAAgB,oBAAoB,MAAM,CAAC,CAAC,CAAC,OAAO,UAAU;EAClF,IAAI,MAAM,aAAa,MAAM,oCAAoC,EAAE,MAAM,CAAC;CAC5E,CAAC;AACH;;;;;;;;AASA,SAAgB,WAA0B,MAAc;CACtD,OAAO,gBAAgB,IAAI,IAAI;AACjC;;;;AAKA,SAAgB,cAAc;CAC5B,OAAO;AACT;;;;AAKA,SAAgB,4BAA4B,MAAsB;CAChE,MAAM,UAAU,gBAAgB,IAAI,IAAI;CACxC,IAAI,SAAS;EACX,QAAQ,MAAM;EACd,QAAQ,MAAM;EAEd,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO;AACT;;;;AAKA,SAAgB,2BAA2B,MAAsB;CAC/D,MAAM,UAAU,gBAAgB,IAAI,IAAI;CACxC,IAAI,SAAS;EACX,QAAQ,MAAM;EACd,QAAQ,MAAM;EAEd,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO;AACT;;;;;AAMA,SAAS,kBAAkB,eAA2D;CACpF,MAAM,YAAY,eAAe,SAAS,OAAO;CAEjD,OAAO,cAAc,QAAQ,SAAY;AAC3C;;;;;;AAOA,eAAsB,kBAAkB,MAA6C;CACnF,MAAM,UAAU,oBAAoB,KAAK;CACzC,MAAM,MAAO,MAAM,MAAM,IAAc,OAAO,KAAM,CAAC;CASrD,MAAM,eAAc,MAPE,QAAQ,IAC5B,IAAI,IAAI,OAAO,QAAgB;EAC7B;EACA,QAAQ,MAAM,MAAM,IAAwB,oBAAoB,KAAK,GAAG,IAAI;CAC9E,EAAE,CACJ,EAE2B,CAAC,QAAQ,UAAU,QAAQ,MAAM,MAAM,CAAC;CAEnE,IAAI,YAAY,WAAW,IAAI,QAAQ;EACrC,MAAM,gBAAgB,OAAO,IAA2B,WAAW;EAEnE,MAAM,MAAM,IACV,SACA,YAAY,KAAK,UAAU,MAAM,EAAE,GACnC,kBAAkB,aAAa,CACjC;CACF;CAEA,OAAO,YAAY,KAAK,UAAU,MAAM,MAAM;AAChD;;;;;AAMA,eAAsB,kBAAkB,MAAc,QAA4B;CAChF,MAAM,gBAAgB,OAAO,IAA2B,WAAW;CAEnE,IAAI,eAAe,SAAS,YAAY,OAAO;CAE/C,MAAM,MAAM,kBAAkB,aAAa;CAC3C,MAAM,aAAa,eAAe,SAAS,cAAc;CACzD,MAAM,MAAM,oBAAoB,KAAK,GAAG,OAAO;CAC/C,MAAM,UAAU,oBAAoB,KAAK;CAEzC,MAAM,MAAM,IAAI,KAAK,QAAQ,GAAG;CAEhC,MAAM,OAAQ,MAAM,MAAM,IAAc,OAAO,KAAM,CAAC;CAEtD,KAAK,KAAK,OAAO,EAAE;CAEnB,OAAO,KAAK,SAAS,YAAY;EAC/B,MAAM,YAAY,KAAK,MAAM;EAE7B,IAAI,WACF,MAAM,MAAM,OAAO,oBAAoB,KAAK,GAAG,WAAW;CAE9D;CAEA,MAAM,MAAM,IAAI,SAAS,MAAM,GAAG;AACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-log.mjs","names":[],"sources":["../../../../../../../core/src/utils/app-log.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\n\nexport const appLog = {\n info: (module: string, message: string) => log.info(\"app\", module, message),\n error: (module: string, message: string) => log.error(\"app\", module, message),\n warn: (module: string, message: string) => log.warn(\"app\", module, message),\n debug: (module: string, message: string) => log.debug(\"app\", module, message),\n success: (module: string, message: string)
|
|
1
|
+
{"version":3,"file":"app-log.mjs","names":[],"sources":["../../../../../../../core/src/utils/app-log.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\n\nexport const appLog = {\n info: (module: string, message: string) => log.info(\"app\", module, message),\n error: (module: string, message: string) => log.error(\"app\", module, message),\n warn: (module: string, message: string) => log.warn(\"app\", module, message),\n debug: (module: string, message: string) => log.debug(\"app\", module, message),\n success: (module: string, message: string) => log.success(\"app\", module, message),\n};\n"],"mappings":";;;AAEA,MAAa,SAAS;CACpB,OAAO,QAAgB,YAAoB,IAAI,KAAK,OAAO,QAAQ,OAAO;CAC1E,QAAQ,QAAgB,YAAoB,IAAI,MAAM,OAAO,QAAQ,OAAO;CAC5E,OAAO,QAAgB,YAAoB,IAAI,KAAK,OAAO,QAAQ,OAAO;CAC1E,QAAQ,QAAgB,YAAoB,IAAI,MAAM,OAAO,QAAQ,OAAO;CAC5E,UAAU,QAAgB,YAAoB,IAAI,QAAQ,OAAO,QAAQ,OAAO;AAClF"}
|
package/esm/utils/queue.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.mjs","names":[],"sources":["../../../../../../../core/src/utils/queue.ts"],"sourcesContent":["/**\n * A utility class for managing a queue of operations.\n * Allows enqueuing values and executing a function when the queue reaches a certain size or after a specified interval.\n * Supports both parallel and sequential execution of the queued operations.\n */\nexport class Queue<T> {\n /** The items currently in the queue. */\n private items: T[] = [];\n\n /** The maximum size of the queue before triggering execution. */\n private readonly maxSize?: number;\n\n /** The interval in milliseconds after which the queue will be executed if not already triggered by size. */\n private readonly interval: number;\n\n /** The function to execute with the items in the queue. */\n private readonly executeFn: (items: T[]) => Promise<void>;\n\n /** Timer for managing the interval-based execution. */\n private timer: NodeJS.Timeout | null = null;\n\n /** Flag to determine if execution should be parallel or sequential. */\n private readonly executeInParallel: boolean;\n\n /** The batch size for processing items. */\n private readonly batchSize: number;\n\n /** Whether the current queue is busy executing */\n private isExecuting = false;\n\n /**\n * Constructs a new Queue instance.\n * @param executeFn - The function to execute with the items in the queue.\n * @param maxSize - The maximum number of items before the queue is executed.\n * @param executeEvery - The time in milliseconds after which the queue is executed if not already triggered.\n * @param executeInParallel - Whether to execute the function in parallel or sequentially.\n * @param batchSize - The number of items to process in each batch.\n */\n public constructor(\n executeFn: (items: T[]) => Promise<void>,\n executeInParallel: boolean = true,\n executeEvery: number = 5000,\n batchSize: number,\n maxSize?: number,\n ) {\n this.executeFn = executeFn;\n this.maxSize = maxSize;\n this.interval = executeEvery;\n this.executeInParallel = executeInParallel;\n this.batchSize = batchSize;\n }\n\n /**\n * Adds an item to the queue.\n * Triggers execution if the queue reaches the maximum size.\n * Starts a timer if not already running.\n * @param item - The item to add to the queue.\n */\n public enqueue(item: T): void {\n this.items.push(item);\n if (this.maxSize && this.items.length >= this.maxSize) {\n this.execute();\n }\n\n if (!this.timer) {\n this.startTimer();\n }\n }\n\n /**\n * Starts a timer to execute the queue after the specified interval.\n */\n private startTimer(): void {\n this.timer = setInterval(() => {\n if (this.items.length > 0) {\n this.execute();\n }\n }, this.interval);\n }\n\n /**\n * Executes the function with the current items in the queue.\n * Processes items in batches and resets the timer.\n */\n private async execute(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n this.isExecuting = true;\n\n // Now there are couple scenarios:\n // 1. Batch size has value, we need to check if its going to be executed in parallel or sequentially\n // 2. Batch size is not provided, we will execute all items in a single call\n if (this.batchSize) {\n const itemsToProcess = this.items.splice(0, this.batchSize);\n if (this.executeInParallel) {\n await Promise.all(itemsToProcess.map(item => this.executeFn([item])));\n } else {\n for (const item of itemsToProcess) {\n await this.executeFn([item]);\n }\n }\n }\n\n this.isExecuting = false;\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,QAAb,MAAsB;;;;;;;;;CAiCpB,AAAO,YACL,WACA,oBAA6B,MAC7B,eAAuB,KACvB,WACA,SACA;eArCmB,CAAC;eAYiB;qBASjB;EAiBpB,KAAK,YAAY;EACjB,KAAK,UAAU;EACf,KAAK,WAAW;EAChB,KAAK,oBAAoB;EACzB,KAAK,YAAY;CACnB;;;;;;;CAQA,AAAO,QAAQ,MAAe;EAC5B,KAAK,MAAM,KAAK,IAAI;EACpB,IAAI,KAAK,WAAW,KAAK,MAAM,UAAU,KAAK,SAC5C,KAAK,QAAQ;EAGf,IAAI,CAAC,KAAK,OACR,KAAK,WAAW;CAEpB;;;;CAKA,AAAQ,aAAmB;EACzB,KAAK,QAAQ,kBAAkB;GAC7B,IAAI,KAAK,MAAM,SAAS,GACtB,KAAK,QAAQ;EAEjB,GAAG,KAAK,QAAQ;CAClB;;;;;CAMA,MAAc,UAAyB;EACrC,IAAI,KAAK,OAAO;GACd,cAAc,KAAK,KAAK;GACxB,KAAK,QAAQ;EACf;EAEA,KAAK,cAAc;EAKnB,IAAI,KAAK,WAAW;GAClB,MAAM,iBAAiB,KAAK,MAAM,OAAO,GAAG,KAAK,SAAS;GAC1D,IAAI,KAAK,mBACP,MAAM,QAAQ,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"queue.mjs","names":[],"sources":["../../../../../../../core/src/utils/queue.ts"],"sourcesContent":["/**\n * A utility class for managing a queue of operations.\n * Allows enqueuing values and executing a function when the queue reaches a certain size or after a specified interval.\n * Supports both parallel and sequential execution of the queued operations.\n */\nexport class Queue<T> {\n /** The items currently in the queue. */\n private items: T[] = [];\n\n /** The maximum size of the queue before triggering execution. */\n private readonly maxSize?: number;\n\n /** The interval in milliseconds after which the queue will be executed if not already triggered by size. */\n private readonly interval: number;\n\n /** The function to execute with the items in the queue. */\n private readonly executeFn: (items: T[]) => Promise<void>;\n\n /** Timer for managing the interval-based execution. */\n private timer: NodeJS.Timeout | null = null;\n\n /** Flag to determine if execution should be parallel or sequential. */\n private readonly executeInParallel: boolean;\n\n /** The batch size for processing items. */\n private readonly batchSize: number;\n\n /** Whether the current queue is busy executing */\n private isExecuting = false;\n\n /**\n * Constructs a new Queue instance.\n * @param executeFn - The function to execute with the items in the queue.\n * @param maxSize - The maximum number of items before the queue is executed.\n * @param executeEvery - The time in milliseconds after which the queue is executed if not already triggered.\n * @param executeInParallel - Whether to execute the function in parallel or sequentially.\n * @param batchSize - The number of items to process in each batch.\n */\n public constructor(\n executeFn: (items: T[]) => Promise<void>,\n executeInParallel: boolean = true,\n executeEvery: number = 5000,\n batchSize: number,\n maxSize?: number,\n ) {\n this.executeFn = executeFn;\n this.maxSize = maxSize;\n this.interval = executeEvery;\n this.executeInParallel = executeInParallel;\n this.batchSize = batchSize;\n }\n\n /**\n * Adds an item to the queue.\n * Triggers execution if the queue reaches the maximum size.\n * Starts a timer if not already running.\n * @param item - The item to add to the queue.\n */\n public enqueue(item: T): void {\n this.items.push(item);\n if (this.maxSize && this.items.length >= this.maxSize) {\n this.execute();\n }\n\n if (!this.timer) {\n this.startTimer();\n }\n }\n\n /**\n * Starts a timer to execute the queue after the specified interval.\n */\n private startTimer(): void {\n this.timer = setInterval(() => {\n if (this.items.length > 0) {\n this.execute();\n }\n }, this.interval);\n }\n\n /**\n * Executes the function with the current items in the queue.\n * Processes items in batches and resets the timer.\n */\n private async execute(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n this.isExecuting = true;\n\n // Now there are couple scenarios:\n // 1. Batch size has value, we need to check if its going to be executed in parallel or sequentially\n // 2. Batch size is not provided, we will execute all items in a single call\n if (this.batchSize) {\n const itemsToProcess = this.items.splice(0, this.batchSize);\n if (this.executeInParallel) {\n await Promise.all(itemsToProcess.map((item) => this.executeFn([item])));\n } else {\n for (const item of itemsToProcess) {\n await this.executeFn([item]);\n }\n }\n }\n\n this.isExecuting = false;\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,QAAb,MAAsB;;;;;;;;;CAiCpB,AAAO,YACL,WACA,oBAA6B,MAC7B,eAAuB,KACvB,WACA,SACA;eArCmB,CAAC;eAYiB;qBASjB;EAiBpB,KAAK,YAAY;EACjB,KAAK,UAAU;EACf,KAAK,WAAW;EAChB,KAAK,oBAAoB;EACzB,KAAK,YAAY;CACnB;;;;;;;CAQA,AAAO,QAAQ,MAAe;EAC5B,KAAK,MAAM,KAAK,IAAI;EACpB,IAAI,KAAK,WAAW,KAAK,MAAM,UAAU,KAAK,SAC5C,KAAK,QAAQ;EAGf,IAAI,CAAC,KAAK,OACR,KAAK,WAAW;CAEpB;;;;CAKA,AAAQ,aAAmB;EACzB,KAAK,QAAQ,kBAAkB;GAC7B,IAAI,KAAK,MAAM,SAAS,GACtB,KAAK,QAAQ;EAEjB,GAAG,KAAK,QAAQ;CAClB;;;;;CAMA,MAAc,UAAyB;EACrC,IAAI,KAAK,OAAO;GACd,cAAc,KAAK,KAAK;GACxB,KAAK,QAAQ;EACf;EAEA,KAAK,cAAc;EAKnB,IAAI,KAAK,WAAW;GAClB,MAAM,iBAAiB,KAAK,MAAM,OAAO,GAAG,KAAK,SAAS;GAC1D,IAAI,KAAK,mBACP,MAAM,QAAQ,IAAI,eAAe,KAAK,SAAS,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAEtE,KAAK,MAAM,QAAQ,gBACjB,MAAM,KAAK,UAAU,CAAC,IAAI,CAAC;EAGjC;EAEA,KAAK,cAAc;CACrB;AACF"}
|
package/esm/utils/sleep.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sleep.mjs","names":[],"sources":["../../../../../../../core/src/utils/sleep.ts"],"sourcesContent":["export function sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"],"mappings":";AAAA,SAAgB,MAAM,IAAY;CAChC,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"sleep.mjs","names":[],"sources":["../../../../../../../core/src/utils/sleep.ts"],"sourcesContent":["export function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":";AAAA,SAAgB,MAAM,IAAY;CAChC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-plugin.mjs","names":[],"sources":["../../../../../../../../core/src/validation/plugins/database-plugin.ts"],"sourcesContent":["/**\r\n * Database Validation Plugin (core)\r\n *\r\n * Injects the request-aware database validation methods onto Seal scalar\r\n * validators:\r\n * - uniqueExceptCurrentUser / uniqueExceptCurrentId\r\n * - existsExceptCurrentUser / existsExceptCurrentId\r\n *\r\n * The base `unique()` / `exists()` methods are owned by\r\n * `@warlock.js/cascade` and registered by its own seal plugin.\r\n */\r\n\r\nimport { type ChildModel, type Model } from \"@warlock.js/cascade\";\r\nimport type { SealPlugin } from \"@warlock.js/seal\";\r\nimport { NumberValidator, ScalarValidator, StringValidator } from \"@warlock.js/seal\";\r\nimport {\r\n existsExceptCurrentIdRule,\r\n existsExceptCurrentUserRule,\r\n uniqueExceptCurrentIdRule,\r\n uniqueExceptCurrentUserRule,\r\n} from \"../database\";\r\nimport type {\r\n ExistsExceptCurrentIdRuleOptions,\r\n ExistsExceptCurrentUserRuleOptions,\r\n UniqueExceptCurrentIdRuleOptions,\r\n UniqueExceptCurrentUserRuleOptions,\r\n} from \"../database\";\r\n\r\n/**\r\n * Database validation plugin for Seal\r\n */\r\nexport const databasePlugin: SealPlugin = {\r\n name: \"database\",\r\n version: \"1.0.0\",\r\n description
|
|
1
|
+
{"version":3,"file":"database-plugin.mjs","names":[],"sources":["../../../../../../../../core/src/validation/plugins/database-plugin.ts"],"sourcesContent":["/**\r\n * Database Validation Plugin (core)\r\n *\r\n * Injects the request-aware database validation methods onto Seal scalar\r\n * validators:\r\n * - uniqueExceptCurrentUser / uniqueExceptCurrentId\r\n * - existsExceptCurrentUser / existsExceptCurrentId\r\n *\r\n * The base `unique()` / `exists()` methods are owned by\r\n * `@warlock.js/cascade` and registered by its own seal plugin.\r\n */\r\n\r\nimport { type ChildModel, type Model } from \"@warlock.js/cascade\";\r\nimport type { SealPlugin } from \"@warlock.js/seal\";\r\nimport { NumberValidator, ScalarValidator, StringValidator } from \"@warlock.js/seal\";\r\nimport {\r\n existsExceptCurrentIdRule,\r\n existsExceptCurrentUserRule,\r\n uniqueExceptCurrentIdRule,\r\n uniqueExceptCurrentUserRule,\r\n} from \"../database\";\r\nimport type {\r\n ExistsExceptCurrentIdRuleOptions,\r\n ExistsExceptCurrentUserRuleOptions,\r\n UniqueExceptCurrentIdRuleOptions,\r\n UniqueExceptCurrentUserRuleOptions,\r\n} from \"../database\";\r\n\r\n/**\r\n * Database validation plugin for Seal\r\n */\r\nexport const databasePlugin: SealPlugin = {\r\n name: \"database\",\r\n version: \"1.0.0\",\r\n description:\r\n \"Adds request-aware database validation methods (except-current-user/id) to validators\",\r\n\r\n install() {\r\n Object.assign(ScalarValidator.prototype, {\r\n uniqueExceptCurrentUser(\r\n this: ScalarValidator,\r\n model: ChildModel<Model> | string,\r\n optionsList?: Partial<UniqueExceptCurrentUserRuleOptions> & {\r\n errorMessage?: string;\r\n },\r\n ) {\r\n const { errorMessage, ...options } = optionsList || {};\r\n\r\n return this.addRule(uniqueExceptCurrentUserRule, errorMessage, {\r\n Model: model,\r\n ...options,\r\n });\r\n },\r\n\r\n uniqueExceptCurrentId(\r\n this: ScalarValidator,\r\n model: ChildModel<Model> | string,\r\n optionsList?: Partial<UniqueExceptCurrentIdRuleOptions> & {\r\n errorMessage?: string;\r\n },\r\n ) {\r\n const { errorMessage, ...options } = optionsList || {};\r\n\r\n return this.addRule(uniqueExceptCurrentIdRule, errorMessage, {\r\n Model: model,\r\n ...options,\r\n });\r\n },\r\n\r\n existsExceptCurrentUser(\r\n this: ScalarValidator,\r\n model: ChildModel<Model> | string,\r\n optionsList?: Partial<ExistsExceptCurrentUserRuleOptions> & {\r\n errorMessage?: string;\r\n },\r\n ) {\r\n const { errorMessage, ...options } = optionsList || {};\r\n\r\n return this.addRule(existsExceptCurrentUserRule, errorMessage, {\r\n Model: model,\r\n ...options,\r\n });\r\n },\r\n\r\n existsExceptCurrentId(\r\n this: ScalarValidator,\r\n model: ChildModel<Model> | string,\r\n optionsList?: Partial<ExistsExceptCurrentIdRuleOptions> & {\r\n errorMessage?: string;\r\n },\r\n ) {\r\n const { errorMessage, ...options } = optionsList || {};\r\n\r\n return this.addRule(existsExceptCurrentIdRule, errorMessage, {\r\n Model: model,\r\n ...options,\r\n });\r\n },\r\n });\r\n\r\n Object.assign(StringValidator.prototype, {\r\n uniqueExceptCurrentUser: ScalarValidator.prototype.uniqueExceptCurrentUser,\r\n uniqueExceptCurrentId: ScalarValidator.prototype.uniqueExceptCurrentId,\r\n existsExceptCurrentUser: ScalarValidator.prototype.existsExceptCurrentUser,\r\n existsExceptCurrentId: ScalarValidator.prototype.existsExceptCurrentId,\r\n });\r\n\r\n Object.assign(NumberValidator.prototype, {});\r\n },\r\n};\r\n"],"mappings":";;;;;;;;;;;AA+BA,MAAa,iBAA6B;CACxC,MAAM;CACN,SAAS;CACT,aACE;CAEF,UAAU;EACR,OAAO,OAAO,gBAAgB,WAAW;GACvC,wBAEE,OACA,aAGA;IACA,MAAM,EAAE,cAAc,GAAG,YAAY,eAAe,CAAC;IAErD,OAAO,KAAK,QAAQ,6BAA6B,cAAc;KAC7D,OAAO;KACP,GAAG;IACL,CAAC;GACH;GAEA,sBAEE,OACA,aAGA;IACA,MAAM,EAAE,cAAc,GAAG,YAAY,eAAe,CAAC;IAErD,OAAO,KAAK,QAAQ,2BAA2B,cAAc;KAC3D,OAAO;KACP,GAAG;IACL,CAAC;GACH;GAEA,wBAEE,OACA,aAGA;IACA,MAAM,EAAE,cAAc,GAAG,YAAY,eAAe,CAAC;IAErD,OAAO,KAAK,QAAQ,6BAA6B,cAAc;KAC7D,OAAO;KACP,GAAG;IACL,CAAC;GACH;GAEA,sBAEE,OACA,aAGA;IACA,MAAM,EAAE,cAAc,GAAG,YAAY,eAAe,CAAC;IAErD,OAAO,KAAK,QAAQ,2BAA2B,cAAc;KAC3D,OAAO;KACP,GAAG;IACL,CAAC;GACH;EACF,CAAC;EAED,OAAO,OAAO,gBAAgB,WAAW;GACvC,yBAAyB,gBAAgB,UAAU;GACnD,uBAAuB,gBAAgB,UAAU;GACjD,yBAAyB,gBAAgB,UAAU;GACnD,uBAAuB,gBAAgB,UAAU;EACnD,CAAC;EAED,OAAO,OAAO,gBAAgB,WAAW,CAAC,CAAC;CAC7C;AACF"}
|