# 🍦 unctx

> Composition-api in Vanilla js

[![npm version][npm-v-src]][npm-v-href]
[![npm downloads][npm-dm-src]][npm-dm-href]
[![package phobia][packagephobia-src]][packagephobia-href]
[![bundle phobia][bundlephobia-src]][bundlephobia-href]
[![codecov][codecov-src]][codecov-href]

## What is it?

[Vue.js](https://vuejs.org) introduced an amazing pattern called [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) that allows organizing complex logic by splitting it into reusable functions and grouping in logical order. `unctx` allows easily implementing composition api pattern in your javascript libraries without hassle.

## Integration

In your **awesome** library:

```bash
yarn add unctx
# or
npm install unctx
```

```js
import { createContext } from 'unctx'

const ctx = createContext()

export const useAwesome = ctx.use

// ...
ctx.call({ test: 1 }, () => {
  // This is similar to vue setup function
  // Any function called here, can use `useAwesome` to get { test: 1 }
})
```

User code:

```js
import { useAwesome } from 'awesome-lib'

// ...
function setup() {
  const ctx = useAwesome()
}
```

**Note:** when no context is presented `ctx.use` will throw an error. Use `ctx.tryUse` for tolerant usages (return nullable context).

## Using Namespaces

To avoid issues with multiple version of library, `unctx` provides a safe global namespace to access context by key (kept in [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)). **Important:** Please use a verbose name for key to avoid conflict with other js libraries. Using npm package name is recommended. Using symbols has no effect since it still causes multiple context issue.

```js
import { useContext, getContext } from 'unctx'

const useAwesome = useContext('awesome-lib')

// or
// const awesomeContext = getContext('awesome-lib')
```

You can also create your own internal namespace with `createNamespace` utility for more advanced use cases.

## Singleton Pattern

If you are sure it is safe to use a shared instance (not depending to request), you can also use `ctx.set` and `ctx.unset` for a [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern).

**Note:** You cannot combine `set` with `call`. Always use `unset` before replacing instance otherwise you will get `Context conflict` error.

```js
import { createContext } from 'unctx'

const ctx = createContext()
ctx.set(new Awesome())

// Replacing instance without unset
// ctx.set(new Awesome(), true)

export const useAwesome = ctx.use
```

## TypeScript

A generic type exists on all utilities to be set for instance/context type:

```ts
// Return type of useAwesome is Awesome | null
const { use: useAwesome } = createContext<Awesome>()
```

## Async Context

Normally, using context is only possible before first await statement:

```js
async function setup() {
  console.log(useAwesome()) // Returns context
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log(useAwesome()) // Returns null
}
```

A simple workaround, is caching context before first await and use it directly:

```js
async function setup() {
  const ctx = useAwesome()
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log(ctx) // We can directly access cached version of ctx
}
```

However, this is not always as easy as making a variable when using nested composables.

Unctx provides a better solution that transforms async to automatically restore context after each await call. This requires using a bundler such as Rollup, Vite or Webpack.

Import and register transform plugin:

```js
import { unctxPlugin } from 'unctx/plugin'

// Rollup
// TODO: Add to rollup configuration
unctxPlugin.rollup()

// Vite
// TODO: Add to vite configuration
unctxPlugin.vite()

// Webpack
// TODO: Add to webpack configuration
unctxPlugin.webpack()
```

Use `ctx.callAsync` instead of `ctx.call`:

```js
await ctx.callAsync('test', setup)
```

Any async function that requires context, should be wrapped with `withAsyncContext`:

```js
import { withAsyncContext } from 'unctx'

const setup = withAsyncContext(async () => {
  console.log(useAwesome()) // Returns context
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log(useAwesome()) // Still returns context with dark magic!
})
```

## Under the hood

Composition of functions is possible using temporary context injection. When calling `ctx.call(instance, cb)`, `instance` argument will be stored in a temporary variable then `cb` is called. Any function inside `cb`, can then implicitly access instance by using `ctx.use` (or `useAwesome`)

## Pitfalls

**context can be only used before first await**:

Please check Async context section.

**`Context conflict` error**:

In your library, you should only keep one `call()` running at a time (unless calling with same reference for first argument)

For instance this makes an error:

```js
ctx.call({ test: 1 }, () => {
  ctx.call({ test: 2 }, () => {
    // Throws error!
  })
})
```

## License

MIT. Made with 💖

<!-- Refs -->
[npm-v-src]: https://flat.badgen.net/npm/v/unctx/latest
[npm-v-href]: https://npmjs.com/package/unctx

[npm-dm-src]: https://flat.badgen.net/npm/dm/unctx
[npm-dm-href]: https://npmjs.com/package/unctx

[packagephobia-src]: https://flat.badgen.net/packagephobia/install/unctx
[packagephobia-href]: https://packagephobia.now.sh/result?p=unctx

[bundlephobia-src]: https://flat.badgen.net/bundlephobia/min/unctx
[bundlephobia-href]: https://bundlephobia.com/result?p=unctx

[codecov-src]: https://flat.badgen.net/codecov/c/github/unjs/unctx/master
[codecov-href]: https://codecov.io/gh/unjs/unctx
