@flattype/models 1.4.1 → 1.6.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 +109 -6
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -18,6 +18,8 @@
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
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
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>
21
+ <a href="https://www.npmjs.com/package/@flattype/angular"><img src="https://img.shields.io/npm/v/@flattype/angular?label=%40flattype%2Fangular&color=8b6cff" alt="npm @flattype/angular"></a>
22
+ <a href="https://www.npmjs.com/package/@flattype/svelte"><img src="https://img.shields.io/npm/v/@flattype/svelte?label=%40flattype%2Fsvelte&color=8b6cff" alt="npm @flattype/svelte"></a>
21
23
  <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>
22
24
  <img src="https://img.shields.io/badge/core-3.7_KB_min%2Bgz-2fd4b2" alt="3.7 KB min+gz">
23
25
  <img src="https://img.shields.io/badge/dependencies-0-2fd4b2" alt="No third-party dependencies">
@@ -31,6 +33,8 @@
31
33
  <a href="#http"><b>HTTP</b></a> ·
32
34
  <a href="#react"><b>React</b></a> ·
33
35
  <a href="#vue"><b>Vue</b></a> ·
36
+ <a href="#angular"><b>Angular</b></a> ·
37
+ <a href="#svelte-and-solid"><b>Svelte &amp; Solid</b></a> ·
34
38
  <a href="#benchmarks"><b>Benchmarks</b></a> ·
35
39
  <a href="https://github.com/karammm/flattype/blob/main/CHANGELOG.md"><b>Changelog</b></a> ·
36
40
  <a href="https://github.com/karammm/flattype/blob/main/ROADMAP.md"><b>Roadmap</b></a>
@@ -39,10 +43,10 @@
39
43
  <p align="center">
40
44
  <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">
41
45
  <img src="https://raw.githubusercontent.com/karammm/flattype/main/docs/assets/flattype-intro.gif" width="820"
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">
46
+ alt="FlatType.js in 77 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, Vue, Angular and Svelte in real apps, and the size comparison with JSON and TOON">
43
47
  </a>
44
48
  <br>
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>
49
+ <sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the 77-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>
46
50
  </p>
47
51
 
48
52
  ---
@@ -101,12 +105,19 @@ app.use(flatExpress()); // server: res.json() answers in Flat Notation when the
101
105
  const users = await flatFetch("/api/users", { model: User, list: true }); // client: User[]
102
106
  ```
103
107
 
104
- And in React or Vue:
108
+ And in React, Vue, Angular, Svelte or Solid:
105
109
 
106
110
  ```tsx
107
111
  import { useFlat } from "@flattype/react"; // or "@flattype/vue": the same API, with refs
108
112
 
109
113
  const { data: users, loading, error } = useFlat("/api/users", { model: User, list: true }); // User[] | undefined
114
+
115
+ // Angular: signals on top of HttpClient
116
+ users = flatResource(() => "/api/users", { model: User, list: true }); // users.value(): User[] | undefined
117
+
118
+ // Svelte: a store, $users.data; Solid: a resource
119
+ const users = flatStore("/api/users", { model: User, list: true });
120
+ const [users] = createFlatResource(() => "/api/users", { model: User, list: true });
110
121
  ```
111
122
 
112
123
  ## Why FlatType
@@ -126,10 +137,12 @@ const { data: users, loading, error } = useFlat("/api/users", { model: User, lis
126
137
  | [`@flattype/models`](https://github.com/karammm/flattype/blob/main/packages/models) | `defineModel`, `infer`, `toSource`: typed models from JSON or Flat Notation | +3.2 KB |
127
138
  | [`@flattype/http`](https://github.com/karammm/flattype/blob/main/packages/http) | `flatFetch`, `createClient`, `flatAxios`, `streamRows`, and `flatExpress` / `flatFastify` / `flatHono` | +2.3 KB |
128
139
  | [`@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 |
140
+ | [`@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.2 KB |
141
+ | [`@flattype/angular`](https://github.com/karammm/flattype/blob/main/packages/angular) | `flatInterceptor` for HttpClient, the signal-based `flatResource` / `flatStream`, the `flatModel` pipe | +1.8 KB |
142
+ | [`@flattype/svelte`](https://github.com/karammm/flattype/blob/main/packages/svelte) | Svelte stores (`flatStore`, `flatStreamStore`, `modelStore`), and `/solid` with `createFlatResource` / `createFlatStream` | +1.1 KB / +0.9 KB |
130
143
  | [`@flattype/shared`](https://github.com/karammm/flattype/blob/main/packages/shared) | Shared TypeScript types | types only |
131
144
 
132
- Angular and Svelte bindings, Claude helpers and a CLI are planned. See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
145
+ Claude helpers and a CLI are planned. See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
133
146
 
134
147
  ## The format
135
148
 
@@ -333,6 +346,96 @@ const { rows, done } = useFlatStream("/export", { model: Row, table: "rows" });
333
346
 
334
347
  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
348
 
349
+ ## Angular
350
+
351
+ ```bash
352
+ npm install @flattype/angular @flattype/models
353
+ ```
354
+
355
+ ```ts
356
+ // main.ts
357
+ bootstrapApplication(AppComponent, {
358
+ providers: [
359
+ provideHttpClient(withInterceptors([flatInterceptor])), // every HttpClient call can read Flat Notation
360
+ provideFlatType({ baseURL: "/api" }),
361
+ ],
362
+ });
363
+ ```
364
+
365
+ ```ts
366
+ @Component({
367
+ imports: [FlatModelPipe],
368
+ template: `
369
+ @for (u of users.value()?.data; track u.id) { <li>{{ u.fullName }}</li> }
370
+ <p>{{ (raw() | flatModel: Address)?.city }}</p>
371
+ <p>{{ events.done() ? "Done" : "Loading… " + events.rows().length }}</p>
372
+ `,
373
+ })
374
+ export class UsersComponent {
375
+ page = signal(1);
376
+ users = flatResource(() => `/users?page=${this.page()}`, { model: UsersPage }); // refetches when page changes
377
+ events = flatStream(() => "/export", { model: Row, table: "rows" }); // rows while downloading
378
+ raw = toSignal(inject(HttpClient).get("/api/address")); // plain HttpClient works too
379
+ }
380
+ ```
381
+
382
+ | API | What it does |
383
+ | --- | --- |
384
+ | `flatInterceptor` | Adds `Accept: text/x-flat, application/json` and decodes either format, error bodies included. Skip a request with `FLAT_TYPE_SKIP` in its `HttpContext` |
385
+ | `flatResource(() => url, options)` | `value`, `error` and `isLoading` signals plus `reload()`. Runs on HttpClient and cancels the old request when the URL changes |
386
+ | `flatStream(() => url, options)` | `rows`, `done` and `error` signals plus `restart()`. Uses `fetch`, because HttpClient can't stream |
387
+ | `flatModel` pipe, `mapModel` / `mapModelList` | Model mapping in templates and in RxJS pipelines |
388
+
389
+ Angular 19+ (built with partial compilation, so it works with every later version). A runnable Angular 22 app with Playwright tests in three browsers is in [`examples/angular-app`](https://github.com/karammm/flattype/blob/main/examples/angular-app).
390
+
391
+ ## Svelte and Solid
392
+
393
+ ```bash
394
+ npm install @flattype/svelte @flattype/models
395
+ ```
396
+
397
+ **Svelte** (3, 4 and 5). The stores follow the Svelte store contract, so `$store` just works:
398
+
399
+ ```svelte
400
+ <script lang="ts">
401
+ import { createFlat } from "@flattype/svelte";
402
+ import { derived, writable } from "svelte/store";
403
+
404
+ const api = createFlat({ baseURL: "/api", headers: () => ({ authorization: `Bearer ${token}` }) });
405
+
406
+ const users = api.store("/users", { model: User, list: true }); // $users.data: User[] | undefined
407
+ const id = writable(1);
408
+ const user = api.store(derived(id, (i) => `/users/${i}`), { model: User }); // the URL can be a store
409
+ const events = api.streamStore("/export", { model: Row, table: "rows" }); // rows while downloading
410
+ </script>
411
+
412
+ {#each $users.data ?? [] as u (u.id)}<li>{u.fullName}</li>{/each}
413
+ <p>{$events.done ? "Done" : `Loading… ${$events.rows.length}`}</p>
414
+ ```
415
+
416
+ **Solid** (`@flattype/svelte/solid`):
417
+
418
+ ```tsx
419
+ import { createFlatResource, createFlatStream } from "@flattype/svelte/solid";
420
+
421
+ const [user, { refetch }] = createFlatResource(() => `/users/${props.id}`, { baseURL: "/api", model: User });
422
+ const events = createFlatStream(() => "/api/export", { model: Row, table: "rows" });
423
+
424
+ <Suspense fallback={<Spinner />}>
425
+ <Show when={user()}>{(u) => <h2>{u().fullName}</h2>}</Show>
426
+ </Suspense>;
427
+ ```
428
+
429
+ | API | What it does |
430
+ | --- | --- |
431
+ | `flatStore(url, options)` | A store of `{ data, error, loading }` plus `refetch()`. Lazy: it fetches on the first subscriber and aborts when the last one leaves. A URL store change aborts the old request |
432
+ | `flatStreamStore(url, options)` | A store of `{ rows, done, error }` plus `restart()`. One update per network chunk |
433
+ | `modelStore(source, Model)` | Maps another store's value to a model |
434
+ | `createFlatResource(() => url, options)` | A Solid resource, for `<Suspense>` and `<ErrorBoundary>`. Aborts on change and on cleanup |
435
+ | `createFlatStream(() => url, options)` | `rows`, `done` and `error` accessors plus `restart()` |
436
+
437
+ The stores also work in Solid, or anywhere else, through the store contract: `from(flatStore(...))`. Runnable apps with Playwright tests in three browsers are in [`examples/svelte-app`](https://github.com/karammm/flattype/blob/main/examples/svelte-app) and [`examples/solid-app`](https://github.com/karammm/flattype/blob/main/examples/solid-app).
438
+
336
439
  ## Benchmarks
337
440
 
338
441
  `pnpm compare` on the fixtures in [`fixtures/`](https://github.com/karammm/flattype/blob/main/fixtures/index.mjs) (Node 24, Apple Silicon):
@@ -364,7 +467,7 @@ pnpm build && pnpm typecheck && pnpm test
364
467
  pnpm bench # encode/decode vs JSON
365
468
  pnpm compare # size and parse time vs JSON and TOON
366
469
  pnpm check:size # bundle budgets
367
- pnpm video # re-render docs/assets/flattype-intro.{mp4,gif} (needs ffmpeg)
470
+ pnpm video # re-render docs/assets/flattype-intro.{mp4,gif} (needs ffmpeg and the Angular example built)
368
471
  ```
369
472
 
370
473
  This is a pnpm + Turborepo monorepo built with tsup and tested with Vitest and fast-check. Releases use Changesets, and all `@flattype/*` packages share one version number. Every release follows the checklist in [ROADMAP.md](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flattype/models",
3
- "version": "1.4.1",
3
+ "version": "1.6.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.4.1",
37
- "@flattype/shared": "1.4.1"
36
+ "@flattype/core": "1.6.0",
37
+ "@flattype/shared": "1.6.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"