@warlock.js/core 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.md +92 -54
  2. package/esm/application/app.d.mts +3 -3
  3. package/esm/application/app.mjs +6 -6
  4. package/esm/application/app.mjs.map +1 -1
  5. package/esm/cli/cli-commands.utils.mjs +7 -3
  6. package/esm/cli/cli-commands.utils.mjs.map +1 -1
  7. package/esm/cli/commands/doctor/checks/health.check.mjs +1 -1
  8. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  9. package/esm/connectors/socket-connector.mjs +13 -16
  10. package/esm/connectors/socket-connector.mjs.map +1 -1
  11. package/esm/container/index.d.mts +20 -7
  12. package/esm/container/index.d.mts.map +1 -1
  13. package/esm/container/index.mjs +8 -5
  14. package/esm/container/index.mjs.map +1 -1
  15. package/esm/database/utils.d.mts +5 -1
  16. package/esm/database/utils.d.mts.map +1 -1
  17. package/esm/database/utils.mjs +7 -3
  18. package/esm/database/utils.mjs.map +1 -1
  19. package/esm/dev-server/dev-logger.mjs +12 -1
  20. package/esm/dev-server/dev-logger.mjs.map +1 -1
  21. package/esm/dev-server/development-server.mjs +1 -1
  22. package/esm/dev-server/development-server.mjs.map +1 -1
  23. package/esm/dev-server/file-event-handler.mjs +72 -13
  24. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  25. package/esm/dev-server/file-manager.d.mts +8 -0
  26. package/esm/dev-server/file-manager.d.mts.map +1 -1
  27. package/esm/dev-server/file-manager.mjs +26 -4
  28. package/esm/dev-server/file-manager.mjs.map +1 -1
  29. package/esm/dev-server/files-orchestrator.mjs +3 -3
  30. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  31. package/esm/dev-server/files-watcher.mjs +16 -2
  32. package/esm/dev-server/files-watcher.mjs.map +1 -1
  33. package/esm/dev-server/flags.mjs +16 -1
  34. package/esm/dev-server/flags.mjs.map +1 -1
  35. package/esm/dev-server/layer-executor.mjs +19 -2
  36. package/esm/dev-server/layer-executor.mjs.map +1 -1
  37. package/esm/dev-server/translation-type-generator.mjs +28 -0
  38. package/esm/dev-server/translation-type-generator.mjs.map +1 -0
  39. package/esm/dev-server/tsconfig-manager.mjs +1 -0
  40. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  41. package/esm/dev-server/type-generator.mjs +41 -5
  42. package/esm/dev-server/type-generator.mjs.map +1 -1
  43. package/esm/encryption/index.mjs +1 -1
  44. package/esm/errors/container-key-missing-error.mjs +18 -6
  45. package/esm/errors/container-key-missing-error.mjs.map +1 -1
  46. package/esm/generations/features/auth-google.feature.mjs +18 -0
  47. package/esm/generations/features/auth-google.feature.mjs.map +1 -0
  48. package/esm/generations/features/auth-passkeys.feature.mjs +19 -0
  49. package/esm/generations/features/auth-passkeys.feature.mjs.map +1 -0
  50. package/esm/generations/features/index.mjs +6 -0
  51. package/esm/generations/features/index.mjs.map +1 -1
  52. package/esm/generations/features/queue.feature.mjs +67 -0
  53. package/esm/generations/features/queue.feature.mjs.map +1 -0
  54. package/esm/generations/stubs.mjs +10 -10
  55. package/esm/generations/stubs.mjs.map +1 -1
  56. package/esm/http/context/request-context.d.mts +14 -11
  57. package/esm/http/context/request-context.d.mts.map +1 -1
  58. package/esm/http/context/request-context.mjs +13 -9
  59. package/esm/http/context/request-context.mjs.map +1 -1
  60. package/esm/http/createHttpApplication.d.mts.map +1 -1
  61. package/esm/http/createHttpApplication.mjs +2 -0
  62. package/esm/http/createHttpApplication.mjs.map +1 -1
  63. package/esm/http/csp.d.mts +139 -0
  64. package/esm/http/csp.d.mts.map +1 -0
  65. package/esm/http/csp.mjs +134 -0
  66. package/esm/http/csp.mjs.map +1 -0
  67. package/esm/http/errors/errors.d.mts +17 -1
  68. package/esm/http/errors/errors.d.mts.map +1 -1
  69. package/esm/http/errors/errors.mjs +20 -1
  70. package/esm/http/errors/errors.mjs.map +1 -1
  71. package/esm/http/index.d.mts +8 -3
  72. package/esm/http/index.mjs +6 -1
  73. package/esm/http/middleware/concurrency-limit.middleware.d.mts +2 -2
  74. package/esm/http/middleware/concurrency-limit.middleware.mjs +1 -1
  75. package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
  76. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  77. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  78. package/esm/http/middleware/inject-request-context.mjs +10 -0
  79. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  80. package/esm/http/middleware/rate-limit.middleware.d.mts +1 -1
  81. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  82. package/esm/http/middleware/utils/idempotency-key.d.mts.map +1 -1
  83. package/esm/http/middleware/utils/idempotency-key.mjs +13 -13
  84. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  85. package/esm/http/request-controller.d.mts +1 -1
  86. package/esm/http/request.d.mts +32 -44
  87. package/esm/http/request.d.mts.map +1 -1
  88. package/esm/http/request.mjs +52 -44
  89. package/esm/http/request.mjs.map +1 -1
  90. package/esm/http/response.d.mts +18 -0
  91. package/esm/http/response.d.mts.map +1 -1
  92. package/esm/http/response.mjs +31 -0
  93. package/esm/http/response.mjs.map +1 -1
  94. package/esm/http/stream-react-response.d.mts +65 -0
  95. package/esm/http/stream-react-response.d.mts.map +1 -0
  96. package/esm/http/stream-react-response.mjs +46 -0
  97. package/esm/http/stream-react-response.mjs.map +1 -0
  98. package/esm/http/tracing/index.mjs +4 -0
  99. package/esm/http/tracing/trace-id.d.mts +14 -0
  100. package/esm/http/tracing/trace-id.d.mts.map +1 -0
  101. package/esm/http/tracing/trace-id.mjs +37 -0
  102. package/esm/http/tracing/trace-id.mjs.map +1 -0
  103. package/esm/http/tracing/tracing-dispatcher.d.mts +42 -0
  104. package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -0
  105. package/esm/http/tracing/tracing-dispatcher.mjs +94 -0
  106. package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -0
  107. package/esm/http/tracing/tracing.type.d.mts +66 -0
  108. package/esm/http/tracing/tracing.type.d.mts.map +1 -0
  109. package/esm/http/types.d.mts +27 -21
  110. package/esm/http/types.d.mts.map +1 -1
  111. package/esm/index.d.mts +9 -4
  112. package/esm/index.mjs +7 -3
  113. package/esm/production/esbuild-preflight.mjs +47 -0
  114. package/esm/production/esbuild-preflight.mjs.map +1 -0
  115. package/esm/production/production-builder.mjs +19 -15
  116. package/esm/production/production-builder.mjs.map +1 -1
  117. package/esm/restful/restful.d.mts +1 -1
  118. package/esm/router/log-request-lifecycle.mjs +12 -2
  119. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  120. package/esm/router/router.d.mts.map +1 -1
  121. package/esm/router/router.mjs +10 -0
  122. package/esm/router/router.mjs.map +1 -1
  123. package/esm/router/types.d.mts +1 -1
  124. package/esm/socket/utils.mjs +1 -2
  125. package/esm/socket/utils.mjs.map +1 -1
  126. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  127. package/esm/storage/drivers/cloud-driver.mjs +2 -5
  128. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  129. package/esm/warlock-config/types.d.mts +12 -0
  130. package/esm/warlock-config/types.d.mts.map +1 -1
  131. package/llms-full.txt +314 -46
  132. package/llms.txt +1 -0
  133. package/package.json +11 -12
  134. package/skills/add-connector/SKILL.md +1 -1
  135. package/skills/build-restful/SKILL.md +2 -2
  136. package/skills/configure-app/SKILL.md +31 -0
  137. package/skills/create-controller/SKILL.md +4 -4
  138. package/skills/request-tracing/SKILL.md +208 -0
  139. package/skills/send-response/SKILL.md +16 -0
  140. package/skills/store-file/SKILL.md +1 -1
  141. package/skills/upload-file/SKILL.md +2 -2
  142. package/skills/use-app-context/SKILL.md +2 -2
  143. package/skills/use-localization/SKILL.md +24 -21
  144. package/skills/use-middleware/SKILL.md +2 -2
  145. package/skills/use-repository/SKILL.md +1 -1
  146. package/skills/use-request-locals/SKILL.md +1 -1
  147. package/skills/wire-socket/SKILL.md +3 -3
  148. package/skills/write-middleware/SKILL.md +11 -5
  149. package/skills/write-use-case/SKILL.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class TSConfigManager {\n /**\n * Aliases list (from tsconfig paths)\n */\n public aliases: Record<string, string[]> = {};\n\n /**\n * Base URL for resolving paths\n */\n public baseUrl: string = \".\";\n\n /**\n * TSConfig\n */\n public tsconfig: any;\n\n public init() {\n if (this.tsconfig) return;\n\n // use typescript to load the tsconfig.json file\n const output = ts.readConfigFile(Path.toAbsolute(\"tsconfig.json\"), ts.sys.readFile);\n\n this.tsconfig = output.config!;\n\n this.aliases = output.config?.compilerOptions?.paths || {};\n\n this.baseUrl = output.config?.compilerOptions?.baseUrl || \".\";\n }\n\n /**\n * Check if the given path is an alias\n * This checks if it's a REAL path alias (not an external package alias)\n *\n * Real aliases map to local paths (e.g., app/* -> src/app/*, src/* -> src/*)\n * External package aliases map to themselves with @ prefix (e.g., @warlock.js/core -> @warlock.js/core)\n */\n public isAlias(path: string) {\n if (!this.tsconfig) {\n this.init();\n }\n\n return Object.keys(this.aliases).some((alias) => {\n // Remove /* from alias pattern for matching\n const aliasPattern = alias.replace(\"/*\", \"\");\n\n if (!path.startsWith(aliasPattern)) {\n return false;\n }\n\n // Check if this is a real alias or just an external package mapping\n const aliasTargets = this.aliases[alias];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return false;\n }\n\n // If the alias starts with @, it's likely an external package alias\n // Example: \"@warlock.js/core\" -> \"@warlock.js/core\" (external package)\n if (aliasPattern.startsWith(\"@\")) {\n return false;\n }\n\n // Otherwise, it's a real path alias (including self-referencing ones like src/* -> src/*)\n // Example: \"app/*\" -> \"src/app/*\" (real alias)\n // Example: \"src/*\" -> \"src/*\" (self-referencing alias, still valid)\n return true;\n });\n }\n\n /**\n * Get the alias key that matches the given import path\n */\n public getMatchingAlias(path: string): string | null {\n const aliasKey = Object.keys(this.aliases).find((alias) => {\n const aliasPattern = alias.replace(\"/*\", \"\");\n return path.startsWith(aliasPattern);\n });\n\n return aliasKey || null;\n }\n\n /**\n * Resolve an alias import path to a relative path based on tsconfig paths\n * Example: \"app/users/services/get-users.service\" -> \"src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias (e.g., \"app/users/services/get-users.service\")\n * @returns The resolved relative path or null if alias not found\n */\n public resolveAliasPath(checkingPath: string): string | null {\n // Find matching alias from tsconfig paths\n const aliasKey = this.getMatchingAlias(checkingPath);\n\n if (!aliasKey) return null;\n\n const aliasTargets = this.aliases[aliasKey];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return null;\n }\n\n // Get the first target path (usually there's only one)\n const targetPattern = aliasTargets[0];\n\n // Replace alias pattern with target pattern\n const aliasPattern = aliasKey.replace(\"/*\", \"\");\n const targetBase = targetPattern.replace(\"/*\", \"\");\n // Remove any leading slash so path.join does not drop the base\n const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\\\]/, \"\");\n\n // Join the target base with the relative part\n const resolvedPath = path.join(targetBase, relativePart);\n\n return Path.normalize(resolvedPath);\n }\n\n /**\n * Resolve an alias import path to an absolute path\n * Example: \"app/users/services/get-users.service\" -> \"/absolute/path/to/src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias\n * @returns The resolved absolute path or null if alias not found\n */\n public resolveAliasToAbsolute(path: string): string | null {\n const relativePath = this.resolveAliasPath(path);\n\n if (!relativePath) return null;\n\n return Path.normalize(Path.toAbsolute(relativePath));\n }\n}\n\nexport const tsconfigManager = new TSConfigManager();\n"],"mappings":";;;;;AAIA,IAAa,kBAAb,MAA6B;;iBAIgB,CAAC;iBAKnB;;CAOzB,AAAO,OAAO;EACZ,IAAI,KAAK,UAAU;EAGnB,MAAM,SAAS,GAAG,eAAe,KAAK,WAAW,eAAe,GAAG,GAAG,IAAI,QAAQ;EAElF,KAAK,WAAW,OAAO;EAEvB,KAAK,UAAU,OAAO,QAAQ,iBAAiB,SAAS,CAAC;EAEzD,KAAK,UAAU,OAAO,QAAQ,iBAAiB,WAAW;CAC5D;;;;;;;;CASA,AAAO,QAAQ,MAAc;EAC3B,IAAI,CAAC,KAAK,UACR,KAAK,KAAK;EAGZ,OAAO,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GAE/C,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAE3C,IAAI,CAAC,KAAK,WAAW,YAAY,GAC/B,OAAO;GAIT,MAAM,eAAe,KAAK,QAAQ;GAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;GAKT,IAAI,aAAa,WAAW,GAAG,GAC7B,OAAO;GAMT,OAAO;EACT,CAAC;CACH;;;;CAKA,AAAO,iBAAiB,MAA6B;EAMnD,OALiB,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GACzD,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAC3C,OAAO,KAAK,WAAW,YAAY;EACrC,CAEc,KAAK;CACrB;;;;;;;;CASA,AAAO,iBAAiB,cAAqC;EAE3D,MAAM,WAAW,KAAK,iBAAiB,YAAY;EAEnD,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,eAAe,KAAK,QAAQ;EAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;EAIT,MAAM,gBAAgB,aAAa;EAGnC,MAAM,eAAe,SAAS,QAAQ,MAAM,EAAE;EAC9C,MAAM,aAAa,cAAc,QAAQ,MAAM,EAAE;EAEjD,MAAM,eAAe,aAAa,UAAU,aAAa,MAAM,CAAC,CAAC,QAAQ,UAAU,EAAE;EAGrF,MAAM,eAAe,KAAK,KAAK,YAAY,YAAY;EAEvD,OAAO,KAAK,UAAU,YAAY;CACpC;;;;;;;;CASA,AAAO,uBAAuB,MAA6B;EACzD,MAAM,eAAe,KAAK,iBAAiB,IAAI;EAE/C,IAAI,CAAC,cAAc,OAAO;EAE1B,OAAO,KAAK,UAAU,KAAK,WAAW,YAAY,CAAC;CACrD;AACF;AAEA,MAAa,kBAAkB,IAAI,gBAAgB"}
1
+ {"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class TSConfigManager {\n /**\n * Aliases list (from tsconfig paths)\n */\n public aliases: Record<string, string[]> = {};\n\n /**\n * Base URL for resolving paths\n */\n public baseUrl: string = \".\";\n\n /**\n * TSConfig\n */\n public tsconfig: any;\n\n public init() {\n if (this.tsconfig) return;\n\n // use typescript to load the tsconfig.json file\n const output = ts.readConfigFile(Path.toAbsolute(\"tsconfig.json\"), ts.sys.readFile);\n\n this.tsconfig = output.config!;\n\n this.aliases = output.config?.compilerOptions?.paths || {};\n\n this.baseUrl = output.config?.compilerOptions?.baseUrl || \".\";\n }\n\n /**\n * Check if the given path is an alias\n * This checks if it's a REAL path alias (not an external package alias)\n *\n * Real aliases map to local paths (e.g., app/* -> src/app/*, src/* -> src/*)\n * External package aliases map to themselves with @ prefix (e.g., @warlock.js/core -> @warlock.js/core)\n */\n public isAlias(path: string) {\n if (!this.tsconfig) {\n this.init();\n }\n\n return Object.keys(this.aliases).some((alias) => {\n // Remove /* from alias pattern for matching\n const aliasPattern = alias.replace(\"/*\", \"\");\n\n if (!path.startsWith(aliasPattern)) {\n return false;\n }\n\n // Check if this is a real alias or just an external package mapping\n const aliasTargets = this.aliases[alias];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return false;\n }\n\n // If the alias starts with @, it's likely an external package alias\n // Example: \"@warlock.js/core\" -> \"@warlock.js/core\" (external package)\n if (aliasPattern.startsWith(\"@\")) {\n return false;\n }\n\n // Otherwise, it's a real path alias (including self-referencing ones like src/* -> src/*)\n // Example: \"app/*\" -> \"src/app/*\" (real alias)\n // Example: \"src/*\" -> \"src/*\" (self-referencing alias, still valid)\n return true;\n });\n }\n\n /**\n * Get the alias key that matches the given import path\n */\n public getMatchingAlias(path: string): string | null {\n const aliasKey = Object.keys(this.aliases).find((alias) => {\n const aliasPattern = alias.replace(\"/*\", \"\");\n return path.startsWith(aliasPattern);\n });\n\n return aliasKey || null;\n }\n\n /**\n * Resolve an alias import path to a relative path based on tsconfig paths\n * Example: \"app/users/services/get-users.service\" -> \"src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias (e.g., \"app/users/services/get-users.service\")\n * @returns The resolved relative path or null if alias not found\n */\n public resolveAliasPath(checkingPath: string): string | null {\n // Find matching alias from tsconfig paths\n const aliasKey = this.getMatchingAlias(checkingPath);\n\n if (!aliasKey) return null;\n\n const aliasTargets = this.aliases[aliasKey];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return null;\n }\n\n // Get the first target path (usually there's only one)\n const targetPattern = aliasTargets[0];\n\n if (targetPattern === undefined) {\n return null;\n }\n\n // Replace alias pattern with target pattern\n const aliasPattern = aliasKey.replace(\"/*\", \"\");\n const targetBase = targetPattern.replace(\"/*\", \"\");\n // Remove any leading slash so path.join does not drop the base\n const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\\\]/, \"\");\n\n // Join the target base with the relative part\n const resolvedPath = path.join(targetBase, relativePart);\n\n return Path.normalize(resolvedPath);\n }\n\n /**\n * Resolve an alias import path to an absolute path\n * Example: \"app/users/services/get-users.service\" -> \"/absolute/path/to/src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias\n * @returns The resolved absolute path or null if alias not found\n */\n public resolveAliasToAbsolute(path: string): string | null {\n const relativePath = this.resolveAliasPath(path);\n\n if (!relativePath) return null;\n\n return Path.normalize(Path.toAbsolute(relativePath));\n }\n}\n\nexport const tsconfigManager = new TSConfigManager();\n"],"mappings":";;;;;AAIA,IAAa,kBAAb,MAA6B;;iBAIgB,CAAC;iBAKnB;;CAOzB,AAAO,OAAO;EACZ,IAAI,KAAK,UAAU;EAGnB,MAAM,SAAS,GAAG,eAAe,KAAK,WAAW,eAAe,GAAG,GAAG,IAAI,QAAQ;EAElF,KAAK,WAAW,OAAO;EAEvB,KAAK,UAAU,OAAO,QAAQ,iBAAiB,SAAS,CAAC;EAEzD,KAAK,UAAU,OAAO,QAAQ,iBAAiB,WAAW;CAC5D;;;;;;;;CASA,AAAO,QAAQ,MAAc;EAC3B,IAAI,CAAC,KAAK,UACR,KAAK,KAAK;EAGZ,OAAO,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GAE/C,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAE3C,IAAI,CAAC,KAAK,WAAW,YAAY,GAC/B,OAAO;GAIT,MAAM,eAAe,KAAK,QAAQ;GAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;GAKT,IAAI,aAAa,WAAW,GAAG,GAC7B,OAAO;GAMT,OAAO;EACT,CAAC;CACH;;;;CAKA,AAAO,iBAAiB,MAA6B;EAMnD,OALiB,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GACzD,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAC3C,OAAO,KAAK,WAAW,YAAY;EACrC,CAEc,KAAK;CACrB;;;;;;;;CASA,AAAO,iBAAiB,cAAqC;EAE3D,MAAM,WAAW,KAAK,iBAAiB,YAAY;EAEnD,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,eAAe,KAAK,QAAQ;EAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;EAIT,MAAM,gBAAgB,aAAa;EAEnC,IAAI,kBAAkB,QACpB,OAAO;EAIT,MAAM,eAAe,SAAS,QAAQ,MAAM,EAAE;EAC9C,MAAM,aAAa,cAAc,QAAQ,MAAM,EAAE;EAEjD,MAAM,eAAe,aAAa,UAAU,aAAa,MAAM,CAAC,CAAC,QAAQ,UAAU,EAAE;EAGrF,MAAM,eAAe,KAAK,KAAK,YAAY,YAAY;EAEvD,OAAO,KAAK,UAAU,YAAY;CACpC;;;;;;;;CASA,AAAO,uBAAuB,MAA6B;EACzD,MAAM,eAAe,KAAK,iBAAiB,IAAI;EAE/C,IAAI,CAAC,cAAc,OAAO;EAE1B,OAAO,KAAK,UAAU,KAAK,WAAW,YAAY,CAAC;CACrD;AACF;AAEA,MAAa,kBAAkB,IAAI,gBAAgB"}
@@ -5,6 +5,7 @@ import { devLogError, devLogInfo, devLogSuccess, devServeLog } from "./dev-logge
5
5
  import { filesOrchestrator } from "./files-orchestrator.mjs";
6
6
  import { readConfigAst } from "./read-config-ast.mjs";
7
7
  import { runTypingsGeneration } from "./run-typings-generation.mjs";
8
+ import { extractTranslationKeys } from "./translation-type-generator.mjs";
8
9
  import { join, resolve } from "path";
9
10
  import { ensureDirectoryAsync } from "@warlock.js/fs";
10
11
  import { constants } from "fs";
@@ -38,16 +39,19 @@ var TypeGenerator = class {
38
39
  await this.ensureOutputDir();
39
40
  const storageFile = join(this.outputDir, "storage.d.ts");
40
41
  const configFile = join(this.outputDir, "config.d.ts");
41
- const [manifestExists, storageExists, configExists] = await Promise.all([
42
+ const translationsFile = join(this.outputDir, "translations.d.ts");
43
+ const [manifestExists, storageExists, configExists, translationsExist] = await Promise.all([
42
44
  this.exists(this.manifestPath),
43
45
  this.exists(storageFile),
44
- this.exists(configFile)
46
+ this.exists(configFile),
47
+ this.exists(translationsFile)
45
48
  ]);
46
- if (!manifestExists || !storageExists || !configExists) await this.fullGeneration();
49
+ if (!manifestExists || !storageExists || !configExists || !translationsExist) await this.fullGeneration();
47
50
  else {
48
51
  await this.loadManifest();
49
52
  await this.reconcile();
50
53
  }
54
+ await this.generateTranslationTypes();
51
55
  await this.saveManifest();
52
56
  }
53
57
  /**
@@ -130,7 +134,7 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
130
134
  * Check if a file change should trigger type regeneration
131
135
  */
132
136
  shouldRegenerateTypes(changedPath) {
133
- return changedPath.includes("src/config/") || changedPath.includes("config/");
137
+ return changedPath.includes("src/config/") || changedPath.includes("config/") || this.isLocalesFile(changedPath);
134
138
  }
135
139
  /**
136
140
  * Handle file change - uses incremental update via cache
@@ -142,6 +146,10 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
142
146
  await this.saveManifest();
143
147
  return;
144
148
  }
149
+ if (this.isLocalesFile(changedPath)) {
150
+ await this.generateTranslationTypes();
151
+ return;
152
+ }
145
153
  const match = changedPath.match(/config\/([^/]+)\.[^.]+$/);
146
154
  if (!match) return;
147
155
  const configName = match[1];
@@ -183,6 +191,31 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
183
191
  devServeLog(`⚠️ Failed to generate config types: ${error}`);
184
192
  }
185
193
  }
194
+ /** Generate web's app-augmented translation-key registry. */
195
+ async generateTranslationTypes() {
196
+ const keys = /* @__PURE__ */ new Set();
197
+ for (const [path, fileManager] of filesOrchestrator.getFiles()) {
198
+ if (!this.isLocalesFile(path)) continue;
199
+ const sourceFile = await readConfigAst(fileManager.absolutePath);
200
+ if (sourceFile) for (const key of extractTranslationKeys(sourceFile)) keys.add(key);
201
+ }
202
+ const content = `// Auto-generated by Warlock.js - DO NOT EDIT
203
+ // Generated from groupedTranslations calls in app locale files
204
+
205
+ import "@warlock.js/web";
206
+
207
+ declare module "@warlock.js/web" {
208
+ interface TranslationKeyRegistry {
209
+ ${Array.from(keys).sort().map((key) => ` ${JSON.stringify(key)}: true;`).join("\n")}
210
+ }
211
+ }
212
+ `;
213
+ await writeFile(join(this.outputDir, "translations.d.ts"), content, "utf-8");
214
+ devLogSuccess(`Generated translation types: ${keys.size} keys`);
215
+ }
216
+ isLocalesFile(path) {
217
+ return Path.normalize(path).includes("/utils/locales.");
218
+ }
186
219
  /**
187
220
  * Quote an interface key that isn't a valid JS identifier (e.g. a config
188
221
  * named `use-cases` or a storage driver `do-spaces`) so the generated
@@ -430,7 +463,10 @@ ${allKeys.map((key) => ` "${key}": true;`).join("\n")}
430
463
  * touched a config file, since nothing else contributes to them.
431
464
  */
432
465
  async executeTypingsGenerator(upcomingFiles) {
433
- if (!Array.from(new Set(upcomingFiles)).some((file) => Path.normalize(file).includes("src/config/"))) return;
466
+ if (!Array.from(new Set(upcomingFiles)).some((file) => {
467
+ const normalizedPath = Path.normalize(file);
468
+ return normalizedPath.includes("src/config/") || this.isLocalesFile(normalizedPath);
469
+ })) return;
434
470
  await runTypingsGeneration(this.typingsGenerationPorts);
435
471
  }
436
472
  };
@@ -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 { join, resolve } from \"path\";\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\";\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\n const [manifestExists, storageExists, configExists] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists) {\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.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 // 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.includes(\"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 changedPath.includes(\"src/config/\") || changedPath.includes(\"config/\");\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 // 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 === \"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.includes(\"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 * 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 actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) =>\n Path.normalize(file).includes(\"src/config/\"),\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 { join, resolve } from \"path\";\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 \"./translation-type-generator\";\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 // 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.includes(\"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 );\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)) {\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 === \"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.includes(\"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 /** Generate web's app-augmented translation-key registry. */\n private async generateTranslationTypes(): Promise<void> {\n const keys = new Set<string>();\n\n for (const [path, fileManager] of filesOrchestrator.getFiles()) {\n if (!this.isLocalesFile(path)) {\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 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 in app locale files\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 actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) => {\n const normalizedPath = Path.normalize(file);\n return normalizedPath.includes(\"src/config/\") || this.isLocalesFile(normalizedPath);\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,5 +1,5 @@
1
+ import { hashPassword, verifyPassword } from "./password.mjs";
1
2
  import { decrypt, encrypt } from "./encrypt.mjs";
2
3
  import { hmacHash } from "./hash.mjs";
3
- import { hashPassword, verifyPassword } from "./password.mjs";
4
4
 
5
5
  export { };
@@ -1,6 +1,12 @@
1
1
  //#region ../core/src/errors/container-key-missing-error.ts
2
2
  /**
3
- * Thrown by `container.getOrFail(key)` when `key` is not registered.
3
+ * Maximum number of registered keys named in a {@link ContainerKeyMissingError}
4
+ * message. Bounded so a container with hundreds of registrations doesn't
5
+ * turn a diagnostic message into a wall of text.
6
+ */
7
+ const MAX_LISTED_KEYS = 20;
8
+ /**
9
+ * Thrown by `container.get(key)` when `key` is not registered.
4
10
  *
5
11
  * On a single-instance runtime this is an ordinary "you forgot to register
6
12
  * this" error, worded exactly as before. When more than one copy of
@@ -13,13 +19,19 @@
13
19
  * there.
14
20
  */
15
21
  var ContainerKeyMissingError = class ContainerKeyMissingError extends Error {
16
- constructor(key, instanceCount) {
17
- super(ContainerKeyMissingError.buildMessage(key, instanceCount));
22
+ constructor(key, instanceCount, registeredKeys = []) {
23
+ super(ContainerKeyMissingError.buildMessage(key, instanceCount, registeredKeys));
18
24
  this.name = "ContainerKeyMissingError";
19
25
  }
20
- static buildMessage(key, instanceCount) {
21
- if (instanceCount <= 1) return `Container key "${key}" is not registered.`;
22
- return `Container key "${key}" is not registered on this instance, but ${instanceCount} separate copies of @warlock.js/core's container are currently loaded in this process. The value may have been set on one of the other instances, not this one — this happens when a source-consumption dev path (e.g. Vite's SSR module runner alongside Node's ESM loader) evaluates the package more than once. A published install resolves to a single copy and is not affected.`;
26
+ static buildMessage(key, instanceCount, registeredKeys) {
27
+ return `${instanceCount <= 1 ? `Container key "${key}" is not registered.` : `Container key "${key}" is not registered on this instance, but ${instanceCount} separate copies of @warlock.js/core's container are currently loaded in this process. The value may have been set on one of the other instances, not this one — this happens when a source-consumption dev path (e.g. Vite's SSR module runner alongside Node's ESM loader) evaluates the package more than once. A published install resolves to a single copy and is not affected.`} ${ContainerKeyMissingError.describeRegisteredKeys(registeredKeys)}`;
28
+ }
29
+ static describeRegisteredKeys(registeredKeys) {
30
+ if (registeredKeys.length === 0) return "No keys are registered.";
31
+ const listed = registeredKeys.slice(0, MAX_LISTED_KEYS);
32
+ const remaining = registeredKeys.length - listed.length;
33
+ const suffix = remaining > 0 ? `, and ${remaining} more` : "";
34
+ return `Registered keys: ${listed.map((registeredKey) => `"${registeredKey}"`).join(", ")}${suffix}.`;
23
35
  }
24
36
  };
25
37
 
@@ -1 +1 @@
1
- {"version":3,"file":"container-key-missing-error.mjs","names":[],"sources":["../../../../../../../core/src/errors/container-key-missing-error.ts"],"sourcesContent":["/**\n * Thrown by `container.getOrFail(key)` when `key` is not registered.\n *\n * On a single-instance runtime this is an ordinary \"you forgot to register\n * this\" error, worded exactly as before. When more than one copy of\n * `@warlock.js/core`'s container module is loaded in the same process —\n * confirmed via `container-instance-registry.ts`, which every loaded copy\n * registers itself into through `globalThis` — the miss may instead mean\n * the value WAS set, just on the other instance. That is a dev/\n * source-consumption condition only: a published install resolves to a\n * single `node_modules` copy, so a single instance is always registered\n * there.\n */\nexport class ContainerKeyMissingError extends Error {\n public constructor(key: string, instanceCount: number) {\n super(ContainerKeyMissingError.buildMessage(key, instanceCount));\n this.name = \"ContainerKeyMissingError\";\n }\n\n private static buildMessage(key: string, instanceCount: number): string {\n if (instanceCount <= 1) {\n return `Container key \"${key}\" is not registered.`;\n }\n\n return (\n `Container key \"${key}\" is not registered on this instance, but ${instanceCount} ` +\n `separate copies of @warlock.js/core's container are currently loaded in this ` +\n `process. The value may have been set on one of the other instances, not this ` +\n `one — this happens when a source-consumption dev path (e.g. Vite's SSR module ` +\n `runner alongside Node's ESM loader) evaluates the package more than once. A ` +\n `published install resolves to a single copy and is not affected.`\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAa,2BAAb,MAAa,iCAAiC,MAAM;CAClD,AAAO,YAAY,KAAa,eAAuB;EACrD,MAAM,yBAAyB,aAAa,KAAK,aAAa,CAAC;EAC/D,KAAK,OAAO;CACd;CAEA,OAAe,aAAa,KAAa,eAA+B;EACtE,IAAI,iBAAiB,GACnB,OAAO,kBAAkB,IAAI;EAG/B,OACE,kBAAkB,IAAI,4CAA4C,cAAc;CAOpF;AACF"}
1
+ {"version":3,"file":"container-key-missing-error.mjs","names":[],"sources":["../../../../../../../core/src/errors/container-key-missing-error.ts"],"sourcesContent":["/**\n * Maximum number of registered keys named in a {@link ContainerKeyMissingError}\n * message. Bounded so a container with hundreds of registrations doesn't\n * turn a diagnostic message into a wall of text.\n */\nconst MAX_LISTED_KEYS = 20;\n\n/**\n * Thrown by `container.get(key)` when `key` is not registered.\n *\n * On a single-instance runtime this is an ordinary \"you forgot to register\n * this\" error, worded exactly as before. When more than one copy of\n * `@warlock.js/core`'s container module is loaded in the same process —\n * confirmed via `container-instance-registry.ts`, which every loaded copy\n * registers itself into through `globalThis` — the miss may instead mean\n * the value WAS set, just on the other instance. That is a dev/\n * source-consumption condition only: a published install resolves to a\n * single `node_modules` copy, so a single instance is always registered\n * there.\n */\nexport class ContainerKeyMissingError extends Error {\n public constructor(key: string, instanceCount: number, registeredKeys: readonly string[] = []) {\n super(ContainerKeyMissingError.buildMessage(key, instanceCount, registeredKeys));\n this.name = \"ContainerKeyMissingError\";\n }\n\n private static buildMessage(\n key: string,\n instanceCount: number,\n registeredKeys: readonly string[],\n ): string {\n const base =\n instanceCount <= 1\n ? `Container key \"${key}\" is not registered.`\n : `Container key \"${key}\" is not registered on this instance, but ${instanceCount} ` +\n `separate copies of @warlock.js/core's container are currently loaded in this ` +\n `process. The value may have been set on one of the other instances, not this ` +\n `one — this happens when a source-consumption dev path (e.g. Vite's SSR module ` +\n `runner alongside Node's ESM loader) evaluates the package more than once. A ` +\n `published install resolves to a single copy and is not affected.`;\n\n return `${base} ${ContainerKeyMissingError.describeRegisteredKeys(registeredKeys)}`;\n }\n\n private static describeRegisteredKeys(registeredKeys: readonly string[]): string {\n if (registeredKeys.length === 0) {\n return \"No keys are registered.\";\n }\n\n const listed = registeredKeys.slice(0, MAX_LISTED_KEYS);\n const remaining = registeredKeys.length - listed.length;\n const suffix = remaining > 0 ? `, and ${remaining} more` : \"\";\n\n return `Registered keys: ${listed.map((registeredKey) => `\"${registeredKey}\"`).join(\", \")}${suffix}.`;\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,kBAAkB;;;;;;;;;;;;;;AAexB,IAAa,2BAAb,MAAa,iCAAiC,MAAM;CAClD,AAAO,YAAY,KAAa,eAAuB,iBAAoC,CAAC,GAAG;EAC7F,MAAM,yBAAyB,aAAa,KAAK,eAAe,cAAc,CAAC;EAC/E,KAAK,OAAO;CACd;CAEA,OAAe,aACb,KACA,eACA,gBACQ;EAWR,OAAO,GATL,iBAAiB,IACb,kBAAkB,IAAI,wBACtB,kBAAkB,IAAI,4CAA4C,cAAc,uXAOvE,GAAG,yBAAyB,uBAAuB,cAAc;CAClF;CAEA,OAAe,uBAAuB,gBAA2C;EAC/E,IAAI,eAAe,WAAW,GAC5B,OAAO;EAGT,MAAM,SAAS,eAAe,MAAM,GAAG,eAAe;EACtD,MAAM,YAAY,eAAe,SAAS,OAAO;EACjD,MAAM,SAAS,YAAY,IAAI,SAAS,UAAU,SAAS;EAE3D,OAAO,oBAAoB,OAAO,KAAK,kBAAkB,IAAI,cAAc,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,OAAO;CACrG;AACF"}
@@ -0,0 +1,18 @@
1
+ import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
2
+
3
+ //#region ../core/src/generations/features/auth-google.feature.ts
4
+ /**
5
+ * `warlock add auth-google` — Google sign-in for @warlock.js/auth. `jose` verifies
6
+ * the id_token; auth loads it lazily, so it is only needed once this is added.
7
+ */
8
+ const authGoogleFeature = {
9
+ description: "Google sign-in for @warlock.js/auth (installs jose for id_token verification). Configure auth.providers.google, then call startProviderLogin / completeProviderLogin",
10
+ dependencies: {
11
+ "@warlock.js/auth": INSTALLED_WARLOCK_VERSION,
12
+ jose: "^6.1.0"
13
+ }
14
+ };
15
+
16
+ //#endregion
17
+ export { authGoogleFeature };
18
+ //# sourceMappingURL=auth-google.feature.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-google.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/auth-google.feature.ts"],"sourcesContent":["import { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\n/**\n * `warlock add auth-google` — Google sign-in for @warlock.js/auth. `jose` verifies\n * the id_token; auth loads it lazily, so it is only needed once this is added.\n */\nexport const authGoogleFeature: FeatureDefinition = {\n description:\n \"Google sign-in for @warlock.js/auth (installs jose for id_token verification). Configure auth.providers.google, then call startProviderLogin / completeProviderLogin\",\n dependencies: {\n \"@warlock.js/auth\": INSTALLED_WARLOCK_VERSION,\n jose: \"^6.1.0\",\n },\n};\n"],"mappings":";;;;;;;AAMA,MAAa,oBAAuC;CAClD,aACE;CACF,cAAc;EACZ,oBAAoB;EACpB,MAAM;CACR;AACF"}
@@ -0,0 +1,19 @@
1
+ import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
2
+
3
+ //#region ../core/src/generations/features/auth-passkeys.feature.ts
4
+ /**
5
+ * `warlock add auth-passkeys` — passkey (WebAuthn) login for @warlock.js/auth.
6
+ * Installs the server library only; the browser half is `@simplewebauthn/browser`,
7
+ * which belongs in whatever bundle runs the ceremony.
8
+ */
9
+ const authPasskeysFeature = {
10
+ description: "Passkey login for @warlock.js/auth (installs @simplewebauthn/server; add @simplewebauthn/browser to your client). Configure auth.passkeys { rpID, rpName, origin }",
11
+ dependencies: {
12
+ "@warlock.js/auth": INSTALLED_WARLOCK_VERSION,
13
+ "@simplewebauthn/server": "^13.1.0"
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { authPasskeysFeature };
19
+ //# sourceMappingURL=auth-passkeys.feature.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-passkeys.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/auth-passkeys.feature.ts"],"sourcesContent":["import { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\n/**\n * `warlock add auth-passkeys` — passkey (WebAuthn) login for @warlock.js/auth.\n * Installs the server library only; the browser half is `@simplewebauthn/browser`,\n * which belongs in whatever bundle runs the ceremony.\n */\nexport const authPasskeysFeature: FeatureDefinition = {\n description:\n \"Passkey login for @warlock.js/auth (installs @simplewebauthn/server; add @simplewebauthn/browser to your client). Configure auth.passkeys { rpID, rpName, origin }\",\n dependencies: {\n \"@warlock.js/auth\": INSTALLED_WARLOCK_VERSION,\n \"@simplewebauthn/server\": \"^13.1.0\",\n },\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,sBAAyC;CACpD,aACE;CACF,cAAc;EACZ,oBAAoB;EACpB,0BAA0B;CAC5B;AACF"}
@@ -8,6 +8,8 @@ import { aiPanopticFeature } from "./ai-panoptic.feature.mjs";
8
8
  import { aiToolsFeature } from "./ai-tools.feature.mjs";
9
9
  import { aiWorkspaceFeature } from "./ai-workspace.feature.mjs";
10
10
  import { aiFeature } from "./ai.feature.mjs";
11
+ import { authGoogleFeature } from "./auth-google.feature.mjs";
12
+ import { authPasskeysFeature } from "./auth-passkeys.feature.mjs";
11
13
  import { heraldFeature } from "./herald.feature.mjs";
12
14
  import { imageFeature } from "./image.feature.mjs";
13
15
  import { mailFeature } from "./mail.feature.mjs";
@@ -15,6 +17,7 @@ import { mongodbFeature } from "./mongodb.feature.mjs";
15
17
  import { mysqlFeature } from "./mysql.feature.mjs";
16
18
  import { notificationsFeature } from "./notifications.feature.mjs";
17
19
  import { postgresFeature } from "./postgres.feature.mjs";
20
+ import { queueFeature } from "./queue.feature.mjs";
18
21
  import { reactEmailFeature } from "./react-email.feature.mjs";
19
22
  import { reactFeature } from "./react.feature.mjs";
20
23
  import { redisFeature } from "./redis.feature.mjs";
@@ -53,9 +56,12 @@ const featuresMap = {
53
56
  tailwind: tailwindFeature,
54
57
  shadcn: shadcnFeature,
55
58
  herald: heraldFeature,
59
+ queue: queueFeature,
56
60
  socket: socketFeature,
57
61
  notifications: notificationsFeature,
58
62
  access: accessFeature,
63
+ "auth-google": authGoogleFeature,
64
+ "auth-passkeys": authPasskeysFeature,
59
65
  ai: aiFeature,
60
66
  "ai-openai": aiOpenaiFeature,
61
67
  "ai-google": aiGoogleFeature,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/index.ts"],"sourcesContent":["import { accessFeature } from \"./access.feature\";\r\nimport { aiAnthropicFeature } from \"./ai-anthropic.feature\";\r\nimport { aiBedrockFeature } from \"./ai-bedrock.feature\";\r\nimport { aiGoogleFeature } from \"./ai-google.feature\";\r\nimport { aiOllamaFeature } from \"./ai-ollama.feature\";\r\nimport { aiOpenaiFeature } from \"./ai-openai.feature\";\r\nimport { aiPanopticFeature } from \"./ai-panoptic.feature\";\r\nimport { aiToolsFeature } from \"./ai-tools.feature\";\r\nimport { aiWorkspaceFeature } from \"./ai-workspace.feature\";\r\nimport { aiFeature } from \"./ai.feature\";\r\nimport { heraldFeature } from \"./herald.feature\";\r\nimport { imageFeature } from \"./image.feature\";\r\nimport { mailFeature } from \"./mail.feature\";\r\nimport { mongodbFeature } from \"./mongodb.feature\";\r\nimport { mysqlFeature } from \"./mysql.feature\";\r\nimport { notificationsFeature } from \"./notifications.feature\";\r\nimport { postgresFeature } from \"./postgres.feature\";\r\nimport { reactEmailFeature } from \"./react-email.feature\";\r\nimport { reactFeature } from \"./react.feature\";\r\nimport { redisFeature } from \"./redis.feature\";\r\nimport { s3Feature } from \"./s3.feature\";\r\nimport { schedulerFeature } from \"./scheduler.feature\";\r\nimport { sesFeature } from \"./ses.feature\";\r\nimport { shadcnFeature } from \"./shadcn.feature\";\r\nimport { socketFeature } from \"./socket.feature\";\r\nimport { tailwindFeature } from \"./tailwind.feature\";\r\nimport { testFeature } from \"./test.feature\";\r\nimport type { FeatureDefinition } from \"./types\";\r\nimport { webFeature } from \"./web.feature\";\r\n\r\nexport type { FeatureDefinition } from \"./types\";\r\n\r\n/**\r\n * The feature registry `warlock add` dispatches against.\r\n *\r\n * This file is an INDEX, nothing more: every entry lives in its own module\r\n * alongside the `onExecuting` body it runs. Key order is load-bearing — it is\r\n * the order `--list` prints and the order the \"not allowed\" error lists — so\r\n * add new features in the place they should appear, not alphabetically.\r\n */\r\nexport const featuresMap: Record<string, FeatureDefinition> = {\r\n \"react-email\": reactEmailFeature,\r\n react: reactFeature,\r\n image: imageFeature,\r\n mail: mailFeature,\r\n ses: sesFeature,\r\n mongodb: mongodbFeature,\r\n scheduler: schedulerFeature,\r\n // swagger / postman intentionally omitted — those packages do not exist yet;\r\n // they will ship together in the unified @warlock.js/api-docs package.\r\n postgres: postgresFeature,\r\n mysql: mysqlFeature,\r\n redis: redisFeature,\r\n s3: s3Feature,\r\n test: testFeature,\r\n web: webFeature,\r\n // Directly after `web`, and only there: it `requires` it, it is useless\r\n // without it, and a reader scanning `--list` for the page stack should meet\r\n // the two together rather than find styling filed between queues and sockets.\r\n tailwind: tailwindFeature,\r\n // Immediately after `tailwind`, for the same reason `tailwind` follows `web`:\r\n // it `requires` it, it appends to the stylesheet that feature creates, and the\r\n // three of them are one stack a reader should meet in build order.\r\n shadcn: shadcnFeature,\r\n herald: heraldFeature,\r\n socket: socketFeature,\r\n notifications: notificationsFeature,\r\n access: accessFeature,\r\n ai: aiFeature,\r\n \"ai-openai\": aiOpenaiFeature,\r\n \"ai-google\": aiGoogleFeature,\r\n \"ai-anthropic\": aiAnthropicFeature,\r\n \"ai-bedrock\": aiBedrockFeature,\r\n \"ai-ollama\": aiOllamaFeature,\r\n \"ai-tools\": aiToolsFeature,\r\n \"ai-panoptic\": aiPanopticFeature,\r\n \"ai-workspace\": aiWorkspaceFeature,\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,cAAiD;CAC5D,eAAe;CACf,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,SAAS;CACT,WAAW;CAGX,UAAU;CACV,OAAO;CACP,OAAO;CACP,IAAI;CACJ,MAAM;CACN,KAAK;CAIL,UAAU;CAIV,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,eAAe;CACf,QAAQ;CACR,IAAI;CACJ,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,YAAY;CACZ,eAAe;CACf,gBAAgB;AAClB"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/index.ts"],"sourcesContent":["import { accessFeature } from \"./access.feature\";\r\nimport { aiAnthropicFeature } from \"./ai-anthropic.feature\";\r\nimport { aiBedrockFeature } from \"./ai-bedrock.feature\";\r\nimport { aiGoogleFeature } from \"./ai-google.feature\";\r\nimport { aiOllamaFeature } from \"./ai-ollama.feature\";\r\nimport { aiOpenaiFeature } from \"./ai-openai.feature\";\r\nimport { aiPanopticFeature } from \"./ai-panoptic.feature\";\r\nimport { aiToolsFeature } from \"./ai-tools.feature\";\r\nimport { aiWorkspaceFeature } from \"./ai-workspace.feature\";\r\nimport { aiFeature } from \"./ai.feature\";\r\nimport { authGoogleFeature } from \"./auth-google.feature\";\r\nimport { authPasskeysFeature } from \"./auth-passkeys.feature\";\r\nimport { heraldFeature } from \"./herald.feature\";\r\nimport { imageFeature } from \"./image.feature\";\r\nimport { mailFeature } from \"./mail.feature\";\r\nimport { mongodbFeature } from \"./mongodb.feature\";\r\nimport { mysqlFeature } from \"./mysql.feature\";\r\nimport { notificationsFeature } from \"./notifications.feature\";\r\nimport { postgresFeature } from \"./postgres.feature\";\r\nimport { queueFeature } from \"./queue.feature\";\r\nimport { reactEmailFeature } from \"./react-email.feature\";\r\nimport { reactFeature } from \"./react.feature\";\r\nimport { redisFeature } from \"./redis.feature\";\r\nimport { s3Feature } from \"./s3.feature\";\r\nimport { schedulerFeature } from \"./scheduler.feature\";\r\nimport { sesFeature } from \"./ses.feature\";\r\nimport { shadcnFeature } from \"./shadcn.feature\";\r\nimport { socketFeature } from \"./socket.feature\";\r\nimport { tailwindFeature } from \"./tailwind.feature\";\r\nimport { testFeature } from \"./test.feature\";\r\nimport type { FeatureDefinition } from \"./types\";\r\nimport { webFeature } from \"./web.feature\";\r\n\r\nexport type { FeatureDefinition } from \"./types\";\r\n\r\n/**\r\n * The feature registry `warlock add` dispatches against.\r\n *\r\n * This file is an INDEX, nothing more: every entry lives in its own module\r\n * alongside the `onExecuting` body it runs. Key order is load-bearing — it is\r\n * the order `--list` prints and the order the \"not allowed\" error lists — so\r\n * add new features in the place they should appear, not alphabetically.\r\n */\r\nexport const featuresMap: Record<string, FeatureDefinition> = {\r\n \"react-email\": reactEmailFeature,\r\n react: reactFeature,\r\n image: imageFeature,\r\n mail: mailFeature,\r\n ses: sesFeature,\r\n mongodb: mongodbFeature,\r\n scheduler: schedulerFeature,\r\n // swagger / postman intentionally omitted — those packages do not exist yet;\r\n // they will ship together in the unified @warlock.js/api-docs package.\r\n postgres: postgresFeature,\r\n mysql: mysqlFeature,\r\n redis: redisFeature,\r\n s3: s3Feature,\r\n test: testFeature,\r\n web: webFeature,\r\n // Directly after `web`, and only there: it `requires` it, it is useless\r\n // without it, and a reader scanning `--list` for the page stack should meet\r\n // the two together rather than find styling filed between queues and sockets.\r\n tailwind: tailwindFeature,\r\n // Immediately after `tailwind`, for the same reason `tailwind` follows `web`:\r\n // it `requires` it, it appends to the stylesheet that feature creates, and the\r\n // three of them are one stack a reader should meet in build order.\r\n shadcn: shadcnFeature,\r\n herald: heraldFeature,\r\n queue: queueFeature,\r\n socket: socketFeature,\r\n notifications: notificationsFeature,\r\n access: accessFeature,\r\n // Login methods for @warlock.js/auth — \"<package>-<vendor>\" like the ai-* entries.\r\n \"auth-google\": authGoogleFeature,\r\n \"auth-passkeys\": authPasskeysFeature,\r\n ai: aiFeature,\r\n \"ai-openai\": aiOpenaiFeature,\r\n \"ai-google\": aiGoogleFeature,\r\n \"ai-anthropic\": aiAnthropicFeature,\r\n \"ai-bedrock\": aiBedrockFeature,\r\n \"ai-ollama\": aiOllamaFeature,\r\n \"ai-tools\": aiToolsFeature,\r\n \"ai-panoptic\": aiPanopticFeature,\r\n \"ai-workspace\": aiWorkspaceFeature,\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,cAAiD;CAC5D,eAAe;CACf,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,SAAS;CACT,WAAW;CAGX,UAAU;CACV,OAAO;CACP,OAAO;CACP,IAAI;CACJ,MAAM;CACN,KAAK;CAIL,UAAU;CAIV,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,eAAe;CACf,QAAQ;CAER,eAAe;CACf,iBAAiB;CACjB,IAAI;CACJ,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,YAAY;CACZ,eAAe;CACf,gBAAgB;AAClB"}
@@ -0,0 +1,67 @@
1
+ import { rootPath } from "../../utils/paths.mjs";
2
+ import "../../utils/index.mjs";
3
+ import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
4
+ import { colors } from "@mongez/copper";
5
+ import { fileExistsAsync, getFileAsync, putFileAsync } from "@warlock.js/fs";
6
+
7
+ //#region ../core/src/generations/features/queue.feature.ts
8
+ const queueConfigStub = `import { env } from "@warlock.js/core";
9
+ import type { QueueConfig } from "@warlock.js/queue";
10
+
11
+ /** Durable job queue configuration. Redis is required by BullMQ. */
12
+ const queueConfig: QueueConfig = {
13
+ connection: {
14
+ host: env("REDIS_HOST", "127.0.0.1"),
15
+ port: env("REDIS_PORT", 6379),
16
+ },
17
+ defaultJobOptions: {
18
+ attempts: 3,
19
+ backoff: { type: "exponential", delay: 1000 },
20
+ },
21
+ workers: {
22
+ enabled: true,
23
+ concurrency: 5,
24
+ shutdownTimeout: 30_000,
25
+ },
26
+ };
27
+
28
+ export default queueConfig;
29
+ `;
30
+ /** Register the queue connector in the app-owned configuration without reformatting it. */
31
+ async function registerQueueConnector() {
32
+ const configPath = rootPath("warlock.config.ts");
33
+ if (!await fileExistsAsync(configPath)) {
34
+ console.log(`${colors.yellowBright("warlock.config.ts")} not found — add this yourself:\n import { queueConnector } from "@warlock.js/queue";\n export default defineConfig({ connectors: [queueConnector()] });`);
35
+ return;
36
+ }
37
+ const current = await getFileAsync(configPath);
38
+ if (current.includes("queueConnector")) {
39
+ console.log(`${colors.yellowBright("queueConnector")} already registered, skipping...`);
40
+ return;
41
+ }
42
+ const importLine = "import { queueConnector } from \"@warlock.js/queue\";";
43
+ let next = current.includes(importLine) ? current : `${importLine}\n${current}`;
44
+ if (/connectors:\s*\[/.test(next)) next = next.replace(/connectors:\s*\[/, "connectors: [queueConnector(),");
45
+ else if (next.includes("defineConfig({")) next = next.replace("defineConfig({", "defineConfig({\n connectors: [queueConnector()],\n");
46
+ else {
47
+ console.log(`${colors.yellowBright("warlock.config.ts")} has no recognisable defineConfig({...}) — add \`connectors: [queueConnector()]\` yourself.`);
48
+ return;
49
+ }
50
+ await putFileAsync(configPath, next);
51
+ console.log(`${colors.green("✓")} Registered queueConnector in warlock.config.ts`);
52
+ console.log("Next: start Redis, then define jobs with defineJob() in an application module before boot.");
53
+ }
54
+ /** `warlock add queue` — durable BullMQ jobs backed by the app's Redis server. */
55
+ const queueFeature = {
56
+ description: "Installs @warlock.js/queue — durable BullMQ jobs backed by Redis. Creates src/config/queue.ts and registers queueConnector() in warlock.config.ts.",
57
+ dependencies: { "@warlock.js/queue": INSTALLED_WARLOCK_VERSION },
58
+ ejectConfig: {
59
+ content: queueConfigStub,
60
+ name: "queue"
61
+ },
62
+ onExecuting: registerQueueConnector
63
+ };
64
+
65
+ //#endregion
66
+ export { queueFeature };
67
+ //# sourceMappingURL=queue.feature.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/queue.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport { rootPath } from \"../../utils\";\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\nconst queueConfigStub = `import { env } from \"@warlock.js/core\";\nimport type { QueueConfig } from \"@warlock.js/queue\";\n\n/** Durable job queue configuration. Redis is required by BullMQ. */\nconst queueConfig: QueueConfig = {\n connection: {\n host: env(\"REDIS_HOST\", \"127.0.0.1\"),\n port: env(\"REDIS_PORT\", 6379),\n },\n defaultJobOptions: {\n attempts: 3,\n backoff: { type: \"exponential\", delay: 1000 },\n },\n workers: {\n enabled: true,\n concurrency: 5,\n shutdownTimeout: 30_000,\n },\n};\n\nexport default queueConfig;\n`;\n\n/** Register the queue connector in the app-owned configuration without reformatting it. */\nasync function registerQueueConnector(): Promise<void> {\n const configPath = rootPath(\"warlock.config.ts\");\n\n if (!(await fileExistsAsync(configPath))) {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} not found — add this yourself:\\n` +\n ` import { queueConnector } from \"@warlock.js/queue\";\\n` +\n ` export default defineConfig({ connectors: [queueConnector()] });`,\n );\n\n return;\n }\n\n const current = await getFileAsync(configPath);\n\n if (current.includes(\"queueConnector\")) {\n console.log(`${colors.yellowBright(\"queueConnector\")} already registered, skipping...`);\n\n return;\n }\n\n const importLine = 'import { queueConnector } from \"@warlock.js/queue\";';\n let next = current.includes(importLine) ? current : `${importLine}\\n${current}`;\n\n if (/connectors:\\s*\\[/.test(next)) {\n next = next.replace(/connectors:\\s*\\[/, \"connectors: [queueConnector(),\");\n } else if (next.includes(\"defineConfig({\")) {\n next = next.replace(\"defineConfig({\", \"defineConfig({\\n connectors: [queueConnector()],\\n\");\n } else {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} has no recognisable defineConfig({...}) — ` +\n \"add `connectors: [queueConnector()]` yourself.\",\n );\n\n return;\n }\n\n await putFileAsync(configPath, next);\n console.log(`${colors.green(\"✓\")} Registered queueConnector in warlock.config.ts`);\n console.log(\n \"Next: start Redis, then define jobs with defineJob() in an application module before boot.\",\n );\n}\n\n/** `warlock add queue` — durable BullMQ jobs backed by the app's Redis server. */\nexport const queueFeature: FeatureDefinition = {\n description:\n \"Installs @warlock.js/queue — durable BullMQ jobs backed by Redis. Creates src/config/queue.ts and registers queueConnector() in warlock.config.ts.\",\n dependencies: {\n \"@warlock.js/queue\": INSTALLED_WARLOCK_VERSION,\n },\n ejectConfig: {\n content: queueConfigStub,\n name: \"queue\",\n },\n onExecuting: registerQueueConnector,\n};\n"],"mappings":";;;;;;;AAKA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;AAwBxB,eAAe,yBAAwC;CACrD,MAAM,aAAa,SAAS,mBAAmB;CAE/C,IAAI,CAAE,MAAM,gBAAgB,UAAU,GAAI;EACxC,QAAQ,IACN,GAAG,OAAO,aAAa,mBAAmB,EAAE,2JAG9C;EAEA;CACF;CAEA,MAAM,UAAU,MAAM,aAAa,UAAU;CAE7C,IAAI,QAAQ,SAAS,gBAAgB,GAAG;EACtC,QAAQ,IAAI,GAAG,OAAO,aAAa,gBAAgB,EAAE,iCAAiC;EAEtF;CACF;CAEA,MAAM,aAAa;CACnB,IAAI,OAAO,QAAQ,SAAS,UAAU,IAAI,UAAU,GAAG,WAAW,IAAI;CAEtE,IAAI,mBAAmB,KAAK,IAAI,GAC9B,OAAO,KAAK,QAAQ,oBAAoB,gCAAgC;MACnE,IAAI,KAAK,SAAS,gBAAgB,GACvC,OAAO,KAAK,QAAQ,kBAAkB,qDAAqD;MACtF;EACL,QAAQ,IACN,GAAG,OAAO,aAAa,mBAAmB,EAAE,4FAE9C;EAEA;CACF;CAEA,MAAM,aAAa,YAAY,IAAI;CACnC,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,gDAAgD;CACjF,QAAQ,IACN,4FACF;AACF;;AAGA,MAAa,eAAkC;CAC7C,aACE;CACF,cAAc,EACZ,qBAAqB,0BACvB;CACA,aAAa;EACX,SAAS;EACT,MAAM;CACR;CACA,aAAa;AACf"}
@@ -471,13 +471,13 @@ import { inApp, type Id } from "@warlock.js/notifications";
471
471
  */
472
472
 
473
473
  /**
474
- * Read \`id\` off \`request.user\` without assuming this app's \`RequestUser\`
475
- * augmentation declares it — \`RequestUser\` is empty by default (see
476
- * \`@warlock.js/core\`'s \`RequestUser\` docs), so a narrow runtime read survives
474
+ * Read \`id\` off \`request.locals.user\` without assuming this app's
475
+ * \`RequestUser\` augmentation declares it — \`RequestUser\` (declared by
476
+ * \`@warlock.js/auth\`) is empty by default, so a narrow runtime read survives
477
477
  * any augmentation shape instead of assuming \`.id\` exists at the type level.
478
478
  * \`inApp\` only ever needs the id (it reduces a \`Notifiable\` to one via
479
479
  * \`recipient.id\` internally), so reading it here — rather than forwarding
480
- * \`request.user\` itself — also skips a needless \`Notifiable\` cast.
480
+ * \`request.locals.user\` itself — also skips a needless \`Notifiable\` cast.
481
481
  */
482
482
  function recipientId(user: unknown): Id {
483
483
  if (user && typeof user === "object" && "id" in user) {
@@ -491,7 +491,7 @@ function recipientId(user: unknown): Id {
491
491
 
492
492
  /** GET /notifications — list, most recent first (page / limit / type / unread via query). */
493
493
  export const listNotificationsController: RequestHandler = async ({ request, response }) => {
494
- const { data, pagination } = await inApp.list(recipientId(request.user), request.all());
494
+ const { data, pagination } = await inApp.list(recipientId(request.locals.user), request.all());
495
495
 
496
496
  return response.success({ notifications: data, pagination });
497
497
  };
@@ -503,7 +503,7 @@ export const unreadNotificationsCountController: RequestHandler = async ({
503
503
  request,
504
504
  response,
505
505
  }) => {
506
- const count = await inApp.countUnread(recipientId(request.user));
506
+ const count = await inApp.countUnread(recipientId(request.locals.user));
507
507
 
508
508
  return response.success({ count });
509
509
  };
@@ -513,7 +513,7 @@ unreadNotificationsCountController.description = "Unread notifications count";
513
513
  /** PATCH /notifications/:id/read — mark one read, return the updated row. */
514
514
  export const markNotificationReadController: RequestHandler = async ({ request, response }) => {
515
515
  const id = request.input("id");
516
- const userId = recipientId(request.user);
516
+ const userId = recipientId(request.locals.user);
517
517
 
518
518
  await inApp.markAsRead(userId, id);
519
519
  const notification = await inApp.find(userId, id);
@@ -528,7 +528,7 @@ export const markAllNotificationsReadController: RequestHandler = async ({
528
528
  request,
529
529
  response,
530
530
  }) => {
531
- const count = await inApp.markAsRead(recipientId(request.user));
531
+ const count = await inApp.markAsRead(recipientId(request.locals.user));
532
532
 
533
533
  return response.success({ count });
534
534
  };
@@ -537,7 +537,7 @@ markAllNotificationsReadController.description = "Mark all notifications read";
537
537
 
538
538
  /** DELETE /notifications — dismiss all for the user. */
539
539
  export const clearNotificationsController: RequestHandler = async ({ request, response }) => {
540
- await inApp.dismiss(recipientId(request.user));
540
+ await inApp.dismiss(recipientId(request.locals.user));
541
541
 
542
542
  return response.noContent();
543
543
  };
@@ -546,7 +546,7 @@ clearNotificationsController.description = "Clear notifications";
546
546
 
547
547
  /** DELETE /notifications/:id — dismiss one. */
548
548
  export const deleteNotificationController: RequestHandler = async ({ request, response }) => {
549
- await inApp.dismiss(recipientId(request.user), request.input("id"));
549
+ await inApp.dismiss(recipientId(request.locals.user), request.input("id"));
550
550
 
551
551
  return response.noContent();
552
552
  };