@bumail/queue 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +44 -8
  2. package/dist/chunks/index-b4xvg3xn.js +23 -0
  3. package/dist/chunks/index-b4xvg3xn.js.map +10 -0
  4. package/dist/chunks/index-vg971acr.js.map +2 -2
  5. package/dist/contract/queue-store.d.ts +8 -2
  6. package/dist/contract/queue-store.d.ts.map +1 -1
  7. package/dist/dsn/build.d.ts +7 -3
  8. package/dist/dsn/build.d.ts.map +1 -1
  9. package/dist/errors.d.ts +5 -2
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/index.js +117 -33
  12. package/dist/index.js.map +8 -7
  13. package/dist/masked.d.ts +8 -0
  14. package/dist/masked.d.ts.map +1 -0
  15. package/dist/postgres/connect.d.ts +6 -0
  16. package/dist/postgres/connect.d.ts.map +1 -1
  17. package/dist/postgres/index.js +49 -28
  18. package/dist/postgres/index.js.map +7 -6
  19. package/dist/postgres/isolation.d.ts +6 -0
  20. package/dist/postgres/isolation.d.ts.map +1 -0
  21. package/dist/postgres/schema.d.ts.map +1 -1
  22. package/dist/postgres/store.d.ts.map +1 -1
  23. package/dist/queue/deliver.d.ts +10 -1
  24. package/dist/queue/deliver.d.ts.map +1 -1
  25. package/dist/queue/lease.d.ts +41 -0
  26. package/dist/queue/lease.d.ts.map +1 -0
  27. package/dist/queue/notify.d.ts +3 -2
  28. package/dist/queue/notify.d.ts.map +1 -1
  29. package/dist/queue/worker.d.ts.map +1 -1
  30. package/dist/redis/connect.d.ts +27 -0
  31. package/dist/redis/connect.d.ts.map +1 -0
  32. package/dist/redis/index.d.ts +3 -0
  33. package/dist/redis/index.d.ts.map +1 -0
  34. package/dist/redis/index.js +438 -0
  35. package/dist/redis/index.js.map +14 -0
  36. package/dist/redis/items.d.ts +13 -0
  37. package/dist/redis/items.d.ts.map +1 -0
  38. package/dist/redis/options.d.ts +47 -0
  39. package/dist/redis/options.d.ts.map +1 -0
  40. package/dist/redis/run.d.ts +12 -0
  41. package/dist/redis/run.d.ts.map +1 -0
  42. package/dist/redis/scripts.d.ts +60 -0
  43. package/dist/redis/scripts.d.ts.map +1 -0
  44. package/dist/redis/store.d.ts +30 -0
  45. package/dist/redis/store.d.ts.map +1 -0
  46. package/docs/README.md +1 -1
  47. package/docs/guide.md +271 -16
  48. package/docs/roadmap.md +49 -7
  49. package/docs/troubleshooting.md +303 -16
  50. package/package.json +9 -3
@@ -0,0 +1,438 @@
1
+ import {
2
+ masked
3
+ } from "../chunks/index-b4xvg3xn.js";
4
+ import {
5
+ QueueError2,
6
+ invalid,
7
+ isStorable,
8
+ checkTime,
9
+ checkOwner,
10
+ checkNewItem,
11
+ checkMaxItems,
12
+ checkClaim,
13
+ checkResult,
14
+ checkList,
15
+ isDone,
16
+ applyAttempt,
17
+ newItem
18
+ } from "../chunks/index-vg971acr.js";
19
+
20
+ // src/redis/connect.ts
21
+ var DEFAULT_PREFIX = "bumail:queue:";
22
+ var PREFIX = /^[a-z][a-z0-9_:.-]{0,39}$/;
23
+ function keysOf(value) {
24
+ const prefix = value ?? DEFAULT_PREFIX;
25
+ if (typeof prefix !== "string" || !PREFIX.test(prefix)) {
26
+ throw invalid(`keyPrefix must be lowercase letters, digits, '_', ':', '.' and '-', starting with a letter, at most 40 characters, not ${JSON.stringify(prefix)}`);
27
+ }
28
+ return {
29
+ prefix,
30
+ items: `${prefix}items`,
31
+ ready: `${prefix}ready`,
32
+ leases: `${prefix}leases`,
33
+ seq: `${prefix}seq`,
34
+ schema: `${prefix}schema`
35
+ };
36
+ }
37
+ var NEEDS = "A Redis queue store needs client, a Bun.RedisClient, or url, a redis:// URL";
38
+ var SCHEMES = [
39
+ "redis:",
40
+ "rediss:",
41
+ "valkey:",
42
+ "valkeys:",
43
+ "redis+tls:",
44
+ "redis+unix:",
45
+ "redis+tls+unix:"
46
+ ];
47
+ function urlOf(value) {
48
+ if (typeof value !== "string" && !(value instanceof URL))
49
+ throw invalid(NEEDS);
50
+ const url = URL.parse(String(value));
51
+ if (url === null || !SCHEMES.includes(url.protocol))
52
+ throw invalid(NEEDS);
53
+ return url;
54
+ }
55
+ function clientFor(url) {
56
+ try {
57
+ return new Bun.RedisClient(url.href);
58
+ } catch (error) {
59
+ const reason = error instanceof Error ? error.message : String(error);
60
+ throw invalid(`The URL in url cannot be opened: ${masked(reason, url.password)}`);
61
+ }
62
+ }
63
+ var isClient = (value) => typeof value === "object" && value !== null && typeof value.send === "function" && typeof value.getBuffer === "function" && typeof value.close === "function";
64
+ function connect(options) {
65
+ if (typeof options !== "object" || options === null)
66
+ throw invalid(NEEDS);
67
+ const keys = keysOf(options.keyPrefix);
68
+ const { client, url } = options;
69
+ if (client !== undefined && url !== undefined) {
70
+ throw invalid("A Redis queue store takes client or url, not both");
71
+ }
72
+ if (client !== undefined) {
73
+ if (!isClient(client))
74
+ throw invalid(NEEDS);
75
+ return { client, owned: false, keys, password: "" };
76
+ }
77
+ const parsed = urlOf(url);
78
+ return {
79
+ client: clientFor(parsed),
80
+ owned: true,
81
+ keys,
82
+ password: parsed.password
83
+ };
84
+ }
85
+
86
+ // src/redis/items.ts
87
+ function fieldsOf(reply) {
88
+ if (Array.isArray(reply)) {
89
+ const fields = {};
90
+ for (let i = 0;i + 1 < reply.length; i += 2) {
91
+ fields[String(reply[i])] = String(reply[i + 1]);
92
+ }
93
+ return fieldsOf(fields);
94
+ }
95
+ if (typeof reply !== "object" || reply === null)
96
+ return;
97
+ return reply["id"] === undefined ? undefined : reply;
98
+ }
99
+ var field = (fields, name) => fields[name] ?? "";
100
+ function itemOf(fields) {
101
+ const item = {
102
+ id: field(fields, "id"),
103
+ from: field(fields, "from"),
104
+ recipients: JSON.parse(field(fields, "recipients")),
105
+ size: Number(field(fields, "size")),
106
+ createdAt: Number(field(fields, "created")),
107
+ nextAttemptAt: Number(field(fields, "next")),
108
+ attempts: Number(field(fields, "attempts")),
109
+ delayNotified: field(fields, "delay") === "1"
110
+ };
111
+ const owner = fields["owner"];
112
+ const expires = fields["expires"];
113
+ if (owner === undefined || expires === undefined)
114
+ return item;
115
+ return { ...item, lease: { owner, expiresAt: Number(expires) } };
116
+ }
117
+ var ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
118
+ var isId = (id) => typeof id === "string" && ID.test(id);
119
+ var time = (value) => String(value);
120
+ var outcomeOf = (item) => isDone(item) ? ["drop"] : [
121
+ "record",
122
+ JSON.stringify(item.recipients),
123
+ time(item.nextAttemptAt),
124
+ `${item.attempts}`,
125
+ item.delayNotified ? "1" : "0"
126
+ ];
127
+
128
+ // src/redis/scripts.ts
129
+ var PRELUDE = `
130
+ local prefix = ARGV[1]
131
+ local function itemKey(member) return prefix .. 'item:' .. string.sub(member, 18) end
132
+ local function seqOf(member) return tonumber(string.sub(member, 1, 16)) end
133
+ `;
134
+ var NEXT_OF = `
135
+ local function nextOf(member)
136
+ local found = redis.call('HGET', itemKey(member), 'next')
137
+ if found then return tonumber(found) end
138
+ -- A member whose hash is gone is dropped, never leased.
139
+ redis.call('ZREM', KEYS[1], member)
140
+ redis.call('ZREM', KEYS[2], member)
141
+ redis.call('ZREM', KEYS[3], member)
142
+ redis.call('DEL', prefix .. 'message:' .. string.sub(member, 18))
143
+ return false
144
+ end
145
+ `;
146
+ var script = (body) => {
147
+ const source = PRELUDE + body;
148
+ const sha = new Bun.CryptoHasher("sha1").update(source).digest("hex");
149
+ return { source, sha };
150
+ };
151
+ var SCHEMA = script(`
152
+ local found = redis.call('GET', KEYS[1])
153
+ if found then return found end
154
+ redis.call('SET', KEYS[1], ARGV[2])
155
+ return ARGV[2]
156
+ `);
157
+ var ADD = script(`
158
+ local max = tonumber(ARGV[8])
159
+ if max then
160
+ local n = redis.call('ZCARD', KEYS[1])
161
+ if n >= max then return {'full', tostring(n)} end
162
+ end
163
+ local member = string.format('%016d', redis.call('INCR', KEYS[3])) .. ':' .. ARGV[2]
164
+ redis.call('HSET', prefix .. 'item:' .. ARGV[2], 'id', ARGV[2], 'from', ARGV[3],
165
+ 'recipients', ARGV[4], 'size', ARGV[5], 'created', ARGV[6], 'next', ARGV[6],
166
+ 'attempts', '0', 'delay', '0', 'rev', '0', 'member', member)
167
+ redis.call('SET', prefix .. 'message:' .. ARGV[2], ARGV[7])
168
+ redis.call('ZADD', KEYS[1], ARGV[6], member)
169
+ redis.call('ZADD', KEYS[2], ARGV[6], member)
170
+ return {'ok'}
171
+ `);
172
+ var CLAIM = script(`${NEXT_OF}
173
+ local now = tonumber(ARGV[3])
174
+ local best, bestNext
175
+ while best == nil do
176
+ local first = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[3], 'LIMIT', 0, 1)[1]
177
+ if not first then break end
178
+ local nextAt = nextOf(first)
179
+ if nextAt then
180
+ best = first
181
+ bestNext = nextAt
182
+ end
183
+ end
184
+ for _, member in ipairs(redis.call('ZRANGEBYSCORE', KEYS[2], '-inf', ARGV[3])) do
185
+ local nextAt = nextOf(member)
186
+ if nextAt and nextAt <= now and (best == nil or nextAt < bestNext
187
+ or (nextAt == bestNext and seqOf(member) < seqOf(best))) then
188
+ best = member
189
+ bestNext = nextAt
190
+ end
191
+ end
192
+ if best == nil then return false end
193
+ redis.call('ZREM', KEYS[1], best)
194
+ redis.call('ZADD', KEYS[2], ARGV[4], best)
195
+ redis.call('HSET', itemKey(best), 'owner', ARGV[2], 'expires', ARGV[4])
196
+ return redis.call('HGETALL', itemKey(best))
197
+ `);
198
+ var RENEW = script(`
199
+ local key = prefix .. 'item:' .. ARGV[2]
200
+ if redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end
201
+ redis.call('HSET', key, 'expires', ARGV[4])
202
+ redis.call('ZADD', KEYS[1], ARGV[4], redis.call('HGET', key, 'member'))
203
+ return 1
204
+ `);
205
+ var COMPLETE = script(`
206
+ local key = prefix .. 'item:' .. ARGV[2]
207
+ if redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end
208
+ if redis.call('HGET', key, 'rev') ~= ARGV[4] then return -1 end
209
+ local member = redis.call('HGET', key, 'member')
210
+ redis.call('ZREM', KEYS[3], member)
211
+ if ARGV[5] == 'drop' then
212
+ redis.call('ZREM', KEYS[1], member)
213
+ redis.call('ZREM', KEYS[2], member)
214
+ redis.call('DEL', key, prefix .. 'message:' .. ARGV[2])
215
+ return 1
216
+ end
217
+ redis.call('HSET', key, 'recipients', ARGV[6], 'next', ARGV[7],
218
+ 'attempts', ARGV[8], 'delay', ARGV[9])
219
+ redis.call('HINCRBY', key, 'rev', 1)
220
+ redis.call('HDEL', key, 'owner', 'expires')
221
+ redis.call('ZADD', KEYS[1], ARGV[7], member)
222
+ redis.call('ZADD', KEYS[2], ARGV[7], member)
223
+ return 1
224
+ `);
225
+ var RESCHEDULE = script(`
226
+ local key = prefix .. 'item:' .. ARGV[2]
227
+ local member = redis.call('HGET', key, 'member')
228
+ if not member then return 0 end
229
+ local owner = redis.call('HGET', key, 'owner')
230
+ if ARGV[4] ~= '' then
231
+ if owner ~= ARGV[4] then return 0 end
232
+ redis.call('HDEL', key, 'owner', 'expires')
233
+ redis.call('ZREM', KEYS[3], member)
234
+ owner = false
235
+ end
236
+ redis.call('HSET', key, 'next', ARGV[3])
237
+ redis.call('ZADD', KEYS[1], ARGV[3], member)
238
+ if not owner then redis.call('ZADD', KEYS[2], ARGV[3], member) end
239
+ return 1
240
+ `);
241
+ var CANCEL = script(`
242
+ local key = prefix .. 'item:' .. ARGV[2]
243
+ local fields = redis.call('HGETALL', key)
244
+ if #fields == 0 then return false end
245
+ local member = redis.call('HGET', key, 'member')
246
+ redis.call('ZREM', KEYS[1], member)
247
+ redis.call('ZREM', KEYS[2], member)
248
+ redis.call('ZREM', KEYS[3], member)
249
+ redis.call('DEL', key, prefix .. 'message:' .. ARGV[2])
250
+ return fields
251
+ `);
252
+ var LIST = script(`
253
+ local out = {}
254
+ for _, member in ipairs(redis.call('ZRANGE', KEYS[1], ARGV[2], ARGV[3])) do
255
+ out[#out + 1] = redis.call('HGETALL', itemKey(member))
256
+ end
257
+ return out
258
+ `);
259
+
260
+ // src/redis/run.ts
261
+ var isNoScript = (error) => error instanceof Error && error.message.startsWith("NOSCRIPT");
262
+ async function run(client, script, keys, args) {
263
+ const rest = [`${keys.length}`, ...keys, ...args];
264
+ try {
265
+ return await client.send("EVALSHA", [script.sha, ...rest]);
266
+ } catch (error) {
267
+ if (!isNoScript(error))
268
+ throw error;
269
+ return client.send("EVAL", [script.source, ...rest]);
270
+ }
271
+ }
272
+ var LAYOUT = 1;
273
+ async function checkLayout(client, keys) {
274
+ const found = Number(await run(client, SCHEMA, [keys.schema], [keys.prefix, `${LAYOUT}`]));
275
+ if (!Number.isInteger(found) || found < 1) {
276
+ throw new QueueError2("INVALID", `The key ${keys.schema} does not hold a layout version: is the prefix another application's?`);
277
+ }
278
+ if (found > LAYOUT) {
279
+ throw new QueueError2("INVALID", `The database is at schema version ${found}, newer than this store's ${LAYOUT}`);
280
+ }
281
+ }
282
+
283
+ // src/redis/store.ts
284
+ class RedisQueueStore {
285
+ #client;
286
+ #owned;
287
+ #keys;
288
+ #password;
289
+ #ready;
290
+ #closed = false;
291
+ constructor({ client, owned, keys, password }) {
292
+ this.#client = client;
293
+ this.#owned = owned;
294
+ this.#keys = keys;
295
+ this.#password = password;
296
+ }
297
+ static open(options) {
298
+ return new RedisQueueStore(connect(options));
299
+ }
300
+ async close() {
301
+ if (this.#closed)
302
+ return;
303
+ this.#closed = true;
304
+ if (this.#owned)
305
+ await this.#client.close();
306
+ }
307
+ #setUp() {
308
+ this.#ready ??= checkLayout(this.#client, this.#keys).catch((error) => {
309
+ this.#ready = undefined;
310
+ if (error instanceof QueueError2)
311
+ throw error;
312
+ const reason = error instanceof Error ? error.message : String(error);
313
+ throw new QueueError2("INVALID", `The Redis queue cannot be set up: ${masked(reason, this.#password)}`);
314
+ });
315
+ return this.#ready;
316
+ }
317
+ async#redis() {
318
+ if (this.#closed)
319
+ throw closed();
320
+ await this.#setUp();
321
+ return this.#client;
322
+ }
323
+ async#run(script, keys, args) {
324
+ const client = await this.#redis();
325
+ return run(client, script, keys, [this.#keys.prefix, ...args]);
326
+ }
327
+ async add(item, options = {}) {
328
+ checkNewItem(item);
329
+ const max = checkMaxItems(options.maxItems);
330
+ const added = newItem(crypto.randomUUID(), item);
331
+ const k = this.#keys;
332
+ const reply = await this.#run(ADD, [k.items, k.ready, k.seq], [
333
+ added.id,
334
+ added.from,
335
+ JSON.stringify(added.recipients),
336
+ `${added.size}`,
337
+ time(added.createdAt),
338
+ item.message,
339
+ max === undefined ? "" : `${max}`
340
+ ]);
341
+ if (reply[0] === "full") {
342
+ throw new QueueError2("QUEUE_FULL", `The queue holds ${reply[1]} items, its limit`);
343
+ }
344
+ return added;
345
+ }
346
+ async get(id) {
347
+ if (!isId(id))
348
+ return;
349
+ const client = await this.#redis();
350
+ const fields = fieldsOf(await client.send("HGETALL", [`${this.#keys.prefix}item:${id}`]));
351
+ return fields ? itemOf(fields) : undefined;
352
+ }
353
+ async list(options) {
354
+ const { offset, limit } = checkList(options);
355
+ const pages = await this.#run(LIST, [this.#keys.items], [`${offset}`, `${offset + limit - 1}`]);
356
+ return pages.flatMap((reply) => {
357
+ const fields = fieldsOf(reply);
358
+ return fields ? [itemOf(fields)] : [];
359
+ });
360
+ }
361
+ async count() {
362
+ const client = await this.#redis();
363
+ return Number(await client.send("ZCARD", [this.#keys.items]));
364
+ }
365
+ async readMessage(id) {
366
+ if (!isId(id))
367
+ return;
368
+ const client = await this.#redis();
369
+ const bytes = await client.getBuffer(`${this.#keys.prefix}message:${id}`);
370
+ return bytes === null ? undefined : new Uint8Array(bytes);
371
+ }
372
+ async claim(request) {
373
+ checkClaim(request);
374
+ const { owner, now, leaseMs } = request;
375
+ const k = this.#keys;
376
+ const fields = fieldsOf(await this.#run(CLAIM, [k.ready, k.leases, k.items], [owner, time(now), time(now + leaseMs)]));
377
+ return fields ? itemOf(fields) : undefined;
378
+ }
379
+ async renew(id, owner, expiresAt) {
380
+ checkOwner(owner);
381
+ checkTime("expiresAt", expiresAt);
382
+ if (!isId(id))
383
+ return false;
384
+ const k = this.#keys;
385
+ const done = await this.#run(RENEW, [k.leases], [id, owner, time(expiresAt)]);
386
+ return Number(done) === 1;
387
+ }
388
+ async complete(id, owner, result) {
389
+ checkOwner(owner);
390
+ checkResult(result);
391
+ if (!isId(id))
392
+ return;
393
+ const k = this.#keys;
394
+ const client = await this.#redis();
395
+ for (let read = 0;read < MAX_READS; read++) {
396
+ const fields = fieldsOf(await client.send("HGETALL", [`${k.prefix}item:${id}`]));
397
+ if (!fields || fields["owner"] !== owner)
398
+ return;
399
+ const rev = fields["rev"];
400
+ if (rev === undefined)
401
+ return;
402
+ const item = applyAttempt(itemOf(fields), result);
403
+ const done = Number(await this.#run(COMPLETE, [k.items, k.ready, k.leases], [id, owner, rev, ...outcomeOf(item)]));
404
+ if (done === 1)
405
+ return item;
406
+ if (done === 0)
407
+ return;
408
+ }
409
+ return;
410
+ }
411
+ async reschedule(id, at, owner) {
412
+ checkTime("at", at);
413
+ if (!isStorable(id))
414
+ return false;
415
+ if (owner !== undefined)
416
+ checkOwner(owner);
417
+ if (!isId(id))
418
+ return false;
419
+ const k = this.#keys;
420
+ const done = await this.#run(RESCHEDULE, [k.items, k.ready, k.leases], [id, time(at), owner ?? ""]);
421
+ return Number(done) === 1;
422
+ }
423
+ async cancel(id) {
424
+ if (!isId(id))
425
+ return;
426
+ const k = this.#keys;
427
+ const fields = fieldsOf(await this.#run(CANCEL, [k.items, k.ready, k.leases], [id]));
428
+ return fields ? itemOf(fields) : undefined;
429
+ }
430
+ }
431
+ var MAX_READS = 8;
432
+ var closed = () => new QueueError2("CLOSED", "The queue store is closed");
433
+ export {
434
+ RedisQueueStore
435
+ };
436
+
437
+ //# debugId=63554783C4CC726464756E2164756E21
438
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/redis/connect.ts", "../src/redis/items.ts", "../src/redis/scripts.ts", "../src/redis/run.ts", "../src/redis/store.ts"],
4
+ "sourcesContent": [
5
+ "import { invalid } from '../errors';\nimport { masked } from '../masked';\nimport type { RedisQueueClient, RedisQueueStoreOptions } from './options';\n\n/** The keys of one queue, each its prefix and a fixed name. */\nexport interface Keys {\n\treadonly prefix: string;\n\t/** Every item, scored by its next attempt: `list` and `count`. */\n\treadonly items: string;\n\t/** The items no lease holds, scored by their next attempt: what a claim takes first. */\n\treadonly ready: string;\n\t/** The leased items, scored by when their leases expire. */\n\treadonly leases: string;\n\t/** The counter that orders items equally due. */\n\treadonly seq: string;\n\t/** The layout version. */\n\treadonly schema: string;\n}\n\n/** The client a store uses, and whether it opened it (and so closes it). */\nexport interface Connection {\n\treadonly client: RedisQueueClient;\n\treadonly owned: boolean;\n\treadonly keys: Keys;\n\t/** The URL's password, so an error that repeats it is masked; `''` for a given client. */\n\treadonly password: string;\n}\n\nconst DEFAULT_PREFIX = 'bumail:queue:';\n\n/**\n * Nothing Redis reads specially: no `{`, so no Cluster hash tag; no `*`,\n * `?`, `[` or `\\`, so a `SCAN MATCH` on the prefix matches its keys only;\n * no space or control character.\n */\nconst PREFIX = /^[a-z][a-z0-9_:.-]{0,39}$/;\n\nexport function keysOf(value: unknown): Keys {\n\tconst prefix = value ?? DEFAULT_PREFIX;\n\tif (typeof prefix !== 'string' || !PREFIX.test(prefix)) {\n\t\tthrow invalid(\n\t\t\t`keyPrefix must be lowercase letters, digits, '_', ':', '.' and '-', starting with a letter, at most 40 characters, not ${JSON.stringify(prefix)}`,\n\t\t);\n\t}\n\treturn {\n\t\tprefix,\n\t\titems: `${prefix}items`,\n\t\tready: `${prefix}ready`,\n\t\tleases: `${prefix}leases`,\n\t\tseq: `${prefix}seq`,\n\t\tschema: `${prefix}schema`,\n\t};\n}\n\nconst NEEDS =\n\t'A Redis queue store needs client, a Bun.RedisClient, or url, a redis:// URL';\n\n/** The schemes `Bun.RedisClient` takes. */\nconst SCHEMES = [\n\t'redis:',\n\t'rediss:',\n\t'valkey:',\n\t'valkeys:',\n\t'redis+tls:',\n\t'redis+unix:',\n\t'redis+tls+unix:',\n];\n\n/** A Redis URL; never echoed, since it may hold a password. */\nfunction urlOf(value: unknown): URL {\n\tif (typeof value !== 'string' && !(value instanceof URL))\n\t\tthrow invalid(NEEDS);\n\tconst url = URL.parse(String(value));\n\tif (url === null || !SCHEMES.includes(url.protocol)) throw invalid(NEEDS);\n\treturn url;\n}\n\n/**\n * A client of the store's own for the URL. `Bun.RedisClient` refuses\n * some URLs at once (a database that is not a number): that is\n * `INVALID`, its reason kept and the password, should it be repeated,\n * masked.\n */\nfunction clientFor(url: URL): RedisQueueClient {\n\ttry {\n\t\treturn new Bun.RedisClient(url.href) as unknown as RedisQueueClient;\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tthrow invalid(\n\t\t\t`The URL in url cannot be opened: ${masked(reason, url.password)}`,\n\t\t);\n\t}\n}\n\nconst isClient = (value: unknown): value is RedisQueueClient =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\ttypeof (value as RedisQueueClient).send === 'function' &&\n\ttypeof (value as RedisQueueClient).getBuffer === 'function' &&\n\ttypeof (value as RedisQueueClient).close === 'function';\n\n/** Checks the options and opens a client for a URL; connects to nothing yet. */\nexport function connect(options: RedisQueueStoreOptions): Connection {\n\tif (typeof options !== 'object' || options === null) throw invalid(NEEDS);\n\tconst keys = keysOf(options.keyPrefix);\n\tconst { client, url } = options;\n\tif (client !== undefined && url !== undefined) {\n\t\tthrow invalid('A Redis queue store takes client or url, not both');\n\t}\n\tif (client !== undefined) {\n\t\tif (!isClient(client)) throw invalid(NEEDS);\n\t\treturn { client, owned: false, keys, password: '' };\n\t}\n\tconst parsed = urlOf(url);\n\treturn {\n\t\tclient: clientFor(parsed),\n\t\towned: true,\n\t\tkeys,\n\t\tpassword: parsed.password,\n\t};\n}\n",
6
+ "import { isDone } from '../contract/checks';\nimport type { QueueItem, RecipientState } from '../contract/types';\n\n/** An item's hash, as `HGETALL` gives it: a map, or a script's flat list of names and values. */\nexport type Fields = Readonly<Record<string, string>>;\n\n/** A script's `HGETALL`, `[name, value, …]`, as a map; a map as it is. */\nexport function fieldsOf(reply: unknown): Fields | undefined {\n\tif (Array.isArray(reply)) {\n\t\tconst fields: Record<string, string> = {};\n\t\tfor (let i = 0; i + 1 < reply.length; i += 2) {\n\t\t\tfields[String(reply[i])] = String(reply[i + 1]);\n\t\t}\n\t\treturn fieldsOf(fields);\n\t}\n\tif (typeof reply !== 'object' || reply === null) return undefined;\n\t// No id: no item, whatever else a hash left half written holds.\n\treturn (reply as Fields)['id'] === undefined ? undefined : (reply as Fields);\n}\n\nconst field = (fields: Fields, name: string): string => fields[name] ?? '';\n\n/** The item a hash holds. Times are kept as JavaScript wrote them, so each reads back exactly. */\nexport function itemOf(fields: Fields): QueueItem {\n\tconst item: QueueItem = {\n\t\tid: field(fields, 'id'),\n\t\tfrom: field(fields, 'from'),\n\t\trecipients: JSON.parse(field(fields, 'recipients')) as RecipientState[],\n\t\tsize: Number(field(fields, 'size')),\n\t\tcreatedAt: Number(field(fields, 'created')),\n\t\tnextAttemptAt: Number(field(fields, 'next')),\n\t\tattempts: Number(field(fields, 'attempts')),\n\t\tdelayNotified: field(fields, 'delay') === '1',\n\t};\n\tconst owner = fields['owner'];\n\tconst expires = fields['expires'];\n\tif (owner === undefined || expires === undefined) return item;\n\treturn { ...item, lease: { owner, expiresAt: Number(expires) } };\n}\n\n/**\n * The ids a store makes, `crypto.randomUUID()`'s. Any other id is one no\n * item has, so no id a caller gives ever reaches a key but an item's.\n */\nconst ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\nexport const isId = (id: unknown): id is string =>\n\ttypeof id === 'string' && ID.test(id);\n\n/** A time as a script takes it: the string JavaScript writes, which reads back exactly. */\nexport const time = (value: number) => String(value);\n\n/** What `COMPLETE` records of an attempt: the item dropped, or its new state. */\nexport const outcomeOf = (item: QueueItem): string[] =>\n\tisDone(item)\n\t\t? ['drop']\n\t\t: [\n\t\t\t\t'record',\n\t\t\t\tJSON.stringify(item.recipients),\n\t\t\t\ttime(item.nextAttemptAt),\n\t\t\t\t`${item.attempts}`,\n\t\t\t\titem.delayNotified ? '1' : '0',\n\t\t\t];\n",
7
+ "/**\n * The Lua scripts a Redis store runs: each operation that writes is one\n * script, so Redis runs it whole, with no other command between its\n * steps. The fixed keys come as `KEYS`; an item's own keys are its\n * prefix (`ARGV[1]`), `item:` or `message:`, and its id.\n *\n * An item is a hash, `<prefix>item:<id>`; its message a string,\n * `<prefix>message:<id>`, written from the bytes given, as they are. Its\n * member in the sorted sets is its sequence number, sixteen digits, `:`,\n * and its id: Redis orders members of one score by their bytes, so items\n * equally due come oldest first. Times travel as the strings JavaScript\n * writes for them and are only compared here (`tonumber`), never written\n * back from Lua, so none loses a digit.\n */\n\n/** What every script starts with. */\nconst PRELUDE = `\nlocal prefix = ARGV[1]\nlocal function itemKey(member) return prefix .. 'item:' .. string.sub(member, 18) end\nlocal function seqOf(member) return tonumber(string.sub(member, 1, 16)) end\n`;\n\n/** For CLAIM: when an item's due time, or false when its hash is gone (evicted, deleted by hand). */\nconst NEXT_OF = `\nlocal function nextOf(member)\n\tlocal found = redis.call('HGET', itemKey(member), 'next')\n\tif found then return tonumber(found) end\n\t-- A member whose hash is gone is dropped, never leased.\n\tredis.call('ZREM', KEYS[1], member)\n\tredis.call('ZREM', KEYS[2], member)\n\tredis.call('ZREM', KEYS[3], member)\n\tredis.call('DEL', prefix .. 'message:' .. string.sub(member, 18))\n\treturn false\nend\n`;\n\n/** A script, and the SHA-1 `EVALSHA` names it by. */\nexport interface Script {\n\treadonly source: string;\n\treadonly sha: string;\n}\n\nconst script = (body: string): Script => {\n\tconst source = PRELUDE + body;\n\tconst sha = new Bun.CryptoHasher('sha1').update(source).digest('hex');\n\treturn { source, sha };\n};\n\n/**\n * KEYS schema; ARGV prefix, version. Writes the version on a new queue;\n * returns the one found.\n */\nexport const SCHEMA = script(`\nlocal found = redis.call('GET', KEYS[1])\nif found then return found end\nredis.call('SET', KEYS[1], ARGV[2])\nreturn ARGV[2]\n`);\n\n/**\n * KEYS items, ready, seq; ARGV prefix, id, from, recipients, size,\n * created, message, max (or ''). `{'full', n}` when `max` items are\n * there already, else `{'ok'}`.\n */\nexport const ADD = script(`\nlocal max = tonumber(ARGV[8])\nif max then\n\tlocal n = redis.call('ZCARD', KEYS[1])\n\tif n >= max then return {'full', tostring(n)} end\nend\nlocal member = string.format('%016d', redis.call('INCR', KEYS[3])) .. ':' .. ARGV[2]\nredis.call('HSET', prefix .. 'item:' .. ARGV[2], 'id', ARGV[2], 'from', ARGV[3],\n\t'recipients', ARGV[4], 'size', ARGV[5], 'created', ARGV[6], 'next', ARGV[6],\n\t'attempts', '0', 'delay', '0', 'rev', '0', 'member', member)\nredis.call('SET', prefix .. 'message:' .. ARGV[2], ARGV[7])\nredis.call('ZADD', KEYS[1], ARGV[6], member)\nredis.call('ZADD', KEYS[2], ARGV[6], member)\nreturn {'ok'}\n`);\n\n/**\n * KEYS ready, leases, items; ARGV prefix, owner, now, expiry. The\n * earliest due, then the oldest, of the items no lease holds and of those\n * whose lease expired, leased to the owner; its fields, or false. The\n * expired leases are read whole: there are as many as the leases a\n * crashed or stalled worker left behind, and the leased items an admin\n * moved later, until they are due.\n */\nexport const CLAIM = script(`${NEXT_OF}\nlocal now = tonumber(ARGV[3])\nlocal best, bestNext\nwhile best == nil do\n\tlocal first = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[3], 'LIMIT', 0, 1)[1]\n\tif not first then break end\n\tlocal nextAt = nextOf(first)\n\tif nextAt then\n\t\tbest = first\n\t\tbestNext = nextAt\n\tend\nend\nfor _, member in ipairs(redis.call('ZRANGEBYSCORE', KEYS[2], '-inf', ARGV[3])) do\n\tlocal nextAt = nextOf(member)\n\tif nextAt and nextAt <= now and (best == nil or nextAt < bestNext\n\t\tor (nextAt == bestNext and seqOf(member) < seqOf(best))) then\n\t\tbest = member\n\t\tbestNext = nextAt\n\tend\nend\nif best == nil then return false end\nredis.call('ZREM', KEYS[1], best)\nredis.call('ZADD', KEYS[2], ARGV[4], best)\nredis.call('HSET', itemKey(best), 'owner', ARGV[2], 'expires', ARGV[4])\nreturn redis.call('HGETALL', itemKey(best))\n`);\n\n/** KEYS leases; ARGV prefix, id, owner, expiry. 1 while the owner holds the lease, else 0. */\nexport const RENEW = script(`\nlocal key = prefix .. 'item:' .. ARGV[2]\nif redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end\nredis.call('HSET', key, 'expires', ARGV[4])\nredis.call('ZADD', KEYS[1], ARGV[4], redis.call('HGET', key, 'member'))\nreturn 1\n`);\n\n/**\n * KEYS items, ready, leases; ARGV prefix, id, owner, rev, then 'drop',\n * or 'record' and recipients, next, attempts, delay. 0 when the owner\n * does not hold the lease; -1 when another outcome was recorded since\n * the item was read at `rev`, to read it again; 1 once recorded, the\n * lease let go of, or the item and its message dropped.\n */\nexport const COMPLETE = script(`\nlocal key = prefix .. 'item:' .. ARGV[2]\nif redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end\nif redis.call('HGET', key, 'rev') ~= ARGV[4] then return -1 end\nlocal member = redis.call('HGET', key, 'member')\nredis.call('ZREM', KEYS[3], member)\nif ARGV[5] == 'drop' then\n\tredis.call('ZREM', KEYS[1], member)\n\tredis.call('ZREM', KEYS[2], member)\n\tredis.call('DEL', key, prefix .. 'message:' .. ARGV[2])\n\treturn 1\nend\nredis.call('HSET', key, 'recipients', ARGV[6], 'next', ARGV[7],\n\t'attempts', ARGV[8], 'delay', ARGV[9])\nredis.call('HINCRBY', key, 'rev', 1)\nredis.call('HDEL', key, 'owner', 'expires')\nredis.call('ZADD', KEYS[1], ARGV[7], member)\nredis.call('ZADD', KEYS[2], ARGV[7], member)\nreturn 1\n`);\n\n/**\n * KEYS items, ready, leases; ARGV prefix, id, at, owner (or ''). With an\n * owner, only while it holds the lease, which it lets go of; without,\n * the lease stays. 1 when moved, else 0.\n */\nexport const RESCHEDULE = script(`\nlocal key = prefix .. 'item:' .. ARGV[2]\nlocal member = redis.call('HGET', key, 'member')\nif not member then return 0 end\nlocal owner = redis.call('HGET', key, 'owner')\nif ARGV[4] ~= '' then\n\tif owner ~= ARGV[4] then return 0 end\n\tredis.call('HDEL', key, 'owner', 'expires')\n\tredis.call('ZREM', KEYS[3], member)\n\towner = false\nend\nredis.call('HSET', key, 'next', ARGV[3])\nredis.call('ZADD', KEYS[1], ARGV[3], member)\nif not owner then redis.call('ZADD', KEYS[2], ARGV[3], member) end\nreturn 1\n`);\n\n/** KEYS items, ready, leases; ARGV prefix, id. The item's fields as it stood, or false; dropped with its message. */\nexport const CANCEL = script(`\nlocal key = prefix .. 'item:' .. ARGV[2]\nlocal fields = redis.call('HGETALL', key)\nif #fields == 0 then return false end\nlocal member = redis.call('HGET', key, 'member')\nredis.call('ZREM', KEYS[1], member)\nredis.call('ZREM', KEYS[2], member)\nredis.call('ZREM', KEYS[3], member)\nredis.call('DEL', key, prefix .. 'message:' .. ARGV[2])\nreturn fields\n`);\n\n/** KEYS items; ARGV prefix, first, last. A page of items, each its fields, in one read. */\nexport const LIST = script(`\nlocal out = {}\nfor _, member in ipairs(redis.call('ZRANGE', KEYS[1], ARGV[2], ARGV[3])) do\n\tout[#out + 1] = redis.call('HGETALL', itemKey(member))\nend\nreturn out\n`);\n",
8
+ "import { QueueError } from '../errors';\nimport type { Keys } from './connect';\nimport type { RedisQueueClient } from './options';\nimport { SCHEMA, type Script } from './scripts';\n\n/** Redis no longer has the script: restarted, failed over, or `SCRIPT FLUSH`ed. */\nconst isNoScript = (error: unknown) =>\n\terror instanceof Error && error.message.startsWith('NOSCRIPT');\n\n/**\n * Runs a script by its SHA-1 (`EVALSHA`), or, when Redis does not have\n * it, by its source (`EVAL`), which also keeps it for the next call.\n * Either way Redis runs it whole.\n */\nexport async function run(\n\tclient: RedisQueueClient,\n\tscript: Script,\n\tkeys: string[],\n\targs: (string | Uint8Array)[],\n): Promise<unknown> {\n\tconst rest = [`${keys.length}`, ...keys, ...args];\n\ttry {\n\t\treturn await client.send('EVALSHA', [script.sha, ...rest]);\n\t} catch (error) {\n\t\tif (!isNoScript(error)) throw error;\n\t\treturn client.send('EVAL', [script.source, ...rest]);\n\t}\n}\n\n/** The layout of the keys; a change is a new version, and a newer one is refused. */\nconst LAYOUT = 1;\n\n/** Reads the layout version, writing it on a new queue; a newer one is `INVALID`. */\nexport async function checkLayout(\n\tclient: RedisQueueClient,\n\tkeys: Keys,\n): Promise<void> {\n\tconst found = Number(\n\t\tawait run(client, SCHEMA, [keys.schema], [keys.prefix, `${LAYOUT}`]),\n\t);\n\tif (!Number.isInteger(found) || found < 1) {\n\t\tthrow new QueueError(\n\t\t\t'INVALID',\n\t\t\t`The key ${keys.schema} does not hold a layout version: is the prefix another application's?`,\n\t\t);\n\t}\n\tif (found > LAYOUT) {\n\t\tthrow new QueueError(\n\t\t\t'INVALID',\n\t\t\t`The database is at schema version ${found}, newer than this store's ${LAYOUT}`,\n\t\t);\n\t}\n}\n",
9
+ "import {\n\tapplyAttempt,\n\tcheckClaim,\n\tcheckList,\n\tcheckMaxItems,\n\tcheckNewItem,\n\tcheckOwner,\n\tcheckResult,\n\tcheckTime,\n\tnewItem,\n} from '../contract/checks';\nimport type { QueueStore } from '../contract/queue-store';\nimport type {\n\tAddOptions,\n\tAttemptResult,\n\tClaimRequest,\n\tNewQueueItem,\n\tQueueItem,\n\tQueueListOptions,\n} from '../contract/types';\nimport { QueueError } from '../errors';\nimport { masked } from '../masked';\nimport { isStorable } from '../text';\nimport { type Connection, connect, type Keys } from './connect';\nimport { fieldsOf, isId, itemOf, outcomeOf, time } from './items';\nimport type { RedisQueueClient, RedisQueueStoreOptions } from './options';\nimport { checkLayout, run } from './run';\nimport {\n\tADD,\n\tCANCEL,\n\tCLAIM,\n\tCOMPLETE,\n\tLIST,\n\tRENEW,\n\tRESCHEDULE,\n\ttype Script,\n} from './scripts';\n\n/**\n * A `QueueStore` on Redis through Bun's own `Bun.RedisClient`: several\n * instances, on one machine or many, share one queue. Every operation\n * that writes is one Lua script, which Redis runs whole, so two\n * instances never take the same item. The due items are in sorted sets,\n * each item a hash, its message a string written byte for byte. One\n * Redis, or a primary with replicas — not Redis Cluster.\n */\nexport class RedisQueueStore implements QueueStore {\n\treadonly #client: RedisQueueClient;\n\treadonly #owned: boolean;\n\treadonly #keys: Keys;\n\treadonly #password: string;\n\t#ready: Promise<void> | undefined;\n\t#closed = false;\n\n\tprivate constructor({ client, owned, keys, password }: Connection) {\n\t\tthis.#client = client;\n\t\tthis.#owned = owned;\n\t\tthis.#keys = keys;\n\t\tthis.#password = password;\n\t}\n\n\t/** Checks the options; connects to nothing until the first call. What is wrong is `INVALID`. */\n\tstatic open(options: RedisQueueStoreOptions): RedisQueueStore {\n\t\treturn new RedisQueueStore(connect(options));\n\t}\n\n\t/** Closes the client the store opened for a URL, never one it was given; closing twice is fine. */\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tif (this.#owned) await this.#client.close();\n\t}\n\n\t/**\n\t * Reads the layout version, writing it on a new queue; once per\n\t * store. A failure is `INVALID`, and the next call tries again.\n\t */\n\t#setUp(): Promise<void> {\n\t\tthis.#ready ??= checkLayout(this.#client, this.#keys).catch((error) => {\n\t\t\tthis.#ready = undefined;\n\t\t\tif (error instanceof QueueError) throw error;\n\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new QueueError(\n\t\t\t\t'INVALID',\n\t\t\t\t`The Redis queue cannot be set up: ${masked(reason, this.#password)}`,\n\t\t\t);\n\t\t});\n\t\treturn this.#ready;\n\t}\n\n\t/** The client, once the layout is checked; `CLOSED` after `close()`. */\n\tasync #redis(): Promise<RedisQueueClient> {\n\t\tif (this.#closed) throw closed();\n\t\tawait this.#setUp();\n\t\treturn this.#client;\n\t}\n\n\tasync #run(script: Script, keys: string[], args: (string | Uint8Array)[]) {\n\t\tconst client = await this.#redis();\n\t\treturn run(client, script, keys, [this.#keys.prefix, ...args]);\n\t}\n\n\tasync add(item: NewQueueItem, options: AddOptions = {}): Promise<QueueItem> {\n\t\tcheckNewItem(item);\n\t\tconst max = checkMaxItems(options.maxItems);\n\t\tconst added = newItem(crypto.randomUUID(), item);\n\t\tconst k = this.#keys;\n\t\tconst reply = (await this.#run(\n\t\t\tADD,\n\t\t\t[k.items, k.ready, k.seq],\n\t\t\t[\n\t\t\t\tadded.id,\n\t\t\t\tadded.from,\n\t\t\t\tJSON.stringify(added.recipients),\n\t\t\t\t`${added.size}`,\n\t\t\t\ttime(added.createdAt),\n\t\t\t\titem.message,\n\t\t\t\tmax === undefined ? '' : `${max}`,\n\t\t\t],\n\t\t)) as string[];\n\t\tif (reply[0] === 'full') {\n\t\t\tthrow new QueueError(\n\t\t\t\t'QUEUE_FULL',\n\t\t\t\t`The queue holds ${reply[1]} items, its limit`,\n\t\t\t);\n\t\t}\n\t\treturn added;\n\t}\n\n\tasync get(id: string): Promise<QueueItem | undefined> {\n\t\tif (!isId(id)) return undefined;\n\t\tconst client = await this.#redis();\n\t\tconst fields = fieldsOf(\n\t\t\tawait client.send('HGETALL', [`${this.#keys.prefix}item:${id}`]),\n\t\t);\n\t\treturn fields ? itemOf(fields) : undefined;\n\t}\n\n\tasync list(options?: QueueListOptions): Promise<QueueItem[]> {\n\t\tconst { offset, limit } = checkList(options);\n\t\tconst pages = (await this.#run(\n\t\t\tLIST,\n\t\t\t[this.#keys.items],\n\t\t\t[`${offset}`, `${offset + limit - 1}`],\n\t\t)) as unknown[];\n\t\treturn pages.flatMap((reply) => {\n\t\t\tconst fields = fieldsOf(reply);\n\t\t\treturn fields ? [itemOf(fields)] : [];\n\t\t});\n\t}\n\n\tasync count(): Promise<number> {\n\t\tconst client = await this.#redis();\n\t\treturn Number(await client.send('ZCARD', [this.#keys.items]));\n\t}\n\n\tasync readMessage(id: string): Promise<Uint8Array | undefined> {\n\t\tif (!isId(id)) return undefined;\n\t\tconst client = await this.#redis();\n\t\tconst bytes = await client.getBuffer(`${this.#keys.prefix}message:${id}`);\n\t\treturn bytes === null ? undefined : new Uint8Array(bytes);\n\t}\n\n\tasync claim(request: ClaimRequest): Promise<QueueItem | undefined> {\n\t\tcheckClaim(request);\n\t\tconst { owner, now, leaseMs } = request;\n\t\tconst k = this.#keys;\n\t\tconst fields = fieldsOf(\n\t\t\tawait this.#run(\n\t\t\t\tCLAIM,\n\t\t\t\t[k.ready, k.leases, k.items],\n\t\t\t\t[owner, time(now), time(now + leaseMs)],\n\t\t\t),\n\t\t);\n\t\treturn fields ? itemOf(fields) : undefined;\n\t}\n\n\tasync renew(id: string, owner: string, expiresAt: number): Promise<boolean> {\n\t\tcheckOwner(owner);\n\t\tcheckTime('expiresAt', expiresAt);\n\t\tif (!isId(id)) return false;\n\t\tconst k = this.#keys;\n\t\tconst done = await this.#run(\n\t\t\tRENEW,\n\t\t\t[k.leases],\n\t\t\t[id, owner, time(expiresAt)],\n\t\t);\n\t\treturn Number(done) === 1;\n\t}\n\n\tasync complete(\n\t\tid: string,\n\t\towner: string,\n\t\tresult: AttemptResult,\n\t): Promise<QueueItem | undefined> {\n\t\tcheckOwner(owner);\n\t\tcheckResult(result);\n\t\tif (!isId(id)) return undefined;\n\t\tconst k = this.#keys;\n\t\tconst client = await this.#redis();\n\t\tfor (let read = 0; read < MAX_READS; read++) {\n\t\t\tconst fields = fieldsOf(\n\t\t\t\tawait client.send('HGETALL', [`${k.prefix}item:${id}`]),\n\t\t\t);\n\t\t\tif (!fields || fields['owner'] !== owner) return undefined;\n\t\t\tconst rev = fields['rev']; // none: damaged by hand, nothing recorded\n\t\t\tif (rev === undefined) return undefined;\n\t\t\tconst item = applyAttempt(itemOf(fields), result);\n\t\t\tconst done = Number(\n\t\t\t\tawait this.#run(\n\t\t\t\t\tCOMPLETE,\n\t\t\t\t\t[k.items, k.ready, k.leases],\n\t\t\t\t\t[id, owner, rev, ...outcomeOf(item)],\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (done === 1) return item;\n\t\t\tif (done === 0) return undefined;\n\t\t}\n\t\treturn undefined; // outrun MAX_READS times: told as a lease lost\n\t}\n\n\tasync reschedule(id: string, at: number, owner?: string): Promise<boolean> {\n\t\tcheckTime('at', at);\n\t\t// As PostgreSQL: an id no store could hold is unknown before the owner is checked.\n\t\tif (!isStorable(id)) return false;\n\t\tif (owner !== undefined) checkOwner(owner);\n\t\tif (!isId(id)) return false;\n\t\tconst k = this.#keys;\n\t\tconst done = await this.#run(\n\t\t\tRESCHEDULE,\n\t\t\t[k.items, k.ready, k.leases],\n\t\t\t[id, time(at), owner ?? ''],\n\t\t);\n\t\treturn Number(done) === 1;\n\t}\n\n\tasync cancel(id: string): Promise<QueueItem | undefined> {\n\t\tif (!isId(id)) return undefined;\n\t\tconst k = this.#keys;\n\t\tconst fields = fieldsOf(\n\t\t\tawait this.#run(CANCEL, [k.items, k.ready, k.leases], [id]),\n\t\t);\n\t\treturn fields ? itemOf(fields) : undefined;\n\t}\n}\n\n/** How often `complete` reads an item, another outcome recorded since each read. */\nconst MAX_READS = 8;\n\nconst closed = () => new QueueError('CLOSED', 'The queue store is closed');\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "63554783C4CC726464756E2164756E21",
13
+ "names": []
14
+ }
@@ -0,0 +1,13 @@
1
+ import type { QueueItem } from '../contract/types';
2
+ /** An item's hash, as `HGETALL` gives it: a map, or a script's flat list of names and values. */
3
+ export type Fields = Readonly<Record<string, string>>;
4
+ /** A script's `HGETALL`, `[name, value, …]`, as a map; a map as it is. */
5
+ export declare function fieldsOf(reply: unknown): Fields | undefined;
6
+ /** The item a hash holds. Times are kept as JavaScript wrote them, so each reads back exactly. */
7
+ export declare function itemOf(fields: Fields): QueueItem;
8
+ export declare const isId: (id: unknown) => id is string;
9
+ /** A time as a script takes it: the string JavaScript writes, which reads back exactly. */
10
+ export declare const time: (value: number) => string;
11
+ /** What `COMPLETE` records of an attempt: the item dropped, or its new state. */
12
+ export declare const outcomeOf: (item: QueueItem) => string[];
13
+ //# sourceMappingURL=items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.d.ts","sourceRoot":"","sources":["../../src/redis/items.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAEnE,iGAAiG;AACjG,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEtD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAW3D;AAID,kGAAkG;AAClG,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAehD;AAQD,eAAO,MAAM,IAAI,GAAI,IAAI,OAAO,KAAG,EAAE,IAAI,MACH,CAAC;AAEvC,2FAA2F;AAC3F,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,WAAkB,CAAC;AAErD,iFAAiF;AACjF,eAAO,MAAM,SAAS,GAAI,MAAM,SAAS,KAAG,MAAM,EAS9C,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Where a Redis queue keeps its items, and the client it reaches them
3
+ * through.
4
+ *
5
+ * Typed by shape rather than as `Bun.RedisClient`, so the declarations a
6
+ * consumer's `tsc` reads from `@bumail/queue/redis` name no global of
7
+ * Bun's: a `Bun.RedisClient` fits `RedisQueueClient` (`open.spec.ts`
8
+ * checks it, though only because method parameters are compared both
9
+ * ways: Bun declares `args: string[]`; that it writes a `Uint8Array` as
10
+ * its bytes is what the Redis specs show), and so does any client with
11
+ * these methods.
12
+ */
13
+ /** A Redis client, such as `new Bun.RedisClient(url)`. */
14
+ export interface RedisQueueClient {
15
+ /**
16
+ * One command; resolves with its reply, a bulk string decoded as
17
+ * UTF-8. An argument may be bytes (`Bun.RedisClient` sends a
18
+ * `Uint8Array` as it is), which is how a message is written.
19
+ */
20
+ send(command: string, args: (string | Uint8Array)[]): Promise<unknown>;
21
+ /** A string value as its bytes, or `null` when there is no such key: how a message is read. */
22
+ getBuffer(key: string): Promise<Uint8Array | null>;
23
+ close(): unknown;
24
+ }
25
+ interface KeyPrefixOption {
26
+ /**
27
+ * Put before each key, so several queues, or a queue and the
28
+ * application, share one Redis database: lowercase letters, digits,
29
+ * `_`, `:`, `.` and `-`, starting with a letter, at most 40. Default
30
+ * `bumail:queue:`.
31
+ */
32
+ readonly keyPrefix?: string;
33
+ }
34
+ /** The application's client, which it configures and closes itself. */
35
+ export interface RedisQueueClientOptions extends KeyPrefixOption {
36
+ readonly client: RedisQueueClient;
37
+ readonly url?: undefined;
38
+ }
39
+ /** A `redis://` (or `rediss://`, …) URL, for which the store opens a client of its own, closed by `close()`. */
40
+ export interface RedisQueueUrlOptions extends KeyPrefixOption {
41
+ readonly url: string | URL;
42
+ readonly client?: undefined;
43
+ }
44
+ /** `client` or `url`, never both. */
45
+ export type RedisQueueStoreOptions = RedisQueueClientOptions | RedisQueueUrlOptions;
46
+ export {};
47
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/redis/options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,+FAA+F;IAC/F,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,KAAK,IAAI,OAAO,CAAC;CACjB;AAED,UAAU,eAAe;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uEAAuE;AACvE,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC/D,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,gHAAgH;AAChH,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC5D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,qCAAqC;AACrC,MAAM,MAAM,sBAAsB,GAC/B,uBAAuB,GACvB,oBAAoB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Keys } from './connect';
2
+ import type { RedisQueueClient } from './options';
3
+ import { type Script } from './scripts';
4
+ /**
5
+ * Runs a script by its SHA-1 (`EVALSHA`), or, when Redis does not have
6
+ * it, by its source (`EVAL`), which also keeps it for the next call.
7
+ * Either way Redis runs it whole.
8
+ */
9
+ export declare function run(client: RedisQueueClient, script: Script, keys: string[], args: (string | Uint8Array)[]): Promise<unknown>;
10
+ /** Reads the layout version, writing it on a new queue; a newer one is `INVALID`. */
11
+ export declare function checkLayout(client: RedisQueueClient, keys: Keys): Promise<void>;
12
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/redis/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAMhD;;;;GAIG;AACH,wBAAsB,GAAG,CACxB,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,GAC3B,OAAO,CAAC,OAAO,CAAC,CAQlB;AAKD,qFAAqF;AACrF,wBAAsB,WAAW,CAChC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,IAAI,GACR,OAAO,CAAC,IAAI,CAAC,CAgBf"}