@flattype/models 1.1.0 → 1.3.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.
Files changed (2) hide show
  1. package/README.md +107 -7
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -15,8 +15,10 @@
15
15
  <p align="center">
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
+ <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>
18
20
  <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>
19
- <img src="https://img.shields.io/badge/core-3.5_KB_min%2Bgz-2fd4b2" alt="3.5 KB min+gz">
21
+ <img src="https://img.shields.io/badge/core-3.7_KB_min%2Bgz-2fd4b2" alt="3.7 KB min+gz">
20
22
  <img src="https://img.shields.io/badge/dependencies-0-2fd4b2" alt="No third-party dependencies">
21
23
  <a href="https://github.com/karammm/flattype/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
22
24
  </p>
@@ -25,6 +27,8 @@
25
27
  <a href="#quick-start"><b>Quick start</b></a> ·
26
28
  <a href="#the-format"><b>Format</b></a> ·
27
29
  <a href="#typed-models"><b>Models</b></a> ·
30
+ <a href="#http"><b>HTTP</b></a> ·
31
+ <a href="#react"><b>React</b></a> ·
28
32
  <a href="#benchmarks"><b>Benchmarks</b></a> ·
29
33
  <a href="https://github.com/karammm/flattype/blob/main/CHANGELOG.md"><b>Changelog</b></a> ·
30
34
  <a href="https://github.com/karammm/flattype/blob/main/ROADMAP.md"><b>Roadmap</b></a>
@@ -33,10 +37,10 @@
33
37
  <p align="center">
34
38
  <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">
35
39
  <img src="https://raw.githubusercontent.com/karammm/flattype/main/docs/assets/flattype-intro.gif" width="820"
36
- alt="FlatType.js in 42 seconds: an API response encoded to Flat Notation, how tables and dot paths keep it small, typed models mapped from snake_case JSON, and the size comparison with JSON and TOON">
40
+ alt="FlatType.js in 62 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 in a real app, and the size comparison with JSON and TOON">
37
41
  </a>
38
42
  <br>
39
- <sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the 42-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>
43
+ <sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the 62-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>
40
44
  </p>
41
45
 
42
46
  ---
@@ -44,7 +48,7 @@
44
48
  ## Quick start
45
49
 
46
50
  ```bash
47
- npm install @flattype/core @flattype/models
51
+ npm install @flattype/core @flattype/models @flattype/http
48
52
  ```
49
53
 
50
54
  ```ts
@@ -85,23 +89,44 @@ type User = Infer<typeof User>; // { id: number; fullName: string; createdAt: Da
85
89
  const users = User.parseList(await res.json()); // full_name → fullName, ISO string → Date
86
90
  ```
87
91
 
92
+ Or let both ends speak Flat Notation, with JSON for every other client:
93
+
94
+ ```ts
95
+ import { flatExpress, flatFetch } from "@flattype/http";
96
+
97
+ app.use(flatExpress()); // server: res.json() answers in Flat Notation when the client asks
98
+
99
+ const users = await flatFetch("/api/users", { model: User, list: true }); // client: User[]
100
+ ```
101
+
102
+ And in React:
103
+
104
+ ```tsx
105
+ import { useFlat } from "@flattype/react";
106
+
107
+ const { data: users, loading, error } = useFlat("/api/users", { model: User, list: true }); // User[] | undefined
108
+ ```
109
+
88
110
  ## Why FlatType
89
111
 
90
112
  - **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.
91
113
  - **Made for LLM context.** You pay for every character you send to Claude or another model. Flat Notation is plain `key: value` text that both models and people read easily, and `decode(text, { strict: false })` recovers data from model replies.
92
114
  - **Typed models without boilerplate.** Declare the fields you want and get full TypeScript types. snake_case keys, numeric strings, ISO dates, defaults and nullable fields are handled for you, and every problem is reported with its path.
115
+ - **Drop-in for your API.** One middleware for Express, Fastify, Hono or any fetch-API server. Clients that ask get Flat Notation, everyone else still gets JSON, and big lists can be streamed row by row.
93
116
  - **Lossless.** `decode(encode(x))` always equals `JSON.parse(JSON.stringify(x))`. Thousands of random values check this on every test run.
94
- - **Small and safe.** The core is 3.5 KB min+gz, with no third-party dependencies. It's ESM + CJS with full types, works in the browser and Node 18+, and guards against `__proto__` pollution.
117
+ - **Small and safe.** The core is 3.7 KB min+gz, with no third-party dependencies. It's ESM + CJS with full types, works in the browser and Node 18+, and guards against `__proto__` pollution.
95
118
 
96
119
  ## Packages
97
120
 
98
121
  | Package | What it does | Size (min+gz) |
99
122
  | --- | --- | ---: |
100
- | [`@flattype/core`](https://github.com/karammm/flattype/blob/main/packages/core) | `encode`, `decode`, `flatten`, `unflatten`, `stats` | 3.5 KB |
123
+ | [`@flattype/core`](https://github.com/karammm/flattype/blob/main/packages/core) | `encode`, `decode`, `createDecoder`, `flatten`, `unflatten`, `stats` | 3.7 KB |
101
124
  | [`@flattype/models`](https://github.com/karammm/flattype/blob/main/packages/models) | `defineModel`, `infer`, `toSource`: typed models from JSON or Flat Notation | +3.2 KB |
125
+ | [`@flattype/http`](https://github.com/karammm/flattype/blob/main/packages/http) | `flatFetch`, `createClient`, `flatAxios`, `streamRows`, and `flatExpress` / `flatFastify` / `flatHono` | +2.3 KB |
126
+ | [`@flattype/react`](https://github.com/karammm/flattype/blob/main/packages/react) | `useFlat`, `useFlatSuspense`, `useFlatStream`, `useModel`, `FlatProvider` | +1.4 KB |
102
127
  | [`@flattype/shared`](https://github.com/karammm/flattype/blob/main/packages/shared) | Shared TypeScript types | types only |
103
128
 
104
- React, Vue, Angular and Svelte bindings, HTTP helpers, Claude helpers and a CLI are planned. See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
129
+ Vue, Angular and Svelte bindings, Claude helpers and a CLI are planned. See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
105
130
 
106
131
  ## The format
107
132
 
@@ -193,6 +218,81 @@ export const UsersPage = defineModel({
193
218
  export type UsersPage = Infer<typeof UsersPage>;
194
219
  ```
195
220
 
221
+ ## HTTP
222
+
223
+ `@flattype/http` adds Flat Notation to an existing API without breaking anyone. A response is re-encoded only when the request says `Accept: text/x-flat`, and `Vary: Accept` keeps caches correct.
224
+
225
+ **Server**
226
+
227
+ ```ts
228
+ import { flatExpress, flatFastify, flatHono, negotiate } from "@flattype/http";
229
+
230
+ expressApp.use(flatExpress()); // Express: res.json(...)
231
+ await fastifyApp.register(flatFastify); // Fastify: object replies
232
+ honoApp.use(flatHono()); // Hono: c.json(...)
233
+ return negotiate(request, Response.json(data)); // Bun, Deno, Next.js route handlers, Workers
234
+ ```
235
+
236
+ **Client**
237
+
238
+ ```ts
239
+ import { createClient, flatAxios, flatFetch, streamRows } from "@flattype/http";
240
+
241
+ const user = await flatFetch("/api/users/7", { model: User }); // typed, throws HttpError on 4xx/5xx
242
+
243
+ const api = createClient({ baseURL: "https://api.example.com/v1", headers: { authorization } });
244
+ const page = await api.get("/users", { query: { page: 2 } });
245
+ await api.post("/users", { name: "Chen" }); // JSON request body
246
+
247
+ const axiosApi = flatAxios(axios.create({ baseURL: "/api" })); // axios: response.data is decoded
248
+
249
+ for await (const { path, index, value } of streamRows("/api/export")) { // rows while still downloading
250
+ table.append(value);
251
+ }
252
+ ```
253
+
254
+ A JSON-only server works with every client helper: the response is just read as JSON.
255
+
256
+ ## React
257
+
258
+ ```bash
259
+ npm install @flattype/react @flattype/models
260
+ ```
261
+
262
+ ```tsx
263
+ import { FlatProvider, useFlat, useFlatStream, useFlatSuspense } from "@flattype/react";
264
+
265
+ <FlatProvider baseURL="/api" headers={{ authorization: `Bearer ${token}` }}>
266
+ <App />
267
+ </FlatProvider>;
268
+
269
+ function Users() {
270
+ const { data, error, loading, refetch } = useFlat("/users", { model: User, list: true });
271
+ if (error) return <ErrorMessage error={error} />; // HttpError, ModelError or a network error
272
+ if (!data) return <Spinner />;
273
+ return <UserList users={data} onRefresh={refetch} />; // data: User[]
274
+ }
275
+
276
+ function Profile({ id }: { id: number }) {
277
+ const { data: user } = useFlatSuspense(`/users/${id}`, { model: User }); // inside <Suspense>
278
+ return <h2>{user.fullName}</h2>; // user: User, never undefined
279
+ }
280
+
281
+ function Export() {
282
+ const { rows, done } = useFlatStream("/export", { model: Row, table: "rows" });
283
+ return <Table rows={rows} footer={done ? "Done" : `Loading… ${rows.length}`} />; // rows appear while downloading
284
+ }
285
+ ```
286
+
287
+ | Hook | Returns | Notes |
288
+ | --- | --- | --- |
289
+ | `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 |
290
+ | `useFlatSuspense(url, options)` | `{ data, refetch }` | Suspends, sends errors to an error boundary, and caches by URL (one request for many components) |
291
+ | `useFlatStream(url, options)` | `{ rows, done, error, restart }` | One render per network chunk. `table` picks one table by path |
292
+ | `useModel(data, Model, { list })` | the mapped value | Memoized. For data from props, stores or sockets |
293
+
294
+ 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).
295
+
196
296
  ## Benchmarks
197
297
 
198
298
  `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.1.0",
3
+ "version": "1.3.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/core": "1.1.0",
37
- "@flattype/shared": "1.1.0"
36
+ "@flattype/core": "1.3.0",
37
+ "@flattype/shared": "1.3.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"