@flattype/models 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
<a href="https://www.npmjs.com/package/@flattype/core"><img src="https://img.shields.io/npm/v/@flattype/core?label=%40flattype%2Fcore&color=8b6cff" alt="npm @flattype/core"></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/@flattype/models"><img src="https://img.shields.io/npm/v/@flattype/models?label=%40flattype%2Fmodels&color=8b6cff" alt="npm @flattype/models"></a>
|
|
18
18
|
<a href="https://www.npmjs.com/package/@flattype/http"><img src="https://img.shields.io/npm/v/@flattype/http?label=%40flattype%2Fhttp&color=8b6cff" alt="npm @flattype/http"></a>
|
|
19
|
+
<a href="https://www.npmjs.com/package/@flattype/react"><img src="https://img.shields.io/npm/v/@flattype/react?label=%40flattype%2Freact&color=8b6cff" alt="npm @flattype/react"></a>
|
|
20
|
+
<a href="https://www.npmjs.com/package/@flattype/vue"><img src="https://img.shields.io/npm/v/@flattype/vue?label=%40flattype%2Fvue&color=8b6cff" alt="npm @flattype/vue"></a>
|
|
19
21
|
<a href="https://github.com/karammm/flattype/actions/workflows/ci.yml"><img src="https://github.com/karammm/flattype/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
20
22
|
<img src="https://img.shields.io/badge/core-3.7_KB_min%2Bgz-2fd4b2" alt="3.7 KB min+gz">
|
|
21
23
|
<img src="https://img.shields.io/badge/dependencies-0-2fd4b2" alt="No third-party dependencies">
|
|
@@ -27,6 +29,8 @@
|
|
|
27
29
|
<a href="#the-format"><b>Format</b></a> ·
|
|
28
30
|
<a href="#typed-models"><b>Models</b></a> ·
|
|
29
31
|
<a href="#http"><b>HTTP</b></a> ·
|
|
32
|
+
<a href="#react"><b>React</b></a> ·
|
|
33
|
+
<a href="#vue"><b>Vue</b></a> ·
|
|
30
34
|
<a href="#benchmarks"><b>Benchmarks</b></a> ·
|
|
31
35
|
<a href="https://github.com/karammm/flattype/blob/main/CHANGELOG.md"><b>Changelog</b></a> ·
|
|
32
36
|
<a href="https://github.com/karammm/flattype/blob/main/ROADMAP.md"><b>Roadmap</b></a>
|
|
@@ -35,10 +39,10 @@
|
|
|
35
39
|
<p align="center">
|
|
36
40
|
<a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">
|
|
37
41
|
<img src="https://raw.githubusercontent.com/karammm/flattype/main/docs/assets/flattype-intro.gif" width="820"
|
|
38
|
-
alt="FlatType.js in
|
|
42
|
+
alt="FlatType.js in 66 seconds: an API response encoded to Flat Notation, how tables and dot paths keep it small, typed models mapped from snake_case JSON, an Express server answering in Flat Notation or JSON, React hooks and Vue composables in real apps, and the size comparison with JSON and TOON">
|
|
39
43
|
</a>
|
|
40
44
|
<br>
|
|
41
|
-
<sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the
|
|
45
|
+
<sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the 66-second intro in full quality</a>. Every result shown is real output, generated by <a href="https://github.com/karammm/flattype/blob/main/scripts/video/record.mjs"><code>scripts/video/record.mjs</code></a>.</sub>
|
|
42
46
|
</p>
|
|
43
47
|
|
|
44
48
|
---
|
|
@@ -97,6 +101,14 @@ app.use(flatExpress()); // server: res.json() answers in Flat Notation when the
|
|
|
97
101
|
const users = await flatFetch("/api/users", { model: User, list: true }); // client: User[]
|
|
98
102
|
```
|
|
99
103
|
|
|
104
|
+
And in React or Vue:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useFlat } from "@flattype/react"; // or "@flattype/vue": the same API, with refs
|
|
108
|
+
|
|
109
|
+
const { data: users, loading, error } = useFlat("/api/users", { model: User, list: true }); // User[] | undefined
|
|
110
|
+
```
|
|
111
|
+
|
|
100
112
|
## Why FlatType
|
|
101
113
|
|
|
102
114
|
- **About half the size on list payloads.** A list of 1,000 users is 45% smaller than minified JSON and 50% smaller than TOON, because keys are written once and nested fields are flattened into the table header.
|
|
@@ -113,9 +125,11 @@ const users = await flatFetch("/api/users", { model: User, list: true }); // cli
|
|
|
113
125
|
| [`@flattype/core`](https://github.com/karammm/flattype/blob/main/packages/core) | `encode`, `decode`, `createDecoder`, `flatten`, `unflatten`, `stats` | 3.7 KB |
|
|
114
126
|
| [`@flattype/models`](https://github.com/karammm/flattype/blob/main/packages/models) | `defineModel`, `infer`, `toSource`: typed models from JSON or Flat Notation | +3.2 KB |
|
|
115
127
|
| [`@flattype/http`](https://github.com/karammm/flattype/blob/main/packages/http) | `flatFetch`, `createClient`, `flatAxios`, `streamRows`, and `flatExpress` / `flatFastify` / `flatHono` | +2.3 KB |
|
|
128
|
+
| [`@flattype/react`](https://github.com/karammm/flattype/blob/main/packages/react) | `useFlat`, `useFlatSuspense`, `useFlatStream`, `useModel`, `FlatProvider` | +1.4 KB |
|
|
129
|
+
| [`@flattype/vue`](https://github.com/karammm/flattype/blob/main/packages/vue) | The same composables for Vue 3, with reactive URLs, plus the `createFlat()` plugin | +1.1 KB |
|
|
116
130
|
| [`@flattype/shared`](https://github.com/karammm/flattype/blob/main/packages/shared) | Shared TypeScript types | types only |
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
Angular and Svelte bindings, Claude helpers and a CLI are planned. See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
|
|
119
133
|
|
|
120
134
|
## The format
|
|
121
135
|
|
|
@@ -242,6 +256,83 @@ for await (const { path, index, value } of streamRows("/api/export")) { // rows
|
|
|
242
256
|
|
|
243
257
|
A JSON-only server works with every client helper: the response is just read as JSON.
|
|
244
258
|
|
|
259
|
+
## React
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm install @flattype/react @flattype/models
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { FlatProvider, useFlat, useFlatStream, useFlatSuspense } from "@flattype/react";
|
|
267
|
+
|
|
268
|
+
<FlatProvider baseURL="/api" headers={{ authorization: `Bearer ${token}` }}>
|
|
269
|
+
<App />
|
|
270
|
+
</FlatProvider>;
|
|
271
|
+
|
|
272
|
+
function Users() {
|
|
273
|
+
const { data, error, loading, refetch } = useFlat("/users", { model: User, list: true });
|
|
274
|
+
if (error) return <ErrorMessage error={error} />; // HttpError, ModelError or a network error
|
|
275
|
+
if (!data) return <Spinner />;
|
|
276
|
+
return <UserList users={data} onRefresh={refetch} />; // data: User[]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function Profile({ id }: { id: number }) {
|
|
280
|
+
const { data: user } = useFlatSuspense(`/users/${id}`, { model: User }); // inside <Suspense>
|
|
281
|
+
return <h2>{user.fullName}</h2>; // user: User, never undefined
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function Export() {
|
|
285
|
+
const { rows, done } = useFlatStream("/export", { model: Row, table: "rows" });
|
|
286
|
+
return <Table rows={rows} footer={done ? "Done" : `Loading… ${rows.length}`} />; // rows appear while downloading
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
| Hook | Returns | Notes |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `useFlat(url, options)` | `{ data, error, loading, refetch }` | A new URL aborts the old request. Old data stays while the next loads. `url: null` or `enabled: false` skips the request |
|
|
293
|
+
| `useFlatSuspense(url, options)` | `{ data, refetch }` | Suspends, sends errors to an error boundary, and caches by URL (one request for many components) |
|
|
294
|
+
| `useFlatStream(url, options)` | `{ rows, done, error, restart }` | One render per network chunk. `table` picks one table by path |
|
|
295
|
+
| `useModel(data, Model, { list })` | the mapped value | Memoized. For data from props, stores or sockets |
|
|
296
|
+
|
|
297
|
+
Works with React 18 and 19, in StrictMode and with any server. A JSON-only API works too. A runnable app with Playwright tests in three browsers is in [`examples/react-app`](https://github.com/karammm/flattype/blob/main/examples/react-app).
|
|
298
|
+
|
|
299
|
+
## Vue
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm install @flattype/vue @flattype/models
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// main.ts
|
|
307
|
+
app.use(createFlat({ baseURL: "/api", headers: () => ({ authorization: `Bearer ${token.value}` }) }));
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```vue
|
|
311
|
+
<script setup lang="ts">
|
|
312
|
+
import { useFlat, useFlatStream, useFlatSuspense } from "@flattype/vue";
|
|
313
|
+
|
|
314
|
+
const props = defineProps<{ id: number }>();
|
|
315
|
+
|
|
316
|
+
// The URL can be a ref or a getter: changing it aborts the old request and loads the new one.
|
|
317
|
+
const { data: user, error, loading, refetch } = useFlat(() => `/users/${props.id}`, { model: User }); // Ref<User | undefined>
|
|
318
|
+
|
|
319
|
+
// Inside <Suspense>, `await` the first response so `data` is always set.
|
|
320
|
+
const { data: me } = await useFlatSuspense("/me", { model: User }); // Ref<User>
|
|
321
|
+
|
|
322
|
+
// Big lists render while they download.
|
|
323
|
+
const { rows, done } = useFlatStream("/export", { model: Row, table: "rows" });
|
|
324
|
+
</script>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
| Composable | Returns | Notes |
|
|
328
|
+
| --- | --- | --- |
|
|
329
|
+
| `useFlat(url, options)` | `{ data, error, loading, refetch }` refs | `url` and `enabled` can be refs or getters. Aborts on change and on unmount |
|
|
330
|
+
| `useFlatSuspense(url, options)` | `Promise<{ data, error, loading, refetch }>` | For `async setup()` in `<Suspense>`. The first error goes to `onErrorCaptured` |
|
|
331
|
+
| `useFlatStream(url, options)` | `{ rows, done, error, restart }` refs | One update per network chunk. `table` picks one table by path |
|
|
332
|
+
| `useModel(source, Model, { list })` | a computed ref | For props, Pinia stores and sockets |
|
|
333
|
+
|
|
334
|
+
Vue 3.3+. A runnable app with Playwright tests in three browsers is in [`examples/vue-app`](https://github.com/karammm/flattype/blob/main/examples/vue-app).
|
|
335
|
+
|
|
245
336
|
## Benchmarks
|
|
246
337
|
|
|
247
338
|
`pnpm compare` on the fixtures in [`fixtures/`](https://github.com/karammm/flattype/blob/main/fixtures/index.mjs) (Node 24, Apple Silicon):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flattype/models",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Typed models for API data: key mapping, coercion and defaults, from JSON or Flat Notation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"flattype"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@flattype/
|
|
37
|
-
"@flattype/
|
|
36
|
+
"@flattype/core": "1.4.0",
|
|
37
|
+
"@flattype/shared": "1.4.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|