@vielzeug/postmaster 2.0.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 (56) hide show
  1. package/README.md +87 -0
  2. package/dist/_json.cjs +2 -0
  3. package/dist/_json.cjs.map +1 -0
  4. package/dist/_json.d.ts +6 -0
  5. package/dist/_json.d.ts.map +1 -0
  6. package/dist/_json.js +33 -0
  7. package/dist/_json.js.map +1 -0
  8. package/dist/definitions.cjs +2 -0
  9. package/dist/definitions.cjs.map +1 -0
  10. package/dist/definitions.d.ts +3 -0
  11. package/dist/definitions.d.ts.map +1 -0
  12. package/dist/definitions.js +18 -0
  13. package/dist/definitions.js.map +1 -0
  14. package/dist/errors.cjs +2 -0
  15. package/dist/errors.cjs.map +1 -0
  16. package/dist/errors.d.ts +8 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +10 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/index.cjs +1 -0
  21. package/dist/index.d.ts +5 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +4 -0
  24. package/dist/indexeddb.cjs +2 -0
  25. package/dist/indexeddb.cjs.map +1 -0
  26. package/dist/indexeddb.d.ts +7 -0
  27. package/dist/indexeddb.d.ts.map +1 -0
  28. package/dist/indexeddb.js +93 -0
  29. package/dist/indexeddb.js.map +1 -0
  30. package/dist/postmaster.cjs +2 -0
  31. package/dist/postmaster.cjs.map +1 -0
  32. package/dist/postmaster.d.ts +3 -0
  33. package/dist/postmaster.d.ts.map +1 -0
  34. package/dist/postmaster.iife.js +2 -0
  35. package/dist/postmaster.iife.js.map +1 -0
  36. package/dist/postmaster.js +2 -0
  37. package/dist/postmaster.js.map +1 -0
  38. package/dist/store-ops.cjs +2 -0
  39. package/dist/store-ops.cjs.map +1 -0
  40. package/dist/store-ops.d.ts +40 -0
  41. package/dist/store-ops.d.ts.map +1 -0
  42. package/dist/store-ops.js +106 -0
  43. package/dist/store-ops.js.map +1 -0
  44. package/dist/testing/memory-store.cjs +2 -0
  45. package/dist/testing/memory-store.cjs.map +1 -0
  46. package/dist/testing/memory-store.d.ts +3 -0
  47. package/dist/testing/memory-store.d.ts.map +1 -0
  48. package/dist/testing/memory-store.js +84 -0
  49. package/dist/testing/memory-store.js.map +1 -0
  50. package/dist/testing.cjs +1 -0
  51. package/dist/testing.d.ts +2 -0
  52. package/dist/testing.d.ts.map +1 -0
  53. package/dist/testing.js +2 -0
  54. package/dist/types.d.ts +137 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @vielzeug/postmaster
2
+
3
+ Typed durable job outbox with leased processing, retries, and dead-letter recovery.
4
+
5
+ Postmaster coordinates delivery of application jobs that must survive reloads, resume later, retry according to an explicit policy, and retain terminal failures for recovery.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add @vielzeug/postmaster
11
+ ```
12
+
13
+ For browser persistence, also install `@vielzeug/vault`:
14
+
15
+ ```sh
16
+ pnpm add @vielzeug/postmaster @vielzeug/vault
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { createPostmaster, defineJobs } from '@vielzeug/postmaster';
23
+ import { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';
24
+
25
+ const jobs = defineJobs({
26
+ createTodo: {
27
+ version: 1,
28
+ validate: (v: unknown) => v as { id: string; title: string },
29
+ key: (p) => p.id,
30
+ execute: async (payload, { key, signal }) => {
31
+ await fetch('/api/todos', {
32
+ method: 'POST',
33
+ body: JSON.stringify(payload),
34
+ headers: { 'Idempotency-Key': key },
35
+ signal,
36
+ });
37
+ },
38
+ retry: { maxAttempts: 5, shouldRetry: (error) => error instanceof TypeError },
39
+ },
40
+ });
41
+
42
+ const store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });
43
+ const postmaster = createPostmaster({ jobs, store });
44
+
45
+ await postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });
46
+ await postmaster.start();
47
+
48
+ // On page unload:
49
+ await postmaster.dispose();
50
+ await store.dispose();
51
+ ```
52
+
53
+ ## At-least-once delivery
54
+
55
+ Postmaster provides at-least-once delivery. Every job must derive a stable idempotency key, and handlers must send or otherwise enforce that key. Never assume exactly-once execution.
56
+
57
+ ## Entry points
58
+
59
+ | Import | Purpose |
60
+ | --- | --- |
61
+ | `@vielzeug/postmaster` | Job definitions, processor, store contract, events, errors |
62
+ | `@vielzeug/postmaster/indexeddb` | Durable browser store backed by Vault IndexedDB |
63
+ | `@vielzeug/postmaster/testing` | Deterministic in-memory store and test helpers |
64
+
65
+ ## Testing
66
+
67
+ ```ts
68
+ import { createPostmaster, defineJobs } from '@vielzeug/postmaster';
69
+ import { createMemoryPostmasterStore } from '@vielzeug/postmaster/testing';
70
+
71
+ const store = createMemoryPostmasterStore();
72
+ const postmaster = createPostmaster({
73
+ jobs: defineJobs({
74
+ send: {
75
+ version: 1,
76
+ validate: (v: unknown) => String(v),
77
+ key: (p) => p,
78
+ execute: async () => {},
79
+ },
80
+ }),
81
+ store,
82
+ });
83
+
84
+ await postmaster.enqueue('send', 'hello');
85
+ await postmaster.flush();
86
+ await postmaster.dispose();
87
+ ```
package/dist/_json.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs");function t(e,t){return e?typeof e==`function`?e(t):e.parse(t):t}function n(t){let n;try{n=JSON.stringify(t)}catch(t){throw new e.PostmasterError(`job payload must be JSON-serializable`,{cause:t})}if(n===void 0)throw new e.PostmasterError(`job payload must be JSON-serializable`);return JSON.parse(n)}function r(e,t){return e instanceof Error?{message:e.message,name:e.name,occurredAt:t}:{message:String(e),name:`Error`,occurredAt:t}}function i(e){return e instanceof DOMException&&e.name===`AbortError`}exports.asJsonValue=n,exports.failureFrom=r,exports.isAbortError=i,exports.runValidate=t;
2
+ //# sourceMappingURL=_json.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_json.cjs","names":[],"sources":["../src/_json.ts"],"sourcesContent":["import { PostmasterError } from './errors.ts';\nimport type { JsonValue, StoredFailure, Validate } from './types.ts';\n\nexport function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T {\n if (!validate) return value as T;\n if (typeof validate === 'function') return validate(value);\n return validate.parse(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n let serialized: string | undefined;\n\n try {\n serialized = JSON.stringify(value);\n } catch (error) {\n throw new PostmasterError('job payload must be JSON-serializable', { cause: error });\n }\n\n if (serialized === undefined) {\n throw new PostmasterError('job payload must be JSON-serializable');\n }\n\n return JSON.parse(serialized) as JsonValue;\n}\n\nexport function failureFrom(error: unknown, now: number): StoredFailure {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, occurredAt: now };\n }\n\n return { message: String(error), name: 'Error', occurredAt: now };\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError';\n}\n"],"mappings":"gCAGA,SAAgB,EAAe,EAAmC,EAAmB,CAGnF,OAFK,EACD,OAAO,GAAa,WAAmB,EAAS,CAAK,EAClD,EAAS,MAAM,CAAK,EAFL,CAGxB,CAEA,SAAgB,EAAY,EAA2B,CACrD,IAAI,EAEJ,GAAI,CACF,EAAa,KAAK,UAAU,CAAK,CACnC,OAAS,EAAO,CACd,MAAM,IAAI,EAAA,gBAAgB,wCAAyC,CAAE,MAAO,CAAM,CAAC,CACrF,CAEA,GAAI,IAAe,IAAA,GACjB,MAAM,IAAI,EAAA,gBAAgB,uCAAuC,EAGnE,OAAO,KAAK,MAAM,CAAU,CAC9B,CAEA,SAAgB,EAAY,EAAgB,EAA4B,CAKtE,OAJI,aAAiB,MACZ,CAAE,QAAS,EAAM,QAAS,KAAM,EAAM,KAAM,WAAY,CAAI,EAG9D,CAAE,QAAS,OAAO,CAAK,EAAG,KAAM,QAAS,WAAY,CAAI,CAClE,CAEA,SAAgB,EAAa,EAAyB,CACpD,OAAO,aAAiB,cAAgB,EAAM,OAAS,YACzD"}
@@ -0,0 +1,6 @@
1
+ import type { JsonValue, StoredFailure, Validate } from './types.ts';
2
+ export declare function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T;
3
+ export declare function asJsonValue(value: unknown): JsonValue;
4
+ export declare function failureFrom(error: unknown, now: number): StoredFailure;
5
+ export declare function isAbortError(error: unknown): boolean;
6
+ //# sourceMappingURL=_json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_json.d.ts","sourceRoot":"","sources":["../src/_json.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAInF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAcrD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAMtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEpD"}
package/dist/_json.js ADDED
@@ -0,0 +1,33 @@
1
+ import { PostmasterError as e } from "./errors.js";
2
+ //#region src/_json.ts
3
+ function t(e, t) {
4
+ return e ? typeof e == "function" ? e(t) : e.parse(t) : t;
5
+ }
6
+ function n(t) {
7
+ let n;
8
+ try {
9
+ n = JSON.stringify(t);
10
+ } catch (t) {
11
+ throw new e("job payload must be JSON-serializable", { cause: t });
12
+ }
13
+ if (n === void 0) throw new e("job payload must be JSON-serializable");
14
+ return JSON.parse(n);
15
+ }
16
+ function r(e, t) {
17
+ return e instanceof Error ? {
18
+ message: e.message,
19
+ name: e.name,
20
+ occurredAt: t
21
+ } : {
22
+ message: String(e),
23
+ name: "Error",
24
+ occurredAt: t
25
+ };
26
+ }
27
+ function i(e) {
28
+ return e instanceof DOMException && e.name === "AbortError";
29
+ }
30
+ //#endregion
31
+ export { n as asJsonValue, r as failureFrom, i as isAbortError, t as runValidate };
32
+
33
+ //# sourceMappingURL=_json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_json.js","names":[],"sources":["../src/_json.ts"],"sourcesContent":["import { PostmasterError } from './errors.ts';\nimport type { JsonValue, StoredFailure, Validate } from './types.ts';\n\nexport function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T {\n if (!validate) return value as T;\n if (typeof validate === 'function') return validate(value);\n return validate.parse(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n let serialized: string | undefined;\n\n try {\n serialized = JSON.stringify(value);\n } catch (error) {\n throw new PostmasterError('job payload must be JSON-serializable', { cause: error });\n }\n\n if (serialized === undefined) {\n throw new PostmasterError('job payload must be JSON-serializable');\n }\n\n return JSON.parse(serialized) as JsonValue;\n}\n\nexport function failureFrom(error: unknown, now: number): StoredFailure {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, occurredAt: now };\n }\n\n return { message: String(error), name: 'Error', occurredAt: now };\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError';\n}\n"],"mappings":";;AAGA,SAAgB,EAAe,GAAmC,GAAmB;CAGnF,OAFK,IACD,OAAO,KAAa,aAAmB,EAAS,CAAK,IAClD,EAAS,MAAM,CAAK,IAFL;AAGxB;AAEA,SAAgB,EAAY,GAA2B;CACrD,IAAI;CAEJ,IAAI;EACF,IAAa,KAAK,UAAU,CAAK;CACnC,SAAS,GAAO;EACd,MAAM,IAAI,EAAgB,yCAAyC,EAAE,OAAO,EAAM,CAAC;CACrF;CAEA,IAAI,MAAe,KAAA,GACjB,MAAM,IAAI,EAAgB,uCAAuC;CAGnE,OAAO,KAAK,MAAM,CAAU;AAC9B;AAEA,SAAgB,EAAY,GAAgB,GAA4B;CAKtE,OAJI,aAAiB,QACZ;EAAE,SAAS,EAAM;EAAS,MAAM,EAAM;EAAM,YAAY;CAAI,IAG9D;EAAE,SAAS,OAAO,CAAK;EAAG,MAAM;EAAS,YAAY;CAAI;AAClE;AAEA,SAAgB,EAAa,GAAyB;CACpD,OAAO,aAAiB,gBAAgB,EAAM,SAAS;AACzD"}
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs");function t(t,n){if(t&&(!Number.isInteger(t.maxAttempts)||t.maxAttempts<1))throw new e.PostmasterError(`job "${n}" retry.maxAttempts must be a positive integer`)}function n(n,r){if(!Number.isInteger(r.version)||r.version<1)throw new e.PostmasterError(`job "${n}" version must be a positive integer`);if(typeof r.execute!=`function`||typeof r.key!=`function`)throw new e.PostmasterError(`job "${n}" must define execute and key`);t(r.retry,n)}function r(e){for(let[t,r]of Object.entries(e))n(t,r);return e}exports.defineJobs=r;
2
+ //# sourceMappingURL=definitions.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.cjs","names":[],"sources":["../src/definitions.ts"],"sourcesContent":["import { PostmasterError } from './errors.ts';\nimport type { JobDefinition, JobDefinitions, RetryPolicy } from './types.ts';\n\nfunction validateRetry(retry: RetryPolicy | undefined, name: string): void {\n if (!retry) return;\n\n if (!Number.isInteger(retry.maxAttempts) || retry.maxAttempts < 1) {\n throw new PostmasterError(`job \"${name}\" retry.maxAttempts must be a positive integer`);\n }\n}\n\nfunction validateJob(name: string, job: JobDefinition<unknown>): void {\n if (!Number.isInteger(job.version) || job.version < 1) {\n throw new PostmasterError(`job \"${name}\" version must be a positive integer`);\n }\n\n if (typeof job.execute !== 'function' || typeof job.key !== 'function') {\n throw new PostmasterError(`job \"${name}\" must define execute and key`);\n }\n\n validateRetry(job.retry, name);\n}\n\nexport function defineJobs<const J extends JobDefinitions>(jobs: J): J {\n for (const [name, job] of Object.entries(jobs)) validateJob(name, job);\n\n return jobs;\n}\n"],"mappings":"gCAGA,SAAS,EAAc,EAAgC,EAAoB,CACpE,OAED,CAAC,OAAO,UAAU,EAAM,WAAW,GAAK,EAAM,YAAc,GAC9D,MAAM,IAAI,EAAA,gBAAgB,QAAQ,EAAK,+CAA+C,CAE1F,CAEA,SAAS,EAAY,EAAc,EAAmC,CACpE,GAAI,CAAC,OAAO,UAAU,EAAI,OAAO,GAAK,EAAI,QAAU,EAClD,MAAM,IAAI,EAAA,gBAAgB,QAAQ,EAAK,qCAAqC,EAG9E,GAAI,OAAO,EAAI,SAAY,YAAc,OAAO,EAAI,KAAQ,WAC1D,MAAM,IAAI,EAAA,gBAAgB,QAAQ,EAAK,8BAA8B,EAGvE,EAAc,EAAI,MAAO,CAAI,CAC/B,CAEA,SAAgB,EAA2C,EAAY,CACrE,IAAK,GAAM,CAAC,EAAM,KAAQ,OAAO,QAAQ,CAAI,EAAG,EAAY,EAAM,CAAG,EAErE,OAAO,CACT"}
@@ -0,0 +1,3 @@
1
+ import type { JobDefinitions } from './types.ts';
2
+ export declare function defineJobs<const J extends JobDefinitions>(jobs: J): J;
3
+ //# sourceMappingURL=definitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../src/definitions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,cAAc,EAAe,MAAM,YAAY,CAAC;AAsB7E,wBAAgB,UAAU,CAAC,KAAK,CAAC,CAAC,SAAS,cAAc,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAIrE"}
@@ -0,0 +1,18 @@
1
+ import { PostmasterError as e } from "./errors.js";
2
+ //#region src/definitions.ts
3
+ function t(t, n) {
4
+ if (t && (!Number.isInteger(t.maxAttempts) || t.maxAttempts < 1)) throw new e(`job "${n}" retry.maxAttempts must be a positive integer`);
5
+ }
6
+ function n(n, r) {
7
+ if (!Number.isInteger(r.version) || r.version < 1) throw new e(`job "${n}" version must be a positive integer`);
8
+ if (typeof r.execute != "function" || typeof r.key != "function") throw new e(`job "${n}" must define execute and key`);
9
+ t(r.retry, n);
10
+ }
11
+ function r(e) {
12
+ for (let [t, r] of Object.entries(e)) n(t, r);
13
+ return e;
14
+ }
15
+ //#endregion
16
+ export { r as defineJobs };
17
+
18
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","names":[],"sources":["../src/definitions.ts"],"sourcesContent":["import { PostmasterError } from './errors.ts';\nimport type { JobDefinition, JobDefinitions, RetryPolicy } from './types.ts';\n\nfunction validateRetry(retry: RetryPolicy | undefined, name: string): void {\n if (!retry) return;\n\n if (!Number.isInteger(retry.maxAttempts) || retry.maxAttempts < 1) {\n throw new PostmasterError(`job \"${name}\" retry.maxAttempts must be a positive integer`);\n }\n}\n\nfunction validateJob(name: string, job: JobDefinition<unknown>): void {\n if (!Number.isInteger(job.version) || job.version < 1) {\n throw new PostmasterError(`job \"${name}\" version must be a positive integer`);\n }\n\n if (typeof job.execute !== 'function' || typeof job.key !== 'function') {\n throw new PostmasterError(`job \"${name}\" must define execute and key`);\n }\n\n validateRetry(job.retry, name);\n}\n\nexport function defineJobs<const J extends JobDefinitions>(jobs: J): J {\n for (const [name, job] of Object.entries(jobs)) validateJob(name, job);\n\n return jobs;\n}\n"],"mappings":";;AAGA,SAAS,EAAc,GAAgC,GAAoB;CACpE,UAED,CAAC,OAAO,UAAU,EAAM,WAAW,KAAK,EAAM,cAAc,IAC9D,MAAM,IAAI,EAAgB,QAAQ,EAAK,+CAA+C;AAE1F;AAEA,SAAS,EAAY,GAAc,GAAmC;CACpE,IAAI,CAAC,OAAO,UAAU,EAAI,OAAO,KAAK,EAAI,UAAU,GAClD,MAAM,IAAI,EAAgB,QAAQ,EAAK,qCAAqC;CAG9E,IAAI,OAAO,EAAI,WAAY,cAAc,OAAO,EAAI,OAAQ,YAC1D,MAAM,IAAI,EAAgB,QAAQ,EAAK,8BAA8B;CAGvE,EAAc,EAAI,OAAO,CAAI;AAC/B;AAEA,SAAgB,EAA2C,GAAY;CACrE,KAAK,IAAM,CAAC,GAAM,MAAQ,OAAO,QAAQ,CAAI,GAAG,EAAY,GAAM,CAAG;CAErE,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};exports.PostmasterDisposedError=t,exports.PostmasterError=e,exports.PostmasterJobError=n;
2
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["export class PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class PostmasterDisposedError extends PostmasterError {}\nexport class PostmasterJobError extends PostmasterError {}\n"],"mappings":"AAAA,IAAa,EAAb,cAAqC,KAAM,CACzC,YAAY,EAAiB,EAAwB,CACnD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,EAAb,cAA6C,CAAgB,CAAC,EACjD,EAAb,cAAwC,CAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare class PostmasterError extends Error {
2
+ constructor(message: string, options?: ErrorOptions);
3
+ }
4
+ export declare class PostmasterDisposedError extends PostmasterError {
5
+ }
6
+ export declare class PostmasterJobError extends PostmasterError {
7
+ }
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAKpD;AAED,qBAAa,uBAAwB,SAAQ,eAAe;CAAG;AAC/D,qBAAa,kBAAmB,SAAQ,eAAe;CAAG"}
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ //#region src/errors.ts
2
+ var e = class extends Error {
3
+ constructor(e, t) {
4
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
+ }
6
+ }, t = class extends e {}, n = class extends e {};
7
+ //#endregion
8
+ export { t as PostmasterDisposedError, e as PostmasterError, n as PostmasterJobError };
9
+
10
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["export class PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class PostmasterDisposedError extends PostmasterError {}\nexport class PostmasterJobError extends PostmasterError {}\n"],"mappings":";AAAA,IAAa,IAAb,cAAqC,MAAM;CACzC,YAAY,GAAiB,GAAwB;EAGnD,AAFA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAEa,IAAb,cAA6C,EAAgB,CAAC,GACjD,IAAb,cAAwC,EAAgB,CAAC"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./definitions.cjs"),n=require("./postmaster.cjs");exports.PostmasterDisposedError=e.PostmasterDisposedError,exports.PostmasterError=e.PostmasterError,exports.PostmasterJobError=e.PostmasterJobError,exports.createPostmaster=n.createPostmaster,exports.defineJobs=t.defineJobs;
@@ -0,0 +1,5 @@
1
+ export { defineJobs } from './definitions.ts';
2
+ export { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';
3
+ export { createPostmaster } from './postmaster.ts';
4
+ export type { CreatePostmasterOptions, EntryFilter, EntryStatus, FlushResult, InferJobPayload, JobContext, JobDefinition, JobDefinitions, JsonPrimitive, JsonValue, Postmaster, PostmasterEntry, PostmasterEvent, PostmasterStats, PostmasterStore, RemoveResult, RetryPolicy, RetryResult, StoredFailure, StoredJob, StoreTx, Validate, } from './types.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACT,UAAU,EACV,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,SAAS,EACT,OAAO,EACP,QAAQ,GACT,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { PostmasterDisposedError as e, PostmasterError as t, PostmasterJobError as n } from "./errors.js";
2
+ import { defineJobs as r } from "./definitions.js";
3
+ import { createPostmaster as i } from "./postmaster.js";
4
+ export { e as PostmasterDisposedError, t as PostmasterError, n as PostmasterJobError, i as createPostmaster, r as defineJobs };
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs");let t=require("@vielzeug/vault"),n=require("@vielzeug/vault/indexeddb");function r(e){let{failure:t,leaseExpiresAt:n,ownerId:r,...i}=e;return{...i,...t===void 0?{}:{failure:t},...n===void 0?{}:{leaseExpiresAt:n},...r===void 0?{}:{ownerId:r}}}function i(t,n){return n().catch(n=>{throw n instanceof e.PostmasterError?n:new e.PostmasterError(`${t} failed`,{cause:n})})}function a(e){let a={jobs:(0,t.table)(`id`,{indexes:[`availableAt`,`leaseExpiresAt`,`status`]})},o=(0,n.createIndexedDB)({name:e.name,schema:a}),s=e=>({async countByStatus(){let[t,n,r]=await Promise.all([e.query(`jobs`).equals(`status`,`queued`).count(),e.query(`jobs`).equals(`status`,`running`).count(),e.query(`jobs`).equals(`status`,`dead-letter`).count()]);return{deadLetter:r,queued:t,running:n}},async delete(t){await e.delete(`jobs`,t)},async findClaimable(t){let n=(await e.query(`jobs`).filter(e=>e.status===`queued`&&e.availableAt<=t||e.status===`running`&&(e.leaseExpiresAt??0)<=t).toArray()).sort((e,t)=>e.availableAt-t.availableAt||e.createdAt-t.createdAt)[0];return n?r(n):void 0},async findNextWake(t){let n=await e.getAll(`jobs`),r;for(let e of n){let n;e.status===`queued`&&e.availableAt>t?n=e.availableAt:e.status===`running`&&(e.leaseExpiresAt??0)>t&&(n=e.leaseExpiresAt),n!==void 0&&(r===void 0||n<r)&&(r=n)}return r},async get(t){let n=await e.get(`jobs`,t);return n?r(n):void 0},async put(t){await e.put(`jobs`,r(t))}});return{get disposalSignal(){return o.disposalSignal},dispose(){return i(`disposing IndexedDB store`,()=>o.dispose())},get disposed(){return o.disposed},list(e){return i(`listing jobs`,async()=>{let t=o.query(`jobs`);return(e?.status===void 0?await t.toArray():await t.equals(`status`,e.status).toArray()).sort((e,t)=>e.createdAt-t.createdAt).map(r)})},subscribe(e){return o.observe(`jobs`,()=>e(),{immediate:!1})},transact(e){return i(`executing transaction`,()=>o.batch([`jobs`],async t=>e(s(t))))},async[Symbol.asyncDispose](){await o.dispose()}}}exports.createIndexedDbPostmasterStore=a;
2
+ //# sourceMappingURL=indexeddb.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.cjs","names":[],"sources":["../src/indexeddb.ts"],"sourcesContent":["import { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\nimport { PostmasterError } from './errors.ts';\nimport type { EntryFilter, PostmasterStats, PostmasterStore, StoredJob, StoreTx } from './types.ts';\n\ntype PostmasterSchema = {\n jobs: ReturnType<typeof table<StoredJob, 'id'>>;\n};\n\ntype IndexedDbPostmasterStoreOptions = {\n readonly name: string;\n};\n\nfunction normalize(entry: StoredJob): StoredJob {\n const { failure, leaseExpiresAt, ownerId, ...rest } = entry;\n return {\n ...rest,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nfunction wrap<T>(operation: string, fn: () => Promise<T>): Promise<T> {\n return fn().catch((cause: unknown) => {\n if (cause instanceof PostmasterError) throw cause;\n throw new PostmasterError(`${operation} failed`, { cause });\n });\n}\n\nexport function createIndexedDbPostmasterStore(options: IndexedDbPostmasterStoreOptions): PostmasterStore {\n const schema: PostmasterSchema = {\n jobs: table<StoredJob, 'id'>('id', {\n indexes: ['availableAt', 'leaseExpiresAt', 'status'],\n }),\n };\n const vault = createIndexedDB({ name: options.name, schema });\n\n const buildTx = (vaultTx: Parameters<Parameters<typeof vault.batch>[1]>[0]): StoreTx => ({\n async countByStatus(): Promise<PostmasterStats> {\n const [queued, running, deadLetter] = await Promise.all([\n vaultTx.query('jobs').equals('status', 'queued').count(),\n vaultTx.query('jobs').equals('status', 'running').count(),\n vaultTx.query('jobs').equals('status', 'dead-letter').count(),\n ]);\n return { deadLetter, queued, running };\n },\n async delete(id: string): Promise<void> {\n await vaultTx.delete('jobs', id);\n },\n async findClaimable(now: number): Promise<StoredJob | undefined> {\n const candidates = await vaultTx\n .query('jobs')\n .filter(\n (entry) =>\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now),\n )\n .toArray();\n const entry = candidates.sort((a, b) => a.availableAt - b.availableAt || a.createdAt - b.createdAt)[0];\n return entry ? normalize(entry) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n const entries = await vaultTx.getAll('jobs');\n let next: number | undefined;\n for (const entry of entries) {\n let wake: number | undefined;\n if (entry.status === 'queued' && entry.availableAt > now) wake = entry.availableAt;\n else if (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) > now) wake = entry.leaseExpiresAt;\n if (wake !== undefined && (next === undefined || wake < next)) next = wake;\n }\n return next;\n },\n async get(id: string): Promise<StoredJob | undefined> {\n const entry = await vaultTx.get('jobs', id);\n return entry ? normalize(entry) : undefined;\n },\n async put(entry: StoredJob): Promise<void> {\n await vaultTx.put('jobs', normalize(entry));\n },\n });\n\n return {\n get disposalSignal() {\n return vault.disposalSignal;\n },\n dispose(): Promise<void> {\n return wrap('disposing IndexedDB store', () => vault.dispose());\n },\n get disposed() {\n return vault.disposed;\n },\n list(filter?: EntryFilter): Promise<StoredJob[]> {\n return wrap('listing jobs', async () => {\n const query = vault.query('jobs');\n const entries =\n filter?.status === undefined ? await query.toArray() : await query.equals('status', filter.status).toArray();\n return entries.sort((a, b) => a.createdAt - b.createdAt).map(normalize);\n });\n },\n subscribe(listener: () => void): () => void {\n return vault.observe('jobs', () => listener(), { immediate: false });\n },\n transact<T>(fn: (tx: StoreTx) => Promise<T>): Promise<T> {\n return wrap('executing transaction', () => vault.batch(['jobs'], async (vaultTx) => fn(buildTx(vaultTx))));\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await vault.dispose();\n },\n };\n}\n\nexport type { IndexedDbPostmasterStoreOptions };\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { PostmasterStore } from './types.ts';
2
+ type IndexedDbPostmasterStoreOptions = {
3
+ readonly name: string;
4
+ };
5
+ export declare function createIndexedDbPostmasterStore(options: IndexedDbPostmasterStoreOptions): PostmasterStore;
6
+ export type { IndexedDbPostmasterStoreOptions };
7
+ //# sourceMappingURL=indexeddb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../src/indexeddb.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgC,eAAe,EAAsB,MAAM,YAAY,CAAC;AAMpG,KAAK,+BAA+B,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAmBF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,eAAe,CAgFxG;AAED,YAAY,EAAE,+BAA+B,EAAE,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { PostmasterError as e } from "./errors.js";
2
+ import { table as t } from "@vielzeug/vault";
3
+ import { createIndexedDB as n } from "@vielzeug/vault/indexeddb";
4
+ //#region src/indexeddb.ts
5
+ function r(e) {
6
+ let { failure: t, leaseExpiresAt: n, ownerId: r, ...i } = e;
7
+ return {
8
+ ...i,
9
+ ...t === void 0 ? {} : { failure: t },
10
+ ...n === void 0 ? {} : { leaseExpiresAt: n },
11
+ ...r === void 0 ? {} : { ownerId: r }
12
+ };
13
+ }
14
+ function i(t, n) {
15
+ return n().catch((n) => {
16
+ throw n instanceof e ? n : new e(`${t} failed`, { cause: n });
17
+ });
18
+ }
19
+ function a(e) {
20
+ let a = { jobs: t("id", { indexes: [
21
+ "availableAt",
22
+ "leaseExpiresAt",
23
+ "status"
24
+ ] }) }, o = n({
25
+ name: e.name,
26
+ schema: a
27
+ }), s = (e) => ({
28
+ async countByStatus() {
29
+ let [t, n, r] = await Promise.all([
30
+ e.query("jobs").equals("status", "queued").count(),
31
+ e.query("jobs").equals("status", "running").count(),
32
+ e.query("jobs").equals("status", "dead-letter").count()
33
+ ]);
34
+ return {
35
+ deadLetter: r,
36
+ queued: t,
37
+ running: n
38
+ };
39
+ },
40
+ async delete(t) {
41
+ await e.delete("jobs", t);
42
+ },
43
+ async findClaimable(t) {
44
+ let n = (await e.query("jobs").filter((e) => e.status === "queued" && e.availableAt <= t || e.status === "running" && (e.leaseExpiresAt ?? 0) <= t).toArray()).sort((e, t) => e.availableAt - t.availableAt || e.createdAt - t.createdAt)[0];
45
+ return n ? r(n) : void 0;
46
+ },
47
+ async findNextWake(t) {
48
+ let n = await e.getAll("jobs"), r;
49
+ for (let e of n) {
50
+ let n;
51
+ e.status === "queued" && e.availableAt > t ? n = e.availableAt : e.status === "running" && (e.leaseExpiresAt ?? 0) > t && (n = e.leaseExpiresAt), n !== void 0 && (r === void 0 || n < r) && (r = n);
52
+ }
53
+ return r;
54
+ },
55
+ async get(t) {
56
+ let n = await e.get("jobs", t);
57
+ return n ? r(n) : void 0;
58
+ },
59
+ async put(t) {
60
+ await e.put("jobs", r(t));
61
+ }
62
+ });
63
+ return {
64
+ get disposalSignal() {
65
+ return o.disposalSignal;
66
+ },
67
+ dispose() {
68
+ return i("disposing IndexedDB store", () => o.dispose());
69
+ },
70
+ get disposed() {
71
+ return o.disposed;
72
+ },
73
+ list(e) {
74
+ return i("listing jobs", async () => {
75
+ let t = o.query("jobs");
76
+ return (e?.status === void 0 ? await t.toArray() : await t.equals("status", e.status).toArray()).sort((e, t) => e.createdAt - t.createdAt).map(r);
77
+ });
78
+ },
79
+ subscribe(e) {
80
+ return o.observe("jobs", () => e(), { immediate: !1 });
81
+ },
82
+ transact(e) {
83
+ return i("executing transaction", () => o.batch(["jobs"], async (t) => e(s(t))));
84
+ },
85
+ async [Symbol.asyncDispose]() {
86
+ await o.dispose();
87
+ }
88
+ };
89
+ }
90
+ //#endregion
91
+ export { a as createIndexedDbPostmasterStore };
92
+
93
+ //# sourceMappingURL=indexeddb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.js","names":[],"sources":["../src/indexeddb.ts"],"sourcesContent":["import { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\nimport { PostmasterError } from './errors.ts';\nimport type { EntryFilter, PostmasterStats, PostmasterStore, StoredJob, StoreTx } from './types.ts';\n\ntype PostmasterSchema = {\n jobs: ReturnType<typeof table<StoredJob, 'id'>>;\n};\n\ntype IndexedDbPostmasterStoreOptions = {\n readonly name: string;\n};\n\nfunction normalize(entry: StoredJob): StoredJob {\n const { failure, leaseExpiresAt, ownerId, ...rest } = entry;\n return {\n ...rest,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nfunction wrap<T>(operation: string, fn: () => Promise<T>): Promise<T> {\n return fn().catch((cause: unknown) => {\n if (cause instanceof PostmasterError) throw cause;\n throw new PostmasterError(`${operation} failed`, { cause });\n });\n}\n\nexport function createIndexedDbPostmasterStore(options: IndexedDbPostmasterStoreOptions): PostmasterStore {\n const schema: PostmasterSchema = {\n jobs: table<StoredJob, 'id'>('id', {\n indexes: ['availableAt', 'leaseExpiresAt', 'status'],\n }),\n };\n const vault = createIndexedDB({ name: options.name, schema });\n\n const buildTx = (vaultTx: Parameters<Parameters<typeof vault.batch>[1]>[0]): StoreTx => ({\n async countByStatus(): Promise<PostmasterStats> {\n const [queued, running, deadLetter] = await Promise.all([\n vaultTx.query('jobs').equals('status', 'queued').count(),\n vaultTx.query('jobs').equals('status', 'running').count(),\n vaultTx.query('jobs').equals('status', 'dead-letter').count(),\n ]);\n return { deadLetter, queued, running };\n },\n async delete(id: string): Promise<void> {\n await vaultTx.delete('jobs', id);\n },\n async findClaimable(now: number): Promise<StoredJob | undefined> {\n const candidates = await vaultTx\n .query('jobs')\n .filter(\n (entry) =>\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now),\n )\n .toArray();\n const entry = candidates.sort((a, b) => a.availableAt - b.availableAt || a.createdAt - b.createdAt)[0];\n return entry ? normalize(entry) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n const entries = await vaultTx.getAll('jobs');\n let next: number | undefined;\n for (const entry of entries) {\n let wake: number | undefined;\n if (entry.status === 'queued' && entry.availableAt > now) wake = entry.availableAt;\n else if (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) > now) wake = entry.leaseExpiresAt;\n if (wake !== undefined && (next === undefined || wake < next)) next = wake;\n }\n return next;\n },\n async get(id: string): Promise<StoredJob | undefined> {\n const entry = await vaultTx.get('jobs', id);\n return entry ? normalize(entry) : undefined;\n },\n async put(entry: StoredJob): Promise<void> {\n await vaultTx.put('jobs', normalize(entry));\n },\n });\n\n return {\n get disposalSignal() {\n return vault.disposalSignal;\n },\n dispose(): Promise<void> {\n return wrap('disposing IndexedDB store', () => vault.dispose());\n },\n get disposed() {\n return vault.disposed;\n },\n list(filter?: EntryFilter): Promise<StoredJob[]> {\n return wrap('listing jobs', async () => {\n const query = vault.query('jobs');\n const entries =\n filter?.status === undefined ? await query.toArray() : await query.equals('status', filter.status).toArray();\n return entries.sort((a, b) => a.createdAt - b.createdAt).map(normalize);\n });\n },\n subscribe(listener: () => void): () => void {\n return vault.observe('jobs', () => listener(), { immediate: false });\n },\n transact<T>(fn: (tx: StoreTx) => Promise<T>): Promise<T> {\n return wrap('executing transaction', () => vault.batch(['jobs'], async (vaultTx) => fn(buildTx(vaultTx))));\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await vault.dispose();\n },\n };\n}\n\nexport type { IndexedDbPostmasterStoreOptions };\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};function r(t,n){if(t&&(!Number.isInteger(t.maxAttempts)||t.maxAttempts<1))throw new e(`job "${n}" retry.maxAttempts must be a positive integer`)}function i(t,n){if(!Number.isInteger(n.version)||n.version<1)throw new e(`job "${t}" version must be a positive integer`);if(typeof n.execute!=`function`||typeof n.key!=`function`)throw new e(`job "${t}" must define execute and key`);r(n.retry,t)}function a(e){for(let[t,n]of Object.entries(e))i(t,n);return e}function o(e,t=3e4){return Math.min(1e3*2**Math.max(0,Number.isNaN(e)?0:Math.floor(e)),t)}function s(e,t){return e?typeof e==`function`?e(t):e.parse(t):t}function c(t){let n;try{n=JSON.stringify(t)}catch(t){throw new e(`job payload must be JSON-serializable`,{cause:t})}if(n===void 0)throw new e(`job payload must be JSON-serializable`);return JSON.parse(n)}function l(e,t){return e instanceof Error?{message:e.message,name:e.name,occurredAt:t}:{message:String(e),name:`Error`,occurredAt:t}}function u(e){return e instanceof DOMException&&e.name===`AbortError`}async function d(e,t){let n=await e.findClaimable(t.now);if(!n)return;let r={...n,attempts:n.attempts+1,failure:void 0,leaseExpiresAt:t.now+t.leaseDuration,ownerId:t.ownerId,status:`running`,updatedAt:t.now};return await e.put(r),r}async function f(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.delete(t.id),!0)}async function p(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function m(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,availableAt:t.availableAt,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function h(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`dead-letter`,updatedAt:t.now}),!0)}async function g(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:t.leaseExpiresAt,updatedAt:t.now}),!0)}async function _(e,t,n){let r=await e.get(t);if(!r)return{status:`not-found`};if(r.status===`running`)return{status:`running`};if(r.status!==`dead-letter`)return{status:`not-dead-letter`};let i={...r,availableAt:n,failure:void 0,status:`queued`,updatedAt:n};return await e.put(i),{entry:b(i),status:`retried`}}async function v(e,t){let n=await e.get(t);return n?n.status===`running`?{status:`running`}:(await e.delete(t),{id:t,status:`removed`}):{status:`not-found`}}async function y(e,t){if(await e.get(t.id))throw Error(`Postmaster entry "${t.id}" already exists`);await e.put(t)}function b(e){return{attempts:e.attempts,availableAt:e.availableAt,createdAt:e.createdAt,...e.failure?{failure:e.failure}:{},id:e.id,key:e.key,name:e.name,status:e.status,updatedAt:e.updatedAt,version:e.version}}var x=()=>({completed:0,deadLettered:0,processed:0,retryScheduled:0});function S(e){let t=new AbortController,n=e.filter(e=>e!==void 0),r=()=>t.abort();for(let e of n){if(e.aborted){r();break}e.addEventListener(`abort`,r,{once:!0})}return t.signal}function C(t){if(!Number.isInteger(t)||t<1e3)throw new e(`leaseDuration must be a positive integer of at least 1000ms`)}function w(r){let{clock:i=Date.now,jobs:a,leaseDuration:w=3e4,signal:T,store:E}=r;C(w);let D=new AbortController,O=crypto.randomUUID(),k=new Set,A=!1,j=!1,M,N,P=new Map,F,I=e=>{let t=e instanceof Error?e:Error(String(e));L({error:t,type:`processor-error`}),queueMicrotask(()=>{throw t})},L=e=>{if(k.size!==0)for(let t of k)try{t(e)}catch{}},R=()=>{if(A)throw new t(`Postmaster is disposed`)},z=()=>{N!==void 0&&clearTimeout(N),N=void 0},B=()=>{z(),j&&!A&&K()},V=async()=>{if(!j||A)return;let e=await E.transact(e=>e.findNextWake(i()));if(e===void 0)return;let t=Math.max(0,Math.min(e-i(),2147483647));N=setTimeout(B,t)},H=e=>{let t=a[e.name];if(!t)throw new n(`no job definition is registered for "${e.name}"`);return t},U=async(e,t)=>await E.transact(n=>h(n,{failure:l(t,i()),id:e.id,now:i(),ownerId:O}))?(L({entry:b({...e,status:`dead-letter`}),type:`dead-lettered`}),{completed:0,deadLettered:1,processed:!0,retryScheduled:0}):(L({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}),W=async(t,r)=>{let a,c;try{if(a=H(t),t.version>a.version)throw new n(`job "${t.name}" version ${t.version} is newer than the registered version`);let e=t.version===a.version?t.payload:a.migrate?.(t.payload,t.version);if(e===void 0)throw new n(`job "${t.name}" version ${t.version} cannot be migrated`);c=s(a.validate,e)}catch(e){return U(t,e)}let d=new AbortController,h=S([D.signal,r,d.signal]),_=setInterval(()=>{E.transact(e=>g(e,{id:t.id,leaseExpiresAt:i()+w,now:i(),ownerId:O})).then(e=>{e||(d.abort(),L({id:t.id,type:`lease-lost`}))}).catch(I)},Math.max(1e3,Math.floor(w/2)));L({entry:b(t),type:`started`});try{return await a.execute(c,{attempt:t.attempts,entryId:t.id,key:t.key,signal:h}),h.aborted?(await E.transact(e=>p(e,{id:t.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await E.transact(e=>f(e,{id:t.id,now:i(),ownerId:O}))?(L({entry:b(t),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(L({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(n){if(h.aborted||u(n))return await E.transact(e=>p(e,{id:t.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let r=!1,s=0;try{if(r=t.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry(n,t.attempts)??!1),!r)return U(t,n);if(s=a.retry?.delay?.(t.attempts-1)??o(t.attempts-1),!Number.isFinite(s)||s<0)throw new e(`job "${t.name}" retry delay must be finite and non-negative`)}catch(e){return U(t,e)}let c=i()+s;return await E.transact(e=>m(e,{availableAt:c,failure:l(n,i()),id:t.id,now:i(),ownerId:O}))?(L({entry:b({...t,availableAt:c,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(L({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}finally{clearInterval(_)}},G=async e=>{if(e?.aborted)throw new DOMException(`The operation was aborted`,`AbortError`);if(D.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await E.transact(e=>d(e,{leaseDuration:w,now:i(),ownerId:O}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};P.set(t.id,t);try{return await W(t,e)}finally{P.delete(t.id)}},K=()=>{M||A||(M=(async()=>{for(;!(!(await G()).processed||A););await V()})().catch(I).finally(()=>{M=void 0}))},q=E.subscribe(B),J=()=>{Y()};T?.addEventListener(`abort`,J,{once:!0});let Y=async()=>{if(A)return;A=!0,L({type:`dispose`}),k.clear(),z(),T?.removeEventListener(`abort`,J),q(),D.abort();let e=[...P.values()];P.clear(),await Promise.all(e.map(e=>E.transact(t=>p(t,{id:e.id,now:i(),ownerId:O}))))};return{get disposalSignal(){return D.signal},async dispose(){await Y()},get disposed(){return A},async enqueue(t,r){R();let o=a[t];if(!o)throw new n(`no job definition is registered for "${t}"`);let l=s(o.validate,r),u=o.key(l);if(typeof u!=`string`||u.length===0)throw new e(`job "${t}" returned an empty key`);let d=i(),f={attempts:0,availableAt:d,createdAt:d,id:crypto.randomUUID(),key:u,name:t,payload:c(l),status:`queued`,updatedAt:d,version:o.version};await E.transact(e=>y(e,f));let p=b(f);return L({entry:p,type:`enqueued`}),B(),p},flush(e){if(R(),F)return F;z();let t=(async()=>{await M;let t=x();try{for(;;){let n=await G(e?.signal);if(!n.processed||(t.processed+=1,t.completed+=n.completed,t.deadLettered+=n.deadLettered,t.retryScheduled+=n.retryScheduled,A))return t}}finally{F=void 0,B()}})();return F=t,t},async list(e){return R(),(await E.list(e)).map(b)},async remove(e){R();let t=await E.transact(t=>v(t,e));return t.status===`removed`&&L({id:e,type:`removed`}),t},async retry(e){R();let t=await E.transact(t=>_(t,e,i()));return t.status===`retried`&&B(),t},async start(){R(),!j&&(j=!0,B())},async stats(){return R(),E.transact(e=>e.countByStatus())},tap(e,t){if(R(),k.add(e),t?.signal){if(t.signal.aborted)return k.delete(e),()=>{};let n=()=>k.delete(e);return t.signal.addEventListener(`abort`,n,{once:!0}),()=>{k.delete(e),t.signal?.removeEventListener(`abort`,n)}}return()=>k.delete(e)},async[Symbol.asyncDispose](){await Y()}}}exports.PostmasterDisposedError=t,exports.PostmasterError=e,exports.PostmasterJobError=n,exports.createPostmaster=w,exports.defineJobs=a;
2
+ //# sourceMappingURL=postmaster.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postmaster.cjs","names":[],"sources":["../src/errors.ts","../src/definitions.ts","../../arsenal/dist/async/backoff.js","../src/_json.ts","../src/store-ops.ts","../src/postmaster.ts"],"sourcesContent":["export class PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class PostmasterDisposedError extends PostmasterError {}\nexport class PostmasterJobError extends PostmasterError {}\n","import { PostmasterError } from './errors.ts';\nimport type { JobDefinition, JobDefinitions, RetryPolicy } from './types.ts';\n\nfunction validateRetry(retry: RetryPolicy | undefined, name: string): void {\n if (!retry) return;\n\n if (!Number.isInteger(retry.maxAttempts) || retry.maxAttempts < 1) {\n throw new PostmasterError(`job \"${name}\" retry.maxAttempts must be a positive integer`);\n }\n}\n\nfunction validateJob(name: string, job: JobDefinition<unknown>): void {\n if (!Number.isInteger(job.version) || job.version < 1) {\n throw new PostmasterError(`job \"${name}\" version must be a positive integer`);\n }\n\n if (typeof job.execute !== 'function' || typeof job.key !== 'function') {\n throw new PostmasterError(`job \"${name}\" must define execute and key`);\n }\n\n validateRetry(job.retry, name);\n}\n\nexport function defineJobs<const J extends JobDefinitions>(jobs: J): J {\n for (const [name, job] of Object.entries(jobs)) validateJob(name, job);\n\n return jobs;\n}\n","//#region src/async/backoff.ts\nfunction e(e, t = 3e4) {\n\treturn Math.min(1e3 * 2 ** Math.max(0, Number.isNaN(e) ? 0 : Math.floor(e)), t);\n}\n//#endregion\nexport { e as backoff };\n\n//# sourceMappingURL=backoff.js.map","import { PostmasterError } from './errors.ts';\nimport type { JsonValue, StoredFailure, Validate } from './types.ts';\n\nexport function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T {\n if (!validate) return value as T;\n if (typeof validate === 'function') return validate(value);\n return validate.parse(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n let serialized: string | undefined;\n\n try {\n serialized = JSON.stringify(value);\n } catch (error) {\n throw new PostmasterError('job payload must be JSON-serializable', { cause: error });\n }\n\n if (serialized === undefined) {\n throw new PostmasterError('job payload must be JSON-serializable');\n }\n\n return JSON.parse(serialized) as JsonValue;\n}\n\nexport function failureFrom(error: unknown, now: number): StoredFailure {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, occurredAt: now };\n }\n\n return { message: String(error), name: 'Error', occurredAt: now };\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError';\n}\n","import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';\n\nexport interface ClaimParams {\n readonly leaseDuration: number;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface OwnedParams {\n readonly id: string;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface RescheduleParams extends OwnedParams {\n readonly availableAt: number;\n readonly failure: StoredFailure;\n}\n\nexport interface DeadLetterParams extends OwnedParams {\n readonly failure: StoredFailure;\n}\n\n/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */\nexport async function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined> {\n const entry = await tx.findClaimable(params.now);\n if (!entry) return undefined;\n const claimed: StoredJob = {\n ...entry,\n attempts: entry.attempts + 1,\n failure: undefined,\n leaseExpiresAt: params.now + params.leaseDuration,\n ownerId: params.ownerId,\n status: 'running',\n updatedAt: params.now,\n };\n await tx.put(claimed);\n return claimed;\n}\n\n/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */\nexport async function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.delete(params.id);\n return true;\n}\n\n/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */\nexport async function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */\nexport async function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n availableAt: params.availableAt,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Move a running job to dead-letter. Returns false if stale. */\nexport async function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'dead-letter',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Renew a lease for the current owner. Returns false if stale. */\nexport async function renewLeaseJob(tx: StoreTx, params: OwnedParams & { leaseExpiresAt: number }): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({ ...entry, leaseExpiresAt: params.leaseExpiresAt, updatedAt: params.now });\n return true;\n}\n\n/** Retry a dead-letter job back to queued. Returns discriminated result. */\nexport async function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n if (entry.status !== 'dead-letter') return { status: 'not-dead-letter' };\n const retried: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(retried);\n return { entry: toEntry(retried), status: 'retried' };\n}\n\n/** Remove a queued or dead-letter job. Running jobs cannot be removed. */\nexport async function removeJob(tx: StoreTx, id: string): Promise<RemoveResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n await tx.delete(id);\n return { id, status: 'removed' };\n}\n\n/** Enqueue a new job. Rejects duplicate ids. */\nexport async function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void> {\n const existing = await tx.get(entry.id);\n if (existing) throw new Error(`Postmaster entry \"${entry.id}\" already exists`);\n await tx.put(entry);\n}\n\nexport function toEntry(entry: StoredJob): PostmasterEntry {\n return {\n attempts: entry.attempts,\n availableAt: entry.availableAt,\n createdAt: entry.createdAt,\n ...(entry.failure ? { failure: entry.failure } : {}),\n id: entry.id,\n key: entry.key,\n name: entry.name,\n status: entry.status,\n updatedAt: entry.updatedAt,\n version: entry.version,\n };\n}\n","import { backoff } from '@vielzeug/arsenal/async';\nimport { asJsonValue, failureFrom, isAbortError, runValidate } from './_json.ts';\nimport { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';\nimport {\n claimJob,\n completeJob,\n deadLetterJob,\n enqueueJob,\n releaseJob,\n removeJob,\n renewLeaseJob,\n rescheduleJob,\n retryJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RetryResult,\n StoredJob,\n} from './types.ts';\n\ntype ExecutionResult = Omit<FlushResult, 'processed'> & { processed: boolean };\ntype MutableFlushResult = { -readonly [K in keyof FlushResult]: FlushResult[K] };\n\nconst emptyResult = (): FlushResult => ({ completed: 0, deadLettered: 0, processed: 0, retryScheduled: 0 });\n\nfunction mergeSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal {\n const controller = new AbortController();\n const active = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n const abort = (): void => controller.abort();\n\n for (const signal of active) {\n if (signal.aborted) {\n abort();\n break;\n }\n signal.addEventListener('abort', abort, { once: true });\n }\n\n return controller.signal;\n}\n\nfunction assertLeaseDuration(value: number): void {\n if (!Number.isInteger(value) || value < 1_000) {\n throw new PostmasterError('leaseDuration must be a positive integer of at least 1000ms');\n }\n}\n\nexport function createPostmaster<J extends JobDefinitions>(options: CreatePostmasterOptions<J>): Postmaster<J> {\n const { clock = Date.now, jobs, leaseDuration = 30_000, signal, store } = options;\n assertLeaseDuration(leaseDuration);\n\n const controller = new AbortController();\n const ownerId = crypto.randomUUID();\n const tappers = new Set<(event: PostmasterEvent) => void>();\n let disposed = false;\n let started = false;\n let running: Promise<void> | undefined;\n let wakeTimer: ReturnType<typeof setTimeout> | undefined;\n const activeEntries = new Map<string, StoredJob>();\n let flushPromise: Promise<FlushResult> | undefined;\n\n const error = (reason: unknown): void => {\n const value = reason instanceof Error ? reason : new Error(String(reason));\n emitTap({ error: value, type: 'processor-error' });\n queueMicrotask(() => {\n throw value;\n });\n };\n\n const emitTap = (event: PostmasterEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect postmaster behavior.\n }\n }\n };\n\n const assertLive = (): void => {\n if (disposed) throw new PostmasterDisposedError('Postmaster is disposed');\n };\n\n const clearWakeTimer = (): void => {\n if (wakeTimer !== undefined) clearTimeout(wakeTimer);\n wakeTimer = undefined;\n };\n\n const wake = (): void => {\n clearWakeTimer();\n if (started && !disposed) pump();\n };\n\n const schedule = async (): Promise<void> => {\n if (!started || disposed) return;\n const next = await store.transact((tx) => tx.findNextWake(clock()));\n if (next === undefined) return;\n const delay = Math.max(0, Math.min(next - clock(), 2_147_483_647));\n wakeTimer = setTimeout(wake, delay);\n };\n\n const resolveJob = (entry: StoredJob): JobDefinition<unknown> => {\n const job = jobs[entry.name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${entry.name}\"`);\n return job;\n };\n\n const deadLetter = async (entry: StoredJob, reason: unknown): Promise<ExecutionResult> => {\n const finalized = await store.transact((tx) =>\n deadLetterJob(tx, { failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n\n if (!finalized) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry({ ...entry, status: 'dead-letter' }), type: 'dead-lettered' });\n return { completed: 0, deadLettered: 1, processed: true, retryScheduled: 0 };\n };\n\n const execute = async (entry: StoredJob, externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n let job: JobDefinition<unknown>;\n let payload: unknown;\n\n try {\n job = resolveJob(entry);\n if (entry.version > job.version) {\n throw new PostmasterJobError(\n `job \"${entry.name}\" version ${entry.version} is newer than the registered version`,\n );\n }\n const raw = entry.version === job.version ? entry.payload : job.migrate?.(entry.payload, entry.version);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" version ${entry.version} cannot be migrated`);\n }\n payload = runValidate(job.validate, raw);\n } catch (reason) {\n return deadLetter(entry, reason);\n }\n\n const taskController = new AbortController();\n const taskSignal = mergeSignals([controller.signal, externalSignal, taskController.signal]);\n const heartbeat = setInterval(\n () => {\n void store\n .transact((tx) =>\n renewLeaseJob(tx, { id: entry.id, leaseExpiresAt: clock() + leaseDuration, now: clock(), ownerId }),\n )\n .then((renewed) => {\n if (!renewed) {\n taskController.abort();\n emitTap({ id: entry.id, type: 'lease-lost' });\n }\n })\n .catch(error);\n },\n Math.max(1_000, Math.floor(leaseDuration / 2)),\n );\n\n emitTap({ entry: toEntry(entry), type: 'started' });\n\n try {\n await job.execute(payload, {\n attempt: entry.attempts,\n entryId: entry.id,\n key: entry.key,\n signal: taskSignal,\n } as JobContext);\n\n if (taskSignal.aborted) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n const completed = await store.transact((tx) => completeJob(tx, { id: entry.id, now: clock(), ownerId }));\n if (!completed) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry(entry), type: 'completed' });\n return { completed: 1, deadLettered: 0, processed: true, retryScheduled: 0 };\n } catch (reason) {\n if (taskSignal.aborted || isAbortError(reason)) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n let shouldRetry = false;\n let delay = 0;\n\n try {\n shouldRetry =\n entry.attempts < (job.retry?.maxAttempts ?? 1) && (job.retry?.shouldRetry(reason, entry.attempts) ?? false);\n if (!shouldRetry) return deadLetter(entry, reason);\n delay = job.retry?.delay?.(entry.attempts - 1) ?? backoff(entry.attempts - 1);\n if (!Number.isFinite(delay) || delay < 0) {\n throw new PostmasterError(`job \"${entry.name}\" retry delay must be finite and non-negative`);\n }\n } catch (retryReason) {\n return deadLetter(entry, retryReason);\n }\n\n const availableAt = clock() + delay;\n const rescheduled = await store.transact((tx) =>\n rescheduleJob(tx, { availableAt, failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n if (!rescheduled) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n emitTap({ entry: toEntry({ ...entry, availableAt, status: 'queued' }), type: 'retry-scheduled' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 1 };\n } finally {\n clearInterval(heartbeat);\n }\n };\n\n const processNext = async (externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n if (externalSignal?.aborted) throw new DOMException('The operation was aborted', 'AbortError');\n if (controller.signal.aborted) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n const entry = await store.transact((tx) => claimJob(tx, { leaseDuration, now: clock(), ownerId }));\n if (!entry) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n activeEntries.set(entry.id, entry);\n try {\n return await execute(entry, externalSignal);\n } finally {\n activeEntries.delete(entry.id);\n }\n };\n\n const pump = (): void => {\n if (running || disposed) return;\n running = (async () => {\n for (;;) {\n const result = await processNext();\n if (!result.processed || disposed) break;\n }\n await schedule();\n })()\n .catch(error)\n .finally(() => {\n running = undefined;\n });\n };\n\n const unsubscribeStore = store.subscribe(wake);\n const externalAbort = (): void => {\n void dispose();\n };\n signal?.addEventListener('abort', externalAbort, { once: true });\n\n const dispose = async (): Promise<void> => {\n if (disposed) return;\n disposed = true;\n emitTap({ type: 'dispose' });\n tappers.clear();\n clearWakeTimer();\n signal?.removeEventListener('abort', externalAbort);\n unsubscribeStore();\n controller.abort();\n const entries = [...activeEntries.values()];\n activeEntries.clear();\n await Promise.all(\n entries.map((entry) => store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }))),\n );\n };\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n async dispose(): Promise<void> {\n await dispose();\n },\n get disposed() {\n return disposed;\n },\n async enqueue<K extends keyof J & string>(name: K, payload: InferJobPayload<J[K]>): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const parsed = runValidate(job.validate, payload);\n const key = job.key(parsed);\n if (typeof key !== 'string' || key.length === 0) {\n throw new PostmasterError(`job \"${name}\" returned an empty key`);\n }\n const now = clock();\n const entry: StoredJob = {\n attempts: 0,\n availableAt: now,\n createdAt: now,\n id: crypto.randomUUID(),\n key,\n name,\n payload: asJsonValue(parsed),\n status: 'queued',\n updatedAt: now,\n version: job.version,\n };\n await store.transact((tx) => enqueueJob(tx, entry));\n const result = toEntry(entry);\n emitTap({ entry: result, type: 'enqueued' });\n wake();\n return result;\n },\n flush(options?: { signal?: AbortSignal }): Promise<FlushResult> {\n assertLive();\n if (flushPromise) return flushPromise;\n clearWakeTimer();\n const promise = (async (): Promise<FlushResult> => {\n await running;\n const result: MutableFlushResult = emptyResult();\n try {\n for (;;) {\n const next = await processNext(options?.signal);\n if (!next.processed) return result;\n result.processed += 1;\n result.completed += next.completed;\n result.deadLettered += next.deadLettered;\n result.retryScheduled += next.retryScheduled;\n if (disposed) return result;\n }\n } finally {\n flushPromise = undefined;\n wake();\n }\n })();\n flushPromise = promise;\n return promise;\n },\n async list(filter?: EntryFilter): Promise<PostmasterEntry[]> {\n assertLive();\n return (await store.list(filter)).map(toEntry);\n },\n async remove(id: string): Promise<RemoveResult> {\n assertLive();\n const result = await store.transact((tx) => removeJob(tx, id));\n if (result.status === 'removed') emitTap({ id, type: 'removed' });\n return result;\n },\n async retry(id: string): Promise<RetryResult> {\n assertLive();\n const result = await store.transact((tx) => retryJob(tx, id, clock()));\n if (result.status === 'retried') wake();\n return result;\n },\n async start(): Promise<void> {\n assertLive();\n if (started) return;\n started = true;\n wake();\n },\n async stats(): Promise<PostmasterStats> {\n assertLive();\n return store.transact((tx) => tx.countByStatus());\n },\n tap(handler: (event: PostmasterEvent) => void, opts?: { readonly signal?: AbortSignal }): () => void {\n assertLive();\n tappers.add(handler);\n\n if (opts?.signal) {\n if (opts.signal.aborted) {\n tappers.delete(handler);\n return () => {};\n }\n const onAbort = () => tappers.delete(handler);\n opts.signal.addEventListener('abort', onAbort, { once: true });\n return () => {\n tappers.delete(handler);\n opts.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return () => tappers.delete(handler);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CreatePostmasterOptions, JobDefinitions, Postmaster } from './types.ts';
2
+ export declare function createPostmaster<J extends JobDefinitions>(options: CreatePostmasterOptions<J>): Postmaster<J>;
3
+ //# sourceMappingURL=postmaster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postmaster.d.ts","sourceRoot":"","sources":["../src/postmaster.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,uBAAuB,EAMvB,cAAc,EACd,UAAU,EAOX,MAAM,YAAY,CAAC;AA6BpB,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CA+U7G"}
@@ -0,0 +1,2 @@
1
+ var Postmaster=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{},r=class extends t{};function i(e,n){if(e&&(!Number.isInteger(e.maxAttempts)||e.maxAttempts<1))throw new t(`job "${n}" retry.maxAttempts must be a positive integer`)}function a(e,n){if(!Number.isInteger(n.version)||n.version<1)throw new t(`job "${e}" version must be a positive integer`);if(typeof n.execute!=`function`||typeof n.key!=`function`)throw new t(`job "${e}" must define execute and key`);i(n.retry,e)}function o(e){for(let[t,n]of Object.entries(e))a(t,n);return e}function s(e,t=3e4){return Math.min(1e3*2**Math.max(0,Number.isNaN(e)?0:Math.floor(e)),t)}function c(e,t){return e?typeof e==`function`?e(t):e.parse(t):t}function l(e){let n;try{n=JSON.stringify(e)}catch(e){throw new t(`job payload must be JSON-serializable`,{cause:e})}if(n===void 0)throw new t(`job payload must be JSON-serializable`);return JSON.parse(n)}function u(e,t){return e instanceof Error?{message:e.message,name:e.name,occurredAt:t}:{message:String(e),name:`Error`,occurredAt:t}}function d(e){return e instanceof DOMException&&e.name===`AbortError`}async function f(e,t){let n=await e.findClaimable(t.now);if(!n)return;let r={...n,attempts:n.attempts+1,failure:void 0,leaseExpiresAt:t.now+t.leaseDuration,ownerId:t.ownerId,status:`running`,updatedAt:t.now};return await e.put(r),r}async function p(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.delete(t.id),!0)}async function m(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function h(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,availableAt:t.availableAt,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function g(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`dead-letter`,updatedAt:t.now}),!0)}async function _(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:t.leaseExpiresAt,updatedAt:t.now}),!0)}async function v(e,t,n){let r=await e.get(t);if(!r)return{status:`not-found`};if(r.status===`running`)return{status:`running`};if(r.status!==`dead-letter`)return{status:`not-dead-letter`};let i={...r,availableAt:n,failure:void 0,status:`queued`,updatedAt:n};return await e.put(i),{entry:x(i),status:`retried`}}async function y(e,t){let n=await e.get(t);return n?n.status===`running`?{status:`running`}:(await e.delete(t),{id:t,status:`removed`}):{status:`not-found`}}async function b(e,t){if(await e.get(t.id))throw Error(`Postmaster entry "${t.id}" already exists`);await e.put(t)}function x(e){return{attempts:e.attempts,availableAt:e.availableAt,createdAt:e.createdAt,...e.failure?{failure:e.failure}:{},id:e.id,key:e.key,name:e.name,status:e.status,updatedAt:e.updatedAt,version:e.version}}var S=()=>({completed:0,deadLettered:0,processed:0,retryScheduled:0});function C(e){let t=new AbortController,n=e.filter(e=>e!==void 0),r=()=>t.abort();for(let e of n){if(e.aborted){r();break}e.addEventListener(`abort`,r,{once:!0})}return t.signal}function w(e){if(!Number.isInteger(e)||e<1e3)throw new t(`leaseDuration must be a positive integer of at least 1000ms`)}function T(e){let{clock:i=Date.now,jobs:a,leaseDuration:o=3e4,signal:T,store:E}=e;w(o);let D=new AbortController,O=crypto.randomUUID(),k=new Set,A=!1,j=!1,M,N,P=new Map,F,I=e=>{let t=e instanceof Error?e:Error(String(e));L({error:t,type:`processor-error`}),queueMicrotask(()=>{throw t})},L=e=>{if(k.size!==0)for(let t of k)try{t(e)}catch{}},R=()=>{if(A)throw new n(`Postmaster is disposed`)},z=()=>{N!==void 0&&clearTimeout(N),N=void 0},B=()=>{z(),j&&!A&&K()},V=async()=>{if(!j||A)return;let e=await E.transact(e=>e.findNextWake(i()));if(e===void 0)return;let t=Math.max(0,Math.min(e-i(),2147483647));N=setTimeout(B,t)},H=e=>{let t=a[e.name];if(!t)throw new r(`no job definition is registered for "${e.name}"`);return t},U=async(e,t)=>await E.transact(n=>g(n,{failure:u(t,i()),id:e.id,now:i(),ownerId:O}))?(L({entry:x({...e,status:`dead-letter`}),type:`dead-lettered`}),{completed:0,deadLettered:1,processed:!0,retryScheduled:0}):(L({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}),W=async(e,n)=>{let a,l;try{if(a=H(e),e.version>a.version)throw new r(`job "${e.name}" version ${e.version} is newer than the registered version`);let t=e.version===a.version?e.payload:a.migrate?.(e.payload,e.version);if(t===void 0)throw new r(`job "${e.name}" version ${e.version} cannot be migrated`);l=c(a.validate,t)}catch(t){return U(e,t)}let f=new AbortController,g=C([D.signal,n,f.signal]),v=setInterval(()=>{E.transact(t=>_(t,{id:e.id,leaseExpiresAt:i()+o,now:i(),ownerId:O})).then(t=>{t||(f.abort(),L({id:e.id,type:`lease-lost`}))}).catch(I)},Math.max(1e3,Math.floor(o/2)));L({entry:x(e),type:`started`});try{return await a.execute(l,{attempt:e.attempts,entryId:e.id,key:e.key,signal:g}),g.aborted?(await E.transact(t=>m(t,{id:e.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await E.transact(t=>p(t,{id:e.id,now:i(),ownerId:O}))?(L({entry:x(e),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(L({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(n){if(g.aborted||d(n))return await E.transact(t=>m(t,{id:e.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let r=!1,o=0;try{if(r=e.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry(n,e.attempts)??!1),!r)return U(e,n);if(o=a.retry?.delay?.(e.attempts-1)??s(e.attempts-1),!Number.isFinite(o)||o<0)throw new t(`job "${e.name}" retry delay must be finite and non-negative`)}catch(t){return U(e,t)}let c=i()+o;return await E.transact(t=>h(t,{availableAt:c,failure:u(n,i()),id:e.id,now:i(),ownerId:O}))?(L({entry:x({...e,availableAt:c,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(L({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}finally{clearInterval(v)}},G=async e=>{if(e?.aborted)throw new DOMException(`The operation was aborted`,`AbortError`);if(D.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await E.transact(e=>f(e,{leaseDuration:o,now:i(),ownerId:O}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};P.set(t.id,t);try{return await W(t,e)}finally{P.delete(t.id)}},K=()=>{M||A||(M=(async()=>{for(;!(!(await G()).processed||A););await V()})().catch(I).finally(()=>{M=void 0}))},q=E.subscribe(B),J=()=>{Y()};T?.addEventListener(`abort`,J,{once:!0});let Y=async()=>{if(A)return;A=!0,L({type:`dispose`}),k.clear(),z(),T?.removeEventListener(`abort`,J),q(),D.abort();let e=[...P.values()];P.clear(),await Promise.all(e.map(e=>E.transact(t=>m(t,{id:e.id,now:i(),ownerId:O}))))};return{get disposalSignal(){return D.signal},async dispose(){await Y()},get disposed(){return A},async enqueue(e,n){R();let o=a[e];if(!o)throw new r(`no job definition is registered for "${e}"`);let s=c(o.validate,n),u=o.key(s);if(typeof u!=`string`||u.length===0)throw new t(`job "${e}" returned an empty key`);let d=i(),f={attempts:0,availableAt:d,createdAt:d,id:crypto.randomUUID(),key:u,name:e,payload:l(s),status:`queued`,updatedAt:d,version:o.version};await E.transact(e=>b(e,f));let p=x(f);return L({entry:p,type:`enqueued`}),B(),p},flush(e){if(R(),F)return F;z();let t=(async()=>{await M;let t=S();try{for(;;){let n=await G(e?.signal);if(!n.processed||(t.processed+=1,t.completed+=n.completed,t.deadLettered+=n.deadLettered,t.retryScheduled+=n.retryScheduled,A))return t}}finally{F=void 0,B()}})();return F=t,t},async list(e){return R(),(await E.list(e)).map(x)},async remove(e){R();let t=await E.transact(t=>y(t,e));return t.status===`removed`&&L({id:e,type:`removed`}),t},async retry(e){R();let t=await E.transact(t=>v(t,e,i()));return t.status===`retried`&&B(),t},async start(){R(),!j&&(j=!0,B())},async stats(){return R(),E.transact(e=>e.countByStatus())},tap(e,t){if(R(),k.add(e),t?.signal){if(t.signal.aborted)return k.delete(e),()=>{};let n=()=>k.delete(e);return t.signal.addEventListener(`abort`,n,{once:!0}),()=>{k.delete(e),t.signal?.removeEventListener(`abort`,n)}}return()=>k.delete(e)},async[Symbol.asyncDispose](){await Y()}}}return e.PostmasterDisposedError=n,e.PostmasterError=t,e.PostmasterJobError=r,e.createPostmaster=T,e.defineJobs=o,e})({});
2
+ //# sourceMappingURL=postmaster.iife.js.map