@travetto/model 5.0.0-rc.9 → 5.0.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.
@@ -3,8 +3,8 @@ import { DependencyRegistry } from '@travetto/di';
3
3
  import { RootRegistry } from '@travetto/registry';
4
4
  import { SuiteRegistry, TestFixtures } from '@travetto/test';
5
5
 
6
- import { isStorageSupported, isStreamSupported } from '../../src/internal/service/common';
7
- import { StreamModel } from '../../src/internal/service/stream';
6
+ import { isBlobSupported, isStorageSupported } from '../../src/internal/service/common';
7
+ import { MODEL_BLOB } from '../../src/internal/service/blob';
8
8
  import { ModelRegistry } from '../../src/registry/model';
9
9
 
10
10
  const Loaded = Symbol();
@@ -61,11 +61,11 @@ export function ModelSuite<T extends { configClass: Class<{ autoCreate?: boolean
61
61
  }
62
62
  }
63
63
  }
64
- if (isStreamSupported(service)) {
64
+ if (isBlobSupported(service)) {
65
65
  if (service.truncateModel) {
66
- await service.truncateModel(StreamModel);
66
+ await service.truncateModel(MODEL_BLOB);
67
67
  } else if (service.deleteModel) {
68
- await service.deleteModel(StreamModel);
68
+ await service.deleteModel(MODEL_BLOB);
69
69
  }
70
70
  }
71
71
  } else {
@@ -1,22 +0,0 @@
1
- import { AppError, Class } from '@travetto/runtime';
2
-
3
- import { ModelType } from '../../types/model';
4
- import { StreamRange } from '../../service/stream';
5
-
6
- class Cls { id: string; }
7
- export const StreamModel: Class<ModelType> = Cls;
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
- }
@@ -1,231 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import { createReadStream, createWriteStream } from 'node:fs';
3
- import os from 'node:os';
4
- import { Readable } from 'node:stream';
5
- import { pipeline } from 'node:stream/promises';
6
- import path from 'node:path';
7
-
8
- import { Class, TimeSpan, Runtime, asFull } from '@travetto/runtime';
9
- import { Injectable } from '@travetto/di';
10
- import { Config } from '@travetto/config';
11
- import { Required } from '@travetto/schema';
12
-
13
- import { ModelCrudSupport } from '../service/crud';
14
- import { ModelStreamSupport, StreamMeta, StreamRange } from '../service/stream';
15
- import { ModelType, OptionalId } from '../types/model';
16
- import { ModelExpirySupport } from '../service/expiry';
17
- import { ModelRegistry } from '../registry/model';
18
- import { ModelStorageSupport } from '../service/storage';
19
- import { ModelCrudUtil } from '../internal/service/crud';
20
- import { ModelExpiryUtil } from '../internal/service/expiry';
21
- import { NotFoundError } from '../error/not-found';
22
- import { ExistsError } from '../error/exists';
23
- import { enforceRange, StreamModel, STREAMS } from '../internal/service/stream';
24
-
25
- type Suffix = '.bin' | '.meta' | '.json' | '.expires';
26
-
27
- const BIN = '.bin';
28
- const META = '.meta';
29
-
30
- @Config('model.file')
31
- export class FileModelConfig {
32
- @Required(false)
33
- folder: string;
34
- namespace: string = '.';
35
- autoCreate?: boolean;
36
- cullRate?: number | TimeSpan;
37
-
38
- async postConstruct(): Promise<void> {
39
- this.folder ??= path.resolve(os.tmpdir(), `trv_file_${Runtime.main.name.replace(/[^a-z]/ig, '_')}`);
40
- }
41
- }
42
-
43
- const exists = (f: string): Promise<boolean> => fs.stat(f).then(() => true, () => false);
44
-
45
- /**
46
- * Standard file support
47
- */
48
- @Injectable()
49
- export class FileModelService implements ModelCrudSupport, ModelStreamSupport, ModelExpirySupport, ModelStorageSupport {
50
-
51
- private static async * scanFolder(folder: string, suffix: string): AsyncGenerator<[id: string, field: string]> {
52
- for (const sub of await fs.readdir(folder)) {
53
- for (const file of await fs.readdir(path.resolve(folder, sub))) {
54
- if (file.endsWith(suffix)) {
55
- yield [file.replace(suffix, ''), path.resolve(folder, sub, file)];
56
- }
57
- }
58
- }
59
- }
60
-
61
- idSource = ModelCrudUtil.uuidSource();
62
-
63
- get client(): string {
64
- return this.config.folder;
65
- }
66
-
67
- /**
68
- * The root location for all activity
69
- */
70
- constructor(public readonly config: FileModelConfig) { }
71
-
72
- async #resolveName<T extends ModelType>(cls: Class<T> | string, suffix?: Suffix, id?: string): Promise<string> {
73
- const name = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
74
- let resolved = path.resolve(this.config.folder, this.config.namespace, name);
75
- if (id) {
76
- resolved = path.resolve(resolved, id.replace(/^[/]/, '').substring(0, 3));
77
- }
78
- let dir = resolved;
79
- if (id) {
80
- resolved = path.resolve(resolved, `${id}${suffix}`);
81
- dir = path.dirname(resolved);
82
- }
83
-
84
- await fs.mkdir(dir, { recursive: true });
85
- return resolved;
86
- }
87
-
88
- async #find<T extends ModelType>(cls: Class<T> | string, suffix: Suffix, id?: string): Promise<string> {
89
- const file = await this.#resolveName(cls, suffix, id);
90
- if (id && !(await exists(file))) {
91
- throw new NotFoundError(cls, id);
92
- }
93
- return file;
94
- }
95
-
96
- postConstruct(): void {
97
- ModelExpiryUtil.registerCull(this);
98
- }
99
-
100
- checkExpiry<T extends ModelType>(cls: Class<T>, item: T): T {
101
- const { expiresAt } = ModelRegistry.get(cls);
102
- if (expiresAt && ModelExpiryUtil.getExpiryState(cls, item).expired) {
103
- throw new NotFoundError(cls, item.id);
104
- }
105
- return item;
106
- }
107
-
108
- async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
109
- await this.#find(cls, '.json', id);
110
-
111
- const file = await this.#resolveName(cls, '.json', id);
112
-
113
- if (await exists(file)) {
114
- const content = await fs.readFile(file);
115
- return this.checkExpiry(cls, await ModelCrudUtil.load(cls, content));
116
- }
117
-
118
- throw new NotFoundError(cls, id);
119
- }
120
-
121
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
122
- if (!item.id) {
123
- item.id = this.idSource.create();
124
- }
125
-
126
- const file = await this.#resolveName(cls, '.json', item.id);
127
-
128
- if (await exists(file)) {
129
- throw new ExistsError(cls, item.id!);
130
- }
131
-
132
- return await this.upsert(cls, item);
133
- }
134
-
135
- async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
136
- await this.get(cls, item.id);
137
- return await this.upsert(cls, item);
138
- }
139
-
140
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
141
- ModelCrudUtil.ensureNotSubType(cls);
142
- const prepped = await ModelCrudUtil.preStore(cls, item, this);
143
-
144
- const file = await this.#resolveName(cls, '.json', item.id);
145
- await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
146
-
147
- return prepped;
148
- }
149
-
150
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
151
- ModelCrudUtil.ensureNotSubType(cls);
152
- const id = item.id;
153
- item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
154
- const file = await this.#resolveName(cls, '.json', item.id);
155
- await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
156
- return asFull<T>(item);
157
- }
158
-
159
- async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
160
- const file = await this.#find(cls, '.json', id);
161
- await fs.unlink(file);
162
- }
163
-
164
- async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
165
- for await (const [id] of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
166
- try {
167
- yield await this.get(cls, id);
168
- } catch (err) {
169
- if (!(err instanceof NotFoundError)) {
170
- throw err;
171
- }
172
- }
173
- }
174
- }
175
-
176
- // Stream
177
- async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
178
- const file = await this.#resolveName(STREAMS, BIN, location);
179
- await Promise.all([
180
- await pipeline(input, createWriteStream(file)),
181
- fs.writeFile(file.replace(BIN, META), JSON.stringify(meta), 'utf8')
182
- ]);
183
- }
184
-
185
- async getStream(location: string, range?: StreamRange): Promise<Readable> {
186
- const file = await this.#find(STREAMS, BIN, location);
187
- if (range) {
188
- const meta = await this.describeStream(location);
189
- range = enforceRange(range, meta.size);
190
- }
191
- return createReadStream(file, range);
192
- }
193
-
194
- async describeStream(location: string): Promise<StreamMeta> {
195
- const file = await this.#find(STREAMS, META, location);
196
- const content = await fs.readFile(file);
197
- const text: StreamMeta = JSON.parse(content.toString('utf8'));
198
- return text;
199
- }
200
-
201
- async deleteStream(location: string): Promise<void> {
202
- const file = await this.#resolveName(STREAMS, BIN, location);
203
- if (await exists(file)) {
204
- await Promise.all([
205
- fs.unlink(file),
206
- fs.unlink(file.replace('.bin', META))
207
- ]);
208
- } else {
209
- throw new NotFoundError('Stream', location);
210
- }
211
- }
212
-
213
- // Expiry
214
- async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
215
- return ModelExpiryUtil.naiveDeleteExpired(this, cls);
216
- }
217
-
218
- // Storage management
219
- async createStorage(): Promise<void> {
220
- const dir = path.resolve(this.config.folder, this.config.namespace);
221
- await fs.mkdir(dir, { recursive: true });
222
- }
223
-
224
- async deleteStorage(): Promise<void> {
225
- await fs.rm(path.resolve(this.config.folder, this.config.namespace), { recursive: true, force: true });
226
- }
227
-
228
- async truncateModel(cls: Class<ModelType>): Promise<void> {
229
- await fs.rm(await this.#resolveName(cls === StreamModel ? STREAMS : cls), { recursive: true, force: true });
230
- }
231
- }
@@ -1,339 +0,0 @@
1
- import { Readable } from 'node:stream';
2
- import { buffer as toBuffer } from 'node:stream/consumers';
3
-
4
- import { Class, TimeSpan, DeepPartial, castTo } from '@travetto/runtime';
5
- import { Injectable } from '@travetto/di';
6
- import { Config } from '@travetto/config';
7
-
8
- import { ModelCrudSupport } from '../service/crud';
9
- import { ModelStreamSupport, StreamMeta, StreamRange } from '../service/stream';
10
- import { ModelType, OptionalId } from '../types/model';
11
- import { ModelExpirySupport } from '../service/expiry';
12
- import { ModelRegistry } from '../registry/model';
13
- import { ModelStorageSupport } from '../service/storage';
14
- import { ModelCrudUtil } from '../internal/service/crud';
15
- import { ModelExpiryUtil } from '../internal/service/expiry';
16
- import { NotFoundError } from '../error/not-found';
17
- import { ExistsError } from '../error/exists';
18
- import { ModelIndexedSupport } from '../service/indexed';
19
- import { ModelIndexedUtil } from '../internal/service/indexed';
20
- import { ModelStorageUtil } from '../internal/service/storage';
21
- import { enforceRange, StreamModel, STREAMS } from '../internal/service/stream';
22
- import { IndexConfig } from '../registry/types';
23
-
24
- const STREAM_META = `${STREAMS}_meta`;
25
-
26
- type StoreType = Map<string, Buffer>;
27
-
28
-
29
- @Config('model.memory')
30
- export class MemoryModelConfig {
31
- autoCreate?: boolean = true;
32
- namespace?: string;
33
- cullRate?: number | TimeSpan;
34
- }
35
-
36
- function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string): string {
37
- return [cls.Ⲑid, typeof idx === 'string' ? idx : idx.name, suffix].filter(x => !!x).join(':');
38
- }
39
-
40
- function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
41
- let id: string | undefined;
42
- if (data instanceof Set) {
43
- id = data.values().next().value;
44
- } else {
45
- id = [...data.entries()].find(([k, v]) => value === undefined || v === value)?.[0];
46
- }
47
- return id;
48
- }
49
-
50
- /**
51
- * Standard in-memory support
52
- */
53
- @Injectable()
54
- export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport {
55
-
56
- #store = new Map<string, StoreType>();
57
- #indices = {
58
- sorted: new Map<string, Map<string, Map<string, number>>>(),
59
- unsorted: new Map<string, Map<string, Set<string>>>()
60
- };
61
-
62
- idSource = ModelCrudUtil.uuidSource();
63
- get client(): Map<string, StoreType> { return this.#store; }
64
-
65
- constructor(public readonly config: MemoryModelConfig) { }
66
-
67
- #getStore<T extends ModelType>(cls: Class<T> | string): StoreType {
68
- const key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
69
- if (!this.#store.has(key)) {
70
- this.#store.set(key, new Map());
71
- }
72
- return this.#store.get(key)!;
73
- }
74
-
75
- #find<T extends ModelType>(cls: Class<T> | string, id?: string, errorState?: 'data' | 'notfound'): StoreType {
76
- const store = this.#getStore(cls);
77
-
78
- if (id && errorState && (errorState === 'notfound' ? !store.has(id) : store.has(id))) {
79
- throw errorState === 'notfound' ? new NotFoundError(cls, id) : new ExistsError(cls, id);
80
- }
81
-
82
- return store;
83
- }
84
-
85
- async #removeIndices<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
86
- try {
87
- const item = await this.get(cls, id);
88
- for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
89
- const idxName = indexName(cls, idx);
90
- const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
91
- this.#indices[idx.type].get(idxName)?.get(key)?.delete(id);
92
- }
93
- } catch (err) {
94
- if (!(err instanceof NotFoundError)) {
95
- throw err;
96
- }
97
- }
98
- }
99
-
100
- async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
101
- for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
102
- const idxName = indexName(cls, idx);
103
- const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
104
- let index = this.#indices[idx.type].get(idxName)?.get(key);
105
-
106
- if (!index) {
107
- if (!this.#indices[idx.type].has(idxName)) {
108
- this.#indices[idx.type].set(idxName, new Map());
109
- }
110
- if (idx.type === 'sorted') {
111
- this.#indices[idx.type].get(idxName)!.set(key, index = new Map());
112
- } else {
113
- this.#indices[idx.type].get(idxName)!.set(key, index = new Set());
114
- }
115
- }
116
-
117
- if (index instanceof Map) {
118
- index?.set(item.id, +sort!);
119
- } else {
120
- index?.add(item.id);
121
- }
122
- }
123
- }
124
-
125
- async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'remove'): Promise<void>;
126
- async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'write'): Promise<T>;
127
- async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'write' | 'remove'): Promise<T | void> {
128
- const store = this.#getStore(cls);
129
- await this.#removeIndices(cls, item.id);
130
- if (action === 'write') {
131
- store.set(item.id, Buffer.from(JSON.stringify(item)));
132
- await this.#writeIndices(cls, item);
133
- return item;
134
- } else {
135
- store.delete(item.id);
136
- }
137
- }
138
-
139
- async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<string> {
140
- const config = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
141
- const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, config, body);
142
- const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
143
- let id: string | undefined;
144
- if (index) {
145
- if (index instanceof Map) {
146
- id = getFirstId(index, +sort!); // Grab first id
147
- } else {
148
- id = getFirstId(index); // Grab first id
149
- }
150
- }
151
- if (id) {
152
- return id;
153
- }
154
- throw new NotFoundError(cls, key);
155
- }
156
-
157
- async postConstruct(): Promise<void> {
158
- await ModelStorageUtil.registerModelChangeListener(this);
159
- ModelExpiryUtil.registerCull(this);
160
-
161
- for (const el of ModelRegistry.getClasses()) {
162
- for (const idx of ModelRegistry.get(el).indices ?? []) {
163
- switch (idx.type) {
164
- case 'unique': {
165
- console.error('Unique indices are not supported for', { cls: el.Ⲑid, idx: idx.name });
166
- break;
167
- }
168
- }
169
- }
170
- }
171
- }
172
-
173
- // CRUD Support
174
- async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
175
- const store = this.#getStore(cls);
176
- if (store.has(id)) {
177
- const res = await ModelCrudUtil.load(cls, store.get(id)!);
178
- if (res) {
179
- if (ModelRegistry.get(cls).expiresAt) {
180
- if (!ModelExpiryUtil.getExpiryState(cls, res).expired) {
181
- return res;
182
- }
183
- } else {
184
- return res;
185
- }
186
- }
187
- }
188
- throw new NotFoundError(cls, id);
189
- }
190
-
191
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
192
- if (!item.id) {
193
- item.id = this.idSource.create();
194
- }
195
- this.#find(cls, item.id, 'data');
196
- return await this.upsert(cls, item);
197
- }
198
-
199
- async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
200
- await this.get(cls, item.id);
201
- return await this.upsert(cls, item);
202
- }
203
-
204
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
205
- const store = this.#getStore(cls);
206
- if (item.id && store.has(item.id)) {
207
- await ModelCrudUtil.load(cls, store.get(item.id)!, 'exists');
208
- }
209
- const prepped = await ModelCrudUtil.preStore(cls, item, this);
210
- return await this.#persist(cls, prepped, 'write');
211
- }
212
-
213
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
214
- const id = item.id;
215
- const clean = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
216
- return await this.#persist(cls, clean, 'write');
217
- }
218
-
219
- async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
220
- const store = this.#getStore(cls);
221
- if (!store.has(id)) {
222
- throw new NotFoundError(cls, id);
223
- }
224
- await ModelCrudUtil.load(cls, store.get(id)!);
225
- const where: ModelType = { id };
226
- await this.#persist(cls, where, 'remove');
227
- }
228
-
229
- async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
230
- for (const id of this.#getStore(cls).keys()) {
231
- try {
232
- yield await this.get(cls, id);
233
- } catch (err) {
234
- if (!(err instanceof NotFoundError)) {
235
- throw err;
236
- }
237
- }
238
- }
239
- }
240
-
241
- // Stream Support
242
- async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
243
- const streams = this.#getStore(STREAMS);
244
- const metaContent = this.#getStore(STREAM_META);
245
- metaContent.set(location, Buffer.from(JSON.stringify(meta)));
246
- streams.set(location, await toBuffer(input));
247
- }
248
-
249
- async getStream(location: string, range?: StreamRange): Promise<Readable> {
250
- const streams = this.#find(STREAMS, location, 'notfound');
251
- let buffer = streams.get(location)!;
252
- if (range) {
253
- range = enforceRange(range, buffer.length);
254
- buffer = buffer.subarray(range.start, range.end! + 1);
255
- }
256
- return Readable.from(buffer);
257
- }
258
-
259
- async describeStream(location: string): Promise<StreamMeta> {
260
- const metaContent = this.#find(STREAM_META, location, 'notfound');
261
- const meta: StreamMeta = JSON.parse(metaContent.get(location)!.toString('utf8'));
262
- return meta;
263
- }
264
-
265
- async deleteStream(location: string): Promise<void> {
266
- const streams = this.#getStore(STREAMS);
267
- const metaContent = this.#getStore(STREAM_META);
268
- if (streams.has(location)) {
269
- streams.delete(location);
270
- metaContent.delete(location);
271
- } else {
272
- throw new NotFoundError('Stream', location);
273
- }
274
- }
275
-
276
- // Expiry
277
- async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
278
- return ModelExpiryUtil.naiveDeleteExpired(this, cls);
279
- }
280
-
281
- // Storage Support
282
- async createStorage(): Promise<void> {
283
- }
284
-
285
- async deleteStorage(): Promise<void> {
286
- this.#store.clear();
287
- this.#indices.sorted.clear();
288
- this.#indices.unsorted.clear();
289
- }
290
-
291
-
292
- async createModel<T extends ModelType>(cls: Class<T>): Promise<void> {
293
- for (const idx of ModelRegistry.get(cls).indices ?? []) {
294
- if (idx.type === 'sorted' || idx.type === 'unsorted') {
295
- this.#indices[idx.type].set(indexName(cls, idx), new Map());
296
- }
297
- }
298
- }
299
-
300
- async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
301
- if (cls === StreamModel) {
302
- this.#getStore(STREAMS).clear();
303
- this.#getStore(STREAM_META).clear();
304
- } else {
305
- this.#getStore(cls).clear();
306
- }
307
- }
308
-
309
- // Indexed
310
- async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
311
- return this.get(cls, await this.#getIdByIndex(cls, idx, body));
312
- }
313
-
314
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
315
- await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
316
- }
317
-
318
- upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> {
319
- return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
320
- }
321
-
322
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
323
- const config = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
324
- const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body, { emptySortValue: null });
325
- const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
326
-
327
- if (index) {
328
- if (index instanceof Set) {
329
- for (const id of index) {
330
- yield this.get(cls, id);
331
- }
332
- } else {
333
- for (const id of [...index.entries()].sort((a, b) => +a[1] - +b[1]).map(([a, b]) => a)) {
334
- yield this.get(cls, id);
335
- }
336
- }
337
- }
338
- }
339
- }
@@ -1,72 +0,0 @@
1
- import { Readable } from 'node:stream';
2
-
3
- export interface StreamMeta {
4
- /**
5
- * File size
6
- */
7
- size: number;
8
- /**
9
- * Mime type of the content
10
- */
11
- contentType: string;
12
- /**
13
- * Hash of the file contents. Different files with the same name, will have the same hash
14
- */
15
- hash?: string;
16
- /**
17
- * The original base filename of the file
18
- */
19
- filename?: string;
20
- /**
21
- * Filenames title, optional for elements like images, audio, videos
22
- */
23
- title?: string;
24
- /**
25
- * Content encoding
26
- */
27
- contentEncoding?: string;
28
- /**
29
- * Content language
30
- */
31
- contentLanguage?: string;
32
- /**
33
- * Cache control
34
- */
35
- cacheControl?: string;
36
- }
37
-
38
- export type StreamRange = { start: number, end?: number };
39
-
40
- /**
41
- * Support for Streams CRD. Stream update is not supported.
42
- *
43
- * @concrete ../internal/service/common#ModelStreamSupportTarget
44
- */
45
- export interface ModelStreamSupport {
46
-
47
- /**
48
- * Upsert stream to storage
49
- * @param location The location of the stream
50
- * @param input The actual stream to write
51
- * @param meta The stream metadata
52
- */
53
- upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void>;
54
-
55
- /**
56
- * Get stream from asset store
57
- * @param location The location of the stream
58
- */
59
- getStream(location: string, range?: StreamRange): Promise<Readable>;
60
-
61
- /**
62
- * Get metadata for stream
63
- * @param location The location of the stream
64
- */
65
- describeStream(location: string): Promise<StreamMeta>;
66
-
67
- /**
68
- * Delete stream by location
69
- * @param location The location of the stream
70
- */
71
- deleteStream(location: string): Promise<void>;
72
- }