@warlock.js/core 5.25.0 → 5.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +5 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/openapi-generator.action.mjs +43 -0
- package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
- package/esm/cli/commands/openapi-generator.command.mjs +44 -0
- package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
- package/esm/cli/commands/typings-generator.command.mjs +4 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/framework-cli-commands.mjs +2 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +2 -2
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/dev-server/build-development-openapi-document.mjs +36 -0
- package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
- package/esm/dev-server/collect-development-api-routes.mjs +27 -0
- package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs +9 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +6 -3
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +17 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/legacy-locales-warning.mjs +16 -0
- package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
- package/esm/dev-server/model-resource-typings.mjs +246 -0
- package/esm/dev-server/model-resource-typings.mjs.map +1 -0
- package/esm/dev-server/module-loader.mjs +16 -0
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/special-file-patterns.mjs +2 -2
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +7 -3
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +73 -6
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +38 -18
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +2 -2
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +8 -2
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs +1 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +1 -17
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -20
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +4 -3
- package/esm/http/index.mjs +1 -1
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/request.d.mts +9 -26
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +1 -22
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +4 -2
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/serialized.d.mts +114 -0
- package/esm/http/serialized.d.mts.map +1 -0
- package/esm/index.d.mts +12 -5
- package/esm/index.mjs +8 -2
- package/esm/localization/index.mjs +4 -0
- package/esm/localization/parse-locale-dictionary.d.mts +23 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/parse-locale-dictionary.mjs +215 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
- package/esm/localization/register-locale-dictionary.d.mts +12 -0
- package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/register-locale-dictionary.mjs +58 -0
- package/esm/localization/register-locale-dictionary.mjs.map +1 -0
- package/esm/openapi/build-openapi-document.d.mts +17 -0
- package/esm/openapi/build-openapi-document.d.mts.map +1 -0
- package/esm/openapi/build-openapi-document.mjs +124 -0
- package/esm/openapi/build-openapi-document.mjs.map +1 -0
- package/esm/openapi/cast-schema.mjs +89 -0
- package/esm/openapi/cast-schema.mjs.map +1 -0
- package/esm/openapi/collect-openapi-document.mjs +40 -0
- package/esm/openapi/collect-openapi-document.mjs.map +1 -0
- package/esm/openapi/development-openapi-provider.d.mts +17 -0
- package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
- package/esm/openapi/development-openapi-provider.mjs +21 -0
- package/esm/openapi/development-openapi-provider.mjs.map +1 -0
- package/esm/openapi/error-responses.mjs +67 -0
- package/esm/openapi/error-responses.mjs.map +1 -0
- package/esm/openapi/index.d.mts +3 -0
- package/esm/openapi/index.mjs +4 -0
- package/esm/openapi/openapi-child-output.mjs +57 -0
- package/esm/openapi/openapi-child-output.mjs.map +1 -0
- package/esm/openapi/openapi-protocol.mjs +30 -0
- package/esm/openapi/openapi-protocol.mjs.map +1 -0
- package/esm/openapi/paths.mjs +63 -0
- package/esm/openapi/paths.mjs.map +1 -0
- package/esm/openapi/request-schemas.mjs +138 -0
- package/esm/openapi/request-schemas.mjs.map +1 -0
- package/esm/openapi/response-schemas.mjs +85 -0
- package/esm/openapi/response-schemas.mjs.map +1 -0
- package/esm/openapi/schema-registry.mjs +143 -0
- package/esm/openapi/schema-registry.mjs.map +1 -0
- package/esm/openapi/security.mjs +98 -0
- package/esm/openapi/security.mjs.map +1 -0
- package/esm/openapi/types.d.mts +119 -0
- package/esm/openapi/types.d.mts.map +1 -0
- package/esm/production/production-builder.mjs +11 -2
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/registration-child-runner.mjs +139 -0
- package/esm/production/registration-child-runner.mjs.map +1 -0
- package/esm/production/route-registration-child.mjs +30 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +22 -119
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/define-resource.d.mts +44 -14
- package/esm/resource/define-resource.d.mts.map +1 -1
- package/esm/resource/define-resource.mjs +0 -40
- package/esm/resource/define-resource.mjs.map +1 -1
- package/esm/resource/index.d.mts +1 -1
- package/esm/resource/resource.d.mts +4 -4
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/resource/types.d.mts +89 -4
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/restful/restful.d.mts +5 -5
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/router/index.d.mts +1 -1
- package/esm/router/named-api-routes-with-responses.mjs +59 -0
- package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
- package/esm/router/response-schema-types.mjs +100 -0
- package/esm/router/response-schema-types.mjs.map +1 -0
- package/esm/router/router.d.mts +2 -2
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +38 -11
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +24 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.d.mts +10 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +16 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +48 -1
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +383 -80
- package/llms.txt +4 -3
- package/package.json +21 -21
- package/skills/README.md +4 -3
- package/skills/create-controller/SKILL.md +43 -2
- package/skills/define-resource/SKILL.md +100 -1
- package/skills/generate-openapi/SKILL.md +120 -0
- package/skills/index.md +3 -3
- package/skills/test-http/SKILL.md +9 -1
- package/skills/use-localization/SKILL.md +39 -26
- package/skills/validate-input/SKILL.md +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { moduleResolve } from \"import-meta-resolve\";\nimport { join, resolve } from \"path\";\nimport { pathToFileURL } from \"node:url\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { extractTranslationKeys } from \"./extract-translation-keys\";\nimport { isTranslationRegisteringSource } from \"./translation-keys-sources\";\n\ntype RouteLocaleBuildModule = {\n listRouteLocaleKeys?: (options: {\n appRoot?: string;\n }) => readonly string[] | Promise<readonly string[]>;\n};\n\nconst WEB_PACKAGE = \"@warlock.js/web\";\nconst WEB_BUILD_PACKAGE = \"@warlock.js/web/build\";\n\nfunction isAbsentOptionalWebPackage(thrown: unknown): boolean {\n return (\n typeof thrown === \"object\" &&\n thrown !== null &&\n \"code\" in thrown &&\n (thrown as { code?: unknown }).code === \"ERR_MODULE_NOT_FOUND\" &&\n thrown instanceof Error &&\n thrown.message.includes(`Cannot find package '${WEB_PACKAGE}'`)\n );\n}\n\n/** Load the build-only web key lister without creating a core-to-web static edge. */\nexport async function listRouteLocaleKeysForTypeGeneration(\n appRoot = process.cwd(),\n): Promise<readonly string[]> {\n // Published web entries are import-only. Resolve with ESM conditions from\n // the consuming project; require.resolve rejects those installed exports.\n const projectUrl = pathToFileURL(resolve(appRoot, \"package.json\"));\n\n try {\n moduleResolve(WEB_PACKAGE, projectUrl);\n } catch (thrown) {\n if (isAbsentOptionalWebPackage(thrown)) return [];\n throw thrown;\n }\n\n const buildModuleUrl = moduleResolve(WEB_BUILD_PACKAGE, projectUrl);\n const buildModule = (await import(buildModuleUrl.href)) as RouteLocaleBuildModule;\n\n if (typeof buildModule.listRouteLocaleKeys !== \"function\") {\n throw new Error(\n `${WEB_BUILD_PACKAGE} does not export listRouteLocaleKeys. Upgrade @warlock.js/web to a compatible version.`,\n );\n }\n\n return buildModule.listRouteLocaleKeys({ appRoot });\n}\n\n/** True only for the route-locales convention under the canonical web root. */\nexport function isRouteLocalesJsonPath(path: string): boolean {\n const normalized = Path.normalize(path);\n const relativePath = normalized.startsWith(\"src/\") ? normalized : Path.toRelative(normalized);\n return /^src\\/web(?:\\/.*)?\\/locales\\.json$/.test(relativePath);\n}\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n const translationsFile = join(this.outputDir, \"translations.d.ts\");\n\n const [manifestExists, storageExists, configExists, translationsExist] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n this.exists(translationsFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists || !translationsExist) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.generateTranslationTypes();\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n this.configCache.clear();\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.split(\"/\").pop()?.replace(/[.][^.]+$/, \"\") === \"index\") continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"⚠️ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys\n .map((k) => ` ${this.toInterfaceKey(k)}: true;`)\n .join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`⚠️ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return (\n changedPath.includes(\"src/config/\") ||\n changedPath.includes(\"config/\") ||\n this.isLocalesFile(changedPath) ||\n isRouteLocalesJsonPath(changedPath)\n );\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n if (this.isLocalesFile(changedPath) || isRouteLocalesJsonPath(changedPath)) {\n await this.generateTranslationTypes();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (!configName || configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.split(\"/\").pop()?.replace(/[.][^.]+$/, \"\") === \"index\") continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`⚠️ Failed to generate config types: ${error}`);\n }\n }\n\n /**\n * Files (relative paths) that contributed to the last generated registry —\n * used to notice a registering file being deleted, since by the time a\n * delete event reaches {@link executeTypingsGenerator} the file is already\n * gone from `filesOrchestrator`'s map and its content can no longer be\n * inspected.\n */\n private translationSourceFiles = new Set<string>();\n\n /**\n * Generate web's app-augmented translation-key registry.\n *\n * Scans every discovered app source file — not only dedicated\n * `utils/locales.*` files — for `groupedTranslations` calls, both the\n * named-group and object forms, at any nesting depth. Dynamic group names,\n * dictionaries, or keys cannot be resolved statically and are skipped; an\n * app relying on those must document them itself.\n */\n private async generateTranslationTypes(): Promise<void> {\n const keys = new Set<string>();\n const sourceFiles = new Set<string>();\n\n for (const [path, fileManager] of filesOrchestrator.getFiles()) {\n if (!isTranslationRegisteringSource(fileManager.source)) {\n continue;\n }\n\n sourceFiles.add(path);\n\n const sourceFile = await readConfigAst(fileManager.absolutePath);\n if (sourceFile) {\n for (const key of extractTranslationKeys(sourceFile)) {\n keys.add(key);\n }\n }\n }\n\n for (const key of await listRouteLocaleKeysForTypeGeneration()) {\n keys.add(key);\n }\n\n this.translationSourceFiles = sourceFiles;\n\n const entries = Array.from(keys)\n .sort()\n .map((key) => ` ${JSON.stringify(key)}: true;`)\n .join(\"\\n\");\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from groupedTranslations calls and route-locales JSON in discovered\n// app source. Dynamic group names/keys cannot be resolved statically and are\n// not included here — document those yourself.\n\nimport \"@warlock.js/web\";\n\ndeclare module \"@warlock.js/web\" {\n interface TranslationKeyRegistry {\n${entries}\n }\n}\n`;\n\n await writeFile(join(this.outputDir, \"translations.d.ts\"), content, \"utf-8\");\n devLogSuccess(`Generated translation types: ${keys.size} keys`);\n }\n\n private isLocalesFile(path: string): boolean {\n return Path.normalize(path).includes(\"/utils/locales.\");\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`⚠️ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`⚠️ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch\n * plausibly touched something the typings depend on: a config file, a file\n * that registers translation keys, or a registering file that was deleted.\n *\n * A deleted file is already gone from `filesOrchestrator` by the time this\n * runs (deletes are processed before `dev-server:batch-complete` fires), so\n * its content can't be re-checked here — `translationSourceFiles` is the\n * set from the last generation, kept for exactly this comparison.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const files = filesOrchestrator.getFiles();\n\n const aKnownTranslationSourceVanished = Array.from(this.translationSourceFiles).some(\n (path) => !files.has(path),\n );\n\n const touchedAConfig =\n aKnownTranslationSourceVanished ||\n Array.from(new Set(upcomingFiles)).some((file) => {\n const normalizedPath = Path.normalize(file);\n if (normalizedPath.includes(\"src/config/\")) return true;\n if (isRouteLocalesJsonPath(normalizedPath)) return true;\n if (this.translationSourceFiles.has(normalizedPath)) return true;\n\n const fileManager = files.get(normalizedPath);\n return fileManager !== undefined && isTranslationRegisteringSource(fileManager.source);\n });\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { moduleResolve } from \"import-meta-resolve\";\nimport { join, resolve } from \"path\";\nimport { pathToFileURL } from \"node:url\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogDim, devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { extractTranslationKeys } from \"./extract-translation-keys\";\nimport {\n isModuleLocaleDictionaryPath,\n isTranslationRegisteringSource,\n} from \"./translation-keys-sources\";\nimport { parseLocaleDictionary } from \"../localization\";\nimport { getFilesFromDirectory } from \"./utils\";\nimport {\n extractModelResourceEntries,\n renderModelResourceRegistry,\n writeFileIfChanged,\n type ModelResourceEntry,\n} from \"./model-resource-typings\";\nimport { parseImports } from \"./parse-imports\";\n\ntype RouteLocaleBuildModule = {\n listRouteLocaleKeys?: (options: {\n appRoot?: string;\n }) => readonly string[] | Promise<readonly string[]>;\n};\n\nconst WEB_PACKAGE = \"@warlock.js/web\";\nconst WEB_BUILD_PACKAGE = \"@warlock.js/web/build\";\n\nfunction isAbsentOptionalWebPackage(thrown: unknown): boolean {\n return (\n typeof thrown === \"object\" &&\n thrown !== null &&\n \"code\" in thrown &&\n (thrown as { code?: unknown }).code === \"ERR_MODULE_NOT_FOUND\" &&\n thrown instanceof Error &&\n thrown.message.includes(`Cannot find package '${WEB_PACKAGE}'`)\n );\n}\n\n/** Load the build-only web key lister without creating a core-to-web static edge. */\nexport async function listRouteLocaleKeysForTypeGeneration(\n appRoot = process.cwd(),\n): Promise<readonly string[]> {\n // Published web entries are import-only. Resolve with ESM conditions from\n // the consuming project; require.resolve rejects those installed exports.\n const projectUrl = pathToFileURL(resolve(appRoot, \"package.json\"));\n\n try {\n moduleResolve(WEB_PACKAGE, projectUrl);\n } catch (thrown) {\n if (isAbsentOptionalWebPackage(thrown)) return [];\n throw thrown;\n }\n\n const buildModuleUrl = moduleResolve(WEB_BUILD_PACKAGE, projectUrl);\n const buildModule = (await import(buildModuleUrl.href)) as RouteLocaleBuildModule;\n\n if (typeof buildModule.listRouteLocaleKeys !== \"function\") {\n throw new Error(\n `${WEB_BUILD_PACKAGE} does not export listRouteLocaleKeys. Upgrade @warlock.js/web to a compatible version.`,\n );\n }\n\n return buildModule.listRouteLocaleKeys({ appRoot });\n}\n\n/** True only for the route-locales convention under the canonical web root. */\nexport function isRouteLocalesJsonPath(path: string): boolean {\n const normalized = Path.normalize(path);\n const relativePath = normalized.startsWith(\"src/\") ? normalized : Path.toRelative(normalized);\n return /^src\\/web(?:\\/.*)?\\/locales\\.json$/.test(relativePath);\n}\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n const translationsFile = join(this.outputDir, \"translations.d.ts\");\n\n const [manifestExists, storageExists, configExists, translationsExist] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n this.exists(translationsFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists || !translationsExist) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.generateTranslationTypes();\n await this.generateModelResourceTypes();\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n this.configCache.clear();\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (\n path\n .split(\"/\")\n .pop()\n ?.replace(/[.][^.]+$/, \"\") === \"index\"\n )\n continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"⚠️ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys\n .map((k) => ` ${this.toInterfaceKey(k)}: true;`)\n .join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`⚠️ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return (\n changedPath.includes(\"src/config/\") ||\n changedPath.includes(\"config/\") ||\n this.isLocalesFile(changedPath) ||\n isRouteLocalesJsonPath(changedPath)\n );\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n if (this.isLocalesFile(changedPath) || isRouteLocalesJsonPath(changedPath)) {\n await this.generateTranslationTypes();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (!configName || configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (\n path\n .split(\"/\")\n .pop()\n ?.replace(/[.][^.]+$/, \"\") === \"index\"\n )\n continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`⚠️ Failed to generate config types: ${error}`);\n }\n }\n\n /**\n * Files (relative paths) that contributed to the last generated registry —\n * used to notice a registering file being deleted, since by the time a\n * delete event reaches {@link executeTypingsGenerator} the file is already\n * gone from `filesOrchestrator`'s map and its content can no longer be\n * inspected.\n */\n private translationSourceFiles = new Set<string>();\n\n /**\n * Generate web's app-augmented translation-key registry.\n *\n * Scans every discovered app source file — not only dedicated\n * `utils/locales.*` files — for `groupedTranslations` calls, both the\n * named-group and object forms, at any nesting depth. Dynamic group names,\n * dictionaries, or keys cannot be resolved statically and are skipped; an\n * app relying on those must document them itself.\n */\n private async generateTranslationTypes(): Promise<void> {\n const keys = new Set<string>();\n const sourceFiles = new Set<string>();\n\n for (const [path, fileManager] of filesOrchestrator.getFiles()) {\n if (!isTranslationRegisteringSource(path, fileManager.source)) {\n continue;\n }\n\n sourceFiles.add(path);\n\n if (isModuleLocaleDictionaryPath(path)) {\n const dictionary = parseLocaleDictionary({\n sourceFile: path,\n source: fileManager.source,\n defaultNamespace: path.split(\"/\")[2]!,\n });\n for (const key of Object.keys(dictionary.entries)) {\n keys.add(key);\n }\n continue;\n }\n\n const sourceFile = await readConfigAst(fileManager.absolutePath);\n if (sourceFile) {\n for (const key of extractTranslationKeys(sourceFile)) {\n keys.add(key);\n }\n }\n }\n\n // The dev source map intentionally tracks executable TypeScript only. Module\n // dictionaries are JSON data, so discover them directly without loading app\n // code; use the runtime parser to keep `$group` and flattening identical.\n for (const absolutePath of await getFilesFromDirectory(\n join(process.cwd(), \"src\", \"app\"),\n \"**/utils/locales.json\",\n )) {\n const path = Path.toRelative(absolutePath);\n sourceFiles.add(path);\n const dictionary = parseLocaleDictionary({\n sourceFile: path,\n source: await readFile(absolutePath, \"utf-8\"),\n defaultNamespace: path.split(\"/\")[2]!,\n });\n for (const key of Object.keys(dictionary.entries)) {\n keys.add(key);\n }\n }\n\n for (const key of await listRouteLocaleKeysForTypeGeneration()) {\n keys.add(key);\n }\n\n this.translationSourceFiles = sourceFiles;\n\n const entries = Array.from(keys)\n .sort()\n .map((key) => ` ${JSON.stringify(key)}: true;`)\n .join(\"\\n\");\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from groupedTranslations calls and route-locales JSON in discovered\n// app source. Dynamic group names/keys cannot be resolved statically and are\n// not included here — document those yourself.\n\nimport \"@warlock.js/web\";\n\ndeclare module \"@warlock.js/web\" {\n interface TranslationKeyRegistry {\n${entries}\n }\n}\n`;\n\n await writeFile(join(this.outputDir, \"translations.d.ts\"), content, \"utf-8\");\n devLogSuccess(`Generated translation types: ${keys.size} keys`);\n }\n\n /**\n * Files that contributed to the last `model-resources.d.ts`: the model files\n * and the modules their resources were resolved to. Kept for the same reason\n * as {@link translationSourceFiles}: a deleted file is already gone from the\n * orchestrator when the batch reaches {@link executeTypingsGenerator}.\n */\n private modelResourceSourceFiles = new Set<string>();\n\n /**\n * Generate `model-resources.d.ts`: the `ModelResourceRegistry` augmentation\n * behind `Serialized<Model>`.\n *\n * Reads every discovered model file (`*.model.ts`) and records the ones that\n * declare `static resource = <Identifier>`. Static AST read only, no app\n * module is imported; see `model-resource-typings.ts` for what is skipped.\n * The file is rewritten only when its text changes.\n */\n private async generateModelResourceTypes(): Promise<void> {\n try {\n const entries: ModelResourceEntry[] = [];\n const sourceFiles = new Set<string>();\n const skippedModels: string[] = [];\n\n for (const [path, fileManager] of filesOrchestrator.getFiles()) {\n if (fileManager.type !== \"model\" || !fileManager.source.includes(\"resource\")) continue;\n\n const imports = await parseImports(fileManager.source, fileManager.absolutePath);\n const extraction = extractModelResourceEntries({\n source: fileManager.source,\n absolutePath: fileManager.absolutePath,\n resolveImport: (specifier) => imports.get(specifier)?.absolutePath,\n });\n\n if (extraction.entries.length > 0) sourceFiles.add(path);\n\n for (const entry of extraction.entries) {\n entries.push(entry);\n sourceFiles.add(Path.toRelative(entry.resourceFile));\n }\n\n for (const skipped of extraction.skipped) {\n skippedModels.push(`${skipped.className} (${skipped.reason})`);\n }\n }\n\n this.modelResourceSourceFiles = sourceFiles;\n\n const written = await writeFileIfChanged(\n join(this.outputDir, \"model-resources.d.ts\"),\n renderModelResourceRegistry(entries, this.outputDir),\n );\n\n if (written) devLogSuccess(`Generated model resource types: ${entries.length} models`);\n\n if (skippedModels.length > 0) {\n devLogDim(\n `Model resources not typed (not statically resolvable): ${skippedModels.join(\", \")}`,\n );\n }\n } catch (error) {\n devServeLog(`⚠️ Failed to generate model resource types: ${error}`);\n }\n }\n\n private isLocalesFile(path: string): boolean {\n return Path.normalize(path).includes(\"/utils/locales.\");\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`⚠️ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`⚠️ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch\n * plausibly touched something the typings depend on: a config file, a file\n * that registers translation keys, or a registering file that was deleted.\n *\n * A deleted file is already gone from `filesOrchestrator` by the time this\n * runs (deletes are processed before `dev-server:batch-complete` fires), so\n * its content can't be re-checked here — `translationSourceFiles` is the\n * set from the last generation, kept for exactly this comparison.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const files = filesOrchestrator.getFiles();\n\n const aKnownTranslationSourceVanished = Array.from(this.translationSourceFiles).some(\n (path) => !files.has(path),\n );\n\n const aKnownModelResourceSourceVanished = Array.from(this.modelResourceSourceFiles).some(\n (path) => !files.has(path),\n );\n\n const touchedAConfig =\n aKnownTranslationSourceVanished ||\n aKnownModelResourceSourceVanished ||\n Array.from(new Set(upcomingFiles)).some((file) => {\n const normalizedPath = Path.normalize(file);\n if (normalizedPath.includes(\"src/config/\")) return true;\n if (isRouteLocalesJsonPath(normalizedPath)) return true;\n if (isModuleLocaleDictionaryPath(normalizedPath)) return true;\n if (this.translationSourceFiles.has(normalizedPath)) return true;\n if (this.modelResourceSourceFiles.has(normalizedPath)) return true;\n if (files.get(normalizedPath)?.type === \"model\") return true;\n\n const fileManager = files.get(normalizedPath);\n return (\n fileManager !== undefined &&\n isTranslationRegisteringSource(normalizedPath, fileManager.source)\n );\n });\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
|
@@ -8,29 +8,49 @@ import { ensureDirectoryAsync, fileExistsAsync, getFileAsync, putFileAsync } fro
|
|
|
8
8
|
|
|
9
9
|
//#region ../core/src/generations/features/access.feature.ts
|
|
10
10
|
async function registerAccessLocale() {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
20
|
-
|
|
11
|
+
const legacyLocalesPath = srcPath("app/shared/utils/locales.ts");
|
|
12
|
+
const localesPath = srcPath("app/access/utils/locales.json");
|
|
13
|
+
const forbidden = {
|
|
14
|
+
en: "You do not have permission to perform this action.",
|
|
15
|
+
ar: "ليس لديك صلاحية لتنفيذ هذا الإجراء."
|
|
16
|
+
};
|
|
17
|
+
if (await fileExistsAsync(legacyLocalesPath)) {
|
|
18
|
+
if ((await getFileAsync(legacyLocalesPath)).includes(`groupedTranslations("access"`)) {
|
|
19
|
+
console.log(`${colors.yellowBright("access")} locale already registered, skipping...`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
21
23
|
if (await fileExistsAsync(localesPath)) {
|
|
22
24
|
const current = await getFileAsync(localesPath);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
let dictionary;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(current);
|
|
28
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") throw new Error();
|
|
29
|
+
dictionary = parsed;
|
|
30
|
+
} catch {
|
|
31
|
+
console.log(`${colors.yellowBright("access")} locale JSON is invalid; add errors.forbidden to src/app/access/utils/locales.json manually.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (dictionary.$group !== void 0 && dictionary.$group !== "access") {
|
|
35
|
+
console.log(`${colors.yellowBright("access")} locale JSON declares a different $group; add access.errors.forbidden manually.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const errors = dictionary.errors;
|
|
39
|
+
if (errors !== void 0 && (errors === null || Array.isArray(errors) || typeof errors !== "object")) {
|
|
40
|
+
console.log(`${colors.yellowBright("access")} locale JSON cannot safely add errors.forbidden; update src/app/access/utils/locales.json manually.`);
|
|
25
41
|
return;
|
|
26
42
|
}
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
const entries = errors ?? {};
|
|
44
|
+
if (entries.forbidden !== void 0) return;
|
|
45
|
+
entries.forbidden = forbidden;
|
|
46
|
+
dictionary.errors = entries;
|
|
47
|
+
await putFileAsync(localesPath, `${JSON.stringify(dictionary, null, 2)}\n`);
|
|
48
|
+
console.log(`${colors.green("✓")} Registered the access locale in src/app/access/utils/locales.json`);
|
|
29
49
|
return;
|
|
30
50
|
}
|
|
31
|
-
await ensureDirectoryAsync(srcPath("app/
|
|
32
|
-
await putFileAsync(localesPath,
|
|
33
|
-
console.log(`${colors.green("✓")} Created src/app/
|
|
51
|
+
await ensureDirectoryAsync(srcPath("app/access/utils"));
|
|
52
|
+
await putFileAsync(localesPath, `${JSON.stringify({ errors: { forbidden } }, null, 2)}\n`);
|
|
53
|
+
console.log(`${colors.green("✓")} Created src/app/access/utils/locales.json with the access locale`);
|
|
34
54
|
}
|
|
35
55
|
async function scaffoldAccessFiles() {
|
|
36
56
|
const resolverPath = srcPath("app/access/services/access-resolver.ts");
|
|
@@ -63,7 +83,7 @@ async function completeAccessInstallation(_options) {
|
|
|
63
83
|
await scaffoldAccessFiles();
|
|
64
84
|
}
|
|
65
85
|
const accessFeature = {
|
|
66
|
-
description: "Installs @warlock.js/access — authorization (RBAC + ABAC): permission checks, ABAC policies, and roles. Ejects config/access.ts, the DatabaseAccessResolver + Role/UserRole models and migrations into src/app/access, and registers the access locale in src/app/
|
|
86
|
+
description: "Installs @warlock.js/access — authorization (RBAC + ABAC): permission checks, ABAC policies, and roles. Ejects config/access.ts, the DatabaseAccessResolver + Role/UserRole models and migrations into src/app/access, and registers the access locale in src/app/access/utils/locales.json",
|
|
67
87
|
dependencies: { "@warlock.js/access": INSTALLED_WARLOCK_VERSION },
|
|
68
88
|
ejectConfig: {
|
|
69
89
|
content: accessConfigStub,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/access.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ensureDirectoryAsync, fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { srcPath } from \"../../utils\";\nimport {\n accessConfigStub,\n accessResolverStub,\n accessRoleMigrationStub,\n accessRoleModelIndexStub,\n accessRoleModelStub,\n accessUserRoleMigrationStub,\n accessUserRoleModelIndexStub,\n accessUserRoleModelStub,\n} from \"../stubs\";\nimport { migrationTimestamp } from \"./shared/migration-timestamp\";\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\nasync function registerAccessLocale() {\n
|
|
1
|
+
{"version":3,"file":"access.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/access.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ensureDirectoryAsync, fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { srcPath } from \"../../utils\";\nimport {\n accessConfigStub,\n accessResolverStub,\n accessRoleMigrationStub,\n accessRoleModelIndexStub,\n accessRoleModelStub,\n accessUserRoleMigrationStub,\n accessUserRoleModelIndexStub,\n accessUserRoleModelStub,\n} from \"../stubs\";\nimport { migrationTimestamp } from \"./shared/migration-timestamp\";\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\nasync function registerAccessLocale() {\n const legacyLocalesPath = srcPath(\"app/shared/utils/locales.ts\");\n const localesPath = srcPath(\"app/access/utils/locales.json\");\n const forbidden = {\n en: \"You do not have permission to perform this action.\",\n ar: \"ليس لديك صلاحية لتنفيذ هذا الإجراء.\",\n };\n\n if (await fileExistsAsync(legacyLocalesPath)) {\n const legacy = await getFileAsync(legacyLocalesPath);\n if (legacy.includes(`groupedTranslations(\"access\"`)) {\n console.log(`${colors.yellowBright(\"access\")} locale already registered, skipping...`);\n return;\n }\n }\n\n if (await fileExistsAsync(localesPath)) {\n const current = await getFileAsync(localesPath);\n let dictionary: Record<string, unknown>;\n try {\n const parsed: unknown = JSON.parse(current);\n if (parsed === null || Array.isArray(parsed) || typeof parsed !== \"object\") throw new Error();\n dictionary = parsed as Record<string, unknown>;\n } catch {\n console.log(\n `${colors.yellowBright(\"access\")} locale JSON is invalid; add errors.forbidden to src/app/access/utils/locales.json manually.`,\n );\n return;\n }\n\n // A root `$group` renames the whole file's namespace; merging there would\n // register the sentence under a key the access checks never read.\n if (dictionary.$group !== undefined && dictionary.$group !== \"access\") {\n console.log(\n `${colors.yellowBright(\"access\")} locale JSON declares a different $group; add access.errors.forbidden manually.`,\n );\n return;\n }\n\n const errors = dictionary.errors;\n if (\n errors !== undefined &&\n (errors === null || Array.isArray(errors) || typeof errors !== \"object\")\n ) {\n console.log(\n `${colors.yellowBright(\"access\")} locale JSON cannot safely add errors.forbidden; update src/app/access/utils/locales.json manually.`,\n );\n return;\n }\n const entries = (errors ?? {}) as Record<string, unknown>;\n if (entries.forbidden !== undefined) return;\n\n entries.forbidden = forbidden;\n dictionary.errors = entries;\n await putFileAsync(localesPath, `${JSON.stringify(dictionary, null, 2)}\\n`);\n console.log(\n `${colors.green(\"✓\")} Registered the access locale in src/app/access/utils/locales.json`,\n );\n return;\n }\n\n await ensureDirectoryAsync(srcPath(\"app/access/utils\"));\n await putFileAsync(localesPath, `${JSON.stringify({ errors: { forbidden } }, null, 2)}\\n`);\n console.log(\n `${colors.green(\"✓\")} Created src/app/access/utils/locales.json with the access locale`,\n );\n}\n\nasync function scaffoldAccessFiles() {\n // The resolver file is the sentinel for \"access already scaffolded\" — its\n // presence means the role/user-role model folders and their timestamped\n // migrations were created too, so we must not re-emit duplicate migrations on\n // a second run.\n const resolverPath = srcPath(\"app/access/services/access-resolver.ts\");\n\n if (await fileExistsAsync(resolverPath)) {\n console.log(\n `${colors.yellowBright(\"src/app/access\")} already scaffolded, skipping resolver + role tables...`,\n );\n\n return;\n }\n\n // 1. Role catalog model folder (model + barrel + migration). The catalog row\n // is role name → granted permissions; managed at runtime in the DB.\n await ensureDirectoryAsync(srcPath(\"app/access/models/role\"));\n await putFileAsync(srcPath(\"app/access/models/role/role.model.ts\"), accessRoleModelStub);\n await putFileAsync(srcPath(\"app/access/models/role/index.ts\"), accessRoleModelIndexStub);\n console.log(`${colors.green(\"✓\")} Created src/app/access/models/role`);\n\n await ensureDirectoryAsync(srcPath(\"app/access/models/role/migrations\"));\n\n // Migration filenames carry a MM-DD-YYYY_HH-MM-SS prefix so cascade infers\n // their createdAt and orders them deterministically (the migrate action\n // discovers src/app/*/models/*/migrations/*). The two tables are independent\n // (no FK between them), but the user-role migration is stamped a second later\n // so the relative order is stable.\n const roleMigrationFile = `${migrationTimestamp()}-role.migration.ts`;\n await putFileAsync(\n srcPath(\"app/access/models/role/migrations\", roleMigrationFile),\n accessRoleMigrationStub,\n );\n console.log(\n `${colors.green(\"✓\")} Created src/app/access/models/role/migrations/${roleMigrationFile}`,\n );\n\n // 2. UserRole assignment model folder (model + barrel + migration). The model\n // statics scope an unresolved tenant to GLOBAL rows only (security\n // invariant) — see the stub for the reasoning.\n await ensureDirectoryAsync(srcPath(\"app/access/models/user-role\"));\n await putFileAsync(\n srcPath(\"app/access/models/user-role/user-role.model.ts\"),\n accessUserRoleModelStub,\n );\n await putFileAsync(srcPath(\"app/access/models/user-role/index.ts\"), accessUserRoleModelIndexStub);\n console.log(`${colors.green(\"✓\")} Created src/app/access/models/user-role`);\n\n await ensureDirectoryAsync(srcPath(\"app/access/models/user-role/migrations\"));\n\n const userRoleMigrationFile = `${migrationTimestamp(1)}-user-role.migration.ts`;\n await putFileAsync(\n srcPath(\"app/access/models/user-role/migrations\", userRoleMigrationFile),\n accessUserRoleMigrationStub,\n );\n console.log(\n `${colors.green(\"✓\")} Created src/app/access/models/user-role/migrations/${userRoleMigrationFile}`,\n );\n\n // 3. The DatabaseAccessResolver — the one required config seam, wired into\n // config/access.ts by the ejected stub.\n await ensureDirectoryAsync(srcPath(\"app/access/services\"));\n await putFileAsync(resolverPath, accessResolverStub);\n console.log(`${colors.green(\"✓\")} Created src/app/access/services/access-resolver.ts`);\n}\n\nasync function completeAccessInstallation(_options: CommandActionData) {\n await registerAccessLocale();\n await scaffoldAccessFiles();\n}\n\nexport const accessFeature: FeatureDefinition = {\n description:\n \"Installs @warlock.js/access — authorization (RBAC + ABAC): permission checks, ABAC policies, and roles. Ejects config/access.ts, the DatabaseAccessResolver + Role/UserRole models and migrations into src/app/access, and registers the access locale in src/app/access/utils/locales.json\",\n dependencies: {\n \"@warlock.js/access\": INSTALLED_WARLOCK_VERSION,\n },\n ejectConfig: {\n content: accessConfigStub,\n name: \"access\",\n },\n onExecuting: completeAccessInstallation,\n};\n"],"mappings":"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"}
|
|
@@ -54,8 +54,8 @@ const bullBoardFeature = {
|
|
|
54
54
|
description: "Installs @bull-board/api and @bull-board/fastify and adds a dashboard block to src/config/queue.ts. Adds the queue feature first when it is missing.",
|
|
55
55
|
requires: ["queue"],
|
|
56
56
|
dependencies: {
|
|
57
|
-
"@bull-board/api": "^9.10.
|
|
58
|
-
"@bull-board/fastify": "^9.10.
|
|
57
|
+
"@bull-board/api": "^9.10.2",
|
|
58
|
+
"@bull-board/fastify": "^9.10.2"
|
|
59
59
|
},
|
|
60
60
|
onExecuting: completeBullBoardInstallation
|
|
61
61
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bull-board.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/bull-board.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getFileAsync, getJsonFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { rootPath, srcPath } from \"../../utils\";\nimport { insertQueueDashboardBlock } from \"./shared/insert-queue-dashboard-block\";\nimport type { FeatureDefinition } from \"./types\";\n\n/** The parts of `package.json` this feature reads to check for its prerequisite. */\ntype ProjectPackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n};\n\n/**\n * Defensive floor: `requires: [\"queue\"]` normally installs queue first, so\n * this only fires when the dashboard step runs without it (e.g. called\n * directly), and then fails loudly instead of writing a half-wired config.\n */\nasync function isQueueInstalled(): Promise<boolean> {\n const packageJson = await getJsonFileAsync<ProjectPackageJson>(rootPath(\"package.json\"));\n\n return Boolean(\n packageJson.dependencies?.[\"@warlock.js/queue\"] ||\n packageJson.devDependencies?.[\"@warlock.js/queue\"],\n );\n}\n\n/** Insert the `dashboard` block into `src/config/queue.ts`, without reformatting it. */\nasync function addDashboardConfigBlock(): Promise<void> {\n const configPath = srcPath(\"config/queue.ts\");\n\n if (!(await fileExistsAsync(configPath))) {\n console.log(\n `${colors.yellowBright(\"src/config/queue.ts\")} not found — add this to your queue config yourself:\\n` +\n ' dashboard: { enabled: true, path: \"/admin/queues\", middleware: [] },\\n',\n );\n\n return;\n }\n\n const current = await getFileAsync(configPath);\n const insertion = insertQueueDashboardBlock(current);\n\n if (insertion.status === \"already-present\") {\n console.log(\n `${colors.yellowBright(\"dashboard\")} config already present in src/config/queue.ts, skipping...`,\n );\n\n return;\n }\n\n if (insertion.status === \"unrecognised\") {\n console.log(\n `${colors.yellowBright(\"!\")} src/config/queue.ts has no recognisable ${colors.yellowBright(\"queueConfig\")} object — ` +\n \"add the dashboard block yourself:\\n\" +\n ' dashboard: { enabled: true, path: \"/admin/queues\", middleware: [] },\\n',\n );\n\n return;\n }\n\n await putFileAsync(configPath, insertion.next);\n console.log(`${colors.green(\"✓\")} Added dashboard config to src/config/queue.ts`);\n}\n\n/**\n * Wire the dashboard into the app: verify queue is installed, then add the\n * `dashboard` block to its config.\n */\nasync function completeBullBoardInstallation(_options: CommandActionData): Promise<void> {\n if (!(await isQueueInstalled())) {\n console.log(\n `${colors.redBright(\"✗\")} @warlock.js/queue is not installed — run ` +\n `${colors.yellowBright(\"warlock add queue\")} first, then ${colors.yellowBright(\"warlock add bull-board\")}.`,\n );\n process.exitCode = 1;\n\n return;\n }\n\n await addDashboardConfigBlock();\n\n console.log(\n \"\\nNext: guard the dashboard with a middleware before it ships to production — an empty \" +\n \"middleware list throws QueueDashboardUnguardedError at boot when NODE_ENV is \\\"production\\\".\",\n );\n}\n\n/** `warlock add bull-board` — a config-driven bull-board dashboard for @warlock.js/queue. */\nexport const bullBoardFeature: FeatureDefinition = {\n description:\n \"Installs @bull-board/api and @bull-board/fastify and adds a dashboard block to src/config/queue.ts. Adds the queue feature first when it is missing.\",\n requires: [\"queue\"],\n dependencies: {\n \"@bull-board/api\": \"^9.10.
|
|
1
|
+
{"version":3,"file":"bull-board.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/bull-board.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getFileAsync, getJsonFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { rootPath, srcPath } from \"../../utils\";\nimport { insertQueueDashboardBlock } from \"./shared/insert-queue-dashboard-block\";\nimport type { FeatureDefinition } from \"./types\";\n\n/** The parts of `package.json` this feature reads to check for its prerequisite. */\ntype ProjectPackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n};\n\n/**\n * Defensive floor: `requires: [\"queue\"]` normally installs queue first, so\n * this only fires when the dashboard step runs without it (e.g. called\n * directly), and then fails loudly instead of writing a half-wired config.\n */\nasync function isQueueInstalled(): Promise<boolean> {\n const packageJson = await getJsonFileAsync<ProjectPackageJson>(rootPath(\"package.json\"));\n\n return Boolean(\n packageJson.dependencies?.[\"@warlock.js/queue\"] ||\n packageJson.devDependencies?.[\"@warlock.js/queue\"],\n );\n}\n\n/** Insert the `dashboard` block into `src/config/queue.ts`, without reformatting it. */\nasync function addDashboardConfigBlock(): Promise<void> {\n const configPath = srcPath(\"config/queue.ts\");\n\n if (!(await fileExistsAsync(configPath))) {\n console.log(\n `${colors.yellowBright(\"src/config/queue.ts\")} not found — add this to your queue config yourself:\\n` +\n ' dashboard: { enabled: true, path: \"/admin/queues\", middleware: [] },\\n',\n );\n\n return;\n }\n\n const current = await getFileAsync(configPath);\n const insertion = insertQueueDashboardBlock(current);\n\n if (insertion.status === \"already-present\") {\n console.log(\n `${colors.yellowBright(\"dashboard\")} config already present in src/config/queue.ts, skipping...`,\n );\n\n return;\n }\n\n if (insertion.status === \"unrecognised\") {\n console.log(\n `${colors.yellowBright(\"!\")} src/config/queue.ts has no recognisable ${colors.yellowBright(\"queueConfig\")} object — ` +\n \"add the dashboard block yourself:\\n\" +\n ' dashboard: { enabled: true, path: \"/admin/queues\", middleware: [] },\\n',\n );\n\n return;\n }\n\n await putFileAsync(configPath, insertion.next);\n console.log(`${colors.green(\"✓\")} Added dashboard config to src/config/queue.ts`);\n}\n\n/**\n * Wire the dashboard into the app: verify queue is installed, then add the\n * `dashboard` block to its config.\n */\nasync function completeBullBoardInstallation(_options: CommandActionData): Promise<void> {\n if (!(await isQueueInstalled())) {\n console.log(\n `${colors.redBright(\"✗\")} @warlock.js/queue is not installed — run ` +\n `${colors.yellowBright(\"warlock add queue\")} first, then ${colors.yellowBright(\"warlock add bull-board\")}.`,\n );\n process.exitCode = 1;\n\n return;\n }\n\n await addDashboardConfigBlock();\n\n console.log(\n \"\\nNext: guard the dashboard with a middleware before it ships to production — an empty \" +\n \"middleware list throws QueueDashboardUnguardedError at boot when NODE_ENV is \\\"production\\\".\",\n );\n}\n\n/** `warlock add bull-board` — a config-driven bull-board dashboard for @warlock.js/queue. */\nexport const bullBoardFeature: FeatureDefinition = {\n description:\n \"Installs @bull-board/api and @bull-board/fastify and adds a dashboard block to src/config/queue.ts. Adds the queue feature first when it is missing.\",\n requires: [\"queue\"],\n dependencies: {\n \"@bull-board/api\": \"^9.10.2\",\n \"@bull-board/fastify\": \"^9.10.2\",\n },\n onExecuting: completeBullBoardInstallation,\n};\n"],"mappings":";;;;;;;;;;;;AAkBA,eAAe,mBAAqC;CAClD,MAAM,cAAc,MAAM,iBAAqC,SAAS,cAAc,CAAC;CAEvF,OAAO,QACL,YAAY,eAAe,wBACzB,YAAY,kBAAkB,oBAClC;AACF;;AAGA,eAAe,0BAAyC;CACtD,MAAM,aAAa,QAAQ,iBAAiB;CAE5C,IAAI,CAAE,MAAM,gBAAgB,UAAU,GAAI;EACxC,QAAQ,IACN,GAAG,OAAO,aAAa,qBAAqB,EAAE;CAEhD;EAEA;CACF;CAGA,MAAM,YAAY,0BAA0B,MADtB,aAAa,UAAU,CACM;CAEnD,IAAI,UAAU,WAAW,mBAAmB;EAC1C,QAAQ,IACN,GAAG,OAAO,aAAa,WAAW,EAAE,4DACtC;EAEA;CACF;CAEA,IAAI,UAAU,WAAW,gBAAgB;EACvC,QAAQ,IACN,GAAG,OAAO,aAAa,GAAG,EAAE,2CAA2C,OAAO,aAAa,aAAa,EAAE;;CAG5G;EAEA;CACF;CAEA,MAAM,aAAa,YAAY,UAAU,IAAI;CAC7C,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,+CAA+C;AAClF;;;;;AAMA,eAAe,8BAA8B,UAA4C;CACvF,IAAI,CAAE,MAAM,iBAAiB,GAAI;EAC/B,QAAQ,IACN,GAAG,OAAO,UAAU,GAAG,EAAE,4CACpB,OAAO,aAAa,mBAAmB,EAAE,eAAe,OAAO,aAAa,wBAAwB,EAAE,EAC7G;EACA,QAAQ,WAAW;EAEnB;CACF;CAEA,MAAM,wBAAwB;CAE9B,QAAQ,IACN,qLAEF;AACF;;AAGA,MAAa,mBAAsC;CACjD,aACE;CACF,UAAU,CAAC,OAAO;CAClB,cAAc;EACZ,mBAAmB;EACnB,uBAAuB;CACzB;CACA,aAAa;AACf"}
|
|
@@ -4,11 +4,17 @@ import { RateLimitPluginOptions } from "@fastify/rate-limit";
|
|
|
4
4
|
/**
|
|
5
5
|
* `@fastify/rate-limit` options that `http.rateLimit` passes straight through.
|
|
6
6
|
*/
|
|
7
|
-
declare const passthroughKeys: readonly ["redis", "nameSpace", "keyGenerator", "skipOnError", "allowList", "continueExceeding", "enableDraftSpec", "global", "hook", "cache", "errorResponseBuilder", "addHeaders", "addHeadersOnExceeding"];
|
|
7
|
+
declare const passthroughKeys: readonly ["redis", "nameSpace", "keyGenerator", "ipv6Subnet", "skipOnError", "allowList", "continueExceeding", "enableDraftSpec", "global", "hook", "cache", "errorResponseBuilder", "addHeaders", "addHeadersOnExceeding"];
|
|
8
8
|
/**
|
|
9
9
|
* Shape of the `http.rateLimit` config.
|
|
10
10
|
*/
|
|
11
|
-
type HttpRateLimitConfig = Pick<RateLimitPluginOptions, (typeof passthroughKeys)[number]> & {
|
|
11
|
+
type HttpRateLimitConfig = Omit<Pick<RateLimitPluginOptions, (typeof passthroughKeys)[number]>, "ipv6Subnet"> & {
|
|
12
|
+
/**
|
|
13
|
+
* IPv6 subnet size used to group client addresses.
|
|
14
|
+
*
|
|
15
|
+
* @default 64
|
|
16
|
+
*/
|
|
17
|
+
ipv6Subnet?: RateLimitPluginOptions["ipv6Subnet"];
|
|
12
18
|
/**
|
|
13
19
|
* Set `false` to skip registering the global rate limiter.
|
|
14
20
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-rate-limit-options.d.mts","names":[],"sources":["../../../../../../../core/src/http/build-rate-limit-options.ts"],"mappings":";;;;;AAAkE;cAK5D,eAAA;;;
|
|
1
|
+
{"version":3,"file":"build-rate-limit-options.d.mts","names":[],"sources":["../../../../../../../core/src/http/build-rate-limit-options.ts"],"mappings":";;;;;AAAkE;cAK5D,eAAA;;;AAeI;KAKE,mBAAA,GAAsB,IAAA,CAChC,IAAA,CAAK,sBAAA,UAAgC,eAAA;EADR;;;;;EAS7B,UAAA,GAAa,sBAAA;EAAA;;;;;EAMb,OAAA;EAdK;;;;;EAoBL,GAAA;EAMA;;;;AAImC;EAJnC,QAAA;;;;EAIA,UAAA,GAAa,sBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-rate-limit-options.mjs","names":[],"sources":["../../../../../../../core/src/http/build-rate-limit-options.ts"],"sourcesContent":["import type { RateLimitPluginOptions } from \"@fastify/rate-limit\";\n\n/**\n * `@fastify/rate-limit` options that `http.rateLimit` passes straight through.\n */\nconst passthroughKeys = [\n \"redis\",\n \"nameSpace\",\n \"keyGenerator\",\n \"skipOnError\",\n \"allowList\",\n \"continueExceeding\",\n \"enableDraftSpec\",\n \"global\",\n \"hook\",\n \"cache\",\n \"errorResponseBuilder\",\n \"addHeaders\",\n \"addHeadersOnExceeding\",\n] as const;\n\n/**\n * Shape of the `http.rateLimit` config.\n */\nexport type HttpRateLimitConfig = Pick<RateLimitPluginOptions, (typeof passthroughKeys)[number]> & {\n /**\n * Set `false` to skip registering the global rate limiter.\n *\n * @default true\n */\n enabled?: boolean;\n /**\n * Max requests per time window.\n *\n * @default 60\n */\n max?: number;\n /**\n * Time window in milliseconds. Alias of `timeWindow`.\n *\n * @default 60 * 1000\n */\n duration?: number;\n /**\n * Time window; takes precedence over `duration` when both are set.\n */\n timeWindow?: RateLimitPluginOptions[\"timeWindow\"];\n};\n\n/**\n * Build the `@fastify/rate-limit` plugin options from `http.rateLimit`.\n * Returns `null` when the limiter is disabled.\n */\nexport function buildRateLimitOptions(\n config: HttpRateLimitConfig | undefined,\n): RateLimitPluginOptions | null {\n const source = config ?? {};\n\n if (source.enabled === false) {\n return null;\n }\n\n const options: Record<string, unknown> = {\n max: source.max ?? 60,\n timeWindow: source.timeWindow ?? source.duration ?? 60 * 1000,\n };\n\n for (const key of passthroughKeys) {\n if (source[key] !== undefined) {\n options[key] = source[key];\n }\n }\n\n return options as RateLimitPluginOptions;\n}\n"],"mappings":";;;;AAKA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;
|
|
1
|
+
{"version":3,"file":"build-rate-limit-options.mjs","names":[],"sources":["../../../../../../../core/src/http/build-rate-limit-options.ts"],"sourcesContent":["import type { RateLimitPluginOptions } from \"@fastify/rate-limit\";\n\n/**\n * `@fastify/rate-limit` options that `http.rateLimit` passes straight through.\n */\nconst passthroughKeys = [\n \"redis\",\n \"nameSpace\",\n \"keyGenerator\",\n \"ipv6Subnet\",\n \"skipOnError\",\n \"allowList\",\n \"continueExceeding\",\n \"enableDraftSpec\",\n \"global\",\n \"hook\",\n \"cache\",\n \"errorResponseBuilder\",\n \"addHeaders\",\n \"addHeadersOnExceeding\",\n] as const;\n\n/**\n * Shape of the `http.rateLimit` config.\n */\nexport type HttpRateLimitConfig = Omit<\n Pick<RateLimitPluginOptions, (typeof passthroughKeys)[number]>,\n \"ipv6Subnet\"\n> & {\n /**\n * IPv6 subnet size used to group client addresses.\n *\n * @default 64\n */\n ipv6Subnet?: RateLimitPluginOptions[\"ipv6Subnet\"];\n /**\n * Set `false` to skip registering the global rate limiter.\n *\n * @default true\n */\n enabled?: boolean;\n /**\n * Max requests per time window.\n *\n * @default 60\n */\n max?: number;\n /**\n * Time window in milliseconds. Alias of `timeWindow`.\n *\n * @default 60 * 1000\n */\n duration?: number;\n /**\n * Time window; takes precedence over `duration` when both are set.\n */\n timeWindow?: RateLimitPluginOptions[\"timeWindow\"];\n};\n\n/**\n * Build the `@fastify/rate-limit` plugin options from `http.rateLimit`.\n * Returns `null` when the limiter is disabled.\n */\nexport function buildRateLimitOptions(\n config: HttpRateLimitConfig | undefined,\n): RateLimitPluginOptions | null {\n const source = config ?? {};\n\n if (source.enabled === false) {\n return null;\n }\n\n const options: Record<string, unknown> = {\n max: source.max ?? 60,\n timeWindow: source.timeWindow ?? source.duration ?? 60 * 1000,\n };\n\n for (const key of passthroughKeys) {\n if (source[key] !== undefined) {\n options[key] = source[key];\n }\n }\n\n return options as RateLimitPluginOptions;\n}\n"],"mappings":";;;;AAKA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AA2CA,SAAgB,sBACd,QAC+B;CAC/B,MAAM,SAAS,UAAU,CAAC;CAE1B,IAAI,OAAO,YAAY,OACrB,OAAO;CAGT,MAAM,UAAmC;EACvC,KAAK,OAAO,OAAO;EACnB,YAAY,OAAO,cAAc,OAAO,YAAY,KAAK;CAC3D;CAEA,KAAK,MAAM,OAAO,iBAChB,IAAI,OAAO,SAAS,QAClB,QAAQ,OAAO,OAAO;CAI1B,OAAO;AACT"}
|
|
@@ -52,22 +52,6 @@ declare class NotAllowedError extends HttpError {
|
|
|
52
52
|
declare class CookieJarUnavailableError extends Error {
|
|
53
53
|
constructor(cookieName: string);
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Thrown by `Request.prototype.user` in development to catch a call site
|
|
57
|
-
* still reading the removed `request.user` getter/setter after the 5.12.0
|
|
58
|
-
* move: the authenticated user now lives at `request.locals.user`, written
|
|
59
|
-
* by `@warlock.js/auth`'s middleware.
|
|
60
|
-
*
|
|
61
|
-
* Development-only diagnostic, not a runtime contract other code should
|
|
62
|
-
* catch — the getter itself is typed `never`, so a caller that still
|
|
63
|
-
* compiles against `request.user` only does so via `any`/an outdated type.
|
|
64
|
-
* Kept in core (not auth) because `request.user` is a core `Request`
|
|
65
|
-
* accessor and core cannot depend on `@warlock.js/auth` to react to it.
|
|
66
|
-
* Slated for removal one release after 5.12.0 — see the CHANGELOG.
|
|
67
|
-
*/
|
|
68
|
-
declare class RequestUserMovedError extends Error {
|
|
69
|
-
constructor();
|
|
70
|
-
}
|
|
71
55
|
//#endregion
|
|
72
|
-
export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError,
|
|
56
|
+
export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
|
|
73
57
|
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"mappings":";cAAa,SAAA,SAAkB,KAAK;EAEzB,MAAA;EACA,OAAA;EACA,OAAA;cAFA,MAAA,UACA,OAAA,UACA,OAAA;AAAA;AAAA,cAOE,qBAAA,SAA8B,SAAS;EAGzC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,iBAAA,SAA0B,SAAS;EAGrC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,cAAA,SAAuB,SAAS;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,WAAA,SAAoB,SAAS;EAG/B,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,aAAA,SAAsB,SAAS;EAGjC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,kBAAA,SAA2B,SAAS;EAGtC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;;;;;;;;;;AAlDa;AAOxB;;cA8Da,yBAAA,SAAkC,KAAK;cAC/B,UAAA;AAAA
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"mappings":";cAAa,SAAA,SAAkB,KAAK;EAEzB,MAAA;EACA,OAAA;EACA,OAAA;cAFA,MAAA,UACA,OAAA,UACA,OAAA;AAAA;AAAA,cAOE,qBAAA,SAA8B,SAAS;EAGzC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,iBAAA,SAA0B,SAAS;EAGrC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,cAAA,SAAuB,SAAS;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,WAAA,SAAoB,SAAS;EAG/B,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,aAAA,SAAsB,SAAS;EAGjC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,kBAAA,SAA2B,SAAS;EAGtC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;AAAA,cAOE,eAAA,SAAwB,SAAS;EAGnC,OAAA;cADP,OAAA,UACO,OAAA;AAAA;;;;;;;;;;AAlDa;AAOxB;;cA8Da,yBAAA,SAAkC,KAAK;cAC/B,UAAA;AAAA"}
|
|
@@ -82,26 +82,7 @@ var CookieJarUnavailableError = class extends Error {
|
|
|
82
82
|
this.name = "CookieJarUnavailableError";
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
|
-
/**
|
|
86
|
-
* Thrown by `Request.prototype.user` in development to catch a call site
|
|
87
|
-
* still reading the removed `request.user` getter/setter after the 5.12.0
|
|
88
|
-
* move: the authenticated user now lives at `request.locals.user`, written
|
|
89
|
-
* by `@warlock.js/auth`'s middleware.
|
|
90
|
-
*
|
|
91
|
-
* Development-only diagnostic, not a runtime contract other code should
|
|
92
|
-
* catch — the getter itself is typed `never`, so a caller that still
|
|
93
|
-
* compiles against `request.user` only does so via `any`/an outdated type.
|
|
94
|
-
* Kept in core (not auth) because `request.user` is a core `Request`
|
|
95
|
-
* accessor and core cannot depend on `@warlock.js/auth` to react to it.
|
|
96
|
-
* Slated for removal one release after 5.12.0 — see the CHANGELOG.
|
|
97
|
-
*/
|
|
98
|
-
var RequestUserMovedError = class extends Error {
|
|
99
|
-
constructor() {
|
|
100
|
-
super("request.user has been removed. The authenticated user now lives at request.locals.user, set by @warlock.js/auth's middleware.");
|
|
101
|
-
this.name = "RequestUserMovedError";
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
85
|
|
|
105
86
|
//#endregion
|
|
106
|
-
export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError,
|
|
87
|
+
export { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, ResourceNotFoundError, ServerError, UnAuthorizedError };
|
|
107
88
|
//# sourceMappingURL=errors.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"sourcesContent":["export class HttpError extends Error {\n public constructor(\n public status: number,\n public message: string,\n public payload?: any,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport class ResourceNotFoundError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(404, message, payload);\n this.name = \"ResourceNotFoundError\";\n }\n}\n\nexport class UnAuthorizedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(401, message, payload);\n this.name = \"UnAuthorizedError\";\n }\n}\n\nexport class ForbiddenError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(403, message, payload);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport class BadRequestError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(400, message, payload);\n this.name = \"BadRequestError\";\n }\n}\n\nexport class ServerError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(500, message, payload);\n this.name = \"ServerError\";\n }\n}\n\nexport class ConflictError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(409, message, payload);\n this.name = \"ConflictError\";\n }\n}\n\nexport class NotAcceptableError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(406, message, payload);\n this.name = \"NotAcceptableError\";\n }\n}\n\nexport class NotAllowedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(405, message, payload);\n this.name = \"NotAllowedError\";\n }\n}\n\n/**\n * Thrown by `Request.prototype.cookie` / `Request.prototype.hasCookie` when\n * `@fastify/cookie` was never registered on this Fastify instance, so\n * `baseRequest.cookies` is `undefined` rather than an (possibly empty) object.\n *\n * A by-name cookie read is a deliberate assertion by the caller — \"this\n * cookie should be readable here\" — so an unavailable jar is a configuration\n * fault, not an absent cookie, and must not be swallowed into a default\n * value or a silent `false`. Contrast `Request.prototype.cookies`, which\n * stays lenient because the framework's own opportunistic reads (e.g.\n * locale resolution) must not throw on a request that simply has no jar.\n */\nexport class CookieJarUnavailableError extends Error {\n public constructor(cookieName: string) {\n super(\n `Cannot read cookie \"${cookieName}\": the cookie jar is unavailable because ` +\n \"@fastify/cookie is not registered on this Fastify instance. \" +\n \"Register the plugin (see core's http/plugins.ts) before reading cookies by name.\",\n );\n\n this.name = \"CookieJarUnavailableError\";\n }\n}\n
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../../core/src/http/errors/errors.ts"],"sourcesContent":["export class HttpError extends Error {\n public constructor(\n public status: number,\n public message: string,\n public payload?: any,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport class ResourceNotFoundError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(404, message, payload);\n this.name = \"ResourceNotFoundError\";\n }\n}\n\nexport class UnAuthorizedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(401, message, payload);\n this.name = \"UnAuthorizedError\";\n }\n}\n\nexport class ForbiddenError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(403, message, payload);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport class BadRequestError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(400, message, payload);\n this.name = \"BadRequestError\";\n }\n}\n\nexport class ServerError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(500, message, payload);\n this.name = \"ServerError\";\n }\n}\n\nexport class ConflictError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(409, message, payload);\n this.name = \"ConflictError\";\n }\n}\n\nexport class NotAcceptableError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(406, message, payload);\n this.name = \"NotAcceptableError\";\n }\n}\n\nexport class NotAllowedError extends HttpError {\n public constructor(\n message: string,\n public payload?: any,\n ) {\n super(405, message, payload);\n this.name = \"NotAllowedError\";\n }\n}\n\n/**\n * Thrown by `Request.prototype.cookie` / `Request.prototype.hasCookie` when\n * `@fastify/cookie` was never registered on this Fastify instance, so\n * `baseRequest.cookies` is `undefined` rather than an (possibly empty) object.\n *\n * A by-name cookie read is a deliberate assertion by the caller — \"this\n * cookie should be readable here\" — so an unavailable jar is a configuration\n * fault, not an absent cookie, and must not be swallowed into a default\n * value or a silent `false`. Contrast `Request.prototype.cookies`, which\n * stays lenient because the framework's own opportunistic reads (e.g.\n * locale resolution) must not throw on a request that simply has no jar.\n */\nexport class CookieJarUnavailableError extends Error {\n public constructor(cookieName: string) {\n super(\n `Cannot read cookie \"${cookieName}\": the cookie jar is unavailable because ` +\n \"@fastify/cookie is not registered on this Fastify instance. \" +\n \"Register the plugin (see core's http/plugins.ts) before reading cookies by name.\",\n );\n\n this.name = \"CookieJarUnavailableError\";\n }\n}\n"],"mappings":";AAAA,IAAa,YAAb,cAA+B,MAAM;CACnC,AAAO,YACL,AAAO,QACP,AAAO,SACP,AAAO,SACP;EACA,MAAM,OAAO;EAJN;EACA;EACA;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,UAAU;CACnD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oBAAb,cAAuC,UAAU;CAC/C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,iBAAb,cAAoC,UAAU;CAC5C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,cAAb,cAAiC,UAAU;CACzC,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gBAAb,cAAmC,UAAU;CAC3C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,qBAAb,cAAwC,UAAU;CAChD,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kBAAb,cAAqC,UAAU;CAC7C,AAAO,YACL,SACA,AAAO,SACP;EACA,MAAM,KAAK,SAAS,OAAO;EAFpB;EAGP,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;AAcA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,YAAoB;EACrC,MACE,uBAAuB,WAAW,sLAGpC;EAEA,KAAK,OAAO;CACd;AACF"}
|
package/esm/http/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { XMLable } from "./xmlable.mjs";
|
|
|
3
3
|
import { CookieOptions, Response, ResponseStatus, SendBufferOptions, SendFileOptions } from "./response.mjs";
|
|
4
4
|
import { FileNamingStrategy, ImageTransformCallback, ImageTransformConfig, ImageVariantDefinition, ImageVariantFit, ImageVariantOutputFormat, PrefixConfig, PrefixOptions, SaveAsOptions, SaveOptions, UploadedFileImageOptions, UploadsConfigurations, UploadsImagesConfigurations } from "./uploads-types.mjs";
|
|
5
5
|
import { FileValidationOptions, UploadedFile, UploadedFileJson } from "./uploaded-file.mjs";
|
|
6
|
-
import { Request } from "./request.mjs";
|
|
6
|
+
import { Request, RequestTranslator } from "./request.mjs";
|
|
7
7
|
import { CspConfig, DEFAULT_CSP_DIRECTIVES, InvalidCspDirectiveError, applyCspHeader, buildCspHeaderValue, mergeCspDirectives, resolveCspConfig, serializeCspDirectives, validateCspConfigAtBoot, validateCspDirectives } from "./csp.mjs";
|
|
8
8
|
import { HttpTracingConfig, TracingContext, TracingHooks, TracingPhaseInfo, TracingRequestEndInfo } from "./tracing/tracing.type.mjs";
|
|
9
9
|
import { DecodedAccessToken, HttpConfigurations, PartialMiddleware, RequestEvent, RequestLocals, ResponseEvent, ResponseSSEController, ResponseStreamController, ReturnedResponse } from "./types.mjs";
|
|
@@ -21,7 +21,7 @@ import { DetectedImageFormat, GeneratedImageDescriptor, GeneratedImageVariant, R
|
|
|
21
21
|
import { GenerateImageVariantsOptions, generateImageVariants } from "./uploads/generate-image-variants.mjs";
|
|
22
22
|
import { ImageVariantsConfigError } from "./uploads/image-variants-config-error.mjs";
|
|
23
23
|
import { uploadedFileController } from "./uploads/uploaded-file.controller.mjs";
|
|
24
|
-
import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError,
|
|
24
|
+
import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
|
|
25
25
|
import { CacheMiddlewareOptions } from "./middleware/cache-response-middleware.mjs";
|
|
26
26
|
import { ConcurrencyLimitOptions } from "./middleware/concurrency-limit.middleware.mjs";
|
|
27
27
|
import { IdempotencyOptions } from "./middleware/idempotency.middleware.mjs";
|
|
@@ -42,4 +42,5 @@ import { HttpServerRebuiltListener, notifyHttpServerRebuilt, onHttpServerRebuilt
|
|
|
42
42
|
import { deriveTraceId, parseTraceparentTraceId } from "./tracing/trace-id.mjs";
|
|
43
43
|
import { TracingContextSource, buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, registerTracingHooks, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
|
|
44
44
|
import { RequestContextStore, requestContext, useCurrentUser, useRequest, useRequestStore } from "./context/request-context.mjs";
|
|
45
|
-
import { requestMemo } from "./context/request-memo.mjs";
|
|
45
|
+
import { requestMemo } from "./context/request-memo.mjs";
|
|
46
|
+
import { ModelResourceRegistry, Serialized, SerializedWire } from "./serialized.mjs";
|
package/esm/http/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { DEFAULT_CSP_DIRECTIVES, InvalidCspDirectiveError, applyCspHeader, build
|
|
|
3
3
|
import { HttpErrorCodes } from "./error-codes.mjs";
|
|
4
4
|
import { isAllowedCsrfOrigin, normalizeOrigin, originOf, ownOrigin, resolveCsrfOriginVerdict } from "./csrf-origin-policy.mjs";
|
|
5
5
|
import { requiresDefaultCsrfGuard, runDefaultCsrfGuard } from "./csrf-default-guard.mjs";
|
|
6
|
-
import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError,
|
|
6
|
+
import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
|
|
7
7
|
import { deriveTraceId, parseTraceparentTraceId } from "./tracing/trace-id.mjs";
|
|
8
8
|
import { buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, registerTracingHooks, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
|
|
9
9
|
import "./tracing/index.mjs";
|
|
@@ -12,7 +12,7 @@ declare function createRequestStore(request: Request<any>, response: Response):
|
|
|
12
12
|
/**
|
|
13
13
|
* Translate a keyword (uses request context for locale)
|
|
14
14
|
*/
|
|
15
|
-
declare function t(keyword: string, placeholders?:
|
|
15
|
+
declare function t(keyword: string, placeholders?: Record<string, unknown>): string;
|
|
16
16
|
//#endregion
|
|
17
17
|
export { createRequestStore, t };
|
|
18
18
|
//# sourceMappingURL=inject-request-context.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AA8CA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBA2KK,CAAA,CAAE,OAAA,UAAiB,
|
|
1
|
+
{"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AA8CA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBA2KK,CAAA,CAAE,OAAA,UAAiB,YAAA,GAAe,MAAM"}
|