web-node
Version:
High level javaScript backend plugin system and configuration merger.
279 lines (278 loc) • 11.7 kB
TypeScript
import { Encoding, Mapping, RecursiveEvaluateable, RecursivePartial, UTILITY_SCOPE } from 'clientnode';
import pluginAPI, { callStack, callStackSynchronous, determineInternalName, determineLocations, evaluateConfiguration, hotReloadAPIFile, hotReloadConfigurationFile, hotReloadFiles, isInLocations, load, loadAll, loadAPI, loadConfiguration, loadConfigurations, loadFile } from './pluginAPI';
export type EvaluateConfigurationScope = typeof UTILITY_SCOPE & {
currentPath: string;
fs: typeof import('fs');
path: typeof import('path');
pluginAPI: typeof pluginAPI;
webNodePath: string;
now: Date;
nowUTCTimestamp: number;
};
export interface MetaPluginConfiguration {
fileNames: Array<string>;
propertyNames: Array<string>;
}
/**
* NOTE: This interface should be extended by plugins to specify their
* configuration schema.
* Can be recursive evaluateable.
*/
export interface PluginConfiguration {
dependencies?: Array<string>;
name?: string;
package: PackageConfiguration;
}
export interface WebNodeConfiguration extends PluginConfiguration {
context: {
path: string;
type: string;
};
debug: boolean;
encoding: Encoding;
interDependencies: Mapping<Array<string> | string>;
name: string;
plugin: {
configuration: MetaPluginConfiguration;
directories: Mapping<{
nameRegularExpressionPattern: string;
path: string;
}>;
hotReloading: boolean;
};
runtimeConfiguration?: EvaluateablePartialConfiguration;
}
export type Configuration<PluginConfigurationType = Mapping<unknown>> = {
core: WebNodeConfiguration;
name: string;
} & PluginConfigurationType & Mapping<PluginConfiguration>;
export type EvaluateablePartialConfiguration = {
core?: RecursiveEvaluateable<RecursivePartial<WebNodeConfiguration>>;
name?: string;
} & Mapping<PluginConfiguration>;
export type PackageConfiguration = Mapping<unknown> & {
documentationWebsite?: {
name?: string;
};
main?: string;
name?: string;
webnode?: EvaluateablePartialConfiguration;
webNode?: EvaluateablePartialConfiguration;
webNodeInternalName?: string;
'web-node'?: EvaluateablePartialConfiguration;
};
export interface Plugin {
api: APIFunction | null;
apiFilePaths: Array<string>;
apiFileLoadTimestamps: Array<number>;
configuration: EvaluateablePartialConfiguration;
configurationFileLoadTimestamps: Array<number>;
configurationFilePaths: Array<string>;
packageConfiguration: PackageConfiguration;
dependencies: Array<string>;
internalName: string;
name: string;
path: string;
scope: null | object;
}
export interface PluginChange {
newScope: Mapping<unknown>;
oldScope: null | Mapping<unknown>;
plugin: Plugin;
target: 'packageConfiguration' | 'scope';
}
export type HookPromiseResult<Type> = Promise<Type | {
promise: Type;
}>;
export type PluginPromises<Type extends Promise<unknown> = Promise<unknown>> = Mapping<null | Type>;
export type Services<PluginServiceType = Mapping<unknown>> = Mapping<unknown> & PluginServiceType;
export type ServicePromises<PluginPromiseType = Mapping<unknown>> = Mapping<Promise<unknown>> & PluginPromiseType;
export interface BaseState<Type = undefined, ConfigurationType extends Configuration = Configuration> {
configuration: ConfigurationType;
data: Type;
hook: string;
plugins: Array<Plugin>;
pluginAPI: {
callStack: typeof callStack;
callStackSynchronous: typeof callStackSynchronous;
determineInternalName: typeof determineInternalName;
determineLocations: typeof determineLocations;
evaluateConfiguration: typeof evaluateConfiguration;
hotReloadAPIFile: typeof hotReloadAPIFile;
hotReloadConfigurationFile: typeof hotReloadConfigurationFile;
hotReloadFiles: typeof hotReloadFiles;
isInLocations: typeof isInLocations;
load: typeof load;
loadAll: typeof loadAll;
loadAPI: typeof loadAPI;
loadConfiguration: typeof loadConfiguration;
loadConfigurations: typeof loadConfigurations;
loadFile: typeof loadFile;
};
}
export interface ChangedState<Type = unknown, ConfigurationType extends Configuration = Configuration> extends BaseState<Type, ConfigurationType> {
triggerHook?: string;
}
export interface ChangedConfigurationState<Type = unknown, ConfigurationType extends Configuration = Configuration> extends ChangedState<Type, ConfigurationType> {
pluginsWithChangedConfiguration: Array<Plugin>;
}
export interface ChangedAPIFileState<Type = unknown, ConfigurationType extends Configuration = Configuration> extends ChangedState<Type, ConfigurationType> {
pluginsWithChangedAPIFiles: Array<Plugin>;
}
export type APIFunction<Output = unknown, State extends BaseState<unknown> = ServicePromisesState> = (state: Omit<State, 'data'> & {
data?: State['data'];
}, ...parameters: Array<unknown>) => Output;
export interface ServicesState<Type = undefined, ConfigurationType extends Configuration = Configuration, ServicesType extends Services = Services> extends BaseState<Type, ConfigurationType> {
services: ServicesType;
}
export interface ServicePromisesState<Type = undefined, ConfigurationType extends Configuration = Configuration, ServicesType extends Services = Services, ServicePromisesType extends ServicePromises = ServicePromises> extends ServicesState<Type, ConfigurationType, ServicesType> {
servicePromises: ServicePromisesType;
}
/**
* Plugins can hook into the following life cycle.
*
* Starting lifecycle with:
* ------------------------
*
* 1. initialize (async)
* 1. preConfigurationLoaded (async)
* 2. postConfigurationLoaded (async)
* 3. preLoadService (async)
* 3.a preLoad_A_Service (async)
* 3.b preLoad_B_Service (async)
* ...
* 4. loadService (async)
* 5.a postLoad_A_Service (async)
* 5.b postLoad_B_Service (async)
* 5. postLoadService (async)
* 6. shouldExit (async)
* 7. exit (sync)
*
* Lifecycle with "hotReloading" (call "callStack" with hook "eventName"):
* ------------------------------------------------------------------
*
* 1. preConfigurationHotLoaded (async)
* 2. postConfigurationHotLoaded (async)
* 3. apiFileReloaded (async)
* 4. eventName (async)
*
* Lifecycle without "hotReloading" (call "callStack" with hook "eventName"):
* ---------------------------------------------------------------------
*
* 1. eventName (async)
*/
export interface PluginHandler {
/**
* Application started, static configuration loaded and all available
* plugins are determined and sorted in there dependency specific
* typological order. Asynchronous tasks are allowed and a returning
* promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
initialize?(state: BaseState): Promise<void>;
/**
* Triggered hook when at least one plugin has a configuration file and
* configuration object has been initialized. Asynchronous tasks are
* allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
preConfigurationLoaded?(state: ChangedConfigurationState): Promise<void>;
/**
* Triggered hook when at least one plugin has a configuration file and
* configuration object has been initialized. Asynchronous tasks are
* allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
postConfigurationLoaded?(state: ChangedConfigurationState): Promise<void>;
/**
* Plugins are initialized now and plugins should initialize their
* continues running services (if they have one). Asynchronous tasks are
* allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to given and maybe extended object of
* services.
*/
preLoadService?(state: ServicesState): Promise<void>;
/**
* Plugins are initialized now and plugins should initialize their
* continues running services (if they have one). Asynchronous tasks are
* allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to given and maybe extended object of
* services.
*/
/**
* Plugins have initialized their continues running service and should
* start them now. A Promise which observes this service should be
* returned. Asynchronous tasks are allowed and a returning promise will be
* respected NOTE: You have to wrap a promise in a promise if a continues
* service should be registered.
* @param state - Application state.
* @returns A mapping to promises which correspond to the plugin specific
* continues services.
*/
loadService?(state: ServicePromisesState): Promise<null | PluginPromises>;
/**
* Plugins have launched their continues running services and returned a
* corresponding promise which can be observed here.
* @param state - Application state.
* @returns A promise which correspond to the plugin specific continues
* service.
*/
/**
* Plugins have launched their continues running services and returned a
* corresponding promise which can be observed here.
* @param state - Application state.
* @returns A promise which correspond to the plugin specific continues
* service promises.
*/
postLoadService?(state: ServicePromisesState): Promise<void>;
/**
* Triggered hook when at least one plugin has a new configuration file and
* configuration object has been changed. Asynchronous tasks are allowed
* and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
preConfigurationHotLoaded?(state: ChangedConfigurationState): Promise<void>;
/**
* Triggered hook when at least one plugin has a new configuration file and
* configuration object has been changed. Asynchronous tasks are allowed
* and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
postConfigurationHotLoaded?(state: ChangedConfigurationState): Promise<void>;
/**
* Triggered hook when at least one plugin has an api file which has been
* changed and is reloaded. Asynchronous tasks are allowed and a returning
* promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
apiFileReloaded?(state: ChangedAPIFileState): Promise<void>;
/**
* Application has thrown an error and will be closed soon. Asynchronous
* tasks are allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
error?(state: ServicesState): Promise<void>;
/**
* Triggers if application will be closed soon. Asynchronous tasks are
* allowed and a returning promise will be respected.
* @param state - Application state.
* @returns Promise resolving to nothing.
*/
shouldExit?(state: ServicePromisesState): Promise<void>;
/**
* Triggers if application will be closed immediately no asynchronous tasks
* allowed anymore.
* @param state - Application state.
* @returns Nothing.
*/
exit?(state: ServicePromisesState): void;
}