@travetto/model 2.0.2 → 2.1.1

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
@@ -196,7 +196,7 @@ export interface ModelBulkSupport extends ModelCrudSupport {
196
196
  ```
197
197
 
198
198
  ## Declaration
199
- Models are declared via the [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L13) decorator, which allows the system to know that this is a class that is compatible with the module. The only requirement for a model is the [ModelType](https://github.com/travetto/travetto/tree/main/module/model/src/types/model.ts#L4)
199
+ Models are declared via the [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L12) decorator, which allows the system to know that this is a class that is compatible with the module. The only requirement for a model is the [ModelType](https://github.com/travetto/travetto/tree/main/module/model/src/types/model.ts#L4)
200
200
 
201
201
  **Code: ModelType**
202
202
  ```typescript
@@ -236,7 +236,7 @@ All fields are optional, but the `id` and `type` are important as those field ty
236
236
  |[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| |
237
237
  |[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|
238
238
  |[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L50)|X|X|X|X|X|X|
239
- |[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L48)|X|X| |X|X|X|
239
+ |[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L47)|X|X| |X|X|X|
240
240
 
241
241
  ## Custom Model Service
242
242
  In addition to the provided contracts, the module also provides common utilities and shared test suites. The common utilities are useful for
@@ -375,7 +375,7 @@ export class MemoryPolymorphicSuite extends ModelPolymorphismSuite {
375
375
 
376
376
  ## CLI - model:export
377
377
 
378
- The module provides the ability to generate an export of the model structure from all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L13)s within the application. This is useful for being able to generate the appropriate files to manually create the data schemas in production.
378
+ The module provides the ability to generate an export of the model structure from all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L12)s within the application. This is useful for being able to generate the appropriate files to manually create the data schemas in production.
379
379
 
380
380
  **Terminal: Running model export**
381
381
  ```bash
@@ -390,7 +390,7 @@ Options:
390
390
 
391
391
  ## CLI - model:install
392
392
 
393
- The module provides the ability to install all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L13)s within the application given the current configuration being targetted. This is useful for being able to prepare the datastore manually.
393
+ The module provides the ability to install all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L12)s within the application given the current configuration being targetted. This is useful for being able to prepare the datastore manually.
394
394
 
395
395
  **Terminal: Running model install**
396
396
  ```bash
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
3
  "displayName": "Data Modeling Support",
4
- "version": "2.0.2",
4
+ "version": "2.1.1",
5
5
  "description": "Datastore abstraction for core operations.",
6
6
  "keywords": [
7
7
  "datastore",
@@ -28,13 +28,13 @@
28
28
  "directory": "module/model"
29
29
  },
30
30
  "dependencies": {
31
- "@travetto/di": "^2.0.2",
32
- "@travetto/config": "^2.0.2",
33
- "@travetto/registry": "^2.0.2",
34
- "@travetto/schema": "^2.0.3"
31
+ "@travetto/di": "^2.1.1",
32
+ "@travetto/config": "^2.1.1",
33
+ "@travetto/registry": "^2.1.1",
34
+ "@travetto/schema": "^2.1.1"
35
35
  },
36
36
  "optionalPeerDependencies": {
37
- "@travetto/cli": "^2.0.2"
37
+ "@travetto/cli": "^2.1.1"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
@@ -1,12 +1,13 @@
1
1
  import * as crypto from 'crypto';
2
2
 
3
3
  import { Class, Util } from '@travetto/base';
4
- import { SchemaValidator } from '@travetto/schema';
4
+ import { SchemaRegistry, SchemaValidator } from '@travetto/schema';
5
5
 
6
6
  import { ModelRegistry } from '../../registry/model';
7
7
  import { ModelType, OptionalId } from '../../types/model';
8
8
  import { NotFoundError } from '../../error/not-found';
9
9
  import { ExistsError } from '../../error/exists';
10
+ import { SubTypeNotSupportedError } from '../../error/invalid-sub-type';
10
11
 
11
12
  /**
12
13
  * Crud utilities
@@ -39,7 +40,7 @@ export class ModelCrudUtil {
39
40
 
40
41
  const result = ModelRegistry.getBaseModel(cls).from(input as object) as T;
41
42
 
42
- if (!(result instanceof cls)) {
43
+ if (!(result instanceof cls || result.constructor.ᚕid === cls.ᚕid)) {
43
44
  if (onTypeMismatch === 'notfound') {
44
45
  throw new NotFoundError(cls, result.id);
45
46
  } else {
@@ -70,7 +71,7 @@ export class ModelCrudUtil {
70
71
 
71
72
  const config = ModelRegistry.get(item.constructor as Class<T>);
72
73
  if (config.subType) { // Subtyping, assign type
73
- item.type = config.subType;
74
+ SchemaRegistry.ensureInstanceTypeField(cls, item);
74
75
  }
75
76
 
76
77
  await SchemaValidator.validate(cls, item);
@@ -96,7 +97,7 @@ export class ModelCrudUtil {
96
97
 
97
98
  const config = ModelRegistry.get(item.constructor as Class<T>);
98
99
  if (config.subType) { // Subtyping, assign type
99
- item.type = config.subType;
100
+ SchemaRegistry.ensureInstanceTypeField(cls, item);
100
101
  }
101
102
 
102
103
  if (view) {
@@ -113,4 +114,13 @@ export class ModelCrudUtil {
113
114
 
114
115
  return item as T;
115
116
  }
117
+
118
+ /**
119
+ * Ensure subtype is not supported
120
+ */
121
+ static ensureNotSubType(cls: Class) {
122
+ if (ModelRegistry.get(cls).subType) {
123
+ throw new SubTypeNotSupportedError(cls);
124
+ }
125
+ }
116
126
  }
@@ -1,4 +1,5 @@
1
- import * as fs from 'fs';
1
+ import * as fs from 'fs/promises';
2
+ import { createReadStream } from 'fs';
2
3
  import * as os from 'os';
3
4
  import * as path from 'path';
4
5
 
@@ -18,7 +19,6 @@ import { ModelCrudUtil } from '../internal/service/crud';
18
19
  import { ModelExpiryUtil } from '../internal/service/expiry';
19
20
  import { NotFoundError } from '../error/not-found';
20
21
  import { ExistsError } from '../error/exists';
21
- import { SubTypeNotSupportedError } from '../error/invalid-sub-type';
22
22
  import { StreamModel, STREAMS } from '../internal/service/stream';
23
23
 
24
24
  type Suffix = '.bin' | '.meta' | '.json' | '.expires';
@@ -48,8 +48,8 @@ export class FileModelConfig {
48
48
  export class FileModelService implements ModelCrudSupport, ModelStreamSupport, ModelExpirySupport, ModelStorageSupport {
49
49
 
50
50
  private static async * scanFolder(folder: string, suffix: string) {
51
- for (const sub of await fs.promises.readdir(folder)) {
52
- for (const file of await fs.promises.readdir(PathUtil.resolveUnix(folder, sub))) {
51
+ for (const sub of await fs.readdir(folder)) {
52
+ for (const file of await fs.readdir(PathUtil.resolveUnix(folder, sub))) {
53
53
  if (file.endsWith(suffix)) {
54
54
  yield [file.replace(suffix, ''), PathUtil.resolveUnix(folder, sub, file)] as [id: string, file: string];
55
55
  }
@@ -80,7 +80,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
80
80
  dir = path.dirname(resolved);
81
81
  }
82
82
  if (!await FsUtil.exists(dir)) {
83
- await fs.promises.mkdir(dir, { recursive: true });
83
+ await fs.mkdir(dir, { recursive: true });
84
84
  }
85
85
  return resolved;
86
86
  }
@@ -115,7 +115,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
115
115
  const file = await this.#resolveName(cls, '.json', id);
116
116
 
117
117
  if (await FsUtil.exists(file)) {
118
- const content = await StreamUtil.streamToBuffer(fs.createReadStream(file));
118
+ const content = await StreamUtil.streamToBuffer(createReadStream(file));
119
119
  return this.checkExpiry(cls, await ModelCrudUtil.load(cls, content));
120
120
  }
121
121
 
@@ -130,7 +130,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
130
130
  const file = await this.#resolveName(cls, '.json', item.id);
131
131
 
132
132
  if (await FsUtil.exists(file)) {
133
- throw new ExistsError(cls, item.id);
133
+ throw new ExistsError(cls, item.id!);
134
134
  }
135
135
 
136
136
  return await this.upsert(cls, item);
@@ -142,32 +142,28 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
142
142
  }
143
143
 
144
144
  async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
145
- if (ModelRegistry.get(cls).subType) {
146
- throw new SubTypeNotSupportedError(cls);
147
- }
145
+ ModelCrudUtil.ensureNotSubType(cls);
148
146
  const prepped = await ModelCrudUtil.preStore(cls, item, this);
149
147
 
150
148
  const file = await this.#resolveName(cls, '.json', item.id);
151
- await fs.promises.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
149
+ await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
152
150
 
153
151
  return prepped;
154
152
  }
155
153
 
156
154
  async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
157
- if (ModelRegistry.get(cls).subType) {
158
- throw new SubTypeNotSupportedError(cls);
159
- }
155
+ ModelCrudUtil.ensureNotSubType(cls);
160
156
  const id = item.id;
161
157
  item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
162
158
  const file = await this.#resolveName(cls, '.json', item.id);
163
- await fs.promises.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
159
+ await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
164
160
 
165
161
  return item as T;
166
162
  }
167
163
 
168
164
  async delete<T extends ModelType>(cls: Class<T>, id: string) {
169
165
  const file = await this.#find(cls, '.json', id);
170
- await fs.promises.unlink(file);
166
+ await fs.unlink(file);
171
167
  }
172
168
 
173
169
  async * list<T extends ModelType>(cls: Class<T>) {
@@ -187,18 +183,18 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
187
183
  const file = await this.#resolveName(STREAMS, BIN, location);
188
184
  await Promise.all([
189
185
  StreamUtil.writeToFile(input, file),
190
- fs.promises.writeFile(file.replace(BIN, META), JSON.stringify(meta), 'utf8')
186
+ fs.writeFile(file.replace(BIN, META), JSON.stringify(meta), 'utf8')
191
187
  ]);
192
188
  }
193
189
 
194
190
  async getStream(location: string) {
195
191
  const file = await this.#find(STREAMS, BIN, location);
196
- return fs.createReadStream(file);
192
+ return createReadStream(file);
197
193
  }
198
194
 
199
195
  async describeStream(location: string) {
200
196
  const file = await this.#find(STREAMS, META, location);
201
- const content = await StreamUtil.streamToBuffer(fs.createReadStream(file));
197
+ const content = await StreamUtil.streamToBuffer(createReadStream(file));
202
198
  const text = JSON.parse(content.toString('utf8'));
203
199
  return text as StreamMeta;
204
200
  }
@@ -207,8 +203,8 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
207
203
  const file = await this.#resolveName(STREAMS, BIN, location);
208
204
  if (await FsUtil.exists(file)) {
209
205
  await Promise.all([
210
- fs.promises.unlink(file),
211
- fs.promises.unlink(file.replace('.bin', META))
206
+ fs.unlink(file),
207
+ fs.unlink(file.replace('.bin', META))
212
208
  ]);
213
209
  } else {
214
210
  throw new NotFoundError('Stream', location);
@@ -229,7 +225,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
229
225
  // Storage mgmt
230
226
  async createStorage() {
231
227
  const dir = PathUtil.resolveUnix(this.config.folder, this.config.namespace);
232
- await fs.promises.mkdir(dir, { recursive: true });
228
+ await fs.mkdir(dir, { recursive: true });
233
229
  }
234
230
 
235
231
  async deleteStorage() {
@@ -1,5 +1,4 @@
1
1
  import { Class } from '@travetto/base';
2
- import { SchemaRegistry } from '@travetto/schema';
3
2
 
4
3
  import { ModelType } from '../types/model';
5
4
  import { ModelRegistry } from './model';
@@ -15,39 +14,11 @@ export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
15
14
  if (typeof conf === 'string') {
16
15
  conf = { store: conf };
17
16
  }
18
-
19
- // Force registry first, and update with extra information after computing
20
- ModelRegistry.register(target, conf);
21
-
22
- const baseModel = ModelRegistry.getBaseModel(target);
23
- if (baseModel !== target) { // Subtyping if base isn't self
24
- if (conf.subType) {
25
- SchemaRegistry.registerSubTypes(target, conf.subType);
26
- } else {
27
- conf.subType = SchemaRegistry.getSubTypeName(target);
28
- }
29
- }
30
17
  ModelRegistry.register(target, conf);
31
18
  return target;
32
19
  };
33
20
  }
34
21
 
35
-
36
- /**
37
- * Base Model decorator, extends `@Schema`
38
- *
39
- * @augments `@trv:schema/Schema`
40
- */
41
- export function BaseModel(conf: Partial<ModelOptions<ModelType>> | string = {}) {
42
- return function <T extends ModelType & { type: string }, U extends Class<T>>(target: U): U {
43
- ModelRegistry.register(target, {
44
- baseType: true,
45
- ...(typeof conf === 'string' ? { store: conf } : conf)
46
- });
47
- return target;
48
- };
49
- }
50
-
51
22
  /**
52
23
  * Defines an index on a model
53
24
  */
@@ -51,12 +51,20 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
51
51
  }
52
52
 
53
53
  createPending(cls: Class): Partial<ModelOptions<ModelType>> {
54
- return { class: cls, indices: [], autoCreate: true };
54
+ return { class: cls, indices: [], autoCreate: true, baseType: cls.ᚕabstract };
55
55
  }
56
56
 
57
57
  onInstallFinalize(cls: Class) {
58
58
  const config = this.pending.get(cls.ᚕid)! as ModelOptions<ModelType>;
59
- delete SchemaRegistry.get(cls).views[AllViewⲐ].schema.id.required; // Allow ids to be optional
59
+
60
+ const schema = SchemaRegistry.get(cls);
61
+ const view = schema.views[AllViewⲐ].schema;
62
+ delete view.id.required; // Allow ids to be optional
63
+
64
+ if ('type' in view && this.getBaseModel(cls) !== cls) {
65
+ config.subType = schema.subType; // Copy from schema
66
+ delete view.type.required; // Allow type to be optional
67
+ }
60
68
  return config;
61
69
  }
62
70
 
@@ -128,7 +136,6 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
128
136
  return this.getStore(this.getBaseModel(cls));
129
137
  }
130
138
 
131
-
132
139
  const name = config.store ?? cls.name.toLowerCase();
133
140
 
134
141
  const candidates = this.getInitialNameMapping().get(name) || [];
@@ -35,9 +35,9 @@ export class ModelOptions<T extends ModelType = ModelType> {
35
35
  */
36
36
  store?: string;
37
37
  /**
38
- * If a sub type, identifier type
38
+ * If a sub type
39
39
  */
40
- subType?: string;
40
+ subType?: boolean;
41
41
  /**
42
42
  * Is a base type?
43
43
  */
@@ -10,9 +10,8 @@ import {
10
10
  } from '..';
11
11
  import { isIndexedSupported } from '../src/internal/service/common';
12
12
  import { ExistsError } from '../src/error/exists';
13
- import { BaseModel } from '../src/registry/decorator';
14
13
 
15
- @BaseModel()
14
+ @Model({ baseType: true })
16
15
  export class Worker {
17
16
  id: string;
18
17
  type: string;
@@ -41,7 +40,7 @@ export class Engineer extends Worker {
41
40
  major: string;
42
41
  }
43
42
 
44
- @BaseModel()
43
+ @Model({ baseType: true })
45
44
  @Index({
46
45
  name: 'worker-name',
47
46
  type: 'sorted',
@@ -1,5 +1,6 @@
1
1
  import * as assert from 'assert';
2
- import * as fs from 'fs';
2
+ import * as fs from 'fs/promises';
3
+ import { createReadStream } from 'fs';
3
4
  import * as crypto from 'crypto';
4
5
 
5
6
  import { PathUtil } from '@travetto/boot';
@@ -25,12 +26,12 @@ export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport
25
26
 
26
27
  async getStream(resource: string) {
27
28
  const file = await ResourceManager.findAbsolute(resource);
28
- const stat = await fs.promises.stat(file);
29
- const hash = await this.getHash(fs.createReadStream(file));
29
+ const stat = await fs.stat(file);
30
+ const hash = await this.getHash(createReadStream(file));
30
31
 
31
32
  return [
32
33
  { size: stat.size, contentType: '', hash, filename: resource },
33
- fs.createReadStream(file)
34
+ createReadStream(file)
34
35
  ] as const;
35
36
  }
36
37