@tsed/schema
Version:
JsonSchema module for Ts.ED Framework
211 lines (210 loc) • 7.14 kB
JavaScript
import { ancestorsOf, classOf, decoratorTypeOf, descriptorOf, isArrayOrArrayClass, isArrowFn, isClass, isClassObject, isCollection, isDate, isObject, isPlainObject, isPrimitiveOrPrimitiveClass, nameOf, prototypeOf, Store } from "@tsed/core";
/**
* @ignore
*/
export const JsonEntitiesContainer = new Map();
/**
* Base class for storing metadata about decorated entities (classes, properties, methods, parameters).
*
* JsonEntityStore is the foundation of Ts.ED's metadata system, managing schema information
* and type metadata for decorated entities. It serves as the parent class for specialized
* stores (JsonClassStore, JsonPropertyStore, JsonMethodStore, JsonParameterStore).
*
* ### Entity Types
*
* Different decorator types create different store subclasses:
* - **Class decorators**: Create JsonClassStore instances
* - **Property decorators**: Create JsonPropertyStore instances
* - **Method decorators**: Create JsonMethodStore instances
* - **Parameter decorators**: Create JsonParameterStore instances
*
* ### Key Responsibilities
*
* - **Schema Management**: Each store maintains a JsonSchema for the decorated entity
* - **Type Resolution**: Resolves TypeScript types, including collections and generics
* - **Metadata Storage**: Stores decorator metadata using Ts.ED's Store system
* - **Inheritance**: Manages parent-child relationships between entities
*
* ### Usage
*
* Stores are typically accessed via static `from()` methods:
*
* ```typescript
* // Get store for a class
* const classStore = JsonEntityStore.from(MyClass);
*
* // Get store for a property
* const propStore = JsonEntityStore.from(MyClass, "propertyName");
*
* // Get store for a method parameter
* const paramStore = JsonEntityStore.from(MyClass, "methodName", 0);
* ```
*
* ### Architecture
*
* The store system creates a hierarchical structure:
* - ClassStore contains PropertyStore and MethodStore children
* - MethodStore contains ParameterStore children
* - Each store has an associated JsonSchema
*
* @public
*/
export class JsonEntityStore {
constructor(options) {
this.isStore = true;
const { target, propertyKey, descriptor, index, decoratorType } = options;
this.target = target;
this.propertyKey = propertyKey;
this.propertyName = propertyKey ? String(propertyKey) : propertyKey || "";
this.descriptor = descriptor;
this.index = index;
this.decoratorType = decoratorType;
this.token = target;
this.store = options.store;
this.parent = this;
}
get collectionType() {
return this._collectionType;
}
set collectionType(value) {
this._collectionType = value;
}
get type() {
return this._type;
}
/**
* Get original type without transformation
* @param value
*/
set type(value) {
if (!value?.$schema?.skip) {
this._type = value;
}
this.build();
}
/**
* Return the JsonSchema
*/
get schema() {
return this._schema;
}
/**
* Return the class name of the entity.
* @returns {string}
*/
get targetName() {
return nameOf(this.token);
}
get isCollection() {
return !!this._collectionType;
}
get isArray() {
return isArrayOrArrayClass(this._collectionType);
}
get discriminatorAncestor() {
const ancestors = ancestorsOf(this.target);
const ancestor = ancestors.find((ancestor) => JsonEntityStore.from(ancestor).schema.isDiscriminator);
return ancestor && JsonEntityStore.from(ancestor);
}
get isPrimitive() {
return isPrimitiveOrPrimitiveClass(this.type);
}
get isDate() {
return isDate(this.computedType);
}
get isObject() {
return isObject(this.computedType);
}
get isClass() {
return isClass(this.computedType);
}
/**
* Return the itemSchema computed type. if the type is a function used for recursive model, the function will be called to
* get the right type.
*/
get computedType() {
return this.itemSchema.class;
}
get itemSchema() {
return this.isCollection ? this.schema.itemSchema() : this.schema;
}
get parentSchema() {
return this.parent.schema;
}
get isDiscriminatorChild() {
return this.schema.isDiscriminator && this.discriminatorAncestor?.schema.discriminator().base !== this.target;
}
get path() {
return this.store.get("path");
}
set path(path) {
this.store.set("path", path);
}
static from(...args) {
if (args[0].isStore) {
return args[0];
}
const target = args[0];
if (args.length > 1) {
args[0] = prototypeOf(args[0]);
}
const store = Store.from(...args);
if (!store.has("JsonEntityStore")) {
const decoratorType = decoratorTypeOf(args);
const entityStore = JsonEntitiesContainer.get(decoratorType);
const jsonSchemaStore = new entityStore({
store,
decoratorType,
target: classOf(target),
propertyKey: args[1],
index: typeof args[2] === "number" ? args[2] : undefined,
descriptor: typeof args[2] === "object" ? args[2] : undefined
});
jsonSchemaStore.build();
store.set("JsonEntityStore", jsonSchemaStore);
}
return store.get("JsonEntityStore");
}
static fromMethod(target, propertyKey) {
return this.from(target, propertyKey, descriptorOf(target, propertyKey));
}
static get(target, propertyKey, descriptor) {
return JsonEntityStore.from(prototypeOf(target), propertyKey, descriptor);
}
isGetterOnly() {
return isObject(this.descriptor) && !this.descriptor.value && this.descriptor.get && !this.descriptor.set;
}
get(key, defaultValue) {
return this.store.get(key, defaultValue);
}
set(key, value) {
return this.store.set(key, value);
}
toString() {
return [this.targetName, this.propertyName, this.index].filter((o) => o !== undefined).join(":");
}
getBestType() {
return this.itemSchema.hasDiscriminator
? this.itemSchema.discriminator().base
: isClassObject(this.type)
? this.itemSchema.getTarget()
: isArrowFn(this.type)
? this.type()
: this.type;
}
is(input) {
return this.decoratorType === input;
}
buildType(type) {
if (isCollection(type)) {
this._collectionType = type;
}
else if (!(type && "$schema" in type && type.$schema.skip)) {
this._type = type;
// issue #1534: Enum metadata stored as plain object instead of String (see: https://github.com/tsedio/tsed/issues/1534)
if (this._type && isPlainObject(this._type)) {
this._type = String;
}
}
}
}