@starci/jest-preset 2.0.0 → 2.1.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 +5 -1
- package/README.md +7 -0
- package/outbox.cjs +73 -1
- package/outbox.d.ts +50 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.
|
|
3
|
+
## 2.1.0 - 2026-09-30
|
|
4
|
+
|
|
5
|
+
- Added: the claim side of `recordingOutbox()`, so a claim-side consumer is specced with the kit double the `OUTBOX` token requires (`spec-infra-double-from-kit`) instead of `mock<Outbox>()`. `queueRecords(...records)` fills a backlog that `claimDue(params)` hands out in order (only the asked queues, at most `limit`, removed once claimed; an empty backlog is an empty claim, a record queued twice is a duplicate delivery), `failNext(operation, error)` makes the next `enqueue`, `claimDue`, `complete`, `retry` or `bury` reject once, and `claims`, `completed`, `retried`, `buried` and `backlog` expose what happened. `RecordingOutbox<M, R>` takes the claimed record type as a second parameter; `ClaimedRecord`, `ClaimParams`, `RetryParams`, `BuryParams` and `OutboxOperation` are exported from `./outbox`. `clear()` resets the claim side too. No breaking change.
|
|
6
|
+
|
|
7
|
+
## 2.0.0 - 2026-09-30
|
|
4
8
|
|
|
5
9
|
- Breaking: `starciJestConfig()` takes no options. The repository `jest.config.js` is a managed file, exactly `module.exports = require("@starci/jest-preset").starciJestConfig()`, rendered by `hfs sync` and compared by `hfs check` (HFS_MANAGED_FILE_DRIFT), so `moduleNameMapper`, `roots`, `tsconfig`, `rootDir`, `unit` and `e2e` overrides are gone. `StarciJestOptions` is removed.
|
|
6
10
|
- Added: the three path aliases (`@features/*`, `@modules/*`, `@tests/*`) are part of the preset (`MODULE_NAME_MAPPER`), matching the aliases the managed `tsconfig.json` declares.
|
package/README.md
CHANGED
|
@@ -114,6 +114,13 @@ outbox.messages // what the store would hold: the first write of each (queue
|
|
|
114
114
|
outbox.writes // every message written, duplicates included
|
|
115
115
|
outbox.entries // every write with its manager and `inTransaction`
|
|
116
116
|
outbox.allInTransaction; outbox.messagesOf("queue"); outbox.clear()
|
|
117
|
+
|
|
118
|
+
// the claim side: script what a worker's claim returns
|
|
119
|
+
outbox.queueRecords(record) // the backlog claimDue hands out, in order, filtered by queue and cut at limit
|
|
120
|
+
outbox.queueRecords(record, record) // a duplicate delivery
|
|
121
|
+
await outbox.claimDue(params) // [] when the backlog is empty
|
|
122
|
+
outbox.failNext("complete", new Error("db down")) // the next call of that operation rejects once
|
|
123
|
+
outbox.claims; outbox.completed; outbox.retried; outbox.buried; outbox.backlog
|
|
117
124
|
```
|
|
118
125
|
|
|
119
126
|
## `builder`, `fakeIds` and the Outcome matchers
|
package/outbox.cjs
CHANGED
|
@@ -7,13 +7,32 @@
|
|
|
7
7
|
* would hold: one message per (queue, eventId), like the real unique key, so a duplicate write keeps one. `writes` is every
|
|
8
8
|
* call, duplicates included, and `entries` adds `inTransaction`: whether the manager was the view a `fakeTransaction` body
|
|
9
9
|
* received, so a spec proves a message is written in the transaction of the change that caused it.
|
|
10
|
+
*
|
|
11
|
+
* The claim side scripts what a worker's claim returns. `queueRecords(...records)` fills the backlog; `claimDue(params)` hands
|
|
12
|
+
* out, in order, up to `params.limit` backlog records whose queue is in `params.queues` and removes them, so an empty backlog is
|
|
13
|
+
* an empty claim and queueing the same record twice is a duplicate delivery. `claims` keeps every claim's params, `completed`,
|
|
14
|
+
* `retried` and `buried` keep what the worker reported, and `failNext(operation, error)` makes the next call of that operation
|
|
15
|
+
* reject once, so a spec drives the failure path.
|
|
10
16
|
*/
|
|
11
17
|
const { isTransactionManager } = require("./entity-manager.cjs")
|
|
12
18
|
|
|
13
19
|
function recordingOutbox() {
|
|
14
20
|
const written = []
|
|
21
|
+
const backlog = []
|
|
22
|
+
const claims = []
|
|
23
|
+
const completed = []
|
|
24
|
+
const retried = []
|
|
25
|
+
const buried = []
|
|
26
|
+
const failures = new Map()
|
|
27
|
+
const failIfScripted = (operation) => {
|
|
28
|
+
if (!failures.has(operation)) return
|
|
29
|
+
const error = failures.get(operation)
|
|
30
|
+
failures.delete(operation)
|
|
31
|
+
throw error
|
|
32
|
+
}
|
|
15
33
|
return {
|
|
16
34
|
async enqueue(manager, message) {
|
|
35
|
+
failIfScripted("enqueue")
|
|
17
36
|
written.push({ message, manager, inTransaction: isTransactionManager(manager) })
|
|
18
37
|
},
|
|
19
38
|
/** The messages the store would hold: the first write of each (queue, eventId). */
|
|
@@ -44,8 +63,61 @@ function recordingOutbox() {
|
|
|
44
63
|
messagesOf(queue) {
|
|
45
64
|
return this.messages.filter((message) => message.queue === queue)
|
|
46
65
|
},
|
|
66
|
+
/** Adds records to the backlog `claimDue` hands out. */
|
|
67
|
+
queueRecords(...records) {
|
|
68
|
+
backlog.push(...records)
|
|
69
|
+
},
|
|
70
|
+
async claimDue(params) {
|
|
71
|
+
claims.push(params)
|
|
72
|
+
failIfScripted("claimDue")
|
|
73
|
+
const claimed = []
|
|
74
|
+
for (const record of [...backlog]) {
|
|
75
|
+
if (claimed.length >= params.limit) break
|
|
76
|
+
if (!params.queues.includes(record.queue)) continue
|
|
77
|
+
claimed.push(record)
|
|
78
|
+
backlog.splice(backlog.indexOf(record), 1)
|
|
79
|
+
}
|
|
80
|
+
return claimed
|
|
81
|
+
},
|
|
82
|
+
async complete(id) {
|
|
83
|
+
failIfScripted("complete")
|
|
84
|
+
completed.push(id)
|
|
85
|
+
},
|
|
86
|
+
async retry(params) {
|
|
87
|
+
failIfScripted("retry")
|
|
88
|
+
retried.push(params)
|
|
89
|
+
},
|
|
90
|
+
async bury(params) {
|
|
91
|
+
failIfScripted("bury")
|
|
92
|
+
buried.push(params)
|
|
93
|
+
},
|
|
94
|
+
/** Makes the next call of `operation` reject with `error`, once. */
|
|
95
|
+
failNext(operation, error) {
|
|
96
|
+
failures.set(operation, error)
|
|
97
|
+
},
|
|
98
|
+
/** The params of every `claimDue` call, in order. */
|
|
99
|
+
get claims() {
|
|
100
|
+
return [...claims]
|
|
101
|
+
},
|
|
102
|
+
/** The ids passed to `complete`, in order. */
|
|
103
|
+
get completed() {
|
|
104
|
+
return [...completed]
|
|
105
|
+
},
|
|
106
|
+
/** The params of every `retry`, in order. */
|
|
107
|
+
get retried() {
|
|
108
|
+
return [...retried]
|
|
109
|
+
},
|
|
110
|
+
/** The params of every `bury`, in order. */
|
|
111
|
+
get buried() {
|
|
112
|
+
return [...buried]
|
|
113
|
+
},
|
|
114
|
+
/** The records still waiting for a claim. */
|
|
115
|
+
get backlog() {
|
|
116
|
+
return [...backlog]
|
|
117
|
+
},
|
|
47
118
|
clear() {
|
|
48
|
-
written.length = 0
|
|
119
|
+
for (const list of [written, backlog, claims, completed, retried, buried]) list.length = 0
|
|
120
|
+
failures.clear()
|
|
49
121
|
},
|
|
50
122
|
}
|
|
51
123
|
}
|
package/outbox.d.ts
CHANGED
|
@@ -12,9 +12,37 @@ export interface OutboxWrite<M extends RecordedMessage = RecordedMessage> {
|
|
|
12
12
|
/** Whether the manager was the view a `fakeTransaction` body received. */
|
|
13
13
|
readonly inTransaction: boolean
|
|
14
14
|
}
|
|
15
|
+
/** A message a worker has claimed, as the outbox port returns it. */
|
|
16
|
+
export interface ClaimedRecord {
|
|
17
|
+
readonly id: string
|
|
18
|
+
readonly queue: string
|
|
19
|
+
readonly eventId: string
|
|
20
|
+
readonly payload: unknown
|
|
21
|
+
readonly attempts: number
|
|
22
|
+
}
|
|
23
|
+
/** What a worker asks for when it claims due messages. */
|
|
24
|
+
export interface ClaimParams {
|
|
25
|
+
readonly at: Date
|
|
26
|
+
readonly queues: ReadonlyArray<string>
|
|
27
|
+
readonly limit: number
|
|
28
|
+
readonly visibilityMs: number
|
|
29
|
+
}
|
|
30
|
+
/** What a worker reports when a delivery failed and should be tried again. */
|
|
31
|
+
export interface RetryParams {
|
|
32
|
+
readonly id: string
|
|
33
|
+
readonly at: Date
|
|
34
|
+
readonly error: string
|
|
35
|
+
}
|
|
36
|
+
/** What a worker reports when a message will not be delivered any more. */
|
|
37
|
+
export interface BuryParams {
|
|
38
|
+
readonly id: string
|
|
39
|
+
readonly error: string
|
|
40
|
+
}
|
|
41
|
+
/** The outbox operations `failNext` can make reject. */
|
|
42
|
+
export type OutboxOperation = "enqueue" | "claimDue" | "complete" | "retry" | "bury"
|
|
15
43
|
|
|
16
|
-
/** An outbox double that records writes. */
|
|
17
|
-
export interface RecordingOutbox<M extends RecordedMessage = RecordedMessage> {
|
|
44
|
+
/** An outbox double that records writes and scripts the claim side. */
|
|
45
|
+
export interface RecordingOutbox<M extends RecordedMessage = RecordedMessage, R extends ClaimedRecord = ClaimedRecord> {
|
|
18
46
|
enqueue(manager: unknown, message: M): Promise<void>
|
|
19
47
|
/** What the store would hold: the first write of each (queue, eventId). */
|
|
20
48
|
readonly messages: ReadonlyArray<M>
|
|
@@ -25,8 +53,27 @@ export interface RecordingOutbox<M extends RecordedMessage = RecordedMessage> {
|
|
|
25
53
|
/** True when something was written and every write went through a `fakeTransaction` manager. */
|
|
26
54
|
readonly allInTransaction: boolean
|
|
27
55
|
messagesOf(queue: string): ReadonlyArray<M>
|
|
56
|
+
/** Adds records to the backlog `claimDue` hands out; the same record twice is a duplicate delivery. */
|
|
57
|
+
queueRecords(...records: ReadonlyArray<R>): void
|
|
58
|
+
/** Hands out, in order, up to `limit` backlog records of the asked queues and removes them; an empty backlog is an empty claim. */
|
|
59
|
+
claimDue(params: ClaimParams): Promise<Array<R>>
|
|
60
|
+
complete(id: string): Promise<void>
|
|
61
|
+
retry(params: RetryParams): Promise<void>
|
|
62
|
+
bury(params: BuryParams): Promise<void>
|
|
63
|
+
/** Makes the next call of `operation` reject with `error`, once. */
|
|
64
|
+
failNext(operation: OutboxOperation, error: Error): void
|
|
65
|
+
/** The params of every `claimDue` call. */
|
|
66
|
+
readonly claims: ReadonlyArray<ClaimParams>
|
|
67
|
+
/** The ids passed to `complete`. */
|
|
68
|
+
readonly completed: ReadonlyArray<string>
|
|
69
|
+
/** The params of every `retry`. */
|
|
70
|
+
readonly retried: ReadonlyArray<RetryParams>
|
|
71
|
+
/** The params of every `bury`. */
|
|
72
|
+
readonly buried: ReadonlyArray<BuryParams>
|
|
73
|
+
/** The records still waiting for a claim. */
|
|
74
|
+
readonly backlog: ReadonlyArray<R>
|
|
28
75
|
clear(): void
|
|
29
76
|
}
|
|
30
77
|
|
|
31
78
|
/** The outbox double. */
|
|
32
|
-
export declare function recordingOutbox<M extends RecordedMessage = RecordedMessage>(): RecordingOutbox<M>
|
|
79
|
+
export declare function recordingOutbox<M extends RecordedMessage = RecordedMessage, R extends ClaimedRecord = ClaimedRecord>(): RecordingOutbox<M, R>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/jest-preset",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The StarCi jest config for Nest repositories (unit, integration, e2e and contract projects, ts-jest with diagnostics off, per-file 100 coverage of *.service.ts only) and the one shared unit-test double kit: typed mock<T>(), mockEntityManager(), fakeTransaction(), fakeCache(), fakeLock(), recordingOutbox(), FakeClock, fakeIds(), builder() and the Outcome matchers toBeRefused/toSucceedWith.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|