@ddd-ts-kit/testing 0.8.0 → 0.10.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 +19 -0
- package/dist/contracts/event-store/companion-stores-suite.d.ts +12 -0
- package/dist/contracts/event-store/companion-stores-suite.d.ts.map +1 -0
- package/dist/contracts/event-store/companion-stores-suite.js +60 -0
- package/dist/contracts/event-store/companion-stores-suite.js.map +1 -0
- package/dist/contracts/event-store/consignment-registered.d.ts +8 -0
- package/dist/contracts/event-store/consignment-registered.d.ts.map +1 -0
- package/dist/contracts/event-store/consignment-registered.js +10 -0
- package/dist/contracts/event-store/consignment-registered.js.map +1 -0
- package/dist/contracts/event-store/consignment-weighed.d.ts +9 -0
- package/dist/contracts/event-store/consignment-weighed.d.ts.map +1 -0
- package/dist/contracts/event-store/consignment-weighed.js +12 -0
- package/dist/contracts/event-store/consignment-weighed.js.map +1 -0
- package/dist/contracts/event-store/consignment.d.ts +16 -0
- package/dist/contracts/event-store/consignment.d.ts.map +1 -0
- package/dist/contracts/event-store/consignment.js +36 -0
- package/dist/contracts/event-store/consignment.js.map +1 -0
- package/dist/contracts/event-store/consignments.d.ts +10 -0
- package/dist/contracts/event-store/consignments.d.ts.map +1 -0
- package/dist/contracts/event-store/consignments.js +34 -0
- package/dist/contracts/event-store/consignments.js.map +1 -0
- package/dist/contracts/event-store/event-of.d.ts +4 -0
- package/dist/contracts/event-store/event-of.d.ts.map +1 -0
- package/dist/contracts/event-store/event-of.js +13 -0
- package/dist/contracts/event-store/event-of.js.map +1 -0
- package/dist/contracts/event-store/event-store-under-contract.d.ts +11 -0
- package/dist/contracts/event-store/event-store-under-contract.d.ts.map +1 -0
- package/dist/contracts/event-store/event-store-under-contract.js +2 -0
- package/dist/contracts/event-store/event-store-under-contract.js.map +1 -0
- package/dist/contracts/event-store/one-transaction-suite.d.ts +18 -0
- package/dist/contracts/event-store/one-transaction-suite.d.ts.map +1 -0
- package/dist/contracts/event-store/one-transaction-suite.js +92 -0
- package/dist/contracts/event-store/one-transaction-suite.js.map +1 -0
- package/dist/contracts/event-store/store-suite.d.ts +10 -0
- package/dist/contracts/event-store/store-suite.d.ts.map +1 -0
- package/dist/contracts/event-store/store-suite.js +44 -0
- package/dist/contracts/event-store/store-suite.js.map +1 -0
- package/dist/contracts/event-store/stream-suite.d.ts +11 -0
- package/dist/contracts/event-store/stream-suite.d.ts.map +1 -0
- package/dist/contracts/event-store/stream-suite.js +91 -0
- package/dist/contracts/event-store/stream-suite.js.map +1 -0
- package/dist/contracts/event-store-contract.d.ts +20 -0
- package/dist/contracts/event-store-contract.d.ts.map +1 -0
- package/dist/contracts/event-store-contract.js +21 -0
- package/dist/contracts/event-store-contract.js.map +1 -0
- package/dist/contracts/event-store-unit-of-work-contract.d.ts +15 -0
- package/dist/contracts/event-store-unit-of-work-contract.d.ts.map +1 -0
- package/dist/contracts/event-store-unit-of-work-contract.js +10 -0
- package/dist/contracts/event-store-unit-of-work-contract.js.map +1 -0
- package/dist/contracts/file-storage/arrangement-suite.d.ts +9 -0
- package/dist/contracts/file-storage/arrangement-suite.d.ts.map +1 -0
- package/dist/contracts/file-storage/arrangement-suite.js +51 -0
- package/dist/contracts/file-storage/arrangement-suite.js.map +1 -0
- package/dist/contracts/file-storage/checksum-of.d.ts +3 -0
- package/dist/contracts/file-storage/checksum-of.d.ts.map +1 -0
- package/dist/contracts/file-storage/checksum-of.js +10 -0
- package/dist/contracts/file-storage/checksum-of.js.map +1 -0
- package/dist/contracts/file-storage/chunks-of.d.ts +3 -0
- package/dist/contracts/file-storage/chunks-of.d.ts.map +1 -0
- package/dist/contracts/file-storage/chunks-of.js +5 -0
- package/dist/contracts/file-storage/chunks-of.js.map +1 -0
- package/dist/contracts/file-storage/content-suite.d.ts +12 -0
- package/dist/contracts/file-storage/content-suite.d.ts.map +1 -0
- package/dist/contracts/file-storage/content-suite.js +110 -0
- package/dist/contracts/file-storage/content-suite.js.map +1 -0
- package/dist/contracts/file-storage/contents.d.ts +7 -0
- package/dist/contracts/file-storage/contents.d.ts.map +1 -0
- package/dist/contracts/file-storage/contents.js +7 -0
- package/dist/contracts/file-storage/contents.js.map +1 -0
- package/dist/contracts/file-storage/file-storage-contract-options.d.ts +17 -0
- package/dist/contracts/file-storage/file-storage-contract-options.d.ts.map +1 -0
- package/dist/contracts/file-storage/file-storage-contract-options.js +2 -0
- package/dist/contracts/file-storage/file-storage-contract-options.js.map +1 -0
- package/dist/contracts/file-storage/file-storage-suite.d.ts +14 -0
- package/dist/contracts/file-storage/file-storage-suite.d.ts.map +1 -0
- package/dist/contracts/file-storage/file-storage-suite.js +30 -0
- package/dist/contracts/file-storage/file-storage-suite.js.map +1 -0
- package/dist/contracts/file-storage/follow-served.d.ts +9 -0
- package/dist/contracts/file-storage/follow-served.d.ts.map +1 -0
- package/dist/contracts/file-storage/follow-served.js +28 -0
- package/dist/contracts/file-storage/follow-served.js.map +1 -0
- package/dist/contracts/file-storage/follow-with-fetch.d.ts +5 -0
- package/dist/contracts/file-storage/follow-with-fetch.d.ts.map +1 -0
- package/dist/contracts/file-storage/follow-with-fetch.js +14 -0
- package/dist/contracts/file-storage/follow-with-fetch.js.map +1 -0
- package/dist/contracts/file-storage/followed-response.d.ts +7 -0
- package/dist/contracts/file-storage/followed-response.d.ts.map +1 -0
- package/dist/contracts/file-storage/followed-response.js +2 -0
- package/dist/contracts/file-storage/followed-response.js.map +1 -0
- package/dist/contracts/file-storage/large-content.d.ts +3 -0
- package/dist/contracts/file-storage/large-content.d.ts.map +1 -0
- package/dist/contracts/file-storage/large-content.js +8 -0
- package/dist/contracts/file-storage/large-content.js.map +1 -0
- package/dist/contracts/file-storage/read-all.d.ts +3 -0
- package/dist/contracts/file-storage/read-all.d.ts.map +1 -0
- package/dist/contracts/file-storage/read-all.js +9 -0
- package/dist/contracts/file-storage/read-all.js.map +1 -0
- package/dist/contracts/file-storage/signed-links-suite.d.ts +11 -0
- package/dist/contracts/file-storage/signed-links-suite.d.ts.map +1 -0
- package/dist/contracts/file-storage/signed-links-suite.js +93 -0
- package/dist/contracts/file-storage/signed-links-suite.js.map +1 -0
- package/dist/contracts/file-storage/signed-request.d.ts +8 -0
- package/dist/contracts/file-storage/signed-request.d.ts.map +1 -0
- package/dist/contracts/file-storage/signed-request.js +2 -0
- package/dist/contracts/file-storage/signed-request.js.map +1 -0
- package/dist/contracts/file-storage/tampered.d.ts +3 -0
- package/dist/contracts/file-storage/tampered.d.ts.map +1 -0
- package/dist/contracts/file-storage/tampered.js +5 -0
- package/dist/contracts/file-storage/tampered.js.map +1 -0
- package/dist/contracts/file-storage-contract.d.ts +11 -0
- package/dist/contracts/file-storage-contract.d.ts.map +1 -0
- package/dist/contracts/file-storage-contract.js +16 -0
- package/dist/contracts/file-storage-contract.js.map +1 -0
- package/dist/contracts.d.ts +9 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +5 -0
- package/dist/contracts.js.map +1 -1
- package/dist/event-sourcing/expect-snapshot-equivalence.d.ts +14 -0
- package/dist/event-sourcing/expect-snapshot-equivalence.d.ts.map +1 -0
- package/dist/event-sourcing/expect-snapshot-equivalence.js +48 -0
- package/dist/event-sourcing/expect-snapshot-equivalence.js.map +1 -0
- package/dist/event-sourcing/given-events.d.ts +12 -0
- package/dist/event-sourcing/given-events.d.ts.map +1 -0
- package/dist/event-sourcing/given-events.js +16 -0
- package/dist/event-sourcing/given-events.js.map +1 -0
- package/dist/event-sourcing/snapshot-equivalence.d.ts +12 -0
- package/dist/event-sourcing/snapshot-equivalence.d.ts.map +1 -0
- package/dist/event-sourcing/snapshot-equivalence.js +2 -0
- package/dist/event-sourcing/snapshot-equivalence.js.map +1 -0
- 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/package.json +4 -4
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CONCURRENCY_CONFLICT } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
3
|
+
import { failureOf } from '../failure-of.js';
|
|
4
|
+
import { eventOf } from './event-of.js';
|
|
5
|
+
/** One stream: appended at the version it was read at, read back in order, refused when somebody appended first. */
|
|
6
|
+
export class StreamSuite extends ContractSuite {
|
|
7
|
+
tests() {
|
|
8
|
+
this.appendsAndReads();
|
|
9
|
+
this.readsWhatWasAppended();
|
|
10
|
+
this.refusesStaleVersions();
|
|
11
|
+
this.keepsStreamsApart();
|
|
12
|
+
}
|
|
13
|
+
appendsAndReads() {
|
|
14
|
+
this.it('creates a stream at version 0 and gives its events back in order, numbered from 1', async () => {
|
|
15
|
+
const appended = await this.subject.append('consignment-c1', 0, [eventOf('e1'), eventOf('e2')]);
|
|
16
|
+
const read = await this.subject.readStream('consignment-c1');
|
|
17
|
+
this.expect(appended.version).toBe(2);
|
|
18
|
+
this.expect(read.map(({ id, version, stream }) => ({ id, version, stream }))).toEqual([
|
|
19
|
+
{ id: 'e1', version: 1, stream: 'consignment-c1' },
|
|
20
|
+
{ id: 'e2', version: 2, stream: 'consignment-c1' },
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
this.it('gives back the type, the version of the shape, the data, the metadata and the instant as appended', async () => {
|
|
24
|
+
const data = {
|
|
25
|
+
label: 'é ✓',
|
|
26
|
+
weight: 12.5,
|
|
27
|
+
fragile: true,
|
|
28
|
+
none: null,
|
|
29
|
+
items: [{ sku: 'a' }],
|
|
30
|
+
nested: { depth: 2 },
|
|
31
|
+
};
|
|
32
|
+
await this.subject.append('consignment-c1', 0, [{ ...eventOf('e1', data), schemaVersion: 3 }]);
|
|
33
|
+
const [read] = await this.subject.readStream('consignment-c1');
|
|
34
|
+
this.expect(read?.type).toBe('shipping.consignment.weighed');
|
|
35
|
+
this.expect(read?.schemaVersion).toBe(3);
|
|
36
|
+
this.expect(read?.data).toEqual(data);
|
|
37
|
+
this.expect(read?.metadata).toEqual({ aggregateId: 'c1', correlationId: 'corr-e1' });
|
|
38
|
+
this.expect(read?.occurredAt.toISOString()).toBe('2026-03-01T10:00:00.000Z');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
readsWhatWasAppended() {
|
|
42
|
+
this.it('appends to a stream at the version it has, and reads it after a version', async () => {
|
|
43
|
+
await this.subject.append('consignment-c1', 0, [eventOf('e1')]);
|
|
44
|
+
await this.subject.append('consignment-c1', 1, [eventOf('e2'), eventOf('e3')]);
|
|
45
|
+
const after = await this.subject.readStream('consignment-c1', 1);
|
|
46
|
+
this.expect(after.map(({ id, version }) => `${id}@${version}`)).toEqual(['e2@2', 'e3@3']);
|
|
47
|
+
});
|
|
48
|
+
this.it('appends nothing at the version the stream has, and refuses nothing at another one', async () => {
|
|
49
|
+
await this.subject.append('consignment-c1', 0, [eventOf('e1')]);
|
|
50
|
+
const unchanged = await this.subject.append('consignment-c1', 1, []);
|
|
51
|
+
const refused = await failureOf(() => this.subject.append('consignment-c1', 0, []));
|
|
52
|
+
this.expect(unchanged).toEqual({ version: 1, position: 1 });
|
|
53
|
+
this.expect(codeOf(refused)).toBe(CONCURRENCY_CONFLICT);
|
|
54
|
+
});
|
|
55
|
+
this.it('reads an unknown stream as empty', async () => {
|
|
56
|
+
this.expect(await this.subject.readStream('consignment-unknown')).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
refusesStaleVersions() {
|
|
60
|
+
this.it('refuses with a concurrency conflict an append at a version the stream no longer has, and keeps none of it', async () => {
|
|
61
|
+
await this.subject.append('consignment-c1', 0, [eventOf('e1')]);
|
|
62
|
+
const behind = await failureOf(() => this.subject.append('consignment-c1', 0, [eventOf('e2')]));
|
|
63
|
+
const ahead = await failureOf(() => this.subject.append('consignment-c1', 2, [eventOf('e3')]));
|
|
64
|
+
this.expect([codeOf(behind), codeOf(ahead)]).toEqual([CONCURRENCY_CONFLICT, CONCURRENCY_CONFLICT]);
|
|
65
|
+
this.expect((await this.subject.readStream('consignment-c1')).map(({ id }) => id)).toEqual(['e1']);
|
|
66
|
+
});
|
|
67
|
+
this.it('lets one of two appends at the same version through, and refuses the other', async () => {
|
|
68
|
+
await this.subject.append('consignment-c1', 0, [eventOf('e1')]);
|
|
69
|
+
const outcomes = await Promise.all([
|
|
70
|
+
failureOf(() => this.subject.append('consignment-c1', 1, [eventOf('left')])),
|
|
71
|
+
failureOf(() => this.subject.append('consignment-c1', 1, [eventOf('right')])),
|
|
72
|
+
]);
|
|
73
|
+
this.expect(outcomes.filter((failure) => failure === undefined).length).toBe(1);
|
|
74
|
+
this.expect(outcomes.map(codeOf).filter((code) => code === CONCURRENCY_CONFLICT).length).toBe(1);
|
|
75
|
+
this.expect((await this.subject.readStream('consignment-c1')).length).toBe(2);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
keepsStreamsApart() {
|
|
79
|
+
this.it('numbers each stream on its own', async () => {
|
|
80
|
+
await this.subject.append('consignment-c1', 0, [eventOf('a1'), eventOf('a2')]);
|
|
81
|
+
await this.subject.append('consignment-c2', 0, [eventOf('b1')]);
|
|
82
|
+
const second = await this.subject.readStream('consignment-c2');
|
|
83
|
+
this.expect(second.map(({ id, version }) => `${id}@${version}`)).toEqual(['b1@1']);
|
|
84
|
+
this.expect((await this.subject.readStream('consignment-c1')).length).toBe(2);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function codeOf(failure) {
|
|
89
|
+
return typeof failure === 'object' && failure !== null && 'code' in failure ? failure.code : undefined;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=stream-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-suite.js","sourceRoot":"","sources":["../../../src/contracts/event-store/stream-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAmB,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,oHAAoH;AACpH,MAAM,OAAO,WAAY,SAAQ,aAAyB;IAC9C,KAAK;QACb,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;YACtG,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChG,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YAE7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACpF,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE;gBAClD,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE;aACnD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CACL,mGAAmG,EACnG,KAAK,IAAI,EAAE;YACT,MAAM,IAAI,GAAG;gBACX,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;gBACrB,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;aACrB,CAAC;YAEF,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/F,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YAE/D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC7D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAC;YACrF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC/E,CAAC,CACF,CAAC;IACJ,CAAC;IAEO,oBAAoB;QAC1B,IAAI,CAAC,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;YAC5F,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChE,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE/E,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;YAEjE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5F,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;YACtG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEhE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACrE,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAEpF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YACrD,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,oBAAoB;QAC1B,IAAI,CAAC,EAAE,CACL,2GAA2G,EAC3G,KAAK,IAAI,EAAE;YACT,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAChG,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/F,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC,CAAC;YACnG,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrG,CAAC,CACF,CAAC;QAEF,IAAI,CAAC,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;YAC/F,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEhE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACjC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC5E,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;aAC9E,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjG,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC/E,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YAE/D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACnF,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,MAAM,CAAC,OAAgB;IAC9B,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACzG,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EventSourcingStores, EventStore } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { Awaitable } from './awaitable.js';
|
|
3
|
+
import type { ContractRunner } from './contract-runner.js';
|
|
4
|
+
export interface EventStoreContractOptions {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
/** A fresh, empty store; called before each test. */
|
|
7
|
+
createStore(): Awaitable<EventStore>;
|
|
8
|
+
/** The snapshot, checkpoint and key stores that come with it, fresh and empty; their suite runs when given. */
|
|
9
|
+
readonly createCompanions?: () => Awaitable<Pick<EventSourcingStores, 'snapshots' | 'checkpoints' | 'subjectKeys'>>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What every adapter of the kernel `EventStore` port guarantees, the in-memory one included:
|
|
13
|
+
* an append at the version the stream still has, a concurrency conflict otherwise and nothing
|
|
14
|
+
* kept of it, each stream in its order, the whole store in the order of the appends with a
|
|
15
|
+
* position, and a read from a position. With `createCompanions`, the stores that come with it:
|
|
16
|
+
* snapshots never replaced by an older one, checkpoints moved from where they stand, keys of data
|
|
17
|
+
* subjects destroyed by an erasure.
|
|
18
|
+
*/
|
|
19
|
+
export declare function eventStoreContract(runner: ContractRunner, options: EventStoreContractOptions): void;
|
|
20
|
+
//# sourceMappingURL=event-store-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-store-contract.d.ts","sourceRoot":"","sources":["../../src/contracts/event-store-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,WAAW,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,+GAA+G;IAC/G,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC;CACrH;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAanG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CompanionStoresSuite } from './event-store/companion-stores-suite.js';
|
|
2
|
+
import { StoreSuite } from './event-store/store-suite.js';
|
|
3
|
+
import { StreamSuite } from './event-store/stream-suite.js';
|
|
4
|
+
/**
|
|
5
|
+
* What every adapter of the kernel `EventStore` port guarantees, the in-memory one included:
|
|
6
|
+
* an append at the version the stream still has, a concurrency conflict otherwise and nothing
|
|
7
|
+
* kept of it, each stream in its order, the whole store in the order of the appends with a
|
|
8
|
+
* position, and a read from a position. With `createCompanions`, the stores that come with it:
|
|
9
|
+
* snapshots never replaced by an older one, checkpoints moved from where they stand, keys of data
|
|
10
|
+
* subjects destroyed by an erasure.
|
|
11
|
+
*/
|
|
12
|
+
export function eventStoreContract(runner, options) {
|
|
13
|
+
const create = () => options.createStore();
|
|
14
|
+
new StreamSuite(runner, create).describe(`${options.name} (event store contract: streams)`);
|
|
15
|
+
new StoreSuite(runner, create).describe(`${options.name} (event store contract: the whole store)`);
|
|
16
|
+
const { createCompanions } = options;
|
|
17
|
+
if (createCompanions !== undefined) {
|
|
18
|
+
new CompanionStoresSuite(runner, () => createCompanions()).describe(`${options.name} (event store contract: snapshots, checkpoints, keys)`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=event-store-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-store-contract.js","sourceRoot":"","sources":["../../src/contracts/event-store-contract.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAU5D;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,OAAkC;IAC3F,MAAM,MAAM,GAAG,GAA0B,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAElE,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,kCAAkC,CAAC,CAAC;IAC5F,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,0CAA0C,CAAC,CAAC;IAEnG,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAErC,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC,QAAQ,CACjE,GAAG,OAAO,CAAC,IAAI,uDAAuD,CACvE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Awaitable } from './awaitable.js';
|
|
2
|
+
import type { ContractRunner } from './contract-runner.js';
|
|
3
|
+
import type { EventStoreUnderContract } from './event-store/event-store-under-contract.js';
|
|
4
|
+
export interface EventStoreUnitOfWorkContractOptions {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
/** An empty event store and an empty outbox of one database, bound to one unit of work; called before each test. */
|
|
7
|
+
createSubject(): Awaitable<EventStoreUnderContract>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* No double write for an event-sourced aggregate: what its save appends to its stream and what the
|
|
11
|
+
* publisher of its events writes to the outbox commit in one transaction, so nothing is published
|
|
12
|
+
* for an append that was rolled back or refused. Held by every transactional event store.
|
|
13
|
+
*/
|
|
14
|
+
export declare function eventStoreUnitOfWorkContract(runner: ContractRunner, options: EventStoreUnitOfWorkContractOptions): void;
|
|
15
|
+
//# sourceMappingURL=event-store-unit-of-work-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-store-unit-of-work-contract.d.ts","sourceRoot":"","sources":["../../src/contracts/event-store-unit-of-work-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AAG3F,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oHAAoH;IACpH,aAAa,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,mCAAmC,GAC3C,IAAI,CAIN"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OneTransactionSuite } from './event-store/one-transaction-suite.js';
|
|
2
|
+
/**
|
|
3
|
+
* No double write for an event-sourced aggregate: what its save appends to its stream and what the
|
|
4
|
+
* publisher of its events writes to the outbox commit in one transaction, so nothing is published
|
|
5
|
+
* for an append that was rolled back or refused. Held by every transactional event store.
|
|
6
|
+
*/
|
|
7
|
+
export function eventStoreUnitOfWorkContract(runner, options) {
|
|
8
|
+
new OneTransactionSuite(runner, () => options.createSubject()).describe(`${options.name} (event store contract: one transaction with the outbox)`);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=event-store-unit-of-work-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-store-unit-of-work-contract.js","sourceRoot":"","sources":["../../src/contracts/event-store-unit-of-work-contract.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAQ7E;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAsB,EACtB,OAA4C;IAE5C,IAAI,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,QAAQ,CACrE,GAAG,OAAO,CAAC,IAAI,0DAA0D,CAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FileStorageSuite } from './file-storage-suite.js';
|
|
2
|
+
/** Files are copied, moved and listed by key, without reading them through the application. */
|
|
3
|
+
export declare class ArrangementSuite extends FileStorageSuite {
|
|
4
|
+
protected tests(): void;
|
|
5
|
+
private copies;
|
|
6
|
+
private moves;
|
|
7
|
+
private lists;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=arrangement-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrangement-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/arrangement-suite.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,+FAA+F;AAC/F,qBAAa,gBAAiB,SAAQ,gBAAgB;IACpD,SAAS,CAAC,KAAK,IAAI,IAAI,CAItB;IAED,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,KAAK;IAkBb,OAAO,CAAC,KAAK;CAoBd"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PDF, TEXT } from './contents.js';
|
|
2
|
+
import { FileStorageSuite } from './file-storage-suite.js';
|
|
3
|
+
/** Files are copied, moved and listed by key, without reading them through the application. */
|
|
4
|
+
export class ArrangementSuite extends FileStorageSuite {
|
|
5
|
+
tests() {
|
|
6
|
+
this.copies();
|
|
7
|
+
this.moves();
|
|
8
|
+
this.lists();
|
|
9
|
+
}
|
|
10
|
+
copies() {
|
|
11
|
+
this.it('copies a file to another key, the original staying', async () => {
|
|
12
|
+
await this.write('staging/documents/a.pdf', PDF);
|
|
13
|
+
await this.subject.copy('staging/documents/a.pdf', 'documents/a.pdf');
|
|
14
|
+
this.expect(await this.read('documents/a.pdf')).toEqual(PDF);
|
|
15
|
+
this.expect(await this.read('staging/documents/a.pdf')).toEqual(PDF);
|
|
16
|
+
this.expect((await this.subject.stat('documents/a.pdf'))?.mediaType).toBe('application/pdf');
|
|
17
|
+
});
|
|
18
|
+
this.it('refuses to copy a file that is not there', async () => {
|
|
19
|
+
this.expect(await this.codeOf(() => this.subject.copy('documents/missing.pdf', 'documents/b.pdf'))).toBe('FILE_NOT_FOUND');
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
moves() {
|
|
23
|
+
this.it('moves a file to another key, nothing staying behind', async () => {
|
|
24
|
+
await this.write('staging/documents/a.txt', TEXT, 'text/plain');
|
|
25
|
+
await this.subject.move('staging/documents/a.txt', 'documents/a.txt');
|
|
26
|
+
this.expect(await this.read('documents/a.txt')).toEqual(TEXT);
|
|
27
|
+
this.expect((await this.subject.stat('documents/a.txt'))?.mediaType).toBe('text/plain');
|
|
28
|
+
this.expect(await this.subject.stat('staging/documents/a.txt')).toBe(undefined);
|
|
29
|
+
});
|
|
30
|
+
this.it('refuses to move a file that is not there', async () => {
|
|
31
|
+
this.expect(await this.codeOf(() => this.subject.move('documents/missing.pdf', 'documents/b.pdf'))).toBe('FILE_NOT_FOUND');
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
lists() {
|
|
35
|
+
this.it('lists the files under a prefix, nested ones included, and no other', async () => {
|
|
36
|
+
await this.write('staging/a.pdf', PDF);
|
|
37
|
+
await this.write('staging/deep/er/b.pdf', TEXT);
|
|
38
|
+
await this.write('documents/c.pdf', PDF);
|
|
39
|
+
const listed = [];
|
|
40
|
+
for await (const file of this.subject.list('staging/')) {
|
|
41
|
+
listed.push([file.key, file.size, file.lastModified instanceof Date]);
|
|
42
|
+
}
|
|
43
|
+
listed.sort((left, right) => String(left[0]).localeCompare(String(right[0])));
|
|
44
|
+
this.expect(listed).toEqual([
|
|
45
|
+
['staging/a.pdf', PDF.length, true],
|
|
46
|
+
['staging/deep/er/b.pdf', TEXT.length, true],
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=arrangement-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrangement-suite.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/arrangement-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checksum-of.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/checksum-of.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1G"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** The checksum a connector answers, computed by the suite from what it wrote. */
|
|
3
|
+
export async function checksumOf(chunks) {
|
|
4
|
+
const hash = createHash('sha256');
|
|
5
|
+
for await (const chunk of chunks) {
|
|
6
|
+
hash.update(chunk);
|
|
7
|
+
}
|
|
8
|
+
return `sha256:${hash.digest('hex')}`;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=checksum-of.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checksum-of.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/checksum-of.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAwD;IACvF,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAElC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks-of.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/chunks-of.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,wBAAuB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,CAE5E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks-of.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/chunks-of.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,KAAiB;IAC/C,MAAM,KAAK,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FileStorageSuite } from './file-storage-suite.js';
|
|
2
|
+
/** Content goes in and out as streams, intact, measured and refused beyond its limit; keys stay inside the storage. */
|
|
3
|
+
export declare class ContentSuite extends FileStorageSuite {
|
|
4
|
+
protected tests(): void;
|
|
5
|
+
private roundTrips;
|
|
6
|
+
private streamsLarge;
|
|
7
|
+
private overwrites;
|
|
8
|
+
private limits;
|
|
9
|
+
private keys;
|
|
10
|
+
private removes;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=content-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/content-suite.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAI3D,uHAAuH;AACvH,qBAAa,YAAa,SAAQ,gBAAgB;IAChD,SAAS,CAAC,KAAK,IAAI,IAAI,CAOtB;IAED,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { checksumOf } from './checksum-of.js';
|
|
2
|
+
import { chunksOf } from './chunks-of.js';
|
|
3
|
+
import { LARGE_SIZE, PDF, TEXT } from './contents.js';
|
|
4
|
+
import { largeContent } from './large-content.js';
|
|
5
|
+
import { FileStorageSuite } from './file-storage-suite.js';
|
|
6
|
+
const INVALID_KEYS = ['', '../escape', '/absolute', 'a//b', '.hidden', 'a/../b', 'a/./b', 'with space', 'tail/'];
|
|
7
|
+
/** Content goes in and out as streams, intact, measured and refused beyond its limit; keys stay inside the storage. */
|
|
8
|
+
export class ContentSuite extends FileStorageSuite {
|
|
9
|
+
tests() {
|
|
10
|
+
this.roundTrips();
|
|
11
|
+
this.streamsLarge();
|
|
12
|
+
this.overwrites();
|
|
13
|
+
this.limits();
|
|
14
|
+
this.keys();
|
|
15
|
+
this.removes();
|
|
16
|
+
}
|
|
17
|
+
roundTrips() {
|
|
18
|
+
this.it('gives back what was written, with its size, media type and checksum', async () => {
|
|
19
|
+
const stored = await this.subject.put('documents/report.pdf', chunksOf(PDF), { mediaType: 'Application/PDF' });
|
|
20
|
+
const metadata = await this.subject.stat('documents/report.pdf');
|
|
21
|
+
const checksum = await checksumOf([PDF]);
|
|
22
|
+
this.expect(stored).toEqual({
|
|
23
|
+
key: 'documents/report.pdf',
|
|
24
|
+
size: PDF.length,
|
|
25
|
+
mediaType: 'application/pdf',
|
|
26
|
+
checksum,
|
|
27
|
+
});
|
|
28
|
+
this.expect(await this.read('documents/report.pdf')).toEqual(PDF);
|
|
29
|
+
this.expect([metadata?.key, metadata?.size, metadata?.mediaType]).toEqual([
|
|
30
|
+
'documents/report.pdf',
|
|
31
|
+
PDF.length,
|
|
32
|
+
'application/pdf',
|
|
33
|
+
]);
|
|
34
|
+
this.expect(metadata?.checksum === undefined || metadata.checksum === checksum).toBe(true);
|
|
35
|
+
this.expect(metadata?.lastModified instanceof Date).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
streamsLarge() {
|
|
39
|
+
this.it('streams a content of several mebibytes, chunk after chunk', async () => {
|
|
40
|
+
const stored = await this.subject.put('exports/large.bin', largeContent(), {
|
|
41
|
+
mediaType: 'application/octet-stream',
|
|
42
|
+
});
|
|
43
|
+
const content = await this.subject.get('exports/large.bin');
|
|
44
|
+
let read = 0;
|
|
45
|
+
for await (const chunk of content?.stream ?? []) {
|
|
46
|
+
read += Buffer.byteLength(chunk instanceof Uint8Array ? chunk : String(chunk));
|
|
47
|
+
}
|
|
48
|
+
this.expect([stored.size, stored.checksum, read]).toEqual([
|
|
49
|
+
LARGE_SIZE,
|
|
50
|
+
await checksumOf(largeContent()),
|
|
51
|
+
LARGE_SIZE,
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
overwrites() {
|
|
56
|
+
this.it('replaces the content written again under a key', async () => {
|
|
57
|
+
await this.write('notes/today.txt', PDF);
|
|
58
|
+
await this.write('notes/today.txt', TEXT, 'text/plain');
|
|
59
|
+
this.expect(await this.read('notes/today.txt')).toEqual(TEXT);
|
|
60
|
+
this.expect((await this.subject.stat('notes/today.txt'))?.mediaType).toBe('text/plain');
|
|
61
|
+
});
|
|
62
|
+
this.it('answers undefined for a key nothing is stored under', async () => {
|
|
63
|
+
this.expect(await this.subject.get('documents/missing.pdf')).toBe(undefined);
|
|
64
|
+
this.expect(await this.subject.stat('documents/missing.pdf')).toBe(undefined);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
limits() {
|
|
68
|
+
this.it('refuses a content beyond its limit, and leaves what the key held before', async () => {
|
|
69
|
+
await this.write('documents/report.pdf', TEXT, 'text/plain');
|
|
70
|
+
const code = await this.codeOf(() => this.subject.put('documents/report.pdf', largeContent(), { mediaType: 'application/pdf', maxBytes: 1_000_000 }));
|
|
71
|
+
this.expect(code).toBe('FILE_TOO_LARGE');
|
|
72
|
+
this.expect(await this.read('documents/report.pdf')).toEqual(TEXT);
|
|
73
|
+
});
|
|
74
|
+
this.it('leaves nothing under a new key whose content was refused', async () => {
|
|
75
|
+
const code = await this.codeOf(() => this.subject.put('documents/new.pdf', chunksOf(PDF), { mediaType: 'application/pdf', maxBytes: 10 }));
|
|
76
|
+
this.expect(code).toBe('FILE_TOO_LARGE');
|
|
77
|
+
this.expect(await this.subject.stat('documents/new.pdf')).toBe(undefined);
|
|
78
|
+
});
|
|
79
|
+
this.it('accepts a content of exactly its limit', async () => {
|
|
80
|
+
const stored = await this.subject.put('documents/exact.pdf', chunksOf(PDF), {
|
|
81
|
+
mediaType: 'application/pdf',
|
|
82
|
+
maxBytes: PDF.length,
|
|
83
|
+
});
|
|
84
|
+
this.expect(stored.size).toBe(PDF.length);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
keys() {
|
|
88
|
+
this.it('refuses a key that could leave its place, before touching anything', async () => {
|
|
89
|
+
for (const key of INVALID_KEYS) {
|
|
90
|
+
this.expect(await this.codeOf(() => this.write(key, PDF))).toBe('INVALID_FILE_KEY');
|
|
91
|
+
this.expect(await this.codeOf(() => this.subject.get(key))).toBe('INVALID_FILE_KEY');
|
|
92
|
+
this.expect(await this.codeOf(() => this.subject.stat(key))).toBe('INVALID_FILE_KEY');
|
|
93
|
+
this.expect(await this.codeOf(() => this.subject.delete(key))).toBe('INVALID_FILE_KEY');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
removes() {
|
|
98
|
+
this.it('forgets a deleted file, and only that one', async () => {
|
|
99
|
+
await this.write('documents/a.pdf', PDF);
|
|
100
|
+
await this.write('documents/b.pdf', PDF);
|
|
101
|
+
await this.subject.delete('documents/a.pdf');
|
|
102
|
+
this.expect(await this.subject.stat('documents/a.pdf')).toBe(undefined);
|
|
103
|
+
this.expect(await this.read('documents/b.pdf')).toEqual(PDF);
|
|
104
|
+
});
|
|
105
|
+
this.it('accepts the deletion of a file that is not there', async () => {
|
|
106
|
+
this.expect(await this.codeOf(() => this.subject.delete('documents/missing.pdf'))).toBe('resolved');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=content-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-suite.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/content-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;AAEjH,uHAAuH;AACvH,MAAM,OAAO,YAAa,SAAQ,gBAAgB;IACtC,KAAK;QACb,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;YACxF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAC/G,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAEzC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBAC1B,GAAG,EAAE,sBAAsB;gBAC3B,IAAI,EAAE,GAAG,CAAC,MAAM;gBAChB,SAAS,EAAE,iBAAiB;gBAC5B,QAAQ;aACT,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAElE,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;gBACxE,sBAAsB;gBACtB,GAAG,CAAC,MAAM;gBACV,iBAAiB;aAClB,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3F,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,YAAY,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YAC9E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,YAAY,EAAE,EAAE;gBACzE,SAAS,EAAE,0BAA0B;aACtC,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YAC5D,IAAI,IAAI,GAAG,CAAC,CAAC;YAEb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACjF,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;gBACxD,UAAU;gBACV,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;gBAChC,UAAU;aACX,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YACnE,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;YAExD,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;YACxE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7E,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;YAC5F,MAAM,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;YAE7D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAChH,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;YAC7E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CACrG,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;gBAC1E,SAAS,EAAE,iBAAiB;gBAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM;aACrB,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;YACvF,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBACpF,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBACrF,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBACtF,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAC1F,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YAC9D,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YAEzC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAE7C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACxE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;YACrE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACtG,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The contents the suites write: a small PDF, a line of text, and the size of a large one. */
|
|
2
|
+
export declare const PDF: Uint8Array<ArrayBuffer>;
|
|
3
|
+
export declare const TEXT: Uint8Array<ArrayBuffer>;
|
|
4
|
+
/** Six mebibytes, enough to cross the part size of a multipart upload. */
|
|
5
|
+
export declare const LARGE_SIZE: number;
|
|
6
|
+
export declare const LARGE_CHUNK = 65536;
|
|
7
|
+
//# sourceMappingURL=contents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contents.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/contents.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,eAAO,MAAM,GAAG,yBAAwF,CAAC;AAEzG,eAAO,MAAM,IAAI,yBAAkE,CAAC;AAEpF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,QAAoB,CAAC;AAE5C,eAAO,MAAM,WAAW,QAAS,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The contents the suites write: a small PDF, a line of text, and the size of a large one. */
|
|
2
|
+
export const PDF = new Uint8Array(Buffer.from('%PDF-1.7\n1 0 obj << /Type /Catalog >> endobj\n%%EOF\n'));
|
|
3
|
+
export const TEXT = new Uint8Array(Buffer.from('Appointment notes, plain text.\n'));
|
|
4
|
+
/** Six mebibytes, enough to cross the part size of a multipart upload. */
|
|
5
|
+
export const LARGE_SIZE = 6 * 1_024 * 1_024;
|
|
6
|
+
export const LARGE_CHUNK = 65_536;
|
|
7
|
+
//# sourceMappingURL=contents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contents.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/contents.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC,CAAC;AAEpF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC;AAE5C,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FileStorage } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { Awaitable } from '../awaitable.js';
|
|
3
|
+
import type { FollowedResponse } from './followed-response.js';
|
|
4
|
+
import type { SignedRequest } from './signed-request.js';
|
|
5
|
+
export interface FileStorageContractOptions {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** A storage that holds nothing; called before each test. */
|
|
8
|
+
createStorage(): Awaitable<FileStorage>;
|
|
9
|
+
/** Makes the time of the signed links pass: advances their clock, or really waits. */
|
|
10
|
+
elapse(ms: number): Awaitable<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Sends a request to a signed link. Default: `fetch`, for a storage that answers its own links.
|
|
13
|
+
* A storage the application serves passes `followServed(storage)`.
|
|
14
|
+
*/
|
|
15
|
+
follow?(request: SignedRequest): Promise<FollowedResponse>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=file-storage-contract-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage-contract-options.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/file-storage-contract-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,aAAa,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC;IACxC,sFAAsF;IACtF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACpC;;;OAGG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage-contract-options.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/file-storage-contract-options.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FileStorage } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
3
|
+
import type { ContractRunner } from '../contract-runner.js';
|
|
4
|
+
import type { FileStorageContractOptions } from './file-storage-contract-options.js';
|
|
5
|
+
/** What the suites of the file storage contract share: the options, and reading back what was written. */
|
|
6
|
+
export declare abstract class FileStorageSuite extends ContractSuite<FileStorage> {
|
|
7
|
+
protected readonly options: FileStorageContractOptions;
|
|
8
|
+
constructor(runner: ContractRunner, options: FileStorageContractOptions);
|
|
9
|
+
protected write(key: string, bytes: Uint8Array, mediaType?: string): Promise<void>;
|
|
10
|
+
protected read(key: string): Promise<Uint8Array | undefined>;
|
|
11
|
+
/** The code of the `FileError` the work rejects with, `resolved` when it does not, the message of another error. */
|
|
12
|
+
protected codeOf(work: () => Promise<unknown>): Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=file-storage-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage-suite.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/file-storage-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAGrF,0GAA0G;AAC1G,8BAAsB,gBAAiB,SAAQ,aAAa,CAAC,WAAW,CAAC;IAGrE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,0BAA0B;IAFxD,YACE,MAAM,EAAE,cAAc,EACH,OAAO,EAAE,0BAA0B,EAGvD;IAED,UAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,SAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAElG;IAED,UAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAIjE;IAED,oHAAoH;IACpH,UAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAQpE;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FileError } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { ContractSuite } from '../contract-suite.js';
|
|
3
|
+
import { chunksOf } from './chunks-of.js';
|
|
4
|
+
import { readAll } from './read-all.js';
|
|
5
|
+
/** What the suites of the file storage contract share: the options, and reading back what was written. */
|
|
6
|
+
export class FileStorageSuite extends ContractSuite {
|
|
7
|
+
options;
|
|
8
|
+
constructor(runner, options) {
|
|
9
|
+
super(runner, () => options.createStorage());
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
async write(key, bytes, mediaType = 'application/pdf') {
|
|
13
|
+
await this.subject.put(key, chunksOf(bytes), { mediaType });
|
|
14
|
+
}
|
|
15
|
+
async read(key) {
|
|
16
|
+
const content = await this.subject.get(key);
|
|
17
|
+
return content === undefined ? undefined : readAll(content.stream);
|
|
18
|
+
}
|
|
19
|
+
/** The code of the `FileError` the work rejects with, `resolved` when it does not, the message of another error. */
|
|
20
|
+
async codeOf(work) {
|
|
21
|
+
try {
|
|
22
|
+
await work();
|
|
23
|
+
return 'resolved';
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return FileError.is(error) ? error.code : String(error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=file-storage-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage-suite.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/file-storage-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,0GAA0G;AAC1G,MAAM,OAAgB,gBAAiB,SAAQ,aAA0B;IAGlD,OAAO;IAF5B,YACE,MAAsB,EACH,OAAmC;QAEtD,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;uBAF1B,OAAO;IAG5B,CAAC;IAES,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,KAAiB,EAAE,SAAS,GAAG,iBAAiB;QACjF,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9D,CAAC;IAES,KAAK,CAAC,IAAI,CAAC,GAAW;QAC9B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE5C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrE,CAAC;IAED,oHAAoH;IAC1G,KAAK,CAAC,MAAM,CAAC,IAA4B;QACjD,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;YAEb,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ServedFileStorage } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { FollowedResponse } from './followed-response.js';
|
|
3
|
+
import type { SignedRequest } from './signed-request.js';
|
|
4
|
+
/**
|
|
5
|
+
* Follows a link of a storage the application serves, without a server: the token is the `token`
|
|
6
|
+
* parameter of the query, answered the way `signedFileRoutes` of `@ddd-ts-kit/http` answers it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function followServed(storage: ServedFileStorage): (request: SignedRequest) => Promise<FollowedResponse>;
|
|
9
|
+
//# sourceMappingURL=follow-served.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-served.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/follow-served.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAiB9G"}
|