@travetto/model 6.0.1 → 7.0.0-rc.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
@@ -16,7 +16,7 @@ yarn add @travetto/model
16
16
  This module provides a set of contracts/interfaces to data model persistence, modification and retrieval. This module builds heavily upon the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding."), which is used for data model validation.
17
17
 
18
18
  ## A Simple Model
19
- A model can be simply defined by usage of the [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L13) decorator, which opts it into the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") contracts, as well as making it available to the [ModelRegistry](https://github.com/travetto/travetto/tree/main/module/model/src/registry/model.ts#L14).
19
+ A model can be simply defined by usage of the [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L14) decorator, which opts it into the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") contracts, as well as making it available to the [ModelRegistryIndex](https://github.com/travetto/travetto/tree/main/module/model/src/registry/registry-index.ts#L16).
20
20
 
21
21
  **Code: Basic Structure**
22
22
  ```typescript
@@ -241,7 +241,7 @@ export interface ModelBulkSupport extends ModelCrudSupport {
241
241
  ```
242
242
 
243
243
  ## Declaration
244
- 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#L10)
244
+ Models are declared via the [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L14) 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#L10)
245
245
 
246
246
  **Code: ModelType**
247
247
  ```typescript
@@ -277,7 +277,7 @@ To enforce that these contracts are honored, the module provides shared test sui
277
277
 
278
278
  **Code: Memory Service Test Configuration**
279
279
  ```typescript
280
- import { DependencyRegistry } from '@travetto/di';
280
+ import { DependencyRegistryIndex } from '@travetto/di';
281
281
  import { AppError, castTo, Class, classConstruct } from '@travetto/runtime';
282
282
 
283
283
  import { ModelBulkUtil } from '../../src/util/bulk.ts';
@@ -328,7 +328,7 @@ export abstract class BaseModelSuite<T> {
328
328
  }
329
329
 
330
330
  get service(): Promise<T> {
331
- return DependencyRegistry.getInstance(this.serviceClass);
331
+ return DependencyRegistryIndex.getInstance(this.serviceClass);
332
332
  }
333
333
 
334
334
  async toArray<U>(src: AsyncIterable<U> | AsyncGenerator<U>): Promise<U[]> {
@@ -342,7 +342,7 @@ export abstract class BaseModelSuite<T> {
342
342
  ```
343
343
 
344
344
  ## CLI - model:export
345
- 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.
345
+ 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#L14)s within the application. This is useful for being able to generate the appropriate files to manually create the data schemas in production.
346
346
 
347
347
  **Terminal: Running model export**
348
348
  ```bash
@@ -365,7 +365,7 @@ Models
365
365
  ```
366
366
 
367
367
  ## CLI - model:install
368
- 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 targeted. This is useful for being able to prepare the datastore manually.
368
+ The module provides the ability to install all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L14)s within the application given the current configuration being targeted. This is useful for being able to prepare the datastore manually.
369
369
 
370
370
  **Terminal: Running model install**
371
371
  ```bash
package/__index__.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './src/registry/decorator.ts';
2
- export * from './src/registry/model.ts';
2
+ export * from './src/registry/registry-index.ts';
3
+ export * from './src/registry/registry-adapter.ts';
3
4
  export * from './src/registry/types.ts';
4
5
  export * from './src/types/model.ts';
5
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
- "version": "6.0.1",
3
+ "version": "7.0.0-rc.0",
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": "^6.0.1",
30
- "@travetto/di": "^6.0.1",
31
- "@travetto/registry": "^6.0.1",
32
- "@travetto/schema": "^6.0.1"
29
+ "@travetto/config": "^7.0.0-rc.0",
30
+ "@travetto/di": "^7.0.0-rc.0",
31
+ "@travetto/registry": "^7.0.0-rc.0",
32
+ "@travetto/schema": "^7.0.0-rc.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^6.0.1",
36
- "@travetto/test": "^6.0.2"
35
+ "@travetto/cli": "^7.0.0-rc.0",
36
+ "@travetto/test": "^7.0.0-rc.0"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@travetto/cli": {
@@ -1,54 +1,61 @@
1
- import { AppError, asConstructable, castTo, Class } from '@travetto/runtime';
2
- import { SchemaRegistry } from '@travetto/schema';
1
+ import { AppError, castTo, Class, getClass } from '@travetto/runtime';
2
+ import { SchemaRegistryIndex } from '@travetto/schema';
3
3
 
4
4
  import { ModelType } from '../types/model.ts';
5
- import { ModelRegistry } from './model.ts';
6
- import { DataHandler, IndexConfig, ModelOptions, PrePersistScope } from './types.ts';
5
+ import { DataHandler, IndexConfig, ModelConfig, PrePersistScope } from './types.ts';
6
+ import { ModelRegistryIndex } from './registry-index.ts';
7
7
 
8
8
  /**
9
9
  * Model decorator, extends `@Schema`
10
10
  *
11
11
  * @augments `@travetto/schema:Schema`
12
+ * @kind decorator
12
13
  */
13
- export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
14
- return function <T extends ModelType, U extends Class<T>>(target: U): U {
14
+ export function Model(conf: Partial<ModelConfig<ModelType>> | string = {}) {
15
+ return function <T extends ModelType, U extends Class<T>>(cls: U): U {
15
16
  if (typeof conf === 'string') {
16
17
  conf = { store: conf };
17
18
  }
18
- ModelRegistry.register(target, conf);
19
- SchemaRegistry.register(target, { baseType: conf.baseType });
20
- return target;
19
+ ModelRegistryIndex.getForRegister(cls).register(conf);
20
+ if (SchemaRegistryIndex.getForRegister(cls).get().fields.id) {
21
+ SchemaRegistryIndex.getForRegister(cls).registerField('id', { required: { active: false } });
22
+ }
23
+ return cls;
21
24
  };
22
25
  }
23
26
 
24
27
  /**
25
28
  * Defines an index on a model
29
+ * @kind decorator
26
30
  */
27
31
  export function Index<T extends ModelType>(...indices: IndexConfig<T>[]) {
28
32
  if (indices.some(x => x.fields.some(f => f === 'id'))) {
29
33
  throw new AppError('Cannot create an index with the id field');
30
34
  }
31
- return function (target: Class<T>): void {
32
- ModelRegistry.getOrCreatePending(target).indices!.push(...indices);
35
+ return function (cls: Class<T>): void {
36
+ ModelRegistryIndex.getForRegister(cls).register({ indices });
33
37
  };
34
38
  }
35
39
 
36
40
  /**
37
41
  * Model field decorator for denoting expiry date/time
38
42
  * @augments `@travetto/schema:Field`
43
+ * @kind decorator
39
44
  */
40
45
  export function ExpiresAt() {
41
- return <K extends string, T extends Partial<Record<K, Date>>>(tgt: T, prop: K): void => {
42
- ModelRegistry.register(asConstructable(tgt).constructor, { expiresAt: prop });
46
+ return <K extends string, T extends Partial<Record<K, Date>>>(instance: T, property: K): void => {
47
+ ModelRegistryIndex.getForRegister(getClass(instance)).register({ expiresAt: property });
43
48
  };
44
49
  }
45
50
 
46
51
  /**
47
52
  * Model class decorator for pre-persist behavior
53
+ * @augments `@travetto/schema:Schema`
54
+ * @kind decorator
48
55
  */
49
56
  export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope = 'all') {
50
- return function (tgt: Class<T>): void {
51
- ModelRegistry.registerDataHandlers(tgt, {
57
+ return function (cls: Class<T>): void {
58
+ ModelRegistryIndex.getForRegister(cls).register({
52
59
  prePersist: [{
53
60
  scope,
54
61
  handler: castTo(handler)
@@ -59,15 +66,17 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
59
66
 
60
67
  /**
61
68
  * Model field decorator for pre-persist value setting
69
+ * @augments `@travetto/schema:Field`
70
+ * @kind decorator
62
71
  */
63
72
  export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PrePersistScope = 'all') {
64
- return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void {
65
- ModelRegistry.registerDataHandlers(asConstructable(tgt).constructor, {
73
+ return function <K extends string, C extends Partial<Record<K, T>>>(instance: C, property: K): void {
74
+ ModelRegistryIndex.getForRegister(getClass(instance)).register({
66
75
  prePersist: [{
67
76
  scope,
68
77
  handler: (inst): void => {
69
78
  const cInst: Record<K, T> = castTo(inst);
70
- cInst[prop] = handler(cInst[prop]);
79
+ cInst[property] = handler(cInst[property]);
71
80
  }
72
81
  }]
73
82
  });
@@ -76,15 +85,17 @@ export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PreP
76
85
 
77
86
  /**
78
87
  * Prevent a field from being persisted
88
+ * @augments `@travetto/schema:Field`
89
+ * @kind decorator
79
90
  */
80
91
  export function Transient<T>() {
81
- return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void {
82
- ModelRegistry.registerDataHandlers(asConstructable(tgt).constructor, {
92
+ return function <K extends string, C extends Partial<Record<K, T>>>(instance: C, property: K): void {
93
+ ModelRegistryIndex.getForRegister(getClass(instance)).register({
83
94
  prePersist: [{
84
95
  scope: 'all',
85
96
  handler: (inst): void => {
86
97
  const cInst: Record<K, T> = castTo(inst);
87
- delete cInst[prop];
98
+ delete cInst[property];
88
99
  }
89
100
  }]
90
101
  });
@@ -93,9 +104,11 @@ export function Transient<T>() {
93
104
 
94
105
  /**
95
106
  * Model class decorator for post-load behavior
107
+ * @augments `@travetto/schema:Schema`
108
+ * @kind decorator
96
109
  */
97
110
  export function PostLoad<T>(handler: DataHandler<T>) {
98
- return function (tgt: Class<T>): void {
99
- ModelRegistry.registerDataHandlers(tgt, { postLoad: [castTo(handler)] });
111
+ return function (cls: Class<T>): void {
112
+ ModelRegistryIndex.getForRegister(cls).register({ postLoad: [castTo(handler)] });
100
113
  };
101
114
  }
@@ -0,0 +1,56 @@
1
+ import type { RegistryAdapter } from '@travetto/registry';
2
+ import { Class } from '@travetto/runtime';
3
+ import { SchemaRegistryIndex } from '@travetto/schema';
4
+
5
+ import { ModelConfig } from './types';
6
+
7
+ function combineClasses(target: ModelConfig, sources: Partial<ModelConfig>[]): ModelConfig {
8
+ for (const source of sources) {
9
+ Object.assign(target, source, {
10
+ indices: [...(target.indices || []), ...(source.indices || [])],
11
+ postLoad: [...(target.postLoad || []), ...(source.postLoad || [])],
12
+ prePersist: [...(target.prePersist || []), ...(source.prePersist || [])],
13
+ });
14
+ }
15
+ return target;
16
+ }
17
+
18
+ export class ModelRegistryAdapter implements RegistryAdapter<ModelConfig> {
19
+ #cls: Class;
20
+ #config: ModelConfig;
21
+
22
+ constructor(cls: Class) {
23
+ this.#cls = cls;
24
+ }
25
+
26
+ register(...data: Partial<ModelConfig>[]): ModelConfig {
27
+ const cfg = this.#config ??= {
28
+ class: this.#cls,
29
+ indices: [],
30
+ autoCreate: true,
31
+ store: this.#cls.name.toLowerCase(),
32
+ postLoad: [],
33
+ prePersist: []
34
+ };
35
+ combineClasses(cfg, data);
36
+ return cfg;
37
+ }
38
+
39
+ finalize(parent?: ModelConfig): void {
40
+ const config = this.#config;
41
+ if (parent) {
42
+ const parentSchema = parent ? SchemaRegistryIndex.getConfig(parent.class) : undefined; // Ensure schema is finalized first
43
+
44
+ if (parentSchema?.discriminatedField && parent.store) {
45
+ config.store = parent.store;
46
+ }
47
+
48
+ config.postLoad = [...parent.postLoad ?? [], ...config.postLoad ?? []];
49
+ config.prePersist = [...parent.prePersist ?? [], ...config.prePersist ?? []];
50
+ }
51
+ }
52
+
53
+ get(): ModelConfig {
54
+ return this.#config;
55
+ }
56
+ }
@@ -0,0 +1,143 @@
1
+ import { ChangeEvent, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
2
+ import { AppError, castTo, Class } from '@travetto/runtime';
3
+ import { SchemaRegistryIndex } from '@travetto/schema';
4
+
5
+ import { IndexConfig, IndexType, ModelConfig } from './types';
6
+ import { ModelType } from '../types/model';
7
+ import { ModelRegistryAdapter } from './registry-adapter';
8
+ import { IndexNotSupported } from '../error/invalid-index';
9
+ import { NotFoundError } from '../error/not-found';
10
+
11
+ type IndexResult<T extends ModelType, K extends IndexType[]> = IndexConfig<T> & { type: K[number] };
12
+
13
+ /**
14
+ * Model registry index for managing model configurations across classes
15
+ */
16
+ export class ModelRegistryIndex implements RegistryIndex {
17
+
18
+ static #instance = Registry.registerIndex(this);
19
+
20
+ static getForRegister(cls: Class): ModelRegistryAdapter {
21
+ return this.#instance.store.getForRegister(cls);
22
+ }
23
+
24
+ static getConfig(cls: Class): ModelConfig {
25
+ return this.#instance.getConfig(cls);
26
+ }
27
+
28
+ static has(cls: Class): boolean {
29
+ return this.#instance.store.has(cls);
30
+ }
31
+
32
+ static getStoreName<T extends ModelType>(cls: Class<T>): string {
33
+ return this.#instance.getStoreName(cls);
34
+ }
35
+
36
+ static getIndices<T extends ModelType, K extends IndexType[]>(cls: Class<T>, supportedTypes?: K): IndexResult<T, K>[] {
37
+ return this.#instance.getIndices(cls, supportedTypes);
38
+ }
39
+
40
+ static getIndex<T extends ModelType, K extends IndexType[]>(cls: Class<T>, name: string, supportedTypes?: K): IndexResult<T, K> {
41
+ return this.#instance.getIndex(cls, name, supportedTypes);
42
+ }
43
+
44
+ static getExpiryFieldName<T extends ModelType>(cls: Class<T>): keyof T {
45
+ return this.#instance.getExpiryFieldName(cls);
46
+ }
47
+
48
+ static getClasses(): Class[] {
49
+ return this.#instance.store.getClasses();
50
+ }
51
+
52
+ /**
53
+ * Default mapping of classes by class name or
54
+ * by requested store name. This is the state at the
55
+ * start of the application.
56
+ */
57
+ #modelNameMapping = new Map<string, Set<string>>();
58
+
59
+ store = new RegistryIndexStore(ModelRegistryAdapter);
60
+
61
+ #addClass(cls: Class): void {
62
+ const schema = SchemaRegistryIndex.getConfig(cls);
63
+
64
+ // Don't index on discriminated schemas
65
+ if (schema.discriminatedType && !schema.discriminatedBase) {
66
+ return;
67
+ }
68
+
69
+ const { store } = this.getConfig(cls);
70
+ let classes = this.#modelNameMapping.get(store);
71
+ if (!classes) {
72
+ this.#modelNameMapping.set(store, classes = new Set());
73
+ }
74
+ classes.add(cls.Ⲑid);
75
+
76
+ // Don't allow two models with same class name, or same store name
77
+ if (classes.size > 1) {
78
+ throw new AppError('Duplicate models with same store name', {
79
+ details: { classes: [...classes].toSorted() }
80
+ });
81
+ }
82
+ }
83
+
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
+ getConfig(cls: Class): ModelConfig<ModelType> {
101
+ return this.store.get(cls).get();
102
+ }
103
+
104
+ /**
105
+ * Get the apparent store for a type, handling polymorphism when appropriate
106
+ */
107
+ getStoreName(cls: Class): string {
108
+ return this.store.get(cls).get().store;
109
+ }
110
+
111
+ /**
112
+ * Get Index
113
+ */
114
+ 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) {
117
+ throw new NotFoundError(`${cls.name} Index`, `${name}`);
118
+ }
119
+ if (supportedTypes && !supportedTypes.includes(cfg.type)) {
120
+ throw new IndexNotSupported(cls, cfg, `${cfg.type} indices are not supported.`);
121
+ }
122
+ return cfg;
123
+ }
124
+
125
+ /**
126
+ * Get Indices
127
+ */
128
+ 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));
130
+ }
131
+
132
+ /**
133
+ * Get expiry field
134
+ * @param cls
135
+ */
136
+ getExpiryFieldName<T extends ModelType>(cls: Class<T>): keyof T {
137
+ const expiry = this.getConfig(cls).expiresAt;
138
+ if (!expiry) {
139
+ throw new AppError(`${cls.name} is not configured with expiry support, please use @ExpiresAt to declare expiration behavior`);
140
+ }
141
+ return castTo(expiry);
142
+ }
143
+ }
@@ -1,24 +1,15 @@
1
- import type { Class, Primitive } from '@travetto/runtime';
1
+ import type { Class, RetainPrimitiveFields } from '@travetto/runtime';
2
2
 
3
3
  import { ModelType } from '../types/model.ts';
4
4
 
5
- type ValidFieldNames<T> = {
6
- [K in keyof T]:
7
- (T[K] extends (Primitive | undefined) ? K :
8
- (T[K] extends (Function | undefined) ? never :
9
- K))
10
- }[keyof T];
11
-
12
- type RetainFields<T> = Pick<T, ValidFieldNames<T>>;
13
-
14
5
  export type SortClauseRaw<T> = {
15
6
  [P in keyof T]?:
16
- T[P] extends object ? SortClauseRaw<RetainFields<T[P]>> : 1 | -1;
7
+ T[P] extends object ? SortClauseRaw<RetainPrimitiveFields<T[P]>> : 1 | -1;
17
8
  };
18
9
 
19
10
  type IndexClauseRaw<T> = {
20
11
  [P in keyof T]?:
21
- T[P] extends object ? IndexClauseRaw<RetainFields<T[P]>> : 1 | -1 | true;
12
+ T[P] extends object ? IndexClauseRaw<RetainPrimitiveFields<T[P]>> : 1 | -1 | true;
22
13
  };
23
14
 
24
15
  export type DataHandler<T = unknown> = (inst: T) => (Promise<T | void> | T | void);
@@ -26,9 +17,9 @@ export type DataHandler<T = unknown> = (inst: T) => (Promise<T | void> | T | voi
26
17
  export type PrePersistScope = 'full' | 'partial' | 'all';
27
18
 
28
19
  /**
29
- * Model options
20
+ * Model config
30
21
  */
31
- export class ModelOptions<T extends ModelType = ModelType> {
22
+ export class ModelConfig<T extends ModelType = ModelType> {
32
23
  /**
33
24
  * Class for model
34
25
  */
@@ -36,15 +27,7 @@ export class ModelOptions<T extends ModelType = ModelType> {
36
27
  /**
37
28
  * Store name
38
29
  */
39
- store?: string;
40
- /**
41
- * If a sub type
42
- */
43
- subType?: boolean;
44
- /**
45
- * Is a base type?
46
- */
47
- baseType?: boolean;
30
+ store: string;
48
31
  /**
49
32
  * Indices
50
33
  */
@@ -54,13 +37,13 @@ export class ModelOptions<T extends ModelType = ModelType> {
54
37
  */
55
38
  extra?: object;
56
39
  /**
57
- * Does the model support expiry
40
+ * Expiry field
58
41
  */
59
- expiresAt: string;
42
+ expiresAt?: string;
60
43
  /**
61
44
  * Auto create in development mode
62
45
  */
63
- autoCreate: boolean;
46
+ autoCreate?: boolean;
64
47
  /**
65
48
  * Pre-persist handlers
66
49
  */
@@ -87,11 +70,11 @@ export type IndexConfig<T extends ModelType> = {
87
70
  /**
88
71
  * Fields and sort order
89
72
  */
90
- fields: IndexClauseRaw<RetainFields<T>>[];
73
+ fields: IndexClauseRaw<RetainPrimitiveFields<T>>[];
91
74
  /**
92
75
  * Type
93
76
  */
94
77
  type: IndexType;
95
78
  };
96
79
 
97
- export type IndexField<T extends ModelType> = IndexClauseRaw<RetainFields<T>>;
80
+ export type IndexField<T extends ModelType> = IndexClauseRaw<RetainPrimitiveFields<T>>;
package/src/util/crud.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { castTo, Class, Util, asConstructable, AppError, hasFunction } from '@travetto/runtime';
2
- import { DataUtil, SchemaRegistry, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
1
+ import { castTo, Class, Util, AppError, hasFunction } from '@travetto/runtime';
2
+ import { DataUtil, SchemaRegistryIndex, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
3
3
 
4
- import { ModelRegistry } from '../registry/model.ts';
4
+ import { ModelRegistryIndex } from '../registry/registry-index.ts';
5
5
  import { ModelIdSource, ModelType, OptionalId } from '../types/model.ts';
6
6
  import { NotFoundError } from '../error/not-found.ts';
7
7
  import { ExistsError } from '../error/exists.ts';
@@ -47,7 +47,7 @@ export class ModelCrudUtil {
47
47
  resolvedInput = input;
48
48
  }
49
49
 
50
- const result = ModelRegistry.getBaseModel(cls).from(resolvedInput);
50
+ const result = SchemaRegistryIndex.getBaseClass(cls).from(resolvedInput);
51
51
 
52
52
  if (!(result instanceof cls || result.constructor.Ⲑid === cls.Ⲑid)) {
53
53
  if (onTypeMismatch === 'notfound') {
@@ -75,10 +75,7 @@ export class ModelCrudUtil {
75
75
  item = cls.from(castTo(item));
76
76
  }
77
77
 
78
- const config = ModelRegistry.get(asConstructable(item).constructor);
79
- if (config.subType) { // Sub-typing, assign type
80
- SchemaRegistry.ensureInstanceTypeField(cls, item);
81
- }
78
+ SchemaRegistryIndex.get(cls).ensureInstanceTypeField(item);
82
79
 
83
80
  item = await this.prePersist(cls, item, scope);
84
81
 
@@ -105,7 +102,8 @@ export class ModelCrudUtil {
105
102
  * Ensure subtype is not supported
106
103
  */
107
104
  static ensureNotSubType(cls: Class): void {
108
- if (ModelRegistry.get(cls).subType) {
105
+ const config = SchemaRegistryIndex.getConfig(cls);
106
+ if (config.discriminatedType && !config.discriminatedBase) {
109
107
  throw new SubTypeNotSupportedError(cls);
110
108
  }
111
109
  }
@@ -114,7 +112,7 @@ export class ModelCrudUtil {
114
112
  * Pre persist behavior
115
113
  */
116
114
  static async prePersist<T>(cls: Class<T>, item: T, scope: PrePersistScope): Promise<T> {
117
- const config = ModelRegistry.get(cls);
115
+ const config = ModelRegistryIndex.getConfig(cls);
118
116
  for (const state of (config.prePersist ?? [])) {
119
117
  if (state.scope === scope || scope === 'all' || state.scope === 'all') {
120
118
  const handler: DataHandler<T> = castTo(state.handler);
@@ -131,7 +129,7 @@ export class ModelCrudUtil {
131
129
  * Post load behavior
132
130
  */
133
131
  static async postLoad<T>(cls: Class<T>, item: T): Promise<T> {
134
- const config = ModelRegistry.get(cls);
132
+ const config = ModelRegistryIndex.getConfig(cls);
135
133
  for (const handler of castTo<DataHandler<T>[]>(config.postLoad ?? [])) {
136
134
  item = await handler(item) ?? item;
137
135
  }
@@ -1,8 +1,8 @@
1
1
  import { ShutdownManager, Class, TimeSpan, TimeUtil, Util, castTo, hasFunction } from '@travetto/runtime';
2
2
 
3
- import { ModelRegistry } from '../registry/model.ts';
4
3
  import { ModelExpirySupport } from '../types/expiry.ts';
5
4
  import { ModelType } from '../types/model.ts';
5
+ import { ModelRegistryIndex } from '../registry/registry-index.ts';
6
6
 
7
7
  /**
8
8
  * Utils for model expiry
@@ -18,7 +18,7 @@ export class ModelExpiryUtil {
18
18
  * Get expiry info for a given item
19
19
  */
20
20
  static getExpiryState<T extends ModelType>(cls: Class<T>, item: T): { expiresAt?: Date, expired?: boolean } {
21
- const expKey = ModelRegistry.getExpiry(cls);
21
+ const expKey = ModelRegistryIndex.getExpiryFieldName(cls);
22
22
  const expiresAt: Date = castTo(item[expKey]);
23
23
 
24
24
  return {
@@ -32,7 +32,7 @@ export class ModelExpiryUtil {
32
32
  * @param svc
33
33
  */
34
34
  static registerCull(svc: ModelExpirySupport & { readonly config?: { cullRate?: number | TimeSpan } }): void {
35
- const cullable = ModelRegistry.getClasses().filter(cls => !!ModelRegistry.get(cls).expiresAt);
35
+ const cullable = ModelRegistryIndex.getClasses().filter(cls => !!ModelRegistryIndex.getConfig(cls).expiresAt);
36
36
  if (svc.deleteExpired && cullable.length) {
37
37
  const running = new AbortController();
38
38
  const cullInterval = TimeUtil.asMillis(svc.config?.cullRate ?? '10m');
@@ -2,11 +2,11 @@ import { castTo, Class, DeepPartial, hasFunction, TypedObject } from '@travetto/
2
2
 
3
3
  import { IndexNotSupported } from '../error/invalid-index.ts';
4
4
  import { NotFoundError } from '../error/not-found.ts';
5
- import { ModelRegistry } from '../registry/model.ts';
6
5
  import type { IndexConfig } from '../registry/types.ts';
7
6
  import type { ModelCrudSupport } from '../types/crud.ts';
8
7
  import type { ModelIndexedSupport } from '../types/indexed.ts';
9
8
  import type { ModelType, OptionalId } from '../types/model.ts';
9
+ import { ModelRegistryIndex } from '../registry/registry-index.ts';
10
10
 
11
11
  type ComputeConfig = {
12
12
  includeSortInFields?: boolean;
@@ -38,7 +38,7 @@ 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' ? ModelRegistry.getIndex(cls, idx) : idx;
41
+ const cfg = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
42
42
  const sortField = cfg.type === 'sorted' ? cfg.fields.at(-1) : undefined;
43
43
 
44
44
  const fields: IndexFieldPart[] = [];
@@ -115,7 +115,7 @@ export class ModelIndexedUtil {
115
115
  ): { type: string, key: string, sort?: number | Date } {
116
116
  const { fields, sorted } = this.computeIndexParts(cls, idx, item, { ...(opts ?? {}), includeSortInFields: false });
117
117
  const key = fields.map(({ value }) => value).map(x => `${x}`).join(opts?.sep ?? DEFAULT_SEP);
118
- const cfg = typeof idx === 'string' ? ModelRegistry.getIndex(cls, idx) : idx;
118
+ const cfg = typeof idx === 'string' ? ModelRegistryIndex.getIndex(cls, idx) : idx;
119
119
  return !sorted ? { type: cfg.type, key } : { type: cfg.type, key, sort: sorted.value };
120
120
  }
121
121
 
@@ -1,9 +1,9 @@
1
1
  import { Class, hasFunction, Runtime } from '@travetto/runtime';
2
- import { SchemaChangeListener } from '@travetto/schema';
2
+ import { SchemaChangeListener, SchemaRegistryIndex } from '@travetto/schema';
3
+ import { Registry } from '@travetto/registry';
3
4
 
4
- import { ModelRegistry } from '../registry/model.ts';
5
5
  import { ModelStorageSupport } from '../types/storage.ts';
6
- import { ModelType } from '../types/model.ts';
6
+ import { ModelRegistryIndex } from '../registry/registry-index.ts';
7
7
 
8
8
  /**
9
9
  * Model storage util
@@ -24,29 +24,29 @@ export class ModelStorageUtil {
24
24
  }
25
25
 
26
26
  const checkType = (cls: Class, enforceBase = true): boolean => {
27
- if (enforceBase && ModelRegistry.getBaseModel(cls) !== cls) {
27
+ if (enforceBase && SchemaRegistryIndex.getBaseClass(cls) !== cls) {
28
28
  return false;
29
29
  }
30
- const { autoCreate } = ModelRegistry.get(cls) ?? {};
31
- return autoCreate;
30
+ const { autoCreate } = ModelRegistryIndex.getConfig(cls) ?? {};
31
+ return autoCreate ?? false;
32
32
  };
33
33
 
34
34
  // If listening for model add/removes/updates
35
35
  if (storage.createModel || storage.deleteModel || storage.changeModel) {
36
- ModelRegistry.on<ModelType>(ev => {
36
+ Registry.onClassChange(ev => {
37
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;
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
41
  }
42
- });
42
+ }, ModelRegistryIndex);
43
43
  }
44
44
 
45
45
  // Initialize on startup (test manages)
46
46
  await storage.createStorage();
47
47
 
48
48
  if (storage.createModel) {
49
- for (const cls of ModelRegistry.getClasses()) {
49
+ for (const cls of ModelRegistryIndex.getClasses()) {
50
50
  if (checkType(cls)) {
51
51
  await storage.createModel(cls);
52
52
  }
@@ -1,14 +1,16 @@
1
1
  import { Env } from '@travetto/runtime';
2
2
  import { CliValidationError, CliCommandShape, cliTpl } from '@travetto/cli';
3
- import { RootRegistry } from '@travetto/registry';
3
+ import { Registry } from '@travetto/registry';
4
+ import { Schema } from '@travetto/schema';
4
5
 
5
- import type { ModelStorageSupport } from '../src/service/storage.ts';
6
+ import type { ModelStorageSupport } from '../src/types/storage.ts';
6
7
 
7
8
  import { ModelCandidateUtil } from './bin/candidate.ts';
8
9
 
9
10
  /**
10
11
  * CLI Entry point for exporting model schemas
11
12
  */
13
+ @Schema()
12
14
  export abstract class BaseModelCommand implements CliCommandShape {
13
15
 
14
16
  /** Application Environment */
@@ -21,7 +23,7 @@ export abstract class BaseModelCommand implements CliCommandShape {
21
23
  }
22
24
 
23
25
  async help(): Promise<string[]> {
24
- await RootRegistry.init();
26
+ await Registry.init();
25
27
 
26
28
  const candidates = await ModelCandidateUtil.export(this.getOp());
27
29
  return [
@@ -36,7 +38,7 @@ export abstract class BaseModelCommand implements CliCommandShape {
36
38
  }
37
39
 
38
40
  async validate(provider: string, models: string[]): Promise<CliValidationError | undefined> {
39
- await RootRegistry.init();
41
+ await Registry.init();
40
42
 
41
43
  const candidates = await ModelCandidateUtil.export(this.getOp());
42
44
  if (provider && !candidates.providers.includes(provider)) {
@@ -1,9 +1,10 @@
1
1
  import { toConcrete, Class } from '@travetto/runtime';
2
- import { InjectableConfig, DependencyRegistry } from '@travetto/di';
2
+ import { InjectableCandidate, DependencyRegistryIndex } from '@travetto/di';
3
+ import { SchemaRegistryIndex } from '@travetto/schema';
3
4
 
4
- import { ModelRegistry } from '../../src/registry/model.ts';
5
5
  import type { ModelStorageSupport } from '../../src/types/storage.ts';
6
6
  import type { ModelType } from '../../src/types/model.ts';
7
+ import { ModelRegistryIndex } from '../../src/registry/registry-index.ts';
7
8
 
8
9
  /**
9
10
  * Utilities for finding candidates for model operations
@@ -23,23 +24,23 @@ export class ModelCandidateUtil {
23
24
  static async #getModels(models?: string[]): Promise<Class<ModelType>[]> {
24
25
  const names = new Set(models ?? []);
25
26
  const all = names.has('*');
26
- return ModelRegistry.getClasses()
27
- .map(x => ModelRegistry.getBaseModel(x))
28
- .filter(x => !models || all || names.has(ModelRegistry.getStore(x)));
27
+ return ModelRegistryIndex.getClasses()
28
+ .map(x => SchemaRegistryIndex.getBaseClass(x))
29
+ .filter(x => !models || all || names.has(ModelRegistryIndex.getStoreName(x)));
29
30
  }
30
31
 
31
32
  /**
32
33
  * Get model names
33
34
  */
34
35
  static async getModelNames(): Promise<string[]> {
35
- return (await this.#getModels()).map(x => ModelRegistry.getStore(x)).toSorted();
36
+ return (await this.#getModels()).map(x => ModelRegistryIndex.getStoreName(x)).toSorted();
36
37
  }
37
38
 
38
39
  /**
39
40
  * Get all providers that are viable candidates
40
41
  */
41
- static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableConfig[]> {
42
- const types = DependencyRegistry.getCandidateTypes(toConcrete<ModelStorageSupport>());
42
+ static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableCandidate[]> {
43
+ const types = DependencyRegistryIndex.getCandidates(toConcrete<ModelStorageSupport>());
43
44
  return types.filter(x => !op || x.class.prototype?.[op]);
44
45
  }
45
46
 
@@ -57,7 +58,7 @@ export class ModelCandidateUtil {
57
58
  */
58
59
  static async getProvider(provider: string): Promise<ModelStorageSupport> {
59
60
  const config = (await this.getProviders()).find(x => x.class.name === `${provider}ModelService`)!;
60
- return DependencyRegistry.getInstance<ModelStorageSupport>(config.class, config.qualifier);
61
+ return DependencyRegistryIndex.getInstance<ModelStorageSupport>(config.candidateType, config.qualifier);
61
62
  }
62
63
 
63
64
  /**
@@ -1,4 +1,4 @@
1
- import { DependencyRegistry } from '@travetto/di';
1
+ import { DependencyRegistryIndex } from '@travetto/di';
2
2
  import { AppError, castTo, Class, classConstruct } from '@travetto/runtime';
3
3
 
4
4
  import { ModelBulkUtil } from '../../src/util/bulk.ts';
@@ -49,7 +49,7 @@ export abstract class BaseModelSuite<T> {
49
49
  }
50
50
 
51
51
  get service(): Promise<T> {
52
- return DependencyRegistry.getInstance(this.serviceClass);
52
+ return DependencyRegistryIndex.getInstance(this.serviceClass);
53
53
  }
54
54
 
55
55
  async toArray<U>(src: AsyncIterable<U> | AsyncGenerator<U>): Promise<U[]> {
@@ -3,7 +3,7 @@ import timers from 'node:timers/promises';
3
3
 
4
4
  import { Suite, Test } from '@travetto/test';
5
5
  import { castTo } from '@travetto/runtime';
6
- import { SubTypeField, Text, TypeMismatchError } from '@travetto/schema';
6
+ import { Schema, DiscriminatorField, Text, TypeMismatchError, Discriminated } from '@travetto/schema';
7
7
  import {
8
8
  ModelIndexedSupport, Index, ModelCrudSupport, Model,
9
9
  NotFoundError, SubTypeNotSupportedError, PersistValue
@@ -14,10 +14,11 @@ import { ExistsError } from '../../src/error/exists.ts';
14
14
 
15
15
  import { BaseModelSuite } from './base.ts';
16
16
 
17
- @Model({ baseType: true })
18
- export class Worker {
17
+ @Schema()
18
+ @Model()
19
+ export abstract class Worker {
19
20
  id: string;
20
- @SubTypeField()
21
+ @DiscriminatorField()
21
22
  _type: string;
22
23
  @Text()
23
24
  name: string;
@@ -42,19 +43,19 @@ export class Engineer extends Worker {
42
43
  major: string;
43
44
  }
44
45
 
45
- @Model({ baseType: true })
46
+ @Model()
46
47
  @Index({
47
48
  name: 'worker-name',
48
49
  type: 'sorted',
49
50
  fields: [{ name: 1 }, { age: 1 }]
50
51
  })
52
+ @Discriminated('type')
51
53
  export class IndexedWorker {
52
54
  id: string;
53
55
  type: string;
54
56
  name: string;
55
57
  age?: number;
56
58
  }
57
-
58
59
  @Model()
59
60
  export class IndexedDoctor extends IndexedWorker {
60
61
  specialty: string;
@@ -1,90 +1,87 @@
1
1
  import { Class } from '@travetto/runtime';
2
- import { DependencyRegistry } from '@travetto/di';
3
- import { RootRegistry } from '@travetto/registry';
4
- import { SuiteRegistry, TestFixtures } from '@travetto/test';
2
+ import { DependencyRegistryIndex } from '@travetto/di';
3
+ import { Registry } from '@travetto/registry';
4
+ import { SuiteRegistryIndex, TestFixtures } from '@travetto/test';
5
+ import { SchemaRegistryIndex } from '@travetto/schema';
5
6
 
6
7
  import { ModelBlobUtil } from '../../src/util/blob.ts';
7
8
  import { ModelStorageUtil } from '../../src/util/storage.ts';
8
- import { ModelRegistry } from '../../src/registry/model.ts';
9
+ import { ModelRegistryIndex } from '../../src/registry/registry-index.ts';
9
10
 
10
11
  const Loaded = Symbol();
11
12
 
13
+ /**
14
+ * @augments `@travetto/schema:Schema`
15
+ * @kind decorator
16
+ */
12
17
  export function ModelSuite<T extends { configClass: Class<{ autoCreate?: boolean, namespace?: string }>, serviceClass: Class }>(qualifier?: symbol) {
13
18
  const fixtures = new TestFixtures(['@travetto/model']);
14
19
  return (target: Class<T>): void => {
15
20
  target.prototype.fixtures = fixtures;
21
+ SuiteRegistryIndex.getForRegister(target).register({
22
+ beforeAll: [
23
+ async function (this: T & { [Loaded]?: boolean }) {
24
+ await Registry.init();
16
25
 
17
- SuiteRegistry.registerPendingListener(
18
- target,
19
- async function (this: T & { [Loaded]?: boolean }) {
20
- await RootRegistry.init();
21
-
22
- if (!this[Loaded]) {
23
- const config = await DependencyRegistry.getInstance(this.configClass);
24
- if ('namespace' in config) {
25
- config.namespace = `test_${Math.trunc(Math.random() * 10000)}`;
26
+ if (!this[Loaded]) {
27
+ const config = await DependencyRegistryIndex.getInstance(this.configClass);
28
+ if ('namespace' in config) {
29
+ config.namespace = `test_${Math.trunc(Math.random() * 10000)}`;
30
+ }
31
+ // We manually create
32
+ config.autoCreate = false;
33
+ this[Loaded] = true;
26
34
  }
27
- // We manually create
28
- config.autoCreate = false;
29
- this[Loaded] = true;
30
35
  }
31
- },
32
- 'beforeAll'
33
- );
34
- SuiteRegistry.registerPendingListener(
35
- target,
36
- async function (this: T) {
37
- const service = await DependencyRegistry.getInstance(this.serviceClass, qualifier);
38
- if (ModelStorageUtil.isSupported(service)) {
39
- await service.createStorage();
40
- if (service.createModel) {
41
- await Promise.all(ModelRegistry.getClasses()
42
- .filter(x => x === ModelRegistry.getBaseModel(x))
43
- .map(m => service.createModel!(m)));
36
+ ],
37
+ beforeEach: [
38
+ async function (this: T) {
39
+ const service = await DependencyRegistryIndex.getInstance(this.serviceClass, qualifier);
40
+ if (ModelStorageUtil.isSupported(service)) {
41
+ await service.createStorage();
42
+ if (service.createModel) {
43
+ await Promise.all(ModelRegistryIndex.getClasses()
44
+ .filter(x => x === SchemaRegistryIndex.getBaseClass(x))
45
+ .map(m => service.createModel!(m)));
46
+ }
44
47
  }
45
48
  }
46
- },
47
- 'beforeEach'
48
- );
49
- SuiteRegistry.registerPendingListener(
50
- target,
51
- async function (this: T) {
52
- const service = await DependencyRegistry.getInstance(this.serviceClass, qualifier);
53
- if (ModelStorageUtil.isSupported(service)) {
54
- const models = ModelRegistry.getClasses().filter(m => m === ModelRegistry.getBaseModel(m));
49
+ ],
50
+ afterEach: [
51
+ async function (this: T) {
52
+ const service = await DependencyRegistryIndex.getInstance(this.serviceClass, qualifier);
53
+ if (ModelStorageUtil.isSupported(service)) {
54
+ const models = ModelRegistryIndex.getClasses().filter(m => m === SchemaRegistryIndex.getBaseClass(m));
55
55
 
56
- if (ModelBlobUtil.isSupported(service) && service.truncateBlob) {
57
- await service.truncateBlob();
58
- }
56
+ if (ModelBlobUtil.isSupported(service) && service.truncateBlob) {
57
+ await service.truncateBlob();
58
+ }
59
59
 
60
- if (service.truncateModel) {
61
- await Promise.all(models.map(x => service.truncateModel!(x)));
62
- } else if (service.deleteModel) {
63
- await Promise.all(models.map(x => service.deleteModel!(x)));
64
- } else {
65
- await service.deleteStorage(); // Purge it all
60
+ if (service.truncateModel) {
61
+ await Promise.all(models.map(x => service.truncateModel!(x)));
62
+ } else if (service.deleteModel) {
63
+ await Promise.all(models.map(x => service.deleteModel!(x)));
64
+ } else {
65
+ await service.deleteStorage(); // Purge it all
66
+ }
66
67
  }
67
68
  }
68
- },
69
- 'afterEach'
70
- );
71
- SuiteRegistry.registerPendingListener(
72
- target,
73
- async function (this: T) {
74
- const service = await DependencyRegistry.getInstance(this.serviceClass, qualifier);
75
- if (ModelStorageUtil.isSupported(service)) {
76
- if (service.deleteModel) {
77
- for (const m of ModelRegistry.getClasses()) {
78
- if (m === ModelRegistry.getBaseModel(m)) {
79
- await service.deleteModel(m);
69
+ ],
70
+ afterAll: [
71
+ async function (this: T) {
72
+ const service = await DependencyRegistryIndex.getInstance(this.serviceClass, qualifier);
73
+ if (ModelStorageUtil.isSupported(service)) {
74
+ if (service.deleteModel) {
75
+ for (const m of ModelRegistryIndex.getClasses()) {
76
+ if (m === SchemaRegistryIndex.getBaseClass(m)) {
77
+ await service.deleteModel(m);
78
+ }
80
79
  }
81
80
  }
81
+ await service.deleteStorage();
82
82
  }
83
- await service.deleteStorage();
84
83
  }
85
- },
86
- 'afterAll'
87
- );
88
-
84
+ ]
85
+ });
89
86
  };
90
87
  }
@@ -1,218 +0,0 @@
1
- import { SchemaRegistry } from '@travetto/schema';
2
- import { MetadataRegistry } from '@travetto/registry';
3
- import { DependencyRegistry } from '@travetto/di';
4
- import { AppError, castTo, Class, describeFunction, asFull } from '@travetto/runtime';
5
-
6
- import { IndexConfig, IndexType, ModelOptions } from './types.ts';
7
- import { NotFoundError } from '../error/not-found.ts';
8
- import { ModelType } from '../types/model.ts';
9
- import { IndexNotSupported } from '../error/invalid-index.ts';
10
-
11
- /**
12
- * Registry for all models, built on the Metadata registry
13
- */
14
- class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
15
- /**
16
- * All stores names
17
- */
18
- stores = new Map<Class, string>();
19
- /**
20
- * All base model classes (inherited from)
21
- */
22
- baseModels = new Map<Class, Class>();
23
- /**
24
- * Indexed base model classes to all subclasses
25
- */
26
- baseModelGrouped = new Map<Class, Class[]>();
27
- /**
28
- * Default mapping of classes by class name or
29
- * by requested store name. This is the state at the
30
- * start of the application.
31
- */
32
- initialModelNameMapping = new Map<string, Class[]>();
33
-
34
- constructor() {
35
- // Listen to schema and dependency
36
- super(SchemaRegistry, DependencyRegistry);
37
- }
38
-
39
- getInitialNameMapping(): Map<string, Class[]> {
40
- if (this.initialModelNameMapping.size === 0) {
41
- for (const cls of this.getClasses()) {
42
- const store = this.get(cls).store ?? cls.name;
43
- if (!this.initialModelNameMapping.has(store)) {
44
- this.initialModelNameMapping.set(store, []);
45
- }
46
- this.initialModelNameMapping.get(store)!.push(cls);
47
- }
48
- }
49
- return this.initialModelNameMapping;
50
- }
51
-
52
- createPending(cls: Class): Partial<ModelOptions<ModelType>> {
53
- return {
54
- class: cls,
55
- indices: [],
56
- autoCreate: true,
57
- baseType: describeFunction(cls).abstract,
58
- postLoad: [],
59
- prePersist: []
60
- };
61
- }
62
-
63
- registerDataHandlers(cls: Class, pConfig?: Partial<ModelOptions<ModelType>>): void {
64
- const cfg = this.getOrCreatePending(cls);
65
- this.register(cls, {
66
- ...cfg,
67
- prePersist: [...cfg.prePersist ?? [], ...pConfig?.prePersist ?? []],
68
- postLoad: [...cfg.postLoad ?? [], ...pConfig?.postLoad ?? []],
69
- });
70
- }
71
-
72
- onInstallFinalize(cls: Class): ModelOptions<ModelType> {
73
- const config = asFull(this.pending.get(cls.Ⲑid)!);
74
-
75
- const schema = SchemaRegistry.get(cls);
76
- const view = schema.totalView.schema;
77
- delete view.id.required; // Allow ids to be optional
78
-
79
- if (schema.subTypeField in view && this.getBaseModel(cls) !== cls) {
80
- config.subType = !!schema.subTypeName; // Copy from schema
81
- delete view[schema.subTypeField].required; // Allow type to be optional
82
- }
83
-
84
- const parent = this.getParentClass(cls);
85
- if (parent && parent !== cls) {
86
- const pCfg = this.get(parent) ?? this.pending.get(MetadataRegistry.id(parent));
87
- config.prePersist = [...pCfg?.prePersist ?? [], ...config.prePersist ?? []];
88
- config.postLoad = [...pCfg?.postLoad ?? [], ...config.postLoad ?? []];
89
- }
90
- return config;
91
- }
92
-
93
- override onUninstallFinalize(cls: Class): void {
94
- this.stores.delete(cls);
95
-
96
- // Force system to recompute on uninstall
97
- this.baseModels.clear();
98
- this.baseModelGrouped.clear();
99
- }
100
-
101
- /**
102
- * Find base class for a given model
103
- */
104
- getBaseModel<T extends ModelType>(cls: Class<T>): Class<T> {
105
- if (!this.baseModels.has(cls)) {
106
- let conf = this.get(cls) ?? this.getOrCreatePending(cls);
107
- let parent = cls;
108
-
109
- while (conf && !conf.baseType) {
110
- parent = this.getParentClass(parent)!;
111
- conf = this.get(parent) ?? this.pending.get(MetadataRegistry.id(parent));
112
- }
113
-
114
- this.baseModels.set(cls, conf ? parent : cls);
115
- }
116
- return this.baseModels.get(cls)!;
117
- }
118
-
119
- /**
120
- * Find all classes by their base types
121
- */
122
- getAllClassesByBaseType(): Map<Class, Class[]> {
123
- if (!this.baseModelGrouped.size) {
124
- const out = new Map<Class, Class[]>();
125
- for (const el of this.entries.keys()) {
126
- const conf = this.entries.get(el)!;
127
- if (conf.baseType) {
128
- continue;
129
- }
130
-
131
- const parent = this.getBaseModel(conf.class);
132
-
133
- if (!out.has(parent)) {
134
- out.set(parent, []);
135
- }
136
-
137
- out.get(parent)!.push(conf.class);
138
- }
139
- this.baseModelGrouped = out;
140
- }
141
- return this.baseModelGrouped;
142
- }
143
-
144
- /**
145
- * Get all classes for a given base type
146
- */
147
- getClassesByBaseType(base: Class): Class[] {
148
- return this.getAllClassesByBaseType().get(base) ?? [];
149
- }
150
-
151
- /**
152
- * Get the apparent store for a type, handling polymorphism when appropriate
153
- */
154
- getStore(cls: Class): string {
155
- if (!this.stores.has(cls)) {
156
- const config = this.get(cls) ?? this.getOrCreatePending(cls);
157
- const base = this.getBaseModel(cls);
158
- if (base !== cls) {
159
- return this.getStore(base);
160
- }
161
-
162
- const name = config.store ?? cls.name.toLowerCase();
163
-
164
- const candidates = this.getInitialNameMapping().get(name) || [];
165
-
166
- // Don't allow two models with same class name, or same store name
167
- if (candidates.length > 1) {
168
- if (config.store) {
169
- throw new AppError('Duplicate models with same store name', {
170
- details: { classes: candidates.map(x => x.Ⲑid) }
171
- });
172
- } else {
173
- throw new AppError('Duplicate models with same class name, but no store name provided', {
174
- details: { classes: candidates.map(x => x.Ⲑid) }
175
- });
176
- }
177
- }
178
-
179
- this.stores.set(cls, name);
180
- }
181
- return this.stores.get(cls)!;
182
- }
183
-
184
- /**
185
- * Get Index
186
- */
187
- getIndex<T extends ModelType, K extends IndexType[]>(cls: Class<T>, name: string, supportedTypes?: K): IndexConfig<T> & { type: K[number] } {
188
- const cfg = this.get(cls).indices?.find((x): x is IndexConfig<T> => x.name === name);
189
- if (!cfg) {
190
- throw new NotFoundError(`${cls.name} Index`, `${name}`);
191
- }
192
- if (supportedTypes && !supportedTypes.includes(cfg.type)) {
193
- throw new IndexNotSupported(cls, cfg, `${cfg.type} indices are not supported.`);
194
- }
195
- return cfg;
196
- }
197
-
198
- /**
199
- * Get Indices
200
- */
201
- getIndices<T extends ModelType, K extends IndexType[]>(cls: Class<T>, supportedTypes?: K): (IndexConfig<T> & { type: K[number] })[] {
202
- return (this.get(cls).indices ?? []).filter((x): x is IndexConfig<T> => !supportedTypes || supportedTypes.includes(x.type));
203
- }
204
-
205
- /**
206
- * Get expiry field
207
- * @param cls
208
- */
209
- getExpiry<T extends ModelType>(cls: Class<T>): keyof T {
210
- const expiry = this.get(cls).expiresAt;
211
- if (!expiry) {
212
- throw new AppError(`${cls.name} is not configured with expiry support, please use @ExpiresAt to declare expiration behavior`);
213
- }
214
- return castTo(expiry);
215
- }
216
- }
217
-
218
- export const ModelRegistry = new $ModelRegistry();