lemon-core
Version:
Lemon Serverless Micro-Service Platform
506 lines (505 loc) • 16.6 kB
TypeScript
import { Elastic6SimpleQueriable, CoreModel } from 'lemon-model';
import { StorageService } from './storage-service';
import { GeneralAPIController } from '../../controllers/general-api-controller';
/**
* class: `CoreKey`
* - to represent internal partition-key and model-id.
*/
export interface CoreKey<ModelType extends string> {
/**
* defualt internal partition-key
*/
_id?: string;
/**
* namespace
*/
ns?: string;
/**
* model-id
*/
id: string;
/**
* model-type
*/
type: ModelType;
}
/**
* class: `CoreKeyMakeable`
* - make internal key by type + id.
*/
export interface CoreKeyMakeable<ModelType extends string> {
/**
* get key object w/ internal partition-key (default as _id).
*
* @param type type of model
* @param id id of model in type
*/
asKey$(type: ModelType, id: string): CoreKey<ModelType>;
/**
* get key-string to use for query. (USED FOR CUSTOM ID NAME);
*
* @param type type of model
* @param id id of model in type
*/
asKey?(type: ModelType, id: string): string;
}
/**
* type: ModelFilter
*
* @param model the new model to update
* @param origin the old model (or 2nd model)
* @return the updated model.
*/
export declare type CoreModelFilter<T> = (model: T, origin?: T) => T;
/**
* class: `CoreModelFilterable`
* - support filters.
*/
export interface CoreModelFilterable<T> {
afterRead: CoreModelFilter<T>;
beforeSave: CoreModelFilter<T>;
afterSave: CoreModelFilter<T>;
beforeUpdate: CoreModelFilter<T>;
afterUpdate: CoreModelFilter<T>;
}
/**
* class: `StorageMakeable`
* - makeable of `TypedStorageService`
*/
export interface StorageMakeable<T extends CoreModel<ModelType>, ModelType extends string> {
/**
* create storage-service w/ fields list.
* @param type type of model
* @param fields list of field (properties)
* @param filter filter of model.
*/
makeStorageService(type: ModelType, fields: string[], filter: CoreModelFilterable<T>): TypedStorageService<T, ModelType>;
}
/**
* class: `GeneralKeyMaker`
* - use ':' as delimiter to join [ns, type, id]
*/
export declare class GeneralKeyMaker<ModelType extends string> implements CoreKeyMakeable<ModelType> {
readonly NS: string;
readonly DELIMITER: string;
constructor(ns?: string, delimiter?: string);
asKey$(type: ModelType, id: string): {
ns: string;
id: string;
type: ModelType;
_id: string;
};
}
/**
* class: `GeneralModelFilter`
* - general model-filter with differential update.
* - to customize, override this class.
*/
export declare class GeneralModelFilter<T extends CoreModel<ModelType>, ModelType extends string> implements CoreModelFilterable<T> {
readonly FIELDS: string[];
/**
* default constructor
*/
constructor(fields: string[]);
/**
* parse `.meta` to json
* @param model the current model
* @param origin the origin model
*/
afterRead(model: T, origin?: T): T;
/**
* filter for before saving.
* - make sure data conversion
* - move the unknown fields to `.meta`.
*
* @param model the current model
* @param origin the origin model
*/
beforeSave(model: T, origin?: T): T;
/**
* called after saving the model.
* - parse `.meta` back to json object.
*
* @param model the saved model
* @param origin the origin model.
*/
afterSave(model: T, origin?: T): T;
/**
* called before updating the model.
* @param model the updated model
* @param incrementals (optional) incremental fields.
*/
beforeUpdate(model: T, incrementals?: T): T;
/**
* called after updating the model.
* @param model the updated model
*/
afterUpdate(model: T): T;
/**
* override this `onBeforeSave()` in sub-class.
* @param model the current model
* @param origin (optional) the origin model
*/
onBeforeSave(model: T, origin?: T): T;
}
/**
* class: `ProxyStorageService`
* - support `nextSeq()`, `doLock()`, `doRelease()`
* - proxed storage-service to wrap the parent storage-service w/ more features.
* - table is supposed to have internal-key as `_id` string.
*
* **Usage**
* ```js
* type MyType = '' | 'test';
* interface MyModel extends CoreModel<MyType>{
* name?: string;
* }
* const storage = new ProxyStorageService<MyModel, MyType>(this, 'TestTable', ['id','name']);
* const $test = storage.makeTypedStorageService('test');
* ```
*/
export declare class ProxyStorageService<T extends CoreModel<ModelType>, ModelType extends string> implements StorageService<T> {
static readonly AUTO_SEQUENCE = 1000000;
static readonly TYPE_SEQUENCE = "sequence";
readonly idName: string;
readonly service: CoreKeyMakeable<ModelType>;
readonly storage: StorageService<T>;
readonly filters: CoreModelFilterable<T>;
/**
* create proxed storage-service.
*
* @param service service to support `CoreKeyMakeable`
* @param storage table-name or the parent storage-service
* @param fields list of fields.
* @param filters filters of `CoreModelFilterable`
* @param idName (optional) internal partition-key (default as '_id')
*/
constructor(service: CoreKeyMakeable<ModelType>, storage: StorageService<T> | string, fields: string[], filters?: CoreModelFilterable<T>, idName?: string);
/**
* factory function to create this `proxy-storage-service`
* @param service key-makeable
* @param table table-name
* @param fields list of fields.
* @param filters model filter.
* @param idName (optional) internal partition-key (default as '_id')
*/
static create<T extends CoreModel<ModelType>, ModelType extends string>(service: CoreKeyMakeable<ModelType>, table: string, fields?: string[], filters?: CoreModelFilterable<T>, idName?: string): ProxyStorageService<T, ModelType>;
/**
* say hello()
*/
hello: () => string;
/**
* read by _id
*/
read: (_id: string) => Promise<T>;
/**
* read or create by _id
*/
readOrCreate: (_id: string, model: T) => Promise<T>;
/**
* save by _id
*/
save: (_id: string, model: T) => Promise<T>;
/**
* update by _id
*/
update: (_id: string, model: T, incrementals?: T) => Promise<T>;
/**
* increment by _id
*/
increment: (_id: string, model: T, $update?: T) => Promise<T>;
/**
* delete by _id
*/
delete: (_id: string) => Promise<T>;
/**
* get key-id by type+id
*/
asKey: (type: ModelType, id: string | number) => string;
/**
* get next auto-sequence number.
*
* @param type type of seqeunce.
* @param nextInit (optional) initial next value if not exist.
* @param nextStep (optional) the incremental step to get next. (default 1)
*/
nextSeq(type: ModelType, nextInit?: number, nextStep?: number): Promise<number>;
/**
* get uuid by type.
* @param type
*/
nextUuid(type?: ModelType): Promise<string>;
/**
* timer to generate the current-time (msec)
*/
private $timer;
setTimer: (timer: () => number) => () => number;
getTime: () => number;
/**
* get time-stamp as now.
*/
asTime(currentTime?: number): {
createdAt: number;
updatedAt: number;
deletedAt: number;
};
/**
* delete sequence-key.
* @param type type of seqeunce.
*/
clearSeq(type: ModelType): Promise<void>;
/**
* read model by key + id with optional auto creation.
*
* @param type model-type
* @param id node-id
* @param $create (optional) initial model if not exist. (or throw 404 error)
*/
doRead(type: ModelType, id: string, $create?: T): Promise<T>;
/**
* delete model by id.
*
* @param type model-type
* @param id node-id
* @param destroy flag to destroy (real delete)
*/
doDelete(type: ModelType, id: string, destroy?: boolean): Promise<T>;
/**
* update model (or it will create automatically)
*
* @param type model-type
* @param id node-id
* @param node model
* @param incrementals (optional) fields to increment
*/
doUpdate(type: ModelType, id: string, node: T, incrementals?: T): Promise<T>;
/**
* update model (or it will create automatically)
*
* @param type model-type
* @param id node-id
*/
doIncrement(type: ModelType, id: string, $inc: T, $up: T): Promise<T>;
/**
* save model by checking origin node.
* - use `doSave()` rather than `doUpdate()` for both create & update.
* - if `$create` is null, throw 404 error it if not found.
*
* @param type model-type
* @param id node-id
* @param node node to save (or update)
* @param $create (optional) initial creation model if not found.
*/
doSave(type: ModelType, id: string, node: T, $create?: T): Promise<T>;
/**
* lock data-entry by type+id w/ limited time tick
* - WARN! must release lock by `doRelease()`
*
* `total-waited-time = tick * interval (msec)`
*
* @param type model-type
* @param id model-id
* @param tick tick count to wait.
* @param interval timeout interval per each tick (in msec, default 1000 = 1sec)
*/
doLock(type: ModelType, id: string, tick?: number, interval?: number): Promise<boolean>;
/**
* release lock by resetting lock = 0.
*
* @param type model-type
* @param id model-id
*/
doRelease(type: ModelType, id: string): Promise<boolean>;
/**
* create storage-service w/ fields list.
* - idName should be `_id`
*
* @param table table-name or dummy file name (ex: `dummy-data.yml`).
* @param fields required for dynamo table.
* @param idName internal partition-key name (default '_id')
*/
static makeStorageService<T>(table: string, fields?: string[], idName?: string): StorageService<T>;
/**
* create proxy-storage-service by type
* @param type model-type
*/
makeTypedStorageService<U extends T>(type: ModelType): TypedStorageService<U, ModelType>;
}
/**
* class: `TypedStorageService`
* - wrap id with type + id.
*/
export declare class TypedStorageService<T extends CoreModel<ModelType>, ModelType extends string> implements StorageService<T> {
readonly type: ModelType;
readonly storage: ProxyStorageService<T, ModelType>;
constructor(service: ProxyStorageService<T, ModelType>, type: ModelType);
/**
* show self service name
*/
hello: () => string;
/**
* get next auto-sequence id in number like `1000003`.
*/
nextId: () => Promise<number>;
/**
* get uuid like `d01764cd-9ef2-41e2-9e88-68e79555c979`
*/
nextUuid: () => Promise<string>;
/**
* read model by key + id with optional auto creation.
* - throws '404 NOT FOUND' if not found.
*
* @param id node-id
*/
read: (id: string | number) => Promise<T>;
/**
* read model by key + id with optional auto creation.
*
* @param id node-id
* @param model initial model if not exist. (or throw 404 error)
*/
readOrCreate: (id: string | number, model: T) => Promise<T>;
/**
* update model (or it will create automatically)
*
* @param id node-id
* @param model model to update
* @param incrementals (optional) fields to increment.
*/
update: (id: string | number, model: T, incrementals?: T) => Promise<T>;
/**
* insert model w/ auto generated id
*
* @param model model to insert
*/
insert: (node: T) => Promise<T>;
/**
* update model (or it will create automatically)
*
* ```ts
* //before: { count: 1 };
* const res = await storage.increment(1, { count: 2 }, { total: 2 });
* //after : { count: 3, total: 2 }
* ```
*
* @param id node-id
* @param $increments model only with numbers
*/
increment: (id: string | number, $increments: T, $update?: T) => Promise<T>;
/**
* delete model by id.
*
* @param id node-id
* @param destroy flag to destroy (real delete)
*/
delete: (id: string | number, destroy?: boolean) => Promise<T>;
/**
* save model by checking origin node.
* - use `doSave()` rather than `doUpdate()` for both create & update.
* - if `$create` is null, throw 404 error it if not found.
*
* @param id node-id
* @param node node to save (or update)
* @param $create (optional) initial creation model.
*/
save: (id: string | number, model: T, $create?: T) => Promise<T>;
/**
* lock data-entry by type+id w/ limited time tick
* - WARN! must release lock by `release(id)`
*
* `total-waited-time = tick * interval (msec)`
*
* **[UPDATES]**
* 1. read original node (or, throw 404 error)
* 2. use internal lock.
*
* @param id model-id to lock
* @param tick tick count to wait.
* @param interval timeout interval per each tick (in msec, default 1000 = 1sec)
*/
lock: (id: string | number, tick?: number, interval?: number) => Promise<boolean>;
/**
* release lock by resetting lock = 0.
* @param id model-id
*/
release: (id: string | number) => Promise<boolean>;
/**
* using `lock()`, guard func with auto lock & release.
*
* ```ts
* const res = await storage.guard(async ()=>{
* return 'abc';
* });
* // res === 'abc'
* ```
*/
guard: <T_1>(id: string | number, handler: () => T_1 | Promise<T_1>, tick?: number, interval?: number) => Promise<any>;
/**
* make `UniqueFieldManager` for field.
*/
makeUniqueFieldManager: (field: string) => UniqueFieldManager<T, ModelType>;
/**
* make `GeneralAPIController` for REST API w/ supporting basic CRUD
*/
makeGeneralAPIController: (search?: Elastic6SimpleQueriable<any>, uniqueField?: string) => GeneralAPIController<this, string>;
}
/**
* class: `ModelUtil`
* - Helper functions for model.
*/
export declare class ModelUtil {
static selfRead: <T>(self: any, key: string, defValue?: T) => T;
static selfPop: <T>(self: any, key: string, defValue?: T) => T;
/**
* attach `.pop()` method to object.
*
* ```js
* const data = CoreModelUtil.buildPop({'a':1});
* assert( 1 === data.pop('a) );
* const final = data.pop();
* assert( final == data );
*/
static buildPop: (thiz: any, popName?: string) => any;
}
/**
* class: `UniqueFieldManager`
* - support `.{field}` is unique in typed-storage-service.
* - make lookup data entry to save the reverse mapping to origin id.
* - set `.stereo` as '#' to mark as lookup. (to filter out from Elastic.search())
* - set `.id` as `#{field}/{name}` or `#{name}`.
* - set `.meta` as origin id.
*/
export declare class UniqueFieldManager<T extends CoreModel<ModelType>, ModelType extends string> {
readonly type: ModelType;
readonly field: string;
readonly storage: TypedStorageService<T, ModelType>;
constructor(storage: TypedStorageService<T, ModelType>, field?: string);
hello: () => string;
/**
* validate value format
* - just check empty string.
* @param value unique value in same type+field.
*/
validate(value: string): boolean;
/**
* convert to internal id by value
* @param value unique value in same type group.
*/
asLookupId(value: string): string;
/**
* lookup model by value
* - use `.meta` property to link with the origin.
* - mark `.stereo` as to '#' to distinguish normal.
*
* @param value unique value in same type group.
* @param $creates (optional) create-set if not found.
*/
findOrCreate(value: string, $creates?: T): Promise<T>;
/**
* update lookup table (or create)
*
* @param model target model
* @param value (optional) new value of model.
*/
updateLookup(model: T, value?: string): Promise<T>;
}