@vielzeug/postmaster 26.10.15 → 26.10.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_json.cjs.map +1 -1
- package/dist/_json.js +32 -1
- package/dist/_json.js.map +1 -1
- package/dist/_store-utils.cjs.map +1 -1
- package/dist/_store-utils.js +13 -1
- package/dist/_store-utils.js.map +1 -1
- package/dist/definitions.cjs.map +1 -1
- package/dist/definitions.js +32 -1
- package/dist/definitions.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +9 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/indexeddb.cjs.map +1 -1
- package/dist/indexeddb.js +175 -1
- package/dist/indexeddb.js.map +1 -1
- package/dist/postmaster.cjs.map +1 -1
- package/dist/postmaster.iife.js.map +1 -1
- package/dist/postmaster.js +490 -1
- package/dist/postmaster.js.map +1 -1
- package/dist/store-ops.cjs.map +1 -1
- package/dist/store-ops.js +106 -1
- package/dist/store-ops.js.map +1 -1
- package/dist/testing/memory-store.cjs.map +1 -1
- package/dist/testing/memory-store.js +75 -1
- package/dist/testing/memory-store.js.map +1 -1
- package/dist/testing.js +1 -1
- package/package.json +8 -8
package/dist/postmaster.js
CHANGED
|
@@ -1,2 +1,491 @@
|
|
|
1
|
-
import{backoff as e,tapper as t}from"@vielzeug/arsenal";var PostmasterError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},PostmasterDisposedError=class extends PostmasterError{},PostmasterJobError=class extends PostmasterError{};function validateRetry(e,t){if(e&&(!Number.isInteger(e.maxAttempts)||e.maxAttempts<1))throw new PostmasterError(`job "${t}" retry.maxAttempts must be a positive integer`)}function validateMigrations(e,t){let n=t.migrate;if(!n)return;let r=Object.keys(n);if(r.length===0)throw new PostmasterError(`job "${e}" migrations must not be empty`);let i=r.map(r=>{let i=Number(r);if(!Number.isInteger(i)||String(i)!==r)throw new PostmasterError(`job "${e}" has invalid migration key "${r}"`);if(i<1||i>=t.version)throw new PostmasterError(`job "${e}" has invalid migration key "${i}" (valid range: 1..${t.version-1})`);if(typeof n[i]!=`function`)throw new PostmasterError(`job "${e}" migration from version ${i} must be a function`);return i}).sort((e,t)=>e-t);for(let t=1;t<i.length;t++)if(i[t]!==i[t-1]+1)throw new PostmasterError(`job "${e}" missing migration from version ${i[t-1]+1}`);if(i.at(-1)!==t.version-1)throw new PostmasterError(`job "${e}" migrations must lead into version ${t.version}`)}function validateJob(e,t){if(!Number.isInteger(t.version)||t.version<1)throw new PostmasterError(`job "${e}" version must be a positive integer`);if(typeof t.execute!=`function`||typeof t.key!=`function`)throw new PostmasterError(`job "${e}" must define execute and key`);validateRetry(t.retry,e),validateMigrations(e,t)}function defineJobs(e){for(let[t,n]of Object.entries(e))validateJob(t,n);return e}function runValidate(e,t){return e?typeof e==`function`?e(t):e.parse(t):t}function asJsonValue(e){let t;try{t=JSON.stringify(e)}catch(e){throw new PostmasterError(`job payload must be JSON-serializable`,{cause:e})}if(t===void 0)throw new PostmasterError(`job payload must be JSON-serializable`);return JSON.parse(t)}function failureFrom(e,t){return e instanceof Error?{message:e.message,name:e.name,occurredAt:t}:{message:String(e),name:`Error`,occurredAt:t}}function isAbortError(e){return e instanceof DOMException&&e.name===`AbortError`}async function claimJob(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 completeJob(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 releaseJob(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 rescheduleJob(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 deadLetterJob(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 renewLeaseJob(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 requeueJob(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:toEntry(i),status:`requeued`}}async function removeJob(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 enqueueJob(e,t){if(await e.get(t.id))throw new PostmasterJobError(`Postmaster entry "${t.id}" already exists`);await e.put(t)}function toEntry(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 emptyResult=()=>({completed:0,deadLettered:0,processed:0,retryScheduled:0});function assertLeaseDuration(e){if(!Number.isInteger(e)||e<1e3)throw new PostmasterError(`leaseDuration must be a positive integer of at least 1000ms`)}function assertAvailableAt(e,t){if(e===void 0)return t;if(!Number.isFinite(e)||e<0||!Number.isSafeInteger(e))throw new PostmasterError(`availableAt must be a finite non-negative safe integer`);return e}function createPostmaster(n){let{clock:r=Date.now,jobs:i,leaseDuration:a=3e4,signal:o,store:s}=n;assertLeaseDuration(a);let c=new AbortController,l=crypto.randomUUID(),u=t(),d=!1,f=!1,p,m,h=new Map,g,error=e=>{let t=e instanceof Error?e:Error(String(e));emitTap({error:t,type:`processor-error`})},emitTap=e=>{u.emit(e)},assertLive=()=>{if(d)throw new PostmasterDisposedError(`Postmaster is disposed`)},clearWakeTimer=()=>{m!==void 0&&clearTimeout(m),m=void 0},wake=()=>{clearWakeTimer(),f&&!d&&pump()},schedule=async()=>{if(!f||d)return;let e=await s.transact(e=>e.findNextWake(r()));if(e===void 0)return;let t=Math.max(0,Math.min(e-r(),2147483647));m=setTimeout(wake,t)},resolveJob=e=>{let t=i[e.name];if(!t)throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);return t},deadLetter=async(e,t)=>await s.transact(n=>deadLetterJob(n,{failure:failureFrom(t,r()),id:e.id,now:r(),ownerId:l}))?(emitTap({entry:toEntry({...e,status:`dead-letter`}),type:`dead-lettered`}),{completed:0,deadLettered:1,processed:!0,retryScheduled:0}):(emitTap({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}),execute=async(t,n)=>{let i,o;try{if(i=resolveJob(t),t.version>i.version)throw new PostmasterJobError(`job "${t.name}" version ${t.version} is newer than the registered version`);let e=t.payload;if(t.version<i.version){let n=i.migrate;if(!n)throw new PostmasterJobError(`job "${t.name}" has no migrations`);for(let r=t.version;r<i.version;r++){let i=n[r];if(typeof i!=`function`)throw new PostmasterJobError(`job "${t.name}" does not support stored version ${t.version}`);if(e=i(e),e===void 0)throw new PostmasterJobError(`job "${t.name}" migration from version ${r} returned undefined`)}}o=runValidate(i.validate,e)}catch(e){return deadLetter(t,e)}let u=new AbortController,d=AbortSignal.any([c.signal,n,u.signal].filter(e=>e!==void 0)),f=setInterval(()=>{s.transact(e=>renewLeaseJob(e,{id:t.id,leaseExpiresAt:r()+a,now:r(),ownerId:l})).then(e=>{e||(u.abort(),emitTap({id:t.id,type:`lease-lost`}))}).catch(error)},Math.max(1e3,Math.floor(a/2)));emitTap({entry:toEntry(t),type:`started`});try{return await i.execute(o,{attempt:t.attempts,entryId:t.id,key:t.key,signal:d}),d.aborted?(await s.transact(e=>releaseJob(e,{id:t.id,now:r(),ownerId:l})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await s.transact(e=>completeJob(e,{id:t.id,now:r(),ownerId:l}))?(emitTap({entry:toEntry(t),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(emitTap({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(n){if(d.aborted||isAbortError(n))return await s.transact(e=>releaseJob(e,{id:t.id,now:r(),ownerId:l})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let a=!1,o=0;try{if(a=t.attempts<(i.retry?.maxAttempts??1)&&(i.retry?.shouldRetry?.(n,t.attempts)??!0),!a)return deadLetter(t,n);if(o=i.retry?.delay?.(t.attempts-1)??e(t.attempts-1),!Number.isFinite(o)||o<0)throw new PostmasterError(`job "${t.name}" retry delay must be finite and non-negative`)}catch(e){return deadLetter(t,e)}let c=r()+o;return await s.transact(e=>rescheduleJob(e,{availableAt:c,failure:failureFrom(n,r()),id:t.id,now:r(),ownerId:l}))?(emitTap({entry:toEntry({...t,availableAt:c,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(emitTap({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}finally{clearInterval(f)}},processNext=async e=>{if(e?.aborted)throw new DOMException(`The operation was aborted`,`AbortError`);if(c.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await s.transact(e=>claimJob(e,{leaseDuration:a,now:r(),ownerId:l}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};h.set(t.id,t);try{return await execute(t,e)}finally{h.delete(t.id)}},pump=()=>{p||d||(p=(async()=>{for(;(await processNext()).processed&&!d;);await schedule()})().catch(error).finally(()=>{p=void 0}))},_=s.subscribe(wake),externalAbort=()=>{dispose()};o?.addEventListener(`abort`,externalAbort,{once:!0});let dispose=async()=>{if(d)return;d=!0,emitTap({type:`dispose`}),u.clear(),clearWakeTimer(),o?.removeEventListener(`abort`,externalAbort),_(),c.abort();let e=[...h.values()];h.clear(),await Promise.all(e.map(e=>s.transact(t=>releaseJob(t,{id:e.id,now:r(),ownerId:l}))))};return{get disposalSignal(){return c.signal},async dispose(){await dispose()},get disposed(){return d},async enqueue(e,t,n){assertLive();let a=i[e];if(!a)throw new PostmasterJobError(`no job definition is registered for "${e}"`);let o=r(),c=assertAvailableAt(n?.availableAt,o),l=runValidate(a.validate,t),u=a.key(l);if(typeof u!=`string`||u.length===0)throw new PostmasterError(`job "${e}" returned an empty key`);let d={attempts:0,availableAt:c,createdAt:o,id:crypto.randomUUID(),key:u,name:e,payload:asJsonValue(l),status:`queued`,updatedAt:o,version:a.version};await s.transact(e=>enqueueJob(e,d));let f=toEntry(d);return emitTap({entry:f,type:`enqueued`}),wake(),f},flush(e){if(assertLive(),g)return g;clearWakeTimer();let t=(async()=>{await p;let t=emptyResult();try{for(;;){let n=await processNext(e?.signal);if(!n.processed||(t.processed+=1,t.completed+=n.completed,t.deadLettered+=n.deadLettered,t.retryScheduled+=n.retryScheduled,d))return t}}finally{g=void 0,wake()}})();return g=t,t},async list(e){return assertLive(),(await s.list(e)).map(toEntry)},async remove(e){assertLive();let t=await s.transact(t=>removeJob(t,e));return t.status===`removed`&&emitTap({id:e,type:`removed`}),t},async requeue(e){assertLive();let t=await s.transact(t=>requeueJob(t,e,r()));return t.status===`requeued`&&wake(),t},start(){assertLive(),!f&&(f=!0,wake())},async stats(){return assertLive(),s.transact(e=>e.countByStatus())},tap(e,t){return assertLive(),u.tap(e,t)},async[Symbol.asyncDispose](){await dispose()}}}export{PostmasterDisposedError,PostmasterError,PostmasterJobError,createPostmaster,defineJobs};
|
|
1
|
+
import { backoff as e, tapper as t } from "@vielzeug/arsenal";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
var PostmasterError = class extends Error {
|
|
4
|
+
constructor(e, t) {
|
|
5
|
+
super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
|
|
6
|
+
}
|
|
7
|
+
}, PostmasterDisposedError = class extends PostmasterError {}, PostmasterJobError = class extends PostmasterError {};
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/definitions.ts
|
|
10
|
+
function validateRetry(e, t) {
|
|
11
|
+
if (e && (!Number.isInteger(e.maxAttempts) || e.maxAttempts < 1)) throw new PostmasterError(`job "${t}" retry.maxAttempts must be a positive integer`);
|
|
12
|
+
}
|
|
13
|
+
function validateMigrations(e, t) {
|
|
14
|
+
let n = t.migrate;
|
|
15
|
+
if (!n) return;
|
|
16
|
+
let r = Object.keys(n);
|
|
17
|
+
if (r.length === 0) throw new PostmasterError(`job "${e}" migrations must not be empty`);
|
|
18
|
+
let i = r.map((r) => {
|
|
19
|
+
let i = Number(r);
|
|
20
|
+
if (!Number.isInteger(i) || String(i) !== r) throw new PostmasterError(`job "${e}" has invalid migration key "${r}"`);
|
|
21
|
+
if (i < 1 || i >= t.version) throw new PostmasterError(`job "${e}" has invalid migration key "${i}" (valid range: 1..${t.version - 1})`);
|
|
22
|
+
if (typeof n[i] != "function") throw new PostmasterError(`job "${e}" migration from version ${i} must be a function`);
|
|
23
|
+
return i;
|
|
24
|
+
}).sort((e, t) => e - t);
|
|
25
|
+
for (let t = 1; t < i.length; t++) if (i[t] !== i[t - 1] + 1) throw new PostmasterError(`job "${e}" missing migration from version ${i[t - 1] + 1}`);
|
|
26
|
+
if (i.at(-1) !== t.version - 1) throw new PostmasterError(`job "${e}" migrations must lead into version ${t.version}`);
|
|
27
|
+
}
|
|
28
|
+
function validateJob(e, t) {
|
|
29
|
+
if (!Number.isInteger(t.version) || t.version < 1) throw new PostmasterError(`job "${e}" version must be a positive integer`);
|
|
30
|
+
if (typeof t.execute != "function" || typeof t.key != "function") throw new PostmasterError(`job "${e}" must define execute and key`);
|
|
31
|
+
validateRetry(t.retry, e), validateMigrations(e, t);
|
|
32
|
+
}
|
|
33
|
+
function defineJobs(e) {
|
|
34
|
+
for (let [t, n] of Object.entries(e)) validateJob(t, n);
|
|
35
|
+
return e;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/_json.ts
|
|
39
|
+
function runValidate(e, t) {
|
|
40
|
+
return e ? typeof e == "function" ? e(t) : e.parse(t) : t;
|
|
41
|
+
}
|
|
42
|
+
function asJsonValue(e) {
|
|
43
|
+
let t;
|
|
44
|
+
try {
|
|
45
|
+
t = JSON.stringify(e);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
throw new PostmasterError("job payload must be JSON-serializable", { cause: e });
|
|
48
|
+
}
|
|
49
|
+
if (t === void 0) throw new PostmasterError("job payload must be JSON-serializable");
|
|
50
|
+
return JSON.parse(t);
|
|
51
|
+
}
|
|
52
|
+
function failureFrom(e, t) {
|
|
53
|
+
return e instanceof Error ? {
|
|
54
|
+
message: e.message,
|
|
55
|
+
name: e.name,
|
|
56
|
+
occurredAt: t
|
|
57
|
+
} : {
|
|
58
|
+
message: String(e),
|
|
59
|
+
name: "Error",
|
|
60
|
+
occurredAt: t
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function isAbortError(e) {
|
|
64
|
+
return e instanceof DOMException && e.name === "AbortError";
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/store-ops.ts
|
|
68
|
+
async function claimJob(e, t) {
|
|
69
|
+
let n = await e.findClaimable(t.now);
|
|
70
|
+
if (!n) return;
|
|
71
|
+
let r = {
|
|
72
|
+
...n,
|
|
73
|
+
attempts: n.attempts + 1,
|
|
74
|
+
failure: void 0,
|
|
75
|
+
leaseExpiresAt: t.now + t.leaseDuration,
|
|
76
|
+
ownerId: t.ownerId,
|
|
77
|
+
status: "running",
|
|
78
|
+
updatedAt: t.now
|
|
79
|
+
};
|
|
80
|
+
return await e.put(r), r;
|
|
81
|
+
}
|
|
82
|
+
async function completeJob(e, t) {
|
|
83
|
+
let n = await e.get(t.id);
|
|
84
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.delete(t.id), !0);
|
|
85
|
+
}
|
|
86
|
+
async function releaseJob(e, t) {
|
|
87
|
+
let n = await e.get(t.id);
|
|
88
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
89
|
+
...n,
|
|
90
|
+
leaseExpiresAt: void 0,
|
|
91
|
+
ownerId: void 0,
|
|
92
|
+
status: "queued",
|
|
93
|
+
updatedAt: t.now
|
|
94
|
+
}), !0);
|
|
95
|
+
}
|
|
96
|
+
async function rescheduleJob(e, t) {
|
|
97
|
+
let n = await e.get(t.id);
|
|
98
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
99
|
+
...n,
|
|
100
|
+
availableAt: t.availableAt,
|
|
101
|
+
failure: t.failure,
|
|
102
|
+
leaseExpiresAt: void 0,
|
|
103
|
+
ownerId: void 0,
|
|
104
|
+
status: "queued",
|
|
105
|
+
updatedAt: t.now
|
|
106
|
+
}), !0);
|
|
107
|
+
}
|
|
108
|
+
async function deadLetterJob(e, t) {
|
|
109
|
+
let n = await e.get(t.id);
|
|
110
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
111
|
+
...n,
|
|
112
|
+
failure: t.failure,
|
|
113
|
+
leaseExpiresAt: void 0,
|
|
114
|
+
ownerId: void 0,
|
|
115
|
+
status: "dead-letter",
|
|
116
|
+
updatedAt: t.now
|
|
117
|
+
}), !0);
|
|
118
|
+
}
|
|
119
|
+
async function renewLeaseJob(e, t) {
|
|
120
|
+
let n = await e.get(t.id);
|
|
121
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
122
|
+
...n,
|
|
123
|
+
leaseExpiresAt: t.leaseExpiresAt,
|
|
124
|
+
updatedAt: t.now
|
|
125
|
+
}), !0);
|
|
126
|
+
}
|
|
127
|
+
async function requeueJob(e, t, n) {
|
|
128
|
+
let r = await e.get(t);
|
|
129
|
+
if (!r) return { status: "not-found" };
|
|
130
|
+
if (r.status === "running") return { status: "running" };
|
|
131
|
+
if (r.status !== "dead-letter") return { status: "not-dead-letter" };
|
|
132
|
+
let i = {
|
|
133
|
+
...r,
|
|
134
|
+
availableAt: n,
|
|
135
|
+
failure: void 0,
|
|
136
|
+
status: "queued",
|
|
137
|
+
updatedAt: n
|
|
138
|
+
};
|
|
139
|
+
return await e.put(i), {
|
|
140
|
+
entry: toEntry(i),
|
|
141
|
+
status: "requeued"
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function removeJob(e, t) {
|
|
145
|
+
let n = await e.get(t);
|
|
146
|
+
return n ? n.status === "running" ? { status: "running" } : (await e.delete(t), {
|
|
147
|
+
id: t,
|
|
148
|
+
status: "removed"
|
|
149
|
+
}) : { status: "not-found" };
|
|
150
|
+
}
|
|
151
|
+
async function enqueueJob(e, t) {
|
|
152
|
+
if (await e.get(t.id)) throw new PostmasterJobError(`Postmaster entry "${t.id}" already exists`);
|
|
153
|
+
await e.put(t);
|
|
154
|
+
}
|
|
155
|
+
function toEntry(e) {
|
|
156
|
+
return {
|
|
157
|
+
attempts: e.attempts,
|
|
158
|
+
availableAt: e.availableAt,
|
|
159
|
+
createdAt: e.createdAt,
|
|
160
|
+
...e.failure ? { failure: e.failure } : {},
|
|
161
|
+
id: e.id,
|
|
162
|
+
key: e.key,
|
|
163
|
+
name: e.name,
|
|
164
|
+
status: e.status,
|
|
165
|
+
updatedAt: e.updatedAt,
|
|
166
|
+
version: e.version
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/postmaster.ts
|
|
171
|
+
var emptyResult = () => ({
|
|
172
|
+
completed: 0,
|
|
173
|
+
deadLettered: 0,
|
|
174
|
+
processed: 0,
|
|
175
|
+
retryScheduled: 0
|
|
176
|
+
});
|
|
177
|
+
function assertLeaseDuration(e) {
|
|
178
|
+
if (!Number.isInteger(e) || e < 1e3) throw new PostmasterError("leaseDuration must be a positive integer of at least 1000ms");
|
|
179
|
+
}
|
|
180
|
+
function assertAvailableAt(e, t) {
|
|
181
|
+
if (e === void 0) return t;
|
|
182
|
+
if (!Number.isFinite(e) || e < 0 || !Number.isSafeInteger(e)) throw new PostmasterError("availableAt must be a finite non-negative safe integer");
|
|
183
|
+
return e;
|
|
184
|
+
}
|
|
185
|
+
function createPostmaster(n) {
|
|
186
|
+
let { clock: r = Date.now, jobs: i, leaseDuration: a = 3e4, signal: o, store: s } = n;
|
|
187
|
+
assertLeaseDuration(a);
|
|
188
|
+
let c = new AbortController(), l = crypto.randomUUID(), u = t(), d = !1, f = !1, p, m, h = /* @__PURE__ */ new Map(), g, error = (e) => {
|
|
189
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
190
|
+
emitTap({
|
|
191
|
+
error: t,
|
|
192
|
+
type: "processor-error"
|
|
193
|
+
});
|
|
194
|
+
}, emitTap = (e) => {
|
|
195
|
+
u.emit(e);
|
|
196
|
+
}, assertLive = () => {
|
|
197
|
+
if (d) throw new PostmasterDisposedError("Postmaster is disposed");
|
|
198
|
+
}, clearWakeTimer = () => {
|
|
199
|
+
m !== void 0 && clearTimeout(m), m = void 0;
|
|
200
|
+
}, wake = () => {
|
|
201
|
+
clearWakeTimer(), f && !d && pump();
|
|
202
|
+
}, schedule = async () => {
|
|
203
|
+
if (!f || d) return;
|
|
204
|
+
let e = await s.transact((e) => e.findNextWake(r()));
|
|
205
|
+
if (e === void 0) return;
|
|
206
|
+
let t = Math.max(0, Math.min(e - r(), 2147483647));
|
|
207
|
+
m = setTimeout(wake, t);
|
|
208
|
+
}, resolveJob = (e) => {
|
|
209
|
+
let t = i[e.name];
|
|
210
|
+
if (!t) throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);
|
|
211
|
+
return t;
|
|
212
|
+
}, deadLetter = async (e, t) => await s.transact((n) => deadLetterJob(n, {
|
|
213
|
+
failure: failureFrom(t, r()),
|
|
214
|
+
id: e.id,
|
|
215
|
+
now: r(),
|
|
216
|
+
ownerId: l
|
|
217
|
+
})) ? (emitTap({
|
|
218
|
+
entry: toEntry({
|
|
219
|
+
...e,
|
|
220
|
+
status: "dead-letter"
|
|
221
|
+
}),
|
|
222
|
+
type: "dead-lettered"
|
|
223
|
+
}), {
|
|
224
|
+
completed: 0,
|
|
225
|
+
deadLettered: 1,
|
|
226
|
+
processed: !0,
|
|
227
|
+
retryScheduled: 0
|
|
228
|
+
}) : (emitTap({
|
|
229
|
+
id: e.id,
|
|
230
|
+
type: "lease-lost"
|
|
231
|
+
}), {
|
|
232
|
+
completed: 0,
|
|
233
|
+
deadLettered: 0,
|
|
234
|
+
processed: !0,
|
|
235
|
+
retryScheduled: 0
|
|
236
|
+
}), execute = async (t, n) => {
|
|
237
|
+
let i, o;
|
|
238
|
+
try {
|
|
239
|
+
if (i = resolveJob(t), t.version > i.version) throw new PostmasterJobError(`job "${t.name}" version ${t.version} is newer than the registered version`);
|
|
240
|
+
let e = t.payload;
|
|
241
|
+
if (t.version < i.version) {
|
|
242
|
+
let n = i.migrate;
|
|
243
|
+
if (!n) throw new PostmasterJobError(`job "${t.name}" has no migrations`);
|
|
244
|
+
for (let r = t.version; r < i.version; r++) {
|
|
245
|
+
let i = n[r];
|
|
246
|
+
if (typeof i != "function") throw new PostmasterJobError(`job "${t.name}" does not support stored version ${t.version}`);
|
|
247
|
+
if (e = i(e), e === void 0) throw new PostmasterJobError(`job "${t.name}" migration from version ${r} returned undefined`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
o = runValidate(i.validate, e);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
return deadLetter(t, e);
|
|
253
|
+
}
|
|
254
|
+
let u = new AbortController(), d = AbortSignal.any([
|
|
255
|
+
c.signal,
|
|
256
|
+
n,
|
|
257
|
+
u.signal
|
|
258
|
+
].filter((e) => e !== void 0)), f = setInterval(() => {
|
|
259
|
+
s.transact((e) => renewLeaseJob(e, {
|
|
260
|
+
id: t.id,
|
|
261
|
+
leaseExpiresAt: r() + a,
|
|
262
|
+
now: r(),
|
|
263
|
+
ownerId: l
|
|
264
|
+
})).then((e) => {
|
|
265
|
+
e || (u.abort(), emitTap({
|
|
266
|
+
id: t.id,
|
|
267
|
+
type: "lease-lost"
|
|
268
|
+
}));
|
|
269
|
+
}).catch(error);
|
|
270
|
+
}, Math.max(1e3, Math.floor(a / 2)));
|
|
271
|
+
emitTap({
|
|
272
|
+
entry: toEntry(t),
|
|
273
|
+
type: "started"
|
|
274
|
+
});
|
|
275
|
+
try {
|
|
276
|
+
return await i.execute(o, {
|
|
277
|
+
attempt: t.attempts,
|
|
278
|
+
entryId: t.id,
|
|
279
|
+
key: t.key,
|
|
280
|
+
signal: d
|
|
281
|
+
}), d.aborted ? (await s.transact((e) => releaseJob(e, {
|
|
282
|
+
id: t.id,
|
|
283
|
+
now: r(),
|
|
284
|
+
ownerId: l
|
|
285
|
+
})), {
|
|
286
|
+
completed: 0,
|
|
287
|
+
deadLettered: 0,
|
|
288
|
+
processed: !0,
|
|
289
|
+
retryScheduled: 0
|
|
290
|
+
}) : await s.transact((e) => completeJob(e, {
|
|
291
|
+
id: t.id,
|
|
292
|
+
now: r(),
|
|
293
|
+
ownerId: l
|
|
294
|
+
})) ? (emitTap({
|
|
295
|
+
entry: toEntry(t),
|
|
296
|
+
type: "completed"
|
|
297
|
+
}), {
|
|
298
|
+
completed: 1,
|
|
299
|
+
deadLettered: 0,
|
|
300
|
+
processed: !0,
|
|
301
|
+
retryScheduled: 0
|
|
302
|
+
}) : (emitTap({
|
|
303
|
+
id: t.id,
|
|
304
|
+
type: "lease-lost"
|
|
305
|
+
}), {
|
|
306
|
+
completed: 0,
|
|
307
|
+
deadLettered: 0,
|
|
308
|
+
processed: !0,
|
|
309
|
+
retryScheduled: 0
|
|
310
|
+
});
|
|
311
|
+
} catch (n) {
|
|
312
|
+
if (d.aborted || isAbortError(n)) return await s.transact((e) => releaseJob(e, {
|
|
313
|
+
id: t.id,
|
|
314
|
+
now: r(),
|
|
315
|
+
ownerId: l
|
|
316
|
+
})), {
|
|
317
|
+
completed: 0,
|
|
318
|
+
deadLettered: 0,
|
|
319
|
+
processed: !0,
|
|
320
|
+
retryScheduled: 0
|
|
321
|
+
};
|
|
322
|
+
let a = !1, o = 0;
|
|
323
|
+
try {
|
|
324
|
+
if (a = t.attempts < (i.retry?.maxAttempts ?? 1) && (i.retry?.shouldRetry?.(n, t.attempts) ?? !0), !a) return deadLetter(t, n);
|
|
325
|
+
if (o = i.retry?.delay?.(t.attempts - 1) ?? e(t.attempts - 1), !Number.isFinite(o) || o < 0) throw new PostmasterError(`job "${t.name}" retry delay must be finite and non-negative`);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
return deadLetter(t, e);
|
|
328
|
+
}
|
|
329
|
+
let c = r() + o;
|
|
330
|
+
return await s.transact((e) => rescheduleJob(e, {
|
|
331
|
+
availableAt: c,
|
|
332
|
+
failure: failureFrom(n, r()),
|
|
333
|
+
id: t.id,
|
|
334
|
+
now: r(),
|
|
335
|
+
ownerId: l
|
|
336
|
+
})) ? (emitTap({
|
|
337
|
+
entry: toEntry({
|
|
338
|
+
...t,
|
|
339
|
+
availableAt: c,
|
|
340
|
+
status: "queued"
|
|
341
|
+
}),
|
|
342
|
+
type: "retry-scheduled"
|
|
343
|
+
}), {
|
|
344
|
+
completed: 0,
|
|
345
|
+
deadLettered: 0,
|
|
346
|
+
processed: !0,
|
|
347
|
+
retryScheduled: 1
|
|
348
|
+
}) : (emitTap({
|
|
349
|
+
id: t.id,
|
|
350
|
+
type: "lease-lost"
|
|
351
|
+
}), {
|
|
352
|
+
completed: 0,
|
|
353
|
+
deadLettered: 0,
|
|
354
|
+
processed: !0,
|
|
355
|
+
retryScheduled: 0
|
|
356
|
+
});
|
|
357
|
+
} finally {
|
|
358
|
+
clearInterval(f);
|
|
359
|
+
}
|
|
360
|
+
}, processNext = async (e) => {
|
|
361
|
+
if (e?.aborted) throw new DOMException("The operation was aborted", "AbortError");
|
|
362
|
+
if (c.signal.aborted) return {
|
|
363
|
+
completed: 0,
|
|
364
|
+
deadLettered: 0,
|
|
365
|
+
processed: !1,
|
|
366
|
+
retryScheduled: 0
|
|
367
|
+
};
|
|
368
|
+
let t = await s.transact((e) => claimJob(e, {
|
|
369
|
+
leaseDuration: a,
|
|
370
|
+
now: r(),
|
|
371
|
+
ownerId: l
|
|
372
|
+
}));
|
|
373
|
+
if (!t) return {
|
|
374
|
+
completed: 0,
|
|
375
|
+
deadLettered: 0,
|
|
376
|
+
processed: !1,
|
|
377
|
+
retryScheduled: 0
|
|
378
|
+
};
|
|
379
|
+
h.set(t.id, t);
|
|
380
|
+
try {
|
|
381
|
+
return await execute(t, e);
|
|
382
|
+
} finally {
|
|
383
|
+
h.delete(t.id);
|
|
384
|
+
}
|
|
385
|
+
}, pump = () => {
|
|
386
|
+
p || d || (p = (async () => {
|
|
387
|
+
for (; (await processNext()).processed && !d;);
|
|
388
|
+
await schedule();
|
|
389
|
+
})().catch(error).finally(() => {
|
|
390
|
+
p = void 0;
|
|
391
|
+
}));
|
|
392
|
+
}, _ = s.subscribe(wake), externalAbort = () => {
|
|
393
|
+
dispose();
|
|
394
|
+
};
|
|
395
|
+
o?.addEventListener("abort", externalAbort, { once: !0 });
|
|
396
|
+
let dispose = async () => {
|
|
397
|
+
if (d) return;
|
|
398
|
+
d = !0, emitTap({ type: "dispose" }), u.clear(), clearWakeTimer(), o?.removeEventListener("abort", externalAbort), _(), c.abort();
|
|
399
|
+
let e = [...h.values()];
|
|
400
|
+
h.clear(), await Promise.all(e.map((e) => s.transact((t) => releaseJob(t, {
|
|
401
|
+
id: e.id,
|
|
402
|
+
now: r(),
|
|
403
|
+
ownerId: l
|
|
404
|
+
}))));
|
|
405
|
+
};
|
|
406
|
+
return {
|
|
407
|
+
get disposalSignal() {
|
|
408
|
+
return c.signal;
|
|
409
|
+
},
|
|
410
|
+
async dispose() {
|
|
411
|
+
await dispose();
|
|
412
|
+
},
|
|
413
|
+
get disposed() {
|
|
414
|
+
return d;
|
|
415
|
+
},
|
|
416
|
+
async enqueue(e, t, n) {
|
|
417
|
+
assertLive();
|
|
418
|
+
let a = i[e];
|
|
419
|
+
if (!a) throw new PostmasterJobError(`no job definition is registered for "${e}"`);
|
|
420
|
+
let o = r(), c = assertAvailableAt(n?.availableAt, o), l = runValidate(a.validate, t), u = a.key(l);
|
|
421
|
+
if (typeof u != "string" || u.length === 0) throw new PostmasterError(`job "${e}" returned an empty key`);
|
|
422
|
+
let d = {
|
|
423
|
+
attempts: 0,
|
|
424
|
+
availableAt: c,
|
|
425
|
+
createdAt: o,
|
|
426
|
+
id: crypto.randomUUID(),
|
|
427
|
+
key: u,
|
|
428
|
+
name: e,
|
|
429
|
+
payload: asJsonValue(l),
|
|
430
|
+
status: "queued",
|
|
431
|
+
updatedAt: o,
|
|
432
|
+
version: a.version
|
|
433
|
+
};
|
|
434
|
+
await s.transact((e) => enqueueJob(e, d));
|
|
435
|
+
let f = toEntry(d);
|
|
436
|
+
return emitTap({
|
|
437
|
+
entry: f,
|
|
438
|
+
type: "enqueued"
|
|
439
|
+
}), wake(), f;
|
|
440
|
+
},
|
|
441
|
+
flush(e) {
|
|
442
|
+
if (assertLive(), g) return g;
|
|
443
|
+
clearWakeTimer();
|
|
444
|
+
let t = (async () => {
|
|
445
|
+
await p;
|
|
446
|
+
let t = emptyResult();
|
|
447
|
+
try {
|
|
448
|
+
for (;;) {
|
|
449
|
+
let n = await processNext(e?.signal);
|
|
450
|
+
if (!n.processed || (t.processed += 1, t.completed += n.completed, t.deadLettered += n.deadLettered, t.retryScheduled += n.retryScheduled, d)) return t;
|
|
451
|
+
}
|
|
452
|
+
} finally {
|
|
453
|
+
g = void 0, wake();
|
|
454
|
+
}
|
|
455
|
+
})();
|
|
456
|
+
return g = t, t;
|
|
457
|
+
},
|
|
458
|
+
async list(e) {
|
|
459
|
+
return assertLive(), (await s.list(e)).map(toEntry);
|
|
460
|
+
},
|
|
461
|
+
async remove(e) {
|
|
462
|
+
assertLive();
|
|
463
|
+
let t = await s.transact((t) => removeJob(t, e));
|
|
464
|
+
return t.status === "removed" && emitTap({
|
|
465
|
+
id: e,
|
|
466
|
+
type: "removed"
|
|
467
|
+
}), t;
|
|
468
|
+
},
|
|
469
|
+
async requeue(e) {
|
|
470
|
+
assertLive();
|
|
471
|
+
let t = await s.transact((t) => requeueJob(t, e, r()));
|
|
472
|
+
return t.status === "requeued" && wake(), t;
|
|
473
|
+
},
|
|
474
|
+
start() {
|
|
475
|
+
assertLive(), !f && (f = !0, wake());
|
|
476
|
+
},
|
|
477
|
+
async stats() {
|
|
478
|
+
return assertLive(), s.transact((e) => e.countByStatus());
|
|
479
|
+
},
|
|
480
|
+
tap(e, t) {
|
|
481
|
+
return assertLive(), u.tap(e, t);
|
|
482
|
+
},
|
|
483
|
+
async [Symbol.asyncDispose]() {
|
|
484
|
+
await dispose();
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
export { PostmasterDisposedError, PostmasterError, PostmasterJobError, createPostmaster, defineJobs };
|
|
490
|
+
|
|
2
491
|
//# sourceMappingURL=postmaster.js.map
|