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.
- package/dist/auth/abilities.d.ts +41 -0
- package/dist/auth/abilities.d.ts.map +1 -0
- package/dist/auth/abilities.js +62 -0
- package/dist/auth/abilities.js.map +1 -0
- package/dist/auth/assert.d.ts +20 -0
- package/dist/auth/assert.d.ts.map +1 -0
- package/dist/auth/assert.js +28 -0
- package/dist/auth/assert.js.map +1 -0
- package/dist/auth/better-auth-provider.d.ts +56 -0
- package/dist/auth/better-auth-provider.d.ts.map +1 -0
- package/dist/auth/better-auth-provider.js +62 -0
- package/dist/auth/better-auth-provider.js.map +1 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +31 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +92 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/types.d.ts +54 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/core/bootstrap.d.ts.map +1 -1
- package/dist/core/bootstrap.js +5 -2
- package/dist/core/bootstrap.js.map +1 -1
- package/dist/core/types.d.ts +8 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/http/route-helpers.d.ts +52 -9
- package/dist/http/route-helpers.d.ts.map +1 -1
- package/dist/http/route-helpers.js +90 -40
- package/dist/http/route-helpers.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs/define-job.d.ts +34 -2
- package/dist/jobs/define-job.d.ts.map +1 -1
- package/dist/jobs/define-job.js.map +1 -1
- package/dist/jobs/drivers/eventbridge-scheduler-driver.d.ts +75 -0
- package/dist/jobs/drivers/eventbridge-scheduler-driver.d.ts.map +1 -0
- package/dist/jobs/drivers/eventbridge-scheduler-driver.js +135 -0
- package/dist/jobs/drivers/eventbridge-scheduler-driver.js.map +1 -0
- package/dist/jobs/drivers/mock-driver.d.ts +45 -10
- package/dist/jobs/drivers/mock-driver.d.ts.map +1 -1
- package/dist/jobs/drivers/mock-driver.js +36 -17
- package/dist/jobs/drivers/mock-driver.js.map +1 -1
- package/dist/jobs/drivers/sqs-driver.d.ts +26 -19
- package/dist/jobs/drivers/sqs-driver.d.ts.map +1 -1
- package/dist/jobs/drivers/sqs-driver.js +54 -69
- package/dist/jobs/drivers/sqs-driver.js.map +1 -1
- package/dist/jobs/errors.d.ts +8 -4
- package/dist/jobs/errors.d.ts.map +1 -1
- package/dist/jobs/errors.js +13 -7
- package/dist/jobs/errors.js.map +1 -1
- package/dist/jobs/index.d.ts +4 -3
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +3 -2
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/job-registry.d.ts +4 -4
- package/dist/jobs/job-registry.d.ts.map +1 -1
- package/dist/jobs/job-registry.js.map +1 -1
- package/dist/jobs/job-service.js +1 -1
- package/dist/jobs/job-service.js.map +1 -1
- package/dist/jobs/schedule-constants.d.ts +0 -4
- package/dist/jobs/schedule-constants.d.ts.map +1 -1
- package/dist/jobs/schedule-constants.js +0 -4
- package/dist/jobs/schedule-constants.js.map +1 -1
- package/dist/jobs/scheduler.d.ts +1 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +46 -10
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/jobs/types.d.ts +59 -50
- package/dist/jobs/types.d.ts.map +1 -1
- package/dist/jobs/utils.d.ts +0 -9
- package/dist/jobs/utils.d.ts.map +1 -1
- package/dist/jobs/utils.js +3 -23
- package/dist/jobs/utils.js.map +1 -1
- package/dist/jobs/worker.d.ts +2 -3
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +183 -82
- package/dist/jobs/worker.js.map +1 -1
- package/dist/list-query/casl.d.ts +18 -19
- package/dist/list-query/casl.d.ts.map +1 -1
- package/dist/list-query/casl.js +40 -46
- package/dist/list-query/casl.js.map +1 -1
- package/dist/uploads/file-upload.d.ts +44 -0
- package/dist/uploads/file-upload.d.ts.map +1 -0
- package/dist/uploads/file-upload.js +145 -0
- package/dist/uploads/file-upload.js.map +1 -0
- package/dist/uploads/index.d.ts +6 -0
- package/dist/uploads/index.d.ts.map +1 -0
- package/dist/uploads/index.js +7 -0
- package/dist/uploads/index.js.map +1 -0
- package/dist/uploads/uploads.service.d.ts +98 -0
- package/dist/uploads/uploads.service.d.ts.map +1 -0
- package/dist/uploads/uploads.service.js +134 -0
- package/dist/uploads/uploads.service.js.map +1 -0
- package/dist/uploads/validators.d.ts +41 -0
- package/dist/uploads/validators.d.ts.map +1 -0
- package/dist/uploads/validators.js +94 -0
- package/dist/uploads/validators.js.map +1 -0
- package/package.json +34 -3
- package/dist/email/compiler/code-generator.d.ts +0 -11
- package/dist/email/compiler/code-generator.d.ts.map +0 -1
- package/dist/email/compiler/code-generator.js +0 -319
- package/dist/email/compiler/code-generator.js.map +0 -1
package/dist/jobs/types.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
/**
|
|
59
|
-
|
|
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
|
package/dist/jobs/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"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"}
|
package/dist/jobs/utils.d.ts
CHANGED
|
@@ -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
|
package/dist/jobs/utils.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/jobs/utils.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
const
|
|
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
|
package/dist/jobs/utils.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/jobs/worker.d.ts
CHANGED
|
@@ -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
|
|
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>,
|
|
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;
|
|
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"}
|
package/dist/jobs/worker.js
CHANGED
|
@@ -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 {
|
|
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,
|
|
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:
|
|
57
|
+
jobId: `eb-${generateJobId()}`,
|
|
43
58
|
payload,
|
|
44
|
-
attemptNumber
|
|
59
|
+
attemptNumber,
|
|
45
60
|
enqueuedAt: new Date(),
|
|
46
|
-
}, { registry, moduleContainer: container, hooks: config.hooks, logger
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
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
|
-
|
|
213
|
+
await context.hooks?.onJobFailed?.(execution, jobContext, err);
|
|
214
|
+
// Return without throwing to acknowledge and discard the message
|
|
215
|
+
return undefined;
|
|
200
216
|
}
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
return
|
|
217
|
+
// Check if retries are exhausted
|
|
218
|
+
if (execution.attemptNumber >= maxAttempts) {
|
|
219
|
+
return handleExhaustedRetries(err, execution, jobContext, context, maxAttempts, sendToDead, jobLogger);
|
|
204
220
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
244
|
+
// Return without throwing to discard
|
|
245
|
+
return undefined;
|
|
213
246
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 (
|
|
229
|
-
|
|
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
|
-
|
|
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
|