@travetto/model 4.1.3 → 5.0.0-rc.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/LICENSE +1 -1
- package/README.md +8 -14
- package/package.json +7 -7
- package/src/internal/service/crud.ts +4 -4
- package/src/internal/service/expiry.ts +1 -1
- package/src/internal/service/stream.ts +18 -2
- package/src/provider/file.ts +16 -22
- package/src/provider/memory.ts +12 -16
- package/src/registry/types.ts +2 -1
- package/src/service/bulk.ts +1 -1
- package/src/service/stream.ts +2 -11
- package/support/test/expiry.ts +2 -2
- package/support/test/indexed.ts +3 -3
- package/support/test/stream.ts +17 -14
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -172,13 +172,7 @@ export interface ModelStreamSupport {
|
|
|
172
172
|
* Get stream from asset store
|
|
173
173
|
* @param location The location of the stream
|
|
174
174
|
*/
|
|
175
|
-
getStream(location: string): Promise<Readable>;
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get partial stream from asset store given a starting byte and an optional ending byte
|
|
179
|
-
* @param location The location of the stream
|
|
180
|
-
*/
|
|
181
|
-
getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream>;
|
|
175
|
+
getStream(location: string, range?: StreamRange): Promise<Readable>;
|
|
182
176
|
|
|
183
177
|
/**
|
|
184
178
|
* Get metadata for stream
|
|
@@ -231,7 +225,7 @@ The `id` is the only required field for a model, as this is a hard requirement o
|
|
|
231
225
|
|[Redis Model Support](https://github.com/travetto/travetto/tree/main/module/model-redis#readme "Redis backing for the travetto model module.")|X|X|X|X| ||
|
|
232
226
|
|[S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.")|X|X| |X|X| |
|
|
233
227
|
|[SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.")|X|X|X|X| |X|
|
|
234
|
-
|[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#
|
|
228
|
+
|[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L54)|X|X|X|X|X|X|
|
|
235
229
|
|[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L50)|X|X| |X|X|X|
|
|
236
230
|
|
|
237
231
|
## Custom Model Service
|
|
@@ -240,12 +234,13 @@ In addition to the provided contracts, the module also provides common utilities
|
|
|
240
234
|
**Code: Memory Service**
|
|
241
235
|
```typescript
|
|
242
236
|
import { Readable } from 'node:stream';
|
|
243
|
-
import {
|
|
237
|
+
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
238
|
+
import { Class, TimeSpan } from '@travetto/base';
|
|
244
239
|
import { DeepPartial } from '@travetto/schema';
|
|
245
240
|
import { Injectable } from '@travetto/di';
|
|
246
241
|
import { Config } from '@travetto/config';
|
|
247
242
|
import { ModelCrudSupport } from '../service/crud';
|
|
248
|
-
import { ModelStreamSupport,
|
|
243
|
+
import { ModelStreamSupport, StreamMeta, StreamRange } from '../service/stream';
|
|
249
244
|
import { ModelType, OptionalId } from '../types/model';
|
|
250
245
|
import { ModelExpirySupport } from '../service/expiry';
|
|
251
246
|
import { ModelRegistry } from '../registry/model';
|
|
@@ -257,13 +252,13 @@ import { ExistsError } from '../error/exists';
|
|
|
257
252
|
import { ModelIndexedSupport } from '../service/indexed';
|
|
258
253
|
import { ModelIndexedUtil } from '../internal/service/indexed';
|
|
259
254
|
import { ModelStorageUtil } from '../internal/service/storage';
|
|
260
|
-
import {
|
|
255
|
+
import { enforceRange, StreamModel, STREAMS } from '../internal/service/stream';
|
|
261
256
|
import { IndexConfig } from '../registry/types';
|
|
262
257
|
const STREAM_META = `${STREAMS}_meta`;
|
|
263
258
|
type StoreType = Map<string, Buffer>;
|
|
264
259
|
@Config('model.memory')
|
|
265
260
|
export class MemoryModelConfig {
|
|
266
|
-
autoCreate?: boolean;
|
|
261
|
+
autoCreate?: boolean = true;
|
|
267
262
|
namespace?: string;
|
|
268
263
|
cullRate?: number | TimeSpan;
|
|
269
264
|
}
|
|
@@ -301,8 +296,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
301
296
|
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T>;
|
|
302
297
|
// Stream Support
|
|
303
298
|
async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void>;
|
|
304
|
-
async getStream(location: string): Promise<Readable>;
|
|
305
|
-
async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream>;
|
|
299
|
+
async getStream(location: string, range?: StreamRange): Promise<Readable>;
|
|
306
300
|
async describeStream(location: string): Promise<StreamMeta>;
|
|
307
301
|
async deleteStream(location: string): Promise<void>;
|
|
308
302
|
// Expiry
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-rc.0",
|
|
4
4
|
"description": "Datastore abstraction for core operations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"datastore",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"directory": "module/model"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^
|
|
30
|
-
"@travetto/di": "^
|
|
31
|
-
"@travetto/registry": "^
|
|
32
|
-
"@travetto/schema": "^
|
|
29
|
+
"@travetto/config": "^5.0.0-rc.0",
|
|
30
|
+
"@travetto/di": "^5.0.0-rc.0",
|
|
31
|
+
"@travetto/registry": "^5.0.0-rc.0",
|
|
32
|
+
"@travetto/schema": "^5.0.0-rc.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^
|
|
36
|
-
"@travetto/test": "^
|
|
35
|
+
"@travetto/cli": "^5.0.0-rc.0",
|
|
36
|
+
"@travetto/test": "^5.0.0-rc.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import { Class,
|
|
4
|
-
import { SchemaRegistry, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
|
|
3
|
+
import { Class, Util } from '@travetto/base';
|
|
4
|
+
import { DataUtil, SchemaRegistry, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import { ModelRegistry } from '../../registry/model';
|
|
7
7
|
import { ModelIdSource, ModelType, OptionalId } from '../../types/model';
|
|
@@ -77,7 +77,7 @@ export class ModelCrudUtil {
|
|
|
77
77
|
item.id = provider.idSource.create();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (DataUtil.isPlainObject(item)) {
|
|
81
81
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
82
82
|
item = cls.from(item as object);
|
|
83
83
|
}
|
|
@@ -118,7 +118,7 @@ export class ModelCrudUtil {
|
|
|
118
118
|
* @param getExisting How to fetch an existing item
|
|
119
119
|
*/
|
|
120
120
|
static async naivePartialUpdate<T extends ModelType>(cls: Class<T>, item: Partial<T>, view: undefined | string, getExisting: () => Promise<T>): Promise<T> {
|
|
121
|
-
if (
|
|
121
|
+
if (DataUtil.isPlainObject(item)) {
|
|
122
122
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
123
123
|
item = cls.from(item as object);
|
|
124
124
|
}
|
|
@@ -34,7 +34,7 @@ export class ModelExpiryUtil {
|
|
|
34
34
|
const cullable = ModelRegistry.getClasses().filter(cls => !!ModelRegistry.get(cls).expiresAt);
|
|
35
35
|
if (svc.deleteExpired && cullable.length) {
|
|
36
36
|
const running = new AbortController();
|
|
37
|
-
const cullInterval = TimeUtil.
|
|
37
|
+
const cullInterval = TimeUtil.asMillis(svc.config?.cullRate ?? '10m');
|
|
38
38
|
|
|
39
39
|
ShutdownManager.onGracefulShutdown(async () => running.abort(), this);
|
|
40
40
|
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import { Class } from '@travetto/base';
|
|
1
|
+
import { AppError, Class } from '@travetto/base';
|
|
2
|
+
|
|
2
3
|
import { ModelType } from '../../types/model';
|
|
4
|
+
import { StreamRange } from '../../service/stream';
|
|
3
5
|
|
|
4
6
|
class Cls { id: string; }
|
|
5
7
|
export const StreamModel: Class<ModelType> = Cls;
|
|
6
|
-
export const STREAMS = '_streams';
|
|
8
|
+
export const STREAMS = '_streams';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Enforce byte range for stream stream/file of a certain size
|
|
13
|
+
*/
|
|
14
|
+
export function enforceRange({ start, end }: StreamRange, size: number): Required<StreamRange> {
|
|
15
|
+
end = Math.min(end ?? size - 1, size - 1);
|
|
16
|
+
|
|
17
|
+
if (Number.isNaN(start) || Number.isNaN(end) || !Number.isFinite(start) || start >= size || start < 0 || start > end) {
|
|
18
|
+
throw new AppError('Invalid position, out of range', 'data');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { start, end };
|
|
22
|
+
}
|
package/src/provider/file.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import { createReadStream } from 'node:fs';
|
|
3
|
-
|
|
2
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
4
3
|
import os from 'node:os';
|
|
5
|
-
|
|
6
4
|
import { Readable } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { RuntimeContext } from '@travetto/manifest';
|
|
9
|
+
import { Class, TimeSpan } from '@travetto/base';
|
|
10
10
|
import { Injectable } from '@travetto/di';
|
|
11
11
|
import { Config } from '@travetto/config';
|
|
12
12
|
import { Required } from '@travetto/schema';
|
|
13
13
|
|
|
14
14
|
import { ModelCrudSupport } from '../service/crud';
|
|
15
|
-
import { ModelStreamSupport,
|
|
15
|
+
import { ModelStreamSupport, StreamMeta, StreamRange } from '../service/stream';
|
|
16
16
|
import { ModelType, OptionalId } from '../types/model';
|
|
17
17
|
import { ModelExpirySupport } from '../service/expiry';
|
|
18
18
|
import { ModelRegistry } from '../registry/model';
|
|
@@ -21,7 +21,7 @@ import { ModelCrudUtil } from '../internal/service/crud';
|
|
|
21
21
|
import { ModelExpiryUtil } from '../internal/service/expiry';
|
|
22
22
|
import { NotFoundError } from '../error/not-found';
|
|
23
23
|
import { ExistsError } from '../error/exists';
|
|
24
|
-
import { StreamModel, STREAMS } from '../internal/service/stream';
|
|
24
|
+
import { enforceRange, StreamModel, STREAMS } from '../internal/service/stream';
|
|
25
25
|
|
|
26
26
|
type Suffix = '.bin' | '.meta' | '.json' | '.expires';
|
|
27
27
|
|
|
@@ -112,7 +112,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
|
|
|
112
112
|
const file = await this.#resolveName(cls, '.json', id);
|
|
113
113
|
|
|
114
114
|
if (await exists(file)) {
|
|
115
|
-
const content = await
|
|
115
|
+
const content = await fs.readFile(file);
|
|
116
116
|
return this.checkExpiry(cls, await ModelCrudUtil.load(cls, content));
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -180,29 +180,23 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
|
|
|
180
180
|
async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
|
|
181
181
|
const file = await this.#resolveName(STREAMS, BIN, location);
|
|
182
182
|
await Promise.all([
|
|
183
|
-
|
|
183
|
+
await pipeline(input, createWriteStream(file)),
|
|
184
184
|
fs.writeFile(file.replace(BIN, META), JSON.stringify(meta), 'utf8')
|
|
185
185
|
]);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async getStream(location: string): Promise<Readable> {
|
|
189
|
-
const file = await this.#find(STREAMS, BIN, location);
|
|
190
|
-
return createReadStream(file);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream> {
|
|
188
|
+
async getStream(location: string, range?: StreamRange): Promise<Readable> {
|
|
194
189
|
const file = await this.#find(STREAMS, BIN, location);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return { stream, range: [start, end] };
|
|
190
|
+
if (range) {
|
|
191
|
+
const meta = await this.describeStream(location);
|
|
192
|
+
range = enforceRange(range, meta.size);
|
|
193
|
+
}
|
|
194
|
+
return createReadStream(file, range);
|
|
201
195
|
}
|
|
202
196
|
|
|
203
197
|
async describeStream(location: string): Promise<StreamMeta> {
|
|
204
198
|
const file = await this.#find(STREAMS, META, location);
|
|
205
|
-
const content = await
|
|
199
|
+
const content = await fs.readFile(file);
|
|
206
200
|
const text: StreamMeta = JSON.parse(content.toString('utf8'));
|
|
207
201
|
return text;
|
|
208
202
|
}
|
package/src/provider/memory.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { Class, TimeSpan } from '@travetto/base';
|
|
4
5
|
import { DeepPartial } from '@travetto/schema';
|
|
5
6
|
import { Injectable } from '@travetto/di';
|
|
6
7
|
import { Config } from '@travetto/config';
|
|
7
8
|
|
|
8
9
|
import { ModelCrudSupport } from '../service/crud';
|
|
9
|
-
import { ModelStreamSupport,
|
|
10
|
+
import { ModelStreamSupport, StreamMeta, StreamRange } from '../service/stream';
|
|
10
11
|
import { ModelType, OptionalId } from '../types/model';
|
|
11
12
|
import { ModelExpirySupport } from '../service/expiry';
|
|
12
13
|
import { ModelRegistry } from '../registry/model';
|
|
@@ -18,7 +19,7 @@ import { ExistsError } from '../error/exists';
|
|
|
18
19
|
import { ModelIndexedSupport } from '../service/indexed';
|
|
19
20
|
import { ModelIndexedUtil } from '../internal/service/indexed';
|
|
20
21
|
import { ModelStorageUtil } from '../internal/service/storage';
|
|
21
|
-
import { StreamModel, STREAMS } from '../internal/service/stream';
|
|
22
|
+
import { enforceRange, StreamModel, STREAMS } from '../internal/service/stream';
|
|
22
23
|
import { IndexConfig } from '../registry/types';
|
|
23
24
|
|
|
24
25
|
const STREAM_META = `${STREAMS}_meta`;
|
|
@@ -244,22 +245,17 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
244
245
|
const streams = this.#getStore(STREAMS);
|
|
245
246
|
const metaContent = this.#getStore(STREAM_META);
|
|
246
247
|
metaContent.set(location, Buffer.from(JSON.stringify(meta)));
|
|
247
|
-
streams.set(location, await
|
|
248
|
+
streams.set(location, await toBuffer(input));
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
async getStream(location: string): Promise<Readable> {
|
|
251
|
+
async getStream(location: string, range?: StreamRange): Promise<Readable> {
|
|
251
252
|
const streams = this.#find(STREAMS, location, 'notfound');
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
[start, end] = StreamUtil.enforceRange(start, end, buffer.length);
|
|
260
|
-
|
|
261
|
-
const stream = await StreamUtil.bufferToStream(buffer.subarray(start, end + 1));
|
|
262
|
-
return { stream, range: [start, end] };
|
|
253
|
+
let buffer = streams.get(location)!;
|
|
254
|
+
if (range) {
|
|
255
|
+
range = enforceRange(range, buffer.length);
|
|
256
|
+
buffer = buffer.subarray(range.start, range.end! + 1);
|
|
257
|
+
}
|
|
258
|
+
return Readable.from(buffer);
|
|
263
259
|
}
|
|
264
260
|
|
|
265
261
|
async describeStream(location: string): Promise<StreamMeta> {
|
package/src/registry/types.ts
CHANGED
package/src/service/bulk.ts
CHANGED
|
@@ -44,7 +44,7 @@ export class BulkProcessError extends AppError {
|
|
|
44
44
|
constructor(public errors: { idx: number, error: ValidationResultError }[]) {
|
|
45
45
|
super('Bulk processing errors have occurred', 'data', {
|
|
46
46
|
errors: errors.map(x => {
|
|
47
|
-
const { message, type, errors: subErrors, details } = x.error;
|
|
47
|
+
const { message, type, details: { errors: subErrors } = {}, details } = x.error;
|
|
48
48
|
return { message, type, errors: subErrors ?? details, idx: x.idx };
|
|
49
49
|
})
|
|
50
50
|
});
|
package/src/service/stream.ts
CHANGED
|
@@ -35,10 +35,7 @@ export interface StreamMeta {
|
|
|
35
35
|
cacheControl?: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export
|
|
39
|
-
stream: Readable;
|
|
40
|
-
range: [number, number];
|
|
41
|
-
}
|
|
38
|
+
export type StreamRange = { start: number, end?: number };
|
|
42
39
|
|
|
43
40
|
/**
|
|
44
41
|
* Support for Streams CRD. Stream update is not supported.
|
|
@@ -59,13 +56,7 @@ export interface ModelStreamSupport {
|
|
|
59
56
|
* Get stream from asset store
|
|
60
57
|
* @param location The location of the stream
|
|
61
58
|
*/
|
|
62
|
-
getStream(location: string): Promise<Readable>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get partial stream from asset store given a starting byte and an optional ending byte
|
|
66
|
-
* @param location The location of the stream
|
|
67
|
-
*/
|
|
68
|
-
getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream>;
|
|
59
|
+
getStream(location: string, range?: StreamRange): Promise<Readable>;
|
|
69
60
|
|
|
70
61
|
/**
|
|
71
62
|
* Get metadata for stream
|
package/support/test/expiry.ts
CHANGED
|
@@ -23,11 +23,11 @@ export abstract class ModelExpirySuite extends BaseModelSuite<ModelExpirySupport
|
|
|
23
23
|
delayFactor: number = 1;
|
|
24
24
|
|
|
25
25
|
async wait(n: number | TimeSpan) {
|
|
26
|
-
await timers.setTimeout(TimeUtil.
|
|
26
|
+
await timers.setTimeout(TimeUtil.asMillis(n) * this.delayFactor);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
timeFromNow(v: number | TimeSpan, unit?: TimeUnit) {
|
|
30
|
-
return
|
|
30
|
+
return TimeUtil.fromNow(TimeUtil.asMillis(v, unit) * this.delayFactor);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
@Test()
|
package/support/test/indexed.ts
CHANGED
|
@@ -149,9 +149,9 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
149
149
|
async queryComplexDateList() {
|
|
150
150
|
const service = await this.service;
|
|
151
151
|
|
|
152
|
-
await service.create(User4, User4.from({ child: { name: 'bob', age: 40 }, createdDate: TimeUtil.
|
|
153
|
-
await service.create(User4, User4.from({ child: { name: 'bob', age: 30 }, createdDate: TimeUtil.
|
|
154
|
-
await service.create(User4, User4.from({ child: { name: 'bob', age: 50 }, createdDate: TimeUtil.
|
|
152
|
+
await service.create(User4, User4.from({ child: { name: 'bob', age: 40 }, createdDate: TimeUtil.fromNow('3d'), color: 'blue' }));
|
|
153
|
+
await service.create(User4, User4.from({ child: { name: 'bob', age: 30 }, createdDate: TimeUtil.fromNow('2d'), color: 'red' }));
|
|
154
|
+
await service.create(User4, User4.from({ child: { name: 'bob', age: 50 }, createdDate: TimeUtil.fromNow('-1d'), color: 'green' }));
|
|
155
155
|
|
|
156
156
|
const arr = await this.toArray(service.listByIndex(User4, 'nameCreated', { child: { name: 'bob' } }));
|
|
157
157
|
|
package/support/test/stream.ts
CHANGED
|
@@ -3,12 +3,13 @@ import assert from 'node:assert';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { Readable } from 'node:stream';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
6
7
|
|
|
7
8
|
import { Suite, Test, TestFixtures } from '@travetto/test';
|
|
8
|
-
import { StreamUtil } from '@travetto/base';
|
|
9
9
|
|
|
10
10
|
import { BaseModelSuite } from './base';
|
|
11
11
|
import { ModelStreamSupport } from '../../src/service/stream';
|
|
12
|
+
import { enforceRange } from '../../src/internal/service/stream';
|
|
12
13
|
|
|
13
14
|
@Suite()
|
|
14
15
|
export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport> {
|
|
@@ -76,32 +77,34 @@ export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport
|
|
|
76
77
|
await service.upsertStream(meta.hash, stream, meta);
|
|
77
78
|
|
|
78
79
|
const retrieved = await service.getStream(meta.hash);
|
|
79
|
-
const content = (await
|
|
80
|
+
const content = (await toBuffer(retrieved)).toString('utf8');
|
|
80
81
|
assert(content.startsWith('abc'));
|
|
81
82
|
assert(content.endsWith('xyz'));
|
|
82
83
|
|
|
83
|
-
const partial = await service.
|
|
84
|
-
const subContent = (await
|
|
85
|
-
|
|
84
|
+
const partial = await service.getStream(meta.hash, { start: 10, end: 20 });
|
|
85
|
+
const subContent = (await toBuffer(partial)).toString('utf8');
|
|
86
|
+
const range = await enforceRange({ start: 10, end: 20 }, meta.size);
|
|
87
|
+
assert(subContent.length === (range.end - range.start) + 1);
|
|
86
88
|
assert(subContent === 'klmnopqrstu');
|
|
87
89
|
|
|
88
|
-
const partialUnbounded = await service.
|
|
89
|
-
const subContent2 = (await
|
|
90
|
-
|
|
90
|
+
const partialUnbounded = await service.getStream(meta.hash, { start: 10 });
|
|
91
|
+
const subContent2 = (await toBuffer(partialUnbounded)).toString('utf8');
|
|
92
|
+
const range2 = await enforceRange({ start: 10 }, meta.size);
|
|
93
|
+
assert(subContent2.length === (range2.end - range2.start) + 1);
|
|
91
94
|
assert(subContent2.startsWith('klm'));
|
|
92
95
|
assert(subContent2.endsWith('xyz'));
|
|
93
96
|
|
|
94
|
-
const partialSingle = await service.
|
|
95
|
-
const subContent3 = (await
|
|
97
|
+
const partialSingle = await service.getStream(meta.hash, { start: 10, end: 10 });
|
|
98
|
+
const subContent3 = (await toBuffer(partialSingle)).toString('utf8');
|
|
96
99
|
assert(subContent3.length === 1);
|
|
97
100
|
assert(subContent3 === 'k');
|
|
98
101
|
|
|
99
|
-
const partialOverbounded = await service.
|
|
100
|
-
const subContent4 = (await
|
|
102
|
+
const partialOverbounded = await service.getStream(meta.hash, { start: 20, end: 40 });
|
|
103
|
+
const subContent4 = (await toBuffer(partialOverbounded)).toString('utf8');
|
|
101
104
|
assert(subContent4.length === 6);
|
|
102
105
|
assert(subContent4.endsWith('xyz'));
|
|
103
106
|
|
|
104
|
-
await assert.rejects(() => service.
|
|
105
|
-
await assert.rejects(() => service.
|
|
107
|
+
await assert.rejects(() => service.getStream(meta.hash, { start: -10, end: 10 }));
|
|
108
|
+
await assert.rejects(() => service.getStream(meta.hash, { start: 30, end: 37 }));
|
|
106
109
|
}
|
|
107
110
|
}
|