UNPKG

lemon-core

Version:
615 lines (614 loc) 18.9 kB
/** * `abstract-service.ts` * - common service design pattern to build micro-service backend. * * @author Tim Hong <tim@lemoncloud.io> * @date 2021-02-23 initial version * @author Steve <steve@lemoncloud.io> * @date 2022-02-18 optimized search w/ ES6.8 * @date 2022-02-22 optimized w/ `lemon-core#3.0` and `@elastic/elasticsearch` * @date 2022-02-24 use `$id` in elastic-search as `_id` in dynamo-table. * @date 2022-03-15 optimized w/ `AbstractProxy` * @date 2022-03-17 optimized w/ `lemon-core#3.0.2` and use `env.ES6_DOCTYPE` * @date 2022-03-31 optimized w/ unit test spec. * @date 2022-05-19 optimized `CacheService` w/ typed key. * * @origin see `lemon-accounts-api/src/service/core-service.ts` * @copyright (C) 2021 LemonCloud Co Ltd. - All Rights Reserved. */ import { AbstractManager, APIHeaders, ApiHttpProxy, CacheService, CoreModel, CoreModelFilterable, DynamoOption, DynamoStreamEvent, Elastic6Option, Elastic6QueryService, Elastic6Service, GeneralKeyMaker, NextContext, NextIdentityAccess, SearchBody, StorageMakeable } from '../cores/'; import { credentials, CrendentialForAWS } from '../environ'; import elasticsearch from '@elastic/elasticsearch'; /** * authentication helper - get identity-id from context * @param context the current request context. */ export declare function asIdentityId(context: NextContext): string | undefined; /** * extract field names from models * - only fields start with lowercase, or all upper. */ export declare const filterFields: (fields: string[], base?: string[]) => string[]; /** * interface `ModelSynchronizer` */ export interface ModelSynchronizer<T extends CoreModel<string> = CoreModel<string>> { /** * callback for filtering items * @param id * @param item */ filter?(id: string, item: T): boolean; /** * callback invoked before synchronization * @param id * @param eventName * @param item * @param diff * @param prev */ onBeforeSync?(id: string, eventName: DynamoStreamEvent, item: T, diff?: string[], prev?: T): Promise<void>; /** * callback invoked after synchronization * @param id * @param eventName * @param item * @param diff * @param prev */ onAfterSync?(id: string, eventName: DynamoStreamEvent, item: T, diff?: string[], prev?: T): Promise<void>; } /** * abstract class `CoreService` * - common abstract to build user service * * @abstract */ export declare abstract class CoreService<Model extends CoreModel<ModelType>, ModelType extends string> extends GeneralKeyMaker<ModelType> implements StorageMakeable<Model, ModelType> { /** dynamo table name */ readonly tableName: string; /** global index name of elasticsearch */ readonly idName: string; /** (optional) current timestamp */ protected current: number; /** * constructor * @param tableName target table-name (or .yml dummy file-name) * @param ns namespace of dataset * @param idName must be `_id` unless otherwise */ protected constructor(tableName?: string, ns?: string, idName?: string); /** * override current time */ setCurrent: (current: number) => number; /** * get the current dynamo-options. */ get dynamoOptions(): DynamoOption; /** * create storage-service w/ fields list. */ makeStorageService<T extends Model>(type: ModelType, fields: string[], filter: CoreModelFilterable<T>): import("../cores/").TypedStorageService<T, ModelType>; } /** * class: `CoreManager` * - shared core manager for all model * * @abstract */ export declare abstract class CoreManager<Model extends CoreModel<ModelType>, ModelType extends string, Service extends CoreService<Model, ModelType>> extends AbstractManager<Model, Service, ModelType> { /** * constructor * @protected */ protected constructor(type: ModelType, parent: Service, fields: string[], uniqueField?: string); /** * say hello() */ hello: () => string; /** * get existence of model * @param id */ exists(id: string): Promise<boolean>; /** * find model - retrieve or null * @param id model-id */ find(id: string): Promise<Model | null>; /** * get model by key * @param key global id(like primary-key) */ findByKey(key: string): Promise<Model | null>; /** * batch get models * - retrieve multi models per each id * - must be matched with idList in sequence order. * * @param idList list of id * @param parrallel (optional) in parrallel size */ getMulti(idList: string[], parrallel?: number): Promise<(Model | null)[]>; /** * batch get models in map by idName */ getMulti$(idList: string[], idName?: string, parrallel?: number): Promise<{ [id: string]: Model; }>; /** * get by unique field value * @param uniqueValue */ getByUniqueField(uniqueValue: string): Promise<Model>; /** * find model by unique field value - retrieve or null * @param uniqueValue */ findByUniqueField(uniqueValue: string): Promise<Model | null>; /** * prepare model * - override `AbstractManager.prepare()` */ prepare(id: string, $def?: Model, isCreate?: boolean): Promise<Model>; /** * update model * - override 'AbstractManager.insert()' * * @deprecated use `AbstractProxy` */ insert(model: Model, initSeq?: number): Promise<Model>; /** * create or update model * @param id model id * @param model model data */ save(id: string, model: Model): Promise<Model>; /** * update model * - override 'AbstractManager.update()' */ update(id: string, model: Model, $inc?: Model): Promise<Model>; /** * update or create model * - override 'AbstractManager.updateOrCreate()' */ updateOrCreate(id: string, model: Model, $inc?: Model): Promise<Model>; /** * delete model * - override 'AbstractManager.delete()' */ delete(id: string, destroy?: boolean): Promise<Model>; /** * prepare default-model when creation * @param $def base-model */ protected prepareDefault($def: Model): Model; /** * update lookup and delete old one if exists */ protected updateLookup(id: string, model: Model, $org?: Model): Promise<void>; } /** * proxy of manager * - save model internally, and update only if changed properties. * Model extends CoreModel<ModelType>, ModelType extends string */ export declare class ManagerProxy<Model extends CoreModel<ModelType>, Manager extends CoreManager<Model, ModelType, CoreService<Model, ModelType>>, ModelType extends string = string> { readonly $mgr: Manager; constructor(proxy: AbstractProxy<string, CoreService<Model, ModelType>>, mgr: Manager); /** * store the origin model. * - `null` means `404 not found` */ protected readonly _org: { [key: string]: Model; }; /** * store the updated one. */ protected readonly _new: { [key: string]: Model; }; /** * get storage linked. */ get storage(): import("../cores/").TypedStorageService<Model, ModelType>; /** * read the origin node (cloned not to change). */ org(id: string, raw?: boolean): Model; /** * check if already read. */ has(id: string): boolean; /** * read the node. * @param id object-id * @param defaultOrThrow (optional) create if not exists, or flag to throw error */ get(id: string, defaultOrThrow?: Model | boolean): Promise<Model>; /** * 객체 정규화 시킴. * - null 에 대해서는 특별히 처리. */ normal: (N: Model) => Model; /** * override w/ model * @param $org the origin model by `.get(id)` * @param model the new model. */ override: ($org: Model, model: Model) => Model; /** * update the node. */ set(id: string, model: Model): Promise<Model>; /** * increment the field of Object[id] * !WARN! this incremented properties should NOT be updated later. */ inc(id: string, model: Model): Promise<Model>; /** * get all the updated node. * * @param onlyUpdated flag to return the only updated set. (useful to check whether to update really!) */ alls(onlyUpdated?: boolean, onlyValid?: boolean): { [key: string]: Model; }; } /** * class: `AbstractProxy` * - common abstract based class for Proxy */ export declare abstract class AbstractProxy<U extends string, T extends CoreService<CoreModel<U>, U>> { /** parrallel factor */ readonly parrallel: number; /** (internal) current context */ readonly context: NextContext; /** (internal) backend-service */ readonly service: T; /** (internal) cache service instance */ readonly cache?: CacheService; /** * constructor of proxy. * @param service user service instance * @param parrallel parrallel count (default 2) * @param cacheScope prefix of cache-key (like `lemon:SS:` or `lemon:SS:user`) */ constructor(context: NextContext, service: T, parrallel?: number, cacheScope?: string); /** * say hello(). */ hello: () => string; /** * list of manager-proxy */ protected _proxies: ManagerProxy<any, any>[]; /** * get all proxies in list. */ protected get allProxies(): ManagerProxy<any, any, string>[]; /** * register this. * * @return size of proxies. */ register(mgr: ManagerProxy<any, any>): number; /** * save all updates by each proxies. * - 업데이트할 항목을 모두 저장함 * * @param options running parameters. */ saveAllUpdates(options?: { /** (optional) the parrallel factor */ parrallel?: number; /** (optional) flag to use only valid value (not null) (default true) */ onlyValid?: boolean; }): Promise<any[]>; /** * report via slack. */ report: (title: string, data: any) => Promise<string>; /** * featch identity-acess from `lemon-accounts-api` * * @deprecated useless anymore since 3.2.10 */ protected fetchIdentityAccess(identityId: string, domain?: string): Promise<{ identityId: string; $identity: NextIdentityAccess<any>; }>; /** * the cached identity model * * @deprecated useless anymore since 3.2.10 */ protected _identity: { [key: string]: NextIdentityAccess; }; /** * fetch(or load) identity. * * @param identityId id to find * @param force (optional) force to reload if not available * @returns the cached identity-access * * @deprecated useless anymore since 3.2.10 */ getIdentity$(identityId: string, force?: boolean): Promise<NextIdentityAccess>; /** * get current identity-id */ getCurrentIdentityId(throwable?: boolean): Promise<string>; /** * get the current identity object (or throw access-error) * * @deprecated useless anymore since 3.2.10 */ getCurrentIdentity$(throwable?: boolean): Promise<NextIdentityAccess>; } /** * type `SearchResult` */ export interface SearchResult<T, U = any> { /** * total count of items searched */ total: number; /** * item list */ list: T[]; /** * pagination cursor */ last?: string[]; /** * aggregation result */ aggregations?: U; } /** * class `Elastic6Synchronizer` * - listen DynamoDBStream events and index into Elasticsearch */ export declare class Elastic6Synchronizer { /** * model synchronizer map * @private */ private readonly synchronizerMap; /** * default model synchronizer * @private */ private readonly defModelSynchronizer; /** * constructor * @param elastic elastic6-service instance * @param dynamoOptions dynamo options */ constructor(elastic: Elastic6Service, dynamoOptions: DynamoOption | { tableName: string; }); /** * set synchronizer for the model * @param type the model-type * @param handler (optional) custom synchronizer. */ enableSynchronization(type: string, handler?: ModelSynchronizer): void; /** * internal callback for filtering * @private */ private filter; /** * internal callback on before synchronization * @private */ private onBeforeSync; /** * internal callback on after synchronization * @private */ private onAfterSync; } /** * type: `Elastic6ContructParams` * - params for `new Elastic6Instance()` */ export interface Elastic6ContructParams { /** url endpoint */ endpoint: string; /** name of index */ indexName: string; /** ES engine version(6.2 ~ 7.x) */ esVersion: string; /** doc-type (only valid under 6.2) */ esDocType: string; /** dynamo table-name to synchronize */ tableName: string; /** (optional) field to make auto-complele */ autocompleteFields?: string[]; } /** * type: `Elastic6SearchParams` */ export interface Elastic6SearchParams { /** index-name */ indexName?: string; /** search-type */ searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'; /** (optional) signature to check the request */ signature?: string; } /** * class `AbstractElastic6Instance` * - to manipulate the shared Elasticsearch resources. */ export declare abstract class AbstractElastic6Instance { /** * Elasticsearch client */ readonly client?: elasticsearch.Client; /** * Elastic6Service instance */ readonly elastic?: Elastic6Service; /** * Elastic6QueryService instance */ readonly query?: Elastic6QueryService<any>; /** * Elastic6Synchronizer instance */ readonly synchronizer?: Elastic6Synchronizer; /** * default constructor */ constructor({ endpoint, indexName, esVersion, esDocType, tableName, autocompleteFields, }: Elastic6ContructParams); /** say hello */ abstract hello(): string; /** * read the current elastic6-option. */ get options(): Elastic6Option; /** * create Elasticsearch index w/ custom settings */ createIndex(): Promise<any>; /** * destroy Elasticsearch index */ destroyIndex(): Promise<any>; /** * display index settings and mappings */ describeIndex(): Promise<any>; /** * multi get * @param _ids _id list */ mget<T>(_ids: string[]): Promise<(T | null)[]>; /** * search raw query * @param body Elasticsearch Query DSL * @param params see 'search_type' in Elasticsearch documentation */ search<T>(body: any, params?: Elastic6SearchParams): Promise<SearchResult<T>>; /** * create async generator that yields items queried until last * @param body Elasticsearch Query DSL * @param searchType see 'search_type' in Elasticsearch documentation */ generateSearchResult<T>(body: any, searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'): AsyncGenerator<T[], void, unknown>; } /** * class: `Elastic6Instance` * - default agent to handle search. */ export declare class Elastic6Instance extends AbstractElastic6Instance { constructor(params: Elastic6ContructParams); hello(): string; /** * expose the internal helpers. (ONLY for debugging) */ get $X(): { /** * describe (or parse) the endpoint url * - it detects if endpoint is the proxied search. * - it detects if endpoint needs the tunneling. * * @param url */ describeEndpointUrl: (url: string, options?: { throwable?: boolean; errScope?: string; }) => { /** protocol */ protocol: string; /** host name */ host: string; /** port number */ port: number; /** region in cloud */ region: string; /** flag to use tunneling w/ ssh */ isTunnel: boolean; /** flag to use proxy */ isProxy: boolean; }; /** * create http-web-proxy agent which using endpoint as proxy server. * - originally refer to `createHttpWebProxy()` * * # as cases. * as proxy agent: GET <endpoint>/<host?>/<path?> * as direct agent: GET <endpoint>/<id?>/<cmd?> * * @param endpoint service url (ex: `https://xyz.execute-api.~/dev/search/0/proxy`) * @param options optionals parameters. */ createHttpSearchProxy: (endpoint: string, options?: { /** client-name (default `env.NAME`) */ name?: string; /** headers */ headers?: APIHeaders; /** path encoder (default encodeURIComponent) */ encoder?: (name: string, path: string) => string; /** relay-key in headers for proxy. */ relayHeaderKey?: string; /** resultKey in response */ resultKey?: string; /** credentials to load */ credentials?: CrendentialForAWS; /** region */ region?: string; }) => ApiHttpProxy; }; } /** * from Elasticsearch document to model item * - replace the elastic's `$id` field to `_id` of dynamo-table. * * @param _source from elastic-search * @param idName (optional) global id of elastic. (default is `$id`) */ export declare function sourceToItem<T>(_source: T, idName?: string): T; /** * `SearchProxyBody` * - the body to `/search/0/proxy` */ export interface SearchProxyBody { /** the main search-body */ body: SearchBody; /** service-name of request (ex: `lemon-sandbox-api`) */ service?: string; /** index name to search via proxy (must be same as `env.ES6_INDEX`) */ index?: string; /** (optional) signature to validate this request. */ signature?: string; } /** * (internal) factory function for `$ES6` * * @param options (optional) for debugging */ export declare const _ES6: (options?: { /** endpoint url */ endpoint?: string; /** index-name */ indexName?: string; /** flag to use search-proxy */ useProxy?: boolean; /** (optional) aws:credentials to sign request in proxy */ credentials?: CrendentialForAWS; }) => Elastic6Instance; /** * const `$ES6` * - default instance as a singleton by env configuration. */ export declare const $ES6: Elastic6Instance;