Skip to content

Commit 1d13c0d

Browse files
committed
Docs
1 parent d1fb870 commit 1d13c0d

File tree

10 files changed

+330
-7
lines changed

10 files changed

+330
-7
lines changed

apps/website/docs/.vitepress/config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export default defineConfig({
117117
]),
118118
...createSidebar('contracts', [
119119
{ text: 'Get Started', link: '/contracts/' },
120+
{ text: 'APIs', link: '/contracts/api' },
120121
]),
121122
'/magazine/': [
122123
{

apps/website/docs/.vitepress/theme/LiveDemo.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Sandpack } from 'sandpack-vue3';
33
44
import repositoryPackageJson from '../../../../../package.json';
55
import webApiRaw from '../../../../../packages/web-api/dist/web-api.js?raw';
6+
import contractsRaw from '../../../../../packages/contracts/dist/contracts.js?raw';
67
78
const repositoryVersions = {
89
...repositoryPackageJson.dependencies,
@@ -14,6 +15,7 @@ const props = defineProps(['demoFile']);
1415
const files = {
1516
'/src/App.vue': props.demoFile,
1617
...localPackage({ name: 'web-api', content: webApiRaw }),
18+
...localPackage({ name: 'contracts', content: contractsRaw })
1719
};
1820
1921
const customSetup = {
@@ -41,10 +43,5 @@ function localPackage({ name, content }) {
4143
</script>
4244

4345
<template>
44-
<Sandpack
45-
template="vue3"
46-
theme="auto"
47-
:files="files"
48-
:customSetup="customSetup"
49-
/>
46+
<Sandpack template="vue3" theme="auto" :files="files" :customSetup="customSetup" />
5047
</template>

apps/website/docs/contracts/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# APIs
2+
3+
Full list of available APIs.
4+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup>
2+
import { arr, num } from '@withease/contracts';
3+
4+
const contract = arr(num);
5+
</script>
6+
7+
<template>
8+
<h1>
9+
Out <em>Contract</em> is ensuring that passed data is an array of numbers
10+
</h1>
11+
<section>
12+
<h2>Valid data example</h2>
13+
<p>Let us pass [1, 2, 3] to the <em>Contract</em></p>
14+
<p>isData() 👉 {{ contract.isData([1, 2, 3]) }}</p>
15+
<p>getErrorMessages() 👉 {{
16+
JSON.stringify(contract.getErrorMessages([1, 2, 3]))
17+
}}</p>
18+
</section>
19+
20+
<section>
21+
<h2>Invalid data example</h2>
22+
<p>Let us pass [1, 'WHOA', 3] to the <em>Contract</em>.
23+
instead
24+
of number.</p>
25+
<p>
26+
isData() 👉 {{ contract.isData([1, 'WHOA', 3]) }}</p>
27+
<p>
28+
getErrorMessages() 👉 {{
29+
JSON.stringify(contract.getErrorMessages([1, 'WHOA', 3]))
30+
}}</p>
31+
</section>
32+
</template>

apps/website/docs/contracts/index.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<script setup>
2+
import pkg from '../../../../packages/contracts/package.json';
3+
import demoFile from './array_numbers.live.vue?raw';
4+
import { data as sizes } from './sizes.data';
5+
import SizeChart from './size_chart.vue';
6+
import bytes from 'bytes'
7+
8+
const maxSize = pkg['size-limit'].at(0).limit;
9+
10+
const allSizes = [
11+
{ name: '@withease/contracts', size: bytes(maxSize) },
12+
...(sizes ?? [])
13+
];
14+
</script>
15+
16+
# contracts
17+
18+
Extremely small library (less than **{{maxSize}}** controlled by CI) for creating [_Contracts_](/protocols/contract) that allows you to introduce data validation on edges of the application with no performance compromises.
19+
20+
## Installation
21+
22+
First, you need to install package:
23+
24+
::: code-group
25+
26+
```sh [pnpm]
27+
pnpm install @withease/contracts
28+
```
29+
30+
```sh [yarn]
31+
yarn add @withease/contracts
32+
```
33+
34+
```sh [npm]
35+
npm install @withease/contracts
36+
```
37+
38+
:::
39+
40+
## Creating a _Contract_
41+
42+
`@withease/contracts` exports bunch of utilities that can be used to create a _Contract_, read the full API reference [here](/contracts/api). Any of the utilities returns a _Contract_ object, that accepts something `unknown` and checks if it is something concrete defined by the used utility.
43+
44+
<LiveDemo :demoFile="demoFile" />
45+
46+
## Usage of a _Contract_
47+
48+
`@withease/contracts` is designed to be compatible with Effector's ecosystem without additional interop, so most of the time you can pass created [_Contract_](/protocols/contract) to other Effector's libraries as is.
49+
50+
### Farfetched
51+
52+
[Farfetched](https://ff.effector.dev) is the advanced data fetching tool for web applications based of Effector. It suggests to ensure that data received from the server is conforms desired [_Contract_](/protocols/contracts).
53+
54+
```ts
55+
import { createJsonQuery } from '@farfetched/core';
56+
import { rec, str, arr, val, or } from '@withease/contracts';
57+
58+
const characterQuery = createJsonQuery({
59+
params: declareParams<{ id: number }>(),
60+
request: {
61+
method: 'GET',
62+
url: ({ id }) => `https://rickandmortyapi.com/api/character/${id}`,
63+
},
64+
response: {
65+
// after receiving data from the server
66+
// check if it is conforms the Contract to ensure
67+
// API does not return something unexpected
68+
contract: rec({
69+
id: str,
70+
name: str,
71+
status: Status,
72+
species: str,
73+
type: str,
74+
gender: Gender,
75+
origin: rec({ name: str, url: str }),
76+
location: rec({ name: str, url: str }),
77+
image: or(val('Female'), val('Male'), val('Genderless')),
78+
episode: arr(str),
79+
}),
80+
},
81+
});
82+
```
83+
84+
### effector-storage
85+
86+
[`effector-storage`](https://github.com/yumauri/effector-storage) is a small module for Effector to sync stores with different storages (local storage, session storage, async storage, IndexedDB, cookies, server side storage, etc).
87+
88+
Since data is stored in an external storage it is important to validate it before using it in the application.
89+
90+
```ts
91+
import { createStore } from 'effector';
92+
import { persist } from 'effector-storage';
93+
import { num } from '@withease/contracts';
94+
95+
const $counter = createStore(0);
96+
97+
persist({
98+
store: $counter,
99+
key: 'counter',
100+
// after reading value from a storage check if a value is number
101+
// to avoid pushing invalid data to the Store
102+
contract: num,
103+
});
104+
```
105+
106+
## Integration with other libraries
107+
108+
Since `@withease/contracts` is compatible [_Contract_](/protocols/contract) protocol it can be used with any library that supports it.
109+
110+
For instance, you can define a part of a [_Contract_](/protocols/contract) with [Zod](https://zod.dev/) and combine it with `@withease/contracts`:
111+
112+
```ts
113+
import { z } from 'zod';
114+
import { arr, rec } from '@withease/contracts';
115+
import { zodContract } from '@farfetched/zod';
116+
117+
const User = z.object({
118+
name: z.string(),
119+
});
120+
121+
const MyContract = arr(
122+
rec({
123+
// 👇 easily integrate Zod via compatibility layer
124+
users: zodContract(User),
125+
})
126+
);
127+
```
128+
129+
The full list of libraries that support _Contract_ protocol can be found [here](/protocols/contract).
130+
131+
## Differences from other libraries
132+
133+
<section v-if="sizes">
134+
It is extremely small and we mean it 👇
135+
136+
<SizeChart :sizes="allSizes" />
137+
138+
::: tip
139+
Data fetched directly from https://esm.run/ and updates on every commit.
140+
:::
141+
142+
</section>
143+
<section v-else>
144+
It is significantly smaller than other libraries for creating _Contracts_.
145+
</section>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script setup>
2+
import { computed, defineProps } from 'vue';
3+
import { Bar } from 'vue-chartjs';
4+
import {
5+
Chart as ChartJS,
6+
Title,
7+
Tooltip,
8+
BarElement,
9+
CategoryScale,
10+
LinearScale,
11+
} from 'chart.js';
12+
import prettyBytes from 'pretty-bytes';
13+
14+
ChartJS.register(Title, Tooltip, BarElement, CategoryScale, LinearScale);
15+
16+
const props = defineProps({
17+
sizes: {
18+
type: Array,
19+
required: true,
20+
},
21+
});
22+
const chartData = computed(() => {
23+
const sortedSizes = props.sizes.toSorted((a, b) => a.size - b.size);
24+
return {
25+
labels: sortedSizes.map((item) => item.name),
26+
datasets: [
27+
{ data: sortedSizes.map((item) => item.size), backgroundColor: '#3451b2' },
28+
],
29+
}
30+
});
31+
32+
const chartOptions = {
33+
responsive: true,
34+
scales: {
35+
y: {
36+
beginAtZero: true,
37+
ticks: {
38+
callback: (value) =>
39+
typeof value === 'number' ? prettyBytes(value) : value,
40+
},
41+
},
42+
},
43+
plugins: {
44+
tooltip: {
45+
callbacks: {
46+
label: (item) => prettyBytes(item.parsed.y),
47+
title: ([item]) => item?.label,
48+
},
49+
},
50+
},
51+
};
52+
</script>
53+
54+
<template>
55+
<Bar id="size-chart" :options="chartOptions" :data="chartData" />
56+
</template>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export default {
2+
async load() {
3+
try {
4+
const [
5+
zodSize,
6+
runtypesSize,
7+
ioTsSize,
8+
fpTsSize,
9+
superstructSize,
10+
typedContractsSize,
11+
valibotSize,
12+
] = await Promise.all([
13+
definePackageSize('zod', 'lib/index.js'),
14+
definePackageSize('runtypes', 'lib/index.js'),
15+
definePackageSize('io-ts', 'lib/index.js'),
16+
definePackageSize('fp-ts', 'lib/index.js'),
17+
definePackageSize('superstruct', 'dist/index.mjs'),
18+
definePackageSize('typed-contracts', 'lib/bundle.js'),
19+
definePackageSize('valibot', './dist/index.js'),
20+
]);
21+
22+
return [
23+
{ name: 'Zod', size: zodSize },
24+
{ name: 'runtypes', size: runtypesSize },
25+
{ name: 'io-ts + fp-ts', size: ioTsSize + fpTsSize },
26+
{ name: 'superstruct', size: superstructSize },
27+
{ name: 'typed-contracts', size: typedContractsSize },
28+
{ name: 'valibot', size: valibotSize },
29+
];
30+
} catch (error) {
31+
return null;
32+
}
33+
},
34+
};
35+
36+
async function definePackageSize(packageName, moduleName) {
37+
const response = await fetch(
38+
`https://esm.run/${packageName}@latest/${moduleName}`
39+
);
40+
41+
const encodedSize = Number(response.headers.get('content-length') ?? 0);
42+
43+
return encodedSize;
44+
}

apps/website/docs/protocols/contract.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ A rule to statically validate received data. Any object following the strict API
1111

1212
::: tip Packages that provide integration for creating _Contract_
1313

14+
- [`@withease/contracts`](/contracts/)
1415
- [`@farfetched/runtypes`](https://farfetched.pages.dev/api/contracts/runtypes.html)
1516
- [`@farfetched/zod`](https://farfetched.pages.dev/api/contracts/zod.html)
1617
- [`@farfetched/io-ts`](https://farfetched.pages.dev/api/contracts/io-ts.html)

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@reduxjs/toolkit": "^2.0.1",
1818
"@size-limit/file": "^7.0.8",
1919
"@types/node": "^20.12.7",
20+
"chart.js": "^4.4.3",
2021
"effector": "23.0.0",
2122
"effector-vue": "23.0.0",
2223
"glob": "^8.0.3",
@@ -37,10 +38,13 @@
3738
"vitepress": "1.2.3",
3839
"vitepress-plugin-rss": "^0.2.8",
3940
"vitest": "~1.5.0",
40-
"vue": "3.4.27"
41+
"vue": "3.4.27",
42+
"vue-chartjs": "^5.3.1"
4143
},
4244
"dependencies": {
4345
"@algolia/client-search": "^4.14.3",
46+
"bytes": "^3.1.2",
47+
"pretty-bytes": "^6.1.1",
4448
"sandpack-vue3": "^3.1.7"
4549
}
4650
}

0 commit comments

Comments
 (0)