glasswork 0.12.2 → 0.13.1

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 (108) hide show
  1. package/dist/auth/abilities.d.ts +41 -0
  2. package/dist/auth/abilities.d.ts.map +1 -0
  3. package/dist/auth/abilities.js +62 -0
  4. package/dist/auth/abilities.js.map +1 -0
  5. package/dist/auth/assert.d.ts +20 -0
  6. package/dist/auth/assert.d.ts.map +1 -0
  7. package/dist/auth/assert.js +28 -0
  8. package/dist/auth/assert.js.map +1 -0
  9. package/dist/auth/better-auth-provider.d.ts +56 -0
  10. package/dist/auth/better-auth-provider.d.ts.map +1 -0
  11. package/dist/auth/better-auth-provider.js +62 -0
  12. package/dist/auth/better-auth-provider.js.map +1 -0
  13. package/dist/auth/index.d.ts +6 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/index.js +5 -0
  16. package/dist/auth/index.js.map +1 -0
  17. package/dist/auth/middleware.d.ts +31 -0
  18. package/dist/auth/middleware.d.ts.map +1 -0
  19. package/dist/auth/middleware.js +92 -0
  20. package/dist/auth/middleware.js.map +1 -0
  21. package/dist/auth/types.d.ts +54 -0
  22. package/dist/auth/types.d.ts.map +1 -0
  23. package/dist/auth/types.js +2 -0
  24. package/dist/auth/types.js.map +1 -0
  25. package/dist/core/bootstrap.d.ts.map +1 -1
  26. package/dist/core/bootstrap.js +5 -2
  27. package/dist/core/bootstrap.js.map +1 -1
  28. package/dist/core/types.d.ts +8 -2
  29. package/dist/core/types.d.ts.map +1 -1
  30. package/dist/http/route-helpers.d.ts +52 -9
  31. package/dist/http/route-helpers.d.ts.map +1 -1
  32. package/dist/http/route-helpers.js +90 -40
  33. package/dist/http/route-helpers.js.map +1 -1
  34. package/dist/index.d.ts +8 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +7 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/jobs/define-job.d.ts +34 -2
  39. package/dist/jobs/define-job.d.ts.map +1 -1
  40. package/dist/jobs/define-job.js.map +1 -1
  41. package/dist/jobs/drivers/eventbridge-scheduler-driver.d.ts +75 -0
  42. package/dist/jobs/drivers/eventbridge-scheduler-driver.d.ts.map +1 -0
  43. package/dist/jobs/drivers/eventbridge-scheduler-driver.js +135 -0
  44. package/dist/jobs/drivers/eventbridge-scheduler-driver.js.map +1 -0
  45. package/dist/jobs/drivers/mock-driver.d.ts +45 -10
  46. package/dist/jobs/drivers/mock-driver.d.ts.map +1 -1
  47. package/dist/jobs/drivers/mock-driver.js +36 -17
  48. package/dist/jobs/drivers/mock-driver.js.map +1 -1
  49. package/dist/jobs/drivers/sqs-driver.d.ts +26 -19
  50. package/dist/jobs/drivers/sqs-driver.d.ts.map +1 -1
  51. package/dist/jobs/drivers/sqs-driver.js +54 -69
  52. package/dist/jobs/drivers/sqs-driver.js.map +1 -1
  53. package/dist/jobs/errors.d.ts +8 -4
  54. package/dist/jobs/errors.d.ts.map +1 -1
  55. package/dist/jobs/errors.js +13 -7
  56. package/dist/jobs/errors.js.map +1 -1
  57. package/dist/jobs/index.d.ts +4 -3
  58. package/dist/jobs/index.d.ts.map +1 -1
  59. package/dist/jobs/index.js +3 -2
  60. package/dist/jobs/index.js.map +1 -1
  61. package/dist/jobs/job-registry.d.ts +4 -4
  62. package/dist/jobs/job-registry.d.ts.map +1 -1
  63. package/dist/jobs/job-registry.js.map +1 -1
  64. package/dist/jobs/job-service.js +1 -1
  65. package/dist/jobs/job-service.js.map +1 -1
  66. package/dist/jobs/schedule-constants.d.ts +0 -4
  67. package/dist/jobs/schedule-constants.d.ts.map +1 -1
  68. package/dist/jobs/schedule-constants.js +0 -4
  69. package/dist/jobs/schedule-constants.js.map +1 -1
  70. package/dist/jobs/scheduler.d.ts +1 -1
  71. package/dist/jobs/scheduler.d.ts.map +1 -1
  72. package/dist/jobs/scheduler.js +46 -10
  73. package/dist/jobs/scheduler.js.map +1 -1
  74. package/dist/jobs/types.d.ts +59 -50
  75. package/dist/jobs/types.d.ts.map +1 -1
  76. package/dist/jobs/utils.d.ts +0 -9
  77. package/dist/jobs/utils.d.ts.map +1 -1
  78. package/dist/jobs/utils.js +3 -23
  79. package/dist/jobs/utils.js.map +1 -1
  80. package/dist/jobs/worker.d.ts +2 -3
  81. package/dist/jobs/worker.d.ts.map +1 -1
  82. package/dist/jobs/worker.js +183 -82
  83. package/dist/jobs/worker.js.map +1 -1
  84. package/dist/list-query/casl.d.ts +18 -19
  85. package/dist/list-query/casl.d.ts.map +1 -1
  86. package/dist/list-query/casl.js +40 -46
  87. package/dist/list-query/casl.js.map +1 -1
  88. package/dist/uploads/file-upload.d.ts +44 -0
  89. package/dist/uploads/file-upload.d.ts.map +1 -0
  90. package/dist/uploads/file-upload.js +145 -0
  91. package/dist/uploads/file-upload.js.map +1 -0
  92. package/dist/uploads/index.d.ts +6 -0
  93. package/dist/uploads/index.d.ts.map +1 -0
  94. package/dist/uploads/index.js +7 -0
  95. package/dist/uploads/index.js.map +1 -0
  96. package/dist/uploads/uploads.service.d.ts +98 -0
  97. package/dist/uploads/uploads.service.d.ts.map +1 -0
  98. package/dist/uploads/uploads.service.js +134 -0
  99. package/dist/uploads/uploads.service.js.map +1 -0
  100. package/dist/uploads/validators.d.ts +41 -0
  101. package/dist/uploads/validators.d.ts.map +1 -0
  102. package/dist/uploads/validators.js +94 -0
  103. package/dist/uploads/validators.js.map +1 -0
  104. package/package.json +34 -3
  105. package/dist/email/compiler/code-generator.d.ts +0 -11
  106. package/dist/email/compiler/code-generator.d.ts.map +0 -1
  107. package/dist/email/compiler/code-generator.js +0 -319
  108. package/dist/email/compiler/code-generator.js.map +0 -1
@@ -6,19 +6,41 @@ import type { Logger } from '../utils/logger.js';
6
6
  export type Duration = string | number;
7
7
  /**
8
8
  * Retry configuration for jobs.
9
- * Phase 1 does not implement retries but the shape is defined for forward compatibility.
9
+ *
10
+ * Glasswork relies on SQS visibility timeout for retry timing, but this
11
+ * configuration influences how the worker handles failures and determines
12
+ * when a job should be considered "dead" (retries exhausted).
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Default: 25 retries, send to DLQ
17
+ * defineJob({ name: 'my-job', handler: ... })
18
+ *
19
+ * // Custom retry count
20
+ * defineJob({ name: 'my-job', retry: { maxAttempts: 5 }, handler: ... })
21
+ *
22
+ * // No retries, discard on failure
23
+ * defineJob({ name: 'my-job', retry: false, handler: ... })
24
+ *
25
+ * // Retry but don't save to DLQ
26
+ * defineJob({ name: 'my-job', retry: { maxAttempts: 10, dead: false }, handler: ... })
27
+ * ```
10
28
  */
11
29
  export interface RetryConfig {
12
- /** Maximum retry attempts (default: 3) */
30
+ /**
31
+ * Maximum number of retries before the job is considered dead.
32
+ * The default is 25 retries.
33
+ *
34
+ * @default 25
35
+ */
13
36
  maxAttempts?: number;
14
- /** Backoff strategy (default: exponential) */
15
- backoff?: 'exponential' | 'linear' | 'fixed';
16
- /** Initial delay between retries (default: 30s) */
17
- initialDelay?: Duration;
18
- /** Maximum delay cap (default: 1h) */
19
- maxDelay?: Duration;
20
- /** Whether to apply jitter to backoff timing (default: true) */
21
- jitter?: boolean;
37
+ /**
38
+ * Whether to save failed jobs to the dead letter queue after exhausting retries.
39
+ * If false, jobs will be discarded (not re-thrown to SQS) after max attempts.
40
+ *
41
+ * @default true
42
+ */
43
+ dead?: boolean;
22
44
  }
23
45
  /**
24
46
  * Execution context passed to job handlers.
@@ -55,8 +77,15 @@ export interface JobDefinition<TPayload> {
55
77
  deadLetterQueue?: string;
56
78
  /** Optional payload schema for validation */
57
79
  schema?: BaseSchema<TPayload, TPayload, BaseIssue<unknown>>;
58
- /** Retry behavior (future phases) */
59
- retry?: RetryConfig;
80
+ /**
81
+ * Retry configuration for this job.
82
+ *
83
+ * - `undefined`: Default (25 retries, send to DLQ)
84
+ * - `number`: Shorthand for { maxAttempts: N }
85
+ * - `false`: Disable retries (discard on first failure)
86
+ * - `RetryConfig`: Full configuration object
87
+ */
88
+ retry?: RetryConfig | number | false;
60
89
  /**
61
90
  * Optional uniqueness constraint. Intended for FIFO queues.
62
91
  * The deduplication key should be stable for the same logical job.
@@ -69,6 +98,22 @@ export interface JobDefinition<TPayload> {
69
98
  /** Handler invoked by the worker */
70
99
  handler: JobHandler<TPayload>;
71
100
  }
101
+ /**
102
+ * Type-erased job definition for use in module registration.
103
+ *
104
+ * Use this type when registering jobs in modules to avoid the awkward
105
+ * `as JobDefinition<unknown>` cast. This works because module registration
106
+ * only needs to access the `name` property and invoke the handler dynamically.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * // In your module:
111
+ * export const MyModule = defineModule({
112
+ * jobs: [myJob], // No cast needed!
113
+ * });
114
+ * ```
115
+ */
116
+ export type AnyJobDefinition = JobDefinition<any>;
72
117
  /**
73
118
  * Job message sent to a queue.
74
119
  */
@@ -93,32 +138,6 @@ export interface EnqueueResult {
93
138
  /** Generated or provided job ID */
94
139
  jobId: string;
95
140
  }
96
- /**
97
- * Options for receiving jobs from a queue.
98
- */
99
- export interface ReceiveOptions {
100
- /** Queue name to poll */
101
- queue?: string;
102
- /** Visibility timeout in seconds */
103
- visibilityTimeout?: number;
104
- /** Maximum number of messages to receive */
105
- maxMessages?: number;
106
- /** Long polling wait time in seconds */
107
- waitTimeSeconds?: number;
108
- }
109
- /**
110
- * Job representation when received from a driver.
111
- */
112
- export interface ReceivedJob {
113
- /** Parsed message */
114
- message: JobMessage & {
115
- jobId: string;
116
- };
117
- /** Driver-specific receipt handle */
118
- receiptHandle?: string;
119
- /** Raw driver payload */
120
- raw?: unknown;
121
- }
122
141
  /**
123
142
  * Queue driver interface.
124
143
  *
@@ -128,6 +147,8 @@ export interface ReceivedJob {
128
147
  export interface QueueDriver {
129
148
  /** Driver name for logging/debugging */
130
149
  readonly name: string;
150
+ /** Default queue name resolved by the driver (optional) */
151
+ readonly defaultQueue?: string;
131
152
  /**
132
153
  * Enqueue a job for immediate processing.
133
154
  */
@@ -140,17 +161,5 @@ export interface QueueDriver {
140
161
  * Enqueue a job for processing after a delay.
141
162
  */
142
163
  enqueueIn?(message: JobMessage, delay: Duration): Promise<EnqueueResult>;
143
- /**
144
- * Receive jobs from the queue (for worker processing).
145
- */
146
- receive?(options?: ReceiveOptions): Promise<ReceivedJob[]>;
147
- /**
148
- * Acknowledge successful job completion.
149
- */
150
- ack?(job: ReceivedJob): Promise<void>;
151
- /**
152
- * Reject a job (will be retried or sent to DLQ).
153
- */
154
- nack?(job: ReceivedJob, error?: Error): Promise<void>;
155
164
  }
156
165
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"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"}
@@ -12,13 +12,4 @@ export declare function calculatePayloadSizeBytes(payload: unknown): number;
12
12
  * Supports number (seconds) or string like "5m", "30s", "1h", "1d".
13
13
  */
14
14
  export declare function durationToSeconds(duration: Duration): number;
15
- /**
16
- * Simple exponential backoff with optional jitter.
17
- */
18
- export declare function computeBackoffSeconds(attemptNumber: number, config: {
19
- baseDelaySeconds: number;
20
- maxDelaySeconds?: number;
21
- strategy?: 'exponential' | 'linear' | 'fixed';
22
- jitter?: boolean;
23
- }): number;
24
15
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/jobs/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIlE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CA2B5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE;IACN,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACA,MAAM,CAoBR"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/jobs/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKlE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CA2B5D"}
@@ -12,8 +12,9 @@ export function generateJobId() {
12
12
  * Calculate the size of a payload once serialized.
13
13
  */
14
14
  export function calculatePayloadSizeBytes(payload) {
15
- const serialized = JSON.stringify(payload);
16
- const encoded = new TextEncoder().encode(serialized ?? 'null');
15
+ // JSON.stringify(undefined) returns undefined, not a string
16
+ const serialized = payload === undefined ? 'null' : JSON.stringify(payload);
17
+ const encoded = new TextEncoder().encode(serialized);
17
18
  return encoded.length;
18
19
  }
19
20
  /**
@@ -45,25 +46,4 @@ export function durationToSeconds(duration) {
45
46
  throw new Error(`Unsupported duration unit: "${unit}"`);
46
47
  }
47
48
  }
48
- /**
49
- * Simple exponential backoff with optional jitter.
50
- */
51
- export function computeBackoffSeconds(attemptNumber, config) {
52
- const { baseDelaySeconds, maxDelaySeconds, strategy = 'exponential', jitter = true } = config;
53
- let delay = baseDelaySeconds;
54
- if (strategy === 'exponential') {
55
- delay = baseDelaySeconds * 2 ** (attemptNumber - 1);
56
- }
57
- else if (strategy === 'linear') {
58
- delay = baseDelaySeconds * attemptNumber;
59
- }
60
- if (maxDelaySeconds) {
61
- delay = Math.min(delay, maxDelaySeconds);
62
- }
63
- if (jitter) {
64
- const random = Math.random();
65
- delay = delay * (0.5 + random / 2); // between 0.5x and 1x
66
- }
67
- return Math.max(0, Math.floor(delay));
68
- }
69
49
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/jobs/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,UAAU,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,CAAC;IAC/D,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,GAAG,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAEpC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG,CAAC;QACT,KAAK,EAAE;YACL,OAAO,KAAK,CAAC;QACf,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACzB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC9B;YACE,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,aAAqB,EACrB,MAKC;IAED,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,QAAQ,GAAG,aAAa,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IAE9F,IAAI,KAAK,GAAG,gBAAgB,CAAC;IAC7B,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAC/B,KAAK,GAAG,gBAAgB,GAAG,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;SAAM,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,KAAK,GAAG,gBAAgB,GAAG,aAAa,CAAC;IAC3C,CAAC;IAED,IAAI,eAAe,EAAE,CAAC;QACpB,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC5D,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/jobs/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,UAAU,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,4DAA4D;IAC5D,MAAM,UAAU,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,GAAG,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAEpC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG,CAAC;QACT,KAAK,EAAE;YACL,OAAO,KAAK,CAAC;QACf,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACzB,KAAK,GAAG;YACN,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC9B;YACE,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { Context, EventBridgeEvent, SQSEvent } from 'aws-lambda';
2
2
  import type { ModuleConfig } from '../core/types.js';
3
3
  import { type Logger } from '../utils/logger.js';
4
- import type { JobContext, QueueDriver } from './types.js';
4
+ import type { JobContext } from './types.js';
5
5
  interface WorkerHooks {
6
6
  onJobStart?: (job: JobExecution, context: JobContext) => Promise<void> | void;
7
7
  onJobComplete?: (job: JobExecution, context: JobContext) => Promise<void> | void;
@@ -10,7 +10,6 @@ interface WorkerHooks {
10
10
  }
11
11
  export interface WorkerConfig {
12
12
  module: ModuleConfig;
13
- driver?: QueueDriver;
14
13
  hooks?: WorkerHooks;
15
14
  logger?: Logger;
16
15
  }
@@ -25,7 +24,7 @@ export interface JobExecution {
25
24
  /**
26
25
  * Bootstrap a worker Lambda handler for processing jobs.
27
26
  */
28
- export declare function bootstrapWorker(config: WorkerConfig): (event: SQSEvent | EventBridgeEvent<string, unknown> | Record<string, unknown>, _ctx?: Context) => Promise<{
27
+ export declare function bootstrapWorker(config: WorkerConfig): (event: SQSEvent | EventBridgeEvent<string, unknown> | Record<string, unknown>, ctx?: Context) => Promise<{
29
28
  batchItemFailures: import("aws-lambda").SQSBatchItemFailure[];
30
29
  success?: undefined;
31
30
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/jobs/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAoB,QAAQ,EAAa,MAAM,YAAY,CAAC;AAGnG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAI/D,OAAO,KAAK,EAAE,UAAU,EAAiB,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,WAAW;IACnB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9E,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7F,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClG;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAUD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,IAKhD,OAAO,QAAQ,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7E,OAAO,OAAO;;;;;;GA4CjB"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/jobs/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAoB,QAAQ,EAAa,MAAM,YAAY,CAAC;AAGnG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAQ/D,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAmB1D,UAAU,WAAW;IACnB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9E,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7F,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClG;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AASD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,IAKhD,OAAO,QAAQ,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7E,MAAM,OAAO;;;;;;GA8ChB"}
@@ -1,17 +1,31 @@
1
1
  import { asValue, createContainer, InjectionMode } from 'awilix';
2
- import { safeParse } from 'valibot';
2
+ import { object, optional, record, safeParse, string, unknown } from 'valibot';
3
3
  import { collectModules, registerModuleProviders, validateNoCycles } from '../core/bootstrap.js';
4
4
  import { createLogger } from '../utils/logger.js';
5
- import { InvalidJobPayloadError, PermanentJobError, TransientJobError } from './errors.js';
5
+ import { InvalidJobPayloadError, PermanentJobError, RetryExhaustedError, TransientJobError, } from './errors.js';
6
6
  import { createJobRegistry } from './job-registry.js';
7
- import { MAX_SQS_DELAY_SECONDS, RUN_AT_METADATA_KEY } from './schedule-constants.js';
7
+ import { generateJobId } from './utils.js';
8
+ /**
9
+ * Default maximum retry attempts before a job is considered dead.
10
+ */
11
+ const DEFAULT_MAX_ATTEMPTS = 25;
12
+ /**
13
+ * Valibot schema for validating SQS job message structure.
14
+ */
15
+ const JobMessageSchema = object({
16
+ jobName: string(),
17
+ payload: optional(unknown()),
18
+ jobId: optional(string()),
19
+ enqueuedAt: optional(string()),
20
+ metadata: optional(record(string(), string())),
21
+ });
8
22
  /**
9
23
  * Bootstrap a worker Lambda handler for processing jobs.
10
24
  */
11
25
  export function bootstrapWorker(config) {
12
26
  const logger = config.logger ?? createLogger('JobsWorker');
13
27
  const statePromise = buildWorkerState(config.module, logger);
14
- return async (event, _ctx) => {
28
+ return async (event, ctx) => {
15
29
  const { container, registry } = await statePromise;
16
30
  if (isSQSEvent(event)) {
17
31
  const batchItemFailures = [];
@@ -22,7 +36,6 @@ export function bootstrapWorker(config) {
22
36
  moduleContainer: container,
23
37
  hooks: config.hooks,
24
38
  logger,
25
- driver: config.driver,
26
39
  });
27
40
  }
28
41
  catch (_error) {
@@ -37,13 +50,15 @@ export function bootstrapWorker(config) {
37
50
  if (!jobName) {
38
51
  throw new Error('Job name is required for non-SQS invocation');
39
52
  }
53
+ // Extract attempt number from Lambda context retry metadata if available
54
+ const attemptNumber = extractAttemptNumber(ctx);
40
55
  await executeJob({
41
56
  jobName,
42
- jobId: 'event',
57
+ jobId: `eb-${generateJobId()}`,
43
58
  payload,
44
- attemptNumber: 1,
59
+ attemptNumber,
45
60
  enqueuedAt: new Date(),
46
- }, { registry, moduleContainer: container, hooks: config.hooks, logger, driver: config.driver });
61
+ }, { registry, moduleContainer: container, hooks: config.hooks, logger });
47
62
  return { success: true };
48
63
  };
49
64
  }
@@ -104,17 +119,23 @@ async function processRecord(record, context) {
104
119
  if (!parsed || typeof parsed !== 'object') {
105
120
  throw new Error('Invalid job message payload');
106
121
  }
122
+ // Validate message structure using Valibot
123
+ const validationResult = safeParse(JobMessageSchema, parsed);
124
+ if (!validationResult.success) {
125
+ context.logger.error('Invalid job message structure', {
126
+ messageId: record.messageId,
127
+ issues: validationResult.issues,
128
+ });
129
+ throw new Error('Invalid job message structure');
130
+ }
107
131
  const parsedMessage = parsed;
108
132
  const jobName = parsedMessage.jobName;
109
133
  const payload = parsedMessage.payload;
110
134
  const jobId = parsedMessage.jobId ?? record.messageId;
111
135
  const enqueuedAt = parsedMessage.enqueuedAt ? new Date(parsedMessage.enqueuedAt) : new Date();
112
136
  const metadata = parsedMessage.metadata;
113
- if (!jobName) {
114
- throw new Error('Job name is required');
115
- }
116
137
  const attemptNumber = record.attributes?.ApproximateReceiveCount
117
- ? Number.parseInt(record.attributes.ApproximateReceiveCount, 10)
138
+ ? Number.parseInt(record.attributes.ApproximateReceiveCount, 10) || 1
118
139
  : 1;
119
140
  const execution = {
120
141
  jobId,
@@ -128,18 +149,11 @@ async function processRecord(record, context) {
128
149
  }
129
150
  async function executeJob(execution, context) {
130
151
  const job = context.registry.getOrThrow(execution.jobName);
131
- if (await rescheduleIfNotDue(execution, job, context)) {
132
- return;
133
- }
152
+ // Normalize retry configuration
153
+ const retryConfig = normalizeRetryConfig(job.retry);
134
154
  const scope = context.moduleContainer.createScope();
135
155
  const services = scope.cradle;
136
- let jobLogger = context.logger;
137
- if (context.moduleContainer.hasRegistration('logger')) {
138
- const baseLogger = context.moduleContainer.resolve('logger');
139
- jobLogger = baseLogger?.child
140
- ? baseLogger.child({ jobId: execution.jobId, jobName: execution.jobName })
141
- : baseLogger;
142
- }
156
+ const jobLogger = resolveJobLogger(context, execution);
143
157
  const jobContext = {
144
158
  services,
145
159
  jobId: execution.jobId,
@@ -159,85 +173,151 @@ async function executeJob(execution, context) {
159
173
  await context.hooks?.onJobComplete?.(execution, jobContext);
160
174
  }
161
175
  catch (error) {
162
- const err = error;
163
- await context.hooks?.onJobFailed?.(execution, jobContext, err);
164
- if (err instanceof PermanentJobError) {
165
- jobLogger?.error('Job permanently failed', { err });
166
- await context.hooks?.onJobDeadLetter?.(execution, jobContext, err);
167
- return;
168
- }
169
- if (err instanceof InvalidJobPayloadError) {
170
- jobLogger?.warn('Invalid job payload, retrying until DLQ', {
171
- err,
172
- jobName: execution.jobName,
173
- jobId: execution.jobId,
174
- attemptNumber: execution.attemptNumber,
175
- });
176
- throw err;
177
- }
178
- if (err instanceof TransientJobError) {
179
- jobLogger?.warn('Job transient failure, retrying', { err, retryAfter: err.retryAfter });
180
- }
181
- throw err;
176
+ await handleJobError(error, execution, jobContext, context, retryConfig, jobLogger);
182
177
  }
183
178
  finally {
184
179
  await scope.dispose();
185
180
  }
186
181
  }
187
- async function rescheduleIfNotDue(execution, job, context) {
188
- const runAtValue = execution.metadata?.[RUN_AT_METADATA_KEY];
189
- if (!runAtValue) {
190
- return false;
182
+ /**
183
+ * Resolve the logger for job execution, scoped to the job if possible.
184
+ */
185
+ function resolveJobLogger(context, execution) {
186
+ if (!context.moduleContainer.hasRegistration('logger')) {
187
+ return context.logger;
188
+ }
189
+ const baseLogger = context.moduleContainer.resolve('logger');
190
+ return baseLogger?.child
191
+ ? baseLogger.child({ jobId: execution.jobId, jobName: execution.jobName })
192
+ : baseLogger;
193
+ }
194
+ /**
195
+ * Handle job execution errors with retry logic.
196
+ */
197
+ async function handleJobError(err, execution, jobContext, context, retryConfig, jobLogger) {
198
+ const { maxAttempts, dead: sendToDead } = retryConfig;
199
+ // PermanentJobError: Never retry, go straight to DLQ
200
+ if (err instanceof PermanentJobError) {
201
+ jobLogger?.error('Job permanently failed', { err });
202
+ await context.hooks?.onJobFailed?.(execution, jobContext, err);
203
+ await context.hooks?.onJobDeadLetter?.(execution, jobContext, err);
204
+ throw err;
191
205
  }
192
- const runAt = new Date(runAtValue);
193
- if (Number.isNaN(runAt.getTime())) {
194
- context.logger.warn('Ignoring invalid runAt metadata; executing immediately', {
206
+ // No retries configured: discard the job silently
207
+ if (maxAttempts === false) {
208
+ jobLogger?.warn('Job failed with retries disabled, discarding', {
209
+ err,
195
210
  jobName: execution.jobName,
196
211
  jobId: execution.jobId,
197
- runAt: runAtValue,
198
212
  });
199
- return false;
213
+ await context.hooks?.onJobFailed?.(execution, jobContext, err);
214
+ // Return without throwing to acknowledge and discard the message
215
+ return undefined;
200
216
  }
201
- const remainingMs = runAt.getTime() - Date.now();
202
- if (remainingMs <= 1000) {
203
- return false;
217
+ // Check if retries are exhausted
218
+ if (execution.attemptNumber >= maxAttempts) {
219
+ return handleExhaustedRetries(err, execution, jobContext, context, maxAttempts, sendToDead, jobLogger);
204
220
  }
205
- const driver = context.driver;
206
- if (!driver || (!driver.enqueueIn && !driver.enqueueAt)) {
207
- context.logger.warn('Job scheduled in future but worker has no driver; executing now', {
221
+ // Normal failure: log and throw to trigger SQS retry
222
+ await context.hooks?.onJobFailed?.(execution, jobContext, err);
223
+ logRetryableError(err, execution, maxAttempts, jobLogger);
224
+ throw err;
225
+ }
226
+ /**
227
+ * Handle the case when all retries are exhausted.
228
+ */
229
+ async function handleExhaustedRetries(err, execution, jobContext, context, maxAttempts, sendToDead, jobLogger) {
230
+ const exhaustedError = new RetryExhaustedError(execution.jobName, execution.attemptNumber, maxAttempts, err);
231
+ jobLogger?.error('Job retries exhausted', {
232
+ err,
233
+ attemptNumber: execution.attemptNumber,
234
+ maxAttempts,
235
+ cause: err.message,
236
+ });
237
+ await context.hooks?.onJobFailed?.(execution, jobContext, err);
238
+ await context.hooks?.onJobDeadLetter?.(execution, jobContext, exhaustedError);
239
+ if (!sendToDead) {
240
+ jobLogger?.info('Job discarded (dead: false)', {
208
241
  jobName: execution.jobName,
209
242
  jobId: execution.jobId,
210
- runAt: runAtValue,
211
243
  });
212
- return false;
244
+ // Return without throwing to discard
245
+ return undefined;
213
246
  }
214
- const delaySeconds = Math.min(MAX_SQS_DELAY_SECONDS, Math.max(1, Math.floor(remainingMs / 1000)));
215
- const message = {
216
- jobName: job.name,
217
- payload: execution.payload,
218
- queue: job.queue,
219
- jobId: execution.jobId,
220
- metadata: {
221
- ...execution.metadata,
222
- [RUN_AT_METADATA_KEY]: runAtValue,
223
- },
224
- };
225
- if (driver.enqueueIn) {
226
- await driver.enqueueIn(message, delaySeconds);
247
+ throw exhaustedError;
248
+ }
249
+ /**
250
+ * Log a retriable error with appropriate context.
251
+ */
252
+ function logRetryableError(err, execution, maxAttempts, jobLogger) {
253
+ if (err instanceof InvalidJobPayloadError) {
254
+ jobLogger?.warn('Invalid job payload, retrying until DLQ', {
255
+ err,
256
+ jobName: execution.jobName,
257
+ jobId: execution.jobId,
258
+ attemptNumber: execution.attemptNumber,
259
+ maxAttempts,
260
+ });
227
261
  }
228
- else if (driver.enqueueAt) {
229
- await driver.enqueueAt(message, runAt);
262
+ else if (err instanceof TransientJobError) {
263
+ jobLogger?.warn('Job transient failure, retrying', {
264
+ err,
265
+ retryAfter: err.retryAfter,
266
+ attemptNumber: execution.attemptNumber,
267
+ maxAttempts,
268
+ });
269
+ }
270
+ else {
271
+ jobLogger?.warn('Job failed, will retry', {
272
+ err,
273
+ attemptNumber: execution.attemptNumber,
274
+ maxAttempts,
275
+ remainingAttempts: maxAttempts - execution.attemptNumber,
276
+ });
230
277
  }
231
- context.logger.info('Deferred job that is not due yet', {
232
- jobName: execution.jobName,
233
- jobId: execution.jobId,
234
- runAt: runAtValue,
235
- delaySeconds,
236
- });
237
- return true;
238
278
  }
279
+ /**
280
+ * Normalize retry configuration to a consistent format.
281
+ * Zero or negative maxAttempts are treated as "no retries" (same as retry: false).
282
+ */
283
+ function normalizeRetryConfig(config) {
284
+ if (config === false) {
285
+ return { maxAttempts: false, dead: false };
286
+ }
287
+ if (typeof config === 'number') {
288
+ // Treat zero or negative as "no retries"
289
+ if (config <= 0) {
290
+ return { maxAttempts: false, dead: false };
291
+ }
292
+ return { maxAttempts: config, dead: true };
293
+ }
294
+ if (config === undefined) {
295
+ return { maxAttempts: DEFAULT_MAX_ATTEMPTS, dead: true };
296
+ }
297
+ // Handle object config with potential invalid maxAttempts
298
+ const maxAttempts = config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
299
+ if (maxAttempts <= 0) {
300
+ return { maxAttempts: false, dead: false };
301
+ }
302
+ return {
303
+ maxAttempts,
304
+ dead: config.dead ?? true,
305
+ };
306
+ }
307
+ /**
308
+ * Type guard to check if an event is an SQS event.
309
+ * Validates presence of Records array with SQS-specific properties.
310
+ */
239
311
  function isSQSEvent(event) {
240
- return Boolean(event?.Records);
312
+ const maybeEvent = event;
313
+ if (!Array.isArray(maybeEvent?.Records) || maybeEvent.Records.length === 0) {
314
+ return false;
315
+ }
316
+ const firstRecord = maybeEvent.Records[0];
317
+ // Check for SQS-specific properties to distinguish from other AWS event types
318
+ return (typeof firstRecord.messageId === 'string' &&
319
+ typeof firstRecord.body === 'string' &&
320
+ typeof firstRecord.receiptHandle === 'string');
241
321
  }
242
322
  function extractJobName(event) {
243
323
  if (typeof event.jobName === 'string')
@@ -257,4 +337,25 @@ function extractPayload(event) {
257
337
  return detail.payload;
258
338
  return undefined;
259
339
  }
340
+ /**
341
+ * Extract attempt number from Lambda context.
342
+ * For EventBridge retries, checks for retry metadata in the context.
343
+ */
344
+ function extractAttemptNumber(ctx) {
345
+ if (!ctx)
346
+ return 1;
347
+ // EventBridge retry information may be available in client context
348
+ // or through environment variables in some configurations
349
+ try {
350
+ const clientContext = ctx
351
+ .clientContext;
352
+ if (clientContext?.custom?.retryAttempt !== undefined) {
353
+ return clientContext.custom.retryAttempt + 1;
354
+ }
355
+ }
356
+ catch {
357
+ // Ignore parsing errors
358
+ }
359
+ return 1;
360
+ }
260
361
  //# sourceMappingURL=worker.js.map