зеркало из https://github.com/microsoft/paris.git
Documented entity field and made some restructuring
This commit is contained in:
Родитель
bec33ed3f7
Коммит
350f495c85
|
@ -1,11 +1,11 @@
|
|||
import {EntityFields} from "./entity-fields";
|
||||
import {Field} from "./entity-field";
|
||||
import {Immutability} from "../services/immutability";
|
||||
import {DataEntityConstructor} from "./data-entity.base";
|
||||
import {ParisConfig} from "../config/paris-config";
|
||||
import {ModelBase} from "../models/model.base";
|
||||
import {HttpOptions} from "../services/http.service";
|
||||
import {EntityId} from "../models/entity-id.type";
|
||||
import {Field} from "./entity-field";
|
||||
|
||||
const DEFAULT_VALUE_ID = "__default";
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
import {DataEntityType} from "./data-entity.base";
|
||||
import {ParisConfig} from "../config/paris-config";
|
||||
import {DataQuery} from "../dataset/data-query";
|
||||
|
||||
/**
|
||||
* Configuration for a model field decorator
|
||||
*/
|
||||
export interface FieldConfig{
|
||||
/**
|
||||
* An ID for the field. By default, the ID is the property's name.
|
||||
*/
|
||||
id?:string,
|
||||
|
||||
/**
|
||||
* Optional name to assign to the field. May be used for reflection, debugging, etc.
|
||||
*/
|
||||
name?:string,
|
||||
|
||||
/**
|
||||
* Specifies which property in the raw data should be assigned to the model's property.
|
||||
* By default, Paris looks for a property of the same name as the property in the model. i.e:
|
||||
*
|
||||
* If an entity has the following property definition:
|
||||
*
|
||||
* @EntityField()
|
||||
* name:string;
|
||||
*
|
||||
* Then when creating the model, if the raw data contains a `name` property with value 'Anna', the resulting model will have a `name` property with value 'Anna'.
|
||||
*
|
||||
* @example <caption>Mapping from a different raw data property with `data`</caption>
|
||||
* If your raw data has properties in snake-case rather than camel-case, you'd need to map the properties:
|
||||
*
|
||||
* @EntityField({ data: "creation_date" })
|
||||
* creationData: Date;
|
||||
*
|
||||
* @example <caption>Using the first available value from the raw data for the model's property</caption>
|
||||
* If an array of strings is provided for `data`, Paris will assign to the model's property value the first value from the raw data which isn't undefined or null:
|
||||
*
|
||||
* @EntityField({ data: ['creation_date', 'init_date', 'start_date'] })
|
||||
* date: Date;
|
||||
*
|
||||
* If the raw data is:
|
||||
* {
|
||||
* "creation_date": null,
|
||||
* "start_date": 1532422166428
|
||||
* }
|
||||
*
|
||||
* Then the model's `date` property will have a value of Date(1532422166428), since both creation_date and init_date have no value in the data.
|
||||
*
|
||||
* @example <caption>Using '__self' for data to pass the whole raw data</caption>
|
||||
* In the case when we want to separate some properties of the raw data to a sub-model, it's possible to use the special value '__self' for the `data` field configuration.
|
||||
* This passes the whole raw data object to the field's creation, rather than just the value of a property. e.g:
|
||||
*
|
||||
* Person extends EntityModelBase{
|
||||
* @EntityField()
|
||||
* name:string;
|
||||
*
|
||||
* @EntityField({ data: '__self' })
|
||||
* address:Address;
|
||||
* }
|
||||
*
|
||||
* In case we want to separate all address properties from a user into an encapsulated object, for the following raw data:
|
||||
*
|
||||
* {
|
||||
* "name": "Anna",
|
||||
* "street": "Prinsengracht 263-267",
|
||||
* "zip": "1016 GV",
|
||||
* "city": "Amsterdam",
|
||||
* "country": "Holland"
|
||||
* }
|
||||
*/
|
||||
data?:"__self" | string | Array<string>,
|
||||
|
||||
/**
|
||||
* A value to assign to the property if the raw data is `null` or undefined.
|
||||
*/
|
||||
defaultValue?:any,
|
||||
|
||||
/**
|
||||
* `arrayOf` is required when the property's type is an array of a sub-model type.
|
||||
* It's required because the ES6 Reflect-metadata module that Paris uses to infer the types of properties doesn't support generics.
|
||||
*
|
||||
* @example <caption>Using a model field's arrayOf configuration for assigning an array sub-model</caption>
|
||||
* // Without the arrayOf, addresses won't be modeled by Paris.
|
||||
* @EntityField({ arrayOf: Address })
|
||||
* addresses: Array<Address>
|
||||
*/
|
||||
arrayOf?:DataEntityType,
|
||||
|
||||
/**
|
||||
* If a field's `required` is set to `true`, it means that the property must have a value for the whole model to be created.
|
||||
* If `required` is `true` and the property has a value of `null` or `undefined`, then the model itself will be null.
|
||||
* @default false
|
||||
*/
|
||||
required?:boolean,
|
||||
|
||||
/**
|
||||
* A condition that has to be satisfied in order to assign value to the property.
|
||||
*
|
||||
* @example <caption>Assigning ZIP code only if street exists</caption>
|
||||
* @EntityField({ require: "street" })
|
||||
* zip:string;
|
||||
*
|
||||
* @example <caption>Assigning ZIP code only if both street and country exist</caption>
|
||||
* @EntityField({ require: (data:AddressRawData) => data.street && data.country })
|
||||
* zip:string;
|
||||
*/
|
||||
require?:((data:any, config?:ParisConfig) => any) | string,
|
||||
|
||||
/**
|
||||
* Parses the raw data before it's used by Paris to create the property's value
|
||||
* Sometimes the value in the raw data is not formatted as we'd like, or more information might be needed to create the desired value. A field's `parse` configuration is available for changing the raw data before it's passed to Paris.
|
||||
* Important: `parse` should return a new RAW data, not a Paris model.
|
||||
*
|
||||
* @example <caption>Parsing a bitwise value into an array</caption>
|
||||
* @EntityField({
|
||||
* arrayOf: NotificationFormat,
|
||||
* parse: (formatBitWise: number) => {
|
||||
* return notificationFormatValues.reduce((formats: Array<number>, notificationFormat) => {
|
||||
* return notificationFormat.id > 0 && (formatBitWise & notificationFormat.id) ? [...formats, notificationFormat.id] : formats;
|
||||
* }, []);
|
||||
* },
|
||||
* })
|
||||
* formatFlavor: Array<NotificationFormat>;
|
||||
*
|
||||
* @param fieldData The field's data from the raw data
|
||||
* @param itemData The whole object's raw data
|
||||
* @param {DataQuery} query The query (if any) that was used for getting the data
|
||||
* @returns {any} new raw data.
|
||||
*/
|
||||
parse?:(fieldData?:any, itemData?:any, query?: DataQuery) => any,
|
||||
|
||||
/**
|
||||
* A method used to serialize the model field's data back into raw data, to be used when saving the model to backend.
|
||||
* `serialize` may also be set to `false`, in which case the field won't be included in the serialized model.
|
||||
*/
|
||||
serialize?: false | ((itemData:any, serializationData?:any) => any)
|
||||
}
|
||||
|
||||
export const FIELD_DATA_SELF = "__self";
|
||||
export type EntityFieldConfigFunctionOrValue = ((data:any, config?:ParisConfig) => string) | string;
|
|
@ -1,18 +1,19 @@
|
|||
import {DataEntityType} from "./data-entity.base";
|
||||
import {Field} from "./entity-field";
|
||||
import {FieldConfig} from "./entity-field.config";
|
||||
import {entityFieldsService} from "../services/entity-fields.service";
|
||||
import {Field} from "./entity-field";
|
||||
|
||||
export function EntityField(fieldConfig?:Field):PropertyDecorator {
|
||||
export function EntityField(fieldConfig?:FieldConfig):PropertyDecorator {
|
||||
return function (entityPrototype: DataEntityType, propertyKey: string | symbol) {
|
||||
let propertyConstructor:Function = (<any>Reflect).getMetadata("design:type", entityPrototype, propertyKey);
|
||||
|
||||
fieldConfig = fieldConfig || {};
|
||||
let fieldConfigCopy:Field = Object.assign({}, fieldConfig);
|
||||
if (!fieldConfigCopy.id)
|
||||
fieldConfigCopy.id = String(propertyKey);
|
||||
let field:Field = Object.assign({}, fieldConfig);
|
||||
if (!field.id)
|
||||
field.id = String(propertyKey);
|
||||
|
||||
fieldConfigCopy.type = fieldConfig.arrayOf || propertyConstructor;
|
||||
fieldConfigCopy.isArray = propertyConstructor === Array;
|
||||
entityFieldsService.addField(entityPrototype, fieldConfigCopy);
|
||||
field.type = fieldConfig.arrayOf || propertyConstructor;
|
||||
field.isArray = propertyConstructor === Array;
|
||||
entityFieldsService.addField(entityPrototype, field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
import {FieldConfig} from "./entity-field.config";
|
||||
import {DataEntityType} from "./data-entity.base";
|
||||
import {ParisConfig} from "../config/paris-config";
|
||||
import {DataQuery} from "../dataset/data-query";
|
||||
|
||||
export interface Field{
|
||||
id?:string,
|
||||
name?:string,
|
||||
data?:"__self" | string | Array<string>,
|
||||
export interface Field extends FieldConfig{
|
||||
entity?:DataEntityType,
|
||||
type?:Function,
|
||||
defaultValue?:any,
|
||||
arrayOf?:DataEntityType,
|
||||
isArray?:boolean,
|
||||
required?:boolean,
|
||||
require?:((data:any, config?:ParisConfig) => any) | string,
|
||||
parse?:(fieldData?:any, itemData?:any, query?: DataQuery) => any,
|
||||
serialize?: false | ((itemData:any, serializationData?:any) => any)
|
||||
}
|
||||
|
||||
export const FIELD_DATA_SELF = "__self";
|
||||
export type EntityFieldConfigFunctionOrValue = ((data:any, config?:ParisConfig) => string) | string;
|
||||
|
|
|
@ -28,6 +28,7 @@ export {EntityEvent} from "./events/entity.event";
|
|||
export {SaveEntityEvent} from "./events/save-entity.event";
|
||||
export {RemoveEntitiesEvent} from "./events/remove-entities.event";
|
||||
export {EntityErrorEvent, EntityErrorTypes} from "./events/entity-error.event";
|
||||
export {FieldConfig} from "./entity/entity-field.config";
|
||||
export {Field} from "./entity/entity-field";
|
||||
export {ApiCall} from "./entity/api-call.decorator";
|
||||
export {ApiCallBackendConfigInterface} from "./models/api-call-backend-config.interface";
|
||||
|
|
|
@ -12,7 +12,7 @@ import {EntityBackendConfig, ModelEntity} from "../entity/entity.config";
|
|||
import {DataEntityConstructor, DataEntityType} from "../entity/data-entity.base";
|
||||
import {Paris} from "../services/paris";
|
||||
import {DataAvailability} from "../dataset/data-availability.enum";
|
||||
import {Field, FIELD_DATA_SELF} from "../entity/entity-field";
|
||||
import {Field} from "../entity/entity-field";
|
||||
import {DataCache} from "../services/cache";
|
||||
import {Index} from "../models";
|
||||
import {EntityConfigBase, EntityGetMethod, IEntityConfigBase} from "../entity/entity-config.base";
|
||||
|
@ -24,6 +24,7 @@ import {AjaxError} from "rxjs/ajax";
|
|||
import {EntityErrorEvent, EntityErrorTypes} from "../events/entity-error.event";
|
||||
import {catchError, map, mergeMap, tap} from "rxjs/operators";
|
||||
import {IReadonlyRepository} from "./repository.interface";
|
||||
import {FIELD_DATA_SELF} from "../entity/entity-field.config";
|
||||
|
||||
export class ReadonlyRepository<TEntity extends ModelBase<TRawData>, TRawData = object> implements IReadonlyRepository<TEntity>{
|
||||
protected _errorSubject$: Subject<EntityErrorEvent>;
|
||||
|
|
Загрузка…
Ссылка в новой задаче