Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/thomasaribart/onion.js
Type-safe & ultra-lightweight lib to declare and use high-order functions based on HotScript!
https://github.com/thomasaribart/onion.js
Last synced: 5 days ago
JSON representation
Type-safe & ultra-lightweight lib to declare and use high-order functions based on HotScript!
- Host: GitHub
- URL: https://github.com/thomasaribart/onion.js
- Owner: ThomasAribart
- License: mit
- Created: 2024-08-20T22:34:13.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2024-09-12T12:51:34.000Z (2 months ago)
- Last Synced: 2024-10-07T17:48:53.646Z (about 1 month ago)
- Language: TypeScript
- Homepage:
- Size: 793 KB
- Stars: 9
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
💖 _Huge thanks to the [sponsors](https://github.com/sponsors/ThomasAribart) who help me maintain this repo:_
# 🧅 Wrap everything, without breaking types 🥲
`OnionJS` is a **type-safe** and **ultra-lightweight** _(2KB)_ library to design and apply **wrappers**, based on [HotScript](https://github.com/gvergnaud/hotscript) **high-order types**.
In particular, it's awesome for building and using type-safe [**middlewares**](https://en.wikipedia.org/wiki/Middleware) (see the [dedicated section](#-building-middlewares)).
## Table of content
- 🎬 [Installation](#-installation)
- 🌈 [Layers](#-layers)
- 🧅Onion.wrap
- ♻️Onion.produce
- 🚀 [Building Middlewares](#-building-middlewares)
- 🏗️ [Composing Layers](#%EF%B8%8F-composing-layers)
- 💪 [Customizable Layers](#-customizable-layers)## 🎬 Installation
```bash
# npm
npm install @onion.js/core# yarn
yarn add @onion.js/core
```## 🌈 Layers
In `OnionJS`, _**Layers**_ are functions that transform _**Subjects**_ from a `before` to an `after` state:
For instance, let's define a layer that `JSON.stringifies` the `'body'` property of an object:
```ts
import type { Layer } from '@onion.js/core'
import type { Objects } from 'hotscript'const jsonStringifyBody: Layer<
Record, // subject type
Objects.Update<'body', string>, // outward HO Type
Objects.Update<'body', unknown> // inward HO Type
> = before => {
const after = {
...before,
body: JSON.stringify(before.body)
}return after
}
```## 🧅 `Onion.wrap`
We can now apply this layer to any object with `Onion.wrap`:
```ts
import { Onion } from '@onion.js/core'const before = {
headers: null,
body: { foo: 'bar' }
}const after = Onion.wrap(before).with(jsonStringifyBody)
// ^? { headers: null; body: string } 🙌
```Notice how the `after` type is correctly inferred thanks to [Hotscript](https://github.com/gvergnaud/hotscript) high-order types!
...And why stop here? Let's add **more layers**:
```ts
import type { Identity } from 'hotscript'// Logs the object
const logObject: Layer<
Record,
Identity,
Identity
> = before => {
console.log(before)
return before
}// Layers are gracefully composed 🙌
const after = Onion.wrap(before).with(
logObject, // 1st layer
jsonStringifyBody, // 2nd layer etc.
...
)
```## ♻️ `Onion.produce`
But wait, that's not all! Layers can also work _**inward**_ 🤯
Given an `after` type and some layers, `OnionJS` can infer the expected `before` type:
For instance, we can reuse `jsonStringifyBody` to produce the same result as above with `Onion.produce`:
```ts
const after = Onion.produce<{ headers: null; body: string }>()
.with(
jsonStringifyBody, // last layer
logObject, // 2nd to last etc.
...
)
.from({ headers: null, body: { foo: 'bar' } })
// ^? ({ headers: null; body: unknown }) => { headers: null; body: string } 🙌
```> ☝️ Note that layers are applied in reverse for improved readability.
## 🚀 Building Middlewares
`OnionJS` really shines when **wrapping functions** with [middlewares](https://en.wikipedia.org/wiki/Middleware).
In this case, layers receive `before` functions and return `after` functions (hence the _"high-order function"_ name):
For instance, let's apply `jsonStringifyBody` to the **output** of a function:
```ts
import type { Layer } from '@onion.js/core'
import type { Functions, Objects } from 'hotscript'const jsonStringifyRespBody: Layer<
(...params: unknown[]) => Record,
Functions.MapReturnType>,
Functions.MapReturnType>
> = before => {
function after(...params: unknown[]) {
return jsonStringifyBody(before(...params))
}return after
}
```Now we can use this layer to `wrap` and `produce` functions 🙌 With literally the same code as above:
```ts
import { Onion } from '@onion.js/core'const before = () => ({ body: { foo: 'bar' } })
const after = Onion.wrap(before).with(jsonStringifyRespBody)
// ^? () => { body: string } 🙌const produced = Onion.produce<() => { body: string }>()
.with(jsonStringifyRespBody)
.from(before)
// ^? (before: () => { body: unknown }) => (() => { body: string }) 🙌
```## 🏗️ Composing Layers
You can create new layers from existing ones with `composeDown` and `composeUp`:
```ts
import { compose, Onion } from '@onion.js/core'const composedLayer = composeDown(
logObject, // 1st layer
jsonStringifyBody, // 2nd layer etc.
...
)
const after = Onion.wrap(before).with(composedLayer)// Similar to:
const after = Onion.wrap(before).with(
logObject,
jsonStringifyBody,
...
)
```> It is advised to use `composeDown` when wrapping, and `composeUp` when producing for better readability.
## 💪 Customizable Layers
Layers can **accept parameters** to allow for customization. But make sure to use [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) if needed!
For instance, let's define a `jsonStringifyProp` layer that `JSON.stringifies` any property you want:
```ts
type JSONStringifyPropLayer = Layer<
Record,
Objects.Update,
Objects.Update
>const jsonStringifyProp =
(key: KEY): JSONStringifyPropLayer =>
before => {
const after = {
...before,
[key]: JSON.stringify(before[key])
}return after
}const after = Onion.wrap({ yolo: { foo: 'bar' } })
// ^? { yolo: string } 🙌
.with(jsonStringifyProp('yolo'))
```We can even compose customizable layers by making good use of the `ComposeUpLayers` and `ComposeDownLayers` type:
```ts
import type { ComposeDownLayers } from '@onion.js/core'type LogAndStringifyPropLayer = ComposeDownLayers<
LogObjectLayer,
JSONStringifyPropLayer
>const logAndStringifyProp = (
key: KEY
): JSONStringifyPropLayer => composeDown(logOject, jsonStringifyProp(key))const after = Onion.wrap({ yolo: { foo: 'bar' } })
// ^? { yolo: string } 🙌
.with(jsonStringifyProp('yolo'))
```