@webda/core
Version:
Expose API with Lambda
740 lines (739 loc) • 20.5 kB
TypeScript
import { WorkerLogLevel, WorkerOutput } from "@webda/workout";
import Ajv, { ErrorObject } from "ajv";
import * as events from "events";
import { OpenAPIV3 } from "openapi-types";
import { Counter, CounterConfiguration, Gauge, GaugeConfiguration, Histogram, HistogramConfiguration } from "prom-client";
import { Writable } from "stream";
import { Application, Configuration } from "./application.js";
import { BinaryService, ContextProvider, ContextProviderInfo, GlobalContext, HttpContext, Logger, OperationContext, RegExpValidator, Service, Store, WebContext, WebdaQL } from "./index.js";
import { Constructor, CoreModel, CoreModelDefinition } from "./models/coremodel.js";
import { RouteInfo, Router } from "./router.js";
import CryptoService from "./services/cryptoservice.js";
export declare class EventEmitterUtils {
static emit(eventEmitter: events.EventEmitter, event: string | number | symbol, data: any): boolean;
static emitSync(eventEmitter: events.EventEmitter, event: string | number | symbol, data: any): Promise<any[]>;
/**
* Display a message if the listener takes too long
* @param start
*/
static elapse(start: number): void;
}
/**
* Copy from https://github.com/ajv-validator/ajv/blob/master/lib/runtime/validation_error.ts
* It is not exported by ajv
*/
export declare class ValidationError extends Error {
readonly errors: Partial<ErrorObject>[];
readonly ajv: true;
readonly validation: true;
constructor(errors: Partial<ErrorObject>[]);
}
/**
* Operation
*/
export declare class OperationError extends Error {
operation: string;
type: "Unknown" | "PermissionDenied" | "InvalidInput";
constructor(operation: string, type: "Unknown" | "PermissionDenied" | "InvalidInput");
}
/**
* Define an operation within webda app
*/
export interface OperationDefinition {
/**
* Id of the operation
*/
id: string;
/**
* Name of the schema that defines operation input
*/
input?: string;
/**
* Name of the schema that defines operation output
*/
output?: string;
/**
* WebdaQL to execute on session to know if
* operation is available to user
*/
permission?: string;
/**
* Service implementing the operation
*/
service: string;
/**
* Method implementing the operation
*/
method: string;
}
/**
* Define an operation within webda app
*/
export interface OperationDefinitionInfo extends OperationDefinition {
/**
* Contains the parse permission query
*/
permissionQuery?: WebdaQL.QueryValidator;
}
/**
*
*/
export type RegistryEntry<T = any> = CoreModel & T;
/**
* Ensure all events store the context in the same place
*/
export interface EventWithContext<T extends OperationContext = OperationContext> {
context: T;
}
/**
* RequestFilter allow a service which implement it to control incoming request
*
* If one of the filter replies with "true" then the request will go through
*/
export interface RequestFilter<T extends WebContext = WebContext> {
/**
* Return true if the request should be allowed
*
* @param context to check for
*/
checkRequest(context: T, type: "CORS" | "AUTH"): Promise<boolean>;
}
/**
* Filter request based on their origin
*
* @category CoreFeatures
*/
export declare class OriginFilter implements RequestFilter<WebContext> {
regexs: RegExpValidator;
constructor(origins: string[]);
/**
*
* @param context
* @returns
*/
checkRequest(context: WebContext): Promise<boolean>;
}
/**
* Authorize requests based on the website
*/
export declare class WebsiteOriginFilter implements RequestFilter<WebContext> {
websites: string[];
constructor(website: any);
checkRequest(context: WebContext): Promise<boolean>;
}
export declare function Bean(constructor: Function): void;
export type CoreEvents = {
/**
* Emitted when new result is sent
*/
"Webda.Result": EventWithContext<WebContext>;
/**
* Emitted when new request comes in
*/
"Webda.Request": EventWithContext<WebContext>;
/**
* Emitted when a request does not match any route
*/
"Webda.404": EventWithContext<WebContext>;
/**
* Emitted when Services have been initialized
*/
"Webda.Init.Services": {
[key: string]: Service;
};
/**
* Emitted when Services have been created
*/
"Webda.Create.Services": {
[key: string]: Service;
};
/**
* Emitted when Core is initialized
*/
"Webda.Init": Configuration;
/**
* Emitted whenever a new Context is created
*/
"Webda.NewContext": {
context: OperationContext;
info: ContextProviderInfo;
};
/**
* Sent when route is added to context
*/
"Webda.UpdateContextRoute": {
context: WebContext;
};
[key: string]: unknown;
};
type NoSchemaResult = null;
type SchemaValidResult = true;
/**
* This is the main class of the framework, it handles the routing, the services initialization and resolution
*
* @class Core
* @category CoreFeatures
*/
export declare class Core<E extends CoreEvents = CoreEvents> extends events.EventEmitter {
/**
* Webda Services
* @hidden
*/
protected services: {
[key: string]: Service;
};
/**
* Application that generates this Core
*/
protected application: Application;
/**
* Router that will route http request in
*/
protected router: Router;
/**
* If Core is already initiated
*/
protected _initiated: boolean;
/**
* Services who failed to create or initialize
*/
protected failedServices: {
[key: string]: any;
};
/**
* Init promise to ensure, webda is initiated
* Used for init() method
*/
protected _init: Promise<void>;
/**
* Configuration loaded from webda.config.json
* @hidden
*/
protected configuration: Configuration;
/**
* JSON Schema validator instance
*/
protected _ajv: Ajv;
/**
* JSON Schema registry
* Save if a schema was added to Ajv already
*/
protected _ajvSchemas: {
[key: string]: true;
};
/**
* Current executor
*/
protected _currentExecutor: any;
protected _configFile: string;
/**
* Contains the current initialization process
*/
protected _initPromise: Promise<void>;
/**
* When the Core was initialized
*/
protected _initTime: number;
/**
* Console logger
* @hidden
*/
protected logger: Logger;
/**
* Request Filter registry
*
* Added via [[Webda.registerRequestFilter]]
* See [[CorsFilter]]
*/
protected _requestFilters: RequestFilter<WebContext>[];
/**
* CORS Filter registry
*
* Added via [[Webda.registerCORSRequestFilter]]
* See [[CorsFilter]]
*/
protected _requestCORSFilters: RequestFilter<WebContext>[];
/**
* Worker output
*
* @see @webda/workout
*/
private workerOutput;
/**
* Store the instance id
*/
private instanceId;
/**
* Application registry
*/
protected registry: Store<RegistryEntry>;
/**
* Manage encryption within the application
*/
protected cryptoService: CryptoService;
/**
* Contains all operations defined by services
*/
protected operations: {
[key: string]: OperationDefinitionInfo;
};
/**
* Cache for model to store resolution
*/
private _modelStoresCache;
/**
* Cache for model to store resolution
*/
private _modelBinariesCache;
/**
* Store the Core singleton
*
* The main storage is on the process itself
* This second storage allow to identify dual import
*/
private static singleton;
/**
* True if the dual import warning has been sent
*/
private _dualImportWarn;
/**
* Registered context providers
*/
private _contextProviders;
/**
* System context
*/
protected globalContext: GlobalContext;
/**
*
*/
interuptables: {
cancel: () => Promise<void>;
}[];
/**
* @params {Object} config - The configuration Object, if undefined will load the configuration file
*/
constructor(application?: Application);
/**
* Register a cancelable process
* @param interuptable
*/
static registerInteruptableProcess(interuptable: {
cancel: () => Promise<void>;
}): void;
/**
* Unregister a cancelable process
* @param interuptable
*/
static unregisterInteruptableProcess(interuptable: {
cancel: () => Promise<void>;
}): void;
/**
* Get the current script location
* @returns
*/
private getScriptUrl;
/**
* Return information on the singleton import and version
* @param singleton
* @returns
*/
private static getSingletonInfo;
/**
* Get the singleton of Webda Core
* @returns
*/
static get(): Core;
/**
* Enforce a specific store for a model
*
* Useful in some specific case when you want to update store dynamically
*
* @param model
* @param store
*/
setModelStore(model: Constructor<CoreModel>, store: Store): void;
/**
* Get the store assigned to this model
* @param model
* @returns
*/
getModelStore<T extends CoreModel>(modelOrConstructor: Constructor<T> | T): Store<T>;
/**
* Get the service that manage a model
* @param modelOrConstructor
* @param attribute
* @returns
*/
getBinaryStore<T extends CoreModel>(modelOrConstructor: Constructor<T> | T, attribute: string): BinaryService;
/**
* Return Core instance id
*
* It is a random generated string
*/
getInstanceId(): string;
/**
* Get absolute url with subpath
* @param subpath
*/
getApiUrl(subpath?: string): string;
/**
* Return application path with subpath
*
* Helper that redirect to this.application.getAppPath
*
* @param subpath
* @returns
*/
getAppPath(subpath?: string): string;
/**
* Retrieve all detected modules definition
*/
getModules(): import("./application").CachedModule;
/**
* Return application definition
*/
getApplication(): Application;
/**
* Get WorkerOutput
*/
getWorkerOutput(): WorkerOutput;
/**
* Init one service
* @param service
*/
protected initService(service: string): Promise<void>;
/**
* Get an object from the application based on its full uuid
* @param fullUuid
* @param partials
*/
getModelObject<T extends CoreModel = CoreModel>(fullUuid: string, partials?: any): Promise<T>;
/**
* Init Webda
*
* It will resolve Services init method and autolink
*/
init(): Promise<void>;
/**
* Pause for time ms
*
* @param time ms
*/
static sleep(time: any): Promise<void>;
/**
* Check if an operation can be executed with the current context
* Not checking the input use `checkOperation` instead to check everything
* @param context
* @param operationId
* @throws OperationError if operation is unknown
* @returns true if operation can be executed
*/
checkOperationPermission(context: OperationContext, operationId: string): boolean;
/**
* Check if an operation can be executed with the current context
* @param context
* @param operationId
*/
checkOperation(context: OperationContext, operationId: string): Promise<void>;
/**
* Call an operation within the framework
*/
callOperation(context: OperationContext, operationId: string): Promise<any>;
/**
* Get available operations
* @returns
*/
listOperations(): {
[key: string]: Omit<OperationDefinition, "service" | "method">;
};
/**
* Register a new operation within the app
* @param operationId
* @param definition
*/
registerOperation(operationId: string, definition: OperationDefinition): void;
/**
* Register a request filtering
*
* Will apply to all requests regardless of the devMode
* @param filter
*/
registerRequestFilter(filter: RequestFilter<WebContext>): void;
/**
* Register a CORS request filtering
*
* Does not apply in devMode
* @param filter
*/
registerCORSFilter(filter: RequestFilter<WebContext>): void;
/**
* Register a new context provider
* @param provider
*/
registerContextProvider(provider: ContextProvider): void;
/**
* Validate the object with schema
*
* @param schema path to use
* @param object to validate
*/
validateSchema(webdaObject: CoreModel | string, object: any, ignoreRequired?: boolean): NoSchemaResult | SchemaValidResult;
/**
* Return webda current version
*
* @returns package version
* @since 0.4.0
*/
getVersion(): string;
/**
* To define the locales just add a locales: ['en-GB', 'fr-FR'] in your host global configuration
*
* @return The configured locales or "en-GB" if none are defined
*/
getLocales(): string[];
/**
* Get a Logger for a class
* @param clazz
*/
getLogger(clazz: string | Service): Logger;
/**
* Add a route dynamicaly
*
* @param {String} url of the route can contains dynamic part like {uuid}
* @param {Object} info the type of executor
*/
addRoute(url: string, info: RouteInfo): void;
/**
* Remove a route dynamicly
*
* @param {String} url to remove
*/
removeRoute(url: string): void;
/**
* Return current Router object
*/
getRouter(): Router;
/**
* Check for a service name and return the wanted singleton or undefined if none found
*
* @param {String} name The service name to retrieve
*/
getService<T extends Service>(name?: string): T;
/**
* Return a map of defined services
* @returns {{}}
*/
getServices(): {
[key: string]: Service;
};
/**
* Return a map of services that extends type
* @param type The type of implementation
* @returns {{}}
*/
getServicesOfType<T extends Service>(type?: Constructor<T, [Core, string, any]>): {
[key: string]: T;
};
getConfiguration(): Configuration;
/**
* Return a map of defined stores
* @returns {{}}
*/
getStores(): {
[key: string]: Store;
};
/**
* Return a map of defined models
* @returns {{}}
*/
getModels(): {
[key: string]: CoreModelDefinition;
};
/**
* Check for a model name and return the wanted class or throw exception if none found
*
* @param {String} name The model name to retrieve
*/
getModel<T extends CoreModel = CoreModel>(name: any): CoreModelDefinition<T>;
/**
* Add to context information and executor based on the http context
*/
updateContextWithRoute(ctx: WebContext): boolean;
/**
* Flush the headers to the response, no more header modification is possible after that
*
* This method should set the `context.setFlushedHeaders()` and use of `context.hasFlushedHeaders()`
*
* @abstract
*/
flushHeaders(_context: WebContext): void;
/**
* Flush the entire response to the client
*/
flush(_context: WebContext): void;
/**
* Return if Webda is in debug mode
*/
isDebug(): boolean;
/**
* Return the global parameters of a domain
*/
getGlobalParams(): any;
/**
* Get the system context
* @returns
*/
getGlobalContext(): GlobalContext;
/**
* Set the system context
* @param context
*/
setGlobalContext(context: GlobalContext): void;
/**
* Reinit one service
* @param service
*/
protected reinitService(service: string): Promise<void>;
/**
* Reinit all services with updated parameters
* @param updates
* @returns
*/
reinit(updates: any): Promise<void>;
/**
* Get a full resolved service parameter
*
* @param service
* @param configuration
* @returns
*/
getServiceParams(service: string, configuration?: {
parameters?: any;
services: any;
}): any;
protected createService(services: any, service: string): void;
getBeans(): any;
/**
* @hidden
*
*/
protected createServices(excludes?: string[]): void;
/**
* A registry is a predefined store
* @returns
*/
getRegistry(): Store<RegistryEntry>;
/**
* Return the crypto service
* @returns
*/
getCrypto(): CryptoService;
/**
* Stop all services
*/
stop(): Promise<void>;
protected jsonFilter(key: string, value: any): any;
static getMachineId(): string;
/**
* Init services and Beans along with Routes
*/
initStatics(): void;
/**
* Get a context based on the info
* @param info
* @returns
*/
newContext<T extends OperationContext>(info: ContextProviderInfo, noInit?: boolean): Promise<OperationContext>;
/**
* Create a new context for a request
*
* @class Service
* @param httpContext THe HTTP request context
* @param stream - The request output stream if any
* @return A new context object to pass along
*/
newWebContext<T extends WebContext>(httpContext: HttpContext, stream?: Writable, noInit?: boolean): Promise<T>;
/**
* Convert an object to JSON using the Webda json filter
*
* @class Service
* @param {Object} object - The object to export
* @return {String} The export of the strip object ( removed all attribute with _ )
*/
toPublicJSON(object: any): string;
/**
* Return a UUID
*
* @param format to return different type of format
* Plan to implement base64 and maybe base85
*/
getUuid(format?: "ascii" | "base64" | "hex" | "binary" | "uuid"): string;
/**
* @override
*/
emit<K extends keyof E>(eventType: K | symbol | string, event?: E[K], ...data: any[]): boolean;
/**
* Emit the event with data and wait for Promise to finish if listener returned a Promise
*/
emitSync<K extends keyof E>(eventType: K | symbol, event?: E[K], ...data: any[]): Promise<any[]>;
/**
* Type the listener part
* @param event
* @param listener
* @param queue
* @returns
*/
on<Key extends keyof E>(event: Key | symbol, listener: (evt: E[Key]) => void): this;
/**
* Logs
* @param level
* @param args
*/
log(level: WorkerLogLevel, ...args: any[]): void;
/**
* Retrieve a global parameter
*/
parameter(name: string): any;
/**
* Verify if a request can be done
*
* @param context Context of the request
*/
protected checkRequest(ctx: WebContext): Promise<boolean>;
/**
* Verify if an origin is allowed to do request on the API
*
* @param context Context of the request
*/
protected checkCORSRequest(ctx: WebContext): Promise<boolean>;
/**
* Export OpenAPI
* @param skipHidden
* @returns
*/
exportOpenAPI(skipHidden?: boolean): OpenAPIV3.Document;
/**
* Get a metric object
*
* Use the Service.getMetric method if possible
*
* This is map from prometheus 3 types of metrics
* Our hope is that we can adapt them to export to other
* metrics system if needed
*
* @param type
* @param configuration
* @returns
*/
getMetric<T = Gauge | Counter | Histogram>(type: Constructor<T, [MetricConfiguration<T>]>, configuration: MetricConfiguration<T>): T;
}
/**
* Generic type for metric
*/
export type MetricConfiguration<T = Counter | Gauge | Histogram, K extends string = string> = T extends Counter ? CounterConfiguration<K> : T extends Gauge ? GaugeConfiguration<K> : HistogramConfiguration<K>;
/**
* Export a Registry type alias
*/
export type Registry<T extends CoreModel = RegistryEntry> = Store<T>;
export { Counter, Gauge, Histogram };