@travetto/model 2.1.3 → 2.2.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.
@@ -15,6 +15,9 @@ type ComputeConfig = {
15
15
  emptySortValue?: unknown;
16
16
  };
17
17
 
18
+ type IndexFieldPart = { path: string[], value: (string | boolean | Date | number) };
19
+ type IndexSortPart = { path: string[], dir: number, value: number | Date };
20
+
18
21
  /**
19
22
  * Utils for working with indexed model services
20
23
  */
@@ -26,34 +29,41 @@ export class ModelIndexedUtil {
26
29
  * @param idx Index config
27
30
  * @param item Item to read values from
28
31
  */
29
- static computeIndexParts<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item: DeepPartial<T>, opts: ComputeConfig = {}) {
32
+ static computeIndexParts<T extends ModelType>(
33
+ cls: Class<T>, idx: IndexConfig<T> | string, item: DeepPartial<T>, opts: ComputeConfig = {}
34
+ ): { fields: IndexFieldPart[], sorted: IndexSortPart | undefined } {
30
35
  const cfg = typeof idx === 'string' ? ModelRegistry.getIndex(cls, idx) : idx;
31
36
  const sortField = cfg.type === 'sorted' ? cfg.fields[cfg.fields.length - 1] : undefined;
32
37
 
33
- const fields: { path: string[], value: (string | boolean | Date | number) }[] = [];
38
+ const fields: IndexFieldPart[] = [];
34
39
  let sortDir: number = 0;
35
- let sorted: { path: string[], dir: number, value: number | Date } | undefined;
40
+ let sorted: IndexSortPart | undefined;
36
41
 
37
42
  for (const field of cfg.fields) {
38
- let f = field as Record<string, unknown>;
39
- let o = item as Record<string, unknown>;
43
+ let f: Record<string, unknown> = field;
44
+ let o: Record<string, unknown> = item;
40
45
  const parts = [];
41
46
 
42
47
  while (o !== undefined && o !== null) {
43
48
  const k = Object.keys(f)[0];
49
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
44
50
  o = (o[k] as Record<string, unknown>);
45
51
  parts.push(k);
52
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
46
53
  const fk = k as (keyof typeof f);
47
54
  if (typeof f[fk] === 'boolean' || typeof f[fk] === 'number') {
48
55
  if (cfg.type === 'sorted') {
56
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
49
57
  sortDir = f[fk] === true ? 1 : f[fk] as number;
50
58
  }
51
59
  break; // At the bottom
52
60
  } else {
61
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
53
62
  f = f[fk] as Record<string, unknown>;
54
63
  }
55
64
  }
56
65
  if (field === sortField) {
66
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
67
  sorted = { path: parts, dir: sortDir, value: o as unknown as number | Date };
58
68
  }
59
69
  if (o === undefined || o === null) {
@@ -61,9 +71,11 @@ export class ModelIndexedUtil {
61
71
  if (empty === undefined || empty === Error) {
62
72
  throw new IndexNotSupported(cls, cfg, `Missing field value for ${parts.join('.')}`);
63
73
  }
74
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
64
75
  o = empty as Record<string, unknown>;
65
76
  } else {
66
77
  if (field !== sortField || (opts.includeSortInFields ?? true)) {
78
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
67
79
  fields.push({ path: parts, value: o as unknown as string | boolean | Date | number });
68
80
  }
69
81
  }
@@ -78,13 +90,14 @@ export class ModelIndexedUtil {
78
90
  * @param cls Type to get index for
79
91
  * @param idx Index config
80
92
  */
81
- static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, cfg?: ComputeConfig) {
82
- const res = {} as Record<string, unknown>;
93
+ static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, cfg?: ComputeConfig): Record<string, unknown> {
94
+ const res: Record<string, unknown> = {};
83
95
  for (const { path, value } of this.computeIndexParts(cls, idx, item ?? {}, cfg).fields) {
84
96
  let sub = res;
85
97
  const all = path.slice(0);
86
98
  const last = all.pop()!;
87
99
  for (const k of all) {
100
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
88
101
  sub = (sub[k] ??= {}) as typeof res;
89
102
  }
90
103
  sub[last] = value;
@@ -98,7 +111,12 @@ export class ModelIndexedUtil {
98
111
  * @param idx Index config
99
112
  * @param item item to process
100
113
  */
101
- static computeIndexKey<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item: DeepPartial<T> = {}, opts?: ComputeConfig & { sep?: string }) {
114
+ static computeIndexKey<T extends ModelType>(
115
+ cls: Class<T>,
116
+ idx: IndexConfig<T> | string,
117
+ item: DeepPartial<T> = {},
118
+ opts?: ComputeConfig & { sep?: string }
119
+ ): { type: string, key: string, sort?: number | Date } {
102
120
  const { fields, sorted } = this.computeIndexParts(cls, idx, item, { ...(opts ?? {}), includeSortInFields: false });
103
121
  const key = fields.map(({ value }) => value).map(x => `${x}`).join(opts?.sep ?? 'ᚕ');
104
122
  const cfg = typeof idx === 'string' ? ModelRegistry.getIndex(cls, idx) : idx;
@@ -117,8 +135,10 @@ export class ModelIndexedUtil {
117
135
  cls: Class<T>, idx: string, body: OptionalId<T>
118
136
  ): Promise<T> {
119
137
  try {
138
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
120
139
  const { id } = await service.getByIndex(cls, idx, body as DeepPartial<T>);
121
140
  body.id = id;
141
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
122
142
  return await service.update(cls, body as T);
123
143
  } catch (err) {
124
144
  if (err instanceof NotFoundError) {
@@ -13,15 +13,16 @@ export class ModelStorageUtil {
13
13
  /**
14
14
  * Register change listener on startup
15
15
  */
16
- static async registerModelChangeListener(storage: ModelStorageSupport, target?: Class) {
16
+ static async registerModelChangeListener(storage: ModelStorageSupport, target?: Class): Promise<void> {
17
17
  if (!EnvUtil.isDynamic() || !(storage?.config?.autoCreate ?? !AppManifest.prod)) {
18
18
  return;
19
19
  }
20
20
 
21
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
21
22
  target = target ?? storage.constructor as Class<ModelStorageSupport>;
22
23
 
23
24
 
24
- const checkType = (cls: Class, enforceBase = true) => {
25
+ const checkType = (cls: Class, enforceBase = true): boolean => {
25
26
  if (enforceBase && ModelRegistry.getBaseModel(cls) !== cls) {
26
27
  return false;
27
28
  }
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
2
2
  import { createReadStream } from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
+ import { Readable } from 'stream';
5
6
 
6
7
  import { FsUtil, PathUtil, StreamUtil } from '@travetto/boot';
7
8
  import { Class, Util, TimeSpan } from '@travetto/base';
@@ -34,7 +35,7 @@ export class FileModelConfig {
34
35
  autoCreate?: boolean;
35
36
  cullRate?: number | TimeSpan;
36
37
 
37
- async postConstruct() {
38
+ async postConstruct(): Promise<void> {
38
39
  if (!this.folder) {
39
40
  this.folder = PathUtil.resolveUnix(os.tmpdir(), Util.uuid().substring(0, 10));
40
41
  }
@@ -47,17 +48,17 @@ export class FileModelConfig {
47
48
  @Injectable()
48
49
  export class FileModelService implements ModelCrudSupport, ModelStreamSupport, ModelExpirySupport, ModelStorageSupport {
49
50
 
50
- private static async * scanFolder(folder: string, suffix: string) {
51
+ private static async * scanFolder(folder: string, suffix: string): AsyncGenerator<[id: string, field: string]> {
51
52
  for (const sub of await fs.readdir(folder)) {
52
53
  for (const file of await fs.readdir(PathUtil.resolveUnix(folder, sub))) {
53
54
  if (file.endsWith(suffix)) {
54
- yield [file.replace(suffix, ''), PathUtil.resolveUnix(folder, sub, file)] as [id: string, file: string];
55
+ yield [file.replace(suffix, ''), PathUtil.resolveUnix(folder, sub, file)];
55
56
  }
56
57
  }
57
58
  }
58
59
  }
59
60
 
60
- get client() {
61
+ get client(): string {
61
62
  return this.config.folder;
62
63
  }
63
64
 
@@ -68,7 +69,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
68
69
  */
69
70
  constructor(public readonly config: FileModelConfig) { }
70
71
 
71
- async #resolveName<T extends ModelType>(cls: Class<T> | string, suffix?: Suffix, id?: string) {
72
+ async #resolveName<T extends ModelType>(cls: Class<T> | string, suffix?: Suffix, id?: string): Promise<string> {
72
73
  const name = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
73
74
  let resolved = PathUtil.resolveUnix(this.config.folder, this.config.namespace, name);
74
75
  if (id) {
@@ -85,7 +86,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
85
86
  return resolved;
86
87
  }
87
88
 
88
- async #find<T extends ModelType>(cls: Class<T> | string, suffix: Suffix, id?: string) {
89
+ async #find<T extends ModelType>(cls: Class<T> | string, suffix: Suffix, id?: string): Promise<string> {
89
90
  const file = await this.#resolveName(cls, suffix, id);
90
91
  if (id && !(await FsUtil.exists(file))) {
91
92
  throw new NotFoundError(cls, id);
@@ -93,11 +94,11 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
93
94
  return file;
94
95
  }
95
96
 
96
- postConstruct() {
97
+ postConstruct(): void {
97
98
  ModelExpiryUtil.registerCull(this);
98
99
  }
99
100
 
100
- checkExpiry<T extends ModelType>(cls: Class<T>, item: T) {
101
+ checkExpiry<T extends ModelType>(cls: Class<T>, item: T): T {
101
102
  const { expiresAt } = ModelRegistry.get(cls);
102
103
  if (expiresAt && ModelExpiryUtil.getExpiryState(cls, item).expired) {
103
104
  throw new NotFoundError(cls, item.id);
@@ -105,11 +106,11 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
105
106
  return item;
106
107
  }
107
108
 
108
- uuid() {
109
+ uuid(): string {
109
110
  return Util.uuid(32);
110
111
  }
111
112
 
112
- async get<T extends ModelType>(cls: Class<T>, id: string) {
113
+ async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
113
114
  await this.#find(cls, '.json', id);
114
115
 
115
116
  const file = await this.#resolveName(cls, '.json', id);
@@ -122,7 +123,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
122
123
  throw new NotFoundError(cls, id);
123
124
  }
124
125
 
125
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
126
+ async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
126
127
  if (!item.id) {
127
128
  item.id = this.uuid();
128
129
  }
@@ -136,12 +137,12 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
136
137
  return await this.upsert(cls, item);
137
138
  }
138
139
 
139
- async update<T extends ModelType>(cls: Class<T>, item: T) {
140
+ async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
140
141
  await this.get(cls, item.id);
141
142
  return await this.upsert(cls, item);
142
143
  }
143
144
 
144
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
145
+ async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
145
146
  ModelCrudUtil.ensureNotSubType(cls);
146
147
  const prepped = await ModelCrudUtil.preStore(cls, item, this);
147
148
 
@@ -151,35 +152,36 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
151
152
  return prepped;
152
153
  }
153
154
 
154
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
155
+ async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
155
156
  ModelCrudUtil.ensureNotSubType(cls);
156
157
  const id = item.id;
157
158
  item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
158
159
  const file = await this.#resolveName(cls, '.json', item.id);
159
160
  await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
160
161
 
162
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
161
163
  return item as T;
162
164
  }
163
165
 
164
- async delete<T extends ModelType>(cls: Class<T>, id: string) {
166
+ async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
165
167
  const file = await this.#find(cls, '.json', id);
166
168
  await fs.unlink(file);
167
169
  }
168
170
 
169
- async * list<T extends ModelType>(cls: Class<T>) {
171
+ async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
170
172
  for await (const [id] of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
171
173
  try {
172
174
  yield await this.get(cls, id);
173
- } catch (e) {
174
- if (!(e instanceof NotFoundError)) {
175
- throw e;
175
+ } catch (err) {
176
+ if (!(err instanceof NotFoundError)) {
177
+ throw err;
176
178
  }
177
179
  }
178
180
  }
179
181
  }
180
182
 
181
183
  // Stream
182
- async upsertStream(location: string, input: NodeJS.ReadableStream, meta: StreamMeta) {
184
+ async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
183
185
  const file = await this.#resolveName(STREAMS, BIN, location);
184
186
  await Promise.all([
185
187
  StreamUtil.writeToFile(input, file),
@@ -187,19 +189,19 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
187
189
  ]);
188
190
  }
189
191
 
190
- async getStream(location: string) {
192
+ async getStream(location: string): Promise<Readable> {
191
193
  const file = await this.#find(STREAMS, BIN, location);
192
194
  return createReadStream(file);
193
195
  }
194
196
 
195
- async describeStream(location: string) {
197
+ async describeStream(location: string): Promise<StreamMeta> {
196
198
  const file = await this.#find(STREAMS, META, location);
197
199
  const content = await StreamUtil.streamToBuffer(createReadStream(file));
198
- const text = JSON.parse(content.toString('utf8'));
199
- return text as StreamMeta;
200
+ const text: StreamMeta = JSON.parse(content.toString('utf8'));
201
+ return text;
200
202
  }
201
203
 
202
- async deleteStream(location: string) {
204
+ async deleteStream(location: string): Promise<void> {
203
205
  const file = await this.#resolveName(STREAMS, BIN, location);
204
206
  if (await FsUtil.exists(file)) {
205
207
  await Promise.all([
@@ -212,7 +214,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
212
214
  }
213
215
 
214
216
  // Expiry
215
- async deleteExpired<T extends ModelType>(cls: Class<T>) {
217
+ async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
216
218
  const deleted = [];
217
219
  for await (const el of this.list(cls)) {
218
220
  if (ModelExpiryUtil.getExpiryState(cls, el).expired) {
@@ -222,17 +224,17 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
222
224
  return (await Promise.all(deleted)).length;
223
225
  }
224
226
 
225
- // Storage mgmt
226
- async createStorage() {
227
+ // Storage management
228
+ async createStorage(): Promise<void> {
227
229
  const dir = PathUtil.resolveUnix(this.config.folder, this.config.namespace);
228
230
  await fs.mkdir(dir, { recursive: true });
229
231
  }
230
232
 
231
- async deleteStorage() {
233
+ async deleteStorage(): Promise<void> {
232
234
  await FsUtil.unlinkRecursive(PathUtil.resolveUnix(this.config.folder, this.config.namespace));
233
235
  }
234
236
 
235
- async truncateModel(cls: Class<ModelType>) {
237
+ async truncateModel(cls: Class<ModelType>): Promise<void> {
236
238
  await FsUtil.unlinkRecursive(await this.#resolveName(cls === StreamModel ? STREAMS : cls));
237
239
  }
238
240
  }
@@ -1,3 +1,5 @@
1
+ import { Readable } from 'stream';
2
+
1
3
  import { StreamUtil } from '@travetto/boot';
2
4
  import { Util, Class, TimeSpan } from '@travetto/base';
3
5
  import { DeepPartial } from '@travetto/schema';
@@ -22,6 +24,8 @@ import { IndexConfig } from '../registry/types';
22
24
 
23
25
  const STREAM_META = `${STREAMS}_meta`;
24
26
 
27
+ type StoreType = Map<string, Buffer>;
28
+
25
29
  @Config('model.memory')
26
30
  export class MemoryModelConfig {
27
31
  autoCreate?: boolean;
@@ -29,14 +33,14 @@ export class MemoryModelConfig {
29
33
  cullRate?: number | TimeSpan;
30
34
  }
31
35
 
32
- function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string) {
36
+ function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string): string {
33
37
  return [cls.ᚕid, typeof idx === 'string' ? idx : idx.name, suffix].filter(x => !!x).join(':');
34
38
  }
35
39
 
36
- function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number) {
40
+ function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
37
41
  let id: string | undefined;
38
42
  if (data instanceof Set) {
39
- id = data.values().next().value as string;
43
+ id = data.values().next().value;
40
44
  } else {
41
45
  id = [...data.entries()].find(([k, v]) => value === undefined || v === value)?.[0];
42
46
  }
@@ -49,16 +53,16 @@ function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | n
49
53
  @Injectable()
50
54
  export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport {
51
55
 
52
- #store = new Map<string, Map<string, Buffer>>();
56
+ #store = new Map<string, StoreType>();
53
57
  #indices = {
54
58
  sorted: new Map<string, Map<string, Map<string, number>>>(),
55
59
  unsorted: new Map<string, Map<string, Set<string>>>()
56
60
  };
57
- get client() { return this.#store; }
61
+ get client(): Map<string, StoreType> { return this.#store; }
58
62
 
59
63
  constructor(public readonly config: MemoryModelConfig) { }
60
64
 
61
- #getStore<T extends ModelType>(cls: Class<T> | string): Map<string, Buffer> {
65
+ #getStore<T extends ModelType>(cls: Class<T> | string): StoreType {
62
66
  const key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
63
67
  if (!this.#store.has(key)) {
64
68
  this.#store.set(key, new Map());
@@ -66,7 +70,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
66
70
  return this.#store.get(key)!;
67
71
  }
68
72
 
69
- #find<T extends ModelType>(cls: Class<T> | string, id?: string, errorState?: 'data' | 'notfound') {
73
+ #find<T extends ModelType>(cls: Class<T> | string, id?: string, errorState?: 'data' | 'notfound'): StoreType {
70
74
  const store = this.#getStore(cls);
71
75
 
72
76
  if (id && errorState && (errorState === 'notfound' ? !store.has(id) : store.has(id))) {
@@ -76,24 +80,26 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
76
80
  return store;
77
81
  }
78
82
 
79
- async #removeIndices<T extends ModelType>(cls: Class<T>, id: string) {
83
+ async #removeIndices<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
80
84
  try {
81
85
  const item = await this.get(cls, id);
82
86
  for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
83
87
  const idxName = indexName(cls, idx);
88
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
84
89
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, item as DeepPartial<T>);
85
90
  this.#indices[idx.type].get(idxName)?.get(key)?.delete(id);
86
91
  }
87
- } catch (e) {
88
- if (!(e instanceof NotFoundError)) {
89
- throw e;
92
+ } catch (err) {
93
+ if (!(err instanceof NotFoundError)) {
94
+ throw err;
90
95
  }
91
96
  }
92
97
  }
93
98
 
94
- async #writeIndices<T extends ModelType>(cls: Class<T>, item: T) {
99
+ async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
95
100
  for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
96
101
  const idxName = indexName(cls, idx);
102
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
97
103
  const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, item as DeepPartial<T>);
98
104
  let index = this.#indices[idx.type].get(idxName)?.get(key);
99
105
 
@@ -115,7 +121,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
115
121
 
116
122
  async #write<T extends ModelType>(cls: Class<T>, item: T, action: 'remove'): Promise<void>;
117
123
  async #write<T extends ModelType>(cls: Class<T>, item: T, action: 'write'): Promise<T>;
118
- async #write<T extends ModelType>(cls: Class<T>, item: T, action: 'write' | 'remove') {
124
+ async #write<T extends ModelType>(cls: Class<T>, item: T, action: 'write' | 'remove'): Promise<T | void> {
119
125
  const store = this.#getStore(cls);
120
126
  await this.#removeIndices(cls, item.id);
121
127
  if (action === 'write') {
@@ -145,7 +151,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
145
151
  throw new NotFoundError(cls, key);
146
152
  }
147
153
 
148
- async postConstruct() {
154
+ async postConstruct(): Promise<void> {
149
155
  await ModelStorageUtil.registerModelChangeListener(this);
150
156
  ModelExpiryUtil.registerCull(this);
151
157
 
@@ -153,7 +159,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
153
159
  for (const idx of ModelRegistry.get(el).indices ?? []) {
154
160
  switch (idx.type) {
155
161
  case 'unique': {
156
- console.error('Unique inidices are not supported for', { cls: el.ᚕid, idx: idx.name });
162
+ console.error('Unique indices are not supported for', { cls: el.ᚕid, idx: idx.name });
157
163
  break;
158
164
  }
159
165
  }
@@ -162,11 +168,11 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
162
168
  }
163
169
 
164
170
  // CRUD Support
165
- uuid() {
171
+ uuid(): string {
166
172
  return Util.uuid(32);
167
173
  }
168
174
 
169
- async get<T extends ModelType>(cls: Class<T>, id: string) {
175
+ async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
170
176
  const store = this.#getStore(cls);
171
177
  if (store.has(id)) {
172
178
  const res = await ModelCrudUtil.load(cls, store.get(id)!);
@@ -183,7 +189,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
183
189
  throw new NotFoundError(cls, id);
184
190
  }
185
191
 
186
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
192
+ async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
187
193
  if (!item.id) {
188
194
  item.id = this.uuid();
189
195
  }
@@ -191,12 +197,12 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
191
197
  return await this.upsert(cls, item);
192
198
  }
193
199
 
194
- async update<T extends ModelType>(cls: Class<T>, item: T) {
200
+ async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
195
201
  await this.get(cls, item.id);
196
202
  return await this.upsert(cls, item);
197
203
  }
198
204
 
199
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
205
+ async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
200
206
  const store = this.#getStore(cls);
201
207
  if (item.id && store.has(item.id)) {
202
208
  await ModelCrudUtil.load(cls, store.get(item.id)!, 'exists');
@@ -205,64 +211,66 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
205
211
  return await this.#write(cls, prepped, 'write');
206
212
  }
207
213
 
208
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
214
+ async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
209
215
  const id = item.id;
210
216
  const clean = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
211
217
  return await this.#write(cls, clean, 'write');
212
218
  }
213
219
 
214
- async delete<T extends ModelType>(cls: Class<T>, id: string) {
220
+ async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
215
221
  const store = this.#getStore(cls);
216
222
  if (!store.has(id)) {
217
223
  throw new NotFoundError(cls, id);
218
224
  }
219
225
  await ModelCrudUtil.load(cls, store.get(id)!);
220
- await this.#write(cls, { id } as T, 'remove');
226
+ const where: ModelType = { id };
227
+ await this.#write(cls, where, 'remove');
221
228
  }
222
229
 
223
- async * list<T extends ModelType>(cls: Class<T>) {
230
+ async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
224
231
  for (const id of this.#getStore(cls).keys()) {
225
232
  try {
226
233
  yield await this.get(cls, id);
227
- } catch (e) {
228
- if (!(e instanceof NotFoundError)) {
229
- throw e;
234
+ } catch (err) {
235
+ if (!(err instanceof NotFoundError)) {
236
+ throw err;
230
237
  }
231
238
  }
232
239
  }
233
240
  }
234
241
 
235
242
  // Stream Support
236
- async upsertStream(location: string, input: NodeJS.ReadableStream, meta: StreamMeta) {
243
+ async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
237
244
  const streams = this.#getStore(STREAMS);
238
- const metas = this.#getStore(STREAM_META);
239
- metas.set(location, Buffer.from(JSON.stringify(meta)));
245
+ const metaContent = this.#getStore(STREAM_META);
246
+ metaContent.set(location, Buffer.from(JSON.stringify(meta)));
240
247
  streams.set(location, await StreamUtil.streamToBuffer(input));
241
248
  }
242
249
 
243
- async getStream(location: string) {
250
+ async getStream(location: string): Promise<Readable> {
244
251
  const streams = this.#find(STREAMS, location, 'notfound');
245
252
  return StreamUtil.bufferToStream(streams.get(location)!);
246
253
  }
247
254
 
248
- async describeStream(location: string) {
249
- const metas = this.#find(STREAM_META, location, 'notfound');
250
- return JSON.parse(metas.get(location)!.toString('utf8')) as StreamMeta;
255
+ async describeStream(location: string): Promise<StreamMeta> {
256
+ const metaContent = this.#find(STREAM_META, location, 'notfound');
257
+ const meta: StreamMeta = JSON.parse(metaContent.get(location)!.toString('utf8'));
258
+ return meta;
251
259
  }
252
260
 
253
- async deleteStream(location: string) {
261
+ async deleteStream(location: string): Promise<void> {
254
262
  const streams = this.#getStore(STREAMS);
255
- const metas = this.#getStore(STREAM_META);
263
+ const metaContent = this.#getStore(STREAM_META);
256
264
  if (streams.has(location)) {
257
265
  streams.delete(location);
258
- metas.delete(location);
266
+ metaContent.delete(location);
259
267
  } else {
260
268
  throw new NotFoundError('Stream', location);
261
269
  }
262
270
  }
263
271
 
264
272
  // Expiry Support
265
- async deleteExpired<T extends ModelType>(cls: Class<T>) {
273
+ async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
266
274
  const deleting = [];
267
275
  const store = this.#getStore(cls);
268
276
  for (const id of [...store.keys()]) {
@@ -274,17 +282,17 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
274
282
  }
275
283
 
276
284
  // Storage Support
277
- async createStorage() {
285
+ async createStorage(): Promise<void> {
278
286
  }
279
287
 
280
- async deleteStorage() {
288
+ async deleteStorage(): Promise<void> {
281
289
  this.#store.clear();
282
290
  this.#indices.sorted.clear();
283
291
  this.#indices.unsorted.clear();
284
292
  }
285
293
 
286
294
 
287
- async createModel<T extends ModelType>(cls: Class<T>) {
295
+ async createModel<T extends ModelType>(cls: Class<T>): Promise<void> {
288
296
  for (const idx of ModelRegistry.get(cls).indices ?? []) {
289
297
  if (idx.type === 'sorted' || idx.type === 'unsorted') {
290
298
  this.#indices[idx.type].set(indexName(cls, idx), new Map());
@@ -292,7 +300,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
292
300
  }
293
301
  }
294
302
 
295
- async truncateModel<T extends ModelType>(cls: Class<T>) {
303
+ async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
296
304
  if (cls === StreamModel) {
297
305
  this.#getStore(STREAMS).clear();
298
306
  this.#getStore(STREAM_META).clear();
@@ -306,7 +314,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
306
314
  return this.get(cls, await this.#getIdByIndex(cls, idx, body));
307
315
  }
308
316
 
309
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
317
+ async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
310
318
  await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
311
319
  }
312
320
 
@@ -314,7 +322,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
314
322
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
315
323
  }
316
324
 
317
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncGenerator<T> {
325
+ async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
318
326
  const config = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
319
327
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body, { emptySortValue: null });
320
328
  const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
@@ -24,7 +24,7 @@ export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
24
24
  */
25
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
26
  export function Index<T>(...indices: IndexConfig<any>[]) {
27
- return function (target: Class<T>) {
27
+ return function (target: Class<T>): void {
28
28
  ModelRegistry.getOrCreatePending(target).indices!.push(...indices);
29
29
  };
30
30
  }
@@ -34,7 +34,8 @@ export function Index<T>(...indices: IndexConfig<any>[]) {
34
34
  * @augments `@trv:schema/Field`
35
35
  */
36
36
  export function ExpiresAt() {
37
- return <K extends string, T extends Partial<Record<K, Date>>>(tgt: T, prop: K) => {
37
+ return <K extends string, T extends Partial<Record<K, Date>>>(tgt: T, prop: K): void => {
38
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
38
39
  ModelRegistry.register(tgt.constructor as Class<T>, { expiresAt: prop });
39
40
  };
40
41
  }