@bumail/queue 0.1.0 → 0.3.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 (66) hide show
  1. package/README.md +71 -5
  2. package/dist/chunks/{index-xhsw4xcz.js → index-vg971acr.js} +70 -6
  3. package/dist/chunks/index-vg971acr.js.map +12 -0
  4. package/dist/chunks/index-y3n1w2az.js +18 -0
  5. package/dist/chunks/index-y3n1w2az.js.map +10 -0
  6. package/dist/contract/checks.d.ts +6 -0
  7. package/dist/contract/checks.d.ts.map +1 -1
  8. package/dist/errors.d.ts +2 -1
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/index.js +86 -55
  11. package/dist/index.js.map +8 -8
  12. package/dist/masked.d.ts +7 -0
  13. package/dist/masked.d.ts.map +1 -0
  14. package/dist/memory/index.js +3 -5
  15. package/dist/memory/index.js.map +2 -2
  16. package/dist/postgres/connect.d.ts +17 -0
  17. package/dist/postgres/connect.d.ts.map +1 -0
  18. package/dist/postgres/index.d.ts +2 -0
  19. package/dist/postgres/index.d.ts.map +1 -0
  20. package/dist/postgres/index.js +374 -0
  21. package/dist/postgres/index.js.map +14 -0
  22. package/dist/postgres/options.d.ts +37 -0
  23. package/dist/postgres/options.d.ts.map +1 -0
  24. package/dist/postgres/rows.d.ts +21 -0
  25. package/dist/postgres/rows.d.ts.map +1 -0
  26. package/dist/postgres/schema.d.ts +20 -0
  27. package/dist/postgres/schema.d.ts.map +1 -0
  28. package/dist/postgres/statements.d.ts +54 -0
  29. package/dist/postgres/statements.d.ts.map +1 -0
  30. package/dist/postgres/store.d.ts +38 -0
  31. package/dist/postgres/store.d.ts.map +1 -0
  32. package/dist/queue/deliver.d.ts +7 -0
  33. package/dist/queue/deliver.d.ts.map +1 -1
  34. package/dist/queue/lease.d.ts +41 -0
  35. package/dist/queue/lease.d.ts.map +1 -0
  36. package/dist/queue/outcome.d.ts.map +1 -1
  37. package/dist/queue/settings/index.d.ts.map +1 -1
  38. package/dist/queue/worker.d.ts.map +1 -1
  39. package/dist/redis/connect.d.ts +25 -0
  40. package/dist/redis/connect.d.ts.map +1 -0
  41. package/dist/redis/index.d.ts +3 -0
  42. package/dist/redis/index.d.ts.map +1 -0
  43. package/dist/redis/index.js +430 -0
  44. package/dist/redis/index.js.map +14 -0
  45. package/dist/redis/items.d.ts +13 -0
  46. package/dist/redis/items.d.ts.map +1 -0
  47. package/dist/redis/options.d.ts +47 -0
  48. package/dist/redis/options.d.ts.map +1 -0
  49. package/dist/redis/run.d.ts +12 -0
  50. package/dist/redis/run.d.ts.map +1 -0
  51. package/dist/redis/scripts.d.ts +60 -0
  52. package/dist/redis/scripts.d.ts.map +1 -0
  53. package/dist/redis/store.d.ts +30 -0
  54. package/dist/redis/store.d.ts.map +1 -0
  55. package/dist/sqlite/index.js +3 -5
  56. package/dist/sqlite/index.js.map +2 -2
  57. package/dist/text.d.ts +9 -3
  58. package/dist/text.d.ts.map +1 -1
  59. package/docs/README.md +1 -1
  60. package/docs/guide.md +314 -7
  61. package/docs/roadmap.md +73 -19
  62. package/docs/troubleshooting.md +317 -7
  63. package/package.json +15 -3
  64. package/dist/chunks/index-p4k4rjb7.js +0 -15
  65. package/dist/chunks/index-p4k4rjb7.js.map +0 -10
  66. package/dist/chunks/index-xhsw4xcz.js.map +0 -10
@@ -1,5 +1,6 @@
1
1
  import type { QueueItem } from '../contract/types';
2
2
  import type { Events } from './events';
3
+ import type { Lapse } from './lease';
3
4
  import type { KeyedLimiter } from './limiter';
4
5
  import { type NotifyContext } from './notify';
5
6
  export interface DeliveryContext extends NotifyContext {
@@ -9,6 +10,12 @@ export interface DeliveryContext extends NotifyContext {
9
10
  readonly stopping: () => boolean;
10
11
  /** The outcome is recorded: the lease needs no more renewal. */
11
12
  readonly recorded: () => void;
13
+ /**
14
+ * Whether the lease could have passed to another worker by `now`: a
15
+ * renewal found it taken, or its last expiry is past. `undefined` while
16
+ * it surely held.
17
+ */
18
+ readonly leaseLapsed: (now: number) => Promise<Lapse | undefined>;
12
19
  }
13
20
  /**
14
21
  * Delivers a claimed item: one session per recipient domain, at most
@@ -1 +1 @@
1
- {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/queue/deliver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAGpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,UAAU,CAAC;AAKtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC9B;AA0FD;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,GACb,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/queue/deliver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAGpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,UAAU,CAAC;AAKtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CAClE;AAwGD;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,SAAS,GACb,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -0,0 +1,41 @@
1
+ import type { QueueItem } from '../contract/types';
2
+ import type { NotifyContext } from './notify';
3
+ /**
4
+ * How a lease was lost. `taken`: a renewal found the item still in the
5
+ * store, held by another worker or by none. `expired`: the lease's last
6
+ * expiry passed, so another worker could claim it.
7
+ */
8
+ export type Lapse = 'taken' | 'expired';
9
+ /** What a worker knows of its lease on an item it delivers. */
10
+ export interface LeaseState {
11
+ readonly item: QueueItem;
12
+ /** The outcome is recorded, or a renewal was refused: no more renewals count. */
13
+ done: boolean;
14
+ /** The lease's last expiry this worker set: the claim's, then each renewal's. */
15
+ expiresAt: number;
16
+ /** A renewal was refused while the item was still in the store. */
17
+ taken: boolean;
18
+ /** The look a refused renewal takes at the item. */
19
+ looking: Promise<void>;
20
+ }
21
+ export declare function leaseOf(ctx: NotifyContext, item: QueueItem): LeaseState;
22
+ /**
23
+ * One renewal. A renewal that is held keeps its expiry, even one that
24
+ * lands after the outcome is recorded. One refused while the item is
25
+ * still there — held by another worker or by none — says so once, and
26
+ * ends the renewals; one refused for an item gone says nothing and ends
27
+ * them: a cancel, or another worker that finished it, which `lapsed` tells
28
+ * apart by the expiry. One that fails (the store busy, unreachable) says
29
+ * so, and the next one tries again: the lease may still be held.
30
+ * Resolves `false` once no more renewals are wanted.
31
+ */
32
+ export declare function renew(ctx: NotifyContext, lease: LeaseState): Promise<boolean>;
33
+ /**
34
+ * Whether the lease could have passed to another worker by `now`: a
35
+ * renewal found it taken, or its last expiry is past. `undefined` while
36
+ * it surely held, by this worker's clock.
37
+ */
38
+ export declare function lapsed(lease: LeaseState, now: number): Promise<Lapse | undefined>;
39
+ /** Resolves once the look a refused renewal took at the item is over. */
40
+ export declare const settled: (lease: LeaseState) => Promise<void>;
41
+ //# sourceMappingURL=lease.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../src/queue/lease.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;AAExC,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;IACd,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CASvE;AAiBD;;;;;;;;;GASG;AACH,wBAAsB,KAAK,CAC1B,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC,CAmBlB;AAED;;;;GAIG;AACH,wBAAsB,MAAM,CAC3B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAI5B;AAED,yEAAyE;AACzE,eAAO,MAAM,OAAO,GAAI,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../src/queue/outcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,cAAc,EACd,SAAS,EAET,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGrE;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,SAGC,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,OACQ,CAAC;AAKvD,8CAA8C;AAC9C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,UAAU,CAWZ;AAsCD,sFAAsF;AACtF,wBAAgB,UAAU,CACzB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,GACT,eAAe,EAAE,CA0BnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,eAAe,EAAE,CAuBnB"}
1
+ {"version":3,"file":"outcome.d.ts","sourceRoot":"","sources":["../../src/queue/outcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,cAAc,EACd,SAAS,EAET,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGrE;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,SAGC,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,OACQ,CAAC;AAKvD,8CAA8C;AAC9C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,UAAU,CAWZ;AAsCD,sFAAsF;AACtF,wBAAgB,UAAU,CACzB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,GACT,eAAe,EAAE,CA6BnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACX,eAAe,EAAE,CAuBnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/queue/settings/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,OAAO,CAAC;AAChD,OAAO,EACN,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,MAAM,UAAU,CAAC;AAKlB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAE1D,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B;AAKD,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAqD1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/queue/settings/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,OAAO,CAAC;AAChD,OAAO,EACN,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,MAAM,UAAU,CAAC;AAKlB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAE1D,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B;AAKD,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAkD1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/queue/worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,MAAM;;gBAaN,GAAG,EAAE,aAAa;IAsE9B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAgCnC,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IAKZ,KAAK,IAAI,IAAI;IAqCb;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAa3B"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/queue/worker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,MAAM;;gBAaN,GAAG,EAAE,aAAa;IAsD9B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAgCnC,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IAKZ,KAAK,IAAI,IAAI;IAqCb;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAa3B"}
@@ -0,0 +1,25 @@
1
+ import type { RedisQueueClient, RedisQueueStoreOptions } from './options';
2
+ /** The keys of one queue, each its prefix and a fixed name. */
3
+ export interface Keys {
4
+ readonly prefix: string;
5
+ /** Every item, scored by its next attempt: `list` and `count`. */
6
+ readonly items: string;
7
+ /** The items no lease holds, scored by their next attempt: what a claim takes first. */
8
+ readonly ready: string;
9
+ /** The leased items, scored by when their leases expire. */
10
+ readonly leases: string;
11
+ /** The counter that orders items equally due. */
12
+ readonly seq: string;
13
+ /** The layout version. */
14
+ readonly schema: string;
15
+ }
16
+ /** The client a store uses, and whether it opened it (and so closes it). */
17
+ export interface Connection {
18
+ readonly client: RedisQueueClient;
19
+ readonly owned: boolean;
20
+ readonly keys: Keys;
21
+ }
22
+ export declare function keysOf(value: unknown): Keys;
23
+ /** Checks the options and opens a client for a URL; connects to nothing yet. */
24
+ export declare function connect(options: RedisQueueStoreOptions): Connection;
25
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/redis/connect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAE1E,+DAA+D;AAC/D,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACpB;AAWD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAe3C;AAiDD,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,UAAU,CAYnE"}
@@ -0,0 +1,3 @@
1
+ export type { RedisQueueClient, RedisQueueClientOptions, RedisQueueStoreOptions, RedisQueueUrlOptions, } from './options';
2
+ export { RedisQueueStore } from './store';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,430 @@
1
+ import {
2
+ masked
3
+ } from "../chunks/index-y3n1w2az.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 };
76
+ }
77
+ return { client: clientFor(urlOf(url)), owned: true, keys };
78
+ }
79
+
80
+ // src/redis/items.ts
81
+ function fieldsOf(reply) {
82
+ if (Array.isArray(reply)) {
83
+ const fields = {};
84
+ for (let i = 0;i + 1 < reply.length; i += 2) {
85
+ fields[String(reply[i])] = String(reply[i + 1]);
86
+ }
87
+ return fieldsOf(fields);
88
+ }
89
+ if (typeof reply !== "object" || reply === null)
90
+ return;
91
+ return reply["id"] === undefined ? undefined : reply;
92
+ }
93
+ var field = (fields, name) => fields[name] ?? "";
94
+ function itemOf(fields) {
95
+ const item = {
96
+ id: field(fields, "id"),
97
+ from: field(fields, "from"),
98
+ recipients: JSON.parse(field(fields, "recipients")),
99
+ size: Number(field(fields, "size")),
100
+ createdAt: Number(field(fields, "created")),
101
+ nextAttemptAt: Number(field(fields, "next")),
102
+ attempts: Number(field(fields, "attempts")),
103
+ delayNotified: field(fields, "delay") === "1"
104
+ };
105
+ const owner = fields["owner"];
106
+ const expires = fields["expires"];
107
+ if (owner === undefined || expires === undefined)
108
+ return item;
109
+ return { ...item, lease: { owner, expiresAt: Number(expires) } };
110
+ }
111
+ var ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
112
+ var isId = (id) => typeof id === "string" && ID.test(id);
113
+ var time = (value) => String(value);
114
+ var outcomeOf = (item) => isDone(item) ? ["drop"] : [
115
+ "record",
116
+ JSON.stringify(item.recipients),
117
+ time(item.nextAttemptAt),
118
+ `${item.attempts}`,
119
+ item.delayNotified ? "1" : "0"
120
+ ];
121
+
122
+ // src/redis/scripts.ts
123
+ var PRELUDE = `
124
+ local prefix = ARGV[1]
125
+ local function itemKey(member) return prefix .. 'item:' .. string.sub(member, 18) end
126
+ local function seqOf(member) return tonumber(string.sub(member, 1, 16)) end
127
+ `;
128
+ var NEXT_OF = `
129
+ local function nextOf(member)
130
+ local found = redis.call('HGET', itemKey(member), 'next')
131
+ if found then return tonumber(found) end
132
+ -- A member whose hash is gone is dropped, never leased.
133
+ redis.call('ZREM', KEYS[1], member)
134
+ redis.call('ZREM', KEYS[2], member)
135
+ redis.call('ZREM', KEYS[3], member)
136
+ redis.call('DEL', prefix .. 'message:' .. string.sub(member, 18))
137
+ return false
138
+ end
139
+ `;
140
+ var script = (body) => {
141
+ const source = PRELUDE + body;
142
+ const sha = new Bun.CryptoHasher("sha1").update(source).digest("hex");
143
+ return { source, sha };
144
+ };
145
+ var SCHEMA = script(`
146
+ local found = redis.call('GET', KEYS[1])
147
+ if found then return found end
148
+ redis.call('SET', KEYS[1], ARGV[2])
149
+ return ARGV[2]
150
+ `);
151
+ var ADD = script(`
152
+ local max = tonumber(ARGV[8])
153
+ if max then
154
+ local n = redis.call('ZCARD', KEYS[1])
155
+ if n >= max then return {'full', tostring(n)} end
156
+ end
157
+ local member = string.format('%016d', redis.call('INCR', KEYS[3])) .. ':' .. ARGV[2]
158
+ redis.call('HSET', prefix .. 'item:' .. ARGV[2], 'id', ARGV[2], 'from', ARGV[3],
159
+ 'recipients', ARGV[4], 'size', ARGV[5], 'created', ARGV[6], 'next', ARGV[6],
160
+ 'attempts', '0', 'delay', '0', 'rev', '0', 'member', member)
161
+ redis.call('SET', prefix .. 'message:' .. ARGV[2], ARGV[7])
162
+ redis.call('ZADD', KEYS[1], ARGV[6], member)
163
+ redis.call('ZADD', KEYS[2], ARGV[6], member)
164
+ return {'ok'}
165
+ `);
166
+ var CLAIM = script(`${NEXT_OF}
167
+ local now = tonumber(ARGV[3])
168
+ local best, bestNext
169
+ while best == nil do
170
+ local first = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[3], 'LIMIT', 0, 1)[1]
171
+ if not first then break end
172
+ local nextAt = nextOf(first)
173
+ if nextAt then
174
+ best = first
175
+ bestNext = nextAt
176
+ end
177
+ end
178
+ for _, member in ipairs(redis.call('ZRANGEBYSCORE', KEYS[2], '-inf', ARGV[3])) do
179
+ local nextAt = nextOf(member)
180
+ if nextAt and nextAt <= now and (best == nil or nextAt < bestNext
181
+ or (nextAt == bestNext and seqOf(member) < seqOf(best))) then
182
+ best = member
183
+ bestNext = nextAt
184
+ end
185
+ end
186
+ if best == nil then return false end
187
+ redis.call('ZREM', KEYS[1], best)
188
+ redis.call('ZADD', KEYS[2], ARGV[4], best)
189
+ redis.call('HSET', itemKey(best), 'owner', ARGV[2], 'expires', ARGV[4])
190
+ return redis.call('HGETALL', itemKey(best))
191
+ `);
192
+ var RENEW = script(`
193
+ local key = prefix .. 'item:' .. ARGV[2]
194
+ if redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end
195
+ redis.call('HSET', key, 'expires', ARGV[4])
196
+ redis.call('ZADD', KEYS[1], ARGV[4], redis.call('HGET', key, 'member'))
197
+ return 1
198
+ `);
199
+ var COMPLETE = script(`
200
+ local key = prefix .. 'item:' .. ARGV[2]
201
+ if redis.call('HGET', key, 'owner') ~= ARGV[3] then return 0 end
202
+ if redis.call('HGET', key, 'rev') ~= ARGV[4] then return -1 end
203
+ local member = redis.call('HGET', key, 'member')
204
+ redis.call('ZREM', KEYS[3], member)
205
+ if ARGV[5] == 'drop' then
206
+ redis.call('ZREM', KEYS[1], member)
207
+ redis.call('ZREM', KEYS[2], member)
208
+ redis.call('DEL', key, prefix .. 'message:' .. ARGV[2])
209
+ return 1
210
+ end
211
+ redis.call('HSET', key, 'recipients', ARGV[6], 'next', ARGV[7],
212
+ 'attempts', ARGV[8], 'delay', ARGV[9])
213
+ redis.call('HINCRBY', key, 'rev', 1)
214
+ redis.call('HDEL', key, 'owner', 'expires')
215
+ redis.call('ZADD', KEYS[1], ARGV[7], member)
216
+ redis.call('ZADD', KEYS[2], ARGV[7], member)
217
+ return 1
218
+ `);
219
+ var RESCHEDULE = script(`
220
+ local key = prefix .. 'item:' .. ARGV[2]
221
+ local member = redis.call('HGET', key, 'member')
222
+ if not member then return 0 end
223
+ local owner = redis.call('HGET', key, 'owner')
224
+ if ARGV[4] ~= '' then
225
+ if owner ~= ARGV[4] then return 0 end
226
+ redis.call('HDEL', key, 'owner', 'expires')
227
+ redis.call('ZREM', KEYS[3], member)
228
+ owner = false
229
+ end
230
+ redis.call('HSET', key, 'next', ARGV[3])
231
+ redis.call('ZADD', KEYS[1], ARGV[3], member)
232
+ if not owner then redis.call('ZADD', KEYS[2], ARGV[3], member) end
233
+ return 1
234
+ `);
235
+ var CANCEL = script(`
236
+ local key = prefix .. 'item:' .. ARGV[2]
237
+ local fields = redis.call('HGETALL', key)
238
+ if #fields == 0 then return false end
239
+ local member = redis.call('HGET', key, 'member')
240
+ redis.call('ZREM', KEYS[1], member)
241
+ redis.call('ZREM', KEYS[2], member)
242
+ redis.call('ZREM', KEYS[3], member)
243
+ redis.call('DEL', key, prefix .. 'message:' .. ARGV[2])
244
+ return fields
245
+ `);
246
+ var LIST = script(`
247
+ local out = {}
248
+ for _, member in ipairs(redis.call('ZRANGE', KEYS[1], ARGV[2], ARGV[3])) do
249
+ out[#out + 1] = redis.call('HGETALL', itemKey(member))
250
+ end
251
+ return out
252
+ `);
253
+
254
+ // src/redis/run.ts
255
+ var isNoScript = (error) => error instanceof Error && error.message.startsWith("NOSCRIPT");
256
+ async function run(client, script, keys, args) {
257
+ const rest = [`${keys.length}`, ...keys, ...args];
258
+ try {
259
+ return await client.send("EVALSHA", [script.sha, ...rest]);
260
+ } catch (error) {
261
+ if (!isNoScript(error))
262
+ throw error;
263
+ return client.send("EVAL", [script.source, ...rest]);
264
+ }
265
+ }
266
+ var LAYOUT = 1;
267
+ async function checkLayout(client, keys) {
268
+ const found = Number(await run(client, SCHEMA, [keys.schema], [keys.prefix, `${LAYOUT}`]));
269
+ if (!Number.isInteger(found) || found < 1) {
270
+ throw new QueueError2("INVALID", `The key ${keys.schema} does not hold a layout version: is the prefix another application's?`);
271
+ }
272
+ if (found > LAYOUT) {
273
+ throw new QueueError2("INVALID", `The database is at schema version ${found}, newer than this store's ${LAYOUT}`);
274
+ }
275
+ }
276
+
277
+ // src/redis/store.ts
278
+ class RedisQueueStore {
279
+ #client;
280
+ #owned;
281
+ #keys;
282
+ #ready;
283
+ #closed = false;
284
+ constructor({ client, owned, keys }) {
285
+ this.#client = client;
286
+ this.#owned = owned;
287
+ this.#keys = keys;
288
+ }
289
+ static open(options) {
290
+ return new RedisQueueStore(connect(options));
291
+ }
292
+ async close() {
293
+ if (this.#closed)
294
+ return;
295
+ this.#closed = true;
296
+ if (this.#owned)
297
+ await this.#client.close();
298
+ }
299
+ #setUp() {
300
+ this.#ready ??= checkLayout(this.#client, this.#keys).catch((error) => {
301
+ this.#ready = undefined;
302
+ if (error instanceof QueueError2)
303
+ throw error;
304
+ const reason = error instanceof Error ? error.message : String(error);
305
+ throw new QueueError2("INVALID", `The Redis queue cannot be set up: ${reason}`);
306
+ });
307
+ return this.#ready;
308
+ }
309
+ async#redis() {
310
+ if (this.#closed)
311
+ throw closed();
312
+ await this.#setUp();
313
+ return this.#client;
314
+ }
315
+ async#run(script, keys, args) {
316
+ const client = await this.#redis();
317
+ return run(client, script, keys, [this.#keys.prefix, ...args]);
318
+ }
319
+ async add(item, options = {}) {
320
+ checkNewItem(item);
321
+ const max = checkMaxItems(options.maxItems);
322
+ const added = newItem(crypto.randomUUID(), item);
323
+ const k = this.#keys;
324
+ const reply = await this.#run(ADD, [k.items, k.ready, k.seq], [
325
+ added.id,
326
+ added.from,
327
+ JSON.stringify(added.recipients),
328
+ `${added.size}`,
329
+ time(added.createdAt),
330
+ item.message,
331
+ max === undefined ? "" : `${max}`
332
+ ]);
333
+ if (reply[0] === "full") {
334
+ throw new QueueError2("QUEUE_FULL", `The queue holds ${reply[1]} items, its limit`);
335
+ }
336
+ return added;
337
+ }
338
+ async get(id) {
339
+ if (!isId(id))
340
+ return;
341
+ const client = await this.#redis();
342
+ const fields = fieldsOf(await client.send("HGETALL", [`${this.#keys.prefix}item:${id}`]));
343
+ return fields ? itemOf(fields) : undefined;
344
+ }
345
+ async list(options) {
346
+ const { offset, limit } = checkList(options);
347
+ const pages = await this.#run(LIST, [this.#keys.items], [`${offset}`, `${offset + limit - 1}`]);
348
+ return pages.flatMap((reply) => {
349
+ const fields = fieldsOf(reply);
350
+ return fields ? [itemOf(fields)] : [];
351
+ });
352
+ }
353
+ async count() {
354
+ const client = await this.#redis();
355
+ return Number(await client.send("ZCARD", [this.#keys.items]));
356
+ }
357
+ async readMessage(id) {
358
+ if (!isId(id))
359
+ return;
360
+ const client = await this.#redis();
361
+ const bytes = await client.getBuffer(`${this.#keys.prefix}message:${id}`);
362
+ return bytes === null ? undefined : new Uint8Array(bytes);
363
+ }
364
+ async claim(request) {
365
+ checkClaim(request);
366
+ const { owner, now, leaseMs } = request;
367
+ const k = this.#keys;
368
+ const fields = fieldsOf(await this.#run(CLAIM, [k.ready, k.leases, k.items], [owner, time(now), time(now + leaseMs)]));
369
+ return fields ? itemOf(fields) : undefined;
370
+ }
371
+ async renew(id, owner, expiresAt) {
372
+ checkOwner(owner);
373
+ checkTime("expiresAt", expiresAt);
374
+ if (!isId(id))
375
+ return false;
376
+ const k = this.#keys;
377
+ const done = await this.#run(RENEW, [k.leases], [id, owner, time(expiresAt)]);
378
+ return Number(done) === 1;
379
+ }
380
+ async complete(id, owner, result) {
381
+ checkOwner(owner);
382
+ checkResult(result);
383
+ if (!isId(id))
384
+ return;
385
+ const k = this.#keys;
386
+ const client = await this.#redis();
387
+ for (let read = 0;read < MAX_READS; read++) {
388
+ const fields = fieldsOf(await client.send("HGETALL", [`${k.prefix}item:${id}`]));
389
+ if (!fields || fields["owner"] !== owner)
390
+ return;
391
+ const rev = fields["rev"];
392
+ if (rev === undefined)
393
+ return;
394
+ const item = applyAttempt(itemOf(fields), result);
395
+ const done = Number(await this.#run(COMPLETE, [k.items, k.ready, k.leases], [id, owner, rev, ...outcomeOf(item)]));
396
+ if (done === 1)
397
+ return item;
398
+ if (done === 0)
399
+ return;
400
+ }
401
+ return;
402
+ }
403
+ async reschedule(id, at, owner) {
404
+ checkTime("at", at);
405
+ if (!isStorable(id))
406
+ return false;
407
+ if (owner !== undefined)
408
+ checkOwner(owner);
409
+ if (!isId(id))
410
+ return false;
411
+ const k = this.#keys;
412
+ const done = await this.#run(RESCHEDULE, [k.items, k.ready, k.leases], [id, time(at), owner ?? ""]);
413
+ return Number(done) === 1;
414
+ }
415
+ async cancel(id) {
416
+ if (!isId(id))
417
+ return;
418
+ const k = this.#keys;
419
+ const fields = fieldsOf(await this.#run(CANCEL, [k.items, k.ready, k.leases], [id]));
420
+ return fields ? itemOf(fields) : undefined;
421
+ }
422
+ }
423
+ var MAX_READS = 8;
424
+ var closed = () => new QueueError2("CLOSED", "The queue store is closed");
425
+ export {
426
+ RedisQueueStore
427
+ };
428
+
429
+ //# debugId=6B9DEBFE69FAED6B64756E2164756E21
430
+ //# 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}\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 };\n\t}\n\treturn { client: clientFor(urlOf(url)), owned: true, keys };\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 { 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\t#ready: Promise<void> | undefined;\n\t#closed = false;\n\n\tprivate constructor({ client, owned, keys }: Connection) {\n\t\tthis.#client = client;\n\t\tthis.#owned = owned;\n\t\tthis.#keys = keys;\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: ${reason}`,\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": "6B9DEBFE69FAED6B64756E2164756E21",
13
+ "names": []
14
+ }