@c9up/bay 0.1.7 → 0.1.9

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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
  interface BayContainer {
8
8
  singleton(token: unknown, factory: () => unknown): void;
9
- resolve<T = unknown>(token: unknown): T;
9
+ resolve<T = unknown>(token: unknown): Promise<T>;
10
10
  }
11
11
  interface BayConfigStore {
12
12
  get<T = unknown>(key: string): T | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"BayProvider.d.ts","sourceRoot":"","sources":["../src/BayProvider.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,UAAU,YAAY;IACrB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC;CACxC;AACD,UAAU,cAAc;IACvB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AACD,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,YAAY,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,OAAO,OAAO,WAAW;IACnB,SAAS,CAAC,GAAG,EAAE,aAAa;gBAAlB,GAAG,EAAE,aAAa;IAExC,QAAQ,IAAI,IAAI;IAiBV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
1
+ {"version":3,"file":"BayProvider.d.ts","sourceRoot":"","sources":["../src/BayProvider.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,UAAU,YAAY;IACrB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjD;AACD,UAAU,cAAc;IACvB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AACD,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,YAAY,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,OAAO,OAAO,WAAW;IACnB,SAAS,CAAC,GAAG,EAAE,aAAa;gBAAlB,GAAG,EAAE,aAAa;IAExC,QAAQ,IAAI,IAAI;IAiBV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
@@ -43,7 +43,7 @@ export default class BayProvider {
43
43
  async boot() {
44
44
  // Populate the `@c9up/bay/services/main` singleton so apps can
45
45
  // `import queue from '@c9up/bay/services/main'` from anywhere.
46
- setQueue(this.app.container.resolve(QueueManager));
46
+ setQueue(await this.app.container.resolve(QueueManager));
47
47
  }
48
48
  async shutdown() { }
49
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BayProvider.js","sourceRoot":"","sources":["../src/BayProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAkC9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,OAAO,OAAO,WAAW;IACT;IAAtB,YAAsB,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;IAAG,CAAC;IAE5C,QAAQ;QACP,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,YAAY,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAoB,OAAO,CAAC,CAAC;YAC/D,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC;YAC9C,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CACd,6BAA6B,UAAU,2BAA2B;oBACjE,sEAAsE,CACvE,CAAC;YACH,CAAC;YACD,OAAO,IAAI,YAAY,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAC1C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CACtD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACT,+DAA+D;QAC/D,+DAA+D;QAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,QAAQ,KAAmB,CAAC;CAClC"}
1
+ {"version":3,"file":"BayProvider.js","sourceRoot":"","sources":["../src/BayProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAkC9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,OAAO,OAAO,WAAW;IACT;IAAtB,YAAsB,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;IAAG,CAAC;IAE5C,QAAQ;QACP,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,YAAY,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAoB,OAAO,CAAC,CAAC;YAC/D,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC;YAC9C,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CACd,6BAA6B,UAAU,2BAA2B;oBACjE,sEAAsE,CACvE,CAAC;YACH,CAAC;YACD,OAAO,IAAI,YAAY,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAC1C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CACtD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACT,+DAA+D;QAC/D,+DAA+D;QAC/D,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,QAAQ,KAAmB,CAAC;CAClC"}
@@ -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;AAkB9C,qBAAa,WAAY,YAAW,WAAW;;gBA+B7C,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE;IA0BtD,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,12 +27,56 @@ 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
+ }
30
44
  export class RedisDriver {
31
- #client;
45
+ #source;
46
+ #resolved;
47
+ #pending;
32
48
  #prefix;
33
49
  #visibilityTimeout;
34
- constructor(client, options) {
35
- this.#client = client;
50
+ /**
51
+ * The client, resolved once. Two workers racing on a cold queue must not
52
+ * each open their own connection, so the in-flight promise is shared.
53
+ */
54
+ async #client() {
55
+ if (this.#resolved)
56
+ return this.#resolved;
57
+ if (typeof this.#source !== "function") {
58
+ this.#resolved = this.#source;
59
+ warnWithoutLmove(this.#resolved);
60
+ return this.#resolved;
61
+ }
62
+ if (!this.#pending) {
63
+ const resolver = this.#source;
64
+ this.#pending = Promise.resolve(resolver()).then((client) => {
65
+ this.#resolved = client;
66
+ this.#pending = undefined;
67
+ warnWithoutLmove(client);
68
+ return client;
69
+ });
70
+ }
71
+ return this.#pending;
72
+ }
73
+ constructor(source, options) {
74
+ this.#source = source;
75
+ // A client handed in directly can be checked now, so the warning keeps
76
+ // landing at construction as it always did. A named connection has no
77
+ // client yet — it is checked when the connection resolves.
78
+ if (typeof source !== "function")
79
+ warnWithoutLmove(source);
36
80
  this.#prefix = options?.prefix ?? "queue:";
37
81
  const visibilityTimeout = options?.visibilityTimeoutMs ?? 30_000;
38
82
  // A non-positive / non-integer timeout makes pop()'s `SET … PX <ms>` fail
@@ -43,28 +87,25 @@ export class RedisDriver {
43
87
  throw new Error(`[bay] RedisDriver visibilityTimeoutMs must be a positive integer (ms), got ${visibilityTimeout}`);
44
88
  }
45
89
  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
90
  }
52
91
  #pendingKey = () => `${this.#prefix}pending`;
53
92
  #processingKey = () => `${this.#prefix}processing`;
54
93
  #failedKey = () => `${this.#prefix}failed`;
55
94
  #leaseKey = (jobId) => `${this.#prefix}lease:${jobId}`;
56
95
  async push(job) {
57
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
96
+ const client = await this.#client();
97
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
58
98
  }
59
99
  async pop() {
100
+ const client = await this.#client();
60
101
  let raw = null;
61
- if (this.#client.lmove) {
62
- raw = await this.#client.lmove(this.#pendingKey(), this.#processingKey(), "LEFT", "RIGHT");
102
+ if (client.lmove) {
103
+ raw = await client.lmove(this.#pendingKey(), this.#processingKey(), "LEFT", "RIGHT");
63
104
  }
64
105
  else {
65
- raw = await this.#client.lpop(this.#pendingKey());
106
+ raw = await client.lpop(this.#pendingKey());
66
107
  if (raw)
67
- await this.#client.rpush(this.#processingKey(), raw);
108
+ await client.rpush(this.#processingKey(), raw);
68
109
  }
69
110
  if (!raw)
70
111
  return null;
@@ -74,36 +115,40 @@ export class RedisDriver {
74
115
  // Malformed payload — purge from `processing` so it can't sit
75
116
  // there indefinitely as a poison pill. recoverStale() also
76
117
  // catches survivors but pop()'s own move is the primary path.
77
- await this.#client.lrem(this.#processingKey(), 1, raw);
118
+ await client.lrem(this.#processingKey(), 1, raw);
78
119
  return null;
79
120
  }
80
- await this.#client.set(this.#leaseKey(parsed.id), raw, "PX", String(this.#visibilityTimeout));
121
+ await client.set(this.#leaseKey(parsed.id), raw, "PX", String(this.#visibilityTimeout));
81
122
  return parsed;
82
123
  }
83
124
  catch {
84
- await this.#client.lrem(this.#processingKey(), 1, raw);
125
+ await client.lrem(this.#processingKey(), 1, raw);
85
126
  return null;
86
127
  }
87
128
  }
88
129
  async complete(job) {
130
+ const client = await this.#client();
89
131
  await this.#removeFromProcessing(job);
90
- await this.#client.del(this.#leaseKey(job.id));
132
+ await client.del(this.#leaseKey(job.id));
91
133
  }
92
134
  async fail(job, error) {
135
+ const client = await this.#client();
93
136
  await this.#removeFromProcessing(job);
94
- await this.#client.del(this.#leaseKey(job.id));
137
+ await client.del(this.#leaseKey(job.id));
95
138
  job.error = error;
96
139
  job.status = "failed";
97
- await this.#client.rpush(this.#failedKey(), JSON.stringify(job));
140
+ await client.rpush(this.#failedKey(), JSON.stringify(job));
98
141
  }
99
142
  async retry(job) {
143
+ const client = await this.#client();
100
144
  await this.#removeFromProcessing(job);
101
- await this.#client.del(this.#leaseKey(job.id));
145
+ await client.del(this.#leaseKey(job.id));
102
146
  job.status = "pending";
103
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
147
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
104
148
  }
105
149
  async recoverStale() {
106
- const processing = await this.#client.lrange(this.#processingKey(), 0, -1);
150
+ const client = await this.#client();
151
+ const processing = await client.lrange(this.#processingKey(), 0, -1);
107
152
  let recovered = 0;
108
153
  for (const raw of processing) {
109
154
  let parsed;
@@ -113,18 +158,18 @@ export class RedisDriver {
113
158
  catch {
114
159
  // Malformed JSON would otherwise sit in processing forever —
115
160
  // LREM purges it so the queue makes progress.
116
- await this.#client.lrem(this.#processingKey(), 1, raw);
161
+ await client.lrem(this.#processingKey(), 1, raw);
117
162
  continue;
118
163
  }
119
164
  if (!isValidJob(parsed)) {
120
- await this.#client.lrem(this.#processingKey(), 1, raw);
165
+ await client.lrem(this.#processingKey(), 1, raw);
121
166
  continue;
122
167
  }
123
- const lease = await this.#client.get(this.#leaseKey(parsed.id));
168
+ const lease = await client.get(this.#leaseKey(parsed.id));
124
169
  if (lease === null) {
125
- await this.#client.lrem(this.#processingKey(), 1, raw);
170
+ await client.lrem(this.#processingKey(), 1, raw);
126
171
  parsed.status = "pending";
127
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(parsed));
172
+ await client.rpush(this.#pendingKey(), JSON.stringify(parsed));
128
173
  recovered++;
129
174
  }
130
175
  }
@@ -140,15 +185,16 @@ export class RedisDriver {
140
185
  * the lease has expired (e.g. recoverStale already handled it).
141
186
  */
142
187
  async #removeFromProcessing(job) {
143
- const stored = await this.#client.get(this.#leaseKey(job.id));
188
+ const client = await this.#client();
189
+ const stored = await client.get(this.#leaseKey(job.id));
144
190
  if (stored !== null) {
145
- const removed = await this.#client.lrem(this.#processingKey(), 1, stored);
191
+ const removed = await client.lrem(this.#processingKey(), 1, stored);
146
192
  if (removed > 0)
147
193
  return;
148
194
  }
149
195
  // Lease missing or already-LREM'd entry not found — best-effort scan
150
196
  // matches by job id and removes the actual stored representation.
151
- const items = await this.#client.lrange(this.#processingKey(), 0, -1);
197
+ const items = await client.lrange(this.#processingKey(), 0, -1);
152
198
  for (const item of items) {
153
199
  let parsed;
154
200
  try {
@@ -158,13 +204,14 @@ export class RedisDriver {
158
204
  continue;
159
205
  }
160
206
  if (isValidJob(parsed) && parsed.id === job.id) {
161
- await this.#client.lrem(this.#processingKey(), 1, item);
207
+ await client.lrem(this.#processingKey(), 1, item);
162
208
  return;
163
209
  }
164
210
  }
165
211
  }
166
212
  async failed() {
167
- const raws = await this.#client.lrange(this.#failedKey(), 0, -1);
213
+ const client = await this.#client();
214
+ const raws = await client.lrange(this.#failedKey(), 0, -1);
168
215
  return raws
169
216
  .map((r) => {
170
217
  try {
@@ -178,7 +225,8 @@ export class RedisDriver {
178
225
  .filter((j) => j !== null);
179
226
  }
180
227
  async size() {
181
- return this.#client.llen(this.#pendingKey());
228
+ const client = await this.#client();
229
+ return client.llen(this.#pendingKey());
182
230
  }
183
231
  }
184
232
  //# sourceMappingURL=RedisDriver.js.map
@@ -1 +1 @@
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;AAED,MAAM,OAAO,WAAW;IACvB,OAAO,CAAc;IACrB,OAAO,CAAS;IAChB,kBAAkB,CAAS;IAE3B,YACC,MAAmB,EACnB,OAA2D;QAE3D,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC;QAC3C,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;QAC5C,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CACX,0EAA0E;gBACzE,sEAAsE;gBACtE,0EAA0E,CAC3E,CAAC;QACH,CAAC;IACF,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,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,GAAG;QACR,IAAI,GAAG,GAAkB,IAAI,CAAC;QAE9B,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACxB,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAC7B,IAAI,CAAC,WAAW,EAAE,EAClB,IAAI,CAAC,cAAc,EAAE,EACrB,MAAM,EACN,OAAO,CACP,CAAC;QACH,CAAC;aAAM,CAAC;YACP,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAClD,IAAI,GAAG;gBAAE,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QAC/D,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,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACvD,OAAO,IAAI,CAAC;YACb,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrB,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,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAQ;QACtB,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAQ,EAAE,KAAa;QACjC,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;QAClB,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC;QACtB,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAQ;QACnB,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;QACvB,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,YAAY;QACjB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,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,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACvD,SAAS;YACV,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACvD,SAAS;YACV,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAChE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;gBACvD,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC;gBAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBACrE,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,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;YAC1E,IAAI,OAAO,GAAG,CAAC;gBAAE,OAAO;QACzB,CAAC;QACD,qEAAqE;QACrE,kEAAkE;QAClE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,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,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;gBACxD,OAAO;YACR,CAAC;QACF,CAAC;IACF,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjE,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,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC;CACD"}
1
+ {"version":3,"file":"RedisDriver.js","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"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"}
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. A connection missing one of
21
+ // them would fail on the first job push, far from the cause.
22
+ const required = [
23
+ "get",
24
+ "set",
25
+ "del",
26
+ "exists",
27
+ "keys",
28
+ "sadd",
29
+ "srem",
30
+ "smembers",
31
+ "expire",
32
+ "ttl",
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,wEAAwE;IACxE,6DAA6D;IAC7D,MAAM,QAAQ,GAAG;QAChB,KAAK;QACL,KAAK;QACL,KAAK;QACL,QAAQ;QACR,MAAM;QACN,MAAM;QACN,MAAM;QACN,UAAU;QACV,QAAQ;QACR,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.7",
3
+ "version": "0.1.9",
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,7 +25,8 @@
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",
@@ -48,6 +49,9 @@
48
49
  "peerDependenciesMeta": {
49
50
  "@c9up/ream": {
50
51
  "optional": true
52
+ },
53
+ "@c9up/quasar": {
54
+ "optional": true
51
55
  }
52
56
  },
53
57
  "repository": {
@@ -10,7 +10,7 @@ import { setQueue } from "./services/main.js";
10
10
  */
11
11
  interface BayContainer {
12
12
  singleton(token: unknown, factory: () => unknown): void;
13
- resolve<T = unknown>(token: unknown): T;
13
+ resolve<T = unknown>(token: unknown): Promise<T>;
14
14
  }
15
15
  interface BayConfigStore {
16
16
  get<T = unknown>(key: string): T | undefined;
@@ -79,7 +79,7 @@ export default class BayProvider {
79
79
  async boot(): Promise<void> {
80
80
  // Populate the `@c9up/bay/services/main` singleton so apps can
81
81
  // `import queue from '@c9up/bay/services/main'` from anywhere.
82
- setQueue(this.app.container.resolve<QueueManager>(QueueManager));
82
+ setQueue(await this.app.container.resolve<QueueManager>(QueueManager));
83
83
  }
84
84
 
85
85
  async shutdown(): Promise<void> {}
@@ -49,16 +49,71 @@ 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
+
52
78
  export class RedisDriver implements QueueDriver {
53
- #client: RedisClient;
79
+ #source: RedisClientSource;
80
+ #resolved: RedisClient | undefined;
81
+ #pending: Promise<RedisClient> | undefined;
54
82
  #prefix: string;
55
83
  #visibilityTimeout: number;
56
84
 
85
+ /**
86
+ * The client, resolved once. Two workers racing on a cold queue must not
87
+ * each open their own connection, so the in-flight promise is shared.
88
+ */
89
+ async #client(): Promise<RedisClient> {
90
+ if (this.#resolved) return this.#resolved;
91
+ if (typeof this.#source !== "function") {
92
+ this.#resolved = this.#source;
93
+ warnWithoutLmove(this.#resolved);
94
+ return this.#resolved;
95
+ }
96
+ if (!this.#pending) {
97
+ const resolver = this.#source;
98
+ this.#pending = Promise.resolve(resolver()).then((client) => {
99
+ this.#resolved = client;
100
+ this.#pending = undefined;
101
+ warnWithoutLmove(client);
102
+ return client;
103
+ });
104
+ }
105
+ return this.#pending;
106
+ }
107
+
57
108
  constructor(
58
- client: RedisClient,
109
+ source: RedisClientSource,
59
110
  options?: { prefix?: string; visibilityTimeoutMs?: number },
60
111
  ) {
61
- this.#client = client;
112
+ this.#source = source;
113
+ // A client handed in directly can be checked now, so the warning keeps
114
+ // landing at construction as it always did. A named connection has no
115
+ // client yet — it is checked when the connection resolves.
116
+ if (typeof source !== "function") warnWithoutLmove(source);
62
117
  this.#prefix = options?.prefix ?? "queue:";
63
118
  const visibilityTimeout = options?.visibilityTimeoutMs ?? 30_000;
64
119
  // A non-positive / non-integer timeout makes pop()'s `SET … PX <ms>` fail
@@ -71,13 +126,6 @@ export class RedisDriver implements QueueDriver {
71
126
  );
72
127
  }
73
128
  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
129
  }
82
130
 
83
131
  #pendingKey = () => `${this.#prefix}pending`;
@@ -86,22 +134,24 @@ export class RedisDriver implements QueueDriver {
86
134
  #leaseKey = (jobId: string) => `${this.#prefix}lease:${jobId}`;
87
135
 
88
136
  async push(job: Job): Promise<void> {
89
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
137
+ const client = await this.#client();
138
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
90
139
  }
91
140
 
92
141
  async pop(): Promise<Job | null> {
142
+ const client = await this.#client();
93
143
  let raw: string | null = null;
94
144
 
95
- if (this.#client.lmove) {
96
- raw = await this.#client.lmove(
145
+ if (client.lmove) {
146
+ raw = await client.lmove(
97
147
  this.#pendingKey(),
98
148
  this.#processingKey(),
99
149
  "LEFT",
100
150
  "RIGHT",
101
151
  );
102
152
  } else {
103
- raw = await this.#client.lpop(this.#pendingKey());
104
- if (raw) await this.#client.rpush(this.#processingKey(), raw);
153
+ raw = await client.lpop(this.#pendingKey());
154
+ if (raw) await client.rpush(this.#processingKey(), raw);
105
155
  }
106
156
 
107
157
  if (!raw) return null;
@@ -111,10 +161,10 @@ export class RedisDriver implements QueueDriver {
111
161
  // Malformed payload — purge from `processing` so it can't sit
112
162
  // there indefinitely as a poison pill. recoverStale() also
113
163
  // catches survivors but pop()'s own move is the primary path.
114
- await this.#client.lrem(this.#processingKey(), 1, raw);
164
+ await client.lrem(this.#processingKey(), 1, raw);
115
165
  return null;
116
166
  }
117
- await this.#client.set(
167
+ await client.set(
118
168
  this.#leaseKey(parsed.id),
119
169
  raw,
120
170
  "PX",
@@ -122,33 +172,37 @@ export class RedisDriver implements QueueDriver {
122
172
  );
123
173
  return parsed;
124
174
  } catch {
125
- await this.#client.lrem(this.#processingKey(), 1, raw);
175
+ await client.lrem(this.#processingKey(), 1, raw);
126
176
  return null;
127
177
  }
128
178
  }
129
179
 
130
180
  async complete(job: Job): Promise<void> {
181
+ const client = await this.#client();
131
182
  await this.#removeFromProcessing(job);
132
- await this.#client.del(this.#leaseKey(job.id));
183
+ await client.del(this.#leaseKey(job.id));
133
184
  }
134
185
 
135
186
  async fail(job: Job, error: string): Promise<void> {
187
+ const client = await this.#client();
136
188
  await this.#removeFromProcessing(job);
137
- await this.#client.del(this.#leaseKey(job.id));
189
+ await client.del(this.#leaseKey(job.id));
138
190
  job.error = error;
139
191
  job.status = "failed";
140
- await this.#client.rpush(this.#failedKey(), JSON.stringify(job));
192
+ await client.rpush(this.#failedKey(), JSON.stringify(job));
141
193
  }
142
194
 
143
195
  async retry(job: Job): Promise<void> {
196
+ const client = await this.#client();
144
197
  await this.#removeFromProcessing(job);
145
- await this.#client.del(this.#leaseKey(job.id));
198
+ await client.del(this.#leaseKey(job.id));
146
199
  job.status = "pending";
147
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(job));
200
+ await client.rpush(this.#pendingKey(), JSON.stringify(job));
148
201
  }
149
202
 
150
203
  async recoverStale(): Promise<number> {
151
- const processing = await this.#client.lrange(this.#processingKey(), 0, -1);
204
+ const client = await this.#client();
205
+ const processing = await client.lrange(this.#processingKey(), 0, -1);
152
206
  let recovered = 0;
153
207
  for (const raw of processing) {
154
208
  let parsed: unknown;
@@ -157,18 +211,18 @@ export class RedisDriver implements QueueDriver {
157
211
  } catch {
158
212
  // Malformed JSON would otherwise sit in processing forever —
159
213
  // LREM purges it so the queue makes progress.
160
- await this.#client.lrem(this.#processingKey(), 1, raw);
214
+ await client.lrem(this.#processingKey(), 1, raw);
161
215
  continue;
162
216
  }
163
217
  if (!isValidJob(parsed)) {
164
- await this.#client.lrem(this.#processingKey(), 1, raw);
218
+ await client.lrem(this.#processingKey(), 1, raw);
165
219
  continue;
166
220
  }
167
- const lease = await this.#client.get(this.#leaseKey(parsed.id));
221
+ const lease = await client.get(this.#leaseKey(parsed.id));
168
222
  if (lease === null) {
169
- await this.#client.lrem(this.#processingKey(), 1, raw);
223
+ await client.lrem(this.#processingKey(), 1, raw);
170
224
  parsed.status = "pending";
171
- await this.#client.rpush(this.#pendingKey(), JSON.stringify(parsed));
225
+ await client.rpush(this.#pendingKey(), JSON.stringify(parsed));
172
226
  recovered++;
173
227
  }
174
228
  }
@@ -185,14 +239,15 @@ export class RedisDriver implements QueueDriver {
185
239
  * the lease has expired (e.g. recoverStale already handled it).
186
240
  */
187
241
  async #removeFromProcessing(job: Job): Promise<void> {
188
- const stored = await this.#client.get(this.#leaseKey(job.id));
242
+ const client = await this.#client();
243
+ const stored = await client.get(this.#leaseKey(job.id));
189
244
  if (stored !== null) {
190
- const removed = await this.#client.lrem(this.#processingKey(), 1, stored);
245
+ const removed = await client.lrem(this.#processingKey(), 1, stored);
191
246
  if (removed > 0) return;
192
247
  }
193
248
  // Lease missing or already-LREM'd entry not found — best-effort scan
194
249
  // matches by job id and removes the actual stored representation.
195
- const items = await this.#client.lrange(this.#processingKey(), 0, -1);
250
+ const items = await client.lrange(this.#processingKey(), 0, -1);
196
251
  for (const item of items) {
197
252
  let parsed: unknown;
198
253
  try {
@@ -201,14 +256,15 @@ export class RedisDriver implements QueueDriver {
201
256
  continue;
202
257
  }
203
258
  if (isValidJob(parsed) && (parsed as { id: string }).id === job.id) {
204
- await this.#client.lrem(this.#processingKey(), 1, item);
259
+ await client.lrem(this.#processingKey(), 1, item);
205
260
  return;
206
261
  }
207
262
  }
208
263
  }
209
264
 
210
265
  async failed(): Promise<Job[]> {
211
- const raws = await this.#client.lrange(this.#failedKey(), 0, -1);
266
+ const client = await this.#client();
267
+ const raws = await client.lrange(this.#failedKey(), 0, -1);
212
268
  return raws
213
269
  .map((r) => {
214
270
  try {
@@ -222,6 +278,7 @@ export class RedisDriver implements QueueDriver {
222
278
  }
223
279
 
224
280
  async size(): Promise<number> {
225
- return this.#client.llen(this.#pendingKey());
281
+ const client = await this.#client();
282
+ return client.llen(this.#pendingKey());
226
283
  }
227
284
  }
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. A connection missing one of
31
+ // them would fail on the first job push, far from the cause.
32
+ const required = [
33
+ "get",
34
+ "set",
35
+ "del",
36
+ "exists",
37
+ "keys",
38
+ "sadd",
39
+ "srem",
40
+ "smembers",
41
+ "expire",
42
+ "ttl",
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 " +