CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-unctx

Composition API pattern implementation for vanilla JavaScript libraries with context injection, async support, and namespace management.

Pending
Overview
Eval results
Files

context-creation.mddocs/

Context Creation & Management

Context creation and management provides the core functionality for creating typed context instances that can inject values throughout a call stack.

API Reference

/**
 * Creates a new context instance with optional async context support
 * @param opts - Configuration options for the context
 * @returns Context instance with getter, setter, and execution methods
 */
function createContext<T = any>(opts: ContextOptions): UseContext<T>;

interface UseContext<T> {
  /**
   * Get the current context. Throws if no context is set.
   * @throws Error when context is not available
   */
  use(): T;
  
  /**
   * Get the current context. Returns null when no context is set.
   * @returns Current context instance or null if not set
   */
  tryUse(): T | null;
  
  /**
   * Set the context as Singleton Pattern.
   * @param instance - The context instance to set
   * @param replace - Whether to replace existing conflicting context
   * @throws Error if context conflict occurs and replace is false
   */
  set(instance?: T, replace?: boolean): void;
  
  /**
   * Clear current context.
   */
  unset(): void;
  
  /**
   * Execute a synchronous function with the provided context.
   * @param instance - Context instance to provide during execution
   * @param callback - Function to execute with context
   * @returns Result of callback execution
   * @throws Error if context conflict occurs
   */
  call<R>(instance: T, callback: () => R): R;
  
  /**
   * Execute an asynchronous function with the provided context.
   * Requires installing the transform plugin to work properly.
   * @param instance - Context instance to provide during execution  
   * @param callback - Async function to execute with context
   * @returns Promise resolving to callback result
   */
  callAsync<R>(instance: T, callback: () => R | Promise<R>): Promise<R>;
}

interface ContextOptions {
  /**
   * Enable async context support using AsyncLocalStorage
   */
  asyncContext?: boolean;
  
  /**
   * AsyncLocalStorage implementation for async context
   */
  AsyncLocalStorage?: typeof AsyncLocalStorage;
}

Basic Usage

Creating and Using Context

import { createContext } from "unctx";

interface User {
  id: number;
  name: string;
  email: string;
}

// Create a typed context
const userContext = createContext<User>();

// Execute code with context
const result = userContext.call(
  { id: 1, name: "Alice", email: "alice@example.com" }, 
  () => {
    // Context is available throughout the call stack
    const currentUser = userContext.use();
    console.log(`Processing user: ${currentUser.name}`);
    
    // Call other functions that can access the same context
    return processUserData();
  }
);

function processUserData() {
  // Context is still available in nested functions
  const user = userContext.use();
  return `Processed data for ${user.email}`;
}

Error Handling

const myContext = createContext<string>();

// This will throw an error - no context set
try {
  myContext.use();
} catch (error) {
  console.error("Context not available:", error.message);
}

// Safe usage - returns null instead of throwing
const safeValue = myContext.tryUse(); // null

Context Conflicts

const ctx = createContext<string>();

ctx.call("A", () => {
  // This will throw "Context conflict" error
  try {
    ctx.call("B", () => {
      console.log("This won't execute");
    });
  } catch (error) {
    console.error(error.message); // "Context conflict"
  }
});

Singleton Pattern

For shared instances that don't depend on request context:

import { createContext } from "unctx";

interface AppConfig {
  apiUrl: string;
  version: string;
}

const configContext = createContext<AppConfig>();

// Set global configuration
configContext.set({
  apiUrl: "https://api.example.com",
  version: "1.0.0"
});

// Use throughout the application
function makeApiCall() {
  const config = configContext.use();
  return fetch(`${config.apiUrl}/data`);
}

// Clear singleton when needed
configContext.unset();

// Replace singleton configuration
configContext.set(newConfig, true); // true = replace existing

Async Context with AsyncLocalStorage

import { createContext } from "unctx";
import { AsyncLocalStorage } from "node:async_hooks";

interface RequestContext {
  requestId: string;
  userId: number;
}

const requestContext = createContext<RequestContext>({
  asyncContext: true,
  AsyncLocalStorage
});

// Context persists across async boundaries
await requestContext.callAsync(
  { requestId: "req-123", userId: 42 },
  async () => {
    console.log(requestContext.use().requestId); // "req-123"
    
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Context is still available after await
    console.log(requestContext.use().requestId); // "req-123"
    
    await processRequest();
  }
);

async function processRequest() {
  // Context available in async nested functions
  const ctx = requestContext.use();
  await fetch(`/api/users/${ctx.userId}`);
}

Error Recovery

const ctx = createContext<string>();

// Context is properly cleaned up even if callback throws
try {
  ctx.call("test", () => {
    throw new Error("Something went wrong");
  });
} catch (error) {
  console.error("Callback failed:", error.message);
}

// Context is cleared, can use with different value
ctx.call("new-value", () => {
  console.log(ctx.use()); // "new-value"
});

Install with Tessl CLI

npx tessl i tessl/npm-unctx

docs

async-context.md

build-plugins.md

context-creation.md

index.md

namespace-management.md

tile.json