Skip to content

borderless/context

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Context

NPM version NPM downloads Build status Test coverage Bundle size

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.

Installation

npm install @borderless/context --save

Usage

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.

Abort

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")));

Timeout

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.

Using Abort

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")]);

Example

Abort Controller

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 });
}

Application Tracing

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();
  }
}

Libraries

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);
}

License

MIT

About

Tiny, type-safe, JavaScript-native `context` implementation

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •