@travetto/model 7.0.0-rc.1 → 7.0.0-rc.3

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
@@ -236,7 +236,7 @@ Finally, there is support for [Bulk](https://github.com/travetto/travetto/tree/m
236
236
  **Code: Bulk Contract**
237
237
  ```typescript
238
238
  export interface ModelBulkSupport extends ModelCrudSupport {
239
- processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[]): Promise<BulkResponse>;
239
+ processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[]): Promise<BulkResponse>;
240
240
  }
241
241
  ```
242
242
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
- "version": "7.0.0-rc.1",
3
+ "version": "7.0.0-rc.3",
4
4
  "description": "Datastore abstraction for core operations.",
5
5
  "keywords": [
6
6
  "datastore",
@@ -26,14 +26,14 @@
26
26
  "directory": "module/model"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/config": "^7.0.0-rc.1",
30
- "@travetto/di": "^7.0.0-rc.1",
31
- "@travetto/registry": "^7.0.0-rc.1",
32
- "@travetto/schema": "^7.0.0-rc.1"
29
+ "@travetto/config": "^7.0.0-rc.3",
30
+ "@travetto/di": "^7.0.0-rc.3",
31
+ "@travetto/registry": "^7.0.0-rc.3",
32
+ "@travetto/schema": "^7.0.0-rc.3"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^7.0.0-rc.1",
36
- "@travetto/test": "^7.0.0-rc.1"
35
+ "@travetto/cli": "^7.0.0-rc.3",
36
+ "@travetto/test": "^7.0.0-rc.3"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@travetto/cli": {
@@ -5,6 +5,9 @@ import { Class, AppError } from '@travetto/runtime';
5
5
  */
6
6
  export class ExistsError extends AppError {
7
7
  constructor(cls: Class | string, id: string) {
8
- super(`${typeof cls === 'string' ? cls : cls.name} with id ${id} already exists`, { category: 'data' });
8
+ super(`${typeof cls === 'string' ? cls : cls.name} with id ${id} already exists`, {
9
+ category: 'data',
10
+ details: { id, type: typeof cls === 'string' ? cls : cls.name }
11
+ });
9
12
  }
10
13
  }
@@ -11,12 +11,12 @@ import { ModelRegistryIndex } from './registry-index.ts';
11
11
  * @augments `@travetto/schema:Schema`
12
12
  * @kind decorator
13
13
  */
14
- export function Model(conf: Partial<ModelConfig<ModelType>> | string = {}) {
14
+ export function Model(config: Partial<ModelConfig<ModelType>> | string = {}) {
15
15
  return function <T extends ModelType, U extends Class<T>>(cls: U): U {
16
- if (typeof conf === 'string') {
17
- conf = { store: conf };
16
+ if (typeof config === 'string') {
17
+ config = { store: config };
18
18
  }
19
- ModelRegistryIndex.getForRegister(cls).register(conf);
19
+ ModelRegistryIndex.getForRegister(cls).register(config);
20
20
  if (SchemaRegistryIndex.getForRegister(cls).get().fields.id) {
21
21
  SchemaRegistryIndex.getForRegister(cls).registerField('id', { required: { active: false } });
22
22
  }
@@ -29,7 +29,7 @@ export function Model(conf: Partial<ModelConfig<ModelType>> | string = {}) {
29
29
  * @kind decorator
30
30
  */
31
31
  export function Index<T extends ModelType>(...indices: IndexConfig<T>[]) {
32
- if (indices.some(x => x.fields.some(f => f === 'id'))) {
32
+ if (indices.some(config => config.fields.some(field => field === 'id'))) {
33
33
  throw new AppError('Cannot create an index with the id field');
34
34
  }
35
35
  return function (cls: Class<T>): void {
@@ -69,7 +69,7 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
69
69
  * @augments `@travetto/schema:Field`
70
70
  * @kind decorator
71
71
  */
72
- export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PrePersistScope = 'all') {
72
+ export function PersistValue<T>(handler: (current: T | undefined) => T, scope: PrePersistScope = 'all') {
73
73
  return function <K extends string, C extends Partial<Record<K, T>>>(instance: C, property: K): void {
74
74
  ModelRegistryIndex.getForRegister(getClass(instance)).register({
75
75
  prePersist: [{
@@ -12,6 +12,9 @@ function combineClasses(target: ModelConfig, sources: Partial<ModelConfig>[]): M
12
12
  prePersist: [...(target.prePersist || []), ...(source.prePersist || [])],
13
13
  });
14
14
  }
15
+ if (target.store) {
16
+ target.store = target.store.toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_');
17
+ }
15
18
  return target;
16
19
  }
17
20
 
@@ -24,16 +27,16 @@ export class ModelRegistryAdapter implements RegistryAdapter<ModelConfig> {
24
27
  }
25
28
 
26
29
  register(...data: Partial<ModelConfig>[]): ModelConfig {
27
- const cfg = this.#config ??= {
30
+ const config = this.#config ??= {
28
31
  class: this.#cls,
29
32
  indices: [],
30
- autoCreate: true,
31
- store: this.#cls.name.toLowerCase(),
33
+ autoCreate: 'development',
34
+ store: this.#cls.name,
32
35
  postLoad: [],
33
36
  prePersist: []
34
37
  };
35
- combineClasses(cfg, data);
36
- return cfg;
38
+ combineClasses(config, data);
39
+ return config;
37
40
  }
38
41
 
39
42
  finalize(parent?: ModelConfig): void {
@@ -1,4 +1,4 @@
1
- import { ChangeEvent, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
1
+ import { RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
2
2
  import { AppError, castTo, Class } from '@travetto/runtime';
3
3
  import { SchemaRegistryIndex } from '@travetto/schema';
4
4
 
@@ -58,7 +58,9 @@ export class ModelRegistryIndex implements RegistryIndex {
58
58
 
59
59
  store = new RegistryIndexStore(ModelRegistryAdapter);
60
60
 
61
- #addClass(cls: Class): void {
61
+ /** @private */ constructor(source: unknown) { Registry.validateConstructor(source); }
62
+
63
+ onCreate(cls: Class): void {
62
64
  const schema = SchemaRegistryIndex.getConfig(cls);
63
65
 
64
66
  // Don't index on discriminated schemas
@@ -81,22 +83,6 @@ export class ModelRegistryIndex implements RegistryIndex {
81
83
  }
82
84
  }
83
85
 
84
- #removeClass(cls: Class): void {
85
- const { store } = this.store.get(cls).get();
86
- this.#modelNameMapping.get(store)?.delete(cls.Ⲑid);
87
- }
88
-
89
- process(events: ChangeEvent<Class>[]): void {
90
- for (const event of events) {
91
- if ('prev' in event) {
92
- this.#removeClass(event.prev);
93
- }
94
- if ('curr' in event) {
95
- this.#addClass(event.curr);
96
- }
97
- }
98
- }
99
-
100
86
  getConfig(cls: Class): ModelConfig<ModelType> {
101
87
  return this.store.get(cls).get();
102
88
  }
@@ -112,21 +98,21 @@ export class ModelRegistryIndex implements RegistryIndex {
112
98
  * Get Index
113
99
  */
114
100
  getIndex<T extends ModelType, K extends IndexType[]>(cls: Class<T>, name: string, supportedTypes?: K): IndexResult<T, K> {
115
- const cfg = this.getConfig(cls).indices?.find((x): x is IndexConfig<T> => x.name === name);
116
- if (!cfg) {
101
+ const config = this.getConfig(cls).indices?.find((idx): idx is IndexConfig<T> => idx.name === name);
102
+ if (!config) {
117
103
  throw new NotFoundError(`${cls.name} Index`, `${name}`);
118
104
  }
119
- if (supportedTypes && !supportedTypes.includes(cfg.type)) {
120
- throw new IndexNotSupported(cls, cfg, `${cfg.type} indices are not supported.`);
105
+ if (supportedTypes && !supportedTypes.includes(config.type)) {
106
+ throw new IndexNotSupported(cls, config, `${config.type} indices are not supported.`);
121
107
  }
122
- return cfg;
108
+ return config;
123
109
  }
124
110
 
125
111
  /**
126
112
  * Get Indices
127
113
  */
128
114
  getIndices<T extends ModelType, K extends IndexType[]>(cls: Class<T>, supportedTypes?: K): IndexResult<T, K>[] {
129
- return (this.getConfig(cls).indices ?? []).filter((x): x is IndexConfig<T> => !supportedTypes || supportedTypes.includes(x.type));
115
+ return (this.getConfig(cls).indices ?? []).filter((idx): idx is IndexConfig<T> => !supportedTypes || supportedTypes.includes(idx.type));
130
116
  }
131
117
 
132
118
  /**
@@ -41,9 +41,9 @@ export class ModelConfig<T extends ModelType = ModelType> {
41
41
  */
42
42
  expiresAt?: string;
43
43
  /**
44
- * Auto create in development mode
44
+ * Allows auto creation of a model storage backing at runtime
45
45
  */
46
- autoCreate?: boolean;
46
+ autoCreate?: 'production' | 'development' | 'off';
47
47
  /**
48
48
  * Pre-persist handlers
49
49
  */
package/src/types/bulk.ts CHANGED
@@ -7,7 +7,7 @@ import { ModelType, OptionalId } from '../types/model.ts';
7
7
  /**
8
8
  * Bulk operation. Each operation has a single action and payload
9
9
  */
10
- export type BulkOp<T extends ModelType> =
10
+ export type BulkOperation<T extends ModelType> =
11
11
  { delete?: T } &
12
12
  { insert?: OptionalId<T> } &
13
13
  { update?: T } &
@@ -47,9 +47,9 @@ export class BulkProcessError extends AppError<{ errors: BulkErrorItem[] }> {
47
47
  super('Bulk processing errors have occurred', {
48
48
  category: 'data',
49
49
  details: {
50
- errors: errors.map(x => {
51
- const { message, type, details: { errors: subErrors } = {} } = x.error;
52
- return { message, type, errors: subErrors, idx: x.idx };
50
+ errors: errors.map(error => {
51
+ const { message, type, details: { errors: subErrors } = {} } = error.error;
52
+ return { message, type, errors: subErrors, idx: error.idx };
53
53
  })
54
54
  }
55
55
  });
@@ -62,5 +62,5 @@ export class BulkProcessError extends AppError<{ errors: BulkErrorItem[] }> {
62
62
  * @concrete
63
63
  */
64
64
  export interface ModelBulkSupport extends ModelCrudSupport {
65
- processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[]): Promise<BulkResponse>;
65
+ processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[]): Promise<BulkResponse>;
66
66
  }
@@ -1,5 +1,4 @@
1
1
  import { Class } from '@travetto/runtime';
2
- import { SchemaChange } from '@travetto/schema';
3
2
 
4
3
  import { ModelType } from '../types/model.ts';
5
4
 
@@ -15,10 +14,10 @@ import { ModelType } from '../types/model.ts';
15
14
  export interface ModelStorageSupport {
16
15
 
17
16
  /**
18
- * Should auto-creation be allowed
17
+ * Should storage modification be allowed
19
18
  */
20
19
  readonly config?: {
21
- autoCreate?: boolean;
20
+ modifyStorage?: boolean;
22
21
  };
23
22
 
24
23
  /**
@@ -30,29 +29,21 @@ export interface ModelStorageSupport {
30
29
  */
31
30
  deleteStorage(): Promise<void>;
32
31
  /**
33
- * Installs model
32
+ * Creates model
34
33
  */
35
- createModel?<T extends ModelType>(e: Class<T>): Promise<void>;
34
+ upsertModel?<T extends ModelType>(cls: Class<T>): Promise<void>;
36
35
  /**
37
- * Installs model
36
+ * Exports model
38
37
  */
39
- exportModel?<T extends ModelType>(e: Class<T>): Promise<string>;
38
+ exportModel?<T extends ModelType>(cls: Class<T>): Promise<string>;
40
39
  /**
41
- * Installs model
40
+ * Deletes model
42
41
  */
43
- deleteModel?<T extends ModelType>(e: Class<T>): Promise<void>;
42
+ deleteModel?<T extends ModelType>(cls: Class<T>): Promise<void>;
44
43
  /**
45
44
  * Removes all data from a model, but leaving the structure in place
46
45
  */
47
- truncateModel?<T extends ModelType>(e: Class<T>): Promise<void>;
48
- /**
49
- * Deals with model internals changing
50
- */
51
- changeModel?<T extends ModelType>(e: Class<T>): Promise<void>;
52
- /**
53
- * An event listener for whenever a model schema is changed
54
- */
55
- changeSchema?(cls: Class, changes: SchemaChange): Promise<void>;
46
+ truncateModel?<T extends ModelType>(cls: Class<T>): Promise<void>;
56
47
  /**
57
48
  * Truncate blob storage data
58
49
  */
package/src/util/blob.ts CHANGED
@@ -15,22 +15,22 @@ export class ModelBlobUtil {
15
15
  /**
16
16
  * Convert input to a Readable, and get what metadata is available
17
17
  */
18
- static async getInput(src: BinaryInput, metadata: BlobMeta = {}): Promise<[Readable, BlobMeta]> {
19
- let input: Readable;
20
- if (src instanceof Blob) {
21
- metadata = { ...BinaryUtil.getBlobMeta(src), ...metadata };
22
- metadata.size ??= src.size;
23
- input = Readable.fromWeb(src.stream());
24
- } else if (typeof src === 'object' && 'pipeThrough' in src) {
25
- input = Readable.fromWeb(src);
26
- } else if (typeof src === 'object' && 'pipe' in src) {
27
- input = src;
18
+ static async getInput(input: BinaryInput, metadata: BlobMeta = {}): Promise<[Readable, BlobMeta]> {
19
+ let result: Readable;
20
+ if (input instanceof Blob) {
21
+ metadata = { ...BinaryUtil.getBlobMeta(input), ...metadata };
22
+ metadata.size ??= input.size;
23
+ result = Readable.fromWeb(input.stream());
24
+ } else if (typeof input === 'object' && 'pipeThrough' in input) {
25
+ result = Readable.fromWeb(input);
26
+ } else if (typeof input === 'object' && 'pipe' in input) {
27
+ result = input;
28
28
  } else {
29
- metadata.size = src.length;
30
- input = Readable.from(src);
29
+ metadata.size = input.length;
30
+ result = Readable.from(input);
31
31
  }
32
32
 
33
- return [input, metadata ?? {}];
33
+ return [result, metadata ?? {}];
34
34
  }
35
35
 
36
36
  /**
package/src/util/bulk.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Class, hasFunction } from '@travetto/runtime';
2
2
 
3
- import { BulkOp, ModelBulkSupport } from '../types/bulk.ts';
3
+ import { BulkOperation, ModelBulkSupport } from '../types/bulk.ts';
4
4
  import { ModelType } from '../types/model.ts';
5
5
  import { ModelCrudProvider, ModelCrudUtil } from './crud.ts';
6
6
 
@@ -9,7 +9,7 @@ export type BulkPreStore<T extends ModelType> = {
9
9
  upsertedIds: Map<number, string>;
10
10
  updatedIds: Map<number, string>;
11
11
  existingUpsertedIds: Map<number, string>;
12
- operations: BulkOp<T>[];
12
+ operations: BulkOperation<T>[];
13
13
  };
14
14
 
15
15
  export class ModelBulkUtil {
@@ -20,12 +20,12 @@ export class ModelBulkUtil {
20
20
  static isSupported = hasFunction<ModelBulkSupport>('processBulk');
21
21
 
22
22
  /**
23
- * Prepares bulk ops for storage
23
+ * Prepares bulk operations for storage
24
24
  * @param cls
25
25
  * @param operations
26
26
  * @param provider
27
27
  */
28
- static async preStore<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[], provider: ModelCrudProvider): Promise<BulkPreStore<T>> {
28
+ static async preStore<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[], provider: ModelCrudProvider): Promise<BulkPreStore<T>> {
29
29
  const insertedIds = new Map<number, string>();
30
30
  const upsertedIds = new Map<number, string>();
31
31
  const updatedIds = new Map<number, string>();
@@ -33,20 +33,20 @@ export class ModelBulkUtil {
33
33
 
34
34
  // Pre store
35
35
  let i = 0;
36
- for (const op of operations) {
37
- if ('insert' in op && op.insert) {
38
- op.insert = await ModelCrudUtil.preStore(cls, op.insert, provider);
39
- insertedIds.set(i, op.insert.id!);
40
- } else if ('update' in op && op.update) {
41
- op.update = await ModelCrudUtil.preStore(cls, op.update, provider);
42
- updatedIds.set(i, op.update.id);
43
- } else if ('upsert' in op && op.upsert) {
44
- const isNew = !op.upsert.id;
45
- op.upsert = await ModelCrudUtil.preStore(cls, op.upsert, provider);
36
+ for (const operation of operations) {
37
+ if ('insert' in operation && operation.insert) {
38
+ operation.insert = await ModelCrudUtil.preStore(cls, operation.insert, provider);
39
+ insertedIds.set(i, operation.insert.id!);
40
+ } else if ('update' in operation && operation.update) {
41
+ operation.update = await ModelCrudUtil.preStore(cls, operation.update, provider);
42
+ updatedIds.set(i, operation.update.id);
43
+ } else if ('upsert' in operation && operation.upsert) {
44
+ const isNew = !operation.upsert.id;
45
+ operation.upsert = await ModelCrudUtil.preStore(cls, operation.upsert, provider);
46
46
  if (isNew) {
47
- upsertedIds.set(i, op.upsert.id!);
47
+ upsertedIds.set(i, operation.upsert.id!);
48
48
  } else {
49
- existingUpsertedIds.set(i, op.upsert.id!);
49
+ existingUpsertedIds.set(i, operation.upsert.id!);
50
50
  }
51
51
  }
52
52
  i += 1;
package/src/util/crud.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { castTo, Class, Util, AppError, hasFunction } from '@travetto/runtime';
1
+ import { castTo, Class, Util, AppError, hasFunction, JSONUtil } from '@travetto/runtime';
2
2
  import { DataUtil, SchemaRegistryIndex, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
3
3
 
4
4
  import { ModelRegistryIndex } from '../registry/registry-index.ts';
@@ -26,9 +26,9 @@ export class ModelCrudUtil {
26
26
  /**
27
27
  * Build a uuid generator
28
28
  */
29
- static uuidSource(len: number = 32): ModelIdSource {
30
- const create = (): string => Util.uuid(len);
31
- const valid = (id: string): boolean => id.length === len && /^[0-9a-f]+$/i.test(id);
29
+ static uuidSource(length: number = 32): ModelIdSource {
30
+ const create = (): string => Util.uuid(length);
31
+ const valid = (id: string): boolean => id.length === length && /^[0-9a-f]+$/i.test(id);
32
32
  return { create, valid };
33
33
  }
34
34
 
@@ -39,10 +39,8 @@ export class ModelCrudUtil {
39
39
  */
40
40
  static async load<T extends ModelType>(cls: Class<T>, input: Buffer | string | object, onTypeMismatch: 'notfound' | 'exists' = 'notfound'): Promise<T> {
41
41
  let resolvedInput: object;
42
- if (typeof input === 'string') {
43
- resolvedInput = JSON.parse(input);
44
- } else if (input instanceof Buffer) {
45
- resolvedInput = JSON.parse(input.toString('utf8'));
42
+ if (typeof input === 'string' || input instanceof Buffer) {
43
+ resolvedInput = JSONUtil.parseSafe(input);
46
44
  } else {
47
45
  resolvedInput = input;
48
46
  }
@@ -82,9 +80,9 @@ export class ModelCrudUtil {
82
80
  let errors: ValidationError[] = [];
83
81
  try {
84
82
  await SchemaValidator.validate(cls, item);
85
- } catch (err) {
86
- if (err instanceof ValidationResultError) {
87
- errors = err.details.errors;
83
+ } catch (error) {
84
+ if (error instanceof ValidationResultError) {
85
+ errors = error.details.errors;
88
86
  }
89
87
  }
90
88
 
@@ -153,9 +151,9 @@ export class ModelCrudUtil {
153
151
  item = { ...item };
154
152
  delete item.id;
155
153
  }
156
- const res = await this.prePersist(cls, castTo(item), 'partial');
154
+ const result = await this.prePersist(cls, castTo(item), 'partial');
157
155
  await SchemaValidator.validatePartial(cls, item, view);
158
- return res;
156
+ return result;
159
157
  }
160
158
 
161
159
  /**
@@ -29,13 +29,13 @@ export class ModelExpiryUtil {
29
29
 
30
30
  /**
31
31
  * Delete all expired on a fixed interval, if supported and needed
32
- * @param svc
32
+ * @param service
33
33
  */
34
- static registerCull(svc: ModelExpirySupport & { readonly config?: { cullRate?: number | TimeSpan } }): void {
34
+ static registerCull(service: ModelExpirySupport & { readonly config?: { cullRate?: number | TimeSpan } }): void {
35
35
  const cullable = ModelRegistryIndex.getClasses().filter(cls => !!ModelRegistryIndex.getConfig(cls).expiresAt);
36
- if (svc.deleteExpired && cullable.length) {
36
+ if (service.deleteExpired && cullable.length) {
37
37
  const running = new AbortController();
38
- const cullInterval = TimeUtil.asMillis(svc.config?.cullRate ?? '10m');
38
+ const cullInterval = TimeUtil.asMillis(service.config?.cullRate ?? '10m');
39
39
 
40
40
  ShutdownManager.onGracefulShutdown(async () => running.abort());
41
41
 
@@ -43,7 +43,7 @@ export class ModelExpiryUtil {
43
43
  await Util.nonBlockingTimeout(1000);
44
44
  while (!running.signal.aborted) {
45
45
  await Util.nonBlockingTimeout(cullInterval);
46
- await Promise.all(cullable.map(cls => svc.deleteExpired(cls)));
46
+ await Promise.all(cullable.map(cls => service.deleteExpired(cls)));
47
47
  }
48
48
  })();
49
49
  }
@@ -38,43 +38,43 @@ export class ModelIndexedUtil {
38
38
  static computeIndexParts<T extends ModelType>(
39
39
  cls: Class<T>, idx: IndexConfig<T> | string, item: DeepPartial<T>, opts: ComputeConfig = {}
40
40
  ): { fields: IndexFieldPart[], sorted: IndexSortPart | undefined } {
41
- const cfg = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
42
- const sortField = cfg.type === 'sorted' ? cfg.fields.at(-1) : undefined;
41
+ const config = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
42
+ const sortField = config.type === 'sorted' ? config.fields.at(-1) : undefined;
43
43
 
44
44
  const fields: IndexFieldPart[] = [];
45
- let sortDir: number = 0;
45
+ let sortDirection: number = 0;
46
46
  let sorted: IndexSortPart | undefined;
47
47
 
48
- for (const field of cfg.fields) {
49
- let f: Record<string, unknown> = field;
50
- let o: Record<string, unknown> = item;
48
+ for (const field of config.fields) {
49
+ let fieldRef: Record<string, unknown> = field;
50
+ let itemRef: Record<string, unknown> = item;
51
51
  const parts = [];
52
52
 
53
- while (o !== undefined && o !== null) {
54
- const k = TypedObject.keys(f)[0];
55
- o = castTo(o[k]);
56
- parts.push(k);
57
- if (typeof f[k] === 'boolean' || typeof f[k] === 'number') {
58
- if (cfg.type === 'sorted') {
59
- sortDir = f[k] === true ? 1 : f[k] === false ? 0 : f[k];
53
+ while (itemRef !== undefined && itemRef !== null) {
54
+ const key = TypedObject.keys(fieldRef)[0];
55
+ itemRef = castTo(itemRef[key]);
56
+ parts.push(key);
57
+ if (typeof fieldRef[key] === 'boolean' || typeof fieldRef[key] === 'number') {
58
+ if (config.type === 'sorted') {
59
+ sortDirection = fieldRef[key] === true ? 1 : fieldRef[key] === false ? 0 : fieldRef[key];
60
60
  }
61
61
  break; // At the bottom
62
62
  } else {
63
- f = castTo(f[k]);
63
+ fieldRef = castTo(fieldRef[key]);
64
64
  }
65
65
  }
66
66
  if (field === sortField) {
67
- sorted = { path: parts, dir: sortDir, value: castTo(o) };
67
+ sorted = { path: parts, dir: sortDirection, value: castTo(itemRef) };
68
68
  }
69
- if (o === undefined || o === null) {
69
+ if (itemRef === undefined || itemRef === null) {
70
70
  const empty = field === sortField ? opts.emptySortValue : opts.emptyValue;
71
71
  if (empty === undefined || empty === Error) {
72
- throw new IndexNotSupported(cls, cfg, `Missing field value for ${parts.join('.')}`);
72
+ throw new IndexNotSupported(cls, config, `Missing field value for ${parts.join('.')}`);
73
73
  }
74
- o = castTo(empty!);
74
+ itemRef = castTo(empty!);
75
75
  } else {
76
76
  if (field !== sortField || (opts.includeSortInFields ?? true)) {
77
- fields.push({ path: parts, value: castTo(o) });
77
+ fields.push({ path: parts, value: castTo(itemRef) });
78
78
  }
79
79
  }
80
80
  }
@@ -87,18 +87,18 @@ export class ModelIndexedUtil {
87
87
  * @param cls Type to get index for
88
88
  * @param idx Index config
89
89
  */
90
- static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, cfg?: ComputeConfig): Record<string, unknown> {
91
- const res: Record<string, unknown> = {};
92
- for (const { path, value } of this.computeIndexParts(cls, idx, item ?? {}, cfg).fields) {
93
- let sub: Record<string, unknown> = res;
90
+ static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, config?: ComputeConfig): Record<string, unknown> {
91
+ const response: Record<string, unknown> = {};
92
+ for (const { path, value } of this.computeIndexParts(cls, idx, item ?? {}, config).fields) {
93
+ let sub: Record<string, unknown> = response;
94
94
  const all = path.slice(0);
95
95
  const last = all.pop()!;
96
- for (const k of all) {
97
- sub = castTo(sub[k] ??= {});
96
+ for (const part of all) {
97
+ sub = castTo(sub[part] ??= {});
98
98
  }
99
99
  sub[last] = value;
100
100
  }
101
- return res;
101
+ return response;
102
102
  }
103
103
 
104
104
  /**
@@ -111,12 +111,12 @@ export class ModelIndexedUtil {
111
111
  cls: Class<T>,
112
112
  idx: IndexConfig<T> | string,
113
113
  item: DeepPartial<T> = {},
114
- opts?: ComputeConfig & { sep?: string }
114
+ config?: ComputeConfig & { separator?: string }
115
115
  ): { type: string, key: string, sort?: number | Date } {
116
- const { fields, sorted } = this.computeIndexParts(cls, idx, item, { ...(opts ?? {}), includeSortInFields: false });
117
- const key = fields.map(({ value }) => value).map(x => `${x}`).join(opts?.sep ?? DEFAULT_SEP);
118
- const cfg = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
119
- return !sorted ? { type: cfg.type, key } : { type: cfg.type, key, sort: sorted.value };
116
+ const { fields, sorted } = this.computeIndexParts(cls, idx, item, { ...(config ?? {}), includeSortInFields: false });
117
+ const key = fields.map(({ value }) => value).map(value => `${value}`).join(config?.separator ?? DEFAULT_SEP);
118
+ const indexConfig = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
119
+ return !sorted ? { type: indexConfig.type, key } : { type: indexConfig.type, key, sort: sorted.value };
120
120
  }
121
121
 
122
122
  /**
@@ -134,11 +134,11 @@ export class ModelIndexedUtil {
134
134
  const { id } = await service.getByIndex(cls, idx, castTo(body));
135
135
  body.id = id;
136
136
  return await service.update(cls, castTo(body));
137
- } catch (err) {
138
- if (err instanceof NotFoundError) {
137
+ } catch (error) {
138
+ if (error instanceof NotFoundError) {
139
139
  return await service.create(cls, body);
140
140
  } else {
141
- throw err;
141
+ throw error;
142
142
  }
143
143
  }
144
144
  }
@@ -1,6 +1,5 @@
1
1
  import { Class, hasFunction, Runtime } from '@travetto/runtime';
2
- import { SchemaChangeListener, SchemaRegistryIndex } from '@travetto/schema';
3
- import { Registry } from '@travetto/registry';
2
+ import { SchemaRegistryIndex } from '@travetto/schema';
4
3
 
5
4
  import { ModelStorageSupport } from '../types/storage.ts';
6
5
  import { ModelRegistryIndex } from '../registry/registry-index.ts';
@@ -16,10 +15,10 @@ export class ModelStorageUtil {
16
15
  static isSupported = hasFunction<ModelStorageSupport>('createStorage');
17
16
 
18
17
  /**
19
- * Register change listener on startup
18
+ * Storage Initialization
20
19
  */
21
- static async registerModelChangeListener(storage: ModelStorageSupport): Promise<void> {
22
- if (!Runtime.dynamic || !(storage?.config?.autoCreate ?? !Runtime.production)) {
20
+ static async storageInitialization(storage: ModelStorageSupport): Promise<void> {
21
+ if (storage.config?.modifyStorage === false) {
23
22
  return;
24
23
  }
25
24
 
@@ -27,39 +26,25 @@ export class ModelStorageUtil {
27
26
  if (enforceBase && SchemaRegistryIndex.getBaseClass(cls) !== cls) {
28
27
  return false;
29
28
  }
29
+
30
30
  const { autoCreate } = ModelRegistryIndex.getConfig(cls) ?? {};
31
- return autoCreate ?? false;
32
- };
33
31
 
34
- // If listening for model add/removes/updates
35
- if (storage.createModel || storage.deleteModel || storage.changeModel) {
36
- Registry.onClassChange(ev => {
37
- switch (ev.type) {
38
- case 'added': checkType(ev.curr) ? storage.createModel?.(ev.curr) : undefined; break;
39
- case 'changed': checkType(ev.curr, false) ? storage.changeModel?.(ev.curr) : undefined; break;
40
- case 'removing': checkType(ev.prev) ? storage.deleteModel?.(ev.prev) : undefined; break;
41
- }
42
- }, ModelRegistryIndex);
43
- }
32
+ if (autoCreate === 'off') {
33
+ return false;
34
+ }
35
+
36
+ return (autoCreate === 'production' || !Runtime.production);
37
+ };
44
38
 
45
39
  // Initialize on startup (test manages)
46
40
  await storage.createStorage();
47
41
 
48
- if (storage.createModel) {
42
+ if (storage.upsertModel) {
49
43
  for (const cls of ModelRegistryIndex.getClasses()) {
50
44
  if (checkType(cls)) {
51
- await storage.createModel(cls);
45
+ await storage.upsertModel(cls);
52
46
  }
53
47
  }
54
48
  }
55
-
56
- // If listening for model add/removes/updates
57
- if (storage.changeSchema) {
58
- SchemaChangeListener.onSchemaChange(ev => {
59
- if (checkType(ev.cls)) {
60
- storage.changeSchema!(ev.cls, ev.change);
61
- }
62
- });
63
- }
64
49
  }
65
50
  }
@@ -16,7 +16,7 @@ export abstract class BaseModelCommand implements CliCommandShape {
16
16
  /** Application Environment */
17
17
  env?: string;
18
18
 
19
- abstract getOp(): keyof ModelStorageSupport;
19
+ abstract getOperation(): keyof ModelStorageSupport;
20
20
 
21
21
  preMain(): void {
22
22
  Env.DEBUG.set(false);
@@ -25,26 +25,26 @@ export abstract class BaseModelCommand implements CliCommandShape {
25
25
  async help(): Promise<string[]> {
26
26
  await Registry.init();
27
27
 
28
- const candidates = await ModelCandidateUtil.export(this.getOp());
28
+ const candidates = await ModelCandidateUtil.export(this.getOperation());
29
29
  return [
30
30
  cliTpl`${{ title: 'Providers' }}`,
31
31
  '-'.repeat(20),
32
- ...candidates.providers.map(p => cliTpl` * ${{ type: p }}`),
32
+ ...candidates.providers.map(type => cliTpl` * ${{ type }}`),
33
33
  '',
34
34
  cliTpl`${{ title: 'Models' }}`,
35
35
  '-'.repeat(20),
36
- ...candidates.models.map(p => cliTpl` * ${{ param: p }}`)
36
+ ...candidates.models.map(param => cliTpl` * ${{ param }}`)
37
37
  ];
38
38
  }
39
39
 
40
40
  async validate(provider: string, models: string[]): Promise<CliValidationError | undefined> {
41
41
  await Registry.init();
42
42
 
43
- const candidates = await ModelCandidateUtil.export(this.getOp());
43
+ const candidates = await ModelCandidateUtil.export(this.getOperation());
44
44
  if (provider && !candidates.providers.includes(provider)) {
45
45
  return { message: `provider: ${provider} is not a valid provider`, source: 'arg' };
46
46
  }
47
- const badModel = models.find(x => x !== '*' && !candidates.models.includes(x));
47
+ const badModel = models.find(model => model !== '*' && !candidates.models.includes(model));
48
48
  if (badModel) {
49
49
  return { message: `model: ${badModel} is not a valid model`, source: 'arg' };
50
50
  }
@@ -11,10 +11,10 @@ import { ModelRegistryIndex } from '../../src/registry/registry-index.ts';
11
11
  */
12
12
  export class ModelCandidateUtil {
13
13
 
14
- static async export(op: keyof ModelStorageSupport): Promise<{ models: string[], providers: string[] }> {
14
+ static async export(operation: keyof ModelStorageSupport): Promise<{ models: string[], providers: string[] }> {
15
15
  return {
16
16
  models: await this.getModelNames(),
17
- providers: await this.getProviderNames(op)
17
+ providers: await this.getProviderNames(operation)
18
18
  };
19
19
  }
20
20
 
@@ -25,30 +25,30 @@ export class ModelCandidateUtil {
25
25
  const names = new Set(models ?? []);
26
26
  const all = names.has('*');
27
27
  return ModelRegistryIndex.getClasses()
28
- .map(x => SchemaRegistryIndex.getBaseClass(x))
29
- .filter(x => !models || all || names.has(ModelRegistryIndex.getStoreName(x)));
28
+ .map(cls => SchemaRegistryIndex.getBaseClass(cls))
29
+ .filter(cls => !models || all || names.has(ModelRegistryIndex.getStoreName(cls)));
30
30
  }
31
31
 
32
32
  /**
33
33
  * Get model names
34
34
  */
35
35
  static async getModelNames(): Promise<string[]> {
36
- return (await this.#getModels()).map(x => ModelRegistryIndex.getStoreName(x)).toSorted();
36
+ return (await this.#getModels()).map(cls => ModelRegistryIndex.getStoreName(cls)).toSorted();
37
37
  }
38
38
 
39
39
  /**
40
40
  * Get all providers that are viable candidates
41
41
  */
42
- static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableCandidate[]> {
43
- const types = DependencyRegistryIndex.getCandidates(toConcrete<ModelStorageSupport>());
44
- return types.filter(x => !op || x.class.prototype?.[op]);
42
+ static async getProviders(operation?: keyof ModelStorageSupport): Promise<InjectableCandidate[]> {
43
+ const candidates = DependencyRegistryIndex.getCandidates(toConcrete<ModelStorageSupport>());
44
+ return candidates.filter(type => !operation || type.class.prototype?.[operation]);
45
45
  }
46
46
 
47
47
  /**
48
48
  * Get list of names of all viable providers
49
49
  */
50
- static async getProviderNames(op?: keyof ModelStorageSupport): Promise<string[]> {
51
- return (await this.getProviders(op))
50
+ static async getProviderNames(operation?: keyof ModelStorageSupport): Promise<string[]> {
51
+ return (await this.getProviders(operation))
52
52
  .map(x => x.class.name.replace(/ModelService/, ''))
53
53
  .toSorted();
54
54
  }
@@ -57,7 +57,7 @@ export class ModelCandidateUtil {
57
57
  * Get a single provider
58
58
  */
59
59
  static async getProvider(provider: string): Promise<ModelStorageSupport> {
60
- const config = (await this.getProviders()).find(x => x.class.name === `${provider}ModelService`)!;
60
+ const config = (await this.getProviders()).find(candidates => candidates.class.name === `${provider}ModelService`)!;
61
61
  return DependencyRegistryIndex.getInstance<ModelStorageSupport>(config.candidateType, config.qualifier);
62
62
  }
63
63
 
@@ -3,12 +3,12 @@ import type { ModelStorageSupport, ModelType } from '@travetto/model';
3
3
 
4
4
  export class ModelInstallUtil {
5
5
  static async run(provider: ModelStorageSupport, models: Class<ModelType>[]): Promise<void> {
6
- if (!provider.createModel) {
6
+ if (!provider.upsertModel) {
7
7
  throw new Error(`${provider} does not support model installation`);
8
8
  }
9
- for (const m of models) {
10
- console.log('Installing', { name: m.Ⲑid });
11
- await provider.createModel(m);
9
+ for (const cls of models) {
10
+ console.log('Installing', { name: cls.Ⲑid });
11
+ await provider.upsertModel(cls);
12
12
  }
13
13
  }
14
14
  }
@@ -10,7 +10,7 @@ import { ModelCandidateUtil } from './bin/candidate.ts';
10
10
  @CliCommand({ with: { env: true, module: true } })
11
11
  export class ModelExportCommand extends BaseModelCommand {
12
12
 
13
- getOp(): 'exportModel' { return 'exportModel'; }
13
+ getOperation(): 'exportModel' { return 'exportModel'; }
14
14
 
15
15
  async main(provider: string, models: string[]): Promise<void> {
16
16
  const resolved = await ModelCandidateUtil.resolve(provider, models);
@@ -10,7 +10,7 @@ import { ModelCandidateUtil } from './bin/candidate.ts';
10
10
  @CliCommand({ with: { env: true, module: true } })
11
11
  export class ModelInstallCommand extends BaseModelCommand {
12
12
 
13
- getOp(): 'createModel' { return 'createModel'; }
13
+ getOperation(): 'upsertModel' { return 'upsertModel'; }
14
14
 
15
15
  async main(provider: string, models: string[]): Promise<void> {
16
16
  const resolved = await ModelCandidateUtil.resolve(provider, models);
@@ -34,18 +34,18 @@ export const ModelTypes = (fn: | Function): DocJSXElement[] => {
34
34
  found.push(link);
35
35
  }
36
36
  }
37
- return found.map(v => <li>{v}</li>);
37
+ return found.map(type => <li>{type}</li>);
38
38
  };
39
39
 
40
- export const ModelCustomConfig = ({ cfg }: { cfg: Function }): DocJSXElement => <>
40
+ export const ModelCustomConfig = ({ config }: { config: Function }): DocJSXElement => <>
41
41
  Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source
42
42
  or config, you can override and register it with the {d.mod('Di')} module.
43
43
 
44
44
  <c.Code title='Wiring up a custom Model Source' src='doc/custom-service.ts' />
45
45
 
46
- where the {cfg} is defined by:
46
+ where the {config} is defined by:
47
47
 
48
- <c.Code title={`Structure of ${cfg.name}`} src={cfg} startRe={/@Config/} />
48
+ <c.Code title={`Structure of ${config.name}`} src={config} startRe={/@Config/} />
49
49
 
50
50
  Additionally, you can see that the class is registered with the {Config} annotation, and so these values can be overridden using the
51
51
  standard {d.mod('Config')}resolution paths.
@@ -39,10 +39,10 @@ export function ModelSuite<T extends { configClass: Class<{ autoCreate?: boolean
39
39
  const service = await DependencyRegistryIndex.getInstance(this.serviceClass, qualifier);
40
40
  if (ModelStorageUtil.isSupported(service)) {
41
41
  await service.createStorage();
42
- if (service.createModel) {
42
+ if (service.upsertModel) {
43
43
  await Promise.all(ModelRegistryIndex.getClasses()
44
44
  .filter(x => x === SchemaRegistryIndex.getBaseClass(x))
45
- .map(m => service.createModel!(m)));
45
+ .map(m => service.upsertModel!(m)));
46
46
  }
47
47
  }
48
48
  }