@lenne.tech/nest-server 11.32.3 → 11.33.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 (262) 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/bin/migrate.js +13 -3
  7. package/dist/core/common/helpers/content-disposition.helper.d.ts +1 -0
  8. package/dist/core/common/helpers/content-disposition.helper.js +14 -0
  9. package/dist/core/common/helpers/content-disposition.helper.js.map +1 -0
  10. package/dist/core/common/helpers/file.helper.d.ts +21 -2
  11. package/dist/core/common/helpers/file.helper.js +69 -13
  12. package/dist/core/common/helpers/file.helper.js.map +1 -1
  13. package/dist/core/common/helpers/graceful-shutdown.helper.d.ts +2 -0
  14. package/dist/core/common/helpers/graceful-shutdown.helper.js +55 -0
  15. package/dist/core/common/helpers/graceful-shutdown.helper.js.map +1 -0
  16. package/dist/core/common/helpers/project-name.helper.d.ts +2 -0
  17. package/dist/core/common/helpers/project-name.helper.js +38 -0
  18. package/dist/core/common/helpers/project-name.helper.js.map +1 -0
  19. package/dist/core/common/interfaces/cron-job-config.interface.d.ts +1 -0
  20. package/dist/core/common/interfaces/server-options.interface.d.ts +39 -0
  21. package/dist/core/common/services/core-cron-jobs.initializer.d.ts +10 -0
  22. package/dist/core/common/services/core-cron-jobs.initializer.js +46 -0
  23. package/dist/core/common/services/core-cron-jobs.initializer.js.map +1 -0
  24. package/dist/core/common/services/core-cron-jobs.registry.d.ts +8 -0
  25. package/dist/core/common/services/core-cron-jobs.registry.js +12 -0
  26. package/dist/core/common/services/core-cron-jobs.registry.js.map +1 -0
  27. package/dist/core/common/services/core-cron-jobs.service.d.ts +37 -7
  28. package/dist/core/common/services/core-cron-jobs.service.js +278 -27
  29. package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
  30. package/dist/core/common/services/core-redis-pubsub.d.ts +20 -0
  31. package/dist/core/common/services/core-redis-pubsub.js +112 -0
  32. package/dist/core/common/services/core-redis-pubsub.js.map +1 -0
  33. package/dist/core/common/services/core-redis.service.d.ts +25 -0
  34. package/dist/core/common/services/core-redis.service.js +154 -0
  35. package/dist/core/common/services/core-redis.service.js.map +1 -0
  36. package/dist/core/common/services/core-s3.service.d.ts +51 -0
  37. package/dist/core/common/services/core-s3.service.js +261 -0
  38. package/dist/core/common/services/core-s3.service.js.map +1 -0
  39. package/dist/core/common/services/core-trust-proxy.initializer.d.ts +16 -0
  40. package/dist/core/common/services/core-trust-proxy.initializer.js +77 -0
  41. package/dist/core/common/services/core-trust-proxy.initializer.js.map +1 -0
  42. package/dist/core/common/services/rate-limit-store.d.ts +61 -0
  43. package/dist/core/common/services/rate-limit-store.js +235 -0
  44. package/dist/core/common/services/rate-limit-store.js.map +1 -0
  45. package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +19 -3
  46. package/dist/core/modules/ai/core-ai-mcp.controller.js +148 -18
  47. package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
  48. package/dist/core/modules/ai/services/core-ai.service.d.ts +7 -2
  49. package/dist/core/modules/ai/services/core-ai.service.js +24 -20
  50. package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
  51. package/dist/core/modules/auth/core-auth.module.js +4 -1
  52. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  53. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +1 -1
  54. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +3 -14
  55. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -1
  56. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +12 -10
  57. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +30 -59
  58. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -1
  59. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +8 -2
  60. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +118 -45
  61. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
  62. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.d.ts +1 -1
  63. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -11
  64. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
  65. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +12 -10
  66. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +30 -85
  67. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
  68. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +0 -1
  69. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  70. package/dist/core/modules/file/core-file.controller.d.ts +15 -3
  71. package/dist/core/modules/file/core-file.controller.js +90 -20
  72. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  73. package/dist/core/modules/file/core-file.resolver.d.ts +4 -4
  74. package/dist/core/modules/file/core-file.resolver.js +23 -16
  75. package/dist/core/modules/file/core-file.resolver.js.map +1 -1
  76. package/dist/core/modules/file/core-file.service.d.ts +41 -7
  77. package/dist/core/modules/file/core-file.service.js +227 -7
  78. package/dist/core/modules/file/core-file.service.js.map +1 -1
  79. package/dist/core/modules/file/file-metadata.helper.d.ts +16 -0
  80. package/dist/core/modules/file/file-metadata.helper.js +33 -0
  81. package/dist/core/modules/file/file-metadata.helper.js.map +1 -0
  82. package/dist/core/modules/file/file-roles.helper.d.ts +4 -0
  83. package/dist/core/modules/file/file-roles.helper.js +53 -0
  84. package/dist/core/modules/file/file-roles.helper.js.map +1 -0
  85. package/dist/core/modules/file/file-storage.helper.d.ts +13 -0
  86. package/dist/core/modules/file/file-storage.helper.js +65 -0
  87. package/dist/core/modules/file/file-storage.helper.js.map +1 -0
  88. package/dist/core/modules/file/filesystem-file.helper.d.ts +22 -0
  89. package/dist/core/modules/file/filesystem-file.helper.js +77 -0
  90. package/dist/core/modules/file/filesystem-file.helper.js.map +1 -0
  91. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +1 -0
  92. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +7 -1
  93. package/dist/core/modules/file/s3-file.helper.d.ts +37 -0
  94. package/dist/core/modules/file/s3-file.helper.js +81 -0
  95. package/dist/core/modules/file/s3-file.helper.js.map +1 -0
  96. package/dist/core/modules/hub/core-hub.controller.d.ts +8 -8
  97. package/dist/core/modules/hub/core-hub.controller.js +24 -22
  98. package/dist/core/modules/hub/core-hub.controller.js.map +1 -1
  99. package/dist/core/modules/hub/helpers/hub-mask.helper.js +2 -2
  100. package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -1
  101. package/dist/core/modules/hub/hub-buffer.d.ts +23 -0
  102. package/dist/core/modules/hub/hub-buffer.js +101 -0
  103. package/dist/core/modules/hub/hub-buffer.js.map +1 -0
  104. package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +7 -5
  105. package/dist/core/modules/hub/services/core-hub-mailbox.service.js +14 -10
  106. package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -1
  107. package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +5 -2
  108. package/dist/core/modules/hub/services/hub-log-buffer.service.js +17 -8
  109. package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -1
  110. package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +4 -2
  111. package/dist/core/modules/hub/services/hub-query-profiler.service.js +13 -7
  112. package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -1
  113. package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +4 -2
  114. package/dist/core/modules/hub/services/hub-trace-buffer.service.js +13 -8
  115. package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -1
  116. package/dist/core/modules/migrate/cli/migrate-cli.d.ts +3 -1
  117. package/dist/core/modules/migrate/cli/migrate-cli.js +29 -4
  118. package/dist/core/modules/migrate/cli/migrate-cli.js.map +1 -1
  119. package/dist/core/modules/migrate/helpers/migration.helper.d.ts +3 -6
  120. package/dist/core/modules/migrate/helpers/migration.helper.js +55 -8
  121. package/dist/core/modules/migrate/helpers/migration.helper.js.map +1 -1
  122. package/dist/core/modules/migrate/migration-runner.d.ts +1 -0
  123. package/dist/core/modules/migrate/migration-runner.js +4 -0
  124. package/dist/core/modules/migrate/migration-runner.js.map +1 -1
  125. package/dist/core/modules/migrate/mongo-state-store.d.ts +1 -0
  126. package/dist/core/modules/migrate/mongo-state-store.js +98 -9
  127. package/dist/core/modules/migrate/mongo-state-store.js.map +1 -1
  128. package/dist/core/modules/system-setup/core-system-setup.service.d.ts +2 -0
  129. package/dist/core/modules/system-setup/core-system-setup.service.js +31 -1
  130. package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
  131. package/dist/core/modules/tenant/core-tenant.guard.d.ts +17 -3
  132. package/dist/core/modules/tenant/core-tenant.guard.js +92 -15
  133. package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
  134. package/dist/core/modules/tenant/core-tenant.module.js +3 -4
  135. package/dist/core/modules/tenant/core-tenant.module.js.map +1 -1
  136. package/dist/core/modules/tus/core-tus.controller.d.ts +2 -0
  137. package/dist/core/modules/tus/core-tus.controller.js +29 -3
  138. package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
  139. package/dist/core/modules/tus/core-tus.service.d.ts +24 -1
  140. package/dist/core/modules/tus/core-tus.service.js +182 -28
  141. package/dist/core/modules/tus/core-tus.service.js.map +1 -1
  142. package/dist/core/modules/tus/interfaces/tus-config.interface.js +3 -0
  143. package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
  144. package/dist/core/modules/tus/tus-redis-locker.d.ts +25 -0
  145. package/dist/core/modules/tus/tus-redis-locker.js +87 -0
  146. package/dist/core/modules/tus/tus-redis-locker.js.map +1 -0
  147. package/dist/core/modules/tus/tus.module.d.ts +1 -0
  148. package/dist/core/modules/tus/tus.module.js +30 -4
  149. package/dist/core/modules/tus/tus.module.js.map +1 -1
  150. package/dist/core.module.js +18 -1
  151. package/dist/core.module.js.map +1 -1
  152. package/dist/index.d.ts +10 -0
  153. package/dist/index.js +10 -0
  154. package/dist/index.js.map +1 -1
  155. package/dist/main.js +2 -1
  156. package/dist/main.js.map +1 -1
  157. package/dist/server/modules/file/file.controller.js.map +1 -1
  158. package/dist/server/modules/file/file.resolver.js +1 -12
  159. package/dist/server/modules/file/file.resolver.js.map +1 -1
  160. package/dist/server/modules/file/file.service.d.ts +5 -1
  161. package/dist/server/modules/file/file.service.js +12 -3
  162. package/dist/server/modules/file/file.service.js.map +1 -1
  163. package/dist/server/modules/user/avatar.controller.d.ts +5 -1
  164. package/dist/server/modules/user/avatar.controller.js +26 -10
  165. package/dist/server/modules/user/avatar.controller.js.map +1 -1
  166. package/dist/server/modules/user/user.model.js +3 -2
  167. package/dist/server/modules/user/user.model.js.map +1 -1
  168. package/dist/server/modules/user/user.module.js +6 -2
  169. package/dist/server/modules/user/user.module.js.map +1 -1
  170. package/dist/server/modules/user/user.service.d.ts +1 -1
  171. package/dist/server/modules/user/user.service.js +6 -13
  172. package/dist/server/modules/user/user.service.js.map +1 -1
  173. package/dist/tsconfig.build.tsbuildinfo +1 -1
  174. package/docs/REQUEST-LIFECYCLE.md +57 -8
  175. package/docs/security-overrides.md +30 -5
  176. package/migration-guides/11.32.3-to-11.32.4.md +335 -0
  177. package/migration-guides/11.32.x-to-11.33.x.md +1551 -0
  178. package/package.json +74 -42
  179. package/src/core/common/helpers/content-disposition.helper.ts +90 -0
  180. package/src/core/common/helpers/file.helper.ts +201 -20
  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 +108 -0
  204. package/src/core/modules/file/README.md +324 -41
  205. package/src/core/modules/file/core-file.controller.ts +267 -19
  206. package/src/core/modules/file/core-file.resolver.ts +46 -12
  207. package/src/core/modules/file/core-file.service.ts +482 -16
  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 +68 -6
  224. package/src/core/modules/migrate/cli/migrate-cli.ts +69 -6
  225. package/src/core/modules/migrate/helpers/migration.helper.ts +144 -10
  226. package/src/core/modules/migrate/migration-runner.ts +10 -1
  227. package/src/core/modules/migrate/mongo-state-store.ts +179 -11
  228. package/src/core/modules/system-setup/README.md +7 -2
  229. package/src/core/modules/system-setup/core-system-setup.service.ts +95 -1
  230. package/src/core/modules/tenant/README.md +21 -1
  231. package/src/core/modules/tenant/core-tenant.guard.ts +135 -14
  232. package/src/core/modules/tenant/core-tenant.module.ts +8 -4
  233. package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +93 -21
  234. package/src/core/modules/tus/README.md +35 -2
  235. package/src/core/modules/tus/core-tus.controller.ts +64 -12
  236. package/src/core/modules/tus/core-tus.service.ts +358 -34
  237. package/src/core/modules/tus/interfaces/tus-config.interface.ts +9 -0
  238. package/src/core/modules/tus/tus-redis-locker.ts +139 -0
  239. package/src/core/modules/tus/tus.module.ts +78 -6
  240. package/src/core.module.ts +26 -1
  241. package/src/index.ts +10 -0
  242. package/src/main.ts +7 -1
  243. package/src/server/modules/file/file.controller.ts +7 -3
  244. package/src/server/modules/file/file.resolver.ts +5 -17
  245. package/src/server/modules/file/file.service.ts +42 -3
  246. package/src/server/modules/user/avatar.controller.ts +39 -9
  247. package/src/server/modules/user/user.model.ts +9 -3
  248. package/src/server/modules/user/user.module.ts +12 -3
  249. package/src/server/modules/user/user.service.ts +20 -17
  250. package/src/test/README.md +7 -2
  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/hub/core-hub.module.spec.ts +0 -108
  255. package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +0 -48
  256. package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +0 -67
  257. package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +0 -61
  258. package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +0 -106
  259. package/src/core/modules/hub/hub-config.helper.spec.ts +0 -108
  260. package/src/core/modules/hub/hub-ring-buffer.spec.ts +0 -95
  261. package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +0 -116
  262. package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +0 -112
@@ -0,0 +1,116 @@
1
+ import { INestApplication, Logger } from '@nestjs/common';
2
+
3
+ import { ConfigService } from '../services/config.service';
4
+
5
+ /** Signals an orchestrator uses to ask a container to stop */
6
+ const SHUTDOWN_SIGNALS: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];
7
+
8
+ /**
9
+ * A delay this long is almost certainly a typo (seconds written as milliseconds, or the other way
10
+ * round). Orchestrators SIGKILL after their grace period anyway — typically 10-30s — so a longer
11
+ * wait cannot be honored, it only guarantees the ungraceful ending it was meant to avoid.
12
+ */
13
+ const MAX_SHUTDOWN_DELAY_MS = 60_000;
14
+
15
+ /**
16
+ * Above this, the delay is very likely longer than what actually gets to run.
17
+ *
18
+ * The cap above catches typos; it does NOT describe the real ceiling, and the gap between the two
19
+ * is where this silently breaks. Three separate limits sit BELOW the 60s cap:
20
+ * Docker/Compose `stop_grace_period` defaults to 10s, Kubernetes `terminationGracePeriodSeconds`
21
+ * to 30s, and this framework's own `installProcessDiagnostics()` arms a 30s watchdog that calls
22
+ * `exit(1)`. A delay of, say, 45s passes the cap without a word and is then force-killed mid-wait:
23
+ * no drain, and not one `onModuleDestroy` / `onApplicationShutdown` hook runs — precisely the
24
+ * ending the delay exists to prevent. Warning here means the dangerous band is no longer silent.
25
+ *
26
+ * The delay is only part of the budget: whatever `app.close()` needs to drain comes after it.
27
+ */
28
+ const SHUTDOWN_DELAY_ADVISORY_MS = 10_000;
29
+
30
+ /**
31
+ * Install signal handling that keeps the app healthy for `IServerOptions.shutdownDelayMs`
32
+ * before shutting it down, then closes it cleanly.
33
+ *
34
+ * **Why this is not a NestJS lifecycle hook.** The obvious place looks like
35
+ * `beforeApplicationShutdown`, but Nest's `close()` runs
36
+ * `onModuleDestroy` → `beforeApplicationShutdown` → dispose (close the HTTP server) →
37
+ * `onApplicationShutdown`. A delay in that hook therefore waits AFTER every module has been
38
+ * destroyed while the socket is still accepting — so the instance keeps taking traffic with its
39
+ * services already torn down. That is worse than not waiting at all, which is why the wait has to
40
+ * happen before `close()` is entered, i.e. in the signal handler.
41
+ *
42
+ * **What the delay is for.** An orchestrator sends SIGTERM and deregisters the instance from the
43
+ * load balancer at the same moment, but deregistration propagates asynchronously — for a beat,
44
+ * requests still arrive. Staying fully healthy through that beat is what lets them be answered
45
+ * instead of dropped.
46
+ *
47
+ * Without a configured delay this is exactly `app.enableShutdownHooks()`, which is what the
48
+ * framework did before.
49
+ *
50
+ * @param app the Nest application to shut down
51
+ * @returns the same app, so it can be chained
52
+ */
53
+ export function installGracefulShutdown<T extends INestApplication>(app: T): T {
54
+ const logger = new Logger('GracefulShutdown');
55
+ const configured = ConfigService.getFastButReadOnly<number | undefined>('shutdownDelayMs');
56
+ const delayMs = typeof configured === 'number' && configured > 0 ? configured : 0;
57
+
58
+ if (!delayMs) {
59
+ // Nest's own handling: close immediately on a signal, running every lifecycle hook.
60
+ app.enableShutdownHooks();
61
+ return app;
62
+ }
63
+
64
+ if (delayMs > MAX_SHUTDOWN_DELAY_MS) {
65
+ logger.warn(
66
+ `shutdownDelayMs is ${delayMs}ms, which exceeds the ${MAX_SHUTDOWN_DELAY_MS}ms cap and is longer than a typical ` +
67
+ `orchestrator grace period — capping it, since a longer wait only ends in SIGKILL.`,
68
+ );
69
+ } else if (delayMs > SHUTDOWN_DELAY_ADVISORY_MS) {
70
+ logger.warn(
71
+ `shutdownDelayMs is ${delayMs}ms. Keep it well below your orchestrator's grace period AND leave room for the ` +
72
+ 'drain that follows it — Docker/Compose stop_grace_period defaults to 10s, Kubernetes ' +
73
+ 'terminationGracePeriodSeconds to 30s, and installProcessDiagnostics() force-exits after 30s. Exceeding any ' +
74
+ 'of them means the process is killed mid-wait and no shutdown hook runs at all.',
75
+ );
76
+ }
77
+ const effectiveDelayMs = Math.min(delayMs, MAX_SHUTDOWN_DELAY_MS);
78
+
79
+ // Deliberately NOT enableShutdownHooks(): Nest would install its own listener for the same
80
+ // signals and close the app immediately, in parallel with the wait below.
81
+ let shuttingDown = false;
82
+ // Hoisted out of the handler so a second signal can CANCEL the pending wait. Left inside, the
83
+ // timer stayed armed and fired after the impatient close had already run, calling app.close()
84
+ // a second time and re-running every onApplicationShutdown hook against a closed app.
85
+ let timer: NodeJS.Timeout | undefined;
86
+
87
+ const handle = (signal: NodeJS.Signals): void => {
88
+ if (shuttingDown) {
89
+ // A second signal means someone is impatient. Stop waiting and close now.
90
+ logger.warn(`${signal} received again — closing immediately`);
91
+ if (timer) {
92
+ clearTimeout(timer);
93
+ timer = undefined;
94
+ }
95
+ void app.close();
96
+ return;
97
+ }
98
+ shuttingDown = true;
99
+ logger.log(`${signal} received — staying healthy for ${effectiveDelayMs}ms so the load balancer can deregister`);
100
+
101
+ timer = setTimeout(() => {
102
+ app
103
+ .close()
104
+ .then(() => logger.log('Shutdown complete'))
105
+ .catch((error) => logger.error(`Shutdown failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
106
+ }, effectiveDelayMs);
107
+ // The listening server keeps the loop alive on its own; this timer must not be what holds it.
108
+ timer.unref?.();
109
+ };
110
+
111
+ for (const signal of SHUTDOWN_SIGNALS) {
112
+ process.on(signal, handle);
113
+ }
114
+
115
+ return app;
116
+ }
@@ -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
  */