= V;
+type EventDependency = (input: P) => Promise;
+
+// Main DependencyValueType Definition
+export type DependencyValueType = T extends ITask
+ ? TaskDependency, ExtractTaskOutput>
+ : T extends IResource
+ ? ResourceDependency>
+ : T extends IEventDefinition
+ ? EventDependency>
: never;
export type DependencyValuesType = {
@@ -43,8 +52,8 @@ export type DependencyValuesType = {
// RegisterableItems Type with Conditional Inclusion
export type RegisterableItems =
- | IResource // Always include IResource
| IResourceWithConfig
+ | IResource
| ITaskDefinition
| IMiddlewareDefinition
| IEventDefinition;
@@ -58,12 +67,15 @@ export interface ITaskDefinition<
id: string;
dependencies?: TDependencies | (() => TDependencies);
middleware?: IMiddlewareDefinition[];
+ /**
+ * Listen to events in a simple way
+ */
on?: IEventDefinition;
/**
- * This represents the order of the event. It only makes sense to be used when `on` is also defined.
- * You use this when you have multiple tasks listening to the same event and want to control the order.
+ * This makes sense only when `on` is specified to provide the order of the execution.
+ * The event with the lowest order will be executed first.
*/
- priority?: number;
+ listenerOrder?: number;
meta?: ITaskMeta;
run: (
input: TEventDefinitionInput extends null ? TInput : TEventDefinitionInput,
@@ -125,10 +137,11 @@ export interface ITask<
}
// Resource interfaces
export interface IResourceDefinintion<
- TConfig = void,
+ TConfig = any,
TValue = unknown,
TDependencies extends DependencyMapType = {},
- THooks = any
+ THooks = any,
+ TRegisterableItems = any
> {
id: string;
dependencies?: TDependencies | ((config: TConfig) => TDependencies);
@@ -243,15 +256,24 @@ export interface IMiddlewareExecutionInput {
next: (taskInputOrResourceConfig?: any) => Promise;
}
-export interface IHookDefinition {
+export interface IHookDefinition<
+ D extends DependencyMapType = {},
+ T = any,
+ B extends boolean = false
+> {
event: "*" | IEventDefinition;
/**
* The higher the number, the higher the priority.
* We recommend using numbers between -1000 and 1000.
*/
- priority?: number;
+ order?: number;
+ /**
+ * These are hooks that run before any resource instantiation.
+ * @param event
+ */
+ early?: B;
run: (
event: IEvent,
- dependencies: DependencyValuesType
+ dependencies: T extends true ? void : DependencyValuesType
) => Promise | void;
}
diff --git a/src/examples/express-mongo/index.ts b/src/examples/express-mongo/index.ts
new file mode 100644
index 0000000..70b786d
--- /dev/null
+++ b/src/examples/express-mongo/index.ts
@@ -0,0 +1 @@
+// TODO
diff --git a/src/globalEvents.ts b/src/globalEvents.ts
index 0aef0c8..4791c97 100644
--- a/src/globalEvents.ts
+++ b/src/globalEvents.ts
@@ -59,6 +59,7 @@ export const globalEvents = {
};
export const globalEventsArray = [
+ globalEvents.log,
globalEvents.beforeInit,
globalEvents.afterInit,
globalEvents.tasks.beforeRun,
diff --git a/src/models/DependencyProcessor.ts b/src/models/DependencyProcessor.ts
index e4496f2..09df87d 100644
--- a/src/models/DependencyProcessor.ts
+++ b/src/models/DependencyProcessor.ts
@@ -12,6 +12,7 @@ import { EventManager } from "./EventManager";
import { ResourceInitializer } from "./ResourceInitializer";
import { TaskRunner } from "./TaskRunner";
import { Errors } from "../errors";
+import { Logger } from "./Logger";
/**
* This class is responsible of setting up dependencies with their respective computedValues.
@@ -24,9 +25,14 @@ export class DependencyProcessor {
constructor(
protected readonly store: Store,
protected readonly eventManager: EventManager,
- protected readonly taskRunner: TaskRunner
+ protected readonly taskRunner: TaskRunner,
+ protected readonly logger: Logger
) {
- this.resourceInitializer = new ResourceInitializer(store, eventManager);
+ this.resourceInitializer = new ResourceInitializer(
+ store,
+ eventManager,
+ logger
+ );
}
/**
@@ -45,6 +51,10 @@ export class DependencyProcessor {
for (const resource of this.store.resources.values()) {
await this.processResourceDependencies(resource);
}
+
+ // leftovers that were registered but not depended upon, except root
+ // they should still be initialized as they might extend other
+ await this.initializeUninitializedResources();
}
private async computeTaskDependencies(
@@ -62,6 +72,11 @@ export class DependencyProcessor {
this.eventManager.addListener(
eventDefinition,
async (receivedEvent) => {
+ this.logger.debug({
+ message: `Task ${task.task.id} listened to event: ${eventDefinition.id}`,
+ event: receivedEvent,
+ });
+
return this.taskRunner.run(
task.task,
receivedEvent,
@@ -69,7 +84,7 @@ export class DependencyProcessor {
);
},
{
- order: task.task.priority || 0,
+ order: task.task.listenerOrder || 0,
}
);
}
@@ -120,10 +135,8 @@ export class DependencyProcessor {
/**
* Processes all hooks, should run before emission of any event.
- * @returns
*/
public attachHooks() {
- // iterate through resources and send them to processHooks
for (const resource of this.store.resources.values()) {
if (resource.resource.hooks) {
this.attachHooksToResource(resource);
@@ -150,7 +163,7 @@ export class DependencyProcessor {
for (const hook of hooks) {
const event = hook.event;
- const order = hook.priority || 0;
+ const order = hook.order || 0;
if (event === "*") {
this.eventManager.addGlobalListener(
async (receivedEvent) => {
@@ -214,6 +227,7 @@ export class DependencyProcessor {
*/
extractEventDependency(object: IEventDefinition>) {
return async (input) => {
+ this.logger.debug(`Emitting event ${object.id}`);
return this.eventManager.emit(object, input);
};
}
diff --git a/src/models/EventManager.ts b/src/models/EventManager.ts
index 450e92b..16309df 100644
--- a/src/models/EventManager.ts
+++ b/src/models/EventManager.ts
@@ -1,5 +1,6 @@
import { EventHandlerType, IEvent, IEventDefinition } from "../defs";
import { Errors } from "../errors";
+import { Logger } from "./Logger";
const HandlerOptionsDefaults = { order: 0 };
diff --git a/src/models/Logger.ts b/src/models/Logger.ts
index 8bc86ed..abb4ad4 100644
--- a/src/models/Logger.ts
+++ b/src/models/Logger.ts
@@ -17,7 +17,7 @@ export interface ILog {
}
export class Logger {
- public static defaultContext = "app";
+ printThreshold: LogLevels | null = null;
public severity = {
trace: 0,
@@ -28,23 +28,43 @@ export class Logger {
critical: 5,
};
- constructor(private eventManager: EventManager) {}
+ constructor(protected eventManager: EventManager) {}
/**
* @param level
* @param message
*/
- public async log(level: LogLevels, data: any): Promise {
+ public async log(
+ level: LogLevels,
+ data: any,
+ source?: string
+ ): Promise {
const log: ILog = {
level,
data,
+ context: source,
timestamp: new Date(),
};
+ if (
+ this.printThreshold &&
+ this.severity[level] >= this.severity[this.printThreshold]
+ ) {
+ this.print(log);
+ }
+
await this.eventManager.emit(globalEvents.log, log);
}
- public async print(log: ILog) {
+ /**
+ * Will print logs after that, use `null` to disable autoprinting.
+ * @param level
+ */
+ public setPrintThreshold(level: LogLevels | null) {
+ this.printThreshold = level;
+ }
+
+ public print(log: ILog) {
// Extract the relevant information from the log
const { level, context, data, timestamp } = log;
@@ -74,27 +94,27 @@ export class Logger {
console.log(logMessage);
}
- public async info(data: any) {
- await this.log("info", data);
+ public async info(data: any, context?: string) {
+ await this.log("info", data, context);
}
- public async error(data: any) {
- await this.log("error", data);
+ public async error(data: any, context?: string) {
+ await this.log("error", data, context);
}
- public async warn(data: any) {
- await this.log("warn", data);
+ public async warn(data: any, context?: string) {
+ await this.log("warn", data, context);
}
- public async debug(data: any) {
- await this.log("debug", data);
+ public async debug(data: any, context?: string) {
+ await this.log("debug", data, context);
}
- public async trace(data: any) {
- await this.log("trace", data);
+ public async trace(data: any, context?: string) {
+ await this.log("trace", data, context);
}
- public async critical(data: any) {
- await this.log("critical", data);
+ public async critical(data: any, context?: string) {
+ await this.log("critical", data, context);
}
}
diff --git a/src/models/ResourceInitializer.ts b/src/models/ResourceInitializer.ts
index 7b4771e..eaf444b 100644
--- a/src/models/ResourceInitializer.ts
+++ b/src/models/ResourceInitializer.ts
@@ -7,11 +7,13 @@ import {
import { EventManager } from "./EventManager";
import { globalEvents } from "../globalEvents";
import { MiddlewareStoreElementType, Store } from "./Store";
+import { Logger } from "./Logger";
export class ResourceInitializer {
constructor(
protected readonly store: Store,
- protected readonly eventManager: EventManager
+ protected readonly eventManager: EventManager,
+ protected readonly logger: Logger
) {}
/**
@@ -27,9 +29,6 @@ export class ResourceInitializer {
config: TConfig,
dependencies: DependencyValuesType
): Promise {
- // begin by dispatching the event of creating it.
- // then ensure the hooks are called
- // then ensure the middleware are called
await this.eventManager.emit(globalEvents.resources.beforeInit, {
config,
resource,
@@ -52,6 +51,8 @@ export class ResourceInitializer {
value,
});
+ this.logger.debug(`Resource ${resource.id} initialized`);
+
return value;
} catch (e) {
error = e;
diff --git a/src/models/Store.ts b/src/models/Store.ts
index b99a2eb..6c9c5b4 100644
--- a/src/models/Store.ts
+++ b/src/models/Store.ts
@@ -17,6 +17,7 @@ import { Errors } from "../errors";
import { globalResources } from "../globalResources";
import { EventManager } from "./EventManager";
import { TaskRunner } from "./TaskRunner";
+import { Logger } from "./Logger";
export type ResourceStoreElementType<
C = any,
@@ -67,7 +68,10 @@ export class Store {
#isLocked = false;
#isInitialized = false;
- constructor(protected readonly eventManager: EventManager) {}
+ constructor(
+ protected readonly eventManager: EventManager,
+ protected readonly logger: Logger
+ ) {}
get isLocked() {
return this.#isLocked;
@@ -378,12 +382,22 @@ export class Store {
});
}
+ private middlewareAsMap(middleware: IMiddlewareDefinition[]) {
+ return middleware.reduce((acc, item) => {
+ acc[item.id] = item;
+ return acc;
+ }, {} as Record);
+ }
+
getDependentNodes(): IDependentNode[] {
const depenedants: IDependentNode[] = [];
for (const task of this.tasks.values()) {
depenedants.push({
id: task.task.id,
- dependencies: task.task.dependencies,
+ dependencies: {
+ ...task.task.dependencies,
+ ...this.middlewareAsMap(task.task.middleware),
+ },
});
}
for (const middleware of this.middlewares.values()) {
@@ -395,7 +409,10 @@ export class Store {
for (const resource of this.resources.values()) {
depenedants.push({
id: resource.resource.id,
- dependencies: resource.resource.dependencies || {},
+ dependencies: {
+ ...resource.resource.dependencies,
+ ...this.middlewareAsMap(resource.resource.middleware),
+ },
});
}
diff --git a/src/models/TaskRunner.ts b/src/models/TaskRunner.ts
index bc621eb..5276707 100644
--- a/src/models/TaskRunner.ts
+++ b/src/models/TaskRunner.ts
@@ -7,6 +7,7 @@ import {
Store,
TaskStoreElementType,
} from "./Store";
+import { Logger } from "./Logger";
export class TaskRunner {
protected readonly runnerStore = new Map<
@@ -16,7 +17,8 @@ export class TaskRunner {
constructor(
protected readonly store: Store,
- protected readonly eventManager: EventManager
+ protected readonly eventManager: EventManager,
+ protected readonly logger: Logger
) {}
/**
@@ -104,6 +106,11 @@ export class TaskRunner {
) {
// this is the final next()
let next = async (input) => {
+ this.logger.debug({
+ message: `Running task ${task.id}`,
+ input,
+ });
+
return task.run.call(null, input, taskDependencies as any);
};
diff --git a/src/run.ts b/src/run.ts
index daa831a..a705222 100644
--- a/src/run.ts
+++ b/src/run.ts
@@ -52,21 +52,23 @@ export type RunnerState = {
middleware: Record;
};
-export type RunnerType = {
- store: Store;
- eventManager: EventManager;
- taskRunner: TaskRunner;
-};
-
export async function run(
resource: IResource,
config?: C
): Promise {
const eventManager = new EventManager();
+
+ // ensure for logger, that it can be used only after: computeAllDependencies() has executed
const logger = new Logger(eventManager);
- const store = new Store(eventManager);
- const taskRunner = new TaskRunner(store, eventManager);
- const processor = new DependencyProcessor(store, eventManager, taskRunner);
+
+ const store = new Store(eventManager, logger);
+ const taskRunner = new TaskRunner(store, eventManager, logger);
+ const processor = new DependencyProcessor(
+ store,
+ eventManager,
+ taskRunner,
+ logger
+ );
// In the registration phase we register deeply all the resources, tasks, middleware and events
store.initializeStore(resource, config);
@@ -80,24 +82,25 @@ export async function run(
throw Errors.circularDependencies(circularDependencies.cycles);
}
+ // the overrides that were registered now will override the other registered resources
await store.processOverrides();
- // a form of hooking, we store the events for all tasks
+ // a form of hooking, we create the events for all tasks and store them so they can be referenced
await store.storeEventsForAllTasks();
await processor.attachHooks();
await processor.computeAllDependencies();
+ await logger.debug("All elements have been initalized..");
+
// Now we can safely compute dependencies without being afraid of an infinite loop.
// The hooking part is done here.
await eventManager.emit(globalEvents.beforeInit);
- // leftovers that were registered but not depended upon, except root
- await processor.initializeUninitializedResources();
-
// Now we can initialise the root resource
await processor.initializeRoot();
await eventManager.emit(globalEvents.afterInit);
+ await logger.debug("System initialized and operational.");
// disallow manipulation or attaching more
store.lock();