@lenne.tech/nest-server 11.32.4 → 11.33.1

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 (264) hide show
  1. package/.claude/rules/architecture.md +16 -0
  2. package/.claude/rules/configurable-features.md +11 -3
  3. package/.claude/rules/testing.md +101 -1
  4. package/CLAUDE.md +5 -0
  5. package/FRAMEWORK-API.md +58 -3
  6. package/dist/config.env.js +6 -0
  7. package/dist/config.env.js.map +1 -1
  8. package/dist/core/common/helpers/content-disposition.helper.d.ts +1 -0
  9. package/dist/core/common/helpers/content-disposition.helper.js +14 -0
  10. package/dist/core/common/helpers/content-disposition.helper.js.map +1 -0
  11. package/dist/core/common/helpers/file.helper.d.ts +7 -0
  12. package/dist/core/common/helpers/file.helper.js +21 -4
  13. package/dist/core/common/helpers/file.helper.js.map +1 -1
  14. package/dist/core/common/helpers/graceful-shutdown.helper.d.ts +2 -0
  15. package/dist/core/common/helpers/graceful-shutdown.helper.js +55 -0
  16. package/dist/core/common/helpers/graceful-shutdown.helper.js.map +1 -0
  17. package/dist/core/common/helpers/project-name.helper.d.ts +2 -0
  18. package/dist/core/common/helpers/project-name.helper.js +38 -0
  19. package/dist/core/common/helpers/project-name.helper.js.map +1 -0
  20. package/dist/core/common/interfaces/cron-job-config.interface.d.ts +1 -0
  21. package/dist/core/common/interfaces/server-options.interface.d.ts +39 -0
  22. package/dist/core/common/services/core-cron-jobs.initializer.d.ts +10 -0
  23. package/dist/core/common/services/core-cron-jobs.initializer.js +46 -0
  24. package/dist/core/common/services/core-cron-jobs.initializer.js.map +1 -0
  25. package/dist/core/common/services/core-cron-jobs.registry.d.ts +8 -0
  26. package/dist/core/common/services/core-cron-jobs.registry.js +12 -0
  27. package/dist/core/common/services/core-cron-jobs.registry.js.map +1 -0
  28. package/dist/core/common/services/core-cron-jobs.service.d.ts +37 -7
  29. package/dist/core/common/services/core-cron-jobs.service.js +278 -27
  30. package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
  31. package/dist/core/common/services/core-redis-pubsub.d.ts +20 -0
  32. package/dist/core/common/services/core-redis-pubsub.js +112 -0
  33. package/dist/core/common/services/core-redis-pubsub.js.map +1 -0
  34. package/dist/core/common/services/core-redis.service.d.ts +25 -0
  35. package/dist/core/common/services/core-redis.service.js +154 -0
  36. package/dist/core/common/services/core-redis.service.js.map +1 -0
  37. package/dist/core/common/services/core-s3.service.d.ts +51 -0
  38. package/dist/core/common/services/core-s3.service.js +261 -0
  39. package/dist/core/common/services/core-s3.service.js.map +1 -0
  40. package/dist/core/common/services/core-trust-proxy.initializer.d.ts +16 -0
  41. package/dist/core/common/services/core-trust-proxy.initializer.js +77 -0
  42. package/dist/core/common/services/core-trust-proxy.initializer.js.map +1 -0
  43. package/dist/core/common/services/rate-limit-store.d.ts +61 -0
  44. package/dist/core/common/services/rate-limit-store.js +235 -0
  45. package/dist/core/common/services/rate-limit-store.js.map +1 -0
  46. package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +19 -3
  47. package/dist/core/modules/ai/core-ai-mcp.controller.js +148 -18
  48. package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
  49. package/dist/core/modules/ai/services/core-ai.service.d.ts +7 -2
  50. package/dist/core/modules/ai/services/core-ai.service.js +24 -20
  51. package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
  52. package/dist/core/modules/auth/core-auth.module.js +4 -1
  53. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  54. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +1 -1
  55. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +3 -14
  56. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -1
  57. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +12 -10
  58. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +30 -59
  59. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -1
  60. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +8 -2
  61. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +118 -45
  62. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
  63. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.d.ts +1 -1
  64. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -11
  65. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
  66. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +12 -10
  67. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +30 -85
  68. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
  69. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +0 -1
  70. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  71. package/dist/core/modules/file/core-file.controller.d.ts +11 -2
  72. package/dist/core/modules/file/core-file.controller.js +52 -15
  73. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  74. package/dist/core/modules/file/core-file.resolver.d.ts +4 -4
  75. package/dist/core/modules/file/core-file.resolver.js +23 -16
  76. package/dist/core/modules/file/core-file.resolver.js.map +1 -1
  77. package/dist/core/modules/file/core-file.service.d.ts +41 -7
  78. package/dist/core/modules/file/core-file.service.js +228 -8
  79. package/dist/core/modules/file/core-file.service.js.map +1 -1
  80. package/dist/core/modules/file/file-metadata.helper.d.ts +16 -0
  81. package/dist/core/modules/file/file-metadata.helper.js +33 -0
  82. package/dist/core/modules/file/file-metadata.helper.js.map +1 -0
  83. package/dist/core/modules/file/file-roles.helper.d.ts +4 -0
  84. package/dist/core/modules/file/file-roles.helper.js +53 -0
  85. package/dist/core/modules/file/file-roles.helper.js.map +1 -0
  86. package/dist/core/modules/file/file-storage.helper.d.ts +13 -0
  87. package/dist/core/modules/file/file-storage.helper.js +65 -0
  88. package/dist/core/modules/file/file-storage.helper.js.map +1 -0
  89. package/dist/core/modules/file/filesystem-file.helper.d.ts +22 -0
  90. package/dist/core/modules/file/filesystem-file.helper.js +77 -0
  91. package/dist/core/modules/file/filesystem-file.helper.js.map +1 -0
  92. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +1 -0
  93. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +7 -1
  94. package/dist/core/modules/file/s3-file.helper.d.ts +37 -0
  95. package/dist/core/modules/file/s3-file.helper.js +81 -0
  96. package/dist/core/modules/file/s3-file.helper.js.map +1 -0
  97. package/dist/core/modules/hub/core-hub.controller.d.ts +8 -8
  98. package/dist/core/modules/hub/core-hub.controller.js +24 -22
  99. package/dist/core/modules/hub/core-hub.controller.js.map +1 -1
  100. package/dist/core/modules/hub/helpers/hub-mask.helper.js +2 -2
  101. package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -1
  102. package/dist/core/modules/hub/hub-buffer.d.ts +23 -0
  103. package/dist/core/modules/hub/hub-buffer.js +101 -0
  104. package/dist/core/modules/hub/hub-buffer.js.map +1 -0
  105. package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +7 -5
  106. package/dist/core/modules/hub/services/core-hub-mailbox.service.js +14 -10
  107. package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -1
  108. package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +5 -2
  109. package/dist/core/modules/hub/services/hub-log-buffer.service.js +17 -8
  110. package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -1
  111. package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +4 -2
  112. package/dist/core/modules/hub/services/hub-query-profiler.service.js +13 -7
  113. package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -1
  114. package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +4 -2
  115. package/dist/core/modules/hub/services/hub-trace-buffer.service.js +13 -8
  116. package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -1
  117. package/dist/core/modules/migrate/helpers/migration.helper.d.ts +2 -6
  118. package/dist/core/modules/migrate/helpers/migration.helper.js +4 -4
  119. package/dist/core/modules/migrate/helpers/migration.helper.js.map +1 -1
  120. package/dist/core/modules/migrate/migration-runner.d.ts +1 -0
  121. package/dist/core/modules/migrate/migration-runner.js +4 -0
  122. package/dist/core/modules/migrate/migration-runner.js.map +1 -1
  123. package/dist/core/modules/migrate/mongo-state-store.d.ts +1 -0
  124. package/dist/core/modules/migrate/mongo-state-store.js +98 -9
  125. package/dist/core/modules/migrate/mongo-state-store.js.map +1 -1
  126. package/dist/core/modules/system-setup/core-system-setup.service.d.ts +2 -0
  127. package/dist/core/modules/system-setup/core-system-setup.service.js +31 -1
  128. package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
  129. package/dist/core/modules/tenant/core-tenant.guard.d.ts +17 -3
  130. package/dist/core/modules/tenant/core-tenant.guard.js +92 -15
  131. package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
  132. package/dist/core/modules/tenant/core-tenant.module.js +3 -4
  133. package/dist/core/modules/tenant/core-tenant.module.js.map +1 -1
  134. package/dist/core/modules/tus/core-tus.controller.d.ts +2 -0
  135. package/dist/core/modules/tus/core-tus.controller.js +29 -3
  136. package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
  137. package/dist/core/modules/tus/core-tus.service.d.ts +24 -1
  138. package/dist/core/modules/tus/core-tus.service.js +182 -28
  139. package/dist/core/modules/tus/core-tus.service.js.map +1 -1
  140. package/dist/core/modules/tus/interfaces/tus-config.interface.js +3 -0
  141. package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
  142. package/dist/core/modules/tus/tus-redis-locker.d.ts +25 -0
  143. package/dist/core/modules/tus/tus-redis-locker.js +87 -0
  144. package/dist/core/modules/tus/tus-redis-locker.js.map +1 -0
  145. package/dist/core/modules/tus/tus.module.d.ts +1 -0
  146. package/dist/core/modules/tus/tus.module.js +30 -4
  147. package/dist/core/modules/tus/tus.module.js.map +1 -1
  148. package/dist/core.module.js +18 -1
  149. package/dist/core.module.js.map +1 -1
  150. package/dist/index.d.ts +10 -0
  151. package/dist/index.js +10 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/main.js +2 -1
  154. package/dist/main.js.map +1 -1
  155. package/dist/server/modules/file/file.controller.js +2 -2
  156. package/dist/server/modules/file/file.controller.js.map +1 -1
  157. package/dist/server/modules/file/file.resolver.js +3 -14
  158. package/dist/server/modules/file/file.resolver.js.map +1 -1
  159. package/dist/server/modules/file/file.service.d.ts +10 -2
  160. package/dist/server/modules/file/file.service.js +23 -3
  161. package/dist/server/modules/file/file.service.js.map +1 -1
  162. package/dist/server/modules/user/avatar.controller.d.ts +5 -1
  163. package/dist/server/modules/user/avatar.controller.js +26 -10
  164. package/dist/server/modules/user/avatar.controller.js.map +1 -1
  165. package/dist/server/modules/user/user.model.js +3 -2
  166. package/dist/server/modules/user/user.model.js.map +1 -1
  167. package/dist/server/modules/user/user.module.js +6 -2
  168. package/dist/server/modules/user/user.module.js.map +1 -1
  169. package/dist/server/modules/user/user.service.d.ts +1 -1
  170. package/dist/server/modules/user/user.service.js +6 -13
  171. package/dist/server/modules/user/user.service.js.map +1 -1
  172. package/dist/tsconfig.build.tsbuildinfo +1 -1
  173. package/docs/REQUEST-LIFECYCLE.md +57 -8
  174. package/docs/security-overrides.md +21 -3
  175. package/migration-guides/11.32.3-to-11.32.4.md +15 -3
  176. package/migration-guides/11.32.x-to-11.33.x.md +1584 -0
  177. package/package.json +74 -42
  178. package/src/config.env.ts +45 -0
  179. package/src/core/common/helpers/content-disposition.helper.ts +90 -0
  180. package/src/core/common/helpers/file.helper.ts +53 -10
  181. package/src/core/common/helpers/graceful-shutdown.helper.ts +116 -0
  182. package/src/core/common/helpers/project-name.helper.ts +71 -0
  183. package/src/core/common/interfaces/cron-job-config.interface.ts +16 -0
  184. package/src/core/common/interfaces/server-options.interface.ts +439 -2
  185. package/src/core/common/services/core-cron-jobs.initializer.ts +39 -0
  186. package/src/core/common/services/core-cron-jobs.registry.ts +45 -0
  187. package/src/core/common/services/core-cron-jobs.service.ts +589 -47
  188. package/src/core/common/services/core-redis-pubsub.ts +183 -0
  189. package/src/core/common/services/core-redis.service.ts +249 -0
  190. package/src/core/common/services/core-s3.service.ts +465 -0
  191. package/src/core/common/services/core-trust-proxy.initializer.ts +112 -0
  192. package/src/core/common/services/rate-limit-store.ts +492 -0
  193. package/src/core/modules/ai/README.md +25 -0
  194. package/src/core/modules/ai/core-ai-mcp.controller.ts +253 -22
  195. package/src/core/modules/ai/services/core-ai.service.ts +33 -24
  196. package/src/core/modules/auth/core-auth.module.ts +7 -1
  197. package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +12 -21
  198. package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +49 -93
  199. package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +202 -66
  200. package/src/core/modules/better-auth/core-better-auth-rate-limit.middleware.ts +11 -18
  201. package/src/core/modules/better-auth/core-better-auth-rate-limiter.service.ts +51 -132
  202. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +7 -1
  203. package/src/core/modules/file/INTEGRATION-CHECKLIST.md +120 -0
  204. package/src/core/modules/file/README.md +308 -53
  205. package/src/core/modules/file/core-file.controller.ts +168 -12
  206. package/src/core/modules/file/core-file.resolver.ts +46 -12
  207. package/src/core/modules/file/core-file.service.ts +517 -17
  208. package/src/core/modules/file/file-metadata.helper.ts +142 -0
  209. package/src/core/modules/file/file-roles.helper.ts +110 -0
  210. package/src/core/modules/file/file-storage.helper.ts +163 -0
  211. package/src/core/modules/file/filesystem-file.helper.ts +184 -0
  212. package/src/core/modules/file/interfaces/file-service-options.interface.ts +14 -0
  213. package/src/core/modules/file/interfaces/file-upload.interface.ts +17 -1
  214. package/src/core/modules/file/s3-file.helper.ts +209 -0
  215. package/src/core/modules/hub/README.md +48 -0
  216. package/src/core/modules/hub/core-hub.controller.ts +18 -16
  217. package/src/core/modules/hub/helpers/hub-mask.helper.ts +21 -4
  218. package/src/core/modules/hub/hub-buffer.ts +198 -0
  219. package/src/core/modules/hub/services/core-hub-mailbox.service.ts +22 -12
  220. package/src/core/modules/hub/services/hub-log-buffer.service.ts +24 -8
  221. package/src/core/modules/hub/services/hub-query-profiler.service.ts +9 -6
  222. package/src/core/modules/hub/services/hub-trace-buffer.service.ts +14 -9
  223. package/src/core/modules/migrate/README.md +33 -6
  224. package/src/core/modules/migrate/helpers/migration.helper.ts +13 -6
  225. package/src/core/modules/migrate/migration-runner.ts +10 -1
  226. package/src/core/modules/migrate/mongo-state-store.ts +179 -11
  227. package/src/core/modules/system-setup/README.md +7 -2
  228. package/src/core/modules/system-setup/core-system-setup.service.ts +95 -1
  229. package/src/core/modules/tenant/README.md +21 -1
  230. package/src/core/modules/tenant/core-tenant.guard.ts +135 -14
  231. package/src/core/modules/tenant/core-tenant.module.ts +8 -4
  232. package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +93 -21
  233. package/src/core/modules/tus/README.md +35 -2
  234. package/src/core/modules/tus/core-tus.controller.ts +64 -12
  235. package/src/core/modules/tus/core-tus.service.ts +358 -34
  236. package/src/core/modules/tus/interfaces/tus-config.interface.ts +9 -0
  237. package/src/core/modules/tus/tus-redis-locker.ts +139 -0
  238. package/src/core/modules/tus/tus.module.ts +78 -6
  239. package/src/core.module.ts +26 -1
  240. package/src/index.ts +10 -0
  241. package/src/main.ts +7 -1
  242. package/src/server/modules/file/file.controller.ts +14 -5
  243. package/src/server/modules/file/file.resolver.ts +11 -19
  244. package/src/server/modules/file/file.service.ts +89 -4
  245. package/src/server/modules/user/avatar.controller.ts +46 -9
  246. package/src/server/modules/user/user.model.ts +9 -3
  247. package/src/server/modules/user/user.module.ts +12 -3
  248. package/src/server/modules/user/user.service.ts +20 -17
  249. package/src/test/README.md +7 -2
  250. package/src/core/common/helpers/file.helper.spec.ts +0 -145
  251. package/src/core/common/helpers/logging.helper.spec.ts +0 -61
  252. package/src/core/common/helpers/process-diagnostics.helper.spec.ts +0 -310
  253. package/src/core/common/services/brevo.service.spec.ts +0 -266
  254. package/src/core/modules/file/core-file.controller.spec.ts +0 -164
  255. package/src/core/modules/hub/core-hub.module.spec.ts +0 -108
  256. package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +0 -48
  257. package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +0 -67
  258. package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +0 -61
  259. package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +0 -106
  260. package/src/core/modules/hub/hub-config.helper.spec.ts +0 -108
  261. package/src/core/modules/hub/hub-ring-buffer.spec.ts +0 -95
  262. package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +0 -116
  263. package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +0 -112
  264. package/src/core/modules/migrate/helpers/migration.helper.spec.ts +0 -85
@@ -0,0 +1,71 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ /**
5
+ * Cached slug for the current process — package.json is read at most once.
6
+ *
7
+ * `undefined` means "not read yet"; a string (including the fallback) means "read".
8
+ */
9
+ let cachedProjectSlug: string | undefined;
10
+
11
+ /** Used when package.json is unreadable or carries no usable name */
12
+ const FALLBACK_SLUG = 'nest-server';
13
+
14
+ /**
15
+ * The project's package name, reduced to a form that is safe as a key namespace.
16
+ *
17
+ * This exists so framework-managed keys in SHARED infrastructure (Redis today) are namespaced per
18
+ * application instead of per framework. A constant default is fine until two applications point at
19
+ * one Redis — a normal staging setup — at which point identically-named cron jobs, rate-limit
20
+ * counters and diagnostic buffers of different applications land on the same keys. That failure is
21
+ * silent: one app's scheduler consumes the other's jobs, and the job simply never runs where it was
22
+ * defined.
23
+ *
24
+ * Deliberately NOT the display name from `getProjectAppName()` in the better-auth config: that one
25
+ * is Title Case with an environment suffix ("My App (Local)"), which is right for a 2FA issuer and
26
+ * wrong for a key prefix.
27
+ *
28
+ * @returns lower-case slug, scope stripped (`@acme/api` → `acme-api`), or `nest-server` as fallback
29
+ */
30
+ export function getProjectSlug(): string {
31
+ if (cachedProjectSlug !== undefined) {
32
+ return cachedProjectSlug;
33
+ }
34
+
35
+ cachedProjectSlug = FALLBACK_SLUG;
36
+
37
+ try {
38
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
39
+ if (raw?.name && typeof raw.name === 'string') {
40
+ const slug = slugify(raw.name);
41
+ if (slug) {
42
+ cachedProjectSlug = slug;
43
+ }
44
+ }
45
+ } catch {
46
+ // Unreadable package.json is not worth failing a boot over — the fallback keeps the previous
47
+ // behavior, which is exactly what a consumer without a readable manifest had before.
48
+ }
49
+
50
+ return cachedProjectSlug;
51
+ }
52
+
53
+ /** Reset the cache — tests only, so a fixture can vary the detected name */
54
+ export function resetProjectSlugCache(): void {
55
+ cachedProjectSlug = undefined;
56
+ }
57
+
58
+ /**
59
+ * Reduce a package name to a key-safe slug.
60
+ *
61
+ * Colons are stripped rather than replaced because the framework uses `:` as its own key separator —
62
+ * a name containing one would otherwise invent a namespace level.
63
+ */
64
+ function slugify(name: string): string {
65
+ return name
66
+ .trim()
67
+ .toLowerCase()
68
+ .replace(/^@/, '')
69
+ .replace(/[^a-z0-9]+/g, '-')
70
+ .replace(/^-+|-+$/g, '');
71
+ }
@@ -23,6 +23,22 @@ export interface CronJobConfig<OC extends CronOnCompleteCommand | null = null, C
23
23
  */
24
24
  disabled?: boolean;
25
25
 
26
+ /**
27
+ * Whether the job is deduplicated across replicas in multi-replica deployments.
28
+ *
29
+ * When enabled, only ONE replica executes each tick: via BullMQ job schedulers when
30
+ * Redis is configured, otherwise via a MongoDB lease per tick. The lease is BEST EFFORT —
31
+ * an unreachable lease store runs the tick everywhere rather than stopping all scheduled
32
+ * work, because a silent fleet-wide outage of every cron job is the worse failure.
33
+ *
34
+ * Set to `false` for jobs that must deliberately run on EVERY replica (e.g. refreshing a
35
+ * process-local cache), or to `true` to deduplicate via the MongoDB lease in a
36
+ * multi-replica deployment that runs without Redis.
37
+ *
38
+ * @default true when `redis` is configured, otherwise false (unchanged single-replica behavior)
39
+ */
40
+ distributed?: boolean;
41
+
26
42
  /**
27
43
  * A function that will fire when the job is complete, when it is stopped.
28
44
  */
@@ -555,8 +555,21 @@ export interface IBetterAuthRateLimit {
555
555
  max?: number;
556
556
 
557
557
  /**
558
- * Maximum number of entries in the in-memory rate limit store.
559
- * When exceeded, the oldest entries are evicted to prevent unbounded memory growth.
558
+ * Maximum number of distinct counters this limiter may hold — the bound on the keyspace a
559
+ * caller can create, since both parts of a counter key are caller-influenced.
560
+ *
561
+ * It means slightly different things per store, because the two have different pressures:
562
+ * - **In-memory store**: live entries in the process. Beyond it, expired entries are evicted
563
+ * first; if everything is still live, new keys fold into shared coarse counters.
564
+ * - **Redis store** (`redis` configured): NEW counters per window, across all replicas. Beyond
565
+ * it, further new keys fold into the same coarse counters until the window rolls over.
566
+ *
567
+ * Either way, saturation degrades towards a COARSER limit — unrelated clients then share a
568
+ * counter and are throttled earlier than their own traffic warrants — never towards no limit.
569
+ *
570
+ * Only Better-Auth exposes this knob. The Legacy Auth limiter has no equivalent config key and
571
+ * uses the same `10000` default on both stores.
572
+ *
560
573
  * @default 10000
561
574
  */
562
575
  maxEntries?: number;
@@ -815,6 +828,112 @@ export interface IErrorCode {
815
828
  autoRegister?: boolean;
816
829
  }
817
830
 
831
+ /**
832
+ * Interface for the file module (GridFS)
833
+ *
834
+ * All three knobs take PLAIN ROLE STRINGS, not `RoleEnum` members, because
835
+ * `@Roles()` itself is `(...roles: string[])`. A project may therefore gate the
836
+ * routes with its own role names (`'company-admin'`, `'editor'`) exactly as it
837
+ * would in a hand-written `@Roles()` call — see `RoleEnum` for the system roles
838
+ * (`S_USER`, `S_VERIFIED`, …) that keep their special meaning.
839
+ *
840
+ * The defaults are deliberately restrictive: a single GridFS bucket is shared by
841
+ * every feature of the consuming project, and the ids identifying its blobs are
842
+ * ObjectIds — not secrets. Widening any of these is a decision the project owner
843
+ * has to make explicitly, and it belongs in `config.env.ts` where it is
844
+ * reviewable, rather than in a decorator override where it is not.
845
+ *
846
+ * ROLES ARE THE COARSE FILTER ONLY. They answer "may this caller reach the
847
+ * route at all", never "may this caller have THIS file". For per-file rules —
848
+ * owner, tenant, published-flag — override `CoreFileService.checkRights()`,
849
+ * which receives the `currentUser` and the resolved file metadata.
850
+ *
851
+ * ⚠ With `multiTenancy` active, a non-system role is validated against
852
+ * `membership.role` rather than `user.roles` (see `CoreTenantGuard`). Since
853
+ * GridFS is accessed through the native driver, `mongooseTenantPlugin` never
854
+ * runs on `fs.files` — the bucket is NOT tenant-scoped. A per-tenant rule must
855
+ * therefore be expressed in `checkRights()`, not by a role name alone.
856
+ */
857
+ export interface IFileConfig {
858
+ /**
859
+ * Roles allowed to DELETE files (`deleteFile` mutation).
860
+ *
861
+ * Kept separate from `uploadRoles` on purpose: "everyone signed in may upload"
862
+ * is a common and reasonable policy, "everyone signed in may delete anyone's
863
+ * file" almost never is.
864
+ *
865
+ * @default ['admin']
866
+ */
867
+ deleteRoles?: string[];
868
+
869
+ /**
870
+ * Roles allowed to DOWNLOAD files and read file info
871
+ * (`GET /files/id/:id`, `GET /files/:filename`, `getFileInfo` query).
872
+ *
873
+ * Note for browser-rendered files: an `<img src>` or `<a href>` cannot send an
874
+ * `Authorization` header, so anything stricter than `S_EVERYONE` only works
875
+ * for those tags when the session travels as a cookie.
876
+ *
877
+ * @default ['admin']
878
+ */
879
+ downloadRoles?: string[];
880
+
881
+ /**
882
+ * Storage driver for CoreFileService. Three equivalent options:
883
+ *
884
+ * - `'s3'` — an S3-compatible bucket (needs `s3`). The only driver that
885
+ * survives horizontal scaling.
886
+ * - `'gridfs'` — MongoDB GridFS. No extra infrastructure; bytes share the database.
887
+ * - `'filesystem'` — the local disk (`storageDir`). Pod-local: not shared between
888
+ * replicas and lost on restart unless the path is a mounted volume.
889
+ *
890
+ * **Set explicitly and it is enforced.** If the chosen store is not available,
891
+ * the boot FAILS rather than falling back — a silent fallback would put files
892
+ * in a store the operator does not believe they are in, with no way to tell
893
+ * afterwards which file went where.
894
+ *
895
+ * **Left unset, it is derived**, most capable first:
896
+ * 1. `'s3'` when `s3.bucket` is configured
897
+ * 2. `'gridfs'` when a database is configured
898
+ * 3. `'filesystem'` when neither is
899
+ *
900
+ * A configured-but-unreachable database is an error in its own right (Mongoose
901
+ * fails the boot), never a reason to fall through to the disk.
902
+ *
903
+ * **Metadata always lives in the database**, whichever driver stores the bytes:
904
+ * filename, content type, length and the custom `metadata` a per-file rule
905
+ * reads have to be queryable. Only a project running without a database at all
906
+ * has to keep its own bookkeeping.
907
+ *
908
+ * Orthogonal to the role knobs above: this decides WHERE the bytes live, they
909
+ * decide WHO may reach them. A per-file rule in `checkRights()` works under
910
+ * every driver — `getRawFileInfo()` checks all metadata stores.
911
+ *
912
+ * @default derived — see above
913
+ */
914
+ storage?: 'filesystem' | 'gridfs' | 's3';
915
+
916
+ /**
917
+ * Directory for the `'filesystem'` storage driver.
918
+ *
919
+ * Relative paths resolve against the process working directory. The directory
920
+ * is created on first write.
921
+ *
922
+ * @default 'uploads/files'
923
+ */
924
+ storageDir?: string;
925
+
926
+ /**
927
+ * Roles allowed to UPLOAD files (`uploadFile` / `uploadFiles` mutations).
928
+ *
929
+ * This does NOT cover TUS resumable uploads, which are served by their own
930
+ * controller — configure those via `tus.roles`.
931
+ *
932
+ * @default ['admin']
933
+ */
934
+ uploadRoles?: string[];
935
+ }
936
+
818
937
  /**
819
938
  * Interface for JWT configuration (main and refresh)
820
939
  */
@@ -1792,6 +1911,12 @@ export interface IServerOptions {
1792
1911
  */
1793
1912
  execAfterInit?: string;
1794
1913
 
1914
+ /**
1915
+ * Configuration of the file module: where the bytes live (`storage`) and who
1916
+ * may reach them (`downloadRoles` / `uploadRoles` / `deleteRoles`).
1917
+ */
1918
+ file?: IFileConfig;
1919
+
1795
1920
  /**
1796
1921
  * Filter configuration and defaults
1797
1922
  */
@@ -2123,6 +2248,35 @@ export interface IServerOptions {
2123
2248
  */
2124
2249
  port?: number;
2125
2250
 
2251
+ /**
2252
+ * Optional central Redis connection used by all distributed features
2253
+ * (rate limiting, cron deduplication, GraphQL subscriptions, caches, Hub collectors).
2254
+ *
2255
+ * Follows the "presence implies enabled" pattern:
2256
+ * - undefined: no Redis — all features fall back to their process-local behavior
2257
+ * - true or {}: enabled with defaults (localhost:6379, db 0)
2258
+ * - { enabled: false, ... }: pre-configured but disabled
2259
+ *
2260
+ * Requires the optional peer dependency `ioredis` to be installed.
2261
+ *
2262
+ * @default undefined (disabled)
2263
+ */
2264
+ redis?: boolean | IRedisConfig;
2265
+
2266
+ /**
2267
+ * Optional central S3-compatible object storage (AWS S3, MinIO, ...).
2268
+ *
2269
+ * Used by CoreFileService (when `file.storage: 's3'`) and as TUS upload staging.
2270
+ * Follows the "presence implies enabled" pattern; without this config,
2271
+ * files stay in GridFS and TUS stages on local disk as before.
2272
+ *
2273
+ * Requires the optional peer dependency `@aws-sdk/client-s3`
2274
+ * (and `@aws-sdk/s3-request-presigner` for presigned downloads).
2275
+ *
2276
+ * @default undefined (disabled)
2277
+ */
2278
+ s3?: IS3Config;
2279
+
2126
2280
  /**
2127
2281
  * Configuration for security pipes and interceptors
2128
2282
  */
@@ -2391,6 +2545,24 @@ export interface IServerOptions {
2391
2545
  */
2392
2546
  sha256?: boolean;
2393
2547
 
2548
+ /**
2549
+ * Delay in milliseconds between receiving a shutdown signal and starting the
2550
+ * NestJS shutdown sequence. Gives load balancers time to deregister the
2551
+ * instance before in-flight connections are drained (zero-downtime deploys).
2552
+ *
2553
+ * Requires `installGracefulShutdown(app)` in main.ts, which REPLACES
2554
+ * `server.enableShutdownHooks()` — keeping both makes Nest close the app in
2555
+ * parallel with the wait, so the delay silently never happens.
2556
+ *
2557
+ * Keep it well below the orchestrator grace period and leave room for the
2558
+ * drain that follows (Compose 10s, Kubernetes 30s, diagnostics watchdog 30s);
2559
+ * exceeding any of them means SIGKILL mid-wait and no shutdown hook runs.
2560
+ * Warns above 10000, capped at 60000.
2561
+ *
2562
+ * @default 0 (no delay)
2563
+ */
2564
+ shutdownDelayMs?: number;
2565
+
2394
2566
  /**
2395
2567
  * Configuration for useStaticAssets
2396
2568
  */
@@ -2458,6 +2630,58 @@ export interface IServerOptions {
2458
2630
  path?: string;
2459
2631
  };
2460
2632
 
2633
+ /**
2634
+ * Express `trust proxy` setting — how far up the `X-Forwarded-For` chain this app believes.
2635
+ *
2636
+ * **This is what makes `request.ip` correct, and every IP-keyed rate limit depends on it.**
2637
+ * Express derives `req.ip` from the forwarded chain only as far as this setting allows and
2638
+ * otherwise reports the socket peer. Left unset (Express default `false`), an app behind Caddy,
2639
+ * nginx or a Kubernetes ingress sees the PROXY's address on every request — so all clients
2640
+ * collapse onto ONE rate-limit bucket and `auth.rateLimit.max` throttles everybody at once
2641
+ * instead of throttling one attacker. With `redis` configured that limit is enforced exactly
2642
+ * fleet-wide, which makes the collapse total rather than per replica.
2643
+ *
2644
+ * Setting it wrong in the other direction is a bypass: trusting more hops than actually sit in
2645
+ * front of the app lets a client prepend its own `X-Forwarded-For` entry and pick a fresh bucket
2646
+ * per request. Count the hops.
2647
+ *
2648
+ * Accepted values (passed through to `app.set('trust proxy', …)` verbatim):
2649
+ * - `false` — trust nothing, use the socket address (Express default). Also the explicit
2650
+ * "no proxy in front of me" answer, which silences the boot warning below.
2651
+ * - `1` / `2` / … — trust exactly N hops closest to this app. The usual answer: one reverse
2652
+ * proxy is `1`, proxy behind a CDN is `2`.
2653
+ * - `'loopback'` / `'linklocal'` / `'uniquelocal'` — trust those address ranges.
2654
+ * - `'10.0.0.0/8'`, `['loopback', '10.0.0.0/8']` — trust specific addresses or subnets.
2655
+ * - `true` — trust the LEFTMOST entry, i.e. whatever the client sent. Never use this on a
2656
+ * public deployment; it hands every caller its own rate-limit bucket.
2657
+ *
2658
+ * A predicate function is deliberately NOT accepted here, although Express supports one: this
2659
+ * value must survive `NEST_SERVER_CONFIG` / `NSC__*` (JSON) and the ConfigService deep clone,
2660
+ * and hop counts, ranges and subnet lists already cover every real deployment shape. If you do
2661
+ * need a predicate, leave `trustProxy` unset and call `app.set('trust proxy', fn)` in your own
2662
+ * `main.ts` — an unset value is never applied, so nothing overwrites it.
2663
+ *
2664
+ * Applied by `CoreModule` during module init, which happens inside `app.init()` / `app.listen()`
2665
+ * — i.e. AFTER your `main.ts` runs. A configured value therefore takes precedence over an
2666
+ * `app.set('trust proxy', …)` of your own.
2667
+ *
2668
+ * When this is unset and an IP-keyed rate limiter (`auth.rateLimit` / `betterAuth.rateLimit`) is
2669
+ * enabled, the framework logs a warning at boot naming the shared-bucket consequence.
2670
+ *
2671
+ * @default false (Express default — the forwarded chain is not trusted)
2672
+ * @since 11.33.0
2673
+ *
2674
+ * @example
2675
+ * ```typescript
2676
+ * // One reverse proxy (Caddy / nginx / ingress) in front of the app
2677
+ * trustProxy: 1,
2678
+ *
2679
+ * // Nothing in front of the app — explicit, and silences the boot warning
2680
+ * trustProxy: false,
2681
+ * ```
2682
+ */
2683
+ trustProxy?: boolean | number | string | string[];
2684
+
2461
2685
  /**
2462
2686
  * TUS resumable upload configuration.
2463
2687
  *
@@ -2488,6 +2712,178 @@ export interface IServerOptions {
2488
2712
  tus?: boolean | ITusConfig;
2489
2713
  }
2490
2714
 
2715
+ /**
2716
+ * Central Redis connection configuration (see IServerOptions.redis).
2717
+ *
2718
+ * All distributed features share this single configuration. Individual features
2719
+ * automatically use Redis when this is enabled and fall back to their
2720
+ * process-local behavior when it is not.
2721
+ */
2722
+ export interface IRedisConfig {
2723
+ /**
2724
+ * Redis database index
2725
+ * @default 0
2726
+ */
2727
+ db?: number;
2728
+
2729
+ /**
2730
+ * Whether Redis is enabled.
2731
+ * Presence of the config object implies true.
2732
+ * @default true (when config object is present)
2733
+ */
2734
+ enabled?: boolean;
2735
+
2736
+ /**
2737
+ * Redis host
2738
+ * @default 'localhost'
2739
+ */
2740
+ host?: string;
2741
+
2742
+ /**
2743
+ * Prefix prepended to every framework-managed Redis key
2744
+ * (rate limits, locks, caches, Hub collectors, BullMQ queue prefix).
2745
+ *
2746
+ * Defaults to your own `package.json` name, slugified (`@acme/api` → `acme-api`),
2747
+ * so two applications sharing one Redis do not collide. Set it explicitly only
2748
+ * when sharing IS intended — giving two applications the same prefix makes one
2749
+ * application's BullMQ worker consume the other's scheduled jobs.
2750
+ *
2751
+ * Note: applied by the framework per key — NOT passed as ioredis `keyPrefix`,
2752
+ * which would conflict with BullMQ's own prefix handling.
2753
+ *
2754
+ * @default the slugified `name` from package.json, or 'nest-server' if unreadable
2755
+ */
2756
+ keyPrefix?: string;
2757
+
2758
+ /**
2759
+ * Additional ioredis options passed through to the client constructor
2760
+ * (e.g. `tls`, `sentinels`, `retryStrategy`).
2761
+ * @default undefined
2762
+ */
2763
+ options?: Record<string, unknown>;
2764
+
2765
+ /**
2766
+ * Redis password
2767
+ * @default undefined (no auth)
2768
+ */
2769
+ password?: string;
2770
+
2771
+ /**
2772
+ * Redis port
2773
+ * @default 6379
2774
+ */
2775
+ port?: number;
2776
+
2777
+ /**
2778
+ * Full Redis connection URL (e.g. 'redis://user:pass@host:6379/0').
2779
+ * Takes precedence over host/port/db/password/username.
2780
+ * @default undefined
2781
+ */
2782
+ url?: string;
2783
+
2784
+ /**
2785
+ * Redis username (Redis 6+ ACL)
2786
+ * @default undefined
2787
+ */
2788
+ username?: string;
2789
+ }
2790
+
2791
+ /**
2792
+ * S3-compatible object storage configuration (see IServerOptions.s3).
2793
+ * Works with AWS S3, MinIO, RustFS and other S3-compatible services.
2794
+ */
2795
+ export interface IS3Config {
2796
+ /**
2797
+ * Access key ID.
2798
+ * Falls back to the AWS SDK default credential chain when omitted
2799
+ * (environment variables, instance profiles, ...).
2800
+ * @default undefined
2801
+ */
2802
+ accessKeyId?: string;
2803
+
2804
+ /**
2805
+ * Create the configured buckets at startup when they do not exist.
2806
+ *
2807
+ * Off by default: production buckets normally come from infrastructure code, and their
2808
+ * credentials often carry no `CreateBucket` permission. Turn it on for a self-hosted
2809
+ * MinIO/RustFS or a local dev stack. Either way the server verifies the buckets at boot and
2810
+ * logs an actionable error when one is missing, instead of letting the first upload fail with
2811
+ * an opaque 500.
2812
+ *
2813
+ * @default false
2814
+ */
2815
+ autoCreateBucket?: boolean;
2816
+
2817
+ /**
2818
+ * Bucket for files stored via CoreFileService
2819
+ */
2820
+ bucket: string;
2821
+
2822
+ /**
2823
+ * Whether S3 is enabled.
2824
+ * Presence of the config object implies true.
2825
+ * @default true (when config object is present)
2826
+ */
2827
+ enabled?: boolean;
2828
+
2829
+ /**
2830
+ * Custom endpoint URL for S3-compatible services (MinIO, RustFS, ...).
2831
+ * Omit for AWS S3.
2832
+ * @default undefined
2833
+ */
2834
+ endpoint?: string;
2835
+
2836
+ /**
2837
+ * Use path-style addressing (required by most self-hosted S3 services).
2838
+ * @default false
2839
+ */
2840
+ forcePathStyle?: boolean;
2841
+
2842
+ /**
2843
+ * Serve downloads as presigned URL redirects instead of streaming
2844
+ * through the API (offloads traffic from the server).
2845
+ * `true` / `{}` enables with defaults.
2846
+ *
2847
+ * The issued URL is a session-less BEARER CAPABILITY: it is authorized once, at issue time, and
2848
+ * afterwards anyone holding the string can download the object until it expires — there is no
2849
+ * revocation short of deleting the object or rotating the signing credentials.
2850
+ * @default false
2851
+ */
2852
+ presignedDownloads?:
2853
+ | boolean
2854
+ | {
2855
+ /**
2856
+ * Presigned URL validity in seconds.
2857
+ *
2858
+ * Keep it just long enough for a download to START. Values above 900s are accepted but
2859
+ * warned about at boot, and anything above 604800s (the AWS SigV4 maximum, 7 days) is
2860
+ * capped — a longer signature is rejected by S3 rather than honored.
2861
+ * @default 300
2862
+ */
2863
+ expiresInSeconds?: number;
2864
+ };
2865
+
2866
+ /**
2867
+ * AWS region
2868
+ * @default 'us-east-1'
2869
+ */
2870
+ region?: string;
2871
+
2872
+ /**
2873
+ * Secret access key.
2874
+ * Falls back to the AWS SDK default credential chain when omitted.
2875
+ * @default undefined
2876
+ */
2877
+ secretAccessKey?: string;
2878
+
2879
+ /**
2880
+ * Bucket used as staging area for resumable TUS uploads.
2881
+ * Configure a lifecycle rule on this bucket to expire aborted uploads.
2882
+ * @default same as `bucket`
2883
+ */
2884
+ stagingBucket?: string;
2885
+ }
2886
+
2491
2887
  export interface ISystemSetup {
2492
2888
  /**
2493
2889
  * Whether system setup is enabled.
@@ -2613,6 +3009,46 @@ export interface ITusConfig {
2613
3009
  */
2614
3010
  path?: string;
2615
3011
 
3012
+ /**
3013
+ * Roles allowed to use the tus endpoints (create, write, read offset, terminate).
3014
+ *
3015
+ * Takes plain role strings, like `@Roles()` itself, so a project can use its
3016
+ * own role names here.
3017
+ *
3018
+ * A tus upload writes into the SAME file store that `file.downloadRoles`
3019
+ * guards — GridFS or S3, whichever `file.storage` selects — so leaving this at
3020
+ * `S_EVERYONE` while the download side is restricted means anonymous callers
3021
+ * may write into, and with the termination extension delete from, a store
3022
+ * only privileged callers may read. That asymmetry is rarely intended;
3023
+ * `S_USER` is the safer default for anything reachable from the internet.
3024
+ *
3025
+ * `OPTIONS` is exempt — it is the CORS preflight, which browsers send without
3026
+ * credentials, and it returns only server capabilities.
3027
+ *
3028
+ * @default ['s_user']
3029
+ */
3030
+ roles?: string[];
3031
+
3032
+ /**
3033
+ * Stage upload chunks in the configured S3 bucket (`IServerOptions.s3`,
3034
+ * `stagingBucket`) instead of local disk, so resumable uploads survive
3035
+ * replica restarts and work without sticky sessions.
3036
+ *
3037
+ * **On by default whenever S3 is usable** — a configured `s3` block with a
3038
+ * `bucket` is enough, no opt-in needed. Set to `false` to force local disk.
3039
+ *
3040
+ * "Usable" is the same test `file.storage`'s automatic default uses: a bucket
3041
+ * must be named. An `s3` block without one is ignored entirely (with a
3042
+ * warning), so staging can never be switched on against a bucket that does
3043
+ * not exist.
3044
+ *
3045
+ * Independent of `file.storage`: chunks may stage in S3 while finished files
3046
+ * are written to GridFS, or the other way round.
3047
+ *
3048
+ * @default true (when S3 is usable)
3049
+ */
3050
+ s3Staging?: boolean;
3051
+
2616
3052
  /**
2617
3053
  * Termination extension configuration.
2618
3054
  * Allows deleting uploads via DELETE.
@@ -2622,6 +3058,7 @@ export interface ITusConfig {
2622
3058
 
2623
3059
  /**
2624
3060
  * Directory for temporary upload chunks.
3061
+ * Only used when uploads are NOT staged in S3 (see `s3Staging`).
2625
3062
  * @default 'uploads/tus'
2626
3063
  */
2627
3064
  uploadDir?: string;
@@ -0,0 +1,39 @@
1
+ import { Injectable, OnApplicationShutdown, OnModuleInit, Optional } from '@nestjs/common';
2
+ import { InjectConnection } from '@nestjs/mongoose';
3
+ import { Connection } from 'mongoose';
4
+
5
+ import { setCronJobsInfrastructure } from './core-cron-jobs.registry';
6
+ import { CoreRedisService } from './core-redis.service';
7
+
8
+ /**
9
+ * Populates the cron-jobs infrastructure registry at bootstrap so that
10
+ * CoreCronJobs subclasses get multi-replica deduplication (Redis/BullMQ or
11
+ * Mongo lease) WITHOUT any constructor changes in consumer projects.
12
+ *
13
+ * Registered as a CoreModule provider; consumers never interact with it.
14
+ */
15
+ @Injectable()
16
+ export class CoreCronJobsInitializer implements OnApplicationShutdown, OnModuleInit {
17
+ constructor(
18
+ @InjectConnection() protected readonly connection: Connection,
19
+ @Optional() protected readonly redisService?: CoreRedisService,
20
+ ) {}
21
+
22
+ onModuleInit(): void {
23
+ setCronJobsInfrastructure({
24
+ connection: this.connection,
25
+ redisService: this.redisService?.enabled ? this.redisService : undefined,
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Drop the module-global references again.
31
+ *
32
+ * The registry outlives the application that filled it, so without this a closed app leaves its
33
+ * (now closed) Mongoose connection and Redis service behind for whatever app starts next in the
34
+ * same process — a test suite, or any host that creates more than one application.
35
+ */
36
+ onApplicationShutdown(): void {
37
+ setCronJobsInfrastructure({});
38
+ }
39
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Runtime registry for the optional cron-job infrastructure.
3
+ *
4
+ * `CoreCronJobs` is abstract and instantiated by the consuming project, so the
5
+ * framework cannot inject the Mongoose connection or `CoreRedisService` into it.
6
+ * Registering them here instead keeps multi-replica deduplication working WITHOUT
7
+ * any change to an existing subclass — the same shape as
8
+ * `core-better-auth.registry.ts`.
9
+ *
10
+ * The file uses only `import type`, which both tsc and SWC erase entirely, so it
11
+ * emits no `require()` at all and is a true leaf: it can never be mid-evaluation
12
+ * when someone imports it (see .claude/rules/architecture.md → "DI Token Placement
13
+ * (SWC-Safe)"). Its exports are hoisted `function` declarations for the same reason.
14
+ *
15
+ * @internal Populated by CoreModule. `CoreCronJobs` reads it lazily on every access,
16
+ * so a registration that happens after the service was constructed still takes effect.
17
+ */
18
+
19
+ import type { CoreRedisService } from './core-redis.service';
20
+ import type { Connection } from 'mongoose';
21
+
22
+ export interface CronJobsInfrastructure {
23
+ /** Mongoose connection used for the per-tick MongoDB lease */
24
+ connection?: Connection;
25
+
26
+ /** Central Redis service; enables the BullMQ job-scheduler mode */
27
+ redisService?: CoreRedisService;
28
+ }
29
+
30
+ let infrastructure: CronJobsInfrastructure = {};
31
+
32
+ /**
33
+ * Returns the registered cron-job infrastructure; empty when nothing was registered.
34
+ */
35
+ export function getCronJobsInfrastructure(): CronJobsInfrastructure {
36
+ return infrastructure;
37
+ }
38
+
39
+ /**
40
+ * Registers the cron-job infrastructure. Pass `{}` to clear it (tests).
41
+ * @internal
42
+ */
43
+ export function setCronJobsInfrastructure(value: CronJobsInfrastructure): void {
44
+ infrastructure = value ?? {};
45
+ }