@ddd-ts-kit/testing 0.7.0 → 0.9.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 +8 -4
- package/dist/contracts/event-bus-contract.js +4 -3
- package/dist/contracts/event-bus-contract.js.map +1 -1
- 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/outbox-store-contract.d.ts +5 -0
- package/dist/contracts/outbox-store-contract.d.ts.map +1 -1
- package/dist/contracts/outbox-store-contract.js +3 -1
- package/dist/contracts/outbox-store-contract.js.map +1 -1
- package/dist/contracts/tenant-envelope.d.ts +4 -0
- package/dist/contracts/tenant-envelope.d.ts.map +1 -0
- package/dist/contracts/tenant-envelope.js +6 -0
- package/dist/contracts/tenant-envelope.js.map +1 -0
- package/dist/contracts/tenant-isolation-contract.d.ts +37 -0
- package/dist/contracts/tenant-isolation-contract.d.ts.map +1 -0
- package/dist/contracts/tenant-isolation-contract.js +117 -0
- package/dist/contracts/tenant-isolation-contract.js.map +1 -0
- package/dist/contracts.d.ts +7 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +4 -0
- package/dist/contracts.js.map +1 -1
- package/dist/execution/as-tenant.d.ts +7 -0
- package/dist/execution/as-tenant.d.ts.map +1 -0
- package/dist/execution/as-tenant.js +10 -0
- package/dist/execution/as-tenant.js.map +1 -0
- package/dist/execution/execution-context-builder.d.ts +4 -1
- package/dist/execution/execution-context-builder.d.ts.map +1 -1
- package/dist/execution/execution-context-builder.js +7 -1
- package/dist/execution/execution-context-builder.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -280,12 +280,14 @@ await scoped(executionScope, anExecutionContext().as(bob).ofTenant('globex'), ()
|
|
|
280
280
|
| Builder | Default | Says |
|
|
281
281
|
|---|---|---|
|
|
282
282
|
| `aSession()` | a user of its own, opened with a password at the test epoch, never expiring | `of(actor)`, `identifiedBy`, `issuedAt`, `expiringAt`, `authenticatedBy`, `withSecondFactor`, `impersonatedBy`, `inLocale`, `inTimeZone`, `withExtensions` |
|
|
283
|
-
| `anExecutionContext()` | the work of a user of its own, origin `test`, correlation of its own | `as(actor)`, `inSession(session)`, `ofTenant`, `correlatedBy`, `causedBy`, `from(origin)`, `dueBy`, `inLocale`, `withExtensions` |
|
|
283
|
+
| `anExecutionContext()` | the work of a user of its own, origin `test`, correlation of its own | `as(actor)`, `inSession(session)`, `ofTenant`, `elevatedOver(tenants, reason?)`, `correlatedBy`, `causedBy`, `from(origin)`, `dueBy`, `inLocale`, `withExtensions` |
|
|
284
284
|
|
|
285
285
|
Both are classes on `Builder`, immutable like every builder, and accept a builder wherever an
|
|
286
286
|
actor or a session is expected (`aSession().of(anActor().withRole('accountant'))` with the
|
|
287
287
|
actor builder of `@ddd-ts-kit/auth/testing`). `scoped(scope, context, work)` runs a block in a
|
|
288
|
-
context given as a value or as a builder, with any `ExecutionScope
|
|
288
|
+
context given as a value or as a builder, with any `ExecutionScope`; `asTenant(scope, 'acme',
|
|
289
|
+
work)` runs it as a member of a tenant, and `elevatedOver('all')` builds the context of platform
|
|
290
|
+
staff under an elevation, without the audit entry `acrossTenants` writes.
|
|
289
291
|
|
|
290
292
|
## End to end
|
|
291
293
|
|
|
@@ -429,9 +431,10 @@ repositoryContract(runner, { ...options, name: 'DrizzleOrderRepository', createR
|
|
|
429
431
|
| Suite | Holds |
|
|
430
432
|
|---|---|
|
|
431
433
|
| `repositoryContract` | save then find, unknown id, update, isolation of the stored copy, optimistic concurrency (any error with code `CONCURRENCY_CONFLICT`) |
|
|
432
|
-
| `
|
|
434
|
+
| `tenantIsolationContract` | one repository under a strategy of tenant isolation: what a tenant saved is hidden from another; another tenant saving under the same id leaves it untouched, by an update or (given `recreate(aggregate)`, a new aggregate under the same id) by an insert; platform staff elevated over every tenant write nothing, and read the other tenant's aggregate when `elevatedReads` is true (shared tables, row-level security); a write of work that names no tenant is refused (`NoCurrentTenantError`) and a read is refused or finds nothing. `createRepository(scope)` reads the tenant from the scope it is given, on storage where the tenants `acme` and `globex` exist. To prove row-level security itself, run it against a repository that writes no tenant condition |
|
|
435
|
+
| `eventBusContract` | any `EventBus` provider: routing by type, several subscribers, unsubscribe, exact envelope round trip (`traceparent`, `correlationId`, `tenantId`), handler failure surfaces. Redeliveries are accepted; return `settle()` from `createBus` for providers that deliver asynchronously |
|
|
433
436
|
| `unitOfWorkContract` | any `UnitOfWork`, called through `run` without a command bus: domain events wait for the end and are dispatched before the commit, so handlers see every save and what they write, the outbox included, commits with it; a failure in a save, in the work, in a handler or in the commit (a deferred constraint) leaves nothing behind; `after-commit` handlers run once the rows are committed, never otherwise. An adapter not built on `withinUnitOfWork` fails here |
|
|
434
|
-
| `outboxStoreContract` | any outbox store: order, limit, lease, concurrent claims, published, failed, dead, `retryDead`, purge |
|
|
437
|
+
| `outboxStoreContract` | any outbox store: order, limit, lease, concurrent claims, published, failed, dead, `retryDead`, purge; with `carriesTenant: true`, the tenant of each event kept |
|
|
435
438
|
| `processedEventStoreContract` | any `ProcessedEventStore`: once per consumer and event, also under concurrent deliveries |
|
|
436
439
|
| `cacheContract` | any `Cache`: round trip of every shape of value, isolation of entries, deletion, time to live |
|
|
437
440
|
| `lockContract` | any `Lock`: one holder at a time also under concurrent requests, release, expiry, a late release ignored, growing fencing token, `waitMs` |
|
|
@@ -439,6 +442,7 @@ repositoryContract(runner, { ...options, name: 'DrizzleOrderRepository', createR
|
|
|
439
442
|
| `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 |
|
|
440
443
|
| `criteriaContract` | any translator of criteria, the in-memory evaluator included: a table of criteria and the ids each answers, in order, over eight fixed rows. Every operator on every type it applies to, decimals as numbers, instants to the millisecond and across zones, case and the characters a `LIKE` would trip on, nulls under each operator, `and`, `or`, `not` and what a negation does to a null, sorts with ties and nulls in both directions, offset pages with their total, every sort walked by cursor one and two rows at a time, the four kinds of access scope, and hostile values. `createReadModel(rows)` answers `{ search(scopedCriteria) }` over exactly those rows, read with `criteriaContractDefinition` |
|
|
441
444
|
| `readModelStoreContract` | any `ReadModelTable` under `TableReadModelStore`: rows of every type round trip, the current row handed to a later event, an event applied once, never one older than the row, order by event id at the same instant and by version when given, concurrent writes ending on the latest, deletions that last, listing by criteria and scope, and `ProjectionRebuild`: rebuilt from the source alone, no event lost while it exports (on a row already copied and on one not yet), stale and new events after the swap, deletions during and before it, a failed export leaving the table as it was, the table still itself after two rebuilds. `createTable()` answers `{ table, unitOfWork }`; `SourceOfTruth` is the fake of the source |
|
|
445
|
+
| `fileStorageContract` | any `FileStorage`: content round trip with size, media type and checksum, several mebibytes streamed, a write beyond `maxBytes` refused leaving the previous content, keys that could leave their place refused, deletion, copy, move, listing by prefix, signed download and upload links that expire, refuse a changed signature or another media type, and are never signed for more than an hour, a download served as an attachment even without a name. `follow` sends a request to a link: `fetch` by default, `followServed(storage)` for a storage the application serves |
|
|
442
446
|
| `rateLimiterContract` | any `RateLimiter`: limit, `remaining` and `retryAfterMs`, refused calls not counted, sliding window, isolation of keys, concurrent calls |
|
|
443
447
|
|
|
444
448
|
The cache, lock and rate limiter suites also take `elapse(ms)`, which makes time pass for the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContractSuite } from './contract-suite.js';
|
|
2
2
|
import { failureOf } from './failure-of.js';
|
|
3
|
-
import {
|
|
3
|
+
import { tenantEnvelope } from './tenant-envelope.js';
|
|
4
4
|
/**
|
|
5
5
|
* What every integration event provider guarantees. Delivery is at-least-once: the suite
|
|
6
6
|
* accepts redeliveries everywhere and never relies on the order of arrival.
|
|
@@ -66,9 +66,9 @@ class EventBusSuite extends ContractSuite {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
handsTheEnvelopeOverUntouched() {
|
|
69
|
-
this.it('hands the envelope over exactly as published, trace and
|
|
69
|
+
this.it('hands the envelope over exactly as published, trace, correlation and tenant included', async () => {
|
|
70
70
|
const happened = this.collect(HAPPENED);
|
|
71
|
-
const published =
|
|
71
|
+
const published = tenantEnvelope(busEvent('evt-1'));
|
|
72
72
|
await this.publish([published]);
|
|
73
73
|
this.expect(happened.events[0]).toEqual(published);
|
|
74
74
|
});
|
|
@@ -78,6 +78,7 @@ class EventBusSuite extends ContractSuite {
|
|
|
78
78
|
this.expect(happened.events[0]).toEqual(busEvent('evt-1'));
|
|
79
79
|
this.expect(happened.events[0]?.traceparent).toBe(undefined);
|
|
80
80
|
this.expect(happened.events[0]?.correlationId).toBe(undefined);
|
|
81
|
+
this.expect(happened.events[0]?.tenantId).toBe(undefined);
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
survivesAFailingHandler() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus-contract.js","sourceRoot":"","sources":["../../src/contracts/event-bus-contract.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"event-bus-contract.js","sourceRoot":"","sources":["../../src/contracts/event-bus-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { contentDisposition } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { readAll } from './read-all.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 function followServed(storage) {
|
|
9
|
+
return async (request) => {
|
|
10
|
+
const token = new URL(request.url).searchParams.get('token') ?? '';
|
|
11
|
+
if (request.method === 'GET') {
|
|
12
|
+
const download = await storage.openSigned(token);
|
|
13
|
+
return download === undefined
|
|
14
|
+
? refused()
|
|
15
|
+
: { status: 200, headers: dispositionOf(download.fileName), body: await readAll(download.stream) };
|
|
16
|
+
}
|
|
17
|
+
const body = Readable.from([Buffer.from(request.body ?? new Uint8Array())]);
|
|
18
|
+
const stored = await storage.acceptSigned(token, body, request.headers?.['content-type'] ?? '');
|
|
19
|
+
return stored === undefined ? refused() : { status: 200, headers: {}, body: new Uint8Array() };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function refused() {
|
|
23
|
+
return { status: 403, headers: {}, body: new Uint8Array() };
|
|
24
|
+
}
|
|
25
|
+
function dispositionOf(fileName) {
|
|
26
|
+
return { 'content-disposition': contentDisposition(fileName) };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=follow-served.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-served.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/follow-served.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAA0B,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAA0B;IACrD,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAEnE,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAEjD,OAAO,QAAQ,KAAK,SAAS;gBAC3B,CAAC,CAAC,OAAO,EAAE;gBACX,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACvG,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;QAEhG,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,EAAE,CAAC;IACjG,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO;IACd,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,aAAa,CAAC,QAA4B;IACjD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FollowedResponse } from './followed-response.js';
|
|
2
|
+
import type { SignedRequest } from './signed-request.js';
|
|
3
|
+
/** Follows a link over the network: for a storage that answers its own links, an object store. */
|
|
4
|
+
export declare function followWithFetch(request: SignedRequest): Promise<FollowedResponse>;
|
|
5
|
+
//# sourceMappingURL=follow-with-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-with-fetch.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/follow-with-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,kGAAkG;AAClG,wBAAsB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAYvF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Follows a link over the network: for a storage that answers its own links, an object store. */
|
|
2
|
+
export async function followWithFetch(request) {
|
|
3
|
+
const response = await fetch(request.url, {
|
|
4
|
+
method: request.method,
|
|
5
|
+
headers: { ...request.headers },
|
|
6
|
+
...(request.body === undefined ? {} : { body: request.body }),
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
status: response.status,
|
|
10
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
11
|
+
body: new Uint8Array(await response.arrayBuffer()),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=follow-with-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-with-fetch.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/follow-with-fetch.ts"],"names":[],"mappings":"AAGA,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAsB;IAC1D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;QAC/B,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;KAC9D,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACvD,IAAI,EAAE,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;KACnD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followed-response.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/followed-response.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followed-response.js","sourceRoot":"","sources":["../../../src/contracts/file-storage/followed-response.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"large-content.d.ts","sourceRoot":"","sources":["../../../src/contracts/file-storage/large-content.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAuB,YAAY,IAAI,aAAa,CAAC,UAAU,CAAC,CAI/D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LARGE_CHUNK, LARGE_SIZE } from './contents.js';
|
|
2
|
+
/** Six mebibytes in chunks of 64 KiB, each filled with its rank: never held whole. */
|
|
3
|
+
export async function* largeContent() {
|
|
4
|
+
for (let offset = 0; offset < LARGE_SIZE; offset += LARGE_CHUNK) {
|
|
5
|
+
yield new Uint8Array(LARGE_CHUNK).fill(offset / LARGE_CHUNK);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=large-content.js.map
|