nuxt-cf-jobs 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +331 -0
  2. package/dist/module.d.mts +73 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +380 -0
  5. package/dist/runtime/server/app.d.ts +80 -0
  6. package/dist/runtime/server/app.js +81 -0
  7. package/dist/runtime/server/d1.d.ts +119 -0
  8. package/dist/runtime/server/d1.js +259 -0
  9. package/dist/runtime/server/dev.d.ts +39 -0
  10. package/dist/runtime/server/dev.js +93 -0
  11. package/dist/runtime/server/dispatch.d.ts +25 -0
  12. package/dist/runtime/server/dispatch.js +66 -0
  13. package/dist/runtime/server/index.d.ts +12 -0
  14. package/dist/runtime/server/index.js +12 -0
  15. package/dist/runtime/server/outbox.d.ts +220 -0
  16. package/dist/runtime/server/outbox.js +246 -0
  17. package/dist/runtime/server/payload.d.ts +3 -0
  18. package/dist/runtime/server/payload.js +3 -0
  19. package/dist/runtime/server/plugins/dev-queues.d.ts +2 -0
  20. package/dist/runtime/server/plugins/dev-queues.js +25 -0
  21. package/dist/runtime/server/policy.d.ts +10 -0
  22. package/dist/runtime/server/policy.js +49 -0
  23. package/dist/runtime/server/queue.d.ts +211 -0
  24. package/dist/runtime/server/queue.js +495 -0
  25. package/dist/runtime/server/registry.d.ts +79 -0
  26. package/dist/runtime/server/registry.js +82 -0
  27. package/dist/runtime/server/schema.d.ts +965 -0
  28. package/dist/runtime/server/schema.js +80 -0
  29. package/dist/runtime/server/testing.d.ts +34 -0
  30. package/dist/runtime/server/testing.js +61 -0
  31. package/dist/runtime/server/types.d.ts +123 -0
  32. package/dist/runtime/server/types.js +0 -0
  33. package/dist/types.d.mts +3 -0
  34. package/package.json +109 -0
@@ -0,0 +1,259 @@
1
+ export const d1DurableJobMigrationSql = [
2
+ "CREATE TABLE IF NOT EXISTS job_batches (id text PRIMARY KEY, name text, parent_batch_id text, total_jobs integer NOT NULL DEFAULT 0, pending_jobs integer NOT NULL DEFAULT 0, failed_jobs integer NOT NULL DEFAULT 0, on_finish text, handler text, allow_failures integer DEFAULT 0, site_id text, user_id integer, created_at integer NOT NULL DEFAULT (unixepoch()), updated_at integer NOT NULL DEFAULT (unixepoch()), finished_at integer)",
3
+ "CREATE TABLE IF NOT EXISTS jobs (id text PRIMARY KEY, queue text NOT NULL, job_type text NOT NULL, batch_id text REFERENCES job_batches(id), user_id integer, site_id text, partner_id text, trace_id text, unique_key text, payload text NOT NULL, attempts integer DEFAULT 0, max_attempts integer DEFAULT 3, reserved_at integer, available_at integer NOT NULL, created_at integer NOT NULL DEFAULT (unixepoch()), completed_at integer, failed_at integer, last_error text, retry_reasons text, rows_fetched integer, rows_inserted integer, d1_rows_read integer, d1_rows_written integer, duration_ms integer)",
4
+ "CREATE TABLE IF NOT EXISTS failed_jobs (id text PRIMARY KEY, queue text NOT NULL, job_type text NOT NULL, batch_id text, user_id integer, site_id text, partner_id text, trace_id text, unique_key text, payload text NOT NULL, exception text NOT NULL, attempts integer NOT NULL, max_attempts integer NOT NULL, failed_at integer NOT NULL)",
5
+ "CREATE INDEX IF NOT EXISTS idx_job_batches_site ON job_batches (site_id)",
6
+ "CREATE INDEX IF NOT EXISTS idx_job_batches_pending ON job_batches (pending_jobs)",
7
+ "CREATE INDEX IF NOT EXISTS idx_job_batches_parent ON job_batches (parent_batch_id)",
8
+ "CREATE INDEX IF NOT EXISTS idx_job_batches_finished_at ON job_batches (finished_at)",
9
+ "CREATE INDEX IF NOT EXISTS idx_jobs_claimable ON jobs (queue, reserved_at, available_at)",
10
+ "CREATE INDEX IF NOT EXISTS idx_jobs_user ON jobs (user_id)",
11
+ "CREATE INDEX IF NOT EXISTS idx_jobs_site ON jobs (site_id)",
12
+ "CREATE INDEX IF NOT EXISTS idx_jobs_partner ON jobs (partner_id)",
13
+ "CREATE INDEX IF NOT EXISTS idx_jobs_type ON jobs (job_type)",
14
+ "CREATE INDEX IF NOT EXISTS idx_jobs_batch ON jobs (batch_id)",
15
+ "CREATE INDEX IF NOT EXISTS idx_jobs_trace ON jobs (trace_id)",
16
+ "CREATE INDEX IF NOT EXISTS idx_jobs_sync_dedup ON jobs (site_id, job_type)",
17
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_jobs_unique_active ON jobs (unique_key) WHERE unique_key IS NOT NULL AND completed_at IS NULL AND failed_at IS NULL",
18
+ "CREATE INDEX IF NOT EXISTS idx_failed_jobs_queue ON failed_jobs (queue)",
19
+ "CREATE INDEX IF NOT EXISTS idx_failed_jobs_site ON failed_jobs (site_id)",
20
+ "CREATE INDEX IF NOT EXISTS idx_failed_jobs_trace ON failed_jobs (trace_id)",
21
+ "CREATE INDEX IF NOT EXISTS idx_failed_jobs_failed_at ON failed_jobs (failed_at)"
22
+ ];
23
+ export function createD1DurableJobRepository(db, opts = {}) {
24
+ const jobsTable = opts.jobsTable ?? "jobs";
25
+ const failedJobsTable = opts.failedJobsTable ?? "failed_jobs";
26
+ const insertJobSql = `
27
+ INSERT OR IGNORE INTO ${jobsTable} (
28
+ id, queue, job_type, batch_id, user_id, site_id, partner_id, trace_id, unique_key, payload,
29
+ attempts, max_attempts, available_at, created_at
30
+ )
31
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
+ `;
33
+ function bindInsertJob(record) {
34
+ return db.prepare(insertJobSql).bind(
35
+ record.id,
36
+ record.queue,
37
+ record.jobType,
38
+ record.batchId ?? null,
39
+ record.userId ?? null,
40
+ record.siteId ?? null,
41
+ record.partnerId ?? null,
42
+ record.traceId,
43
+ record.uniqueKey ?? null,
44
+ record.payload,
45
+ record.attempts,
46
+ record.maxAttempts,
47
+ record.availableAt,
48
+ record.createdAt
49
+ );
50
+ }
51
+ return {
52
+ async migrate() {
53
+ for (const statement of d1DurableJobMigrationSql)
54
+ await db.exec(statement);
55
+ },
56
+ async insertJob(record) {
57
+ const result = await bindInsertJob(record).run();
58
+ return typeof result.meta?.changes === "number" ? result.meta.changes > 0 : result.success === true;
59
+ },
60
+ async insertJobs(records, insertOpts) {
61
+ if (records.length === 0)
62
+ return { inserted: [], chunks: [] };
63
+ const batchSize = Math.max(1, Math.min(insertOpts?.batchSize ?? 90, 100));
64
+ const chunks = [];
65
+ const inserted = [];
66
+ for (let i = 0; i < records.length; i += batchSize) {
67
+ const slice = records.slice(i, i + batchSize);
68
+ const stmts = slice.map(bindInsertJob);
69
+ try {
70
+ const results = typeof db.batch === "function" ? await db.batch(stmts) : await Promise.all(stmts.map((s) => s.run()));
71
+ const changes = results.reduce((sum, r) => sum + (r.meta?.changes ?? (r.success ? 1 : 0)), 0);
72
+ chunks.push({ ok: true, ids: slice.map((r) => r.id), changes });
73
+ if (changes === slice.length)
74
+ inserted.push(...slice);
75
+ else if (changes > 0 && typeof db.batch !== "function") {
76
+ for (let j = 0; j < slice.length; j++)
77
+ if ((results[j]?.meta?.changes ?? 0) > 0)
78
+ inserted.push(slice[j]);
79
+ }
80
+ } catch (error) {
81
+ chunks.push({ ok: false, ids: slice.map((r) => r.id), changes: 0, error });
82
+ }
83
+ }
84
+ if (chunks.length > 0 && chunks.every((c) => !c.ok))
85
+ throw chunks[0].error ?? new Error("All insertJobs chunks failed");
86
+ return { inserted, chunks };
87
+ },
88
+ async claimJob(id) {
89
+ const now = currentUnixSeconds();
90
+ const job = await db.prepare(`
91
+ UPDATE ${jobsTable}
92
+ SET reserved_at = ?, attempts = attempts + 1
93
+ WHERE id = ?
94
+ AND reserved_at IS NULL
95
+ AND available_at <= ?
96
+ AND completed_at IS NULL
97
+ AND failed_at IS NULL
98
+ RETURNING *
99
+ `).bind(now, id, now).first();
100
+ if (job)
101
+ fireHook(() => opts.onJobClaimed?.({ job }));
102
+ return job;
103
+ },
104
+ async resolveClaimMiss(id) {
105
+ const job = await db.prepare(`
106
+ SELECT reserved_at, completed_at, failed_at
107
+ FROM ${jobsTable}
108
+ WHERE id = ?
109
+ `).bind(id).first();
110
+ if (!job)
111
+ return "not-found";
112
+ if (job.completed_at || job.failed_at)
113
+ return "already-resolved";
114
+ return "in-flight";
115
+ },
116
+ async completeJob(job, result) {
117
+ const durationMs = result && typeof result === "object" && "durationMs" in result && typeof result.durationMs === "number" ? result.durationMs : null;
118
+ await db.prepare(`
119
+ UPDATE ${jobsTable}
120
+ SET completed_at = unixepoch(), reserved_at = NULL, duration_ms = COALESCE(?, duration_ms)
121
+ WHERE id = ?
122
+ `).bind(durationMs, job.id).run();
123
+ fireHook(() => opts.onJobCompleted?.({ job, durationMs, result }));
124
+ },
125
+ async failJob(job, error) {
126
+ await db.prepare(`
127
+ INSERT OR REPLACE INTO ${failedJobsTable} (
128
+ id, queue, job_type, batch_id, user_id, site_id, partner_id, trace_id, unique_key, payload,
129
+ exception, attempts, max_attempts, failed_at
130
+ )
131
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch())
132
+ `).bind(
133
+ job.id,
134
+ job.queue,
135
+ job.job_type,
136
+ job.batch_id,
137
+ job.user_id,
138
+ job.site_id,
139
+ job.partner_id,
140
+ job.trace_id,
141
+ job.unique_key,
142
+ job.payload,
143
+ error,
144
+ job.attempts,
145
+ job.max_attempts
146
+ ).run();
147
+ await db.prepare(`DELETE FROM ${jobsTable} WHERE id = ?`).bind(job.id).run();
148
+ fireHook(() => opts.onJobFailed?.({ job, error }));
149
+ },
150
+ async releaseJob(job, releaseOpts) {
151
+ await db.prepare(`
152
+ UPDATE ${jobsTable}
153
+ SET reserved_at = NULL, available_at = ?, last_error = COALESCE(?, last_error)
154
+ WHERE id = ?
155
+ `).bind(resolveAvailableAt(releaseOpts), releaseOpts?.error ?? null, job.id).run();
156
+ fireHook(() => opts.onJobReleased?.({ job, opts: releaseOpts }));
157
+ },
158
+ async recordFailure(input) {
159
+ await db.prepare(`
160
+ INSERT OR REPLACE INTO ${failedJobsTable} (
161
+ id, queue, job_type, batch_id, user_id, site_id, partner_id, trace_id, unique_key, payload,
162
+ exception, attempts, max_attempts, failed_at
163
+ )
164
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch())
165
+ `).bind(
166
+ input.id ?? crypto.randomUUID(),
167
+ input.queue,
168
+ input.jobType,
169
+ input.batchId ?? null,
170
+ input.userId ?? null,
171
+ input.siteId ?? null,
172
+ input.partnerId ?? null,
173
+ input.traceId ?? null,
174
+ input.uniqueKey ?? null,
175
+ input.payload,
176
+ input.exception,
177
+ input.attempts,
178
+ input.maxAttempts ?? input.attempts
179
+ ).run();
180
+ },
181
+ async findDispatchableJobs(query = {}) {
182
+ return await all(db.prepare(`
183
+ SELECT *
184
+ FROM ${jobsTable}
185
+ WHERE reserved_at IS NULL
186
+ AND available_at <= ?
187
+ AND completed_at IS NULL
188
+ AND failed_at IS NULL
189
+ ORDER BY available_at ASC
190
+ LIMIT ?
191
+ `).bind(query.now ?? currentUnixSeconds(), query.limit ?? 100));
192
+ },
193
+ async findStaleReservedJobs(query) {
194
+ return await all(db.prepare(`
195
+ SELECT *
196
+ FROM ${jobsTable}
197
+ WHERE reserved_at IS NOT NULL
198
+ AND reserved_at <= ?
199
+ AND completed_at IS NULL
200
+ AND failed_at IS NULL
201
+ ORDER BY reserved_at ASC
202
+ LIMIT ?
203
+ `).bind(query.staleBefore, query.limit ?? 100));
204
+ },
205
+ async releaseStaleReservedJobs(query) {
206
+ const result = await db.prepare(`
207
+ UPDATE ${jobsTable}
208
+ SET reserved_at = NULL, available_at = ?, last_error = COALESCE(?, last_error)
209
+ WHERE id IN (
210
+ SELECT id
211
+ FROM ${jobsTable}
212
+ WHERE reserved_at IS NOT NULL
213
+ AND reserved_at <= ?
214
+ AND completed_at IS NULL
215
+ AND failed_at IS NULL
216
+ LIMIT ?
217
+ )
218
+ `).bind(
219
+ query.availableAt ?? query.now ?? currentUnixSeconds(),
220
+ query.error ?? null,
221
+ query.staleBefore,
222
+ query.limit ?? 100
223
+ ).run();
224
+ return result.meta?.changes ?? 0;
225
+ },
226
+ toDispatchableJob(job) {
227
+ return {
228
+ id: job.id,
229
+ queue: job.queue,
230
+ payload: JSON.parse(job.payload),
231
+ attempts: job.attempts,
232
+ batchId: job.batch_id,
233
+ siteId: job.site_id,
234
+ userId: job.user_id
235
+ };
236
+ }
237
+ };
238
+ }
239
+ function currentUnixSeconds() {
240
+ return Math.floor(Date.now() / 1e3);
241
+ }
242
+ function fireHook(fn) {
243
+ try {
244
+ const result = fn();
245
+ if (result && typeof result.then === "function")
246
+ result.catch(() => {
247
+ });
248
+ } catch {
249
+ }
250
+ }
251
+ function resolveAvailableAt(opts) {
252
+ if (typeof opts?.availableAt === "number")
253
+ return opts.availableAt;
254
+ return currentUnixSeconds() + (opts?.delaySeconds ?? 0);
255
+ }
256
+ async function all(statement) {
257
+ const result = await statement.all?.();
258
+ return result?.results ?? [];
259
+ }
@@ -0,0 +1,39 @@
1
+ import type { QueueBindingsConfig } from './types.js';
2
+ export interface DevQueueMessage {
3
+ id?: string;
4
+ body: Record<string, unknown>;
5
+ attempts: number;
6
+ timestamp?: Date | number;
7
+ ack: () => void;
8
+ retry: (opts?: {
9
+ delaySeconds?: number;
10
+ }) => void;
11
+ }
12
+ export interface DevQueueBatch {
13
+ queue: string;
14
+ messages: DevQueueMessage[];
15
+ ackAll: () => void;
16
+ retryAll: (opts?: {
17
+ delaySeconds?: number;
18
+ }) => void;
19
+ }
20
+ export interface DevQueueHookPayload {
21
+ batch: DevQueueBatch;
22
+ env: Record<string, unknown>;
23
+ }
24
+ export interface DevQueueRuntime {
25
+ env: Record<string, unknown>;
26
+ enqueue: (binding: string, body: Record<string, unknown>, opts?: {
27
+ delaySeconds?: number;
28
+ attempts?: number;
29
+ }) => void;
30
+ dispose: () => void;
31
+ }
32
+ export interface DevQueueRuntimeOptions {
33
+ queues: QueueBindingsConfig;
34
+ baseEnv?: Record<string, unknown>;
35
+ onBatch: (payload: DevQueueHookPayload) => Promise<void> | void;
36
+ onError?: (error: unknown) => void;
37
+ maxAttempts?: number;
38
+ }
39
+ export declare function createDevQueueRuntime(opts: DevQueueRuntimeOptions): DevQueueRuntime;
@@ -0,0 +1,93 @@
1
+ import { resolveCloudflareQueueName } from "./queue.js";
2
+ export function createDevQueueRuntime(opts) {
3
+ const env = { ...opts.baseEnv ?? {} };
4
+ const bindingToLogical = /* @__PURE__ */ new Map();
5
+ const timers = /* @__PURE__ */ new Set();
6
+ const maxAttempts = opts.maxAttempts ?? 10;
7
+ let disposed = false;
8
+ for (const [logicalName, config] of Object.entries(opts.queues)) {
9
+ const binding = typeof config === "string" ? config : config?.binding;
10
+ if (!binding)
11
+ continue;
12
+ bindingToLogical.set(binding, logicalName);
13
+ const queue = {
14
+ async send(message, sendOpts) {
15
+ enqueue(binding, message, { delaySeconds: sendOpts?.delaySeconds });
16
+ },
17
+ async sendBatch(messages, sendOpts) {
18
+ for (const message of messages) {
19
+ enqueue(binding, message.body, {
20
+ delaySeconds: message.delaySeconds ?? sendOpts?.delaySeconds
21
+ });
22
+ }
23
+ }
24
+ };
25
+ env[binding] = queue;
26
+ }
27
+ function enqueue(binding, body, eopts) {
28
+ if (disposed)
29
+ return;
30
+ const logical = bindingToLogical.get(binding);
31
+ if (!logical)
32
+ return;
33
+ const cfQueueName = resolveCloudflareQueueName(opts.queues, logical);
34
+ const attempts = (eopts?.attempts ?? 0) + 1;
35
+ const delayMs = Math.max(0, (eopts?.delaySeconds ?? 0) * 1e3);
36
+ const id = eopts?.id ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`);
37
+ const fire = () => {
38
+ let settled = false;
39
+ const message = {
40
+ id,
41
+ body,
42
+ attempts,
43
+ timestamp: /* @__PURE__ */ new Date(),
44
+ ack() {
45
+ settled = true;
46
+ },
47
+ retry(retryOpts) {
48
+ if (settled)
49
+ return;
50
+ settled = true;
51
+ if (attempts >= maxAttempts)
52
+ return;
53
+ enqueue(binding, body, { delaySeconds: retryOpts?.delaySeconds, attempts, id });
54
+ }
55
+ };
56
+ const batch = {
57
+ queue: cfQueueName,
58
+ messages: [message],
59
+ ackAll() {
60
+ settled = true;
61
+ },
62
+ retryAll(retryOpts) {
63
+ if (settled)
64
+ return;
65
+ settled = true;
66
+ if (attempts >= maxAttempts)
67
+ return;
68
+ enqueue(binding, body, { delaySeconds: retryOpts?.delaySeconds, attempts, id });
69
+ }
70
+ };
71
+ Promise.resolve().then(() => opts.onBatch({ batch, env })).catch((error) => opts.onError?.(error));
72
+ };
73
+ if (delayMs === 0) {
74
+ queueMicrotask(fire);
75
+ return;
76
+ }
77
+ const timer = setTimeout(() => {
78
+ timers.delete(timer);
79
+ fire();
80
+ }, delayMs);
81
+ timers.add(timer);
82
+ }
83
+ return {
84
+ env,
85
+ enqueue,
86
+ dispose() {
87
+ disposed = true;
88
+ for (const timer of timers)
89
+ clearTimeout(timer);
90
+ timers.clear();
91
+ }
92
+ };
93
+ }
@@ -0,0 +1,25 @@
1
+ import type { DispatchableJob, DispatchResult, JobContext, JobControlResult, JobDefinition, JobHandler, JobMiddleware } from './types.js';
2
+ export interface JobRegistryLike<Env, Db, Logger> {
3
+ getHandler: (name: string) => JobHandler<unknown, Env, Db, Logger> | undefined;
4
+ getJobDefinition?: (name: string) => JobDefinition<string, unknown, string, Env, Db, Logger> | undefined;
5
+ }
6
+ export interface DispatchContextInput<Job extends DispatchableJob> {
7
+ job: Job;
8
+ taskName: string;
9
+ payload: Record<string, unknown>;
10
+ control: JobControlResult;
11
+ }
12
+ export interface DispatchRegisteredJobOptions<Job extends DispatchableJob, Env, Db, Logger> {
13
+ registry: JobRegistryLike<Env, Db, Logger>;
14
+ job: Job;
15
+ createContext: (input: DispatchContextInput<Job>) => JobContext<Env, Db, Logger> | Promise<JobContext<Env, Db, Logger>>;
16
+ onHandledThrow?: (input: DispatchContextInput<Job> & {
17
+ error: unknown;
18
+ }) => void | Promise<void>;
19
+ onUnhandledThrow?: (input: DispatchContextInput<Job> & {
20
+ error: unknown;
21
+ }) => void | Promise<void>;
22
+ onComplete?: (input: DispatchContextInput<Job>) => void | Promise<void>;
23
+ }
24
+ export declare function dispatchRegisteredJob<Job extends DispatchableJob, Env, Db, Logger>(opts: DispatchRegisteredJobOptions<Job, Env, Db, Logger>): Promise<DispatchResult>;
25
+ export declare function runJobThroughMiddleware<Payload, Env, Db, Logger>(payload: Payload, ctx: JobContext<Env, Db, Logger>, middleware: Array<JobMiddleware<Payload, Env, Db, Logger>>, destination: () => Promise<void>): Promise<void>;
@@ -0,0 +1,66 @@
1
+ import { parseJobInput } from "./registry.js";
2
+ export async function dispatchRegisteredJob(opts) {
3
+ const payload = opts.job.payload;
4
+ const taskName = payload._task;
5
+ if (typeof taskName !== "string" || taskName.length === 0) {
6
+ return { success: false, error: "No _task in payload", handlerNotFound: true };
7
+ }
8
+ const definition = opts.registry.getJobDefinition?.(taskName);
9
+ const handler = definition?.handle ?? opts.registry.getHandler(taskName);
10
+ if (!handler) {
11
+ return { success: false, error: `No handler for task: ${taskName}`, handlerNotFound: true };
12
+ }
13
+ const { _task, _continuations, ...cleanPayload } = payload;
14
+ const parsedPayload = parseJobInput(definition, cleanPayload);
15
+ if (!parsedPayload.success) {
16
+ return {
17
+ success: false,
18
+ error: `Invalid payload for task: ${taskName}`,
19
+ invalidPayload: true,
20
+ validationError: parsedPayload.error
21
+ };
22
+ }
23
+ const control = { handled: false };
24
+ const input = {
25
+ job: opts.job,
26
+ taskName,
27
+ payload: parsedPayload.data,
28
+ control
29
+ };
30
+ const ctx = await opts.createContext(input);
31
+ try {
32
+ await runJobThroughMiddleware(
33
+ parsedPayload.data,
34
+ ctx,
35
+ definition?.middleware ?? [],
36
+ () => handler(parsedPayload.data, ctx)
37
+ );
38
+ } catch (error) {
39
+ if (control.handled) {
40
+ await opts.onHandledThrow?.({ ...input, error });
41
+ return { success: true, control };
42
+ }
43
+ if (definition?.failed) {
44
+ await Promise.resolve(definition.failed(parsedPayload.data, ctx, error)).catch((failedError) => {
45
+ opts.onUnhandledThrow?.({ ...input, error: failedError });
46
+ });
47
+ }
48
+ await opts.onUnhandledThrow?.({ ...input, error });
49
+ throw error;
50
+ }
51
+ await opts.onComplete?.(input);
52
+ return { success: true, control: control.handled ? control : void 0 };
53
+ }
54
+ export async function runJobThroughMiddleware(payload, ctx, middleware, destination) {
55
+ let index = -1;
56
+ async function run(i) {
57
+ if (i <= index)
58
+ throw new Error("Job middleware called next() multiple times");
59
+ index = i;
60
+ const layer = middleware[i];
61
+ if (!layer)
62
+ return destination();
63
+ await layer(payload, ctx, () => run(i + 1));
64
+ }
65
+ await run(0);
66
+ }
@@ -0,0 +1,12 @@
1
+ export * from './app.js';
2
+ export * from './d1.js';
3
+ export * from './dev.js';
4
+ export * from './dispatch.js';
5
+ export * from './outbox.js';
6
+ export * from './payload.js';
7
+ export * from './policy.js';
8
+ export * from './queue.js';
9
+ export * from './registry.js';
10
+ export * from './schema.js';
11
+ export * from './testing.js';
12
+ export * from './types.js';
@@ -0,0 +1,12 @@
1
+ export * from "./app.js";
2
+ export * from "./d1.js";
3
+ export * from "./dev.js";
4
+ export * from "./dispatch.js";
5
+ export * from "./outbox.js";
6
+ export * from "./payload.js";
7
+ export * from "./policy.js";
8
+ export * from "./queue.js";
9
+ export * from "./registry.js";
10
+ export * from "./schema.js";
11
+ export * from "./testing.js";
12
+ export * from "./types.js";