зеркало из https://github.com/microsoft/paris.git
Merge branch 'master' of https://github.com/Microsoft/paris into github_master
This commit is contained in:
Коммит
679e87d4a7
|
@ -1,11 +1,11 @@
|
||||||
import {EntityFields} from "./entity-fields";
|
import {EntityFields} from "./entity-fields";
|
||||||
import {Field} from "./entity-field";
|
|
||||||
import {Immutability} from "../services/immutability";
|
import {Immutability} from "../services/immutability";
|
||||||
import {DataEntityConstructor} from "./data-entity.base";
|
import {DataEntityConstructor} from "./data-entity.base";
|
||||||
import {ParisConfig} from "../config/paris-config";
|
import {ParisConfig} from "../config/paris-config";
|
||||||
import {ModelBase} from "../models/model.base";
|
import {ModelBase} from "../models/model.base";
|
||||||
import {HttpOptions} from "../services/http.service";
|
import {HttpOptions} from "../services/http.service";
|
||||||
import {EntityId} from "../models/entity-id.type";
|
import {EntityId} from "../models/entity-id.type";
|
||||||
|
import {Field} from "./entity-field";
|
||||||
|
|
||||||
const DEFAULT_VALUE_ID = "__default";
|
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 {DataEntityType} from "./data-entity.base";
|
||||||
import {Field} from "./entity-field";
|
import {FieldConfig} from "./entity-field.config";
|
||||||
import {entityFieldsService} from "../services/entity-fields.service";
|
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) {
|
return function (entityPrototype: DataEntityType, propertyKey: string | symbol) {
|
||||||
let propertyConstructor:Function = (<any>Reflect).getMetadata("design:type", entityPrototype, propertyKey);
|
let propertyConstructor:Function = (<any>Reflect).getMetadata("design:type", entityPrototype, propertyKey);
|
||||||
|
|
||||||
fieldConfig = fieldConfig || {};
|
fieldConfig = fieldConfig || {};
|
||||||
let fieldConfigCopy:Field = Object.assign({}, fieldConfig);
|
let field:Field = Object.assign({}, fieldConfig);
|
||||||
if (!fieldConfigCopy.id)
|
if (!field.id)
|
||||||
fieldConfigCopy.id = String(propertyKey);
|
field.id = String(propertyKey);
|
||||||
|
|
||||||
fieldConfigCopy.type = fieldConfig.arrayOf || propertyConstructor;
|
field.type = fieldConfig.arrayOf || propertyConstructor;
|
||||||
fieldConfigCopy.isArray = propertyConstructor === Array;
|
field.isArray = propertyConstructor === Array;
|
||||||
entityFieldsService.addField(entityPrototype, fieldConfigCopy);
|
entityFieldsService.addField(entityPrototype, field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,8 @@
|
||||||
|
import {FieldConfig} from "./entity-field.config";
|
||||||
import {DataEntityType} from "./data-entity.base";
|
import {DataEntityType} from "./data-entity.base";
|
||||||
import {ParisConfig} from "../config/paris-config";
|
|
||||||
import {DataQuery} from "../dataset/data-query";
|
|
||||||
|
|
||||||
export interface Field{
|
export interface Field extends FieldConfig{
|
||||||
id?:string,
|
|
||||||
name?:string,
|
|
||||||
data?:"__self" | string | Array<string>,
|
|
||||||
entity?:DataEntityType,
|
entity?:DataEntityType,
|
||||||
type?:Function,
|
type?:Function,
|
||||||
defaultValue?:any,
|
|
||||||
arrayOf?:DataEntityType,
|
|
||||||
isArray?:boolean,
|
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 {SaveEntityEvent} from "./events/save-entity.event";
|
||||||
export {RemoveEntitiesEvent} from "./events/remove-entities.event";
|
export {RemoveEntitiesEvent} from "./events/remove-entities.event";
|
||||||
export {EntityErrorEvent, EntityErrorTypes} from "./events/entity-error.event";
|
export {EntityErrorEvent, EntityErrorTypes} from "./events/entity-error.event";
|
||||||
|
export {FieldConfig} from "./entity/entity-field.config";
|
||||||
export {Field} from "./entity/entity-field";
|
export {Field} from "./entity/entity-field";
|
||||||
export {ApiCall} from "./entity/api-call.decorator";
|
export {ApiCall} from "./entity/api-call.decorator";
|
||||||
export {ApiCallBackendConfigInterface} from "./models/api-call-backend-config.interface";
|
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 {DataEntityConstructor, DataEntityType} from "../entity/data-entity.base";
|
||||||
import {Paris} from "../services/paris";
|
import {Paris} from "../services/paris";
|
||||||
import {DataAvailability} from "../dataset/data-availability.enum";
|
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 {DataCache} from "../services/cache";
|
||||||
import {Index} from "../models";
|
import {Index} from "../models";
|
||||||
import {EntityConfigBase, EntityGetMethod, IEntityConfigBase} from "../entity/entity-config.base";
|
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 {EntityErrorEvent, EntityErrorTypes} from "../events/entity-error.event";
|
||||||
import {catchError, map, mergeMap, tap} from "rxjs/operators";
|
import {catchError, map, mergeMap, tap} from "rxjs/operators";
|
||||||
import {IReadonlyRepository} from "./repository.interface";
|
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>{
|
export class ReadonlyRepository<TEntity extends ModelBase<TRawData>, TRawData = object> implements IReadonlyRepository<TEntity>{
|
||||||
protected _errorSubject$: Subject<EntityErrorEvent>;
|
protected _errorSubject$: Subject<EntityErrorEvent>;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче