@travetto/model 4.1.0 → 4.1.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/LICENSE +1 -1
- package/package.json +7 -7
- package/src/internal/service/crud.ts +11 -8
- package/src/provider/memory.ts +4 -1
- package/src/registry/decorator.ts +14 -8
- package/src/registry/types.ts +3 -1
- package/support/test/crud.ts +30 -5
package/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.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": "^4.1.
|
|
30
|
-
"@travetto/di": "^4.1.
|
|
31
|
-
"@travetto/registry": "^4.1.
|
|
32
|
-
"@travetto/schema": "^4.1.
|
|
29
|
+
"@travetto/config": "^4.1.1",
|
|
30
|
+
"@travetto/di": "^4.1.1",
|
|
31
|
+
"@travetto/registry": "^4.1.1",
|
|
32
|
+
"@travetto/schema": "^4.1.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^4.1.
|
|
36
|
-
"@travetto/test": "^4.1.
|
|
35
|
+
"@travetto/cli": "^4.1.1",
|
|
36
|
+
"@travetto/test": "^4.1.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
|
@@ -8,7 +8,7 @@ import { ModelIdSource, ModelType, OptionalId } from '../../types/model';
|
|
|
8
8
|
import { NotFoundError } from '../../error/not-found';
|
|
9
9
|
import { ExistsError } from '../../error/exists';
|
|
10
10
|
import { SubTypeNotSupportedError } from '../../error/invalid-sub-type';
|
|
11
|
-
import { DataHandler } from '../../registry/types';
|
|
11
|
+
import { DataHandler, PrePersistScope } from '../../registry/types';
|
|
12
12
|
|
|
13
13
|
export type ModelCrudProvider = {
|
|
14
14
|
idSource: ModelIdSource;
|
|
@@ -72,7 +72,7 @@ export class ModelCrudUtil {
|
|
|
72
72
|
* @param cls Type to store for
|
|
73
73
|
* @param item Item to store
|
|
74
74
|
*/
|
|
75
|
-
static async preStore<T extends ModelType>(cls: Class<T>, item: Partial<OptionalId<T>>, provider: ModelCrudProvider): Promise<T> {
|
|
75
|
+
static async preStore<T extends ModelType>(cls: Class<T>, item: Partial<OptionalId<T>>, provider: ModelCrudProvider, scope: PrePersistScope = 'all'): Promise<T> {
|
|
76
76
|
if (!item.id) {
|
|
77
77
|
item.id = provider.idSource.create();
|
|
78
78
|
}
|
|
@@ -88,7 +88,7 @@ export class ModelCrudUtil {
|
|
|
88
88
|
SchemaRegistry.ensureInstanceTypeField(cls, item);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
item = await this.prePersist(cls, item);
|
|
91
|
+
item = await this.prePersist(cls, item, scope);
|
|
92
92
|
|
|
93
93
|
let errors: ValidationError[] = [];
|
|
94
94
|
try {
|
|
@@ -137,7 +137,7 @@ export class ModelCrudUtil {
|
|
|
137
137
|
|
|
138
138
|
item = Object.assign(existing, item);
|
|
139
139
|
|
|
140
|
-
item = await this.prePersist(cls, item);
|
|
140
|
+
item = await this.prePersist(cls, item, 'partial');
|
|
141
141
|
|
|
142
142
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
143
143
|
return item as T;
|
|
@@ -155,11 +155,14 @@ export class ModelCrudUtil {
|
|
|
155
155
|
/**
|
|
156
156
|
* Pre persist behavior
|
|
157
157
|
*/
|
|
158
|
-
static async prePersist<T>(cls: Class<T>, item: T): Promise<T> {
|
|
158
|
+
static async prePersist<T>(cls: Class<T>, item: T, scope: PrePersistScope): Promise<T> {
|
|
159
159
|
const config = ModelRegistry.get(cls);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
for (const state of (config.prePersist ?? [])) {
|
|
161
|
+
if (state.scope === scope || scope === 'all' || state.scope === 'all') {
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
163
|
+
const handler = state.handler as unknown as DataHandler<T>;
|
|
164
|
+
item = await handler(item) ?? item;
|
|
165
|
+
}
|
|
163
166
|
}
|
|
164
167
|
if (typeof item === 'object' && item && 'prePersist' in item && typeof item['prePersist'] === 'function') {
|
|
165
168
|
item = await item.prePersist() ?? item;
|
package/src/provider/memory.ts
CHANGED
|
@@ -27,7 +27,7 @@ type StoreType = Map<string, Buffer>;
|
|
|
27
27
|
|
|
28
28
|
@Config('model.memory')
|
|
29
29
|
export class MemoryModelConfig {
|
|
30
|
-
autoCreate?: boolean;
|
|
30
|
+
autoCreate?: boolean = true;
|
|
31
31
|
namespace?: string;
|
|
32
32
|
cullRate?: number | TimeSpan;
|
|
33
33
|
}
|
|
@@ -105,6 +105,9 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
105
105
|
let index = this.#indices[idx.type].get(idxName)?.get(key);
|
|
106
106
|
|
|
107
107
|
if (!index) {
|
|
108
|
+
if (!this.#indices[idx.type].has(idxName)) {
|
|
109
|
+
this.#indices[idx.type].set(idxName, new Map());
|
|
110
|
+
}
|
|
108
111
|
if (idx.type === 'sorted') {
|
|
109
112
|
this.#indices[idx.type].get(idxName)!.set(key, index = new Map());
|
|
110
113
|
} else {
|
|
@@ -3,7 +3,7 @@ import { SchemaRegistry } from '@travetto/schema';
|
|
|
3
3
|
|
|
4
4
|
import { ModelType } from '../types/model';
|
|
5
5
|
import { ModelRegistry } from './model';
|
|
6
|
-
import { DataHandler, IndexConfig, ModelOptions } from './types';
|
|
6
|
+
import { DataHandler, IndexConfig, ModelOptions, PrePersistScope } from './types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Model decorator, extends `@Schema`
|
|
@@ -45,27 +45,33 @@ export function ExpiresAt() {
|
|
|
45
45
|
/**
|
|
46
46
|
* Model class decorator for pre-persist behavior
|
|
47
47
|
*/
|
|
48
|
-
export function PrePersist<T>(handler: DataHandler<T
|
|
48
|
+
export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope = 'all') {
|
|
49
49
|
return function (tgt: Class<T>): void {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
ModelRegistry.registerDataHandlers(tgt, {
|
|
51
|
+
prePersist: [{
|
|
52
|
+
scope,
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
54
|
+
handler: handler as DataHandler
|
|
55
|
+
}]
|
|
56
|
+
});
|
|
52
57
|
};
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Model field decorator for pre-persist value setting
|
|
57
62
|
*/
|
|
58
|
-
export function PersistValue<T>(handler: (curr: T | undefined) => T) {
|
|
63
|
+
export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PrePersistScope = 'all') {
|
|
59
64
|
return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void {
|
|
60
65
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
61
66
|
ModelRegistry.registerDataHandlers(tgt.constructor as Class<C>, {
|
|
62
|
-
prePersist: [
|
|
63
|
-
|
|
67
|
+
prePersist: [{
|
|
68
|
+
scope,
|
|
69
|
+
handler: (inst): void => {
|
|
64
70
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
65
71
|
const cInst = (inst as unknown as Record<K, T>);
|
|
66
72
|
cInst[prop] = handler(cInst[prop]);
|
|
67
73
|
}
|
|
68
|
-
]
|
|
74
|
+
}]
|
|
69
75
|
});
|
|
70
76
|
};
|
|
71
77
|
}
|
package/src/registry/types.ts
CHANGED
|
@@ -23,6 +23,8 @@ type IndexClauseRaw<T> = {
|
|
|
23
23
|
|
|
24
24
|
export type DataHandler<T = unknown> = (inst: T) => (Promise<T | void> | T | void);
|
|
25
25
|
|
|
26
|
+
export type PrePersistScope = 'full' | 'partial' | 'all';
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* Model options
|
|
28
30
|
*/
|
|
@@ -62,7 +64,7 @@ export class ModelOptions<T extends ModelType = ModelType> {
|
|
|
62
64
|
/**
|
|
63
65
|
* Pre-persist handlers
|
|
64
66
|
*/
|
|
65
|
-
prePersist?: DataHandler<unknown>[];
|
|
67
|
+
prePersist?: { scope: PrePersistScope, handler: DataHandler<unknown> }[];
|
|
66
68
|
/**
|
|
67
69
|
* Post-load handlers
|
|
68
70
|
*/
|
package/support/test/crud.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
3
|
import { Suite, Test } from '@travetto/test';
|
|
4
|
-
import { Schema, Text, Precision, } from '@travetto/schema';
|
|
5
|
-
import { ModelCrudSupport, Model, NotFoundError } from '@travetto/model';
|
|
4
|
+
import { Schema, Text, Precision, Required, } from '@travetto/schema';
|
|
5
|
+
import { ModelCrudSupport, Model, NotFoundError, PersistValue } from '@travetto/model';
|
|
6
6
|
|
|
7
7
|
import { BaseModelSuite } from './base';
|
|
8
8
|
|
|
@@ -55,7 +55,14 @@ class User2 {
|
|
|
55
55
|
@Model()
|
|
56
56
|
class Dated {
|
|
57
57
|
id: string;
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
@PersistValue(v => v ?? new Date(), 'full')
|
|
60
|
+
@Required(false)
|
|
61
|
+
createdDate: Date;
|
|
62
|
+
|
|
63
|
+
@PersistValue(v => new Date())
|
|
64
|
+
@Required(false)
|
|
65
|
+
updatedDate: Date;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
@Suite()
|
|
@@ -204,11 +211,29 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
|
204
211
|
@Test('verify dates')
|
|
205
212
|
async testDates() {
|
|
206
213
|
const service = await this.service;
|
|
207
|
-
const res = await service.create(Dated, Dated.from({
|
|
214
|
+
const res = await service.create(Dated, Dated.from({ createdDate: new Date() }));
|
|
215
|
+
|
|
216
|
+
assert(res.createdDate instanceof Date);
|
|
217
|
+
}
|
|
208
218
|
|
|
209
|
-
|
|
219
|
+
@Test('verify prepersist on create/update')
|
|
220
|
+
async testPrePersist() {
|
|
221
|
+
const service = await this.service;
|
|
222
|
+
const res = await service.create(Dated, Dated.from({}));
|
|
223
|
+
const created = res.createdDate;
|
|
224
|
+
assert(res.createdDate instanceof Date);
|
|
225
|
+
assert(res.updatedDate instanceof Date);
|
|
226
|
+
|
|
227
|
+
await new Promise(r => setTimeout(r, 100));
|
|
228
|
+
|
|
229
|
+
const final = await service.updatePartial(Dated, { id: res.id });
|
|
230
|
+
assert(final.createdDate instanceof Date);
|
|
231
|
+
assert(final.createdDate.getTime() === created?.getTime());
|
|
232
|
+
assert(final.updatedDate instanceof Date);
|
|
233
|
+
assert(final.createdDate.getTime() < final.updatedDate?.getTime());
|
|
210
234
|
}
|
|
211
235
|
|
|
236
|
+
|
|
212
237
|
@Test('verify list')
|
|
213
238
|
async list() {
|
|
214
239
|
const service = await this.service;
|