@chidchanun/bcp 0.2.7 → 0.2.8
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/README.md +62 -35
- package/docs/README.md +38 -35
- package/docs/api-manifest.json +12 -1
- package/docs/api-reference.md +31 -0
- package/docs/background-jobs.md +356 -0
- package/docs/docs-web-manifest.json +5 -3
- package/docs/platform-manifest.json +13 -4
- package/docs/releases/0.2.8.md +150 -0
- package/package.json +6 -1
- package/packages/bundler/src/client-boundary.ts +1 -0
- package/packages/client/src/jobs.ts +16 -0
- package/packages/server/src/jobs.ts +822 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Background Jobs Platform
|
|
2
|
+
|
|
3
|
+
BCP Framework `0.2.8` adds a server-only background jobs contract through `bcp/jobs`.
|
|
4
|
+
|
|
5
|
+
The goal is to provide one framework API for enqueueing and processing background work while keeping storage/queue providers replaceable.
|
|
6
|
+
|
|
7
|
+
## Public entrypoint
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
createJobQueue,
|
|
12
|
+
createMemoryJobQueueAdapter,
|
|
13
|
+
} from "bcp/jobs";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`bcp/jobs` is server-only and must not be imported by pages or other client-bundled modules.
|
|
17
|
+
|
|
18
|
+
## Create a queue
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import {
|
|
22
|
+
createJobQueue,
|
|
23
|
+
} from "bcp/jobs";
|
|
24
|
+
|
|
25
|
+
export const jobs =
|
|
26
|
+
createJobQueue();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Without an explicit adapter BCP uses the process-local in-memory adapter.
|
|
30
|
+
|
|
31
|
+
The memory adapter is intended for:
|
|
32
|
+
|
|
33
|
+
- development,
|
|
34
|
+
- unit/integration tests,
|
|
35
|
+
- single-process prototypes,
|
|
36
|
+
- applications where losing queued jobs during restart is acceptable.
|
|
37
|
+
|
|
38
|
+
For durable production processing, implement `JobQueueAdapter` against shared infrastructure such as a database, Redis-backed queue, managed queue service, or another durable store.
|
|
39
|
+
|
|
40
|
+
## Register handlers
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
jobs.register<{
|
|
44
|
+
userId: number;
|
|
45
|
+
}>(
|
|
46
|
+
"email.welcome",
|
|
47
|
+
async ({
|
|
48
|
+
payload,
|
|
49
|
+
job,
|
|
50
|
+
signal,
|
|
51
|
+
}) => {
|
|
52
|
+
console.log(
|
|
53
|
+
"Attempt",
|
|
54
|
+
job.attempts
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (signal.aborted) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await sendWelcomeEmail(
|
|
62
|
+
payload.userId
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A job name can have only one registered handler within a queue instance.
|
|
69
|
+
|
|
70
|
+
The unregister function returned by `register()` can remove the handler:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const unregister =
|
|
74
|
+
jobs.register(
|
|
75
|
+
"search.reindex",
|
|
76
|
+
reindexSearch
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
unregister();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Enqueue jobs
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const job =
|
|
86
|
+
await jobs.enqueue(
|
|
87
|
+
"email.welcome",
|
|
88
|
+
{
|
|
89
|
+
userId: 42,
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The returned record starts in `queued` state.
|
|
95
|
+
|
|
96
|
+
Job states are:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
queued
|
|
100
|
+
running
|
|
101
|
+
succeeded
|
|
102
|
+
failed
|
|
103
|
+
cancelled
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Delayed jobs
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await jobs.enqueue(
|
|
110
|
+
"invoice.reminder",
|
|
111
|
+
{
|
|
112
|
+
invoiceId: 9001,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
delayMs:
|
|
116
|
+
15 * 60 * 1000,
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A delayed job remains queued until `availableAt` is reached.
|
|
122
|
+
|
|
123
|
+
`delayMs` is a delay from enqueue time; `0` means the job is immediately eligible for reservation.
|
|
124
|
+
|
|
125
|
+
## Retries
|
|
126
|
+
|
|
127
|
+
The default maximum attempt count is `3`.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const jobs =
|
|
131
|
+
createJobQueue({
|
|
132
|
+
defaultMaxAttempts: 5,
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Override per job:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
await jobs.enqueue(
|
|
140
|
+
"payment.sync",
|
|
141
|
+
payload,
|
|
142
|
+
{
|
|
143
|
+
maxAttempts: 8,
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
When a handler throws, the job is queued again while attempts remain.
|
|
149
|
+
|
|
150
|
+
Default retry delay uses capped exponential backoff:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
attempt 1 -> 1 second
|
|
154
|
+
attempt 2 -> 2 seconds
|
|
155
|
+
attempt 3 -> 4 seconds
|
|
156
|
+
...
|
|
157
|
+
maximum -> 30 seconds
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use a fixed delay:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
createJobQueue({
|
|
164
|
+
retryDelayMs: 5_000,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Or a custom backoff function:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
createJobQueue({
|
|
172
|
+
retryDelayMs: attempt =>
|
|
173
|
+
attempt * 10_000,
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The callback receives the attempt number that just failed.
|
|
178
|
+
|
|
179
|
+
## Manual processing
|
|
180
|
+
|
|
181
|
+
`processNext()` reserves and processes one eligible job:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const processed =
|
|
185
|
+
await jobs.processNext();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
It returns `false` when no eligible queued job is available.
|
|
189
|
+
|
|
190
|
+
This form is useful for deterministic tests and custom process supervisors.
|
|
191
|
+
|
|
192
|
+
## Start a worker
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const worker =
|
|
196
|
+
jobs.startWorker({
|
|
197
|
+
concurrency: 4,
|
|
198
|
+
pollIntervalMs: 250,
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`concurrency` controls the number of independent processing loops in the current process.
|
|
203
|
+
|
|
204
|
+
Stop gracefully:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
await worker.stop();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Closing the queue stops all workers created by that queue and then closes the adapter when it exposes `close()`:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
await jobs.close();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Cancellation
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const cancelled =
|
|
220
|
+
await jobs.cancel(job.id);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Queued and running records can transition to `cancelled`. The in-memory adapter will not allow a later completion/failure write to replace a cancelled terminal state.
|
|
224
|
+
|
|
225
|
+
Handlers receive an `AbortSignal` for worker lifecycle shutdown. Application handlers should check the signal during long-running operations when graceful cancellation matters.
|
|
226
|
+
|
|
227
|
+
## Read queue state
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const job =
|
|
231
|
+
await jobs.get(jobId);
|
|
232
|
+
|
|
233
|
+
const allJobs =
|
|
234
|
+
await jobs.list();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
These APIs are intended primarily for diagnostics/admin tooling and testing. Durable adapters may choose their own indexing/storage strategy behind the contract.
|
|
238
|
+
|
|
239
|
+
## Adapter contract
|
|
240
|
+
|
|
241
|
+
Production adapters implement:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import type {
|
|
245
|
+
JobQueueAdapter,
|
|
246
|
+
} from "bcp/jobs";
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Required operations:
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
enqueue
|
|
253
|
+
reserve
|
|
254
|
+
complete
|
|
255
|
+
fail
|
|
256
|
+
cancel
|
|
257
|
+
get
|
|
258
|
+
list
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Optional:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
close
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`reserve(now)` must atomically claim one eligible queued job and increment its attempt count. This atomicity requirement is important for multi-worker or multi-instance adapters.
|
|
268
|
+
|
|
269
|
+
For durable adapters, use storage-level locking/transactions/leases appropriate to the provider. Do not implement `reserve()` as an unlocked read followed by an unrelated write.
|
|
270
|
+
|
|
271
|
+
## Unique job IDs
|
|
272
|
+
|
|
273
|
+
BCP generates UUID job IDs by default.
|
|
274
|
+
|
|
275
|
+
Applications can supply an explicit ID for idempotent enqueueing:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
await jobs.enqueue(
|
|
279
|
+
"statement.generate",
|
|
280
|
+
{
|
|
281
|
+
accountId: 12,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id:
|
|
285
|
+
"statement-12-2026-08",
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The memory adapter rejects duplicate IDs.
|
|
291
|
+
|
|
292
|
+
A durable adapter should enforce equivalent uniqueness at the storage layer.
|
|
293
|
+
|
|
294
|
+
## Observability
|
|
295
|
+
|
|
296
|
+
Background job infrastructure can be combined with `bcp/observability`.
|
|
297
|
+
|
|
298
|
+
Recommended metrics include:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
jobs_enqueued_total
|
|
302
|
+
jobs_completed_total
|
|
303
|
+
jobs_failed_total
|
|
304
|
+
jobs_retry_total
|
|
305
|
+
jobs_active
|
|
306
|
+
job_duration_seconds
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Avoid putting job IDs, user IDs, email addresses, URLs, or other unbounded values into metric labels.
|
|
310
|
+
|
|
311
|
+
Use a bounded job-name label when your application has a controlled handler catalog.
|
|
312
|
+
|
|
313
|
+
## Deployment model
|
|
314
|
+
|
|
315
|
+
The queue contract is intentionally independent from the web server lifecycle.
|
|
316
|
+
|
|
317
|
+
A small deployment may start a worker in the same Node.js process as the application. Higher reliability deployments should normally run workers as separate processes/containers using a shared durable adapter.
|
|
318
|
+
|
|
319
|
+
Example topology:
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
BCP web application
|
|
323
|
+
|
|
|
324
|
+
| enqueue
|
|
325
|
+
v
|
|
326
|
+
shared durable queue
|
|
327
|
+
/ \
|
|
328
|
+
worker A worker B
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The built-in memory adapter cannot coordinate across processes or containers.
|
|
332
|
+
|
|
333
|
+
## Delivery semantics
|
|
334
|
+
|
|
335
|
+
BCP's adapter contract is designed for practical at-least-once processing semantics with durable adapters.
|
|
336
|
+
|
|
337
|
+
A worker may retry a job after an error. Application handlers should therefore be idempotent when side effects cannot safely run twice.
|
|
338
|
+
|
|
339
|
+
Examples:
|
|
340
|
+
|
|
341
|
+
- use a unique database key before sending a one-time notification,
|
|
342
|
+
- use idempotency keys with payment providers,
|
|
343
|
+
- write processing state transactionally where possible,
|
|
344
|
+
- avoid assuming one handler invocation means one side effect exactly once.
|
|
345
|
+
|
|
346
|
+
## Security
|
|
347
|
+
|
|
348
|
+
Job payloads are server-side application data. Do not expose `bcp/jobs` to client bundles.
|
|
349
|
+
|
|
350
|
+
Avoid storing secrets in payloads when the backing adapter may persist or log the full job record. Prefer stable identifiers and load sensitive data inside the handler from the authoritative store.
|
|
351
|
+
|
|
352
|
+
## 0.2.8 scope
|
|
353
|
+
|
|
354
|
+
`0.2.8` provides the queue/worker contract and process-local reference adapter.
|
|
355
|
+
|
|
356
|
+
It does not include a built-in Redis, PostgreSQL, SQS, RabbitMQ, Kafka, cron, or workflow-engine adapter. Those can be added behind `JobQueueAdapter` in later milestones without changing application-facing queue semantics.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"framework": "bcp",
|
|
4
|
-
"versionTarget": "0.2.
|
|
4
|
+
"versionTarget": "0.2.8",
|
|
5
5
|
"releaseState": "unreleased",
|
|
6
6
|
"sections": [
|
|
7
7
|
{
|
|
@@ -57,12 +57,13 @@
|
|
|
57
57
|
{
|
|
58
58
|
"id": "runtime",
|
|
59
59
|
"title": "Runtime & Infrastructure",
|
|
60
|
-
"description": "Middleware, observability, logging, caching, security and production hardening.",
|
|
60
|
+
"description": "Middleware, background jobs, observability, logging, caching, security and production hardening.",
|
|
61
61
|
"pages": [
|
|
62
62
|
{ "route": "/docs/middleware", "source": "middleware.md", "title": "Middleware" },
|
|
63
63
|
{ "route": "/docs/hydration", "source": "hydration.md", "title": "Hydration" },
|
|
64
64
|
{ "route": "/docs/development-logging", "source": "development-logging.md", "title": "Logging" },
|
|
65
65
|
{ "route": "/docs/observability", "source": "observability.md", "title": "Observability Platform v2" },
|
|
66
|
+
{ "route": "/docs/background-jobs", "source": "background-jobs.md", "title": "Background Jobs Platform" },
|
|
66
67
|
{ "route": "/docs/caching", "source": "caching.md", "title": "Caching" },
|
|
67
68
|
{ "route": "/docs/security", "source": "security.md", "title": "Security" },
|
|
68
69
|
{ "route": "/docs/production-hardening", "source": "production-hardening.md", "title": "Production Hardening" }
|
|
@@ -108,7 +109,8 @@
|
|
|
108
109
|
}
|
|
109
110
|
],
|
|
110
111
|
"releases": [
|
|
111
|
-
{ "route": "/releases/0.2.
|
|
112
|
+
{ "route": "/releases/0.2.8", "source": "releases/0.2.8.md", "version": "0.2.8", "state": "unreleased" },
|
|
113
|
+
{ "route": "/releases/0.2.7", "source": "releases/0.2.7.md", "version": "0.2.7" },
|
|
112
114
|
{ "route": "/releases/0.2.6", "source": "releases/0.2.6.md", "version": "0.2.6" },
|
|
113
115
|
{ "route": "/releases/0.2.5", "source": "releases/0.2.5.md", "version": "0.2.5" },
|
|
114
116
|
{ "route": "/releases/0.2.4", "source": "releases/0.2.4.md", "version": "0.2.4" },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"framework": "bcp",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"releaseState": "unreleased",
|
|
6
|
-
"baseline": "
|
|
6
|
+
"baseline": "background-jobs-platform",
|
|
7
7
|
"runtime": {
|
|
8
8
|
"node": ">=24.11.0",
|
|
9
9
|
"react": "19",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"bcp/error",
|
|
20
20
|
"bcp/database",
|
|
21
21
|
"bcp/auth",
|
|
22
|
+
"bcp/jobs",
|
|
22
23
|
"bcp/observability",
|
|
23
24
|
"bcp/server",
|
|
24
25
|
"bcp/server-only",
|
|
@@ -65,6 +66,13 @@
|
|
|
65
66
|
"prometheusMetrics": true,
|
|
66
67
|
"requestMetricsMiddleware": true,
|
|
67
68
|
"healthChecks": true,
|
|
69
|
+
"backgroundJobsPlatform": true,
|
|
70
|
+
"jobQueueAdapterContract": true,
|
|
71
|
+
"inMemoryJobQueue": true,
|
|
72
|
+
"delayedJobs": true,
|
|
73
|
+
"jobRetries": true,
|
|
74
|
+
"jobWorkerConcurrency": true,
|
|
75
|
+
"jobCancellation": true,
|
|
68
76
|
"databaseMigrations": true,
|
|
69
77
|
"databaseAdapterContract": true,
|
|
70
78
|
"databasePostgresql": true,
|
|
@@ -104,7 +112,7 @@
|
|
|
104
112
|
"s3-compatible"
|
|
105
113
|
],
|
|
106
114
|
"compatibility": {
|
|
107
|
-
"previousBaseline": "0.2.
|
|
115
|
+
"previousBaseline": "0.2.7",
|
|
108
116
|
"intentionalBreakingChangesFromPreviousBaseline": false,
|
|
109
117
|
"migrationGuide": "migration-0.2.md"
|
|
110
118
|
},
|
|
@@ -121,7 +129,8 @@
|
|
|
121
129
|
"authSessionStore": "auth-session-store.md",
|
|
122
130
|
"authorizationSecurity": "authorization-security.md",
|
|
123
131
|
"observability": "observability.md",
|
|
132
|
+
"backgroundJobs": "background-jobs.md",
|
|
124
133
|
"migrationGuide": "migration-0.2.md",
|
|
125
|
-
"releaseNotes": "releases/0.2.
|
|
134
|
+
"releaseNotes": "releases/0.2.8.md"
|
|
126
135
|
}
|
|
127
136
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# BCP Framework 0.2.8
|
|
2
|
+
|
|
3
|
+
**Milestone:** Background Jobs Platform
|
|
4
|
+
|
|
5
|
+
`0.2.8` adds a framework-native server-side background-job queue contract with a process-local reference adapter, delayed execution, retries/backoff, worker concurrency and cancellation.
|
|
6
|
+
|
|
7
|
+
> Release state: unreleased until the final RC sequence, tag and npm publication complete.
|
|
8
|
+
|
|
9
|
+
## New public entrypoint
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
createJobQueue,
|
|
14
|
+
createMemoryJobQueueAdapter,
|
|
15
|
+
} from "bcp/jobs";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`bcp/jobs` is server-only and is protected by both package browser exports and the BCP client-boundary validator.
|
|
19
|
+
|
|
20
|
+
## Queue adapter contract
|
|
21
|
+
|
|
22
|
+
Applications and ecosystem packages can implement `JobQueueAdapter` for durable/shared infrastructure.
|
|
23
|
+
|
|
24
|
+
The contract covers:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
enqueue
|
|
28
|
+
reserve
|
|
29
|
+
complete
|
|
30
|
+
fail
|
|
31
|
+
cancel
|
|
32
|
+
get
|
|
33
|
+
list
|
|
34
|
+
close (optional)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`reserve()` is the adapter's atomic claim boundary for multi-worker processing.
|
|
38
|
+
|
|
39
|
+
## In-memory reference adapter
|
|
40
|
+
|
|
41
|
+
`createMemoryJobQueueAdapter()` provides a dependency-free process-local adapter for development, tests and prototypes.
|
|
42
|
+
|
|
43
|
+
The memory adapter is intentionally not durable and cannot coordinate multiple Node.js processes or containers.
|
|
44
|
+
|
|
45
|
+
## Delayed jobs
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
await jobs.enqueue(
|
|
49
|
+
"report.generate",
|
|
50
|
+
payload,
|
|
51
|
+
{
|
|
52
|
+
delayMs: 60_000,
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Jobs are eligible for reservation only after `availableAt`.
|
|
58
|
+
|
|
59
|
+
## Retry and backoff
|
|
60
|
+
|
|
61
|
+
Queues default to three maximum attempts and capped exponential retry delay.
|
|
62
|
+
|
|
63
|
+
Applications can configure a fixed delay or callback:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
createJobQueue({
|
|
67
|
+
defaultMaxAttempts: 5,
|
|
68
|
+
retryDelayMs: attempt =>
|
|
69
|
+
attempt * 5_000,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Per-job `maxAttempts` overrides the queue default.
|
|
74
|
+
|
|
75
|
+
## Workers
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const worker =
|
|
79
|
+
jobs.startWorker({
|
|
80
|
+
concurrency: 4,
|
|
81
|
+
pollIntervalMs: 250,
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Workers run concurrent processing loops and can be stopped gracefully.
|
|
86
|
+
|
|
87
|
+
`jobs.close()` stops all workers started by that queue and closes the adapter when supported.
|
|
88
|
+
|
|
89
|
+
## Cancellation
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
await jobs.cancel(jobId);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Cancelled records remain terminal; later worker completion/failure writes do not overwrite cancellation in the memory adapter.
|
|
96
|
+
|
|
97
|
+
## Manual processing
|
|
98
|
+
|
|
99
|
+
`processNext()` remains available for deterministic tests and custom supervisors.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const processed =
|
|
103
|
+
await jobs.processNext();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Package and platform contracts
|
|
107
|
+
|
|
108
|
+
`0.2.8` adds:
|
|
109
|
+
|
|
110
|
+
- `bcp/jobs` to the framework package export map,
|
|
111
|
+
- TypeScript path mapping for repository development,
|
|
112
|
+
- server-only client-boundary enforcement,
|
|
113
|
+
- Background Jobs capability metadata in `platform-manifest.json`,
|
|
114
|
+
- API-manifest ownership and docs-web navigation,
|
|
115
|
+
- prepared-package smoke coverage.
|
|
116
|
+
|
|
117
|
+
## Compatibility
|
|
118
|
+
|
|
119
|
+
`0.2.8` does not intentionally remove or rename existing `0.2.7` public APIs.
|
|
120
|
+
|
|
121
|
+
Existing applications do not need to adopt background jobs.
|
|
122
|
+
|
|
123
|
+
The built-in queue is opt-in and no additional runtime dependency is installed.
|
|
124
|
+
|
|
125
|
+
## Delivery semantics
|
|
126
|
+
|
|
127
|
+
Durable adapters should be designed for at-least-once processing. Job handlers should be idempotent when duplicate side effects are unsafe.
|
|
128
|
+
|
|
129
|
+
`0.2.8` does not promise exactly-once execution.
|
|
130
|
+
|
|
131
|
+
## Not included
|
|
132
|
+
|
|
133
|
+
This milestone does not ship built-in adapters for Redis, PostgreSQL, SQS, RabbitMQ, Kafka, cron scheduling or workflow orchestration.
|
|
134
|
+
|
|
135
|
+
Those providers/features can be layered behind the new queue contract in later versions.
|
|
136
|
+
|
|
137
|
+
## Validation
|
|
138
|
+
|
|
139
|
+
Before tagging/publishing `0.2.8` run:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run typecheck
|
|
143
|
+
npm run test:unit
|
|
144
|
+
npm run test:integration
|
|
145
|
+
npm run test:e2e
|
|
146
|
+
npm run test:package
|
|
147
|
+
npm run rc:check
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The final release tag must point to the exact commit that passed the complete RC sequence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,6 +62,11 @@
|
|
|
62
62
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
63
63
|
"default": "./packages/client/src/auth.ts"
|
|
64
64
|
},
|
|
65
|
+
"./jobs": {
|
|
66
|
+
"types": "./packages/client/src/jobs.ts",
|
|
67
|
+
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
68
|
+
"default": "./packages/client/src/jobs.ts"
|
|
69
|
+
},
|
|
65
70
|
"./observability": {
|
|
66
71
|
"types": "./packages/client/src/observability.ts",
|
|
67
72
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createJobQueue,
|
|
3
|
+
createMemoryJobQueueAdapter,
|
|
4
|
+
type BackgroundJobQueue,
|
|
5
|
+
type EnqueueJobOptions,
|
|
6
|
+
type JobHandler,
|
|
7
|
+
type JobHandlerContext,
|
|
8
|
+
type JobQueueAdapter,
|
|
9
|
+
type JobQueueOptions,
|
|
10
|
+
type JobRecord,
|
|
11
|
+
type JobRetryDelay,
|
|
12
|
+
type JobState,
|
|
13
|
+
type JobWorker,
|
|
14
|
+
type MemoryJobQueueAdapter,
|
|
15
|
+
type StartJobWorkerOptions,
|
|
16
|
+
} from "../../server/src/jobs.js";
|