@warlock.js/core 5.35.0 → 5.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/esm/dev-server/module-loader.mjs +1 -1
  3. package/esm/dev-server/parse-imports.mjs +1 -1
  4. package/esm/dev-server/type-generator.mjs +1 -1
  5. package/esm/e2e/cdp.d.mts.map +1 -1
  6. package/esm/e2e/cdp.mjs +9 -2
  7. package/esm/e2e/cdp.mjs.map +1 -1
  8. package/esm/e2e/page.d.mts +9 -1
  9. package/esm/e2e/page.d.mts.map +1 -1
  10. package/esm/e2e/page.mjs +45 -7
  11. package/esm/e2e/page.mjs.map +1 -1
  12. package/esm/e2e/types.d.mts +18 -0
  13. package/esm/e2e/types.d.mts.map +1 -1
  14. package/esm/e2e/types.mjs.map +1 -1
  15. package/esm/errors/error-codes.d.mts +24 -0
  16. package/esm/errors/error-codes.d.mts.map +1 -1
  17. package/esm/errors/error-codes.mjs +24 -0
  18. package/esm/errors/error-codes.mjs.map +1 -1
  19. package/esm/generations/features/test.feature.mjs +7 -7
  20. package/esm/generations/features/test.feature.mjs.map +1 -1
  21. package/esm/generations/features/web.feature.mjs +3 -3
  22. package/esm/generations/features/web.feature.mjs.map +1 -1
  23. package/esm/index.d.mts +4 -2
  24. package/esm/index.mjs +3 -1
  25. package/esm/localization/index.mjs +3 -1
  26. package/esm/localization/translation-usage-graph.d.mts +49 -0
  27. package/esm/localization/translation-usage-graph.d.mts.map +1 -0
  28. package/esm/localization/translation-usage-graph.mjs +128 -0
  29. package/esm/localization/translation-usage-graph.mjs.map +1 -0
  30. package/esm/localization/translation-usage.d.mts +85 -0
  31. package/esm/localization/translation-usage.d.mts.map +1 -0
  32. package/esm/localization/translation-usage.mjs +307 -0
  33. package/esm/localization/translation-usage.mjs.map +1 -0
  34. package/esm/production/build-budgets.d.mts +18 -0
  35. package/esm/production/build-budgets.d.mts.map +1 -1
  36. package/esm/production/build-budgets.mjs.map +1 -1
  37. package/esm/production/route-registration-snapshot.mjs +5 -3
  38. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  39. package/esm/router/router.d.mts +2 -0
  40. package/esm/router/router.d.mts.map +1 -1
  41. package/esm/router/router.mjs +4 -1
  42. package/esm/router/router.mjs.map +1 -1
  43. package/esm/router/types.d.mts +19 -0
  44. package/esm/router/types.d.mts.map +1 -1
  45. package/esm/tooling/check/api-route-references.d.mts +16 -0
  46. package/esm/tooling/check/api-route-references.d.mts.map +1 -0
  47. package/esm/tooling/check/api-route-references.mjs +146 -0
  48. package/esm/tooling/check/api-route-references.mjs.map +1 -0
  49. package/esm/tooling/check/conventions-step.d.mts +6 -3
  50. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  51. package/esm/tooling/check/conventions-step.mjs +19 -10
  52. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  53. package/esm/tooling/check/run-check.d.mts.map +1 -1
  54. package/esm/tooling/check/run-check.mjs +4 -2
  55. package/esm/tooling/check/run-check.mjs.map +1 -1
  56. package/esm/tooling/check/translation-conventions.mjs +32 -0
  57. package/esm/tooling/check/translation-conventions.mjs.map +1 -0
  58. package/esm/tooling/inspect/collect-files.mjs +1 -1
  59. package/esm/upgrade/codemods/index.mjs +2 -0
  60. package/esm/upgrade/codemods/index.mjs.map +1 -1
  61. package/esm/upgrade/codemods/vite-vitest.mjs +170 -0
  62. package/esm/upgrade/codemods/vite-vitest.mjs.map +1 -0
  63. package/esm/upgrade/notes.mjs +4 -0
  64. package/esm/upgrade/notes.mjs.map +1 -1
  65. package/esm/warlock-config/index.d.mts +2 -2
  66. package/llms-full.txt +127 -16
  67. package/package.json +13 -13
  68. package/skills/register-route/SKILL.md +13 -0
  69. package/skills/test-service/SKILL.md +8 -8
  70. package/skills/upgrade-from-5-29/SKILL.md +91 -0
  71. package/skills/use-localization/SKILL.md +4 -0
  72. package/skills/warlock-check/SKILL.md +11 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translation-usage.mjs","names":["ast.parseSourceFile","ast.positionOf","ast.parentOf"],"sources":["../../../../../../../../packages/core/src/localization/translation-usage.ts"],"sourcesContent":["import * as ast from \"../tooling/syntax/ast\";\n\n/**\n * What one page (or one module) can look up in the translation table, derived\n * from static source. Consumers decide what to ship; this type only says which\n * keys a client render may ask for.\n *\n * - `keys` are exact keys. A key also delivers its whole subtree, so a declared\n * group (`\"members\"`) and a literal leaf (`\"members.title\"`) use one matching\n * rule.\n * - `prefixes` come from template keys with a static prefix\n * (`` trans(`statuses.${status}`) `` gives `\"statuses.\"`). Every key that\n * starts with one is reachable.\n * - `dynamic` is set when a lookup key cannot be known before render. The\n * consumer must then assume every key is reachable.\n */\nexport type TranslationUsage = {\n readonly keys: readonly string[];\n readonly prefixes: readonly string[];\n /**\n * Key-shaped string literals written anywhere in the walked modules, outside\n * a recognized lookup (a helper's argument, a constant, an object that maps to\n * keys, a prop). A literal counts as used when it equals an existing key,\n * names a group or subtree, or is the base of plural variants\n * (`\"cart.items\"` reaches `\"cart.items_one\"` and `\"cart.items_other\"`).\n */\n readonly literals: readonly string[];\n /** The static heads of template literals with substitutions, which reach every key that starts with one. */\n readonly literalPrefixes: readonly string[];\n readonly dynamic: boolean;\n /** Where each unresolvable lookup was written, for diagnostics. */\n readonly dynamicSites: readonly TranslationDynamicSite[];\n};\n\nexport type TranslationDynamicSite = {\n readonly file: string;\n readonly line: number;\n readonly column: number;\n /** The source text of the lookup's key argument, as written. */\n readonly text: string;\n};\n\n/** What one source file contributes: its own usage plus the modules it loads at run time. */\nexport type TranslationSourceScan = {\n readonly usage: TranslationUsage;\n /** Specifiers of value imports, re-exports and dynamic `import()` calls. */\n readonly imports: readonly string[];\n};\n\n/** The translator functions every framework and application spelling resolves to, with the key's argument index. */\nconst TRANSLATOR_KEY_ARGUMENT: Readonly<Record<string, number>> = Object.freeze({\n trans: 0,\n t: 0,\n transX: 0,\n transFrom: 1,\n});\n\n/** Factories that return a translator bound to the current request or page. */\nconst TRANSLATOR_FACTORIES = new Set([\"useTrans\"]);\n\n/** Parent node types in which a bare translator identifier is handed on as a value. */\nconst VALUE_POSITIONS = new Set([\n \"CallExpression\",\n \"NewExpression\",\n \"JSXExpressionContainer\",\n \"Property\",\n \"ArrayExpression\",\n \"ReturnStatement\",\n \"AssignmentExpression\",\n \"VariableDeclarator\",\n \"ArrowFunctionExpression\",\n \"ConditionalExpression\",\n \"LogicalExpression\",\n \"SpreadElement\",\n]);\n\n/** Build a usage value from parts, deduplicated and frozen. */\nexport function usageOf(parts: {\n readonly keys?: readonly string[];\n readonly prefixes?: readonly string[];\n readonly literals?: readonly string[];\n readonly literalPrefixes?: readonly string[];\n readonly dynamic?: boolean;\n readonly dynamicSites?: readonly TranslationDynamicSite[];\n}): TranslationUsage {\n return Object.freeze({\n keys: Object.freeze([...new Set(parts.keys ?? [])]),\n prefixes: Object.freeze([...new Set(parts.prefixes ?? [])]),\n literals: Object.freeze([...new Set(parts.literals ?? [])]),\n literalPrefixes: Object.freeze([...new Set(parts.literalPrefixes ?? [])]),\n dynamic: parts.dynamic === true,\n dynamicSites: Object.freeze([...(parts.dynamicSites ?? [])]),\n });\n}\n\nexport const EMPTY_TRANSLATION_USAGE: TranslationUsage = usageOf({});\n\n/**\n * Turn one `config.translations` entry into the usage it declares.\n *\n * `\"statuses.*\"` declares every key under `statuses.`, `\"members\"` declares a\n * key and its subtree, and `\"*\"` declares everything. A wildcard anywhere but\n * the final `.*`, or an empty entry, is refused rather than guessed at.\n */\nexport function translationDeclarationUsage(entry: string, file: string): TranslationUsage {\n const invalid = (): Error =>\n new Error(\n `config.translations entry ${JSON.stringify(entry)} in \"${file}\" must be a translation key, ` +\n 'a group, or a prefix such as \"statuses.*\"; a wildcard is allowed only as the final \".*\".',\n );\n\n if (entry.length === 0 || /\\s/.test(entry)) throw invalid();\n if (entry === \"*\") return usageOf({ prefixes: [\"\"] });\n\n if (entry.endsWith(\".*\")) {\n const prefix = entry.slice(0, -1);\n if (prefix.includes(\"*\") || prefix.includes(\"..\")) throw invalid();\n return usageOf({ prefixes: [prefix] });\n }\n\n if (entry.includes(\"*\")) throw invalid();\n\n return usageOf({ keys: [entry] });\n}\n\n/** The union of several usages: a dynamic lookup anywhere makes the union dynamic. */\nexport function mergeTranslationUsage(usages: readonly TranslationUsage[]): TranslationUsage {\n return usageOf({\n keys: usages.flatMap((usage) => usage.keys),\n prefixes: usages.flatMap((usage) => usage.prefixes),\n literals: usages.flatMap((usage) => usage.literals),\n literalPrefixes: usages.flatMap((usage) => usage.literalPrefixes),\n dynamic: usages.some((usage) => usage.dynamic),\n dynamicSites: usages.flatMap((usage) => usage.dynamicSites),\n });\n}\n\n/**\n * Whether `key` is `declared`, lives under it, or is one of its plural\n * variants. The localization runtime resolves `trans(\"cart.items\", { count })`\n * to `cart.items_one`, `cart.items_other` and the other count suffixes, so a\n * name that is only a base of such keys reaches them. A suffix is one segment:\n * `\"cart.items_one\"` matches `\"cart.items\"`, `\"cart.items_one.note\"` does not.\n */\nfunction reaches(declared: string, key: string): boolean {\n if (declared.endsWith(\".\")) return key.startsWith(declared);\n if (key === declared || key.startsWith(`${declared}.`)) return true;\n if (!key.startsWith(`${declared}_`)) return false;\n\n return !key.includes(\".\", declared.length);\n}\n\n/** Whether a usage can reach `key`. A dynamic usage reaches every key. */\nexport function isTranslationKeyUsed(usage: TranslationUsage, key: string): boolean {\n if (usage.dynamic) return true;\n if (usage.keys.some((declared) => reaches(declared, key))) return true;\n if (usage.literals.some((literal) => reaches(literal, key))) return true;\n\n return (\n usage.prefixes.some((prefix) => key.startsWith(prefix)) ||\n usage.literalPrefixes.some((prefix) => key.startsWith(prefix))\n );\n}\n\n/** Whether a string could be a translation key, a group or a key prefix: dotted word segments, no spaces. */\nconst KEY_SHAPE = /^[A-Za-z_$][\\w$-]*(?:\\.[\\w$-]+)*\\.?$/;\n\n/** Longest literal considered; a real key is far shorter, and anything longer is prose or data. */\nconst MAX_LITERAL_LENGTH = 160;\n\nfunction isKeyShaped(text: string): boolean {\n return text.length <= MAX_LITERAL_LENGTH && KEY_SHAPE.test(text);\n}\n\ntype KeyCandidate = { readonly kind: \"key\" | \"prefix\"; readonly value: string };\n\n/** Candidates, `\"dynamic\"` when the key is unknown, or `\"none\"` when there is no key (an inline translation object, no argument). */\ntype KeyResolution = readonly KeyCandidate[] | \"dynamic\" | \"none\";\n\nfunction unwrap(node: ast.Node): ast.Node {\n let current = node;\n while (\n current.type === \"ParenthesizedExpression\" ||\n current.type === \"TSAsExpression\" ||\n current.type === \"TSSatisfiesExpression\" ||\n current.type === \"TSNonNullExpression\"\n ) {\n current = current.expression;\n }\n return current;\n}\n\nfunction combine(left: KeyResolution, right: KeyResolution): KeyResolution {\n if (left === \"dynamic\" || right === \"dynamic\") return \"dynamic\";\n if (left === \"none\") return right;\n if (right === \"none\") return left;\n return [...left, ...right];\n}\n\nfunction resolveKeyExpression(\n node: ast.Node | undefined,\n constants: ReadonlyMap<string, string>,\n): KeyResolution {\n if (node === undefined) return \"none\";\n\n const value = unwrap(node);\n\n if (value.type === \"Literal\" && typeof value.value === \"string\") {\n return [{ kind: \"key\", value: value.value }];\n }\n\n if (value.type === \"TemplateLiteral\") {\n const head = value.quasis[0]?.value.cooked ?? \"\";\n if (head.length === 0) return \"dynamic\";\n return value.expressions.length === 0\n ? [{ kind: \"key\", value: head }]\n : [{ kind: \"prefix\", value: head }];\n }\n\n if (value.type === \"Identifier\") {\n const constant = constants.get(value.name);\n return constant === undefined ? \"dynamic\" : [{ kind: \"key\", value: constant }];\n }\n\n if (value.type === \"ConditionalExpression\") {\n return combine(\n resolveKeyExpression(value.consequent, constants),\n resolveKeyExpression(value.alternate, constants),\n );\n }\n\n if (value.type === \"LogicalExpression\") {\n return combine(\n resolveKeyExpression(value.left, constants),\n resolveKeyExpression(value.right, constants),\n );\n }\n\n // An inline `{ en: \"...\", ar: \"...\" }` translation carries its own text.\n if (value.type === \"ObjectExpression\") return \"none\";\n\n return \"dynamic\";\n}\n\n/** Top-level `const NAME = \"literal\"` declarations, which a key may name through an identifier. */\nfunction moduleConstants(source: ast.SourceFile): Map<string, string> {\n const constants = new Map<string, string>();\n\n for (const statement of source.body) {\n const declaration =\n statement.type === \"ExportNamedDeclaration\" ? statement.declaration : statement;\n\n if (declaration?.type !== \"VariableDeclaration\" || declaration.kind !== \"const\") continue;\n\n for (const declarator of declaration.declarations) {\n if (declarator.id.type !== \"Identifier\" || declarator.init === null) continue;\n\n const init = unwrap(declarator.init);\n\n if (init.type === \"Literal\" && typeof init.value === \"string\") {\n constants.set(declarator.id.name, init.value);\n } else if (init.type === \"TemplateLiteral\" && init.expressions.length === 0) {\n constants.set(declarator.id.name, init.quasis[0]?.value.cooked ?? \"\");\n }\n }\n }\n\n return constants;\n}\n\n/**\n * Local names bound to a translator: the result of `useTrans()`, including an\n * aliased import of it. Calling one of them is a lookup like calling `trans`.\n */\nfunction translatorBindings(source: ast.SourceFile): Set<string> {\n const factories = new Set(TRANSLATOR_FACTORIES);\n const bindings = new Set<string>();\n\n for (const statement of source.body) {\n if (statement.type !== \"ImportDeclaration\") continue;\n for (const specifier of statement.specifiers) {\n if (specifier.type !== \"ImportSpecifier\") continue;\n const imported = specifier.imported.type === \"Identifier\" ? specifier.imported.name : \"\";\n if (TRANSLATOR_FACTORIES.has(imported)) factories.add(specifier.local.name);\n }\n }\n\n const visit = (node: ast.Node): void => {\n if (\n node.type === \"VariableDeclarator\" &&\n node.id.type === \"Identifier\" &&\n node.init !== null &&\n node.init.type === \"CallExpression\" &&\n node.init.callee.type === \"Identifier\" &&\n factories.has(node.init.callee.name)\n ) {\n bindings.add(node.id.name);\n }\n ast.forEachChild(node, visit);\n };\n\n ast.forEachChild(source, visit);\n\n return bindings;\n}\n\nfunction isTranslatorName(name: string, bindings: ReadonlySet<string>): boolean {\n return Object.hasOwn(TRANSLATOR_KEY_ARGUMENT, name) || bindings.has(name);\n}\n\n/**\n * Scan one source file for translation lookups and the modules it loads.\n *\n * Literal keys, static-prefix templates, identifiers bound to a module-level\n * string constant, and choices between those (`a ? \"x\" : \"y\"`) all resolve. Any\n * other key expression is `dynamic`, and so is a translator that escapes as a\n * value (passed on, returned, stored): whoever receives it can call it with any\n * key.\n */\nexport function scanTranslationSource(fileName: string, text: string): TranslationSourceScan {\n const source = ast.parseSourceFile(fileName, text);\n const constants = moduleConstants(source);\n const bindings = translatorBindings(source);\n const keys: string[] = [];\n const prefixes: string[] = [];\n const literals: string[] = [];\n const literalPrefixes: string[] = [];\n const dynamicSites: TranslationDynamicSite[] = [];\n const imports: string[] = [];\n\n const markDynamic = (node: ast.Node, lookup: string): void => {\n dynamicSites.push({ file: fileName, ...ast.positionOf(source, node), text: lookup });\n };\n\n const visit = (node: ast.Node): void => {\n if (node.type === \"ImportDeclaration\") {\n const specifiers = node.specifiers;\n const typeOnly =\n node.importKind === \"type\" ||\n (specifiers.length > 0 &&\n specifiers.every(\n (specifier) => specifier.type === \"ImportSpecifier\" && specifier.importKind === \"type\",\n ));\n if (!typeOnly) imports.push(node.source.value);\n } else if (\n (node.type === \"ExportNamedDeclaration\" || node.type === \"ExportAllDeclaration\") &&\n node.source !== null &&\n node.exportKind !== \"type\"\n ) {\n imports.push(node.source.value);\n } else if (node.type === \"ImportExpression\") {\n const specifier = unwrap(node.source);\n if (specifier.type === \"Literal\" && typeof specifier.value === \"string\") {\n imports.push(specifier.value);\n }\n } else if (node.type === \"CallExpression\") {\n const callee = unwrap(node.callee);\n\n if (callee.type === \"Identifier\" && isTranslatorName(callee.name, bindings)) {\n const index = TRANSLATOR_KEY_ARGUMENT[callee.name] ?? 0;\n const argument = node.arguments[index];\n const resolution =\n argument === undefined\n ? \"none\"\n : argument.type === \"SpreadElement\"\n ? \"dynamic\"\n : resolveKeyExpression(argument, constants);\n\n if (resolution === \"dynamic\") {\n markDynamic(argument ?? node, argument === undefined ? \"\" : sourceText(text, argument));\n } else if (resolution !== \"none\") {\n for (const candidate of resolution) {\n if (candidate.kind === \"key\") keys.push(candidate.value);\n else prefixes.push(candidate.value);\n }\n }\n }\n } else if (node.type === \"Identifier\" && isTranslatorName(node.name, bindings)) {\n const parent = ast.parentOf(node);\n if (parent !== undefined && escapesAsValue(parent, node)) markDynamic(node, node.name);\n } else if (node.type === \"Literal\" && typeof node.value === \"string\") {\n if (!isModuleSpecifier(node) && isKeyShaped(node.value)) literals.push(node.value);\n } else if (node.type === \"TemplateLiteral\") {\n const head = node.quasis[0]?.value.cooked ?? \"\";\n if (isKeyShaped(head)) {\n (node.expressions.length === 0 ? literals : literalPrefixes).push(head);\n }\n }\n\n ast.forEachChild(node, visit);\n };\n\n ast.forEachChild(source, visit);\n\n return {\n usage: usageOf({\n keys,\n prefixes,\n // A literal already read as a lookup key is not repeated.\n literals: literals.filter((literal) => !keys.includes(literal)),\n literalPrefixes: literalPrefixes.filter((literal) => !prefixes.includes(literal)),\n dynamic: dynamicSites.length > 0,\n dynamicSites,\n }),\n imports: [...new Set(imports)],\n };\n}\n\n/** The specifier of an import, re-export or dynamic import: a module path, never a translation key. */\nfunction isModuleSpecifier(node: ast.Node): boolean {\n const parent = ast.parentOf(node);\n\n if (parent === undefined) return false;\n\n switch (parent.type) {\n case \"ImportDeclaration\":\n case \"ExportNamedDeclaration\":\n case \"ExportAllDeclaration\":\n case \"ImportExpression\":\n return parent.source === node;\n default:\n return false;\n }\n}\n\n/** A translator identifier handed on as a value, rather than called or declared. */\nfunction escapesAsValue(parent: ast.Node, node: ast.Node): boolean {\n if (!VALUE_POSITIONS.has(parent.type)) return false;\n\n switch (parent.type) {\n case \"CallExpression\":\n case \"NewExpression\":\n return parent.callee !== node;\n case \"Property\":\n return parent.value === node;\n case \"VariableDeclarator\":\n return parent.init === node;\n case \"ArrowFunctionExpression\":\n return parent.body === node;\n case \"AssignmentExpression\":\n return parent.right === node;\n default:\n return true;\n }\n}\n\nfunction sourceText(text: string, node: ast.Node): string {\n return text.slice(node.start, node.end);\n}\n\n/**\n * The `config.translations` declaration of a page's setup file, as its raw\n * entries. Read statically, like `config.route`, so the build never runs the\n * file. `undefined` when the file declares none.\n */\nexport function readTranslationDeclarations(\n fileName: string,\n text: string,\n): readonly string[] | undefined {\n const source = ast.parseSourceFile(fileName, text);\n const malformed = (): Error =>\n new Error(`config.translations in \"${fileName}\" must be an array of string literals.`);\n\n for (const statement of source.body) {\n const declaration =\n statement.type === \"ExportNamedDeclaration\" ? statement.declaration : undefined;\n if (declaration?.type !== \"VariableDeclaration\") continue;\n\n for (const declarator of declaration.declarations) {\n if (declarator.id.type !== \"Identifier\" || declarator.id.name !== \"config\") continue;\n if (declarator.init === null) continue;\n\n const init = unwrap(declarator.init);\n if (init.type !== \"ObjectExpression\") continue;\n\n for (const property of init.properties) {\n if (property.type !== \"Property\" || property.computed) continue;\n\n const name =\n property.key.type === \"Identifier\"\n ? property.key.name\n : property.key.type === \"Literal\" && typeof property.key.value === \"string\"\n ? property.key.value\n : undefined;\n if (name !== \"translations\") continue;\n\n const value = unwrap(property.value);\n if (value.type !== \"ArrayExpression\") throw malformed();\n\n return value.elements.map((element) => {\n const literal = element === null ? undefined : unwrap(element);\n if (literal?.type !== \"Literal\" || typeof literal.value !== \"string\") throw malformed();\n return literal.value;\n });\n }\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -3,12 +3,30 @@ export type PageBudget = {
3
3
  js?: string;
4
4
  css?: string;
5
5
  };
6
+ /**
7
+ * The inline payload budget (`build.budgets.payload`): how much hydration data
8
+ * one rendered document may embed. Measured at runtime, per document, and
9
+ * reported once per route; see `web/src/server/payload-budget.ts`.
10
+ */
11
+ export type PayloadBudget = {
12
+ /** Limit for one document, e.g. "64kb"; defaults to 64kb. */
13
+ page?: string;
14
+ /** Route-path globs supporting *, ** and ?. The later matching entry sets the limit. */
15
+ pages?: Record<string, string>;
16
+ /**
17
+ * `"warn"` (default) logs a warning and `"error"` logs at error level. Neither
18
+ * fails a request: the budget reports, it does not gate.
19
+ */
20
+ mode?: "warn" | "error";
21
+ };
6
22
  export type BuildBudgets = {
7
23
  page?: PageBudget;
8
24
  /** Route-path globs supporting *, ** and ?. Later matching entries win per field. */
9
25
  pages?: Record<string, PageBudget>;
10
26
  /** Defaults to error when budgets are configured. */
11
27
  mode?: "error" | "warn";
28
+ /** Inline payload budget per rendered document. Checked at runtime, not by `warlock build`. */
29
+ payload?: PayloadBudget;
12
30
  };
13
31
  export type BudgetPage = {
14
32
  page: string;
@@ -1 +1 @@
1
- {"version":3,"file":"build-budgets.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/production/build-budgets.ts"],"mappings":";YAGY;EAAe;EAAa;;YAC5B;EACV,OAAO;;EAEP,QAAQ,eAAe;;EAEvB;;YAEU;EACV;EACA;EACA;EACA;EACA;IAAW;IAAY;;EACvB;EACA;IAAW;IAAY;;EACvB;IAAU;IAAY;;;YAEZ;EACV;EACA;EACA;EACA;EACA,SAAS;;YAEC;EACV;EACA;EACA,OAAO;EACP,YAAY"}
1
+ {"version":3,"file":"build-budgets.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/production/build-budgets.ts"],"mappings":";YAGY;EAAe;EAAa;;;;;;;YAM5B;;EAEV;;EAEA,QAAQ;;;;;EAKR;;YAEU;EACV,OAAO;;EAEP,QAAQ,eAAe;;EAEvB;;EAEA,UAAU;;YAEA;EACV;EACA;EACA;EACA;EACA;IAAW;IAAY;;EACvB;EACA;IAAW;IAAY;;EACvB;IAAU;IAAY;;;YAEZ;EACV;EACA;EACA;EACA;EACA,SAAS;;YAEC;EACV;EACA;EACA,OAAO;EACP,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"build-budgets.mjs","names":[],"sources":["../../../../../../../../packages/core/src/production/build-budgets.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport type PageBudget = { js?: string; css?: string };\nexport type BuildBudgets = {\n page?: PageBudget;\n /** Route-path globs supporting *, ** and ?. Later matching entries win per field. */\n pages?: Record<string, PageBudget>;\n /** Defaults to error when budgets are configured. */\n mode?: \"error\" | \"warn\";\n};\nexport type BudgetPage = {\n page: string;\n source: string;\n js: number;\n css: number;\n dynamic: { js: number; css: number };\n files: string[];\n modules: { id: string; bytes: number }[];\n delta?: { js: number; css: number };\n};\nexport type BudgetViolation = {\n page: string;\n type: \"js\" | \"css\";\n budget: number;\n actual: number;\n modules: BudgetPage[\"modules\"];\n};\nexport type BuildBudgetReport = {\n version: 1;\n compression: \"gzip\";\n pages: BudgetPage[];\n violations: BudgetViolation[];\n};\nexport const BUILD_BUDGET_REPORT_FILE = \"build-budgets.json\";\n\nexport function parseBudgetSize(size: string): number {\n const match = /^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb)$/i.exec(size.trim());\n if (!match)\n throw new Error(\n `Invalid build budget ${JSON.stringify(size)}: use bytes, kb or mb, e.g. \"150kb\".`,\n );\n const units: Record<string, number> = { b: 1, kb: 1024, mb: 1024 * 1024 };\n const bytes = Number(match[1]) * (units[match[2]?.toLowerCase() ?? \"\"] ?? 0);\n if (!Number.isFinite(bytes)) throw new Error(`Invalid build budget ${JSON.stringify(size)}.`);\n return bytes;\n}\n\nfunction routeMatches(pattern: string, route: string): boolean {\n if ([\"[\", \"]\", \"{\", \"}\", \"(\", \")\", \"!\", \"\\\\\"].some((token) => pattern.includes(token))) {\n throw new Error(\n `Invalid build budget glob ${JSON.stringify(pattern)}: supported wildcards are *, ** and ?.`,\n );\n }\n let expression = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const character = pattern[i];\n if (character === \"*\" && pattern[i + 1] === \"*\") {\n i++;\n if (pattern[i + 1] === \"/\") {\n expression += \"(?:.*/)?\";\n i++;\n } else expression += \".*\";\n } else if (character === \"*\") expression += \"[^/]*\";\n else if (character === \"?\") expression += \"[^/]\";\n else expression += character?.replace(/[.+^$|]/g, \"\\\\$&\") ?? \"\";\n }\n return new RegExp(`${expression}$`).test(route);\n}\n\nexport function evaluateBuildBudgets(\n pages: BudgetPage[],\n budgets?: BuildBudgets,\n previous?: BuildBudgetReport,\n): BuildBudgetReport {\n if (budgets?.mode !== undefined && budgets.mode !== \"error\" && budgets.mode !== \"warn\") {\n throw new Error('build.budgets.mode must be \"error\" or \"warn\".');\n }\n // Validate even overrides that do not match any current route.\n for (const budget of [budgets?.page, ...Object.values(budgets?.pages ?? {})]) {\n for (const size of Object.values(budget ?? {})) {\n if (size !== undefined) parseBudgetSize(size);\n }\n }\n for (const pattern of Object.keys(budgets?.pages ?? {})) routeMatches(pattern, \"\");\n const violations: BudgetViolation[] = [];\n const result = pages\n .map((page) => {\n let limits = { ...budgets?.page };\n for (const [pattern, override] of Object.entries(budgets?.pages ?? {})) {\n if (routeMatches(pattern, page.page)) limits = { ...limits, ...override };\n }\n for (const type of [\"js\", \"css\"] as const) {\n const configured = limits[type];\n if (configured === undefined) continue;\n const budget = parseBudgetSize(configured);\n if (page[type] > budget)\n violations.push({\n page: page.page,\n type,\n budget,\n actual: page[type],\n modules: page.modules,\n });\n }\n const old = previous?.pages.find(\n (entry) => entry.page === page.page && entry.source === page.source,\n );\n return {\n ...page,\n delta: old ? { js: page.js - old.js, css: page.css - old.css } : undefined,\n };\n })\n .sort((a, b) => b.js + b.css - a.js - a.css || a.page.localeCompare(b.page));\n return { version: 1, compression: \"gzip\", pages: result, violations };\n}\n\nexport async function readBuildBudgetReport(\n outdir: string,\n): Promise<BuildBudgetReport | undefined> {\n try {\n const report: BuildBudgetReport = JSON.parse(\n await readFile(path.join(outdir, BUILD_BUDGET_REPORT_FILE), \"utf8\"),\n );\n if (\n !report ||\n typeof report !== \"object\" ||\n report.version !== 1 ||\n report.compression !== \"gzip\" ||\n !Array.isArray(report.pages)\n )\n return undefined;\n return report;\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error && \"code\" in error && error.code === \"ENOENT\")\n )\n return undefined;\n throw error;\n }\n}\n\nconst kb = (bytes: number) => `${(bytes / 1024).toFixed(2)}kb`;\nconst delta = (value: number | undefined) =>\n value === undefined ? \"—\" : `${value >= 0 ? \"+\" : \"\"}${kb(value)}`;\nexport function formatBuildBudgets(report: BuildBudgetReport): string {\n return [\n \"First-visit client assets (gzip); dynamic imports excluded from budgets\",\n \"Page | JS | Δ JS | CSS | Δ CSS | Dynamic JS | Dynamic CSS | Largest modules (rendered bytes)\",\n ...report.pages.map(\n (page) =>\n `${page.page} | ${kb(page.js)} | ${delta(page.delta?.js)} | ${kb(page.css)} | ${delta(page.delta?.css)} | ${kb(page.dynamic.js)} | ${kb(page.dynamic.css)} | ${page.modules\n .slice(0, 3)\n .map((module) => `${module.id} (${module.bytes}b)`)\n .join(\", \")}`,\n ),\n ].join(\"\\n\");\n}\nexport function formatBudgetViolations(report: BuildBudgetReport): string {\n return report.violations\n .map(\n (violation) =>\n `Page ${violation.page}: ${violation.type.toUpperCase()} budget ${kb(violation.budget)}, actual ${kb(violation.actual)} (limit ${violation.budget}b, actual ${violation.actual}b). Biggest modules (rendered bytes): ${violation.modules.map((module) => `${module.id} (${module.bytes}b)`).join(\", \") || \"none\"}`,\n )\n .join(\"\\n\");\n}\nexport async function writeBuildBudgetReport(\n outdir: string,\n report: BuildBudgetReport,\n): Promise<void> {\n await writeFile(\n path.join(outdir, BUILD_BUDGET_REPORT_FILE),\n `${JSON.stringify(report, null, 2)}\\n`,\n );\n}\n"],"mappings":";;;;AAkCA,MAAa,2BAA2B;AAExC,SAAgB,gBAAgB,MAAsB;CACpD,MAAM,QAAQ,iCAAiC,KAAK,KAAK,KAAK,CAAC;CAC/D,IAAI,CAAC,OACH,MAAM,IAAI,MACR,wBAAwB,KAAK,UAAU,IAAI,EAAE,qCAC/C;CAEF,MAAM,QAAQ,OAAO,MAAM,EAAE,KAAK;EADM,GAAG;EAAG,IAAI;EAAM,IAAI;CACtB,EAAE,MAAM,EAAE,EAAE,YAAY,KAAK,OAAO;CAC1E,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,MAAM,wBAAwB,KAAK,UAAU,IAAI,EAAE,EAAE;CAC5F,OAAO;AACT;AAEA,SAAS,aAAa,SAAiB,OAAwB;CAC7D,IAAI;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;CAAI,CAAC,CAAC,MAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,GACnF,MAAM,IAAI,MACR,6BAA6B,KAAK,UAAU,OAAO,EAAE,uCACvD;CAEF,IAAI,aAAa;CACjB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,YAAY,QAAQ;EAC1B,IAAI,cAAc,OAAO,QAAQ,IAAI,OAAO,KAAK;GAC/C;GACA,IAAI,QAAQ,IAAI,OAAO,KAAK;IAC1B,cAAc;IACd;GACF,OAAO,cAAc;EACvB,OAAO,IAAI,cAAc,KAAK,cAAc;OACvC,IAAI,cAAc,KAAK,cAAc;OACrC,cAAc,WAAW,QAAQ,YAAY,MAAM,KAAK;CAC/D;CACA,OAAO,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC,CAAC,KAAK,KAAK;AAChD;AAEA,SAAgB,qBACd,OACA,SACA,UACmB;CACnB,IAAI,SAAS,SAAS,UAAa,QAAQ,SAAS,WAAW,QAAQ,SAAS,QAC9E,MAAM,IAAI,MAAM,mDAA+C;CAGjE,KAAK,MAAM,UAAU,CAAC,SAAS,MAAM,GAAG,OAAO,OAAO,SAAS,SAAS,CAAC,CAAC,CAAC,GACzE,KAAK,MAAM,QAAQ,OAAO,OAAO,UAAU,CAAC,CAAC,GAC3C,IAAI,SAAS,QAAW,gBAAgB,IAAI;CAGhD,KAAK,MAAM,WAAW,OAAO,KAAK,SAAS,SAAS,CAAC,CAAC,GAAG,aAAa,SAAS,EAAE;CACjF,MAAM,aAAgC,CAAC;CA6BvC,OAAO;EAAE,SAAS;EAAG,aAAa;EAAQ,OA5B3B,MACZ,KAAK,SAAS;GACb,IAAI,SAAS,EAAE,GAAG,SAAS,KAAK;GAChC,KAAK,MAAM,CAAC,SAAS,aAAa,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GACnE,IAAI,aAAa,SAAS,KAAK,IAAI,GAAG,SAAS;IAAE,GAAG;IAAQ,GAAG;GAAS;GAE1E,KAAK,MAAM,QAAQ,CAAC,MAAM,KAAK,GAAY;IACzC,MAAM,aAAa,OAAO;IAC1B,IAAI,eAAe,QAAW;IAC9B,MAAM,SAAS,gBAAgB,UAAU;IACzC,IAAI,KAAK,QAAQ,QACf,WAAW,KAAK;KACd,MAAM,KAAK;KACX;KACA;KACA,QAAQ,KAAK;KACb,SAAS,KAAK;IAChB,CAAC;GACL;GACA,MAAM,MAAM,UAAU,MAAM,MACzB,UAAU,MAAM,SAAS,KAAK,QAAQ,MAAM,WAAW,KAAK,MAC/D;GACA,OAAO;IACL,GAAG;IACH,OAAO,MAAM;KAAE,IAAI,KAAK,KAAK,IAAI;KAAI,KAAK,KAAK,MAAM,IAAI;IAAI,IAAI;GACnE;EACF,CAAC,CAAC,CACD,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,IAAI,CACtB;EAAG;CAAW;AACtE;AAEA,eAAsB,sBACpB,QACwC;CACxC,IAAI;EACF,MAAM,SAA4B,KAAK,MACrC,MAAM,SAAS,KAAK,KAAK,QAAQ,wBAAwB,GAAG,MAAM,CACpE;EACA,IACE,CAAC,UACD,OAAO,WAAW,YAClB,OAAO,YAAY,KACnB,OAAO,gBAAgB,UACvB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO;EACT,OAAO;CACT,SAAS,OAAO;EACd,IACE,iBAAiB,eAChB,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAE7D,OAAO;EACT,MAAM;CACR;AACF;AAEA,MAAM,MAAM,UAAkB,IAAI,QAAQ,KAAI,CAAE,QAAQ,CAAC,EAAE;AAC3D,MAAM,SAAS,UACb,UAAU,SAAY,MAAM,GAAG,SAAS,IAAI,MAAM,KAAK,GAAG,KAAK;AACjE,SAAgB,mBAAmB,QAAmC;CACpE,OAAO;EACL;EACA;EACA,GAAG,OAAO,MAAM,KACb,SACC,GAAG,KAAK,KAAK,KAAK,GAAG,KAAK,EAAE,EAAE,KAAK,MAAM,KAAK,OAAO,EAAE,EAAE,KAAK,GAAG,KAAK,GAAG,EAAE,KAAK,MAAM,KAAK,OAAO,GAAG,EAAE,KAAK,GAAG,KAAK,QAAQ,EAAE,EAAE,KAAK,GAAG,KAAK,QAAQ,GAAG,EAAE,KAAK,KAAK,QACjK,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,WAAW,GAAG,OAAO,GAAG,IAAI,OAAO,MAAM,GAAG,CAAC,CAClD,KAAK,IAAI,GAChB;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AACA,SAAgB,uBAAuB,QAAmC;CACxE,OAAO,OAAO,WACX,KACE,cACC,QAAQ,UAAU,KAAK,IAAI,UAAU,KAAK,YAAY,EAAE,UAAU,GAAG,UAAU,MAAM,EAAE,WAAW,GAAG,UAAU,MAAM,EAAE,UAAU,UAAU,OAAO,YAAY,UAAU,OAAO,wCAAwC,UAAU,QAAQ,KAAK,WAAW,GAAG,OAAO,GAAG,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,KAAK,QAC9S,CAAC,CACA,KAAK,IAAI;AACd;AACA,eAAsB,uBACpB,QACA,QACe;CACf,MAAM,UACJ,KAAK,KAAK,QAAQ,wBAAwB,GAC1C,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,GACrC;AACF"}
1
+ {"version":3,"file":"build-budgets.mjs","names":[],"sources":["../../../../../../../../packages/core/src/production/build-budgets.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport type PageBudget = { js?: string; css?: string };\n/**\n * The inline payload budget (`build.budgets.payload`): how much hydration data\n * one rendered document may embed. Measured at runtime, per document, and\n * reported once per route; see `web/src/server/payload-budget.ts`.\n */\nexport type PayloadBudget = {\n /** Limit for one document, e.g. \"64kb\"; defaults to 64kb. */\n page?: string;\n /** Route-path globs supporting *, ** and ?. The later matching entry sets the limit. */\n pages?: Record<string, string>;\n /**\n * `\"warn\"` (default) logs a warning and `\"error\"` logs at error level. Neither\n * fails a request: the budget reports, it does not gate.\n */\n mode?: \"warn\" | \"error\";\n};\nexport type BuildBudgets = {\n page?: PageBudget;\n /** Route-path globs supporting *, ** and ?. Later matching entries win per field. */\n pages?: Record<string, PageBudget>;\n /** Defaults to error when budgets are configured. */\n mode?: \"error\" | \"warn\";\n /** Inline payload budget per rendered document. Checked at runtime, not by `warlock build`. */\n payload?: PayloadBudget;\n};\nexport type BudgetPage = {\n page: string;\n source: string;\n js: number;\n css: number;\n dynamic: { js: number; css: number };\n files: string[];\n modules: { id: string; bytes: number }[];\n delta?: { js: number; css: number };\n};\nexport type BudgetViolation = {\n page: string;\n type: \"js\" | \"css\";\n budget: number;\n actual: number;\n modules: BudgetPage[\"modules\"];\n};\nexport type BuildBudgetReport = {\n version: 1;\n compression: \"gzip\";\n pages: BudgetPage[];\n violations: BudgetViolation[];\n};\nexport const BUILD_BUDGET_REPORT_FILE = \"build-budgets.json\";\n\nexport function parseBudgetSize(size: string): number {\n const match = /^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb)$/i.exec(size.trim());\n if (!match)\n throw new Error(\n `Invalid build budget ${JSON.stringify(size)}: use bytes, kb or mb, e.g. \"150kb\".`,\n );\n const units: Record<string, number> = { b: 1, kb: 1024, mb: 1024 * 1024 };\n const bytes = Number(match[1]) * (units[match[2]?.toLowerCase() ?? \"\"] ?? 0);\n if (!Number.isFinite(bytes)) throw new Error(`Invalid build budget ${JSON.stringify(size)}.`);\n return bytes;\n}\n\nfunction routeMatches(pattern: string, route: string): boolean {\n if ([\"[\", \"]\", \"{\", \"}\", \"(\", \")\", \"!\", \"\\\\\"].some((token) => pattern.includes(token))) {\n throw new Error(\n `Invalid build budget glob ${JSON.stringify(pattern)}: supported wildcards are *, ** and ?.`,\n );\n }\n let expression = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const character = pattern[i];\n if (character === \"*\" && pattern[i + 1] === \"*\") {\n i++;\n if (pattern[i + 1] === \"/\") {\n expression += \"(?:.*/)?\";\n i++;\n } else expression += \".*\";\n } else if (character === \"*\") expression += \"[^/]*\";\n else if (character === \"?\") expression += \"[^/]\";\n else expression += character?.replace(/[.+^$|]/g, \"\\\\$&\") ?? \"\";\n }\n return new RegExp(`${expression}$`).test(route);\n}\n\nexport function evaluateBuildBudgets(\n pages: BudgetPage[],\n budgets?: BuildBudgets,\n previous?: BuildBudgetReport,\n): BuildBudgetReport {\n if (budgets?.mode !== undefined && budgets.mode !== \"error\" && budgets.mode !== \"warn\") {\n throw new Error('build.budgets.mode must be \"error\" or \"warn\".');\n }\n // Validate even overrides that do not match any current route.\n for (const budget of [budgets?.page, ...Object.values(budgets?.pages ?? {})]) {\n for (const size of Object.values(budget ?? {})) {\n if (size !== undefined) parseBudgetSize(size);\n }\n }\n for (const pattern of Object.keys(budgets?.pages ?? {})) routeMatches(pattern, \"\");\n const violations: BudgetViolation[] = [];\n const result = pages\n .map((page) => {\n let limits = { ...budgets?.page };\n for (const [pattern, override] of Object.entries(budgets?.pages ?? {})) {\n if (routeMatches(pattern, page.page)) limits = { ...limits, ...override };\n }\n for (const type of [\"js\", \"css\"] as const) {\n const configured = limits[type];\n if (configured === undefined) continue;\n const budget = parseBudgetSize(configured);\n if (page[type] > budget)\n violations.push({\n page: page.page,\n type,\n budget,\n actual: page[type],\n modules: page.modules,\n });\n }\n const old = previous?.pages.find(\n (entry) => entry.page === page.page && entry.source === page.source,\n );\n return {\n ...page,\n delta: old ? { js: page.js - old.js, css: page.css - old.css } : undefined,\n };\n })\n .sort((a, b) => b.js + b.css - a.js - a.css || a.page.localeCompare(b.page));\n return { version: 1, compression: \"gzip\", pages: result, violations };\n}\n\nexport async function readBuildBudgetReport(\n outdir: string,\n): Promise<BuildBudgetReport | undefined> {\n try {\n const report: BuildBudgetReport = JSON.parse(\n await readFile(path.join(outdir, BUILD_BUDGET_REPORT_FILE), \"utf8\"),\n );\n if (\n !report ||\n typeof report !== \"object\" ||\n report.version !== 1 ||\n report.compression !== \"gzip\" ||\n !Array.isArray(report.pages)\n )\n return undefined;\n return report;\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n (error instanceof Error && \"code\" in error && error.code === \"ENOENT\")\n )\n return undefined;\n throw error;\n }\n}\n\nconst kb = (bytes: number) => `${(bytes / 1024).toFixed(2)}kb`;\nconst delta = (value: number | undefined) =>\n value === undefined ? \"—\" : `${value >= 0 ? \"+\" : \"\"}${kb(value)}`;\nexport function formatBuildBudgets(report: BuildBudgetReport): string {\n return [\n \"First-visit client assets (gzip); dynamic imports excluded from budgets\",\n \"Page | JS | Δ JS | CSS | Δ CSS | Dynamic JS | Dynamic CSS | Largest modules (rendered bytes)\",\n ...report.pages.map(\n (page) =>\n `${page.page} | ${kb(page.js)} | ${delta(page.delta?.js)} | ${kb(page.css)} | ${delta(page.delta?.css)} | ${kb(page.dynamic.js)} | ${kb(page.dynamic.css)} | ${page.modules\n .slice(0, 3)\n .map((module) => `${module.id} (${module.bytes}b)`)\n .join(\", \")}`,\n ),\n ].join(\"\\n\");\n}\nexport function formatBudgetViolations(report: BuildBudgetReport): string {\n return report.violations\n .map(\n (violation) =>\n `Page ${violation.page}: ${violation.type.toUpperCase()} budget ${kb(violation.budget)}, actual ${kb(violation.actual)} (limit ${violation.budget}b, actual ${violation.actual}b). Biggest modules (rendered bytes): ${violation.modules.map((module) => `${module.id} (${module.bytes}b)`).join(\", \") || \"none\"}`,\n )\n .join(\"\\n\");\n}\nexport async function writeBuildBudgetReport(\n outdir: string,\n report: BuildBudgetReport,\n): Promise<void> {\n await writeFile(\n path.join(outdir, BUILD_BUDGET_REPORT_FILE),\n `${JSON.stringify(report, null, 2)}\\n`,\n );\n}\n"],"mappings":"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"}
@@ -32,12 +32,14 @@ function validateSnapshot(value) {
32
32
  const routes = value.routes.map((route) => {
33
33
  if (!isRecord(route) || typeof route.name !== "string" || typeof route.path !== "string" || !isRequestMethod(route.method)) throw new Error("Route registration child sent an invalid route record.");
34
34
  const response = route.response;
35
- const allowedKeys = response === void 0 ? 3 : 4;
36
- if (Object.keys(route).length !== allowedKeys || response !== void 0 && !isResponseTypeRecord(response)) throw new Error("Route registration child sent a route record with non-browser-safe fields.");
35
+ const clientVisible = route.clientVisible;
36
+ const allowedKeys = 3 + (response === void 0 ? 0 : 1) + (clientVisible === void 0 ? 0 : 1);
37
+ if (Object.keys(route).length !== allowedKeys || response !== void 0 && !isResponseTypeRecord(response) || clientVisible !== void 0 && clientVisible !== false) throw new Error("Route registration child sent a route record with non-browser-safe fields.");
37
38
  const base = {
38
39
  name: route.name,
39
40
  path: route.path,
40
- method: normalizeRequestMethod(route.method)
41
+ method: normalizeRequestMethod(route.method),
42
+ ...clientVisible === false ? { clientVisible: false } : {}
41
43
  };
42
44
  return Object.freeze(response === void 0 ? base : {
43
45
  ...base,
@@ -1 +1 @@
1
- {"version":3,"file":"route-registration-snapshot.mjs","names":[],"sources":["../../../../../../../../packages/core/src/production/route-registration-snapshot.ts"],"sourcesContent":["import type { NamedApiRoute, RequestMethod } from \"../router/types\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\nimport {\n runRegistrationChild,\n type RegistrationChildForker,\n type RegistrationChildProcess,\n} from \"./registration-child-runner\";\n\nconst PROTOCOL_VERSION = 1;\n\nexport type RouteRegistrationSnapshot = Readonly<{\n version: typeof PROTOCOL_VERSION;\n routes: readonly NamedApiRoute[];\n}>;\n\nexport type RouteRegistrationChildRequest = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n}>;\n\nexport type RouteRegistrationChildProcess = RegistrationChildProcess;\n\nexport type RouteRegistrationSnapshotOptions = Readonly<{\n cwd?: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n timeoutMs?: number;\n childEntry?: string;\n forkProcess?: RegistrationChildForker;\n}>;\n\n/**\n * Collect named application API routes in a fresh process.\n *\n * The child imports application registration modules but deliberately does not\n * register, boot, or start connectors. Application module top-level code can\n * still have arbitrary user-owned side effects; this boundary is framework-only.\n */\nexport function collectRouteRegistrationSnapshot(\n options: RouteRegistrationSnapshotOptions = {},\n): Promise<RouteRegistrationSnapshot> {\n const cwd = options.cwd ?? process.cwd();\n\n return runRegistrationChild<RouteRegistrationSnapshot>({\n cwd,\n timeoutMs: options.timeoutMs,\n childEntry: options.childEntry,\n forkProcess: options.forkProcess,\n request: {\n version: PROTOCOL_VERSION,\n cwd,\n environment: options.environment,\n runtimeStrategy: options.runtimeStrategy,\n } satisfies RouteRegistrationChildRequest,\n resultMessageType: \"route-registration:snapshot\",\n resultNoun: \"snapshot\",\n readResult: (message) => validateSnapshot(message.snapshot),\n });\n}\n\nfunction validateSnapshot(value: unknown): RouteRegistrationSnapshot {\n if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) {\n throw new Error(\"Route registration child sent an invalid snapshot protocol payload.\");\n }\n\n const routes = value.routes.map((route) => {\n if (\n !isRecord(route) ||\n typeof route.name !== \"string\" ||\n typeof route.path !== \"string\" ||\n !isRequestMethod(route.method)\n ) {\n throw new Error(\"Route registration child sent an invalid route record.\");\n }\n\n const response = route.response;\n const allowedKeys = response === undefined ? 3 : 4;\n\n if (\n Object.keys(route).length !== allowedKeys ||\n (response !== undefined && !isResponseTypeRecord(response))\n ) {\n throw new Error(\"Route registration child sent a route record with non-browser-safe fields.\");\n }\n\n const base = {\n name: route.name,\n path: route.path,\n method: normalizeRequestMethod(route.method),\n };\n\n return Object.freeze(\n response === undefined ? base : { ...base, response: Object.freeze({ ...response }) },\n );\n });\n\n return Object.freeze({ version: PROTOCOL_VERSION, routes: Object.freeze(routes) });\n}\n\nfunction isResponseTypeRecord(value: unknown): value is Record<string, string> {\n return isRecord(value) && Object.values(value).every((entry) => typeof entry === \"string\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRequestMethod(value: unknown): value is RequestMethod {\n return (\n typeof value === \"string\" &&\n [\"get\", \"post\", \"put\", \"delete\", \"patch\", \"options\", \"head\", \"all\"].includes(\n value.toLowerCase(),\n )\n );\n}\n\nfunction normalizeRequestMethod(value: RequestMethod | string): RequestMethod {\n return value.toLowerCase() === \"all\" ? \"all\" : (value.toUpperCase() as RequestMethod);\n}\n"],"mappings":";;;AAQA,MAAM,mBAAmB;;;;;;;;AAgCzB,SAAgB,iCACd,UAA4C,CAAC,GACT;CACpC,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CAEvC,OAAO,qBAAgD;EACrD;EACA,WAAW,QAAQ;EACnB,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB,SAAS;GACP,SAAS;GACT;GACA,aAAa,QAAQ;GACrB,iBAAiB,QAAQ;EAC3B;EACA,mBAAmB;EACnB,YAAY;EACZ,aAAa,YAAY,iBAAiB,QAAQ,QAAQ;CAC5D,CAAC;AACH;AAEA,SAAS,iBAAiB,OAA2C;CACnE,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,YAAY,oBAAoB,CAAC,MAAM,QAAQ,MAAM,MAAM,GACvF,MAAM,IAAI,MAAM,qEAAqE;CAGvF,MAAM,SAAS,MAAM,OAAO,KAAK,UAAU;EACzC,IACE,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,SAAS,YACtB,CAAC,gBAAgB,MAAM,MAAM,GAE7B,MAAM,IAAI,MAAM,wDAAwD;EAG1E,MAAM,WAAW,MAAM;EACvB,MAAM,cAAc,aAAa,SAAY,IAAI;EAEjD,IACE,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,eAC7B,aAAa,UAAa,CAAC,qBAAqB,QAAQ,GAEzD,MAAM,IAAI,MAAM,4EAA4E;EAG9F,MAAM,OAAO;GACX,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,QAAQ,uBAAuB,MAAM,MAAM;EAC7C;EAEA,OAAO,OAAO,OACZ,aAAa,SAAY,OAAO;GAAE,GAAG;GAAM,UAAU,OAAO,OAAO,EAAE,GAAG,SAAS,CAAC;EAAE,CACtF;CACF,CAAC;CAED,OAAO,OAAO,OAAO;EAAE,SAAS;EAAkB,QAAQ,OAAO,OAAO,MAAM;CAAE,CAAC;AACnF;AAEA,SAAS,qBAAqB,OAAiD;CAC7E,OAAO,SAAS,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO,UAAU,OAAO,UAAU,QAAQ;AAC3F;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,gBAAgB,OAAwC;CAC/D,OACE,OAAO,UAAU,YACjB;EAAC;EAAO;EAAQ;EAAO;EAAU;EAAS;EAAW;EAAQ;CAAK,CAAC,CAAC,SAClE,MAAM,YAAY,CACpB;AAEJ;AAEA,SAAS,uBAAuB,OAA8C;CAC5E,OAAO,MAAM,YAAY,MAAM,QAAQ,QAAS,MAAM,YAAY;AACpE"}
1
+ {"version":3,"file":"route-registration-snapshot.mjs","names":[],"sources":["../../../../../../../../packages/core/src/production/route-registration-snapshot.ts"],"sourcesContent":["import type { NamedApiRoute, RequestMethod } from \"../router/types\";\nimport type { Environment, RuntimeStrategy } from \"../utils/environment\";\nimport {\n runRegistrationChild,\n type RegistrationChildForker,\n type RegistrationChildProcess,\n} from \"./registration-child-runner\";\n\nconst PROTOCOL_VERSION = 1;\n\nexport type RouteRegistrationSnapshot = Readonly<{\n version: typeof PROTOCOL_VERSION;\n routes: readonly NamedApiRoute[];\n}>;\n\nexport type RouteRegistrationChildRequest = Readonly<{\n version: typeof PROTOCOL_VERSION;\n cwd: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n}>;\n\nexport type RouteRegistrationChildProcess = RegistrationChildProcess;\n\nexport type RouteRegistrationSnapshotOptions = Readonly<{\n cwd?: string;\n environment?: Environment;\n runtimeStrategy?: RuntimeStrategy;\n timeoutMs?: number;\n childEntry?: string;\n forkProcess?: RegistrationChildForker;\n}>;\n\n/**\n * Collect named application API routes in a fresh process.\n *\n * The child imports application registration modules but deliberately does not\n * register, boot, or start connectors. Application module top-level code can\n * still have arbitrary user-owned side effects; this boundary is framework-only.\n */\nexport function collectRouteRegistrationSnapshot(\n options: RouteRegistrationSnapshotOptions = {},\n): Promise<RouteRegistrationSnapshot> {\n const cwd = options.cwd ?? process.cwd();\n\n return runRegistrationChild<RouteRegistrationSnapshot>({\n cwd,\n timeoutMs: options.timeoutMs,\n childEntry: options.childEntry,\n forkProcess: options.forkProcess,\n request: {\n version: PROTOCOL_VERSION,\n cwd,\n environment: options.environment,\n runtimeStrategy: options.runtimeStrategy,\n } satisfies RouteRegistrationChildRequest,\n resultMessageType: \"route-registration:snapshot\",\n resultNoun: \"snapshot\",\n readResult: (message) => validateSnapshot(message.snapshot),\n });\n}\n\nfunction validateSnapshot(value: unknown): RouteRegistrationSnapshot {\n if (!isRecord(value) || value.version !== PROTOCOL_VERSION || !Array.isArray(value.routes)) {\n throw new Error(\"Route registration child sent an invalid snapshot protocol payload.\");\n }\n\n const routes = value.routes.map((route) => {\n if (\n !isRecord(route) ||\n typeof route.name !== \"string\" ||\n typeof route.path !== \"string\" ||\n !isRequestMethod(route.method)\n ) {\n throw new Error(\"Route registration child sent an invalid route record.\");\n }\n\n const response = route.response;\n const clientVisible = route.clientVisible;\n const allowedKeys =\n 3 + (response === undefined ? 0 : 1) + (clientVisible === undefined ? 0 : 1);\n\n if (\n Object.keys(route).length !== allowedKeys ||\n (response !== undefined && !isResponseTypeRecord(response)) ||\n (clientVisible !== undefined && clientVisible !== false)\n ) {\n throw new Error(\"Route registration child sent a route record with non-browser-safe fields.\");\n }\n\n const base = {\n name: route.name,\n path: route.path,\n method: normalizeRequestMethod(route.method),\n ...(clientVisible === false ? { clientVisible: false as const } : {}),\n };\n\n return Object.freeze(\n response === undefined ? base : { ...base, response: Object.freeze({ ...response }) },\n );\n });\n\n return Object.freeze({ version: PROTOCOL_VERSION, routes: Object.freeze(routes) });\n}\n\nfunction isResponseTypeRecord(value: unknown): value is Record<string, string> {\n return isRecord(value) && Object.values(value).every((entry) => typeof entry === \"string\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRequestMethod(value: unknown): value is RequestMethod {\n return (\n typeof value === \"string\" &&\n [\"get\", \"post\", \"put\", \"delete\", \"patch\", \"options\", \"head\", \"all\"].includes(\n value.toLowerCase(),\n )\n );\n}\n\nfunction normalizeRequestMethod(value: RequestMethod | string): RequestMethod {\n return value.toLowerCase() === \"all\" ? \"all\" : (value.toUpperCase() as RequestMethod);\n}\n"],"mappings":";;;AAQA,MAAM,mBAAmB;;;;;;;;AAgCzB,SAAgB,iCACd,UAA4C,CAAC,GACT;CACpC,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CAEvC,OAAO,qBAAgD;EACrD;EACA,WAAW,QAAQ;EACnB,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB,SAAS;GACP,SAAS;GACT;GACA,aAAa,QAAQ;GACrB,iBAAiB,QAAQ;EAC3B;EACA,mBAAmB;EACnB,YAAY;EACZ,aAAa,YAAY,iBAAiB,QAAQ,QAAQ;CAC5D,CAAC;AACH;AAEA,SAAS,iBAAiB,OAA2C;CACnE,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,YAAY,oBAAoB,CAAC,MAAM,QAAQ,MAAM,MAAM,GACvF,MAAM,IAAI,MAAM,qEAAqE;CAGvF,MAAM,SAAS,MAAM,OAAO,KAAK,UAAU;EACzC,IACE,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,SAAS,YACtB,CAAC,gBAAgB,MAAM,MAAM,GAE7B,MAAM,IAAI,MAAM,wDAAwD;EAG1E,MAAM,WAAW,MAAM;EACvB,MAAM,gBAAgB,MAAM;EAC5B,MAAM,cACJ,KAAK,aAAa,SAAY,IAAI,MAAM,kBAAkB,SAAY,IAAI;EAE5E,IACE,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,eAC7B,aAAa,UAAa,CAAC,qBAAqB,QAAQ,KACxD,kBAAkB,UAAa,kBAAkB,OAElD,MAAM,IAAI,MAAM,4EAA4E;EAG9F,MAAM,OAAO;GACX,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,QAAQ,uBAAuB,MAAM,MAAM;GAC3C,GAAI,kBAAkB,QAAQ,EAAE,eAAe,MAAe,IAAI,CAAC;EACrE;EAEA,OAAO,OAAO,OACZ,aAAa,SAAY,OAAO;GAAE,GAAG;GAAM,UAAU,OAAO,OAAO,EAAE,GAAG,SAAS,CAAC;EAAE,CACtF;CACF,CAAC;CAED,OAAO,OAAO,OAAO;EAAE,SAAS;EAAkB,QAAQ,OAAO,OAAO,MAAM;CAAE,CAAC;AACnF;AAEA,SAAS,qBAAqB,OAAiD;CAC7E,OAAO,SAAS,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO,UAAU,OAAO,UAAU,QAAQ;AAC3F;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,gBAAgB,OAAwC;CAC/D,OACE,OAAO,UAAU,YACjB;EAAC;EAAO;EAAQ;EAAO;EAAU;EAAS;EAAW;EAAQ;CAAK,CAAC,CAAC,SAClE,MAAM,YAAY,CACpB;AAEJ;AAEA,SAAS,uBAAuB,OAA8C;CAC5E,OAAO,MAAM,YAAY,MAAM,QAAQ,QAAS,MAAM,YAAY;AACpE"}
@@ -221,6 +221,8 @@ export declare class Router {
221
221
  *
222
222
  * Names are the registered names, including method suffixes. `all` stays
223
223
  * intact as route metadata; consumers decide whether their use supports it.
224
+ * A route that opted out with `clientVisible: false` carries that flag, and
225
+ * only that flag, so browser-facing consumers can leave it out.
224
226
  */
225
227
  getNamedApiRoutes(options?: NamedApiRoutesOptions): readonly NamedApiRoute[];
226
228
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/router/router.ts"],"mappings":";;;;;;qBAqKa;;;;UAIH;;;;;;;;;UAUA;;;;;UAMA;;;;;UAUA;;;;iBAKO;;;;YAKL;IACR,SAAS;IACT,WAAW;;;;;YAMH,gBAAgB,iBAAiB,QAAQ,QAAQ,QAAQ;;;;;YAOzD,QAAQ;;;;SASJ,eAAW;UAQlB;;;;EAOA,eAAe,WAAW,QAAQ,QAAQ,QAAQ;;;;EASlD,cAAc,WAAW,QAAQ,QAAQ,QAAQ;;;;EASjD,SAAS,cAAc,YAAY;;;;;;;;;;;;;;EAmBnC,UAAU,SAAS,sBAAsB,eAAc;;YAOpD,0BAA0B,QAAQ;;;;;;YAelC,mBACR,cACA,SAAS,gBACT,eAAc;;;;EAmBT,KACL,cACA,kBACA,oBACA,eAAc;;;;EAcT,WACL,cACA,kBACA,oBACA,eAAc;;;;EAcT,MACL,OAAO,wBACP,oBACA,eAAc;;;;EAUT,YACL,OAAO,wBACP,oBACA,eAAc;;;;EAUT,MACL,cACA,iBACA,UAAU,KAAK;EAEV,MAAM,SAAS;;;;EAoBf,IACL,QAAQ,iBACR,yBACA,SAAS,oBACT,UAAS;;;;EA8HJ,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,KAAK,yBAAyB,SAAS,oBAAoB,UAAS;;;;EAOpE,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,OAAO,yBAAyB,SAAS,oBAAoB,UAAS;;;;EAOtE,MAAM,cAAc,SAAS,oBAAoB,UAAS;;;;EAO1D,KAAK,cAAc,SAAS,oBAAoB,UAAS;;;;EAOzD,QAAQ,cAAc,SAAS,oBAAoB,UAAS;;;;EAO5D,MAAM,cAAc,UAAS,eAAiB;;;;;;;;;;;EAc9C,gBACL,cACA,UAAU,eACV,UAAS;IACP,OAAO;IACP,SAAS;IACT,UAAU,QAAQ,OAAO,gBAAgB;MACvC,aAAa;;;;;;EAsGZ,MAAM,SAAS,sBAAsB,UAAU;;;;EA4D/C,OAAO,gBAAgB;;;;;;;;;;;;;EAgBvB,QAAQ,0BAA0B;;;;;;;EAU5B,eAAe,SAC1B,oBACA,gBAAgB,IAAI,QAAQ,KAC3B,QAAQ;;;;;;EAiCJ,yBAAyB;;;;;;;;;EA0BnB,2BAA2B,GACtC,aAAa,kBACb,eAAe,IAAI,QAAQ,KAC1B,QAAQ;;;;UAoDH;;;;EA4ED,QAAI;;;;;;;EAUJ,kBAAkB,UAAS,iCAAsC;;;;;;;;;EAyBjE;;;;EAOA,KAAK,QAAQ;;;;;EAqDb,cAAc,QAAQ;;;;EAiKtB,SAAS,cAAc,SAAQ;;;;UAkC9B;;qBA+EG,QAAM"}
1
+ {"version":3,"file":"router.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/router/router.ts"],"mappings":";;;;;;qBAqKa;;;;UAIH;;;;;;;;;UAUA;;;;;UAMA;;;;;UAUA;;;;iBAKO;;;;YAKL;IACR,SAAS;IACT,WAAW;;;;;YAMH,gBAAgB,iBAAiB,QAAQ,QAAQ,QAAQ;;;;;YAOzD,QAAQ;;;;SASJ,eAAW;UAQlB;;;;EAOA,eAAe,WAAW,QAAQ,QAAQ,QAAQ;;;;EASlD,cAAc,WAAW,QAAQ,QAAQ,QAAQ;;;;EASjD,SAAS,cAAc,YAAY;;;;;;;;;;;;;;EAmBnC,UAAU,SAAS,sBAAsB,eAAc;;YAOpD,0BAA0B,QAAQ;;;;;;YAelC,mBACR,cACA,SAAS,gBACT,eAAc;;;;EAmBT,KACL,cACA,kBACA,oBACA,eAAc;;;;EAcT,WACL,cACA,kBACA,oBACA,eAAc;;;;EAcT,MACL,OAAO,wBACP,oBACA,eAAc;;;;EAUT,YACL,OAAO,wBACP,oBACA,eAAc;;;;EAUT,MACL,cACA,iBACA,UAAU,KAAK;EAEV,MAAM,SAAS;;;;EAoBf,IACL,QAAQ,iBACR,yBACA,SAAS,oBACT,UAAS;;;;EA8HJ,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,KAAK,yBAAyB,SAAS,oBAAoB,UAAS;;;;EAOpE,IAAI,cAAc,SAAS,oBAAoB,UAAS;;;;EAOxD,OAAO,yBAAyB,SAAS,oBAAoB,UAAS;;;;EAOtE,MAAM,cAAc,SAAS,oBAAoB,UAAS;;;;EAO1D,KAAK,cAAc,SAAS,oBAAoB,UAAS;;;;EAOzD,QAAQ,cAAc,SAAS,oBAAoB,UAAS;;;;EAO5D,MAAM,cAAc,UAAS,eAAiB;;;;;;;;;;;EAc9C,gBACL,cACA,UAAU,eACV,UAAS;IACP,OAAO;IACP,SAAS;IACT,UAAU,QAAQ,OAAO,gBAAgB;MACvC,aAAa;;;;;;EAsGZ,MAAM,SAAS,sBAAsB,UAAU;;;;EA4D/C,OAAO,gBAAgB;;;;;;;;;;;;;EAgBvB,QAAQ,0BAA0B;;;;;;;EAU5B,eAAe,SAC1B,oBACA,gBAAgB,IAAI,QAAQ,KAC3B,QAAQ;;;;;;EAiCJ,yBAAyB;;;;;;;;;EA0BnB,2BAA2B,GACtC,aAAa,kBACb,eAAe,IAAI,QAAQ,KAC1B,QAAQ;;;;UAoDH;;;;EA4ED,QAAI;;;;;;;;;EAYJ,kBAAkB,UAAS,iCAAsC;;;;;;;;;EAgCjE;;;;EAOA,KAAK,QAAQ;;;;;EAqDb,cAAc,QAAQ;;;;EAiKtB,SAAS,cAAc,SAAQ;;;;UAkC9B;;qBA+EG,QAAM"}
@@ -554,13 +554,16 @@ var Router = class Router {
554
554
  *
555
555
  * Names are the registered names, including method suffixes. `all` stays
556
556
  * intact as route metadata; consumers decide whether their use supports it.
557
+ * A route that opted out with `clientVisible: false` carries that flag, and
558
+ * only that flag, so browser-facing consumers can leave it out.
557
559
  */
558
560
  getNamedApiRoutes(options = {}) {
559
561
  return Object.freeze(this.routes.filter((route) => !route.isPage && typeof route.name === "string" && route.name.length > 0).map((route) => {
560
562
  const snapshot = {
561
563
  name: route.name,
562
564
  path: route.path,
563
- method: route.method
565
+ method: route.method,
566
+ ...route.clientVisible === false ? { clientVisible: false } : {}
564
567
  };
565
568
  const schema = route.handler?.responseSchema;
566
569
  const response = schema ? options.resolveResponse?.(schema, route) : void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"router.mjs","names":["configAccessor","config"],"sources":["../../../../../../../../packages/core/src/router/router.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport { WarlockError } from \"../errors/warlock-error\";\nimport type { FastifyStaticOptions } from \"@fastify/static\";\nimport { ltrim, merge, toCamelCase, trim } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport { log } from \"@warlock.js/logger\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { container } from \"../container\";\nimport { registerLazyStatic } from \"../http/lazy-static\";\nimport { buildCorsOptions } from \"../http/build-cors-options\";\nimport { Request } from \"../http/request\";\nimport { buildRequestLogSnapshot } from \"../http/request-log-snapshot\";\nimport { Response } from \"../http/response\";\nimport { type FastifyInstance } from \"../http/server\";\nimport { buildTracingContext, dispatchPhase, isTracingEnabled } from \"../http/tracing\";\nimport { describeRouteForLog } from \"./describe-route-for-log\";\nimport { logRequestLifecycle } from \"./log-request-lifecycle\";\nimport {\n forgetPositionalHandlerSuspects,\n inspectHandlerSignature,\n reportPositionalHandlerSuspects,\n} from \"./positional-handler-diagnostics\";\nimport { reportUnguardedMutatingRoutes } from \"./unguarded-route-diagnostics\";\nimport { config as configAccessor } from \"../config/config-getter\";\nimport { normalizeRoutePath } from \"./normalize-route-path\";\nimport { RouteBuilder } from \"./route-builder\";\nimport { resolveRouteRateLimit, resolveStaticRateLimit } from \"./resolve-route-rate-limit\";\nimport { RouteNameTakenError } from \"./route-name-taken-error\";\nimport { DEV_DISPATCH_METHODS, buildNotFoundBody, createDevRateLimiter } from \"./dev-dispatch\";\nimport { RouteRegistry } from \"./route-registry\";\nimport { routeNameMethodSuffix } from \"./route-name-method-suffix\";\nimport type {\n GroupedRoutesOptions,\n HttpContext,\n NamedApiRoute,\n NamedApiRoutesOptions,\n RequestHandler,\n RequestHandlerType,\n RequestHandlerValidation,\n ResourceMethod,\n Route,\n RouteOptions,\n RouteResource,\n RouterGroupCallback,\n RouterStacks,\n StaticMountOptions,\n} from \"./types\";\n\n/**\n * A Fastify lifecycle hook as it may be declared in a route's `serverOptions`.\n * Fastify accepts a single hook or an array in either position.\n */\ntype RouteLifecycleHook = (\n request: FastifyRequest,\n reply: FastifyReply,\n) => unknown | Promise<unknown>;\n\n/**\n * The pre-handler phases the dev server forwards from a route's `serverOptions`.\n *\n * These are the admission-control phases and they share the `(request, reply)`\n * signature. `preParsing` is deliberately absent — it must return the payload\n * stream, so forwarding it generically would corrupt the body rather than\n * guard it. `bodyLimit` cannot be forwarded at all; Fastify reads it at\n * registration time and no hook can bound a body already being parsed.\n */\nconst FORWARDED_ROUTE_PHASES = [\"onRequest\", \"preValidation\", \"preHandler\"] as const;\n\ntype ForwardedRoutePhase = (typeof FORWARDED_ROUTE_PHASES)[number];\n\n/**\n * The route the dev dispatcher matched, stashed on the request so the hooks and\n * the wildcard handler all act on one match rather than each matching again.\n */\ntype DevMatchedRoute = { route: Route; params: Record<string, string> };\n\ntype DevDispatchRequest = FastifyRequest & {\n matchedDevRoute?: DevMatchedRoute;\n};\n\nfunction toHookList(\n hook: RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n): RouteLifecycleHook[] {\n if (!hook) {\n return [];\n }\n\n return Array.isArray(hook) ? hook : [hook];\n}\n\n/**\n * Run a matched route's hooks for one phase.\n *\n * Returning the reply is how an async Fastify hook signals that it has answered\n * the request; the chain must stop there, so the value is propagated rather\n * than discarded.\n */\nasync function runRouteHooks(\n route: Route,\n phase: ForwardedRoutePhase,\n request: FastifyRequest,\n reply: FastifyReply,\n) {\n const hooks = toHookList(\n route.serverOptions?.[phase] as RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n );\n\n for (const hook of hooks) {\n const outcome = await hook(request, reply);\n\n if (outcome === reply) {\n return reply;\n }\n }\n\n return undefined;\n}\n\n/**\n * Bound functions do not inherit static properties, so the handler metadata that\n * documentation and typings read (`responseSchema`) is copied onto the bound copy.\n */\nfunction copyResponseSchema(source: unknown, target: RequestHandler): void {\n const responseSchema = (source as RequestHandler | undefined)?.responseSchema;\n\n if (responseSchema) {\n target.responseSchema = responseSchema;\n }\n}\n\n/**\n * Bind a restful resource method to its resource, keeping its `responseSchema`.\n */\nfunction bindResourceHandler(\n resource: RouteResource,\n method: ResourceMethod | \"bulkDelete\",\n): RequestHandler {\n const source = resource[method];\n const handler = source?.bind(resource) as RequestHandler;\n\n copyResponseSchema(source, handler);\n\n return handler;\n}\n\n/**\n * Describe one claimant of a route name, for the duplicate-name error.\n *\n * `warlock dev` registers SSR page routes on the same router as API routes, so\n * the two share a single route-name namespace and collide across kinds. The\n * error is only actionable if it says which claimant is which, so `isPage` —\n * the router's only route-kind discriminator — is what separates them here.\n *\n * `sourceFile` is stamped only on routes registered through `withSourceFile`.\n * Routes registered without that scope have no owner, so say the source is\n * unknown rather than printing an empty string.\n */\nfunction describeRouteClaimant(route: Route) {\n const type = route.isPage ? \"page route\" : \"API route\";\n const source = route.sourceFile ? `declared in ${route.sourceFile}` : \"source file unknown\";\n\n return `${type} ${route.method} ${route.path} (${source})`;\n}\n\nexport class Router {\n /**\n * Routes list\n */\n private routes: Route[] = [];\n\n /**\n * Bumped on every mutation of {@link routes}.\n *\n * The dev dispatcher caches a route registry and needs to know when it is\n * stale. Comparing `routes.length` would miss an HMR reload that replaces a\n * route without changing the count — the registry would then keep matching\n * paths that no longer exist. A version counter cannot miss that.\n */\n private routesVersion = 0;\n\n /**\n * In-progress atomic replacement, when one exists. Reads continue to use\n * {@link routes}; mutations and collision checks use this isolated draft.\n */\n private routeReplacementTransaction?: {\n routes: Route[];\n removedSourceFiles: Set<string>;\n addedRoutes: Route[];\n };\n\n /**\n * Set by the dev server: whether to warn about mutating routes that have no\n * auth middleware and no `public: true` (`http.warnUnguardedRoutes`).\n */\n private warnUnguardedRoutes = false;\n\n /**\n * Router Instance\n */\n private static instance: Router;\n\n /**\n * Static paths\n */\n protected staticDirectories: {\n options: FastifyStaticOptions;\n rateLimit: StaticMountOptions[\"rateLimit\"];\n }[] = [];\n\n /**\n * Event listeners\n */\n protected eventListeners: Record<string, ((router: Router, server: FastifyInstance) => void)[]> =\n {};\n\n /**\n * Stacks\n * Stacks will be used for grouping routes to add prefix, name or middleware\n */\n protected stacks: RouterStacks = {\n prefix: [],\n name: [],\n middleware: [],\n };\n\n /**\n * Get router instance\n */\n public static getInstance() {\n if (!Router.instance) {\n Router.instance = new Router();\n }\n\n return Router.instance;\n }\n\n private constructor() {\n //\n }\n\n /**\n * Listen to router before scan\n */\n public beforeScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.beforeScan = [...(this.eventListeners.beforeScan || []), callback];\n\n return this;\n }\n\n /**\n * Listen to router after scanning\n */\n public afterScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.afterScanning = [...(this.eventListeners.afterScanning || []), callback];\n\n return this;\n }\n\n /**\n * Redirect path to another path\n */\n public redirect(from: string, to: string, redirectMode: \"temporary\" | \"permanent\" = \"temporary\") {\n return this.get(from, ({ response }) => {\n response.redirect(to, redirectMode === \"temporary\" ? 302 : 301);\n });\n }\n\n /**\n * Serve a static folder.\n *\n * Static mounts are exempt from the global rate limiter by default; set\n * `http.rateLimit.staticFiles: true` to count every mount, or pass\n * `{ rateLimit }` to decide for this mount alone: `true` counts it against\n * the global limiter, `false` exempts it, and an object gives it its own\n * limit and bucket.\n *\n * @example\n * router.directory({ root: rootPath(\"docs\"), prefix: \"/docs/\" });\n * router.directory({ root: rootPath(\"exports\"), prefix: \"/exports/\" }, { rateLimit: true });\n */\n public directory(options: FastifyStaticOptions, mountOptions: StaticMountOptions = {}) {\n this.staticDirectories.push({ options, rateLimit: mountOptions.rateLimit });\n\n return this;\n }\n\n /** Declare static routes at boot; load the upstream sender on the first file request. */\n protected registerStaticDirectories(server: FastifyInstance) {\n for (const directory of this.staticDirectories) {\n registerLazyStatic(\n server,\n { ...directory.options, decorateReply: false },\n { rateLimit: resolveStaticRateLimit(directory.rateLimit) },\n );\n }\n }\n\n /**\n * Register a GET route that serves a static file. Like `directory()`, it is\n * exempt from the global rate limiter unless `http.rateLimit.staticFiles`\n * or its own `rateLimit` says otherwise.\n */\n protected addStaticFileRoute(\n path: string,\n handler: RequestHandler,\n mountOptions: StaticMountOptions = {},\n ) {\n this.get(\n path,\n handler,\n mountOptions.rateLimit === undefined ? {} : { rateLimit: mountOptions.rateLimit },\n );\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n const route = routes[routes.length - 1];\n\n if (route) route.$staticFile = true;\n\n return this;\n }\n\n /**\n * Serve file\n */\n public file(\n path: string,\n location: string,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n return this.addStaticFileRoute(\n path,\n ({ response }) => {\n response.sendFile(location, cacheTime);\n },\n mountOptions,\n );\n }\n\n /**\n * Serve cached file, it will cache the file to 1 year by default\n */\n public cachedFile(\n path: string,\n location: string,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n return this.addStaticFileRoute(\n path,\n ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n },\n mountOptions,\n );\n }\n\n /**\n * Serve list of files\n */\n public files(\n files: Record<string, string>,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n for (const [path, location] of Object.entries(files)) {\n this.file(path, location, cacheTime, mountOptions);\n }\n }\n\n /**\n * Serve list of cached files, it will cache the file to 1 year by default\n */\n public cachedFiles(\n files: Record<string, string>,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n for (const [path, location] of Object.entries(files)) {\n this.cachedFile(path, location, cacheTime, mountOptions);\n }\n }\n\n /**\n * Add proxy route\n */\n public proxy(\n path: string,\n baseUrl: string,\n options?: Omit<FastifyHttpProxyOptions, \"prefix\" | \"upstream\">,\n ): this;\n public proxy(options: FastifyHttpProxyOptions): this;\n public proxy(...args: any[]) {\n this.beforeScanning((_router, server) => {\n if (args.length === 1) {\n server.register(proxy, args[0]);\n } else {\n server.register(proxy, {\n prefix: args[0],\n upstream: args[1],\n ...args[2],\n });\n }\n });\n\n return this;\n }\n\n /**\n * Add route to routes list\n */\n public add(\n method: Route[\"method\"],\n path: string | string[],\n handler: RequestHandlerType,\n options: RouteOptions = {},\n ) {\n if (Array.isArray(path)) {\n path.forEach((p) => this.add(method, p, handler, options));\n return this;\n }\n\n const prefix = this.stacks.prefix.reduce((path, prefix) => {\n return normalizeRoutePath(path, prefix);\n }, \"\");\n\n const name = this.stacks.name.reduceRight(\n (name, prefixName) => {\n return trim(prefixName + \".\" + name, \".\");\n },\n options.name || trim(path.replace(/\\//g, \".\"), \".\"),\n );\n\n // The one place a route path becomes canonical. `normalizeRoutePath` is\n // exported so the build-time page-route manifest can produce the very same\n // string without registering anything.\n path = normalizeRoutePath(prefix, path);\n\n const middlewarePrecedence = options.middlewarePrecedence || \"after\";\n\n // Work on a copy: the caller may reuse one options object for several\n // routes, and merging the group stack into it would compound per route.\n options = { ...options };\n\n if (middlewarePrecedence === \"before\") {\n options.middleware = [...(options.middleware || []), ...this.stacks.middleware];\n } else {\n options.middleware = [...this.stacks.middleware, ...(options.middleware || [])];\n }\n\n // An enclosing `router.group({ public: true })` makes the route public\n // unless it says otherwise itself.\n if (options.public === undefined && (this.stacks.public ?? 0) > 0) {\n options.public = true;\n }\n\n if (Array.isArray(handler)) {\n const [controller, action] = handler;\n\n if (typeof controller[action] !== \"function\") {\n throw new WarlockError(\n \"WL3002\",\n `Invalid controller action \"${action}\" for controller \"${controller.constructor.name}\"`,\n );\n }\n\n handler = controller[action].bind(controller) as RequestHandler;\n copyResponseSchema(controller[action], handler);\n\n if (!handler.validation) {\n handler.validation = {};\n if (controller[`${action}ValidationSchema`]) {\n handler.validation.schema = controller[`${action}ValidationSchema`]();\n }\n\n if (controller[`${action}Validate`]) {\n handler.validation.validate = controller[`${action}Validate`];\n }\n }\n }\n\n const routeData: Route = {\n method,\n path,\n handler,\n ...options,\n name,\n rateLimit: options.rateLimit,\n $prefix: prefix || \"/\",\n // it must be a new array to avoid modifying the original array\n $prefixStack: [...this.stacks.prefix],\n // Inject source file from stacks if set\n sourceFile: this.stacks.sourceFile || \"\",\n };\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n\n if (routeData.name) {\n // check if the name exists\n const route = routes.find((route) => route.name === routeData.name);\n\n if (route) {\n // check again if the route name exists with the same method\n if (route.method === routeData.method) {\n throw new RouteNameTakenError(\n routeData.name,\n `Route name \"${routeData.name}\" is already taken.\\n` +\n ` already registered: ${describeRouteClaimant(route)}\\n` +\n ` now being added: ${describeRouteClaimant(routeData)}\\n` +\n `Pages and API routes share one route-name namespace, so a name can only be ` +\n `claimed once. Rename one of them via the \"name\" route option.`,\n );\n } else {\n routeData.name += routeNameMethodSuffix(routeData.method);\n }\n }\n }\n\n // Registration-time only — nothing here runs per request. Suspects are\n // collected now and reported together at boot (see `scan`), so a route file\n // written against v4 produces one legible list instead of a mystery 500 per\n // request. Recorded after the collision check so a route that never lands\n // is never reported.\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.addedRoutes.push(routeData);\n } else {\n inspectHandlerSignature(routeData.handler, routeData);\n }\n\n routes.push(routeData);\n\n if (!this.routeReplacementTransaction) {\n this.routesVersion++;\n }\n\n return this;\n }\n\n /**\n * Add a request that accepts all methods\n */\n public any(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"all\" as Route[\"method\"], path, handler, options);\n }\n\n /**\n * Add get request method\n */\n public get(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"GET\", path, handler, options);\n }\n\n /**\n * Add post request method\n */\n public post(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"POST\", path, handler, options);\n }\n\n /**\n * Add put request method\n */\n public put(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PUT\", path, handler, options);\n }\n\n /**\n * Add delete request method\n */\n public delete(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"DELETE\", path, handler, options);\n }\n\n /**\n * Add patch request method\n */\n public patch(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PATCH\", path, handler, options);\n }\n\n /**\n * Add head request method\n */\n public head(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"HEAD\", path, handler, options);\n }\n\n /**\n * Add options request method\n */\n public options(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"OPTIONS\", path, handler, options);\n }\n\n /**\n * Get a chainable route builder for the same route path\n */\n public route(path: string, options: RouteOptions = {}) {\n return new RouteBuilder(this, path, options);\n }\n\n /**\n * Add full restful resource routes\n * This method will generate the following routes:\n * 1. GET /path: list all resources\n * 2. GET /path/:id: get a single resource\n * 3. POST /path: create a new resource\n * 4. PUT /path/:id: update a resource\n * 5. DELETE /path/:id: delete a resource\n * 6. PATCH /path/:id: update a resource partially\n */\n public restfulResource(\n path: string,\n resource: RouteResource,\n options: RouteOptions & {\n only?: ResourceMethod[];\n except?: ResourceMethod[];\n replace?: Partial<Record<ResourceMethod, RequestHandler>> & {\n bulkDelete?: RequestHandler;\n };\n } = {},\n ) {\n // Derive the base resource name from the path before the group below\n // clears it for use as the routes' relative path. `options.name`, when\n // given, replaces the path-derived name entirely (see the explicit\n // `name` passed to `group()`) rather than composing with it.\n const baseResourceName = options.name || toCamelCase(ltrim(path, \"/\"));\n\n return this.group({ prefix: path, name: baseResourceName }, () => {\n const path = \"\";\n\n // clone the resource so we don't mess up with it\n const routeResource = resource;\n\n const isAcceptableResource = (type: ResourceMethod) => {\n return Boolean(\n // check if the route is not excluded\n (!options.except || !options.except.includes(type)) &&\n // check if the only option is set and the route is included\n (!options.only || options.only.includes(type)),\n );\n };\n\n if (routeResource.list && isAcceptableResource(\"list\")) {\n this.get(path, options.replace?.list || bindResourceHandler(routeResource, \"list\"), {\n ...options,\n name: \"list\",\n restful: true,\n });\n }\n\n if (routeResource.get && isAcceptableResource(\"get\")) {\n this.get(path + \"/:id\", options.replace?.get || bindResourceHandler(routeResource, \"get\"), {\n ...options,\n name: \"single\",\n restful: true,\n });\n }\n\n if (routeResource.create && isAcceptableResource(\"create\")) {\n const handler = options.replace?.create || this.manageValidation(routeResource, \"create\");\n\n this.post(path, handler, {\n ...options,\n name: \"create\",\n restful: true,\n });\n }\n\n if (routeResource.update && isAcceptableResource(\"update\")) {\n const handler = options.replace?.update || this.manageValidation(routeResource, \"update\");\n\n this.put(path + \"/:id\", handler, {\n ...options,\n name: \"update\",\n restful: true,\n });\n }\n\n if (routeResource.patch && isAcceptableResource(\"patch\")) {\n const handler = options.replace?.patch || this.manageValidation(routeResource, \"patch\");\n\n this.patch(path + \"/:id\", handler, {\n ...options,\n name: \"patch\",\n restful: true,\n });\n }\n\n if (routeResource.delete && isAcceptableResource(\"delete\")) {\n this.delete(\n path + \"/:id\",\n options.replace?.delete || bindResourceHandler(routeResource, \"delete\"),\n {\n ...options,\n name: \"delete\",\n restful: true,\n },\n );\n }\n\n if (routeResource.bulkDelete && isAcceptableResource(\"delete\")) {\n this.delete(\n path,\n options.replace?.bulkDelete || bindResourceHandler(routeResource, \"bulkDelete\"),\n {\n ...options,\n name: \"bulkDelete\",\n restful: true,\n },\n );\n }\n\n return this;\n });\n }\n\n /**\n * Group routes with options\n */\n public group(options: GroupedRoutesOptions, callback: RouterGroupCallback) {\n const {\n prefix,\n // name must always be defined because\n // if there are multiple groups without name\n // they might generate the same route name\n // thus causing an error\n // in this case we need always to make sure that\n // the name is always defined.\n name = prefix ? trim(prefix.replace(/\\//g, \".\"), \".\") : undefined,\n middleware,\n public: isPublic,\n } = options;\n\n if (isPublic) {\n this.stacks.public = (this.stacks.public ?? 0) + 1;\n }\n\n if (prefix) {\n this.stacks.prefix.push(prefix);\n }\n\n if (name) {\n this.stacks.name.push(name);\n }\n\n if (middleware) {\n this.stacks.middleware.push(...middleware);\n }\n\n try {\n callback();\n } finally {\n // Always pop/splice this group's stacks, even if the callback throws,\n // so a throwing group never leaks state onto the process-global singleton.\n if (prefix) {\n this.stacks.prefix.pop();\n }\n\n if (name) {\n this.stacks.name.pop();\n }\n\n if (middleware) {\n this.stacks.middleware.splice(\n this.stacks.middleware.length - middleware.length,\n middleware.length,\n );\n }\n if (isPublic) {\n this.stacks.public = (this.stacks.public ?? 1) - 1;\n }\n }\n\n return this;\n }\n\n /**\n * Add prefix to all routes in the given callback\n */\n public prefix(prefix: string, callback: () => void) {\n return this.group({ prefix }, callback);\n }\n\n /**\n * Add API version prefix to all routes in the given callback\n * Automatically formats the version as /v{version}\n * @example\n * router.version(\"1\", () => {\n * router.get(\"/users\", getUsersV1); // /v1/users\n * });\n *\n * router.version(\"2\", () => {\n * router.get(\"/users\", getUsersV2); // /v2/users\n * });\n */\n public version(version: string | number, callback: () => void) {\n return this.prefix(`/v${version}`, callback);\n }\n\n /**\n * Wrap route additions with a source file path\n * Used for tracking which routes come from which file (for HMR)\n * @param sourceFile Relative path to the source file (e.g., \"src/app/users/routes.ts\")\n * @param callback Function that adds routes (will have sourceFile injected)\n */\n public async withSourceFile<T = any>(\n sourceFile: string,\n callback: () => T | Promise<T>,\n ): Promise<T | undefined> {\n // Set source file in stacks\n this.stacks.sourceFile = sourceFile;\n\n try {\n // Execute callback (routes added here will have sourceFile injected)\n return await callback();\n } catch (error) {\n // Log loudly, then RETHROW. Swallowing here silently dropped a whole\n // route file (duplicate name, invalid action, import error) so the\n // surface 404'd with no boot error. The caller decides what a throw\n // means: at boot it aborts the boot, during HMR the dev server's\n // batch-reload handler catches it, prints it, and keeps serving.\n log.error({\n module: \"router\",\n action: \"registerRoutes\",\n message: `Failed to register routes from \"${sourceFile}\"`,\n context: { sourceFile, error },\n });\n\n throw error;\n } finally {\n // Always clear source file from stacks so a throwing file never leaks\n // its sourceFile onto the process-global singleton.\n delete this.stacks.sourceFile;\n }\n }\n\n /**\n * Remove all routes that belong to a specific source file\n * Used when reloading routes files via HMR\n * @param sourceFile Relative path to the source file\n */\n public removeRoutesBySourceFile(sourceFile: string): void {\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.routes = this.routeReplacementTransaction.routes.filter(\n (route) => route.sourceFile !== sourceFile,\n );\n this.routeReplacementTransaction.removedSourceFiles.add(sourceFile);\n return;\n }\n\n this.routes = this.routes.filter((route) => route.sourceFile !== sourceFile);\n\n // Keep the signature diagnostics in step, so a reload that fixes a handler\n // stops reporting the version it replaced.\n forgetPositionalHandlerSuspects(sourceFile);\n\n this.routesVersion++;\n }\n\n /**\n * Atomically replace every route owned by the given exact source-file keys.\n *\n * The installer mutates an isolated draft. Public reads keep seeing the old\n * live table across awaits, and a failed installer discards every draft\n * mutation. A successful installer swaps the whole draft in one synchronous\n * commit and invalidates the dev route registry exactly once.\n */\n public async replaceRoutesBySourceFiles<T>(\n sourceFiles: Iterable<string>,\n install: () => T | Promise<T>,\n ): Promise<T> {\n if (this.routeReplacementTransaction) {\n throw new Error(\"Cannot nest route replacement transactions\");\n }\n\n const removedSourceFiles = new Set(sourceFiles);\n\n this.routeReplacementTransaction = {\n routes: this.routes.filter((route) => !removedSourceFiles.has(route.sourceFile)),\n removedSourceFiles,\n addedRoutes: [],\n };\n\n try {\n const result = await install();\n const transaction = this.routeReplacementTransaction!;\n\n this.routes = transaction.routes;\n this.routeReplacementTransaction = undefined;\n\n // Diagnostic state follows the same commit boundary as the route table.\n // Added routes were deliberately not inspected while still speculative.\n for (const sourceFile of transaction.removedSourceFiles) {\n forgetPositionalHandlerSuspects(sourceFile);\n }\n\n const committedRoutes = new Set(transaction.routes);\n\n for (const route of transaction.addedRoutes) {\n if (committedRoutes.has(route)) {\n inspectHandlerSignature(route.handler, route);\n }\n }\n\n if (this.warnUnguardedRoutes) {\n reportUnguardedMutatingRoutes(\n transaction.addedRoutes.filter((route) => committedRoutes.has(route)),\n );\n }\n\n this.routesVersion++;\n\n return result;\n } catch (error) {\n this.routeReplacementTransaction = undefined;\n throw error;\n }\n }\n\n /**\n * Manage validation system for the given resource\n */\n private manageValidation(resource: RouteResource, method: \"create\" | \"update\" | \"patch\") {\n const handler = bindResourceHandler(resource, method);\n\n const methodValidation = resource?.validation?.[method];\n\n if (method === \"patch\") {\n handler.validation = methodValidation;\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n\n if (resource.validation?.patch) {\n handler.validation = merge(resource.validation.patch, handler.validation);\n }\n\n return handler;\n }\n\n if (!resource.validation || (!methodValidation && !resource.validation.all)) return handler;\n\n if (resource.validation.all) {\n const validationMethods = {\n all: resource?.validation?.all?.validate,\n [method]: methodValidation?.validate,\n };\n\n const validation: RequestHandlerValidation = {};\n\n if (resource.validation.all.schema || methodValidation?.schema) {\n if (!methodValidation?.schema && resource.validation.all.schema) {\n // Case 2: Only all.schema exists - clone it for this method\n validation.schema = resource.validation.all.schema;\n } else if (methodValidation?.schema && resource.validation.all.schema) {\n // Case 3: Both exist - merge them (all is base, method overrides)\n validation.schema = resource.validation.all.schema.merge(methodValidation.schema);\n } else if (methodValidation?.schema && !resource.validation.all.schema) {\n // Case 1: Only method schema exists - use it directly\n validation.schema = methodValidation.schema;\n }\n // Case 4: Neither exists - handled by outer if being false\n }\n\n if (validationMethods.all || validationMethods[method]) {\n validation.validate = async ({ request, response }: HttpContext) => {\n if (validationMethods.all) {\n const output = await validationMethods.all.call(resource, { request, response });\n\n if (output) return output;\n }\n\n if (validationMethods[method]) {\n return await validationMethods[method]?.call(resource, { request, response });\n }\n\n return;\n };\n }\n\n if (!isEmpty(validation)) {\n handler.validation = validation;\n }\n } else {\n handler.validation = resource.validation[method];\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n }\n\n return handler;\n }\n\n /**\n * Get all routes list\n */\n public list() {\n return this.routes;\n }\n\n /**\n * Return fresh browser-safe snapshots of named, non-page routes.\n *\n * Names are the registered names, including method suffixes. `all` stays\n * intact as route metadata; consumers decide whether their use supports it.\n */\n public getNamedApiRoutes(options: NamedApiRoutesOptions = {}): readonly NamedApiRoute[] {\n return Object.freeze(\n this.routes\n .filter((route) => !route.isPage && typeof route.name === \"string\" && route.name.length > 0)\n .map((route) => {\n const snapshot = { name: route.name!, path: route.path, method: route.method };\n const schema = route.handler?.responseSchema;\n const response = schema ? options.resolveResponse?.(schema, route) : undefined;\n\n // A route without a declared schema keeps exactly the three-key record.\n if (!response || Object.keys(response).length === 0) return Object.freeze(snapshot);\n\n return Object.freeze({ ...snapshot, response: Object.freeze({ ...response }) });\n }),\n );\n }\n\n /**\n * Number of routes currently registered on the singleton.\n *\n * Used as a readiness signal: a successful boot that ends up with zero\n * routes almost always means a route module failed to register silently\n * (see `withSourceFile`), so callers can surface that instead of letting\n * the whole surface 404.\n */\n public routeCount(): number {\n return this.routes.length;\n }\n\n /**\n * Register routes to the server\n */\n public scan(server: FastifyInstance) {\n // Every route is registered by the time we scan, so this is the first\n // moment the whole list can be reported at once — and it is still before a\n // single request is served.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n // Fastify automatically exposes HEAD for every GET route. When an app\n // declares its own HEAD route at the same path, suppress only that GET\n // route's implicit registration so the explicit handler owns HEAD,\n // regardless of declaration order.\n const explicitHeadPaths = new Set(\n this.routes\n .filter((route) => route.method.toLowerCase() === \"head\")\n .map((route) => route.path),\n );\n\n this.routes.forEach((route) => {\n const requestMethod = route.method.toLowerCase();\n const requestMethodFunction = server[requestMethod].bind(server);\n\n const options = {\n ...route.serverOptions,\n ...(requestMethod === \"get\" &&\n explicitHeadPaths.has(route.path) && { exposeHeadRoute: false }),\n config: {\n ...route.serverOptions?.config,\n // `undefined` keeps the global policy; `false` exempts the route.\n rateLimit: resolveRouteRateLimit(route),\n },\n };\n\n requestMethodFunction(\n route.path,\n options,\n async (baseRequest: FastifyRequest, reply: FastifyReply) => {\n const { output, response } = await this.handleRoute(route)(baseRequest, reply);\n\n return output || response.baseResponse;\n },\n );\n });\n\n this.registerStaticDirectories(server);\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Scan routes for the development server\n * Uses wildcard routing with find-my-way for HMR support\n */\n public scanDevServer(server: FastifyInstance) {\n // Same boot-time report as production `scan`. Routes reloaded later by HMR\n // are still recorded, but only surface on the next scan or via\n // `warlock doctor`.\n reportPositionalHandlerSuspects();\n\n // Development only: list mutating routes nobody guarded. Routes added by a\n // later HMR reload are reported when their replacement commits.\n this.warnUnguardedRoutes =\n configAccessor.key<boolean>(\"http.warnUnguardedRoutes\", true) !== false;\n if (this.warnUnguardedRoutes) {\n reportUnguardedMutatingRoutes(this.routes);\n }\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n let routeRegistry: RouteRegistry | undefined;\n let registryVersion = -1;\n\n const applyDevRateLimit = createDevRateLimiter(\n server,\n config.get(\"http.rateLimit.global\") !== false,\n );\n\n // Rebuilt when the route table changes rather than per request. Building it\n // inside the handler re-registered every route on every hit — and `all`\n // routes expand into seven registrations each. Keyed on `routesVersion`\n // rather than on the route count, so an HMR reload that swaps a route\n // without changing the count still invalidates.\n const resolveRouteRegistry = () => {\n if (routeRegistry && registryVersion === this.routesVersion) {\n return routeRegistry;\n }\n\n routeRegistry = new RouteRegistry();\n\n routeRegistry.register(this.routes);\n\n registryVersion = this.routesVersion;\n\n return routeRegistry;\n };\n\n // Matching happens here, in Fastify's own onRequest phase, so a route's\n // `serverOptions.onRequest` still runs BEFORE body parsing. Production\n // `scan()` gets that from per-route registration; the wildcard dispatcher\n // has no per-route slot, so without this the hook silently never runs.\n server.addHook(\n \"onRequest\",\n async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n // Only requests Fastify routed to the dev wildcard belong to the app's\n // router. A plugin's own route (devtools' `/__warlock/api/requests/:id`)\n // must keep its params even when an app catch-all matches the URL too.\n if (fastifyRequest.routeOptions.url !== \"*\") {\n return undefined;\n }\n\n const match = resolveRouteRegistry().find(fastifyRequest.method, fastifyRequest.url);\n\n if (!match) {\n return undefined;\n }\n\n (fastifyRequest as DevDispatchRequest).matchedDevRoute = match;\n\n fastifyRequest.params = match.params;\n\n // Same slot production gives it: the limiter runs first in onRequest,\n // ahead of the route's own hooks.\n await applyDevRateLimit(match.route, fastifyRequest, fastifyReply);\n\n if (fastifyReply.sent) {\n return fastifyReply;\n }\n\n return runRouteHooks(match.route, \"onRequest\", fastifyRequest, fastifyReply);\n },\n );\n\n for (const phase of FORWARDED_ROUTE_PHASES.filter((name) => name !== \"onRequest\")) {\n server.addHook(phase, async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n if (!match) {\n return undefined;\n }\n\n return runRouteHooks(match.route, phase, fastifyRequest, fastifyReply);\n });\n }\n\n // Shared handler for wildcard routing\n const wildcardHandler = async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n // No match found - return 404\n if (!match) {\n return fastifyReply\n .code(404)\n .send(buildNotFoundBody(fastifyRequest.method, fastifyRequest.url));\n }\n\n try {\n // Call the matched route handler\n const { output, response } = await this.handleRoute(match.route)(\n fastifyRequest,\n fastifyReply,\n );\n\n return output || response.baseResponse;\n } catch (error) {\n log.error(\"router\", \"dev-dispatch\", error as Error, {\n method: fastifyRequest.method,\n url: buildRequestLogSnapshot({\n id: fastifyRequest.id,\n method: fastifyRequest.method,\n url: fastifyRequest.url,\n route: match.route,\n }).url,\n });\n\n throw error;\n }\n };\n\n // Every verb production can register. Fastify's implicit HEAD for the GET\n // wildcard is switched off so the HEAD wildcard owns it (the registry falls\n // back to the GET route, exactly as production's implicit HEAD does).\n // `@fastify/cors` registers its own OPTIONS \"*\" catch-all whenever\n // preflight is on. Use the plugin's options as well as hasRoute so this\n // dispatcher works both after HTTP plugin boot and before queued plugins.\n const corsOwnsPreflight = buildCorsOptions().preflight !== false;\n\n for (const method of DEV_DISPATCH_METHODS) {\n if (\n method === \"OPTIONS\" &&\n (corsOwnsPreflight || server.hasRoute({ method: \"OPTIONS\", url: \"*\" }))\n ) {\n continue;\n }\n\n server.route({\n method,\n url: \"*\",\n ...(method === \"GET\" && { exposeHeadRoute: false }),\n // The dispatcher limits each matched route itself (see\n // `createDevRateLimiter`), so the plugin must not limit the wildcard:\n // that would count exempt routes and unmatched URLs too.\n config: { rateLimit: false },\n handler: wildcardHandler,\n });\n }\n\n this.registerStaticDirectories(server);\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Get the route path for the given route name\n */\n public getRoute(name: string, params: Record<string, any> = {}) {\n const route = this.routes.find((route) => route.name === name);\n\n if (!route) {\n throw new WarlockError(\"WL3003\", `Route name \"${name}\" not found`);\n }\n\n const used = new Set<string>();\n\n const path = route.path.replace(/:([A-Za-z0-9_]+)(\\?)?/g, (_match, key: string, optional) => {\n const value = params[key];\n\n if (value === undefined || value === null) {\n if (optional) return \"\";\n\n throw new WarlockError(\"WL3004\", `Route \"${name}\" is missing the \"${key}\" param`);\n }\n\n used.add(key);\n\n return encodeURIComponent(String(value));\n });\n\n const query = Object.keys(params)\n .filter((key) => !used.has(key) && params[key] !== undefined && params[key] !== null)\n .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`)\n .join(\"&\");\n\n return query ? `${path}?${query}` : path;\n }\n\n /**\n * Handle the given route\n */\n private handleRoute(route: Route) {\n return async (fastifyRequest: FastifyRequest, fastifyResponse: FastifyReply) => {\n const request = new Request();\n const response = new Response();\n response.setResponse(fastifyResponse);\n request.response = response;\n\n response.request = request;\n\n // \"route.match\" tracing phase: the pattern is\n // already known here (`route.path`), so this brackets the cost of\n // stamping the request/route onto the `Request` instance rather than\n // route-registry matching itself, which happens upstream of this\n // function for both the production `scan()` path and the dev wildcard\n // dispatcher (both funnel into `handleRoute`).\n const tracingEnabled = isTracingEnabled();\n const routeMatchStartedAt = tracingEnabled ? performance.now() : 0;\n\n request.setRequest(fastifyRequest).setRoute(route);\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(request), {\n name: \"route.match\",\n durationMs: performance.now() - routeMatchStartedAt,\n attrs: { route: route.path },\n });\n }\n\n /*\n Both ends of the entry, not just the start — see\n `log-request-lifecycle.ts`. A start line on its own made a hung request\n and an instant one look identical, and the completion line is what makes\n a MISSING one legible as a hang.\n */\n const result = await logRequestLifecycle(\n {\n info: (entry) => log.info(entry),\n warn: (entry) => log.warn(entry),\n error: (entry) => log.error(entry),\n now: () => performance.now(),\n },\n {\n module: \"route\",\n action: describeRouteForLog(route.method, route.path),\n requestId: request.id,\n // A bounded snapshot, built once with the route's logging policy\n // applied — never the Request/Response instances, whose native\n // request, reply and URL graphs would reach every log channel.\n context: { request: request.toLogSnapshot() },\n request,\n // Read after the run: the status is decided during the request.\n statusCode: () => fastifyResponse.statusCode,\n },\n () => request.execute(),\n );\n\n // A handler may return a plain object (`ReturnedResponse`). Route it\n // through `Response.send` so status, events and hooks apply, exactly as\n // middleware outputs already do.\n let output = result;\n\n if (\n output !== undefined &&\n output !== null &&\n !(output instanceof Response) &&\n (output as unknown) !== fastifyResponse\n ) {\n output = await response.send(output);\n }\n\n return {\n output,\n response,\n request,\n };\n };\n }\n}\n\nexport const router = Router.getInstance();\n\ncontainer.set(\"router\", router);\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.mjs","names":["configAccessor","config"],"sources":["../../../../../../../../packages/core/src/router/router.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport { WarlockError } from \"../errors/warlock-error\";\nimport type { FastifyStaticOptions } from \"@fastify/static\";\nimport { ltrim, merge, toCamelCase, trim } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport { log } from \"@warlock.js/logger\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { container } from \"../container\";\nimport { registerLazyStatic } from \"../http/lazy-static\";\nimport { buildCorsOptions } from \"../http/build-cors-options\";\nimport { Request } from \"../http/request\";\nimport { buildRequestLogSnapshot } from \"../http/request-log-snapshot\";\nimport { Response } from \"../http/response\";\nimport { type FastifyInstance } from \"../http/server\";\nimport { buildTracingContext, dispatchPhase, isTracingEnabled } from \"../http/tracing\";\nimport { describeRouteForLog } from \"./describe-route-for-log\";\nimport { logRequestLifecycle } from \"./log-request-lifecycle\";\nimport {\n forgetPositionalHandlerSuspects,\n inspectHandlerSignature,\n reportPositionalHandlerSuspects,\n} from \"./positional-handler-diagnostics\";\nimport { reportUnguardedMutatingRoutes } from \"./unguarded-route-diagnostics\";\nimport { config as configAccessor } from \"../config/config-getter\";\nimport { normalizeRoutePath } from \"./normalize-route-path\";\nimport { RouteBuilder } from \"./route-builder\";\nimport { resolveRouteRateLimit, resolveStaticRateLimit } from \"./resolve-route-rate-limit\";\nimport { RouteNameTakenError } from \"./route-name-taken-error\";\nimport { DEV_DISPATCH_METHODS, buildNotFoundBody, createDevRateLimiter } from \"./dev-dispatch\";\nimport { RouteRegistry } from \"./route-registry\";\nimport { routeNameMethodSuffix } from \"./route-name-method-suffix\";\nimport type {\n GroupedRoutesOptions,\n HttpContext,\n NamedApiRoute,\n NamedApiRoutesOptions,\n RequestHandler,\n RequestHandlerType,\n RequestHandlerValidation,\n ResourceMethod,\n Route,\n RouteOptions,\n RouteResource,\n RouterGroupCallback,\n RouterStacks,\n StaticMountOptions,\n} from \"./types\";\n\n/**\n * A Fastify lifecycle hook as it may be declared in a route's `serverOptions`.\n * Fastify accepts a single hook or an array in either position.\n */\ntype RouteLifecycleHook = (\n request: FastifyRequest,\n reply: FastifyReply,\n) => unknown | Promise<unknown>;\n\n/**\n * The pre-handler phases the dev server forwards from a route's `serverOptions`.\n *\n * These are the admission-control phases and they share the `(request, reply)`\n * signature. `preParsing` is deliberately absent — it must return the payload\n * stream, so forwarding it generically would corrupt the body rather than\n * guard it. `bodyLimit` cannot be forwarded at all; Fastify reads it at\n * registration time and no hook can bound a body already being parsed.\n */\nconst FORWARDED_ROUTE_PHASES = [\"onRequest\", \"preValidation\", \"preHandler\"] as const;\n\ntype ForwardedRoutePhase = (typeof FORWARDED_ROUTE_PHASES)[number];\n\n/**\n * The route the dev dispatcher matched, stashed on the request so the hooks and\n * the wildcard handler all act on one match rather than each matching again.\n */\ntype DevMatchedRoute = { route: Route; params: Record<string, string> };\n\ntype DevDispatchRequest = FastifyRequest & {\n matchedDevRoute?: DevMatchedRoute;\n};\n\nfunction toHookList(\n hook: RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n): RouteLifecycleHook[] {\n if (!hook) {\n return [];\n }\n\n return Array.isArray(hook) ? hook : [hook];\n}\n\n/**\n * Run a matched route's hooks for one phase.\n *\n * Returning the reply is how an async Fastify hook signals that it has answered\n * the request; the chain must stop there, so the value is propagated rather\n * than discarded.\n */\nasync function runRouteHooks(\n route: Route,\n phase: ForwardedRoutePhase,\n request: FastifyRequest,\n reply: FastifyReply,\n) {\n const hooks = toHookList(\n route.serverOptions?.[phase] as RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n );\n\n for (const hook of hooks) {\n const outcome = await hook(request, reply);\n\n if (outcome === reply) {\n return reply;\n }\n }\n\n return undefined;\n}\n\n/**\n * Bound functions do not inherit static properties, so the handler metadata that\n * documentation and typings read (`responseSchema`) is copied onto the bound copy.\n */\nfunction copyResponseSchema(source: unknown, target: RequestHandler): void {\n const responseSchema = (source as RequestHandler | undefined)?.responseSchema;\n\n if (responseSchema) {\n target.responseSchema = responseSchema;\n }\n}\n\n/**\n * Bind a restful resource method to its resource, keeping its `responseSchema`.\n */\nfunction bindResourceHandler(\n resource: RouteResource,\n method: ResourceMethod | \"bulkDelete\",\n): RequestHandler {\n const source = resource[method];\n const handler = source?.bind(resource) as RequestHandler;\n\n copyResponseSchema(source, handler);\n\n return handler;\n}\n\n/**\n * Describe one claimant of a route name, for the duplicate-name error.\n *\n * `warlock dev` registers SSR page routes on the same router as API routes, so\n * the two share a single route-name namespace and collide across kinds. The\n * error is only actionable if it says which claimant is which, so `isPage` —\n * the router's only route-kind discriminator — is what separates them here.\n *\n * `sourceFile` is stamped only on routes registered through `withSourceFile`.\n * Routes registered without that scope have no owner, so say the source is\n * unknown rather than printing an empty string.\n */\nfunction describeRouteClaimant(route: Route) {\n const type = route.isPage ? \"page route\" : \"API route\";\n const source = route.sourceFile ? `declared in ${route.sourceFile}` : \"source file unknown\";\n\n return `${type} ${route.method} ${route.path} (${source})`;\n}\n\nexport class Router {\n /**\n * Routes list\n */\n private routes: Route[] = [];\n\n /**\n * Bumped on every mutation of {@link routes}.\n *\n * The dev dispatcher caches a route registry and needs to know when it is\n * stale. Comparing `routes.length` would miss an HMR reload that replaces a\n * route without changing the count — the registry would then keep matching\n * paths that no longer exist. A version counter cannot miss that.\n */\n private routesVersion = 0;\n\n /**\n * In-progress atomic replacement, when one exists. Reads continue to use\n * {@link routes}; mutations and collision checks use this isolated draft.\n */\n private routeReplacementTransaction?: {\n routes: Route[];\n removedSourceFiles: Set<string>;\n addedRoutes: Route[];\n };\n\n /**\n * Set by the dev server: whether to warn about mutating routes that have no\n * auth middleware and no `public: true` (`http.warnUnguardedRoutes`).\n */\n private warnUnguardedRoutes = false;\n\n /**\n * Router Instance\n */\n private static instance: Router;\n\n /**\n * Static paths\n */\n protected staticDirectories: {\n options: FastifyStaticOptions;\n rateLimit: StaticMountOptions[\"rateLimit\"];\n }[] = [];\n\n /**\n * Event listeners\n */\n protected eventListeners: Record<string, ((router: Router, server: FastifyInstance) => void)[]> =\n {};\n\n /**\n * Stacks\n * Stacks will be used for grouping routes to add prefix, name or middleware\n */\n protected stacks: RouterStacks = {\n prefix: [],\n name: [],\n middleware: [],\n };\n\n /**\n * Get router instance\n */\n public static getInstance() {\n if (!Router.instance) {\n Router.instance = new Router();\n }\n\n return Router.instance;\n }\n\n private constructor() {\n //\n }\n\n /**\n * Listen to router before scan\n */\n public beforeScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.beforeScan = [...(this.eventListeners.beforeScan || []), callback];\n\n return this;\n }\n\n /**\n * Listen to router after scanning\n */\n public afterScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.afterScanning = [...(this.eventListeners.afterScanning || []), callback];\n\n return this;\n }\n\n /**\n * Redirect path to another path\n */\n public redirect(from: string, to: string, redirectMode: \"temporary\" | \"permanent\" = \"temporary\") {\n return this.get(from, ({ response }) => {\n response.redirect(to, redirectMode === \"temporary\" ? 302 : 301);\n });\n }\n\n /**\n * Serve a static folder.\n *\n * Static mounts are exempt from the global rate limiter by default; set\n * `http.rateLimit.staticFiles: true` to count every mount, or pass\n * `{ rateLimit }` to decide for this mount alone: `true` counts it against\n * the global limiter, `false` exempts it, and an object gives it its own\n * limit and bucket.\n *\n * @example\n * router.directory({ root: rootPath(\"docs\"), prefix: \"/docs/\" });\n * router.directory({ root: rootPath(\"exports\"), prefix: \"/exports/\" }, { rateLimit: true });\n */\n public directory(options: FastifyStaticOptions, mountOptions: StaticMountOptions = {}) {\n this.staticDirectories.push({ options, rateLimit: mountOptions.rateLimit });\n\n return this;\n }\n\n /** Declare static routes at boot; load the upstream sender on the first file request. */\n protected registerStaticDirectories(server: FastifyInstance) {\n for (const directory of this.staticDirectories) {\n registerLazyStatic(\n server,\n { ...directory.options, decorateReply: false },\n { rateLimit: resolveStaticRateLimit(directory.rateLimit) },\n );\n }\n }\n\n /**\n * Register a GET route that serves a static file. Like `directory()`, it is\n * exempt from the global rate limiter unless `http.rateLimit.staticFiles`\n * or its own `rateLimit` says otherwise.\n */\n protected addStaticFileRoute(\n path: string,\n handler: RequestHandler,\n mountOptions: StaticMountOptions = {},\n ) {\n this.get(\n path,\n handler,\n mountOptions.rateLimit === undefined ? {} : { rateLimit: mountOptions.rateLimit },\n );\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n const route = routes[routes.length - 1];\n\n if (route) route.$staticFile = true;\n\n return this;\n }\n\n /**\n * Serve file\n */\n public file(\n path: string,\n location: string,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n return this.addStaticFileRoute(\n path,\n ({ response }) => {\n response.sendFile(location, cacheTime);\n },\n mountOptions,\n );\n }\n\n /**\n * Serve cached file, it will cache the file to 1 year by default\n */\n public cachedFile(\n path: string,\n location: string,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n return this.addStaticFileRoute(\n path,\n ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n },\n mountOptions,\n );\n }\n\n /**\n * Serve list of files\n */\n public files(\n files: Record<string, string>,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n for (const [path, location] of Object.entries(files)) {\n this.file(path, location, cacheTime, mountOptions);\n }\n }\n\n /**\n * Serve list of cached files, it will cache the file to 1 year by default\n */\n public cachedFiles(\n files: Record<string, string>,\n cacheTime?: number,\n mountOptions: StaticMountOptions = {},\n ) {\n for (const [path, location] of Object.entries(files)) {\n this.cachedFile(path, location, cacheTime, mountOptions);\n }\n }\n\n /**\n * Add proxy route\n */\n public proxy(\n path: string,\n baseUrl: string,\n options?: Omit<FastifyHttpProxyOptions, \"prefix\" | \"upstream\">,\n ): this;\n public proxy(options: FastifyHttpProxyOptions): this;\n public proxy(...args: any[]) {\n this.beforeScanning((_router, server) => {\n if (args.length === 1) {\n server.register(proxy, args[0]);\n } else {\n server.register(proxy, {\n prefix: args[0],\n upstream: args[1],\n ...args[2],\n });\n }\n });\n\n return this;\n }\n\n /**\n * Add route to routes list\n */\n public add(\n method: Route[\"method\"],\n path: string | string[],\n handler: RequestHandlerType,\n options: RouteOptions = {},\n ) {\n if (Array.isArray(path)) {\n path.forEach((p) => this.add(method, p, handler, options));\n return this;\n }\n\n const prefix = this.stacks.prefix.reduce((path, prefix) => {\n return normalizeRoutePath(path, prefix);\n }, \"\");\n\n const name = this.stacks.name.reduceRight(\n (name, prefixName) => {\n return trim(prefixName + \".\" + name, \".\");\n },\n options.name || trim(path.replace(/\\//g, \".\"), \".\"),\n );\n\n // The one place a route path becomes canonical. `normalizeRoutePath` is\n // exported so the build-time page-route manifest can produce the very same\n // string without registering anything.\n path = normalizeRoutePath(prefix, path);\n\n const middlewarePrecedence = options.middlewarePrecedence || \"after\";\n\n // Work on a copy: the caller may reuse one options object for several\n // routes, and merging the group stack into it would compound per route.\n options = { ...options };\n\n if (middlewarePrecedence === \"before\") {\n options.middleware = [...(options.middleware || []), ...this.stacks.middleware];\n } else {\n options.middleware = [...this.stacks.middleware, ...(options.middleware || [])];\n }\n\n // An enclosing `router.group({ public: true })` makes the route public\n // unless it says otherwise itself.\n if (options.public === undefined && (this.stacks.public ?? 0) > 0) {\n options.public = true;\n }\n\n if (Array.isArray(handler)) {\n const [controller, action] = handler;\n\n if (typeof controller[action] !== \"function\") {\n throw new WarlockError(\n \"WL3002\",\n `Invalid controller action \"${action}\" for controller \"${controller.constructor.name}\"`,\n );\n }\n\n handler = controller[action].bind(controller) as RequestHandler;\n copyResponseSchema(controller[action], handler);\n\n if (!handler.validation) {\n handler.validation = {};\n if (controller[`${action}ValidationSchema`]) {\n handler.validation.schema = controller[`${action}ValidationSchema`]();\n }\n\n if (controller[`${action}Validate`]) {\n handler.validation.validate = controller[`${action}Validate`];\n }\n }\n }\n\n const routeData: Route = {\n method,\n path,\n handler,\n ...options,\n name,\n rateLimit: options.rateLimit,\n $prefix: prefix || \"/\",\n // it must be a new array to avoid modifying the original array\n $prefixStack: [...this.stacks.prefix],\n // Inject source file from stacks if set\n sourceFile: this.stacks.sourceFile || \"\",\n };\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n\n if (routeData.name) {\n // check if the name exists\n const route = routes.find((route) => route.name === routeData.name);\n\n if (route) {\n // check again if the route name exists with the same method\n if (route.method === routeData.method) {\n throw new RouteNameTakenError(\n routeData.name,\n `Route name \"${routeData.name}\" is already taken.\\n` +\n ` already registered: ${describeRouteClaimant(route)}\\n` +\n ` now being added: ${describeRouteClaimant(routeData)}\\n` +\n `Pages and API routes share one route-name namespace, so a name can only be ` +\n `claimed once. Rename one of them via the \"name\" route option.`,\n );\n } else {\n routeData.name += routeNameMethodSuffix(routeData.method);\n }\n }\n }\n\n // Registration-time only — nothing here runs per request. Suspects are\n // collected now and reported together at boot (see `scan`), so a route file\n // written against v4 produces one legible list instead of a mystery 500 per\n // request. Recorded after the collision check so a route that never lands\n // is never reported.\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.addedRoutes.push(routeData);\n } else {\n inspectHandlerSignature(routeData.handler, routeData);\n }\n\n routes.push(routeData);\n\n if (!this.routeReplacementTransaction) {\n this.routesVersion++;\n }\n\n return this;\n }\n\n /**\n * Add a request that accepts all methods\n */\n public any(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"all\" as Route[\"method\"], path, handler, options);\n }\n\n /**\n * Add get request method\n */\n public get(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"GET\", path, handler, options);\n }\n\n /**\n * Add post request method\n */\n public post(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"POST\", path, handler, options);\n }\n\n /**\n * Add put request method\n */\n public put(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PUT\", path, handler, options);\n }\n\n /**\n * Add delete request method\n */\n public delete(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"DELETE\", path, handler, options);\n }\n\n /**\n * Add patch request method\n */\n public patch(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PATCH\", path, handler, options);\n }\n\n /**\n * Add head request method\n */\n public head(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"HEAD\", path, handler, options);\n }\n\n /**\n * Add options request method\n */\n public options(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"OPTIONS\", path, handler, options);\n }\n\n /**\n * Get a chainable route builder for the same route path\n */\n public route(path: string, options: RouteOptions = {}) {\n return new RouteBuilder(this, path, options);\n }\n\n /**\n * Add full restful resource routes\n * This method will generate the following routes:\n * 1. GET /path: list all resources\n * 2. GET /path/:id: get a single resource\n * 3. POST /path: create a new resource\n * 4. PUT /path/:id: update a resource\n * 5. DELETE /path/:id: delete a resource\n * 6. PATCH /path/:id: update a resource partially\n */\n public restfulResource(\n path: string,\n resource: RouteResource,\n options: RouteOptions & {\n only?: ResourceMethod[];\n except?: ResourceMethod[];\n replace?: Partial<Record<ResourceMethod, RequestHandler>> & {\n bulkDelete?: RequestHandler;\n };\n } = {},\n ) {\n // Derive the base resource name from the path before the group below\n // clears it for use as the routes' relative path. `options.name`, when\n // given, replaces the path-derived name entirely (see the explicit\n // `name` passed to `group()`) rather than composing with it.\n const baseResourceName = options.name || toCamelCase(ltrim(path, \"/\"));\n\n return this.group({ prefix: path, name: baseResourceName }, () => {\n const path = \"\";\n\n // clone the resource so we don't mess up with it\n const routeResource = resource;\n\n const isAcceptableResource = (type: ResourceMethod) => {\n return Boolean(\n // check if the route is not excluded\n (!options.except || !options.except.includes(type)) &&\n // check if the only option is set and the route is included\n (!options.only || options.only.includes(type)),\n );\n };\n\n if (routeResource.list && isAcceptableResource(\"list\")) {\n this.get(path, options.replace?.list || bindResourceHandler(routeResource, \"list\"), {\n ...options,\n name: \"list\",\n restful: true,\n });\n }\n\n if (routeResource.get && isAcceptableResource(\"get\")) {\n this.get(path + \"/:id\", options.replace?.get || bindResourceHandler(routeResource, \"get\"), {\n ...options,\n name: \"single\",\n restful: true,\n });\n }\n\n if (routeResource.create && isAcceptableResource(\"create\")) {\n const handler = options.replace?.create || this.manageValidation(routeResource, \"create\");\n\n this.post(path, handler, {\n ...options,\n name: \"create\",\n restful: true,\n });\n }\n\n if (routeResource.update && isAcceptableResource(\"update\")) {\n const handler = options.replace?.update || this.manageValidation(routeResource, \"update\");\n\n this.put(path + \"/:id\", handler, {\n ...options,\n name: \"update\",\n restful: true,\n });\n }\n\n if (routeResource.patch && isAcceptableResource(\"patch\")) {\n const handler = options.replace?.patch || this.manageValidation(routeResource, \"patch\");\n\n this.patch(path + \"/:id\", handler, {\n ...options,\n name: \"patch\",\n restful: true,\n });\n }\n\n if (routeResource.delete && isAcceptableResource(\"delete\")) {\n this.delete(\n path + \"/:id\",\n options.replace?.delete || bindResourceHandler(routeResource, \"delete\"),\n {\n ...options,\n name: \"delete\",\n restful: true,\n },\n );\n }\n\n if (routeResource.bulkDelete && isAcceptableResource(\"delete\")) {\n this.delete(\n path,\n options.replace?.bulkDelete || bindResourceHandler(routeResource, \"bulkDelete\"),\n {\n ...options,\n name: \"bulkDelete\",\n restful: true,\n },\n );\n }\n\n return this;\n });\n }\n\n /**\n * Group routes with options\n */\n public group(options: GroupedRoutesOptions, callback: RouterGroupCallback) {\n const {\n prefix,\n // name must always be defined because\n // if there are multiple groups without name\n // they might generate the same route name\n // thus causing an error\n // in this case we need always to make sure that\n // the name is always defined.\n name = prefix ? trim(prefix.replace(/\\//g, \".\"), \".\") : undefined,\n middleware,\n public: isPublic,\n } = options;\n\n if (isPublic) {\n this.stacks.public = (this.stacks.public ?? 0) + 1;\n }\n\n if (prefix) {\n this.stacks.prefix.push(prefix);\n }\n\n if (name) {\n this.stacks.name.push(name);\n }\n\n if (middleware) {\n this.stacks.middleware.push(...middleware);\n }\n\n try {\n callback();\n } finally {\n // Always pop/splice this group's stacks, even if the callback throws,\n // so a throwing group never leaks state onto the process-global singleton.\n if (prefix) {\n this.stacks.prefix.pop();\n }\n\n if (name) {\n this.stacks.name.pop();\n }\n\n if (middleware) {\n this.stacks.middleware.splice(\n this.stacks.middleware.length - middleware.length,\n middleware.length,\n );\n }\n if (isPublic) {\n this.stacks.public = (this.stacks.public ?? 1) - 1;\n }\n }\n\n return this;\n }\n\n /**\n * Add prefix to all routes in the given callback\n */\n public prefix(prefix: string, callback: () => void) {\n return this.group({ prefix }, callback);\n }\n\n /**\n * Add API version prefix to all routes in the given callback\n * Automatically formats the version as /v{version}\n * @example\n * router.version(\"1\", () => {\n * router.get(\"/users\", getUsersV1); // /v1/users\n * });\n *\n * router.version(\"2\", () => {\n * router.get(\"/users\", getUsersV2); // /v2/users\n * });\n */\n public version(version: string | number, callback: () => void) {\n return this.prefix(`/v${version}`, callback);\n }\n\n /**\n * Wrap route additions with a source file path\n * Used for tracking which routes come from which file (for HMR)\n * @param sourceFile Relative path to the source file (e.g., \"src/app/users/routes.ts\")\n * @param callback Function that adds routes (will have sourceFile injected)\n */\n public async withSourceFile<T = any>(\n sourceFile: string,\n callback: () => T | Promise<T>,\n ): Promise<T | undefined> {\n // Set source file in stacks\n this.stacks.sourceFile = sourceFile;\n\n try {\n // Execute callback (routes added here will have sourceFile injected)\n return await callback();\n } catch (error) {\n // Log loudly, then RETHROW. Swallowing here silently dropped a whole\n // route file (duplicate name, invalid action, import error) so the\n // surface 404'd with no boot error. The caller decides what a throw\n // means: at boot it aborts the boot, during HMR the dev server's\n // batch-reload handler catches it, prints it, and keeps serving.\n log.error({\n module: \"router\",\n action: \"registerRoutes\",\n message: `Failed to register routes from \"${sourceFile}\"`,\n context: { sourceFile, error },\n });\n\n throw error;\n } finally {\n // Always clear source file from stacks so a throwing file never leaks\n // its sourceFile onto the process-global singleton.\n delete this.stacks.sourceFile;\n }\n }\n\n /**\n * Remove all routes that belong to a specific source file\n * Used when reloading routes files via HMR\n * @param sourceFile Relative path to the source file\n */\n public removeRoutesBySourceFile(sourceFile: string): void {\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.routes = this.routeReplacementTransaction.routes.filter(\n (route) => route.sourceFile !== sourceFile,\n );\n this.routeReplacementTransaction.removedSourceFiles.add(sourceFile);\n return;\n }\n\n this.routes = this.routes.filter((route) => route.sourceFile !== sourceFile);\n\n // Keep the signature diagnostics in step, so a reload that fixes a handler\n // stops reporting the version it replaced.\n forgetPositionalHandlerSuspects(sourceFile);\n\n this.routesVersion++;\n }\n\n /**\n * Atomically replace every route owned by the given exact source-file keys.\n *\n * The installer mutates an isolated draft. Public reads keep seeing the old\n * live table across awaits, and a failed installer discards every draft\n * mutation. A successful installer swaps the whole draft in one synchronous\n * commit and invalidates the dev route registry exactly once.\n */\n public async replaceRoutesBySourceFiles<T>(\n sourceFiles: Iterable<string>,\n install: () => T | Promise<T>,\n ): Promise<T> {\n if (this.routeReplacementTransaction) {\n throw new Error(\"Cannot nest route replacement transactions\");\n }\n\n const removedSourceFiles = new Set(sourceFiles);\n\n this.routeReplacementTransaction = {\n routes: this.routes.filter((route) => !removedSourceFiles.has(route.sourceFile)),\n removedSourceFiles,\n addedRoutes: [],\n };\n\n try {\n const result = await install();\n const transaction = this.routeReplacementTransaction!;\n\n this.routes = transaction.routes;\n this.routeReplacementTransaction = undefined;\n\n // Diagnostic state follows the same commit boundary as the route table.\n // Added routes were deliberately not inspected while still speculative.\n for (const sourceFile of transaction.removedSourceFiles) {\n forgetPositionalHandlerSuspects(sourceFile);\n }\n\n const committedRoutes = new Set(transaction.routes);\n\n for (const route of transaction.addedRoutes) {\n if (committedRoutes.has(route)) {\n inspectHandlerSignature(route.handler, route);\n }\n }\n\n if (this.warnUnguardedRoutes) {\n reportUnguardedMutatingRoutes(\n transaction.addedRoutes.filter((route) => committedRoutes.has(route)),\n );\n }\n\n this.routesVersion++;\n\n return result;\n } catch (error) {\n this.routeReplacementTransaction = undefined;\n throw error;\n }\n }\n\n /**\n * Manage validation system for the given resource\n */\n private manageValidation(resource: RouteResource, method: \"create\" | \"update\" | \"patch\") {\n const handler = bindResourceHandler(resource, method);\n\n const methodValidation = resource?.validation?.[method];\n\n if (method === \"patch\") {\n handler.validation = methodValidation;\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n\n if (resource.validation?.patch) {\n handler.validation = merge(resource.validation.patch, handler.validation);\n }\n\n return handler;\n }\n\n if (!resource.validation || (!methodValidation && !resource.validation.all)) return handler;\n\n if (resource.validation.all) {\n const validationMethods = {\n all: resource?.validation?.all?.validate,\n [method]: methodValidation?.validate,\n };\n\n const validation: RequestHandlerValidation = {};\n\n if (resource.validation.all.schema || methodValidation?.schema) {\n if (!methodValidation?.schema && resource.validation.all.schema) {\n // Case 2: Only all.schema exists - clone it for this method\n validation.schema = resource.validation.all.schema;\n } else if (methodValidation?.schema && resource.validation.all.schema) {\n // Case 3: Both exist - merge them (all is base, method overrides)\n validation.schema = resource.validation.all.schema.merge(methodValidation.schema);\n } else if (methodValidation?.schema && !resource.validation.all.schema) {\n // Case 1: Only method schema exists - use it directly\n validation.schema = methodValidation.schema;\n }\n // Case 4: Neither exists - handled by outer if being false\n }\n\n if (validationMethods.all || validationMethods[method]) {\n validation.validate = async ({ request, response }: HttpContext) => {\n if (validationMethods.all) {\n const output = await validationMethods.all.call(resource, { request, response });\n\n if (output) return output;\n }\n\n if (validationMethods[method]) {\n return await validationMethods[method]?.call(resource, { request, response });\n }\n\n return;\n };\n }\n\n if (!isEmpty(validation)) {\n handler.validation = validation;\n }\n } else {\n handler.validation = resource.validation[method];\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n }\n\n return handler;\n }\n\n /**\n * Get all routes list\n */\n public list() {\n return this.routes;\n }\n\n /**\n * Return fresh browser-safe snapshots of named, non-page routes.\n *\n * Names are the registered names, including method suffixes. `all` stays\n * intact as route metadata; consumers decide whether their use supports it.\n * A route that opted out with `clientVisible: false` carries that flag, and\n * only that flag, so browser-facing consumers can leave it out.\n */\n public getNamedApiRoutes(options: NamedApiRoutesOptions = {}): readonly NamedApiRoute[] {\n return Object.freeze(\n this.routes\n .filter((route) => !route.isPage && typeof route.name === \"string\" && route.name.length > 0)\n .map((route) => {\n // `clientVisible: false` is the only key this flag ever adds, and it is\n // present only then: the common snapshot keeps its exact shape.\n const snapshot = {\n name: route.name!,\n path: route.path,\n method: route.method,\n ...(route.clientVisible === false ? { clientVisible: false as const } : {}),\n };\n const schema = route.handler?.responseSchema;\n const response = schema ? options.resolveResponse?.(schema, route) : undefined;\n\n // A route without a declared schema keeps exactly the three-key record.\n if (!response || Object.keys(response).length === 0) return Object.freeze(snapshot);\n\n return Object.freeze({ ...snapshot, response: Object.freeze({ ...response }) });\n }),\n );\n }\n\n /**\n * Number of routes currently registered on the singleton.\n *\n * Used as a readiness signal: a successful boot that ends up with zero\n * routes almost always means a route module failed to register silently\n * (see `withSourceFile`), so callers can surface that instead of letting\n * the whole surface 404.\n */\n public routeCount(): number {\n return this.routes.length;\n }\n\n /**\n * Register routes to the server\n */\n public scan(server: FastifyInstance) {\n // Every route is registered by the time we scan, so this is the first\n // moment the whole list can be reported at once — and it is still before a\n // single request is served.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n // Fastify automatically exposes HEAD for every GET route. When an app\n // declares its own HEAD route at the same path, suppress only that GET\n // route's implicit registration so the explicit handler owns HEAD,\n // regardless of declaration order.\n const explicitHeadPaths = new Set(\n this.routes\n .filter((route) => route.method.toLowerCase() === \"head\")\n .map((route) => route.path),\n );\n\n this.routes.forEach((route) => {\n const requestMethod = route.method.toLowerCase();\n const requestMethodFunction = server[requestMethod].bind(server);\n\n const options = {\n ...route.serverOptions,\n ...(requestMethod === \"get\" &&\n explicitHeadPaths.has(route.path) && { exposeHeadRoute: false }),\n config: {\n ...route.serverOptions?.config,\n // `undefined` keeps the global policy; `false` exempts the route.\n rateLimit: resolveRouteRateLimit(route),\n },\n };\n\n requestMethodFunction(\n route.path,\n options,\n async (baseRequest: FastifyRequest, reply: FastifyReply) => {\n const { output, response } = await this.handleRoute(route)(baseRequest, reply);\n\n return output || response.baseResponse;\n },\n );\n });\n\n this.registerStaticDirectories(server);\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Scan routes for the development server\n * Uses wildcard routing with find-my-way for HMR support\n */\n public scanDevServer(server: FastifyInstance) {\n // Same boot-time report as production `scan`. Routes reloaded later by HMR\n // are still recorded, but only surface on the next scan or via\n // `warlock doctor`.\n reportPositionalHandlerSuspects();\n\n // Development only: list mutating routes nobody guarded. Routes added by a\n // later HMR reload are reported when their replacement commits.\n this.warnUnguardedRoutes =\n configAccessor.key<boolean>(\"http.warnUnguardedRoutes\", true) !== false;\n if (this.warnUnguardedRoutes) {\n reportUnguardedMutatingRoutes(this.routes);\n }\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n let routeRegistry: RouteRegistry | undefined;\n let registryVersion = -1;\n\n const applyDevRateLimit = createDevRateLimiter(\n server,\n config.get(\"http.rateLimit.global\") !== false,\n );\n\n // Rebuilt when the route table changes rather than per request. Building it\n // inside the handler re-registered every route on every hit — and `all`\n // routes expand into seven registrations each. Keyed on `routesVersion`\n // rather than on the route count, so an HMR reload that swaps a route\n // without changing the count still invalidates.\n const resolveRouteRegistry = () => {\n if (routeRegistry && registryVersion === this.routesVersion) {\n return routeRegistry;\n }\n\n routeRegistry = new RouteRegistry();\n\n routeRegistry.register(this.routes);\n\n registryVersion = this.routesVersion;\n\n return routeRegistry;\n };\n\n // Matching happens here, in Fastify's own onRequest phase, so a route's\n // `serverOptions.onRequest` still runs BEFORE body parsing. Production\n // `scan()` gets that from per-route registration; the wildcard dispatcher\n // has no per-route slot, so without this the hook silently never runs.\n server.addHook(\n \"onRequest\",\n async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n // Only requests Fastify routed to the dev wildcard belong to the app's\n // router. A plugin's own route (devtools' `/__warlock/api/requests/:id`)\n // must keep its params even when an app catch-all matches the URL too.\n if (fastifyRequest.routeOptions.url !== \"*\") {\n return undefined;\n }\n\n const match = resolveRouteRegistry().find(fastifyRequest.method, fastifyRequest.url);\n\n if (!match) {\n return undefined;\n }\n\n (fastifyRequest as DevDispatchRequest).matchedDevRoute = match;\n\n fastifyRequest.params = match.params;\n\n // Same slot production gives it: the limiter runs first in onRequest,\n // ahead of the route's own hooks.\n await applyDevRateLimit(match.route, fastifyRequest, fastifyReply);\n\n if (fastifyReply.sent) {\n return fastifyReply;\n }\n\n return runRouteHooks(match.route, \"onRequest\", fastifyRequest, fastifyReply);\n },\n );\n\n for (const phase of FORWARDED_ROUTE_PHASES.filter((name) => name !== \"onRequest\")) {\n server.addHook(phase, async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n if (!match) {\n return undefined;\n }\n\n return runRouteHooks(match.route, phase, fastifyRequest, fastifyReply);\n });\n }\n\n // Shared handler for wildcard routing\n const wildcardHandler = async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n // No match found - return 404\n if (!match) {\n return fastifyReply\n .code(404)\n .send(buildNotFoundBody(fastifyRequest.method, fastifyRequest.url));\n }\n\n try {\n // Call the matched route handler\n const { output, response } = await this.handleRoute(match.route)(\n fastifyRequest,\n fastifyReply,\n );\n\n return output || response.baseResponse;\n } catch (error) {\n log.error(\"router\", \"dev-dispatch\", error as Error, {\n method: fastifyRequest.method,\n url: buildRequestLogSnapshot({\n id: fastifyRequest.id,\n method: fastifyRequest.method,\n url: fastifyRequest.url,\n route: match.route,\n }).url,\n });\n\n throw error;\n }\n };\n\n // Every verb production can register. Fastify's implicit HEAD for the GET\n // wildcard is switched off so the HEAD wildcard owns it (the registry falls\n // back to the GET route, exactly as production's implicit HEAD does).\n // `@fastify/cors` registers its own OPTIONS \"*\" catch-all whenever\n // preflight is on. Use the plugin's options as well as hasRoute so this\n // dispatcher works both after HTTP plugin boot and before queued plugins.\n const corsOwnsPreflight = buildCorsOptions().preflight !== false;\n\n for (const method of DEV_DISPATCH_METHODS) {\n if (\n method === \"OPTIONS\" &&\n (corsOwnsPreflight || server.hasRoute({ method: \"OPTIONS\", url: \"*\" }))\n ) {\n continue;\n }\n\n server.route({\n method,\n url: \"*\",\n ...(method === \"GET\" && { exposeHeadRoute: false }),\n // The dispatcher limits each matched route itself (see\n // `createDevRateLimiter`), so the plugin must not limit the wildcard:\n // that would count exempt routes and unmatched URLs too.\n config: { rateLimit: false },\n handler: wildcardHandler,\n });\n }\n\n this.registerStaticDirectories(server);\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Get the route path for the given route name\n */\n public getRoute(name: string, params: Record<string, any> = {}) {\n const route = this.routes.find((route) => route.name === name);\n\n if (!route) {\n throw new WarlockError(\"WL3003\", `Route name \"${name}\" not found`);\n }\n\n const used = new Set<string>();\n\n const path = route.path.replace(/:([A-Za-z0-9_]+)(\\?)?/g, (_match, key: string, optional) => {\n const value = params[key];\n\n if (value === undefined || value === null) {\n if (optional) return \"\";\n\n throw new WarlockError(\"WL3004\", `Route \"${name}\" is missing the \"${key}\" param`);\n }\n\n used.add(key);\n\n return encodeURIComponent(String(value));\n });\n\n const query = Object.keys(params)\n .filter((key) => !used.has(key) && params[key] !== undefined && params[key] !== null)\n .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`)\n .join(\"&\");\n\n return query ? `${path}?${query}` : path;\n }\n\n /**\n * Handle the given route\n */\n private handleRoute(route: Route) {\n return async (fastifyRequest: FastifyRequest, fastifyResponse: FastifyReply) => {\n const request = new Request();\n const response = new Response();\n response.setResponse(fastifyResponse);\n request.response = response;\n\n response.request = request;\n\n // \"route.match\" tracing phase: the pattern is\n // already known here (`route.path`), so this brackets the cost of\n // stamping the request/route onto the `Request` instance rather than\n // route-registry matching itself, which happens upstream of this\n // function for both the production `scan()` path and the dev wildcard\n // dispatcher (both funnel into `handleRoute`).\n const tracingEnabled = isTracingEnabled();\n const routeMatchStartedAt = tracingEnabled ? performance.now() : 0;\n\n request.setRequest(fastifyRequest).setRoute(route);\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(request), {\n name: \"route.match\",\n durationMs: performance.now() - routeMatchStartedAt,\n attrs: { route: route.path },\n });\n }\n\n /*\n Both ends of the entry, not just the start — see\n `log-request-lifecycle.ts`. A start line on its own made a hung request\n and an instant one look identical, and the completion line is what makes\n a MISSING one legible as a hang.\n */\n const result = await logRequestLifecycle(\n {\n info: (entry) => log.info(entry),\n warn: (entry) => log.warn(entry),\n error: (entry) => log.error(entry),\n now: () => performance.now(),\n },\n {\n module: \"route\",\n action: describeRouteForLog(route.method, route.path),\n requestId: request.id,\n // A bounded snapshot, built once with the route's logging policy\n // applied — never the Request/Response instances, whose native\n // request, reply and URL graphs would reach every log channel.\n context: { request: request.toLogSnapshot() },\n request,\n // Read after the run: the status is decided during the request.\n statusCode: () => fastifyResponse.statusCode,\n },\n () => request.execute(),\n );\n\n // A handler may return a plain object (`ReturnedResponse`). Route it\n // through `Response.send` so status, events and hooks apply, exactly as\n // middleware outputs already do.\n let output = result;\n\n if (\n output !== undefined &&\n output !== null &&\n !(output instanceof Response) &&\n (output as unknown) !== fastifyResponse\n ) {\n output = await response.send(output);\n }\n\n return {\n output,\n response,\n request,\n };\n };\n }\n}\n\nexport const router = Router.getInstance();\n\ncontainer.set(\"router\", router);\n"],"mappings":"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"}
@@ -323,6 +323,20 @@ export interface RouteOptions {
323
323
  * router.post("/auth/login", loginController, { public: true });
324
324
  */
325
325
  public?: boolean;
326
+ /**
327
+ * Whether this named API route may be embedded in a page for browser use.
328
+ *
329
+ * Defaults to `true`. Set `false` for an endpoint that must never be
330
+ * reachable through `apiHref()` or `useSubmitForm({ route })` from client
331
+ * code (an internal webhook, an admin-only job trigger). Such a route is never
332
+ * written into any page's browser route table, and a client reference to its
333
+ * name is a `warlock check` error (`WL9020`). Server-side authorization still
334
+ * decides who may call the endpoint.
335
+ *
336
+ * @example
337
+ * router.post("/internal/reindex", reindexController, { name: "internal.reindex", clientVisible: false });
338
+ */
339
+ clientVisible?: boolean;
326
340
  }
327
341
  export type RequestMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD" | "all";
328
342
  /** Browser-safe snapshot metadata for a named application API route. */
@@ -337,6 +351,11 @@ export type NamedApiRoute = Readonly<{
337
351
  * (paths relative to `.warlock/typings/`), object literal types and arrays.
338
352
  */
339
353
  response?: Readonly<Record<string, string>>;
354
+ /**
355
+ * Present only as `false`, and only on a route that opted out with
356
+ * `clientVisible: false`. Absent means the route may be embedded in pages.
357
+ */
358
+ clientVisible?: false;
340
359
  }>;
341
360
  /** Options for `router.getNamedApiRoutes()`. */
342
361
  export type NamedApiRoutesOptions = Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/router/types.ts"],"mappings":";;;;;;;;;;;;;YAUY,qBAAqB;;;;;;;YAQrB,WAAW,0BAA0B,UAAU;GACxD,SAAS,YAAY,qBAAqB;;YAGjC;YAEA,qBAAqB,uBAAuB;;;;YAK5C;YAEA,oBAAoB,gBAAgB;YAEpC,yBAAyB,iBAAiB,UAAU;;;;EAI9D,WAAW,WAAW;;;;;;EAMtB;;;;EAIA,SAAS;;;;;;;;EAQT,SAAS;;;;;;;;;;;;;;;;;;;EAmBT,YAAY,yBAAyB;;;;;YAM3B;;;;EAIV;;;;EAIA;;;;;YAMU,yBAAyB,iBAAiB,UAAU;EAC9D,SAAS;EACT,UAAU;;;;EAIV,OAAO;;;;;YAMG,yBAAyB,iBAAiB,UAAU,YAC9D,QAAQ,kBACR,SAAS,yBAAyB,cAEhC,+CAGA,QAAQ;iBAEK;;;;EAIf,SAAS;;;;EAIT,UAAU;;;;EAIV,WAAW,QAAQ;;;;EAInB;;;;EAIA,mBAAmB,QAAQ,sBAAsB;;;;;;;;;YAUvC,YAAY,iBAAiB,UAAU;;;;EAIjD,SAAS;;;;EAIT,UAAU;;YAGA,eAAe,iBAAiB,UAAU;;;;GAInD,SAAS,YAAY,YAAY;;;;EAKlC,aAAa,yBAAyB;;;;EAKtC;;;;;EAMA,iBAAiB;;;;;;YAOP;;;;EAIV;;;;EAIA;;;;EAIA;;;;;;YAOU;;;;;;;EAOV,YAAY;;iBAGG;;;;EAIf,aAAa;;;;EAIb;;;;EAIA;;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BA,gBAAgB;;;;;;EAMhB;;;;EAIA;;;;EAIA;;;;;;;;;;;;;;;;EAgBA,YAAY;;;;;;;;;;;;;;;;;;;;;;;EAuBZ;;;;;;;;;;;;;;EAcA,aAAa;;;;;;;;;;;EAWb,UAAU;;;;;;;;;;;;EAYV;;YAGU;;YAWA,gBAAgB;EAC1B;EACA;EACA,QAAQ;;;;;;;EAOR,WAAW,SAAS;;;YAIV,wBAAwB;;;;;EAKlC,mBACE,QAAQ,gBACR,OAAO,UACJ,SAAS;;;;;YAMJ,QAAQ;;;;EAIlB,QAAQ;;;;EAIR;;;;EAIA;;;;;EAKA,SAAS;;;;;;EAMT;;;;;;EAMA;;;;EAIA,YAAY;;;;;;EAMZ;;YAGU,YAAY,GAAG,gBAAgB,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG;;;;YAKjE;;;;EAIV,aAAa;;;;EAIb;;;;;EAKA;;;;;;EAMA;;;YAIU;;;;EAIV,OAAO;;;;EAIP,MAAM;;;;EAIN,SAAS;;;;EAIT,SAAS;;;;EAIT,QAAQ;;;;EAIR,SAAS;;;;EAIT,aAAa;;;;EAIb;;;;IAIE,MAAM;;;;IAIN,SAAS;;;;IAIT,SAAS;;;;IAIT,QAAQ;;;YAIA;EACV;EACA;EACA,YAAY;;;;;EAKZ;;;;EAIA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/router/types.ts"],"mappings":";;;;;;;;;;;;;YAUY,qBAAqB;;;;;;;YAQrB,WAAW,0BAA0B,UAAU;GACxD,SAAS,YAAY,qBAAqB;;YAGjC;YAEA,qBAAqB,uBAAuB;;;;YAK5C;YAEA,oBAAoB,gBAAgB;YAEpC,yBAAyB,iBAAiB,UAAU;;;;EAI9D,WAAW,WAAW;;;;;;EAMtB;;;;EAIA,SAAS;;;;;;;;EAQT,SAAS;;;;;;;;;;;;;;;;;;;EAmBT,YAAY,yBAAyB;;;;;YAM3B;;;;EAIV;;;;EAIA;;;;;YAMU,yBAAyB,iBAAiB,UAAU;EAC9D,SAAS;EACT,UAAU;;;;EAIV,OAAO;;;;;YAMG,yBAAyB,iBAAiB,UAAU,YAC9D,QAAQ,kBACR,SAAS,yBAAyB,cAEhC,+CAGA,QAAQ;iBAEK;;;;EAIf,SAAS;;;;EAIT,UAAU;;;;EAIV,WAAW,QAAQ;;;;EAInB;;;;EAIA,mBAAmB,QAAQ,sBAAsB;;;;;;;;;YAUvC,YAAY,iBAAiB,UAAU;;;;EAIjD,SAAS;;;;EAIT,UAAU;;YAGA,eAAe,iBAAiB,UAAU;;;;GAInD,SAAS,YAAY,YAAY;;;;EAKlC,aAAa,yBAAyB;;;;EAKtC;;;;;EAMA,iBAAiB;;;;;;YAOP;;;;EAIV;;;;EAIA;;;;EAIA;;;;;;YAOU;;;;;;;EAOV,YAAY;;iBAGG;;;;EAIf,aAAa;;;;EAIb;;;;EAIA;;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BA,gBAAgB;;;;;;EAMhB;;;;EAIA;;;;EAIA;;;;;;;;;;;;;;;;EAgBA,YAAY;;;;;;;;;;;;;;;;;;;;;;;EAuBZ;;;;;;;;;;;;;;EAcA,aAAa;;;;;;;;;;;EAWb,UAAU;;;;;;;;;;;;EAYV;;;;;;;;;;;;;;EAcA;;YAGU;;YAWA,gBAAgB;EAC1B;EACA;EACA,QAAQ;;;;;;;EAOR,WAAW,SAAS;;;;;EAKpB;;;YAIU,wBAAwB;;;;;EAKlC,mBACE,QAAQ,gBACR,OAAO,UACJ,SAAS;;;;;YAMJ,QAAQ;;;;EAIlB,QAAQ;;;;EAIR;;;;EAIA;;;;;EAKA,SAAS;;;;;;EAMT;;;;;;EAMA;;;;EAIA,YAAY;;;;;;EAMZ;;YAGU,YAAY,GAAG,gBAAgB,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG;;;;YAKjE;;;;EAIV,aAAa;;;;EAIb;;;;;EAKA;;;;;;EAMA;;;YAIU;;;;EAIV,OAAO;;;;EAIP,MAAM;;;;EAIN,SAAS;;;;EAIT,SAAS;;;;EAIT,QAAQ;;;;EAIR,SAAS;;;;EAIT,aAAa;;;;EAIb;;;;IAIE,MAAM;;;;IAIN,SAAS;;;;IAIT,SAAS;;;;IAIT,QAAQ;;;YAIA;EACV;EACA;EACA,YAAY;;;;;EAKZ;;;;EAIA"}