@usehenri/jobs 0.0.0 → 1.2.0
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 +311 -0
- package/LICENSE +21 -0
- package/README.md +8 -1
- package/index.js +45 -0
- package/module.js +8 -0
- package/package.json +50 -10
- package/src/batch.js +379 -0
- package/src/config.js +186 -0
- package/src/cron.js +237 -0
- package/src/definitions.js +236 -0
- package/src/duration.js +112 -0
- package/src/errors.js +115 -0
- package/src/jobs.js +1839 -0
- package/src/keys.js +65 -0
- package/src/module.js +442 -0
- package/src/runner.js +918 -0
- package/src/serialize.js +177 -0
- package/src/store/index.js +37 -0
- package/src/store/mongo.js +1334 -0
- package/src/store/schema.js +499 -0
- package/src/store/sql.js +1744 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# @usehenri/jobs
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#342](https://github.com/usehenri/henri/pull/342) [`1b316c6`](https://github.com/usehenri/henri/commit/1b316c6f3c5d5eb5752c70b534092d1052956cc6) Thanks [@reel](https://github.com/reel)! - Real background jobs, in a new package: `@usehenri/jobs`.
|
|
8
|
+
|
|
9
|
+
What henri called workers was a hook that ran a file at boot, and the documented example was a `setInterval`. There was no way to say "send this mail later" or "process this upload in the background": no queue, no retries, no schedule, and nothing to look at when something failed. Rails has had that for a decade and now ships a database-backed queue by default; this is henri's.
|
|
10
|
+
|
|
11
|
+
A job is `app/jobs/<name>.js` exporting `perform(args, context)` alongside its `queue`, `priority`, `maxAttempts`, `timeout` and `backoff` — the shape models and controllers already use. `henri generate job <name>` writes one and `henri destroy job <name>` removes it. The application enqueues with `henri.jobs.perform(name, args, options)`, `performIn(delay, ...)` and `performAt(date, ...)`, which write one row and return; `performNow()` runs one inline for tests and the console. Arguments are stored as JSON and anything that would not survive the round trip — a model instance, a function, `undefined`, a `Date` that would come back a string, a cycle — is refused with the path that holds it instead of being dropped silently.
|
|
12
|
+
|
|
13
|
+
`henri jobs` runs a worker process: it claims jobs, performs `jobs.concurrency` of them at a time, honours the recurring schedules, puts back the jobs of runners that died and stops on `SIGINT`, `SIGTERM` and `SIGQUIT` after finishing what it holds. `--queue` limits it to some queues and `--once` drains what is due and exits. Several runners are meant to run at once against one database: claiming is a single statement on every dialect — `FOR UPDATE SKIP LOCKED` on PostgreSQL, `UPDATE ... ORDER BY ... LIMIT` on MySQL, `UPDLOCK, READPAST` on MSSQL, a subquery on sqlite, an atomic `findOneAndUpdate` per document on MongoDB — so it is its own transaction, the state is part of its own `WHERE`, and the rows it took carry a token it reads them back by. No job is ever performed twice because two runners raced.
|
|
14
|
+
|
|
15
|
+
A job that throws is retried with an exponential backoff and, once out of attempts, kept in a **dead letter queue** with its error, its stack and the history of every attempt. The queue is at-least-once, so the guide says to write a job the way you would write a webhook handler: the outcome of an attempt is fenced by the token of the claim it belongs to, so a runner that was recovered from cannot write over the one that took its job, but a job that was recovered from does run twice. `henri jobs:dead`, `jobs:show <id>`, `jobs:retry` and `jobs:discard` drive it from the command line, `henri.jobs.dead.*` from the application. `henri jobs:status` gives the counts by queue and state, the timings of the finished jobs and how long the oldest due job has waited — with `--json`, like every other henri command.
|
|
16
|
+
|
|
17
|
+
Recurring jobs are declared under `jobs.recurring` in the configuration (a five-field cron expression read in UTC, or a plain `every` interval) and honoured by the runner itself, with no second process. Missed runs do not pile up: the moment that follows is computed from now, so an hour of downtime on an hourly job costs one run, not sixty.
|
|
18
|
+
|
|
19
|
+
The queue owns `henri_jobs` and `henri_jobs_schedules` and reaches them through the store adapter's own `query()` or its MongoDB collections, never through a model, so it cannot collide with the application's schema and works on a store that has no models at all. `henri jobs:install` creates the tables and is idempotent; the boot creates them too unless `jobs.install` says otherwise.
|
|
20
|
+
|
|
21
|
+
`henri.mailers.deliverLater()` now goes through it: core registers the delivery handler, and the rendered message becomes a job on the `mailers` queue, retried and visible like any other.
|
|
22
|
+
|
|
23
|
+
`app/workers` is untouched and still the right answer for a long-lived process that starts with the server. The [Jobs guide](https://usehenri.io/guides/jobs/) says when to reach for which.
|
|
24
|
+
|
|
25
|
+
- [#437](https://github.com/usehenri/henri/pull/437) [`d074e8b`](https://github.com/usehenri/henri/commit/d074e8b482582e25f80d8a14b4735e69a2b7821e) Thanks [@reel](https://github.com/reel)! - Job batches: forty jobs, and one that runs when they are all done.
|
|
26
|
+
`henri.jobs.batch({ callback, args, jobs })` enqueues them and seals the batch,
|
|
27
|
+
or takes a function that adds them itself for a list too long to write out. The
|
|
28
|
+
callback is an ordinary job of `app/jobs` and is handed the counts under
|
|
29
|
+
`batch`.
|
|
30
|
+
|
|
31
|
+
A batch **finishes**, it does not succeed: the callback runs once every job has
|
|
32
|
+
reached a terminal state, `dead` included, so a batch that half failed still
|
|
33
|
+
calls it and `failed` is a number to branch on. It runs **exactly once**, and
|
|
34
|
+
never before the last job is terminal. `total` is written once, when the batch
|
|
35
|
+
is sealed; `done` is advanced by a single `SET done = done + 1` guarded by the
|
|
36
|
+
claim token of the attempt that wrote the outcome, so the counter is never read
|
|
37
|
+
into the process to be written back and it moves for exactly one runner — the
|
|
38
|
+
one whose outcome landed. The callback is then enqueued under a unique key of
|
|
39
|
+
the batch's own, which makes settling idempotent, and the runner's sweep counts
|
|
40
|
+
the rows of a batch nothing else could count (a runner killed between an
|
|
41
|
+
outcome and its count, a job the recovery buried).
|
|
42
|
+
|
|
43
|
+
An existing queue gains one column and one table, added by the same idempotent
|
|
44
|
+
`henri jobs:install` the boot already runs and tolerated the same way: an
|
|
45
|
+
application that makes no batch is unaffected, and `batch()` on a store that has
|
|
46
|
+
neither is refused (`HENRI_JOB_BATCH_UNINSTALLED`) rather than counting nowhere.
|
|
47
|
+
|
|
48
|
+
`henri jobs:batches`, `henri jobs:list --batch <id>`, `henri jobs:status` and
|
|
49
|
+
`henri.jobs.batches.*` are how a batch is read back.
|
|
50
|
+
|
|
51
|
+
- [#370](https://github.com/usehenri/henri/pull/370) [`d9f3be4`](https://github.com/usehenri/henri/commit/d9f3be49c5929d929a220220bf6e72fdcb135595) Thanks [@reel](https://github.com/reel)! - Every failure henri raises now has a stable code.
|
|
52
|
+
|
|
53
|
+
Rust prints `E0382`, TypeScript `TS2345`, Next.js a link to a page. henri had
|
|
54
|
+
four names, all in the command line — `USAGE`, `FAILED`, `NOT_A_PROJECT`,
|
|
55
|
+
`NEEDS_TTY` — and its runtime failures had none at all: a boot that stopped, a
|
|
56
|
+
model that would not load, a store that refused a schema key, a view engine
|
|
57
|
+
that was missing, all of them a message and nothing else. A message gets
|
|
58
|
+
reworded; a code does not.
|
|
59
|
+
|
|
60
|
+
Ninety-one of them, in one namespace across core, the adapters, the queue, the
|
|
61
|
+
view engines, the command line and `henri mcp`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
HENRI_MODEL_UNKNOWN_TYPE
|
|
65
|
+
HENRI_BOOT_CIRCULAR_DEPENDENCY
|
|
66
|
+
HENRI_STORE_URL_MISSING
|
|
67
|
+
HENRI_VIEW_INERTIA_UNAVAILABLE
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`HENRI_` makes the whole code unique enough to search the web with, the area
|
|
71
|
+
says which part of the framework raised it, and the reason reads without a
|
|
72
|
+
lookup — the shape of node's own `ERR_*` codes, and of the four names the
|
|
73
|
+
command line already had.
|
|
74
|
+
|
|
75
|
+
The code reaches you wherever the failure does. In the boot log:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
view ✏ HENRI_VIEW_UNKNOWN_RENDERER => Unable to load 'reactt' renderer...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
In the error body of the JSON API, next to what it already answered with:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"statusCode": 500,
|
|
86
|
+
"error": "Internal Server Error",
|
|
87
|
+
"code": "HENRI_STORE_NOT_STARTED",
|
|
88
|
+
"message": "Internal Server Error"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
In the terminal and in `--json`, where a boot failure now keeps the code of
|
|
93
|
+
what actually went wrong instead of collapsing into `FAILED`:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
$ henri server
|
|
97
|
+
henri server failed [HENRI_CONFIG_ENV_TYPE]: HENRI_CONFIG__port is not a number, and "port" is one in the configuration
|
|
98
|
+
|
|
99
|
+
$ henri server --json
|
|
100
|
+
{"error":{"code":"HENRI_CONFIG_ENV_TYPE","command":"server","exitCode":1,"hint":null,"message":"..."}}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
And in the answers of `henri mcp`, so an agent branches on the code rather
|
|
104
|
+
than on the wording.
|
|
105
|
+
|
|
106
|
+
The catalogue is `packages/core/error-codes.json`: one entry per code with
|
|
107
|
+
what it means, what usually causes it and how to fix it, published as
|
|
108
|
+
[the error code reference](https://usehenri.io/reference/errors/). It is data,
|
|
109
|
+
and a test compares it with the source and with the page — every code raised
|
|
110
|
+
has an entry, every entry is raised somewhere, no two mean the same thing.
|
|
111
|
+
|
|
112
|
+
`config.errors.url` turns a code into a link. It is a template holding
|
|
113
|
+
`{code}` (`"https://example.com/e/{code}"`), unset by default: henri ships no
|
|
114
|
+
address, and nothing prints a link until you give it one.
|
|
115
|
+
|
|
116
|
+
**Breaking**: the `code` of `henri <command> --json` now names the failure
|
|
117
|
+
rather than the exit status. `USAGE` is `HENRI_CLI_USAGE`, `FAILED`
|
|
118
|
+
`HENRI_CLI_FAILED`, `NOT_A_PROJECT` `HENRI_CLI_NOT_A_PROJECT`, `NEEDS_TTY`
|
|
119
|
+
`HENRI_CLI_NEEDS_TTY`, `CONFIG_INVALID` `HENRI_CONFIG_INVALID`; a command may
|
|
120
|
+
now answer something finer still. The `exitCode`, and the exit status itself,
|
|
121
|
+
are unchanged: a script branching on `0`, `1`, `2`, `3` or `4` keeps working.
|
|
122
|
+
The codes of `@usehenri/jobs` (`UNKNOWN_JOB`, `BAD_ARGUMENTS`, `TIMEOUT`, ...)
|
|
123
|
+
and of `henri mcp` (`NO_SERVER`, `UNREACHABLE`, ...) moved into the same
|
|
124
|
+
namespace for the same reason.
|
|
125
|
+
|
|
126
|
+
- [#357](https://github.com/usehenri/henri/pull/357) [`5ccd537`](https://github.com/usehenri/henri/commit/5ccd537b3621b54d11e7f24ccca39643ae7d5cf7) Thanks [@reel](https://github.com/reel)! - `@usehenri/jobs` registers itself
|
|
127
|
+
|
|
128
|
+
The queue was already a package, but `@usehenri/core` carried the module that
|
|
129
|
+
loaded it: an application without `@usehenri/jobs` still had a `henri.jobs`,
|
|
130
|
+
an inert object whose every method explained what to install. The package
|
|
131
|
+
ships the henri module itself now (`"henri": { "module": "./module.js" }`),
|
|
132
|
+
the way `@usehenri/graphql` does, so depending on it is what puts
|
|
133
|
+
`henri.jobs` in the boot -- at level 4, so `henri jobs` still binds no port.
|
|
134
|
+
|
|
135
|
+
An application that uses jobs needs `@usehenri/jobs` in its `package.json`,
|
|
136
|
+
which it almost certainly already has -- nothing worked without it. `henri
|
|
137
|
+
doctor` reports it as a missing dependency as soon as `app/jobs` holds a file
|
|
138
|
+
or the configuration has a `jobs` block, and `henri jobs` says the same.
|
|
139
|
+
Nothing else changes: the queue, the runner, the retries, the dead letter
|
|
140
|
+
queue, the recurring schedules, the tables and every `henri jobs` command are
|
|
141
|
+
untouched, and installing the package is still not the same as using it -- an
|
|
142
|
+
application with neither `app/jobs` nor a `jobs` block creates no table.
|
|
143
|
+
|
|
144
|
+
An application that does not use jobs has nothing to install and hears
|
|
145
|
+
nothing at boot. `henri.jobs` is `undefined` rather than an object that does
|
|
146
|
+
nothing, which the type declarations say too, so code reading it guards with
|
|
147
|
+
`henri.jobs &&`. The one thing that now speaks up is a mail that cannot be
|
|
148
|
+
delivered when it was asked to be: `deliverLater({ wait })` or
|
|
149
|
+
`deliverLater({ at })` without a queue fails with the install line instead of
|
|
150
|
+
sending the message immediately. `deliverLater()` with nothing to honour is
|
|
151
|
+
unchanged -- it delivers out of band, silently.
|
|
152
|
+
|
|
153
|
+
`henri new` does not add the dependency: the scaffold's `app/jobs` is empty,
|
|
154
|
+
so the module would be inert in every application that never writes a job.
|
|
155
|
+
|
|
156
|
+
- [#434](https://github.com/usehenri/henri/pull/434) [`2625067`](https://github.com/usehenri/henri/commit/26250673d91ab70ad024739d02b647754f75267d) Thanks [@reel](https://github.com/reel)! - Per-job concurrency limits. `henri jobs --concurrency` bounds a runner; a job
|
|
157
|
+
now bounds itself across every runner: `concurrency: 1` for one at a time,
|
|
158
|
+
`concurrency: { limit: 3, key: 'tenantId' }` for three per key, and `group` for
|
|
159
|
+
several jobs sharing one bound. The permit is taken before the work, from a
|
|
160
|
+
table the queue owns whose primary key is the bound — the one primitive that
|
|
161
|
+
means the same thing on PostgreSQL, MySQL, MSSQL, sqlite and MongoDB, which an
|
|
162
|
+
in-claim `COUNT(*)` does not. The claim statement keeps its shape and an
|
|
163
|
+
application with no limited job sends the one it always sent.
|
|
164
|
+
|
|
165
|
+
An existing queue gains one column, added by the same idempotent
|
|
166
|
+
`henri jobs:install` the boot already runs. It is tolerated: an application
|
|
167
|
+
with no limited job is unaffected whether it applied or not, and one that
|
|
168
|
+
declares a limit whose table cannot hold it refuses to start
|
|
169
|
+
(`HENRI_JOB_LIMIT_UNINSTALLED`) rather than running unbounded. A job already in
|
|
170
|
+
the queue when the limit was declared is bounded too.
|
|
171
|
+
|
|
172
|
+
`henri jobs:status` and `henri.jobs.limits()` report what was asked for and
|
|
173
|
+
which slots are held. The guide says why henri mounts no dashboard, and what
|
|
174
|
+
to build one from.
|
|
175
|
+
|
|
176
|
+
- [#400](https://github.com/usehenri/henri/pull/400) [`2689779`](https://github.com/usehenri/henri/commit/26897798b840fd28a4bc091c050a83457b36905d) Thanks [@reel](https://github.com/reel)! - OpenTelemetry, out of the box: `henri.telemetry`.
|
|
177
|
+
|
|
178
|
+
henri answers three observability questions already — `pen` says what happened, `henri.reporter` says what failed, `henri.analyze()` says what the boot did — and each of them from one process. None of them says where the time went inside one request, and none of them follows that request into the queue, the mail transport or a webhook receiver. This is the fourth: a trace, through OpenTelemetry, which is the only vendor-neutral way to hand one to whatever the deployment already runs.
|
|
179
|
+
|
|
180
|
+
**henri ships the instrumentation and none of the pipeline.** `@opentelemetry/api` is an **optional peer dependency**; there is no SDK, no exporter, no sampler, no resource and no collector address, because those belong to the deployment that knows the service name, the environment and how much traffic it can afford to keep. The guide shows the smallest bootstrap that works, and it is the application's file.
|
|
181
|
+
|
|
182
|
+
**An application that does not install the api pays nothing**, and nothing here is not a flag tested per request. Nothing is installed at all: the middleware is not mounted, the store adapter is not wrapped, no instrument is created, `@opentelemetry/api` is never required, and there is no boot line, because there is nothing to say. Measured on an empty JSON route at 30 000 requests: the baseline with the package absent, +1.3 to 2.2 µs per request with it present and no SDK registered, +2.4 to 4.6 µs while exporting.
|
|
183
|
+
|
|
184
|
+
**What a span carries is the error reporter's rule, word for word**, because a span attribute is a log field with a different name on it. A request span is named for its route _pattern_ (`GET /artworks/:id`) and carries the method, the route, the status and `henri.request_id` — the four of them, from `requestOf()`, which is literally the reporter's own function, so the two cannot drift. Nothing that came from the client is in a span: no url, no path, no query, no body, no parameters, no headers, no user, no SQL text, no mail recipient, no job arguments and no webhook body. That is a deliberate departure from the HTTP semantic conventions, which ask a server span for `url.path`, and the reason is the reporter's: `/users/ada@example.com` is a path in some applications and a personal field in all of them. Attributes an application passes to `henri.telemetry.span()` go through the masking of a log line.
|
|
185
|
+
|
|
186
|
+
The children are the boundaries henri already knows, without reaching into anybody's driver: `adapter.query()` (the raw SQL henri runs on its own behalf — the queue's claim, the trail's insert), the view render, the mail send, a job run in `@usehenri/jobs` and a delivery attempt in `@usehenri/webhooks`. A model call an application makes is deliberately not one: that is `@opentelemetry/instrumentation-pg` and its siblings, registered in the same bootstrap, and their spans land under henri's request span because the context is already active. The **boot** is a span too, and it is reconstructed from `henri.analyze()` after the fact, with the timings it had already measured — so nothing is timed twice and nothing runs during a boot for the sake of a trace. A boot that failed is emitted with the module that failed carrying the error.
|
|
187
|
+
|
|
188
|
+
**Propagation, and which identifier wins.** An incoming `traceparent` is honoured, so a request that arrives inside somebody else's trace continues it; `henri.telemetry.inject()` writes one onto the requests henri makes for the application, which today is a webhook delivery, inside the span of the attempt the receiver is answering. `traceparent` decides the trace and `X-Request-Id` decides the request id, and **neither is derived from the other**: inventing a trace id from a request id would orphan the parent that is waiting, and a trace usually spans several requests, so a trace id is not unique per request. The span carries `henri.request_id`, which is the join between a log line and a trace. `traceparent` is not written onto the response, because it would hand an internal identifier to whoever asked and `X-Request-Id` already answers that need.
|
|
189
|
+
|
|
190
|
+
**The metrics are the four that mean something**: `http.server.request.duration` by method, route and status — whose count _is_ the request count, which is why there is no counter beside it — `henri.jobs.queue.depth`, `henri.jobs.claim.duration` and `henri.cache.operations`. The last is read straight off the counters `henri.cache.stats()` already kept, and it and the queue depth are observable instruments, so nothing is recorded while a request runs.
|
|
191
|
+
|
|
192
|
+
**When the exporter is down or slow, a span is dropped** — the cache's rule, where a dead backend is a miss. henri owns none of the pipeline: nothing is ever awaited, `forceFlush()` is never called, there is no queue and no map of open spans here, and every span ends in a `finally` or when the response closes. A sampled-out span costs an object. A tracer that throws is logged and, after five failures, telemetry turns itself off for the life of the process and says so once.
|
|
193
|
+
|
|
194
|
+
`config.telemetry` is `{ enabled, metrics, propagate, spans }`, or `false`. Leave it out and telemetry follows the package: on when `@opentelemetry/api` resolves from the application, off when it does not. `enabled: true` says the application requires it, and a boot without the package then fails with `HENRI_TELEMETRY_UNAVAILABLE` rather than going quiet.
|
|
195
|
+
|
|
196
|
+
`henri doctor` reports `deps.declared` when `enabled` is `true` and the package is in no `package.json`, so that boot failure is one a check catches first.
|
|
197
|
+
|
|
198
|
+
- [#383](https://github.com/usehenri/henri/pull/383) [`72cd1d3`](https://github.com/usehenri/henri/commit/72cd1d35ffb99311bbca815c1f6ab41ee3682f64) Thanks [@reel](https://github.com/reel)! - Outbound webhooks, signed and retried, in a new package: `@usehenri/webhooks`.
|
|
199
|
+
|
|
200
|
+
Every application that integrates with anything eventually pushes events to a url a customer typed into a settings form. Everyone writes it by hand, and everyone gets the same two parts wrong: the signature, which is what lets the receiver believe the request, and the url, which is a server-side request forgery with a registration form in front of it. The queue landing in 1.1 made the rest of it nearly free, so here it is.
|
|
201
|
+
|
|
202
|
+
The surface is one call: `await henri.webhooks.emit('invoice.paid', payload)` writes one row per subscribed endpoint and returns. `register()`, `rotate()`, `disable()` and `remove()` manage the endpoints from the application, and `henri webhooks:add|list|show|update|rotate|disable|enable|remove|send|status|install` from the command line. Endpoints carry an `owner`, which is the tenant they belong to: an `emit()` with an owner reaches that owner's endpoints, one without reaches the endpoints that have none, and henri will not fan an event across tenants for you.
|
|
203
|
+
|
|
204
|
+
**Signing** follows [Standard Webhooks](https://www.standardwebhooks.com) to the byte — `webhook-id`, `webhook-timestamp` and `webhook-signature`, HMAC-SHA256 over `id.timestamp.body`, base64, behind a `v1,` scheme prefix — so a receiver with any Standard Webhooks library verifies henri with nothing written. GitHub and Shopify sign only the body, which replays forever and cannot be deduplicated; Stripe adds the timestamp and is what this is closest to. henri differs from Stripe in three ways on purpose: the timestamp is its own header, the signature is base64, and **the delivery id is part of the signed content** — the same `webhook-id` on every attempt of one delivery, so "have I already processed this?" is answered from authenticated bytes rather than from a body nobody has verified yet. Nothing a receiver could route on is sent outside the signature: the event type is in the signed body and in no header. The guide carries the verification snippet a receiver pastes, and `signature.spec.js` runs that exact transcription against what the package signs, so the page cannot drift from the code. Secrets rotate with a grace during which both sign, and are stored encrypted (AES-256-GCM under a key derived from `secret`).
|
|
205
|
+
|
|
206
|
+
**Delivery is a job** and nothing else, so the retries, the exponential backoff, the dead letter queue and the operator's view of all of it are the queue's: `henri jobs:list --queue webhooks`, `henri jobs:dead`, `henri jobs:show <id>`, `henri jobs:retry <id>`. There is no deliveries table. Eight attempts, ten seconds tripling to six hours — about three days. A `2xx` is a delivery; a `5xx`, a timeout, a connection error and any other `4xx` are retried; a `3xx` is a failure that is **not** retried and is never followed, because a redirect is the receiver choosing a url after henri checked the one it was given; a `410 Gone` is not retried either and disables the endpoint.
|
|
207
|
+
|
|
208
|
+
**A url is checked when the request is made**, not when it was registered, because DNS answers differently later. Every address the name resolves to is refused if it is loopback, link-local (where `169.254.169.254` lives), private, carrier-grade NAT, multicast, reserved, documentation, or an IPv4 address wearing an IPv6 costume — and one bad answer refuses the name. The socket then connects to the address that was checked and to no other, so nothing can resolve the name a second time in between. `webhooks.allowPrivate` and `webhooks.allowHttp` lift it for a development configuration, and `henri audit` reports either of them in production (`webhooks.private-addresses-allowed`, a new A10:2021 category in the report, and `webhooks.http-allowed`).
|
|
209
|
+
|
|
210
|
+
Two seams were added to `@usehenri/jobs` for it, and both are useful on their own. `henri.jobs.define(name, definition)` lets a package ship a job of its own without asking every application to write a file that forwards the call — a file of `app/jobs` with the same name still wins. And an error carrying `retryable: false` is buried on the spot with its reason instead of spending every attempt to learn the same thing: a `410 Gone`, an address that must not be reached, a payload a remote API will refuse identically in six hours.
|
|
211
|
+
|
|
212
|
+
`henri new` does not install any of this. The guide is [Webhooks](https://usehenri.io/guides/webhooks/), and it says what is deliberately left out: receiving webhooks, a UI, a subscription policy richer than `invoice.*`, `Retry-After`, ordering guarantees, and anything that needs a message broker.
|
|
213
|
+
|
|
214
|
+
- [#445](https://github.com/usehenri/henri/pull/445) [`808d824`](https://github.com/usehenri/henri/commit/808d82471d59e64ccc735f617bab293eb572c46b) Thanks [@reel](https://github.com/reel)! - The queue and the version table know which tenant a row belongs to.
|
|
215
|
+
|
|
216
|
+
`henri_jobs` gains a `tenant` column. `henri.jobs.perform()` stamps the tenant
|
|
217
|
+
of the request or job it was called from, the way `henri.webhooks.emit()`
|
|
218
|
+
already defaults its `owner`, and **the runner enters that tenant before it
|
|
219
|
+
calls `perform()`** — so a job that touches a tenanted model no longer needs a
|
|
220
|
+
first line putting the tenant back, and forgetting it is no longer a job that
|
|
221
|
+
dies in the dead letter queue. A row with no tenant enters no scope, because
|
|
222
|
+
null is not every tenant: a recurring occurrence, a job enqueued from a script,
|
|
223
|
+
one enqueued with `tenant: null` and every row that predates the column all
|
|
224
|
+
behave exactly as they did. Naming a different tenant while one is in scope is
|
|
225
|
+
`HENRI_TENANT_CROSS_WRITE`. `henri jobs:list --tenant`, `jobs:dead`,
|
|
226
|
+
`jobs:retry --all`, `jobs:discard --all` and `jobs:perform` take it, `jobs:show`
|
|
227
|
+
prints it and every `--json` job carries it. A batch carries the tenant it was
|
|
228
|
+
made in, so its callback is performed there too.
|
|
229
|
+
|
|
230
|
+
`henri_versions` gains one as well, read off the record's own tenant column
|
|
231
|
+
rather than off the scope, so a sweep running across every customer still names
|
|
232
|
+
the right one. `henri.versions.list()`, `count()`, `of()` and `get()` are
|
|
233
|
+
narrowed to the tenant in scope and **refused** (`HENRI_TENANT_REQUIRED`) when
|
|
234
|
+
there is none — a table of everybody's old values is read the way a tenanted
|
|
235
|
+
model is. `restore()` on a record that is _gone_ creates it, and a create is
|
|
236
|
+
stamped with the tenant in scope, so restoring another tenant's version (or one
|
|
237
|
+
written before the column existed) is `HENRI_VERSION_CROSS_TENANT` rather than
|
|
238
|
+
that record quietly appearing here. `henri versions`, `versions:show` and
|
|
239
|
+
`versions:restore` run across every tenant like the operator commands they are,
|
|
240
|
+
take `--tenant`, and reify as the tenant the row names.
|
|
241
|
+
|
|
242
|
+
`henri privacy:export`, `henri privacy:erase` and `henri retention:sweep` now
|
|
243
|
+
walk the models inside `henri.tenancy.unscoped()`. They were unusable on a
|
|
244
|
+
tenanted model before this: from a command line there is no tenant in scope, so
|
|
245
|
+
the first model call raised `HENRI_TENANT_REQUIRED`.
|
|
246
|
+
|
|
247
|
+
**An application with no `config.tenancy` is unaffected**: no column is asked
|
|
248
|
+
for, nothing is stamped, nothing is narrowed and there is no boot line. Both
|
|
249
|
+
columns arrive through the tolerated `ALTER` of the idempotent install, and both
|
|
250
|
+
stores ask the table rather than trusting it ran — so an application that turned
|
|
251
|
+
tenancy on and whose table cannot hold the column fails the boot naming it
|
|
252
|
+
(`HENRI_JOB_TENANT_UNINSTALLED`, `HENRI_VERSION_TENANT_UNINSTALLED`) instead of
|
|
253
|
+
writing rows nothing can scope. For most people the upgrade is the next deploy.
|
|
254
|
+
|
|
255
|
+
- [#381](https://github.com/usehenri/henri/pull/381) [`41470bf`](https://github.com/usehenri/henri/commit/41470bf378d83ca3d35d00e8c31796fea5eb15e0) Thanks [@reel](https://github.com/reel)! - Retention, and the access trail.
|
|
256
|
+
|
|
257
|
+
A model now says how long it keeps its records, in its options:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
options: {
|
|
261
|
+
retention: { action: 'anonymize', after: '2y', from: 'decidedAt' },
|
|
262
|
+
},
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`action` is one of the three verbs henri already had -- `delete`,
|
|
266
|
+
`soft-delete` (only on a `paranoid` model) and `anonymize` (exactly what an
|
|
267
|
+
erasure writes) -- and `from` is the date column the clock starts on, which
|
|
268
|
+
is rarely `createdAt`. A record whose `from` is null never ages out and is
|
|
269
|
+
counted separately. A model with more than one class of records writes a
|
|
270
|
+
list of named rules with a `where` each.
|
|
271
|
+
|
|
272
|
+
`henri.retention.sweep()` enforces them and needs nothing installed:
|
|
273
|
+
`henri retention:sweep --yes` is what a cron line runs, and with
|
|
274
|
+
`@usehenri/jobs` installed `config.retention.schedule` registers the
|
|
275
|
+
recurring `henri/retention` job. The boot says which of the two it is, by
|
|
276
|
+
name, so a rule nothing applies is never silent.
|
|
277
|
+
|
|
278
|
+
Two things stand between a wrong rule and a deleted table: a rule writes
|
|
279
|
+
nothing until its token is in `config.retention.approved` (a line in the
|
|
280
|
+
configuration, so a person, a diff and a review), and
|
|
281
|
+
`config.retention.batch` bounds one run. `henri retention:sweep` without
|
|
282
|
+
`--yes` plans, counts and prints, and writes nothing. Every sweep leaves a
|
|
283
|
+
receipt in `config.retention.receipts`.
|
|
284
|
+
|
|
285
|
+
`config.trail` turns on the access trail: an append-only, hash-chained
|
|
286
|
+
record of who read or changed personal data, in a table henri owns. It
|
|
287
|
+
records the export, the erasure, every retention sweep and -- with
|
|
288
|
+
`trail.reads` -- the answers henri serializes; `henri.trail.record()` is how
|
|
289
|
+
an application adds its own. It holds field names, counts, public
|
|
290
|
+
identifiers and digests, and refuses anything else
|
|
291
|
+
(`HENRI_TRAIL_VALUE_REFUSED`). `henri trail`, `henri trail:about <who>` (which
|
|
292
|
+
answers from an address whose digest is all that is stored) and
|
|
293
|
+
`henri trail:verify` read it back.
|
|
294
|
+
|
|
295
|
+
`henri.jobs.recur(name, entry)` is the seam a framework module uses to ask
|
|
296
|
+
for a schedule the configuration did not write; an entry the application
|
|
297
|
+
declared under the same name still wins.
|
|
298
|
+
|
|
299
|
+
The drizzle adapter no longer offers to drop the tables henri owns
|
|
300
|
+
(`henri_jobs`, `henri_jobs_schedules`, `henri_trail`): a push that obeyed
|
|
301
|
+
would have taken an application's job history or its audit trail with it.
|
|
302
|
+
|
|
303
|
+
### Patch Changes
|
|
304
|
+
|
|
305
|
+
- [#359](https://github.com/usehenri/henri/pull/359) [`a4d53bf`](https://github.com/usehenri/henri/commit/a4d53bf155d0d6f62800864258fa49615a794a9d) Thanks [@reel](https://github.com/reel)! - A duration with a long run of whitespace is refused in constant time
|
|
306
|
+
|
|
307
|
+
`'5m'`, `'2h'` and their friends were matched by a pattern with `\s*` at both ends. Around an optional group that is quadratic: on `'1'` followed by sixty thousand spaces the two star quantifiers split the run between them one position at a time, which measured two seconds before the value was refused. A duration reaches the parser from `henri jobs:perform --in=`, and from whatever an application passes to `enqueue()`, so it is not always a value its author typed.
|
|
308
|
+
|
|
309
|
+
The surrounding whitespace is trimmed before the pattern runs rather than matched by it. The same input is now refused in under a millisecond, and a test pins it.
|
|
310
|
+
- Updated dependencies [[`792a15a`](https://github.com/usehenri/henri/commit/792a15ade614cf8b920d9197586f9866700d458e), [`1e23664`](https://github.com/usehenri/henri/commit/1e23664829bd1a356de28f404cfb21c9ae211388), [`5b627ad`](https://github.com/usehenri/henri/commit/5b627adfa37e9f16bc75af96cc8ff5308a91f688), [`4dff51e`](https://github.com/usehenri/henri/commit/4dff51edc050e29398c793a5aedb48776b7b7119), [`60dbf33`](https://github.com/usehenri/henri/commit/60dbf33c2a4f14e328a0df1cb44be061e15431e5), [`1b316c6`](https://github.com/usehenri/henri/commit/1b316c6f3c5d5eb5752c70b534092d1052956cc6), [`d074e8b`](https://github.com/usehenri/henri/commit/d074e8b482582e25f80d8a14b4735e69a2b7821e), [`7fd13f6`](https://github.com/usehenri/henri/commit/7fd13f631b75f7aa152b73046b50c6902ae3ca93), [`b559fb7`](https://github.com/usehenri/henri/commit/b559fb72b391eeb21a3f6a0cda1515e01ecbfafc), [`1c0dfe8`](https://github.com/usehenri/henri/commit/1c0dfe84a98eff2122512256c4f42ec7ccde4212), [`93060a8`](https://github.com/usehenri/henri/commit/93060a86df795dbbd99bf1895beb0cf14c4b86de), [`e031900`](https://github.com/usehenri/henri/commit/e031900082f28aec72af4cda9cd959f932e2ebc7), [`9173000`](https://github.com/usehenri/henri/commit/91730005efa88f073bfaaf67078c3ec0e137b459), [`62fac46`](https://github.com/usehenri/henri/commit/62fac46fd6cae5581979b73daf99700fd246e0ea), [`b7f33e2`](https://github.com/usehenri/henri/commit/b7f33e28a5e4844391befd75d08e42c4cf6212ed), [`3d6f3fc`](https://github.com/usehenri/henri/commit/3d6f3fc048d05db41be86069608d342e437408cb), [`b278119`](https://github.com/usehenri/henri/commit/b2781190de436eb5838e866446c9a0c8210bb6ca), [`b161e1b`](https://github.com/usehenri/henri/commit/b161e1b8fad94af2d2afc351dc1bc07dabbb1379), [`7cb0b04`](https://github.com/usehenri/henri/commit/7cb0b04b29b61dedaa82fcd1972646fb3765acfc), [`1616e34`](https://github.com/usehenri/henri/commit/1616e343a612be2bffffcfa5b23bfa8ad191bbe3), [`c8f5367`](https://github.com/usehenri/henri/commit/c8f53678b33341d086b467f801e959314afc7860), [`bcf4ce2`](https://github.com/usehenri/henri/commit/bcf4ce22bcd294844504164fac1fa4aef1ffec41), [`ab5a8e4`](https://github.com/usehenri/henri/commit/ab5a8e4a88c80cca070a2b8ad398c80babdaff11), [`43d267f`](https://github.com/usehenri/henri/commit/43d267f0f9d192b2c01e89c3925b7daf5000041b), [`9f868f3`](https://github.com/usehenri/henri/commit/9f868f3d9162fa218e34304110210e6949f97d5c), [`d88bf7f`](https://github.com/usehenri/henri/commit/d88bf7fe038a6b58e7bed02ff4c90755f6c0e65e), [`49398a6`](https://github.com/usehenri/henri/commit/49398a6308f0760f01c6ff2ec98aaa35f484474d), [`89dda62`](https://github.com/usehenri/henri/commit/89dda62da456a0a55600e79cfb65ce89f11258e2), [`62fac46`](https://github.com/usehenri/henri/commit/62fac46fd6cae5581979b73daf99700fd246e0ea), [`a93d6cc`](https://github.com/usehenri/henri/commit/a93d6cc39b33b261089e91f3e757b54fefc9fe15), [`d9f3be4`](https://github.com/usehenri/henri/commit/d9f3be49c5929d929a220220bf6e72fdcb135595), [`c44f025`](https://github.com/usehenri/henri/commit/c44f025acec3d5bbbb57e2310d02184a1053a10d), [`67cfb20`](https://github.com/usehenri/henri/commit/67cfb200ea0e0b31bacf2af183db6467b0fa011d), [`e661f98`](https://github.com/usehenri/henri/commit/e661f98fe8f8acce15aa10ce2dc320c5a2cb006f), [`43a0e1a`](https://github.com/usehenri/henri/commit/43a0e1a6a320baa43298391e1c1e0334d6cd28d5), [`cee57b9`](https://github.com/usehenri/henri/commit/cee57b9d3521a4a70c715222eae1f18ff4a6c128), [`61bf75c`](https://github.com/usehenri/henri/commit/61bf75cbaccda1aecff34408a175b2d85447d7a8), [`2c8a826`](https://github.com/usehenri/henri/commit/2c8a8265262dbf6ea5c3e73e8e7892a230d4d0f0), [`46d5dbc`](https://github.com/usehenri/henri/commit/46d5dbcc983c03e96ae5a87d7288c5d8a5adbc24), [`ba97ea9`](https://github.com/usehenri/henri/commit/ba97ea968f0b34cd67b7a3e803ecd34543b8aaaf), [`5ccd537`](https://github.com/usehenri/henri/commit/5ccd537b3621b54d11e7f24ccca39643ae7d5cf7), [`d88bf7f`](https://github.com/usehenri/henri/commit/d88bf7fe038a6b58e7bed02ff4c90755f6c0e65e), [`9895cbf`](https://github.com/usehenri/henri/commit/9895cbf4be85b476e341a5be915e3049e5a027de), [`61bf75c`](https://github.com/usehenri/henri/commit/61bf75cbaccda1aecff34408a175b2d85447d7a8), [`5a150d5`](https://github.com/usehenri/henri/commit/5a150d576208571c32b9cd12827d035e31ed4313), [`3c1c5b8`](https://github.com/usehenri/henri/commit/3c1c5b83ea135b000ddd6ffbfd05457b000f2f7c), [`2625067`](https://github.com/usehenri/henri/commit/26250673d91ab70ad024739d02b647754f75267d), [`aa3f90b`](https://github.com/usehenri/henri/commit/aa3f90bd6f42bb05431c33f3e4bf2202cb6bb7c6), [`a1c6769`](https://github.com/usehenri/henri/commit/a1c6769099e3dc28b22b5338a2a57b13bdf69f7a), [`0a8bb41`](https://github.com/usehenri/henri/commit/0a8bb415d352cd75b12d07e591c8ec7c16774a99), [`ec1c8c4`](https://github.com/usehenri/henri/commit/ec1c8c419f4d9063a7617472b2970fdb8a929fa1), [`dd2731d`](https://github.com/usehenri/henri/commit/dd2731d6a20fd96aa1be1aeb5e6ec0155001326b), [`ab52e18`](https://github.com/usehenri/henri/commit/ab52e187c420dfe381f03ed51c5c141fda525acb), [`b7b56e1`](https://github.com/usehenri/henri/commit/b7b56e190ae774abc0096fe2aebaf91f823115af), [`b7038ce`](https://github.com/usehenri/henri/commit/b7038ceaa430f4a0b9eaf7e983fc2844421bf636), [`1ea0f85`](https://github.com/usehenri/henri/commit/1ea0f85066b86fba31f58937cc10abb6359e6a26), [`762062a`](https://github.com/usehenri/henri/commit/762062aadc450d49b1a2d15524f9d579ab4f60e7), [`01a561a`](https://github.com/usehenri/henri/commit/01a561aa58650ec15df1c2659795a5e4c5bbfd53), [`bd1b630`](https://github.com/usehenri/henri/commit/bd1b63083b3817b8c47b8a187de76027458a1b32), [`27b5513`](https://github.com/usehenri/henri/commit/27b5513d1cae8aba734fe27da0ace2a82423e2f4), [`e31a3f7`](https://github.com/usehenri/henri/commit/e31a3f73e7e8facf3cedf7460f115e57995f32c3), [`2689779`](https://github.com/usehenri/henri/commit/26897798b840fd28a4bc091c050a83457b36905d), [`72cd1d3`](https://github.com/usehenri/henri/commit/72cd1d35ffb99311bbca815c1f6ab41ee3682f64), [`a2e1ec2`](https://github.com/usehenri/henri/commit/a2e1ec29df52462f12ebaae9bfbc1ad4f427b27f), [`afead74`](https://github.com/usehenri/henri/commit/afead7489498ed42e1893a25123ea772cac2ca09), [`a4ecba5`](https://github.com/usehenri/henri/commit/a4ecba50c663f4d5c741adbb6cd9bc0eefe0e5cc), [`baec3fd`](https://github.com/usehenri/henri/commit/baec3fd22be92bf8ffbaeb251b0b6c2771f8347a), [`1103628`](https://github.com/usehenri/henri/commit/110362808f8ec6d73a75ff7fc89a77f3e943d773), [`e865d94`](https://github.com/usehenri/henri/commit/e865d945d65419ac676f5a2fe3ba3b6114a1e53d), [`4274567`](https://github.com/usehenri/henri/commit/4274567e20a980657f07df9ec7db25296c7d55f5), [`aea429c`](https://github.com/usehenri/henri/commit/aea429ca99338a62370ab3e3d94bdc6b8c227601), [`8a8e3b3`](https://github.com/usehenri/henri/commit/8a8e3b33d7967b81f66633aa25c3075318f01d60), [`18715f9`](https://github.com/usehenri/henri/commit/18715f90ea8958dc57da1bb029b8209c36b84cc6), [`0d2ebc3`](https://github.com/usehenri/henri/commit/0d2ebc344bfcd80533ef900638083e4c105407bb), [`49398a6`](https://github.com/usehenri/henri/commit/49398a6308f0760f01c6ff2ec98aaa35f484474d), [`ec64e44`](https://github.com/usehenri/henri/commit/ec64e44e7b79a02da5fc587a72a9a6c900836982), [`831aa5c`](https://github.com/usehenri/henri/commit/831aa5c011f3432630c68b8d26755d2582f82f74), [`16824e8`](https://github.com/usehenri/henri/commit/16824e8fe9ccc6a04dab5d9b2481c29ff4f6b64b), [`fda9366`](https://github.com/usehenri/henri/commit/fda9366e9ed2b072764a995c5aa60205ca7a4725), [`1a86acb`](https://github.com/usehenri/henri/commit/1a86acbf15e4a43e5fb81277bb22e101c06e77a4), [`808d824`](https://github.com/usehenri/henri/commit/808d82471d59e64ccc735f617bab293eb572c46b), [`0b32fbd`](https://github.com/usehenri/henri/commit/0b32fbde19c95da8fe07fab76933840a4242c71c), [`c0c16e8`](https://github.com/usehenri/henri/commit/c0c16e873ba440aee9832160553cbb12ab81bd2c), [`41470bf`](https://github.com/usehenri/henri/commit/41470bf378d83ca3d35d00e8c31796fea5eb15e0), [`8e44e7e`](https://github.com/usehenri/henri/commit/8e44e7e882dd8741b3ac632651b453389d76bf2c), [`de1c1e0`](https://github.com/usehenri/henri/commit/de1c1e02ed83d13dcfeb8e44012f309eb663f03e), [`4b4677d`](https://github.com/usehenri/henri/commit/4b4677d4a09d39fe50b1fa4af577600342578daf)]:
|
|
311
|
+
- @usehenri/core@1.2.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-present, Félix-Antoine Paradis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<!-- generated by scripts/prepublish.js -->
|
|
2
|
+
|
|
1
3
|
# @usehenri/jobs
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Henri background jobs: a database backed queue with retries, a dead letter queue and recurring jobs.
|
|
6
|
+
|
|
7
|
+
Part of [henri](https://usehenri.io), the Rails-like React framework for
|
|
8
|
+
Node.js: [documentation](https://usehenri.io),
|
|
9
|
+
[source and issues](https://github.com/usehenri/henri),
|
|
10
|
+
[changelog](https://github.com/usehenri/henri/blob/master/packages/jobs/CHANGELOG.md).
|
package/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const config = require('./src/config');
|
|
2
|
+
const cron = require('./src/cron');
|
|
3
|
+
const definitions = require('./src/definitions');
|
|
4
|
+
const errors = require('./src/errors');
|
|
5
|
+
const serialize = require('./src/serialize');
|
|
6
|
+
const store = require('./src/store');
|
|
7
|
+
|
|
8
|
+
const JobsModule = require('./src/module');
|
|
9
|
+
|
|
10
|
+
const { Jobs, MAIL_JOB, STATES, toJob } = require('./src/jobs');
|
|
11
|
+
const { Batch, toBatch } = require('./src/batch');
|
|
12
|
+
const { Runner } = require('./src/runner');
|
|
13
|
+
const { duration } = require('./src/duration');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Background jobs for henri.
|
|
17
|
+
*
|
|
18
|
+
* This package ships the henri module itself (`"henri": { "module":
|
|
19
|
+
* "./module.js" }` in its package.json), so an application that depends on
|
|
20
|
+
* it has the queue in its boot as `henri.jobs`. Building one by hand is for
|
|
21
|
+
* a runner, a test or a script that has a henri instance but no module.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} henri A henri instance
|
|
24
|
+
* @param {object} [options={}] `config` (the `jobs` block), `cwd`, `adapter`
|
|
25
|
+
* @returns {Jobs} The queue
|
|
26
|
+
*/
|
|
27
|
+
const create = (henri, options = {}) => new Jobs(henri, options);
|
|
28
|
+
|
|
29
|
+
module.exports = create;
|
|
30
|
+
module.exports.Batch = Batch;
|
|
31
|
+
module.exports.Jobs = Jobs;
|
|
32
|
+
module.exports.JobsModule = JobsModule;
|
|
33
|
+
module.exports.MAIL_JOB = MAIL_JOB;
|
|
34
|
+
module.exports.Runner = Runner;
|
|
35
|
+
module.exports.STATES = STATES;
|
|
36
|
+
module.exports.config = config;
|
|
37
|
+
module.exports.create = create;
|
|
38
|
+
module.exports.cron = cron;
|
|
39
|
+
module.exports.definitions = definitions;
|
|
40
|
+
module.exports.duration = duration;
|
|
41
|
+
module.exports.errors = errors;
|
|
42
|
+
module.exports.serialize = serialize;
|
|
43
|
+
module.exports.store = store;
|
|
44
|
+
module.exports.toBatch = toBatch;
|
|
45
|
+
module.exports.toJob = toJob;
|
package/module.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The henri module this package ships.
|
|
3
|
+
*
|
|
4
|
+
* `package.json` points at this file with `"henri": { "module": "./module.js" }`,
|
|
5
|
+
* which is all core reads: an application depending on `@usehenri/jobs`
|
|
6
|
+
* has the module in its boot, as `henri.jobs`.
|
|
7
|
+
*/
|
|
8
|
+
module.exports = require('./src/module');
|
package/package.json
CHANGED
|
@@ -1,15 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Placeholder creating @usehenri/jobs on npm; the first real version is published by the henri release workflow",
|
|
3
|
-
"homepage": "https://usehenri.io",
|
|
4
|
-
"license": "MIT",
|
|
5
2
|
"name": "@usehenri/jobs",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "henri background jobs: a database backed queue with retries, a dead letter queue and recurring jobs",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Felix-Antoine Paradis",
|
|
7
|
+
"homepage": "https://usehenri.io",
|
|
9
8
|
"repository": {
|
|
10
|
-
"directory": "packages/jobs",
|
|
11
9
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/usehenri/henri.git"
|
|
10
|
+
"url": "git+https://github.com/usehenri/henri.git",
|
|
11
|
+
"directory": "packages/jobs"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/usehenri/henri/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"henri",
|
|
18
|
+
"jobs",
|
|
19
|
+
"queue",
|
|
20
|
+
"background",
|
|
21
|
+
"worker",
|
|
22
|
+
"cron"
|
|
23
|
+
],
|
|
24
|
+
"main": "index.js",
|
|
25
|
+
"henri": {
|
|
26
|
+
"module": "./module.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"module.js",
|
|
31
|
+
"src",
|
|
32
|
+
"CHANGELOG.md"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"provenance": true
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"debug": "^4.4.3",
|
|
43
|
+
"glob": "^13.0.6"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@usehenri/core": "^1.2.0"
|
|
13
47
|
},
|
|
14
|
-
"
|
|
15
|
-
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@usehenri/core": "^1.2.0",
|
|
50
|
+
"@usehenri/mongoose": "^1.2.0",
|
|
51
|
+
"@usehenri/sequelize": "^1.2.0",
|
|
52
|
+
"mongodb-memory-server": "^11.2.0",
|
|
53
|
+
"sqlite3": "^6.0.1"
|
|
54
|
+
}
|
|
55
|
+
}
|