Tiny, type-safe, JavaScript-native
context
implementation.
Why? Working on a project across browsers, workers and node.js requires different implementations on the same thing, e.g. fetch
vs require('http')
. Go's context
package provides a nice abstraction to bring all the interfaces together. By implementing a JavaScript first variation, we can achieve the same benefits.
npm install @borderless/context --save
Context values are unidirectional.
import { background, withValue } from "@borderless/context";
// Extend the default `background` context with a value.
const ctx = withValue(background, "test", "test");
ctx.value("test"); //=> "test"
background.value("test"); // Invalid.
Use withAbort
to support cancellation of execution in your application.
import { withAbort } from "@borderless/context";
const [ctx, abort] = withAbort(parentCtx);
onUserCancelsTask(() => abort(new Error("User canceled task")));
Use withTimeout
when you want to abort after a specific duration:
import { withTimeout } from "@borderless/context";
const [ctx, abort] = withTimeout(parentCtx, 5000); // You can still `abort` manually.
The useAbort
method will return a Promise
which rejects when aborted.
import { useAbort } from "@borderless/context";
// Race between the abort signal and making an ajax request.
Promise.race([useAbort(ctx), ajax("http://example.com")]);
Use context
with other abort signals, such as fetch
.
import { useAbort, Context } from "@borderless/context";
function request(ctx: Context<{}>, url: string) {
const controller = new AbortController();
withAbort(ctx).catch(e => controller.abort());
return fetch(url, { signal: controller.signal });
}
Distributed application tracing is a natural example for context
:
import { Context, withValue } from "@borderless/context";
// Use a unique symbol for tracing.
const spanKey = Symbol("span");
// Start a new span, and automatically use "parent" span.
export function startSpan<T extends { [spanKey]?: Span }>(
ctx: Context<T>,
name: string
): [Span, Context<T & { [spanKey]: Span }>] {
const span = tracer.startSpan(name, {
childOf: ctx.value(spanKey)
});
return [span, withValue(ctx, spanKey, span)];
}
// server.js
export async function app(req, next) {
const [span, ctx] = startSpan(req.ctx, "app");
req.ctx = ctx;
try {
return await next();
} finally {
span.finish();
}
}
// middleware.js
export async function middleware(req, next) {
const [span, ctx] = startSpan(req.ctx, "middleware");
req.ctx = ctx;
try {
return await next();
} finally {
span.finish();
}
}
JavaScript and TypeScript libraries can accept a typed context
argument.
import { Context, withValue } from "@borderless/context";
export function withSentry<T>(ctx: Context<T>) {
return withValue(ctx, sentryKey, someSentryImplementation);
}
export function captureException(
ctx: Context<{ [sentryKey]: SomeSentryImplementation }>,
error: Error
) {
return ctx.value(sentryKey).captureException(error);
}
MIT