@nestjs-pipeline/job-context 0.4.0 → 0.4.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.
Files changed (2) hide show
  1. package/README.md +2 -194
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -8,12 +8,7 @@ the correlation id and the principal. A job then makes the decisions the request
8
8
  have made. System-started work, such as a cron job, declares its context explicitly
9
9
  instead. Nothing falls back to a default tenant or an anonymous principal.
10
10
 
11
- It depends on no other pipeline package. It reads and restores the tenant and the
12
- correlation id through the sources it is given, usually `tenantSource` of
13
- [`@nestjs-pipeline/tenant`](https://github.com/aristoteliss/nestjs-pipeline/tree/master/packages/pipeline-tenant#readme)
14
- and `correlationSource` of
15
- [`@nestjs-pipeline/correlation`](https://github.com/aristoteliss/nestjs-pipeline/tree/master/packages/pipeline-correlation#readme),
16
- so pipelines a job dispatches get the same tenant and correlation id.
11
+ **Documentation:** [guide](https://aristoteliss.github.io/nestjs-pipeline/packages/nestjs-pipeline/job-context/) · [API reference](https://aristoteliss.github.io/nestjs-pipeline/api/nestjs-pipeline/job-context/) · [all packages](https://aristoteliss.github.io/nestjs-pipeline/)
17
12
 
18
13
  ## Installation
19
14
 
@@ -23,194 +18,7 @@ pnpm add @nestjs-pipeline/job-context @nestjs/common
23
18
 
24
19
  Requires Node.js 22.12 or later and `@nestjs/common` `^12.1.0`.
25
20
 
26
- Published as an ES module; a CommonJS application loads it with `require()`. Coming from
27
- 0.3.x, see [Upgrading from 0.3.x](https://github.com/aristoteliss/nestjs-pipeline#upgrading-from-03x).
28
-
29
- ## Setup
30
-
31
- Implement `IJobPrincipal` over the application's authentication state, and register it
32
- with the tenants jobs may run in and the tenant and correlation id sources:
33
-
34
- `Capability`, `SessionRepository`, `SessionsModule`, `currentPrincipal`,
35
- `runAsPrincipal` and `SessionRevokedError` stand for the application's own authentication
36
- code.
37
-
38
- ```typescript
39
- import { Injectable, Module } from '@nestjs/common';
40
- import { correlationSource } from '@nestjs-pipeline/correlation';
41
- import {
42
- type IJobPrincipal,
43
- JobContextModule,
44
- type PrincipalReference,
45
- } from '@nestjs-pipeline/job-context';
46
- import { tenantSource } from '@nestjs-pipeline/tenant';
47
-
48
- @Injectable()
49
- export class SessionJobPrincipal implements IJobPrincipal<Capability> {
50
- constructor(private readonly sessions: SessionRepository) {}
51
-
52
- capture(): PrincipalReference | undefined {
53
- const principal = currentPrincipal();
54
- return principal && { id: principal.id, type: principal.type, sessionId: principal.sid };
55
- }
56
-
57
- async restore<T>(
58
- principal: PrincipalReference,
59
- work: () => Promise<T>,
60
- grants?: readonly Capability[],
61
- ): Promise<T> {
62
- if (grants) return runAsPrincipal({ ...principal, grants }, work);
63
- const session = await this.sessions.findActive(principal.sessionId, principal.id);
64
- if (!session) throw new SessionRevokedError();
65
- return runAsPrincipal(principal, work);
66
- }
67
- }
68
-
69
- @Module({
70
- imports: [
71
- JobContextModule.forRoot({
72
- principal: SessionJobPrincipal,
73
- tenants: ['tenant_a', 'tenant_b'],
74
- sources: { tenantId: tenantSource, correlationId: correlationSource },
75
- imports: [SessionsModule],
76
- }),
77
- ],
78
- })
79
- export class JobsModule {}
80
- ```
81
-
82
- `tenants` is a list, or a function that returns one. A function is called once, when the
83
- application builds the module's providers, so the list can come from configuration read
84
- at startup (`tenants: () => config().tenants`) instead of when the module file is
85
- imported. An empty list throws: a list in `forRoot`, a function when the application
86
- starts.
87
-
88
- `capture` reads the current principal when a job is enqueued; only its `id`, `type` and
89
- `sessionId` are kept. `restore` runs when the job does, inside the job's tenant and
90
- correlation id: it must re-check the principal against current state, bind it the way a
91
- request would, and throw to refuse the job.
92
-
93
- ## Usage
94
-
95
- Stamp the payload when enqueuing, from inside the request or handler, so the tenant,
96
- correlation id and principal are current:
97
-
98
- ```typescript
99
- import { InjectQueue } from '@nestjs/bullmq';
100
- import { EventsHandler, type IEventHandler } from '@nestjs/cqrs';
101
- import { withJobContext, type WithJobContext } from '@nestjs-pipeline/job-context';
102
- import type { Queue } from 'bullmq';
103
-
104
- type WelcomeEmail = { userId: string; email: string };
105
-
106
- @EventsHandler(UserRegisteredEvent)
107
- export class EnqueueWelcomeEmail implements IEventHandler<UserRegisteredEvent> {
108
- constructor(
109
- @InjectQueue(WELCOME_EMAIL_QUEUE)
110
- private readonly queue: Queue<WithJobContext<WelcomeEmail>>,
111
- ) {}
112
-
113
- async handle({ userId, email }: UserRegisteredEvent) {
114
- await this.queue.add('send', withJobContext({ userId, email }));
115
- }
116
- }
117
- ```
118
-
119
- Restore it in the processor. Place the decorator under the transport decorator:
120
-
121
- ```typescript
122
- import { Processor, WorkerHost } from '@nestjs/bullmq';
123
- import { CommandBus } from '@nestjs/cqrs';
124
- import { InJobContext, type WithJobContext } from '@nestjs-pipeline/job-context';
125
- import type { Job } from 'bullmq';
126
-
127
- @Processor(WELCOME_EMAIL_QUEUE)
128
- export class SendWelcomeEmailProcessor extends WorkerHost {
129
- constructor(private readonly commandBus: CommandBus) {
130
- super();
131
- }
132
-
133
- @InJobContext()
134
- async process(job: Job<WithJobContext<WelcomeEmail>>) {
135
- await this.commandBus.execute(new SendWelcomeEmailCommand(job.data));
136
- }
137
- }
138
- ```
139
-
140
- `@InJobContext()` reads `data.jobContext` from the first argument (a BullMQ `Job`); pass
141
- `{ path: 'jobContext' }` for a transport that hands the payload itself.
142
-
143
- ```typescript
144
- @EventPattern('user.registered')
145
- @InJobContext({ path: 'jobContext' })
146
- async onRegistered(@Payload() data: WithJobContext<WelcomeEmail>) {
147
- await this.commandBus.execute(new SendWelcomeEmailCommand(data));
148
- }
149
- ```
150
-
151
- A refused job throws `MissingJobContextError` or `InvalidJobContextError` before the method
152
- runs; let the queue mark it failed rather than retrying it, since the payload will not change.
153
-
154
- Declare system-started work:
155
-
156
- ```typescript
157
- import { AsSystem } from '@nestjs-pipeline/job-context';
158
- import { Cron } from '@nestjs/schedule';
159
-
160
- @Cron('0 3 * * *')
161
- @AsSystem({
162
- principal: { id: 'session-cleanup', type: 'service' },
163
- grants: [{ action: 'delete', subject: 'Auth' }],
164
- })
165
- async purgeSessions() {
166
- await this.commandBus.execute(new PurgeExpiredSessionsCommand());
167
- }
168
- ```
169
-
170
- ## Behavior
171
-
172
- - **`withJobContext(data)`** returns a copy of `data` with `jobContext`: the tenant and
173
- the correlation id of the configured sources (a new one from the correlation source's
174
- `create()` when none is active), and the captured principal. It throws `MissingJobContextError` without a
175
- running `JobContextModule`, a tenant, or a principal, and `TypeError` for a payload that
176
- is not a plain object.
177
- - **`@InJobContext()`** validates the payload's context before the method runs. It refuses
178
- a missing context (`MissingJobContextError`), and a malformed one, an unconfigured
179
- tenant, a correlation id the correlation source's `accepts` refuses
180
- (`correlationSource` accepts at most 128 characters of `A-Z a-z 0-9 . _ ~ : / + = @ -`), or a
181
- principal carrying any field beyond `id`, `type` and `sessionId`, such as grants
182
- (`InvalidJobContextError`). It then runs the method inside the tenant, the correlation
183
- id, and `restore(principal, work)`. The method becomes async.
184
- - **`@AsSystem({ principal, grants })`** runs the method once per configured tenant, one
185
- after another, each with a new correlation id from `create()` and `restore(principal, work, grants)`. A
186
- failing tenant does not stop the others; the method then rejects with an
187
- `AggregateError` of the failures. Return values are discarded.
188
- - **`JobContextModule.forRoot`** registers the principal port, tenants and sources when the module
189
- is instantiated and removes them at application shutdown. The decorators wrap methods
190
- outside dependency injection, so they use the registration of the running application;
191
- without one they fail closed. Register it once per application.
192
-
193
- ## Security
194
-
195
- A payload is data: anyone who can write to the queue can write it. The package therefore
196
- carries an identity reference only, never grants, and `restore` decides what that
197
- identity may do now. Re-check a user's session and account in `restore`, so a revoked
198
- session or a deleted user refuses the job. Grants come only from `@AsSystem`, in code.
199
- The tenant must be one of the configured tenants.
200
-
201
- ## API
202
-
203
- | Export | Kind | Description |
204
- | --- | --- | --- |
205
- | `withJobContext(data)` | function | Copies `data` and adds the current `jobContext` |
206
- | `InJobContext(options?)` | decorator | Runs a job method in its payload's context; `path` defaults to `'data.jobContext'` |
207
- | `AsSystem(options)` | decorator | Runs system work once per tenant as the declared principal and grants |
208
- | `JobContextModule.forRoot(options)` | module | Registers `principal` (a class), `tenants` (a list, or a function called at startup), `sources` and optional `imports` |
209
- | `ContextSource`, `CorrelationSource`, `JobContextSources` | type | `{ current, run }`; the correlation source adds `create()` and `accepts(id)`; and the `{ tenantId, correlationId }` pair `sources` takes |
210
- | `IJobPrincipal<TGrant>` | interface | Application port: `capture()` and `restore(principal, work, grants?)` |
211
- | `PrincipalReference` | type | `{ id, type, sessionId? }` |
212
- | `JobContext`, `WithJobContext<T>` | type | The carried context, and a payload with it |
213
- | `MissingJobContextError`, `InvalidJobContextError` | error | Framework-neutral; map them in the consumer if needed |
21
+ Published as an ES module; a CommonJS application loads it with `require()`. Coming from 0.3.x, see [Upgrading from 0.3.x](https://aristoteliss.github.io/nestjs-pipeline/upgrading/from-0-3/).
214
22
 
215
23
  ## License
216
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs-pipeline/job-context",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Carries the tenant, correlation id and principal of a request into the queue jobs it enqueues, and gives system-started work an explicit context",
5
5
  "author": "Aristotelis",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/aristoteliss/nestjs-pipeline.git",
14
14
  "directory": "packages/pipeline-job-context"
15
15
  },
16
- "homepage": "https://github.com/aristoteliss/nestjs-pipeline/tree/master/packages/pipeline-job-context#readme",
16
+ "homepage": "https://aristoteliss.github.io/nestjs-pipeline/packages/nestjs-pipeline/job-context/",
17
17
  "bugs": {
18
18
  "url": "https://github.com/aristoteliss/nestjs-pipeline/issues"
19
19
  },