@travetto/model 3.1.6 → 3.1.7
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 +9 -2
- package/package.json +1 -1
- package/src/internal/service/expiry.ts +19 -0
- package/src/internal/service/stream.ts +10 -2
- package/src/provider/file.ts +14 -13
- package/src/provider/memory.ts +13 -15
- package/src/service/stream.ts +16 -1
- package/support/fixtures/text.txt +1 -0
- package/support/test/stream.ts +33 -0
package/README.md
CHANGED
|
@@ -174,6 +174,12 @@ export interface ModelStreamSupport {
|
|
|
174
174
|
*/
|
|
175
175
|
getStream(location: string): Promise<Readable>;
|
|
176
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>;
|
|
182
|
+
|
|
177
183
|
/**
|
|
178
184
|
* Get metadata for stream
|
|
179
185
|
* @param location The location of the stream
|
|
@@ -251,7 +257,7 @@ import { DeepPartial } from '@travetto/schema';
|
|
|
251
257
|
import { Injectable } from '@travetto/di';
|
|
252
258
|
import { Config } from '@travetto/config';
|
|
253
259
|
import { ModelCrudSupport } from '../service/crud';
|
|
254
|
-
import { ModelStreamSupport, StreamMeta } from '../service/stream';
|
|
260
|
+
import { ModelStreamSupport, PartialStream, StreamMeta } from '../service/stream';
|
|
255
261
|
import { ModelType, OptionalId } from '../types/model';
|
|
256
262
|
import { ModelExpirySupport } from '../service/expiry';
|
|
257
263
|
import { ModelRegistry } from '../registry/model';
|
|
@@ -263,7 +269,7 @@ import { ExistsError } from '../error/exists';
|
|
|
263
269
|
import { ModelIndexedSupport } from '../service/indexed';
|
|
264
270
|
import { ModelIndexedUtil } from '../internal/service/indexed';
|
|
265
271
|
import { ModelStorageUtil } from '../internal/service/storage';
|
|
266
|
-
import { StreamModel, STREAMS } from '../internal/service/stream';
|
|
272
|
+
import { ModelStreamUtil, StreamModel, STREAMS } from '../internal/service/stream';
|
|
267
273
|
import { IndexConfig } from '../registry/types';
|
|
268
274
|
const STREAM_META = `${STREAMS}_meta`;
|
|
269
275
|
type StoreType = Map<string, Buffer>;
|
|
@@ -308,6 +314,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
308
314
|
// Stream Support
|
|
309
315
|
async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void>;
|
|
310
316
|
async getStream(location: string): Promise<Readable>;
|
|
317
|
+
async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream>;
|
|
311
318
|
async describeStream(location: string): Promise<StreamMeta>;
|
|
312
319
|
async deleteStream(location: string): Promise<void>;
|
|
313
320
|
// Expiry Support
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { ShutdownManager, Class, TimeSpan, TimeUtil } from '@travetto/base';
|
|
|
3
3
|
import { ModelRegistry } from '../../registry/model';
|
|
4
4
|
import { ModelExpirySupport } from '../../service/expiry';
|
|
5
5
|
import { ModelType } from '../../types/model';
|
|
6
|
+
import { NotFoundError } from '../../error/not-found';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Utils for model expiry
|
|
@@ -50,4 +51,22 @@ export class ModelExpiryUtil {
|
|
|
50
51
|
})();
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Simple cull operation for a given model type
|
|
57
|
+
* @param svc
|
|
58
|
+
*/
|
|
59
|
+
static async naiveDeleteExpired<T extends ModelType>(svc: ModelExpirySupport, cls: Class<T>, suppressErrors = false): Promise<number> {
|
|
60
|
+
const deleting = [];
|
|
61
|
+
for await (const el of svc.list(cls)) {
|
|
62
|
+
if (this.getExpiryState(cls, el).expired) {
|
|
63
|
+
deleting.push(svc.delete(cls, el.id).catch(err => {
|
|
64
|
+
if (!suppressErrors && !(err instanceof NotFoundError)) {
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return (await Promise.all(deleting)).length;
|
|
71
|
+
}
|
|
53
72
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { Class } from '@travetto/base';
|
|
1
|
+
import { AppError, Class } from '@travetto/base';
|
|
2
2
|
import { ModelType } from '../../types/model';
|
|
3
3
|
|
|
4
4
|
class Cls { id: string; }
|
|
5
5
|
export const StreamModel: Class<ModelType> = Cls;
|
|
6
|
-
export const STREAMS = '_streams';
|
|
6
|
+
export const STREAMS = '_streams';
|
|
7
|
+
|
|
8
|
+
export class ModelStreamUtil {
|
|
9
|
+
static checkRange(start: number, end: number, size: number): void {
|
|
10
|
+
if (Number.isNaN(start) || Number.isNaN(end) || !Number.isFinite(start) || start < 0 || end >= size) {
|
|
11
|
+
throw new AppError('Invalid position, out of range', 'data');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/provider/file.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Config } from '@travetto/config';
|
|
|
12
12
|
import { Required } from '@travetto/schema';
|
|
13
13
|
|
|
14
14
|
import { ModelCrudSupport } from '../service/crud';
|
|
15
|
-
import { ModelStreamSupport, StreamMeta } from '../service/stream';
|
|
15
|
+
import { ModelStreamSupport, PartialStream, StreamMeta } 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 { ModelStreamUtil, StreamModel, STREAMS } from '../internal/service/stream';
|
|
25
25
|
|
|
26
26
|
type Suffix = '.bin' | '.meta' | '.json' | '.expires';
|
|
27
27
|
|
|
@@ -190,6 +190,17 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
|
|
|
190
190
|
return createReadStream(file);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream> {
|
|
194
|
+
const file = await this.#find(STREAMS, BIN, location);
|
|
195
|
+
const meta = await this.describeStream(location);
|
|
196
|
+
end ??= meta.size - 1;
|
|
197
|
+
|
|
198
|
+
ModelStreamUtil.checkRange(start, end, meta.size);
|
|
199
|
+
|
|
200
|
+
const stream = createReadStream(file, { start, end });
|
|
201
|
+
return { stream, range: [start, end] };
|
|
202
|
+
}
|
|
203
|
+
|
|
193
204
|
async describeStream(location: string): Promise<StreamMeta> {
|
|
194
205
|
const file = await this.#find(STREAMS, META, location);
|
|
195
206
|
const content = await StreamUtil.streamToBuffer(createReadStream(file));
|
|
@@ -211,17 +222,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
|
|
|
211
222
|
|
|
212
223
|
// Expiry
|
|
213
224
|
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
|
|
214
|
-
|
|
215
|
-
for await (const el of this.list(cls)) {
|
|
216
|
-
if (ModelExpiryUtil.getExpiryState(cls, el).expired) {
|
|
217
|
-
deleting.push(this.delete(cls, el.id).catch(err => {
|
|
218
|
-
if (!(err instanceof NotFoundError)) {
|
|
219
|
-
throw err;
|
|
220
|
-
}
|
|
221
|
-
}));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return (await Promise.all(deleting)).length;
|
|
225
|
+
return ModelExpiryUtil.naiveDeleteExpired(this, cls);
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
// Storage management
|
package/src/provider/memory.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Injectable } from '@travetto/di';
|
|
|
6
6
|
import { Config } from '@travetto/config';
|
|
7
7
|
|
|
8
8
|
import { ModelCrudSupport } from '../service/crud';
|
|
9
|
-
import { ModelStreamSupport, StreamMeta } from '../service/stream';
|
|
9
|
+
import { ModelStreamSupport, PartialStream, StreamMeta } from '../service/stream';
|
|
10
10
|
import { ModelType, OptionalId } from '../types/model';
|
|
11
11
|
import { ModelExpirySupport } from '../service/expiry';
|
|
12
12
|
import { ModelRegistry } from '../registry/model';
|
|
@@ -18,7 +18,7 @@ import { ExistsError } from '../error/exists';
|
|
|
18
18
|
import { ModelIndexedSupport } from '../service/indexed';
|
|
19
19
|
import { ModelIndexedUtil } from '../internal/service/indexed';
|
|
20
20
|
import { ModelStorageUtil } from '../internal/service/storage';
|
|
21
|
-
import { StreamModel, STREAMS } from '../internal/service/stream';
|
|
21
|
+
import { ModelStreamUtil, StreamModel, STREAMS } from '../internal/service/stream';
|
|
22
22
|
import { IndexConfig } from '../registry/types';
|
|
23
23
|
|
|
24
24
|
const STREAM_META = `${STREAMS}_meta`;
|
|
@@ -249,6 +249,15 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
249
249
|
return StreamUtil.bufferToStream(streams.get(location)!);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream> {
|
|
253
|
+
const streams = this.#find(STREAMS, location, 'notfound');
|
|
254
|
+
const buffer = streams.get(location)!;
|
|
255
|
+
end ??= (buffer.length - 1);
|
|
256
|
+
ModelStreamUtil.checkRange(start, end, buffer.length);
|
|
257
|
+
const stream = await StreamUtil.bufferToStream(buffer.subarray(start, end + 1));
|
|
258
|
+
return { stream, range: [start, end] };
|
|
259
|
+
}
|
|
260
|
+
|
|
252
261
|
async describeStream(location: string): Promise<StreamMeta> {
|
|
253
262
|
const metaContent = this.#find(STREAM_META, location, 'notfound');
|
|
254
263
|
const meta: StreamMeta = JSON.parse(metaContent.get(location)!.toString('utf8'));
|
|
@@ -266,20 +275,9 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
266
275
|
}
|
|
267
276
|
}
|
|
268
277
|
|
|
269
|
-
// Expiry
|
|
278
|
+
// Expiry
|
|
270
279
|
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
|
|
271
|
-
|
|
272
|
-
const store = this.#getStore(cls);
|
|
273
|
-
for (const id of [...store.keys()]) {
|
|
274
|
-
if ((ModelExpiryUtil.getExpiryState(cls, await this.get(cls, id))).expired) {
|
|
275
|
-
deleting.push(this.delete(cls, id).catch(err => {
|
|
276
|
-
if (!(err instanceof NotFoundError)) {
|
|
277
|
-
throw err;
|
|
278
|
-
}
|
|
279
|
-
}));
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return (await Promise.all(deleting)).length;
|
|
280
|
+
return ModelExpiryUtil.naiveDeleteExpired(this, cls);
|
|
283
281
|
}
|
|
284
282
|
|
|
285
283
|
// Storage Support
|
package/src/service/stream.ts
CHANGED
|
@@ -14,9 +14,18 @@ export interface StreamMeta {
|
|
|
14
14
|
*/
|
|
15
15
|
hash: string;
|
|
16
16
|
/**
|
|
17
|
-
* The original filename of the file
|
|
17
|
+
* The original base filename of the file
|
|
18
18
|
*/
|
|
19
19
|
filename: string;
|
|
20
|
+
/**
|
|
21
|
+
* Filenames title, optional for elements like images, audio, videos
|
|
22
|
+
*/
|
|
23
|
+
title?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PartialStream {
|
|
27
|
+
stream: Readable;
|
|
28
|
+
range: [number, number];
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -40,6 +49,12 @@ export interface ModelStreamSupport {
|
|
|
40
49
|
*/
|
|
41
50
|
getStream(location: string): Promise<Readable>;
|
|
42
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Get partial stream from asset store given a starting byte and an optional ending byte
|
|
54
|
+
* @param location The location of the stream
|
|
55
|
+
*/
|
|
56
|
+
getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream>;
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* Get metadata for stream
|
|
45
60
|
* @param location The location of the stream
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
abcdefghijklmnopqrstuvwxyz
|
package/support/test/stream.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Suite, Test, TestFixtures } from '@travetto/test';
|
|
|
6
6
|
|
|
7
7
|
import { BaseModelSuite } from './base';
|
|
8
8
|
import { ModelStreamSupport } from '../../src/service/stream';
|
|
9
|
+
import { StreamUtil } from '@travetto/base';
|
|
9
10
|
|
|
10
11
|
@Suite()
|
|
11
12
|
export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport> {
|
|
@@ -68,4 +69,36 @@ export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport
|
|
|
68
69
|
await service.getStream(meta.hash);
|
|
69
70
|
});
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
@Test()
|
|
74
|
+
async partialStream(): Promise<void> {
|
|
75
|
+
const service = await this.service;
|
|
76
|
+
const [meta, stream] = await this.getStream('/text.txt');
|
|
77
|
+
|
|
78
|
+
await service.upsertStream(meta.hash, stream, meta);
|
|
79
|
+
|
|
80
|
+
const retrieved = await service.getStream(meta.hash);
|
|
81
|
+
const content = (await StreamUtil.toBuffer(retrieved)).toString('utf8');
|
|
82
|
+
assert(content.startsWith('abc'));
|
|
83
|
+
assert(content.endsWith('xyz'));
|
|
84
|
+
|
|
85
|
+
const partial = await service.getStreamPartial(meta.hash, 10, 20);
|
|
86
|
+
const subContent = (await StreamUtil.toBuffer(partial.stream)).toString('utf8');
|
|
87
|
+
assert(subContent.length === (partial.range[1] - partial.range[0]) + 1);
|
|
88
|
+
assert(subContent === 'klmnopqrstu');
|
|
89
|
+
|
|
90
|
+
const partialUnbounded = await service.getStreamPartial(meta.hash, 10);
|
|
91
|
+
const subContent2 = (await StreamUtil.toBuffer(partialUnbounded.stream)).toString('utf8');
|
|
92
|
+
assert(subContent2.length === (partialUnbounded.range[1] - partialUnbounded.range[0]) + 1);
|
|
93
|
+
assert(subContent2.startsWith('klm'));
|
|
94
|
+
assert(subContent2.endsWith('xyz'));
|
|
95
|
+
|
|
96
|
+
const partialSingle = await service.getStreamPartial(meta.hash, 10, 10);
|
|
97
|
+
const subContent3 = (await StreamUtil.toBuffer(partialSingle.stream)).toString('utf8');
|
|
98
|
+
assert(subContent3.length === 1);
|
|
99
|
+
assert(subContent3 === 'k');
|
|
100
|
+
|
|
101
|
+
await assert.rejects(() => service.getStreamPartial(meta.hash, -10, 10));
|
|
102
|
+
await assert.rejects(() => service.getStreamPartial(meta.hash, 0, 27));
|
|
103
|
+
}
|
|
71
104
|
}
|