@c9up/bay 0.1.8 → 0.1.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ ream configure @c9up/bay
13
13
 
14
14
  ## Usage
15
15
 
16
- Register the provider in your app, then configure it under `config/bay.ts`:
16
+ Register the provider in your app, then configure it under `config/queue.ts` — the provider reads `config.get('queue')`, so a `config/bay.ts` would be loaded under the key `bay` and never seen:
17
17
 
18
18
  ```ts
19
19
  // reamrc.ts
@@ -29,9 +29,16 @@ export interface RedisClient {
29
29
  set(key: string, value: string, ...args: string[]): Promise<string | null>;
30
30
  get(key: string): Promise<string | null>;
31
31
  }
32
+ /**
33
+ * Where the client comes from. A resolver is what lets a queue name its
34
+ * connection (`quasarConnection("jobs")`) instead of being handed a client:
35
+ * the driver is built synchronously, the first command that needs the client
36
+ * is not.
37
+ */
38
+ export type RedisClientSource = RedisClient | (() => RedisClient | Promise<RedisClient>);
32
39
  export declare class RedisDriver implements QueueDriver {
33
40
  #private;
34
- constructor(client: RedisClient, options?: {
41
+ constructor(source: RedisClientSource, options?: {
35
42
  prefix?: string;
36
43
  visibilityTimeoutMs?: number;
37
44
  });
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,CACL,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,OAAO,EACtB,EAAE,EAAE,MAAM,GAAG,OAAO,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzC;AAcD,qBAAa,WAAY,YAAW,WAAW;;gBAM7C,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE;IA6BtD,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7B,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAsC1B,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IA4D/B,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAcxB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG7B"}
1
+ {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,CACL,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,OAAO,EACtB,EAAE,EAAE,MAAM,GAAG,OAAO,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzC;AAcD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAC1B,WAAW,GACX,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;AA6B9C,qBAAa,WAAY,YAAW,WAAW;;gBAsC7C,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE;IA8BtD,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7B,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAuC1B,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5C,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IA8D/B,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAexB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI7B"}
@@ -27,13 +27,79 @@ function isValidJob(obj) {
27
27
  typeof j.maxAttempts === "number" &&
28
28
  typeof j.status === "string");
29
29
  }
30
+ /**
31
+ * LMOVE (Redis 6.2+) is what makes pop() atomic. Warned once, when the client
32
+ * is resolved rather than at construction — a driver that names its connection
33
+ * has no client to inspect yet.
34
+ */
35
+ const warned = new WeakSet();
36
+ function warnWithoutLmove(client) {
37
+ if (typeof client.lmove === "function" || warned.has(client))
38
+ return;
39
+ warned.add(client);
40
+ console.warn("[bay] RedisDriver: client lacks LMOVE (Redis <6.2). pop() falls back to " +
41
+ "a non-atomic lpop+rpush, downgrading delivery from at-least-once to " +
42
+ "at-most-once — a crash between the two commands loses the in-flight job.");
43
+ }
44
+ /**
45
+ * A key prefix that ends in a separator.
46
+ *
47
+ * `:` is Redis's conventional namespace separator; a prefix already ending in
48
+ * one of the usual separators is left alone.
49
+ */
50
+ function withSeparator(prefix) {
51
+ if (prefix.length === 0)
52
+ return prefix;
53
+ return /[:.\-_/]$/.test(prefix) ? prefix : `${prefix}:`;
54
+ }
30
55
  export class RedisDriver {
31
- #client;
56
+ #source;
57
+ #resolved;
58
+ #pending;
32
59
  #prefix;
33
60
  #visibilityTimeout;
34
- constructor(client, options) {
35
- this.#client = client;
36
- this.#prefix = options?.prefix ?? "queue:";
61
+ /**
62
+ * The client, resolved once. Two workers racing on a cold queue must not
63
+ * each open their own connection, so the in-flight promise is shared.
64
+ */
65
+ async #client() {
66
+ if (this.#resolved)
67
+ return this.#resolved;
68
+ if (typeof this.#source !== "function") {
69
+ this.#resolved = this.#source;
70
+ warnWithoutLmove(this.#resolved);
71
+ return this.#resolved;
72
+ }
73
+ if (!this.#pending) {
74
+ const resolver = this.#source;
75
+ this.#pending = Promise.resolve(resolver())
76
+ .then((client) => {
77
+ this.#resolved = client;
78
+ warnWithoutLmove(client);
79
+ return client;
80
+ })
81
+ // Cleared on failure too. Clearing only on success left the
82
+ // REJECTED promise cached forever, so one transient outage at
83
+ // startup broke every later call for the life of the process —
84
+ // a permanent failure with no error of its own to explain it.
85
+ .finally(() => {
86
+ this.#pending = undefined;
87
+ });
88
+ }
89
+ return this.#pending;
90
+ }
91
+ constructor(source, options) {
92
+ this.#source = source;
93
+ // A client handed in directly can be checked now, so the warning keeps
94
+ // landing at construction as it always did. A named connection has no
95
+ // client yet — it is checked when the connection resolves.
96
+ if (typeof source !== "function")
97
+ warnWithoutLmove(source);
98
+ // Normalised rather than documented: every key is built by concatenation
99
+ // (`${prefix}pending`), so a prefix without a trailing separator yields
100
+ // "myapppending" — unreadable, and able to collide with a neighbouring
101
+ // prefix. Nothing warned, because nothing failed.
102
+ this.#prefix = withSeparator(options?.prefix ?? "queue:");
37
103
  const visibilityTimeout = options?.visibilityTimeoutMs ?? 30_000;
38
104
  // A non-positive / non-integer timeout makes pop()'s `SET … PX <ms>` fail
39
105
  // on a real Redis; the catch then removes the job from `processing` and
@@ -43,28 +109,25 @@ export class RedisDriver {
43
109
  throw new Error(`[bay] RedisDriver visibilityTimeoutMs must be a positive integer (ms), got ${visibilityTimeout}`);
44
110
  }
45
111
  this.#visibilityTimeout = visibilityTimeout;
46
- if (typeof client.lmove !== "function") {
47
- console.warn("[bay] RedisDriver: client lacks LMOVE (Redis <6.2). pop() falls back to " +
48
- "a non-atomic lpop+rpush, downgrading delivery from at-least-once to " +
49
- "at-most-once — a crash between the two commands loses the in-flight job.");
50
- }
51
112
  }
52
113
  #pendingKey = () => `${this.#prefix}pending`;
53
114
  #processingKey = () => `${this.#prefix}processing`;
54
115
  #failedKey = () => `${this.#prefix}failed`;
55
116
  #leaseKey = (jobId) => `${this.#prefix}lease:${jobId}`;
56
117
  async push(job) {
57
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
118
+ const client = await this.#client();
119
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
58
120
  }
59
121
  async pop() {
122
+ const client = await this.#client();
60
123
  let raw = null;
61
- if (this.#client.lmove) {
62
- raw = await this.#client.lmove(this.#pendingKey(), this.#processingKey(), "LEFT", "RIGHT");
124
+ if (client.lmove) {
125
+ raw = await client.lmove(this.#pendingKey(), this.#processingKey(), "LEFT", "RIGHT");
63
126
  }
64
127
  else {
65
- raw = await this.#client.lpop(this.#pendingKey());
128
+ raw = await client.lpop(this.#pendingKey());
66
129
  if (raw)
67
- await this.#client.rpush(this.#processingKey(), raw);
130
+ await client.rpush(this.#processingKey(), raw);
68
131
  }
69
132
  if (!raw)
70
133
  return null;
@@ -74,36 +137,40 @@ export class RedisDriver {
74
137
  // Malformed payload — purge from `processing` so it can't sit
75
138
  // there indefinitely as a poison pill. recoverStale() also
76
139
  // catches survivors but pop()'s own move is the primary path.
77
- await this.#client.lrem(this.#processingKey(), 1, raw);
140
+ await client.lrem(this.#processingKey(), 1, raw);
78
141
  return null;
79
142
  }
80
- await this.#client.set(this.#leaseKey(parsed.id), raw, "PX", String(this.#visibilityTimeout));
143
+ await client.set(this.#leaseKey(parsed.id), raw, "PX", String(this.#visibilityTimeout));
81
144
  return parsed;
82
145
  }
83
146
  catch {
84
- await this.#client.lrem(this.#processingKey(), 1, raw);
147
+ await client.lrem(this.#processingKey(), 1, raw);
85
148
  return null;
86
149
  }
87
150
  }
88
151
  async complete(job) {
152
+ const client = await this.#client();
89
153
  await this.#removeFromProcessing(job);
90
- await this.#client.del(this.#leaseKey(job.id));
154
+ await client.del(this.#leaseKey(job.id));
91
155
  }
92
156
  async fail(job, error) {
157
+ const client = await this.#client();
93
158
  await this.#removeFromProcessing(job);
94
- await this.#client.del(this.#leaseKey(job.id));
159
+ await client.del(this.#leaseKey(job.id));
95
160
  job.error = error;
96
161
  job.status = "failed";
97
- await this.#client.rpush(this.#failedKey(), JSON.stringify(job));
162
+ await client.rpush(this.#failedKey(), JSON.stringify(job));
98
163
  }
99
164
  async retry(job) {
165
+ const client = await this.#client();
100
166
  await this.#removeFromProcessing(job);
101
- await this.#client.del(this.#leaseKey(job.id));
167
+ await client.del(this.#leaseKey(job.id));
102
168
  job.status = "pending";
103
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
169
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
104
170
  }
105
171
  async recoverStale() {
106
- const processing = await this.#client.lrange(this.#processingKey(), 0, -1);
172
+ const client = await this.#client();
173
+ const processing = await client.lrange(this.#processingKey(), 0, -1);
107
174
  let recovered = 0;
108
175
  for (const raw of processing) {
109
176
  let parsed;
@@ -113,18 +180,18 @@ export class RedisDriver {
113
180
  catch {
114
181
  // Malformed JSON would otherwise sit in processing forever —
115
182
  // LREM purges it so the queue makes progress.
116
- await this.#client.lrem(this.#processingKey(), 1, raw);
183
+ await client.lrem(this.#processingKey(), 1, raw);
117
184
  continue;
118
185
  }
119
186
  if (!isValidJob(parsed)) {
120
- await this.#client.lrem(this.#processingKey(), 1, raw);
187
+ await client.lrem(this.#processingKey(), 1, raw);
121
188
  continue;
122
189
  }
123
- const lease = await this.#client.get(this.#leaseKey(parsed.id));
190
+ const lease = await client.get(this.#leaseKey(parsed.id));
124
191
  if (lease === null) {
125
- await this.#client.lrem(this.#processingKey(), 1, raw);
192
+ await client.lrem(this.#processingKey(), 1, raw);
126
193
  parsed.status = "pending";
127
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(parsed));
194
+ await client.rpush(this.#pendingKey(), JSON.stringify(parsed));
128
195
  recovered++;
129
196
  }
130
197
  }
@@ -140,15 +207,16 @@ export class RedisDriver {
140
207
  * the lease has expired (e.g. recoverStale already handled it).
141
208
  */
142
209
  async #removeFromProcessing(job) {
143
- const stored = await this.#client.get(this.#leaseKey(job.id));
210
+ const client = await this.#client();
211
+ const stored = await client.get(this.#leaseKey(job.id));
144
212
  if (stored !== null) {
145
- const removed = await this.#client.lrem(this.#processingKey(), 1, stored);
213
+ const removed = await client.lrem(this.#processingKey(), 1, stored);
146
214
  if (removed > 0)
147
215
  return;
148
216
  }
149
217
  // Lease missing or already-LREM'd entry not found — best-effort scan
150
218
  // matches by job id and removes the actual stored representation.
151
- const items = await this.#client.lrange(this.#processingKey(), 0, -1);
219
+ const items = await client.lrange(this.#processingKey(), 0, -1);
152
220
  for (const item of items) {
153
221
  let parsed;
154
222
  try {
@@ -158,13 +226,14 @@ export class RedisDriver {
158
226
  continue;
159
227
  }
160
228
  if (isValidJob(parsed) && parsed.id === job.id) {
161
- await this.#client.lrem(this.#processingKey(), 1, item);
229
+ await client.lrem(this.#processingKey(), 1, item);
162
230
  return;
163
231
  }
164
232
  }
165
233
  }
166
234
  async failed() {
167
- const raws = await this.#client.lrange(this.#failedKey(), 0, -1);
235
+ const client = await this.#client();
236
+ const raws = await client.lrange(this.#failedKey(), 0, -1);
168
237
  return raws
169
238
  .map((r) => {
170
239
  try {
@@ -178,7 +247,8 @@ export class RedisDriver {
178
247
  .filter((j) => j !== null);
179
248
  }
180
249
  async size() {
181
- return this.#client.llen(this.#pendingKey());
250
+ const client = await this.#client();
251
+ return client.llen(this.#pendingKey());
182
252
  }
183
253
  }
184
254
  //# sourceMappingURL=RedisDriver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.js","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RedisDriver.js","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAqBH,SAAS,UAAU,CAAC,GAAY;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,OAAO,CACN,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;QACxB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAC9B,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;QACjC,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAC5B,CAAC;AACH,CAAC;AAYD;;;;GAIG;AACH,MAAM,MAAM,GAAG,IAAI,OAAO,EAAU,CAAC;AACrC,SAAS,gBAAgB,CAAC,MAAmB;IAC5C,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO;IACrE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CACX,0EAA0E;QACzE,sEAAsE;QACtE,0EAA0E,CAC3E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,MAAc;IACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACvC,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC;AACzD,CAAC;AAED,MAAM,OAAO,WAAW;IACvB,OAAO,CAAoB;IAC3B,SAAS,CAA0B;IACnC,QAAQ,CAAmC;IAC3C,OAAO,CAAS;IAChB,kBAAkB,CAAS;IAE3B;;;OAGG;IACH,KAAK,CAAC,OAAO;QACZ,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1C,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC;YAC9B,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;YAC9B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;iBACzC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBAChB,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;gBACxB,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,OAAO,MAAM,CAAC;YACf,CAAC,CAAC;gBACF,4DAA4D;gBAC5D,8DAA8D;gBAC9D,+DAA+D;gBAC/D,8DAA8D;iBAC7D,OAAO,CAAC,GAAG,EAAE;gBACb,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,YACC,MAAyB,EACzB,OAA2D;QAE3D,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,uEAAuE;QACvE,sEAAsE;QACtE,2DAA2D;QAC3D,IAAI,OAAO,MAAM,KAAK,UAAU;YAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC3D,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,CAAC,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,CAAC;QAC1D,MAAM,iBAAiB,GAAG,OAAO,EAAE,mBAAmB,IAAI,MAAM,CAAC;QACjE,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,gBAAgB;QAChB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,IAAI,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CACd,8EAA8E,iBAAiB,EAAE,CACjG,CAAC;QACH,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,iBAAiB,CAAC;IAC7C,CAAC;IAED,WAAW,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,SAAS,CAAC;IAC7C,cAAc,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,YAAY,CAAC;IACnD,UAAU,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,QAAQ,CAAC;IAC3C,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,SAAS,KAAK,EAAE,CAAC;IAE/D,KAAK,CAAC,IAAI,CAAC,GAAQ;QAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,GAAG;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,GAAG,GAAkB,IAAI,CAAC;QAE9B,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CACvB,IAAI,CAAC,WAAW,EAAE,EAClB,IAAI,CAAC,cAAc,EAAE,EACrB,MAAM,EACN,OAAO,CACP,CAAC;QACH,CAAC;aAAM,CAAC;YACP,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAC5C,IAAI,GAAG;gBAAE,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,8DAA8D;gBAC9D,2DAA2D;gBAC3D,8DAA8D;gBAC9D,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACjD,OAAO,IAAI,CAAC;YACb,CAAC;YACD,MAAM,MAAM,CAAC,GAAG,CACf,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EACzB,GAAG,EACH,IAAI,EACJ,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAC/B,CAAC;YACF,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAQ;QACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAQ,EAAE,KAAa;QACjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACzC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;QAClB,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC;QACtB,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAQ;QACnB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACzC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;QACvB,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,YAAY;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrE,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACR,6DAA6D;gBAC7D,8CAA8C;gBAC9C,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACjD,SAAS;YACV,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACjD,SAAS;YACV,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACjD,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC;gBAC1B,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC/D,SAAS,EAAE,CAAC;YACb,CAAC;QACF,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,qBAAqB,CAAC,GAAQ;QACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACxD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;YACpE,IAAI,OAAO,GAAG,CAAC;gBAAE,OAAO;QACzB,CAAC;QACD,qEAAqE;QACrE,kEAAkE;QAClE,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACR,SAAS;YACV,CAAC;YACD,IAAI,UAAU,CAAC,MAAM,CAAC,IAAK,MAAyB,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE,CAAC;gBACpE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;gBAClD,OAAO;YACR,CAAC;QACF,CAAC;IACF,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO,IAAI;aACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACV,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3C,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAY,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,IAAI;QACT,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACxC,CAAC;CACD"}
package/dist/index.d.ts CHANGED
@@ -17,4 +17,5 @@ import type { BayProviderConfig } from "./BayProvider.js";
17
17
  * parity. Identity at runtime; the generic preserves literal types for inference.
18
18
  */
19
19
  export declare function defineConfig<T extends BayProviderConfig>(config: T): T;
20
+ export { quasarConnection } from "./quasar.js";
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEtE;AACD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -15,4 +15,5 @@ export { QueueManager } from "./QueueManager.js";
15
15
  export function defineConfig(config) {
16
16
  return config;
17
17
  }
18
+ export { quasarConnection } from "./quasar.js";
18
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAA8B,MAAS;IAClE,OAAO,MAAM,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAA8B,MAAS;IAClE,OAAO,MAAM,CAAC;AACf,CAAC;AACD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolving a Redis connection by name, from `@c9up/quasar`.
3
+ *
4
+ * Bay does not depend on quasar: it is an optional peer, and this module
5
+ * never imports it statically. The specifier is built at runtime so the
6
+ * TypeScript build stays free of it too — a hard type import would make echo
7
+ * unbuildable for anyone who queues in memory.
8
+ *
9
+ * The shape is checked before use rather than asserted, the same way bay
10
+ * duck-types its host framework.
11
+ */
12
+ import type { RedisClient } from "./drivers/RedisDriver.js";
13
+ /**
14
+ * A resolver for `new RedisDriver(quasarConnection("jobs"))` — quasar is
15
+ * loaded on the first queue command, not at config time.
16
+ */
17
+ export declare function quasarConnection(name?: string): () => Promise<RedisClient>;
18
+ //# sourceMappingURL=quasar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quasar.d.ts","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAoC5D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CA+B1E"}
package/dist/quasar.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Resolving a Redis connection by name, from `@c9up/quasar`.
3
+ *
4
+ * Bay does not depend on quasar: it is an optional peer, and this module
5
+ * never imports it statically. The specifier is built at runtime so the
6
+ * TypeScript build stays free of it too — a hard type import would make echo
7
+ * unbuildable for anyone who queues in memory.
8
+ *
9
+ * The shape is checked before use rather than asserted, the same way bay
10
+ * duck-types its host framework.
11
+ */
12
+ function isConnectionSource(value) {
13
+ return (typeof value === "object" &&
14
+ value !== null &&
15
+ typeof Reflect.get(value, "connection") === "function");
16
+ }
17
+ function isRedisClient(value) {
18
+ if (typeof value !== "object" || value === null)
19
+ return false;
20
+ // The commands this driver actually issues — every non-optional member of
21
+ // `RedisClient`. A connection missing one would fail on the first job push,
22
+ // far from the cause. `lmove` is deliberately absent: the driver declares it
23
+ // optional and falls back when it is not there.
24
+ const required = [
25
+ "rpush",
26
+ "lpop",
27
+ "lrem",
28
+ "llen",
29
+ "lrange",
30
+ "del",
31
+ "set",
32
+ "get",
33
+ ];
34
+ return required.every((name) => typeof Reflect.get(value, name) === "function");
35
+ }
36
+ /**
37
+ * A resolver for `new RedisDriver(quasarConnection("jobs"))` — quasar is
38
+ * loaded on the first queue command, not at config time.
39
+ */
40
+ export function quasarConnection(name) {
41
+ return async () => {
42
+ const specifier = "@c9up/quasar/services/main";
43
+ let loaded;
44
+ try {
45
+ loaded = await import(/* @vite-ignore */ specifier);
46
+ }
47
+ catch (cause) {
48
+ throw new Error(`Bay: the "${name ?? "default"}" queue asks for a quasar connection, but @c9up/quasar is not installed.\n` +
49
+ " pnpm add @c9up/quasar", { cause });
50
+ }
51
+ const manager = isConnectionSource(loaded)
52
+ ? loaded
53
+ : Reflect.get(Object(loaded), "default");
54
+ if (!isConnectionSource(manager)) {
55
+ throw new Error("Bay: @c9up/quasar/services/main did not expose a connection() manager");
56
+ }
57
+ const connection = manager.connection(name);
58
+ if (!isRedisClient(connection)) {
59
+ throw new Error(`Bay: quasar connection "${name ?? "default"}" does not carry the commands this queue needs`);
60
+ }
61
+ return connection;
62
+ };
63
+ }
64
+ //# sourceMappingURL=quasar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quasar.js","sourceRoot":"","sources":["../src/quasar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,SAAS,kBAAkB,CAAC,KAAc;IACzC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,UAAU,CACtD,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,gDAAgD;IAChD,MAAM,QAAQ,GAAG;QAChB,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM;QACN,QAAQ;QACR,KAAK;QACL,KAAK;QACL,KAAK;KACL,CAAC;IACF,OAAO,QAAQ,CAAC,KAAK,CACpB,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,UAAU,CACxD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC7C,OAAO,KAAK,IAAI,EAAE;QACjB,MAAM,SAAS,GAAG,4BAA4B,CAAC;QAC/C,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CACd,aAAa,IAAI,IAAI,SAAS,4EAA4E;gBACzG,yBAAyB,EAC1B,EAAE,KAAK,EAAE,CACT,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC;YACzC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACd,uEAAuE,CACvE,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACd,2BAA2B,IAAI,IAAI,SAAS,gDAAgD,CAC5F,CAAC;QACH,CAAC;QACD,OAAO,UAAU,CAAC;IACnB,CAAC,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,YAAY,GAAG,SAAS,CAEnD;AAED,QAAA,MAAM,KAAK,EAAE,YAWX,CAAC;AAEH,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,YAAY,GAAG,SAAS,CAEnD;AAED,QAAA,MAAM,KAAK,EAAE,YAoBX,CAAC;AAEH,eAAe,KAAK,CAAC"}
@@ -23,6 +23,15 @@ export function getQueue() {
23
23
  }
24
24
  const queue = new Proxy({}, {
25
25
  get(_target, prop) {
26
+ // A module loader inspects what it imports before anyone uses it: it reads
27
+ // `then` to decide whether the namespace is thenable, and various symbols
28
+ // for interop and formatting. Throwing on those turns a plain
29
+ // `import { setX } from ".../services/main"` into a crash at import time,
30
+ // far from any real use. They are not members of what this stands in for,
31
+ // so answer undefined and let a genuine access be the one that reports.
32
+ if (typeof prop === "symbol" || prop === "then") {
33
+ return undefined;
34
+ }
26
35
  if (!instance) {
27
36
  throw new Error("[bay] QueueManager singleton accessed before BayProvider.boot() ran " +
28
37
  "or `setQueue(myQueue)` was called. Wire one of them first.");
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAAkC,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,KAAmB;IAC3C,QAAQ,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ;IACvB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,KAAK,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IACzD,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,sEAAsE;gBACrE,4DAA4D,CAC7D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAAkC,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,KAAmB;IAC3C,QAAQ,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ;IACvB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,KAAK,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IACzD,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,2EAA2E;QAC3E,0EAA0E;QAC1E,8DAA8D;QAC9D,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,sEAAsE;gBACrE,4DAA4D,CAC7D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/bay",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Queue — pluggable job-queue contract with memory + Redis drivers for the Ream framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,10 +25,12 @@
25
25
  }
26
26
  },
27
27
  "peerDependencies": {
28
- "@c9up/ream": "^0.1.0"
28
+ "@c9up/ream": "^0.1.0",
29
+ "@c9up/quasar": "^0.1.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@biomejs/biome": "^2.4.10",
33
+ "@c9up/quasar": "^0.1.0",
32
34
  "@types/node": "^22.19.15",
33
35
  "typescript": "^6.0.2",
34
36
  "vitest": "^4.1.2"
@@ -48,6 +50,9 @@
48
50
  "peerDependenciesMeta": {
49
51
  "@c9up/ream": {
50
52
  "optional": true
53
+ },
54
+ "@c9up/quasar": {
55
+ "optional": true
51
56
  }
52
57
  },
53
58
  "repository": {
@@ -49,17 +49,94 @@ function isValidJob(obj: unknown): obj is Job {
49
49
  );
50
50
  }
51
51
 
52
+ /**
53
+ * Where the client comes from. A resolver is what lets a queue name its
54
+ * connection (`quasarConnection("jobs")`) instead of being handed a client:
55
+ * the driver is built synchronously, the first command that needs the client
56
+ * is not.
57
+ */
58
+ export type RedisClientSource =
59
+ | RedisClient
60
+ | (() => RedisClient | Promise<RedisClient>);
61
+
62
+ /**
63
+ * LMOVE (Redis 6.2+) is what makes pop() atomic. Warned once, when the client
64
+ * is resolved rather than at construction — a driver that names its connection
65
+ * has no client to inspect yet.
66
+ */
67
+ const warned = new WeakSet<object>();
68
+ function warnWithoutLmove(client: RedisClient): void {
69
+ if (typeof client.lmove === "function" || warned.has(client)) return;
70
+ warned.add(client);
71
+ console.warn(
72
+ "[bay] RedisDriver: client lacks LMOVE (Redis <6.2). pop() falls back to " +
73
+ "a non-atomic lpop+rpush, downgrading delivery from at-least-once to " +
74
+ "at-most-once — a crash between the two commands loses the in-flight job.",
75
+ );
76
+ }
77
+
78
+ /**
79
+ * A key prefix that ends in a separator.
80
+ *
81
+ * `:` is Redis's conventional namespace separator; a prefix already ending in
82
+ * one of the usual separators is left alone.
83
+ */
84
+ function withSeparator(prefix: string): string {
85
+ if (prefix.length === 0) return prefix;
86
+ return /[:.\-_/]$/.test(prefix) ? prefix : `${prefix}:`;
87
+ }
88
+
52
89
  export class RedisDriver implements QueueDriver {
53
- #client: RedisClient;
90
+ #source: RedisClientSource;
91
+ #resolved: RedisClient | undefined;
92
+ #pending: Promise<RedisClient> | undefined;
54
93
  #prefix: string;
55
94
  #visibilityTimeout: number;
56
95
 
96
+ /**
97
+ * The client, resolved once. Two workers racing on a cold queue must not
98
+ * each open their own connection, so the in-flight promise is shared.
99
+ */
100
+ async #client(): Promise<RedisClient> {
101
+ if (this.#resolved) return this.#resolved;
102
+ if (typeof this.#source !== "function") {
103
+ this.#resolved = this.#source;
104
+ warnWithoutLmove(this.#resolved);
105
+ return this.#resolved;
106
+ }
107
+ if (!this.#pending) {
108
+ const resolver = this.#source;
109
+ this.#pending = Promise.resolve(resolver())
110
+ .then((client) => {
111
+ this.#resolved = client;
112
+ warnWithoutLmove(client);
113
+ return client;
114
+ })
115
+ // Cleared on failure too. Clearing only on success left the
116
+ // REJECTED promise cached forever, so one transient outage at
117
+ // startup broke every later call for the life of the process —
118
+ // a permanent failure with no error of its own to explain it.
119
+ .finally(() => {
120
+ this.#pending = undefined;
121
+ });
122
+ }
123
+ return this.#pending;
124
+ }
125
+
57
126
  constructor(
58
- client: RedisClient,
127
+ source: RedisClientSource,
59
128
  options?: { prefix?: string; visibilityTimeoutMs?: number },
60
129
  ) {
61
- this.#client = client;
62
- this.#prefix = options?.prefix ?? "queue:";
130
+ this.#source = source;
131
+ // A client handed in directly can be checked now, so the warning keeps
132
+ // landing at construction as it always did. A named connection has no
133
+ // client yet — it is checked when the connection resolves.
134
+ if (typeof source !== "function") warnWithoutLmove(source);
135
+ // Normalised rather than documented: every key is built by concatenation
136
+ // (`${prefix}pending`), so a prefix without a trailing separator yields
137
+ // "myapppending" — unreadable, and able to collide with a neighbouring
138
+ // prefix. Nothing warned, because nothing failed.
139
+ this.#prefix = withSeparator(options?.prefix ?? "queue:");
63
140
  const visibilityTimeout = options?.visibilityTimeoutMs ?? 30_000;
64
141
  // A non-positive / non-integer timeout makes pop()'s `SET … PX <ms>` fail
65
142
  // on a real Redis; the catch then removes the job from `processing` and
@@ -71,13 +148,6 @@ export class RedisDriver implements QueueDriver {
71
148
  );
72
149
  }
73
150
  this.#visibilityTimeout = visibilityTimeout;
74
- if (typeof client.lmove !== "function") {
75
- console.warn(
76
- "[bay] RedisDriver: client lacks LMOVE (Redis <6.2). pop() falls back to " +
77
- "a non-atomic lpop+rpush, downgrading delivery from at-least-once to " +
78
- "at-most-once — a crash between the two commands loses the in-flight job.",
79
- );
80
- }
81
151
  }
82
152
 
83
153
  #pendingKey = () => `${this.#prefix}pending`;
@@ -86,22 +156,24 @@ export class RedisDriver implements QueueDriver {
86
156
  #leaseKey = (jobId: string) => `${this.#prefix}lease:${jobId}`;
87
157
 
88
158
  async push(job: Job): Promise<void> {
89
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
159
+ const client = await this.#client();
160
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
90
161
  }
91
162
 
92
163
  async pop(): Promise<Job | null> {
164
+ const client = await this.#client();
93
165
  let raw: string | null = null;
94
166
 
95
- if (this.#client.lmove) {
96
- raw = await this.#client.lmove(
167
+ if (client.lmove) {
168
+ raw = await client.lmove(
97
169
  this.#pendingKey(),
98
170
  this.#processingKey(),
99
171
  "LEFT",
100
172
  "RIGHT",
101
173
  );
102
174
  } else {
103
- raw = await this.#client.lpop(this.#pendingKey());
104
- if (raw) await this.#client.rpush(this.#processingKey(), raw);
175
+ raw = await client.lpop(this.#pendingKey());
176
+ if (raw) await client.rpush(this.#processingKey(), raw);
105
177
  }
106
178
 
107
179
  if (!raw) return null;
@@ -111,10 +183,10 @@ export class RedisDriver implements QueueDriver {
111
183
  // Malformed payload — purge from `processing` so it can't sit
112
184
  // there indefinitely as a poison pill. recoverStale() also
113
185
  // catches survivors but pop()'s own move is the primary path.
114
- await this.#client.lrem(this.#processingKey(), 1, raw);
186
+ await client.lrem(this.#processingKey(), 1, raw);
115
187
  return null;
116
188
  }
117
- await this.#client.set(
189
+ await client.set(
118
190
  this.#leaseKey(parsed.id),
119
191
  raw,
120
192
  "PX",
@@ -122,33 +194,37 @@ export class RedisDriver implements QueueDriver {
122
194
  );
123
195
  return parsed;
124
196
  } catch {
125
- await this.#client.lrem(this.#processingKey(), 1, raw);
197
+ await client.lrem(this.#processingKey(), 1, raw);
126
198
  return null;
127
199
  }
128
200
  }
129
201
 
130
202
  async complete(job: Job): Promise<void> {
203
+ const client = await this.#client();
131
204
  await this.#removeFromProcessing(job);
132
- await this.#client.del(this.#leaseKey(job.id));
205
+ await client.del(this.#leaseKey(job.id));
133
206
  }
134
207
 
135
208
  async fail(job: Job, error: string): Promise<void> {
209
+ const client = await this.#client();
136
210
  await this.#removeFromProcessing(job);
137
- await this.#client.del(this.#leaseKey(job.id));
211
+ await client.del(this.#leaseKey(job.id));
138
212
  job.error = error;
139
213
  job.status = "failed";
140
- await this.#client.rpush(this.#failedKey(), JSON.stringify(job));
214
+ await client.rpush(this.#failedKey(), JSON.stringify(job));
141
215
  }
142
216
 
143
217
  async retry(job: Job): Promise<void> {
218
+ const client = await this.#client();
144
219
  await this.#removeFromProcessing(job);
145
- await this.#client.del(this.#leaseKey(job.id));
220
+ await client.del(this.#leaseKey(job.id));
146
221
  job.status = "pending";
147
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
222
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
148
223
  }
149
224
 
150
225
  async recoverStale(): Promise<number> {
151
- const processing = await this.#client.lrange(this.#processingKey(), 0, -1);
226
+ const client = await this.#client();
227
+ const processing = await client.lrange(this.#processingKey(), 0, -1);
152
228
  let recovered = 0;
153
229
  for (const raw of processing) {
154
230
  let parsed: unknown;
@@ -157,18 +233,18 @@ export class RedisDriver implements QueueDriver {
157
233
  } catch {
158
234
  // Malformed JSON would otherwise sit in processing forever —
159
235
  // LREM purges it so the queue makes progress.
160
- await this.#client.lrem(this.#processingKey(), 1, raw);
236
+ await client.lrem(this.#processingKey(), 1, raw);
161
237
  continue;
162
238
  }
163
239
  if (!isValidJob(parsed)) {
164
- await this.#client.lrem(this.#processingKey(), 1, raw);
240
+ await client.lrem(this.#processingKey(), 1, raw);
165
241
  continue;
166
242
  }
167
- const lease = await this.#client.get(this.#leaseKey(parsed.id));
243
+ const lease = await client.get(this.#leaseKey(parsed.id));
168
244
  if (lease === null) {
169
- await this.#client.lrem(this.#processingKey(), 1, raw);
245
+ await client.lrem(this.#processingKey(), 1, raw);
170
246
  parsed.status = "pending";
171
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(parsed));
247
+ await client.rpush(this.#pendingKey(), JSON.stringify(parsed));
172
248
  recovered++;
173
249
  }
174
250
  }
@@ -185,14 +261,15 @@ export class RedisDriver implements QueueDriver {
185
261
  * the lease has expired (e.g. recoverStale already handled it).
186
262
  */
187
263
  async #removeFromProcessing(job: Job): Promise<void> {
188
- const stored = await this.#client.get(this.#leaseKey(job.id));
264
+ const client = await this.#client();
265
+ const stored = await client.get(this.#leaseKey(job.id));
189
266
  if (stored !== null) {
190
- const removed = await this.#client.lrem(this.#processingKey(), 1, stored);
267
+ const removed = await client.lrem(this.#processingKey(), 1, stored);
191
268
  if (removed > 0) return;
192
269
  }
193
270
  // Lease missing or already-LREM'd entry not found — best-effort scan
194
271
  // matches by job id and removes the actual stored representation.
195
- const items = await this.#client.lrange(this.#processingKey(), 0, -1);
272
+ const items = await client.lrange(this.#processingKey(), 0, -1);
196
273
  for (const item of items) {
197
274
  let parsed: unknown;
198
275
  try {
@@ -201,14 +278,15 @@ export class RedisDriver implements QueueDriver {
201
278
  continue;
202
279
  }
203
280
  if (isValidJob(parsed) && (parsed as { id: string }).id === job.id) {
204
- await this.#client.lrem(this.#processingKey(), 1, item);
281
+ await client.lrem(this.#processingKey(), 1, item);
205
282
  return;
206
283
  }
207
284
  }
208
285
  }
209
286
 
210
287
  async failed(): Promise<Job[]> {
211
- const raws = await this.#client.lrange(this.#failedKey(), 0, -1);
288
+ const client = await this.#client();
289
+ const raws = await client.lrange(this.#failedKey(), 0, -1);
212
290
  return raws
213
291
  .map((r) => {
214
292
  try {
@@ -222,6 +300,7 @@ export class RedisDriver implements QueueDriver {
222
300
  }
223
301
 
224
302
  async size(): Promise<number> {
225
- return this.#client.llen(this.#pendingKey());
303
+ const client = await this.#client();
304
+ return client.llen(this.#pendingKey());
226
305
  }
227
306
  }
package/src/index.ts CHANGED
@@ -22,3 +22,4 @@ import type { BayProviderConfig } from "./BayProvider.js";
22
22
  export function defineConfig<T extends BayProviderConfig>(config: T): T {
23
23
  return config;
24
24
  }
25
+ export { quasarConnection } from "./quasar.js";
package/src/quasar.ts ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Resolving a Redis connection by name, from `@c9up/quasar`.
3
+ *
4
+ * Bay does not depend on quasar: it is an optional peer, and this module
5
+ * never imports it statically. The specifier is built at runtime so the
6
+ * TypeScript build stays free of it too — a hard type import would make echo
7
+ * unbuildable for anyone who queues in memory.
8
+ *
9
+ * The shape is checked before use rather than asserted, the same way bay
10
+ * duck-types its host framework.
11
+ */
12
+
13
+ import type { RedisClient } from "./drivers/RedisDriver.js";
14
+
15
+ /** The slice of quasar's manager this needs: a connection, by name. */
16
+ interface ConnectionSource {
17
+ connection(name?: string): unknown;
18
+ }
19
+
20
+ function isConnectionSource(value: unknown): value is ConnectionSource {
21
+ return (
22
+ typeof value === "object" &&
23
+ value !== null &&
24
+ typeof Reflect.get(value, "connection") === "function"
25
+ );
26
+ }
27
+
28
+ function isRedisClient(value: unknown): value is RedisClient {
29
+ if (typeof value !== "object" || value === null) return false;
30
+ // The commands this driver actually issues — every non-optional member of
31
+ // `RedisClient`. A connection missing one would fail on the first job push,
32
+ // far from the cause. `lmove` is deliberately absent: the driver declares it
33
+ // optional and falls back when it is not there.
34
+ const required = [
35
+ "rpush",
36
+ "lpop",
37
+ "lrem",
38
+ "llen",
39
+ "lrange",
40
+ "del",
41
+ "set",
42
+ "get",
43
+ ];
44
+ return required.every(
45
+ (name) => typeof Reflect.get(value, name) === "function",
46
+ );
47
+ }
48
+
49
+ /**
50
+ * A resolver for `new RedisDriver(quasarConnection("jobs"))` — quasar is
51
+ * loaded on the first queue command, not at config time.
52
+ */
53
+ export function quasarConnection(name?: string): () => Promise<RedisClient> {
54
+ return async () => {
55
+ const specifier = "@c9up/quasar/services/main";
56
+ let loaded: unknown;
57
+ try {
58
+ loaded = await import(/* @vite-ignore */ specifier);
59
+ } catch (cause) {
60
+ throw new Error(
61
+ `Bay: the "${name ?? "default"}" queue asks for a quasar connection, but @c9up/quasar is not installed.\n` +
62
+ " pnpm add @c9up/quasar",
63
+ { cause },
64
+ );
65
+ }
66
+
67
+ const manager = isConnectionSource(loaded)
68
+ ? loaded
69
+ : Reflect.get(Object(loaded), "default");
70
+ if (!isConnectionSource(manager)) {
71
+ throw new Error(
72
+ "Bay: @c9up/quasar/services/main did not expose a connection() manager",
73
+ );
74
+ }
75
+
76
+ const connection = manager.connection(name);
77
+ if (!isRedisClient(connection)) {
78
+ throw new Error(
79
+ `Bay: quasar connection "${name ?? "default"}" does not carry the commands this queue needs`,
80
+ );
81
+ }
82
+ return connection;
83
+ };
84
+ }
@@ -29,6 +29,15 @@ export function getQueue(): QueueManager | undefined {
29
29
 
30
30
  const queue: QueueManager = new Proxy({} as QueueManager, {
31
31
  get(_target, prop) {
32
+ // A module loader inspects what it imports before anyone uses it: it reads
33
+ // `then` to decide whether the namespace is thenable, and various symbols
34
+ // for interop and formatting. Throwing on those turns a plain
35
+ // `import { setX } from ".../services/main"` into a crash at import time,
36
+ // far from any real use. They are not members of what this stands in for,
37
+ // so answer undefined and let a genuine access be the one that reports.
38
+ if (typeof prop === "symbol" || prop === "then") {
39
+ return undefined;
40
+ }
32
41
  if (!instance) {
33
42
  throw new Error(
34
43
  "[bay] QueueManager singleton accessed before BayProvider.boot() ran " +