зеркало из https://github.com/microsoft/paris.git
Added parseDataSet to entity configuration
This commit is contained in:
Родитель
dd4f567d3c
Коммит
e7f7f5a34a
|
@ -2,5 +2,6 @@ export interface DataSet<T>{
|
|||
count:number,
|
||||
items:Array<T>,
|
||||
next?:string,
|
||||
previous?:string
|
||||
previous?:string,
|
||||
meta?:object
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import {EntityConfigBase} from "./entity-config.base";
|
|||
import {ModelBase} from "../models/model.base";
|
||||
import {EntityId} from "../models/entity-id.type";
|
||||
|
||||
export interface DataEntityConstructor<TEntity extends ModelBase, TRawData = any, TId extends EntityId = string> extends DataEntityType<TEntity, TRawData, TId>{
|
||||
export interface DataEntityConstructor<TEntity extends ModelBase, TRawData = any, TId extends EntityId = string, TDataSet = any> extends DataEntityType<TEntity, TRawData, TId, TDataSet>{
|
||||
new(data?:any, rawData?:TRawData): TEntity
|
||||
}
|
||||
|
||||
export interface DataEntityType<TEntity extends ModelBase = any, TRawData = any, TId extends EntityId = string>{
|
||||
export interface DataEntityType<TEntity extends ModelBase = any, TRawData = any, TId extends EntityId = string, TDataSet = any>{
|
||||
new(data?:EntityModelConfigBase, rawData?:TRawData):TEntity,
|
||||
singularName?:string,
|
||||
pluralName?:string,
|
||||
|
|
|
@ -5,10 +5,10 @@ import {DataQuery} from "../dataset/data-query";
|
|||
import {HttpOptions, RequestMethod} from "../services/http.service";
|
||||
import {ModelBase} from "../models/model.base";
|
||||
import {ApiCallBackendConfigInterface} from "../models/api-call-backend-config.interface";
|
||||
import {EntityModelBase} from "../models/entity-model.base";
|
||||
import {EntityId} from "../models/entity-id.type";
|
||||
import {DataSet} from "../dataset/dataset";
|
||||
|
||||
export class ModelEntity<TEntity extends ModelBase = any, TRawData = any, TId extends EntityId = string> extends EntityConfigBase<TEntity, TRawData, TId> implements EntityConfig<TEntity, TRawData, TId> {
|
||||
export class ModelEntity<TEntity extends ModelBase = any, TRawData = any, TId extends EntityId = string, TDataSet = any> extends EntityConfigBase<TEntity, TRawData, TId> implements EntityConfig<TEntity, TRawData, TId> {
|
||||
endpoint:EntityConfigFunctionOrValue;
|
||||
loadAll?:boolean = false;
|
||||
cache?:boolean | ModelEntityCacheConfig<TEntity>;
|
||||
|
@ -17,6 +17,7 @@ export class ModelEntity<TEntity extends ModelBase = any, TRawData = any, TId ex
|
|||
allItemsEndpoint?:string;
|
||||
allItemsEndpointTrailingSlash?:boolean;
|
||||
parseDataQuery?:(dataQuery:DataQuery) => { [index:string]:any };
|
||||
parseDataSet?:(dataSet:TDataSet) => DataSet<TRawData>;
|
||||
parseItemQuery?:(itemId:EntityId, entity?:IEntityConfigBase<TEntity, TRawData, TId>, config?:ParisConfig, params?:{ [index:string]:any }) => string;
|
||||
parseSaveQuery?:(item:TEntity, entity?:IEntityConfigBase, config?:ParisConfig) => string;
|
||||
parseRemoveQuery?:(items:Array<TEntity>, entity?:IEntityConfigBase, config?:ParisConfig) => string;
|
||||
|
@ -35,11 +36,12 @@ export class ModelEntity<TEntity extends ModelBase = any, TRawData = any, TId ex
|
|||
export interface EntityConfig<
|
||||
TEntity extends ModelBase,
|
||||
TRawData = any,
|
||||
TId extends EntityId = string>
|
||||
extends IEntityConfigBase<TEntity, TRawData, TId>, EntityBackendConfig<TEntity, TRawData, TId>
|
||||
TId extends EntityId = string,
|
||||
TDataSet = any>
|
||||
extends IEntityConfigBase<TEntity, TRawData, TId>, EntityBackendConfig<TEntity, TRawData, TId, TDataSet>
|
||||
{ }
|
||||
|
||||
export interface EntityBackendConfig<TEntity extends ModelBase, TRawData = any, TId extends EntityId = string> extends ApiCallBackendConfigInterface{
|
||||
export interface EntityBackendConfig<TEntity extends ModelBase, TRawData = any, TId extends EntityId = string, TDataSet = any> extends ApiCallBackendConfigInterface{
|
||||
/**
|
||||
* If true, all the Entity's items are fetched whenever any is needed, and then cached so subsequent requests are retrieved from cache rather than backend.
|
||||
* This makes sense to use for Entities whose values are few and not expected to change, such as enums.
|
||||
|
@ -101,6 +103,18 @@ export interface EntityBackendConfig<TEntity extends ModelBase, TRawData = any,
|
|||
*/
|
||||
parseDataQuery?:(dataQuery:DataQuery) => { [index:string]:any },
|
||||
|
||||
/**
|
||||
* For query results, Paris accepts either an array of items or an object. That object may contain properties such as 'count', 'next' and 'previous'.
|
||||
* `parseDataSet`, if available, receives the object as it was returned from the API and parses it to a DataSet interface, so the original properties are available in the DataSet.
|
||||
*
|
||||
* @example <caption>Parsing a DataSet from a raw object returned by the backend</caption>
|
||||
* ```typescript
|
||||
* parseDataSet: (rawDataSet:TodoRawDataSet) => ({ items: rawDataSet.todoItems, next: rawDataSet.$nextPage, count: rawDataSet.total })
|
||||
* ```
|
||||
* @param dataSet
|
||||
*/
|
||||
parseDataSet?:(dataSet:TDataSet) => DataSet<TRawData>;
|
||||
|
||||
/**
|
||||
* When getting an Entity from backend (when calling repository.getItemById), Paris follows the REST standard and fetches it by GET from /{the Entity's endpoint}/{ID}.
|
||||
* `parseItemQuery` allows to specify a different URL. This is useful if your API doesn't follow the REST standard.
|
||||
|
|
|
@ -2,8 +2,8 @@ import {EntityConfig, ModelEntity} from "./entity.config";
|
|||
import {DataEntityType} from "./data-entity.base";
|
||||
import {entitiesService} from "../services/entities.service";
|
||||
|
||||
export function Entity(config:EntityConfig<any, any, any>){
|
||||
return (target:DataEntityType<any, any, any>) => {
|
||||
export function Entity(config:EntityConfig<any, any, any, any>){
|
||||
return (target:DataEntityType<any, any, any, any>) => {
|
||||
let entity:ModelEntity = new ModelEntity(config, target.prototype.constructor);
|
||||
target.entityConfig = entity;
|
||||
target.singularName = config.singularName;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import {EntityModelBase} from "../models/entity-model.base";
|
||||
import {Entity} from "../entity/entity.decorator";
|
||||
import {EntityField} from "../entity/entity-field.decorator";
|
||||
|
||||
@Entity({
|
||||
singularName: "Todo list",
|
||||
pluralName: "Todo lists",
|
||||
endpoint: "list",
|
||||
parseDataSet: (rawDataSet:TodoListRawDataSet) => ({
|
||||
items: rawDataSet.lists,
|
||||
next: rawDataSet.$nextPage,
|
||||
count: rawDataSet.total,
|
||||
meta: {
|
||||
lastUpdate: new Date(rawDataSet.lastUpdate)
|
||||
}
|
||||
})
|
||||
})
|
||||
export class TodoList extends EntityModelBase<number>{
|
||||
@EntityField()
|
||||
name:string;
|
||||
}
|
||||
|
||||
interface TodoListRawDataSet {
|
||||
lists: Array<any>,
|
||||
$nextPage: string,
|
||||
total: number,
|
||||
lastUpdate: number
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { suite, test, slow, timeout } from "mocha-typescript";
|
||||
import "reflect-metadata"
|
||||
import * as chai from 'chai';
|
||||
import '../services/paris.init.spec';
|
||||
import {DataSet} from "../dataset/dataset";
|
||||
import {parseDataSet} from "./data-to-model";
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
const rawDataSet:RawDataSet = {
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: "First"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Seconds"
|
||||
}
|
||||
],
|
||||
$next: '/api/todolist?page=2',
|
||||
total: 123
|
||||
};
|
||||
|
||||
describe('Raw data -> model', () => {
|
||||
describe('Create a DataSet', () => {
|
||||
let dataSet:DataSet<SimpleEntity>;
|
||||
|
||||
before(() => {
|
||||
console.log("REFLECT", Reflect);
|
||||
dataSet = parseDataSet<SimpleEntity, RawDataSet>(rawDataSet, 'results', parseRawDataSet);
|
||||
});
|
||||
|
||||
it('has items', () => {
|
||||
expect(dataSet.items.length).to.equal(rawDataSet.results.length);
|
||||
});
|
||||
|
||||
it('has a next property', () => {
|
||||
expect(dataSet.next).to.equal(rawDataSet.$next);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
function parseRawDataSet(rawDataSet:RawDataSet):DataSet<SimpleEntity>{
|
||||
return {
|
||||
items: rawDataSet.results,
|
||||
next: rawDataSet.$next,
|
||||
count: rawDataSet.total
|
||||
}
|
||||
}
|
||||
|
||||
interface SimpleEntity{
|
||||
id:number,
|
||||
name:string
|
||||
}
|
||||
|
||||
interface RawDataSet {
|
||||
results: Array<SimpleEntity>,
|
||||
$next: string,
|
||||
total: number
|
||||
}
|
|
@ -8,46 +8,53 @@ import {DataSet} from "../dataset/dataset";
|
|||
import {ReadonlyRepository} from "./readonly-repository";
|
||||
import {map} from "rxjs/operators";
|
||||
|
||||
export function rawDataToDataSet<T extends ModelBase, R = any>(
|
||||
rawDataSet:any,
|
||||
entityConstructor:DataEntityConstructor<T>,
|
||||
const DEFAULT_ALL_ITEMS_PROPERTY = 'items';
|
||||
|
||||
export function rawDataToDataSet<TEntity extends ModelBase, TRawData = any, TDataSet extends any = any>(
|
||||
rawDataSet:TDataSet,
|
||||
entityConstructor:DataEntityConstructor<TEntity>,
|
||||
allItemsProperty:string,
|
||||
paris:Paris,
|
||||
dataOptions:DataOptions = defaultDataOptions,
|
||||
query?:DataQuery):Observable<DataSet<T>>{
|
||||
let rawItems: Array<R> = rawDataSet instanceof Array ? rawDataSet : rawDataSet[allItemsProperty];
|
||||
query?:DataQuery):Observable<DataSet<TEntity>>{
|
||||
let dataSet:DataSet<TRawData> = parseDataSet(rawDataSet, allItemsProperty, entityConstructor.entityConfig.parseDataSet);
|
||||
|
||||
if (!rawItems || !rawItems.length)
|
||||
return of({ count: 0, items: [] });
|
||||
if (!dataSet.items || !dataSet.items.length)
|
||||
return of({ count: 0, items: [] });
|
||||
|
||||
return modelArray<T, R>(rawItems, entityConstructor, paris, dataOptions, query).pipe(
|
||||
map((items:Array<T>) => {
|
||||
return Object.freeze({
|
||||
count: rawDataSet.count,
|
||||
items: items,
|
||||
next: rawDataSet.next,
|
||||
previous: rawDataSet.previous
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
return modelArray<TEntity, TRawData>(dataSet.items, entityConstructor, paris, dataOptions, query).pipe(
|
||||
map((items:Array<TEntity>) => {
|
||||
return Object.freeze(Object.assign(dataSet, {
|
||||
items: items,
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function modelArray<T extends ModelBase, R = any>(
|
||||
data:Array<any>,
|
||||
entityConstructor:DataEntityConstructor<T>,
|
||||
export function parseDataSet<TRawData = any, TDataSet extends any = any>(rawDataSet:TDataSet, allItemsProperty:string = DEFAULT_ALL_ITEMS_PROPERTY, parseDataSet?:(rawDataSet:TDataSet) => DataSet<TRawData>):DataSet<TRawData>{
|
||||
return rawDataSet instanceof Array
|
||||
? { count: 0, items: rawDataSet }
|
||||
: parseDataSet
|
||||
? parseDataSet(rawDataSet) || { count: 0, items: [] }
|
||||
: { count: rawDataSet.count, items: rawDataSet[allItemsProperty] };
|
||||
}
|
||||
|
||||
export function modelArray<TEntity extends ModelBase, TRawData = any>(
|
||||
rawData:Array<TRawData>,
|
||||
entityConstructor:DataEntityConstructor<TEntity>,
|
||||
paris:Paris,
|
||||
dataOptions:DataOptions = defaultDataOptions,
|
||||
query?:DataQuery):Observable<Array<T>>{
|
||||
if (!data.length)
|
||||
query?:DataQuery):Observable<Array<TEntity>>{
|
||||
if (!rawData.length)
|
||||
return of([]);
|
||||
else {
|
||||
const itemCreators: Array<Observable<T>> = data.map((itemData: R) =>
|
||||
modelItem<T, R>(entityConstructor, itemData, paris, dataOptions, query));
|
||||
const itemCreators: Array<Observable<TEntity>> = rawData.map((itemData: TRawData) =>
|
||||
modelItem<TEntity, TRawData>(entityConstructor, itemData, paris, dataOptions, query));
|
||||
|
||||
return combineLatest.apply(this, itemCreators);
|
||||
}
|
||||
}
|
||||
|
||||
export function modelItem<T extends ModelBase, R = any>(entityConstructor:DataEntityConstructor<T>, data:R, paris:Paris, dataOptions: DataOptions = defaultDataOptions, query?:DataQuery):Observable<T>{
|
||||
return ReadonlyRepository.getModelData(data, entityConstructor.entityConfig || entityConstructor.valueObjectConfig, paris.config, paris, dataOptions, query);
|
||||
export function modelItem<TEntity extends ModelBase, TRawData = any>(entityConstructor:DataEntityConstructor<TEntity>, rawData:TRawData, paris:Paris, dataOptions: DataOptions = defaultDataOptions, query?:DataQuery):Observable<TEntity>{
|
||||
return ReadonlyRepository.getModelData(rawData, entityConstructor.entityConfig || entityConstructor.valueObjectConfig, paris.config, paris, dataOptions, query);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"build": "gulp build",
|
||||
"dev": "rollup -c -w",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "mocha -r ts-node/register lib/**/*.spec.ts",
|
||||
"test": "mocha --opts ./test/mocha.opts",
|
||||
"docs": "typedoc --options typedocconfig.ts"
|
||||
},
|
||||
"author": "Yossi Kolesnicov",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"gulp-typescript": "^3.1.3",
|
||||
"husky": "^1.0.0-rc.13",
|
||||
"intl": "^1.2.5",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash-es": "4.17.10",
|
||||
"merge2": "^1.0.2",
|
||||
"mocha": "^5.2.0",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
--ui mocha-typescript
|
||||
lib/**/*.spec.ts
|
Загрузка…
Ссылка в новой задаче