@iskra-bun/worker-kit 0.1.0 → 0.3.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 +68 -0
- package/README.md +8 -2
- package/dist/index.d.ts +153 -18
- package/dist/index.js +251 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +335 -31
- package/src/types.ts +79 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# @iskra-bun/worker-kit
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3579944: **Security:** runtime hardening from the second audit round.
|
|
8
|
+
|
|
9
|
+
- `process-kit` (**breaking**): a child no longer inherits the app's whole environment. It gets the variables programs need and that carry no secrets (`PATH`, `HOME`, `USER`, `SHELL`, `TERM`, the locale, `TZ`, the temp dir, `NODE_ENV`, and the Windows essentials), plus `env`: `DATABASE_URL`, `AUTH_SECRET`, cloud keys and whatever was loaded from `.env` reached every child, third-party code included. The new `inheritEnv` option (validated by the core config schema, as is `maxPendingStdinBytes`) takes more names to pass, or `true` for all of them as before.
|
|
10
|
+
- `process-kit`: `send()` refuses a message, with one warning until the child catches up, when the bytes still waiting for a child that is not reading its stdin would go over `maxPendingStdinBytes` (8 MiB by default). They piled up in the app's memory without a bound: 256 MiB sent to such a child grew RSS by 263 MiB. `send()` now resolves to whether the message was sent (`false` for every refusal).
|
|
11
|
+
- `core`: OpenTelemetry. The options of each entry of `otel.instrumentations` reach the instrumentation (the config schema kept only `enabled`, so hooks and `redactedQueryParams` were dropped). HTTP spans export URLs with the values of secret-looking query parameters (`SECRET_QUERY_PARAMS`: `token`, `access_token`, `api_key`, `key`, `code`, `state`, `sig`, `X-Amz-Signature`…) replaced by `REDACTED`, through instrumentation-http's `redactedQueryParams` / `redactedQueryParamsServer` (which the app can set) and a `requestHook` for releases without them; the app's own `requestHook` still runs. The startup log shows only the endpoint's origin (its path, query or password can be an API key), and a plain `http://` endpoint on a remote host logs a warning. New exports: `SECRET_QUERY_PARAMS`, `redactUrl()`, `autoInstrumentationOptions()`, `describeOtelEndpoint()`.
|
|
12
|
+
- `web-kit`: `OtelTracingFeature` exported `url.full` as requested (@hono/otel sets it to `c.req.url`): the `?token=` of an email verification link, the token of better-auth's `/reset-password/<token>` and `?api_key=` reached the collector. The values of `SECRET_QUERY_PARAMS` (or the new `redactedQueryParams`) and that path token are now `REDACTED`. New `ignoreIncomingTraceContext` option to start a new trace per request instead of continuing the client's `traceparent` (default unchanged). `@opentelemetry/api` is now a direct dependency (it already came with `@hono/otel`).
|
|
13
|
+
- `web-kit`: `OpenAPIFeature`'s `/docs` page loaded `@scalar/api-reference@latest` on the app's origin. It now loads a pinned release (1.68.0) with its SRI hash and `crossorigin`, sends a Content-Security-Policy (scripts from that host only; requests only to the app and the spec's `servers`), turns off Scalar's web fonts and AI agent (which sends the spec to Scalar's servers), and HTML-escapes the title. New options: `docs: false` serves neither `/openapi.json` nor `/docs`; `authorize(c)` gates both (they are registered before middleware added after `initialize()`, so a `basicAuth()` there did not cover them); `scalar: { src, integrity }` or `false`.
|
|
14
|
+
- `web-kit` (**breaking**): `HealthCheckFeature`'s `/health/ready` lists the check names (`checks`, `failed`) and `/health/live` the `uptime` only with `includeDetails: true`, like `/health`; the names of failed readiness checks are logged instead.
|
|
15
|
+
- `worker-kit` (**breaking**): finished jobs are no longer kept in Redis forever with their payloads (BullMQ's default when `removeOnComplete`/`removeOnFail` are unset, which worker-kit never set). The queue keeps the last 1000 completed jobs and the failed ones of the last 7 days (at most 5000); `defaultJobOptions` or a job's options override it (`false` keeps them all), and both options now take BullMQ's `{ age, count }` form. `result()` of a job removed since rejects. The dead-letter example in the docs logged the whole payload with `console.error`, outside the logger's redaction; it logs ids now.
|
|
16
|
+
|
|
17
|
+
- fd36d6d: Job routing and connection fixes.
|
|
18
|
+
|
|
19
|
+
- A job with no registered handler used to be marked **completed** (the worker returned early), so it silently disappeared. It now fails with BullMQ's `UnrecoverableError` (no pointless retries), stays in the failed set, and is dead-lettered when `deadLetter` is on.
|
|
20
|
+
- New `consume: false` option for producer-only processes (e.g. an API node): `start()` creates no `Worker`, and `enqueue` accepts jobs whose handler lives in another process. Previously every producer had to register the handler and therefore also consume jobs.
|
|
21
|
+
- Redis URLs keep the ACL username, percent-decode credentials, and `rediss://` enables TLS.
|
|
22
|
+
- BullMQ queue/worker connection errors go to the app logger instead of the console.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 840439a: Packages declare the runtime they are tested on: `engines.bun` `>=1.3.0` (the monorepo now builds and tests on Bun 1.3). `create-iskra`, a CLI that also runs under `npm create iskra`, declares `engines.node` `>=18`.
|
|
27
|
+
|
|
28
|
+
Every package is published with an npm provenance attestation (`publishConfig.provenance`), linking each version to the commit and CI run that built it.
|
|
29
|
+
|
|
30
|
+
- cb3ec43: Register what each kit puts on the app with core's new registries: `app.context.get('db' | 'kv' | 'oracle')` returns the kit's driver, and the `process:*`, `socket:connected` / `socket:disconnected` and `worker:dead-letter` events have typed payloads. `ProcessManager.send()` takes `unknown` data.
|
|
31
|
+
- 87f6de2: `KVManager` throws when its constructor gets `adapter`, `driver` or `connection`: the store is chosen by the App config (`kv: { driver, connection }`), and the README's `new KVManager({ adapter: 'redis' })` was silently ignored, leaving the app on per-process memory. Without a `kv` driver it now logs a warning in production instead of an info line. The READMEs of kv-kit, worker-kit (`connection` and `queueName`, not `queue`), db-kit (the App's `db` config) and process-kit (the App's `processes` config) show working examples.
|
|
32
|
+
- 58d4a8f: `concurrency: 0` now means producer-only, like `consume: false`. It used to fall back to a concurrency of 1, so a service that set it to only enqueue (forms-app's forms-api did) also consumed jobs from the queue it had no handler for, and those jobs were lost.
|
|
33
|
+
- ee559ec: Per-job options no longer erase `defaultJobOptions`: every unset option was passed to BullMQ as `undefined`, which overrides the queue default, so a job enqueued with just `{ priority }` or `{ delay }`, and every job from `schedule()`, lost its `attempts`, `backoff` and `removeOnComplete`/`removeOnFail` (no retries, and completed repeat jobs kept in Redis forever). IPv6 Redis URLs (`redis://[::1]:6379`) now connect: the host kept its brackets.
|
|
34
|
+
- fba319a: A job whose handler throws is logged once at error level ("Job failed", now with `attemptsMade`) instead of twice: the processor also logged a `JobError` at error level before BullMQ's `failed` event logged it again. That processor log is now debug.
|
|
35
|
+
- Updated dependencies [620da18]
|
|
36
|
+
- Updated dependencies [b635a2c]
|
|
37
|
+
- Updated dependencies [5b2b0fd]
|
|
38
|
+
- Updated dependencies [58d4a8f]
|
|
39
|
+
- Updated dependencies [5c70c5b]
|
|
40
|
+
- Updated dependencies [ec198d4]
|
|
41
|
+
- Updated dependencies [cb3ec43]
|
|
42
|
+
- Updated dependencies [ef2009b]
|
|
43
|
+
- Updated dependencies [840439a]
|
|
44
|
+
- Updated dependencies [dbf8817]
|
|
45
|
+
- Updated dependencies [3dc5581]
|
|
46
|
+
- Updated dependencies [9872d30]
|
|
47
|
+
- Updated dependencies [f2346f5]
|
|
48
|
+
- Updated dependencies [3579944]
|
|
49
|
+
- @iskra-bun/core@0.2.0
|
|
50
|
+
|
|
51
|
+
## 0.2.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- f9654df: New worker features:
|
|
56
|
+
|
|
57
|
+
- Scheduled/repeat jobs: a `repeat` option on `JobOptions` plus a `schedule(name, data, repeat, opts?)` convenience for cron/interval jobs.
|
|
58
|
+
- Dead-letter handling: opt-in `deadLetter` emits a `worker:dead-letter` event with the job and `failedReason` once retries are exhausted.
|
|
59
|
+
- Job results: handlers may return a value (`JobHandler<T, R>`); the enqueue descriptor exposes a `result()` helper backed by BullMQ `QueueEvents`.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- f9654df: `register`, `enqueue`, and `JobHandler` are now generic over the job payload type, so a handler's `job.data` and the enqueued payload are typed instead of `any`. Defaults to `unknown`, so existing call sites compile unchanged.
|
|
64
|
+
- f9654df: `stop()` now drains in-flight jobs by fully closing the worker before closing the queue, instead of closing both concurrently (which could leave a running job stuck in the `active` state).
|
|
65
|
+
- Fix a connection leak after `stop()`. `stop()` now sets the stopped flag first, and `getQueueEvents()` throws `WorkerManager is stopped; cannot open QueueEvents` rather than lazily opening a new orphaned connection. Job descriptors created after stop reject instead of silently holding an open connection.
|
|
66
|
+
- Updated dependencies [f9654df]
|
|
67
|
+
- Updated dependencies
|
|
68
|
+
- Updated dependencies [f9654df]
|
|
69
|
+
- @iskra-bun/core@0.1.1
|
|
70
|
+
|
|
3
71
|
## 0.1.0
|
|
4
72
|
|
|
5
73
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -17,14 +17,20 @@ import { App } from '@iskra-bun/core'
|
|
|
17
17
|
import { WorkerManager } from '@iskra-bun/worker-kit'
|
|
18
18
|
|
|
19
19
|
const app = new App({ name: 'mi-worker' })
|
|
20
|
-
app.register(
|
|
20
|
+
app.register(
|
|
21
|
+
new WorkerManager({
|
|
22
|
+
connection: process.env.REDIS_URL ?? 'redis://localhost:6379',
|
|
23
|
+
queueName: 'mis-jobs',
|
|
24
|
+
concurrency: 2,
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
21
27
|
|
|
22
28
|
await app.start()
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
## Documentacion
|
|
26
32
|
|
|
27
|
-
Guia completa: [
|
|
33
|
+
Guia completa: [@iskra-bun/worker-kit](https://iskra-docs.fly.dev/es/packages/worker-kit/)
|
|
28
34
|
|
|
29
35
|
## Licencia
|
|
30
36
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
import { IskraError, Driver, App } from '@iskra-bun/core';
|
|
2
|
+
import { KeepJobs } from 'bullmq';
|
|
2
3
|
|
|
3
4
|
interface WorkerManagerOptions {
|
|
4
|
-
/** URL de conexión a Redis (ej: 'redis://localhost:6379') */
|
|
5
|
+
/** URL de conexión a Redis (ej: 'redis://user:pass@localhost:6379/0'; `rediss://` activa TLS) */
|
|
5
6
|
connection: string | {
|
|
6
7
|
host: string;
|
|
7
8
|
port: number;
|
|
9
|
+
username?: string;
|
|
8
10
|
password?: string;
|
|
9
11
|
db?: number;
|
|
12
|
+
tls?: object;
|
|
10
13
|
};
|
|
11
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* `false` = solo productor: `start()` no crea un Worker y `enqueue` acepta
|
|
16
|
+
* jobs sin handler local (los procesa otro proceso). Default: true.
|
|
17
|
+
*/
|
|
18
|
+
consume?: boolean;
|
|
19
|
+
/** Cantidad de jobs que se procesan en paralelo (default: 1). `0` = solo productor, como `consume: false`. */
|
|
12
20
|
concurrency?: number;
|
|
13
21
|
/** Nombre de la queue en Redis (default: 'iskra-jobs') */
|
|
14
22
|
queueName?: string;
|
|
15
23
|
/** Opciones por defecto para cada job */
|
|
16
24
|
defaultJobOptions?: JobOptions;
|
|
25
|
+
/**
|
|
26
|
+
* Activa el ruteo a dead-letter: cuando un job agota todos sus reintentos
|
|
27
|
+
* se emite el evento `worker:dead-letter` en el bus de eventos de la App.
|
|
28
|
+
* Opt-in para no cambiar el comportamiento existente (default: false).
|
|
29
|
+
*/
|
|
30
|
+
deadLetter?: boolean;
|
|
17
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Especificación de repetición para jobs programados.
|
|
34
|
+
*
|
|
35
|
+
* - Un string se interpreta como un patrón cron (ej: '0 0 * * *').
|
|
36
|
+
* - `{ every: ms }` repite cada `ms` milisegundos.
|
|
37
|
+
* - `{ pattern: cron }` repite según el patrón cron, con opciones extra.
|
|
38
|
+
*/
|
|
39
|
+
type RepeatSpec = string | {
|
|
40
|
+
every: number;
|
|
41
|
+
limit?: number;
|
|
42
|
+
} | {
|
|
43
|
+
pattern: string;
|
|
44
|
+
limit?: number;
|
|
45
|
+
tz?: string;
|
|
46
|
+
};
|
|
18
47
|
interface JobOptions {
|
|
19
48
|
/** Reintentos en caso de fallo */
|
|
20
49
|
attempts?: number;
|
|
@@ -27,17 +56,59 @@ interface JobOptions {
|
|
|
27
56
|
type: 'fixed' | 'exponential';
|
|
28
57
|
delay: number;
|
|
29
58
|
};
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Jobs completados que quedan en Redis, con sus datos: `true` los borra,
|
|
61
|
+
* un número conserva los últimos N, `{ age, count }` por edad (s) y
|
|
62
|
+
* cantidad. Default `{ count: 1000 }`; `false` los conserva todos.
|
|
63
|
+
*/
|
|
64
|
+
removeOnComplete?: boolean | number | KeepJobs;
|
|
65
|
+
/**
|
|
66
|
+
* Jobs fallidos (sin reintentos pendientes) que quedan en Redis, igual que
|
|
67
|
+
* removeOnComplete. Default `{ age: 7 días, count: 5000 }`; `false` los conserva todos.
|
|
68
|
+
*/
|
|
69
|
+
removeOnFail?: boolean | number | KeepJobs;
|
|
70
|
+
/**
|
|
71
|
+
* Programa el job como repetible (cron o intervalo).
|
|
72
|
+
* Se reenvía a la opción `repeat` de BullMQ.
|
|
73
|
+
*/
|
|
74
|
+
repeat?: RepeatSpec;
|
|
34
75
|
}
|
|
35
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Handler de un job. Puede devolver un valor `R` que queda disponible como
|
|
78
|
+
* resultado del job (recuperable vía `job.waitUntilFinished`). Devolver `void`
|
|
79
|
+
* sigue siendo válido (R por defecto es `void`).
|
|
80
|
+
*/
|
|
81
|
+
type JobHandler<T = unknown, R = void> = (job: {
|
|
36
82
|
id: string;
|
|
37
83
|
name: string;
|
|
38
|
-
data:
|
|
84
|
+
data: T;
|
|
85
|
+
attemptsMade: number;
|
|
86
|
+
}) => Promise<R>;
|
|
87
|
+
/**
|
|
88
|
+
* Payload del evento `worker:dead-letter`, emitido cuando un job agota todos
|
|
89
|
+
* sus reintentos y `deadLetter` está activado.
|
|
90
|
+
*/
|
|
91
|
+
interface DeadLetterPayload {
|
|
92
|
+
jobId: string | undefined;
|
|
93
|
+
name: string | undefined;
|
|
94
|
+
data: unknown;
|
|
95
|
+
failedReason: string | undefined;
|
|
39
96
|
attemptsMade: number;
|
|
40
|
-
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Descriptor devuelto por `enqueue`/`schedule`. Además de los datos del job,
|
|
100
|
+
* expone `result()` para esperar el valor de retorno del handler.
|
|
101
|
+
*/
|
|
102
|
+
interface JobDescriptor<T = unknown, R = unknown> {
|
|
103
|
+
id: string;
|
|
104
|
+
name: string;
|
|
105
|
+
data: T;
|
|
106
|
+
/**
|
|
107
|
+
* Espera a que el job termine y resuelve con el valor que devolvió el
|
|
108
|
+
* handler. Lanza si el job falló. Requiere una conexión a Redis viva.
|
|
109
|
+
*/
|
|
110
|
+
result(ttlMs?: number): Promise<R>;
|
|
111
|
+
}
|
|
41
112
|
|
|
42
113
|
declare class QueueError extends IskraError {
|
|
43
114
|
constructor(message: string, options?: {
|
|
@@ -58,25 +129,89 @@ declare class WorkerManager implements Driver {
|
|
|
58
129
|
private handlers;
|
|
59
130
|
private queue;
|
|
60
131
|
private worker;
|
|
132
|
+
private queueEvents;
|
|
133
|
+
private stopped;
|
|
61
134
|
private options;
|
|
135
|
+
/** Tope de tamaño (bytes) del payload serializado de un job. */
|
|
136
|
+
private static readonly MAX_PAYLOAD_BYTES;
|
|
62
137
|
constructor(options: WorkerManagerOptions);
|
|
63
138
|
init(app: App): Promise<void>;
|
|
64
139
|
/**
|
|
65
|
-
* Registra un handler para un tipo de job.
|
|
140
|
+
* Registra un handler para un tipo de job. El handler puede devolver un
|
|
141
|
+
* valor `R` que queda disponible como resultado del job.
|
|
142
|
+
*/
|
|
143
|
+
register<T = unknown, R = void>(jobName: string, handler: JobHandler<T, R>): this;
|
|
144
|
+
/**
|
|
145
|
+
* Encola un job para ser procesado. Devuelve un descriptor que, además de
|
|
146
|
+
* los datos del job, expone `result()` para esperar el valor de retorno del
|
|
147
|
+
* handler.
|
|
148
|
+
*/
|
|
149
|
+
enqueue<T = unknown, R = unknown>(name: string, data: T, opts?: JobOptions): Promise<JobDescriptor<T, R>>;
|
|
150
|
+
/**
|
|
151
|
+
* Programa un job repetible (cron o intervalo). Conveniencia sobre
|
|
152
|
+
* `enqueue` con la opción `repeat` ya configurada.
|
|
153
|
+
*
|
|
154
|
+
* @param repeat patrón cron (string) o `{ every: ms }`/`{ pattern: cron }`.
|
|
66
155
|
*/
|
|
67
|
-
|
|
156
|
+
schedule<T = unknown, R = unknown>(name: string, data: T, repeat: RepeatSpec, opts?: JobOptions): Promise<JobDescriptor<T, R>>;
|
|
68
157
|
/**
|
|
69
|
-
*
|
|
158
|
+
* `consume: false`, or `concurrency: 0` (which used to fall back to 1, so
|
|
159
|
+
* a service meant to only enqueue consumed jobs it had no handler for).
|
|
70
160
|
*/
|
|
71
|
-
|
|
72
|
-
id: string;
|
|
73
|
-
name: string;
|
|
74
|
-
data: any;
|
|
75
|
-
}>;
|
|
161
|
+
private get producerOnly();
|
|
76
162
|
start(): Promise<void>;
|
|
77
163
|
stop(): Promise<void>;
|
|
164
|
+
private logConnectionError;
|
|
165
|
+
/**
|
|
166
|
+
* Turns a redis:// or rediss:// URL into ioredis options, keeping the ACL
|
|
167
|
+
* username, the percent-decoded password, the db index and TLS (rediss).
|
|
168
|
+
*/
|
|
78
169
|
private parseConnection;
|
|
170
|
+
/**
|
|
171
|
+
* Only the options that were given: BullMQ merges `{ ...defaultJobOptions,
|
|
172
|
+
* ...opts }`, so an explicit `undefined` erased the queue default (a job
|
|
173
|
+
* enqueued with just `{ priority }`, and every scheduled job, lost its
|
|
174
|
+
* attempts, backoff and removeOn* settings).
|
|
175
|
+
*/
|
|
79
176
|
private mapJobOptions;
|
|
177
|
+
/**
|
|
178
|
+
* Normaliza una RepeatSpec a la forma `repeat` de BullMQ:
|
|
179
|
+
* - string → `{ pattern: cron }`
|
|
180
|
+
* - `{ every }` / `{ pattern }` → se reenvían tal cual.
|
|
181
|
+
*/
|
|
182
|
+
private mapRepeat;
|
|
183
|
+
/**
|
|
184
|
+
* Valida la entrada de `enqueue` ANTES de tocar Redis, para evitar que
|
|
185
|
+
* entrada no confiable inunde la queue, almacene payloads gigantes o
|
|
186
|
+
* programe repeticiones malformadas. Lanza `QueueError` ante cualquier
|
|
187
|
+
* problema; no muta nada.
|
|
188
|
+
*/
|
|
189
|
+
private validateEnqueue;
|
|
190
|
+
/** Rechaza payloads cuya serialización JSON excede el tope configurado. */
|
|
191
|
+
private validatePayloadSize;
|
|
192
|
+
/** Rechaza specs de repetición vacías, intervalos no positivos o crons en blanco. */
|
|
193
|
+
private validateRepeat;
|
|
194
|
+
/**
|
|
195
|
+
* Maneja el evento `failed` del worker. Loggea el fallo y, si el job agotó
|
|
196
|
+
* todos sus reintentos y `deadLetter` está activado, emite
|
|
197
|
+
* `worker:dead-letter` en el bus de eventos de la App.
|
|
198
|
+
*/
|
|
199
|
+
private onFailed;
|
|
200
|
+
/**
|
|
201
|
+
* Construye el descriptor de un job, incluyendo el helper `result()` que
|
|
202
|
+
* espera el valor de retorno del handler vía `job.waitUntilFinished`.
|
|
203
|
+
*/
|
|
204
|
+
private buildDescriptor;
|
|
205
|
+
/**
|
|
206
|
+
* Devuelve (creando perezosamente) una instancia compartida de QueueEvents
|
|
207
|
+
* usada para esperar resultados de jobs.
|
|
208
|
+
*/
|
|
209
|
+
private getQueueEvents;
|
|
210
|
+
}
|
|
211
|
+
declare module '@iskra-bun/core' {
|
|
212
|
+
interface AppEvents {
|
|
213
|
+
'worker:dead-letter': DeadLetterPayload;
|
|
214
|
+
}
|
|
80
215
|
}
|
|
81
216
|
|
|
82
|
-
export { JobError, type JobHandler, type JobOptions, QueueError, WorkerManager, type WorkerManagerOptions };
|
|
217
|
+
export { type DeadLetterPayload, type JobDescriptor, JobError, type JobHandler, type JobOptions, QueueError, type RepeatSpec, WorkerManager, type WorkerManagerOptions };
|