@beignet/core 0.0.1 → 0.0.3
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/CHANGELOG.md +27 -0
- package/README.md +202 -8
- package/dist/application/index.d.ts +93 -9
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +11 -11
- package/dist/application/index.js.map +1 -1
- package/dist/client/client.d.ts +73 -12
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +37 -12
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +6 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +69 -8
- package/dist/client/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +84 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +36 -0
- package/dist/config/index.js.map +1 -1
- package/dist/contracts/contract-builder.d.ts +49 -22
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +48 -21
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/contracts/contract-group.d.ts +35 -19
- package/dist/contracts/contract-group.d.ts.map +1 -1
- package/dist/contracts/contract-group.js +35 -19
- package/dist/contracts/contract-group.js.map +1 -1
- package/dist/contracts/contract-like.d.ts +4 -4
- package/dist/contracts/contract-like.d.ts.map +1 -1
- package/dist/contracts/contract-like.js +2 -1
- package/dist/contracts/contract-like.js.map +1 -1
- package/dist/contracts/index.d.ts +28 -0
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +12 -0
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/openapi-meta.d.ts +8 -8
- package/dist/contracts/openapi-meta.d.ts.map +1 -1
- package/dist/contracts/path-template.d.ts +27 -0
- package/dist/contracts/path-template.d.ts.map +1 -1
- package/dist/contracts/path-template.js +6 -0
- package/dist/contracts/path-template.js.map +1 -1
- package/dist/contracts/types.d.ts +104 -10
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js +15 -0
- package/dist/contracts/types.js.map +1 -1
- package/dist/contracts/utils.d.ts +6 -0
- package/dist/contracts/utils.d.ts.map +1 -1
- package/dist/contracts/utils.js +6 -0
- package/dist/contracts/utils.js.map +1 -1
- package/dist/domain/entity.d.ts +22 -11
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +5 -1
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/events.d.ts +5 -2
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +4 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/value-object.d.ts +19 -9
- package/dist/domain/value-object.d.ts.map +1 -1
- package/dist/domain/value-object.js +5 -1
- package/dist/domain/value-object.js.map +1 -1
- package/dist/errors/catalog.d.ts +40 -16
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +18 -7
- package/dist/errors/catalog.js.map +1 -1
- package/dist/errors/response.d.ts +16 -4
- package/dist/errors/response.d.ts.map +1 -1
- package/dist/errors/response.js +3 -3
- package/dist/errors/response.js.map +1 -1
- package/dist/errors/validation.d.ts +10 -1
- package/dist/errors/validation.d.ts.map +1 -1
- package/dist/errors/validation.js +3 -0
- package/dist/errors/validation.js.map +1 -1
- package/dist/events/index.d.ts +133 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +30 -0
- package/dist/events/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +355 -0
- package/dist/idempotency/index.d.ts.map +1 -0
- package/dist/idempotency/index.js +360 -0
- package/dist/idempotency/index.js.map +1 -0
- package/dist/jobs/index.d.ts +248 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +183 -1
- package/dist/jobs/index.js.map +1 -1
- package/dist/mail/index.d.ts +149 -0
- package/dist/mail/index.d.ts.map +1 -1
- package/dist/mail/index.js +30 -0
- package/dist/mail/index.js.map +1 -1
- package/dist/notifications/index.d.ts +369 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +310 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/openapi/index.d.ts +132 -16
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/index.js.map +1 -1
- package/dist/outbox/index.d.ts +474 -0
- package/dist/outbox/index.d.ts.map +1 -0
- package/dist/outbox/index.js +538 -0
- package/dist/outbox/index.js.map +1 -0
- package/dist/pagination/index.d.ts +166 -0
- package/dist/pagination/index.d.ts.map +1 -0
- package/dist/pagination/index.js +96 -0
- package/dist/pagination/index.js.map +1 -0
- package/dist/ports/audit.d.ts +271 -0
- package/dist/ports/audit.d.ts.map +1 -1
- package/dist/ports/audit.js +128 -0
- package/dist/ports/audit.js.map +1 -1
- package/dist/ports/auth.d.ts +70 -0
- package/dist/ports/auth.d.ts.map +1 -1
- package/dist/ports/auth.js +30 -0
- package/dist/ports/auth.js.map +1 -1
- package/dist/ports/cache.d.ts +41 -0
- package/dist/ports/cache.d.ts.map +1 -1
- package/dist/ports/cache.js +10 -0
- package/dist/ports/cache.js.map +1 -1
- package/dist/ports/clock.d.ts +38 -0
- package/dist/ports/clock.d.ts.map +1 -1
- package/dist/ports/clock.js +20 -0
- package/dist/ports/clock.js.map +1 -1
- package/dist/ports/id-generator.d.ts +37 -0
- package/dist/ports/id-generator.d.ts.map +1 -1
- package/dist/ports/id-generator.js +22 -0
- package/dist/ports/id-generator.js.map +1 -1
- package/dist/ports/index.d.ts +83 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +41 -5
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/logger.d.ts +56 -0
- package/dist/ports/logger.d.ts.map +1 -1
- package/dist/ports/logger.js +17 -0
- package/dist/ports/logger.js.map +1 -1
- package/dist/ports/policy.d.ts +132 -0
- package/dist/ports/policy.d.ts.map +1 -1
- package/dist/ports/policy.js +45 -0
- package/dist/ports/policy.js.map +1 -1
- package/dist/ports/rate-limit.d.ts +25 -0
- package/dist/ports/rate-limit.d.ts.map +1 -1
- package/dist/ports/rate-limit.js +10 -0
- package/dist/ports/rate-limit.js.map +1 -1
- package/dist/ports/redaction.d.ts +101 -0
- package/dist/ports/redaction.d.ts.map +1 -1
- package/dist/ports/redaction.js +59 -0
- package/dist/ports/redaction.js.map +1 -1
- package/dist/ports/storage.d.ts +100 -0
- package/dist/ports/storage.d.ts.map +1 -1
- package/dist/ports/storage.js +10 -0
- package/dist/ports/storage.js.map +1 -1
- package/dist/ports/testing.d.ts +47 -0
- package/dist/ports/testing.d.ts.map +1 -1
- package/dist/ports/testing.js +23 -0
- package/dist/ports/testing.js.map +1 -1
- package/dist/ports/unit-of-work.d.ts +60 -3
- package/dist/ports/unit-of-work.d.ts.map +1 -1
- package/dist/ports/unit-of-work.js +11 -2
- package/dist/ports/unit-of-work.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +205 -1
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js +14 -0
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/provider.d.ts +14 -1
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +246 -0
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +27 -0
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/health.d.ts +14 -5
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/health.js +5 -2
- package/dist/server/health.js.map +1 -1
- package/dist/server/hooks/auth.d.ts +68 -26
- package/dist/server/hooks/auth.d.ts.map +1 -1
- package/dist/server/hooks/auth.js +44 -55
- package/dist/server/hooks/auth.js.map +1 -1
- package/dist/server/hooks/cors.d.ts +27 -0
- package/dist/server/hooks/cors.d.ts.map +1 -1
- package/dist/server/hooks/cors.js +12 -0
- package/dist/server/hooks/cors.js.map +1 -1
- package/dist/server/hooks/errors.d.ts +15 -6
- package/dist/server/hooks/errors.d.ts.map +1 -1
- package/dist/server/hooks/errors.js.map +1 -1
- package/dist/server/hooks/index.d.ts +4 -1
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +3 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/logging.d.ts +36 -0
- package/dist/server/hooks/logging.d.ts.map +1 -1
- package/dist/server/hooks/logging.js +6 -0
- package/dist/server/hooks/logging.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +33 -0
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +11 -0
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/http.d.ts +222 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +20 -1
- package/dist/server/http.js.map +1 -1
- package/dist/server/index.d.ts +19 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +7 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/openapi.d.ts +5 -3
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/openapi.js +4 -2
- package/dist/server/openapi.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts +9 -0
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +9 -0
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/server.d.ts +159 -19
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +72 -31
- package/dist/server/server.js.map +1 -1
- package/dist/testing/index.d.ts +171 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +127 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/uploads/client.d.ts +278 -0
- package/dist/uploads/client.d.ts.map +1 -0
- package/dist/uploads/client.js +428 -0
- package/dist/uploads/client.js.map +1 -0
- package/dist/uploads/index.d.ts +361 -0
- package/dist/uploads/index.d.ts.map +1 -0
- package/dist/uploads/index.js +543 -0
- package/dist/uploads/index.js.map +1 -0
- package/package.json +31 -2
- package/src/application/index.ts +85 -22
- package/src/client/client.ts +73 -12
- package/src/client/index.ts +12 -0
- package/src/client/types.ts +70 -9
- package/src/config/index.ts +86 -0
- package/src/contracts/contract-builder.ts +49 -22
- package/src/contracts/contract-group.ts +35 -19
- package/src/contracts/contract-like.ts +4 -4
- package/src/contracts/index.ts +28 -1
- package/src/contracts/openapi-meta.ts +8 -8
- package/src/contracts/path-template.ts +27 -0
- package/src/contracts/types.ts +111 -10
- package/src/contracts/utils.ts +6 -0
- package/src/domain/entity.ts +22 -11
- package/src/domain/events.ts +5 -2
- package/src/domain/value-object.ts +19 -9
- package/src/errors/catalog.ts +40 -16
- package/src/errors/response.ts +16 -4
- package/src/errors/validation.ts +10 -1
- package/src/events/index.ts +134 -0
- package/src/idempotency/index.ts +767 -0
- package/src/jobs/index.ts +437 -5
- package/src/mail/index.ts +149 -0
- package/src/notifications/index.ts +771 -0
- package/src/openapi/index.ts +133 -16
- package/src/outbox/index.ts +1104 -0
- package/src/pagination/index.ts +278 -0
- package/src/ports/audit.ts +271 -0
- package/src/ports/auth.ts +70 -0
- package/src/ports/cache.ts +41 -0
- package/src/ports/clock.ts +38 -0
- package/src/ports/id-generator.ts +37 -0
- package/src/ports/index.ts +106 -11
- package/src/ports/logger.ts +56 -0
- package/src/ports/policy.ts +133 -0
- package/src/ports/rate-limit.ts +25 -0
- package/src/ports/redaction.ts +101 -0
- package/src/ports/storage.ts +100 -0
- package/src/ports/testing.ts +47 -0
- package/src/ports/unit-of-work.ts +60 -3
- package/src/providers/instrumentation.ts +211 -1
- package/src/providers/provider.ts +14 -1
- package/src/schedules/index.ts +247 -0
- package/src/server/health.ts +14 -5
- package/src/server/hooks/auth.ts +105 -120
- package/src/server/hooks/cors.ts +27 -0
- package/src/server/hooks/errors.ts +15 -6
- package/src/server/hooks/index.ts +4 -5
- package/src/server/hooks/logging.ts +36 -0
- package/src/server/hooks/rate-limit.ts +33 -0
- package/src/server/http.ts +249 -1
- package/src/server/index.ts +19 -1
- package/src/server/openapi.ts +5 -3
- package/src/server/providers/loadProviderConfig.ts +9 -0
- package/src/server/server.ts +296 -30
- package/src/testing/index.ts +348 -0
- package/src/uploads/client.ts +861 -0
- package/src/uploads/index.ts +1067 -0
package/src/jobs/index.ts
CHANGED
|
@@ -1,82 +1,335 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Any Standard Schema compatible validator.
|
|
5
|
+
*/
|
|
3
6
|
export type StandardSchema = StandardSchemaV1<unknown, unknown>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Value or promise of that value.
|
|
10
|
+
*/
|
|
4
11
|
export type MaybePromise<T> = T | Promise<T>;
|
|
5
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Infer the parsed output type from a Standard Schema.
|
|
15
|
+
*/
|
|
6
16
|
export type InferSchemaOutput<T extends StandardSchemaV1> =
|
|
7
17
|
StandardSchemaV1.InferOutput<T>;
|
|
8
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Duration accepted by retry helpers. Numbers are milliseconds.
|
|
21
|
+
*/
|
|
22
|
+
export type JobRetryDuration =
|
|
23
|
+
| number
|
|
24
|
+
| `${number}ms`
|
|
25
|
+
| `${number}s`
|
|
26
|
+
| `${number}m`
|
|
27
|
+
| `${number}h`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Retry strategy understood by Beignet job adapters.
|
|
31
|
+
*/
|
|
32
|
+
export type JobRetryStrategy = "none" | "fixed" | "exponential";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Arguments passed to a retry predicate.
|
|
36
|
+
*/
|
|
37
|
+
export interface JobRetryPredicateArgs {
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown by the previous attempt.
|
|
40
|
+
*/
|
|
41
|
+
error: unknown;
|
|
42
|
+
/**
|
|
43
|
+
* One-based attempt number that just failed.
|
|
44
|
+
*/
|
|
45
|
+
attempt: number;
|
|
46
|
+
/**
|
|
47
|
+
* Maximum attempts allowed for this delivery.
|
|
48
|
+
*/
|
|
49
|
+
maxAttempts: number;
|
|
50
|
+
/**
|
|
51
|
+
* Job name when the retry decision is for a job.
|
|
52
|
+
*/
|
|
53
|
+
jobName?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Return whether a failed attempt should be retried.
|
|
58
|
+
*/
|
|
59
|
+
export type JobRetryPredicate = (args: JobRetryPredicateArgs) => boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Job definition created by `defineJob(...)`.
|
|
63
|
+
*/
|
|
9
64
|
export interface JobDef<
|
|
10
65
|
Name extends string = string,
|
|
11
66
|
Payload extends StandardSchema = StandardSchema,
|
|
12
67
|
Ctx = unknown,
|
|
13
68
|
> {
|
|
69
|
+
/**
|
|
70
|
+
* Discriminator for job definitions.
|
|
71
|
+
*/
|
|
14
72
|
readonly kind: "job";
|
|
73
|
+
/**
|
|
74
|
+
* Stable job name used by dispatchers and provider adapters.
|
|
75
|
+
*/
|
|
15
76
|
readonly name: Name;
|
|
77
|
+
/**
|
|
78
|
+
* Standard Schema payload validator.
|
|
79
|
+
*/
|
|
16
80
|
readonly payload: Payload;
|
|
81
|
+
/**
|
|
82
|
+
* Optional human-readable description for docs and tooling.
|
|
83
|
+
*/
|
|
17
84
|
readonly description?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Retry metadata for durable job providers.
|
|
87
|
+
*/
|
|
18
88
|
readonly retry?: JobRetryOptions;
|
|
89
|
+
/**
|
|
90
|
+
* Handle a parsed job payload.
|
|
91
|
+
*/
|
|
19
92
|
handle(
|
|
20
93
|
args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>,
|
|
21
94
|
): MaybePromise<void>;
|
|
22
95
|
}
|
|
23
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Infer the parsed payload type for a job definition.
|
|
99
|
+
*/
|
|
24
100
|
export type InferJobPayload<J extends JobDef> =
|
|
25
101
|
J["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;
|
|
26
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Arguments passed to a job handler.
|
|
105
|
+
*/
|
|
27
106
|
export interface JobHandleArgs<J extends JobDef, Ctx> {
|
|
107
|
+
/**
|
|
108
|
+
* Job definition being handled.
|
|
109
|
+
*/
|
|
28
110
|
job: J;
|
|
111
|
+
/**
|
|
112
|
+
* Parsed job payload.
|
|
113
|
+
*/
|
|
29
114
|
payload: InferJobPayload<J>;
|
|
115
|
+
/**
|
|
116
|
+
* Handler context.
|
|
117
|
+
*/
|
|
30
118
|
ctx: Ctx;
|
|
31
119
|
}
|
|
32
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Retry metadata that durable job providers can map to their own retry model.
|
|
123
|
+
*/
|
|
33
124
|
export interface JobRetryOptions {
|
|
34
125
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
126
|
+
* Retry strategy. Raw objects without a strategy default to exponential
|
|
127
|
+
* backoff so existing `{ attempts }` style definitions stay meaningful.
|
|
128
|
+
*/
|
|
129
|
+
strategy?: JobRetryStrategy;
|
|
130
|
+
/**
|
|
131
|
+
* Maximum total attempts, including the first attempt.
|
|
39
132
|
*/
|
|
40
133
|
attempts?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Delay between attempts for fixed retry policies.
|
|
136
|
+
*/
|
|
137
|
+
delay?: JobRetryDuration;
|
|
138
|
+
/**
|
|
139
|
+
* Initial delay for exponential retry policies.
|
|
140
|
+
*/
|
|
141
|
+
initialDelay?: JobRetryDuration;
|
|
142
|
+
/**
|
|
143
|
+
* Maximum delay for exponential retry policies.
|
|
144
|
+
*/
|
|
145
|
+
maxDelay?: JobRetryDuration;
|
|
146
|
+
/**
|
|
147
|
+
* Exponential multiplier. Defaults to `2`.
|
|
148
|
+
*/
|
|
149
|
+
factor?: number;
|
|
150
|
+
/**
|
|
151
|
+
* Whether adapters that compute delays should add jitter.
|
|
152
|
+
*/
|
|
153
|
+
jitter?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Optional app-owned retry classifier.
|
|
156
|
+
*/
|
|
157
|
+
retryIf?: JobRetryPredicate;
|
|
41
158
|
}
|
|
42
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Options for declaring a typed job.
|
|
162
|
+
*/
|
|
43
163
|
export interface DefineJobOptions<
|
|
44
164
|
Name extends string,
|
|
45
165
|
Payload extends StandardSchema,
|
|
46
166
|
Ctx,
|
|
47
167
|
> {
|
|
168
|
+
/**
|
|
169
|
+
* Standard Schema payload validator.
|
|
170
|
+
*/
|
|
48
171
|
payload: Payload;
|
|
172
|
+
/**
|
|
173
|
+
* Optional human-readable description for docs and tooling.
|
|
174
|
+
*/
|
|
49
175
|
description?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Retry metadata for durable job providers.
|
|
178
|
+
*/
|
|
50
179
|
retry?: JobRetryOptions;
|
|
180
|
+
/**
|
|
181
|
+
* Handle a parsed job payload.
|
|
182
|
+
*/
|
|
51
183
|
handle(
|
|
52
184
|
args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>,
|
|
53
185
|
): MaybePromise<void>;
|
|
54
186
|
}
|
|
55
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Options for a fixed job retry policy.
|
|
190
|
+
*/
|
|
191
|
+
export interface FixedJobRetryOptions {
|
|
192
|
+
/**
|
|
193
|
+
* Maximum total attempts, including the first attempt.
|
|
194
|
+
*/
|
|
195
|
+
attempts: number;
|
|
196
|
+
/**
|
|
197
|
+
* Delay between attempts.
|
|
198
|
+
*/
|
|
199
|
+
delay: JobRetryDuration;
|
|
200
|
+
/**
|
|
201
|
+
* Optional app-owned retry classifier.
|
|
202
|
+
*/
|
|
203
|
+
retryIf?: JobRetryPredicate;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Options for an exponential job retry policy.
|
|
208
|
+
*/
|
|
209
|
+
export interface ExponentialJobRetryOptions {
|
|
210
|
+
/**
|
|
211
|
+
* Maximum total attempts, including the first attempt.
|
|
212
|
+
*/
|
|
213
|
+
attempts: number;
|
|
214
|
+
/**
|
|
215
|
+
* Initial delay. Defaults to `1s`.
|
|
216
|
+
*/
|
|
217
|
+
initialDelay?: JobRetryDuration;
|
|
218
|
+
/**
|
|
219
|
+
* Maximum delay. Defaults to `1m`.
|
|
220
|
+
*/
|
|
221
|
+
maxDelay?: JobRetryDuration;
|
|
222
|
+
/**
|
|
223
|
+
* Exponential multiplier. Defaults to `2`.
|
|
224
|
+
*/
|
|
225
|
+
factor?: number;
|
|
226
|
+
/**
|
|
227
|
+
* Whether computed delays should include jitter.
|
|
228
|
+
*/
|
|
229
|
+
jitter?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Optional app-owned retry classifier.
|
|
232
|
+
*/
|
|
233
|
+
retryIf?: JobRetryPredicate;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Retry helper namespace for job definitions.
|
|
238
|
+
*/
|
|
239
|
+
export const retry = {
|
|
240
|
+
/**
|
|
241
|
+
* Disable retries. The first failure is terminal.
|
|
242
|
+
*/
|
|
243
|
+
none(): JobRetryOptions {
|
|
244
|
+
return {
|
|
245
|
+
strategy: "none",
|
|
246
|
+
attempts: 1,
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Retry with the same delay between attempts.
|
|
252
|
+
*/
|
|
253
|
+
fixed(options: FixedJobRetryOptions): JobRetryOptions {
|
|
254
|
+
return validateJobRetryOptions({
|
|
255
|
+
strategy: "fixed",
|
|
256
|
+
attempts: options.attempts,
|
|
257
|
+
delay: options.delay,
|
|
258
|
+
retryIf: options.retryIf,
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Retry with exponential backoff.
|
|
264
|
+
*/
|
|
265
|
+
exponential(options: ExponentialJobRetryOptions): JobRetryOptions {
|
|
266
|
+
return validateJobRetryOptions({
|
|
267
|
+
strategy: "exponential",
|
|
268
|
+
attempts: options.attempts,
|
|
269
|
+
initialDelay: options.initialDelay,
|
|
270
|
+
maxDelay: options.maxDelay,
|
|
271
|
+
factor: options.factor,
|
|
272
|
+
jitter: options.jitter,
|
|
273
|
+
retryIf: options.retryIf,
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
} as const;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Options for the inline job dispatcher.
|
|
280
|
+
*/
|
|
56
281
|
export interface InlineJobDispatcherOptions<Ctx> {
|
|
282
|
+
/**
|
|
283
|
+
* Static job context or factory evaluated for each dispatched job.
|
|
284
|
+
*/
|
|
57
285
|
ctx?: Ctx | (() => MaybePromise<Ctx>);
|
|
286
|
+
/**
|
|
287
|
+
* Called when a dispatched inline job fails. When omitted, errors are
|
|
288
|
+
* rethrown to the caller.
|
|
289
|
+
*/
|
|
58
290
|
onError?: (error: unknown, job: JobDef<string, StandardSchema, Ctx>) => void;
|
|
59
291
|
}
|
|
60
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Local/test job dispatcher that executes job handlers inline.
|
|
295
|
+
*/
|
|
61
296
|
export interface InlineJobDispatcher<Ctx = unknown> {
|
|
297
|
+
/**
|
|
298
|
+
* Validate a payload and run the job handler immediately.
|
|
299
|
+
*/
|
|
62
300
|
dispatch<J extends JobDef<string, StandardSchema, Ctx>>(
|
|
63
301
|
job: J,
|
|
64
302
|
payload: InferJobPayload<J>,
|
|
65
303
|
): Promise<void>;
|
|
66
304
|
}
|
|
67
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Context-bound job helper factory.
|
|
308
|
+
*/
|
|
68
309
|
export interface JobHandlers<Ctx> {
|
|
310
|
+
/**
|
|
311
|
+
* Define a job with the bound context type.
|
|
312
|
+
*/
|
|
69
313
|
defineJob<Name extends string, Payload extends StandardSchema>(
|
|
70
314
|
name: Name,
|
|
71
315
|
options: DefineJobOptions<Name, Payload, Ctx>,
|
|
72
316
|
): JobDef<Name, Payload, Ctx>;
|
|
73
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Create an inline dispatcher with the bound context type.
|
|
320
|
+
*/
|
|
74
321
|
createInlineJobDispatcher(
|
|
75
322
|
options?: InlineJobDispatcherOptions<Ctx>,
|
|
76
323
|
): InlineJobDispatcher<Ctx>;
|
|
77
324
|
}
|
|
78
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Error thrown when job payload validation fails.
|
|
328
|
+
*/
|
|
79
329
|
export class JobValidationError extends Error {
|
|
330
|
+
/**
|
|
331
|
+
* Raw Standard Schema validation issues.
|
|
332
|
+
*/
|
|
80
333
|
readonly issues: readonly StandardSchemaV1.Issue[];
|
|
81
334
|
|
|
82
335
|
constructor(args: {
|
|
@@ -112,6 +365,165 @@ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
|
|
|
112
365
|
.join("; ");
|
|
113
366
|
}
|
|
114
367
|
|
|
368
|
+
function assertPositiveInteger(name: string, value: number): void {
|
|
369
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
370
|
+
throw new Error(`${name} must be a positive integer`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function assertPositiveNumber(name: string, value: number): void {
|
|
375
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
376
|
+
throw new Error(`${name} must be a positive number`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function durationToMs(name: string, value: JobRetryDuration): number {
|
|
381
|
+
if (typeof value === "number") {
|
|
382
|
+
assertPositiveInteger(name, value);
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const match = /^(\d+)(ms|s|m|h)$/.exec(value);
|
|
387
|
+
if (!match) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
`${name} must be a positive millisecond value or duration string like "500ms", "30s", "5m", or "1h".`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const amount = Number(match[1]);
|
|
394
|
+
assertPositiveInteger(name, amount);
|
|
395
|
+
|
|
396
|
+
switch (match[2]) {
|
|
397
|
+
case "ms":
|
|
398
|
+
return amount;
|
|
399
|
+
case "s":
|
|
400
|
+
return amount * 1000;
|
|
401
|
+
case "m":
|
|
402
|
+
return amount * 60_000;
|
|
403
|
+
case "h":
|
|
404
|
+
return amount * 3_600_000;
|
|
405
|
+
default:
|
|
406
|
+
throw new Error(`${name} has an unsupported duration unit.`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function validateJobRetryOptions(options: JobRetryOptions): JobRetryOptions {
|
|
411
|
+
const strategy = options.strategy ?? "exponential";
|
|
412
|
+
|
|
413
|
+
if (!["none", "fixed", "exponential"].includes(strategy)) {
|
|
414
|
+
throw new Error("retry.strategy must be none, fixed, or exponential");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const attempts = options.attempts ?? (strategy === "none" ? 1 : undefined);
|
|
418
|
+
if (attempts === undefined) {
|
|
419
|
+
throw new Error("retry.attempts is required");
|
|
420
|
+
}
|
|
421
|
+
assertPositiveInteger("retry.attempts", attempts);
|
|
422
|
+
|
|
423
|
+
if (strategy === "none" && attempts !== 1) {
|
|
424
|
+
throw new Error("retry.none() must use exactly one attempt");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (strategy === "fixed") {
|
|
428
|
+
if (options.delay === undefined) {
|
|
429
|
+
throw new Error("retry.delay is required for fixed retry policies");
|
|
430
|
+
}
|
|
431
|
+
durationToMs("retry.delay", options.delay);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (strategy === "exponential") {
|
|
435
|
+
if (options.initialDelay !== undefined) {
|
|
436
|
+
durationToMs("retry.initialDelay", options.initialDelay);
|
|
437
|
+
}
|
|
438
|
+
if (options.maxDelay !== undefined) {
|
|
439
|
+
durationToMs("retry.maxDelay", options.maxDelay);
|
|
440
|
+
}
|
|
441
|
+
if (options.factor !== undefined) {
|
|
442
|
+
assertPositiveNumber("retry.factor", options.factor);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
...options,
|
|
448
|
+
strategy,
|
|
449
|
+
attempts,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Return the maximum total attempts configured by a retry policy.
|
|
455
|
+
*/
|
|
456
|
+
export function getJobRetryMaxAttempts(
|
|
457
|
+
options: JobRetryOptions | undefined,
|
|
458
|
+
): number | undefined {
|
|
459
|
+
return options ? validateJobRetryOptions(options).attempts : undefined;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Return whether a failed job attempt should be retried.
|
|
464
|
+
*/
|
|
465
|
+
export function shouldRetryJob(
|
|
466
|
+
options: JobRetryOptions | undefined,
|
|
467
|
+
args: JobRetryPredicateArgs,
|
|
468
|
+
): boolean {
|
|
469
|
+
if (!options) return args.attempt < args.maxAttempts;
|
|
470
|
+
|
|
471
|
+
const retryOptions = validateJobRetryOptions(options);
|
|
472
|
+
const maxAttempts = Math.min(
|
|
473
|
+
args.maxAttempts,
|
|
474
|
+
retryOptions.attempts ?? args.maxAttempts,
|
|
475
|
+
);
|
|
476
|
+
if (retryOptions.strategy === "none") return false;
|
|
477
|
+
if (args.attempt >= maxAttempts) return false;
|
|
478
|
+
|
|
479
|
+
return retryOptions.retryIf?.({ ...args, maxAttempts }) ?? true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Compute the next retry delay in milliseconds for a failed job attempt.
|
|
484
|
+
*/
|
|
485
|
+
export function getJobRetryDelayMs(
|
|
486
|
+
options: JobRetryOptions | undefined,
|
|
487
|
+
args: Pick<JobRetryPredicateArgs, "attempt" | "error" | "jobName">,
|
|
488
|
+
): number {
|
|
489
|
+
const retryOptions = options
|
|
490
|
+
? validateJobRetryOptions(options)
|
|
491
|
+
: retry.exponential({ attempts: 3 });
|
|
492
|
+
|
|
493
|
+
let delayMs: number;
|
|
494
|
+
if (retryOptions.strategy === "fixed") {
|
|
495
|
+
delayMs = durationToMs("retry.delay", retryOptions.delay ?? "1s");
|
|
496
|
+
} else if (retryOptions.strategy === "none") {
|
|
497
|
+
delayMs = 0;
|
|
498
|
+
} else {
|
|
499
|
+
const initialDelayMs = durationToMs(
|
|
500
|
+
"retry.initialDelay",
|
|
501
|
+
retryOptions.initialDelay ?? "1s",
|
|
502
|
+
);
|
|
503
|
+
const maxDelayMs = durationToMs(
|
|
504
|
+
"retry.maxDelay",
|
|
505
|
+
retryOptions.maxDelay ?? "1m",
|
|
506
|
+
);
|
|
507
|
+
const factor = retryOptions.factor ?? 2;
|
|
508
|
+
delayMs = Math.min(
|
|
509
|
+
maxDelayMs,
|
|
510
|
+
initialDelayMs * factor ** Math.max(0, args.attempt - 1),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (retryOptions.jitter && delayMs > 0) {
|
|
515
|
+
delayMs = Math.ceil(delayMs * (0.5 + Math.random()));
|
|
516
|
+
if (retryOptions.strategy === "exponential") {
|
|
517
|
+
delayMs = Math.min(
|
|
518
|
+
delayMs,
|
|
519
|
+
durationToMs("retry.maxDelay", retryOptions.maxDelay ?? "1m"),
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return delayMs;
|
|
525
|
+
}
|
|
526
|
+
|
|
115
527
|
async function parsePayload<Schema extends StandardSchemaV1>(
|
|
116
528
|
schema: Schema,
|
|
117
529
|
input: unknown,
|
|
@@ -143,6 +555,13 @@ async function resolveCtx<Ctx>(
|
|
|
143
555
|
return ctx as Ctx;
|
|
144
556
|
}
|
|
145
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Define a typed job.
|
|
560
|
+
*
|
|
561
|
+
* Retry options are provider hints. Inline dispatchers validate payloads and
|
|
562
|
+
* call `handle(...)` immediately; durable providers may enqueue or schedule the
|
|
563
|
+
* job according to their own runtime.
|
|
564
|
+
*/
|
|
146
565
|
export function defineJob<
|
|
147
566
|
Name extends string,
|
|
148
567
|
Payload extends StandardSchema,
|
|
@@ -151,16 +570,23 @@ export function defineJob<
|
|
|
151
570
|
name: Name,
|
|
152
571
|
options: DefineJobOptions<Name, Payload, Ctx>,
|
|
153
572
|
): JobDef<Name, Payload, Ctx> {
|
|
573
|
+
const retryOptions = options.retry
|
|
574
|
+
? validateJobRetryOptions(options.retry)
|
|
575
|
+
: undefined;
|
|
576
|
+
|
|
154
577
|
return {
|
|
155
578
|
kind: "job",
|
|
156
579
|
name,
|
|
157
580
|
payload: options.payload,
|
|
158
581
|
description: options.description,
|
|
159
|
-
retry:
|
|
582
|
+
retry: retryOptions,
|
|
160
583
|
handle: options.handle as JobDef<Name, Payload, Ctx>["handle"],
|
|
161
584
|
};
|
|
162
585
|
}
|
|
163
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Validate and parse a job payload with the job's Standard Schema.
|
|
589
|
+
*/
|
|
164
590
|
export async function parseJobPayload<J extends JobDef>(
|
|
165
591
|
job: J,
|
|
166
592
|
payload: unknown,
|
|
@@ -170,6 +596,9 @@ export async function parseJobPayload<J extends JobDef>(
|
|
|
170
596
|
})) as InferJobPayload<J>;
|
|
171
597
|
}
|
|
172
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Create a local/test dispatcher that runs job handlers inline.
|
|
601
|
+
*/
|
|
173
602
|
export function createInlineJobDispatcher<Ctx>(
|
|
174
603
|
options: InlineJobDispatcherOptions<Ctx> = {},
|
|
175
604
|
): InlineJobDispatcher<Ctx> {
|
|
@@ -193,6 +622,9 @@ export function createInlineJobDispatcher<Ctx>(
|
|
|
193
622
|
};
|
|
194
623
|
}
|
|
195
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Create job helper methods bound to an application context type.
|
|
627
|
+
*/
|
|
196
628
|
export function createJobHandlers<Ctx>(): JobHandlers<Ctx> {
|
|
197
629
|
return {
|
|
198
630
|
defineJob<Name extends string, Payload extends StandardSchema>(
|