@ddd-ts-kit/testing 0.10.0 → 0.12.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 +21 -0
- package/dist/contracts/cache-purger-contract.d.ts +19 -0
- package/dist/contracts/cache-purger-contract.d.ts.map +1 -0
- package/dist/contracts/cache-purger-contract.js +69 -0
- package/dist/contracts/cache-purger-contract.js.map +1 -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 +4 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -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,7 +472,10 @@ 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 |
|
|
478
|
+
| `cachePurgerContract` | any `CachePurger` with its shared cache (`store`, `holds`): a tag purges what it names and nothing else, a kind reaches each element, the tenants stay apart, purging is idempotent |
|
|
458
479
|
| `lockContract` | any `Lock`: one holder at a time also under concurrent requests, release, expiry, a late release ignored, growing fencing token, `waitMs` |
|
|
459
480
|
| `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 |
|
|
460
481
|
| `loggerContract` | any `Logger`: every level with its message and fields of every shape, level and message that no field forges, children that add their fields and nest, fields read at each entry, an entry below the level dropped without reading a field, `isLevelEnabled` answering what is written (for a child too), no throw on a circular field or a getter that throws, at the top of the fields included; an `Error` passed as a field written as its description (`type`, `code` of a `DomainError`, `message`, own `stack`, `cause`, eight errors deep at most), its message replaced when the logger scrubs unless it is a `DomainError`; every entry in the sink once `flush()` resolves, then no throw from a log call or a second flush. `createLogger({ level, fields, scrubMessages })` answers the logger and `lines()`, what its sink received as `{ level, message, fields }`. No capability flag: `InMemoryLogger` takes the same three options and passes the whole suite |
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CachePurger } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { Awaitable } from './awaitable.js';
|
|
3
|
+
import type { ContractRunner } from './contract-runner.js';
|
|
4
|
+
/** A shared cache with its purger: answers are filed under tags through the cache itself, then purged. */
|
|
5
|
+
export interface CachePurgerUnderContract {
|
|
6
|
+
readonly purger: CachePurger;
|
|
7
|
+
/** Has the cache keep an answer under `key`, filed under `tags`. */
|
|
8
|
+
store(key: string, tags: readonly string[]): Awaitable<void>;
|
|
9
|
+
/** Whether the cache still serves the answer under `key` without asking the application. */
|
|
10
|
+
holds(key: string): Awaitable<boolean>;
|
|
11
|
+
}
|
|
12
|
+
export interface CachePurgerContractOptions {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
/** A cache holding nothing yet, or nothing under the keys of the test; called before each test. */
|
|
15
|
+
createCache(): Awaitable<CachePurgerUnderContract>;
|
|
16
|
+
}
|
|
17
|
+
/** What every adapter of the kernel `CachePurger` port guarantees, the in-memory one included. */
|
|
18
|
+
export declare function cachePurgerContract(runner: ContractRunner, options: CachePurgerContractOptions): void;
|
|
19
|
+
//# sourceMappingURL=cache-purger-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-purger-contract.d.ts","sourceRoot":"","sources":["../../src/contracts/cache-purger-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,0GAA0G;AAC1G,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,oEAAoE;IACpE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7D,4FAA4F;IAC5F,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,WAAW,IAAI,SAAS,CAAC,wBAAwB,CAAC,CAAC;CACpD;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,0BAA0B,GAAG,IAAI,CAErG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cacheTag, tenantCacheTags } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { ContractSuite } from './contract-suite.js';
|
|
3
|
+
/** What every adapter of the kernel `CachePurger` port guarantees, the in-memory one included. */
|
|
4
|
+
export function cachePurgerContract(runner, options) {
|
|
5
|
+
new CachePurgerSuite(runner, options).describe(`${options.name} (cache purger contract)`);
|
|
6
|
+
}
|
|
7
|
+
const MONDAY = cacheTag('calendar', 'c-1');
|
|
8
|
+
const TUESDAY = cacheTag('calendar', 'c-2');
|
|
9
|
+
const EVERY_CALENDAR = cacheTag('calendar');
|
|
10
|
+
class CachePurgerSuite extends ContractSuite {
|
|
11
|
+
constructor(runner, options) {
|
|
12
|
+
super(runner, () => options.createCache());
|
|
13
|
+
}
|
|
14
|
+
tests() {
|
|
15
|
+
this.purgesWhatATagNames();
|
|
16
|
+
this.keepsWhatItDoesNotName();
|
|
17
|
+
this.isIdempotent();
|
|
18
|
+
}
|
|
19
|
+
purgesWhatATagNames() {
|
|
20
|
+
this.it('purges the answers filed under a tag, and only those', async () => {
|
|
21
|
+
await this.subject.store('/calendars/c-1', [MONDAY, EVERY_CALENDAR]);
|
|
22
|
+
await this.subject.store('/calendars/c-2', [TUESDAY, EVERY_CALENDAR]);
|
|
23
|
+
await this.subject.purger.purge([MONDAY]);
|
|
24
|
+
this.expect(await this.subject.holds('/calendars/c-1')).toBe(false);
|
|
25
|
+
this.expect(await this.subject.holds('/calendars/c-2')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
this.it('purges the answers filed under any of several tags, a kind reaching each of its elements', async () => {
|
|
28
|
+
await this.subject.store('/calendars/c-1', [MONDAY, EVERY_CALENDAR]);
|
|
29
|
+
await this.subject.store('/calendars/c-2', [TUESDAY, EVERY_CALENDAR]);
|
|
30
|
+
await this.subject.store('/calendars', [EVERY_CALENDAR]);
|
|
31
|
+
await this.subject.purger.purge([EVERY_CALENDAR]);
|
|
32
|
+
this.expect(await this.subject.holds('/calendars/c-1')).toBe(false);
|
|
33
|
+
this.expect(await this.subject.holds('/calendars/c-2')).toBe(false);
|
|
34
|
+
this.expect(await this.subject.holds('/calendars')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
keepsWhatItDoesNotName() {
|
|
38
|
+
this.it('purges a tag written with the characters of a pattern, and nothing the pattern would match', async () => {
|
|
39
|
+
const written = cacheTag('calendar', 'c.1(a)');
|
|
40
|
+
await this.subject.store('/calendars/written', [written]);
|
|
41
|
+
await this.subject.store('/calendars/matched', [cacheTag('calendar', 'cx1a')]);
|
|
42
|
+
await this.subject.purger.purge([written]);
|
|
43
|
+
this.expect(await this.subject.holds('/calendars/written')).toBe(false);
|
|
44
|
+
this.expect(await this.subject.holds('/calendars/matched')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
this.it('keeps the answers of another tenant', async () => {
|
|
47
|
+
const [ofAcme = MONDAY, ofGlobex = MONDAY] = [
|
|
48
|
+
...tenantCacheTags([MONDAY], 'acme'),
|
|
49
|
+
...tenantCacheTags([MONDAY], 'globex'),
|
|
50
|
+
];
|
|
51
|
+
await this.subject.store('/acme/calendars/c-1', [ofAcme]);
|
|
52
|
+
await this.subject.store('/globex/calendars/c-1', [ofGlobex]);
|
|
53
|
+
await this.subject.purger.purge([ofAcme]);
|
|
54
|
+
this.expect(await this.subject.holds('/acme/calendars/c-1')).toBe(false);
|
|
55
|
+
this.expect(await this.subject.holds('/globex/calendars/c-1')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
isIdempotent() {
|
|
59
|
+
this.it('purges nothing for a tag nothing is filed under, an empty list, or a second time', async () => {
|
|
60
|
+
await this.subject.store('/calendars/c-2', [TUESDAY]);
|
|
61
|
+
await this.subject.purger.purge([cacheTag('calendar', 'unknown')]);
|
|
62
|
+
await this.subject.purger.purge([]);
|
|
63
|
+
await this.subject.purger.purge([MONDAY]);
|
|
64
|
+
await this.subject.purger.purge([MONDAY]);
|
|
65
|
+
this.expect(await this.subject.holds('/calendars/c-2')).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=cache-purger-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-purger-contract.js","sourceRoot":"","sources":["../../src/contracts/cache-purger-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,qCAAqC;AACrC,MAAM,OAAO,SAAU,SAAQ,aAAoC;IACvD,KAAK;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;YAClE,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEhE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE7C,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;YACxE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAEtB,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpG,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzF,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACtB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAE9D,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;YAClG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;YAC3E,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YAC9G,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAChE,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAEjD,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3D,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,EAAU,EAAE,MAAoE;QACrG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACjC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,GAAS;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACjF,CAAC;CACF"}
|
|
@@ -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"}
|