nuxt-cf-jobs 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-cf-jobs",
3
3
  "configKey": "cfJobs",
4
- "version": "0.12.2",
4
+ "version": "0.12.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -87,10 +87,17 @@ export interface D1DurableJobRepositoryOptions<Queue extends string = string> {
87
87
  durationMs: number | null;
88
88
  result?: unknown;
89
89
  }) => void;
90
- /** Fire-and-forget hook invoked after `failJob` writes succeed. Errors are swallowed. */
90
+ /**
91
+ * Fire-and-forget hook invoked after `failJob` writes succeed. Errors are swallowed.
92
+ *
93
+ * `error` is the HEADLINE (`"TypeError: <message>"`) — safe for issue titles,
94
+ * realtime payloads and Analytics Engine blobs. `cause` is the ORIGINAL thrown
95
+ * value: report it as-is (`captureException(cause)`) to keep the native stack.
96
+ */
91
97
  onJobFailed?: (input: {
92
98
  job: D1DurableJobRecord<Queue>;
93
99
  error: string;
100
+ cause?: unknown;
94
101
  }) => void;
95
102
  /** Fire-and-forget hook invoked after `releaseJob` writes succeed. Errors are swallowed. */
96
103
  onJobReleased?: (input: {
@@ -149,7 +149,7 @@ export function createD1DurableJobRepository(db, opts = {}) {
149
149
  assertOwnedMutation(resultUpdate, job.id);
150
150
  fireHook(() => opts.onJobCompleted?.({ job, durationMs, result }));
151
151
  },
152
- async failJob(job, error) {
152
+ async failJob(job, error, failOpts) {
153
153
  const insert = await db.prepare(`
154
154
  INSERT OR REPLACE INTO ${failedJobsTable} (
155
155
  id, queue, job_type, batch_id, user_id, site_id, partner_id, trace_id, unique_key, payload,
@@ -180,7 +180,7 @@ export function createD1DurableJobRepository(db, opts = {}) {
180
180
  AND failed_at IS NULL
181
181
  `).bind(job.id, job.reserved_at, job.attempts).run();
182
182
  assertOwnedMutation(deleted, job.id);
183
- fireHook(() => opts.onJobFailed?.({ job, error: headlineOf(error) }));
183
+ fireHook(() => opts.onJobFailed?.({ job, error: headlineOf(error), cause: failOpts?.cause }));
184
184
  },
185
185
  async releaseJob(job, releaseOpts) {
186
186
  const result = await db.prepare(`
@@ -16,8 +16,16 @@ export interface JobMetricsEvent {
16
16
  batchId: string | null;
17
17
  siteId: string | null;
18
18
  userId: number | null;
19
- /** Present for `failed` / `released` (the release reason). */
19
+ /** Present for `failed` / `released` (the release reason). Single-line headline. */
20
20
  error?: string;
21
+ /**
22
+ * Present for `failed`: the ORIGINAL thrown value, not a rendering of it. An
23
+ * error-tracker sink should `captureException(cause)` so it groups on the real
24
+ * throw site and keeps the native stack + `cause` chain, rather than rebuilding a
25
+ * synthetic `new Error(error)`. Dimension-oriented sinks (Analytics Engine) must
26
+ * ignore it — it is an arbitrary object, not a blob.
27
+ */
28
+ cause?: unknown;
21
29
  /** Optional completion result supplied by the consumer. */
22
30
  result?: unknown;
23
31
  rowsFetched?: number;
@@ -21,7 +21,8 @@ function toEvent(job, status, extra) {
21
21
  batchId: job.batch_id,
22
22
  siteId: job.site_id,
23
23
  userId: job.user_id,
24
- error: extra.error
24
+ error: extra.error,
25
+ ...extra.cause !== void 0 ? { cause: extra.cause } : {}
25
26
  };
26
27
  }
27
28
  export function metricsSinkToRepoHooks(sink) {
@@ -32,8 +33,8 @@ export function metricsSinkToRepoHooks(sink) {
32
33
  event.result = result;
33
34
  return sink.record(event);
34
35
  },
35
- onJobFailed({ job, error }) {
36
- return sink.record(toEvent(job, "failed", { durationMs: job.duration_ms ?? null, error }));
36
+ onJobFailed({ job, error, cause }) {
37
+ return sink.record(toEvent(job, "failed", { durationMs: job.duration_ms ?? null, error, cause }));
37
38
  },
38
39
  onJobReleased({ job, opts }) {
39
40
  return sink.record(toEvent(job, "released", { error: opts?.error }));
@@ -97,11 +97,20 @@ export interface ReleaseDurableJobOptions {
97
97
  availableAt?: number;
98
98
  error?: string;
99
99
  }
100
+ /**
101
+ * Well-known `failJob` options, always available regardless of a repository's own
102
+ * `FailOptions` (`unknown & T` is `T`). `cause` is the ORIGINAL thrown value — the
103
+ * `error` string is a rendering for the `failed_jobs` row, but a telemetry hook
104
+ * wants the instance so it can report the real stack instead of rebuilding one.
105
+ */
106
+ export interface FailDurableJobOptions {
107
+ cause?: unknown;
108
+ }
100
109
  export interface DurableJobLifecycle<Job, CompleteResult = void, FailOptions = unknown, ReleaseResult = void> {
101
110
  claimJob: (id: string) => Promise<Job | null>;
102
111
  resolveClaimMiss?: (id: string) => Promise<DurableJobClaimMiss>;
103
112
  completeJob: (job: Job, result?: unknown) => Promise<CompleteResult>;
104
- failJob: (job: Job, error: string, opts?: FailOptions) => Promise<void>;
113
+ failJob: (job: Job, error: string, opts?: FailOptions & FailDurableJobOptions) => Promise<void>;
105
114
  releaseJob?: (job: Job, opts?: ReleaseDurableJobOptions) => Promise<ReleaseResult>;
106
115
  }
107
116
  export interface QueuePublisher<Queue extends string = string, Message extends QueueJobMessage<Queue> = QueueJobMessage<Queue>> {
@@ -228,7 +237,7 @@ export declare function createQueuePublisher<Env extends Record<string, unknown>
228
237
  }): QueuePublisher<Queue, Message>;
229
238
  export declare function claimDurableJob<Job>(lifecycle: Pick<DurableJobLifecycle<Job>, 'claimJob' | 'resolveClaimMiss'>, id: string): Promise<DurableJobClaimResult<Job>>;
230
239
  export declare function completeDurableJob<Job, CompleteResult>(lifecycle: Pick<DurableJobLifecycle<Job, CompleteResult>, 'completeJob'>, job: Job, result?: unknown): Promise<CompleteResult>;
231
- export declare function failDurableJob<Job, FailOptions>(lifecycle: Pick<DurableJobLifecycle<Job, unknown, FailOptions>, 'failJob'>, job: Job, error: string, opts?: FailOptions): Promise<void>;
240
+ export declare function failDurableJob<Job, FailOptions>(lifecycle: Pick<DurableJobLifecycle<Job, unknown, FailOptions>, 'failJob'>, job: Job, error: string, opts?: FailOptions & FailDurableJobOptions): Promise<void>;
232
241
  export declare function releaseDurableJob<Job, ReleaseResult>(lifecycle: Pick<DurableJobLifecycle<Job, unknown, unknown, ReleaseResult>, 'releaseJob'>, job: Job, opts?: ReleaseDurableJobOptions): Promise<ReleaseResult | undefined>;
233
242
  export declare function findDispatchableDurableJobs<Queue extends string, Record extends Pick<DurableJobRecord<Queue>, 'id' | 'queue'>>(repository: Pick<DurableJobRecoveryRepository<Queue, Record>, 'findDispatchableJobs'>, query?: DurableJobRecoveryQuery): Promise<Record[]>;
234
243
  export declare function releaseStaleReservedDurableJobs<Queue extends string, Record extends Pick<DurableJobRecord<Queue>, 'id' | 'queue'>>(repository: Pick<DurableJobRecoveryRepository<Queue, Record>, 'releaseStaleReservedJobs'>, query: DurableJobStaleRecoveryQuery): Promise<number>;
@@ -338,7 +338,7 @@ export async function runDurableJobMessage(opts) {
338
338
  const permanent = opts.isPermanentFailure ? opts.isPermanentFailure({ error, storedJob, attempts: job.attempts, maxAttempts }) : typeof maxAttempts === "number" && job.attempts >= maxAttempts;
339
339
  if (permanent) {
340
340
  try {
341
- await failDurableJob(opts.lifecycle, storedJob, describeCauseWithStack(error));
341
+ await failDurableJob(opts.lifecycle, storedJob, describeCauseWithStack(error), { cause: error });
342
342
  } catch (failError) {
343
343
  const obsolete = obsoleteDelivery(failError);
344
344
  if (obsolete)
@@ -44,15 +44,16 @@ export interface RunLightweightMessageOptions<Env = unknown, Db = unknown, Logge
44
44
  /** Mark an id as terminally handled. Retried/released messages must not be marked. */
45
45
  markDuplicate?: (id: string | undefined) => void;
46
46
  /**
47
- * Diagnostic sink for dropped/invalid messages (no throw). `stack` is present
48
- * when the entry came from a thrown defect, so a consumer can report the real
49
- * stack rather than reconstructing `new Error(error)`.
47
+ * Diagnostic sink for dropped/invalid messages (no throw). When the entry came from
48
+ * a thrown defect, `cause` is the original value (report it directly) and `stack` its
49
+ * rendering so a consumer never has to reconstruct `new Error(error)`.
50
50
  */
51
51
  onLog?: (event: {
52
52
  stage: string;
53
53
  taskName?: string;
54
54
  error?: string;
55
55
  stack?: string;
56
+ cause?: unknown;
56
57
  }) => void;
57
58
  }
58
59
  export type RunLightweightMessageResult = {
@@ -84,6 +85,7 @@ export interface ConsumeQueueBatchOptions<Queue extends string, Env, Db, Logger>
84
85
  jobId?: string;
85
86
  error?: string;
86
87
  stack?: string;
88
+ cause?: unknown;
87
89
  }) => void;
88
90
  createJobScope?: (storedJob: D1DurableJobRecord<Queue>) => DurableJobScope<D1DurableJobRecord<Queue>>;
89
91
  isPermanentFailure?: (input: {
@@ -177,6 +179,7 @@ export interface CreateDurableJobsRuntimeOptions<Queue extends string = string,
177
179
  jobId?: string;
178
180
  error?: string;
179
181
  stack?: string;
182
+ cause?: unknown;
180
183
  }) => void;
181
184
  /** Per-job scope: wrap dispatch (e.g. telemetry) + observe each settlement. */
182
185
  createJobScope?: (storedJob: D1DurableJobRecord<Queue>) => DurableJobScope<D1DurableJobRecord<Queue>>;
@@ -93,7 +93,7 @@ export async function runLightweightMessage(opts) {
93
93
  return { status: "completed" };
94
94
  } catch (error) {
95
95
  const delaySeconds = resolveJobRetryDelay(definition, message.attempts);
96
- opts.onLog?.({ stage: "unexpected", taskName, error: describeCause(error), stack: describeCauseWithStack(error) });
96
+ opts.onLog?.({ stage: "unexpected", taskName, error: describeCause(error), stack: describeCauseWithStack(error), cause: error });
97
97
  message.retry({ delaySeconds });
98
98
  return { status: "errored" };
99
99
  }
@@ -246,7 +246,7 @@ export function createDurableJobsRuntime(opts) {
246
246
  });
247
247
  }
248
248
  } catch (error) {
249
- opts.onLog?.({ stage: "onfinish-failed", taskName: c.name, jobId: batch.id, error: describeCause(error), stack: describeCauseWithStack(error) });
249
+ opts.onLog?.({ stage: "onfinish-failed", taskName: c.name, jobId: batch.id, error: describeCause(error), stack: describeCauseWithStack(error), cause: error });
250
250
  }
251
251
  });
252
252
  const dedup = opts.dedup ? createMessageDedup(opts.dedup) : void 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-cf-jobs",
3
3
  "type": "module",
4
- "version": "0.12.2",
4
+ "version": "0.12.4",
5
5
  "description": "Nuxt module for typed Cloudflare queue jobs.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",