@c9up/ream 0.2.8 → 0.2.10

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 (183) hide show
  1. package/README.md +1 -0
  2. package/dist/Application.d.ts +26 -6
  3. package/dist/Application.d.ts.map +1 -1
  4. package/dist/Application.js +40 -12
  5. package/dist/Application.js.map +1 -1
  6. package/dist/ErrorBoundary.d.ts.map +1 -1
  7. package/dist/ErrorBoundary.js +2 -1
  8. package/dist/ErrorBoundary.js.map +1 -1
  9. package/dist/Ignitor.d.ts +13 -0
  10. package/dist/Ignitor.d.ts.map +1 -1
  11. package/dist/Ignitor.js +149 -26
  12. package/dist/Ignitor.js.map +1 -1
  13. package/dist/bodyparser/MultipartFile.d.ts +8 -0
  14. package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
  15. package/dist/bodyparser/MultipartFile.js +46 -1
  16. package/dist/bodyparser/MultipartFile.js.map +1 -1
  17. package/dist/commands/Migrate.d.ts +9 -0
  18. package/dist/commands/Migrate.d.ts.map +1 -0
  19. package/dist/commands/Migrate.js +16 -0
  20. package/dist/commands/Migrate.js.map +1 -0
  21. package/dist/commands/MigrateBase.d.ts +27 -0
  22. package/dist/commands/MigrateBase.d.ts.map +1 -0
  23. package/dist/commands/MigrateBase.js +77 -0
  24. package/dist/commands/MigrateBase.js.map +1 -0
  25. package/dist/commands/MigrateRollback.d.ts +9 -0
  26. package/dist/commands/MigrateRollback.d.ts.map +1 -0
  27. package/dist/commands/MigrateRollback.js +16 -0
  28. package/dist/commands/MigrateRollback.js.map +1 -0
  29. package/dist/commands/MigrateStatus.d.ts +9 -0
  30. package/dist/commands/MigrateStatus.d.ts.map +1 -0
  31. package/dist/commands/MigrateStatus.js +19 -0
  32. package/dist/commands/MigrateStatus.js.map +1 -0
  33. package/dist/commands/ScheduleList.d.ts +18 -0
  34. package/dist/commands/ScheduleList.d.ts.map +1 -0
  35. package/dist/commands/ScheduleList.js +55 -0
  36. package/dist/commands/ScheduleList.js.map +1 -0
  37. package/dist/commands/ScheduleRun.d.ts +19 -0
  38. package/dist/commands/ScheduleRun.d.ts.map +1 -0
  39. package/dist/commands/ScheduleRun.js +61 -0
  40. package/dist/commands/ScheduleRun.js.map +1 -0
  41. package/dist/commands/index.d.ts +15 -0
  42. package/dist/commands/index.d.ts.map +1 -0
  43. package/dist/commands/index.js +63 -0
  44. package/dist/commands/index.js.map +1 -0
  45. package/dist/commands/scheduler_resolver.d.ts +12 -0
  46. package/dist/commands/scheduler_resolver.d.ts.map +1 -0
  47. package/dist/commands/scheduler_resolver.js +19 -0
  48. package/dist/commands/scheduler_resolver.js.map +1 -0
  49. package/dist/env/Env.d.ts.map +1 -1
  50. package/dist/env/Env.js +2 -1
  51. package/dist/env/Env.js.map +1 -1
  52. package/dist/env/loadEnvFiles.d.ts.map +1 -1
  53. package/dist/env/loadEnvFiles.js +5 -1
  54. package/dist/env/loadEnvFiles.js.map +1 -1
  55. package/dist/env/nodeEnv.d.ts +24 -0
  56. package/dist/env/nodeEnv.d.ts.map +1 -0
  57. package/dist/env/nodeEnv.js +40 -0
  58. package/dist/env/nodeEnv.js.map +1 -0
  59. package/dist/errors/ReamError.d.ts +6 -1
  60. package/dist/errors/ReamError.d.ts.map +1 -1
  61. package/dist/errors/ReamError.js +8 -2
  62. package/dist/errors/ReamError.js.map +1 -1
  63. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  64. package/dist/graphql/GraphQLEngine.js +3 -2
  65. package/dist/graphql/GraphQLEngine.js.map +1 -1
  66. package/dist/http/Exception.d.ts.map +1 -1
  67. package/dist/http/Exception.js +2 -1
  68. package/dist/http/Exception.js.map +1 -1
  69. package/dist/http/Request.d.ts +18 -3
  70. package/dist/http/Request.d.ts.map +1 -1
  71. package/dist/http/Request.js +71 -8
  72. package/dist/http/Request.js.map +1 -1
  73. package/dist/http/Response.d.ts +11 -4
  74. package/dist/http/Response.d.ts.map +1 -1
  75. package/dist/http/Response.js +46 -17
  76. package/dist/http/Response.js.map +1 -1
  77. package/dist/index.d.ts +3 -3
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +2 -2
  80. package/dist/index.js.map +1 -1
  81. package/dist/router/Router.d.ts.map +1 -1
  82. package/dist/router/Router.js +14 -5
  83. package/dist/router/Router.js.map +1 -1
  84. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  85. package/dist/rpc/RpcRouter.js +2 -1
  86. package/dist/rpc/RpcRouter.js.map +1 -1
  87. package/dist/scheduler/ScheduleProvider.d.ts +14 -0
  88. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
  89. package/dist/scheduler/ScheduleProvider.js +62 -14
  90. package/dist/scheduler/ScheduleProvider.js.map +1 -1
  91. package/dist/scheduler/config.d.ts +29 -0
  92. package/dist/scheduler/config.d.ts.map +1 -0
  93. package/dist/scheduler/config.js +14 -0
  94. package/dist/scheduler/config.js.map +1 -0
  95. package/dist/scheduler/index.d.ts +4 -0
  96. package/dist/scheduler/index.d.ts.map +1 -1
  97. package/dist/scheduler/index.js +3 -0
  98. package/dist/scheduler/index.js.map +1 -1
  99. package/dist/scheduler/locks/LockBackend.d.ts +13 -3
  100. package/dist/scheduler/locks/LockBackend.d.ts.map +1 -1
  101. package/dist/scheduler/locks/LockBackend.js +20 -4
  102. package/dist/scheduler/locks/LockBackend.js.map +1 -1
  103. package/dist/scheduler/locks/MemoryLockBackend.d.ts +5 -6
  104. package/dist/scheduler/locks/MemoryLockBackend.d.ts.map +1 -1
  105. package/dist/scheduler/locks/MemoryLockBackend.js +5 -13
  106. package/dist/scheduler/locks/MemoryLockBackend.js.map +1 -1
  107. package/dist/scheduler/locks/RedisLockBackend.d.ts +53 -0
  108. package/dist/scheduler/locks/RedisLockBackend.d.ts.map +1 -0
  109. package/dist/scheduler/locks/RedisLockBackend.js +83 -0
  110. package/dist/scheduler/locks/RedisLockBackend.js.map +1 -0
  111. package/dist/scheduler/locks/locks.d.ts +39 -0
  112. package/dist/scheduler/locks/locks.d.ts.map +1 -0
  113. package/dist/scheduler/locks/locks.js +42 -0
  114. package/dist/scheduler/locks/locks.js.map +1 -0
  115. package/dist/scheduler/locks/quasar.d.ts +17 -0
  116. package/dist/scheduler/locks/quasar.d.ts.map +1 -0
  117. package/dist/scheduler/locks/quasar.js +51 -0
  118. package/dist/scheduler/locks/quasar.js.map +1 -0
  119. package/dist/security/CookieSigner.d.ts +16 -13
  120. package/dist/security/CookieSigner.d.ts.map +1 -1
  121. package/dist/security/CookieSigner.js +33 -14
  122. package/dist/security/CookieSigner.js.map +1 -1
  123. package/dist/services/encryption.d.ts +12 -2
  124. package/dist/services/encryption.d.ts.map +1 -1
  125. package/dist/services/encryption.js +11 -1
  126. package/dist/services/encryption.js.map +1 -1
  127. package/dist/session/Session.d.ts +8 -7
  128. package/dist/session/Session.d.ts.map +1 -1
  129. package/dist/session/Session.js.map +1 -1
  130. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  131. package/dist/session/SessionMiddleware.js +4 -1
  132. package/dist/session/SessionMiddleware.js.map +1 -1
  133. package/dist/testing/ApiResponse.d.ts.map +1 -1
  134. package/dist/testing/ApiResponse.js +1 -1
  135. package/dist/testing/ApiResponse.js.map +1 -1
  136. package/dist/testing/RequestBuilder.d.ts.map +1 -1
  137. package/dist/testing/RequestBuilder.js +1 -1
  138. package/dist/testing/RequestBuilder.js.map +1 -1
  139. package/dist/utils/Macroable.d.ts.map +1 -1
  140. package/dist/utils/Macroable.js +1 -0
  141. package/dist/utils/Macroable.js.map +1 -1
  142. package/events.win32-x64-msvc.node +0 -0
  143. package/index.win32-x64-msvc.node +0 -0
  144. package/package.json +9 -1
  145. package/scheduler.win32-x64-msvc.node +0 -0
  146. package/src/Application.ts +42 -15
  147. package/src/ErrorBoundary.ts +3 -1
  148. package/src/Ignitor.ts +160 -30
  149. package/src/bodyparser/MultipartFile.ts +48 -1
  150. package/src/commands/Migrate.ts +18 -0
  151. package/src/commands/MigrateBase.ts +81 -0
  152. package/src/commands/MigrateRollback.ts +18 -0
  153. package/src/commands/MigrateStatus.ts +22 -0
  154. package/src/commands/ScheduleList.ts +66 -0
  155. package/src/commands/ScheduleRun.ts +65 -0
  156. package/src/commands/index.ts +73 -0
  157. package/src/commands/scheduler_resolver.ts +23 -0
  158. package/src/env/Env.ts +2 -1
  159. package/src/env/loadEnvFiles.ts +5 -1
  160. package/src/env/nodeEnv.ts +38 -0
  161. package/src/errors/ReamError.ts +9 -2
  162. package/src/graphql/GraphQLEngine.ts +3 -2
  163. package/src/http/Exception.ts +2 -1
  164. package/src/http/Request.ts +73 -8
  165. package/src/http/Response.ts +58 -19
  166. package/src/index.ts +8 -1
  167. package/src/router/Router.ts +14 -5
  168. package/src/rpc/RpcRouter.ts +2 -1
  169. package/src/scheduler/ScheduleProvider.ts +65 -15
  170. package/src/scheduler/config.ts +34 -0
  171. package/src/scheduler/index.ts +8 -0
  172. package/src/scheduler/locks/LockBackend.ts +26 -3
  173. package/src/scheduler/locks/MemoryLockBackend.ts +5 -19
  174. package/src/scheduler/locks/RedisLockBackend.ts +114 -0
  175. package/src/scheduler/locks/locks.ts +49 -0
  176. package/src/scheduler/locks/quasar.ts +67 -0
  177. package/src/security/CookieSigner.ts +36 -14
  178. package/src/services/encryption.ts +12 -2
  179. package/src/session/Session.ts +8 -7
  180. package/src/session/SessionMiddleware.ts +4 -1
  181. package/src/testing/ApiResponse.ts +1 -2
  182. package/src/testing/RequestBuilder.ts +1 -2
  183. package/src/utils/Macroable.ts +1 -0
@@ -28,11 +28,13 @@ import { Scheduler } from './Scheduler.js';
28
28
  let activeProvider;
29
29
  export class ScheduleProvider extends Provider {
30
30
  scheduler;
31
- #booted = false;
31
+ /** Task names already registered, so a second pass adds only what is new. */
32
+ #known = new Set();
32
33
  #registered = false;
33
34
  constructor(app, options = {}) {
34
35
  super(app);
35
- this.scheduler = options.scheduler ?? new Scheduler();
36
+ this.scheduler =
37
+ options.scheduler ?? new Scheduler(schedulerOptionsFrom(app.config.get('scheduler')));
36
38
  }
37
39
  /**
38
40
  * Phase 1 — bind the scheduler into the IoC container under the
@@ -64,9 +66,42 @@ export class ScheduleProvider extends Provider {
64
66
  container.singleton('scheduler', () => scheduler);
65
67
  this.#registered = true;
66
68
  }
69
+ /**
70
+ * Discovery runs at START, not at boot, and that is the whole point.
71
+ *
72
+ * `app/modules/**` is auto-loaded during the start phase — after every
73
+ * provider has booted. A `@Service()` carrying `@Schedule` there, which is
74
+ * where one naturally lives, was therefore registered into the service
75
+ * registry AFTER this had already read it. The task was never registered,
76
+ * and nothing said so: no error, no warning, the application started
77
+ * normally and the task simply never fired.
78
+ *
79
+ * Reading the registry once the modules are in place is what fixes it. A
80
+ * service declared anywhere earlier — a provider, a preload — is in the
81
+ * registry by then too, so nothing is lost by waiting.
82
+ */
67
83
  async boot() {
68
- if (this.#booted)
69
- return;
84
+ // Anything already in the registry — declared by a provider, or imported
85
+ // by one. The rest is caught at start().
86
+ this.#discover();
87
+ }
88
+ async start() {
89
+ this.#discover();
90
+ if (activeProvider !== undefined && activeProvider !== this) {
91
+ // A previous app's scheduler is still running — stop it before this one
92
+ // starts, otherwise every @Schedule task fires twice per tick.
93
+ activeProvider.scheduler.stop();
94
+ }
95
+ activeProvider = this;
96
+ this.scheduler.start();
97
+ }
98
+ /**
99
+ * Walk the service registry and register every `@Schedule` it declares.
100
+ *
101
+ * Idempotent: a second call registers nothing twice, so an application that
102
+ * restarts its Ignitor does not end up firing everything in duplicate.
103
+ */
104
+ #discover() {
70
105
  const registry = getServiceRegistry();
71
106
  const registered = [];
72
107
  try {
@@ -90,6 +125,9 @@ export class ScheduleProvider extends Provider {
90
125
  });
91
126
  }
92
127
  const taskName = `${target.name}.${methodName}`;
128
+ // Registered on an earlier pass: skip rather than fire it twice.
129
+ if (this.#known.has(taskName))
130
+ continue;
93
131
  try {
94
132
  this.scheduler.register(taskName, cronExpr, async () => {
95
133
  // Resolve from the container on every fire so transient
@@ -102,6 +140,7 @@ export class ScheduleProvider extends Provider {
102
140
  await method.call(instance);
103
141
  });
104
142
  registered.push(taskName);
143
+ this.#known.add(taskName);
105
144
  }
106
145
  catch (cause) {
107
146
  const inner = cause;
@@ -112,7 +151,6 @@ export class ScheduleProvider extends Provider {
112
151
  }
113
152
  }
114
153
  }
115
- this.#booted = true;
116
154
  }
117
155
  catch (err) {
118
156
  // Roll back any partial registrations so a retry does not hit
@@ -129,15 +167,6 @@ export class ScheduleProvider extends Provider {
129
167
  throw err;
130
168
  }
131
169
  }
132
- async start() {
133
- if (activeProvider !== undefined && activeProvider !== this) {
134
- // A previous app's scheduler is still running — stop it before this one starts,
135
- // otherwise every @Schedule task fires twice per tick.
136
- activeProvider.scheduler.stop();
137
- }
138
- activeProvider = this;
139
- this.scheduler.start();
140
- }
141
170
  async shutdown() {
142
171
  if (activeProvider === this)
143
172
  activeProvider = undefined;
@@ -146,4 +175,23 @@ export class ScheduleProvider extends Provider {
146
175
  }
147
176
  // Default export so reamrc's provider loader can `() => import('@c9up/ream/<feature>/provider')` (resolves to { default }), matching events/rpc. Named export above stays.
148
177
  export default ScheduleProvider;
178
+ /**
179
+ * Turn `config/scheduler.ts` into the options the `Scheduler` takes.
180
+ *
181
+ * The lock is built here rather than in the config file because that file is
182
+ * read before the application boots — a Redis connection named in it does not
183
+ * exist yet, so the helpers hand back a factory and this calls it.
184
+ */
185
+ function schedulerOptionsFrom(config) {
186
+ if (config === undefined)
187
+ return {};
188
+ const options = {};
189
+ if (config.lock !== undefined) {
190
+ options.lockBackend = typeof config.lock === 'function' ? config.lock() : config.lock;
191
+ }
192
+ if (config.defaultLockTtlMs !== undefined) {
193
+ options.defaultLockTtlMs = config.defaultLockTtlMs;
194
+ }
195
+ return options;
196
+ }
149
197
  //# sourceMappingURL=ScheduleProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScheduleProvider.js","sourceRoot":"","sources":["../../src/scheduler/ScheduleProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ScheduleProvider.js","sourceRoot":"","sources":["../../src/scheduler/ScheduleProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Author-time config for `config/scheduler.ts`.
3
+ *
4
+ * import { defineConfig, locks } from '@c9up/ream/scheduler/config'
5
+ *
6
+ * export default defineConfig({
7
+ * lock: locks.redis({ connection: 'main' }),
8
+ * })
9
+ */
10
+ import type { LockBackend } from './locks/LockBackend.js';
11
+ import type { LockBackendFactory } from './locks/locks.js';
12
+ export interface SchedulerConfig {
13
+ /**
14
+ * The lock consulted before a task fires, so that only one instance runs
15
+ * it. Takes a backend, or a factory answering one — which is what the
16
+ * `locks.*` helpers return, so a config file can name a Redis lock before
17
+ * the connection exists.
18
+ *
19
+ * Left out, the scheduler locks nothing: every instance runs every task.
20
+ * Correct on one instance, and a duplicate run per replica past that.
21
+ */
22
+ lock?: LockBackend | LockBackendFactory;
23
+ /** How long a lease is held before it expires on its own. Default 60 000. */
24
+ defaultLockTtlMs?: number;
25
+ }
26
+ export declare function defineConfig(config: SchedulerConfig): SchedulerConfig;
27
+ export type { LockBackend } from './locks/LockBackend.js';
28
+ export { type LockBackendFactory, locks } from './locks/locks.js';
29
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/scheduler/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,WAAW,GAAG,kBAAkB,CAAA;IACvC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Author-time config for `config/scheduler.ts`.
3
+ *
4
+ * import { defineConfig, locks } from '@c9up/ream/scheduler/config'
5
+ *
6
+ * export default defineConfig({
7
+ * lock: locks.redis({ connection: 'main' }),
8
+ * })
9
+ */
10
+ export function defineConfig(config) {
11
+ return config;
12
+ }
13
+ export { locks } from './locks/locks.js';
14
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/scheduler/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,OAAO,MAAM,CAAA;AACf,CAAC;AAGD,OAAO,EAA2B,KAAK,EAAE,MAAM,kBAAkB,CAAA"}
@@ -1,5 +1,9 @@
1
+ export type { SchedulerConfig } from './config.js';
2
+ export { defineConfig as defineSchedulerConfig } from './config.js';
1
3
  export type { LockBackend } from './locks/LockBackend.js';
4
+ export { type LockBackendFactory, locks } from './locks/locks.js';
2
5
  export { MemoryLockBackend } from './locks/MemoryLockBackend.js';
6
+ export { type LockRedisClient, type LockRedisResolver, RedisLockBackend, } from './locks/RedisLockBackend.js';
3
7
  export type { ErrorReporter } from './observability/ErrorReporter.js';
4
8
  export type { ScheduleEvent, ScheduleTaskCompletedEvent, ScheduleTaskFailedEvent, ScheduleTaskSkippedEvent, ScheduleTaskStartedEvent, } from './observability/ScheduleEvent.js';
5
9
  export type { ScheduleEventSink } from './observability/ScheduleEventSink.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,YAAY,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AACrE,YAAY,EACV,aAAa,EACb,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kCAAkC,CAAA;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAC7E,YAAY,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACpF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,6BAA6B,CAAA;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AACrE,YAAY,EACV,aAAa,EACb,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kCAAkC,CAAA;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAC7E,YAAY,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACpF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1,4 +1,7 @@
1
+ export { defineConfig as defineSchedulerConfig } from './config.js';
2
+ export { locks } from './locks/locks.js';
1
3
  export { MemoryLockBackend } from './locks/MemoryLockBackend.js';
4
+ export { RedisLockBackend, } from './locks/RedisLockBackend.js';
2
5
  export { StatsTracker } from './observability/StatsTracker.js';
3
6
  export { getScheduleMetadata, SCHEDULE_METADATA_KEY, Schedule } from './Schedule.js';
4
7
  export { ScheduleProvider } from './ScheduleProvider.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAWhE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAOxD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnE,OAAO,EAA2B,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EAGL,gBAAgB,GACjB,MAAM,6BAA6B,CAAA;AAWpC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAOxD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
@@ -3,9 +3,12 @@
3
3
  *
4
4
  * The scheduler consults a `LockBackend` immediately before firing a
5
5
  * registered task so that in multi-instance deployments only one
6
- * instance runs a given task on each tick. Redis, database
7
- * advisory-lock, and other backends live in user-land — this module
8
- * ships only the interface and a single-process `MemoryLockBackend`.
6
+ * instance runs a given task on each tick.
7
+ *
8
+ * Two backends ship with it: `MemoryLockBackend`, correct while the
9
+ * application runs in one process, and `RedisLockBackend`, which is
10
+ * what a second replica needs — see `locks` for the factories a
11
+ * config file names.
9
12
  *
10
13
  * @implements Story 28.3
11
14
  */
@@ -34,4 +37,11 @@ export interface LockBackend {
34
37
  */
35
38
  release(name: string): Promise<void>;
36
39
  }
40
+ /**
41
+ * Reject a TTL that cannot bound anything. A zero or negative lease
42
+ * expires the instant it is taken, so every instance would acquire the
43
+ * same lock and run the same task — the exact outcome locking exists to
44
+ * prevent, arriving silently.
45
+ */
46
+ export declare function assertValidTtl(ttlMs: number): void;
37
47
  //# sourceMappingURL=LockBackend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LockBackend.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/LockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEtD;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC"}
1
+ {"version":3,"file":"LockBackend.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/LockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEtD;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAUlD"}
@@ -3,11 +3,27 @@
3
3
  *
4
4
  * The scheduler consults a `LockBackend` immediately before firing a
5
5
  * registered task so that in multi-instance deployments only one
6
- * instance runs a given task on each tick. Redis, database
7
- * advisory-lock, and other backends live in user-land — this module
8
- * ships only the interface and a single-process `MemoryLockBackend`.
6
+ * instance runs a given task on each tick.
7
+ *
8
+ * Two backends ship with it: `MemoryLockBackend`, correct while the
9
+ * application runs in one process, and `RedisLockBackend`, which is
10
+ * what a second replica needs — see `locks` for the factories a
11
+ * config file names.
9
12
  *
10
13
  * @implements Story 28.3
11
14
  */
12
- export {};
15
+ import { ReamError } from '../../errors/ReamError.js';
16
+ /**
17
+ * Reject a TTL that cannot bound anything. A zero or negative lease
18
+ * expires the instant it is taken, so every instance would acquire the
19
+ * same lock and run the same task — the exact outcome locking exists to
20
+ * prevent, arriving silently.
21
+ */
22
+ export function assertValidTtl(ttlMs) {
23
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
24
+ throw new ReamError('SCHEDULE_INVALID_LOCK_TTL', `Lock TTL must be a finite positive number, got ${ttlMs}`, {
25
+ hint: 'Use a millisecond value greater than zero (typical range 1_000 - 600_000).',
26
+ });
27
+ }
28
+ }
13
29
  //# sourceMappingURL=LockBackend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LockBackend.js","sourceRoot":"","sources":["../../../src/scheduler/locks/LockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
1
+ {"version":3,"file":"LockBackend.js","sourceRoot":"","sources":["../../../src/scheduler/locks/LockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AA6BrD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,SAAS,CACjB,2BAA2B,EAC3B,kDAAkD,KAAK,EAAE,EACzD;YACE,IAAI,EAAE,4EAA4E;SACnF,CACF,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Default single-process lock backend backed by a `Map`.
3
3
  *
4
- * Correct for apps that run in a single Node.js process. For
5
- * horizontally-scaled deployments, implement `LockBackend` against a
6
- * shared store (Redis `SET NX PX`, Postgres advisory locks, etc.) —
7
- * those drivers live in user-land per the project's "agnostic per
8
- * package" rule.
4
+ * Correct for apps that run in a single Node.js process, and only
5
+ * those: two processes each keep their own map, so both acquire the
6
+ * same name and both run the task. Reach for `RedisLockBackend` the
7
+ * moment a second replica exists.
9
8
  *
10
9
  * Expiry is evaluated **lazily** at `acquire` time — no background
11
10
  * timer, no `setInterval`. This preserves Story 28.1's
@@ -19,7 +18,7 @@
19
18
  *
20
19
  * @implements Story 28.3
21
20
  */
22
- import type { LockBackend } from './LockBackend.js';
21
+ import { type LockBackend } from './LockBackend.js';
23
22
  export declare class MemoryLockBackend implements LockBackend {
24
23
  #private;
25
24
  acquire(name: string, ttlMs: number): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"MemoryLockBackend.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/MemoryLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAgBnD,qBAAa,iBAAkB,YAAW,WAAW;;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBtD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI3D,gEAAgE;IAChE,gBAAgB,IAAI,MAAM;CAG3B"}
1
+ {"version":3,"file":"MemoryLockBackend.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/MemoryLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAInE,qBAAa,iBAAkB,YAAW,WAAW;;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBtD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI3D,gEAAgE;IAChE,gBAAgB,IAAI,MAAM;CAG3B"}
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Default single-process lock backend backed by a `Map`.
3
3
  *
4
- * Correct for apps that run in a single Node.js process. For
5
- * horizontally-scaled deployments, implement `LockBackend` against a
6
- * shared store (Redis `SET NX PX`, Postgres advisory locks, etc.) —
7
- * those drivers live in user-land per the project's "agnostic per
8
- * package" rule.
4
+ * Correct for apps that run in a single Node.js process, and only
5
+ * those: two processes each keep their own map, so both acquire the
6
+ * same name and both run the task. Reach for `RedisLockBackend` the
7
+ * moment a second replica exists.
9
8
  *
10
9
  * Expiry is evaluated **lazily** at `acquire` time — no background
11
10
  * timer, no `setInterval`. This preserves Story 28.1's
@@ -19,15 +18,8 @@
19
18
  *
20
19
  * @implements Story 28.3
21
20
  */
22
- import { ReamError } from '../../errors/ReamError.js';
21
+ import { assertValidTtl } from './LockBackend.js';
23
22
  const SWEEP_THRESHOLD = 256;
24
- function assertValidTtl(ttlMs) {
25
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
26
- throw new ReamError('SCHEDULE_INVALID_LOCK_TTL', `Lock TTL must be a finite positive number, got ${ttlMs}`, {
27
- hint: 'Use a millisecond value greater than zero (typical range 1_000 – 600_000).',
28
- });
29
- }
30
- }
31
23
  export class MemoryLockBackend {
32
24
  #locks = new Map();
33
25
  async acquire(name, ttlMs) {
@@ -1 +1 @@
1
- {"version":3,"file":"MemoryLockBackend.js","sourceRoot":"","sources":["../../../src/scheduler/locks/MemoryLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAGrD,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,SAAS,CACjB,2BAA2B,EAC3B,kDAAkD,KAAK,EAAE,EACzD;YACE,IAAI,EAAE,4EAA4E;SACnF,CACF,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,OAAO,iBAAiB;IAC5B,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAElC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAAa;QACvC,cAAc,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,8DAA8D;QAC9D,iEAAiE;QACjE,qDAAqD;QACrD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxC,IAAI,MAAM,IAAI,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5C,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,CAAA;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAY,EAAE,QAAgB;QAClD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED,gEAAgE;IAChE,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAA;IACzB,CAAC;CACF"}
1
+ {"version":3,"file":"MemoryLockBackend.js","sourceRoot":"","sources":["../../../src/scheduler/locks/MemoryLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,cAAc,EAAoB,MAAM,kBAAkB,CAAA;AAEnE,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B,MAAM,OAAO,iBAAiB;IAC5B,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAElC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAAa;QACvC,cAAc,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,8DAA8D;QAC9D,iEAAiE;QACjE,qDAAqD;QACrD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxC,IAAI,MAAM,IAAI,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5C,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,CAAA;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAY,EAAE,QAAgB;QAClD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED,gEAAgE;IAChE,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Redis lock backend — the one a second replica needs.
3
+ *
4
+ * Without a shared lock, every instance of an application runs every
5
+ * scheduled task on every tick: a nightly invoice run goes out N times,
6
+ * a reminder email arrives N times, and nothing in the logs says so.
7
+ * `MemoryLockBackend` does not close that gap — each process keeps its
8
+ * own map — and the scheduler locks nothing at all by default.
9
+ *
10
+ * No import of a Redis package. The client is taken structurally — `set`
11
+ * and `eval` — so this works against a `@c9up/quasar` connection without
12
+ * ream depending on quasar, which is an optional peer.
13
+ */
14
+ import { type LockBackend } from './LockBackend.js';
15
+ /** The commands this issues. Any client answering them will do. */
16
+ export interface LockRedisClient {
17
+ /** `SET key value PX <ttl> NX` — answers null when the key already exists. */
18
+ set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
19
+ /** `EVAL script numKeys ...` — used for the compare-and-delete release. */
20
+ eval(script: string, numKeys: number, ...args: unknown[]): Promise<unknown>;
21
+ }
22
+ /**
23
+ * How the backend gets its client: the client itself, or something that
24
+ * answers with one.
25
+ *
26
+ * The resolver form is what a config file needs. `config/scheduler.ts` is
27
+ * read before the application boots, so the connection does not exist yet
28
+ * and cannot be awaited there — a function defers the lookup to the first
29
+ * task that fires.
30
+ */
31
+ export type LockRedisResolver = LockRedisClient | (() => LockRedisClient | Promise<LockRedisClient>);
32
+ export interface RedisLockBackendOptions {
33
+ /** Key prefix. Default `"ream:schedule:lock:"`. */
34
+ prefix?: string;
35
+ }
36
+ export declare class RedisLockBackend implements LockBackend {
37
+ #private;
38
+ constructor(client: LockRedisResolver, options?: RedisLockBackendOptions);
39
+ /**
40
+ * `SET key token PX ttl NX` — one round trip, atomic on the server, which
41
+ * is what makes the decision the same for every instance asking at once.
42
+ *
43
+ * The TTL is what recovers the lock after a crash: an instance that dies
44
+ * holding it never releases, and the lease simply expires.
45
+ */
46
+ acquire(name: string, ttlMs: number): Promise<boolean>;
47
+ /**
48
+ * Idempotent, as the contract requires: releasing a name this instance
49
+ * never acquired — or one whose lease has since expired — does nothing.
50
+ */
51
+ release(name: string): Promise<void>;
52
+ }
53
+ //# sourceMappingURL=RedisLockBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisLockBackend.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/RedisLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnE,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrE,2EAA2E;IAC3E,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5E;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;AAEpG,MAAM,WAAW,uBAAuB;IACtC,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAkBD,qBAAa,gBAAiB,YAAW,WAAW;;gBAOtC,MAAM,EAAE,iBAAiB,EAAE,OAAO,GAAE,uBAA4B;IAmB5E;;;;;;OAMG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY5D;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAO3C"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Redis lock backend — the one a second replica needs.
3
+ *
4
+ * Without a shared lock, every instance of an application runs every
5
+ * scheduled task on every tick: a nightly invoice run goes out N times,
6
+ * a reminder email arrives N times, and nothing in the logs says so.
7
+ * `MemoryLockBackend` does not close that gap — each process keeps its
8
+ * own map — and the scheduler locks nothing at all by default.
9
+ *
10
+ * No import of a Redis package. The client is taken structurally — `set`
11
+ * and `eval` — so this works against a `@c9up/quasar` connection without
12
+ * ream depending on quasar, which is an optional peer.
13
+ */
14
+ import { randomUUID } from 'node:crypto';
15
+ import { assertValidTtl } from './LockBackend.js';
16
+ /**
17
+ * Release only a lease we still hold.
18
+ *
19
+ * A plain `DEL` is the classic way to break this: a task that outlives its
20
+ * TTL loses the lock, another instance acquires it and starts running, and
21
+ * the first one then deletes THAT instance's lease on its way out — leaving
22
+ * the name free while a task is still running under it. Comparing the token
23
+ * before deleting makes the release a no-op once the lease is no longer ours.
24
+ */
25
+ const RELEASE = `
26
+ if redis.call('get', KEYS[1]) == ARGV[1] then
27
+ return redis.call('del', KEYS[1])
28
+ end
29
+ return 0
30
+ `;
31
+ export class RedisLockBackend {
32
+ #source;
33
+ #prefix;
34
+ #resolved;
35
+ /** Our token per held name, so a release can prove the lease is ours. */
36
+ #tokens = new Map();
37
+ constructor(client, options = {}) {
38
+ this.#source = client;
39
+ this.#prefix = options.prefix ?? 'ream:schedule:lock:';
40
+ }
41
+ /** The client, resolved once and kept. */
42
+ #client() {
43
+ if (!this.#resolved) {
44
+ this.#resolved = Promise.resolve(typeof this.#source === 'function' ? this.#source() : this.#source);
45
+ }
46
+ return this.#resolved;
47
+ }
48
+ #key(name) {
49
+ return `${this.#prefix}${name}`;
50
+ }
51
+ /**
52
+ * `SET key token PX ttl NX` — one round trip, atomic on the server, which
53
+ * is what makes the decision the same for every instance asking at once.
54
+ *
55
+ * The TTL is what recovers the lock after a crash: an instance that dies
56
+ * holding it never releases, and the lease simply expires.
57
+ */
58
+ async acquire(name, ttlMs) {
59
+ assertValidTtl(ttlMs);
60
+ const client = await this.#client();
61
+ const token = randomUUID();
62
+ // Redis takes an integer number of milliseconds; a fractional TTL is a
63
+ // protocol error, not a rounding detail.
64
+ const result = await client.set(this.#key(name), token, 'PX', Math.ceil(ttlMs), 'NX');
65
+ if (result === null || result === undefined)
66
+ return false;
67
+ this.#tokens.set(name, token);
68
+ return true;
69
+ }
70
+ /**
71
+ * Idempotent, as the contract requires: releasing a name this instance
72
+ * never acquired — or one whose lease has since expired — does nothing.
73
+ */
74
+ async release(name) {
75
+ const token = this.#tokens.get(name);
76
+ if (token === undefined)
77
+ return;
78
+ this.#tokens.delete(name);
79
+ const client = await this.#client();
80
+ await client.eval(RELEASE, 1, this.#key(name), token);
81
+ }
82
+ }
83
+ //# sourceMappingURL=RedisLockBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisLockBackend.js","sourceRoot":"","sources":["../../../src/scheduler/locks/RedisLockBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,cAAc,EAAoB,MAAM,kBAAkB,CAAA;AA0BnE;;;;;;;;GAQG;AACH,MAAM,OAAO,GAAG;;;;;CAKf,CAAA;AAED,MAAM,OAAO,gBAAgB;IAClB,OAAO,CAAmB;IAC1B,OAAO,CAAQ;IACxB,SAAS,CAAsC;IAC/C,yEAAyE;IAChE,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IAE5C,YAAY,MAAyB,EAAE,UAAmC,EAAE;QAC1E,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAAA;IACxD,CAAC;IAED,0CAA0C;IAC1C,OAAO;QACL,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAC9B,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CACnE,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAED,IAAI,CAAC,IAAY;QACf,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAA;IACjC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAAa;QACvC,cAAc,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;QACnC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAA;QAC1B,uEAAuE;QACvE,yCAAyC;QACzC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;QACrF,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACzD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC7B,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAM;QAC/B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;QACnC,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;IACvD,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The lock backends a scheduler config names.
3
+ *
4
+ * // config/scheduler.ts
5
+ * import { defineConfig, locks } from '@c9up/ream'
6
+ *
7
+ * export default defineConfig({
8
+ * lock: locks.redis({ connection: 'main' }),
9
+ * })
10
+ *
11
+ * Without a lock the scheduler runs every task on every instance, which is
12
+ * correct while there is one, and duplicates every run once there are two.
13
+ *
14
+ * Factories are lazy: the backend is built when the scheduler is, so naming
15
+ * a Redis lock in a config that runs single-instance costs nothing until it
16
+ * is selected.
17
+ */
18
+ import type { LockBackend } from './LockBackend.js';
19
+ import { type LockRedisResolver } from './RedisLockBackend.js';
20
+ /** A lock backend, built on first use. */
21
+ export type LockBackendFactory = () => LockBackend;
22
+ export declare const locks: {
23
+ /**
24
+ * In this process's memory. Two processes each keep their own, so this
25
+ * bounds nothing across replicas — it is for a single-process deployment
26
+ * and for tests.
27
+ */
28
+ memory(): LockBackendFactory;
29
+ /**
30
+ * Redis. `connection` takes a client, a function answering one, or the
31
+ * NAME of a `@c9up/quasar` connection — the last of which is resolved at
32
+ * runtime without ream importing quasar, which stays an optional peer.
33
+ */
34
+ redis(options: {
35
+ connection: LockRedisResolver | string;
36
+ prefix?: string;
37
+ }): LockBackendFactory;
38
+ };
39
+ //# sourceMappingURL=locks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../../src/scheduler/locks/locks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,uBAAuB,CAAA;AAEhF,0CAA0C;AAC1C,MAAM,MAAM,kBAAkB,GAAG,MAAM,WAAW,CAAA;AAElD,eAAO,MAAM,KAAK;IAChB;;;;OAIG;cACO,kBAAkB;IAI5B;;;;OAIG;mBACY;QAAE,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,kBAAkB;CAOhG,CAAA"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The lock backends a scheduler config names.
3
+ *
4
+ * // config/scheduler.ts
5
+ * import { defineConfig, locks } from '@c9up/ream'
6
+ *
7
+ * export default defineConfig({
8
+ * lock: locks.redis({ connection: 'main' }),
9
+ * })
10
+ *
11
+ * Without a lock the scheduler runs every task on every instance, which is
12
+ * correct while there is one, and duplicates every run once there are two.
13
+ *
14
+ * Factories are lazy: the backend is built when the scheduler is, so naming
15
+ * a Redis lock in a config that runs single-instance costs nothing until it
16
+ * is selected.
17
+ */
18
+ import { MemoryLockBackend } from './MemoryLockBackend.js';
19
+ import { quasarConnection } from './quasar.js';
20
+ import { RedisLockBackend } from './RedisLockBackend.js';
21
+ export const locks = {
22
+ /**
23
+ * In this process's memory. Two processes each keep their own, so this
24
+ * bounds nothing across replicas — it is for a single-process deployment
25
+ * and for tests.
26
+ */
27
+ memory() {
28
+ return () => new MemoryLockBackend();
29
+ },
30
+ /**
31
+ * Redis. `connection` takes a client, a function answering one, or the
32
+ * NAME of a `@c9up/quasar` connection — the last of which is resolved at
33
+ * runtime without ream importing quasar, which stays an optional peer.
34
+ */
35
+ redis(options) {
36
+ const client = typeof options.connection === 'string'
37
+ ? quasarConnection(options.connection)
38
+ : options.connection;
39
+ return () => new RedisLockBackend(client, { prefix: options.prefix });
40
+ },
41
+ };
42
+ //# sourceMappingURL=locks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.js","sourceRoot":"","sources":["../../../src/scheduler/locks/locks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAA0B,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAKhF,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;;;;OAIG;IACH,MAAM;QACJ,OAAO,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE,CAAA;IACtC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAoE;QACxE,MAAM,MAAM,GACV,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;YACpC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC;YACtC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAA;QACxB,OAAO,GAAG,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACvE,CAAC;CACF,CAAA"}