@quiltjs/quilt
Version:
Lightweight, type-safe handler and router abstraction for Node HTTP servers.
83 lines • 2.91 kB
JavaScript
function now() {
if (typeof performance !== 'undefined' &&
typeof performance.now === 'function') {
return performance.now();
}
return Date.now();
}
/**
* Executes a handler graph for a given context.
*
* Each handler runs at most once per execution, and its output is cached
* and provided to dependants via the `deps` parameter. The final return
* value is ignored; callers should rely on handler side-effects (for
* example, writing to an HTTP response) or on dependency outputs.
*/
export async function executeHandler(handler, ctx, hooks) {
// Cache to store handler outputs
const cache = new Map();
/**
* Flattens the dependency tree into an ordered array of handlers.
* Ensures dependencies are executed before their dependents.
* Also checks for cyclic dependencies.
*/
function flattenDependencies(currentHandler) {
const flatOrder = [];
const visited = new Set();
const visiting = new Set();
function visit(h) {
if (visiting.has(h)) {
throw new Error('Cyclic dependency detected in handler graph');
}
if (visited.has(h))
return;
visiting.add(h);
for (const dep of Object.values(h.dependencies)) {
visit(dep);
}
visiting.delete(h);
visited.add(h);
flatOrder.push(h);
}
visit(currentHandler);
return flatOrder;
}
// Flatten the dependency tree (with cycle detection) and execute handlers in order
const flatOrder = flattenDependencies(handler);
for (const currentHandler of flatOrder) {
const depsOutputs = {};
for (const [depKey, depHandler] of Object.entries(currentHandler.dependencies)) {
depsOutputs[depKey] = cache.get(depHandler);
}
const start = hooks ? now() : 0;
if (hooks?.onHandlerStart) {
await hooks.onHandlerStart({ handler: currentHandler, ctx });
}
try {
const outputs = await currentHandler.execute(ctx, depsOutputs);
cache.set(currentHandler, outputs);
if (hooks?.onHandlerSuccess) {
const end = now();
await hooks.onHandlerSuccess({
handler: currentHandler,
ctx,
durationMs: end - start,
output: outputs,
});
}
}
catch (error) {
if (hooks?.onHandlerError) {
const end = now();
await hooks.onHandlerError({
handler: currentHandler,
ctx,
durationMs: end - start,
error,
});
}
throw error;
}
}
}
//# sourceMappingURL=executeHandler.js.map