@travetto/model-file 8.0.0-alpha.9 → 8.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.
package/README.md CHANGED
@@ -14,6 +14,6 @@ yarn add @travetto/model-file
14
14
  ```
15
15
 
16
16
  This module provides an file-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). Supported features:
17
- * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
18
- * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
19
17
  * [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
18
+ * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
19
+ * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
package/__index__.ts CHANGED
@@ -1 +1 @@
1
- export * from './src/service.ts';
1
+ export * from './src/service.ts';
package/package.json CHANGED
@@ -1,39 +1,42 @@
1
1
  {
2
2
  "name": "@travetto/model-file",
3
- "version": "8.0.0-alpha.9",
4
- "type": "module",
3
+ "version": "8.0.0",
5
4
  "description": "File system backing for the travetto model module.",
6
5
  "keywords": [
7
6
  "datastore",
8
7
  "file",
9
8
  "schema",
10
- "typescript",
11
- "travetto"
9
+ "travetto",
10
+ "typescript"
12
11
  ],
13
12
  "homepage": "https://travetto.io",
14
13
  "license": "MIT",
15
14
  "author": {
16
- "email": "travetto.framework@gmail.com",
17
- "name": "Travetto Framework"
15
+ "name": "Travetto Framework",
16
+ "email": "travetto.framework@gmail.com"
17
+ },
18
+ "repository": {
19
+ "url": "git+https://github.com/travetto/travetto.git",
20
+ "directory": "module/model-file"
18
21
  },
19
22
  "files": [
20
23
  "__index__.ts",
21
24
  "src"
22
25
  ],
26
+ "type": "module",
23
27
  "main": "__index__.ts",
24
- "repository": {
25
- "url": "git+https://github.com/travetto/travetto.git",
26
- "directory": "module/model-file"
28
+ "publishConfig": {
29
+ "access": "public"
27
30
  },
28
31
  "dependencies": {
29
- "@travetto/config": "^8.0.0-alpha.9",
30
- "@travetto/di": "^8.0.0-alpha.9",
31
- "@travetto/model": "^8.0.0-alpha.9",
32
- "@travetto/schema": "^8.0.0-alpha.9"
32
+ "@travetto/config": "^8.0.0",
33
+ "@travetto/di": "^8.0.0",
34
+ "@travetto/model": "^8.0.0",
35
+ "@travetto/schema": "^8.0.0"
33
36
  },
34
37
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.14",
36
- "@travetto/test": "^8.0.0-alpha.9"
38
+ "@travetto/cli": "^8.0.0",
39
+ "@travetto/test": "^8.0.0"
37
40
  },
38
41
  "peerDependenciesMeta": {
39
42
  "@travetto/cli": {
@@ -45,8 +48,5 @@
45
48
  },
46
49
  "travetto": {
47
50
  "displayName": "File Model Support"
48
- },
49
- "publishConfig": {
50
- "access": "public"
51
51
  }
52
52
  }
package/src/service.ts CHANGED
@@ -1,19 +1,36 @@
1
- import fs from 'node:fs/promises';
2
1
  import { createReadStream, createWriteStream } from 'node:fs';
2
+ import fs from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
 
6
- import {
7
- type Class, type TimeSpan, Runtime, type BinaryMetadata, type ByteRange, type BinaryType,
8
- BinaryUtil, JSONUtil, BinaryMetadataUtil
9
- } from '@travetto/runtime';
10
- import { Injectable, PostConstruct } from '@travetto/di';
11
6
  import { Config } from '@travetto/config';
12
- import { Required } from '@travetto/schema';
7
+ import { Injectable, PostConstruct } from '@travetto/di';
13
8
  import {
14
- type ModelCrudSupport, type ModelExpirySupport, type ModelStorageSupport, type ModelType, ModelRegistryIndex,
15
- NotFoundError, type OptionalId, ExistsError, type ModelBlobSupport, ModelCrudUtil, ModelExpiryUtil
9
+ ExistsError,
10
+ type ModelBlobSupport,
11
+ type ModelCrudSupport,
12
+ ModelCrudUtil,
13
+ type ModelExpirySupport,
14
+ ModelExpiryUtil,
15
+ type ModelListOptions,
16
+ ModelRegistryIndex,
17
+ type ModelStorageSupport,
18
+ type ModelType,
19
+ NotFoundError,
20
+ type OptionalId
16
21
  } from '@travetto/model';
22
+ import {
23
+ type BinaryMetadata,
24
+ BinaryMetadataUtil,
25
+ type BinaryType,
26
+ BinaryUtil,
27
+ type ByteRange,
28
+ type Class,
29
+ JSONUtil,
30
+ Runtime,
31
+ type TimeSpan
32
+ } from '@travetto/runtime';
33
+ import { Required } from '@travetto/schema';
17
34
 
18
35
  type Suffix = '.bin' | '.meta' | '.json' | '.expires';
19
36
 
@@ -32,33 +49,58 @@ export class FileModelConfig {
32
49
 
33
50
  @PostConstruct()
34
51
  async finalizeConfig(): Promise<void> {
35
- this.folder ??= path.resolve(os.tmpdir(), `trv_file_${Runtime.main.name.replace(/[^a-z]/ig, '_')}`);
52
+ this.folder ??= path.resolve(os.tmpdir(), `trv_file_${Runtime.main.name.replace(/[^a-z]/gi, '_')}`);
36
53
  }
37
54
  }
38
55
 
39
- const exists = (file: string): Promise<boolean> => fs.stat(file).then(() => true, () => false);
56
+ const exists = (file: string): Promise<boolean> =>
57
+ fs.stat(file).then(
58
+ () => true,
59
+ () => false
60
+ );
40
61
 
41
62
  /**
42
63
  * Standard file support
43
64
  */
44
65
  @Injectable()
45
66
  export class FileModelService implements ModelCrudSupport, ModelBlobSupport, ModelExpirySupport, ModelStorageSupport {
46
-
47
67
  /** @private */
48
- static async * scanFolder(folder: string, suffix: string): AsyncGenerator<[id: string, field: string]> {
68
+ static async *scanFolder(folder: string, suffix: string, options?: ModelListOptions): AsyncGenerator<[id: string, field: string][]> {
69
+ const found: [id: string, field: string][] = [];
70
+ const batchSize = options?.batchSizeHint ?? 100;
71
+ const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
72
+ let produced = 0;
49
73
  for (const sub of await fs.readdir(folder)) {
74
+ if (options?.abort?.aborted) {
75
+ break;
76
+ }
50
77
  for (const file of await fs.readdir(path.resolve(folder, sub))) {
78
+ if (options?.abort?.aborted) {
79
+ break;
80
+ }
51
81
  if (file.endsWith(suffix)) {
52
- yield [file.replace(suffix, ''), path.resolve(folder, sub, file)];
82
+ found.push([file.replace(suffix, ''), path.resolve(folder, sub, file)]);
83
+ produced += 1;
84
+ if (produced >= maxCount) {
85
+ break;
86
+ }
87
+ if (found.length >= batchSize) {
88
+ yield found.splice(0, found.length);
89
+ }
53
90
  }
54
91
  }
55
92
  }
93
+ if (found.length) {
94
+ yield found;
95
+ }
56
96
  }
57
97
 
58
98
  idSource = ModelCrudUtil.uuidSource();
59
99
  config: FileModelConfig;
60
100
 
61
- constructor(config: FileModelConfig) { this.config = config; }
101
+ constructor(config: FileModelConfig) {
102
+ this.config = config;
103
+ }
62
104
 
63
105
  /**
64
106
  * The root location for all activity
@@ -160,30 +202,28 @@ export class FileModelService implements ModelCrudSupport, ModelBlobSupport, Mod
160
202
  await fs.unlink(file);
161
203
  }
162
204
 
163
- async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
164
- for await (const [id] of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
165
- try {
166
- yield await this.get(cls, id);
167
- } catch (error) {
168
- if (!(error instanceof NotFoundError)) {
169
- throw error;
170
- }
171
- }
205
+ async *list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
206
+ for await (const batch of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json', options)) {
207
+ yield ModelCrudUtil.filterOutNotFound(batch.map(([id]) => this.get(cls, id)));
172
208
  }
173
209
  }
174
210
 
175
211
  // Blob
176
212
  async upsertBlob(location: string, input: BinaryType, metadata?: BinaryMetadata, overwrite = true): Promise<void> {
177
- if (!overwrite && await this.getBlobMetadata(location).then(() => true, () => false)) {
213
+ if (
214
+ !overwrite &&
215
+ (await this.getBlobMetadata(location).then(
216
+ () => true,
217
+ () => false
218
+ ))
219
+ ) {
178
220
  return;
179
221
  }
180
222
  const resolved = await BinaryMetadataUtil.compute(input, metadata);
181
223
  const file = await this.#resolveName(ModelBlobNamespace, BIN, location);
182
224
  await Promise.all([
183
225
  BinaryUtil.pipeline(input, createWriteStream(file)),
184
- BinaryUtil.pipeline(
185
- JSONUtil.toBinaryArray(resolved),
186
- createWriteStream(file.replace(BIN, META)))
226
+ BinaryUtil.pipeline(JSONUtil.toBinaryArray(resolved), createWriteStream(file.replace(BIN, META)))
187
227
  ]);
188
228
  }
189
229
 
@@ -204,10 +244,7 @@ export class FileModelService implements ModelCrudSupport, ModelBlobSupport, Mod
204
244
  async deleteBlob(location: string): Promise<void> {
205
245
  const file = await this.#resolveName(ModelBlobNamespace, BIN, location);
206
246
  if (await exists(file)) {
207
- await Promise.all([
208
- fs.unlink(file),
209
- fs.unlink(file.replace('.bin', META))
210
- ]);
247
+ await Promise.all([fs.unlink(file), fs.unlink(file.replace('.bin', META))]);
211
248
  } else {
212
249
  throw new NotFoundError(ModelBlobNamespace, location);
213
250
  }
@@ -221,14 +258,16 @@ export class FileModelService implements ModelCrudSupport, ModelBlobSupport, Mod
221
258
  // Expiry
222
259
  async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
223
260
  let deleted = 0;
224
- for await (const [_id, file] of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
225
- try {
226
- const item = await ModelCrudUtil.load(cls, await fs.readFile(file));
227
- if (ModelExpiryUtil.getExpiryState(cls, item).expired) {
228
- await fs.rm(file, { force: true });
229
- deleted += 1;
230
- }
231
- } catch { } // Don't let a single failure stop the process
261
+ for await (const batch of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
262
+ for (const [_id, file] of batch) {
263
+ try {
264
+ const item = await ModelCrudUtil.load(cls, await fs.readFile(file));
265
+ if (ModelExpiryUtil.getExpiryState(cls, item).expired) {
266
+ await fs.rm(file, { force: true });
267
+ deleted += 1;
268
+ }
269
+ } catch {} // Don't let a single failure stop the process
270
+ }
232
271
  }
233
272
  return deleted;
234
273
  }
@@ -250,4 +289,4 @@ export class FileModelService implements ModelCrudSupport, ModelBlobSupport, Mod
250
289
  async truncateBlob(): Promise<void> {
251
290
  await fs.rm(await this.#resolveName(ModelBlobNamespace), { recursive: true, force: true });
252
291
  }
253
- }
292
+ }