@vielzeug/postmaster 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/dist/_json.cjs +2 -0
- package/dist/_json.cjs.map +1 -0
- package/dist/_json.d.ts +6 -0
- package/dist/_json.d.ts.map +1 -0
- package/dist/_json.js +33 -0
- package/dist/_json.js.map +1 -0
- package/dist/definitions.cjs +2 -0
- package/dist/definitions.cjs.map +1 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +18 -0
- package/dist/definitions.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/indexeddb.cjs +2 -0
- package/dist/indexeddb.cjs.map +1 -0
- package/dist/indexeddb.d.ts +7 -0
- package/dist/indexeddb.d.ts.map +1 -0
- package/dist/indexeddb.js +93 -0
- package/dist/indexeddb.js.map +1 -0
- package/dist/postmaster.cjs +2 -0
- package/dist/postmaster.cjs.map +1 -0
- package/dist/postmaster.d.ts +3 -0
- package/dist/postmaster.d.ts.map +1 -0
- package/dist/postmaster.iife.js +2 -0
- package/dist/postmaster.iife.js.map +1 -0
- package/dist/postmaster.js +2 -0
- package/dist/postmaster.js.map +1 -0
- package/dist/store-ops.cjs +2 -0
- package/dist/store-ops.cjs.map +1 -0
- package/dist/store-ops.d.ts +40 -0
- package/dist/store-ops.d.ts.map +1 -0
- package/dist/store-ops.js +106 -0
- package/dist/store-ops.js.map +1 -0
- package/dist/testing/memory-store.cjs +2 -0
- package/dist/testing/memory-store.cjs.map +1 -0
- package/dist/testing/memory-store.d.ts +3 -0
- package/dist/testing/memory-store.d.ts.map +1 -0
- package/dist/testing/memory-store.js +84 -0
- package/dist/testing/memory-store.js.map +1 -0
- package/dist/testing.cjs +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +2 -0
- package/dist/types.d.ts +137 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postmaster.iife.js","names":[],"sources":["../src/errors.ts","../src/definitions.ts","../../arsenal/dist/async/backoff.js","../src/_json.ts","../src/store-ops.ts","../src/postmaster.ts"],"sourcesContent":["export class PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class PostmasterDisposedError extends PostmasterError {}\nexport class PostmasterJobError extends PostmasterError {}\n","import { PostmasterError } from './errors.ts';\nimport type { JobDefinition, JobDefinitions, RetryPolicy } from './types.ts';\n\nfunction validateRetry(retry: RetryPolicy | undefined, name: string): void {\n if (!retry) return;\n\n if (!Number.isInteger(retry.maxAttempts) || retry.maxAttempts < 1) {\n throw new PostmasterError(`job \"${name}\" retry.maxAttempts must be a positive integer`);\n }\n}\n\nfunction validateJob(name: string, job: JobDefinition<unknown>): void {\n if (!Number.isInteger(job.version) || job.version < 1) {\n throw new PostmasterError(`job \"${name}\" version must be a positive integer`);\n }\n\n if (typeof job.execute !== 'function' || typeof job.key !== 'function') {\n throw new PostmasterError(`job \"${name}\" must define execute and key`);\n }\n\n validateRetry(job.retry, name);\n}\n\nexport function defineJobs<const J extends JobDefinitions>(jobs: J): J {\n for (const [name, job] of Object.entries(jobs)) validateJob(name, job);\n\n return jobs;\n}\n","//#region src/async/backoff.ts\nfunction e(e, t = 3e4) {\n\treturn Math.min(1e3 * 2 ** Math.max(0, Number.isNaN(e) ? 0 : Math.floor(e)), t);\n}\n//#endregion\nexport { e as backoff };\n\n//# sourceMappingURL=backoff.js.map","import { PostmasterError } from './errors.ts';\nimport type { JsonValue, StoredFailure, Validate } from './types.ts';\n\nexport function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T {\n if (!validate) return value as T;\n if (typeof validate === 'function') return validate(value);\n return validate.parse(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n let serialized: string | undefined;\n\n try {\n serialized = JSON.stringify(value);\n } catch (error) {\n throw new PostmasterError('job payload must be JSON-serializable', { cause: error });\n }\n\n if (serialized === undefined) {\n throw new PostmasterError('job payload must be JSON-serializable');\n }\n\n return JSON.parse(serialized) as JsonValue;\n}\n\nexport function failureFrom(error: unknown, now: number): StoredFailure {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, occurredAt: now };\n }\n\n return { message: String(error), name: 'Error', occurredAt: now };\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError';\n}\n","import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';\n\nexport interface ClaimParams {\n readonly leaseDuration: number;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface OwnedParams {\n readonly id: string;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface RescheduleParams extends OwnedParams {\n readonly availableAt: number;\n readonly failure: StoredFailure;\n}\n\nexport interface DeadLetterParams extends OwnedParams {\n readonly failure: StoredFailure;\n}\n\n/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */\nexport async function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined> {\n const entry = await tx.findClaimable(params.now);\n if (!entry) return undefined;\n const claimed: StoredJob = {\n ...entry,\n attempts: entry.attempts + 1,\n failure: undefined,\n leaseExpiresAt: params.now + params.leaseDuration,\n ownerId: params.ownerId,\n status: 'running',\n updatedAt: params.now,\n };\n await tx.put(claimed);\n return claimed;\n}\n\n/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */\nexport async function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.delete(params.id);\n return true;\n}\n\n/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */\nexport async function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */\nexport async function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n availableAt: params.availableAt,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Move a running job to dead-letter. Returns false if stale. */\nexport async function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'dead-letter',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Renew a lease for the current owner. Returns false if stale. */\nexport async function renewLeaseJob(tx: StoreTx, params: OwnedParams & { leaseExpiresAt: number }): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({ ...entry, leaseExpiresAt: params.leaseExpiresAt, updatedAt: params.now });\n return true;\n}\n\n/** Retry a dead-letter job back to queued. Returns discriminated result. */\nexport async function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n if (entry.status !== 'dead-letter') return { status: 'not-dead-letter' };\n const retried: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(retried);\n return { entry: toEntry(retried), status: 'retried' };\n}\n\n/** Remove a queued or dead-letter job. Running jobs cannot be removed. */\nexport async function removeJob(tx: StoreTx, id: string): Promise<RemoveResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n await tx.delete(id);\n return { id, status: 'removed' };\n}\n\n/** Enqueue a new job. Rejects duplicate ids. */\nexport async function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void> {\n const existing = await tx.get(entry.id);\n if (existing) throw new Error(`Postmaster entry \"${entry.id}\" already exists`);\n await tx.put(entry);\n}\n\nexport function toEntry(entry: StoredJob): PostmasterEntry {\n return {\n attempts: entry.attempts,\n availableAt: entry.availableAt,\n createdAt: entry.createdAt,\n ...(entry.failure ? { failure: entry.failure } : {}),\n id: entry.id,\n key: entry.key,\n name: entry.name,\n status: entry.status,\n updatedAt: entry.updatedAt,\n version: entry.version,\n };\n}\n","import { backoff } from '@vielzeug/arsenal/async';\nimport { asJsonValue, failureFrom, isAbortError, runValidate } from './_json.ts';\nimport { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';\nimport {\n claimJob,\n completeJob,\n deadLetterJob,\n enqueueJob,\n releaseJob,\n removeJob,\n renewLeaseJob,\n rescheduleJob,\n retryJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RetryResult,\n StoredJob,\n} from './types.ts';\n\ntype ExecutionResult = Omit<FlushResult, 'processed'> & { processed: boolean };\ntype MutableFlushResult = { -readonly [K in keyof FlushResult]: FlushResult[K] };\n\nconst emptyResult = (): FlushResult => ({ completed: 0, deadLettered: 0, processed: 0, retryScheduled: 0 });\n\nfunction mergeSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal {\n const controller = new AbortController();\n const active = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n const abort = (): void => controller.abort();\n\n for (const signal of active) {\n if (signal.aborted) {\n abort();\n break;\n }\n signal.addEventListener('abort', abort, { once: true });\n }\n\n return controller.signal;\n}\n\nfunction assertLeaseDuration(value: number): void {\n if (!Number.isInteger(value) || value < 1_000) {\n throw new PostmasterError('leaseDuration must be a positive integer of at least 1000ms');\n }\n}\n\nexport function createPostmaster<J extends JobDefinitions>(options: CreatePostmasterOptions<J>): Postmaster<J> {\n const { clock = Date.now, jobs, leaseDuration = 30_000, signal, store } = options;\n assertLeaseDuration(leaseDuration);\n\n const controller = new AbortController();\n const ownerId = crypto.randomUUID();\n const tappers = new Set<(event: PostmasterEvent) => void>();\n let disposed = false;\n let started = false;\n let running: Promise<void> | undefined;\n let wakeTimer: ReturnType<typeof setTimeout> | undefined;\n const activeEntries = new Map<string, StoredJob>();\n let flushPromise: Promise<FlushResult> | undefined;\n\n const error = (reason: unknown): void => {\n const value = reason instanceof Error ? reason : new Error(String(reason));\n emitTap({ error: value, type: 'processor-error' });\n queueMicrotask(() => {\n throw value;\n });\n };\n\n const emitTap = (event: PostmasterEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect postmaster behavior.\n }\n }\n };\n\n const assertLive = (): void => {\n if (disposed) throw new PostmasterDisposedError('Postmaster is disposed');\n };\n\n const clearWakeTimer = (): void => {\n if (wakeTimer !== undefined) clearTimeout(wakeTimer);\n wakeTimer = undefined;\n };\n\n const wake = (): void => {\n clearWakeTimer();\n if (started && !disposed) pump();\n };\n\n const schedule = async (): Promise<void> => {\n if (!started || disposed) return;\n const next = await store.transact((tx) => tx.findNextWake(clock()));\n if (next === undefined) return;\n const delay = Math.max(0, Math.min(next - clock(), 2_147_483_647));\n wakeTimer = setTimeout(wake, delay);\n };\n\n const resolveJob = (entry: StoredJob): JobDefinition<unknown> => {\n const job = jobs[entry.name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${entry.name}\"`);\n return job;\n };\n\n const deadLetter = async (entry: StoredJob, reason: unknown): Promise<ExecutionResult> => {\n const finalized = await store.transact((tx) =>\n deadLetterJob(tx, { failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n\n if (!finalized) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry({ ...entry, status: 'dead-letter' }), type: 'dead-lettered' });\n return { completed: 0, deadLettered: 1, processed: true, retryScheduled: 0 };\n };\n\n const execute = async (entry: StoredJob, externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n let job: JobDefinition<unknown>;\n let payload: unknown;\n\n try {\n job = resolveJob(entry);\n if (entry.version > job.version) {\n throw new PostmasterJobError(\n `job \"${entry.name}\" version ${entry.version} is newer than the registered version`,\n );\n }\n const raw = entry.version === job.version ? entry.payload : job.migrate?.(entry.payload, entry.version);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" version ${entry.version} cannot be migrated`);\n }\n payload = runValidate(job.validate, raw);\n } catch (reason) {\n return deadLetter(entry, reason);\n }\n\n const taskController = new AbortController();\n const taskSignal = mergeSignals([controller.signal, externalSignal, taskController.signal]);\n const heartbeat = setInterval(\n () => {\n void store\n .transact((tx) =>\n renewLeaseJob(tx, { id: entry.id, leaseExpiresAt: clock() + leaseDuration, now: clock(), ownerId }),\n )\n .then((renewed) => {\n if (!renewed) {\n taskController.abort();\n emitTap({ id: entry.id, type: 'lease-lost' });\n }\n })\n .catch(error);\n },\n Math.max(1_000, Math.floor(leaseDuration / 2)),\n );\n\n emitTap({ entry: toEntry(entry), type: 'started' });\n\n try {\n await job.execute(payload, {\n attempt: entry.attempts,\n entryId: entry.id,\n key: entry.key,\n signal: taskSignal,\n } as JobContext);\n\n if (taskSignal.aborted) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n const completed = await store.transact((tx) => completeJob(tx, { id: entry.id, now: clock(), ownerId }));\n if (!completed) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry(entry), type: 'completed' });\n return { completed: 1, deadLettered: 0, processed: true, retryScheduled: 0 };\n } catch (reason) {\n if (taskSignal.aborted || isAbortError(reason)) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n let shouldRetry = false;\n let delay = 0;\n\n try {\n shouldRetry =\n entry.attempts < (job.retry?.maxAttempts ?? 1) && (job.retry?.shouldRetry(reason, entry.attempts) ?? false);\n if (!shouldRetry) return deadLetter(entry, reason);\n delay = job.retry?.delay?.(entry.attempts - 1) ?? backoff(entry.attempts - 1);\n if (!Number.isFinite(delay) || delay < 0) {\n throw new PostmasterError(`job \"${entry.name}\" retry delay must be finite and non-negative`);\n }\n } catch (retryReason) {\n return deadLetter(entry, retryReason);\n }\n\n const availableAt = clock() + delay;\n const rescheduled = await store.transact((tx) =>\n rescheduleJob(tx, { availableAt, failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n if (!rescheduled) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n emitTap({ entry: toEntry({ ...entry, availableAt, status: 'queued' }), type: 'retry-scheduled' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 1 };\n } finally {\n clearInterval(heartbeat);\n }\n };\n\n const processNext = async (externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n if (externalSignal?.aborted) throw new DOMException('The operation was aborted', 'AbortError');\n if (controller.signal.aborted) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n const entry = await store.transact((tx) => claimJob(tx, { leaseDuration, now: clock(), ownerId }));\n if (!entry) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n activeEntries.set(entry.id, entry);\n try {\n return await execute(entry, externalSignal);\n } finally {\n activeEntries.delete(entry.id);\n }\n };\n\n const pump = (): void => {\n if (running || disposed) return;\n running = (async () => {\n for (;;) {\n const result = await processNext();\n if (!result.processed || disposed) break;\n }\n await schedule();\n })()\n .catch(error)\n .finally(() => {\n running = undefined;\n });\n };\n\n const unsubscribeStore = store.subscribe(wake);\n const externalAbort = (): void => {\n void dispose();\n };\n signal?.addEventListener('abort', externalAbort, { once: true });\n\n const dispose = async (): Promise<void> => {\n if (disposed) return;\n disposed = true;\n emitTap({ type: 'dispose' });\n tappers.clear();\n clearWakeTimer();\n signal?.removeEventListener('abort', externalAbort);\n unsubscribeStore();\n controller.abort();\n const entries = [...activeEntries.values()];\n activeEntries.clear();\n await Promise.all(\n entries.map((entry) => store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }))),\n );\n };\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n async dispose(): Promise<void> {\n await dispose();\n },\n get disposed() {\n return disposed;\n },\n async enqueue<K extends keyof J & string>(name: K, payload: InferJobPayload<J[K]>): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const parsed = runValidate(job.validate, payload);\n const key = job.key(parsed);\n if (typeof key !== 'string' || key.length === 0) {\n throw new PostmasterError(`job \"${name}\" returned an empty key`);\n }\n const now = clock();\n const entry: StoredJob = {\n attempts: 0,\n availableAt: now,\n createdAt: now,\n id: crypto.randomUUID(),\n key,\n name,\n payload: asJsonValue(parsed),\n status: 'queued',\n updatedAt: now,\n version: job.version,\n };\n await store.transact((tx) => enqueueJob(tx, entry));\n const result = toEntry(entry);\n emitTap({ entry: result, type: 'enqueued' });\n wake();\n return result;\n },\n flush(options?: { signal?: AbortSignal }): Promise<FlushResult> {\n assertLive();\n if (flushPromise) return flushPromise;\n clearWakeTimer();\n const promise = (async (): Promise<FlushResult> => {\n await running;\n const result: MutableFlushResult = emptyResult();\n try {\n for (;;) {\n const next = await processNext(options?.signal);\n if (!next.processed) return result;\n result.processed += 1;\n result.completed += next.completed;\n result.deadLettered += next.deadLettered;\n result.retryScheduled += next.retryScheduled;\n if (disposed) return result;\n }\n } finally {\n flushPromise = undefined;\n wake();\n }\n })();\n flushPromise = promise;\n return promise;\n },\n async list(filter?: EntryFilter): Promise<PostmasterEntry[]> {\n assertLive();\n return (await store.list(filter)).map(toEntry);\n },\n async remove(id: string): Promise<RemoveResult> {\n assertLive();\n const result = await store.transact((tx) => removeJob(tx, id));\n if (result.status === 'removed') emitTap({ id, type: 'removed' });\n return result;\n },\n async retry(id: string): Promise<RetryResult> {\n assertLive();\n const result = await store.transact((tx) => retryJob(tx, id, clock()));\n if (result.status === 'retried') wake();\n return result;\n },\n async start(): Promise<void> {\n assertLive();\n if (started) return;\n started = true;\n wake();\n },\n async stats(): Promise<PostmasterStats> {\n assertLive();\n return store.transact((tx) => tx.countByStatus());\n },\n tap(handler: (event: PostmasterEvent) => void, opts?: { readonly signal?: AbortSignal }): () => void {\n assertLive();\n tappers.add(handler);\n\n if (opts?.signal) {\n if (opts.signal.aborted) {\n tappers.delete(handler);\n return () => {};\n }\n const onAbort = () => tappers.delete(handler);\n opts.signal.addEventListener('abort', onAbort, { once: true });\n return () => {\n tappers.delete(handler);\n opts.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return () => tappers.delete(handler);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};function r(t,n){if(t&&(!Number.isInteger(t.maxAttempts)||t.maxAttempts<1))throw new e(`job "${n}" retry.maxAttempts must be a positive integer`)}function i(t,n){if(!Number.isInteger(n.version)||n.version<1)throw new e(`job "${t}" version must be a positive integer`);if(typeof n.execute!=`function`||typeof n.key!=`function`)throw new e(`job "${t}" must define execute and key`);r(n.retry,t)}function a(e){for(let[t,n]of Object.entries(e))i(t,n);return e}function o(e,t=3e4){return Math.min(1e3*2**Math.max(0,Number.isNaN(e)?0:Math.floor(e)),t)}function s(e,t){return e?typeof e==`function`?e(t):e.parse(t):t}function c(t){let n;try{n=JSON.stringify(t)}catch(t){throw new e(`job payload must be JSON-serializable`,{cause:t})}if(n===void 0)throw new e(`job payload must be JSON-serializable`);return JSON.parse(n)}function l(e,t){return e instanceof Error?{message:e.message,name:e.name,occurredAt:t}:{message:String(e),name:`Error`,occurredAt:t}}function u(e){return e instanceof DOMException&&e.name===`AbortError`}async function d(e,t){let n=await e.findClaimable(t.now);if(!n)return;let r={...n,attempts:n.attempts+1,failure:void 0,leaseExpiresAt:t.now+t.leaseDuration,ownerId:t.ownerId,status:`running`,updatedAt:t.now};return await e.put(r),r}async function f(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.delete(t.id),!0)}async function p(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function m(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,availableAt:t.availableAt,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`queued`,updatedAt:t.now}),!0)}async function h(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,failure:t.failure,leaseExpiresAt:void 0,ownerId:void 0,status:`dead-letter`,updatedAt:t.now}),!0)}async function g(e,t){let n=await e.get(t.id);return!n||n.status!==`running`||n.ownerId!==t.ownerId?!1:(await e.put({...n,leaseExpiresAt:t.leaseExpiresAt,updatedAt:t.now}),!0)}async function _(e,t,n){let r=await e.get(t);if(!r)return{status:`not-found`};if(r.status===`running`)return{status:`running`};if(r.status!==`dead-letter`)return{status:`not-dead-letter`};let i={...r,availableAt:n,failure:void 0,status:`queued`,updatedAt:n};return await e.put(i),{entry:b(i),status:`retried`}}async function v(e,t){let n=await e.get(t);return n?n.status===`running`?{status:`running`}:(await e.delete(t),{id:t,status:`removed`}):{status:`not-found`}}async function y(e,t){if(await e.get(t.id))throw Error(`Postmaster entry "${t.id}" already exists`);await e.put(t)}function b(e){return{attempts:e.attempts,availableAt:e.availableAt,createdAt:e.createdAt,...e.failure?{failure:e.failure}:{},id:e.id,key:e.key,name:e.name,status:e.status,updatedAt:e.updatedAt,version:e.version}}var x=()=>({completed:0,deadLettered:0,processed:0,retryScheduled:0});function S(e){let t=new AbortController,n=e.filter(e=>e!==void 0),r=()=>t.abort();for(let e of n){if(e.aborted){r();break}e.addEventListener(`abort`,r,{once:!0})}return t.signal}function C(t){if(!Number.isInteger(t)||t<1e3)throw new e(`leaseDuration must be a positive integer of at least 1000ms`)}function w(r){let{clock:i=Date.now,jobs:a,leaseDuration:w=3e4,signal:T,store:E}=r;C(w);let D=new AbortController,O=crypto.randomUUID(),k=new Set,A=!1,j=!1,M,N,P=new Map,F,I=e=>{let t=e instanceof Error?e:Error(String(e));L({error:t,type:`processor-error`}),queueMicrotask(()=>{throw t})},L=e=>{if(k.size!==0)for(let t of k)try{t(e)}catch{}},R=()=>{if(A)throw new t(`Postmaster is disposed`)},z=()=>{N!==void 0&&clearTimeout(N),N=void 0},B=()=>{z(),j&&!A&&K()},V=async()=>{if(!j||A)return;let e=await E.transact(e=>e.findNextWake(i()));if(e===void 0)return;let t=Math.max(0,Math.min(e-i(),2147483647));N=setTimeout(B,t)},H=e=>{let t=a[e.name];if(!t)throw new n(`no job definition is registered for "${e.name}"`);return t},U=async(e,t)=>await E.transact(n=>h(n,{failure:l(t,i()),id:e.id,now:i(),ownerId:O}))?(L({entry:b({...e,status:`dead-letter`}),type:`dead-lettered`}),{completed:0,deadLettered:1,processed:!0,retryScheduled:0}):(L({id:e.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}),W=async(t,r)=>{let a,c;try{if(a=H(t),t.version>a.version)throw new n(`job "${t.name}" version ${t.version} is newer than the registered version`);let e=t.version===a.version?t.payload:a.migrate?.(t.payload,t.version);if(e===void 0)throw new n(`job "${t.name}" version ${t.version} cannot be migrated`);c=s(a.validate,e)}catch(e){return U(t,e)}let d=new AbortController,h=S([D.signal,r,d.signal]),_=setInterval(()=>{E.transact(e=>g(e,{id:t.id,leaseExpiresAt:i()+w,now:i(),ownerId:O})).then(e=>{e||(d.abort(),L({id:t.id,type:`lease-lost`}))}).catch(I)},Math.max(1e3,Math.floor(w/2)));L({entry:b(t),type:`started`});try{return await a.execute(c,{attempt:t.attempts,entryId:t.id,key:t.key,signal:h}),h.aborted?(await E.transact(e=>p(e,{id:t.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0}):await E.transact(e=>f(e,{id:t.id,now:i(),ownerId:O}))?(L({entry:b(t),type:`completed`}),{completed:1,deadLettered:0,processed:!0,retryScheduled:0}):(L({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}catch(n){if(h.aborted||u(n))return await E.transact(e=>p(e,{id:t.id,now:i(),ownerId:O})),{completed:0,deadLettered:0,processed:!0,retryScheduled:0};let r=!1,s=0;try{if(r=t.attempts<(a.retry?.maxAttempts??1)&&(a.retry?.shouldRetry(n,t.attempts)??!1),!r)return U(t,n);if(s=a.retry?.delay?.(t.attempts-1)??o(t.attempts-1),!Number.isFinite(s)||s<0)throw new e(`job "${t.name}" retry delay must be finite and non-negative`)}catch(e){return U(t,e)}let c=i()+s;return await E.transact(e=>m(e,{availableAt:c,failure:l(n,i()),id:t.id,now:i(),ownerId:O}))?(L({entry:b({...t,availableAt:c,status:`queued`}),type:`retry-scheduled`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:1}):(L({id:t.id,type:`lease-lost`}),{completed:0,deadLettered:0,processed:!0,retryScheduled:0})}finally{clearInterval(_)}},G=async e=>{if(e?.aborted)throw new DOMException(`The operation was aborted`,`AbortError`);if(D.signal.aborted)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};let t=await E.transact(e=>d(e,{leaseDuration:w,now:i(),ownerId:O}));if(!t)return{completed:0,deadLettered:0,processed:!1,retryScheduled:0};P.set(t.id,t);try{return await W(t,e)}finally{P.delete(t.id)}},K=()=>{M||A||(M=(async()=>{for(;!(!(await G()).processed||A););await V()})().catch(I).finally(()=>{M=void 0}))},q=E.subscribe(B),J=()=>{Y()};T?.addEventListener(`abort`,J,{once:!0});let Y=async()=>{if(A)return;A=!0,L({type:`dispose`}),k.clear(),z(),T?.removeEventListener(`abort`,J),q(),D.abort();let e=[...P.values()];P.clear(),await Promise.all(e.map(e=>E.transact(t=>p(t,{id:e.id,now:i(),ownerId:O}))))};return{get disposalSignal(){return D.signal},async dispose(){await Y()},get disposed(){return A},async enqueue(t,r){R();let o=a[t];if(!o)throw new n(`no job definition is registered for "${t}"`);let l=s(o.validate,r),u=o.key(l);if(typeof u!=`string`||u.length===0)throw new e(`job "${t}" returned an empty key`);let d=i(),f={attempts:0,availableAt:d,createdAt:d,id:crypto.randomUUID(),key:u,name:t,payload:c(l),status:`queued`,updatedAt:d,version:o.version};await E.transact(e=>y(e,f));let p=b(f);return L({entry:p,type:`enqueued`}),B(),p},flush(e){if(R(),F)return F;z();let t=(async()=>{await M;let t=x();try{for(;;){let n=await G(e?.signal);if(!n.processed||(t.processed+=1,t.completed+=n.completed,t.deadLettered+=n.deadLettered,t.retryScheduled+=n.retryScheduled,A))return t}}finally{F=void 0,B()}})();return F=t,t},async list(e){return R(),(await E.list(e)).map(b)},async remove(e){R();let t=await E.transact(t=>v(t,e));return t.status===`removed`&&L({id:e,type:`removed`}),t},async retry(e){R();let t=await E.transact(t=>_(t,e,i()));return t.status===`retried`&&B(),t},async start(){R(),!j&&(j=!0,B())},async stats(){return R(),E.transact(e=>e.countByStatus())},tap(e,t){if(R(),k.add(e),t?.signal){if(t.signal.aborted)return k.delete(e),()=>{};let n=()=>k.delete(e);return t.signal.addEventListener(`abort`,n,{once:!0}),()=>{k.delete(e),t.signal?.removeEventListener(`abort`,n)}}return()=>k.delete(e)},async[Symbol.asyncDispose](){await Y()}}}export{t as PostmasterDisposedError,e as PostmasterError,n as PostmasterJobError,w as createPostmaster,a as defineJobs};
|
|
2
|
+
//# sourceMappingURL=postmaster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postmaster.js","names":[],"sources":["../src/errors.ts","../src/definitions.ts","../../arsenal/dist/async/backoff.js","../src/_json.ts","../src/store-ops.ts","../src/postmaster.ts"],"sourcesContent":["export class PostmasterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class PostmasterDisposedError extends PostmasterError {}\nexport class PostmasterJobError extends PostmasterError {}\n","import { PostmasterError } from './errors.ts';\nimport type { JobDefinition, JobDefinitions, RetryPolicy } from './types.ts';\n\nfunction validateRetry(retry: RetryPolicy | undefined, name: string): void {\n if (!retry) return;\n\n if (!Number.isInteger(retry.maxAttempts) || retry.maxAttempts < 1) {\n throw new PostmasterError(`job \"${name}\" retry.maxAttempts must be a positive integer`);\n }\n}\n\nfunction validateJob(name: string, job: JobDefinition<unknown>): void {\n if (!Number.isInteger(job.version) || job.version < 1) {\n throw new PostmasterError(`job \"${name}\" version must be a positive integer`);\n }\n\n if (typeof job.execute !== 'function' || typeof job.key !== 'function') {\n throw new PostmasterError(`job \"${name}\" must define execute and key`);\n }\n\n validateRetry(job.retry, name);\n}\n\nexport function defineJobs<const J extends JobDefinitions>(jobs: J): J {\n for (const [name, job] of Object.entries(jobs)) validateJob(name, job);\n\n return jobs;\n}\n","//#region src/async/backoff.ts\nfunction e(e, t = 3e4) {\n\treturn Math.min(1e3 * 2 ** Math.max(0, Number.isNaN(e) ? 0 : Math.floor(e)), t);\n}\n//#endregion\nexport { e as backoff };\n\n//# sourceMappingURL=backoff.js.map","import { PostmasterError } from './errors.ts';\nimport type { JsonValue, StoredFailure, Validate } from './types.ts';\n\nexport function runValidate<T>(validate: Validate<T> | undefined, value: unknown): T {\n if (!validate) return value as T;\n if (typeof validate === 'function') return validate(value);\n return validate.parse(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n let serialized: string | undefined;\n\n try {\n serialized = JSON.stringify(value);\n } catch (error) {\n throw new PostmasterError('job payload must be JSON-serializable', { cause: error });\n }\n\n if (serialized === undefined) {\n throw new PostmasterError('job payload must be JSON-serializable');\n }\n\n return JSON.parse(serialized) as JsonValue;\n}\n\nexport function failureFrom(error: unknown, now: number): StoredFailure {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, occurredAt: now };\n }\n\n return { message: String(error), name: 'Error', occurredAt: now };\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError';\n}\n","import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';\n\nexport interface ClaimParams {\n readonly leaseDuration: number;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface OwnedParams {\n readonly id: string;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface RescheduleParams extends OwnedParams {\n readonly availableAt: number;\n readonly failure: StoredFailure;\n}\n\nexport interface DeadLetterParams extends OwnedParams {\n readonly failure: StoredFailure;\n}\n\n/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */\nexport async function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined> {\n const entry = await tx.findClaimable(params.now);\n if (!entry) return undefined;\n const claimed: StoredJob = {\n ...entry,\n attempts: entry.attempts + 1,\n failure: undefined,\n leaseExpiresAt: params.now + params.leaseDuration,\n ownerId: params.ownerId,\n status: 'running',\n updatedAt: params.now,\n };\n await tx.put(claimed);\n return claimed;\n}\n\n/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */\nexport async function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.delete(params.id);\n return true;\n}\n\n/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */\nexport async function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */\nexport async function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n availableAt: params.availableAt,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Move a running job to dead-letter. Returns false if stale. */\nexport async function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'dead-letter',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Renew a lease for the current owner. Returns false if stale. */\nexport async function renewLeaseJob(tx: StoreTx, params: OwnedParams & { leaseExpiresAt: number }): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({ ...entry, leaseExpiresAt: params.leaseExpiresAt, updatedAt: params.now });\n return true;\n}\n\n/** Retry a dead-letter job back to queued. Returns discriminated result. */\nexport async function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n if (entry.status !== 'dead-letter') return { status: 'not-dead-letter' };\n const retried: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(retried);\n return { entry: toEntry(retried), status: 'retried' };\n}\n\n/** Remove a queued or dead-letter job. Running jobs cannot be removed. */\nexport async function removeJob(tx: StoreTx, id: string): Promise<RemoveResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n await tx.delete(id);\n return { id, status: 'removed' };\n}\n\n/** Enqueue a new job. Rejects duplicate ids. */\nexport async function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void> {\n const existing = await tx.get(entry.id);\n if (existing) throw new Error(`Postmaster entry \"${entry.id}\" already exists`);\n await tx.put(entry);\n}\n\nexport function toEntry(entry: StoredJob): PostmasterEntry {\n return {\n attempts: entry.attempts,\n availableAt: entry.availableAt,\n createdAt: entry.createdAt,\n ...(entry.failure ? { failure: entry.failure } : {}),\n id: entry.id,\n key: entry.key,\n name: entry.name,\n status: entry.status,\n updatedAt: entry.updatedAt,\n version: entry.version,\n };\n}\n","import { backoff } from '@vielzeug/arsenal/async';\nimport { asJsonValue, failureFrom, isAbortError, runValidate } from './_json.ts';\nimport { PostmasterDisposedError, PostmasterError, PostmasterJobError } from './errors.ts';\nimport {\n claimJob,\n completeJob,\n deadLetterJob,\n enqueueJob,\n releaseJob,\n removeJob,\n renewLeaseJob,\n rescheduleJob,\n retryJob,\n toEntry,\n} from './store-ops.ts';\nimport type {\n CreatePostmasterOptions,\n EntryFilter,\n FlushResult,\n InferJobPayload,\n JobContext,\n JobDefinition,\n JobDefinitions,\n Postmaster,\n PostmasterEntry,\n PostmasterEvent,\n PostmasterStats,\n RemoveResult,\n RetryResult,\n StoredJob,\n} from './types.ts';\n\ntype ExecutionResult = Omit<FlushResult, 'processed'> & { processed: boolean };\ntype MutableFlushResult = { -readonly [K in keyof FlushResult]: FlushResult[K] };\n\nconst emptyResult = (): FlushResult => ({ completed: 0, deadLettered: 0, processed: 0, retryScheduled: 0 });\n\nfunction mergeSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal {\n const controller = new AbortController();\n const active = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n const abort = (): void => controller.abort();\n\n for (const signal of active) {\n if (signal.aborted) {\n abort();\n break;\n }\n signal.addEventListener('abort', abort, { once: true });\n }\n\n return controller.signal;\n}\n\nfunction assertLeaseDuration(value: number): void {\n if (!Number.isInteger(value) || value < 1_000) {\n throw new PostmasterError('leaseDuration must be a positive integer of at least 1000ms');\n }\n}\n\nexport function createPostmaster<J extends JobDefinitions>(options: CreatePostmasterOptions<J>): Postmaster<J> {\n const { clock = Date.now, jobs, leaseDuration = 30_000, signal, store } = options;\n assertLeaseDuration(leaseDuration);\n\n const controller = new AbortController();\n const ownerId = crypto.randomUUID();\n const tappers = new Set<(event: PostmasterEvent) => void>();\n let disposed = false;\n let started = false;\n let running: Promise<void> | undefined;\n let wakeTimer: ReturnType<typeof setTimeout> | undefined;\n const activeEntries = new Map<string, StoredJob>();\n let flushPromise: Promise<FlushResult> | undefined;\n\n const error = (reason: unknown): void => {\n const value = reason instanceof Error ? reason : new Error(String(reason));\n emitTap({ error: value, type: 'processor-error' });\n queueMicrotask(() => {\n throw value;\n });\n };\n\n const emitTap = (event: PostmasterEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect postmaster behavior.\n }\n }\n };\n\n const assertLive = (): void => {\n if (disposed) throw new PostmasterDisposedError('Postmaster is disposed');\n };\n\n const clearWakeTimer = (): void => {\n if (wakeTimer !== undefined) clearTimeout(wakeTimer);\n wakeTimer = undefined;\n };\n\n const wake = (): void => {\n clearWakeTimer();\n if (started && !disposed) pump();\n };\n\n const schedule = async (): Promise<void> => {\n if (!started || disposed) return;\n const next = await store.transact((tx) => tx.findNextWake(clock()));\n if (next === undefined) return;\n const delay = Math.max(0, Math.min(next - clock(), 2_147_483_647));\n wakeTimer = setTimeout(wake, delay);\n };\n\n const resolveJob = (entry: StoredJob): JobDefinition<unknown> => {\n const job = jobs[entry.name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${entry.name}\"`);\n return job;\n };\n\n const deadLetter = async (entry: StoredJob, reason: unknown): Promise<ExecutionResult> => {\n const finalized = await store.transact((tx) =>\n deadLetterJob(tx, { failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n\n if (!finalized) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry({ ...entry, status: 'dead-letter' }), type: 'dead-lettered' });\n return { completed: 0, deadLettered: 1, processed: true, retryScheduled: 0 };\n };\n\n const execute = async (entry: StoredJob, externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n let job: JobDefinition<unknown>;\n let payload: unknown;\n\n try {\n job = resolveJob(entry);\n if (entry.version > job.version) {\n throw new PostmasterJobError(\n `job \"${entry.name}\" version ${entry.version} is newer than the registered version`,\n );\n }\n const raw = entry.version === job.version ? entry.payload : job.migrate?.(entry.payload, entry.version);\n if (raw === undefined) {\n throw new PostmasterJobError(`job \"${entry.name}\" version ${entry.version} cannot be migrated`);\n }\n payload = runValidate(job.validate, raw);\n } catch (reason) {\n return deadLetter(entry, reason);\n }\n\n const taskController = new AbortController();\n const taskSignal = mergeSignals([controller.signal, externalSignal, taskController.signal]);\n const heartbeat = setInterval(\n () => {\n void store\n .transact((tx) =>\n renewLeaseJob(tx, { id: entry.id, leaseExpiresAt: clock() + leaseDuration, now: clock(), ownerId }),\n )\n .then((renewed) => {\n if (!renewed) {\n taskController.abort();\n emitTap({ id: entry.id, type: 'lease-lost' });\n }\n })\n .catch(error);\n },\n Math.max(1_000, Math.floor(leaseDuration / 2)),\n );\n\n emitTap({ entry: toEntry(entry), type: 'started' });\n\n try {\n await job.execute(payload, {\n attempt: entry.attempts,\n entryId: entry.id,\n key: entry.key,\n signal: taskSignal,\n } as JobContext);\n\n if (taskSignal.aborted) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n const completed = await store.transact((tx) => completeJob(tx, { id: entry.id, now: clock(), ownerId }));\n if (!completed) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n emitTap({ entry: toEntry(entry), type: 'completed' });\n return { completed: 1, deadLettered: 0, processed: true, retryScheduled: 0 };\n } catch (reason) {\n if (taskSignal.aborted || isAbortError(reason)) {\n await store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }));\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n\n let shouldRetry = false;\n let delay = 0;\n\n try {\n shouldRetry =\n entry.attempts < (job.retry?.maxAttempts ?? 1) && (job.retry?.shouldRetry(reason, entry.attempts) ?? false);\n if (!shouldRetry) return deadLetter(entry, reason);\n delay = job.retry?.delay?.(entry.attempts - 1) ?? backoff(entry.attempts - 1);\n if (!Number.isFinite(delay) || delay < 0) {\n throw new PostmasterError(`job \"${entry.name}\" retry delay must be finite and non-negative`);\n }\n } catch (retryReason) {\n return deadLetter(entry, retryReason);\n }\n\n const availableAt = clock() + delay;\n const rescheduled = await store.transact((tx) =>\n rescheduleJob(tx, { availableAt, failure: failureFrom(reason, clock()), id: entry.id, now: clock(), ownerId }),\n );\n if (!rescheduled) {\n emitTap({ id: entry.id, type: 'lease-lost' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 0 };\n }\n emitTap({ entry: toEntry({ ...entry, availableAt, status: 'queued' }), type: 'retry-scheduled' });\n return { completed: 0, deadLettered: 0, processed: true, retryScheduled: 1 };\n } finally {\n clearInterval(heartbeat);\n }\n };\n\n const processNext = async (externalSignal?: AbortSignal): Promise<ExecutionResult> => {\n if (externalSignal?.aborted) throw new DOMException('The operation was aborted', 'AbortError');\n if (controller.signal.aborted) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n const entry = await store.transact((tx) => claimJob(tx, { leaseDuration, now: clock(), ownerId }));\n if (!entry) return { completed: 0, deadLettered: 0, processed: false, retryScheduled: 0 };\n activeEntries.set(entry.id, entry);\n try {\n return await execute(entry, externalSignal);\n } finally {\n activeEntries.delete(entry.id);\n }\n };\n\n const pump = (): void => {\n if (running || disposed) return;\n running = (async () => {\n for (;;) {\n const result = await processNext();\n if (!result.processed || disposed) break;\n }\n await schedule();\n })()\n .catch(error)\n .finally(() => {\n running = undefined;\n });\n };\n\n const unsubscribeStore = store.subscribe(wake);\n const externalAbort = (): void => {\n void dispose();\n };\n signal?.addEventListener('abort', externalAbort, { once: true });\n\n const dispose = async (): Promise<void> => {\n if (disposed) return;\n disposed = true;\n emitTap({ type: 'dispose' });\n tappers.clear();\n clearWakeTimer();\n signal?.removeEventListener('abort', externalAbort);\n unsubscribeStore();\n controller.abort();\n const entries = [...activeEntries.values()];\n activeEntries.clear();\n await Promise.all(\n entries.map((entry) => store.transact((tx) => releaseJob(tx, { id: entry.id, now: clock(), ownerId }))),\n );\n };\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n async dispose(): Promise<void> {\n await dispose();\n },\n get disposed() {\n return disposed;\n },\n async enqueue<K extends keyof J & string>(name: K, payload: InferJobPayload<J[K]>): Promise<PostmasterEntry> {\n assertLive();\n const job = jobs[name];\n if (!job) throw new PostmasterJobError(`no job definition is registered for \"${name}\"`);\n const parsed = runValidate(job.validate, payload);\n const key = job.key(parsed);\n if (typeof key !== 'string' || key.length === 0) {\n throw new PostmasterError(`job \"${name}\" returned an empty key`);\n }\n const now = clock();\n const entry: StoredJob = {\n attempts: 0,\n availableAt: now,\n createdAt: now,\n id: crypto.randomUUID(),\n key,\n name,\n payload: asJsonValue(parsed),\n status: 'queued',\n updatedAt: now,\n version: job.version,\n };\n await store.transact((tx) => enqueueJob(tx, entry));\n const result = toEntry(entry);\n emitTap({ entry: result, type: 'enqueued' });\n wake();\n return result;\n },\n flush(options?: { signal?: AbortSignal }): Promise<FlushResult> {\n assertLive();\n if (flushPromise) return flushPromise;\n clearWakeTimer();\n const promise = (async (): Promise<FlushResult> => {\n await running;\n const result: MutableFlushResult = emptyResult();\n try {\n for (;;) {\n const next = await processNext(options?.signal);\n if (!next.processed) return result;\n result.processed += 1;\n result.completed += next.completed;\n result.deadLettered += next.deadLettered;\n result.retryScheduled += next.retryScheduled;\n if (disposed) return result;\n }\n } finally {\n flushPromise = undefined;\n wake();\n }\n })();\n flushPromise = promise;\n return promise;\n },\n async list(filter?: EntryFilter): Promise<PostmasterEntry[]> {\n assertLive();\n return (await store.list(filter)).map(toEntry);\n },\n async remove(id: string): Promise<RemoveResult> {\n assertLive();\n const result = await store.transact((tx) => removeJob(tx, id));\n if (result.status === 'removed') emitTap({ id, type: 'removed' });\n return result;\n },\n async retry(id: string): Promise<RetryResult> {\n assertLive();\n const result = await store.transact((tx) => retryJob(tx, id, clock()));\n if (result.status === 'retried') wake();\n return result;\n },\n async start(): Promise<void> {\n assertLive();\n if (started) return;\n started = true;\n wake();\n },\n async stats(): Promise<PostmasterStats> {\n assertLive();\n return store.transact((tx) => tx.countByStatus());\n },\n tap(handler: (event: PostmasterEvent) => void, opts?: { readonly signal?: AbortSignal }): () => void {\n assertLive();\n tappers.add(handler);\n\n if (opts?.signal) {\n if (opts.signal.aborted) {\n tappers.delete(handler);\n return () => {};\n }\n const onAbort = () => tappers.delete(handler);\n opts.signal.addEventListener('abort', onAbort, { once: true });\n return () => {\n tappers.delete(handler);\n opts.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return () => tappers.delete(handler);\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await dispose();\n },\n };\n}\n"],"mappings":"AAAA,IAAa,EAAb,cAAqC,KAAM,CACzC,YAAY,EAAiB,EAAwB,CACnD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,EAAb,cAA6C,CAAgB,CAAC,EACjD,EAAb,cAAwC,CAAgB,CAAC,ECNzD,SAAS,EAAc,EAAgC,EAAoB,CACpE,OAED,CAAC,OAAO,UAAU,EAAM,WAAW,GAAK,EAAM,YAAc,GAC9D,MAAM,IAAI,EAAgB,QAAQ,EAAK,+CAA+C,CAE1F,CAEA,SAAS,EAAY,EAAc,EAAmC,CACpE,GAAI,CAAC,OAAO,UAAU,EAAI,OAAO,GAAK,EAAI,QAAU,EAClD,MAAM,IAAI,EAAgB,QAAQ,EAAK,qCAAqC,EAG9E,GAAI,OAAO,EAAI,SAAY,YAAc,OAAO,EAAI,KAAQ,WAC1D,MAAM,IAAI,EAAgB,QAAQ,EAAK,8BAA8B,EAGvE,EAAc,EAAI,MAAO,CAAI,CAC/B,CAEA,SAAgB,EAA2C,EAAY,CACrE,IAAK,GAAM,CAAC,EAAM,KAAQ,OAAO,QAAQ,CAAI,EAAG,EAAY,EAAM,CAAG,EAErE,OAAO,CACT,CC1BA,SAAS,EAAE,EAAG,EAAI,IAAK,CACtB,OAAO,KAAK,IAAI,IAAM,GAAK,KAAK,IAAI,EAAG,OAAO,MAAM,CAAC,EAAI,EAAI,KAAK,MAAM,CAAC,CAAC,EAAG,CAAC,CAC/E,CCAA,SAAgB,EAAe,EAAmC,EAAmB,CAGnF,OAFK,EACD,OAAO,GAAa,WAAmB,EAAS,CAAK,EAClD,EAAS,MAAM,CAAK,EAFL,CAGxB,CAEA,SAAgB,EAAY,EAA2B,CACrD,IAAI,EAEJ,GAAI,CACF,EAAa,KAAK,UAAU,CAAK,CACnC,OAAS,EAAO,CACd,MAAM,IAAI,EAAgB,wCAAyC,CAAE,MAAO,CAAM,CAAC,CACrF,CAEA,GAAI,IAAe,IAAA,GACjB,MAAM,IAAI,EAAgB,uCAAuC,EAGnE,OAAO,KAAK,MAAM,CAAU,CAC9B,CAEA,SAAgB,EAAY,EAAgB,EAA4B,CAKtE,OAJI,aAAiB,MACZ,CAAE,QAAS,EAAM,QAAS,KAAM,EAAM,KAAM,WAAY,CAAI,EAG9D,CAAE,QAAS,OAAO,CAAK,EAAG,KAAM,QAAS,WAAY,CAAI,CAClE,CAEA,SAAgB,EAAa,EAAyB,CACpD,OAAO,aAAiB,cAAgB,EAAM,OAAS,YACzD,CCXA,eAAsB,EAAS,EAAa,EAAqD,CAC/F,IAAM,EAAQ,MAAM,EAAG,cAAc,EAAO,GAAG,EAC/C,GAAI,CAAC,EAAO,OACZ,IAAM,EAAqB,CACzB,GAAG,EACH,SAAU,EAAM,SAAW,EAC3B,QAAS,IAAA,GACT,eAAgB,EAAO,IAAM,EAAO,cACpC,QAAS,EAAO,QAChB,OAAQ,UACR,UAAW,EAAO,GACpB,EAEA,OADA,MAAM,EAAG,IAAI,CAAO,EACb,CACT,CAGA,eAAsB,EAAY,EAAa,EAAuC,CACpF,IAAM,EAAQ,MAAM,EAAG,IAAI,EAAO,EAAE,EAGpC,MAFI,CAAC,GAAS,EAAM,SAAW,WAAa,EAAM,UAAY,EAAO,QAAgB,IACrF,MAAM,EAAG,OAAO,EAAO,EAAE,EAClB,GACT,CAGA,eAAsB,EAAW,EAAa,EAAuC,CACnF,IAAM,EAAQ,MAAM,EAAG,IAAI,EAAO,EAAE,EASpC,MARI,CAAC,GAAS,EAAM,SAAW,WAAa,EAAM,UAAY,EAAO,QAAgB,IACrF,MAAM,EAAG,IAAI,CACX,GAAG,EACH,eAAgB,IAAA,GAChB,QAAS,IAAA,GACT,OAAQ,SACR,UAAW,EAAO,GACpB,CAAC,EACM,GACT,CAGA,eAAsB,EAAc,EAAa,EAA4C,CAC3F,IAAM,EAAQ,MAAM,EAAG,IAAI,EAAO,EAAE,EAWpC,MAVI,CAAC,GAAS,EAAM,SAAW,WAAa,EAAM,UAAY,EAAO,QAAgB,IACrF,MAAM,EAAG,IAAI,CACX,GAAG,EACH,YAAa,EAAO,YACpB,QAAS,EAAO,QAChB,eAAgB,IAAA,GAChB,QAAS,IAAA,GACT,OAAQ,SACR,UAAW,EAAO,GACpB,CAAC,EACM,GACT,CAGA,eAAsB,EAAc,EAAa,EAA4C,CAC3F,IAAM,EAAQ,MAAM,EAAG,IAAI,EAAO,EAAE,EAUpC,MATI,CAAC,GAAS,EAAM,SAAW,WAAa,EAAM,UAAY,EAAO,QAAgB,IACrF,MAAM,EAAG,IAAI,CACX,GAAG,EACH,QAAS,EAAO,QAChB,eAAgB,IAAA,GAChB,QAAS,IAAA,GACT,OAAQ,cACR,UAAW,EAAO,GACpB,CAAC,EACM,GACT,CAGA,eAAsB,EAAc,EAAa,EAAoE,CACnH,IAAM,EAAQ,MAAM,EAAG,IAAI,EAAO,EAAE,EAGpC,MAFI,CAAC,GAAS,EAAM,SAAW,WAAa,EAAM,UAAY,EAAO,QAAgB,IACrF,MAAM,EAAG,IAAI,CAAE,GAAG,EAAO,eAAgB,EAAO,eAAgB,UAAW,EAAO,GAAI,CAAC,EAChF,GACT,CAGA,eAAsB,EAAS,EAAa,EAAY,EAAmC,CACzF,IAAM,EAAQ,MAAM,EAAG,IAAI,CAAE,EAC7B,GAAI,CAAC,EAAO,MAAO,CAAE,OAAQ,WAAY,EACzC,GAAI,EAAM,SAAW,UAAW,MAAO,CAAE,OAAQ,SAAU,EAC3D,GAAI,EAAM,SAAW,cAAe,MAAO,CAAE,OAAQ,iBAAkB,EACvE,IAAM,EAAqB,CACzB,GAAG,EACH,YAAa,EACb,QAAS,IAAA,GACT,OAAQ,SACR,UAAW,CACb,EAEA,OADA,MAAM,EAAG,IAAI,CAAO,EACb,CAAE,MAAO,EAAQ,CAAO,EAAG,OAAQ,SAAU,CACtD,CAGA,eAAsB,EAAU,EAAa,EAAmC,CAC9E,IAAM,EAAQ,MAAM,EAAG,IAAI,CAAE,EAI7B,OAHK,EACD,EAAM,SAAW,UAAkB,CAAE,OAAQ,SAAU,GAC3D,MAAM,EAAG,OAAO,CAAE,EACX,CAAE,KAAI,OAAQ,SAAU,GAHZ,CAAE,OAAQ,WAAY,CAI3C,CAGA,eAAsB,EAAW,EAAa,EAAiC,CAE7E,GAAI,MADmB,EAAG,IAAI,EAAM,EAAE,EACxB,MAAU,MAAM,qBAAqB,EAAM,GAAG,iBAAiB,EAC7E,MAAM,EAAG,IAAI,CAAK,CACpB,CAEA,SAAgB,EAAQ,EAAmC,CACzD,MAAO,CACL,SAAU,EAAM,SAChB,YAAa,EAAM,YACnB,UAAW,EAAM,UACjB,GAAI,EAAM,QAAU,CAAE,QAAS,EAAM,OAAQ,EAAI,CAAC,EAClD,GAAI,EAAM,GACV,IAAK,EAAM,IACX,KAAM,EAAM,KACZ,OAAQ,EAAM,OACd,UAAW,EAAM,UACjB,QAAS,EAAM,OACjB,CACF,CChHA,IAAM,OAAkC,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,EAAG,eAAgB,CAAE,GAEzG,SAAS,EAAa,EAA4D,CAChF,IAAM,EAAa,IAAI,gBACjB,EAAS,EAAQ,OAAQ,GAAkC,IAAW,IAAA,EAAS,EAC/E,MAAoB,EAAW,MAAM,EAE3C,IAAK,IAAM,KAAU,EAAQ,CAC3B,GAAI,EAAO,QAAS,CAClB,EAAM,EACN,KACF,CACA,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,CACxD,CAEA,OAAO,EAAW,MACpB,CAEA,SAAS,EAAoB,EAAqB,CAChD,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,IACtC,MAAM,IAAI,EAAgB,6DAA6D,CAE3F,CAEA,SAAgB,EAA2C,EAAoD,CAC7G,GAAM,CAAE,QAAQ,KAAK,IAAK,OAAM,gBAAgB,IAAQ,SAAQ,SAAU,EAC1E,EAAoB,CAAa,EAEjC,IAAM,EAAa,IAAI,gBACjB,EAAU,OAAO,WAAW,EAC5B,EAAU,IAAI,IAChB,EAAW,GACX,EAAU,GACV,EACA,EACE,EAAgB,IAAI,IACtB,EAEE,EAAS,GAA0B,CACvC,IAAM,EAAQ,aAAkB,MAAQ,EAAa,MAAM,OAAO,CAAM,CAAC,EACzE,EAAQ,CAAE,MAAO,EAAO,KAAM,iBAAkB,CAAC,EACjD,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,EAEM,EAAW,GAAiC,CAC5C,KAAQ,OAAS,EACrB,IAAK,IAAM,KAAU,EACnB,GAAI,CACF,EAAO,CAAK,CACd,MAAQ,CAER,CAEJ,EAEM,MAAyB,CAC7B,GAAI,EAAU,MAAM,IAAI,EAAwB,wBAAwB,CAC1E,EAEM,MAA6B,CAC7B,IAAc,IAAA,IAAW,aAAa,CAAS,EACnD,EAAY,IAAA,EACd,EAEM,MAAmB,CACvB,EAAe,EACX,GAAW,CAAC,GAAU,EAAK,CACjC,EAEM,EAAW,SAA2B,CAC1C,GAAI,CAAC,GAAW,EAAU,OAC1B,IAAM,EAAO,MAAM,EAAM,SAAU,GAAO,EAAG,aAAa,EAAM,CAAC,CAAC,EAClE,GAAI,IAAS,IAAA,GAAW,OACxB,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,EAAO,EAAM,EAAG,UAAa,CAAC,EACjE,EAAY,WAAW,EAAM,CAAK,CACpC,EAEM,EAAc,GAA6C,CAC/D,IAAM,EAAM,EAAK,EAAM,MACvB,GAAI,CAAC,EAAK,MAAM,IAAI,EAAmB,wCAAwC,EAAM,KAAK,EAAE,EAC5F,OAAO,CACT,EAEM,EAAa,MAAO,EAAkB,IAKrC,MAJmB,EAAM,SAAU,GACtC,EAAc,EAAI,CAAE,QAAS,EAAY,EAAQ,EAAM,CAAC,EAAG,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAClG,GAOA,EAAQ,CAAE,MAAO,EAAQ,CAAE,GAAG,EAAO,OAAQ,aAAc,CAAC,EAAG,KAAM,eAAgB,CAAC,EAC/E,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,IALzE,EAAQ,CAAE,GAAI,EAAM,GAAI,KAAM,YAAa,CAAC,EACrC,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,GAOzE,EAAU,MAAO,EAAkB,IAA2D,CAClG,IAAI,EACA,EAEJ,GAAI,CAEF,GADA,EAAM,EAAW,CAAK,EAClB,EAAM,QAAU,EAAI,QACtB,MAAM,IAAI,EACR,QAAQ,EAAM,KAAK,YAAY,EAAM,QAAQ,sCAC/C,EAEF,IAAM,EAAM,EAAM,UAAY,EAAI,QAAU,EAAM,QAAU,EAAI,UAAU,EAAM,QAAS,EAAM,OAAO,EACtG,GAAI,IAAQ,IAAA,GACV,MAAM,IAAI,EAAmB,QAAQ,EAAM,KAAK,YAAY,EAAM,QAAQ,oBAAoB,EAEhG,EAAU,EAAY,EAAI,SAAU,CAAG,CACzC,OAAS,EAAQ,CACf,OAAO,EAAW,EAAO,CAAM,CACjC,CAEA,IAAM,EAAiB,IAAI,gBACrB,EAAa,EAAa,CAAC,EAAW,OAAQ,EAAgB,EAAe,MAAM,CAAC,EACpF,EAAY,gBACV,CACJ,EACG,SAAU,GACT,EAAc,EAAI,CAAE,GAAI,EAAM,GAAI,eAAgB,EAAM,EAAI,EAAe,IAAK,EAAM,EAAG,SAAQ,CAAC,CACpG,CAAC,CACA,KAAM,GAAY,CACZ,IACH,EAAe,MAAM,EACrB,EAAQ,CAAE,GAAI,EAAM,GAAI,KAAM,YAAa,CAAC,EAEhD,CAAC,CAAC,CACD,MAAM,CAAK,CAChB,EACA,KAAK,IAAI,IAAO,KAAK,MAAM,EAAgB,CAAC,CAAC,CAC/C,EAEA,EAAQ,CAAE,MAAO,EAAQ,CAAK,EAAG,KAAM,SAAU,CAAC,EAElD,GAAI,CAoBF,OAnBA,MAAM,EAAI,QAAQ,EAAS,CACzB,QAAS,EAAM,SACf,QAAS,EAAM,GACf,IAAK,EAAM,IACX,OAAQ,CACV,CAAe,EAEX,EAAW,SACb,MAAM,EAAM,SAAU,GAAO,EAAW,EAAI,CAAE,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAAC,EAC7E,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,GAIxE,MADmB,EAAM,SAAU,GAAO,EAAY,EAAI,CAAE,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAAC,GAMvG,EAAQ,CAAE,MAAO,EAAQ,CAAK,EAAG,KAAM,WAAY,CAAC,EAC7C,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,IALzE,EAAQ,CAAE,GAAI,EAAM,GAAI,KAAM,YAAa,CAAC,EACrC,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,EAK/E,OAAS,EAAQ,CACf,GAAI,EAAW,SAAW,EAAa,CAAM,EAE3C,OADA,MAAM,EAAM,SAAU,GAAO,EAAW,EAAI,CAAE,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAAC,EAC7E,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,EAG7E,IAAI,EAAc,GACd,EAAQ,EAEZ,GAAI,CAGF,GAFA,EACE,EAAM,UAAY,EAAI,OAAO,aAAe,KAAO,EAAI,OAAO,YAAY,EAAQ,EAAM,QAAQ,GAAK,IACnG,CAAC,EAAa,OAAO,EAAW,EAAO,CAAM,EAEjD,GADA,EAAQ,EAAI,OAAO,QAAQ,EAAM,SAAW,CAAC,GAAK,EAAQ,EAAM,SAAW,CAAC,EACxE,CAAC,OAAO,SAAS,CAAK,GAAK,EAAQ,EACrC,MAAM,IAAI,EAAgB,QAAQ,EAAM,KAAK,8CAA8C,CAE/F,OAAS,EAAa,CACpB,OAAO,EAAW,EAAO,CAAW,CACtC,CAEA,IAAM,EAAc,EAAM,EAAI,EAS9B,OALK,MAHqB,EAAM,SAAU,GACxC,EAAc,EAAI,CAAE,cAAa,QAAS,EAAY,EAAQ,EAAM,CAAC,EAAG,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAC/G,GAKA,EAAQ,CAAE,MAAO,EAAQ,CAAE,GAAG,EAAO,cAAa,OAAQ,QAAS,CAAC,EAAG,KAAM,iBAAkB,CAAC,EACzF,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,IAJzE,EAAQ,CAAE,GAAI,EAAM,GAAI,KAAM,YAAa,CAAC,EACrC,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAM,eAAgB,CAAE,EAI/E,QAAU,CACR,cAAc,CAAS,CACzB,CACF,EAEM,EAAc,KAAO,IAA2D,CACpF,GAAI,GAAgB,QAAS,MAAM,IAAI,aAAa,4BAA6B,YAAY,EAC7F,GAAI,EAAW,OAAO,QAAS,MAAO,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAO,eAAgB,CAAE,EAC3G,IAAM,EAAQ,MAAM,EAAM,SAAU,GAAO,EAAS,EAAI,CAAE,gBAAe,IAAK,EAAM,EAAG,SAAQ,CAAC,CAAC,EACjG,GAAI,CAAC,EAAO,MAAO,CAAE,UAAW,EAAG,aAAc,EAAG,UAAW,GAAO,eAAgB,CAAE,EACxF,EAAc,IAAI,EAAM,GAAI,CAAK,EACjC,GAAI,CACF,OAAO,MAAM,EAAQ,EAAO,CAAc,CAC5C,QAAU,CACR,EAAc,OAAO,EAAM,EAAE,CAC/B,CACF,EAEM,MAAmB,CACnB,GAAW,IACf,GAAW,SAAY,CACrB,KAEM,IAAC,MADgB,EAAY,EAAA,CACrB,WAAa,KAE3B,MAAM,EAAS,CACjB,EAAA,CAAG,CAAC,CACD,MAAM,CAAK,CAAC,CACZ,YAAc,CACb,EAAU,IAAA,EACZ,CAAC,EACL,EAEM,EAAmB,EAAM,UAAU,CAAI,EACvC,MAA4B,CAChC,EAAa,CACf,EACA,GAAQ,iBAAiB,QAAS,EAAe,CAAE,KAAM,EAAK,CAAC,EAE/D,IAAM,EAAU,SAA2B,CACzC,GAAI,EAAU,OACd,EAAW,GACX,EAAQ,CAAE,KAAM,SAAU,CAAC,EAC3B,EAAQ,MAAM,EACd,EAAe,EACf,GAAQ,oBAAoB,QAAS,CAAa,EAClD,EAAiB,EACjB,EAAW,MAAM,EACjB,IAAM,EAAU,CAAC,GAAG,EAAc,OAAO,CAAC,EAC1C,EAAc,MAAM,EACpB,MAAM,QAAQ,IACZ,EAAQ,IAAK,GAAU,EAAM,SAAU,GAAO,EAAW,EAAI,CAAE,GAAI,EAAM,GAAI,IAAK,EAAM,EAAG,SAAQ,CAAC,CAAC,CAAC,CACxG,CACF,EAEA,MAAO,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAW,MACpB,EACA,MAAM,SAAyB,CAC7B,MAAM,EAAQ,CAChB,EACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,MAAM,QAAoC,EAAS,EAA0D,CAC3G,EAAW,EACX,IAAM,EAAM,EAAK,GACjB,GAAI,CAAC,EAAK,MAAM,IAAI,EAAmB,wCAAwC,EAAK,EAAE,EACtF,IAAM,EAAS,EAAY,EAAI,SAAU,CAAO,EAC1C,EAAM,EAAI,IAAI,CAAM,EAC1B,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAC5C,MAAM,IAAI,EAAgB,QAAQ,EAAK,wBAAwB,EAEjE,IAAM,EAAM,EAAM,EACZ,EAAmB,CACvB,SAAU,EACV,YAAa,EACb,UAAW,EACX,GAAI,OAAO,WAAW,EACtB,MACA,OACA,QAAS,EAAY,CAAM,EAC3B,OAAQ,SACR,UAAW,EACX,QAAS,EAAI,OACf,EACA,MAAM,EAAM,SAAU,GAAO,EAAW,EAAI,CAAK,CAAC,EAClD,IAAM,EAAS,EAAQ,CAAK,EAG5B,OAFA,EAAQ,CAAE,MAAO,EAAQ,KAAM,UAAW,CAAC,EAC3C,EAAK,EACE,CACT,EACA,MAAM,EAA0D,CAE9D,GADA,EAAW,EACP,EAAc,OAAO,EACzB,EAAe,EACf,IAAM,GAAW,SAAkC,CACjD,MAAM,EACN,IAAM,EAA6B,EAAY,EAC/C,GAAI,CACF,OAAS,CACP,IAAM,EAAO,MAAM,EAAY,GAAS,MAAM,EAM9C,GALI,CAAC,EAAK,YACV,EAAO,WAAa,EACpB,EAAO,WAAa,EAAK,UACzB,EAAO,cAAgB,EAAK,aAC5B,EAAO,gBAAkB,EAAK,eAC1B,GAAU,OAAO,CACvB,CACF,QAAU,CACR,EAAe,IAAA,GACf,EAAK,CACP,CACF,EAAA,CAAG,EAEH,MADA,GAAe,EACR,CACT,EACA,MAAM,KAAK,EAAkD,CAE3D,OADA,EAAW,GACH,MAAM,EAAM,KAAK,CAAM,EAAA,CAAG,IAAI,CAAO,CAC/C,EACA,MAAM,OAAO,EAAmC,CAC9C,EAAW,EACX,IAAM,EAAS,MAAM,EAAM,SAAU,GAAO,EAAU,EAAI,CAAE,CAAC,EAE7D,OADI,EAAO,SAAW,WAAW,EAAQ,CAAE,KAAI,KAAM,SAAU,CAAC,EACzD,CACT,EACA,MAAM,MAAM,EAAkC,CAC5C,EAAW,EACX,IAAM,EAAS,MAAM,EAAM,SAAU,GAAO,EAAS,EAAI,EAAI,EAAM,CAAC,CAAC,EAErE,OADI,EAAO,SAAW,WAAW,EAAK,EAC/B,CACT,EACA,MAAM,OAAuB,CAC3B,EAAW,EACP,KACJ,EAAU,GACV,EAAK,EACP,EACA,MAAM,OAAkC,CAEtC,OADA,EAAW,EACJ,EAAM,SAAU,GAAO,EAAG,cAAc,CAAC,CAClD,EACA,IAAI,EAA2C,EAAsD,CAInG,GAHA,EAAW,EACX,EAAQ,IAAI,CAAO,EAEf,GAAM,OAAQ,CAChB,GAAI,EAAK,OAAO,QAEd,OADA,EAAQ,OAAO,CAAO,MACT,CAAC,EAEhB,IAAM,MAAgB,EAAQ,OAAO,CAAO,EAE5C,OADA,EAAK,OAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,MAChD,CACX,EAAQ,OAAO,CAAO,EACtB,EAAK,QAAQ,oBAAoB,QAAS,CAAO,CACnD,CACF,CAEA,UAAa,EAAQ,OAAO,CAAO,CACrC,EACA,MAAO,OAAO,eAA+B,CAC3C,MAAM,EAAQ,CAChB,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
async function e(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 t(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 n(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 r(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 i(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 a(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 o(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:l(i),status:`retried`}}async function s(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 c(e,t){if(await e.get(t.id))throw Error(`Postmaster entry "${t.id}" already exists`);await e.put(t)}function l(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}}exports.claimJob=e,exports.completeJob=t,exports.deadLetterJob=i,exports.enqueueJob=c,exports.releaseJob=n,exports.removeJob=s,exports.renewLeaseJob=a,exports.rescheduleJob=r,exports.retryJob=o,exports.toEntry=l;
|
|
2
|
+
//# sourceMappingURL=store-ops.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-ops.cjs","names":[],"sources":["../src/store-ops.ts"],"sourcesContent":["import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';\n\nexport interface ClaimParams {\n readonly leaseDuration: number;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface OwnedParams {\n readonly id: string;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface RescheduleParams extends OwnedParams {\n readonly availableAt: number;\n readonly failure: StoredFailure;\n}\n\nexport interface DeadLetterParams extends OwnedParams {\n readonly failure: StoredFailure;\n}\n\n/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */\nexport async function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined> {\n const entry = await tx.findClaimable(params.now);\n if (!entry) return undefined;\n const claimed: StoredJob = {\n ...entry,\n attempts: entry.attempts + 1,\n failure: undefined,\n leaseExpiresAt: params.now + params.leaseDuration,\n ownerId: params.ownerId,\n status: 'running',\n updatedAt: params.now,\n };\n await tx.put(claimed);\n return claimed;\n}\n\n/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */\nexport async function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.delete(params.id);\n return true;\n}\n\n/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */\nexport async function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */\nexport async function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n availableAt: params.availableAt,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Move a running job to dead-letter. Returns false if stale. */\nexport async function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'dead-letter',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Renew a lease for the current owner. Returns false if stale. */\nexport async function renewLeaseJob(tx: StoreTx, params: OwnedParams & { leaseExpiresAt: number }): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({ ...entry, leaseExpiresAt: params.leaseExpiresAt, updatedAt: params.now });\n return true;\n}\n\n/** Retry a dead-letter job back to queued. Returns discriminated result. */\nexport async function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n if (entry.status !== 'dead-letter') return { status: 'not-dead-letter' };\n const retried: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(retried);\n return { entry: toEntry(retried), status: 'retried' };\n}\n\n/** Remove a queued or dead-letter job. Running jobs cannot be removed. */\nexport async function removeJob(tx: StoreTx, id: string): Promise<RemoveResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n await tx.delete(id);\n return { id, status: 'removed' };\n}\n\n/** Enqueue a new job. Rejects duplicate ids. */\nexport async function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void> {\n const existing = await tx.get(entry.id);\n if (existing) throw new Error(`Postmaster entry \"${entry.id}\" already exists`);\n await tx.put(entry);\n}\n\nexport function toEntry(entry: StoredJob): PostmasterEntry {\n return {\n attempts: entry.attempts,\n availableAt: entry.availableAt,\n createdAt: entry.createdAt,\n ...(entry.failure ? { failure: entry.failure } : {}),\n id: entry.id,\n key: entry.key,\n name: entry.name,\n status: entry.status,\n updatedAt: entry.updatedAt,\n version: entry.version,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';
|
|
2
|
+
export interface ClaimParams {
|
|
3
|
+
readonly leaseDuration: number;
|
|
4
|
+
readonly now: number;
|
|
5
|
+
readonly ownerId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OwnedParams {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly now: number;
|
|
10
|
+
readonly ownerId: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RescheduleParams extends OwnedParams {
|
|
13
|
+
readonly availableAt: number;
|
|
14
|
+
readonly failure: StoredFailure;
|
|
15
|
+
}
|
|
16
|
+
export interface DeadLetterParams extends OwnedParams {
|
|
17
|
+
readonly failure: StoredFailure;
|
|
18
|
+
}
|
|
19
|
+
/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */
|
|
20
|
+
export declare function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined>;
|
|
21
|
+
/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */
|
|
22
|
+
export declare function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean>;
|
|
23
|
+
/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */
|
|
24
|
+
export declare function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean>;
|
|
25
|
+
/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */
|
|
26
|
+
export declare function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean>;
|
|
27
|
+
/** Move a running job to dead-letter. Returns false if stale. */
|
|
28
|
+
export declare function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean>;
|
|
29
|
+
/** Renew a lease for the current owner. Returns false if stale. */
|
|
30
|
+
export declare function renewLeaseJob(tx: StoreTx, params: OwnedParams & {
|
|
31
|
+
leaseExpiresAt: number;
|
|
32
|
+
}): Promise<boolean>;
|
|
33
|
+
/** Retry a dead-letter job back to queued. Returns discriminated result. */
|
|
34
|
+
export declare function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult>;
|
|
35
|
+
/** Remove a queued or dead-letter job. Running jobs cannot be removed. */
|
|
36
|
+
export declare function removeJob(tx: StoreTx, id: string): Promise<RemoveResult>;
|
|
37
|
+
/** Enqueue a new job. Rejects duplicate ids. */
|
|
38
|
+
export declare function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void>;
|
|
39
|
+
export declare function toEntry(entry: StoredJob): PostmasterEntry;
|
|
40
|
+
//# sourceMappingURL=store-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-ops.d.ts","sourceRoot":"","sources":["../src/store-ops.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEhH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAED,0FAA0F;AAC1F,wBAAsB,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAc/F;AAED,yFAAyF;AACzF,wBAAsB,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAKpF;AAED,iGAAiG;AACjG,wBAAsB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAWnF;AAED,iGAAiG;AACjG,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3F;AAED,iEAAiE;AACjE,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAY3F;AAED,mEAAmE;AACnE,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAKnH;AAED,4EAA4E;AAC5E,wBAAsB,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAczF;AAED,0EAA0E;AAC1E,wBAAsB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAM9E;AAED,gDAAgD;AAChD,wBAAsB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7E;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,eAAe,CAazD"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region src/store-ops.ts
|
|
2
|
+
async function e(e, t) {
|
|
3
|
+
let n = await e.findClaimable(t.now);
|
|
4
|
+
if (!n) return;
|
|
5
|
+
let r = {
|
|
6
|
+
...n,
|
|
7
|
+
attempts: n.attempts + 1,
|
|
8
|
+
failure: void 0,
|
|
9
|
+
leaseExpiresAt: t.now + t.leaseDuration,
|
|
10
|
+
ownerId: t.ownerId,
|
|
11
|
+
status: "running",
|
|
12
|
+
updatedAt: t.now
|
|
13
|
+
};
|
|
14
|
+
return await e.put(r), r;
|
|
15
|
+
}
|
|
16
|
+
async function t(e, t) {
|
|
17
|
+
let n = await e.get(t.id);
|
|
18
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.delete(t.id), !0);
|
|
19
|
+
}
|
|
20
|
+
async function n(e, t) {
|
|
21
|
+
let n = await e.get(t.id);
|
|
22
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
23
|
+
...n,
|
|
24
|
+
leaseExpiresAt: void 0,
|
|
25
|
+
ownerId: void 0,
|
|
26
|
+
status: "queued",
|
|
27
|
+
updatedAt: t.now
|
|
28
|
+
}), !0);
|
|
29
|
+
}
|
|
30
|
+
async function r(e, t) {
|
|
31
|
+
let n = await e.get(t.id);
|
|
32
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
33
|
+
...n,
|
|
34
|
+
availableAt: t.availableAt,
|
|
35
|
+
failure: t.failure,
|
|
36
|
+
leaseExpiresAt: void 0,
|
|
37
|
+
ownerId: void 0,
|
|
38
|
+
status: "queued",
|
|
39
|
+
updatedAt: t.now
|
|
40
|
+
}), !0);
|
|
41
|
+
}
|
|
42
|
+
async function i(e, t) {
|
|
43
|
+
let n = await e.get(t.id);
|
|
44
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
45
|
+
...n,
|
|
46
|
+
failure: t.failure,
|
|
47
|
+
leaseExpiresAt: void 0,
|
|
48
|
+
ownerId: void 0,
|
|
49
|
+
status: "dead-letter",
|
|
50
|
+
updatedAt: t.now
|
|
51
|
+
}), !0);
|
|
52
|
+
}
|
|
53
|
+
async function a(e, t) {
|
|
54
|
+
let n = await e.get(t.id);
|
|
55
|
+
return !n || n.status !== "running" || n.ownerId !== t.ownerId ? !1 : (await e.put({
|
|
56
|
+
...n,
|
|
57
|
+
leaseExpiresAt: t.leaseExpiresAt,
|
|
58
|
+
updatedAt: t.now
|
|
59
|
+
}), !0);
|
|
60
|
+
}
|
|
61
|
+
async function o(e, t, n) {
|
|
62
|
+
let r = await e.get(t);
|
|
63
|
+
if (!r) return { status: "not-found" };
|
|
64
|
+
if (r.status === "running") return { status: "running" };
|
|
65
|
+
if (r.status !== "dead-letter") return { status: "not-dead-letter" };
|
|
66
|
+
let i = {
|
|
67
|
+
...r,
|
|
68
|
+
availableAt: n,
|
|
69
|
+
failure: void 0,
|
|
70
|
+
status: "queued",
|
|
71
|
+
updatedAt: n
|
|
72
|
+
};
|
|
73
|
+
return await e.put(i), {
|
|
74
|
+
entry: l(i),
|
|
75
|
+
status: "retried"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function s(e, t) {
|
|
79
|
+
let n = await e.get(t);
|
|
80
|
+
return n ? n.status === "running" ? { status: "running" } : (await e.delete(t), {
|
|
81
|
+
id: t,
|
|
82
|
+
status: "removed"
|
|
83
|
+
}) : { status: "not-found" };
|
|
84
|
+
}
|
|
85
|
+
async function c(e, t) {
|
|
86
|
+
if (await e.get(t.id)) throw Error(`Postmaster entry "${t.id}" already exists`);
|
|
87
|
+
await e.put(t);
|
|
88
|
+
}
|
|
89
|
+
function l(e) {
|
|
90
|
+
return {
|
|
91
|
+
attempts: e.attempts,
|
|
92
|
+
availableAt: e.availableAt,
|
|
93
|
+
createdAt: e.createdAt,
|
|
94
|
+
...e.failure ? { failure: e.failure } : {},
|
|
95
|
+
id: e.id,
|
|
96
|
+
key: e.key,
|
|
97
|
+
name: e.name,
|
|
98
|
+
status: e.status,
|
|
99
|
+
updatedAt: e.updatedAt,
|
|
100
|
+
version: e.version
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { e as claimJob, t as completeJob, i as deadLetterJob, c as enqueueJob, n as releaseJob, s as removeJob, a as renewLeaseJob, r as rescheduleJob, o as retryJob, l as toEntry };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=store-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-ops.js","names":[],"sources":["../src/store-ops.ts"],"sourcesContent":["import type { PostmasterEntry, RemoveResult, RetryResult, StoredFailure, StoredJob, StoreTx } from './types.ts';\n\nexport interface ClaimParams {\n readonly leaseDuration: number;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface OwnedParams {\n readonly id: string;\n readonly now: number;\n readonly ownerId: string;\n}\n\nexport interface RescheduleParams extends OwnedParams {\n readonly availableAt: number;\n readonly failure: StoredFailure;\n}\n\nexport interface DeadLetterParams extends OwnedParams {\n readonly failure: StoredFailure;\n}\n\n/** Claim the earliest eligible job atomically. Returns the claimed entry or undefined. */\nexport async function claimJob(tx: StoreTx, params: ClaimParams): Promise<StoredJob | undefined> {\n const entry = await tx.findClaimable(params.now);\n if (!entry) return undefined;\n const claimed: StoredJob = {\n ...entry,\n attempts: entry.attempts + 1,\n failure: undefined,\n leaseExpiresAt: params.now + params.leaseDuration,\n ownerId: params.ownerId,\n status: 'running',\n updatedAt: params.now,\n };\n await tx.put(claimed);\n return claimed;\n}\n\n/** Complete and delete a job. Returns false if the owner is stale or the job is gone. */\nexport async function completeJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.delete(params.id);\n return true;\n}\n\n/** Release a claimed job back to queued without consuming an attempt. Returns false if stale. */\nexport async function releaseJob(tx: StoreTx, params: OwnedParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Reschedule a running job back to queued with a future availableAt. Returns false if stale. */\nexport async function rescheduleJob(tx: StoreTx, params: RescheduleParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n availableAt: params.availableAt,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'queued',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Move a running job to dead-letter. Returns false if stale. */\nexport async function deadLetterJob(tx: StoreTx, params: DeadLetterParams): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({\n ...entry,\n failure: params.failure,\n leaseExpiresAt: undefined,\n ownerId: undefined,\n status: 'dead-letter',\n updatedAt: params.now,\n });\n return true;\n}\n\n/** Renew a lease for the current owner. Returns false if stale. */\nexport async function renewLeaseJob(tx: StoreTx, params: OwnedParams & { leaseExpiresAt: number }): Promise<boolean> {\n const entry = await tx.get(params.id);\n if (!entry || entry.status !== 'running' || entry.ownerId !== params.ownerId) return false;\n await tx.put({ ...entry, leaseExpiresAt: params.leaseExpiresAt, updatedAt: params.now });\n return true;\n}\n\n/** Retry a dead-letter job back to queued. Returns discriminated result. */\nexport async function retryJob(tx: StoreTx, id: string, now: number): Promise<RetryResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n if (entry.status !== 'dead-letter') return { status: 'not-dead-letter' };\n const retried: StoredJob = {\n ...entry,\n availableAt: now,\n failure: undefined,\n status: 'queued',\n updatedAt: now,\n };\n await tx.put(retried);\n return { entry: toEntry(retried), status: 'retried' };\n}\n\n/** Remove a queued or dead-letter job. Running jobs cannot be removed. */\nexport async function removeJob(tx: StoreTx, id: string): Promise<RemoveResult> {\n const entry = await tx.get(id);\n if (!entry) return { status: 'not-found' };\n if (entry.status === 'running') return { status: 'running' };\n await tx.delete(id);\n return { id, status: 'removed' };\n}\n\n/** Enqueue a new job. Rejects duplicate ids. */\nexport async function enqueueJob(tx: StoreTx, entry: StoredJob): Promise<void> {\n const existing = await tx.get(entry.id);\n if (existing) throw new Error(`Postmaster entry \"${entry.id}\" already exists`);\n await tx.put(entry);\n}\n\nexport function toEntry(entry: StoredJob): PostmasterEntry {\n return {\n attempts: entry.attempts,\n availableAt: entry.availableAt,\n createdAt: entry.createdAt,\n ...(entry.failure ? { failure: entry.failure } : {}),\n id: entry.id,\n key: entry.key,\n name: entry.name,\n status: entry.status,\n updatedAt: entry.updatedAt,\n version: entry.version,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){let{failure:t,leaseExpiresAt:n,ownerId:r,...i}=e;return{...i,...t===void 0?{}:{failure:t},...n===void 0?{}:{leaseExpiresAt:n},...r===void 0?{}:{ownerId:r}}}function t(t=[]){let n=new Map(t.map(t=>[t.id,e(t)])),r=new AbortController,i=new Set,a=!1,o=Promise.resolve(),s=e=>{let t=o.then(e,e);return o=t.then(()=>void 0,()=>void 0),t},c=()=>{for(let e of i)e()},l=()=>{if(a)throw Error(`Postmaster store is disposed`)},u={async countByStatus(){let e=0,t=0,r=0;for(let i of n.values())i.status===`dead-letter`?e+=1:i.status===`queued`?t+=1:r+=1;return{deadLetter:e,queued:t,running:r}},async delete(e){n.delete(e)},async findClaimable(t){let r;for(let e of n.values())(e.status===`queued`&&e.availableAt<=t||e.status===`running`&&(e.leaseExpiresAt??0)<=t)&&(!r||e.availableAt<r.availableAt||e.availableAt===r.availableAt&&e.createdAt<r.createdAt)&&(r=e);return r?e(r):void 0},async findNextWake(e){let t;for(let r of n.values()){let n;r.status===`queued`&&r.availableAt>e?n=r.availableAt:r.status===`running`&&(r.leaseExpiresAt??0)>e&&(n=r.leaseExpiresAt),n!==void 0&&(t===void 0||n<t)&&(t=n)}return t},async get(t){let r=n.get(t);return r?e(r):void 0},async put(t){n.set(t.id,e(t))}};return{get disposalSignal(){return r.signal},async dispose(){a||(a=!0,r.abort(),i.clear())},get disposed(){return a},async list(t){return s(()=>(l(),[...n.values()].filter(e=>t?.status===void 0||e.status===t.status).sort((e,t)=>e.createdAt-t.createdAt).map(e)))},subscribe(e){return l(),i.add(e),()=>i.delete(e)},transact(e){return s(async()=>{l();let t=await e(u);return c(),t})},async[Symbol.asyncDispose](){await this.dispose()}}}exports.createMemoryPostmasterStore=t;
|
|
2
|
+
//# sourceMappingURL=memory-store.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.cjs","names":[],"sources":["../../src/testing/memory-store.ts"],"sourcesContent":["import type { EntryFilter, PostmasterStats, PostmasterStore, StoredJob, StoreTx } from '../types.ts';\n\nfunction normalize(entry: StoredJob): StoredJob {\n const { failure, leaseExpiresAt, ownerId, ...rest } = entry;\n return {\n ...rest,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nexport function createMemoryPostmasterStore(entries: readonly StoredJob[] = []): PostmasterStore {\n const records = new Map(entries.map((entry) => [entry.id, normalize(entry)]));\n const controller = new AbortController();\n const listeners = new Set<() => void>();\n let disposed = false;\n let chain = Promise.resolve();\n\n const exclusive = <T>(fn: () => T | Promise<T>): Promise<T> => {\n const result = chain.then(fn, fn);\n chain = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n const notify = (): void => {\n for (const listener of listeners) listener();\n };\n\n const live = (): void => {\n if (disposed) throw new Error('Postmaster store is disposed');\n };\n\n const tx: StoreTx = {\n async countByStatus(): Promise<PostmasterStats> {\n let deadLetter = 0;\n let queued = 0;\n let running = 0;\n for (const entry of records.values()) {\n if (entry.status === 'dead-letter') deadLetter += 1;\n else if (entry.status === 'queued') queued += 1;\n else running += 1;\n }\n return { deadLetter, queued, running };\n },\n async delete(id: string): Promise<void> {\n records.delete(id);\n },\n async findClaimable(now: number): Promise<StoredJob | undefined> {\n let best: StoredJob | undefined;\n for (const entry of records.values()) {\n const claimable =\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now);\n if (!claimable) continue;\n if (\n !best ||\n entry.availableAt < best.availableAt ||\n (entry.availableAt === best.availableAt && entry.createdAt < best.createdAt)\n ) {\n best = entry;\n }\n }\n return best ? normalize(best) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n let next: number | undefined;\n for (const entry of records.values()) {\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 = records.get(id);\n return entry ? normalize(entry) : undefined;\n },\n async put(entry: StoredJob): Promise<void> {\n records.set(entry.id, normalize(entry));\n },\n };\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n async dispose(): Promise<void> {\n if (disposed) return;\n disposed = true;\n controller.abort();\n listeners.clear();\n },\n get disposed() {\n return disposed;\n },\n async list(filter?: EntryFilter): Promise<StoredJob[]> {\n return exclusive(() => {\n live();\n return [...records.values()]\n .filter((entry) => filter?.status === undefined || entry.status === filter.status)\n .sort((a, b) => a.createdAt - b.createdAt)\n .map(normalize);\n });\n },\n subscribe(listener: () => void): () => void {\n live();\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n transact<T>(fn: (tx: StoreTx) => Promise<T>): Promise<T> {\n return exclusive(async () => {\n live();\n const result = await fn(tx);\n notify();\n return result;\n });\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/testing/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,eAAe,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAYrG,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,SAAS,SAAS,EAAO,GAAG,eAAe,CAkH/F"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//#region src/testing/memory-store.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
let { failure: t, leaseExpiresAt: n, ownerId: r, ...i } = e;
|
|
4
|
+
return {
|
|
5
|
+
...i,
|
|
6
|
+
...t === void 0 ? {} : { failure: t },
|
|
7
|
+
...n === void 0 ? {} : { leaseExpiresAt: n },
|
|
8
|
+
...r === void 0 ? {} : { ownerId: r }
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function t(t = []) {
|
|
12
|
+
let n = new Map(t.map((t) => [t.id, e(t)])), r = new AbortController(), i = /* @__PURE__ */ new Set(), a = !1, o = Promise.resolve(), s = (e) => {
|
|
13
|
+
let t = o.then(e, e);
|
|
14
|
+
return o = t.then(() => void 0, () => void 0), t;
|
|
15
|
+
}, c = () => {
|
|
16
|
+
for (let e of i) e();
|
|
17
|
+
}, l = () => {
|
|
18
|
+
if (a) throw Error("Postmaster store is disposed");
|
|
19
|
+
}, u = {
|
|
20
|
+
async countByStatus() {
|
|
21
|
+
let e = 0, t = 0, r = 0;
|
|
22
|
+
for (let i of n.values()) i.status === "dead-letter" ? e += 1 : i.status === "queued" ? t += 1 : r += 1;
|
|
23
|
+
return {
|
|
24
|
+
deadLetter: e,
|
|
25
|
+
queued: t,
|
|
26
|
+
running: r
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
async delete(e) {
|
|
30
|
+
n.delete(e);
|
|
31
|
+
},
|
|
32
|
+
async findClaimable(t) {
|
|
33
|
+
let r;
|
|
34
|
+
for (let e of n.values()) (e.status === "queued" && e.availableAt <= t || e.status === "running" && (e.leaseExpiresAt ?? 0) <= t) && (!r || e.availableAt < r.availableAt || e.availableAt === r.availableAt && e.createdAt < r.createdAt) && (r = e);
|
|
35
|
+
return r ? e(r) : void 0;
|
|
36
|
+
},
|
|
37
|
+
async findNextWake(e) {
|
|
38
|
+
let t;
|
|
39
|
+
for (let r of n.values()) {
|
|
40
|
+
let n;
|
|
41
|
+
r.status === "queued" && r.availableAt > e ? n = r.availableAt : r.status === "running" && (r.leaseExpiresAt ?? 0) > e && (n = r.leaseExpiresAt), n !== void 0 && (t === void 0 || n < t) && (t = n);
|
|
42
|
+
}
|
|
43
|
+
return t;
|
|
44
|
+
},
|
|
45
|
+
async get(t) {
|
|
46
|
+
let r = n.get(t);
|
|
47
|
+
return r ? e(r) : void 0;
|
|
48
|
+
},
|
|
49
|
+
async put(t) {
|
|
50
|
+
n.set(t.id, e(t));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
get disposalSignal() {
|
|
55
|
+
return r.signal;
|
|
56
|
+
},
|
|
57
|
+
async dispose() {
|
|
58
|
+
a || (a = !0, r.abort(), i.clear());
|
|
59
|
+
},
|
|
60
|
+
get disposed() {
|
|
61
|
+
return a;
|
|
62
|
+
},
|
|
63
|
+
async list(t) {
|
|
64
|
+
return s(() => (l(), [...n.values()].filter((e) => t?.status === void 0 || e.status === t.status).sort((e, t) => e.createdAt - t.createdAt).map(e)));
|
|
65
|
+
},
|
|
66
|
+
subscribe(e) {
|
|
67
|
+
return l(), i.add(e), () => i.delete(e);
|
|
68
|
+
},
|
|
69
|
+
transact(e) {
|
|
70
|
+
return s(async () => {
|
|
71
|
+
l();
|
|
72
|
+
let t = await e(u);
|
|
73
|
+
return c(), t;
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
async [Symbol.asyncDispose]() {
|
|
77
|
+
await this.dispose();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { t as createMemoryPostmasterStore };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=memory-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.js","names":[],"sources":["../../src/testing/memory-store.ts"],"sourcesContent":["import type { EntryFilter, PostmasterStats, PostmasterStore, StoredJob, StoreTx } from '../types.ts';\n\nfunction normalize(entry: StoredJob): StoredJob {\n const { failure, leaseExpiresAt, ownerId, ...rest } = entry;\n return {\n ...rest,\n ...(failure !== undefined ? { failure } : {}),\n ...(leaseExpiresAt !== undefined ? { leaseExpiresAt } : {}),\n ...(ownerId !== undefined ? { ownerId } : {}),\n };\n}\n\nexport function createMemoryPostmasterStore(entries: readonly StoredJob[] = []): PostmasterStore {\n const records = new Map(entries.map((entry) => [entry.id, normalize(entry)]));\n const controller = new AbortController();\n const listeners = new Set<() => void>();\n let disposed = false;\n let chain = Promise.resolve();\n\n const exclusive = <T>(fn: () => T | Promise<T>): Promise<T> => {\n const result = chain.then(fn, fn);\n chain = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n const notify = (): void => {\n for (const listener of listeners) listener();\n };\n\n const live = (): void => {\n if (disposed) throw new Error('Postmaster store is disposed');\n };\n\n const tx: StoreTx = {\n async countByStatus(): Promise<PostmasterStats> {\n let deadLetter = 0;\n let queued = 0;\n let running = 0;\n for (const entry of records.values()) {\n if (entry.status === 'dead-letter') deadLetter += 1;\n else if (entry.status === 'queued') queued += 1;\n else running += 1;\n }\n return { deadLetter, queued, running };\n },\n async delete(id: string): Promise<void> {\n records.delete(id);\n },\n async findClaimable(now: number): Promise<StoredJob | undefined> {\n let best: StoredJob | undefined;\n for (const entry of records.values()) {\n const claimable =\n (entry.status === 'queued' && entry.availableAt <= now) ||\n (entry.status === 'running' && (entry.leaseExpiresAt ?? 0) <= now);\n if (!claimable) continue;\n if (\n !best ||\n entry.availableAt < best.availableAt ||\n (entry.availableAt === best.availableAt && entry.createdAt < best.createdAt)\n ) {\n best = entry;\n }\n }\n return best ? normalize(best) : undefined;\n },\n async findNextWake(now: number): Promise<number | undefined> {\n let next: number | undefined;\n for (const entry of records.values()) {\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 = records.get(id);\n return entry ? normalize(entry) : undefined;\n },\n async put(entry: StoredJob): Promise<void> {\n records.set(entry.id, normalize(entry));\n },\n };\n\n return {\n get disposalSignal() {\n return controller.signal;\n },\n async dispose(): Promise<void> {\n if (disposed) return;\n disposed = true;\n controller.abort();\n listeners.clear();\n },\n get disposed() {\n return disposed;\n },\n async list(filter?: EntryFilter): Promise<StoredJob[]> {\n return exclusive(() => {\n live();\n return [...records.values()]\n .filter((entry) => filter?.status === undefined || entry.status === filter.status)\n .sort((a, b) => a.createdAt - b.createdAt)\n .map(normalize);\n });\n },\n subscribe(listener: () => void): () => void {\n live();\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n transact<T>(fn: (tx: StoreTx) => Promise<T>): Promise<T> {\n return exclusive(async () => {\n live();\n const result = await fn(tx);\n notify();\n return result;\n });\n },\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./testing/memory-store.cjs");exports.createMemoryPostmasterStore=e.createMemoryPostmasterStore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/testing.js
ADDED