@vielzeug/postmaster 26.11.5 → 26.11.6

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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PostmasterError as e } from "./errors.js";
2
- //#region src/_json.ts
2
+ // src/_json.ts
3
3
  function runValidate(e, t) {
4
4
  return e ? typeof e == "function" ? e(t) : e.parse(t) : t;
5
5
  }
@@ -27,7 +27,6 @@ function failureFrom(e, t) {
27
27
  function isAbortError(e) {
28
28
  return e instanceof DOMException && e.name === "AbortError";
29
29
  }
30
- //#endregion
31
30
  export { asJsonValue, failureFrom, isAbortError, runValidate };
32
31
 
33
32
  //# sourceMappingURL=_json.js.map
@@ -1,4 +1,4 @@
1
- //#region src/_store-utils.ts
1
+ // src/_store-utils.ts
2
2
  function normalize(e) {
3
3
  let { failure: t, leaseExpiresAt: n, ownerId: r, ...i } = e;
4
4
  return {
@@ -8,7 +8,6 @@ function normalize(e) {
8
8
  ...r === void 0 ? {} : { ownerId: r }
9
9
  };
10
10
  }
11
- //#endregion
12
11
  export { normalize };
13
12
 
14
13
  //# sourceMappingURL=_store-utils.js.map
@@ -1,5 +1,5 @@
1
1
  import { PostmasterError as e } from "./errors.js";
2
- //#region src/definitions.ts
2
+ // src/definitions.ts
3
3
  function validateRetry(t, n) {
4
4
  if (t && (!Number.isInteger(t.maxAttempts) || t.maxAttempts < 1)) throw new e(`job "${n}" retry.maxAttempts must be a positive integer`);
5
5
  }
@@ -27,7 +27,6 @@ function defineJobs(e) {
27
27
  for (let [t, n] of Object.entries(e)) validateJob(t, n);
28
28
  return e;
29
29
  }
30
- //#endregion
31
30
  export { defineJobs };
32
31
 
33
32
  //# sourceMappingURL=definitions.js.map
package/dist/errors.js CHANGED
@@ -1,10 +1,9 @@
1
- //#region src/errors.ts
1
+ // src/errors.ts
2
2
  var PostmasterError = class extends Error {
3
3
  constructor(e, t) {
4
4
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
5
  }
6
6
  }, PostmasterDisposedError = class extends PostmasterError {}, PostmasterJobError = class extends PostmasterError {};
7
- //#endregion
8
7
  export { PostmasterDisposedError, PostmasterError, PostmasterJobError };
9
8
 
10
9
  //# sourceMappingURL=errors.js.map
package/dist/index.cjs CHANGED
@@ -1 +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;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./definitions.cjs"),t=require("./errors.cjs"),n=require("./postmaster.cjs");exports.PostmasterDisposedError=t.PostmasterDisposedError,exports.PostmasterError=t.PostmasterError,exports.PostmasterJobError=t.PostmasterJobError,exports.createPostmaster=n.createPostmaster,exports.defineJobs=e.defineJobs;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { PostmasterDisposedError as e, PostmasterError as t, PostmasterJobError as n } from "./errors.js";
2
- import { defineJobs as r } from "./definitions.js";
1
+ import { defineJobs as e } from "./definitions.js";
2
+ import { PostmasterDisposedError as t, PostmasterError as n, PostmasterJobError as r } from "./errors.js";
3
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 };
4
+ export { t as PostmasterDisposedError, n as PostmasterError, r as PostmasterJobError, i as createPostmaster, e as defineJobs };
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./_store-utils.cjs");let n=require("@vielzeug/arsenal"),r=require("@vielzeug/vault"),i=require("@vielzeug/vault/indexeddb");var a=Symbol(`corrupt`),o=[`dead-letter`,`queued`,`running`];function isEntryStatus(e){return typeof e==`string`&&o.includes(e)}function isNonEmptyString(e){return typeof e==`string`&&e.length>0}function isNonNegativeInteger(e){return Number.isInteger(e)&&e>=0}function isTimestamp(e){return Number.isSafeInteger(e)&&e>=0}function isJsonValue(e,t=new Set){if(e===null||typeof e==`boolean`||typeof e==`string`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||t.has(e))return!1;t.add(e);let r=Array.isArray(e)?e.every(e=>isJsonValue(e,t)):(0,n.isPlainObject)(e)&&Object.values(e).every(e=>isJsonValue(e,t));return t.delete(e),r}function isStoredFailure(e){return(0,n.isPlainObject)(e)&&typeof e.message==`string`&&isNonEmptyString(e.name)&&isTimestamp(e.occurredAt)}function parseStoredJob(t){if(!(0,n.isPlainObject)(t))throw new e.PostmasterError(`invalid stored job: expected a plain object`);let{id:r,name:i,key:a,status:o,version:s,payload:c,attempts:l,availableAt:u,createdAt:d,updatedAt:f,failure:p,leaseExpiresAt:m,ownerId:h}=t;if(!isNonEmptyString(r))throw new e.PostmasterError(`invalid stored job: id must be a non-empty string`);if(!isNonEmptyString(i))throw new e.PostmasterError(`invalid stored job: name must be a non-empty string`);if(!isNonEmptyString(a))throw new e.PostmasterError(`invalid stored job: key must be a non-empty string`);if(!isEntryStatus(o))throw new e.PostmasterError(`invalid stored job: status must be one of dead-letter, queued, running`);if(!isNonNegativeInteger(s)||s<1)throw new e.PostmasterError(`invalid stored job: version must be a positive integer`);if(!isJsonValue(c))throw new e.PostmasterError(`invalid stored job: payload must be a JSON value`);if(!isNonNegativeInteger(l))throw new e.PostmasterError(`invalid stored job: attempts must be a non-negative integer`);if(!isTimestamp(u))throw new e.PostmasterError(`invalid stored job: availableAt must be a timestamp`);if(!isTimestamp(d))throw new e.PostmasterError(`invalid stored job: createdAt must be a timestamp`);if(!isTimestamp(f))throw new e.PostmasterError(`invalid stored job: updatedAt must be a timestamp`);if(p!==void 0&&!isStoredFailure(p))throw new e.PostmasterError(`invalid stored job: failure must be a StoredFailure`);if(o===`running`){if(l<1)throw new e.PostmasterError(`invalid stored job: running jobs must have an attempt`);if(!isTimestamp(m))throw new e.PostmasterError(`invalid stored job: running jobs must have a lease expiry`);if(!isNonEmptyString(h))throw new e.PostmasterError(`invalid stored job: running jobs must have an owner`);if(p!==void 0)throw new e.PostmasterError(`invalid stored job: running jobs must not have a failure`)}else if(m!==void 0||h!==void 0)throw new e.PostmasterError(`invalid stored job: only running jobs may have lease ownership`);return{attempts:l,availableAt:u,createdAt:d,id:r,key:a,name:i,payload:c,status:o,updatedAt:f,version:s,...p===void 0?{}:{failure:p},...m===void 0?{}:{leaseExpiresAt:m},...h===void 0?{}:{ownerId:h}}}function isValidRecord(e){return!(a in e)}function wrap(t,n){return n().catch(n=>{throw n instanceof e.PostmasterError?n:new e.PostmasterError(`${t} failed`,{cause:n})})}function createIndexedDbPostmasterStore(o){let s=new Set,decode=e=>{try{return parseStoredJob(e)}catch(t){let r=(0,n.isPlainObject)(e)&&isNonEmptyString(e.id)?e.id:void 0,i=t instanceof Error?t.message:String(t),c={...r===void 0?{}:{id:r},reason:i},l=`${r??``}\u0000${i}`;if(!s.has(l)){s.add(l);try{o.onCorruptRecord?.(c)}catch{}}return{[a]:c,id:r??``,...(0,n.isPlainObject)(e)&&isTimestamp(e.availableAt)?{availableAt:e.availableAt}:{},...(0,n.isPlainObject)(e)&&isTimestamp(e.leaseExpiresAt)?{leaseExpiresAt:e.leaseExpiresAt}:{},...(0,n.isPlainObject)(e)&&isEntryStatus(e.status)?{status:e.status}:{}}}},c={jobs:(0,r.table)(`id`,{indexes:[`availableAt`,`leaseExpiresAt`,`status`]})},l=(0,i.createIndexedDB)({codecs:{jobs:{decode,encode:t=>{if(!isValidRecord(t))throw new e.PostmasterError(`cannot persist a corrupt stored job`);return parseStoredJob(t)}}},name:o.name,schema:c}),buildTx=e=>({async countByStatus(){let t=(await e.getAll(`jobs`)).filter(isValidRecord),n=0,r=0,i=0;for(let e of t)e.status===`queued`?n+=1:e.status===`running`?r+=1:e.status===`dead-letter`&&(i+=1);return{deadLetter:i,queued:n,running:r}},async delete(t){await e.delete(`jobs`,t)},async findClaimable(n){let r=(await e.getAll(`jobs`)).filter(isValidRecord).filter(e=>e.status===`queued`&&e.availableAt<=n||e.status===`running`&&(e.leaseExpiresAt??0)<=n);return r.sort((e,t)=>e.availableAt-t.availableAt||e.createdAt-t.createdAt),r[0]?t.normalize(r[0]):void 0},async findNextWake(t){let n=(await e.getAll(`jobs`)).filter(isValidRecord),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(n){let r=await e.get(`jobs`,n);return r&&isValidRecord(r)?t.normalize(r):void 0},async put(n){await e.put(`jobs`,t.normalize(n))}});return{get disposalSignal(){return l.disposalSignal},dispose(){return wrap(`disposing IndexedDB store`,()=>l.dispose())},get disposed(){return l.disposed},list(e){return wrap(`listing jobs`,async()=>(e?.status===void 0?await l.getAll(`jobs`):await l.getAllByIndex(`jobs`,`status`,e.status)).filter(isValidRecord).sort((e,t)=>e.createdAt-t.createdAt).map(t.normalize))},subscribe(e){return l.observe(`jobs`,()=>e(),{immediate:!1})},transact(e){return wrap(`executing transaction`,()=>l.batch([`jobs`],async t=>e(buildTx(t))))},async[Symbol.asyncDispose](){await l.dispose()}}}exports.createIndexedDbPostmasterStore=createIndexedDbPostmasterStore;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_store-utils.cjs"),t=require("./errors.cjs");let n=require("@vielzeug/arsenal"),r=require("@vielzeug/vault"),i=require("@vielzeug/vault/indexeddb");var a=Symbol(`corrupt`),o=[`dead-letter`,`queued`,`running`];function isEntryStatus(e){return typeof e==`string`&&o.includes(e)}function isNonEmptyString(e){return typeof e==`string`&&e.length>0}function isNonNegativeInteger(e){return Number.isInteger(e)&&e>=0}function isTimestamp(e){return Number.isSafeInteger(e)&&e>=0}function isJsonValue(e,t=new Set){if(e===null||typeof e==`boolean`||typeof e==`string`)return!0;if(typeof e==`number`)return Number.isFinite(e);if(typeof e!=`object`||t.has(e))return!1;t.add(e);let r=Array.isArray(e)?e.every(e=>isJsonValue(e,t)):(0,n.isPlainObject)(e)&&Object.values(e).every(e=>isJsonValue(e,t));return t.delete(e),r}function isStoredFailure(e){return(0,n.isPlainObject)(e)&&typeof e.message==`string`&&isNonEmptyString(e.name)&&isTimestamp(e.occurredAt)}function parseStoredJob(e){if(!(0,n.isPlainObject)(e))throw new t.PostmasterError(`invalid stored job: expected a plain object`);let{id:r,name:i,key:a,status:o,version:s,payload:c,attempts:l,availableAt:u,createdAt:d,updatedAt:f,failure:p,leaseExpiresAt:m,ownerId:h}=e;if(!isNonEmptyString(r))throw new t.PostmasterError(`invalid stored job: id must be a non-empty string`);if(!isNonEmptyString(i))throw new t.PostmasterError(`invalid stored job: name must be a non-empty string`);if(!isNonEmptyString(a))throw new t.PostmasterError(`invalid stored job: key must be a non-empty string`);if(!isEntryStatus(o))throw new t.PostmasterError(`invalid stored job: status must be one of dead-letter, queued, running`);if(!isNonNegativeInteger(s)||s<1)throw new t.PostmasterError(`invalid stored job: version must be a positive integer`);if(!isJsonValue(c))throw new t.PostmasterError(`invalid stored job: payload must be a JSON value`);if(!isNonNegativeInteger(l))throw new t.PostmasterError(`invalid stored job: attempts must be a non-negative integer`);if(!isTimestamp(u))throw new t.PostmasterError(`invalid stored job: availableAt must be a timestamp`);if(!isTimestamp(d))throw new t.PostmasterError(`invalid stored job: createdAt must be a timestamp`);if(!isTimestamp(f))throw new t.PostmasterError(`invalid stored job: updatedAt must be a timestamp`);if(p!==void 0&&!isStoredFailure(p))throw new t.PostmasterError(`invalid stored job: failure must be a StoredFailure`);if(o===`running`){if(l<1)throw new t.PostmasterError(`invalid stored job: running jobs must have an attempt`);if(!isTimestamp(m))throw new t.PostmasterError(`invalid stored job: running jobs must have a lease expiry`);if(!isNonEmptyString(h))throw new t.PostmasterError(`invalid stored job: running jobs must have an owner`);if(p!==void 0)throw new t.PostmasterError(`invalid stored job: running jobs must not have a failure`)}else if(m!==void 0||h!==void 0)throw new t.PostmasterError(`invalid stored job: only running jobs may have lease ownership`);return{attempts:l,availableAt:u,createdAt:d,id:r,key:a,name:i,payload:c,status:o,updatedAt:f,version:s,...p===void 0?{}:{failure:p},...m===void 0?{}:{leaseExpiresAt:m},...h===void 0?{}:{ownerId:h}}}function isValidRecord(e){return!(a in e)}function wrap(e,n){return n().catch(n=>{throw n instanceof t.PostmasterError?n:new t.PostmasterError(`${e} failed`,{cause:n})})}function createIndexedDbPostmasterStore(o){let s=new Set,decode=e=>{try{return parseStoredJob(e)}catch(t){let r=(0,n.isPlainObject)(e)&&isNonEmptyString(e.id)?e.id:void 0,i=t instanceof Error?t.message:String(t),c={...r===void 0?{}:{id:r},reason:i},l=`${r??``}\u0000${i}`;if(!s.has(l)){s.add(l);try{o.onCorruptRecord?.(c)}catch{}}return{[a]:c,id:r??``,...(0,n.isPlainObject)(e)&&isTimestamp(e.availableAt)?{availableAt:e.availableAt}:{},...(0,n.isPlainObject)(e)&&isTimestamp(e.leaseExpiresAt)?{leaseExpiresAt:e.leaseExpiresAt}:{},...(0,n.isPlainObject)(e)&&isEntryStatus(e.status)?{status:e.status}:{}}}},c={jobs:(0,r.table)(`id`,{indexes:[`availableAt`,`leaseExpiresAt`,`status`]})},l=(0,i.createIndexedDB)({codecs:{jobs:{decode,encode:e=>{if(!isValidRecord(e))throw new t.PostmasterError(`cannot persist a corrupt stored job`);return parseStoredJob(e)}}},name:o.name,schema:c}),buildTx=t=>({async countByStatus(){let e=(await t.getAll(`jobs`)).filter(isValidRecord),n=0,r=0,i=0;for(let t of e)t.status===`queued`?n+=1:t.status===`running`?r+=1:t.status===`dead-letter`&&(i+=1);return{deadLetter:i,queued:n,running:r}},async delete(e){await t.delete(`jobs`,e)},async findClaimable(n){let r=(await t.getAll(`jobs`)).filter(isValidRecord).filter(e=>e.status===`queued`&&e.availableAt<=n||e.status===`running`&&(e.leaseExpiresAt??0)<=n);return r.sort((e,t)=>e.availableAt-t.availableAt||e.createdAt-t.createdAt),r[0]?e.normalize(r[0]):void 0},async findNextWake(e){let n=(await t.getAll(`jobs`)).filter(isValidRecord),r;for(let t of n){let n;t.status===`queued`&&t.availableAt>e?n=t.availableAt:t.status===`running`&&(t.leaseExpiresAt??0)>e&&(n=t.leaseExpiresAt),n!==void 0&&(r===void 0||n<r)&&(r=n)}return r},async get(n){let r=await t.get(`jobs`,n);return r&&isValidRecord(r)?e.normalize(r):void 0},async put(n){await t.put(`jobs`,e.normalize(n))}});return{get disposalSignal(){return l.disposalSignal},dispose(){return wrap(`disposing IndexedDB store`,()=>l.dispose())},get disposed(){return l.disposed},list(t){return wrap(`listing jobs`,async()=>(t?.status===void 0?await l.getAll(`jobs`):await l.getAllByIndex(`jobs`,`status`,t.status)).filter(isValidRecord).sort((e,t)=>e.createdAt-t.createdAt).map(e.normalize))},subscribe(e){return l.observe(`jobs`,()=>e(),{immediate:!1})},transact(e){return wrap(`executing transaction`,()=>l.batch([`jobs`],async t=>e(buildTx(t))))},async[Symbol.asyncDispose](){await l.dispose()}}}exports.createIndexedDbPostmasterStore=createIndexedDbPostmasterStore;
2
2
  //# sourceMappingURL=indexeddb.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexeddb.cjs","names":["isPlainObject","PostmasterError","table","createIndexedDB","normalize","require_errors","require__store_utils","_vielzeug_arsenal","_vielzeug_vault","_vielzeug_vault_indexeddb","CORRUPT","ENTRY_STATUSES","value","seen","valid","item","id","name","key","status","version","payload","attempts","availableAt","createdAt","updatedAt","failure","leaseExpiresAt","ownerId","record","fn","cause","operation","reported","reason","issue","signature","options","schema","vault","entries","vaultTx","queued","running","deadLetter","entry","candidates","now","a","b","next","wake","filter","listener"],"sources":["../src/indexeddb.ts"],"sourcesContent":["import { isPlainObject } from '@vielzeug/arsenal';\nimport { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\nimport { normalize } from './_store-utils.ts';\nimport { PostmasterError } from './errors.ts';\nimport type {\n EntryFilter,\n EntryStatus,\n JsonValue,\n PostmasterStats,\n PostmasterStore,\n StoredFailure,\n StoredJob,\n StoreTx,\n} from './types.ts';\n\nconst CORRUPT = Symbol('corrupt');\n\ntype CorruptRecord = {\n readonly [CORRUPT]: CorruptStoredJob;\n readonly availableAt?: number;\n readonly id: string;\n readonly leaseExpiresAt?: number;\n readonly status?: EntryStatus;\n};\n\ntype DatabaseJob = CorruptRecord | StoredJob;\n\ntype PostmasterSchema = {\n jobs: ReturnType<typeof table<DatabaseJob, 'id'>>;\n};\n\nexport interface CorruptStoredJob {\n readonly id?: string;\n readonly reason: string;\n}\n\ntype IndexedDbPostmasterStoreOptions = {\n readonly name: string;\n readonly onCorruptRecord?: (record: CorruptStoredJob) => void;\n};\n\nconst ENTRY_STATUSES: readonly EntryStatus[] = ['dead-letter', 'queued', 'running'];\n\nfunction isEntryStatus(value: unknown): value is EntryStatus {\n return typeof value === 'string' && ENTRY_STATUSES.includes(value as EntryStatus);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n return Number.isInteger(value) && (value as number) >= 0;\n}\n\nfunction isTimestamp(value: unknown): value is number {\n return Number.isSafeInteger(value) && (value as number) >= 0;\n}\n\nfunction isJsonValue(value: unknown, seen = new Set<object>()): value is JsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (typeof value !== 'object' || seen.has(value)) return false;\n\n seen.add(value);\n const valid = Array.isArray(value)\n ? value.every((item) => isJsonValue(item, seen))\n : isPlainObject(value) && Object.values(value).every((item) => isJsonValue(item, seen));\n seen.delete(value);\n return valid;\n}\n\nfunction isStoredFailure(value: unknown): value is StoredFailure {\n return (\n isPlainObject(value) &&\n typeof value.message === 'string' &&\n isNonEmptyString(value.name) &&\n isTimestamp(value.occurredAt)\n );\n}\n\n/**\n * Validates that persisted data conforms to the {@link StoredJob} shape before\n * it re-enters Postmaster's in-memory world. Replaces the prior no-op cast and\n * enforces Vault's required-codec boundary on durable records.\n */\nfunction parseStoredJob(value: unknown): StoredJob {\n if (!isPlainObject(value)) {\n throw new PostmasterError('invalid stored job: expected a plain object');\n }\n\n const {\n id,\n name,\n key,\n status,\n version,\n payload,\n attempts,\n availableAt,\n createdAt,\n updatedAt,\n failure,\n leaseExpiresAt,\n ownerId,\n } = value;\n\n if (!isNonEmptyString(id)) throw new PostmasterError('invalid stored job: id must be a non-empty string');\n if (!isNonEmptyString(name)) throw new PostmasterError('invalid stored job: name must be a non-empty string');\n if (!isNonEmptyString(key)) throw new PostmasterError('invalid stored job: key must be a non-empty string');\n if (!isEntryStatus(status)) {\n throw new PostmasterError('invalid stored job: status must be one of dead-letter, queued, running');\n }\n if (!isNonNegativeInteger(version) || version < 1) {\n throw new PostmasterError('invalid stored job: version must be a positive integer');\n }\n if (!isJsonValue(payload)) throw new PostmasterError('invalid stored job: payload must be a JSON value');\n if (!isNonNegativeInteger(attempts)) {\n throw new PostmasterError('invalid stored job: attempts must be a non-negative integer');\n }\n if (!isTimestamp(availableAt)) throw new PostmasterError('invalid stored job: availableAt must be a timestamp');\n if (!isTimestamp(createdAt)) throw new PostmasterError('invalid stored job: createdAt must be a timestamp');\n if (!isTimestamp(updatedAt)) throw new PostmasterError('invalid stored job: updatedAt must be a timestamp');\n if (failure !== undefined && !isStoredFailure(failure)) {\n throw new PostmasterError('invalid stored job: failure must be a StoredFailure');\n }\n\n if (status === 'running') {\n if (attempts < 1) throw new PostmasterError('invalid stored job: running jobs must have an attempt');\n if (!isTimestamp(leaseExpiresAt)) {\n throw new PostmasterError('invalid stored job: running jobs must have a lease expiry');\n }\n if (!isNonEmptyString(ownerId)) {\n throw new PostmasterError('invalid stored job: running jobs must have an owner');\n }\n if (failure !== undefined) throw new PostmasterError('invalid stored job: running jobs must not have a failure');\n } else if (leaseExpiresAt !== undefined || ownerId !== undefined) {\n throw new PostmasterError('invalid stored job: only running jobs may have lease ownership');\n }\n\n return {\n attempts,\n availableAt,\n createdAt,\n id,\n key,\n name,\n payload,\n status,\n updatedAt,\n version,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nfunction isValidRecord(record: DatabaseJob): record is StoredJob {\n return !(CORRUPT in record);\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 reported = new Set<string>();\n const decode = (value: unknown): DatabaseJob => {\n try {\n return parseStoredJob(value);\n } catch (cause) {\n const id = isPlainObject(value) && isNonEmptyString(value.id) ? value.id : undefined;\n const reason = cause instanceof Error ? cause.message : String(cause);\n const issue: CorruptStoredJob = { ...(id === undefined ? {} : { id }), reason };\n const signature = `${id ?? ''}\\u0000${reason}`;\n if (!reported.has(signature)) {\n reported.add(signature);\n try {\n options.onCorruptRecord?.(issue);\n } catch {}\n }\n return {\n [CORRUPT]: issue,\n id: id ?? '',\n ...(isPlainObject(value) && isTimestamp(value.availableAt) ? { availableAt: value.availableAt } : {}),\n ...(isPlainObject(value) && isTimestamp(value.leaseExpiresAt) ? { leaseExpiresAt: value.leaseExpiresAt } : {}),\n ...(isPlainObject(value) && isEntryStatus(value.status) ? { status: value.status } : {}),\n };\n }\n };\n const schema: PostmasterSchema = {\n jobs: table<DatabaseJob, 'id'>('id', {\n indexes: ['availableAt', 'leaseExpiresAt', 'status'],\n }),\n };\n const vault = createIndexedDB({\n codecs: {\n jobs: {\n decode,\n encode: (record) => {\n if (!isValidRecord(record)) throw new PostmasterError('cannot persist a corrupt stored job');\n return parseStoredJob(record);\n },\n },\n },\n name: options.name,\n schema,\n });\n\n const buildTx = (vaultTx: Parameters<Parameters<typeof vault.batch>[1]>[0]): StoreTx => ({\n async countByStatus(): Promise<PostmasterStats> {\n const entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\n let queued = 0;\n let running = 0;\n let deadLetter = 0;\n for (const entry of entries) {\n if (entry.status === 'queued') queued += 1;\n else if (entry.status === 'running') running += 1;\n else if (entry.status === 'dead-letter') deadLetter += 1;\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 entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\n const candidates = entries.filter(\n (entry) =>\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now),\n );\n candidates.sort((a, b) => a.availableAt - b.availableAt || a.createdAt - b.createdAt);\n return candidates[0] ? normalize(candidates[0]) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n const entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\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 && isValidRecord(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 entries =\n filter?.status === undefined\n ? await vault.getAll('jobs')\n : await vault.getAllByIndex('jobs', 'status', filter.status);\n return entries\n .filter(isValidRecord)\n .sort((a, b) => a.createdAt - b.createdAt)\n .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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"}
1
+ {"version":3,"file":"indexeddb.cjs","names":["isPlainObject","PostmasterError","table","createIndexedDB","normalize","require__store_utils","require_errors","_vielzeug_arsenal","_vielzeug_vault","_vielzeug_vault_indexeddb","CORRUPT","ENTRY_STATUSES","value","seen","valid","item","id","name","key","status","version","payload","attempts","availableAt","createdAt","updatedAt","failure","leaseExpiresAt","ownerId","record","fn","cause","operation","reported","reason","issue","signature","options","schema","vault","entries","vaultTx","queued","running","deadLetter","entry","candidates","now","a","b","next","wake","filter","listener"],"sources":["../src/indexeddb.ts"],"sourcesContent":["import { isPlainObject } from '@vielzeug/arsenal';\nimport { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\nimport { normalize } from './_store-utils.ts';\nimport { PostmasterError } from './errors.ts';\nimport type {\n EntryFilter,\n EntryStatus,\n JsonValue,\n PostmasterStats,\n PostmasterStore,\n StoredFailure,\n StoredJob,\n StoreTx,\n} from './types.ts';\n\nconst CORRUPT = Symbol('corrupt');\n\ntype CorruptRecord = {\n readonly [CORRUPT]: CorruptStoredJob;\n readonly availableAt?: number;\n readonly id: string;\n readonly leaseExpiresAt?: number;\n readonly status?: EntryStatus;\n};\n\ntype DatabaseJob = CorruptRecord | StoredJob;\n\ntype PostmasterSchema = {\n jobs: ReturnType<typeof table<DatabaseJob, 'id'>>;\n};\n\nexport interface CorruptStoredJob {\n readonly id?: string;\n readonly reason: string;\n}\n\ntype IndexedDbPostmasterStoreOptions = {\n readonly name: string;\n readonly onCorruptRecord?: (record: CorruptStoredJob) => void;\n};\n\nconst ENTRY_STATUSES: readonly EntryStatus[] = ['dead-letter', 'queued', 'running'];\n\nfunction isEntryStatus(value: unknown): value is EntryStatus {\n return typeof value === 'string' && ENTRY_STATUSES.includes(value as EntryStatus);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n return Number.isInteger(value) && (value as number) >= 0;\n}\n\nfunction isTimestamp(value: unknown): value is number {\n return Number.isSafeInteger(value) && (value as number) >= 0;\n}\n\nfunction isJsonValue(value: unknown, seen = new Set<object>()): value is JsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (typeof value !== 'object' || seen.has(value)) return false;\n\n seen.add(value);\n const valid = Array.isArray(value)\n ? value.every((item) => isJsonValue(item, seen))\n : isPlainObject(value) && Object.values(value).every((item) => isJsonValue(item, seen));\n seen.delete(value);\n return valid;\n}\n\nfunction isStoredFailure(value: unknown): value is StoredFailure {\n return (\n isPlainObject(value) &&\n typeof value.message === 'string' &&\n isNonEmptyString(value.name) &&\n isTimestamp(value.occurredAt)\n );\n}\n\n/**\n * Validates that persisted data conforms to the {@link StoredJob} shape before\n * it re-enters Postmaster's in-memory world. Replaces the prior no-op cast and\n * enforces Vault's required-codec boundary on durable records.\n */\nfunction parseStoredJob(value: unknown): StoredJob {\n if (!isPlainObject(value)) {\n throw new PostmasterError('invalid stored job: expected a plain object');\n }\n\n const {\n id,\n name,\n key,\n status,\n version,\n payload,\n attempts,\n availableAt,\n createdAt,\n updatedAt,\n failure,\n leaseExpiresAt,\n ownerId,\n } = value;\n\n if (!isNonEmptyString(id)) throw new PostmasterError('invalid stored job: id must be a non-empty string');\n if (!isNonEmptyString(name)) throw new PostmasterError('invalid stored job: name must be a non-empty string');\n if (!isNonEmptyString(key)) throw new PostmasterError('invalid stored job: key must be a non-empty string');\n if (!isEntryStatus(status)) {\n throw new PostmasterError('invalid stored job: status must be one of dead-letter, queued, running');\n }\n if (!isNonNegativeInteger(version) || version < 1) {\n throw new PostmasterError('invalid stored job: version must be a positive integer');\n }\n if (!isJsonValue(payload)) throw new PostmasterError('invalid stored job: payload must be a JSON value');\n if (!isNonNegativeInteger(attempts)) {\n throw new PostmasterError('invalid stored job: attempts must be a non-negative integer');\n }\n if (!isTimestamp(availableAt)) throw new PostmasterError('invalid stored job: availableAt must be a timestamp');\n if (!isTimestamp(createdAt)) throw new PostmasterError('invalid stored job: createdAt must be a timestamp');\n if (!isTimestamp(updatedAt)) throw new PostmasterError('invalid stored job: updatedAt must be a timestamp');\n if (failure !== undefined && !isStoredFailure(failure)) {\n throw new PostmasterError('invalid stored job: failure must be a StoredFailure');\n }\n\n if (status === 'running') {\n if (attempts < 1) throw new PostmasterError('invalid stored job: running jobs must have an attempt');\n if (!isTimestamp(leaseExpiresAt)) {\n throw new PostmasterError('invalid stored job: running jobs must have a lease expiry');\n }\n if (!isNonEmptyString(ownerId)) {\n throw new PostmasterError('invalid stored job: running jobs must have an owner');\n }\n if (failure !== undefined) throw new PostmasterError('invalid stored job: running jobs must not have a failure');\n } else if (leaseExpiresAt !== undefined || ownerId !== undefined) {\n throw new PostmasterError('invalid stored job: only running jobs may have lease ownership');\n }\n\n return {\n attempts,\n availableAt,\n createdAt,\n id,\n key,\n name,\n payload,\n status,\n updatedAt,\n version,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nfunction isValidRecord(record: DatabaseJob): record is StoredJob {\n return !(CORRUPT in record);\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 reported = new Set<string>();\n const decode = (value: unknown): DatabaseJob => {\n try {\n return parseStoredJob(value);\n } catch (cause) {\n const id = isPlainObject(value) && isNonEmptyString(value.id) ? value.id : undefined;\n const reason = cause instanceof Error ? cause.message : String(cause);\n const issue: CorruptStoredJob = { ...(id === undefined ? {} : { id }), reason };\n const signature = `${id ?? ''}\\u0000${reason}`;\n if (!reported.has(signature)) {\n reported.add(signature);\n try {\n options.onCorruptRecord?.(issue);\n } catch {}\n }\n return {\n [CORRUPT]: issue,\n id: id ?? '',\n ...(isPlainObject(value) && isTimestamp(value.availableAt) ? { availableAt: value.availableAt } : {}),\n ...(isPlainObject(value) && isTimestamp(value.leaseExpiresAt) ? { leaseExpiresAt: value.leaseExpiresAt } : {}),\n ...(isPlainObject(value) && isEntryStatus(value.status) ? { status: value.status } : {}),\n };\n }\n };\n const schema: PostmasterSchema = {\n jobs: table<DatabaseJob, 'id'>('id', {\n indexes: ['availableAt', 'leaseExpiresAt', 'status'],\n }),\n };\n const vault = createIndexedDB({\n codecs: {\n jobs: {\n decode,\n encode: (record) => {\n if (!isValidRecord(record)) throw new PostmasterError('cannot persist a corrupt stored job');\n return parseStoredJob(record);\n },\n },\n },\n name: options.name,\n schema,\n });\n\n const buildTx = (vaultTx: Parameters<Parameters<typeof vault.batch>[1]>[0]): StoreTx => ({\n async countByStatus(): Promise<PostmasterStats> {\n const entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\n let queued = 0;\n let running = 0;\n let deadLetter = 0;\n for (const entry of entries) {\n if (entry.status === 'queued') queued += 1;\n else if (entry.status === 'running') running += 1;\n else if (entry.status === 'dead-letter') deadLetter += 1;\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 entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\n const candidates = entries.filter(\n (entry) =>\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now),\n );\n candidates.sort((a, b) => a.availableAt - b.availableAt || a.createdAt - b.createdAt);\n return candidates[0] ? normalize(candidates[0]) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n const entries = (await vaultTx.getAll('jobs')).filter(isValidRecord);\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 && isValidRecord(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 entries =\n filter?.status === undefined\n ? await vault.getAll('jobs')\n : await vault.getAllByIndex('jobs', 'status', filter.status);\n return entries\n .filter(isValidRecord)\n .sort((a, b) => a.createdAt - b.createdAt)\n .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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"}
package/dist/indexeddb.js CHANGED
@@ -1,9 +1,9 @@
1
- import { PostmasterError as e } from "./errors.js";
2
- import { normalize as t } from "./_store-utils.js";
1
+ import { normalize as e } from "./_store-utils.js";
2
+ import { PostmasterError as t } from "./errors.js";
3
3
  import { isPlainObject as n } from "@vielzeug/arsenal";
4
4
  import { table as r } from "@vielzeug/vault";
5
5
  import { createIndexedDB as i } from "@vielzeug/vault/indexeddb";
6
- //#region src/indexeddb.ts
6
+ // src/indexeddb.ts
7
7
  var a = Symbol("corrupt"), o = [
8
8
  "dead-letter",
9
9
  "queued",
@@ -32,26 +32,26 @@ function isJsonValue(e, t = /* @__PURE__ */ new Set()) {
32
32
  function isStoredFailure(e) {
33
33
  return n(e) && typeof e.message == "string" && isNonEmptyString(e.name) && isTimestamp(e.occurredAt);
34
34
  }
35
- function parseStoredJob(t) {
36
- if (!n(t)) throw new e("invalid stored job: expected a plain object");
37
- let { id: r, name: i, key: a, status: o, version: s, payload: c, attempts: l, availableAt: u, createdAt: d, updatedAt: f, failure: p, leaseExpiresAt: m, ownerId: h } = t;
38
- if (!isNonEmptyString(r)) throw new e("invalid stored job: id must be a non-empty string");
39
- if (!isNonEmptyString(i)) throw new e("invalid stored job: name must be a non-empty string");
40
- if (!isNonEmptyString(a)) throw new e("invalid stored job: key must be a non-empty string");
41
- if (!isEntryStatus(o)) throw new e("invalid stored job: status must be one of dead-letter, queued, running");
42
- if (!isNonNegativeInteger(s) || s < 1) throw new e("invalid stored job: version must be a positive integer");
43
- if (!isJsonValue(c)) throw new e("invalid stored job: payload must be a JSON value");
44
- if (!isNonNegativeInteger(l)) throw new e("invalid stored job: attempts must be a non-negative integer");
45
- if (!isTimestamp(u)) throw new e("invalid stored job: availableAt must be a timestamp");
46
- if (!isTimestamp(d)) throw new e("invalid stored job: createdAt must be a timestamp");
47
- if (!isTimestamp(f)) throw new e("invalid stored job: updatedAt must be a timestamp");
48
- if (p !== void 0 && !isStoredFailure(p)) throw new e("invalid stored job: failure must be a StoredFailure");
35
+ function parseStoredJob(e) {
36
+ if (!n(e)) throw new t("invalid stored job: expected a plain object");
37
+ let { id: r, name: i, key: a, status: o, version: s, payload: c, attempts: l, availableAt: u, createdAt: d, updatedAt: f, failure: p, leaseExpiresAt: m, ownerId: h } = e;
38
+ if (!isNonEmptyString(r)) throw new t("invalid stored job: id must be a non-empty string");
39
+ if (!isNonEmptyString(i)) throw new t("invalid stored job: name must be a non-empty string");
40
+ if (!isNonEmptyString(a)) throw new t("invalid stored job: key must be a non-empty string");
41
+ if (!isEntryStatus(o)) throw new t("invalid stored job: status must be one of dead-letter, queued, running");
42
+ if (!isNonNegativeInteger(s) || s < 1) throw new t("invalid stored job: version must be a positive integer");
43
+ if (!isJsonValue(c)) throw new t("invalid stored job: payload must be a JSON value");
44
+ if (!isNonNegativeInteger(l)) throw new t("invalid stored job: attempts must be a non-negative integer");
45
+ if (!isTimestamp(u)) throw new t("invalid stored job: availableAt must be a timestamp");
46
+ if (!isTimestamp(d)) throw new t("invalid stored job: createdAt must be a timestamp");
47
+ if (!isTimestamp(f)) throw new t("invalid stored job: updatedAt must be a timestamp");
48
+ if (p !== void 0 && !isStoredFailure(p)) throw new t("invalid stored job: failure must be a StoredFailure");
49
49
  if (o === "running") {
50
- if (l < 1) throw new e("invalid stored job: running jobs must have an attempt");
51
- if (!isTimestamp(m)) throw new e("invalid stored job: running jobs must have a lease expiry");
52
- if (!isNonEmptyString(h)) throw new e("invalid stored job: running jobs must have an owner");
53
- if (p !== void 0) throw new e("invalid stored job: running jobs must not have a failure");
54
- } else if (m !== void 0 || h !== void 0) throw new e("invalid stored job: only running jobs may have lease ownership");
50
+ if (l < 1) throw new t("invalid stored job: running jobs must have an attempt");
51
+ if (!isTimestamp(m)) throw new t("invalid stored job: running jobs must have a lease expiry");
52
+ if (!isNonEmptyString(h)) throw new t("invalid stored job: running jobs must have an owner");
53
+ if (p !== void 0) throw new t("invalid stored job: running jobs must not have a failure");
54
+ } else if (m !== void 0 || h !== void 0) throw new t("invalid stored job: only running jobs may have lease ownership");
55
55
  return {
56
56
  attempts: l,
57
57
  availableAt: u,
@@ -71,9 +71,9 @@ function parseStoredJob(t) {
71
71
  function isValidRecord(e) {
72
72
  return !(a in e);
73
73
  }
74
- function wrap(t, n) {
74
+ function wrap(e, n) {
75
75
  return n().catch((n) => {
76
- throw n instanceof e ? n : new e(`${t} failed`, { cause: n });
76
+ throw n instanceof t ? n : new t(`${e} failed`, { cause: n });
77
77
  });
78
78
  }
79
79
  function createIndexedDbPostmasterStore(o) {
@@ -106,44 +106,44 @@ function createIndexedDbPostmasterStore(o) {
106
106
  ] }) }, l = i({
107
107
  codecs: { jobs: {
108
108
  decode,
109
- encode: (t) => {
110
- if (!isValidRecord(t)) throw new e("cannot persist a corrupt stored job");
111
- return parseStoredJob(t);
109
+ encode: (e) => {
110
+ if (!isValidRecord(e)) throw new t("cannot persist a corrupt stored job");
111
+ return parseStoredJob(e);
112
112
  }
113
113
  } },
114
114
  name: o.name,
115
115
  schema: c
116
- }), buildTx = (e) => ({
116
+ }), buildTx = (t) => ({
117
117
  async countByStatus() {
118
- let t = (await e.getAll("jobs")).filter(isValidRecord), n = 0, r = 0, i = 0;
119
- for (let e of t) e.status === "queued" ? n += 1 : e.status === "running" ? r += 1 : e.status === "dead-letter" && (i += 1);
118
+ let e = (await t.getAll("jobs")).filter(isValidRecord), n = 0, r = 0, i = 0;
119
+ for (let t of e) t.status === "queued" ? n += 1 : t.status === "running" ? r += 1 : t.status === "dead-letter" && (i += 1);
120
120
  return {
121
121
  deadLetter: i,
122
122
  queued: n,
123
123
  running: r
124
124
  };
125
125
  },
126
- async delete(t) {
127
- await e.delete("jobs", t);
126
+ async delete(e) {
127
+ await t.delete("jobs", e);
128
128
  },
129
129
  async findClaimable(n) {
130
- let r = (await e.getAll("jobs")).filter(isValidRecord).filter((e) => e.status === "queued" && e.availableAt <= n || e.status === "running" && (e.leaseExpiresAt ?? 0) <= n);
131
- return r.sort((e, t) => e.availableAt - t.availableAt || e.createdAt - t.createdAt), r[0] ? t(r[0]) : void 0;
130
+ let r = (await t.getAll("jobs")).filter(isValidRecord).filter((e) => e.status === "queued" && e.availableAt <= n || e.status === "running" && (e.leaseExpiresAt ?? 0) <= n);
131
+ return r.sort((e, t) => e.availableAt - t.availableAt || e.createdAt - t.createdAt), r[0] ? e(r[0]) : void 0;
132
132
  },
133
- async findNextWake(t) {
134
- let n = (await e.getAll("jobs")).filter(isValidRecord), r;
135
- for (let e of n) {
133
+ async findNextWake(e) {
134
+ let n = (await t.getAll("jobs")).filter(isValidRecord), r;
135
+ for (let t of n) {
136
136
  let n;
137
- 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);
137
+ t.status === "queued" && t.availableAt > e ? n = t.availableAt : t.status === "running" && (t.leaseExpiresAt ?? 0) > e && (n = t.leaseExpiresAt), n !== void 0 && (r === void 0 || n < r) && (r = n);
138
138
  }
139
139
  return r;
140
140
  },
141
141
  async get(n) {
142
- let r = await e.get("jobs", n);
143
- return r && isValidRecord(r) ? t(r) : void 0;
142
+ let r = await t.get("jobs", n);
143
+ return r && isValidRecord(r) ? e(r) : void 0;
144
144
  },
145
145
  async put(n) {
146
- await e.put("jobs", t(n));
146
+ await t.put("jobs", e(n));
147
147
  }
148
148
  });
149
149
  return {
@@ -156,8 +156,8 @@ function createIndexedDbPostmasterStore(o) {
156
156
  get disposed() {
157
157
  return l.disposed;
158
158
  },
159
- list(e) {
160
- return wrap("listing jobs", async () => (e?.status === void 0 ? await l.getAll("jobs") : await l.getAllByIndex("jobs", "status", e.status)).filter(isValidRecord).sort((e, t) => e.createdAt - t.createdAt).map(t));
159
+ list(t) {
160
+ return wrap("listing jobs", async () => (t?.status === void 0 ? await l.getAll("jobs") : await l.getAllByIndex("jobs", "status", t.status)).filter(isValidRecord).sort((e, t) => e.createdAt - t.createdAt).map(e));
161
161
  },
162
162
  subscribe(e) {
163
163
  return l.observe("jobs", () => e(), { immediate: !1 });
@@ -170,7 +170,6 @@ function createIndexedDbPostmasterStore(o) {
170
170
  }
171
171
  };
172
172
  }
173
- //#endregion
174
173
  export { createIndexedDbPostmasterStore };
175
174
 
176
175
  //# sourceMappingURL=indexeddb.js.map
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@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(t){let{clock:n=Date.now,jobs:r,leaseDuration:i=3e4,signal:a,store:o}=t;assertLeaseDuration(i);let s=new AbortController,c=crypto.randomUUID(),l=(0,e.tapper)(),u=!1,d=!1,f,p,m=new Map,h,error=e=>{let t=e instanceof Error?e:Error(String(e));emitTap({error:t,type:`processor-error`})},emitTap=e=>{l.emit(e)},assertLive=()=>{if(u)throw new PostmasterDisposedError(`Postmaster is disposed`)},clearWakeTimer=()=>{p!==void 0&&clearTimeout(p),p=void 0},wake=()=>{clearWakeTimer(),d&&!u&&pump()},schedule=async()=>{if(!d||u)return;let e=await o.transact(e=>e.findNextWake(n()));if(e===void 0)return;let t=Math.max(0,Math.min(e-n(),2147483647));p=setTimeout(wake,t)},resolveJob=e=>{let t=r[e.name];if(!t)throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);return t},deadLetter=async(e,t)=>await o.transact(r=>deadLetterJob(r,{failure:failureFrom(t,n()),id:e.id,now:n(),ownerId:c}))?(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,r)=>{let a,l;try{if(a=resolveJob(t),t.version>a.version)throw new PostmasterJobError(`job "${t.name}" version ${t.version} is newer than the registered version`);let e=t.payload;if(t.version<a.version){let n=a.migrate;if(!n)throw new PostmasterJobError(`job "${t.name}" has no migrations`);for(let r=t.version;r<a.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`)}}l=runValidate(a.validate,e)}catch(e){return deadLetter(t,e)}let u=new AbortController,d=AbortSignal.any([s.signal,r,u.signal].filter(e=>e!==void 0)),f=setInterval(()=>{o.transact(e=>renewLeaseJob(e,{id:t.id,leaseExpiresAt:n()+i,now:n(),ownerId:c})).then(e=>{e||(u.abort(),emitTap({id:t.id,type:`lease-lost`}))}).catch(error)},Math.max(1e3,Math.floor(i/2)));emitTap({entry:toEntry(t),type:`started`});try{return await a.execute(l,{attempt:t.attempts,entryId:t.id,key:t.key,signal:d}),d.aborted?(await o.transact(e=>releaseJob(e,{id:t.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await o.transact(e=>completeJob(e,{id:t.id,now:n(),ownerId:c}))?(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(r){if(d.aborted||isAbortError(r))return await o.transact(e=>releaseJob(e,{id:t.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let i=!1,s=0;try{if(i=t.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry?.(r,t.attempts)??!0),!i)return deadLetter(t,r);if(s=a.retry?.delay?.(t.attempts-1)??(0,e.backoff)(t.attempts-1),!Number.isFinite(s)||s<0)throw new PostmasterError(`job "${t.name}" retry delay must be finite and non-negative`)}catch(e){return deadLetter(t,e)}let l=n()+s;return await o.transact(e=>rescheduleJob(e,{availableAt:l,failure:failureFrom(r,n()),id:t.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry({...t,availableAt:l,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(s.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await o.transact(e=>claimJob(e,{leaseDuration:i,now:n(),ownerId:c}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};m.set(t.id,t);try{return await execute(t,e)}finally{m.delete(t.id)}},pump=()=>{f||u||(f=(async()=>{for(;(await processNext()).processed&&!u;);await schedule()})().catch(error).finally(()=>{f=void 0}))},g=o.subscribe(wake),externalAbort=()=>{dispose()};a?.addEventListener(`abort`,externalAbort,{once:!0});let dispose=async()=>{if(u)return;u=!0,emitTap({type:`dispose`}),l.clear(),clearWakeTimer(),a?.removeEventListener(`abort`,externalAbort),g(),s.abort();let e=[...m.values()];m.clear(),await Promise.all(e.map(e=>o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c}))))};return{get disposalSignal(){return s.signal},async dispose(){await dispose()},get disposed(){return u},async enqueue(e,t,i){assertLive();let a=r[e];if(!a)throw new PostmasterJobError(`no job definition is registered for "${e}"`);let s=n(),c=assertAvailableAt(i?.availableAt,s),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:s,id:crypto.randomUUID(),key:u,name:e,payload:asJsonValue(l),status:`queued`,updatedAt:s,version:a.version};await o.transact(e=>enqueueJob(e,d));let f=toEntry(d);return emitTap({entry:f,type:`enqueued`}),wake(),f},flush(e){if(assertLive(),h)return h;clearWakeTimer();let t=(async()=>{await f;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,u))return t}}finally{h=void 0,wake()}})();return h=t,t},async list(e){return assertLive(),(await o.list(e)).map(toEntry)},async remove(e){assertLive();let t=await o.transact(t=>removeJob(t,e));return t.status===`removed`&&emitTap({id:e,type:`removed`}),t},async requeue(e){assertLive();let t=await o.transact(t=>requeueJob(t,e,n()));return t.status===`requeued`&&wake(),t},start(){assertLive(),!d&&(d=!0,wake())},async stats(){return assertLive(),o.transact(e=>e.countByStatus())},tap(e,t){return assertLive(),l.tap(e,t)},async[Symbol.asyncDispose](){await dispose()}}}exports.PostmasterDisposedError=PostmasterDisposedError,exports.PostmasterError=PostmasterError,exports.PostmasterJobError=PostmasterJobError,exports.createPostmaster=createPostmaster,exports.defineJobs=defineJobs;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@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(t){let{clock:n=Date.now,jobs:r,leaseDuration:i=3e4,signal:a,store:o}=t;assertLeaseDuration(i);let s=new AbortController,c=crypto.randomUUID(),l=(0,e.tapper)(),u=!1,d=!1,f,p,m=new Map,h,error=e=>{let t=e instanceof Error?e:Error(String(e));emitTap({error:t,type:`processor-error`})},emitTap=e=>{l.emit(e)},assertLive=()=>{if(u)throw new PostmasterDisposedError(`Postmaster is disposed`)},clearWakeTimer=()=>{p!==void 0&&clearTimeout(p),p=void 0},wake=()=>{clearWakeTimer(),d&&!u&&pump()},schedule=async()=>{if(!d||u)return;let e=await o.transact(e=>e.findNextWake(n()));if(e===void 0)return;let t=Math.max(0,Math.min(e-n(),2147483647));p=setTimeout(wake,t)},resolveJob=e=>{let t=r[e.name];if(!t)throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);return t},deadLetter=async(e,t)=>await o.transact(r=>deadLetterJob(r,{failure:failureFrom(t,n()),id:e.id,now:n(),ownerId:c}))?(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,r)=>{let a,l;try{if(a=resolveJob(t),t.version>a.version)throw new PostmasterJobError(`job "${t.name}" version ${t.version} is newer than the registered version`);let e=t.payload;if(t.version<a.version){let n=a.migrate;if(!n)throw new PostmasterJobError(`job "${t.name}" has no migrations`);for(let r=t.version;r<a.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`)}}l=runValidate(a.validate,e)}catch(e){return deadLetter(t,e)}let u=new AbortController,d=AbortSignal.any([s.signal,r,u.signal].filter(e=>e!==void 0)),f=setInterval(()=>{o.transact(e=>renewLeaseJob(e,{id:t.id,leaseExpiresAt:n()+i,now:n(),ownerId:c})).then(e=>{e||(u.abort(),emitTap({id:t.id,type:`lease-lost`}))}).catch(error)},Math.max(1e3,Math.floor(i/2)));emitTap({entry:toEntry(t),type:`started`});try{return await a.execute(l,{attempt:t.attempts,entryId:t.id,key:t.key,signal:d}),d.aborted?(await o.transact(e=>releaseJob(e,{id:t.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await o.transact(e=>completeJob(e,{id:t.id,now:n(),ownerId:c}))?(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(r){if(d.aborted||isAbortError(r))return await o.transact(e=>releaseJob(e,{id:t.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let i=!1,s=0;try{if(i=t.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry?.(r,t.attempts)??!0),!i)return deadLetter(t,r);if(s=a.retry?.delay?.(t.attempts-1)??(0,e.backoff)(t.attempts-1),!Number.isFinite(s)||s<0)throw new PostmasterError(`job "${t.name}" retry delay must be finite and non-negative`)}catch(e){return deadLetter(t,e)}let l=n()+s;return await o.transact(e=>rescheduleJob(e,{availableAt:l,failure:failureFrom(r,n()),id:t.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry({...t,availableAt:l,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(s.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await o.transact(e=>claimJob(e,{leaseDuration:i,now:n(),ownerId:c}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};m.set(t.id,t);try{return await execute(t,e)}finally{m.delete(t.id)}},pump=()=>{f||u||(f=(async()=>{for(;(await processNext()).processed&&!u;);await schedule()})().catch(error).finally(()=>{f=void 0}))},g=o.subscribe(wake),externalAbort=()=>{dispose()};a?.addEventListener(`abort`,externalAbort,{once:!0});let dispose=async()=>{if(u)return;u=!0,emitTap({type:`dispose`}),l.clear(),clearWakeTimer(),a?.removeEventListener(`abort`,externalAbort),g(),s.abort();let e=[...m.values()];m.clear(),await Promise.all(e.map(e=>o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c}))))};return{get disposalSignal(){return s.signal},async dispose(){await dispose()},get disposed(){return u},async enqueue(e,t,i){assertLive();let a=r[e];if(!a)throw new PostmasterJobError(`no job definition is registered for "${e}"`);let s=n(),c=assertAvailableAt(i?.availableAt,s),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:s,id:crypto.randomUUID(),key:u,name:e,payload:asJsonValue(l),status:`queued`,updatedAt:s,version:a.version};await o.transact(e=>enqueueJob(e,d));let f=toEntry(d);return emitTap({entry:f,type:`enqueued`}),wake(),f},flush(e){if(assertLive(),h)return h;clearWakeTimer();let t=(async()=>{await f;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,u))return t}}finally{h=void 0,wake()}})();return h=t,t},async list(e){return assertLive(),(await o.list(e)).map(toEntry)},async remove(e){assertLive();let t=await o.transact(t=>removeJob(t,e));return t.status===`removed`&&emitTap({id:e,type:`removed`}),t},async requeue(e){assertLive();let t=await o.transact(t=>requeueJob(t,e,n()));return t.status===`requeued`&&wake(),t},start(){assertLive(),d||(d=!0,wake())},async stats(){return assertLive(),o.transact(e=>e.countByStatus())},tap(e,t){return assertLive(),l.tap(e,t)},async[Symbol.asyncDispose](){await dispose()}}}exports.PostmasterDisposedError=PostmasterDisposedError,exports.PostmasterError=PostmasterError,exports.PostmasterJobError=PostmasterJobError,exports.createPostmaster=createPostmaster,exports.defineJobs=defineJobs;
2
2
  //# sourceMappingURL=postmaster.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"postmaster.cjs","names":["tapper","backoff","_vielzeug_arsenal","message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"postmaster.cjs","names":["tapper","backoff","_vielzeug_arsenal","message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Postmaster=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});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(e){let{clock:n=Date.now,jobs:r,leaseDuration:i=3e4,signal:a,store:o}=e;assertLeaseDuration(i);let s=new AbortController,c=crypto.randomUUID(),l=(0,t.tapper)(),u=!1,d=!1,f,p,m=new Map,h,error=e=>{let t=e instanceof Error?e:Error(String(e));emitTap({error:t,type:`processor-error`})},emitTap=e=>{l.emit(e)},assertLive=()=>{if(u)throw new PostmasterDisposedError(`Postmaster is disposed`)},clearWakeTimer=()=>{p!==void 0&&clearTimeout(p),p=void 0},wake=()=>{clearWakeTimer(),d&&!u&&pump()},schedule=async()=>{if(!d||u)return;let e=await o.transact(e=>e.findNextWake(n()));if(e===void 0)return;let t=Math.max(0,Math.min(e-n(),2147483647));p=setTimeout(wake,t)},resolveJob=e=>{let t=r[e.name];if(!t)throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);return t},deadLetter=async(e,t)=>await o.transact(r=>deadLetterJob(r,{failure:failureFrom(t,n()),id:e.id,now:n(),ownerId:c}))?(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(e,r)=>{let a,l;try{if(a=resolveJob(e),e.version>a.version)throw new PostmasterJobError(`job "${e.name}" version ${e.version} is newer than the registered version`);let t=e.payload;if(e.version<a.version){let n=a.migrate;if(!n)throw new PostmasterJobError(`job "${e.name}" has no migrations`);for(let r=e.version;r<a.version;r++){let i=n[r];if(typeof i!=`function`)throw new PostmasterJobError(`job "${e.name}" does not support stored version ${e.version}`);if(t=i(t),t===void 0)throw new PostmasterJobError(`job "${e.name}" migration from version ${r} returned undefined`)}}l=runValidate(a.validate,t)}catch(t){return deadLetter(e,t)}let u=new AbortController,d=AbortSignal.any([s.signal,r,u.signal].filter(e=>e!==void 0)),f=setInterval(()=>{o.transact(t=>renewLeaseJob(t,{id:e.id,leaseExpiresAt:n()+i,now:n(),ownerId:c})).then(t=>{t||(u.abort(),emitTap({id:e.id,type:`lease-lost`}))}).catch(error)},Math.max(1e3,Math.floor(i/2)));emitTap({entry:toEntry(e),type:`started`});try{return await a.execute(l,{attempt:e.attempts,entryId:e.id,key:e.key,signal:d}),d.aborted?(await o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await o.transact(t=>completeJob(t,{id:e.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry(e),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(emitTap({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(r){if(d.aborted||isAbortError(r))return await o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let i=!1,s=0;try{if(i=e.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry?.(r,e.attempts)??!0),!i)return deadLetter(e,r);if(s=a.retry?.delay?.(e.attempts-1)??(0,t.backoff)(e.attempts-1),!Number.isFinite(s)||s<0)throw new PostmasterError(`job "${e.name}" retry delay must be finite and non-negative`)}catch(t){return deadLetter(e,t)}let l=n()+s;return await o.transact(t=>rescheduleJob(t,{availableAt:l,failure:failureFrom(r,n()),id:e.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry({...e,availableAt:l,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(emitTap({id:e.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(s.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await o.transact(e=>claimJob(e,{leaseDuration:i,now:n(),ownerId:c}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};m.set(t.id,t);try{return await execute(t,e)}finally{m.delete(t.id)}},pump=()=>{f||u||(f=(async()=>{for(;(await processNext()).processed&&!u;);await schedule()})().catch(error).finally(()=>{f=void 0}))},g=o.subscribe(wake),externalAbort=()=>{dispose()};a?.addEventListener(`abort`,externalAbort,{once:!0});let dispose=async()=>{if(u)return;u=!0,emitTap({type:`dispose`}),l.clear(),clearWakeTimer(),a?.removeEventListener(`abort`,externalAbort),g(),s.abort();let e=[...m.values()];m.clear(),await Promise.all(e.map(e=>o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c}))))};return{get disposalSignal(){return s.signal},async dispose(){await dispose()},get disposed(){return u},async enqueue(e,t,i){assertLive();let a=r[e];if(!a)throw new PostmasterJobError(`no job definition is registered for "${e}"`);let s=n(),c=assertAvailableAt(i?.availableAt,s),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:s,id:crypto.randomUUID(),key:u,name:e,payload:asJsonValue(l),status:`queued`,updatedAt:s,version:a.version};await o.transact(e=>enqueueJob(e,d));let f=toEntry(d);return emitTap({entry:f,type:`enqueued`}),wake(),f},flush(e){if(assertLive(),h)return h;clearWakeTimer();let t=(async()=>{await f;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,u))return t}}finally{h=void 0,wake()}})();return h=t,t},async list(e){return assertLive(),(await o.list(e)).map(toEntry)},async remove(e){assertLive();let t=await o.transact(t=>removeJob(t,e));return t.status===`removed`&&emitTap({id:e,type:`removed`}),t},async requeue(e){assertLive();let t=await o.transact(t=>requeueJob(t,e,n()));return t.status===`requeued`&&wake(),t},start(){assertLive(),!d&&(d=!0,wake())},async stats(){return assertLive(),o.transact(e=>e.countByStatus())},tap(e,t){return assertLive(),l.tap(e,t)},async[Symbol.asyncDispose](){await dispose()}}}return e.PostmasterDisposedError=PostmasterDisposedError,e.PostmasterError=PostmasterError,e.PostmasterJobError=PostmasterJobError,e.createPostmaster=createPostmaster,e.defineJobs=defineJobs,e})({},Arsenal);
1
+ var Postmaster=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});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(e){let{clock:n=Date.now,jobs:r,leaseDuration:i=3e4,signal:a,store:o}=e;assertLeaseDuration(i);let s=new AbortController,c=crypto.randomUUID(),l=(0,t.tapper)(),u=!1,d=!1,f,p,m=new Map,h,error=e=>{let t=e instanceof Error?e:Error(String(e));emitTap({error:t,type:`processor-error`})},emitTap=e=>{l.emit(e)},assertLive=()=>{if(u)throw new PostmasterDisposedError(`Postmaster is disposed`)},clearWakeTimer=()=>{p!==void 0&&clearTimeout(p),p=void 0},wake=()=>{clearWakeTimer(),d&&!u&&pump()},schedule=async()=>{if(!d||u)return;let e=await o.transact(e=>e.findNextWake(n()));if(e===void 0)return;let t=Math.max(0,Math.min(e-n(),2147483647));p=setTimeout(wake,t)},resolveJob=e=>{let t=r[e.name];if(!t)throw new PostmasterJobError(`no job definition is registered for "${e.name}"`);return t},deadLetter=async(e,t)=>await o.transact(r=>deadLetterJob(r,{failure:failureFrom(t,n()),id:e.id,now:n(),ownerId:c}))?(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(e,r)=>{let a,l;try{if(a=resolveJob(e),e.version>a.version)throw new PostmasterJobError(`job "${e.name}" version ${e.version} is newer than the registered version`);let t=e.payload;if(e.version<a.version){let n=a.migrate;if(!n)throw new PostmasterJobError(`job "${e.name}" has no migrations`);for(let r=e.version;r<a.version;r++){let i=n[r];if(typeof i!=`function`)throw new PostmasterJobError(`job "${e.name}" does not support stored version ${e.version}`);if(t=i(t),t===void 0)throw new PostmasterJobError(`job "${e.name}" migration from version ${r} returned undefined`)}}l=runValidate(a.validate,t)}catch(t){return deadLetter(e,t)}let u=new AbortController,d=AbortSignal.any([s.signal,r,u.signal].filter(e=>e!==void 0)),f=setInterval(()=>{o.transact(t=>renewLeaseJob(t,{id:e.id,leaseExpiresAt:n()+i,now:n(),ownerId:c})).then(t=>{t||(u.abort(),emitTap({id:e.id,type:`lease-lost`}))}).catch(error)},Math.max(1e3,Math.floor(i/2)));emitTap({entry:toEntry(e),type:`started`});try{return await a.execute(l,{attempt:e.attempts,entryId:e.id,key:e.key,signal:d}),d.aborted?(await o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await o.transact(t=>completeJob(t,{id:e.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry(e),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(emitTap({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(r){if(d.aborted||isAbortError(r))return await o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let i=!1,s=0;try{if(i=e.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry?.(r,e.attempts)??!0),!i)return deadLetter(e,r);if(s=a.retry?.delay?.(e.attempts-1)??(0,t.backoff)(e.attempts-1),!Number.isFinite(s)||s<0)throw new PostmasterError(`job "${e.name}" retry delay must be finite and non-negative`)}catch(t){return deadLetter(e,t)}let l=n()+s;return await o.transact(t=>rescheduleJob(t,{availableAt:l,failure:failureFrom(r,n()),id:e.id,now:n(),ownerId:c}))?(emitTap({entry:toEntry({...e,availableAt:l,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(emitTap({id:e.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(s.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await o.transact(e=>claimJob(e,{leaseDuration:i,now:n(),ownerId:c}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};m.set(t.id,t);try{return await execute(t,e)}finally{m.delete(t.id)}},pump=()=>{f||u||(f=(async()=>{for(;(await processNext()).processed&&!u;);await schedule()})().catch(error).finally(()=>{f=void 0}))},g=o.subscribe(wake),externalAbort=()=>{dispose()};a?.addEventListener(`abort`,externalAbort,{once:!0});let dispose=async()=>{if(u)return;u=!0,emitTap({type:`dispose`}),l.clear(),clearWakeTimer(),a?.removeEventListener(`abort`,externalAbort),g(),s.abort();let e=[...m.values()];m.clear(),await Promise.all(e.map(e=>o.transact(t=>releaseJob(t,{id:e.id,now:n(),ownerId:c}))))};return{get disposalSignal(){return s.signal},async dispose(){await dispose()},get disposed(){return u},async enqueue(e,t,i){assertLive();let a=r[e];if(!a)throw new PostmasterJobError(`no job definition is registered for "${e}"`);let s=n(),c=assertAvailableAt(i?.availableAt,s),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:s,id:crypto.randomUUID(),key:u,name:e,payload:asJsonValue(l),status:`queued`,updatedAt:s,version:a.version};await o.transact(e=>enqueueJob(e,d));let f=toEntry(d);return emitTap({entry:f,type:`enqueued`}),wake(),f},flush(e){if(assertLive(),h)return h;clearWakeTimer();let t=(async()=>{await f;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,u))return t}}finally{h=void 0,wake()}})();return h=t,t},async list(e){return assertLive(),(await o.list(e)).map(toEntry)},async remove(e){assertLive();let t=await o.transact(t=>removeJob(t,e));return t.status===`removed`&&emitTap({id:e,type:`removed`}),t},async requeue(e){assertLive();let t=await o.transact(t=>requeueJob(t,e,n()));return t.status===`requeued`&&wake(),t},start(){assertLive(),d||(d=!0,wake())},async stats(){return assertLive(),o.transact(e=>e.countByStatus())},tap(e,t){return assertLive(),l.tap(e,t)},async[Symbol.asyncDispose](){await dispose()}}}return e.PostmasterDisposedError=PostmasterDisposedError,e.PostmasterError=PostmasterError,e.PostmasterJobError=PostmasterJobError,e.createPostmaster=createPostmaster,e.defineJobs=defineJobs,e})({},Arsenal);
2
2
  //# sourceMappingURL=postmaster.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"postmaster.iife.js","names":["tapper","backoff","exports","message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","_vielzeug_arsenal","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"postmaster.iife.js","names":["tapper","backoff","exports","message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","_vielzeug_arsenal","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,12 +1,11 @@
1
1
  import { backoff as e, tapper as t } from "@vielzeug/arsenal";
2
- //#region src/errors.ts
2
+ // src/errors.ts
3
3
  var PostmasterError = class extends Error {
4
4
  constructor(e, t) {
5
5
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
6
6
  }
7
7
  }, PostmasterDisposedError = class extends PostmasterError {}, PostmasterJobError = class extends PostmasterError {};
8
- //#endregion
9
- //#region src/definitions.ts
8
+ // src/definitions.ts
10
9
  function validateRetry(e, t) {
11
10
  if (e && (!Number.isInteger(e.maxAttempts) || e.maxAttempts < 1)) throw new PostmasterError(`job "${t}" retry.maxAttempts must be a positive integer`);
12
11
  }
@@ -34,8 +33,7 @@ function defineJobs(e) {
34
33
  for (let [t, n] of Object.entries(e)) validateJob(t, n);
35
34
  return e;
36
35
  }
37
- //#endregion
38
- //#region src/_json.ts
36
+ // src/_json.ts
39
37
  function runValidate(e, t) {
40
38
  return e ? typeof e == "function" ? e(t) : e.parse(t) : t;
41
39
  }
@@ -63,8 +61,7 @@ function failureFrom(e, t) {
63
61
  function isAbortError(e) {
64
62
  return e instanceof DOMException && e.name === "AbortError";
65
63
  }
66
- //#endregion
67
- //#region src/store-ops.ts
64
+ // src/store-ops.ts
68
65
  async function claimJob(e, t) {
69
66
  let n = await e.findClaimable(t.now);
70
67
  if (!n) return;
@@ -166,8 +163,7 @@ function toEntry(e) {
166
163
  version: e.version
167
164
  };
168
165
  }
169
- //#endregion
170
- //#region src/postmaster.ts
166
+ // src/postmaster.ts
171
167
  var emptyResult = () => ({
172
168
  completed: 0,
173
169
  deadLettered: 0,
@@ -472,7 +468,7 @@ function createPostmaster(n) {
472
468
  return t.status === "requeued" && wake(), t;
473
469
  },
474
470
  start() {
475
- assertLive(), !f && (f = !0, wake());
471
+ assertLive(), f || (f = !0, wake());
476
472
  },
477
473
  async stats() {
478
474
  return assertLive(), s.transact((e) => e.countByStatus());
@@ -485,7 +481,6 @@ function createPostmaster(n) {
485
481
  }
486
482
  };
487
483
  }
488
- //#endregion
489
484
  export { PostmasterDisposedError, PostmasterError, PostmasterJobError, createPostmaster, defineJobs };
490
485
 
491
486
  //# sourceMappingURL=postmaster.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"postmaster.js","names":["message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","tapper","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","backoff","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"postmaster.js","names":["message","options","retry","name","migrations","job","keys","versions","version","key","a","b","index","jobs","validate","value","serialized","error","now","entry","tx","params","claimed","id","requeued","defaultValue","leaseDuration","controller","ownerId","tappers","tapper","disposed","started","running","wakeTimer","activeEntries","flushPromise","reason","event","next","store","clock","delay","payload","raw","v","step","taskController","taskSignal","externalSignal","s","heartbeat","renewed","shouldRetry","backoff","retryReason","availableAt","unsubscribeStore","signal","entries","parsed","result","promise","filter","handler","opts"],"sources":["../src/errors.ts","../src/definitions.ts","../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 validateMigrations(name: string, job: JobDefinition<unknown>): void {\n const migrations = job.migrate;\n if (!migrations) return;\n\n const keys = Object.keys(migrations);\n if (keys.length === 0) throw new PostmasterError(`job \"${name}\" migrations must not be empty`);\n\n const versions = keys\n .map((key) => {\n const version = Number(key);\n if (!Number.isInteger(version) || String(version) !== key) {\n throw new PostmasterError(`job \"${name}\" has invalid migration key \"${key}\"`);\n }\n if (version < 1 || version >= job.version) {\n throw new PostmasterError(\n `job \"${name}\" has invalid migration key \"${version}\" (valid range: 1..${job.version - 1})`,\n );\n }\n if (typeof migrations[version] !== 'function') {\n throw new PostmasterError(`job \"${name}\" migration from version ${version} must be a function`);\n }\n return version;\n })\n .sort((a, b) => a - b);\n\n for (let index = 1; index < versions.length; index++) {\n if (versions[index] !== versions[index - 1] + 1) {\n throw new PostmasterError(`job \"${name}\" missing migration from version ${versions[index - 1] + 1}`);\n }\n }\n\n if (versions.at(-1) !== job.version - 1) {\n throw new PostmasterError(`job \"${name}\" migrations must lead into version ${job.version}`);\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 validateMigrations(name, job);\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","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 { PostmasterJobError } from './errors.ts';\nimport type { PostmasterEntry, RemoveResult, RequeueResult, 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/** Requeue a dead-letter job back to queued. Returns discriminated result. */\nexport async function requeueJob(tx: StoreTx, id: string, now: number): Promise<RequeueResult> {\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 requeued: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(requeued);\n return { entry: toEntry(requeued), status: 'requeued' };\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 PostmasterJobError(`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, tapper } from '@vielzeug/arsenal';\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 requeueJob,\n rescheduleJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EnqueueOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RequeueResult,\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 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\nfunction assertAvailableAt(value: number | undefined, defaultValue: number): number {\n if (value === undefined) return defaultValue;\n if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(value)) {\n throw new PostmasterError('availableAt must be a finite non-negative safe integer');\n }\n return value;\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 = tapper<PostmasterEvent>();\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 };\n\n const emitTap = (event: PostmasterEvent): void => {\n tappers.emit(event);\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 let raw: unknown = entry.payload;\n if (entry.version < job.version) {\n const migrations = job.migrate;\n if (!migrations) {\n throw new PostmasterJobError(`job \"${entry.name}\" has no migrations`);\n }\n for (let v = entry.version; v < job.version; v++) {\n const step = migrations[v];\n if (typeof step !== 'function') {\n throw new PostmasterJobError(`job \"${entry.name}\" does not support stored version ${entry.version}`);\n }\n raw = step(raw);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" migration from version ${v} returned undefined`);\n }\n }\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 = AbortSignal.any(\n [controller.signal, externalSignal, taskController.signal].filter((s): s is AbortSignal => s !== undefined),\n );\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) ?? true);\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>(\n name: K,\n payload: InferJobPayload<J[K]>,\n options?: EnqueueOptions,\n ): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const now = clock();\n const availableAt = assertAvailableAt(options?.availableAt, now);\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 entry: StoredJob = {\n attempts: 0,\n availableAt,\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 requeue(id: string): Promise<RequeueResult> {\n assertLive();\n const result = await store.transact((tx) => requeueJob(tx, id, clock()));\n if (result.status === 'requeued') wake();\n return result;\n },\n start(): 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\n return tappers.tap(handler, opts);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
package/dist/store-ops.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PostmasterJobError as e } from "./errors.js";
2
- //#region src/store-ops.ts
2
+ // src/store-ops.ts
3
3
  async function claimJob(e, t) {
4
4
  let n = await e.findClaimable(t.now);
5
5
  if (!n) return;
@@ -101,7 +101,6 @@ function toEntry(e) {
101
101
  version: e.version
102
102
  };
103
103
  }
104
- //#endregion
105
104
  export { claimJob, completeJob, deadLetterJob, enqueueJob, releaseJob, removeJob, renewLeaseJob, requeueJob, rescheduleJob, toEntry };
106
105
 
107
106
  //# sourceMappingURL=store-ops.js.map
@@ -1,5 +1,5 @@
1
1
  import { normalize as e } from "../_store-utils.js";
2
- //#region src/testing/memory-store.ts
2
+ // src/testing/memory-store.ts
3
3
  function createMemoryPostmasterStore(t = []) {
4
4
  let n = new Map(t.map((t) => [t.id, e(t)])), r = new AbortController(), i = /* @__PURE__ */ new Set(), a = !1, o = Promise.resolve(), exclusive = (e) => {
5
5
  let t = o.then(e, e);
@@ -70,7 +70,6 @@ function createMemoryPostmasterStore(t = []) {
70
70
  }
71
71
  };
72
72
  }
73
- //#endregion
74
73
  export { createMemoryPostmasterStore };
75
74
 
76
75
  //# sourceMappingURL=memory-store.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/postmaster",
3
- "version": "26.11.5",
3
+ "version": "26.11.6",
4
4
  "description": "Typed durable job outbox with leased processing, retries, and dead-letter recovery",
5
5
  "repository": {
6
6
  "type": "git",
@@ -59,25 +59,25 @@
59
59
  "registry": "https://registry.npmjs.org/"
60
60
  },
61
61
  "scripts": {
62
- "build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types",
62
+ "build": "vite build && bun run --silent build:bundle && bun run --silent build:types",
63
63
  "build:bundle": "vite build --config vite.bundle.config.ts",
64
64
  "build:types": "tsc -p tsconfig.declarations.json",
65
65
  "fix": "biome check --write src",
66
66
  "lint": "biome ci src",
67
- "prepublishOnly": "pnpm --silent run build",
67
+ "prepublishOnly": "bun run --silent build",
68
68
  "preview": "vite preview",
69
69
  "test": "vitest"
70
70
  },
71
71
  "dependencies": {
72
- "@vielzeug/arsenal": "26.11.5",
73
- "@vielzeug/vault": "26.11.5"
72
+ "@vielzeug/arsenal": "26.11.6",
73
+ "@vielzeug/vault": "26.11.6"
74
74
  },
75
75
  "devDependencies": {
76
- "@types/node": "^26.6.5",
77
- "@vielzeug/courier": "26.11.5",
78
- "fake-indexeddb": "^6.2.6",
76
+ "@types/node": "^26.6.6",
77
+ "@vielzeug/courier": "26.11.6",
78
+ "fake-indexeddb": "^6.2.7",
79
79
  "typescript": "^6.0.3",
80
- "vite": "^8.3.4",
81
- "vitest": "^5.0.5"
80
+ "vite": "^8.3.6",
81
+ "vitest": "^5.1.0"
82
82
  }
83
83
  }