@node-in-layers/core
Version:
The core library for the Node In Layers rapid web development framework.
715 lines (714 loc) • 23.3 kB
TypeScript
import z, { ZodType } from 'zod';
import { DataDescription, ModelFactory, ModelInstanceFetcher, ModelType, JsonAble, JsonObj } from 'functional-models';
type ModelConstructor = Readonly<{
create: <T extends DataDescription, TModelExtensions extends object = object, TModelInstanceExtensions extends object = object>(modelProps: ModelProps) => ModelType<T, TModelExtensions, TModelInstanceExtensions>;
}>;
/**
* A domain within a system.
* @interface
*/
type App = Readonly<{
/**
* The name of the domain
*/
name: string;
/**
* The description of the domain
*/
description?: string;
/**
* Optional: Services layer
*/
services?: AppLayer<Config, any>;
/**
* Optional: Features layer
*/
features?: AppLayer<Config, any>;
/**
* Optional: Globals layer
*/
globals?: GlobalsLayer<Config, any>;
/**
* Optional: Models
*/
models?: Record<string, ModelConstructor>;
}>;
/**
* Log Levels
*/
declare enum LogLevel {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
SILENT = 5
}
/**
* Log Levels by names.
*/
declare enum LogLevelNames {
trace = "trace",
debug = "debug",
info = "info",
warn = "warn",
error = "error",
silent = "silent"
}
/**
* A Promise or not a promise.
*/
type MaybePromise<T> = Promise<T> | T;
/**
* The format of log messages to the console.
*/
declare enum LogFormat {
/**
* Console logs json
*/
json = "json",
/**
* A custom logger. (Must provide a RootLogger via customLogger)
*/
custom = "custom",
/**
* A simple datetime: message log format to console
*/
simple = "simple",
/**
* Logs messages over TCP. Must provide tcp options in logOptions
*/
tcp = "tcp",
full = "full"
}
/**
* A standardized error object.
* @interface
*/
type ErrorObject = Readonly<{
/**
* Shows that this is an error object.
*/
error: Readonly<{
/**
* A unique string code for the error
*/
code: string;
/**
* A user friendly error message.
*/
message: string;
/**
* Additional details in a string format.
*/
details?: string;
/**
* Additional data as an object
*/
data?: Record<string, JsonAble>;
/**
* A trace of the error
*/
trace?: string;
/**
* A suberror that has the cause of the error.
*/
cause?: ErrorObject;
}>;
}>;
/**
* Common props that can be passed between layers
* @interface
*/
type CrossLayerProps<T extends object = object> = Readonly<{
logging?: {
ids?: readonly LogId[];
};
}> & T;
/**
* A very useful way to describe all layered functions, so that crossLayer
* props can be passed through a system.
* @example
* ```typescript
* type MyService = Readonly<{
* myFunc: LayerFunction<(myArg: string) => Promise<string>>
* }>
* ```
* This creates an object...
*
* @example
* ```typescript
* {
* myFunc: (myArg: strinng, crossLayerProps) => Promise<string>
* }
* ```
* @interface
*/
type LayerFunction<T extends (...args: any[]) => any> = T extends (...args: infer Args) => infer ReturnType ? (...args: [...Args, crossLayerProps?: CrossLayerProps]) => ReturnType : never;
/**
* A function argument that types inputs and outputs.
*/
type TypedFunction<T, A extends Array<any>> = (...args: A) => T;
/**
* A function that is wrapped with logging calls.
*/
type LogWrapSync<T, A extends Array<any>> = (functionLogger: FunctionLogger, ...args: A) => T;
/**
* A function argument that types inputs and outputs and is a promise.
*/
type TypedFunctionAsync<T, A extends Array<any>> = (...args: A) => Promise<T>;
/**
* An Async function that is wrapped with logging functions.
*/
type LogWrapAsync<T, A extends Array<any>> = (functionLogger: FunctionLogger, ...args: A) => Promise<T>;
/**
* A function level logger.
*/
type FunctionLogger = Logger;
/**
* A logger for a layer. (Services/Features/etc)
* Already has the domain's name appended to the logging data as well as the runtimeId.
* @interface
*/
type LayerLogger = Logger & Readonly<{
/**
* A generic function for wrapping logs. Not generally intended to be used
* by users, but used internally.
* @param functionName - The name of the function
* @param func - The function itself
*/
_logWrap: <T, A extends Array<any>>(functionName: string, func: LogWrapAsync<T, A> | LogWrapSync<T, A>) => (...a: A) => Promise<T> | T;
/**
* Creates a logging wrap around a function. This should be used on asynchronous functions.
* Executes the function when called but will create a start and end message.
* The first argument is the logger, followed by the normal arguments.
* Should automatically handle error logging as well.
* NOTE: This function automatically handles crossLayerProps, assuming its the final argument in the function call.
* @param functionName - The name of the function
* @param func - The function itself
*/
_logWrapAsync: <T, A extends Array<any>>(functionName: string, func: LogWrapAsync<T, A>) => (...a: A) => Promise<T>;
/**
* Creates a logging wrap around a synchronous function. This should be used on synchronous functions.
* Executes the function when called but will create a start and end message.
* The first argument is the logger, followed by the normal arguments.
* Should automatically handle error logging as well.
* NOTE: This function automatically handles crossLayerProps, assuming its the final argument in the function call.
* @param functionName - The name of the function
* @param func - The function itself
*/
_logWrapSync: <T, A extends Array<any>>(functionName: string, func: LogWrapSync<T, A>) => (...a: A) => T;
/**
* Gets a function level logger. This is not the recommended logger for most uses especially within layers.
* Use getInnerLogger.
* A common pattern is to wrap the
* @param name - The name of the function
* @param crossLayerProps - Any additional crossLayerProps.
*/
getFunctionLogger: (name: string, crossLayerProps?: CrossLayerProps) => FunctionLogger;
/**
* The primary recommended way to log within a function. Creates a logger within a function, makes sure the ids are passed along.
* @param functionName - The name of the function
* @param crossLayerProps - Any additional crossLayerProps.
*/
getInnerLogger: (functionName: string, crossLayerProps?: CrossLayerProps) => FunctionLogger;
}>;
/**
* A logger for an domain.
* @interface
*/
type AppLogger = Logger & Readonly<{
getLayerLogger: (layerName: CommonLayerName | string, crossLayerProps?: CrossLayerProps) => LayerLogger;
}>;
type GetAppLogger = (domainName: string) => AppLogger;
declare enum CommonLayerName {
models = "models",
services = "services",
features = "features",
entries = "entries"
}
/**
* Options for a specific instance of logging.
*/
type LogInstanceOptions = Readonly<{
ignoreSizeLimit?: boolean;
}>;
/**
* A log object
* @interface
*/
type Logger = Readonly<{
/**
* Trace statement
* @param message - The logs message
* @param dataOrError - An object of data, or an object with errors.
*/
trace: (message: string, dataOrError?: Record<string, JsonAble | object> | ErrorObject, options?: LogInstanceOptions) => MaybePromise<void>;
/**
* Debug statement
* @param msg
*/
debug: (message: string, dataOrError?: Record<string, JsonAble | object> | ErrorObject, options?: LogInstanceOptions) => MaybePromise<void>;
/**
* An info statement
* @param msg
*/
info: (message: string, dataOrError?: Record<string, JsonAble | object> | ErrorObject, options?: LogInstanceOptions) => MaybePromise<void>;
/**
* Warning statement
* @param msg
*/
warn: (message: string, dataOrError?: Record<string, JsonAble | object> | ErrorObject, options?: LogInstanceOptions) => MaybePromise<void>;
/**
* An error statement.
* @param msg
*/
error: (message: string, dataOrError?: Record<string, JsonAble | object> | ErrorObject, options?: LogInstanceOptions) => MaybePromise<void>;
/**
* Embeds data, so that subsequent log messages (and loggers), can log that data without having to know details about it.
* @param data
*/
applyData: (data: Record<string, JsonAble>) => Logger;
/**
* Creates a logger by adding an id to the id stack.
*/
getIdLogger: (name: string, logIdorKey: LogId | string, id?: string) => Logger;
/**
* Gets a sub logger.
* @param name - The name of the sub logger.
*/
getSubLogger: (name: string) => Logger;
/**
* Gets all ids associated with this logger. Useful for passing on.
*/
getIds: () => readonly LogId[];
}>;
type HighLevelLogger = Logger & Readonly<{
getAppLogger: GetAppLogger;
}>;
/**
* A base level log object, that creates a logger
* @interface
*/
type RootLogger<TConfig extends Config = Config> = Readonly<{
/**
* Gets a logger object wrapping the components.
* @param context - Context used for configuring a logger.
* @param props - Any additional logging information to include with the logger.
*/
getLogger: (context: CommonContext<TConfig>, props?: {
ids?: readonly LogId[];
data?: Record<string, any>;
}) => HighLevelLogger;
}>;
/**
* A log id object.
* @interface
*/
type LogId = Readonly<Record<string, string>>;
/**
* A fully fleshed out log message.
* @interface
*/
type LogMessage<T extends Record<string, JsonAble> = Record<string, JsonAble>> = Readonly<{
/**
* The unique id for this log message. Every log message has a unique id.
*/
id: string;
/**
* The name of the logger. This is assembled from nested names joined with ':'.
*/
logger: string;
/**
* The environment this log was produced in.
*/
environment: string;
/**
* A stack of ids that get added on and removed. Useful for tracing
* throughout a system. The first ones, are the oldest, and the last ones are the newest.
*/
ids?: readonly LogId[];
/**
* The log level
*/
logLevel: LogLevelNames;
/**
* The datetime of the message
*/
datetime: Date;
/**
* The log's message
*/
message: string;
}> & Partial<ErrorObject> & T;
/**
* A base functionfunction that can handle a log message.
*/
type LogFunction = (logMessage: LogMessage) => void | Promise<void>;
/**
* A method that can do logging once given a context.
*/
type LogMethod<TConfig extends Config = Config> = (context: CommonContext<TConfig>) => LogFunction;
/**
* Core Namespaces.
*/
declare enum CoreNamespace {
root = "@node-in-layers/core",
globals = "@node-in-layers/core/globals",
layers = "@node-in-layers/core/layers",
models = "@node-in-layers/core/models"
}
/**
* A generic layer
*/
type GenericLayer = Record<string, any>;
/**
* Props that go into a model constructor.
* @interface
*/
type ModelProps<TConfig extends Config = Config, TModelOverrides extends object = object, TModelInstanceOverrides extends object = object> = Readonly<{
context: CommonContext<TConfig>;
Model: ModelFactory<TModelOverrides, TModelOverrides>;
fetcher: ModelInstanceFetcher<TModelOverrides, TModelInstanceOverrides>;
getModel: <T extends DataDescription>(namespace: string, modelName: string) => () => ModelType<T, TModelOverrides, TModelInstanceOverrides>;
}>;
/**
* Custom model properties. getModel is provided by the framework.
* @interface
*/
type PartialModelProps<TModelOverrides extends object = object, TModelInstanceOverrides extends object = object> = Readonly<{
Model: ModelFactory<TModelOverrides, TModelOverrides>;
fetcher: ModelInstanceFetcher<TModelOverrides, TModelInstanceOverrides>;
}>;
/**
* A function that can get model props from a services context.
*/
type GetModelPropsFunc = (context: ServicesContext, ...args: any[]) => PartialModelProps;
/**
* Services for the layer domain
*/
type LayerServices = Readonly<{
/**
* The standard default function for getting model props
*/
getModelProps: (context: ServicesContext) => ModelProps;
/**
* Loads a layer.
* @param domain
* @param layer
* @param existingLayers
*/
loadLayer: (domain: App, layer: string, existingLayers: LayerContext) => MaybePromise<GenericLayer | undefined>;
}>;
/**
* The services layer for the core layers domain
* @interface
*/
type LayerServicesLayer = {
/**
* A logger for this service.
*/
log: LayerLogger;
/**
* Services
*/
services: {
[CoreNamespace.layers]: LayerServices;
};
};
type LayerDescription = string | readonly string[];
/**
* String model name, to either service name, or namespace, db key, and optional additional args.
*/
type ModelToModelFactoryNamespace = Record<string, string | [string, string] | [string, string, any[]]>;
/**
* String namespace to namespace factory.
*/
type NamespaceToFactory = Record<string, ModelToModelFactoryNamespace>;
/**
* The core configurations
* @interface
*/
type CoreConfig = Readonly<{
/**
* Options for logging.
*/
logging: {
/**
* The log level to log at. Anything below this level, and it'll be ignored.
*/
logLevel: LogLevelNames;
/**
* The format of log messages. If multiple are included, then multiple logging approaches will be used.
*/
logFormat: LogFormat | readonly LogFormat[];
/**
* The maximum number of characters a log can be. NOTE: This is the count of any optional data properties,
* and does NOT include any core fields. Defaults to 50,000
*/
maxLogSizeInCharacters?: number;
/**
* When logFormat is tcp, these options are used to configure AXIOS.
*/
tcpLoggingOptions?: Readonly<{
/**
* The url to log to.
*/
url: string;
/**
* Any headers that are needed, such api keys.
*/
headers?: Record<string, string | object>;
}>;
/**
* A custom RootLogger that replaces the default one.
*/
customLogger?: RootLogger;
/**
* If using a function wrap with a LayerLogger, what LogLevel
* should it be at?
* Default: feature=info, services=trace everything else is debug
*/
getFunctionWrapLogLevel?: (layerName: string, functionName?: string) => LogLevelNames;
/**
* Optional structure for NOT wrapping log messages around a layers function.
* domain -> layer -> function
*
* You can also ignore ALL functions in a domain's layer.
*
* @example
* ```javascript
* {
* ignoreLayerFunctions: {
* '@node-in-layers/rest-api/express': {
* // We will ignore ALL of the express layer
* express: true,
*
* // Ignore specific functions.
* features: {
* modelCrudsRouter: true,
* modelCrudsController: true,
* }
* },
* // This will ignore the entire layer.
* 'myDomain.layer': true,
* }
* }
* ```
* @interface
*/
ignoreLayerFunctions?: Record<string, boolean | Record<string, Record<string, boolean> | boolean>>;
};
/**
* The layers to be loaded, in their order.
* Can be either string names for regular layers, or an array of strings, for a composite layer with multiple sub-layers.
*/
layerOrder: readonly LayerDescription[];
/**
* Already loaded domains.
* Most often take the form of doing require/imports directly in the config.
*/
apps: readonly App[];
/**
* Optional: The namespace to the domain.services that has a "getModelProps()" function used for loading models
*/
modelFactory?: string;
/**
* Optional: When true, wrappers are built around models to bubble up CRUDS interfaces for models through services and features.
*/
modelCruds?: boolean;
/**
* Optional: Provides granular getModelProps() for specific models.
*/
customModelFactory?: NamespaceToFactory;
}>;
/**
* A basic config object
* @interface
*/
type Config = Readonly<{
/**
* The systems name
*/
systemName: string;
/**
* The environment
*/
environment: string;
/**
* Core level configurations
*/
[CoreNamespace.root]: CoreConfig;
}>;
/**
* A generic layer within an domain
* @interface
*/
type AppLayer<TConfig extends Config = Config, TContext extends object = object, TLayer extends object = object> = Readonly<{
/**
* Creates the layer.
* @param context
*/
create: (context: LayerContext<TConfig, TContext>) => MaybePromise<TLayer>;
}>;
/**
* The base level context that everything recieves.
* @interface
*/
type CommonContext<TConfig extends Config = Config> = Readonly<{
/**
* The configuration file.
*/
config: TConfig;
/**
* A root logger.
*/
rootLogger: RootLogger;
/**
* Constants.
*/
constants: {
/**
* The environment
*/
environment: string;
/**
* The working directory.
*/
workingDirectory: string;
/**
* A uuid that represents the runtime.
*/
runtimeId: string;
};
}>;
/**
* The context for a layer
*/
type LayerContext<TConfig extends Config = Config, TContext extends object = object> = CommonContext<TConfig> & TContext & {
/**
* The logger for this layer
*/
log: LayerLogger;
};
/**
* A context for layers that consume services. (Services and features generally)
* @interface
*/
type ServicesContext<TConfig extends Config = Config, TServices extends object = object, TContext extends object = object> = LayerContext<TConfig, {
/**
* A models object that has namespace to an object that has "getModels()"
*/
models: Record<string, {
/**
* Gets the models for this given namespace.
*/
getModels: <TModelType extends ModelType<any>>() => Record<string, TModelType>;
}>;
/**
* A services object.
*/
services: TServices;
} & TContext>;
/**
* A factory for creating the service.
* @interface
*/
type ServicesLayerFactory<TConfig extends Config = Config, TServices extends object = object, TContext extends object = object, TLayer extends object = object> = Readonly<{
/**
* Creates the services layer
* @param context
*/
create: (context: ServicesContext<TConfig, TServices, TContext>) => TLayer;
}>;
type GlobalsLayer<TConfig extends Config = Config, TGlobals extends object = object> = Readonly<{
create: (context: CommonContext<TConfig>) => Promise<TGlobals>;
}>;
/**
* A context for layers that consume features. (Features and entries generally)
* @interface
*/
type FeaturesContext<TConfig extends Config = Config, TServices extends object = object, TFeatures extends object = object, TGlobals extends object = object> = LayerContext<TConfig, {
/**
* Services
*/
services: TServices;
/**
* Features
*/
features: TFeatures;
} & TGlobals>;
type FeaturesLayerFactory<TConfig extends Config = Config, TContext extends object = object, TServices extends object = object, TFeatures extends object = object, TLayer extends object = object> = Readonly<{
create: (context: FeaturesContext<TConfig, TServices, TFeatures, TContext>) => TLayer;
}>;
/**
* Describes a complete system, with services and features.
*/
type System<TConfig extends Config = Config, TServices extends object = object, TFeatures extends object = object> = CommonContext<TConfig> & {
services: TServices;
features: TFeatures;
};
type Without<T, U> = {
[P in Exclude<keyof T, keyof U>]?: never;
};
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
/**
* A standardized response. Either the normal result, or an error object.
*/
type Response<R> = XOR<R, ErrorObject>;
type TrueMaybePromise<T> = XOR<Promise<T>, T>;
/**
* Helper type to determine the correct return type
* Response<T> for non-void; void stays void. Supports sync/async via MaybePromise.
*/
type NilFunctionReturn<TOutput> = [TOutput] extends [void] ? TrueMaybePromise<void> : TrueMaybePromise<Response<TOutput>>;
/**
* A node in layer function. This standardized function takes all its arguments via a props object, and then it takes an optional
* CrossLayerProps for between layer communications.
*/
type NilFunction<TProps extends JsonObj, TOutput extends XOR<JsonObj, void>> = (props: TProps, crossLayerProps?: CrossLayerProps) => NilFunctionReturn<TOutput>;
/**
* A node in layer function that has been annotated with a schema.
* @interface
*/
type NilAnnotatedFunction<TProps extends JsonObj, TOutput extends JsonObj | void> = NilFunction<TProps, TOutput> & Readonly<{
/**
* The name of the function.
*/
functionName: string;
/**
* The domain the function is within.
*/
domain: string;
/**
* A Zod schema that describes the function
*/
schema: z.ZodFunction<z.ZodTuple<[ZodType<TProps>, ZodType<CrossLayerProps | undefined>]>, ZodType<NilFunctionReturn<TOutput>>>;
}>;
/**
* The arguments to an Annotated Function
* @interface
*/
type AnnotatedFunctionProps<TProps extends JsonObj, TOutput extends JsonObj | void> = {
/**
* The name of the function.
*/
functionName: string;
/**
* The domain the function is within.
*/
domain: string;
/**
* An optional description that explains how to use the function and what it does.
*/
description?: string;
/**
* The input arguments for the function.
*/
args: ZodType<TProps>;
/**
* The returns (if not a void)
*/
returns?: ZodType<TOutput extends void ? never : TOutput>;
};
export { Response, Config, App, LogFormat, LogLevel, LogLevelNames, AppLayer, LayerContext, ServicesContext, ServicesLayerFactory, FeaturesContext, System, RootLogger, CommonContext, CoreNamespace, FeaturesLayerFactory, Logger, LayerDescription, MaybePromise, LayerServices, GenericLayer, LayerServicesLayer, ModelProps, ModelConstructor, GetModelPropsFunc, PartialModelProps, LogMessage, LogFunction, LogMethod, LogId, CoreConfig, ErrorObject, CommonLayerName, CrossLayerProps, AppLogger, LayerLogger, FunctionLogger, HighLevelLogger, TypedFunction, TypedFunctionAsync, LogWrapSync, LogWrapAsync, LayerFunction, LogInstanceOptions, NilAnnotatedFunction, NilFunction, XOR, TrueMaybePromise, AnnotatedFunctionProps, NilFunctionReturn, };