{"id":14189,"library":"unctx","title":"unctx: Composition API Utilities","description":"Unctx is a lightweight JavaScript library that implements the Composition API pattern, popularized by Vue.js, in vanilla JS. It empowers developers to structure complex logic into reusable functions and manage context-like state within their libraries and applications. The current stable version is 2.5.0, with consistent releases indicating active development. A key differentiator is its robust handling of asynchronous contexts, offering support for Node.js's native `AsyncLocalStorage` for persistence across async operations, alongside a build-time transformation for environments lacking native support. It also features a global namespacing mechanism via `globalThis` to mitigate context conflicts across multiple library instances or versions, emphasizing unique and verbose keys for stability.","status":"active","version":"2.5.0","language":"javascript","source_language":"en","source_url":"https://github.com/unjs/unctx","tags":["javascript","typescript"],"install":[{"cmd":"npm install unctx","lang":"bash","label":"npm"},{"cmd":"yarn add unctx","lang":"bash","label":"yarn"},{"cmd":"pnpm add unctx","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Required for native async context support in Node.js environments via `AsyncLocalStorage`.","package":"node:async_hooks","optional":true},{"reason":"Required for the build-time async context transformation plugin. Users of the plugin need to ensure compatibility.","package":"unplugin","optional":true}],"imports":[{"note":"Primary function to create a new, isolated context. Unctx is an ESM-first package.","wrong":"const { createContext } = require('unctx')","symbol":"createContext","correct":"import { createContext } from 'unctx'"},{"note":"Used for globally namespaced contexts, accepting a string key to retrieve or create a context.","wrong":"const useContext = require('unctx').useContext","symbol":"useContext","correct":"import { useContext } from 'unctx'"},{"note":"The build-time transformation plugin is imported from a subpath and requires a bundler like Rollup, Vite, or Webpack.","wrong":"import { unctxPlugin } from 'unctx'","symbol":"unctxPlugin","correct":"import { unctxPlugin } from 'unctx/plugin'"},{"note":"Used when enabling native async context. Requires Node.js (or a polyfill/runtime with support like Cloudflare Workers).","wrong":"import AsyncLocalStorage from 'async_hooks'","symbol":"AsyncLocalStorage","correct":"import { AsyncLocalStorage } from 'node:async_hooks'"}],"quickstart":{"code":"import { createContext } from 'unctx';\nimport { AsyncLocalStorage } from 'node:async_hooks'; // Only needed for native async context in Node.js\n\n// Create a context, optionally enabling native async context\nconst ctx = createContext({\n  asyncContext: true, // Enable native async context if available\n  AsyncLocalStorage: typeof AsyncLocalStorage !== 'undefined' ? AsyncLocalStorage : undefined\n});\n\n// Define a composable that uses this context\nexport const useCounter = () => {\n  const state = ctx.use();\n  if (!state) {\n    throw new Error('useCounter must be called within an active context.');\n  }\n  return {\n    increment: () => state.count++,\n    decrement: () => state.count--,\n    getCount: () => state.count,\n    setCount: (value) => { state.count = value; }\n  };\n};\n\nasync function runExample() {\n  console.log('--- Without active context ---');\n  try {\n    useCounter().getCount(); // This will throw if no context is active\n  } catch (e) {\n    console.error(e.message);\n  }\n\n  console.log('\\n--- Running with context ---');\n  await ctx.call({\n    count: 0 // Initial context state\n  }, async () => {\n    const counter = useCounter();\n    console.log('Initial count:', counter.getCount()); // Should be 0\n    counter.increment();\n    console.log('After increment:', counter.getCount()); // Should be 1\n\n    await new Promise(resolve => setTimeout(resolve, 50));\n    // With asyncContext: true, the context should persist across await\n    console.log('After await, count:', counter.getCount()); // Should still be 1\n    counter.increment();\n    console.log('Final count:', counter.getCount()); // Should be 2\n  });\n\n  console.log('\\n--- After context call ends ---');\n  try {\n    useCounter().getCount();\n  } catch (e) {\n    console.error(e.message);\n  }\n}\n\nrunExample();","lang":"typescript","description":"This quickstart demonstrates creating a context, defining a composable, using `ctx.call` to activate the context, and showing how `AsyncLocalStorage` (if configured) preserves context across `await` statements. It also highlights the error when no context is active."},"warnings":[{"fix":"Review your `unplugin` configuration and update to `unplugin` v2 if necessary. Consult the `unplugin` documentation for migration steps.","message":"Unctx v2.4.0 updated its `unplugin` dependency to v2. Users utilizing the `unctx/plugin` for build-time async context transformation might experience issues or need to update their `unplugin` setup to ensure compatibility.","severity":"breaking","affected_versions":">=2.4.0"},{"fix":"Always ensure `ctx.use()` is called within a `ctx.call()` scope, or use `ctx.tryUse()` if a nullable context is acceptable for tolerant usages.","message":"Calling `ctx.use()` (or `useAwesome()` in the example) when no context has been set via `ctx.call()` will throw an error.","severity":"gotcha","affected_versions":">=2.0.0"},{"fix":"For Node.js, enable native async context by passing `{ asyncContext: true, AsyncLocalStorage }` to `createContext` and `import { AsyncLocalStorage } from 'node:async_hooks'`. For other environments, use the `unctx/plugin` for build-time async transformation.","message":"Without enabling `asyncContext: true` and providing `AsyncLocalStorage` (or using the build-time transform), context set by `ctx.call()` will be lost across asynchronous operations like `await` or `setTimeout`.","severity":"gotcha","affected_versions":"<2.3.0 (before native async context support), or >=2.3.0 without explicit `asyncContext: true` configuration"},{"fix":"Adopt a clear naming convention for your context keys, ideally derived from your package's unique identifier to minimize collision risk.","message":"When using `useContext` or `getContext` with namespaces, always provide a verbose and unique string key (e.g., your npm package name) to avoid conflicts within `globalThis` if multiple libraries use `unctx`.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Migrate usages of `ctx.use()` that previously might have implicitly handled `undefined` to `ctx.tryUse()`, or ensure `ctx.use()` is only called when a context is guaranteed to be active.","message":"Version 2.0.0 introduced strict `ctx.use()` behavior, meaning it now throws errors if no context is found. The `ctx.tryUse()` method was introduced as a non-throwing alternative.","severity":"deprecated","affected_versions":"<2.0.0"}],"env_vars":null,"search_vec":"'2.5.0':49 'across':75,102 'activ':54 'alongsid':78 'also':90 'api':3,15 'applic':43 'async':76 'asynchron':64 'asynclocalstorag':72 'build':81 'build-tim':80 'complex':28 'composit':2,14 'conflict':101 'consist':51 'context':36,65,100 'context-lik':35 'current':45 'develop':25,55 'differenti':58 'emphas':108 'empow':24 'environ':85 'featur':91 'function':32 'global':93 'globalthi':97 'handl':62 'implement':12 'indic':53 'instanc':105 'javascript':9,115 'js':22 'key':57,112 'lack':86 'librari':10,41,104 'lightweight':8 'like':37 'logic':29 'manag':34 'mechan':95 'mitig':99 'multipl':103 'namespac':94 'nativ':71,87 'node.js':69 'offer':66 'oper':77 'pattern':16 'persist':74 'popular':17 'releas':52 'reusabl':31 'robust':61 'stabil':114 'stabl':46 'state':38 'structur':27 'support':67,88 'time':82 'transform':83 'typescript':116 'unctx':1,5 'uniqu':109 'util':4 'vanilla':21 'verbos':111 'version':47,107 'via':96 'vue.js':19 'within':39","created_at":"2026-04-20T01:58:23.707525+00:00","updated_at":"2026-04-20T01:58:23.707525+00:00","problems":[{"fix":"Wrap the code that calls `ctx.use()` within a `ctx.call()` block, or ensure a namespaced context has been established using `useContext` or `getContext` and populated with `ctx.call()`.","cause":"The `ctx.use()` method was called outside of an active context created by `ctx.call()` or a namespaced context provided by `useContext`/`getContext`.","error":"Error: [unctx] No active context"},{"fix":"For Node.js environments, enable native async context by passing `{ asyncContext: true, AsyncLocalStorage }` to `createContext` and importing `AsyncLocalStorage` from `node:async_hooks`. For environments without native `AsyncLocalStorage`, configure and use the `unctx/plugin` within your bundler (e.g., Rollup, Vite, Webpack) to enable build-time async transformation.","cause":"The context was lost across an asynchronous boundary because native async context was not enabled or the build-time transform was not applied.","error":"Context value is undefined (or null) after await/setTimeout"}],"ecosystem":"npm","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":null,"cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/unjs/unctx","docs":null,"changelog":null,"pypi":null,"npm":"https://www.npmjs.com/package/unctx","openapi_spec":null,"status_page":null,"smithery":null,"categories":["web-framework"],"base_url":null,"auth_type":null,"provenance":{"verified_status":null,"verified_at":null,"last_verified":"2026-06-17","next_check":"2026-07-18","install_tag":null}}