Skip to content

Commit

Permalink
rewire lifecycles
Browse files Browse the repository at this point in the history
  • Loading branch information
zoe-codez committed Jun 19, 2024
1 parent b34089c commit 88bd765
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 144 deletions.
10 changes: 5 additions & 5 deletions src/extensions/internal.extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import {
START,
TBlackHole,
TContext,
TLoadableChildLifecycle,
TModuleMappings,
TResolvedModuleMappings,
YEAR,
} from "..";
import { CreateLifecycle } from "./lifecycle.extension";

const EVERYTHING_ELSE = 1;
const MONTHS = 12;
Expand Down Expand Up @@ -240,14 +240,14 @@ export class InternalDefinition {
completedLifecycleEvents: Set<LifecycleStages>;

/**
* Roughly speaking, what's the application doing? Mostly useful for debugging
* for internal operations
*/
phase: Phase;
lifecycle: ReturnType<typeof CreateLifecycle>;

/**
* Registry for various lifecycle events that were registered by services
* Roughly speaking, what's the application doing? Mostly useful for debugging
*/
lifecycleHooks: Map<string, TLoadableChildLifecycle>;
phase: Phase;

/**
* association of projects to { service : Declaration Function }
Expand Down
138 changes: 89 additions & 49 deletions src/extensions/lifecycle.extension.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,99 @@
import {
CallbackList,
DOWN,
each,
eachSeries,
LIFECYCLE_STAGES,
LifecycleCallback,
LifecycleStages,
TLoadableChildLifecycle,
NONE,
sleep,
TLifecycleBase,
UP,
} from "../helpers";
import { InternalDefinition } from "./internal.extension";
import { ILogger } from "./logger.extension";

export function CreateChildLifecycle(
internal: InternalDefinition,
logger: ILogger,
): TLoadableChildLifecycle {
const childCallbacks = {} as Record<LifecycleStages, CallbackList>;

const [
// ! This list must be sorted!
onPreInit,
onPostConfig,
onBootstrap,
onReady,
onPreShutdown,
onShutdownStart,
onShutdownComplete,
] = LIFECYCLE_STAGES.map((stage) => {
childCallbacks[stage] = [];
return (callback: LifecycleCallback, priority?: number) => {
if (internal.boot.completedLifecycleEvents.has(stage)) {
// this is makes "earliest run time" logic way easier to implement
// intended mode of operation
if (["PreInit", "PostConfig", "Bootstrap", "Ready"].includes(stage)) {
setImmediate(async () => await callback());
return;
}
// What does this mean in reality?
// Probably a broken unit test, I really don't know what workflow would cause this
logger.fatal(
{ name: CreateChildLifecycle },
`on${stage} late attach, cannot attach callback`,
);
return;
import { is } from "./is.extension";

type EventMapObject = {
callback: LifecycleCallback;
priority: number;
};
const PRE_CALLBACKS_START = 0;

export function CreateLifecycle() {
const events = new Map<LifecycleStages, EventMapObject[]>(
LIFECYCLE_STAGES.map((event) => [event, []]),
);

function attachEvent(
callback: LifecycleCallback,
name: LifecycleStages,
priority?: number,
) {
const stageList = events.get(name);
if (!is.array(stageList)) {
if (!name.includes("Shutdown")) {
setImmediate(async () => await callback());

Check warning on line 34 in src/extensions/lifecycle.extension.ts

View check run for this annotation

Codecov / codecov/patch

src/extensions/lifecycle.extension.ts#L34

Added line #L34 was not covered by tests
}
childCallbacks[stage].push([callback, priority]);
};
});
return;

Check warning on line 36 in src/extensions/lifecycle.extension.ts

View check run for this annotation

Codecov / codecov/patch

src/extensions/lifecycle.extension.ts#L36

Added line #L36 was not covered by tests
}
stageList.push({ callback, priority });
}

return {
getCallbacks: (stage: LifecycleStages) =>
childCallbacks[stage] as CallbackList,
onBootstrap,
onPostConfig,
onPreInit,
onPreShutdown,
onReady,
onShutdownComplete,
onShutdownStart,
events: {
onBootstrap: (callback, priority) =>
attachEvent(callback, "Bootstrap", priority),
onPostConfig: (callback, priority) =>
attachEvent(callback, "PostConfig", priority),
onPreInit: (callback, priority) =>
attachEvent(callback, "PreInit", priority),
onPreShutdown: (callback, priority) =>
attachEvent(callback, "PreShutdown", priority),
onReady: (callback, priority) => attachEvent(callback, "Ready", priority),
onShutdownComplete: (callback, priority) =>
attachEvent(callback, "ShutdownComplete", priority),
onShutdownStart: (callback, priority) =>
attachEvent(callback, "ShutdownStart", priority),
} satisfies TLifecycleBase,
async exec(stage: LifecycleStages): Promise<string> {
const start = Date.now();
const list = events.get(stage);
events.delete(stage);
if (!is.empty(list)) {
const sorted = list.filter(({ priority }) => priority !== undefined);
const quick = list.filter(({ priority }) => priority === undefined);
const positive = [] as EventMapObject[];
const negative = [] as EventMapObject[];

sorted.forEach((i) => {
if (i.priority >= PRE_CALLBACKS_START) {
positive.push(i);
return;
}
negative.push(i);
});

// * callbacks with a priority greater than 0
// high to low (1000 => 0)
await eachSeries(
positive.sort((a, b) => (a.priority < b.priority ? UP : DOWN)),
async ({ callback }) => await callback(),
);

// * callbacks without a priority
// any order
await each(quick, async ({ callback }) => await callback());

// * callbacks with a priority less than 0
// high to low (-1 => -1000)
await eachSeries(
negative.sort((a, b) => (a.priority < b.priority ? UP : DOWN)),
async ({ callback }) => await callback(),
);
}
// TODO Update this comment with why this sleep exists
// I forgot why, but it seems on purpose
await sleep(NONE);
return `${Date.now() - start}ms`;
},
};
}
Loading

0 comments on commit 88bd765

Please sign in to comment.