@warlock.js/core 5.14.0 → 5.15.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 CHANGED
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  > ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an _Upgrading_ section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
8
8
 
9
+ ## 5.15.0 - 2026-09-18
10
+
11
+ ### Added
12
+
13
+ - `warlock add sitemap` installs `@warlock.js/sitemap`, writes `src/config/sitemap.ts`, and registers `sitemapConnector()` in `warlock.config.ts`. The generated config ships **disabled**: a sitemap needs the application public origin and a generated app cannot know it, so the block explains the two steps to turn it on rather than producing an app that refuses to boot.
14
+ - `app.publicUrl` config key, with a `PUBLIC_APP_URL` environment fallback: the one absolute-URL source for every consumer that needs an origin. It never falls back to a request-derived host — an absolute URL built from the wrong host is worse than a boot that refuses to start, because nothing downstream reports it.
15
+
16
+ ### Changed
17
+
18
+ - **BREAKING (fail-loud):** `request.cookie(name)` and `request.hasCookie(name)` now throw `CookieJarUnavailableError` when `@fastify/cookie` is not registered on the Fastify instance, instead of returning `undefined` / `false`. An unregistered plugin is a configuration fault, and it was previously indistinguishable from "the caller sent no such cookie" — under `authMiddleware([], "cookie:token")` it surfaced as a permanent, unexplained 401. Apps built on `createHttpApplication` are unaffected: core registers the plugin before anything mounts. The exposure is a host that mounts a guarded surface on its own Fastify instance. `request.cookies` stays lenient and still returns `{}`, so the framework's own opportunistic reads — locale resolution among them — are unchanged.
19
+
9
20
  ## 5.14.0 - 2026-09-17
10
21
 
11
22
  ### Added
@@ -16,6 +16,15 @@ type AppConfigurations = {
16
16
  * @default localhost:
17
17
  */
18
18
  baseUrl?: string;
19
+ /**
20
+ * The application's public origin — the absolute URL other consumers (the
21
+ * sitemap route, canonical links, OG tags, absolute URLs in mail) build
22
+ * links against. Optional in general; falls back to the `PUBLIC_APP_URL`
23
+ * env var (see {@link getPublicUrl}). A consumer that requires it (e.g. the
24
+ * sitemap route) fails boot loudly, naming both, when neither is set — it
25
+ * never falls back to a request-derived origin.
26
+ */
27
+ publicUrl?: string;
19
28
  /**
20
29
  * Application timezone
21
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"application-config-types.d.mts","names":[],"sources":["../../../../../../../core/src/application/application-config-types.ts"],"mappings":";KAAY,iBAAA;EAAA;;;EAIV,OAAA;EAAA;;;;;EAMA,UAAA;EAcW;;;;;EARX,OAAA;;;;EAIA,QAAA;;;;EAIA,WAAA;AAAA"}
1
+ {"version":3,"file":"application-config-types.d.mts","names":[],"sources":["../../../../../../../core/src/application/application-config-types.ts"],"mappings":";KAAY,iBAAA;EAAA;;;EAIV,OAAA;EAAA;;;;;EAMA,UAAA;EAuBW;AAAA;;;;EAjBX,OAAA;;;;;;;;;EASA,SAAA;;;;EAIA,QAAA;;;;EAIA,WAAA;AAAA"}
@@ -1,4 +1,5 @@
1
1
  import { app } from "./app.mjs";
2
2
  import { Application, BootContext, BootListener, BootValidator, ShutdownListener } from "./application.mjs";
3
3
  import { BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BootSignal, BootSignalType, isBootSignal, sendBootSignal } from "./boot-signal.mjs";
4
- import { AppConfigurations } from "./application-config-types.mjs";
4
+ import { AppConfigurations } from "./application-config-types.mjs";
5
+ import { getPublicUrl } from "./public-url.mjs";
@@ -1,5 +1,6 @@
1
1
  import { BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, isBootSignal, sendBootSignal } from "./boot-signal.mjs";
2
2
  import { Application } from "./application.mjs";
3
3
  import { app } from "./app.mjs";
4
+ import { getPublicUrl } from "./public-url.mjs";
4
5
 
5
6
  export { };
@@ -0,0 +1,20 @@
1
+ //#region ../core/src/application/public-url.d.ts
2
+ /**
3
+ * The application's public origin — the ONE absolute-URL source every
4
+ * consumer that needs one (the sitemap route, canonical links, OG tags,
5
+ * absolute URLs in mail) reads instead of each keeping its own copy.
6
+ *
7
+ * `app.publicUrl` wins over the `PUBLIC_APP_URL` env fallback. Deliberately
8
+ * does not fall back further to a request-derived origin: a sitemap (or any
9
+ * other absolute URL) served from the wrong host is worse than a boot that
10
+ * refuses to start, because nothing downstream ever tells you it was wrong.
11
+ *
12
+ * Optional in general — most apps have no consumer that needs it yet.
13
+ * Returns `undefined` rather than throwing; a consumer that requires the
14
+ * value (e.g. the sitemap route, at boot) is responsible for failing loudly
15
+ * itself, naming both `app.publicUrl` and `PUBLIC_APP_URL`.
16
+ */
17
+ declare function getPublicUrl(): string | undefined;
18
+ //#endregion
19
+ export { getPublicUrl };
20
+ //# sourceMappingURL=public-url.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-url.d.mts","names":[],"sources":["../../../../../../../core/src/application/public-url.ts"],"mappings":";;AAiBA;;;;AAA4B;;;;;;;;;;iBAAZ,YAAA"}
@@ -0,0 +1,26 @@
1
+ import { config } from "../config/config-getter.mjs";
2
+ import "../config/index.mjs";
3
+
4
+ //#region ../core/src/application/public-url.ts
5
+ /**
6
+ * The application's public origin — the ONE absolute-URL source every
7
+ * consumer that needs one (the sitemap route, canonical links, OG tags,
8
+ * absolute URLs in mail) reads instead of each keeping its own copy.
9
+ *
10
+ * `app.publicUrl` wins over the `PUBLIC_APP_URL` env fallback. Deliberately
11
+ * does not fall back further to a request-derived origin: a sitemap (or any
12
+ * other absolute URL) served from the wrong host is worse than a boot that
13
+ * refuses to start, because nothing downstream ever tells you it was wrong.
14
+ *
15
+ * Optional in general — most apps have no consumer that needs it yet.
16
+ * Returns `undefined` rather than throwing; a consumer that requires the
17
+ * value (e.g. the sitemap route, at boot) is responsible for failing loudly
18
+ * itself, naming both `app.publicUrl` and `PUBLIC_APP_URL`.
19
+ */
20
+ function getPublicUrl() {
21
+ return config.key("app.publicUrl") || process.env.PUBLIC_APP_URL || void 0;
22
+ }
23
+
24
+ //#endregion
25
+ export { getPublicUrl };
26
+ //# sourceMappingURL=public-url.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-url.mjs","names":[],"sources":["../../../../../../../core/src/application/public-url.ts"],"sourcesContent":["import { config } from \"../config\";\n\n/**\n * The application's public origin — the ONE absolute-URL source every\n * consumer that needs one (the sitemap route, canonical links, OG tags,\n * absolute URLs in mail) reads instead of each keeping its own copy.\n *\n * `app.publicUrl` wins over the `PUBLIC_APP_URL` env fallback. Deliberately\n * does not fall back further to a request-derived origin: a sitemap (or any\n * other absolute URL) served from the wrong host is worse than a boot that\n * refuses to start, because nothing downstream ever tells you it was wrong.\n *\n * Optional in general — most apps have no consumer that needs it yet.\n * Returns `undefined` rather than throwing; a consumer that requires the\n * value (e.g. the sitemap route, at boot) is responsible for failing loudly\n * itself, naming both `app.publicUrl` and `PUBLIC_APP_URL`.\n */\nexport function getPublicUrl(): string | undefined {\n return config.key<string | undefined>(\"app.publicUrl\") || process.env.PUBLIC_APP_URL || undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiBA,SAAgB,eAAmC;CACjD,OAAO,OAAO,IAAwB,eAAe,KAAK,QAAQ,IAAI,kBAAkB;AAC1F"}
@@ -26,6 +26,7 @@ import { s3Feature } from "./s3.feature.mjs";
26
26
  import { schedulerFeature } from "./scheduler.feature.mjs";
27
27
  import { sesFeature } from "./ses.feature.mjs";
28
28
  import { shadcnFeature } from "./shadcn.feature.mjs";
29
+ import { sitemapFeature } from "./sitemap.feature.mjs";
29
30
  import { socketFeature } from "./socket.feature.mjs";
30
31
  import { tailwindFeature } from "./tailwind.feature.mjs";
31
32
  import { testFeature } from "./test.feature.mjs";
@@ -56,6 +57,7 @@ const featuresMap = {
56
57
  web: webFeature,
57
58
  tailwind: tailwindFeature,
58
59
  shadcn: shadcnFeature,
60
+ sitemap: sitemapFeature,
59
61
  herald: heraldFeature,
60
62
  queue: queueFeature,
61
63
  "bull-board": bullBoardFeature,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/index.ts"],"sourcesContent":["import { accessFeature } from \"./access.feature\";\r\nimport { aiAnthropicFeature } from \"./ai-anthropic.feature\";\r\nimport { aiBedrockFeature } from \"./ai-bedrock.feature\";\r\nimport { aiGoogleFeature } from \"./ai-google.feature\";\r\nimport { aiOllamaFeature } from \"./ai-ollama.feature\";\r\nimport { aiOpenaiFeature } from \"./ai-openai.feature\";\r\nimport { aiPanopticFeature } from \"./ai-panoptic.feature\";\r\nimport { aiToolsFeature } from \"./ai-tools.feature\";\r\nimport { aiWorkspaceFeature } from \"./ai-workspace.feature\";\r\nimport { aiFeature } from \"./ai.feature\";\r\nimport { authGoogleFeature } from \"./auth-google.feature\";\r\nimport { authPasskeysFeature } from \"./auth-passkeys.feature\";\r\nimport { bullBoardFeature } from \"./bull-board.feature\";\r\nimport { heraldFeature } from \"./herald.feature\";\r\nimport { imageFeature } from \"./image.feature\";\r\nimport { mailFeature } from \"./mail.feature\";\r\nimport { mongodbFeature } from \"./mongodb.feature\";\r\nimport { mysqlFeature } from \"./mysql.feature\";\r\nimport { notificationsFeature } from \"./notifications.feature\";\r\nimport { postgresFeature } from \"./postgres.feature\";\r\nimport { queueFeature } from \"./queue.feature\";\r\nimport { reactEmailFeature } from \"./react-email.feature\";\r\nimport { reactFeature } from \"./react.feature\";\r\nimport { redisFeature } from \"./redis.feature\";\r\nimport { s3Feature } from \"./s3.feature\";\r\nimport { schedulerFeature } from \"./scheduler.feature\";\r\nimport { sesFeature } from \"./ses.feature\";\r\nimport { shadcnFeature } from \"./shadcn.feature\";\r\nimport { socketFeature } from \"./socket.feature\";\r\nimport { tailwindFeature } from \"./tailwind.feature\";\r\nimport { testFeature } from \"./test.feature\";\r\nimport type { FeatureDefinition } from \"./types\";\r\nimport { webFeature } from \"./web.feature\";\r\n\r\nexport type { FeatureDefinition } from \"./types\";\r\n\r\n/**\r\n * The feature registry `warlock add` dispatches against.\r\n *\r\n * This file is an INDEX, nothing more: every entry lives in its own module\r\n * alongside the `onExecuting` body it runs. Key order is load-bearing — it is\r\n * the order `--list` prints and the order the \"not allowed\" error lists — so\r\n * add new features in the place they should appear, not alphabetically.\r\n */\r\nexport const featuresMap: Record<string, FeatureDefinition> = {\r\n \"react-email\": reactEmailFeature,\r\n react: reactFeature,\r\n image: imageFeature,\r\n mail: mailFeature,\r\n ses: sesFeature,\r\n mongodb: mongodbFeature,\r\n scheduler: schedulerFeature,\r\n // swagger / postman intentionally omitted — those packages do not exist yet;\r\n // they will ship together in the unified @warlock.js/api-docs package.\r\n postgres: postgresFeature,\r\n mysql: mysqlFeature,\r\n redis: redisFeature,\r\n s3: s3Feature,\r\n test: testFeature,\r\n web: webFeature,\r\n // Directly after `web`, and only there: it `requires` it, it is useless\r\n // without it, and a reader scanning `--list` for the page stack should meet\r\n // the two together rather than find styling filed between queues and sockets.\r\n tailwind: tailwindFeature,\r\n // Immediately after `tailwind`, for the same reason `tailwind` follows `web`:\r\n // it `requires` it, it appends to the stylesheet that feature creates, and the\r\n // three of them are one stack a reader should meet in build order.\r\n shadcn: shadcnFeature,\r\n herald: heraldFeature,\r\n queue: queueFeature,\r\n // Directly after `queue`, for the same reason `tailwind` follows `web`: it\r\n // needs queue already installed and configured, and a reader scanning\r\n // `--list` for job-queue features should meet the two together.\r\n \"bull-board\": bullBoardFeature,\r\n socket: socketFeature,\r\n notifications: notificationsFeature,\r\n access: accessFeature,\r\n // Login methods for @warlock.js/auth — \"<package>-<vendor>\" like the ai-* entries.\r\n \"auth-google\": authGoogleFeature,\r\n \"auth-passkeys\": authPasskeysFeature,\r\n ai: aiFeature,\r\n \"ai-openai\": aiOpenaiFeature,\r\n \"ai-google\": aiGoogleFeature,\r\n \"ai-anthropic\": aiAnthropicFeature,\r\n \"ai-bedrock\": aiBedrockFeature,\r\n \"ai-ollama\": aiOllamaFeature,\r\n \"ai-tools\": aiToolsFeature,\r\n \"ai-panoptic\": aiPanopticFeature,\r\n \"ai-workspace\": aiWorkspaceFeature,\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,cAAiD;CAC5D,eAAe;CACf,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,SAAS;CACT,WAAW;CAGX,UAAU;CACV,OAAO;CACP,OAAO;CACP,IAAI;CACJ,MAAM;CACN,KAAK;CAIL,UAAU;CAIV,QAAQ;CACR,QAAQ;CACR,OAAO;CAIP,cAAc;CACd,QAAQ;CACR,eAAe;CACf,QAAQ;CAER,eAAe;CACf,iBAAiB;CACjB,IAAI;CACJ,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,YAAY;CACZ,eAAe;CACf,gBAAgB;AAClB"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/index.ts"],"sourcesContent":["import { accessFeature } from \"./access.feature\";\r\nimport { aiAnthropicFeature } from \"./ai-anthropic.feature\";\r\nimport { aiBedrockFeature } from \"./ai-bedrock.feature\";\r\nimport { aiGoogleFeature } from \"./ai-google.feature\";\r\nimport { aiOllamaFeature } from \"./ai-ollama.feature\";\r\nimport { aiOpenaiFeature } from \"./ai-openai.feature\";\r\nimport { aiPanopticFeature } from \"./ai-panoptic.feature\";\r\nimport { aiToolsFeature } from \"./ai-tools.feature\";\r\nimport { aiWorkspaceFeature } from \"./ai-workspace.feature\";\r\nimport { aiFeature } from \"./ai.feature\";\r\nimport { authGoogleFeature } from \"./auth-google.feature\";\r\nimport { authPasskeysFeature } from \"./auth-passkeys.feature\";\r\nimport { bullBoardFeature } from \"./bull-board.feature\";\r\nimport { heraldFeature } from \"./herald.feature\";\r\nimport { imageFeature } from \"./image.feature\";\r\nimport { mailFeature } from \"./mail.feature\";\r\nimport { mongodbFeature } from \"./mongodb.feature\";\r\nimport { mysqlFeature } from \"./mysql.feature\";\r\nimport { notificationsFeature } from \"./notifications.feature\";\r\nimport { postgresFeature } from \"./postgres.feature\";\r\nimport { queueFeature } from \"./queue.feature\";\r\nimport { reactEmailFeature } from \"./react-email.feature\";\r\nimport { reactFeature } from \"./react.feature\";\r\nimport { redisFeature } from \"./redis.feature\";\r\nimport { s3Feature } from \"./s3.feature\";\r\nimport { schedulerFeature } from \"./scheduler.feature\";\r\nimport { sesFeature } from \"./ses.feature\";\r\nimport { shadcnFeature } from \"./shadcn.feature\";\r\nimport { sitemapFeature } from \"./sitemap.feature\";\r\nimport { socketFeature } from \"./socket.feature\";\r\nimport { tailwindFeature } from \"./tailwind.feature\";\r\nimport { testFeature } from \"./test.feature\";\r\nimport type { FeatureDefinition } from \"./types\";\r\nimport { webFeature } from \"./web.feature\";\r\n\r\nexport type { FeatureDefinition } from \"./types\";\r\n\r\n/**\r\n * The feature registry `warlock add` dispatches against.\r\n *\r\n * This file is an INDEX, nothing more: every entry lives in its own module\r\n * alongside the `onExecuting` body it runs. Key order is load-bearing — it is\r\n * the order `--list` prints and the order the \"not allowed\" error lists — so\r\n * add new features in the place they should appear, not alphabetically.\r\n */\r\nexport const featuresMap: Record<string, FeatureDefinition> = {\r\n \"react-email\": reactEmailFeature,\r\n react: reactFeature,\r\n image: imageFeature,\r\n mail: mailFeature,\r\n ses: sesFeature,\r\n mongodb: mongodbFeature,\r\n scheduler: schedulerFeature,\r\n // swagger / postman intentionally omitted — those packages do not exist yet;\r\n // they will ship together in the unified @warlock.js/api-docs package.\r\n postgres: postgresFeature,\r\n mysql: mysqlFeature,\r\n redis: redisFeature,\r\n s3: s3Feature,\r\n test: testFeature,\r\n web: webFeature,\r\n // Directly after `web`, and only there: it `requires` it, it is useless\r\n // without it, and a reader scanning `--list` for the page stack should meet\r\n // the two together rather than find styling filed between queues and sockets.\r\n tailwind: tailwindFeature,\r\n // Immediately after `tailwind`, for the same reason `tailwind` follows `web`:\r\n // it `requires` it, it appends to the stylesheet that feature creates, and the\r\n // three of them are one stack a reader should meet in build order.\r\n shadcn: shadcnFeature,\r\n // Directly after the web/tailwind/shadcn stack, for the same reason: it\r\n // `requires` web (it reads the page registry `listRoutablePages()`\r\n // exposes) and a reader scanning `--list` for the page stack should meet it\r\n // there rather than filed between queues and sockets.\r\n sitemap: sitemapFeature,\r\n herald: heraldFeature,\r\n queue: queueFeature,\r\n // Directly after `queue`, for the same reason `tailwind` follows `web`: it\r\n // needs queue already installed and configured, and a reader scanning\r\n // `--list` for job-queue features should meet the two together.\r\n \"bull-board\": bullBoardFeature,\r\n socket: socketFeature,\r\n notifications: notificationsFeature,\r\n access: accessFeature,\r\n // Login methods for @warlock.js/auth — \"<package>-<vendor>\" like the ai-* entries.\r\n \"auth-google\": authGoogleFeature,\r\n \"auth-passkeys\": authPasskeysFeature,\r\n ai: aiFeature,\r\n \"ai-openai\": aiOpenaiFeature,\r\n \"ai-google\": aiGoogleFeature,\r\n \"ai-anthropic\": aiAnthropicFeature,\r\n \"ai-bedrock\": aiBedrockFeature,\r\n \"ai-ollama\": aiOllamaFeature,\r\n \"ai-tools\": aiToolsFeature,\r\n \"ai-panoptic\": aiPanopticFeature,\r\n \"ai-workspace\": aiWorkspaceFeature,\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAa,cAAiD;CAC5D,eAAe;CACf,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,SAAS;CACT,WAAW;CAGX,UAAU;CACV,OAAO;CACP,OAAO;CACP,IAAI;CACJ,MAAM;CACN,KAAK;CAIL,UAAU;CAIV,QAAQ;CAKR,SAAS;CACT,QAAQ;CACR,OAAO;CAIP,cAAc;CACd,QAAQ;CACR,eAAe;CACf,QAAQ;CAER,eAAe;CACf,iBAAiB;CACjB,IAAI;CACJ,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,YAAY;CACZ,eAAe;CACf,gBAAgB;AAClB"}
@@ -0,0 +1,74 @@
1
+ import { rootPath } from "../../utils/paths.mjs";
2
+ import "../../utils/index.mjs";
3
+ import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
4
+ import { insertConnectorEntry } from "./shared/insert-connector-entry.mjs";
5
+ import { colors } from "@mongez/copper";
6
+ import { fileExistsAsync, getFileAsync, putFileAsync } from "@warlock.js/fs";
7
+
8
+ //#region ../core/src/generations/features/sitemap.feature.ts
9
+ const sitemapConfigStub = `import type { SitemapConfig } from "@warlock.js/sitemap";
10
+
11
+ /**
12
+ * Runtime sitemap.xml generation.
13
+ *
14
+ * Ships DISABLED because a sitemap needs this application's public origin and
15
+ * a freshly generated app has no way to know it. Two steps to turn it on:
16
+ *
17
+ * 1. set \`app.publicUrl\` in src/config/app.ts, or the PUBLIC_APP_URL
18
+ * environment variable;
19
+ * 2. flip \`enabled\` to true here.
20
+ *
21
+ * With it enabled and no origin configured, boot REFUSES rather than serving
22
+ * absolute URLs built from a guessed host — a sitemap pointing at the wrong
23
+ * domain is worse than one that never starts, because nothing downstream
24
+ * reports it.
25
+ */
26
+ const sitemapConfig: SitemapConfig = {
27
+ enabled: false,
28
+ path: "/sitemap.xml",
29
+ defaults: { changefreq: "weekly", priority: 0.5 },
30
+ };
31
+
32
+ export default sitemapConfig;
33
+ `;
34
+ /** Register the sitemap connector in the app-owned configuration without reformatting it. */
35
+ async function registerSitemapConnector() {
36
+ const configPath = rootPath("warlock.config.ts");
37
+ if (!await fileExistsAsync(configPath)) {
38
+ console.log(`${colors.yellowBright("warlock.config.ts")} not found — add this yourself:\n import { sitemapConnector } from "@warlock.js/sitemap";\n export default defineConfig({ connectors: [sitemapConnector()] });`);
39
+ return;
40
+ }
41
+ const current = await getFileAsync(configPath);
42
+ if (current.includes("sitemapConnector")) {
43
+ console.log(`${colors.yellowBright("sitemapConnector")} already registered, skipping...`);
44
+ return;
45
+ }
46
+ const importLine = "import { sitemapConnector } from \"@warlock.js/sitemap\";";
47
+ let next = current.includes(importLine) ? current : `${importLine}\n${current}`;
48
+ const insertion = insertConnectorEntry(next, "sitemapConnector()");
49
+ if (insertion.status === "already-present") return;
50
+ if (insertion.status === "added") next = insertion.next;
51
+ else if (next.includes("defineConfig({")) next = next.replace("defineConfig({", "defineConfig({\n connectors: [sitemapConnector()],\n");
52
+ else {
53
+ console.log(`${colors.yellowBright("warlock.config.ts")} has no recognisable defineConfig({...}) — add \`connectors: [sitemapConnector()]\` yourself.`);
54
+ return;
55
+ }
56
+ await putFileAsync(configPath, next);
57
+ console.log(`${colors.green("✓")} Registered sitemapConnector in warlock.config.ts`);
58
+ console.log("Next: set `app.publicUrl` (src/config/app.ts) or the PUBLIC_APP_URL environment variable — the sitemap route refuses to boot without it.");
59
+ }
60
+ /** `warlock add sitemap` — runtime sitemap.xml generation, backed by the page registry. */
61
+ const sitemapFeature = {
62
+ description: "Installs @warlock.js/sitemap — runtime sitemap.xml generation from the page registry. Creates src/config/sitemap.ts and registers sitemapConnector() in warlock.config.ts. Requires app.publicUrl (or PUBLIC_APP_URL) to be set.",
63
+ requires: ["web"],
64
+ dependencies: { "@warlock.js/sitemap": INSTALLED_WARLOCK_VERSION },
65
+ ejectConfig: {
66
+ content: sitemapConfigStub,
67
+ name: "sitemap"
68
+ },
69
+ onExecuting: registerSitemapConnector
70
+ };
71
+
72
+ //#endregion
73
+ export { sitemapFeature };
74
+ //# sourceMappingURL=sitemap.feature.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/sitemap.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport { rootPath } from \"../../utils\";\nimport { insertConnectorEntry } from \"./shared/insert-connector-entry\";\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\nconst sitemapConfigStub = `import type { SitemapConfig } from \"@warlock.js/sitemap\";\n\n/**\n * Runtime sitemap.xml generation.\n *\n * Ships DISABLED because a sitemap needs this application's public origin and\n * a freshly generated app has no way to know it. Two steps to turn it on:\n *\n * 1. set \\`app.publicUrl\\` in src/config/app.ts, or the PUBLIC_APP_URL\n * environment variable;\n * 2. flip \\`enabled\\` to true here.\n *\n * With it enabled and no origin configured, boot REFUSES rather than serving\n * absolute URLs built from a guessed host — a sitemap pointing at the wrong\n * domain is worse than one that never starts, because nothing downstream\n * reports it.\n */\nconst sitemapConfig: SitemapConfig = {\n enabled: false,\n path: \"/sitemap.xml\",\n defaults: { changefreq: \"weekly\", priority: 0.5 },\n};\n\nexport default sitemapConfig;\n`;\n\n/** Register the sitemap connector in the app-owned configuration without reformatting it. */\nasync function registerSitemapConnector(): Promise<void> {\n const configPath = rootPath(\"warlock.config.ts\");\n\n if (!(await fileExistsAsync(configPath))) {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} not found — add this yourself:\\n` +\n ` import { sitemapConnector } from \"@warlock.js/sitemap\";\\n` +\n ` export default defineConfig({ connectors: [sitemapConnector()] });`,\n );\n\n return;\n }\n\n const current = await getFileAsync(configPath);\n\n if (current.includes(\"sitemapConnector\")) {\n console.log(`${colors.yellowBright(\"sitemapConnector\")} already registered, skipping...`);\n\n return;\n }\n\n const importLine = 'import { sitemapConnector } from \"@warlock.js/sitemap\";';\n let next = current.includes(importLine) ? current : `${importLine}\\n${current}`;\n\n const insertion = insertConnectorEntry(next, \"sitemapConnector()\");\n\n if (insertion.status === \"already-present\") {\n return;\n }\n\n if (insertion.status === \"added\") {\n next = insertion.next;\n } else if (next.includes(\"defineConfig({\")) {\n next = next.replace(\n \"defineConfig({\",\n \"defineConfig({\\n connectors: [sitemapConnector()],\\n\",\n );\n } else {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} has no recognisable defineConfig({...}) — ` +\n \"add `connectors: [sitemapConnector()]` yourself.\",\n );\n\n return;\n }\n\n await putFileAsync(configPath, next);\n console.log(`${colors.green(\"✓\")} Registered sitemapConnector in warlock.config.ts`);\n console.log(\n \"Next: set `app.publicUrl` (src/config/app.ts) or the PUBLIC_APP_URL environment \" +\n \"variable — the sitemap route refuses to boot without it.\",\n );\n}\n\n/** `warlock add sitemap` — runtime sitemap.xml generation, backed by the page registry. */\nexport const sitemapFeature: FeatureDefinition = {\n description:\n \"Installs @warlock.js/sitemap — runtime sitemap.xml generation from the page registry. Creates src/config/sitemap.ts and registers sitemapConnector() in warlock.config.ts. Requires app.publicUrl (or PUBLIC_APP_URL) to be set.\",\n requires: [\"web\"],\n dependencies: {\n \"@warlock.js/sitemap\": INSTALLED_WARLOCK_VERSION,\n },\n ejectConfig: {\n content: sitemapConfigStub,\n name: \"sitemap\",\n },\n onExecuting: registerSitemapConnector,\n};\n"],"mappings":";;;;;;;;AAMA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B1B,eAAe,2BAA0C;CACvD,MAAM,aAAa,SAAS,mBAAmB;CAE/C,IAAI,CAAE,MAAM,gBAAgB,UAAU,GAAI;EACxC,QAAQ,IACN,GAAG,OAAO,aAAa,mBAAmB,EAAE,iKAG9C;EAEA;CACF;CAEA,MAAM,UAAU,MAAM,aAAa,UAAU;CAE7C,IAAI,QAAQ,SAAS,kBAAkB,GAAG;EACxC,QAAQ,IAAI,GAAG,OAAO,aAAa,kBAAkB,EAAE,iCAAiC;EAExF;CACF;CAEA,MAAM,aAAa;CACnB,IAAI,OAAO,QAAQ,SAAS,UAAU,IAAI,UAAU,GAAG,WAAW,IAAI;CAEtE,MAAM,YAAY,qBAAqB,MAAM,oBAAoB;CAEjE,IAAI,UAAU,WAAW,mBACvB;CAGF,IAAI,UAAU,WAAW,SACvB,OAAO,UAAU;MACZ,IAAI,KAAK,SAAS,gBAAgB,GACvC,OAAO,KAAK,QACV,kBACA,uDACF;MACK;EACL,QAAQ,IACN,GAAG,OAAO,aAAa,mBAAmB,EAAE,8FAE9C;EAEA;CACF;CAEA,MAAM,aAAa,YAAY,IAAI;CACnC,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,kDAAkD;CACnF,QAAQ,IACN,0IAEF;AACF;;AAGA,MAAa,iBAAoC;CAC/C,aACE;CACF,UAAU,CAAC,KAAK;CAChB,cAAc,EACZ,uBAAuB,0BACzB;CACA,aAAa;EACX,SAAS;EACT,MAAM;CACR;CACA,aAAa;AACf"}
@@ -37,6 +37,21 @@ declare class NotAllowedError extends HttpError {
37
37
  payload?: any | undefined;
38
38
  constructor(message: string, payload?: any | undefined);
39
39
  }
40
+ /**
41
+ * Thrown by `Request.prototype.cookie` / `Request.prototype.hasCookie` when
42
+ * `@fastify/cookie` was never registered on this Fastify instance, so
43
+ * `baseRequest.cookies` is `undefined` rather than an (possibly empty) object.
44
+ *
45
+ * A by-name cookie read is a deliberate assertion by the caller — "this
46
+ * cookie should be readable here" — so an unavailable jar is a configuration
47
+ * fault, not an absent cookie, and must not be swallowed into a default
48
+ * value or a silent `false`. Contrast `Request.prototype.cookies`, which
49
+ * stays lenient because the framework's own opportunistic reads (e.g.
50
+ * locale resolution) must not throw on a request that simply has no jar.
51
+ */
52
+ declare class CookieJarUnavailableError extends Error {
53
+ constructor(cookieName: string);
54
+ }
40
55
  /**
41
56
  * Thrown by `Request.prototype.user` in development to catch a call site
42
57
  * still reading the removed `request.user` getter/setter after the 5.12.0
@@ -54,5 +69,5 @@ declare class RequestUserMovedError extends Error {
54
69
  constructor();
55
70
  }
56
71
  //#endregion
57
- export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
72
+ export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
58
73
  //# sourceMappingURL=errors.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"mappings":";cAAa,SAAA,SAAkB,KAAK;EAEzB,MAAA;EACA,OAAA;EACA,OAAA;cAFA,MAAA,UACA,OAAA,UACA,OAAA;AAAA;AAAA,cAOE,qBAAA,SAA8B,SAAS;EAGzC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,iBAAA,SAA0B,SAAS;EAGrC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,cAAA,SAAuB,SAAS;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,WAAA,SAAoB,SAAS;EAG/B,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,aAAA,SAAsB,SAAS;EAGjC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,kBAAA,SAA2B,SAAS;EAGtC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;;;;;;;;;;AAlDa;AAOxB;;;cA+Da,qBAAA,SAA8B,KAAK;EAAL,WAAA;AAAA"}
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"mappings":";cAAa,SAAA,SAAkB,KAAK;EAEzB,MAAA;EACA,OAAA;EACA,OAAA;cAFA,MAAA,UACA,OAAA,UACA,OAAA;AAAA;AAAA,cAOE,qBAAA,SAA8B,SAAS;EAGzC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,iBAAA,SAA0B,SAAS;EAGrC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,cAAA,SAAuB,SAAS;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,WAAA,SAAoB,SAAS;EAG/B,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,aAAA,SAAsB,SAAS;EAGjC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,kBAAA,SAA2B,SAAS;EAGtC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;;;;;;;;;;AAlDa;AAOxB;;cA8Da,yBAAA,SAAkC,KAAK;cAC/B,UAAA;AAAA;;;;;;AA5DG;AAOxB;;;;;;;cA6Ea,qBAAA,SAA8B,KAAK;EAAL,WAAA;AAAA"}
@@ -65,6 +65,24 @@ var NotAllowedError = class extends HttpError {
65
65
  }
66
66
  };
67
67
  /**
68
+ * Thrown by `Request.prototype.cookie` / `Request.prototype.hasCookie` when
69
+ * `@fastify/cookie` was never registered on this Fastify instance, so
70
+ * `baseRequest.cookies` is `undefined` rather than an (possibly empty) object.
71
+ *
72
+ * A by-name cookie read is a deliberate assertion by the caller — "this
73
+ * cookie should be readable here" — so an unavailable jar is a configuration
74
+ * fault, not an absent cookie, and must not be swallowed into a default
75
+ * value or a silent `false`. Contrast `Request.prototype.cookies`, which
76
+ * stays lenient because the framework's own opportunistic reads (e.g.
77
+ * locale resolution) must not throw on a request that simply has no jar.
78
+ */
79
+ var CookieJarUnavailableError = class extends Error {
80
+ constructor(cookieName) {
81
+ super(`Cannot read cookie "${cookieName}": the cookie jar is unavailable because @fastify/cookie is not registered on this Fastify instance. Register the plugin (see core's http/plugins.ts) before reading cookies by name.`);
82
+ this.name = "CookieJarUnavailableError";
83
+ }
84
+ };
85
+ /**
68
86
  * Thrown by `Request.prototype.user` in development to catch a call site
69
87
  * still reading the removed `request.user` getter/setter after the 5.12.0
70
88
  * move: the authenticated user now lives at `request.locals.user`, written
@@ -85,5 +103,5 @@ var RequestUserMovedError = class extends Error {
85
103
  };
86
104
 
87
105
  //#endregion
88
- export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
106
+ export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
89
107
  //# sourceMappingURL=errors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"sourcesContent":["export class HttpError extends Error {\n public constructor(\n public status: number,\n public message: string,\n public payload?: any,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport class ResourceNotFoundError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(404, message, payload);\n this.name = \"ResourceNotFoundError\";\n }\n}\n\nexport class UnAuthorizedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(401, message, payload);\n this.name = \"UnAuthorizedError\";\n }\n}\n\nexport class ForbiddenError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(403, message, payload);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport class BadRequestError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(400, message, payload);\n this.name = \"BadRequestError\";\n }\n}\n\nexport class ServerError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(500, message, payload);\n this.name = \"ServerError\";\n }\n}\n\nexport class ConflictError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(409, message, payload);\n this.name = \"ConflictError\";\n }\n}\n\nexport class NotAcceptableError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(406, message, payload);\n this.name = \"NotAcceptableError\";\n }\n}\n\nexport class NotAllowedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(405, message, payload);\n this.name = \"NotAllowedError\";\n }\n}\n\n/**\n * Thrown by `Request.prototype.user` in development to catch a call site\n * still reading the removed `request.user` getter/setter after the 5.12.0\n * move: the authenticated user now lives at `request.locals.user`, written\n * by `@warlock.js/auth`'s middleware.\n *\n * Development-only diagnostic, not a runtime contract other code should\n * catch — the getter itself is typed `never`, so a caller that still\n * compiles against `request.user` only does so via `any`/an outdated type.\n * Kept in core (not auth) because `request.user` is a core `Request`\n * accessor and core cannot depend on `@warlock.js/auth` to react to it.\n * Slated for removal one release after 5.12.0 — see the CHANGELOG.\n */\nexport class RequestUserMovedError extends Error {\n public constructor() {\n super(\n \"request.user has been removed. The authenticated user now lives at \" +\n \"request.locals.user, set by @warlock.js/auth's middleware.\",\n );\n this.name = \"RequestUserMovedError\";\n }\n}\n"],"mappings":";AAAA,IAAa,YAAb,cAA+B,MAAM;CACnC,AAAO,YACL,AAAO,QACP,AAAO,SACP,AAAO,SACP;EACA,MAAM,OAAO;EAJN;EACA;EACA;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,UAAU;CACnD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oBAAb,cAAuC,UAAU;CAC/C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,iBAAb,cAAoC,UAAU;CAC5C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,cAAb,cAAiC,UAAU;CACzC,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gBAAb,cAAmC,UAAU;CAC3C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,qBAAb,cAAwC,UAAU;CAChD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;AAeA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,cAAc;EACnB,MACE,+HAEF;EACA,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"sourcesContent":["export class HttpError extends Error {\n public constructor(\n public status: number,\n public message: string,\n public payload?: any,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport class ResourceNotFoundError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(404, message, payload);\n this.name = \"ResourceNotFoundError\";\n }\n}\n\nexport class UnAuthorizedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(401, message, payload);\n this.name = \"UnAuthorizedError\";\n }\n}\n\nexport class ForbiddenError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(403, message, payload);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport class BadRequestError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(400, message, payload);\n this.name = \"BadRequestError\";\n }\n}\n\nexport class ServerError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(500, message, payload);\n this.name = \"ServerError\";\n }\n}\n\nexport class ConflictError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(409, message, payload);\n this.name = \"ConflictError\";\n }\n}\n\nexport class NotAcceptableError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(406, message, payload);\n this.name = \"NotAcceptableError\";\n }\n}\n\nexport class NotAllowedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(405, message, payload);\n this.name = \"NotAllowedError\";\n }\n}\n\n/**\n * Thrown by `Request.prototype.cookie` / `Request.prototype.hasCookie` when\n * `@fastify/cookie` was never registered on this Fastify instance, so\n * `baseRequest.cookies` is `undefined` rather than an (possibly empty) object.\n *\n * A by-name cookie read is a deliberate assertion by the caller — \"this\n * cookie should be readable here\" — so an unavailable jar is a configuration\n * fault, not an absent cookie, and must not be swallowed into a default\n * value or a silent `false`. Contrast `Request.prototype.cookies`, which\n * stays lenient because the framework's own opportunistic reads (e.g.\n * locale resolution) must not throw on a request that simply has no jar.\n */\nexport class CookieJarUnavailableError extends Error {\n public constructor(cookieName: string) {\n super(\n `Cannot read cookie \"${cookieName}\": the cookie jar is unavailable because ` +\n \"@fastify/cookie is not registered on this Fastify instance. \" +\n \"Register the plugin (see core's http/plugins.ts) before reading cookies by name.\",\n );\n\n this.name = \"CookieJarUnavailableError\";\n }\n}\n\n/**\n * Thrown by `Request.prototype.user` in development to catch a call site\n * still reading the removed `request.user` getter/setter after the 5.12.0\n * move: the authenticated user now lives at `request.locals.user`, written\n * by `@warlock.js/auth`'s middleware.\n *\n * Development-only diagnostic, not a runtime contract other code should\n * catch — the getter itself is typed `never`, so a caller that still\n * compiles against `request.user` only does so via `any`/an outdated type.\n * Kept in core (not auth) because `request.user` is a core `Request`\n * accessor and core cannot depend on `@warlock.js/auth` to react to it.\n * Slated for removal one release after 5.12.0 — see the CHANGELOG.\n */\nexport class RequestUserMovedError extends Error {\n public constructor() {\n super(\n \"request.user has been removed. The authenticated user now lives at \" +\n \"request.locals.user, set by @warlock.js/auth's middleware.\",\n );\n this.name = \"RequestUserMovedError\";\n }\n}\n"],"mappings":";AAAA,IAAa,YAAb,cAA+B,MAAM;CACnC,AAAO,YACL,AAAO,QACP,AAAO,SACP,AAAO,SACP;EACA,MAAM,OAAO;EAJN;EACA;EACA;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,UAAU;CACnD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oBAAb,cAAuC,UAAU;CAC/C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,iBAAb,cAAoC,UAAU;CAC5C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,cAAb,cAAiC,UAAU;CACzC,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gBAAb,cAAmC,UAAU;CAC3C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,qBAAb,cAAwC,UAAU;CAChD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;AAcA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,YAAoB;EACrC,MACE,uBAAuB,WAAW,sLAGpC;EAEA,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;AAeA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,cAAc;EACnB,MACE,+HAEF;EACA,KAAK,OAAO;CACd;AACF"}
@@ -14,7 +14,7 @@ import { logResponse, wrapResponseInDataKey } from "./events.mjs";
14
14
  import { HealthCheck, HealthStatus, health } from "./health.mjs";
15
15
  import { RequestController } from "./request-controller.mjs";
16
16
  import { UPLOADS_DEFAULTS, uploadsConfig } from "./uploads-config.mjs";
17
- import { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
17
+ import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
18
18
  import { CacheMiddlewareOptions } from "./middleware/cache-response-middleware.mjs";
19
19
  import { ConcurrencyLimitOptions } from "./middleware/concurrency-limit.middleware.mjs";
20
20
  import { IdempotencyOptions } from "./middleware/idempotency.middleware.mjs";
@@ -1,6 +1,6 @@
1
1
  import { requestContext, useCurrentUser, useRequest, useRequestStore } from "./context/request-context.mjs";
2
2
  import { DEFAULT_CSP_DIRECTIVES, InvalidCspDirectiveError, applyCspHeader, buildCspHeaderValue, mergeCspDirectives, resolveCspConfig, serializeCspDirectives, validateCspConfigAtBoot, validateCspDirectives } from "./csp.mjs";
3
- import { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
3
+ import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
4
4
  import { deriveTraceId, parseTraceparentTraceId } from "./tracing/trace-id.mjs";
5
5
  import { buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
6
6
  import "./tracing/index.mjs";
@@ -248,6 +248,14 @@ declare class Request<RequestValidation = any> {
248
248
  * Get all cookies from the current request
249
249
  */
250
250
  get cookies(): Record<string, string | undefined>;
251
+ /**
252
+ * Assert the cookie jar exists before a by-name read. `get cookies()` stays
253
+ * lenient (returns `{}`) for the framework's own opportunistic reads, but a
254
+ * deliberate by-name read from application code must fail loudly when
255
+ * `@fastify/cookie` was never registered, rather than being indistinguishable
256
+ * from "the caller sent no such cookie".
257
+ */
258
+ private assertCookieJarAvailable;
251
259
  /**
252
260
  * Get a particular cookie value or fallback to default
253
261
  */
@@ -1 +1 @@
1
- {"version":3,"file":"request.d.mts","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"mappings":"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"}
1
+ {"version":3,"file":"request.d.mts","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { config } from "../config/config-getter.mjs";
2
2
  import { LOCALE_COOKIE_NAME, resolveLocaleConfiguration } from "../config/locale-configuration.mjs";
3
- import { RequestUserMovedError } from "./errors/errors.mjs";
3
+ import { CookieJarUnavailableError, RequestUserMovedError } from "./errors/errors.mjs";
4
4
  import { deriveTraceId } from "./tracing/trace-id.mjs";
5
5
  import { buildTracingContext, dispatchPhase, isTracingEnabled } from "./tracing/tracing-dispatcher.mjs";
6
6
  import "./tracing/index.mjs";
@@ -226,9 +226,20 @@ var Request = class Request {
226
226
  return this.baseRequest.cookies || {};
227
227
  }
228
228
  /**
229
+ * Assert the cookie jar exists before a by-name read. `get cookies()` stays
230
+ * lenient (returns `{}`) for the framework's own opportunistic reads, but a
231
+ * deliberate by-name read from application code must fail loudly when
232
+ * `@fastify/cookie` was never registered, rather than being indistinguishable
233
+ * from "the caller sent no such cookie".
234
+ */
235
+ assertCookieJarAvailable(name) {
236
+ if (this.baseRequest.cookies === void 0) throw new CookieJarUnavailableError(name);
237
+ }
238
+ /**
229
239
  * Get a particular cookie value or fallback to default
230
240
  */
231
241
  cookie(name, defaultValue) {
242
+ this.assertCookieJarAvailable(name);
232
243
  const value = this.cookies[name] ?? defaultValue;
233
244
  try {
234
245
  return JSON.parse(value);
@@ -240,6 +251,7 @@ var Request = class Request {
240
251
  * Determine if the request has the specified cookie
241
252
  */
242
253
  hasCookie(name) {
254
+ this.assertCookieJarAvailable(name);
243
255
  return this.cookies[name] !== void 0;
244
256
  }
245
257
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { trans, transFrom } from \"@mongez/localization\";\nimport { Random, except, get, only, rtrim, set, unset } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport type { LogLevel } from \"@warlock.js/logger\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseValidator, v } from \"@warlock.js/seal\";\nimport type { FastifyRequest } from \"fastify\";\nimport { randomBytes } from \"node:crypto\";\nimport { type IncomingHttpHeaders } from \"node:http2\";\nimport { Application } from \"../application/application\";\nimport { config } from \"../config/config-getter\";\nimport { LOCALE_COOKIE_NAME, resolveLocaleConfiguration } from \"../config/locale-configuration\";\nimport type { Middleware, Route } from \"../router\";\nimport { validateAll } from \"../validation/validateAll\";\nimport { RequestUserMovedError } from \"./errors\";\nimport { createRequestStore } from \"./middleware/inject-request-context\";\nimport { Response } from \"./response\";\nimport { buildTracingContext, deriveTraceId, dispatchPhase, isTracingEnabled } from \"./tracing\";\nimport type { DecodedAccessToken, RequestEvent, RequestLocals } from \"./types\";\nimport { UploadedFile } from \"./uploaded-file\";\n\ntype StandardHeaders = {\n // copy every declared property from http.IncomingHttpHeaders\n // but remove index signatures\n [\n K in keyof IncomingHttpHeaders as string extends K ? never : number extends K ? never : K\n ]: IncomingHttpHeaders[K];\n};\n\ntype HeaderKeys = keyof StandardHeaders;\n\nexport class Request<RequestValidation = any> {\n /**\n * Underlying Fastify request — a public escape hatch to capabilities the\n * framework's high-level helpers don't yet cover.\n *\n * **Prefer framework methods first**: `request.input()`, `request.header()`,\n * `request.body`, `request.query`, `request.params`, `request.file()`,\n * `request.locals.user` (set by `@warlock.js/auth`), `request.detectIp()`,\n * etc. They handle locale, parsing,\n * trust-proxy, and validation pipeline integration correctly.\n *\n * **Reach for `baseRequest` only** when the framework genuinely lacks a\n * helper for what you need — and when you do, file an issue so we can add\n * it. The escape hatch is the release valve that lets consumers move\n * faster than the framework, but every long-term reach here is a missing\n * helper waiting to be added.\n */\n public baseRequest!: FastifyRequest;\n\n /**\n * Response Object\n */\n public response!: Response;\n\n /**\n * Route Object\n */\n public route!: Route;\n\n /**\n * Parsed Request Payload\n */\n protected payload: any = {};\n\n /**\n * Backing field for `decodedAccessToken` — see the accessor below.\n */\n private _decodedAccessToken?: DecodedAccessToken;\n\n /**\n * Decoded access token payload (set by auth middleware).\n *\n * A prototype accessor, not a plain field, so assignment can mark the\n * request `authDerived` (see the setter below and `RequestLocals` in\n * `types.ts`) without every call site remembering to do so itself.\n */\n public get decodedAccessToken(): DecodedAccessToken | undefined {\n return this._decodedAccessToken;\n }\n\n public set decodedAccessToken(value: DecodedAccessToken | undefined) {\n this._decodedAccessToken = value;\n this.locals.authDerived = true;\n }\n\n /**\n * REMOVED in 5.12.0 — the authenticated user now lives at\n * `request.locals.user`, a key `@warlock.js/auth` declares via module\n * augmentation on `RequestLocals` and writes from its middleware after a\n * successful token resolution. `RequestUser` moved out of core to\n * `@warlock.js/auth` alongside it.\n *\n * This getter is a development-time diagnostic only, kept for one release\n * so a call site that still reads `request.user` fails loudly at runtime\n * instead of silently reading `undefined`. It is typed `never` so it\n * cannot reintroduce an auth-shaped type into core, and it throws\n * unconditionally outside production so the failure is impossible to miss\n * in local dev — see `RequestUserMovedError`.\n *\n * There is no setter: nothing in core or downstream packages should ever\n * assign to `request.user` again.\n */\n public get user(): never {\n if (Application.isDevelopment) {\n throw new RequestUserMovedError();\n }\n\n return undefined as never;\n }\n\n /**\n * Private, server-only, per-request data bag.\n *\n * Distinct from the input payload (`body` / `query` / `params` / `all()`):\n * a write here never surfaces in `request.all()`, `request.validated()`, or\n * `request.input()`. That is the trap `request.set()` sets for private data\n * — it writes into the payload `all` bag, so anything stored there leaks\n * into every input accessor and, from there, into the client-facing\n * payload. `locals` is the correct home for private per-request app data\n * (a resolved session, a fetched-once model) that must never be mistaken\n * for client input.\n *\n * Augmentable via module augmentation, in the module that OWNS the key:\n *\n * ```typescript\n * declare module \"@warlock.js/core\" {\n * interface RequestLocals {\n * session?: { token: string };\n * }\n * }\n * ```\n *\n * A plain class-field initializer is sufficient for \"fresh per request\":\n * `router.ts:925` constructs `new Request()` for every incoming request —\n * `Request` instances are not pooled or reused across requests — so this\n * initializer runs exactly once per request and no value can leak in from\n * a prior one.\n */\n public locals: RequestLocals = {};\n\n /**\n * Backing field for the lazily-generated CSP nonce. Left `undefined` until\n * the first `request.nonce` read; see the `nonce` getter below.\n */\n protected _nonce?: string;\n\n /**\n * Per-request Content-Security-Policy nonce — a fresh, unguessable value\n * the web layer hands to `<Scripts nonce={...} />` (the inline payload\n * script) and to the `Content-Security-Policy` header, so a strict\n * `script-src 'nonce-...'` allows only the script this request actually\n * rendered.\n *\n * Generated LAZILY on first access, not eagerly in `setRequest()`: most\n * requests (API routes, anything that isn't rendering HTML) never read it,\n * and spending a `randomBytes` call on every single request for a value\n * most of them discard is wasted entropy draw + CPU. Once generated it is\n * cached in `_nonce`, so every subsequent read within the SAME request\n * returns the identical value — required, since the header and the inline\n * `<script>` tag must agree on one nonce. `_nonce` is a plain field on a\n * per-request `Request` instance (see `locals` above — `router.ts:925`,\n * no pooling), so the cache can never leak into the next request; a fresh\n * `Request` means a fresh, unset `_nonce`.\n *\n * 16 random bytes, base64-encoded — the size the CSP Level 3 spec's own\n * examples use, and far more entropy than an attacker could feasibly guess\n * to defeat the policy.\n */\n public get nonce(): string {\n if (!this._nonce) {\n this._nonce = randomBytes(16).toString(\"base64\");\n }\n\n return this._nonce;\n }\n\n /**\n * Current request instance\n */\n public static current: Request;\n\n /**\n * Translation method\n * Type of it is the same as the type of trans function\n */\n public trans: ReturnType<typeof trans> = trans;\n\n /**\n * Alias to trans method\n */\n public t: ReturnType<typeof trans> = trans;\n\n /*\n * v5 removed the `[key: string]: any` index signature (eed20184). Attaching\n * arbitrary properties compiled silently and hid real bugs behind `any`.\n * The sanctioned extension paths are:\n * - `request.locals` (augment `RequestLocals` via module augmentation) for\n * per-request attached data, e.g. models fetched in validation middleware.\n * - `requestMemo(key, fn)` for per-request memoized computation.\n * - Module augmentation of the `Request` class itself for new typed members.\n */\n\n /**\n * Locale code\n */\n protected _locale = \"\";\n\n /**\n * Validated data\n */\n protected validatedData?: RequestValidation;\n\n /**\n * Request id\n */\n public id = Random.string(32);\n\n /**\n * Trace id. The inbound `traceparent` header's trace id\n * when valid, otherwise `id`. Resolved once in `setRequest`, alongside\n * `id` itself — see `resolveTraceId`.\n */\n public traceId = \"\";\n\n /**\n * Start Time\n */\n public startTime = Date.now();\n\n /**\n * End Time\n */\n public endTime?: undefined | number;\n\n /**\n * Set request handler\n */\n public setRequest(request: FastifyRequest) {\n this.baseRequest = request;\n\n this.resolveRequestId();\n\n this.resolveTraceId();\n\n this.parsePayload();\n\n // Resolve the locale at CALL time, never at bind time. `setRequest` runs\n // before routing, so a locale set later (path locale, `setLocaleCode`, the\n // web layer's C3 derivation) must steer translations too — the old\n // `transFrom.bind(null, localeCode)` snapshot made `request.locale` and\n // `request.trans()` silently disagree for the rest of the request.\n this.trans = this.t = (keyword: string, placeholders?: any) =>\n transFrom(this.getLocaleCode(), keyword, placeholders);\n\n return this;\n }\n\n /**\n * Inherit `X-Request-Id` from the incoming request, fall back to a custom\n * generator, then to the field-init default (`Random.string(32)`).\n *\n * Inherited values are validated (length cap + printable-ASCII) to prevent\n * log-injection from a malicious client. Disable the whole behavior by\n * setting `http.requestId.enabled = false` — in which case the field-init\n * default is used regardless of any incoming header.\n */\n protected resolveRequestId() {\n const requestIdConfig = config.key(\"http.requestId\") || {};\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = (requestIdConfig.header || \"x-request-id\").toLowerCase();\n const incoming = this.baseRequest.headers[headerName];\n\n if (Request.isValidRequestId(incoming)) {\n this.id = incoming;\n\n return;\n }\n\n if (typeof requestIdConfig.generator === \"function\") {\n this.id = requestIdConfig.generator();\n }\n }\n\n /**\n * Derive `traceId`: the inbound\n * `traceparent` header's trace id when it is a valid W3C traceparent,\n * otherwise `id`. Always runs — unlike request-id inheritance this has no\n * `enabled: false` escape hatch, since `traceId` is only ever read when\n * tracing hooks are enabled (see `./tracing`).\n */\n protected resolveTraceId() {\n const header = this.baseRequest.headers.traceparent;\n const traceparent = Array.isArray(header) ? header[0] : header;\n\n this.traceId = deriveTraceId(traceparent, this.id);\n }\n\n /**\n * Validate a candidate request-id value. Accepts non-empty printable ASCII\n * up to 128 characters — tight enough to reject newline / control-character\n * log-injection, loose enough to accept UUIDs, ULIDs, snowflakes, etc.\n */\n protected static isValidRequestId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= 128 &&\n /^[\\x21-\\x7e]+$/.test(value)\n );\n }\n\n /**\n * Translate from the given locale code\n */\n public transFrom(localeCode: string, keyword: string, placeholders?: any) {\n return transFrom(localeCode, keyword, placeholders);\n }\n\n /**\n * Cache one supported locale without coercing request-controlled input.\n *\n * The default is the answer for a client that asked for NOTHING. A client\n * that did ask is only overridden when its value fails a declared\n * `app.localeCodes` allow-list; with no list declared there is nothing to\n * fail, so the requested locale passes through unchanged.\n */\n protected cacheLocale(candidate: unknown): string {\n const { defaultLocaleCode, localeCodes } = resolveLocaleConfiguration(\n config.key(\"app.localeCode\"),\n config.key(\"app.localeCodes\"),\n );\n\n const requested = typeof candidate === \"string\" && candidate.length > 0 ? candidate : undefined;\n\n this._locale =\n requested !== undefined && (localeCodes === undefined || localeCodes.includes(requested))\n ? requested\n : defaultLocaleCode;\n\n return this._locale;\n }\n\n /**\n * Resolve the first present Mode B source. Unsupported values fail closed to\n * the configured default instead of widening the application's locale set.\n */\n protected resolveLocale(): string {\n const candidate = [\n this.query[\"locale\"],\n this.cookies[LOCALE_COOKIE_NAME],\n this.header(\"locale\"),\n ].find((value) => typeof value === \"string\" && value.length > 0);\n\n return this.cacheLocale(candidate);\n }\n\n /**\n * Get current locale code\n */\n public get locale(): string {\n if (this._locale) return this._locale;\n\n return this.resolveLocale();\n }\n\n /**\n * Set locale code\n */\n public set locale(localeCode: string) {\n this.cacheLocale(localeCode);\n }\n\n /**\n * Set locale code\n */\n public setLocaleCode(localeCode: string) {\n this.locale = localeCode;\n\n return this;\n }\n\n /**\n * @deprecated Use `request.locale`. This alias is removed after one version.\n * The legacy default argument is accepted for source compatibility but the\n * resolved default is owned exclusively by app configuration.\n */\n public getLocaleCode(_legacyDefaultLocaleCode?: string): string {\n return this.locale;\n }\n\n /**\n * Get http protocol\n */\n public get protocol() {\n return this.baseRequest.protocol;\n }\n\n /**\n * Validate the given validation schema\n */\n public async validate(validation: BaseValidator, selectedInputs?: string[]) {\n return await v.validate(validation, selectedInputs ? this.only(selectedInputs) : this.all());\n }\n\n /**\n * Get value of the given header\n */\n public header<TCustomHeader extends string = HeaderKeys>(\n name: TCustomHeader | HeaderKeys,\n defaultValue: any = null,\n ) {\n return this.baseRequest.headers[name.toLocaleLowerCase()] ?? defaultValue;\n }\n\n /**\n * Get all cookies from the current request\n */\n public get cookies(): Record<string, string | undefined> {\n return this.baseRequest.cookies || {};\n }\n\n /**\n * Get a particular cookie value or fallback to default\n */\n public cookie(name: string, defaultValue?: any): string | any {\n const value = this.cookies[name] ?? defaultValue;\n\n try {\n return JSON.parse(value);\n } catch (error) {\n return value;\n }\n }\n\n /**\n * Determine if the request has the specified cookie\n */\n public hasCookie(name: string): boolean {\n return this.cookies[name] !== undefined;\n }\n\n /**\n * Get the current request domain\n */\n public get domain() {\n return this.baseRequest.hostname.replace(/^www\\./, \"\");\n }\n\n /**\n * Get hostname\n */\n public get hostname() {\n return this.domain;\n }\n\n /**\n * Get request origin\n */\n public get origin() {\n return this.baseRequest.headers.origin as string;\n }\n\n /**\n * Get the domain of the origin\n */\n public get originDomain() {\n const domain = this.origin ? new URL(this.origin).hostname : null;\n\n if (domain?.startsWith(\"www.\")) {\n return domain.replace(/^www\\./, \"\");\n }\n\n return domain;\n }\n\n /**\n * Get authorization header value\n */\n public get authorizationValue(): string {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return \"\";\n\n const [type, value] = authorization.split(\" \");\n\n if (![\"bearer\", \"key\"].includes(type.toLowerCase())) return \"\";\n\n return value || \"\";\n }\n\n /**\n * Get access token from Authorization header\n *\n * If the Authorization header does not start with `Bearer` value then return null\n */\n public get accessToken(): string | undefined {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return;\n\n const [type, value] = authorization.split(\" \");\n\n if (type.toLowerCase() !== \"bearer\") return;\n\n return value;\n }\n\n /**\n * Get the authorization header\n */\n public get authorization() {\n return this.header(\"authorization\");\n }\n\n /**\n * Get current request method\n */\n public get method(): string {\n return this.baseRequest.method;\n }\n\n /**\n * Parse the payload and merge it from the request body, params and query string\n */\n /**\n * Turn a bracket-notation key into the dotted path `set()` expects.\n *\n * `a[b][c]` -> `a.b.c`. Used only for NON-numeric nesting; numeric indices\n * keep the array-of-objects path in {@link parseBody}, which builds real\n * arrays rather than objects with numeric keys.\n */\n protected bracketKeyToPath(key: string): string {\n return key.replace(/\\]\\[/g, \".\").replace(/\\[/g, \".\").replace(/\\]/g, \"\");\n }\n\n /**\n * Apply the `key[]` array marker to a parsed value.\n *\n * The subtlety this exists to remove: a key declared `[]` should ALWAYS be an\n * array, but the underlying query/body parser only hands us one when the\n * caller sent the key more than once. Deciding the TYPE from the number of\n * occurrences means one selected filter is a string and two are an array —\n * a shape that changes under the user's hands.\n */\n protected arrayValueFor(value: any, isArrayKey: boolean, parse: (value: any) => any) {\n if (Array.isArray(value)) return value.map(parse);\n\n return isArrayKey ? [parse(value)] : parse(value);\n }\n\n protected parsePayload() {\n this.payload.body = this.parseBody(this.baseRequest.body);\n\n this.payload.query = this.parseBody(this.baseRequest.query);\n this.payload.params = { ...(this.baseRequest.params || {}) };\n this.payload.all = {\n ...this.payload.body,\n ...this.payload.query,\n ...this.payload.params,\n };\n }\n\n /**\n * Parse body payload\n */\n protected parseBody(data: any) {\n try {\n if (!data) return {};\n\n const body: any = {};\n\n const arrayOfObjectValues: any = {};\n\n for (let key in data) {\n const value = data[key];\n\n let isArrayKey = false;\n\n if (key.endsWith(\"[]\")) {\n isArrayKey = true;\n }\n\n key = rtrim(key, \"[]\");\n\n // check if the key is has a square brackets, then convert it into object\n // i.e user[email] => user: {email: \"value\"}\n // also check if its an array of objects\n\n if (key.includes(\"[\")) {\n // check if its an array of objects\n if (key.includes(\"][\")) {\n const keyParts = key.split(\"[\");\n\n const keyName = keyParts[0];\n const firstBracket = keyParts[1];\n const secondBracket = keyParts[2];\n\n /*\n `key.includes(\"][\")` guarantees all three segments — but that is a\n property of the string test above, not of these reads, so each is\n `string | undefined`.\n\n When the shape is not what this branch assumes, fall through to\n the generic bracket path rather than skipping the key. That is the\n same choice the NaN branch below makes, and for the same reason\n spelled out there: the failure this code has already been bitten\n by is answering with a shape the caller did not send. Dropping the\n pair silently would be that bug again, in a new place.\n */\n if (\n keyName === undefined ||\n firstBracket === undefined ||\n secondBracket === undefined\n ) {\n set(\n body,\n this.bracketKeyToPath(key),\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n const keyNameParts = firstBracket.split(\"]\");\n\n const index = Number(keyNameParts[0]);\n\n /*\n A NON-NUMERIC first segment is not an array index — it is a deeper\n nested object. `a[b][c]=x` reaches this branch because it contains\n \"][\", but `Number(\"b\")` is NaN, and the code below used to write to\n `[NaN]`: that sets a \"NaN\" PROPERTY on an array whose length stays\n 0, so the request arrived as `{a: []}` and the value was gone. No\n error, no warning — the caller simply never got `x`.\n\n Deciding between refusing (4xx) and interpreting: a doubly-nested\n key is unambiguous and is exactly what every bracket-notation\n parser means by it, so we interpret. Refusing would reject a URL\n shape that is standard elsewhere and that we ourselves already\n honour one level shallower, five lines below. What was definitely\n wrong was answering with a shape the caller did not send.\n\n Numeric indices keep the array-of-objects path below unchanged —\n `items[0][name]` is still an array.\n */\n if (Number.isNaN(index)) {\n set(\n body,\n this.bracketKeyToPath(key),\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n const bucket = (arrayOfObjectValues[keyName] ??= []);\n\n const entry = (bucket[index] ??= {});\n\n // now get the key after the index\n const keyNameParts2 = secondBracket.split(\"]\");\n const keyName2 = keyNameParts2[0];\n\n // `split` always yields a first element, so this holds — but an\n // undefined key here would write a property literally named\n // \"undefined\" onto the entry, which is the same silent-wrong-shape\n // outcome the comment above describes.\n if (keyName2 === undefined) continue;\n\n entry[keyName2] = this.parseValue(value);\n\n continue;\n }\n\n const keyParts = key.split(\"[\");\n const keyName = keyParts[0];\n // `key.includes(\"[\")` puts at least two segments here. Falling back\n // to the whole key rather than asserting keeps the parse total: an\n // undefined segment would make `keyNameParts[0]` undefined too, and\n // this branch writes that straight into the body shape.\n const keyNameParts = (keyParts[1] ?? key).split(\"]\");\n\n /*\n `isArrayKey` is honoured HERE, and used not to be. `filter[tags][]=a`\n sets the flag at the top of the loop, but this branch only wrapped\n when the underlying value was ALREADY an array — which it is for two\n or more occurrences and is not for one. So `filter[tags][]=a` arrived\n as `{filter:{tags:\"a\"}}` while `…=a&…=b` arrived as `{tags:[\"a\",\"b\"]}`:\n the same declared shape, two different types, decided by how many\n times the caller happened to send it.\n\n That single-element case is the one a UI hits first — one filter\n chip selected — and `@warlock.js/web`'s decoder reads it as an array,\n so the page and the server disagreed about the same URL.\n */\n set(\n body,\n keyName + \".\" + keyNameParts[0],\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n if (Array.isArray(value)) {\n set(body, key, value.map(this.parseValue.bind(this)));\n } else if (isArrayKey) {\n if (body[key]) {\n body[key].push(this.parseValue(value));\n } else {\n body[key] = [this.parseValue(value)];\n\n continue;\n }\n } else {\n set(body, key, this.parseValue(value));\n }\n }\n\n // now merge the array of objects into the body\n for (const key in arrayOfObjectValues) {\n body[key] = arrayOfObjectValues[key];\n }\n\n return body;\n } catch (error) {\n console.log(error);\n this.log(error, \"error\");\n }\n }\n\n /**\n * Parse the given data\n */\n protected parseValue(data: any) {\n // data.value appears only in the multipart form data\n // if it json, then just return the data\n if (data?.file) return new UploadedFile(data);\n if (data?.value !== undefined && data?.fields && data?.type) {\n data = data.value;\n }\n\n if (data === \"false\") return false;\n\n if (data === \"true\") return true;\n\n if (data === \"null\") return null;\n\n if (typeof data === \"string\") return data.trim();\n\n return data;\n }\n\n /**\n * Set route handler\n */\n public setRoute(route: Route) {\n this.route = route;\n\n // pass the route to the response object\n this.response.setRoute(route);\n\n return this;\n }\n\n /**\n * Trigger an http event\n */\n public trigger(eventName: RequestEvent, ...args: any[]) {\n return events.trigger(`request.${eventName}`, ...args, this);\n }\n\n /**\n * Listen to the given event\n */\n public on(eventName: RequestEvent, callback: any) {\n return events.subscribe(`request.${eventName}`, callback);\n }\n\n /**\n * Make a log message\n */\n public log(message: any, level: LogLevel = \"info\") {\n if (!config.key(\"http.log\")) return;\n\n log.log({\n module: \"request\",\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.id}`,\n message,\n type: level,\n context: {\n request: this,\n },\n });\n }\n\n /**\n * Get current request path\n */\n public get path() {\n return this.baseRequest.url;\n }\n\n /**\n * {@alias}\n */\n public get url() {\n return this.baseRequest.url;\n }\n\n /**\n * Get full url\n */\n public get fullUrl() {\n return this.protocol + \"://\" + this.hostname + this.path;\n }\n\n /**\n * Drive the middleware chain for the current route, then defer to the\n * controller. Returns the first response value any middleware short-circuits\n * with, or `undefined` to continue into validation + handler.\n *\n * @internal Framework orchestration — do not call from app code. Will move\n * to a dedicated controller dispatcher in a future refactor.\n */\n public async runMiddleware() {\n // measure request time\n // check for middleware first\n const middlewareOutput = await this.executeMiddleware();\n\n if (middlewareOutput !== undefined) {\n // 👇🏻 make sure first its not a response instance\n if (middlewareOutput instanceof Response) return middlewareOutput;\n // 👇🏻 send the response\n return this.response.send(middlewareOutput);\n }\n\n const handler = this.route.handler;\n\n if (!handler.validation) return;\n\n // 👇🏻 check for validation using validateAll helper function — timed as\n // the \"validation\" tracing phase when tracing is\n // enabled; a single boolean check and zero allocation otherwise.\n const tracingEnabled = isTracingEnabled();\n const validationStartedAt = tracingEnabled ? performance.now() : 0;\n\n const validationOutput = await validateAll(handler.validation, this, this.response);\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(this), {\n name: \"validation\",\n durationMs: performance.now() - validationStartedAt,\n });\n }\n\n return validationOutput;\n }\n\n /**\n * Return the request handler attached to the current route.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n public getHandler() {\n return this.route.handler;\n }\n\n /**\n * Get inputs that has been validated only\n * You can also pass an array of inputs to get only the validated inputs\n */\n public validated<Output = RequestValidation>(inputs?: (keyof Output | (string & {}))[]): Output {\n if (this.validatedData) {\n return inputs\n ? only(this.validatedData as Output, inputs as string[])\n : (this.validatedData as Output);\n }\n\n return {} as Output;\n }\n\n /**\n * Get inputs that has been validated except the given inputs\n */\n public validatedExcept(...inputs: string[]): RequestValidation {\n return except(this.validated(), inputs);\n }\n\n /**\n * Set validated data\n */\n public setValidatedData(data: RequestValidation) {\n this.validatedData = data;\n }\n\n /**\n * Top-level entry into the request lifecycle — opens the context store,\n * runs middleware, drives the handler, handles errors.\n *\n * @internal Framework orchestration — do not call from app code. Wired\n * from the Fastify route handler in `router.scan()`.\n */\n public async execute() {\n try {\n // call executingAction event\n\n this.log(\"Executing the request\");\n\n return await createRequestStore(this, this.response);\n } catch (error) {\n this.log(error, \"error\");\n\n throw error;\n }\n }\n\n /**\n * Iterate the collected middlewares in order; return the first short-circuit\n * value or `undefined` when every middleware passes through.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected async executeMiddleware() {\n // collect all middlewares for current route\n const middlewares = this.collectMiddlewares();\n\n // check if there are no middlewares, then return\n if (middlewares.length === 0) return;\n\n this.log(\"About to execute request middlewares\");\n\n // trigger the executingMiddleware event\n this.trigger(\"executingMiddleware\", middlewares, this.route);\n\n const tracingEnabled = isTracingEnabled();\n\n for (const [index, middleware] of middlewares.entries()) {\n this.log(\"Executing middleware \" + colors.yellowBright(middleware.name));\n\n const middlewareStartedAt = tracingEnabled ? performance.now() : 0;\n\n const output = await middleware({\n request: this,\n response: this.response,\n });\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(this), {\n name: \"middleware\",\n durationMs: performance.now() - middlewareStartedAt,\n attrs: { name: middleware.name, index },\n });\n }\n\n this.log(\"Executed middleware \" + colors.yellowBright(middleware.name), \"success\");\n\n if (output !== undefined) {\n this.log(\n colors.yellow(\"request intercepted by middleware \") + colors.cyanBright(middleware.name),\n \"warn\",\n );\n\n this.trigger(\"executedMiddleware\");\n\n this.log(\"Request middlewares executed\", \"success\");\n\n return output;\n }\n }\n\n this.log(\"Request middlewares executed\", \"success\");\n\n // trigger the executedMiddleware event\n this.trigger(\"executedMiddleware\", middlewares, this.route);\n }\n\n /**\n * Gather the middleware list for the current route — today just the\n * route-level array; future extraction may merge group + app-wide layers.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected collectMiddlewares(): Middleware[] {\n const middlewaresList: Middleware[] = [];\n\n // collect route middlewares\n if (this.route.middleware) {\n middlewaresList.push(...this.route.middleware);\n }\n\n return middlewaresList;\n }\n\n /**\n * Get request input value from query string, params or body\n */\n public input(key: string, defaultValue?: any) {\n return get(this.payload.all, key, defaultValue);\n }\n\n /**\n * Get email input value, this will lowercase the value\n */\n public email(key: string = \"email\", defaultValue: string = \"\"): string {\n return this.input(key, defaultValue)?.toLowerCase() || defaultValue;\n }\n\n /**\n * @alias input\n */\n public get(key: string, defaultValue?: any) {\n return this.input(key, defaultValue);\n }\n\n /**\n * Determine if request has input value\n */\n public has(key: string) {\n return get(this.payload.all, key, undefined) !== undefined;\n }\n\n /**\n * Set request input value\n */\n public set(key: string, value: any) {\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Set the given value if the request does not have the input\n */\n public setDefault(key: string, value: any) {\n if (this.has(key)) return this;\n\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Unset request payload keys\n */\n public unset(...keys: string[]) {\n this.payload.all = unset(this.payload.all, keys);\n\n return this;\n }\n\n /**\n * Get request body\n */\n public get body() {\n return this.payload.body;\n }\n\n /**\n * Set request body value\n */\n public setBody(key: string, value: any) {\n set(this.payload.body, key, value);\n\n return this;\n }\n\n /**\n * Get body inputs except files\n */\n public get bodyInputs() {\n const inputs = this.payload.body;\n\n const bodyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (value.file && value.fieldname) continue;\n\n bodyInputs[key] = value;\n }\n\n return bodyInputs;\n }\n\n /**\n * Get request file in UploadedFile instance\n */\n public file(key: string): UploadedFile | undefined {\n const file = this.input(key);\n\n return file;\n }\n\n /**\n * Get uploaded files from the request for the given name\n * If the given name is not present in the request, return an empty array\n */\n public files(name: string): UploadedFile[] {\n return this.input(name) || [];\n }\n\n /**\n * Get request params\n */\n public get params() {\n return this.payload.params;\n }\n\n /**\n * Set request params value\n */\n public setParam(key: string, value: any) {\n set(this.payload.params, key, value);\n\n return this;\n }\n\n /**\n * Get request query\n */\n public get query() {\n return this.payload.query;\n }\n\n /**\n * Set request query value\n */\n public setQuery(key: string, value: any) {\n set(this.payload.query, key, value);\n\n return this;\n }\n\n /**\n * Get all inputs\n */\n public all() {\n return this.payload.all;\n }\n\n /**\n * Get all inputs except params\n */\n public allExceptParams() {\n return {\n ...this.payload.query,\n ...this.payload.body,\n };\n }\n\n /**\n * Get all heavy inputs except params\n */\n public heavyExceptParams() {\n const inputs = this.allExceptParams();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only heavy inputs, the input with a value\n */\n public heavy() {\n const inputs = this.all();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only the given keys from the request data\n */\n public only(keys: string[]) {\n return only(this.all(), keys);\n }\n\n /**\n * Pluck the given keys from the request data\n */\n public pluck(keys: string[]) {\n const data = this.only(keys);\n\n this.unset(...keys);\n\n return data;\n }\n\n /**\n * Get all request inputs except the given keys\n */\n public except(keys: string[]) {\n return except(this.all(), keys);\n }\n\n /**\n * Get boolean input value\n */\n public bool(key: string, defaultValue = false) {\n const value = this.input(key, defaultValue);\n\n if (value === \"true\") {\n return true;\n }\n\n if (value === \"false\") {\n return false;\n }\n\n if (value === 0) {\n return false;\n }\n\n return Boolean(value);\n }\n\n /**\n * Get integer input value\n */\n public int(key: string, defaultValue: number = 0): number | undefined {\n const value = this.input(key, defaultValue);\n\n if (!value && value !== 0) return undefined;\n\n return parseInt(value);\n }\n\n /**\n * Shorthand getter to get id param\n */\n public get idParam() {\n return this.int(\"id\");\n }\n\n /**\n * Get string input value\n */\n public string(key: string, defaultValue: string = \"\"): string {\n const value = this.input(key, defaultValue);\n\n return String(value);\n }\n\n /**\n * Get float input value\n */\n public float(key: string, defaultValue: number = 0): number {\n const value = this.input(key, defaultValue);\n\n return parseFloat(value) || 0;\n }\n\n /**\n * Get number input value\n */\n public number(key: string, defaultValue: number = 0): number {\n const value = Number(this.input(key, defaultValue));\n\n return isNaN(value) ? defaultValue : value;\n }\n\n /**\n * Immediate-peer IP as Fastify reports it — the address that connected to\n * the server socket, with `trustProxy` resolution applied. Use this when\n * you specifically need the peer address (rate-limit-by-direct-connection,\n * health-check origin verification).\n *\n * **For most use cases prefer `request.detectIp()`** — behind any proxy\n * (load balancer, CDN, sidecar) `ip` reports the proxy, not the real client.\n */\n public get ip() {\n return this.baseRequest.ip;\n }\n\n /**\n * Best-effort real client IP — the value everything IP-scoped keys on\n * (ip-filter allowlists, rate-limit buckets, idempotency scoping).\n *\n * `X-Forwarded-For` resolution is **delegated to Fastify**: `baseRequest.ip`\n * is already the client address Fastify's `trustProxy` machinery picked out\n * of the chain, so every shape `http.trustProxy` accepts is honoured here\n * with exactly the semantics Fastify documents:\n *\n * - `false` (default) — no header is trusted; the socket peer address wins.\n * Both forwarding headers are client-settable, so without a trusted edge\n * that rewrites them any client could otherwise forge its own IP.\n * - `true` — the whole chain is trusted; the leftmost hop (original client)\n * wins.\n * - `number` — that many rightmost hops are trusted, so an edge that\n * APPENDS to `X-Forwarded-For` yields the real client rather than whatever\n * the client prepended.\n * - CIDR / IP list (string, comma-separated string, or array) or a custom\n * predicate — the chain is walked right-to-left and stops at the first hop\n * that isn't a trusted proxy.\n *\n * `X-Real-IP` is NOT part of that resolution — Fastify never looks at it,\n * and unlike `X-Forwarded-For` it carries no chain, so there is nothing to\n * validate a proxy allowlist against. It is therefore honoured\n * only under `trustProxy: true` (\"everything upstream is mine\"), where it is\n * no weaker than the trust already granted. Under a bounded `trustProxy`\n * (CIDR / IP list) it is ignored: a trusted-but-passthrough edge that\n * forwards the client's own `X-Real-IP` verbatim would otherwise hand any\n * client a way around the bound.\n *\n * **Prefer this over `request.ip` for any caller behind a proxy** (load\n * balancer, CDN, reverse proxy, k8s ingress).\n */\n public detectIp() {\n // Trusting `X-Real-IP` is only sound when the config trusts the entire\n // upstream chain; bounded shapes get chain-aware resolution instead.\n // Typed as `unknown`: config.get(key, fallback) infers the FALLBACK's type, so\n // the literal `false` narrowed this to `false` and TypeScript called the\n // comparison unreachable. The stored value is genuinely unconstrained at compile\n // time - trustProxy accepts a boolean, a CIDR list or a predicate - so `unknown`\n // is what it actually is, and the === true check is the narrowing.\n const trustProxy: unknown = config.get(\"http.trustProxy\", false);\n\n if (trustProxy === true) {\n const realIp = this.header(\"x-real-ip\");\n\n if (realIp) {\n // `split` always yields a first element, so `?? \"\"` changes nothing —\n // and an empty address is already falsy, so it falls through to the\n // next source exactly as a blank header does. This is the CLIENT IP\n // used for rate limiting and logging; it must never become the string\n // \"undefined\".\n const address = (String(realIp).split(\",\")[0] ?? \"\").trim();\n\n if (address) return address;\n }\n }\n\n // Fastify resolved this against the configured `trustProxy` already:\n // socket peer when trust is off, the correct hop of `X-Forwarded-For`\n // when it is on. Re-parsing the header here would mean a second, weaker\n // trust model that could disagree with `request.ip` and with the plugins\n // (rate limit, proxy) that key on it.\n return this.baseRequest.ip;\n }\n\n /**\n * An alias to detectIp\n */\n public get realIp() {\n return this.detectIp();\n }\n\n /**\n * Get request ips\n */\n public get ips() {\n return this.baseRequest.ips;\n }\n\n /**\n * Get request referer\n */\n public get referer() {\n return this.baseRequest.headers.referer;\n }\n\n /**\n * Get user agent\n */\n public get userAgent() {\n return this.baseRequest.headers[\"user-agent\"];\n }\n\n /**\n * Get request headers\n */\n public get headers(): typeof this.baseRequest.headers {\n return this.baseRequest.headers;\n }\n\n /**\n * Set the given header\n */\n public setHeader(key: HeaderKeys, value: string) {\n this.baseRequest.headers[key.toLowerCase()] = value;\n\n return this;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,UAAb,MAAa,QAAiC;;iBAgCnB,CAAC;gBA4EK,CAAC;eA+CS;WAKJ;iBAejB;YAUR,OAAO,OAAO,EAAE;iBAOX;mBAKE,KAAK,IAAI;;;;;;;;;CAvJ5B,IAAW,qBAAqD;EAC9D,OAAO,KAAK;CACd;CAEA,IAAW,mBAAmB,OAAuC;EACnE,KAAK,sBAAsB;EAC3B,KAAK,OAAO,cAAc;CAC5B;;;;;;;;;;;;;;;;;;CAmBA,IAAW,OAAc;EACvB,IAAI,YAAY,eACd,MAAM,IAAI,sBAAsB;CAIpC;;;;;;;;;;;;;;;;;;;;;;;CA4DA,IAAW,QAAgB;EACzB,IAAI,CAAC,KAAK,QACR,KAAK,SAAS,YAAY,EAAE,CAAC,CAAC,SAAS,QAAQ;EAGjD,OAAO,KAAK;CACd;;;;CA+DA,AAAO,WAAW,SAAyB;EACzC,KAAK,cAAc;EAEnB,KAAK,iBAAiB;EAEtB,KAAK,eAAe;EAEpB,KAAK,aAAa;EAOlB,KAAK,QAAQ,KAAK,KAAK,SAAiB,iBACtC,UAAU,KAAK,cAAc,GAAG,SAAS,YAAY;EAEvD,OAAO;CACT;;;;;;;;;;CAWA,AAAU,mBAAmB;EAC3B,MAAM,kBAAkB,OAAO,IAAI,gBAAgB,KAAK,CAAC;EAEzD,IAAI,gBAAgB,YAAY,OAAO;EAEvC,MAAM,cAAc,gBAAgB,UAAU,eAAc,CAAE,YAAY;EAC1E,MAAM,WAAW,KAAK,YAAY,QAAQ;EAE1C,IAAI,QAAQ,iBAAiB,QAAQ,GAAG;GACtC,KAAK,KAAK;GAEV;EACF;EAEA,IAAI,OAAO,gBAAgB,cAAc,YACvC,KAAK,KAAK,gBAAgB,UAAU;CAExC;;;;;;;;CASA,AAAU,iBAAiB;EACzB,MAAM,SAAS,KAAK,YAAY,QAAQ;EACxC,MAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,OAAO,KAAK;EAExD,KAAK,UAAU,cAAc,aAAa,KAAK,EAAE;CACnD;;;;;;CAOA,OAAiB,iBAAiB,OAAiC;EACjE,OACE,OAAO,UAAU,YACjB,MAAM,SAAS,KACf,MAAM,UAAU,OAChB,iBAAiB,KAAK,KAAK;CAE/B;;;;CAKA,AAAO,UAAU,YAAoB,SAAiB,cAAoB;EACxE,OAAO,UAAU,YAAY,SAAS,YAAY;CACpD;;;;;;;;;CAUA,AAAU,YAAY,WAA4B;EAChD,MAAM,EAAE,mBAAmB,gBAAgB,2BACzC,OAAO,IAAI,gBAAgB,GAC3B,OAAO,IAAI,iBAAiB,CAC9B;EAEA,MAAM,YAAY,OAAO,cAAc,YAAY,UAAU,SAAS,IAAI,YAAY;EAEtF,KAAK,UACH,cAAc,WAAc,gBAAgB,UAAa,YAAY,SAAS,SAAS,KACnF,YACA;EAEN,OAAO,KAAK;CACd;;;;;CAMA,AAAU,gBAAwB;EAChC,MAAM,YAAY;GAChB,KAAK,MAAM;GACX,KAAK,QAAQ;GACb,KAAK,OAAO,QAAQ;EACtB,CAAC,CAAC,MAAM,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;EAE/D,OAAO,KAAK,YAAY,SAAS;CACnC;;;;CAKA,IAAW,SAAiB;EAC1B,IAAI,KAAK,SAAS,OAAO,KAAK;EAE9B,OAAO,KAAK,cAAc;CAC5B;;;;CAKA,IAAW,OAAO,YAAoB;EACpC,KAAK,YAAY,UAAU;CAC7B;;;;CAKA,AAAO,cAAc,YAAoB;EACvC,KAAK,SAAS;EAEd,OAAO;CACT;;;;;;CAOA,AAAO,cAAc,0BAA2C;EAC9D,OAAO,KAAK;CACd;;;;CAKA,IAAW,WAAW;EACpB,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,MAAa,SAAS,YAA2B,gBAA2B;EAC1E,OAAO,MAAM,EAAE,SAAS,YAAY,iBAAiB,KAAK,KAAK,cAAc,IAAI,KAAK,IAAI,CAAC;CAC7F;;;;CAKA,AAAO,OACL,MACA,eAAoB,MACpB;EACA,OAAO,KAAK,YAAY,QAAQ,KAAK,kBAAkB,MAAM;CAC/D;;;;CAKA,IAAW,UAA8C;EACvD,OAAO,KAAK,YAAY,WAAW,CAAC;CACtC;;;;CAKA,AAAO,OAAO,MAAc,cAAkC;EAC5D,MAAM,QAAQ,KAAK,QAAQ,SAAS;EAEpC,IAAI;GACF,OAAO,KAAK,MAAM,KAAK;EACzB,SAAS,OAAO;GACd,OAAO;EACT;CACF;;;;CAKA,AAAO,UAAU,MAAuB;EACtC,OAAO,KAAK,QAAQ,UAAU;CAChC;;;;CAKA,IAAW,SAAS;EAClB,OAAO,KAAK,YAAY,SAAS,QAAQ,UAAU,EAAE;CACvD;;;;CAKA,IAAW,WAAW;EACpB,OAAO,KAAK;CACd;;;;CAKA,IAAW,SAAS;EAClB,OAAO,KAAK,YAAY,QAAQ;CAClC;;;;CAKA,IAAW,eAAe;EACxB,MAAM,SAAS,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,WAAW;EAE7D,IAAI,QAAQ,WAAW,MAAM,GAC3B,OAAO,OAAO,QAAQ,UAAU,EAAE;EAGpC,OAAO;CACT;;;;CAKA,IAAW,qBAA6B;EACtC,MAAM,gBAAgB,KAAK,OAAO,eAAe;EAEjD,IAAI,CAAC,eAAe,OAAO;EAE3B,MAAM,CAAC,MAAM,SAAS,cAAc,MAAM,GAAG;EAE7C,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,SAAS,KAAK,YAAY,CAAC,GAAG,OAAO;EAE5D,OAAO,SAAS;CAClB;;;;;;CAOA,IAAW,cAAkC;EAC3C,MAAM,gBAAgB,KAAK,OAAO,eAAe;EAEjD,IAAI,CAAC,eAAe;EAEpB,MAAM,CAAC,MAAM,SAAS,cAAc,MAAM,GAAG;EAE7C,IAAI,KAAK,YAAY,MAAM,UAAU;EAErC,OAAO;CACT;;;;CAKA,IAAW,gBAAgB;EACzB,OAAO,KAAK,OAAO,eAAe;CACpC;;;;CAKA,IAAW,SAAiB;EAC1B,OAAO,KAAK,YAAY;CAC1B;;;;;;;;;;;CAYA,AAAU,iBAAiB,KAAqB;EAC9C,OAAO,IAAI,QAAQ,SAAS,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,EAAE;CACxE;;;;;;;;;;CAWA,AAAU,cAAc,OAAY,YAAqB,OAA4B;EACnF,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,KAAK;EAEhD,OAAO,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,KAAK;CAClD;CAEA,AAAU,eAAe;EACvB,KAAK,QAAQ,OAAO,KAAK,UAAU,KAAK,YAAY,IAAI;EAExD,KAAK,QAAQ,QAAQ,KAAK,UAAU,KAAK,YAAY,KAAK;EAC1D,KAAK,QAAQ,SAAS,EAAE,GAAI,KAAK,YAAY,UAAU,CAAC,EAAG;EAC3D,KAAK,QAAQ,MAAM;GACjB,GAAG,KAAK,QAAQ;GAChB,GAAG,KAAK,QAAQ;GAChB,GAAG,KAAK,QAAQ;EAClB;CACF;;;;CAKA,AAAU,UAAU,MAAW;EAC7B,IAAI;GACF,IAAI,CAAC,MAAM,OAAO,CAAC;GAEnB,MAAM,OAAY,CAAC;GAEnB,MAAM,sBAA2B,CAAC;GAElC,KAAK,IAAI,OAAO,MAAM;IACpB,MAAM,QAAQ,KAAK;IAEnB,IAAI,aAAa;IAEjB,IAAI,IAAI,SAAS,IAAI,GACnB,aAAa;IAGf,MAAM,MAAM,KAAK,IAAI;IAMrB,IAAI,IAAI,SAAS,GAAG,GAAG;KAErB,IAAI,IAAI,SAAS,IAAI,GAAG;MACtB,MAAM,WAAW,IAAI,MAAM,GAAG;MAE9B,MAAM,UAAU,SAAS;MACzB,MAAM,eAAe,SAAS;MAC9B,MAAM,gBAAgB,SAAS;MAc/B,IACE,YAAY,UACZ,iBAAiB,UACjB,kBAAkB,QAClB;OACA,IACE,MACA,KAAK,iBAAiB,GAAG,GACzB,KAAK,cAAc,OAAO,YAAY,KAAK,WAAW,KAAK,IAAI,CAAC,CAClE;OAEA;MACF;MAEA,MAAM,eAAe,aAAa,MAAM,GAAG;MAE3C,MAAM,QAAQ,OAAO,aAAa,EAAE;MAoBpC,IAAI,OAAO,MAAM,KAAK,GAAG;OACvB,IACE,MACA,KAAK,iBAAiB,GAAG,GACzB,KAAK,cAAc,OAAO,YAAY,KAAK,WAAW,KAAK,IAAI,CAAC,CAClE;OAEA;MACF;MAEA,MAAM,SAAU,oBAAoB,aAAa,CAAC;MAElD,MAAM,QAAS,OAAO,WAAW,CAAC;MAIlC,MAAM,WADgB,cAAc,MAAM,GACb,CAAC,CAAC;MAM/B,IAAI,aAAa,QAAW;MAE5B,MAAM,YAAY,KAAK,WAAW,KAAK;MAEvC;KACF;KAEA,MAAM,WAAW,IAAI,MAAM,GAAG;KAC9B,MAAM,UAAU,SAAS;KAKzB,MAAM,gBAAgB,SAAS,MAAM,IAAG,CAAE,MAAM,GAAG;KAenD,IACE,MACA,UAAU,MAAM,aAAa,IAC7B,KAAK,cAAc,OAAO,YAAY,KAAK,WAAW,KAAK,IAAI,CAAC,CAClE;KAEA;IACF;IAEA,IAAI,MAAM,QAAQ,KAAK,GACrB,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,WAAW,KAAK,IAAI,CAAC,CAAC;SAC/C,IAAI,YACT,IAAI,KAAK,MACP,KAAK,IAAI,CAAC,KAAK,KAAK,WAAW,KAAK,CAAC;SAChC;KACL,KAAK,OAAO,CAAC,KAAK,WAAW,KAAK,CAAC;KAEnC;IACF;SAEA,IAAI,MAAM,KAAK,KAAK,WAAW,KAAK,CAAC;GAEzC;GAGA,KAAK,MAAM,OAAO,qBAChB,KAAK,OAAO,oBAAoB;GAGlC,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,IAAI,KAAK;GACjB,KAAK,IAAI,OAAO,OAAO;EACzB;CACF;;;;CAKA,AAAU,WAAW,MAAW;EAG9B,IAAI,MAAM,MAAM,OAAO,IAAI,aAAa,IAAI;EAC5C,IAAI,MAAM,UAAU,UAAa,MAAM,UAAU,MAAM,MACrD,OAAO,KAAK;EAGd,IAAI,SAAS,SAAS,OAAO;EAE7B,IAAI,SAAS,QAAQ,OAAO;EAE5B,IAAI,SAAS,QAAQ,OAAO;EAE5B,IAAI,OAAO,SAAS,UAAU,OAAO,KAAK,KAAK;EAE/C,OAAO;CACT;;;;CAKA,AAAO,SAAS,OAAc;EAC5B,KAAK,QAAQ;EAGb,KAAK,SAAS,SAAS,KAAK;EAE5B,OAAO;CACT;;;;CAKA,AAAO,QAAQ,WAAyB,GAAG,MAAa;EACtD,OAAO,OAAO,QAAQ,WAAW,aAAa,GAAG,MAAM,IAAI;CAC7D;;;;CAKA,AAAO,GAAG,WAAyB,UAAe;EAChD,OAAO,OAAO,UAAU,WAAW,aAAa,QAAQ;CAC1D;;;;CAKA,AAAO,IAAI,SAAc,QAAkB,QAAQ;EACjD,IAAI,CAAC,OAAO,IAAI,UAAU,GAAG;EAE7B,IAAI,IAAI;GACN,QAAQ;GACR,QAAQ,KAAK,MAAM,SAAS,MAAM,KAAK,MAAM,KAAK,QAAQ,MAAM,EAAE,IAAI,IAAI,KAAK;GAC/E;GACA,MAAM;GACN,SAAS,EACP,SAAS,KACX;EACF,CAAC;CACH;;;;CAKA,IAAW,OAAO;EAChB,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,IAAW,MAAM;EACf,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,IAAW,UAAU;EACnB,OAAO,KAAK,WAAW,QAAQ,KAAK,WAAW,KAAK;CACtD;;;;;;;;;CAUA,MAAa,gBAAgB;EAG3B,MAAM,mBAAmB,MAAM,KAAK,kBAAkB;EAEtD,IAAI,qBAAqB,QAAW;GAElC,IAAI,4BAA4B,UAAU,OAAO;GAEjD,OAAO,KAAK,SAAS,KAAK,gBAAgB;EAC5C;EAEA,MAAM,UAAU,KAAK,MAAM;EAE3B,IAAI,CAAC,QAAQ,YAAY;EAKzB,MAAM,iBAAiB,iBAAiB;EACxC,MAAM,sBAAsB,iBAAiB,YAAY,IAAI,IAAI;EAEjE,MAAM,mBAAmB,MAAM,YAAY,QAAQ,YAAY,MAAM,KAAK,QAAQ;EAElF,IAAI,gBACF,cAAc,oBAAoB,IAAI,GAAG;GACvC,MAAM;GACN,YAAY,YAAY,IAAI,IAAI;EAClC,CAAC;EAGH,OAAO;CACT;;;;;;CAOA,AAAO,aAAa;EAClB,OAAO,KAAK,MAAM;CACpB;;;;;CAMA,AAAO,UAAsC,QAAmD;EAC9F,IAAI,KAAK,eACP,OAAO,SACH,KAAK,KAAK,eAAyB,MAAkB,IACpD,KAAK;EAGZ,OAAO,CAAC;CACV;;;;CAKA,AAAO,gBAAgB,GAAG,QAAqC;EAC7D,OAAO,OAAO,KAAK,UAAU,GAAG,MAAM;CACxC;;;;CAKA,AAAO,iBAAiB,MAAyB;EAC/C,KAAK,gBAAgB;CACvB;;;;;;;;CASA,MAAa,UAAU;EACrB,IAAI;GAGF,KAAK,IAAI,uBAAuB;GAEhC,OAAO,MAAM,mBAAmB,MAAM,KAAK,QAAQ;EACrD,SAAS,OAAO;GACd,KAAK,IAAI,OAAO,OAAO;GAEvB,MAAM;EACR;CACF;;;;;;;CAQA,MAAgB,oBAAoB;EAElC,MAAM,cAAc,KAAK,mBAAmB;EAG5C,IAAI,YAAY,WAAW,GAAG;EAE9B,KAAK,IAAI,sCAAsC;EAG/C,KAAK,QAAQ,uBAAuB,aAAa,KAAK,KAAK;EAE3D,MAAM,iBAAiB,iBAAiB;EAExC,KAAK,MAAM,CAAC,OAAO,eAAe,YAAY,QAAQ,GAAG;GACvD,KAAK,IAAI,0BAA0B,OAAO,aAAa,WAAW,IAAI,CAAC;GAEvE,MAAM,sBAAsB,iBAAiB,YAAY,IAAI,IAAI;GAEjE,MAAM,SAAS,MAAM,WAAW;IAC9B,SAAS;IACT,UAAU,KAAK;GACjB,CAAC;GAED,IAAI,gBACF,cAAc,oBAAoB,IAAI,GAAG;IACvC,MAAM;IACN,YAAY,YAAY,IAAI,IAAI;IAChC,OAAO;KAAE,MAAM,WAAW;KAAM;IAAM;GACxC,CAAC;GAGH,KAAK,IAAI,yBAAyB,OAAO,aAAa,WAAW,IAAI,GAAG,SAAS;GAEjF,IAAI,WAAW,QAAW;IACxB,KAAK,IACH,OAAO,OAAO,oCAAoC,IAAI,OAAO,WAAW,WAAW,IAAI,GACvF,MACF;IAEA,KAAK,QAAQ,oBAAoB;IAEjC,KAAK,IAAI,gCAAgC,SAAS;IAElD,OAAO;GACT;EACF;EAEA,KAAK,IAAI,gCAAgC,SAAS;EAGlD,KAAK,QAAQ,sBAAsB,aAAa,KAAK,KAAK;CAC5D;;;;;;;CAQA,AAAU,qBAAmC;EAC3C,MAAM,kBAAgC,CAAC;EAGvC,IAAI,KAAK,MAAM,YACb,gBAAgB,KAAK,GAAG,KAAK,MAAM,UAAU;EAG/C,OAAO;CACT;;;;CAKA,AAAO,MAAM,KAAa,cAAoB;EAC5C,OAAO,IAAI,KAAK,QAAQ,KAAK,KAAK,YAAY;CAChD;;;;CAKA,AAAO,MAAM,MAAc,SAAS,eAAuB,IAAY;EACrE,OAAO,KAAK,MAAM,KAAK,YAAY,CAAC,EAAE,YAAY,KAAK;CACzD;;;;CAKA,AAAO,IAAI,KAAa,cAAoB;EAC1C,OAAO,KAAK,MAAM,KAAK,YAAY;CACrC;;;;CAKA,AAAO,IAAI,KAAa;EACtB,OAAO,IAAI,KAAK,QAAQ,KAAK,KAAK,MAAS,MAAM;CACnD;;;;CAKA,AAAO,IAAI,KAAa,OAAY;EAClC,IAAI,KAAK,QAAQ,KAAK,KAAK,KAAK;EAEhC,OAAO;CACT;;;;CAKA,AAAO,WAAW,KAAa,OAAY;EACzC,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;EAE1B,IAAI,KAAK,QAAQ,KAAK,KAAK,KAAK;EAEhC,OAAO;CACT;;;;CAKA,AAAO,MAAM,GAAG,MAAgB;EAC9B,KAAK,QAAQ,MAAM,MAAM,KAAK,QAAQ,KAAK,IAAI;EAE/C,OAAO;CACT;;;;CAKA,IAAW,OAAO;EAChB,OAAO,KAAK,QAAQ;CACtB;;;;CAKA,AAAO,QAAQ,KAAa,OAAY;EACtC,IAAI,KAAK,QAAQ,MAAM,KAAK,KAAK;EAEjC,OAAO;CACT;;;;CAKA,IAAW,aAAa;EACtB,MAAM,SAAS,KAAK,QAAQ;EAE5B,MAAM,aAAkB,CAAC;EAEzB,KAAK,MAAM,OAAO,QAAQ;GACxB,MAAM,QAAQ,OAAO;GAErB,IAAI,MAAM,QAAQ,MAAM,WAAW;GAEnC,WAAW,OAAO;EACpB;EAEA,OAAO;CACT;;;;CAKA,AAAO,KAAK,KAAuC;EAGjD,OAFa,KAAK,MAAM,GAEd;CACZ;;;;;CAMA,AAAO,MAAM,MAA8B;EACzC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC;CAC9B;;;;CAKA,IAAW,SAAS;EAClB,OAAO,KAAK,QAAQ;CACtB;;;;CAKA,AAAO,SAAS,KAAa,OAAY;EACvC,IAAI,KAAK,QAAQ,QAAQ,KAAK,KAAK;EAEnC,OAAO;CACT;;;;CAKA,IAAW,QAAQ;EACjB,OAAO,KAAK,QAAQ;CACtB;;;;CAKA,AAAO,SAAS,KAAa,OAAY;EACvC,IAAI,KAAK,QAAQ,OAAO,KAAK,KAAK;EAElC,OAAO;CACT;;;;CAKA,AAAO,MAAM;EACX,OAAO,KAAK,QAAQ;CACtB;;;;CAKA,AAAO,kBAAkB;EACvB,OAAO;GACL,GAAG,KAAK,QAAQ;GAChB,GAAG,KAAK,QAAQ;EAClB;CACF;;;;CAKA,AAAO,oBAAoB;EACzB,MAAM,SAAS,KAAK,gBAAgB;EAEpC,MAAM,cAAmB,CAAC;EAE1B,KAAK,MAAM,OAAO,QAAQ;GACxB,MAAM,QAAQ,OAAO;GAErB,IAAI,QAAQ,KAAK,KAAK,UAAU,MAAM;GAEtC,YAAY,OAAO;EACrB;EAEA,OAAO;CACT;;;;CAKA,AAAO,QAAQ;EACb,MAAM,SAAS,KAAK,IAAI;EAExB,MAAM,cAAmB,CAAC;EAE1B,KAAK,MAAM,OAAO,QAAQ;GACxB,MAAM,QAAQ,OAAO;GAErB,IAAI,QAAQ,KAAK,KAAK,UAAU,MAAM;GAEtC,YAAY,OAAO;EACrB;EAEA,OAAO;CACT;;;;CAKA,AAAO,KAAK,MAAgB;EAC1B,OAAO,KAAK,KAAK,IAAI,GAAG,IAAI;CAC9B;;;;CAKA,AAAO,MAAM,MAAgB;EAC3B,MAAM,OAAO,KAAK,KAAK,IAAI;EAE3B,KAAK,MAAM,GAAG,IAAI;EAElB,OAAO;CACT;;;;CAKA,AAAO,OAAO,MAAgB;EAC5B,OAAO,OAAO,KAAK,IAAI,GAAG,IAAI;CAChC;;;;CAKA,AAAO,KAAK,KAAa,eAAe,OAAO;EAC7C,MAAM,QAAQ,KAAK,MAAM,KAAK,YAAY;EAE1C,IAAI,UAAU,QACZ,OAAO;EAGT,IAAI,UAAU,SACZ,OAAO;EAGT,IAAI,UAAU,GACZ,OAAO;EAGT,OAAO,QAAQ,KAAK;CACtB;;;;CAKA,AAAO,IAAI,KAAa,eAAuB,GAAuB;EACpE,MAAM,QAAQ,KAAK,MAAM,KAAK,YAAY;EAE1C,IAAI,CAAC,SAAS,UAAU,GAAG,OAAO;EAElC,OAAO,SAAS,KAAK;CACvB;;;;CAKA,IAAW,UAAU;EACnB,OAAO,KAAK,IAAI,IAAI;CACtB;;;;CAKA,AAAO,OAAO,KAAa,eAAuB,IAAY;EAC5D,MAAM,QAAQ,KAAK,MAAM,KAAK,YAAY;EAE1C,OAAO,OAAO,KAAK;CACrB;;;;CAKA,AAAO,MAAM,KAAa,eAAuB,GAAW;EAC1D,MAAM,QAAQ,KAAK,MAAM,KAAK,YAAY;EAE1C,OAAO,WAAW,KAAK,KAAK;CAC9B;;;;CAKA,AAAO,OAAO,KAAa,eAAuB,GAAW;EAC3D,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,YAAY,CAAC;EAElD,OAAO,MAAM,KAAK,IAAI,eAAe;CACvC;;;;;;;;;;CAWA,IAAW,KAAK;EACd,OAAO,KAAK,YAAY;CAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCA,AAAO,WAAW;EAUhB,IAF4B,OAAO,IAAI,mBAAmB,KAE7C,MAAM,MAAM;GACvB,MAAM,SAAS,KAAK,OAAO,WAAW;GAEtC,IAAI,QAAQ;IAMV,MAAM,WAAW,OAAO,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,GAAE,CAAE,KAAK;IAE1D,IAAI,SAAS,OAAO;GACtB;EACF;EAOA,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,IAAW,SAAS;EAClB,OAAO,KAAK,SAAS;CACvB;;;;CAKA,IAAW,MAAM;EACf,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,IAAW,UAAU;EACnB,OAAO,KAAK,YAAY,QAAQ;CAClC;;;;CAKA,IAAW,YAAY;EACrB,OAAO,KAAK,YAAY,QAAQ;CAClC;;;;CAKA,IAAW,UAA2C;EACpD,OAAO,KAAK,YAAY;CAC1B;;;;CAKA,AAAO,UAAU,KAAiB,OAAe;EAC/C,KAAK,YAAY,QAAQ,IAAI,YAAY,KAAK;EAE9C,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { trans, transFrom } from \"@mongez/localization\";\nimport { Random, except, get, only, rtrim, set, unset } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport type { LogLevel } from \"@warlock.js/logger\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseValidator, v } from \"@warlock.js/seal\";\nimport type { FastifyRequest } from \"fastify\";\nimport { randomBytes } from \"node:crypto\";\nimport { type IncomingHttpHeaders } from \"node:http2\";\nimport { Application } from \"../application/application\";\nimport { config } from \"../config/config-getter\";\nimport { LOCALE_COOKIE_NAME, resolveLocaleConfiguration } from \"../config/locale-configuration\";\nimport type { Middleware, Route } from \"../router\";\nimport { validateAll } from \"../validation/validateAll\";\nimport { CookieJarUnavailableError, RequestUserMovedError } from \"./errors\";\nimport { createRequestStore } from \"./middleware/inject-request-context\";\nimport { Response } from \"./response\";\nimport { buildTracingContext, deriveTraceId, dispatchPhase, isTracingEnabled } from \"./tracing\";\nimport type { DecodedAccessToken, RequestEvent, RequestLocals } from \"./types\";\nimport { UploadedFile } from \"./uploaded-file\";\n\ntype StandardHeaders = {\n // copy every declared property from http.IncomingHttpHeaders\n // but remove index signatures\n [\n K in keyof IncomingHttpHeaders as string extends K ? never : number extends K ? never : K\n ]: IncomingHttpHeaders[K];\n};\n\ntype HeaderKeys = keyof StandardHeaders;\n\nexport class Request<RequestValidation = any> {\n /**\n * Underlying Fastify request — a public escape hatch to capabilities the\n * framework's high-level helpers don't yet cover.\n *\n * **Prefer framework methods first**: `request.input()`, `request.header()`,\n * `request.body`, `request.query`, `request.params`, `request.file()`,\n * `request.locals.user` (set by `@warlock.js/auth`), `request.detectIp()`,\n * etc. They handle locale, parsing,\n * trust-proxy, and validation pipeline integration correctly.\n *\n * **Reach for `baseRequest` only** when the framework genuinely lacks a\n * helper for what you need — and when you do, file an issue so we can add\n * it. The escape hatch is the release valve that lets consumers move\n * faster than the framework, but every long-term reach here is a missing\n * helper waiting to be added.\n */\n public baseRequest!: FastifyRequest;\n\n /**\n * Response Object\n */\n public response!: Response;\n\n /**\n * Route Object\n */\n public route!: Route;\n\n /**\n * Parsed Request Payload\n */\n protected payload: any = {};\n\n /**\n * Backing field for `decodedAccessToken` — see the accessor below.\n */\n private _decodedAccessToken?: DecodedAccessToken;\n\n /**\n * Decoded access token payload (set by auth middleware).\n *\n * A prototype accessor, not a plain field, so assignment can mark the\n * request `authDerived` (see the setter below and `RequestLocals` in\n * `types.ts`) without every call site remembering to do so itself.\n */\n public get decodedAccessToken(): DecodedAccessToken | undefined {\n return this._decodedAccessToken;\n }\n\n public set decodedAccessToken(value: DecodedAccessToken | undefined) {\n this._decodedAccessToken = value;\n this.locals.authDerived = true;\n }\n\n /**\n * REMOVED in 5.12.0 — the authenticated user now lives at\n * `request.locals.user`, a key `@warlock.js/auth` declares via module\n * augmentation on `RequestLocals` and writes from its middleware after a\n * successful token resolution. `RequestUser` moved out of core to\n * `@warlock.js/auth` alongside it.\n *\n * This getter is a development-time diagnostic only, kept for one release\n * so a call site that still reads `request.user` fails loudly at runtime\n * instead of silently reading `undefined`. It is typed `never` so it\n * cannot reintroduce an auth-shaped type into core, and it throws\n * unconditionally outside production so the failure is impossible to miss\n * in local dev — see `RequestUserMovedError`.\n *\n * There is no setter: nothing in core or downstream packages should ever\n * assign to `request.user` again.\n */\n public get user(): never {\n if (Application.isDevelopment) {\n throw new RequestUserMovedError();\n }\n\n return undefined as never;\n }\n\n /**\n * Private, server-only, per-request data bag.\n *\n * Distinct from the input payload (`body` / `query` / `params` / `all()`):\n * a write here never surfaces in `request.all()`, `request.validated()`, or\n * `request.input()`. That is the trap `request.set()` sets for private data\n * — it writes into the payload `all` bag, so anything stored there leaks\n * into every input accessor and, from there, into the client-facing\n * payload. `locals` is the correct home for private per-request app data\n * (a resolved session, a fetched-once model) that must never be mistaken\n * for client input.\n *\n * Augmentable via module augmentation, in the module that OWNS the key:\n *\n * ```typescript\n * declare module \"@warlock.js/core\" {\n * interface RequestLocals {\n * session?: { token: string };\n * }\n * }\n * ```\n *\n * A plain class-field initializer is sufficient for \"fresh per request\":\n * `router.ts:925` constructs `new Request()` for every incoming request —\n * `Request` instances are not pooled or reused across requests — so this\n * initializer runs exactly once per request and no value can leak in from\n * a prior one.\n */\n public locals: RequestLocals = {};\n\n /**\n * Backing field for the lazily-generated CSP nonce. Left `undefined` until\n * the first `request.nonce` read; see the `nonce` getter below.\n */\n protected _nonce?: string;\n\n /**\n * Per-request Content-Security-Policy nonce — a fresh, unguessable value\n * the web layer hands to `<Scripts nonce={...} />` (the inline payload\n * script) and to the `Content-Security-Policy` header, so a strict\n * `script-src 'nonce-...'` allows only the script this request actually\n * rendered.\n *\n * Generated LAZILY on first access, not eagerly in `setRequest()`: most\n * requests (API routes, anything that isn't rendering HTML) never read it,\n * and spending a `randomBytes` call on every single request for a value\n * most of them discard is wasted entropy draw + CPU. Once generated it is\n * cached in `_nonce`, so every subsequent read within the SAME request\n * returns the identical value — required, since the header and the inline\n * `<script>` tag must agree on one nonce. `_nonce` is a plain field on a\n * per-request `Request` instance (see `locals` above — `router.ts:925`,\n * no pooling), so the cache can never leak into the next request; a fresh\n * `Request` means a fresh, unset `_nonce`.\n *\n * 16 random bytes, base64-encoded — the size the CSP Level 3 spec's own\n * examples use, and far more entropy than an attacker could feasibly guess\n * to defeat the policy.\n */\n public get nonce(): string {\n if (!this._nonce) {\n this._nonce = randomBytes(16).toString(\"base64\");\n }\n\n return this._nonce;\n }\n\n /**\n * Current request instance\n */\n public static current: Request;\n\n /**\n * Translation method\n * Type of it is the same as the type of trans function\n */\n public trans: ReturnType<typeof trans> = trans;\n\n /**\n * Alias to trans method\n */\n public t: ReturnType<typeof trans> = trans;\n\n /*\n * v5 removed the `[key: string]: any` index signature (eed20184). Attaching\n * arbitrary properties compiled silently and hid real bugs behind `any`.\n * The sanctioned extension paths are:\n * - `request.locals` (augment `RequestLocals` via module augmentation) for\n * per-request attached data, e.g. models fetched in validation middleware.\n * - `requestMemo(key, fn)` for per-request memoized computation.\n * - Module augmentation of the `Request` class itself for new typed members.\n */\n\n /**\n * Locale code\n */\n protected _locale = \"\";\n\n /**\n * Validated data\n */\n protected validatedData?: RequestValidation;\n\n /**\n * Request id\n */\n public id = Random.string(32);\n\n /**\n * Trace id. The inbound `traceparent` header's trace id\n * when valid, otherwise `id`. Resolved once in `setRequest`, alongside\n * `id` itself — see `resolveTraceId`.\n */\n public traceId = \"\";\n\n /**\n * Start Time\n */\n public startTime = Date.now();\n\n /**\n * End Time\n */\n public endTime?: undefined | number;\n\n /**\n * Set request handler\n */\n public setRequest(request: FastifyRequest) {\n this.baseRequest = request;\n\n this.resolveRequestId();\n\n this.resolveTraceId();\n\n this.parsePayload();\n\n // Resolve the locale at CALL time, never at bind time. `setRequest` runs\n // before routing, so a locale set later (path locale, `setLocaleCode`, the\n // web layer's C3 derivation) must steer translations too — the old\n // `transFrom.bind(null, localeCode)` snapshot made `request.locale` and\n // `request.trans()` silently disagree for the rest of the request.\n this.trans = this.t = (keyword: string, placeholders?: any) =>\n transFrom(this.getLocaleCode(), keyword, placeholders);\n\n return this;\n }\n\n /**\n * Inherit `X-Request-Id` from the incoming request, fall back to a custom\n * generator, then to the field-init default (`Random.string(32)`).\n *\n * Inherited values are validated (length cap + printable-ASCII) to prevent\n * log-injection from a malicious client. Disable the whole behavior by\n * setting `http.requestId.enabled = false` — in which case the field-init\n * default is used regardless of any incoming header.\n */\n protected resolveRequestId() {\n const requestIdConfig = config.key(\"http.requestId\") || {};\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = (requestIdConfig.header || \"x-request-id\").toLowerCase();\n const incoming = this.baseRequest.headers[headerName];\n\n if (Request.isValidRequestId(incoming)) {\n this.id = incoming;\n\n return;\n }\n\n if (typeof requestIdConfig.generator === \"function\") {\n this.id = requestIdConfig.generator();\n }\n }\n\n /**\n * Derive `traceId`: the inbound\n * `traceparent` header's trace id when it is a valid W3C traceparent,\n * otherwise `id`. Always runs — unlike request-id inheritance this has no\n * `enabled: false` escape hatch, since `traceId` is only ever read when\n * tracing hooks are enabled (see `./tracing`).\n */\n protected resolveTraceId() {\n const header = this.baseRequest.headers.traceparent;\n const traceparent = Array.isArray(header) ? header[0] : header;\n\n this.traceId = deriveTraceId(traceparent, this.id);\n }\n\n /**\n * Validate a candidate request-id value. Accepts non-empty printable ASCII\n * up to 128 characters — tight enough to reject newline / control-character\n * log-injection, loose enough to accept UUIDs, ULIDs, snowflakes, etc.\n */\n protected static isValidRequestId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= 128 &&\n /^[\\x21-\\x7e]+$/.test(value)\n );\n }\n\n /**\n * Translate from the given locale code\n */\n public transFrom(localeCode: string, keyword: string, placeholders?: any) {\n return transFrom(localeCode, keyword, placeholders);\n }\n\n /**\n * Cache one supported locale without coercing request-controlled input.\n *\n * The default is the answer for a client that asked for NOTHING. A client\n * that did ask is only overridden when its value fails a declared\n * `app.localeCodes` allow-list; with no list declared there is nothing to\n * fail, so the requested locale passes through unchanged.\n */\n protected cacheLocale(candidate: unknown): string {\n const { defaultLocaleCode, localeCodes } = resolveLocaleConfiguration(\n config.key(\"app.localeCode\"),\n config.key(\"app.localeCodes\"),\n );\n\n const requested = typeof candidate === \"string\" && candidate.length > 0 ? candidate : undefined;\n\n this._locale =\n requested !== undefined && (localeCodes === undefined || localeCodes.includes(requested))\n ? requested\n : defaultLocaleCode;\n\n return this._locale;\n }\n\n /**\n * Resolve the first present Mode B source. Unsupported values fail closed to\n * the configured default instead of widening the application's locale set.\n */\n protected resolveLocale(): string {\n const candidate = [\n this.query[\"locale\"],\n this.cookies[LOCALE_COOKIE_NAME],\n this.header(\"locale\"),\n ].find((value) => typeof value === \"string\" && value.length > 0);\n\n return this.cacheLocale(candidate);\n }\n\n /**\n * Get current locale code\n */\n public get locale(): string {\n if (this._locale) return this._locale;\n\n return this.resolveLocale();\n }\n\n /**\n * Set locale code\n */\n public set locale(localeCode: string) {\n this.cacheLocale(localeCode);\n }\n\n /**\n * Set locale code\n */\n public setLocaleCode(localeCode: string) {\n this.locale = localeCode;\n\n return this;\n }\n\n /**\n * @deprecated Use `request.locale`. This alias is removed after one version.\n * The legacy default argument is accepted for source compatibility but the\n * resolved default is owned exclusively by app configuration.\n */\n public getLocaleCode(_legacyDefaultLocaleCode?: string): string {\n return this.locale;\n }\n\n /**\n * Get http protocol\n */\n public get protocol() {\n return this.baseRequest.protocol;\n }\n\n /**\n * Validate the given validation schema\n */\n public async validate(validation: BaseValidator, selectedInputs?: string[]) {\n return await v.validate(validation, selectedInputs ? this.only(selectedInputs) : this.all());\n }\n\n /**\n * Get value of the given header\n */\n public header<TCustomHeader extends string = HeaderKeys>(\n name: TCustomHeader | HeaderKeys,\n defaultValue: any = null,\n ) {\n return this.baseRequest.headers[name.toLocaleLowerCase()] ?? defaultValue;\n }\n\n /**\n * Get all cookies from the current request\n */\n public get cookies(): Record<string, string | undefined> {\n return this.baseRequest.cookies || {};\n }\n\n /**\n * Assert the cookie jar exists before a by-name read. `get cookies()` stays\n * lenient (returns `{}`) for the framework's own opportunistic reads, but a\n * deliberate by-name read from application code must fail loudly when\n * `@fastify/cookie` was never registered, rather than being indistinguishable\n * from \"the caller sent no such cookie\".\n */\n private assertCookieJarAvailable(name: string): void {\n if (this.baseRequest.cookies === undefined) {\n throw new CookieJarUnavailableError(name);\n }\n }\n\n /**\n * Get a particular cookie value or fallback to default\n */\n public cookie(name: string, defaultValue?: any): string | any {\n this.assertCookieJarAvailable(name);\n\n const value = this.cookies[name] ?? defaultValue;\n\n try {\n return JSON.parse(value);\n } catch (error) {\n return value;\n }\n }\n\n /**\n * Determine if the request has the specified cookie\n */\n public hasCookie(name: string): boolean {\n this.assertCookieJarAvailable(name);\n\n return this.cookies[name] !== undefined;\n }\n\n /**\n * Get the current request domain\n */\n public get domain() {\n return this.baseRequest.hostname.replace(/^www\\./, \"\");\n }\n\n /**\n * Get hostname\n */\n public get hostname() {\n return this.domain;\n }\n\n /**\n * Get request origin\n */\n public get origin() {\n return this.baseRequest.headers.origin as string;\n }\n\n /**\n * Get the domain of the origin\n */\n public get originDomain() {\n const domain = this.origin ? new URL(this.origin).hostname : null;\n\n if (domain?.startsWith(\"www.\")) {\n return domain.replace(/^www\\./, \"\");\n }\n\n return domain;\n }\n\n /**\n * Get authorization header value\n */\n public get authorizationValue(): string {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return \"\";\n\n const [type, value] = authorization.split(\" \");\n\n if (![\"bearer\", \"key\"].includes(type.toLowerCase())) return \"\";\n\n return value || \"\";\n }\n\n /**\n * Get access token from Authorization header\n *\n * If the Authorization header does not start with `Bearer` value then return null\n */\n public get accessToken(): string | undefined {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return;\n\n const [type, value] = authorization.split(\" \");\n\n if (type.toLowerCase() !== \"bearer\") return;\n\n return value;\n }\n\n /**\n * Get the authorization header\n */\n public get authorization() {\n return this.header(\"authorization\");\n }\n\n /**\n * Get current request method\n */\n public get method(): string {\n return this.baseRequest.method;\n }\n\n /**\n * Parse the payload and merge it from the request body, params and query string\n */\n /**\n * Turn a bracket-notation key into the dotted path `set()` expects.\n *\n * `a[b][c]` -> `a.b.c`. Used only for NON-numeric nesting; numeric indices\n * keep the array-of-objects path in {@link parseBody}, which builds real\n * arrays rather than objects with numeric keys.\n */\n protected bracketKeyToPath(key: string): string {\n return key.replace(/\\]\\[/g, \".\").replace(/\\[/g, \".\").replace(/\\]/g, \"\");\n }\n\n /**\n * Apply the `key[]` array marker to a parsed value.\n *\n * The subtlety this exists to remove: a key declared `[]` should ALWAYS be an\n * array, but the underlying query/body parser only hands us one when the\n * caller sent the key more than once. Deciding the TYPE from the number of\n * occurrences means one selected filter is a string and two are an array —\n * a shape that changes under the user's hands.\n */\n protected arrayValueFor(value: any, isArrayKey: boolean, parse: (value: any) => any) {\n if (Array.isArray(value)) return value.map(parse);\n\n return isArrayKey ? [parse(value)] : parse(value);\n }\n\n protected parsePayload() {\n this.payload.body = this.parseBody(this.baseRequest.body);\n\n this.payload.query = this.parseBody(this.baseRequest.query);\n this.payload.params = { ...(this.baseRequest.params || {}) };\n this.payload.all = {\n ...this.payload.body,\n ...this.payload.query,\n ...this.payload.params,\n };\n }\n\n /**\n * Parse body payload\n */\n protected parseBody(data: any) {\n try {\n if (!data) return {};\n\n const body: any = {};\n\n const arrayOfObjectValues: any = {};\n\n for (let key in data) {\n const value = data[key];\n\n let isArrayKey = false;\n\n if (key.endsWith(\"[]\")) {\n isArrayKey = true;\n }\n\n key = rtrim(key, \"[]\");\n\n // check if the key is has a square brackets, then convert it into object\n // i.e user[email] => user: {email: \"value\"}\n // also check if its an array of objects\n\n if (key.includes(\"[\")) {\n // check if its an array of objects\n if (key.includes(\"][\")) {\n const keyParts = key.split(\"[\");\n\n const keyName = keyParts[0];\n const firstBracket = keyParts[1];\n const secondBracket = keyParts[2];\n\n /*\n `key.includes(\"][\")` guarantees all three segments — but that is a\n property of the string test above, not of these reads, so each is\n `string | undefined`.\n\n When the shape is not what this branch assumes, fall through to\n the generic bracket path rather than skipping the key. That is the\n same choice the NaN branch below makes, and for the same reason\n spelled out there: the failure this code has already been bitten\n by is answering with a shape the caller did not send. Dropping the\n pair silently would be that bug again, in a new place.\n */\n if (\n keyName === undefined ||\n firstBracket === undefined ||\n secondBracket === undefined\n ) {\n set(\n body,\n this.bracketKeyToPath(key),\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n const keyNameParts = firstBracket.split(\"]\");\n\n const index = Number(keyNameParts[0]);\n\n /*\n A NON-NUMERIC first segment is not an array index — it is a deeper\n nested object. `a[b][c]=x` reaches this branch because it contains\n \"][\", but `Number(\"b\")` is NaN, and the code below used to write to\n `[NaN]`: that sets a \"NaN\" PROPERTY on an array whose length stays\n 0, so the request arrived as `{a: []}` and the value was gone. No\n error, no warning — the caller simply never got `x`.\n\n Deciding between refusing (4xx) and interpreting: a doubly-nested\n key is unambiguous and is exactly what every bracket-notation\n parser means by it, so we interpret. Refusing would reject a URL\n shape that is standard elsewhere and that we ourselves already\n honour one level shallower, five lines below. What was definitely\n wrong was answering with a shape the caller did not send.\n\n Numeric indices keep the array-of-objects path below unchanged —\n `items[0][name]` is still an array.\n */\n if (Number.isNaN(index)) {\n set(\n body,\n this.bracketKeyToPath(key),\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n const bucket = (arrayOfObjectValues[keyName] ??= []);\n\n const entry = (bucket[index] ??= {});\n\n // now get the key after the index\n const keyNameParts2 = secondBracket.split(\"]\");\n const keyName2 = keyNameParts2[0];\n\n // `split` always yields a first element, so this holds — but an\n // undefined key here would write a property literally named\n // \"undefined\" onto the entry, which is the same silent-wrong-shape\n // outcome the comment above describes.\n if (keyName2 === undefined) continue;\n\n entry[keyName2] = this.parseValue(value);\n\n continue;\n }\n\n const keyParts = key.split(\"[\");\n const keyName = keyParts[0];\n // `key.includes(\"[\")` puts at least two segments here. Falling back\n // to the whole key rather than asserting keeps the parse total: an\n // undefined segment would make `keyNameParts[0]` undefined too, and\n // this branch writes that straight into the body shape.\n const keyNameParts = (keyParts[1] ?? key).split(\"]\");\n\n /*\n `isArrayKey` is honoured HERE, and used not to be. `filter[tags][]=a`\n sets the flag at the top of the loop, but this branch only wrapped\n when the underlying value was ALREADY an array — which it is for two\n or more occurrences and is not for one. So `filter[tags][]=a` arrived\n as `{filter:{tags:\"a\"}}` while `…=a&…=b` arrived as `{tags:[\"a\",\"b\"]}`:\n the same declared shape, two different types, decided by how many\n times the caller happened to send it.\n\n That single-element case is the one a UI hits first — one filter\n chip selected — and `@warlock.js/web`'s decoder reads it as an array,\n so the page and the server disagreed about the same URL.\n */\n set(\n body,\n keyName + \".\" + keyNameParts[0],\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n if (Array.isArray(value)) {\n set(body, key, value.map(this.parseValue.bind(this)));\n } else if (isArrayKey) {\n if (body[key]) {\n body[key].push(this.parseValue(value));\n } else {\n body[key] = [this.parseValue(value)];\n\n continue;\n }\n } else {\n set(body, key, this.parseValue(value));\n }\n }\n\n // now merge the array of objects into the body\n for (const key in arrayOfObjectValues) {\n body[key] = arrayOfObjectValues[key];\n }\n\n return body;\n } catch (error) {\n console.log(error);\n this.log(error, \"error\");\n }\n }\n\n /**\n * Parse the given data\n */\n protected parseValue(data: any) {\n // data.value appears only in the multipart form data\n // if it json, then just return the data\n if (data?.file) return new UploadedFile(data);\n if (data?.value !== undefined && data?.fields && data?.type) {\n data = data.value;\n }\n\n if (data === \"false\") return false;\n\n if (data === \"true\") return true;\n\n if (data === \"null\") return null;\n\n if (typeof data === \"string\") return data.trim();\n\n return data;\n }\n\n /**\n * Set route handler\n */\n public setRoute(route: Route) {\n this.route = route;\n\n // pass the route to the response object\n this.response.setRoute(route);\n\n return this;\n }\n\n /**\n * Trigger an http event\n */\n public trigger(eventName: RequestEvent, ...args: any[]) {\n return events.trigger(`request.${eventName}`, ...args, this);\n }\n\n /**\n * Listen to the given event\n */\n public on(eventName: RequestEvent, callback: any) {\n return events.subscribe(`request.${eventName}`, callback);\n }\n\n /**\n * Make a log message\n */\n public log(message: any, level: LogLevel = \"info\") {\n if (!config.key(\"http.log\")) return;\n\n log.log({\n module: \"request\",\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.id}`,\n message,\n type: level,\n context: {\n request: this,\n },\n });\n }\n\n /**\n * Get current request path\n */\n public get path() {\n return this.baseRequest.url;\n }\n\n /**\n * {@alias}\n */\n public get url() {\n return this.baseRequest.url;\n }\n\n /**\n * Get full url\n */\n public get fullUrl() {\n return this.protocol + \"://\" + this.hostname + this.path;\n }\n\n /**\n * Drive the middleware chain for the current route, then defer to the\n * controller. Returns the first response value any middleware short-circuits\n * with, or `undefined` to continue into validation + handler.\n *\n * @internal Framework orchestration — do not call from app code. Will move\n * to a dedicated controller dispatcher in a future refactor.\n */\n public async runMiddleware() {\n // measure request time\n // check for middleware first\n const middlewareOutput = await this.executeMiddleware();\n\n if (middlewareOutput !== undefined) {\n // 👇🏻 make sure first its not a response instance\n if (middlewareOutput instanceof Response) return middlewareOutput;\n // 👇🏻 send the response\n return this.response.send(middlewareOutput);\n }\n\n const handler = this.route.handler;\n\n if (!handler.validation) return;\n\n // 👇🏻 check for validation using validateAll helper function — timed as\n // the \"validation\" tracing phase when tracing is\n // enabled; a single boolean check and zero allocation otherwise.\n const tracingEnabled = isTracingEnabled();\n const validationStartedAt = tracingEnabled ? performance.now() : 0;\n\n const validationOutput = await validateAll(handler.validation, this, this.response);\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(this), {\n name: \"validation\",\n durationMs: performance.now() - validationStartedAt,\n });\n }\n\n return validationOutput;\n }\n\n /**\n * Return the request handler attached to the current route.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n public getHandler() {\n return this.route.handler;\n }\n\n /**\n * Get inputs that has been validated only\n * You can also pass an array of inputs to get only the validated inputs\n */\n public validated<Output = RequestValidation>(inputs?: (keyof Output | (string & {}))[]): Output {\n if (this.validatedData) {\n return inputs\n ? only(this.validatedData as Output, inputs as string[])\n : (this.validatedData as Output);\n }\n\n return {} as Output;\n }\n\n /**\n * Get inputs that has been validated except the given inputs\n */\n public validatedExcept(...inputs: string[]): RequestValidation {\n return except(this.validated(), inputs);\n }\n\n /**\n * Set validated data\n */\n public setValidatedData(data: RequestValidation) {\n this.validatedData = data;\n }\n\n /**\n * Top-level entry into the request lifecycle — opens the context store,\n * runs middleware, drives the handler, handles errors.\n *\n * @internal Framework orchestration — do not call from app code. Wired\n * from the Fastify route handler in `router.scan()`.\n */\n public async execute() {\n try {\n // call executingAction event\n\n this.log(\"Executing the request\");\n\n return await createRequestStore(this, this.response);\n } catch (error) {\n this.log(error, \"error\");\n\n throw error;\n }\n }\n\n /**\n * Iterate the collected middlewares in order; return the first short-circuit\n * value or `undefined` when every middleware passes through.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected async executeMiddleware() {\n // collect all middlewares for current route\n const middlewares = this.collectMiddlewares();\n\n // check if there are no middlewares, then return\n if (middlewares.length === 0) return;\n\n this.log(\"About to execute request middlewares\");\n\n // trigger the executingMiddleware event\n this.trigger(\"executingMiddleware\", middlewares, this.route);\n\n const tracingEnabled = isTracingEnabled();\n\n for (const [index, middleware] of middlewares.entries()) {\n this.log(\"Executing middleware \" + colors.yellowBright(middleware.name));\n\n const middlewareStartedAt = tracingEnabled ? performance.now() : 0;\n\n const output = await middleware({\n request: this,\n response: this.response,\n });\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(this), {\n name: \"middleware\",\n durationMs: performance.now() - middlewareStartedAt,\n attrs: { name: middleware.name, index },\n });\n }\n\n this.log(\"Executed middleware \" + colors.yellowBright(middleware.name), \"success\");\n\n if (output !== undefined) {\n this.log(\n colors.yellow(\"request intercepted by middleware \") + colors.cyanBright(middleware.name),\n \"warn\",\n );\n\n this.trigger(\"executedMiddleware\");\n\n this.log(\"Request middlewares executed\", \"success\");\n\n return output;\n }\n }\n\n this.log(\"Request middlewares executed\", \"success\");\n\n // trigger the executedMiddleware event\n this.trigger(\"executedMiddleware\", middlewares, this.route);\n }\n\n /**\n * Gather the middleware list for the current route — today just the\n * route-level array; future extraction may merge group + app-wide layers.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected collectMiddlewares(): Middleware[] {\n const middlewaresList: Middleware[] = [];\n\n // collect route middlewares\n if (this.route.middleware) {\n middlewaresList.push(...this.route.middleware);\n }\n\n return middlewaresList;\n }\n\n /**\n * Get request input value from query string, params or body\n */\n public input(key: string, defaultValue?: any) {\n return get(this.payload.all, key, defaultValue);\n }\n\n /**\n * Get email input value, this will lowercase the value\n */\n public email(key: string = \"email\", defaultValue: string = \"\"): string {\n return this.input(key, defaultValue)?.toLowerCase() || defaultValue;\n }\n\n /**\n * @alias input\n */\n public get(key: string, defaultValue?: any) {\n return this.input(key, defaultValue);\n }\n\n /**\n * Determine if request has input value\n */\n public has(key: string) {\n return get(this.payload.all, key, undefined) !== undefined;\n }\n\n /**\n * Set request input value\n */\n public set(key: string, value: any) {\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Set the given value if the request does not have the input\n */\n public setDefault(key: string, value: any) {\n if (this.has(key)) return this;\n\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Unset request payload keys\n */\n public unset(...keys: string[]) {\n this.payload.all = unset(this.payload.all, keys);\n\n return this;\n }\n\n /**\n * Get request body\n */\n public get body() {\n return this.payload.body;\n }\n\n /**\n * Set request body value\n */\n public setBody(key: string, value: any) {\n set(this.payload.body, key, value);\n\n return this;\n }\n\n /**\n * Get body inputs except files\n */\n public get bodyInputs() {\n const inputs = this.payload.body;\n\n const bodyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (value.file && value.fieldname) continue;\n\n bodyInputs[key] = value;\n }\n\n return bodyInputs;\n }\n\n /**\n * Get request file in UploadedFile instance\n */\n public file(key: string): UploadedFile | undefined {\n const file = this.input(key);\n\n return file;\n }\n\n /**\n * Get uploaded files from the request for the given name\n * If the given name is not present in the request, return an empty array\n */\n public files(name: string): UploadedFile[] {\n return this.input(name) || [];\n }\n\n /**\n * Get request params\n */\n public get params() {\n return this.payload.params;\n }\n\n /**\n * Set request params value\n */\n public setParam(key: string, value: any) {\n set(this.payload.params, key, value);\n\n return this;\n }\n\n /**\n * Get request query\n */\n public get query() {\n return this.payload.query;\n }\n\n /**\n * Set request query value\n */\n public setQuery(key: string, value: any) {\n set(this.payload.query, key, value);\n\n return this;\n }\n\n /**\n * Get all inputs\n */\n public all() {\n return this.payload.all;\n }\n\n /**\n * Get all inputs except params\n */\n public allExceptParams() {\n return {\n ...this.payload.query,\n ...this.payload.body,\n };\n }\n\n /**\n * Get all heavy inputs except params\n */\n public heavyExceptParams() {\n const inputs = this.allExceptParams();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only heavy inputs, the input with a value\n */\n public heavy() {\n const inputs = this.all();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only the given keys from the request data\n */\n public only(keys: string[]) {\n return only(this.all(), keys);\n }\n\n /**\n * Pluck the given keys from the request data\n */\n public pluck(keys: string[]) {\n const data = this.only(keys);\n\n this.unset(...keys);\n\n return data;\n }\n\n /**\n * Get all request inputs except the given keys\n */\n public except(keys: string[]) {\n return except(this.all(), keys);\n }\n\n /**\n * Get boolean input value\n */\n public bool(key: string, defaultValue = false) {\n const value = this.input(key, defaultValue);\n\n if (value === \"true\") {\n return true;\n }\n\n if (value === \"false\") {\n return false;\n }\n\n if (value === 0) {\n return false;\n }\n\n return Boolean(value);\n }\n\n /**\n * Get integer input value\n */\n public int(key: string, defaultValue: number = 0): number | undefined {\n const value = this.input(key, defaultValue);\n\n if (!value && value !== 0) return undefined;\n\n return parseInt(value);\n }\n\n /**\n * Shorthand getter to get id param\n */\n public get idParam() {\n return this.int(\"id\");\n }\n\n /**\n * Get string input value\n */\n public string(key: string, defaultValue: string = \"\"): string {\n const value = this.input(key, defaultValue);\n\n return String(value);\n }\n\n /**\n * Get float input value\n */\n public float(key: string, defaultValue: number = 0): number {\n const value = this.input(key, defaultValue);\n\n return parseFloat(value) || 0;\n }\n\n /**\n * Get number input value\n */\n public number(key: string, defaultValue: number = 0): number {\n const value = Number(this.input(key, defaultValue));\n\n return isNaN(value) ? defaultValue : value;\n }\n\n /**\n * Immediate-peer IP as Fastify reports it — the address that connected to\n * the server socket, with `trustProxy` resolution applied. Use this when\n * you specifically need the peer address (rate-limit-by-direct-connection,\n * health-check origin verification).\n *\n * **For most use cases prefer `request.detectIp()`** — behind any proxy\n * (load balancer, CDN, sidecar) `ip` reports the proxy, not the real client.\n */\n public get ip() {\n return this.baseRequest.ip;\n }\n\n /**\n * Best-effort real client IP — the value everything IP-scoped keys on\n * (ip-filter allowlists, rate-limit buckets, idempotency scoping).\n *\n * `X-Forwarded-For` resolution is **delegated to Fastify**: `baseRequest.ip`\n * is already the client address Fastify's `trustProxy` machinery picked out\n * of the chain, so every shape `http.trustProxy` accepts is honoured here\n * with exactly the semantics Fastify documents:\n *\n * - `false` (default) — no header is trusted; the socket peer address wins.\n * Both forwarding headers are client-settable, so without a trusted edge\n * that rewrites them any client could otherwise forge its own IP.\n * - `true` — the whole chain is trusted; the leftmost hop (original client)\n * wins.\n * - `number` — that many rightmost hops are trusted, so an edge that\n * APPENDS to `X-Forwarded-For` yields the real client rather than whatever\n * the client prepended.\n * - CIDR / IP list (string, comma-separated string, or array) or a custom\n * predicate — the chain is walked right-to-left and stops at the first hop\n * that isn't a trusted proxy.\n *\n * `X-Real-IP` is NOT part of that resolution — Fastify never looks at it,\n * and unlike `X-Forwarded-For` it carries no chain, so there is nothing to\n * validate a proxy allowlist against. It is therefore honoured\n * only under `trustProxy: true` (\"everything upstream is mine\"), where it is\n * no weaker than the trust already granted. Under a bounded `trustProxy`\n * (CIDR / IP list) it is ignored: a trusted-but-passthrough edge that\n * forwards the client's own `X-Real-IP` verbatim would otherwise hand any\n * client a way around the bound.\n *\n * **Prefer this over `request.ip` for any caller behind a proxy** (load\n * balancer, CDN, reverse proxy, k8s ingress).\n */\n public detectIp() {\n // Trusting `X-Real-IP` is only sound when the config trusts the entire\n // upstream chain; bounded shapes get chain-aware resolution instead.\n // Typed as `unknown`: config.get(key, fallback) infers the FALLBACK's type, so\n // the literal `false` narrowed this to `false` and TypeScript called the\n // comparison unreachable. The stored value is genuinely unconstrained at compile\n // time - trustProxy accepts a boolean, a CIDR list or a predicate - so `unknown`\n // is what it actually is, and the === true check is the narrowing.\n const trustProxy: unknown = config.get(\"http.trustProxy\", false);\n\n if (trustProxy === true) {\n const realIp = this.header(\"x-real-ip\");\n\n if (realIp) {\n // `split` always yields a first element, so `?? \"\"` changes nothing —\n // and an empty address is already falsy, so it falls through to the\n // next source exactly as a blank header does. This is the CLIENT IP\n // used for rate limiting and logging; it must never become the string\n // \"undefined\".\n const address = (String(realIp).split(\",\")[0] ?? \"\").trim();\n\n if (address) return address;\n }\n }\n\n // Fastify resolved this against the configured `trustProxy` already:\n // socket peer when trust is off, the correct hop of `X-Forwarded-For`\n // when it is on. Re-parsing the header here would mean a second, weaker\n // trust model that could disagree with `request.ip` and with the plugins\n // (rate limit, proxy) that key on it.\n return this.baseRequest.ip;\n }\n\n /**\n * An alias to detectIp\n */\n public get realIp() {\n return this.detectIp();\n }\n\n /**\n * Get request ips\n */\n public get ips() {\n return this.baseRequest.ips;\n }\n\n /**\n * Get request referer\n */\n public get referer() {\n return this.baseRequest.headers.referer;\n }\n\n /**\n * Get user agent\n */\n public get userAgent() {\n return this.baseRequest.headers[\"user-agent\"];\n }\n\n /**\n * Get request headers\n */\n public get headers(): typeof this.baseRequest.headers {\n return this.baseRequest.headers;\n }\n\n /**\n * Set the given header\n */\n public setHeader(key: HeaderKeys, value: string) {\n this.baseRequest.headers[key.toLowerCase()] = value;\n\n return this;\n }\n}\n"],"mappings":"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"}
package/esm/index.d.mts CHANGED
@@ -32,7 +32,7 @@ import { logResponse, wrapResponseInDataKey } from "./http/events.mjs";
32
32
  import { HealthCheck, HealthStatus, health } from "./http/health.mjs";
33
33
  import { RequestController } from "./http/request-controller.mjs";
34
34
  import { UPLOADS_DEFAULTS, uploadsConfig } from "./http/uploads-config.mjs";
35
- import { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./http/errors/errors.mjs";
35
+ import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./http/errors/errors.mjs";
36
36
  import { CacheMiddlewareOptions } from "./http/middleware/cache-response-middleware.mjs";
37
37
  import { ConcurrencyLimitOptions } from "./http/middleware/concurrency-limit.middleware.mjs";
38
38
  import { IdempotencyOptions } from "./http/middleware/idempotency.middleware.mjs";
@@ -67,6 +67,7 @@ import { Environment, RuntimeStrategy, environment, setEnvironment } from "./uti
67
67
  import { Application, BootContext, BootListener, BootValidator, ShutdownListener } from "./application/application.mjs";
68
68
  import { BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BootSignal, BootSignalType, isBootSignal, sendBootSignal } from "./application/boot-signal.mjs";
69
69
  import { AppConfigurations } from "./application/application-config-types.mjs";
70
+ import { getPublicUrl } from "./application/public-url.mjs";
70
71
  import { BenchmarkSnapshots } from "./benchmark/benchmark-snapshots.mjs";
71
72
  import { BenchmarkProfiler } from "./benchmark/profiler.mjs";
72
73
  import { BenchmarkChannel, BenchmarkConfigurations, BenchmarkErrorResult, BenchmarkOptions, BenchmarkProfilerOptions, BenchmarkResult, BenchmarkSnapshotsOptions, BenchmarkStats, BenchmarkSuccessResult } from "./benchmark/types.mjs";
@@ -166,7 +167,7 @@ import { WarlockConfigManager, isUnknownTsExtensionError, warlockConfigManager }
166
167
  import { env } from "@mongez/dotenv";
167
168
  import { colors } from "@mongez/copper";
168
169
  export * from "@mongez/localization";
169
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieOptions, CspConfig, CursorPaginationOptions, CursorPaginationResult, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, HttpTracingConfig, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, InvalidCspDirectiveError, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipeableReactStream, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUserMovedError, type ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, StreamReactResponseOptions, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TracingContext, TracingContextSource, TracingHooks, TracingPhaseInfo, TracingRequestEndInfo, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
170
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieJarUnavailableError, CookieOptions, CspConfig, CursorPaginationOptions, CursorPaginationResult, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, HttpTracingConfig, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, InvalidCspDirectiveError, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipeableReactStream, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUserMovedError, type ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, StreamReactResponseOptions, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TracingContext, TracingContextSource, TracingHooks, TracingPhaseInfo, TracingRequestEndInfo, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
170
171
  import "./config/types.mjs";
171
172
  import "./http/request.mjs";
172
173
  import "./http/types.mjs";
package/esm/index.mjs CHANGED
@@ -21,7 +21,7 @@ import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./utils/urls.
21
21
  import { isNewerVersion } from "./utils/version-compare.mjs";
22
22
  import "./utils/index.mjs";
23
23
  import { DEFAULT_CSP_DIRECTIVES, InvalidCspDirectiveError, applyCspHeader, buildCspHeaderValue, mergeCspDirectives, resolveCspConfig, serializeCspDirectives, validateCspConfigAtBoot, validateCspDirectives } from "./http/csp.mjs";
24
- import { BadRequestError, ConflictError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./http/errors/errors.mjs";
24
+ import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./http/errors/errors.mjs";
25
25
  import { deriveTraceId, parseTraceparentTraceId } from "./http/tracing/trace-id.mjs";
26
26
  import { buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, resetTracingConfigForTests, resolveTracingConfig } from "./http/tracing/tracing-dispatcher.mjs";
27
27
  import { createRequestStore, t } from "./http/middleware/inject-request-context.mjs";
@@ -66,6 +66,7 @@ import { RequestLog } from "./http/database/RequestLog.mjs";
66
66
  import { HttpErrorCodes } from "./http/error-codes.mjs";
67
67
  import { logResponse, wrapResponseInDataKey } from "./http/events.mjs";
68
68
  import { app } from "./application/app.mjs";
69
+ import { getPublicUrl } from "./application/public-url.mjs";
69
70
  import "./application/index.mjs";
70
71
  import { health } from "./http/health.mjs";
71
72
  import { RequestController } from "./http/request-controller.mjs";
@@ -163,4 +164,4 @@ import { colors } from "@mongez/copper";
163
164
 
164
165
  export * from "@mongez/localization"
165
166
 
166
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, InvalidCspDirectiveError, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, RequestUserMovedError, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageConnector, StorageError, StorageFile, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
167
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, CookieJarUnavailableError, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, InvalidCspDirectiveError, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, RequestUserMovedError, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageConnector, StorageError, StorageFile, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
package/llms-full.txt CHANGED
@@ -964,7 +964,7 @@ Don't call `setBaseUrl` per request — it's process-global and races every othe
964
964
 
965
965
  ---
966
966
  name: configure-app
967
- description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
967
+ description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app''s public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
968
968
  ---
969
969
 
970
970
  # Warlock — configure the app
@@ -1069,7 +1069,7 @@ same nonce reaches the page's `<script>` tags.
1069
1069
  | CLI commands (registered via `warlock <cmd>`) | `warlock.config.ts > cli` |
1070
1070
  | HTTP server tuning per env (port, host, retry) | `warlock.config.ts > server` |
1071
1071
  | HTTP runtime (CORS, cookies, rate limits, upload size) | `src/config/http.ts` |
1072
- | App identity (name, baseUrl, timezone, locales) | `src/config/app.ts` |
1072
+ | App identity (name, baseUrl, publicUrl, timezone, locales) | `src/config/app.ts` |
1073
1073
  | Subsystem configs (auth, mail, storage, cache, ai, …) | `src/config/<name>.ts` |
1074
1074
 
1075
1075
  Heuristic: if the setting changes how the framework **boots, builds, or scaffolds**, it goes in `warlock.config.ts`. If it changes how a **subsystem behaves at runtime**, it goes in `src/config/`.
@@ -1203,6 +1203,33 @@ const config = {
1203
1203
 
1204
1204
  Avoid scattering `process.env.NODE_ENV === "production"` checks — they don't get the same default-handling.
1205
1205
 
1206
+ ### `app.publicUrl` — the app's public origin (5.15.0)
1207
+
1208
+ ```ts title="src/config/app.ts"
1209
+ import type { AppConfigurations } from "@warlock.js/core";
1210
+
1211
+ const appConfigurations: AppConfigurations = {
1212
+ appName: "My App",
1213
+ publicUrl: "https://example.com",
1214
+ };
1215
+
1216
+ export default appConfigurations;
1217
+ ```
1218
+
1219
+ The one absolute-URL source every consumer that needs one — the sitemap
1220
+ route, canonical links, OG tags, absolute URLs in mail — reads instead of
1221
+ keeping its own copy. Optional in general (most apps have no consumer that
1222
+ needs it yet); read it with `getPublicUrl()`, which returns `app.publicUrl`,
1223
+ falling back to the `PUBLIC_APP_URL` env var, or `undefined` when neither is
1224
+ set.
1225
+
1226
+ `getPublicUrl()` never throws — it is a consumer's job to fail loudly when it
1227
+ requires the value. `@warlock.js/sitemap` is the first such consumer: with
1228
+ `sitemap.enabled: true` and no `app.publicUrl`/`PUBLIC_APP_URL` set, boot
1229
+ refuses to start (`MissingPublicUrlError`, naming both) rather than falling
1230
+ back to a request-derived host — a sitemap served from the wrong host is
1231
+ worse than one that never boots.
1232
+
1206
1233
  ## Common patterns
1207
1234
 
1208
1235
  ### Adding a new subsystem
@@ -4378,7 +4405,7 @@ await Mail.to(user.email)
4378
4405
 
4379
4406
  ---
4380
4407
  name: send-response
4381
- description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4408
+ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4382
4409
  ---
4383
4410
 
4384
4411
  # Warlock — send a response
@@ -4580,6 +4607,20 @@ Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: tru
4580
4607
 
4581
4608
  Precedence, lowest to highest: **framework defaults → `http.cookies.options` → the per-call `options` argument.** Set an app-wide policy in config, override per cookie when a specific one genuinely needs different treatment.
4582
4609
 
4610
+ ### Reading cookies back — `request.cookie()` / `request.hasCookie()` throw when the jar is unavailable (5.15.0)
4611
+
4612
+ `request.cookie(name)` and `request.hasCookie(name)` are a deliberate
4613
+ by-name assertion — "this cookie should be readable here" — so when
4614
+ `@fastify/cookie` is not registered on the Fastify instance, both now
4615
+ **throw `CookieJarUnavailableError`** naming the missing cookie, instead of
4616
+ silently returning `undefined` / `false`. Register the plugin (see core's
4617
+ `http/plugins.ts`) before reading cookies by name.
4618
+
4619
+ `request.cookies` (the plain getter, no by-name assertion) is unchanged and
4620
+ stays lenient — it returns `{}` when the jar is unavailable, because the
4621
+ framework's own opportunistic reads (e.g. locale resolution) must not throw
4622
+ on a request that simply has no jar.
4623
+
4583
4624
  These are the flags whose absence never fails a test and is fatal in production: the app works perfectly and is simply insecure. Opting out is now explicit and visible in review.
4584
4625
 
4585
4626
  These mutate the response in place; chain or call before the final `return response.<helper>()`.
@@ -6656,6 +6697,12 @@ All three lookups go through `@mongez/localization`'s `trans()` under the hood,
6656
6697
 
6657
6698
  When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic groups/keys and placeholders are not inferred.
6658
6699
 
6700
+ `useTrans()` now works correctly across hydration (5.15.0): the hydration
6701
+ payload ships a `translations` key with the active locale's keywords, and
6702
+ `@warlock.js/web` registers them into this same lookup table before the
6703
+ client hydrates — see `@warlock.js/web/write-the-root/SKILL.md`'s "`useTrans()`
6704
+ survives hydration" section for the failure this fixed.
6705
+
6659
6706
  ### Locale on a specific lookup
6660
6707
 
6661
6708
  ```ts
package/llms.txt CHANGED
@@ -10,7 +10,7 @@
10
10
  - [benchmark-code](@warlock.js/core/benchmark-code/SKILL.md): Wrap a function with `measure(name, fn, options?)` to time it and classify the latency — onComplete/onError/onFinish hooks, `latencyRange` thresholds, `BenchmarkProfiler` for percentiles, `BenchmarkSnapshots` for raw captures. Triggers: `measure`, `BenchmarkProfiler`, `BenchmarkSnapshots`, `BenchmarkChannel`, `ConsoleChannel`, `latencyRange`, `shouldBenchmarkError`; "time this operation", "profile a slow service", "emit p50/p95/p99 metrics", "classify latency against thresholds"; typical import `import { measure, BenchmarkProfiler } from "@warlock.js/core"`. Skip: retry composition — `@warlock.js/core/retry-operation/SKILL.md`; benchmark config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `prom-client`, `pino`, `perf_hooks`, `console.time`.
11
11
  - [build-restful](@warlock.js/core/build-restful/SKILL.md): Generate standard CRUD endpoints — via `router.route(path).list().show().create().update().destroy()` chain or the `Restful` base class. Pick the chain by default; reach for `Restful` when you want repository-bound defaults. Triggers: `router.route`, `Restful`, `router.restfulResource`, `RouteResource`, `.crud`, `.nest`, `beforeCreate`, `onCreate`; "build a CRUD API", "register list/show/create/update/destroy", "repository-bound default handlers", "override a single REST action"; typical import `import { router, Restful } from "@warlock.js/core"`. Skip: wider router surface — `@warlock.js/core/register-route/SKILL.md`; per-action controllers — `@warlock.js/core/create-controller/SKILL.md`; wire mapping — `@warlock.js/core/define-resource/SKILL.md`; competing pattern: hand-rolled controllers, `@nestjs/swagger` decorator-driven CRUD.
12
12
  - [build-url](@warlock.js/core/build-url/SKILL.md): HTTP URL helpers — `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, anchored at `app.baseUrl`. Use to render `src` / `href` / API URLs in resources and responses. `setBaseUrl` is wired by the HTTP connector from `config.get("app.baseUrl")`. Triggers: `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, `setBaseUrl`, `BASE_URL`; "render an avatar src URL", "absolute download link", "embed asset URL in email", "URL helpers vs path helpers"; typical import `import { url, publicUrl, uploadsUrl } from "@warlock.js/core"`. Skip: filesystem paths — `@warlock.js/core/resolve-path/SKILL.md`; signed CDN URLs — `@warlock.js/core/store-file/SKILL.md`; resource output — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: hand-rolled `${baseUrl}/...` template strings.
13
- - [configure-app](@warlock.js/core/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.
13
+ - [configure-app](@warlock.js/core/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app's public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.
14
14
  - [create-controller](@warlock.js/core/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.input`, `controller.validation`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — `@warlock.js/core/send-response/SKILL.md`; schema authoring — `@warlock.js/core/validate-input/SKILL.md`; URL wiring — `@warlock.js/core/register-route/SKILL.md`; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
15
15
  - [create-module](@warlock.js/core/create-module/SKILL.md): Scaffold a new feature module under `src/app/<name>/` via `warlock generate.module` and the follow-up generators for controllers, models, repositories, resources, and validation schemas. Triggers: `warlock generate.module`, `generate.controller`, `generate.service`, `generate.model`, `generate.repository`, `generate.resource`, `generate.migration`, `--minimal`, `gen.m`; "scaffold a new module", "create CRUD bootstrap", "add a controller to a module", "generate a model"; typical CLI `npx warlock generate.module <name>`. Skip: framework-wide layout rules — `@warlock.js/core/warlock-conventions/SKILL.md`; routes file shape — `@warlock.js/core/register-route/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing tooling: `@nestjs/cli`, `hygen`, hand-rolled folder layouts.
16
16
  - [define-resource](@warlock.js/core/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — `@warlock.js/core/use-localization/SKILL.md`; URL casting — `@warlock.js/core/build-url/SKILL.md`; controller side — `@warlock.js/core/create-controller/SKILL.md`; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
@@ -26,7 +26,7 @@
26
26
  - [retry-operation](@warlock.js/core/retry-operation/SKILL.md): Wrap a flaky operation with `retry(fn, options)` — now provided by `@mongez/reinforcements` (not `@warlock.js/core`). `attempts` total tries, `delay` + `backoff` (linear/exponential/fn), `maxDelay`, `jitter`, `shouldRetry` to bail on permanent errors, `signal` to cancel, plus `retryable()` to pre-bind options. Triggers: `retry`, `retryable`, `RetryOptions`, `attempts`, `backoff`, `jitter`, `maxDelay`, `shouldRetry`, `signal`; "retry a flaky API call", "handle transient errors", "exponential backoff with jitter", "wrap an external request"; typical import `import { retry } from "@mongez/reinforcements"`. Skip: timing the retried op — `@warlock.js/core/benchmark-code/SKILL.md`; use-case-level `retry` option — `@warlock.js/core/write-use-case/SKILL.md`; competing libs `p-retry`, `async-retry`, `cockatiel`.
27
27
  - [run-app](@warlock.js/core/run-app/SKILL.md): Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`, `EsbuildBinaryMissingError`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — `@warlock.js/core/write-cli-command/SKILL.md`; config shape — `@warlock.js/core/configure-app/SKILL.md`; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.
28
28
  - [send-mail](@warlock.js/core/send-mail/SKILL.md): Send transactional email — `Mail` fluent builder, `sendMail()` direct call, React Email components. Test mode auto-captures into an in-memory mailbox; dev mode logs. Triggers: `Mail.to`, `sendMail`, `setMailMode`, `mailEvents`, `assertMailSent`, `getTestMailbox`, `wasMailSentTo`, `closeAllMailers`; "send a transactional email", "build a React Email template", "configure SMTP or SES", "assert an email was sent in tests"; typical import `import { Mail, sendMail } from "@warlock.js/core"`. Skip: per-config wiring — `@warlock.js/core/configure-app/SKILL.md`; layered service patterns — `@warlock.js/core/warlock-conventions/SKILL.md`; competing libs `nodemailer` direct, `@sendgrid/mail`, `resend`, `mailgun.js`.
29
- - [send-response](@warlock.js/core/send-response/SKILL.md): Send HTTP responses via @warlock.js/core's Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
29
+ - [send-response](@warlock.js/core/send-response/SKILL.md): Send HTTP responses via @warlock.js/core's Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
30
30
  - [store-file](@warlock.js/core/store-file/SKILL.md): Read/write/delete files via the `storage` singleton — disks, drivers (local/S3/R2/DO Spaces), `storage.use(name)`, `StorageFile` handles, presigned URLs. Triggers: `storage.put`, `storage.get`, `storage.use`, `StorageFile`, `storageConfigurations`, `getPresignedUrl`, `getPresignedUploadUrl`; "save an uploaded file", "switch between local and S3", "generate a presigned URL", "read file metadata"; typical import `import { storage } from "@warlock.js/core"`. Skip: multipart parsing + image chain — `@warlock.js/core/upload-file/SKILL.md`; image transforms — `@warlock.js/core/process-image/SKILL.md`; storage config shape — `@warlock.js/core/configure-app/SKILL.md`; competing libs `@aws-sdk/client-s3`, `multer`, `formidable`.
31
31
  - [test-http](@warlock.js/core/test-http/SKILL.md): Integration tests against a real HTTP server — `startHttpTestServer()` boots one shared server in globalSetup, then `testGet` / `testPost` / `expectJson` make typed requests against it. Triggers: `startHttpTestServer`, `startHttpTestServer({ port })`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`, `PortInUseError`, `assertPortIsAvailable`, `isPortAvailable`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape", "test server port already in use", "EADDRINUSE while running tests", "run tests while the dev server is up"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core/tests"`. Skip: pure unit tests — `@warlock.js/core/test-service/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing libs `supertest`, `light-my-request`, `nock`.
32
32
  - [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps the framework with its own DB/cache connections so you can call your code directly, and `teardownTest()` closes it. Triggers: `setupTest`, `teardownTest`, `src/test-setup.ts`, `tests.connectors`, `tests.setupTimeout`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest, teardownTest } from "@warlock.js/core/tests"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.
package/package.json CHANGED
@@ -25,12 +25,12 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^2.1.4",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.14.0",
29
- "@warlock.js/cascade": "5.14.0",
30
- "@warlock.js/context": "5.14.0",
31
- "@warlock.js/logger": "5.14.0",
32
- "@warlock.js/seal": "5.14.0",
33
- "@warlock.js/fs": "5.14.0",
28
+ "@warlock.js/cache": "5.15.0",
29
+ "@warlock.js/cascade": "5.15.0",
30
+ "@warlock.js/context": "5.15.0",
31
+ "@warlock.js/logger": "5.15.0",
32
+ "@warlock.js/seal": "5.15.0",
33
+ "@warlock.js/fs": "5.15.0",
34
34
  "chokidar": "^5.0.0",
35
35
  "dayjs": "^1.11.19",
36
36
  "es-module-lexer": "^2.0.0",
@@ -56,10 +56,10 @@
56
56
  "react": "^19.2.3",
57
57
  "react-dom": "^19.2.3",
58
58
  "@react-email/render": "^2.0.5",
59
- "@warlock.js/herald": "5.14.0",
60
- "@warlock.js/ai": "5.14.0",
61
- "@warlock.js/access": "5.14.0",
62
- "@warlock.js/notifications": "5.14.0"
59
+ "@warlock.js/herald": "5.15.0",
60
+ "@warlock.js/ai": "5.15.0",
61
+ "@warlock.js/access": "5.15.0",
62
+ "@warlock.js/notifications": "5.15.0"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "sharp": {
@@ -122,7 +122,7 @@
122
122
  ],
123
123
  "author": "hassanzohdy",
124
124
  "license": "MIT",
125
- "version": "5.14.0",
125
+ "version": "5.15.0",
126
126
  "type": "module",
127
127
  "main": "./esm/index.mjs",
128
128
  "module": "./esm/index.mjs",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: configure-app
3
- description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, and the `config()` getter for runtime reads. Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
3
+ description: 'Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app''s public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — `@warlock.js/cache/cache-basics/SKILL.md`; mail config — `@warlock.js/core/send-mail/SKILL.md`; storage config — `@warlock.js/core/store-file/SKILL.md`; sitemap boot-time refusal on a missing origin — `@warlock.js/sitemap/sitemap-overview/SKILL.md`; competing libs `dotenv` direct, `convict`, `node-config`.'
4
4
  ---
5
5
 
6
6
  # Warlock — configure the app
@@ -105,7 +105,7 @@ same nonce reaches the page's `<script>` tags.
105
105
  | CLI commands (registered via `warlock <cmd>`) | `warlock.config.ts > cli` |
106
106
  | HTTP server tuning per env (port, host, retry) | `warlock.config.ts > server` |
107
107
  | HTTP runtime (CORS, cookies, rate limits, upload size) | `src/config/http.ts` |
108
- | App identity (name, baseUrl, timezone, locales) | `src/config/app.ts` |
108
+ | App identity (name, baseUrl, publicUrl, timezone, locales) | `src/config/app.ts` |
109
109
  | Subsystem configs (auth, mail, storage, cache, ai, …) | `src/config/<name>.ts` |
110
110
 
111
111
  Heuristic: if the setting changes how the framework **boots, builds, or scaffolds**, it goes in `warlock.config.ts`. If it changes how a **subsystem behaves at runtime**, it goes in `src/config/`.
@@ -239,6 +239,33 @@ const config = {
239
239
 
240
240
  Avoid scattering `process.env.NODE_ENV === "production"` checks — they don't get the same default-handling.
241
241
 
242
+ ### `app.publicUrl` — the app's public origin (5.15.0)
243
+
244
+ ```ts title="src/config/app.ts"
245
+ import type { AppConfigurations } from "@warlock.js/core";
246
+
247
+ const appConfigurations: AppConfigurations = {
248
+ appName: "My App",
249
+ publicUrl: "https://example.com",
250
+ };
251
+
252
+ export default appConfigurations;
253
+ ```
254
+
255
+ The one absolute-URL source every consumer that needs one — the sitemap
256
+ route, canonical links, OG tags, absolute URLs in mail — reads instead of
257
+ keeping its own copy. Optional in general (most apps have no consumer that
258
+ needs it yet); read it with `getPublicUrl()`, which returns `app.publicUrl`,
259
+ falling back to the `PUBLIC_APP_URL` env var, or `undefined` when neither is
260
+ set.
261
+
262
+ `getPublicUrl()` never throws — it is a consumer's job to fail loudly when it
263
+ requires the value. `@warlock.js/sitemap` is the first such consumer: with
264
+ `sitemap.enabled: true` and no `app.publicUrl`/`PUBLIC_APP_URL` set, boot
265
+ refuses to start (`MissingPublicUrlError`, naming both) rather than falling
266
+ back to a request-derived host — a sitemap served from the wrong host is
267
+ worse than one that never boots.
268
+
242
269
  ## Common patterns
243
270
 
244
271
  ### Adding a new subsystem
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: send-response
3
- description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
3
+ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.'
4
4
  ---
5
5
 
6
6
  # Warlock — send a response
@@ -202,6 +202,20 @@ Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: tru
202
202
 
203
203
  Precedence, lowest to highest: **framework defaults → `http.cookies.options` → the per-call `options` argument.** Set an app-wide policy in config, override per cookie when a specific one genuinely needs different treatment.
204
204
 
205
+ ### Reading cookies back — `request.cookie()` / `request.hasCookie()` throw when the jar is unavailable (5.15.0)
206
+
207
+ `request.cookie(name)` and `request.hasCookie(name)` are a deliberate
208
+ by-name assertion — "this cookie should be readable here" — so when
209
+ `@fastify/cookie` is not registered on the Fastify instance, both now
210
+ **throw `CookieJarUnavailableError`** naming the missing cookie, instead of
211
+ silently returning `undefined` / `false`. Register the plugin (see core's
212
+ `http/plugins.ts`) before reading cookies by name.
213
+
214
+ `request.cookies` (the plain getter, no by-name assertion) is unchanged and
215
+ stays lenient — it returns `{}` when the jar is unavailable, because the
216
+ framework's own opportunistic reads (e.g. locale resolution) must not throw
217
+ on a request that simply has no jar.
218
+
205
219
  These are the flags whose absence never fails a test and is fatal in production: the app works perfectly and is simply insecure. Opting out is now explicit and visible in review.
206
220
 
207
221
  These mutate the response in place; chain or call before the final `return response.<helper>()`.
@@ -100,6 +100,12 @@ All three lookups go through `@mongez/localization`'s `trans()` under the hood,
100
100
 
101
101
  When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic groups/keys and placeholders are not inferred.
102
102
 
103
+ `useTrans()` now works correctly across hydration (5.15.0): the hydration
104
+ payload ships a `translations` key with the active locale's keywords, and
105
+ `@warlock.js/web` registers them into this same lookup table before the
106
+ client hydrates — see `@warlock.js/web/write-the-root/SKILL.md`'s "`useTrans()`
107
+ survives hydration" section for the failure this fixed.
108
+
103
109
  ### Locale on a specific lookup
104
110
 
105
111
  ```ts