@travetto/model
Advanced tools
Comparing version 3.3.3 to 3.3.4
{ | ||
"name": "@travetto/model", | ||
"version": "3.3.3", | ||
"version": "3.3.4", | ||
"description": "Datastore abstraction for core operations.", | ||
@@ -29,9 +29,9 @@ "keywords": [ | ||
"dependencies": { | ||
"@travetto/config": "^3.3.3", | ||
"@travetto/config": "^3.3.4", | ||
"@travetto/di": "^3.3.3", | ||
"@travetto/registry": "^3.3.3", | ||
"@travetto/schema": "^3.3.3" | ||
"@travetto/schema": "^3.3.4" | ||
}, | ||
"peerDependencies": { | ||
"@travetto/cli": "^3.3.4", | ||
"@travetto/cli": "^3.3.5", | ||
"@travetto/test": "^3.3.4" | ||
@@ -38,0 +38,0 @@ }, |
@@ -219,14 +219,6 @@ <!-- This file was generated by @travetto/doc and should not be modified directly --> | ||
id: string; | ||
/** | ||
* Run before saving | ||
*/ | ||
prePersist?(): void | Promise<void>; | ||
/** | ||
* Run after loading | ||
*/ | ||
postLoad?(): void | Promise<void>; | ||
} | ||
``` | ||
All fields are optional, but the `id` and `type` are important as those field types are unable to be changed. This may make using existing data models impossible if types other than strings are required. Additionally, the type field, is intended to record the base model type and cannot be remapped. This is important to support polymorphism, not only in [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."), but also in [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding."). | ||
The `id` is the only required field for a model, as this is a hard requirement on naming and type. This may make using existing data models impossible if types other than strings are required. Additionally, the `type` field, is intended to record the base model type, but can be remapped. This is important to support polymorphism, not only in [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."), but also in [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding."). | ||
@@ -233,0 +225,0 @@ ## Implementations |
@@ -11,2 +11,3 @@ import crypto from 'crypto'; | ||
import { SubTypeNotSupportedError } from '../../error/invalid-sub-type'; | ||
import { DataHandler } from '../../registry/types'; | ||
@@ -66,6 +67,3 @@ export type ModelCrudProvider = { | ||
if (result.postLoad) { | ||
await result.postLoad(); | ||
} | ||
return result; | ||
return this.postLoad(cls, result); | ||
} | ||
@@ -95,5 +93,3 @@ | ||
if (item.prePersist) { | ||
await item.prePersist(); | ||
} | ||
item = await this.prePersist(cls, item); | ||
@@ -147,5 +143,3 @@ let errors: ValidationError[] = []; | ||
if (item.prePersist) { | ||
await item.prePersist(); | ||
} | ||
item = await this.prePersist(cls, item); | ||
@@ -164,2 +158,32 @@ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
} | ||
/** | ||
* Pre persist behavior | ||
*/ | ||
static async prePersist<T>(cls: Class<T>, item: T): Promise<T> { | ||
const config = ModelRegistry.get(cls); | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
for (const handler of (config.prePersist ?? []) as unknown as DataHandler<T>[]) { | ||
item = await handler(item) ?? item; | ||
} | ||
if (typeof item === 'object' && item && 'prePersist' in item && typeof item['prePersist'] === 'function') { | ||
item = await item.prePersist() ?? item; | ||
} | ||
return item; | ||
} | ||
/** | ||
* Post load behavior | ||
*/ | ||
static async postLoad<T>(cls: Class<T>, item: T): Promise<T> { | ||
const config = ModelRegistry.get(cls); | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
for (const handler of (config.postLoad ?? []) as unknown as DataHandler<T>[]) { | ||
item = await handler(item) ?? item; | ||
} | ||
if (typeof item === 'object' && item && 'postLoad' in item && typeof item['postLoad'] === 'function') { | ||
item = await item.postLoad() ?? item; | ||
} | ||
return item; | ||
} | ||
} |
@@ -6,3 +6,3 @@ import { Class } from '@travetto/base'; | ||
import { ModelRegistry } from './model'; | ||
import { IndexConfig, ModelOptions } from './types'; | ||
import { DataHandler, IndexConfig, ModelOptions } from './types'; | ||
@@ -44,2 +44,41 @@ /** | ||
}; | ||
} | ||
/** | ||
* Model class decorator for pre-persist behavior | ||
*/ | ||
export function PrePersist<T>(handler: DataHandler<T>) { | ||
return function (tgt: Class<T>): void { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
ModelRegistry.registerDataHandlers(tgt, { prePersist: [handler as DataHandler] }); | ||
}; | ||
} | ||
/** | ||
* Model field decorator for pre-persist value setting | ||
*/ | ||
export function PersistValue<T>(handler: (curr: T | undefined) => T) { | ||
return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
ModelRegistry.registerDataHandlers(tgt.constructor as Class<C>, { | ||
prePersist: [ | ||
(inst): void => { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const cInst = (inst as unknown as Record<K, T>); | ||
cInst[prop] = handler(cInst[prop]); | ||
} | ||
] | ||
}); | ||
}; | ||
} | ||
/** | ||
* Model class decorator for post-load behavior | ||
*/ | ||
export function PostLoad<T>(handler: DataHandler<T>) { | ||
return function (tgt: Class<T>): void { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
ModelRegistry.registerDataHandlers(tgt, { postLoad: [handler as DataHandler] }); | ||
}; | ||
} |
@@ -55,5 +55,14 @@ import { RootIndex } from '@travetto/manifest'; | ||
createPending(cls: Class): Partial<ModelOptions<ModelType>> { | ||
return { class: cls, indices: [], autoCreate: true, baseType: RootIndex.getFunctionMetadata(cls)?.abstract }; | ||
return { class: cls, indices: [], autoCreate: true, baseType: RootIndex.getFunctionMetadata(cls)?.abstract, postLoad: [], prePersist: [] }; | ||
} | ||
registerDataHandlers(cls: Class, pConfig?: Partial<ModelOptions<ModelType>>): void { | ||
const cfg = this.getOrCreatePending(cls); | ||
this.register(cls, { | ||
...cfg, | ||
prePersist: [...cfg.prePersist ?? [], ...pConfig?.prePersist ?? []], | ||
postLoad: [...cfg.postLoad ?? [], ...pConfig?.postLoad ?? []], | ||
}); | ||
} | ||
onInstallFinalize(cls: Class): ModelOptions<ModelType> { | ||
@@ -70,2 +79,12 @@ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
delete view[schema.subTypeField].required; // Allow type to be optional | ||
let parent = this.getParentClass(cls); | ||
let from = cls; | ||
// Merge inherited prepersist/postload | ||
while (parent && from !== parent) { | ||
const pCfg = this.get(parent); | ||
config.prePersist = [...pCfg.prePersist ?? [], ...config.prePersist ?? []]; | ||
config.postLoad = [...pCfg.postLoad ?? [], ...config.postLoad ?? []]; | ||
from = parent; | ||
parent = this.getParentClass(from); | ||
} | ||
} | ||
@@ -72,0 +91,0 @@ return config; |
@@ -24,2 +24,4 @@ import { Primitive, Class } from '@travetto/base'; | ||
export type DataHandler<T = unknown> = (inst: T) => (Promise<T | void> | T | void); | ||
/** | ||
@@ -61,2 +63,10 @@ * Model options | ||
autoCreate: boolean; | ||
/** | ||
* Pre-persist handlers | ||
*/ | ||
prePersist?: DataHandler<unknown>[]; | ||
/** | ||
* Post-load handlers | ||
*/ | ||
postLoad?: DataHandler<unknown>[]; | ||
} | ||
@@ -63,0 +73,0 @@ |
@@ -16,12 +16,4 @@ /** | ||
id: string; | ||
/** | ||
* Run before saving | ||
*/ | ||
prePersist?(): void | Promise<void>; | ||
/** | ||
* Run after loading | ||
*/ | ||
postLoad?(): void | Promise<void>; | ||
} | ||
export type OptionalId<T extends { id: string }> = Omit<T, 'id'> & { id?: string }; |
@@ -49,2 +49,6 @@ import assert from 'assert'; | ||
name: string; | ||
prePersist() { | ||
this.name = `${this.name}-suff`; | ||
} | ||
} | ||
@@ -179,2 +183,3 @@ | ||
assert(o.address === undefined); | ||
assert(o.name === 'bob-suff'); | ||
@@ -181,0 +186,0 @@ await service.updatePartial(User2, User2.from({ |
@@ -51,9 +51,5 @@ import assert from 'assert'; | ||
id: string; | ||
createdDate?: Date; | ||
createdDate?: Date = new Date(); | ||
color: string; | ||
child: Child; | ||
prePersist?() { | ||
this.createdDate ??= new Date(); | ||
} | ||
} | ||
@@ -161,3 +157,3 @@ | ||
const arr = await this.toArray(service.listByIndex(User4, 'nameCreated', User4.from({ child: { name: 'bob' } }))); | ||
const arr = await this.toArray(service.listByIndex(User4, 'nameCreated', { child: { name: 'bob' } })); | ||
@@ -164,0 +160,0 @@ assert(arr[0].color === 'green' && arr[0].child.name === 'bob' && arr[0].child.age === 50); |
@@ -5,6 +5,6 @@ import assert from 'assert'; | ||
import { Suite, Test } from '@travetto/test'; | ||
import { Text, TypeMismatchError } from '@travetto/schema'; | ||
import { SubTypeField, Text, TypeMismatchError } from '@travetto/schema'; | ||
import { | ||
ModelIndexedSupport, Index, ModelCrudSupport, Model, | ||
NotFoundError, SubTypeNotSupportedError | ||
NotFoundError, SubTypeNotSupportedError, PersistValue | ||
} from '@travetto/model'; | ||
@@ -20,11 +20,10 @@ | ||
id: string; | ||
type: string; | ||
@SubTypeField() | ||
_type: string; | ||
@Text() | ||
name: string; | ||
age?: number; | ||
@PersistValue(() => new Date()) | ||
updatedDate?: Date; | ||
prePersist() { | ||
this.updatedDate = new Date(); | ||
} | ||
} | ||
@@ -31,0 +30,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
160379
2978
420
Updated@travetto/config@^3.3.4
Updated@travetto/schema@^3.3.4