@travetto/model 4.1.2 → 4.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 ArcSine Technologies
3
+ Copyright (c) 2020 ArcSine Technologies
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
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.0",
30
- "@travetto/di": "^4.1.0",
31
- "@travetto/registry": "^4.1.0",
32
- "@travetto/schema": "^4.1.0"
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.0",
36
- "@travetto/test": "^4.1.0"
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
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
161
- for (const handler of (config.prePersist ?? []) as unknown as DataHandler<T>[]) {
162
- item = await handler(item) ?? item;
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;
@@ -221,7 +221,17 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
221
221
 
222
222
  // Expiry
223
223
  async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
224
- return ModelExpiryUtil.naiveDeleteExpired(this, cls);
224
+ let deleted = 0;
225
+ for await (const [_id, file] of FileModelService.scanFolder(await this.#resolveName(cls, '.json'), '.json')) {
226
+ try {
227
+ const res = await ModelCrudUtil.load(cls, await fs.readFile(file));
228
+ if (ModelExpiryUtil.getExpiryState(cls, res).expired) {
229
+ await fs.rm(file, { force: true });
230
+ deleted += 1;
231
+ }
232
+ } catch { } // Don't let a single failure stop the process
233
+ }
234
+ return deleted;
225
235
  }
226
236
 
227
237
  // Storage management
@@ -281,7 +281,18 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
281
281
 
282
282
  // Expiry
283
283
  async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
284
- return ModelExpiryUtil.naiveDeleteExpired(this, cls);
284
+ const store = this.#getStore(cls);
285
+ let deleted = 0;
286
+ for (const key of [...store.keys()]) {
287
+ try {
288
+ const res = await ModelCrudUtil.load(cls, store.get(key)!);
289
+ if (ModelExpiryUtil.getExpiryState(cls, res).expired) {
290
+ store.delete(key);
291
+ deleted += 1;
292
+ }
293
+ } catch { } // Do not let a single error stop the process
294
+ }
295
+ return deleted;
285
296
  }
286
297
 
287
298
  // Storage Support
@@ -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
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
51
- ModelRegistry.registerDataHandlers(tgt, { prePersist: [handler as DataHandler] });
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
- (inst): void => {
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
  }
@@ -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
  */
@@ -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
- time?: Date;
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({ time: new Date() }));
214
+ const res = await service.create(Dated, Dated.from({ createdDate: new Date() }));
215
+
216
+ assert(res.createdDate instanceof Date);
217
+ }
208
218
 
209
- assert(res.time instanceof Date);
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;