@webda/core
Version:
Expose API with Lambda
1,008 lines (1,007 loc) • 27.3 kB
TypeScript
import { Counter, EventWithContext, Histogram } from "../core.js";
import { ConfigurationProvider, WebdaError } from "../index.js";
import { Constructor, CoreModel, CoreModelDefinition, FilterAttributes } from "../models/coremodel.js";
import { Service, ServiceParameters } from "../services/service.js";
import { OperationContext, WebContext } from "../utils/context.js";
import { HttpMethodType } from "../utils/httpcontext.js";
import { WebdaQL } from "./webdaql/query.js";
export declare class StoreNotFoundError extends WebdaError.CodeError {
constructor(uuid: string, storeName: string);
}
export declare class UpdateConditionFailError extends WebdaError.CodeError {
constructor(uuid: string, conditionField: string, condition: string | Date);
}
interface EventStore {
/**
* Target object
*/
object: CoreModel;
/**
* Object id
*/
object_id: string;
/**
* Store emitting
*/
store: Store;
/**
* Context of the operation
*/
context?: OperationContext;
}
/**
* Event called before save of an object
*/
export interface EventStoreSave extends EventStore {
}
/**
* Event called after save of an object
*/
export interface EventStoreSaved extends EventStoreSave {
}
/**
* Event called before delete of an object
*/
export interface EventStoreDelete extends EventStore {
}
/**
* Event called after delete of an object
*/
export interface EventStoreDeleted extends EventStoreDelete {
}
/**
* Event called on retrieval of an object
*/
export interface EventStoreGet extends EventStore {
}
/**
* Event called before action on an object
*/
export interface EventStoreAction extends EventWithContext {
/**
* Name of the action
*/
action: string;
/**
* Target object unless it is a global action
*/
object?: CoreModel;
/**
* Model of the object if global action
*/
model?: CoreModelDefinition;
/**
* Emitting store
*/
store: Store;
}
/**
* Event called after action on an object
*/
export interface EventStoreActioned extends EventStoreAction {
/**
* Result of the action
*/
result: any;
}
/**
* Event called before update of an object
*/
export interface EventStoreUpdate extends EventStore {
/**
* Update content
*/
update: any;
}
/**
* Event called after update of an object
*/
export interface EventStoreUpdated extends EventStoreUpdate {
/**
* Object uuid
*/
object_id: string;
/**
* Object before update
*/
previous: any;
}
/**
* Event called before patch update of an object
*/
export interface EventStorePatchUpdate extends EventStoreUpdate {
}
/**
* Event called after patch update of an object
*/
export interface EventStorePatchUpdated extends EventStoreUpdated {
}
/**
* Event called after partial update of an object
*/
export interface EventStorePartialUpdated<T extends CoreModel = CoreModel> {
/**
* Object uuid
*/
object_id: string;
/**
* Emitting store
*/
store: Store<T>;
/**
* Update date
*/
updateDate?: Date;
/**
* Info on the update
*/
partial_update: {
/**
* If incremental update
*/
increments?: {
/**
* Increment value
*/
value: number;
/**
* Property to increment
*/
property: string;
}[];
/**
* Add item to a collection
*/
addItem?: {
/**
* Item to add
*/
value: any;
/**
* Collection name
*/
property: string;
/**
* Index to add
*/
index: number;
};
/**
* If this is a patch
*/
patch?: any;
/**
* Delete an item from collection
*/
deleteItem?: {
/**
* Collection name
*/
property: string;
/**
* Index in the collection
*/
index: number;
};
deleteAttribute?: string;
};
}
/**
* Event sent when a query on the store is emitted
*/
export interface EventStoreQuery {
/**
* Request sent
*/
query: string;
/**
* Emitting store
*/
store: Store;
/**
* The parsed query by our grammar
*/
parsedQuery: WebdaQL.Query;
/**
* Context in which the query was run
*/
context: OperationContext;
}
/**
* Event sent when query is resolved
*/
export interface EventStoreQueried extends EventStoreQuery {
/**
* Results from the query
*/
results: CoreModel[];
/**
* The next continuation token
*/
continuationToken: string;
}
/**
* Event sent when object is created via POST request
*/
export interface EventStoreWebCreate extends EventWithContext {
/**
* Properties for the object
*/
values: any;
/**
* Target object
*/
object: CoreModel;
/**
* Object id
*/
object_id: string;
/**
* Emitting store
*/
store: Store;
}
/**
* Event sent when object is retrieved via GET request
*/
export interface EventStoreWebGet extends EventWithContext {
/**
* Emitting store
*/
store: Store;
/**
* Target object
*/
object: CoreModel;
}
/**
* Event sent when object is retrieved via GET request
*/
export interface EventStoreWebGetNotFound extends EventWithContext {
/**
* Emitting store
*/
store: Store;
/**
* Target object
*/
uuid: string;
}
/**
* Event sent when object is updated via PUT request
*/
export interface EventStoreWebUpdate extends EventWithContext {
/**
* Type of update
*/
method: "PATCH" | "PUT";
/**
* Updates to do on the object
*/
updates: any;
/**
* Target object
*/
object: CoreModel;
/**
* Emitting store
*/
store: Store;
}
/**
* Event sent when object is deleted via DELETE request
*/
export interface EventStoreWebDelete extends EventWithContext {
/**
* Object uuid
*/
object_id: string;
/**
* Emitting store
*/
store: Store;
}
/**
* @deprecated Store should not be exposed directly anymore
* You should use the DomainService instead
*/
export type StoreExposeParameters = {
/**
* URL endpoint to use to expose REST Resources API
*
* @default service.getName().toLowerCase()
*/
url?: string;
/**
* You can restrict any part of the CRUD
*
* @default {}
*/
restrict?: {
/**
* Do not expose the POST
*/
create?: boolean;
/**
* Do not expose the PUT and PATCH
*/
update?: boolean;
/**
* Do not expose the GET
*/
get?: boolean;
/**
* Do not expose the DELETE
*/
delete?: boolean;
/**
* Do not expose the query endpoint
*/
query?: boolean;
};
/**
* For confidentiality sometimes you might prefer to expose query through PUT
* To avoid GET logging
*
* @default "GET"
*/
queryMethod?: "PUT" | "GET";
};
/**
* Represent a query result on the Store
*/
export interface StoreFindResult<T> {
/**
* Current result objects
*/
results: T[];
/**
* Continuation Token if more results are available
*/
continuationToken?: string;
/**
* Remaining filtering to do as current store cannot filter
*
* If `true`, no more filtering is required apart from permissions
* If `filter === undefined`, a full postquery filtering will happen
* Otherwise filter.eval while be used on every results
*/
filter?: WebdaQL.Expression | true;
}
/**
* Store parameter
*/
export declare class StoreParameters extends ServiceParameters {
/**
* Webda model to use within the Store
*
* @default "Webda/CoreModel"
*/
model?: string;
/**
* Additional models
*
* Allow this store to manage other models
*
* @default []
*/
additionalModels?: string[];
/**
* async delete
*/
asyncDelete: boolean;
/**
* Expose the service to an urls
*
* @deprecated will probably be removed in 4.0 in favor of Expose annotation
*/
expose?: StoreExposeParameters;
/**
* Allow to load object that does not have the type data
*
* If set to true, then the Store will only managed the defined _model and no
* model extending this one
*
* @default false
*/
strict?: boolean;
/**
* When __type model not found, use the model
* If strict is setup this parameter is not used
*
* @default true
*/
defaultModel?: boolean;
/**
* If set, Store will ignore the __type
*
* @default false
*/
forceModel?: boolean;
/**
* Slow query threshold
*
* @default 30000
*/
slowQueryThreshold: number;
/**
* Model Aliases to allow easier rename of Model
*/
modelAliases?: {
[key: string]: string;
};
/**
* Disable default memory cache
*/
noCache?: boolean;
constructor(params: any, service: Service<any>);
}
export type StoreEvents = {
"Store.PartialUpdated": EventStorePartialUpdated;
"Store.Save": EventStoreSave;
"Store.Saved": EventStoreSaved;
"Store.PatchUpdate": EventStorePatchUpdate;
"Store.PatchUpdated": EventStorePatchUpdated;
"Store.Update": EventStoreUpdate;
"Store.Updated": EventStoreUpdated;
"Store.Delete": EventStoreDelete;
"Store.Deleted": EventStoreDeleted;
"Store.Get": EventStoreGet;
"Store.Query": EventStoreQuery;
"Store.Queried": EventStoreQueried;
"Store.WebCreate": EventStoreWebCreate;
"Store.Action": EventStoreAction;
"Store.Actioned": EventStoreActioned;
"Store.WebUpdate": EventStoreWebUpdate;
"Store.WebGetNotFound": EventStoreWebGetNotFound;
"Store.WebGet": EventStoreWebGet;
"Store.WebDelete": EventStoreWebDelete;
};
/**
* A mapping service allow to link two object together
*
* Therefore they need to handle the cascadeDelete
*/
export interface MappingService<T = any> {
newModel(object: any): T;
}
/**
* This class handle NoSQL storage and mapping (duplication) between NoSQL object
* TODO Create the mapping documentation
*
* It use basic CRUD, and can expose those 4 to through HTTP
*
* It emits events :
* Store.Save: Before saving the object
* Store.Saved: After saving the object
* Store.Update: Before updating the object
* Store.Updated: After updating the object
* Store.Delete: Before deleting the object
* Store.Deleted: After deleting the object
* Store.Get: When getting the object
* Store.Action: When an action will be done on an object
* Store.Actioned: When an action has been done on an object
*
* Mapping:
*
*
* Parameters
*
* map: { ... }
* expose: { // Enable the HTTP exposure
* url: '', // The url to expose to by default it is service name in lowercase ( users for example )
* restrict: {
* create: true, // Don't expose the POST /users
* update: true, // Don't expose the PUT /users/{uuid}
* delete: true, // Don't expose the DELETE /users/{uuid}
* get: true // Don't expose the GET /users/{uuid}
* }
* }
* @category CoreServices
*/
declare abstract class Store<T extends CoreModel = CoreModel, K extends StoreParameters = StoreParameters, E extends StoreEvents = StoreEvents> extends Service<K, E> implements ConfigurationProvider, MappingService<T> {
/**
* Cache store
*/
_cacheStore: Store<T>;
/**
* Contain the reverse map
*/
_reverseMap: {
mapper: MappingService;
property: string;
}[];
/**
* Contains the current model
*/
_model: CoreModelDefinition<T>;
/**
* Store teh manager hierarchy with their depth
*/
_modelsHierarchy: {
[key: string]: number;
};
/**
* Contains the current model type
*/
_modelType: string;
/**
* Contain the model uuid field
*/
protected _uuidField: string;
/**
* Add metrics counter
* ' UNION SELECT name, tbl_name as email, "" as col1, "" as col2, "" as col3, "" as col4, "" as col5, "" as col6, "" as col7, "" as col8 FROM sqlite_master --
* {"email":"' UNION SELECT name as profileImage, tbl_name as email, '' AS column3 FROM sqlite_master --","password":"we"}
*/
metrics: {
cache_invalidations: Counter;
operations_total: Counter;
slow_queries_total: Counter;
cache_hits: Counter;
queries: Histogram;
};
/**
* Load the parameters for a service
*/
abstract loadParameters(params: any): StoreParameters;
/**
* Retrieve the Model
*
* @throws Error if model is not found
*/
computeParameters(): void;
logSlowQuery(_query: string, _reason: string, _time: number): void;
/**
* Invalidate a cache entry
* @param uid
*/
invalidateCache(uid: string): Promise<void>;
/**
* @override
*/
initMetrics(): void;
/**
* Return Store current model
* @returns
*/
getModel(): CoreModelDefinition;
/**
* Return if a model is handled by the store
* @param model
* @return distance from the managed class -1 means not managed, 0 manage exactly this model, >0 manage an ancestor model
*
*/
handleModel(model: Constructor<CoreModel> | CoreModel): number;
/**
* Get From Cache or main
* @param uuid
* @param raiseIfNotFound
* @returns
*/
_getFromCache(uuid: string, raiseIfNotFound?: boolean): Promise<T>;
/**
* Get object from store
* @param uid
* @returns
*/
getObject(uid: string): Promise<T>;
/**
* @override
*/
getUrl(url: string, methods: HttpMethodType[]): string;
static getOpenAPI(): void;
/**
* @inheritdoc
*/
initRoutes(): void;
/**
* OVerwrite the model
* Used mainly in test
*/
setModel(model: CoreModelDefinition<T>): void;
/**
* We should ignore exception from the store
*/
cacheStorePatchException(): void;
/**
* Init a model from the current stored data
*
* Initial the reverse map as well
*
* @param object
* @returns
*/
protected initModel(object?: any): T;
/**
* Get a new model with this data preloaded
* @param object
* @returns
*/
newModel(object?: any): T;
/**
* Add reverse map information
*
* @param prop
* @param cascade
* @param store
*/
addReverseMap(prop: string, store: MappingService): void;
/**
* Increment attributes of an object
*
* @param uid
* @param info
* @returns
*/
incrementAttributes<FK extends FilterAttributes<T, number>>(uid: string, info: {
property: FK;
value: number;
}[]): Promise<Date>;
/**
* Helper function that call incrementAttributes
* @param uid
* @param prop
* @param value
* @returns
*/
incrementAttribute<FK extends FilterAttributes<T, number>>(uid: string, prop: FK, value: number): Promise<Date>;
/**
* Add or update an item to an array in the model
*
* @param uid of the model
* @param prop of the model to add in
* @param item to add in the array
* @param index if specified update item in this index
* @param itemWriteCondition value of the condition to test (in case of update)
* @param itemWriteConditionField field to read the condition from (in case of update)
*/
upsertItemToCollection<FK extends FilterAttributes<T, Array<any>>>(uid: string, prop: FK, item: any, index?: number, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<Date>;
/**
* Remove an item from an array in the model
*
* @param uid of the model
* @param prop of the model to remove from
* @param index of the item to remove in the array
* @param itemWriteCondition value of the condition
* @param itemWriteConditionField field to read the condition from
*/
deleteItemFromCollection<FK extends FilterAttributes<T, Array<any>>>(uid: string, prop: FK, index: number, itemWriteCondition: any, itemWriteConditionField?: string): Promise<Date>;
/**
* Iterate through the results
*
* This can be resource consuming
*
* @param query
* @param context
*/
iterate(query?: string, context?: OperationContext): AsyncGenerator<T>;
/**
* Query all the results
*
*
* @param query
* @param context
* @returns
* @deprecated use iterate instead
*/
queryAll(query: string, context?: OperationContext): Promise<T[]>;
/**
* Check that __type Comparison is only used with = and CONTAINS
* If CONTAINS is used, move __type to __types
* If __type = store._model, remove it
*/
queryTypeUpdater(query: WebdaQL.Query): WebdaQL.Query;
/**
* Query store with WebdaQL
* @param query
* @param context to apply permission
*/
query(query: string, context?: OperationContext): Promise<{
results: T[];
continuationToken?: string;
}>;
/**
* Expose query to http
*/
httpQuery(ctx: WebContext): Promise<void>;
/**
* Handle StoreEvent and update cache based on it
* Then emit the event, it allows the cache to be updated
* before listeners are called
*
* @param event
* @param data
*/
emitStoreEvent<Key extends keyof StoreEvents>(event: Key, data: E[Key] & {
emitterId?: string;
}): Promise<void>;
/**
* Save an object
*
* @param {Object} Object to save
* @param {String} Uuid to use, if not specified take the object.uuid or generate one if not found
* @return {Promise} with saved object
*
* Might want to rename to create
*/
save(object: any, ctx?: OperationContext): Promise<T>;
/**
*
* @param object
* @param ctx
* @returns
*/
create(object: any, ctx?: OperationContext): Promise<any>;
/**
* Patch an object
*
* @param object
* @param reverseMap
* @returns
*/
patch<FK extends keyof T>(object: Partial<T>, reverseMap?: boolean, conditionField?: FK | null, conditionValue?: any): Promise<T | undefined>;
/**
* Check if an UpdateCondition is met
* @param model
* @param conditionField
* @param condition
* @param uid
*/
checkUpdateCondition<CK extends keyof T>(model: T, conditionField?: CK, condition?: any, uid?: string): void;
/**
* Check if an UpdateCondition is met
* @param model
* @param conditionField
* @param condition
* @param uid
*/
checkCollectionUpdateCondition<FK extends FilterAttributes<T, Array<any>>, CK extends keyof T>(model: T, collection: FK, conditionField?: CK, condition?: any, index?: number): void;
/**
* Update conditionally
* @param uuid
* @param updates
* @param conditionField
* @param condition
*/
conditionalPatch<CK extends keyof T>(uuid: string, updates: Partial<T>, conditionField: CK, condition: any): Promise<boolean>;
/**
*
* @param model
* @param prop
* @param item
* @param index
* @param itemWriteCondition
* @param itemWriteConditionField
* @param updateDate
*/
simulateUpsertItemToCollection<FK extends FilterAttributes<T, Array<any>>>(model: T, prop: FK, item: any, updateDate: Date, index?: number, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<void>;
/**
* Update an object
*
* If no attribute can be updated then return undefined
*
* @param {Object} Object to save
* @param {Boolean} reverseMap internal use only, for disable map resolution
* @return {Promise} with saved object
*/
update<CK extends keyof T>(object: any, reverseMap?: boolean, partial?: boolean, conditionField?: CK | null, conditionValue?: any): Promise<T | undefined>;
/**
* Manage the store migration for __type case sensitivity
*/
v3Migration(): Promise<void>;
/**
*
*/
recomputeTypeShortId(): Promise<void>;
/**
* Ensure model aliases are not used in this store
*
* So alias can be cleaned
*/
cleanModelAliases(): Promise<void>;
/**
* Recompute type case
*/
recomputeTypeCase(): Promise<void>;
/**
* Recompute the __types for all objects (storeMigration.Registry.typesCompute)
*/
recomputeTypes(): Promise<void>;
/**
* Delete a migration
* @param name
*/
cancelMigration(name: string): Promise<void>;
/**
* Get a migration
* @param name
*/
getMigration(name: string): Promise<any>;
/**
* Add a migration mechanism to store
* @param name
* @param patcher
*/
migration(name: string, patcher: (object: T) => Promise<Partial<T> | (() => Promise<void>) | undefined>, batchSize?: number): Promise<void>;
/**
* Remove an attribute from an object
*
* @param uuid
* @param attribute
* @returns
*/
removeAttribute<CK extends keyof T>(uuid: string, attribute: CK, itemWriteCondition?: any, itemWriteConditionField?: CK): Promise<void>;
/**
* Cascade delete a related object
*
* @param obj
* @param uuid
* @returns
*/
cascadeDelete(obj: CoreModel, _uuid: string): Promise<any>;
/**
* Delete an object from the store without condition nor async
* @param uid to delete
* @returns
*/
forceDelete(uid: string): Promise<void>;
/**
* Delete an object
*
* @param {String} uuid to delete
* @param {Boolean} delete sync even if asyncDelete is active
* @return {Promise} the deletion promise
*/
delete<CK extends keyof T>(uid: string | T, writeCondition?: any, writeConditionField?: CK, sync?: boolean): Promise<void>;
/**
* By default we cannot know if the store will trigger or not
*
* @param id
* @param callback
*/
canTriggerConfiguration(_id: string, _callback: () => void): boolean;
/**
* Provide a way to store configuration in store
* @param {string} id
* @returns {Promise<Map<string, any>>}
*/
getConfiguration(id: string): Promise<{
[key: string]: any;
}>;
/**
* Upsert the uuid object
* @param uuid
* @param data
*/
put(uuid: string, data: Partial<T>): Promise<T>;
/**
* Get an object
*
* @param {String} uuid to get
* @return {Promise} the object retrieved ( can be undefined if not found )
*/
get(uid: string, ctx?: OperationContext, defaultValue?: any): Promise<T>;
/**
* Set one attribute in an object
*
* this is an helper function that calls patch
*
* @param uid of the object
* @param property to update1
* @param value new value
* @returns
*/
setAttribute<CK extends keyof T>(uid: string, property: CK, value: any): Promise<void>;
/**
* @override
*/
protected simulateFind(query: WebdaQL.Query, uuids: string[]): Promise<StoreFindResult<T>>;
getOpenApiReplacements(): {
modelName: string;
};
/**
* Handle POST
* @param ctx
*/
httpCreate(ctx: WebContext): Promise<void>;
/**
* Create a new object based on the context
* @param ctx
* @param model
*/
operationCreate(ctx: OperationContext, model: string): Promise<void>;
/**
* Handle obect action
* @param ctx
*/
httpAction(ctx: WebContext, actionMethod?: string): Promise<void>;
/**
* Handle collection action
* @param ctx
*/
httpGlobalAction(ctx: WebContext, model?: CoreModelDefinition): Promise<void>;
/**
* Handle HTTP Update for an object
*
* @param ctx context of the request
*/
httpUpdate(ctx: WebContext): Promise<void>;
/**
* Handle GET on object
*
* @param ctx context of the request
*/
httpGet(ctx: WebContext): Promise<void>;
/**
* Handle HTTP request
*
* @param ctx context of the request
* @returns
*/
httpDelete(ctx: WebContext): Promise<void>;
/**
* Return the model uuid field
*/
getUuidField(): string;
/**
* Check if an object exists
* @abstract
* @params {String} uuid of the object or the object
*/
exists(uid: string | CoreModel): Promise<boolean>;
/**
* Search within the store
*/
abstract find(query: WebdaQL.Query): Promise<StoreFindResult<T>>;
/**
* Check if an object exists
* @abstract
*/
abstract _exists(uid: string): Promise<boolean>;
/**
* The underlying store should recheck writeCondition only if it does not require
* another get()
*
* @param uid
* @param writeCondition
* @param itemWriteConditionField
*/
protected abstract _delete(uid: string, writeCondition?: any, itemWriteConditionField?: string): Promise<void>;
/**
* Retrieve an element from the store
*
* @param uid to retrieve
* @param raiseIfNotFound raise an StoreNotFound exception if not found
*/
protected abstract _get(uid: string, raiseIfNotFound?: boolean): Promise<T>;
/**
* Get an object
*
* @param {Array} uuid to gets if undefined then retrieve the all table
* @return {Promise} the objects retrieved ( can be [] if not found )
*/
abstract getAll(list?: string[]): Promise<T[]>;
protected abstract _update(object: any, uid: string, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<any>;
protected abstract _patch(object: any, uid: string, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<any>;
protected abstract _removeAttribute(uuid: string, attribute: string, itemWriteCondition?: any, itemWriteConditionField?: string): Promise<void>;
/**
* Save within the store
* @param object
*/
protected abstract _save(object: T): Promise<any>;
/**
* Increment the attribute
* @param uid
* @param prop
* @param value
* @param updateDate
*/
protected abstract _incrementAttributes(uid: string, params: {
property: string;
value: number;
}[], updateDate: Date): Promise<any>;
protected abstract _upsertItemToCollection(uid: string, prop: string, item: any, index: number, itemWriteCondition: any, itemWriteConditionField: string, updateDate: Date): Promise<any>;
protected abstract _deleteItemFromCollection(uid: string, prop: string, index: number, itemWriteCondition: any, itemWriteConditionField: string, updateDate: Date): Promise<any>;
}
export { Store };