@ddd-ts-kit/testing 0.10.0 → 0.11.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/README.md +20 -0
- package/dist/contracts/job-store/a-job.d.ts +24 -0
- package/dist/contracts/job-store/a-job.d.ts.map +1 -0
- package/dist/contracts/job-store/a-job.js +28 -0
- package/dist/contracts/job-store/a-job.js.map +1 -0
- package/dist/contracts/job-store/claim-suite.d.ts +15 -0
- package/dist/contracts/job-store/claim-suite.d.ts.map +1 -0
- package/dist/contracts/job-store/claim-suite.js +126 -0
- package/dist/contracts/job-store/claim-suite.js.map +1 -0
- package/dist/contracts/job-store/job-fixtures.d.ts +5 -0
- package/dist/contracts/job-store/job-fixtures.d.ts.map +1 -0
- package/dist/contracts/job-store/job-fixtures.js +5 -0
- package/dist/contracts/job-store/job-fixtures.js.map +1 -0
- package/dist/contracts/job-store/job-store-under-contract.d.ts +61 -0
- package/dist/contracts/job-store/job-store-under-contract.d.ts.map +1 -0
- package/dist/contracts/job-store/job-store-under-contract.js +2 -0
- package/dist/contracts/job-store/job-store-under-contract.js.map +1 -0
- package/dist/contracts/job-store/keep-suite.d.ts +11 -0
- package/dist/contracts/job-store/keep-suite.d.ts.map +1 -0
- package/dist/contracts/job-store/keep-suite.js +55 -0
- package/dist/contracts/job-store/keep-suite.js.map +1 -0
- package/dist/contracts/job-store/later.d.ts +3 -0
- package/dist/contracts/job-store/later.d.ts.map +1 -0
- package/dist/contracts/job-store/later.js +6 -0
- package/dist/contracts/job-store/later.js.map +1 -0
- package/dist/contracts/job-store/report-suite.d.ts +12 -0
- package/dist/contracts/job-store/report-suite.d.ts.map +1 -0
- package/dist/contracts/job-store/report-suite.js +82 -0
- package/dist/contracts/job-store/report-suite.js.map +1 -0
- package/dist/contracts/job-store/transaction-suite.d.ts +23 -0
- package/dist/contracts/job-store/transaction-suite.d.ts.map +1 -0
- package/dist/contracts/job-store/transaction-suite.js +68 -0
- package/dist/contracts/job-store/transaction-suite.js.map +1 -0
- package/dist/contracts/job-store-contract.d.ts +24 -0
- package/dist/contracts/job-store-contract.d.ts.map +1 -0
- package/dist/contracts/job-store-contract.js +22 -0
- package/dist/contracts/job-store-contract.js.map +1 -0
- package/dist/contracts/process-store-contract.d.ts +28 -0
- package/dist/contracts/process-store-contract.d.ts.map +1 -0
- package/dist/contracts/process-store-contract.js +81 -0
- package/dist/contracts/process-store-contract.js.map +1 -0
- package/dist/contracts.d.ts +3 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +2 -0
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/processes/command-matcher.d.ts +8 -0
- package/dist/processes/command-matcher.d.ts.map +1 -0
- package/dist/processes/command-matcher.js +2 -0
- package/dist/processes/command-matcher.js.map +1 -0
- package/dist/processes/given-process.d.ts +18 -0
- package/dist/processes/given-process.d.ts.map +1 -0
- package/dist/processes/given-process.js +17 -0
- package/dist/processes/given-process.js.map +1 -0
- package/dist/processes/process-outcome.d.ts +20 -0
- package/dist/processes/process-outcome.d.ts.map +1 -0
- package/dist/processes/process-outcome.js +67 -0
- package/dist/processes/process-outcome.js.map +1 -0
- package/dist/scenarios/event-mismatches.d.ts.map +1 -1
- package/dist/scenarios/event-mismatches.js +1 -18
- package/dist/scenarios/event-mismatches.js.map +1 -1
- package/dist/scenarios/subset-differences.d.ts +3 -0
- package/dist/scenarios/subset-differences.d.ts.map +1 -0
- package/dist/scenarios/subset-differences.js +21 -0
- package/dist/scenarios/subset-differences.js.map +1 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -200,6 +200,24 @@ givenEvents((id, history) => AttendanceRecord.rehydrate(id, history), [opened, n
|
|
|
200
200
|
aggregate: for every version of the history, the aggregate rebuilt from the snapshot its policy
|
|
201
201
|
would store and the events after it has the state and the version of the full replay.
|
|
202
202
|
|
|
203
|
+
## Process steps
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { givenProcess } from '@ddd-ts-kit/testing';
|
|
207
|
+
|
|
208
|
+
givenProcess<DeadlineState>(undefined)
|
|
209
|
+
.when((current) => deadlines.requested(booked, current))
|
|
210
|
+
.thenState({ appointmentId: 'appointment-1', confirmBefore: '2026-03-01T12:00:00.000Z' })
|
|
211
|
+
.thenTimeouts([{ name: 'deadline', at: deadline }])
|
|
212
|
+
.thenCommands([]);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A step of a process manager is a function of its state and of what happened: `givenProcess(state)`
|
|
216
|
+
(or `undefined` for an instance that does not exist) runs it and asserts its decision without a
|
|
217
|
+
store, a queue or a bus. `thenIgnored()`, `thenState(state)`, `thenCommands([...])` (each by its
|
|
218
|
+
class or by the properties that matter), `thenTimeouts([...])`, `thenCancelled([...])` and
|
|
219
|
+
`thenEnded()` chain; a failure is an `AssertionError` that says what differs.
|
|
220
|
+
|
|
203
221
|
## Refusals
|
|
204
222
|
|
|
205
223
|
A business refusal is known by its code, and carries its facts as data: a test asserts both,
|
|
@@ -454,6 +472,8 @@ repositoryContract(runner, { ...options, name: 'DrizzleOrderRepository', createR
|
|
|
454
472
|
| `processedEventStoreContract` | any `ProcessedEventStore`: once per consumer and event, also under concurrent deliveries |
|
|
455
473
|
| `eventStoreContract` | any `EventStore`: a stream created at version 0 and read back in order, numbered from 1, with its type, shape version, data, metadata and instant; an append at a version the stream no longer has (behind or ahead) refused with `CONCURRENCY_CONFLICT` and nothing kept of it; one of two appends at the same version through; streams numbered apart; the whole store read in the order of the appends, positions rising from 1, after a position and within a limit; `head()` |
|
|
456
474
|
| `eventStoreUnitOfWorkContract` | a transactional `EventStore` with the outbox of its database under one `UnitOfWork`: the stream and the outbox records published for its events commit together; nothing left in the outbox for an append rolled back, nor for one refused by a concurrency conflict. `createSubject()` answers `{ unitOfWork, store, outbox }` |
|
|
475
|
+
| `processStoreContract` | any `ProcessStore` of process managers: an instance as saved (JSON state, armed timeouts, end), a version that is not the next one refused with `CONCURRENCY_CONFLICT`, one of two first saves winning, and with `unitOfWork` nothing left by a rolled back save |
|
|
476
|
+
| `jobStoreContract` | any `JobStore` of background jobs: due jobs earliest first and each to one claim, delayed jobs, idempotency keys scoped by tenant (two tenants never share one), the tenant and the subject `find` shows, the lease and its heartbeats, reports heard only from the attempt that holds the job (progress, checkpoint handed to the next attempt, success, retry, dead and revived, release), cancellation of a waiting and of a running job, purge. With `unitOfWork` (over the same context as the store just created), a job added, cancelled or revived in a unit of work that rolls back is left as it was, and one that commits is added, cancelled or revived. The claims ask for a lease of 30 s |
|
|
457
477
|
| `cacheContract` | any `Cache`: round trip of every shape of value, isolation of entries, deletion, time to live |
|
|
458
478
|
| `lockContract` | any `Lock`: one holder at a time also under concurrent requests, release, expiry, a late release ignored, growing fencing token, `waitMs` |
|
|
459
479
|
| `jobSchedulerContract` | any `JobScheduler`: registration and description, a duplicate name and an unreadable schedule refused, `runNow` (also for a disabled job, an unknown name rejected), nothing by time before `start` or after `stop`, no overlap by default, a failing job answered `failed` with its code while the others go on, the execution context of a run |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EnqueuedBy } from '@ddd-ts-kit/kernel';
|
|
2
|
+
export interface JobOverrides {
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
readonly queue?: string;
|
|
5
|
+
readonly runAt?: Date;
|
|
6
|
+
readonly idempotencyKey?: string;
|
|
7
|
+
readonly payload?: unknown;
|
|
8
|
+
/** The tenant of the work that enqueues it: `tenant-3` by default, `null` for none. */
|
|
9
|
+
readonly tenantId?: string | null;
|
|
10
|
+
readonly subject?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A job of the contract: `billing.compile-statements`, due at `NOW` unless told otherwise. */
|
|
13
|
+
export declare function aJob(id: string, overrides?: JobOverrides): {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
queue: string;
|
|
17
|
+
payload: {};
|
|
18
|
+
enqueuedBy: EnqueuedBy;
|
|
19
|
+
enqueuedAt: Date;
|
|
20
|
+
runAt: Date;
|
|
21
|
+
idempotencyKey?: string;
|
|
22
|
+
subject?: string;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=a-job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-job.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/a-job.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAOrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,+FAA+F;AAC/F,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,GAAE,YAAiB;;;;;;;;;;EAc5D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NOW, QUEUE } from './job-fixtures.js';
|
|
2
|
+
const TENANT = 'tenant-3';
|
|
3
|
+
const TRACEPARENT = '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01';
|
|
4
|
+
/** A job of the contract: `billing.compile-statements`, due at `NOW` unless told otherwise. */
|
|
5
|
+
export function aJob(id, overrides = {}) {
|
|
6
|
+
const { name = 'billing.compile-statements', queue = QUEUE, runAt = NOW, idempotencyKey, subject } = overrides;
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
name,
|
|
10
|
+
queue,
|
|
11
|
+
payload: overrides.payload ?? { month: '2026-02', customers: ['c-1', 'c-2'], draft: false, total: 12.5 },
|
|
12
|
+
enqueuedBy: enqueuedBy(overrides.tenantId === undefined ? TENANT : overrides.tenantId),
|
|
13
|
+
enqueuedAt: NOW,
|
|
14
|
+
runAt,
|
|
15
|
+
...(idempotencyKey === undefined ? {} : { idempotencyKey }),
|
|
16
|
+
...(subject === undefined ? {} : { subject }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function enqueuedBy(tenantId) {
|
|
20
|
+
const tenant = tenantId === null ? {} : { tenantId };
|
|
21
|
+
return {
|
|
22
|
+
correlationId: 'corr-1',
|
|
23
|
+
actor: { id: 'user-7', type: 'user', ...tenant, roles: ['accountant'], attributes: {} },
|
|
24
|
+
...tenant,
|
|
25
|
+
traceparent: TRACEPARENT,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=a-job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-job.js","sourceRoot":"","sources":["../../../src/contracts/job-store/a-job.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B,MAAM,WAAW,GAAG,yDAAyD,CAAC;AAa9E,+FAA+F;AAC/F,MAAM,UAAU,IAAI,CAAC,EAAU,EAAE,SAAS,GAAiB,EAAE;IAC3D,MAAM,EAAE,IAAI,GAAG,4BAA4B,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC;IAE/G,OAAO;QACL,EAAE;QACF,IAAI;QACJ,KAAK;QACL,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;QACxG,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC;QACtF,UAAU,EAAE,GAAG;QACf,KAAK;QACL,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;QAC3D,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,QAAuB;IACzC,MAAM,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;IAErD,OAAO;QACL,aAAa,EAAE,QAAQ;QACvB,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;QACvF,GAAG,MAAM;QACT,WAAW,EAAE,WAAW;KACzB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import type { JobStoreUnderContract } from './job-store-under-contract.js';
|
|
3
|
+
/** What `add` keeps and what `claim` hands out: due jobs of the queue, earliest first, each to one claim. */
|
|
4
|
+
export declare class ClaimSuite extends ContractSuite<JobStoreUnderContract> {
|
|
5
|
+
protected tests(): void;
|
|
6
|
+
private handsOutWhatWasAdded;
|
|
7
|
+
private respectsTheInstant;
|
|
8
|
+
private dropsDuplicates;
|
|
9
|
+
private keepsTheKeysOfTenantsApart;
|
|
10
|
+
private leasesWhatItHandsOut;
|
|
11
|
+
private addMany;
|
|
12
|
+
private claim;
|
|
13
|
+
private claimedIds;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=claim-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/claim-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,6GAA6G;AAC7G,qBAAa,UAAW,SAAQ,aAAa,CAAC,qBAAqB,CAAC;IAClE,SAAS,CAAC,KAAK,IAAI,IAAI,CAMtB;IAED,OAAO,CAAC,oBAAoB;IAsC5B,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,oBAAoB;YA+Bd,OAAO;IAMrB,OAAO,CAAC,KAAK;YAIC,UAAU;CAGzB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import { aJob } from './a-job.js';
|
|
3
|
+
import { LEASE_MS, NOW, QUEUE } from './job-fixtures.js';
|
|
4
|
+
import { later } from './later.js';
|
|
5
|
+
/** What `add` keeps and what `claim` hands out: due jobs of the queue, earliest first, each to one claim. */
|
|
6
|
+
export class ClaimSuite extends ContractSuite {
|
|
7
|
+
tests() {
|
|
8
|
+
this.handsOutWhatWasAdded();
|
|
9
|
+
this.respectsTheInstant();
|
|
10
|
+
this.dropsDuplicates();
|
|
11
|
+
this.keepsTheKeysOfTenantsApart();
|
|
12
|
+
this.leasesWhatItHandsOut();
|
|
13
|
+
}
|
|
14
|
+
handsOutWhatWasAdded() {
|
|
15
|
+
this.it('starts empty', async () => {
|
|
16
|
+
this.expect(await this.subject.stats(QUEUE)).toEqual({ waiting: 0, running: 0, dead: 0 });
|
|
17
|
+
this.expect(await this.claimedIds(10, NOW)).toEqual([]);
|
|
18
|
+
this.expect(await this.subject.find('unknown')).toBe(undefined);
|
|
19
|
+
});
|
|
20
|
+
this.it('hands out the job exactly as added, the attempt counted, and shows it running', async () => {
|
|
21
|
+
const added = await this.subject.add(aJob('job-1'));
|
|
22
|
+
const [claimed] = await this.claim(10, NOW);
|
|
23
|
+
const { enqueuedBy, payload, name } = aJob('job-1');
|
|
24
|
+
this.expect(added).toEqual({ id: 'job-1', duplicate: false });
|
|
25
|
+
this.expect(claimed).toEqual({
|
|
26
|
+
id: 'job-1',
|
|
27
|
+
name,
|
|
28
|
+
queue: QUEUE,
|
|
29
|
+
payload,
|
|
30
|
+
enqueuedBy,
|
|
31
|
+
enqueuedAt: NOW,
|
|
32
|
+
attempt: 1,
|
|
33
|
+
});
|
|
34
|
+
this.expect((await this.subject.find('job-1'))?.status).toBe('running');
|
|
35
|
+
this.expect(await this.subject.stats(QUEUE)).toEqual({ waiting: 0, running: 1, dead: 0 });
|
|
36
|
+
});
|
|
37
|
+
this.it('hands out the earliest first and only the jobs of the queue asked', async () => {
|
|
38
|
+
await this.subject.add(aJob('job-1', { runAt: later(-1_000) }));
|
|
39
|
+
await this.subject.add(aJob('job-2', { runAt: later(-3_000) }));
|
|
40
|
+
await this.subject.add(aJob('job-3', { name: 'audit.verify-chain', queue: 'audit' }));
|
|
41
|
+
await this.subject.add(aJob('job-4', { runAt: later(-1_000) }));
|
|
42
|
+
this.expect(await this.claimedIds(10, NOW)).toEqual(['job-2', 'job-1', 'job-4']);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
respectsTheInstant() {
|
|
46
|
+
this.it('keeps a job due later until its instant', async () => {
|
|
47
|
+
await this.subject.add(aJob('job-1', { runAt: later(60_000) }));
|
|
48
|
+
this.expect(await this.claimedIds(10, later(59_999))).toEqual([]);
|
|
49
|
+
this.expect(await this.claimedIds(10, later(60_000))).toEqual(['job-1']);
|
|
50
|
+
});
|
|
51
|
+
this.it('honours the limit and leaves the rest to the next claim', async () => {
|
|
52
|
+
await this.addMany(['job-1', 'job-2', 'job-3']);
|
|
53
|
+
this.expect(await this.claimedIds(2, NOW)).toEqual(['job-1', 'job-2']);
|
|
54
|
+
this.expect(await this.claimedIds(2, NOW)).toEqual(['job-3']);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
dropsDuplicates() {
|
|
58
|
+
this.it('answers the job already kept for the same name and idempotency key, and adds nothing', async () => {
|
|
59
|
+
await this.subject.add(aJob('job-1', { idempotencyKey: 'feb' }));
|
|
60
|
+
const again = await this.subject.add(aJob('job-2', { idempotencyKey: 'feb' }));
|
|
61
|
+
const otherName = await this.subject.add(aJob('job-3', { idempotencyKey: 'feb', name: 'billing.send-statements' }));
|
|
62
|
+
this.expect(again).toEqual({ id: 'job-1', duplicate: true });
|
|
63
|
+
this.expect(otherName).toEqual({ id: 'job-3', duplicate: false });
|
|
64
|
+
this.expect(await this.subject.find('job-2')).toBe(undefined);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
keepsTheKeysOfTenantsApart() {
|
|
68
|
+
this.it('keeps the idempotency keys of two tenants, and of work without a tenant, apart', async () => {
|
|
69
|
+
await this.subject.add(aJob('job-1', { idempotencyKey: 'feb' }));
|
|
70
|
+
const otherTenant = await this.subject.add(aJob('job-2', { idempotencyKey: 'feb', tenantId: 'tenant-4' }));
|
|
71
|
+
const noTenant = await this.subject.add(aJob('job-3', { idempotencyKey: 'feb', tenantId: null }));
|
|
72
|
+
const noTenantAgain = await this.subject.add(aJob('job-4', { idempotencyKey: 'feb', tenantId: null }));
|
|
73
|
+
this.expect([otherTenant, noTenant, noTenantAgain]).toEqual([
|
|
74
|
+
{ id: 'job-2', duplicate: false },
|
|
75
|
+
{ id: 'job-3', duplicate: false },
|
|
76
|
+
{ id: 'job-3', duplicate: true },
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
this.it('shows the tenant and the subject of a job, never its payload', async () => {
|
|
80
|
+
await this.subject.add(aJob('job-1', { subject: 'customer-9' }));
|
|
81
|
+
await this.subject.add(aJob('job-2', { tenantId: null }));
|
|
82
|
+
const [first, second] = [await this.subject.find('job-1'), await this.subject.find('job-2')];
|
|
83
|
+
this.expect([first?.tenantId, first?.subject, first !== undefined && 'payload' in first]).toEqual([
|
|
84
|
+
'tenant-3',
|
|
85
|
+
'customer-9',
|
|
86
|
+
false,
|
|
87
|
+
]);
|
|
88
|
+
this.expect([second?.tenantId, second?.subject]).toEqual([undefined, undefined]);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
leasesWhatItHandsOut() {
|
|
92
|
+
this.it('hides a claimed job until its lease ends, then hands it out again, one attempt more', async () => {
|
|
93
|
+
await this.subject.add(aJob('job-1'));
|
|
94
|
+
await this.claim(10, NOW);
|
|
95
|
+
this.expect(await this.claimedIds(10, later(LEASE_MS - 1))).toEqual([]);
|
|
96
|
+
const [again] = await this.claim(10, later(LEASE_MS));
|
|
97
|
+
this.expect(again?.attempt).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
this.it('keeps a job hidden while its heartbeats extend the lease', async () => {
|
|
100
|
+
await this.subject.add(aJob('job-1'));
|
|
101
|
+
await this.claim(10, NOW);
|
|
102
|
+
this.expect(await this.subject.heartbeat({ id: 'job-1', attempt: 1 }, later(LEASE_MS * 2))).toBe(true);
|
|
103
|
+
this.expect(await this.claimedIds(10, later(LEASE_MS))).toEqual([]);
|
|
104
|
+
this.expect(await this.subject.heartbeat({ id: 'job-1', attempt: 2 }, later(LEASE_MS * 3))).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
this.it('never hands the same job to two concurrent claims', async () => {
|
|
107
|
+
await this.addMany(['job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6']);
|
|
108
|
+
const batches = await Promise.all([this.claimedIds(4, NOW), this.claimedIds(4, NOW)]);
|
|
109
|
+
const ids = batches.flat();
|
|
110
|
+
this.expect(ids.length).toBe(6);
|
|
111
|
+
this.expect(new Set(ids).size).toBe(6);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async addMany(ids) {
|
|
115
|
+
for (const id of ids) {
|
|
116
|
+
await this.subject.add(aJob(id));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
claim(limit, now) {
|
|
120
|
+
return this.subject.claim({ queue: QUEUE, limit, now, leaseMs: LEASE_MS });
|
|
121
|
+
}
|
|
122
|
+
async claimedIds(limit, now) {
|
|
123
|
+
return (await this.claim(limit, now)).map((job) => job.id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=claim-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-suite.js","sourceRoot":"","sources":["../../../src/contracts/job-store/claim-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-fixtures.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/job-fixtures.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,eAAO,MAAM,GAAG,MAAuC,CAAC;AAExD,eAAO,MAAM,QAAQ,QAAS,CAAC;AAE/B,eAAO,MAAM,KAAK,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-fixtures.js","sourceRoot":"","sources":["../../../src/contracts/job-store/job-fixtures.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;AAExD,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE/B,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { EnqueuedBy, EnqueuedJob, JobProgress, QueuedJob } from '@ddd-ts-kit/kernel';
|
|
2
|
+
interface Lease {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly attempt: number;
|
|
5
|
+
}
|
|
6
|
+
interface Claimed {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly queue: string;
|
|
10
|
+
readonly payload: unknown;
|
|
11
|
+
readonly enqueuedBy: EnqueuedBy;
|
|
12
|
+
readonly enqueuedAt: Date;
|
|
13
|
+
readonly attempt: number;
|
|
14
|
+
readonly checkpoint?: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The job store port of `@ddd-ts-kit/scheduling`, restated structurally so that this package
|
|
18
|
+
* does not depend on it. Any `JobStore` fits.
|
|
19
|
+
*/
|
|
20
|
+
export interface JobStoreUnderContract {
|
|
21
|
+
add(job: {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly queue: string;
|
|
25
|
+
readonly payload: unknown;
|
|
26
|
+
readonly enqueuedBy: EnqueuedBy;
|
|
27
|
+
readonly enqueuedAt: Date;
|
|
28
|
+
readonly runAt: Date;
|
|
29
|
+
readonly idempotencyKey?: string;
|
|
30
|
+
readonly subject?: string;
|
|
31
|
+
}): Promise<EnqueuedJob>;
|
|
32
|
+
claim(request: {
|
|
33
|
+
queue: string;
|
|
34
|
+
limit: number;
|
|
35
|
+
now: Date;
|
|
36
|
+
leaseMs: number;
|
|
37
|
+
}): Promise<Claimed[]>;
|
|
38
|
+
heartbeat(lease: Lease, leaseUntil: Date): Promise<boolean>;
|
|
39
|
+
update(lease: Lease, update: {
|
|
40
|
+
progress?: JobProgress;
|
|
41
|
+
checkpoint?: unknown;
|
|
42
|
+
}, leaseUntil: Date): Promise<boolean>;
|
|
43
|
+
complete(lease: Lease, at: Date): Promise<boolean>;
|
|
44
|
+
fail(lease: Lease, failure: {
|
|
45
|
+
errorCode: string;
|
|
46
|
+
retryAt: Date | null;
|
|
47
|
+
at: Date;
|
|
48
|
+
}): Promise<boolean>;
|
|
49
|
+
release(lease: Lease, runAt: Date): Promise<boolean>;
|
|
50
|
+
cancel(id: string, at: Date): Promise<boolean>;
|
|
51
|
+
find(id: string): Promise<QueuedJob | undefined>;
|
|
52
|
+
retryDead(ids: readonly string[], now: Date): Promise<number>;
|
|
53
|
+
purgeFinished(before: Date): Promise<number>;
|
|
54
|
+
stats(queue: string): Promise<{
|
|
55
|
+
readonly waiting: number;
|
|
56
|
+
readonly running: number;
|
|
57
|
+
readonly dead: number;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=job-store-under-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store-under-contract.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/job-store-under-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store-under-contract.js","sourceRoot":"","sources":["../../../src/contracts/job-store/job-store-under-contract.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import type { JobStoreUnderContract } from './job-store-under-contract.js';
|
|
3
|
+
/** Cancellation and housekeeping. */
|
|
4
|
+
export declare class KeepSuite extends ContractSuite<JobStoreUnderContract> {
|
|
5
|
+
protected tests(): void;
|
|
6
|
+
private cancels;
|
|
7
|
+
private purges;
|
|
8
|
+
private finished;
|
|
9
|
+
private claim;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=keep-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keep-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/keep-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,qCAAqC;AACrC,qBAAa,SAAU,SAAQ,aAAa,CAAC,qBAAqB,CAAC;IACjE,SAAS,CAAC,KAAK,IAAI,IAAI,CAGtB;IAED,OAAO,CAAC,OAAO;IA+Bf,OAAO,CAAC,MAAM;YAcA,QAAQ;IAMtB,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import { aJob } from './a-job.js';
|
|
3
|
+
import { LEASE_MS, NOW, QUEUE } from './job-fixtures.js';
|
|
4
|
+
import { later } from './later.js';
|
|
5
|
+
/** Cancellation and housekeeping. */
|
|
6
|
+
export class KeepSuite extends ContractSuite {
|
|
7
|
+
tests() {
|
|
8
|
+
this.cancels();
|
|
9
|
+
this.purges();
|
|
10
|
+
}
|
|
11
|
+
cancels() {
|
|
12
|
+
this.it('cancels a waiting job: it is never handed out', async () => {
|
|
13
|
+
await this.subject.add(aJob('job-1', { runAt: later(60_000) }));
|
|
14
|
+
this.expect(await this.subject.cancel('job-1', NOW)).toBe(true);
|
|
15
|
+
const job = await this.subject.find('job-1');
|
|
16
|
+
this.expect([job?.status, job?.finishedAt]).toEqual(['cancelled', NOW]);
|
|
17
|
+
this.expect(await this.claim(later(60_000))).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
this.it('cancels a running job: its next report is refused', async () => {
|
|
20
|
+
await this.subject.add(aJob('job-1'));
|
|
21
|
+
await this.claim(NOW);
|
|
22
|
+
this.expect(await this.subject.cancel('job-1', NOW)).toBe(true);
|
|
23
|
+
this.expect(await this.subject.heartbeat({ id: 'job-1', attempt: 1 }, later(LEASE_MS))).toBe(false);
|
|
24
|
+
this.expect(await this.subject.complete({ id: 'job-1', attempt: 1 }, NOW)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
this.it('cannot cancel what is over or unknown', async () => {
|
|
27
|
+
await this.subject.add(aJob('job-1'));
|
|
28
|
+
await this.claim(NOW);
|
|
29
|
+
await this.subject.complete({ id: 'job-1', attempt: 1 }, NOW);
|
|
30
|
+
this.expect(await this.subject.cancel('job-1', NOW)).toBe(false);
|
|
31
|
+
this.expect(await this.subject.cancel('unknown', NOW)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
purges() {
|
|
35
|
+
this.it('purges what succeeded or was cancelled before the instant, and keeps the dead', async () => {
|
|
36
|
+
await this.finished('job-1', (lease) => this.subject.complete(lease, NOW));
|
|
37
|
+
await this.finished('job-2', (lease) => this.subject.fail(lease, { errorCode: 'X', retryAt: null, at: NOW }));
|
|
38
|
+
await this.subject.add(aJob('job-3', { runAt: later(60_000) }));
|
|
39
|
+
await this.subject.cancel('job-3', later(1_000));
|
|
40
|
+
this.expect(await this.subject.purgeFinished(NOW)).toBe(0);
|
|
41
|
+
this.expect(await this.subject.purgeFinished(later(1_001))).toBe(2);
|
|
42
|
+
this.expect(await this.subject.find('job-1')).toBe(undefined);
|
|
43
|
+
this.expect((await this.subject.find('job-2'))?.status).toBe('dead');
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async finished(id, finish) {
|
|
47
|
+
await this.subject.add(aJob(id));
|
|
48
|
+
await this.claim(NOW);
|
|
49
|
+
await finish({ id, attempt: 1 });
|
|
50
|
+
}
|
|
51
|
+
claim(now) {
|
|
52
|
+
return this.subject.claim({ queue: QUEUE, limit: 10, now, leaseMs: LEASE_MS });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=keep-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keep-suite.js","sourceRoot":"","sources":["../../../src/contracts/job-store/keep-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"later.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/later.ts"],"names":[],"mappings":"AAEA,0CAA0C;AAC1C,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"later.js","sourceRoot":"","sources":["../../../src/contracts/job-store/later.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,0CAA0C;AAC1C,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import type { JobStoreUnderContract } from './job-store-under-contract.js';
|
|
3
|
+
/** What a worker reports of an attempt: progress and checkpoint, success, failure, release. Only the attempt that holds the job is heard. */
|
|
4
|
+
export declare class ReportSuite extends ContractSuite<JobStoreUnderContract> {
|
|
5
|
+
protected tests(): void;
|
|
6
|
+
private progressAndOutcomes;
|
|
7
|
+
private deadAndRevived;
|
|
8
|
+
private releasedAndStale;
|
|
9
|
+
private claimFirst;
|
|
10
|
+
private claim;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=report-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/report-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAI3E,6IAA6I;AAC7I,qBAAa,WAAY,SAAQ,aAAa,CAAC,qBAAqB,CAAC;IACnE,SAAS,CAAC,KAAK,IAAI,IAAI,CAItB;IAED,OAAO,CAAC,mBAAmB;IA6C3B,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,gBAAgB;YAsBV,UAAU;IAKxB,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import { aJob } from './a-job.js';
|
|
3
|
+
import { LEASE_MS, NOW, QUEUE } from './job-fixtures.js';
|
|
4
|
+
import { later } from './later.js';
|
|
5
|
+
const FIRST = { id: 'job-1', attempt: 1 };
|
|
6
|
+
/** What a worker reports of an attempt: progress and checkpoint, success, failure, release. Only the attempt that holds the job is heard. */
|
|
7
|
+
export class ReportSuite extends ContractSuite {
|
|
8
|
+
tests() {
|
|
9
|
+
this.progressAndOutcomes();
|
|
10
|
+
this.deadAndRevived();
|
|
11
|
+
this.releasedAndStale();
|
|
12
|
+
}
|
|
13
|
+
progressAndOutcomes() {
|
|
14
|
+
this.it('shows the progress and hands the checkpoint to the next attempt', async () => {
|
|
15
|
+
await this.claimFirst();
|
|
16
|
+
this.expect(await this.subject.update(FIRST, { progress: { done: 40, total: 120 } }, later(LEASE_MS))).toBe(true);
|
|
17
|
+
this.expect(await this.subject.update(FIRST, { checkpoint: { after: 'c-1' } }, later(LEASE_MS))).toBe(true);
|
|
18
|
+
this.expect((await this.subject.find('job-1'))?.progress).toEqual({ done: 40, total: 120 });
|
|
19
|
+
const [resumed] = await this.claim(later(LEASE_MS));
|
|
20
|
+
this.expect([resumed?.attempt, resumed?.checkpoint]).toEqual([2, { after: 'c-1' }]);
|
|
21
|
+
});
|
|
22
|
+
this.it('finishes a job that succeeded: never handed out again', async () => {
|
|
23
|
+
await this.claimFirst();
|
|
24
|
+
this.expect(await this.subject.complete(FIRST, later(5_000))).toBe(true);
|
|
25
|
+
const job = await this.subject.find('job-1');
|
|
26
|
+
this.expect([job?.status, job?.finishedAt]).toEqual(['succeeded', later(5_000)]);
|
|
27
|
+
this.expect(await this.claim(later(LEASE_MS * 10))).toEqual([]);
|
|
28
|
+
this.expect(await this.subject.stats(QUEUE)).toEqual({ waiting: 0, running: 0, dead: 0 });
|
|
29
|
+
});
|
|
30
|
+
this.it('retries a failed job at its instant, not before, with its error code', async () => {
|
|
31
|
+
await this.claimFirst();
|
|
32
|
+
this.expect(await this.subject.fail(FIRST, { errorCode: 'LEDGER_DOWN', retryAt: later(5_000), at: NOW })).toBe(true);
|
|
33
|
+
const waiting = await this.subject.find('job-1');
|
|
34
|
+
this.expect([waiting?.status, waiting?.lastErrorCode, waiting?.runAt]).toEqual([
|
|
35
|
+
'waiting',
|
|
36
|
+
'LEDGER_DOWN',
|
|
37
|
+
later(5_000),
|
|
38
|
+
]);
|
|
39
|
+
this.expect(await this.claim(later(4_999))).toEqual([]);
|
|
40
|
+
this.expect((await this.claim(later(5_000)))[0]?.attempt).toBe(2);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
deadAndRevived() {
|
|
44
|
+
this.it('sets a dead job aside, then revives it with a fresh attempt budget', async () => {
|
|
45
|
+
await this.claimFirst();
|
|
46
|
+
await this.subject.fail(FIRST, { errorCode: 'LEDGER_DOWN', retryAt: null, at: NOW });
|
|
47
|
+
this.expect((await this.subject.find('job-1'))?.status).toBe('dead');
|
|
48
|
+
this.expect(await this.subject.stats(QUEUE)).toEqual({ waiting: 0, running: 0, dead: 1 });
|
|
49
|
+
this.expect(await this.claim(later(LEASE_MS * 10))).toEqual([]);
|
|
50
|
+
this.expect(await this.subject.retryDead([], later(1_000))).toBe(0);
|
|
51
|
+
this.expect(await this.subject.retryDead(['job-1', 'unknown'], later(1_000))).toBe(1);
|
|
52
|
+
const [revived] = await this.claim(later(1_000));
|
|
53
|
+
this.expect(revived?.attempt).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
releasedAndStale() {
|
|
57
|
+
this.it('takes a released job back without counting the attempt', async () => {
|
|
58
|
+
await this.claimFirst();
|
|
59
|
+
this.expect(await this.subject.release(FIRST, NOW)).toBe(true);
|
|
60
|
+
this.expect((await this.subject.find('job-1'))?.status).toBe('waiting');
|
|
61
|
+
this.expect((await this.claim(NOW))[0]?.attempt).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
this.it('ignores an attempt whose lease was taken over', async () => {
|
|
64
|
+
await this.claimFirst();
|
|
65
|
+
await this.claim(later(LEASE_MS));
|
|
66
|
+
this.expect(await this.subject.update(FIRST, { progress: { done: 1 } }, later(LEASE_MS * 2))).toBe(false);
|
|
67
|
+
this.expect(await this.subject.complete(FIRST, later(LEASE_MS))).toBe(false);
|
|
68
|
+
this.expect(await this.subject.fail(FIRST, { errorCode: 'X', retryAt: null, at: NOW })).toBe(false);
|
|
69
|
+
this.expect(await this.subject.release(FIRST, NOW)).toBe(false);
|
|
70
|
+
this.expect((await this.subject.find('job-1'))?.status).toBe('running');
|
|
71
|
+
this.expect(await this.subject.complete({ id: 'job-1', attempt: 2 }, later(LEASE_MS))).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async claimFirst() {
|
|
75
|
+
await this.subject.add(aJob('job-1'));
|
|
76
|
+
await this.claim(NOW);
|
|
77
|
+
}
|
|
78
|
+
claim(now) {
|
|
79
|
+
return this.subject.claim({ queue: QUEUE, limit: 10, now, leaseMs: LEASE_MS });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=report-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-suite.js","sourceRoot":"","sources":["../../../src/contracts/job-store/report-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
2
|
+
import type { JobStoreUnderContract } from './job-store-under-contract.js';
|
|
3
|
+
/** The unit of work the store is used in, as the kernel port states it. */
|
|
4
|
+
export interface UnitOfWorkOfTheStore {
|
|
5
|
+
run<T>(work: () => Promise<T>): Promise<T>;
|
|
6
|
+
}
|
|
7
|
+
interface StoreInUnitOfWork {
|
|
8
|
+
readonly store: JobStoreUnderContract;
|
|
9
|
+
readonly unitOfWork: UnitOfWorkOfTheStore;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A job added inside a unit of work exists if and only if the unit of work committed; a job
|
|
13
|
+
* cancelled or revived inside one is cancelled or revived if and only if it committed.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TransactionSuite extends ContractSuite<StoreInUnitOfWork> {
|
|
16
|
+
protected tests(): void;
|
|
17
|
+
private adds;
|
|
18
|
+
private cancels;
|
|
19
|
+
private revives;
|
|
20
|
+
private claimed;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=transaction-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/job-store/transaction-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5C;AAED,UAAU,iBAAiB;IACzB,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;CAC3C;AAID;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,aAAa,CAAC,iBAAiB,CAAC;IACpE,SAAS,CAAC,KAAK,IAAI,IAAI,CAItB;IAED,OAAO,CAAC,IAAI;IAqBZ,OAAO,CAAC,OAAO;IAyBf,OAAO,CAAC,OAAO;YAmBD,OAAO;CAKtB"}
|