@softure-ai/jobs 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +86 -10
- package/dist/index.d.ts +55 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +69 -4
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +73 -10
- package/dist/options.js.map +1 -1
- package/dist/schema.d.ts +92 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +8 -1
- package/dist/schema.js.map +1 -1
- package/dist/server/claim.d.ts +4 -1
- package/dist/server/claim.d.ts.map +1 -1
- package/dist/server/claim.js +13 -3
- package/dist/server/claim.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/maintenance.d.ts +5 -2
- package/dist/server/maintenance.d.ts.map +1 -1
- package/dist/server/maintenance.js +5 -3
- package/dist/server/maintenance.js.map +1 -1
- package/dist/server/run-due.d.ts +1 -1
- package/dist/server/run-due.d.ts.map +1 -1
- package/dist/server/run-due.js +3 -1
- package/dist/server/run-due.js.map +1 -1
- package/dist/server/schedules.d.ts +49 -0
- package/dist/server/schedules.d.ts.map +1 -0
- package/dist/server/schedules.js +133 -0
- package/dist/server/schedules.js.map +1 -0
- package/dist/worker/run-job-worker.d.ts.map +1 -1
- package/dist/worker/run-job-worker.js +21 -3
- package/dist/worker/run-job-worker.js.map +1 -1
- package/dist/zoned-time.d.ts +19 -0
- package/dist/zoned-time.d.ts.map +1 -0
- package/dist/zoned-time.js +61 -0
- package/dist/zoned-time.js.map +1 -0
- package/migrations/0002_create_schedules.sql +12 -0
- package/module.json +2 -2
- package/package.json +1 -1
- package/src/index.ts +13 -4
- package/src/options.ts +109 -26
- package/src/schema.ts +9 -1
- package/src/server/claim.ts +11 -3
- package/src/server/index.ts +7 -0
- package/src/server/maintenance.ts +7 -3
- package/src/server/run-due.ts +3 -1
- package/src/server/schedules.ts +157 -0
- package/src/worker/run-job-worker.ts +18 -3
- package/src/zoned-time.ts +75 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
|
|
|
4
4
|
production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
|
|
5
5
|
"Definition of done").
|
|
6
6
|
|
|
7
|
+
## 0.1.2
|
|
8
|
+
|
|
9
|
+
- Schedules at local times of day (#554): `schedules: { "<queue>": { at: ["09:00", "21:00"], timeZone: "Europe/Warsaw" } }`
|
|
10
|
+
enqueues one run per listed local time, with the zone's offset on each date, so the run keeps its local time across
|
|
11
|
+
DST changes (a skipped time runs just after the change, a repeated one once). The same "once per slot however many
|
|
12
|
+
workers run" guarantee as periods, no migration. A schedule takes exactly one of `every` and `at`; `timeZone` is
|
|
13
|
+
required with `at` and refused with `every`. Period schedules are unchanged. `getQueueStats` reports an `at`
|
|
14
|
+
schedule's `everySeconds` as the longest gap between its times. New types `PeriodScheduleOptions` and
|
|
15
|
+
`LocalTimeScheduleOptions`; `JobScheduleOptions` is their union, so code that read `schedule.every` checks
|
|
16
|
+
`"every" in schedule` first.
|
|
17
|
+
- Migration for an app that runs a job at a local time from a host cron: add the schedule to `jobs({ schedules })`
|
|
18
|
+
(to move billing's reminder mail: `"billing.access-reminders": { at: ["09:00", "21:00"], timeZone: "<zone>" }`
|
|
19
|
+
after `...billingJobSchedules`, which replaces its daily UTC period), make sure the worker runs the queue's handler
|
|
20
|
+
(`...billingJobHandlers` or `createBillingJobHandlers({ reminders })`), then remove the host cron entry (the deploy
|
|
21
|
+
`maintain` hook) so the job does not run twice.
|
|
22
|
+
|
|
23
|
+
## 0.1.1
|
|
24
|
+
|
|
25
|
+
- Recurring schedules (#491): `jobs({ schedules: { "<queue>": { every: "1h", payload } } })`. Workers enqueue each
|
|
26
|
+
period's run once, however many run (a conditional upsert of the new table `jobs.schedules`, migration
|
|
27
|
+
`0002_create_schedules.sql`, in the same transaction as the job); missed periods collapse into one run; while a run
|
|
28
|
+
is still live the next period joins it. A schedule's queue takes the default queue settings unless `queues` names
|
|
29
|
+
it, and `queues` may be left out when schedules define them. `runDueJobs` enqueues due runs first;
|
|
30
|
+
`enqueueDueSchedules` serves apps that drain from a cron. `getQueueStats` reports each queue's `schedule`
|
|
31
|
+
(`everySeconds`, `lastEnqueuedAt`, `lastSucceededAt`, `isLate`). auth, mcp-access, waitlist, security and analytics
|
|
32
|
+
ship their prune handlers and schedules in their `./jobs` entries.
|
|
33
|
+
|
|
7
34
|
## 0.1.0
|
|
8
35
|
|
|
9
36
|
- First version (#471): background jobs in Postgres (`jobs.jobs`). `jobs({ queues })` in `softure.config.ts`;
|
|
@@ -13,3 +40,4 @@ production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../
|
|
|
13
40
|
lease, a heartbeat for long jobs, fenced completion, retry with exponential backoff and jitter, a terminal `failed`
|
|
14
41
|
state, pruning, and release of unfinished jobs on SIGTERM; `runDueJobs` (`@softure-ai/jobs/testing`) runs due jobs
|
|
15
42
|
in-process for tests and cron-driven apps.
|
|
43
|
+
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ received message.
|
|
|
9
9
|
|
|
10
10
|
A job queue in one table (`jobs.jobs`) with enqueue inside the caller's transaction and dedup keys, claims with
|
|
11
11
|
`FOR UPDATE SKIP LOCKED` under a lease, fenced completion, retry with exponential backoff, a worker process with
|
|
12
|
-
graceful shutdown,
|
|
12
|
+
graceful shutdown, recurring schedules that run once per period however many workers run, and an in-process runner
|
|
13
|
+
for tests and cron-driven apps.
|
|
13
14
|
|
|
14
15
|
## 2. Installation
|
|
15
16
|
|
|
@@ -46,12 +47,17 @@ export default defineSoftureConfig({
|
|
|
46
47
|
| `queues.<name>.backoff` | `{ baseSeconds: 10, maxSeconds: 3600 }` | delay after attempt n is `min(max, base * 2^(n-1))`, shortened by up to 20 % at random |
|
|
47
48
|
| `queues.<name>.leaseSeconds` | `300` | how long a claimed job is reserved; the worker extends it while the handler runs, so it bounds how long a crashed worker's job waits, not how long a handler may take |
|
|
48
49
|
| `queues.<name>.concurrency` | `1` | jobs of the queue one worker process runs at once |
|
|
50
|
+
| `schedules.<queue>.every` | (one of `every`, `at`) | how often the schedule runs: `<n>m`, `<n>h` or `<n>d`, from `1m` to `7d`, aligned to UTC (§3, "Schedules") |
|
|
51
|
+
| `schedules.<queue>.at` | (one of `every`, `at`) | local times of day the schedule runs at, `HH:MM` (`00:00` to `23:59`), each once, up to 48 |
|
|
52
|
+
| `schedules.<queue>.timeZone` | (required with `at`) | the IANA time zone of `at`, such as `Europe/Warsaw` or `UTC` |
|
|
53
|
+
| `schedules.<queue>.payload` | `{}` | the payload of every scheduled run |
|
|
49
54
|
| `pollIntervalMs` | `1000` | how often an idle worker looks for due jobs |
|
|
50
55
|
| `shutdownGraceMs` | `20000` | how long a stopping worker waits for running handlers before releasing their jobs |
|
|
51
56
|
| `retention` | `{ succeededDays: 7, failedDays: 30, cancelledDays: 7 }` | how long finished jobs are kept; the worker prunes once an hour |
|
|
52
57
|
|
|
53
58
|
A queue name is lowercase letters, digits, `_`, `.` and `-`. A queue the config does not define is refused by
|
|
54
|
-
`enqueueJob` and by the worker: a typo is a bug, not a new queue.
|
|
59
|
+
`enqueueJob` and by the worker: a typo is a bug, not a new queue. A schedule defines its queue too, with the default
|
|
60
|
+
settings unless `queues` names it; the config needs at least one queue or schedule.
|
|
55
61
|
|
|
56
62
|
### Handlers
|
|
57
63
|
|
|
@@ -148,11 +154,75 @@ logged and retried on the next round; a database restart does not end the worker
|
|
|
148
154
|
`runJobWorker({ db, config, handlers, signal })` is the loop without the process part, for an app that runs it inside
|
|
149
155
|
a process of its own.
|
|
150
156
|
|
|
157
|
+
### Schedules
|
|
158
|
+
|
|
159
|
+
A schedule runs its queue's handler once per period (`every`) or at local times of day (`at` in `timeZone`), instead
|
|
160
|
+
of an app cron:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { authJobSchedules } from "@softure-ai/auth/jobs";
|
|
164
|
+
import { securityJobSchedules } from "@softure-ai/security/jobs";
|
|
165
|
+
|
|
166
|
+
jobs({
|
|
167
|
+
schedules: {
|
|
168
|
+
...authJobSchedules, // auth.prune-sessions hourly, auth.prune-password-resets daily
|
|
169
|
+
...securityJobSchedules, // security.prune-rate-limits hourly
|
|
170
|
+
"app.digest": { every: "1d", payload: { kind: "weekly" } },
|
|
171
|
+
// The trial-ending reminder at 09:00 and 21:00 in the users' time zone, in place of billing's daily UTC period.
|
|
172
|
+
"billing.access-reminders": { at: ["09:00", "21:00"], timeZone: "Europe/Warsaw" },
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// src/jobs/handlers.ts: the packages' handlers next to the app's own
|
|
179
|
+
import { authJobHandlers } from "@softure-ai/auth/jobs";
|
|
180
|
+
import { securityJobHandlers } from "@softure-ai/security/jobs";
|
|
181
|
+
|
|
182
|
+
export const handlers: JobHandlers = { ...authJobHandlers, ...securityJobHandlers, "app.digest": digestHandler };
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- Periods start on whole multiples of `every` counted from 1970-01-01 00:00 UTC: `1h` on the full hour, `1d` at
|
|
186
|
+
midnight UTC, `15m` at :00, :15, :30, :45. A run is due at the start of its period.
|
|
187
|
+
- An `at` schedule's slots are its listed times on each local date in `timeZone`, with the zone's offset on that date:
|
|
188
|
+
`09:00` in `Europe/Warsaw` is 07:00 UTC in summer and 08:00 UTC in winter. A time a DST change skips runs as far
|
|
189
|
+
after the change as it would have been (`02:30` on the spring-forward day runs at 03:30); a time a DST change
|
|
190
|
+
repeats runs once, at its first occurrence. A worker that starts after a listed time enqueues that slot's run at
|
|
191
|
+
once, the same way a period schedule enqueues the period it starts in.
|
|
192
|
+
- Once per period, however many workers run: a worker takes a period with a conditional upsert of
|
|
193
|
+
`jobs.schedules` (the period must be later than the stored one) and enqueues the run in the same transaction, so a
|
|
194
|
+
missed or doubled tick cannot happen. A worker enqueues only the schedules of the queues it runs.
|
|
195
|
+
- Missed periods or times (no worker ran) collapse into one run for the current slot.
|
|
196
|
+
- Every scheduled run has the dedup key `schedule`: while a run is still pending or running (retrying a failure), the
|
|
197
|
+
next period joins it instead of queueing a second run behind it.
|
|
198
|
+
- Scheduled runs are ordinary jobs: retries, `maxAttempts`, `getJob`, `retryJob` and pruning apply.
|
|
199
|
+
- A package's prune handlers live in its `./jobs` entry (`@softure-ai/auth/jobs`, `@softure-ai/mcp-access/jobs`,
|
|
200
|
+
`@softure-ai/waitlist/jobs`, `@softure-ai/security/jobs`, `@softure-ai/analytics/jobs`), with `@softure-ai/jobs` as an
|
|
201
|
+
optional peer.
|
|
202
|
+
|
|
203
|
+
`getQueueStats` reports each scheduled queue's `schedule`: `everySeconds` (for an `at` schedule the longest gap
|
|
204
|
+
between two of its times, 24h for one time), `lastEnqueuedAt` (the start of the latest slot a run was enqueued for), `lastSucceededAt` (any succeeded job of the queue, a run enqueued by hand too) and
|
|
205
|
+
`isLate`: no success within two periods, counted from when a worker first saw the schedule, or no worker has seen it
|
|
206
|
+
at all. The module's health check does not fail on a late schedule (a late cleanup must not take the app out of
|
|
207
|
+
rotation); an app that wants an alarm adds a check of its own:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
ops({
|
|
211
|
+
checks: {
|
|
212
|
+
"app.schedules": async (context) => {
|
|
213
|
+
const late = (await getQueueStats(context as JobsContext)).filter((stats) => stats.schedule?.isLate === true);
|
|
214
|
+
return late.length === 0 ? ok() : err("app.schedules_late");
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
151
220
|
### Without a worker
|
|
152
221
|
|
|
153
|
-
`runDueJobs(ctx, { handlers })` (from `@softure-ai/jobs/server` or `/testing`)
|
|
154
|
-
|
|
155
|
-
`pruneJobs(ctx)` from the same cron.
|
|
222
|
+
`runDueJobs(ctx, { handlers })` (from `@softure-ai/jobs/server` or `/testing`) enqueues the due runs of the schedules
|
|
223
|
+
among its queues, then runs every due job once, in this process, one after another, and returns
|
|
224
|
+
`{ succeeded, retried, failed, stale }`. A cron can drain a queue with it; call `pruneJobs(ctx)` from the same cron.
|
|
225
|
+
`enqueueDueSchedules(ctx, { queues? })` only enqueues the due scheduled runs.
|
|
156
226
|
|
|
157
227
|
### Steering and overview
|
|
158
228
|
|
|
@@ -163,7 +233,7 @@ From `@softure-ai/jobs/server`, each on `ctx = { db, clock, config }`:
|
|
|
163
233
|
| `getJob(ctx, id)` | the `Job` or `null` |
|
|
164
234
|
| `cancelJob(ctx, id)` | a pending job becomes `cancelled`; `jobs.not_cancellable` for a running or finished one, `jobs.not_found` |
|
|
165
235
|
| `retryJob(ctx, id)` | a failed or cancelled job runs again from scratch (attempts 0, due now); `jobs.not_retryable`, `jobs.dedup_key_taken` while a live job has its key, `jobs.not_found` |
|
|
166
|
-
| `getQueueStats(ctx)` | per queue: counts per status, the due backlog
|
|
236
|
+
| `getQueueStats(ctx)` | per queue: counts per status, the due backlog, the `run_at` of its oldest due job and its `schedule` (or `null`) |
|
|
167
237
|
| `pruneJobs(ctx)` | deletes finished jobs past their retention; returns the count |
|
|
168
238
|
|
|
169
239
|
`getQueueStats` suits an app health check of its own, `ops({ checks: { "app.jobs": … } })`, e.g. unavailable when the
|
|
@@ -176,7 +246,7 @@ Nothing to mount: the module has no routes or pages. The worker is a process (§
|
|
|
176
246
|
|
|
177
247
|
## 5. Migrations and tables
|
|
178
248
|
|
|
179
|
-
Schema `jobs`,
|
|
249
|
+
Schema `jobs`, two tables, `migrations/0001_create_jobs.sql` and `migrations/0002_create_schedules.sql`:
|
|
180
250
|
|
|
181
251
|
`jobs.jobs`: `id` (bigint identity; a string in the API), `queue`, `payload` (jsonb), `status` (`pending`, `running`,
|
|
182
252
|
`succeeded`, `failed`, `cancelled`), `attempts`, `max_attempts`, `run_at`, `locked_until`, `locked_by` (the worker,
|
|
@@ -184,7 +254,11 @@ Schema `jobs`, one table, `migrations/0001_create_jobs.sql`:
|
|
|
184
254
|
`updated_at`, `finished_at`. Checks keep the lease columns set exactly while `running` and `finished_at` set exactly
|
|
185
255
|
for finished jobs; a partial unique index allows one live job per queue and dedup key.
|
|
186
256
|
|
|
187
|
-
|
|
257
|
+
`jobs.schedules`: `name` (the schedule's queue, primary key), `last_slot_at` (the start of the latest period a run
|
|
258
|
+
was enqueued for), `last_succeeded_at`, `created_at`, `updated_at`. One row per schedule a worker has seen; a row of
|
|
259
|
+
a schedule removed from the config stays and is ignored.
|
|
260
|
+
|
|
261
|
+
The app role needs `SELECT, INSERT, UPDATE, DELETE` on the tables, which the `@softure-ai/ops` roles recipe grants on
|
|
188
262
|
every schema the migrator creates.
|
|
189
263
|
|
|
190
264
|
## 6. Environment variables
|
|
@@ -207,7 +281,8 @@ No UI.
|
|
|
207
281
|
## 10. Hooks
|
|
208
282
|
|
|
209
283
|
The handlers (§3) are the app's code. `logger` (`info`, `warn`, `error`; `@softure-ai/observability`'s `Logger` fits)
|
|
210
|
-
receives the worker's lines: started, stopped, a thrown handler with its error, a lost lease,
|
|
284
|
+
receives the worker's lines: started, stopped, a scheduled run enqueued, a thrown handler with its error, a lost lease,
|
|
285
|
+
a release, pruning.
|
|
211
286
|
|
|
212
287
|
## 11. GDPR
|
|
213
288
|
|
|
@@ -218,7 +293,8 @@ logger gets payload paths, never values, for a payload that does not match its s
|
|
|
218
293
|
## 12. Limitations
|
|
219
294
|
|
|
220
295
|
- Polling, no `LISTEN/NOTIFY`: a job waits up to `pollIntervalMs` when the worker is idle.
|
|
221
|
-
-
|
|
296
|
+
- Schedules take a fixed period aligned to UTC (`every`) or times of day in a time zone (`at`); no cron expressions,
|
|
297
|
+
no days of the week.
|
|
222
298
|
- No priorities within a queue and no rate limits per queue beyond `concurrency`; split work into queues instead.
|
|
223
299
|
- At-least-once delivery (§3, "Handlers run at least once").
|
|
224
300
|
- PGlite has one connection, so tests run claims one after another; concurrent claiming is Postgres behaviour
|
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export declare const jobs: import("@softure-ai/core").ModuleFactory<{}, {
|
|
|
13
13
|
not_retryable: string;
|
|
14
14
|
dedup_key_taken: string;
|
|
15
15
|
};
|
|
16
|
-
}, import("zod").ZodObject<{
|
|
17
|
-
queues: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
|
|
16
|
+
}, import("zod").ZodPipe<import("zod").ZodObject<{
|
|
17
|
+
queues: import("zod").ZodDefault<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
|
|
18
18
|
maxAttempts: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
19
19
|
backoff: import("zod").ZodDefault<import("zod").ZodObject<{
|
|
20
20
|
baseSeconds: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
@@ -22,7 +22,18 @@ export declare const jobs: import("@softure-ai/core").ModuleFactory<{}, {
|
|
|
22
22
|
}, import("zod/v4/core").$strict>>;
|
|
23
23
|
leaseSeconds: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
24
24
|
concurrency: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
25
|
-
}, import("zod/v4/core").$strict
|
|
25
|
+
}, import("zod/v4/core").$strict>>>;
|
|
26
|
+
schedules: import("zod").ZodDefault<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodPipe<import("zod").ZodObject<{
|
|
27
|
+
every: import("zod").ZodOptional<import("zod").ZodString>;
|
|
28
|
+
at: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
29
|
+
timeZone: import("zod").ZodOptional<import("zod").ZodString>;
|
|
30
|
+
payload: import("zod").ZodDefault<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
|
|
31
|
+
}, import("zod/v4/core").$strict>, import("zod").ZodTransform<import("./options.js").PeriodScheduleOptions | import("./options.js").LocalTimeScheduleOptions, {
|
|
32
|
+
payload: Record<string, unknown>;
|
|
33
|
+
every?: string | undefined;
|
|
34
|
+
at?: string[] | undefined;
|
|
35
|
+
timeZone?: string | undefined;
|
|
36
|
+
}>>>>;
|
|
26
37
|
pollIntervalMs: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
27
38
|
shutdownGraceMs: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
28
39
|
retention: import("zod").ZodDefault<import("zod").ZodObject<{
|
|
@@ -30,10 +41,48 @@ export declare const jobs: import("@softure-ai/core").ModuleFactory<{}, {
|
|
|
30
41
|
failedDays: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
31
42
|
cancelledDays: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
32
43
|
}, import("zod/v4/core").$strict>>;
|
|
33
|
-
}, import("zod/v4/core").$strict
|
|
44
|
+
}, import("zod/v4/core").$strict>, import("zod").ZodTransform<{
|
|
45
|
+
queues: {
|
|
46
|
+
[x: string]: {
|
|
47
|
+
maxAttempts: number;
|
|
48
|
+
backoff: {
|
|
49
|
+
baseSeconds: number;
|
|
50
|
+
maxSeconds: number;
|
|
51
|
+
};
|
|
52
|
+
leaseSeconds: number;
|
|
53
|
+
concurrency: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
schedules: Record<string, import("./options.js").PeriodScheduleOptions | import("./options.js").LocalTimeScheduleOptions>;
|
|
57
|
+
pollIntervalMs: number;
|
|
58
|
+
shutdownGraceMs: number;
|
|
59
|
+
retention: {
|
|
60
|
+
succeededDays: number;
|
|
61
|
+
failedDays: number;
|
|
62
|
+
cancelledDays: number;
|
|
63
|
+
};
|
|
64
|
+
}, {
|
|
65
|
+
queues: Record<string, {
|
|
66
|
+
maxAttempts: number;
|
|
67
|
+
backoff: {
|
|
68
|
+
baseSeconds: number;
|
|
69
|
+
maxSeconds: number;
|
|
70
|
+
};
|
|
71
|
+
leaseSeconds: number;
|
|
72
|
+
concurrency: number;
|
|
73
|
+
}>;
|
|
74
|
+
schedules: Record<string, import("./options.js").PeriodScheduleOptions | import("./options.js").LocalTimeScheduleOptions>;
|
|
75
|
+
pollIntervalMs: number;
|
|
76
|
+
shutdownGraceMs: number;
|
|
77
|
+
retention: {
|
|
78
|
+
succeededDays: number;
|
|
79
|
+
failedDays: number;
|
|
80
|
+
cancelledDays: number;
|
|
81
|
+
};
|
|
82
|
+
}>>>;
|
|
34
83
|
export { JOB_STATUSES, type Job, type JobFailure, type JobOutcome, type JobsErrorCode, type JobStatus, } from "./contract.js";
|
|
35
84
|
export { jobsMessages, type JobsMessages } from "./messages/index.js";
|
|
36
|
-
export { jobsOptionsSchema, type JobQueueOptions, type JobsOptions, type JobsOptionsInput } from "./options.js";
|
|
85
|
+
export { jobsOptionsSchema, type JobQueueOptions, type JobScheduleOptions, type JobSchedulesInput, type JobsOptions, type JobsOptionsInput, type LocalTimeScheduleOptions, type PeriodScheduleOptions, } from "./options.js";
|
|
37
86
|
export { defineJobHandler, failJob, retryJobLater, type JobHandler, type JobHandlerContext, type JobHandlers } from "./handlers.js";
|
|
38
|
-
export { jobs as jobsTable } from "./schema.js";
|
|
87
|
+
export { jobs as jobsTable, schedules as jobSchedulesTable } from "./schema.js";
|
|
39
88
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,SAAS,SAAS,CAAC;AAEhC;;;GAGG;AACH,eAAO,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,SAAS,SAAS,CAAC;AAEhC;;;GAGG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiBf,CAAC;AAEH,OAAO,EACL,YAAY,EACZ,KAAK,GAAG,EACR,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EACL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACpI,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,SAAS,IAAI,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,10 +13,10 @@ export const MODULE_ID = "jobs";
|
|
|
13
13
|
export const jobs = defineModule({
|
|
14
14
|
manifest: {
|
|
15
15
|
id: MODULE_ID,
|
|
16
|
-
version: "0.1.
|
|
16
|
+
version: "0.1.2",
|
|
17
17
|
dependsOn: {},
|
|
18
18
|
dbSchema: "jobs",
|
|
19
|
-
tables: ["jobs"],
|
|
19
|
+
tables: ["jobs", "schedules"],
|
|
20
20
|
env: [],
|
|
21
21
|
switches: [],
|
|
22
22
|
routes: {},
|
|
@@ -30,7 +30,7 @@ export const jobs = defineModule({
|
|
|
30
30
|
});
|
|
31
31
|
export { JOB_STATUSES, } from "./contract.js";
|
|
32
32
|
export { jobsMessages } from "./messages/index.js";
|
|
33
|
-
export { jobsOptionsSchema } from "./options.js";
|
|
33
|
+
export { jobsOptionsSchema, } from "./options.js";
|
|
34
34
|
export { defineJobHandler, failJob, retryJobLater } from "./handlers.js";
|
|
35
|
-
export { jobs as jobsTable } from "./schema.js";
|
|
35
|
+
export { jobs as jobsTable, schedules as jobSchedulesTable } from "./schema.js";
|
|
36
36
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAC/G,oHAAoH;AACpH,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAC;IAC/B,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAC/G,oHAAoH;AACpH,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAC;IAC/B,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC;QAC7B,GAAG,EAAE,EAAE;QACP,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;KAC5C;IACD,QAAQ,EAAE,YAAY;IACtB,OAAO,EAAE,iBAAiB;IAC1B,UAAU,EAAE,EAAE,GAAG,EAAE,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE;IAC5E,MAAM,EAAE,cAAc;CACvB,CAAC,CAAC;AAEH,OAAO,EACL,YAAY,GAMb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EACL,iBAAiB,GAQlB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAA6D,MAAM,eAAe,CAAC;AACpI,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,SAAS,IAAI,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/options.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/** Seconds of an `every` text such as `15m`, `1h` or `1d`; null when it is not one or is outside 1m to 7d. */
|
|
3
|
+
export declare function parseEverySeconds(every: string): number | null;
|
|
4
|
+
/** A schedule that runs once per period, aligned to UTC. */
|
|
5
|
+
export interface PeriodScheduleOptions {
|
|
6
|
+
readonly every: string;
|
|
7
|
+
readonly payload: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/** A schedule that runs at local times of day in a time zone; `at` is sorted. */
|
|
10
|
+
export interface LocalTimeScheduleOptions {
|
|
11
|
+
readonly at: readonly string[];
|
|
12
|
+
readonly timeZone: string;
|
|
13
|
+
readonly payload: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export declare const jobsOptionsSchema: z.ZodPipe<z.ZodObject<{
|
|
16
|
+
queues: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
4
17
|
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
5
18
|
backoff: z.ZodDefault<z.ZodObject<{
|
|
6
19
|
baseSeconds: z.ZodDefault<z.ZodNumber>;
|
|
@@ -8,7 +21,18 @@ export declare const jobsOptionsSchema: z.ZodObject<{
|
|
|
8
21
|
}, z.core.$strict>>;
|
|
9
22
|
leaseSeconds: z.ZodDefault<z.ZodNumber>;
|
|
10
23
|
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
11
|
-
}, z.core.$strict
|
|
24
|
+
}, z.core.$strict>>>;
|
|
25
|
+
schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodObject<{
|
|
26
|
+
every: z.ZodOptional<z.ZodString>;
|
|
27
|
+
at: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
timeZone: z.ZodOptional<z.ZodString>;
|
|
29
|
+
payload: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
30
|
+
}, z.core.$strict>, z.ZodTransform<PeriodScheduleOptions | LocalTimeScheduleOptions, {
|
|
31
|
+
payload: Record<string, unknown>;
|
|
32
|
+
every?: string | undefined;
|
|
33
|
+
at?: string[] | undefined;
|
|
34
|
+
timeZone?: string | undefined;
|
|
35
|
+
}>>>>;
|
|
12
36
|
pollIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
13
37
|
shutdownGraceMs: z.ZodDefault<z.ZodNumber>;
|
|
14
38
|
retention: z.ZodDefault<z.ZodObject<{
|
|
@@ -16,8 +40,49 @@ export declare const jobsOptionsSchema: z.ZodObject<{
|
|
|
16
40
|
failedDays: z.ZodDefault<z.ZodNumber>;
|
|
17
41
|
cancelledDays: z.ZodDefault<z.ZodNumber>;
|
|
18
42
|
}, z.core.$strict>>;
|
|
19
|
-
}, z.core.$strict
|
|
43
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
44
|
+
queues: {
|
|
45
|
+
[x: string]: {
|
|
46
|
+
maxAttempts: number;
|
|
47
|
+
backoff: {
|
|
48
|
+
baseSeconds: number;
|
|
49
|
+
maxSeconds: number;
|
|
50
|
+
};
|
|
51
|
+
leaseSeconds: number;
|
|
52
|
+
concurrency: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
schedules: Record<string, PeriodScheduleOptions | LocalTimeScheduleOptions>;
|
|
56
|
+
pollIntervalMs: number;
|
|
57
|
+
shutdownGraceMs: number;
|
|
58
|
+
retention: {
|
|
59
|
+
succeededDays: number;
|
|
60
|
+
failedDays: number;
|
|
61
|
+
cancelledDays: number;
|
|
62
|
+
};
|
|
63
|
+
}, {
|
|
64
|
+
queues: Record<string, {
|
|
65
|
+
maxAttempts: number;
|
|
66
|
+
backoff: {
|
|
67
|
+
baseSeconds: number;
|
|
68
|
+
maxSeconds: number;
|
|
69
|
+
};
|
|
70
|
+
leaseSeconds: number;
|
|
71
|
+
concurrency: number;
|
|
72
|
+
}>;
|
|
73
|
+
schedules: Record<string, PeriodScheduleOptions | LocalTimeScheduleOptions>;
|
|
74
|
+
pollIntervalMs: number;
|
|
75
|
+
shutdownGraceMs: number;
|
|
76
|
+
retention: {
|
|
77
|
+
succeededDays: number;
|
|
78
|
+
failedDays: number;
|
|
79
|
+
cancelledDays: number;
|
|
80
|
+
};
|
|
81
|
+
}>>;
|
|
20
82
|
export type JobsOptionsInput = z.input<typeof jobsOptionsSchema>;
|
|
21
83
|
export type JobsOptions = z.output<typeof jobsOptionsSchema>;
|
|
22
84
|
export type JobQueueOptions = JobsOptions["queues"][string];
|
|
85
|
+
export type JobScheduleOptions = JobsOptions["schedules"][string];
|
|
86
|
+
/** Schedules as an app or a package writes them, e.g. a package's ready-made prune schedules. */
|
|
87
|
+
export type JobSchedulesInput = NonNullable<JobsOptionsInput["schedules"]>;
|
|
23
88
|
//# sourceMappingURL=options.d.ts.map
|
package/dist/options.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBtB,8GAA8G;AAC9G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK9D;AAyCH,4DAA4D;AAC5D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAyBD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyB1B,CAAC;AAQL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC7D,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5D,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,iGAAiG;AACjG,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC"}
|
package/dist/options.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The options an app passes to `jobs({ ... })` in softure.config.ts, parsed at startup.
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { isTimeZone } from "./zoned-time.js";
|
|
3
4
|
const QUEUE_NAME = /^[a-z][a-z0-9_.-]{0,62}$/;
|
|
4
5
|
const QUEUE_NAME_HINT = "is not a queue name: lowercase letters, digits, _ . and -, starting with a letter";
|
|
5
6
|
const DAY_SECONDS = 24 * 60 * 60;
|
|
@@ -14,6 +15,55 @@ const backoffSchema = z
|
|
|
14
15
|
message: "maxSeconds must be at least baseSeconds",
|
|
15
16
|
path: ["maxSeconds"],
|
|
16
17
|
});
|
|
18
|
+
const EVERY = /^([1-9][0-9]{0,4})(m|h|d)$/;
|
|
19
|
+
const UNIT_SECONDS = { m: 60, h: 60 * 60, d: DAY_SECONDS };
|
|
20
|
+
const MAX_EVERY_SECONDS = 7 * DAY_SECONDS;
|
|
21
|
+
const EVERY_HINT = "must be a whole number of minutes, hours or days (e.g. 15m, 1h, 1d), from 1m to 7d";
|
|
22
|
+
/** Seconds of an `every` text such as `15m`, `1h` or `1d`; null when it is not one or is outside 1m to 7d. */
|
|
23
|
+
export function parseEverySeconds(every) {
|
|
24
|
+
const match = EVERY.exec(every);
|
|
25
|
+
if (match === null)
|
|
26
|
+
return null;
|
|
27
|
+
const seconds = Number(match[1]) * UNIT_SECONDS[match[2]];
|
|
28
|
+
return seconds <= MAX_EVERY_SECONDS ? seconds : null;
|
|
29
|
+
}
|
|
30
|
+
const AT_TIME = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
|
|
31
|
+
const AT_HINT = "must be times of day as HH:MM (00:00 to 23:59), each once";
|
|
32
|
+
const MAX_AT_TIMES = 48;
|
|
33
|
+
const scheduleSchema = z
|
|
34
|
+
.strictObject({
|
|
35
|
+
/** How often the schedule runs: `15m`, `1h`, `1d`. Runs start on whole periods counted from 1970-01-01 00:00 UTC. */
|
|
36
|
+
every: z.string().refine((every) => parseEverySeconds(every) !== null, { message: EVERY_HINT }).optional(),
|
|
37
|
+
/** Instead of `every`: the local times of day the schedule runs at, `HH:MM`, in `timeZone`. */
|
|
38
|
+
at: z
|
|
39
|
+
.array(z.string().regex(AT_TIME, { message: AT_HINT }))
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(MAX_AT_TIMES)
|
|
42
|
+
.refine((times) => new Set(times).size === times.length, { message: AT_HINT })
|
|
43
|
+
.optional(),
|
|
44
|
+
/** The IANA time zone of `at`, such as `Europe/Warsaw`; required with `at`. */
|
|
45
|
+
timeZone: z.string().refine(isTimeZone, { message: "is not a time zone the runtime knows (an IANA name such as Europe/Warsaw)" }).optional(),
|
|
46
|
+
/** The payload of every run; default `{}`. */
|
|
47
|
+
payload: z.record(z.string(), z.unknown()).default({}),
|
|
48
|
+
})
|
|
49
|
+
.superRefine((schedule, context) => {
|
|
50
|
+
if ((schedule.every === undefined) === (schedule.at === undefined)) {
|
|
51
|
+
context.addIssue({ code: "custom", path: ["every"], message: "set either every or at, not both" });
|
|
52
|
+
}
|
|
53
|
+
if (schedule.at !== undefined && schedule.timeZone === undefined) {
|
|
54
|
+
context.addIssue({ code: "custom", path: ["timeZone"], message: "is required with at" });
|
|
55
|
+
}
|
|
56
|
+
if (schedule.every !== undefined && schedule.timeZone !== undefined) {
|
|
57
|
+
context.addIssue({ code: "custom", path: ["timeZone"], message: "applies to at only; every runs in UTC" });
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.transform((schedule) => {
|
|
61
|
+
if (schedule.at !== undefined && schedule.timeZone !== undefined) {
|
|
62
|
+
return { at: [...schedule.at].sort(), timeZone: schedule.timeZone, payload: schedule.payload };
|
|
63
|
+
}
|
|
64
|
+
// The refinement above leaves every set whenever at is not.
|
|
65
|
+
return { every: schedule.every ?? "", payload: schedule.payload };
|
|
66
|
+
});
|
|
17
67
|
const queueSchema = z.strictObject({
|
|
18
68
|
/** Attempts before a job is `failed` for good; the first run counts. */
|
|
19
69
|
maxAttempts: z.number().int().min(1).max(1000).default(5),
|
|
@@ -35,22 +85,35 @@ const retentionSchema = z.strictObject({
|
|
|
35
85
|
/** Days a cancelled job is kept. */
|
|
36
86
|
cancelledDays: z.number().int().min(0).max(3650).default(7),
|
|
37
87
|
});
|
|
38
|
-
export const jobsOptionsSchema = z
|
|
88
|
+
export const jobsOptionsSchema = z
|
|
89
|
+
.strictObject({
|
|
39
90
|
/** The queues, by name. A job is enqueued to one of them and runs with its settings. */
|
|
40
|
-
queues: z.record(z.string(), queueSchema).superRefine(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
}),
|
|
91
|
+
queues: z.record(z.string(), queueSchema).default({}).superRefine(checkNames),
|
|
92
|
+
/**
|
|
93
|
+
* Recurring runs, by queue name: the worker enqueues one job per period (`every`) or per local time of day (`at`
|
|
94
|
+
* in `timeZone`) however many workers run. A schedule's queue takes the default queue settings unless `queues`
|
|
95
|
+
* names it.
|
|
96
|
+
*/
|
|
97
|
+
schedules: z.record(z.string(), scheduleSchema).default({}).superRefine(checkNames),
|
|
49
98
|
/** How long an idle worker waits before it looks for due jobs again. */
|
|
50
99
|
pollIntervalMs: z.number().int().min(50).max(60_000).default(1000),
|
|
51
100
|
/** How long a stopping worker waits for running handlers before it releases their jobs. */
|
|
52
101
|
shutdownGraceMs: z.number().int().min(0).max(10 * 60_000).default(20_000),
|
|
53
102
|
/** How long finished jobs are kept; the worker prunes once an hour. */
|
|
54
103
|
retention: retentionSchema.default({ succeededDays: 7, failedDays: 30, cancelledDays: 7 }),
|
|
104
|
+
})
|
|
105
|
+
.superRefine((options, context) => {
|
|
106
|
+
if (Object.keys(options.queues).length === 0 && Object.keys(options.schedules).length === 0) {
|
|
107
|
+
context.addIssue({ code: "custom", path: ["queues"], message: "must define at least one queue or schedule" });
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.transform((options) => {
|
|
111
|
+
const scheduledOnly = Object.keys(options.schedules).filter((name) => !Object.hasOwn(options.queues, name));
|
|
112
|
+
return { ...options, queues: { ...options.queues, ...Object.fromEntries(scheduledOnly.map((name) => [name, queueSchema.parse({})])) } };
|
|
55
113
|
});
|
|
114
|
+
function checkNames(record, context) {
|
|
115
|
+
for (const name of Object.keys(record).filter((candidate) => !QUEUE_NAME.test(candidate))) {
|
|
116
|
+
context.addIssue({ code: "custom", path: [name], message: QUEUE_NAME_HINT });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
56
119
|
//# sourceMappingURL=options.js.map
|
package/dist/options.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|