Composition API pattern implementation for vanilla JavaScript libraries with context injection, async support, and namespace management.
—
Namespace management provides a global context registry system to avoid conflicts between different library versions and enable context sharing across modules.
/**
* Create a namespace for managing multiple contexts
* @param defaultOpts - Default options applied to all contexts in namespace
* @returns Namespace instance for managing contexts by key
*/
function createNamespace<T = any>(defaultOpts: ContextOptions): ContextNamespace;
/**
* Get context from the default global namespace
* @param key - Unique key identifying the context (recommend using package name)
* @param opts - Context options, merged with namespace defaults
* @returns Context instance for the given key
*/
function getContext<T>(key: string, opts?: ContextOptions): UseContext<T>;
/**
* Get use function for named context from default global namespace
* @param key - Unique key identifying the context
* @param opts - Context options
* @returns Function that returns current context value when called
*/
function useContext<T>(key: string, opts?: ContextOptions): () => T;
interface ContextNamespace {
/**
* Get or create a context by key within the namespace
* @param key - Unique key for the context
* @param opts - Context options, merged with namespace defaults
* @returns Context instance for the key
*/
get<T>(key: string, opts?: ContextOptions): UseContext<T>;
}
/**
* The default global namespace instance
*/
const defaultNamespace: ContextNamespace;import { useContext, getContext } from "unctx";
interface UserSession {
userId: string;
token: string;
permissions: string[];
}
// Get context use function (recommended for library authors)
const useUserSession = useContext<UserSession>("my-auth-lib");
// Or get full context instance
const userSessionContext = getContext<UserSession>("my-auth-lib");
// Set up context in your library initialization
userSessionContext.set({
userId: "user-123",
token: "jwt-token",
permissions: ["read", "write"]
});
// Use in any module
function authenticatedFetch(url: string) {
const session = useUserSession();
return fetch(url, {
headers: {
'Authorization': `Bearer ${session.token}`
}
});
}import { createNamespace } from "unctx";
import { AsyncLocalStorage } from "node:async_hooks";
// Create namespace with default async context support
const myLibNamespace = createNamespace({
asyncContext: true,
AsyncLocalStorage
});
interface DatabaseConnection {
host: string;
database: string;
transaction?: string;
}
// Get context from custom namespace
const dbContext = myLibNamespace.get<DatabaseConnection>("database");
// All contexts in this namespace inherit async support
await dbContext.callAsync(
{ host: "localhost", database: "myapp" },
async () => {
const db = dbContext.use();
await executeQuery(db);
}
);Namespaces prevent conflicts when multiple versions of a library are present:
// Library v1.0 code
import { useContext } from "unctx";
const useMyLib = useContext<ConfigV1>("my-awesome-lib");
// Library v2.0 code (different interface)
import { useContext } from "unctx";
const useMyLib = useContext<ConfigV2>("my-awesome-lib");
// Both can coexist without conflicts - they share the same context
// but are typed differently in each version// In your library (my-http-client/src/context.ts)
import { useContext, getContext } from "unctx";
interface HttpClientConfig {
baseURL: string;
timeout: number;
retries: number;
}
// Export the use function for consumers
export const useHttpConfig = useContext<HttpClientConfig>("my-http-client");
// Keep the full context internal for library setup
const httpConfigContext = getContext<HttpClientConfig>("my-http-client");
export function configureHttpClient(config: HttpClientConfig) {
httpConfigContext.set(config);
}
// In your library methods
export function makeRequest(endpoint: string) {
const config = useHttpConfig();
return fetch(`${config.baseURL}${endpoint}`, {
timeout: config.timeout
});
}// In application code
import { configureHttpClient, makeRequest } from "my-http-client";
// Configure library globally
configureHttpClient({
baseURL: "https://api.example.com",
timeout: 5000,
retries: 3
});
// Use throughout application
async function fetchUserData(userId: string) {
// Library automatically uses configured context
return makeRequest(`/users/${userId}`);
}import { createNamespace } from "unctx";
// Create namespace with default options
const appNamespace = createNamespace({
asyncContext: true // All contexts get async support
});
// Contexts inherit namespace defaults
const userContext = appNamespace.get<User>("user");
const sessionContext = appNamespace.get<Session>("session");
// Both contexts have async support enabled
await userContext.callAsync(userData, async () => {
await sessionContext.callAsync(sessionData, async () => {
// Both contexts available across async boundaries
const user = userContext.use();
const session = sessionContext.use();
await processUserSession(user, session);
});
});Different namespaces provide complete isolation:
import { createNamespace } from "unctx";
const libANamespace = createNamespace();
const libBNamespace = createNamespace();
const libAContext = libANamespace.get<string>("shared-key");
const libBContext = libBNamespace.get<number>("shared-key");
// These are completely separate contexts despite same key
libAContext.set("string value");
libBContext.set(42);
console.log(libAContext.use()); // "string value"
console.log(libBContext.use()); // 42// Use package name as key to avoid conflicts
const useMyLib = useContext<Config>("@company/my-lib");
// For scoped packages, use full scope
const useScoped = useContext<Data>("@scope/package-name");
// Add version suffix if needed for breaking changes
const useMyLibV2 = useContext<ConfigV2>("my-lib-v2");import { getContext } from "unctx";
const appContext = getContext<AppState>("my-app");
function safeGetAppState(): AppState | null {
try {
return appContext.use();
} catch {
// Context not available, return fallback
return null;
}
}
// Or use tryUse for safe access
function getAppStateWithFallback(): AppState {
return appContext.tryUse() ?? getDefaultAppState();
}import { getContext } from "unctx";
const resourceContext = getContext<Resource>("my-resource");
// Application shutdown cleanup
export function cleanup() {
const resource = resourceContext.tryUse();
if (resource) {
resource.cleanup();
resourceContext.unset();
}
}
// Test isolation
beforeEach(() => {
resourceContext.unset();
});Install with Tessl CLI
npx tessl i tessl/npm-unctx