@flattype/models 1.7.0 → 1.8.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 +58 -3
- package/dist/index.cjs +12 -6
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
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
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>
|
|
23
23
|
<a href="https://www.npmjs.com/package/@flattype/ai"><img src="https://img.shields.io/npm/v/@flattype/ai?label=%40flattype%2Fai&color=8b6cff" alt="npm @flattype/ai"></a>
|
|
24
|
+
<a href="https://www.npmjs.com/package/@flattype/cli"><img src="https://img.shields.io/npm/v/@flattype/cli?label=%40flattype%2Fcli&color=8b6cff" alt="npm @flattype/cli"></a>
|
|
24
25
|
<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>
|
|
25
26
|
<img src="https://img.shields.io/badge/core-3.7_KB_min%2Bgz-2fd4b2" alt="3.7 KB min+gz">
|
|
26
27
|
<img src="https://img.shields.io/badge/dependencies-0-2fd4b2" alt="No third-party dependencies">
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
<a href="#angular"><b>Angular</b></a> ·
|
|
38
39
|
<a href="#svelte-and-solid"><b>Svelte & Solid</b></a> ·
|
|
39
40
|
<a href="#claude-and-llms"><b>Claude & LLMs</b></a> ·
|
|
41
|
+
<a href="#cli"><b>CLI</b></a> ·
|
|
40
42
|
<a href="#benchmarks"><b>Benchmarks</b></a> ·
|
|
41
43
|
<a href="https://github.com/karammm/flattype/blob/main/CHANGELOG.md"><b>Changelog</b></a> ·
|
|
42
44
|
<a href="https://github.com/karammm/flattype/blob/main/ROADMAP.md"><b>Roadmap</b></a>
|
|
@@ -45,10 +47,10 @@
|
|
|
45
47
|
<p align="center">
|
|
46
48
|
<a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">
|
|
47
49
|
<img src="https://raw.githubusercontent.com/karammm/flattype/main/docs/assets/flattype-intro.gif" width="820"
|
|
48
|
-
alt="FlatType.js in
|
|
50
|
+
alt="FlatType.js in 84 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, the CLI generating models from OpenAPI, and the size comparison with JSON and TOON">
|
|
49
51
|
</a>
|
|
50
52
|
<br>
|
|
51
|
-
<sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the
|
|
53
|
+
<sub>▶ <a href="https://github.com/karammm/flattype/blob/main/docs/assets/flattype-intro.mp4">Watch the 84-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>
|
|
52
54
|
</p>
|
|
53
55
|
|
|
54
56
|
---
|
|
@@ -143,9 +145,10 @@ const [users] = createFlatResource(() => "/api/users", { model: User, list: true
|
|
|
143
145
|
| [`@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 |
|
|
144
146
|
| [`@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 |
|
|
145
147
|
| [`@flattype/ai`](https://github.com/karammm/flattype/blob/main/packages/ai) | `toPrompt`, `fromModelOutput`, `countTokens`, and MCP / Claude tool results | +1.3 KB |
|
|
148
|
+
| [`@flattype/cli`](https://github.com/karammm/flattype/blob/main/packages/cli) | The `flattype` command: `encode`, `decode`, `stats`, and `gen` (models from samples or OpenAPI) | Node tool |
|
|
146
149
|
| [`@flattype/shared`](https://github.com/karammm/flattype/blob/main/packages/shared) | Shared TypeScript types | types only |
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
Docs, a playground and published benchmarks are next on the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md). See the [roadmap](https://github.com/karammm/flattype/blob/main/ROADMAP.md).
|
|
149
152
|
|
|
150
153
|
## The format
|
|
151
154
|
|
|
@@ -493,6 +496,58 @@ messages.push({ role: "user", content: [claudeToolResult(toolUse.id, await runTo
|
|
|
493
496
|
|
|
494
497
|
See [`examples/ai-claude`](https://github.com/karammm/flattype/blob/main/examples/ai-claude) for a token report, a full Claude round trip, and a runnable MCP server (`claude mcp add flattype-orders -- node mcp-server.mjs`).
|
|
495
498
|
|
|
499
|
+
## CLI
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
npx @flattype/cli --help # or: npm install -D @flattype/cli, then use `flattype …`
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Convert, in pipes or files
|
|
507
|
+
curl -s https://api.example.com/users | npx @flattype/cli encode
|
|
508
|
+
npx @flattype/cli decode users.flat -o users.json
|
|
509
|
+
|
|
510
|
+
# Compare sizes
|
|
511
|
+
npx @flattype/cli stats users.json
|
|
512
|
+
# bytes gzip
|
|
513
|
+
# JSON 169545 12890
|
|
514
|
+
# Flat 93606 12568
|
|
515
|
+
# saved 45% 2%
|
|
516
|
+
|
|
517
|
+
# Generate typed models from an OpenAPI spec (3.0, 3.1 or Swagger 2; JSON or YAML)…
|
|
518
|
+
npx @flattype/cli gen --openapi openapi.yaml -o src/models.ts
|
|
519
|
+
|
|
520
|
+
# …or from a real response
|
|
521
|
+
curl -s https://api.example.com/users > users.json && npx @flattype/cli gen --sample users.json --name UsersPage
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
For an OpenAPI schema like
|
|
525
|
+
|
|
526
|
+
```yaml
|
|
527
|
+
Customer:
|
|
528
|
+
type: object
|
|
529
|
+
required: [id, full_name, created_at]
|
|
530
|
+
properties:
|
|
531
|
+
id: { type: integer }
|
|
532
|
+
full_name: { type: string }
|
|
533
|
+
created_at: { type: string, format: date-time }
|
|
534
|
+
address: { $ref: "#/components/schemas/Address" }
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`flattype gen --openapi` writes:
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
export const Customer = defineModel({
|
|
541
|
+
id: "integer",
|
|
542
|
+
fullName: "string",
|
|
543
|
+
createdAt: "date",
|
|
544
|
+
address: { type: Address, optional: true },
|
|
545
|
+
}, { keys: "snake" });
|
|
546
|
+
export type Customer = Infer<typeof Customer>;
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
It handles `$ref` between schemas, `allOf`, `required` (other fields become optional), `nullable` and `type: [..., "null"]`, `date` and `date-time` formats, enums and inline objects. A recursive reference becomes `"any"`, and the CLI warns you. Exit code 1 means bad input, 2 means a usage error. From code, use `fromOpenAPI(spec)` together with `toSource(models)`.
|
|
550
|
+
|
|
496
551
|
## Benchmarks
|
|
497
552
|
|
|
498
553
|
`pnpm compare` on the fixtures in [`fixtures/`](https://github.com/karammm/flattype/blob/main/fixtures/index.mjs) (Node 24, Apple Silicon):
|
package/dist/index.cjs
CHANGED
|
@@ -321,8 +321,10 @@ function singular(s) {
|
|
|
321
321
|
if (/[^s]s$/.test(s)) return s.slice(0, -1);
|
|
322
322
|
return s;
|
|
323
323
|
}
|
|
324
|
-
function toSource(
|
|
324
|
+
function toSource(modelOrModels, name) {
|
|
325
|
+
const roots = isModel(modelOrModels) ? [[name ?? "Model", modelOrModels]] : Object.entries(modelOrModels);
|
|
325
326
|
const names = /* @__PURE__ */ new Map();
|
|
327
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
326
328
|
const used = /* @__PURE__ */ new Set();
|
|
327
329
|
const blocks = [];
|
|
328
330
|
const unique = (base) => {
|
|
@@ -332,10 +334,13 @@ function toSource(model, name) {
|
|
|
332
334
|
return n;
|
|
333
335
|
};
|
|
334
336
|
const emit = (m, want) => {
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
let id = names.get(m);
|
|
338
|
+
if (id && emitted.has(m)) return id;
|
|
339
|
+
if (!id) {
|
|
340
|
+
id = unique(want);
|
|
341
|
+
names.set(m, id);
|
|
342
|
+
}
|
|
343
|
+
emitted.add(m);
|
|
339
344
|
const lines = [];
|
|
340
345
|
for (const [field, spec] of Object.entries(m.shape)) {
|
|
341
346
|
const key = IDENT.test(field) ? field : JSON.stringify(field);
|
|
@@ -363,7 +368,8 @@ export type ${id} = Infer<typeof ${id}>;`);
|
|
|
363
368
|
if (o.from !== void 0) parts.push(`from: ${JSON.stringify(o.from)}`);
|
|
364
369
|
return `{ ${parts.join(", ")} }`;
|
|
365
370
|
};
|
|
366
|
-
|
|
371
|
+
for (const [n, m] of roots) if (!names.has(m)) names.set(m, unique(pascal(n)));
|
|
372
|
+
for (const [, m] of roots) emit(m, names.get(m));
|
|
367
373
|
return `import { defineModel, type Infer } from "@flattype/models";
|
|
368
374
|
|
|
369
375
|
${blocks.join("\n\n")}
|
package/dist/index.d.cts
CHANGED
|
@@ -109,8 +109,12 @@ declare function defineModel<const S extends Shape>(shape: S, options?: ModelOpt
|
|
|
109
109
|
declare function infer(sample: unknown, options?: {
|
|
110
110
|
keys?: KeyStyle;
|
|
111
111
|
}): Model<Record<string, unknown>>;
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Prints TypeScript source for a model and the models nested in it, or for several named models at once
|
|
114
|
+
* (`toSource({ User, Order })`), where a model used by another is referenced by its name, not repeated.
|
|
115
|
+
*/
|
|
113
116
|
declare function toSource(model: Model<unknown>, name: string): string;
|
|
117
|
+
declare function toSource(models: Record<string, Model<unknown>>): string;
|
|
114
118
|
|
|
115
119
|
/** `createdAt` → `created_at`, `userID` → `user_id`. */
|
|
116
120
|
declare function toSnake(s: string): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -109,8 +109,12 @@ declare function defineModel<const S extends Shape>(shape: S, options?: ModelOpt
|
|
|
109
109
|
declare function infer(sample: unknown, options?: {
|
|
110
110
|
keys?: KeyStyle;
|
|
111
111
|
}): Model<Record<string, unknown>>;
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Prints TypeScript source for a model and the models nested in it, or for several named models at once
|
|
114
|
+
* (`toSource({ User, Order })`), where a model used by another is referenced by its name, not repeated.
|
|
115
|
+
*/
|
|
113
116
|
declare function toSource(model: Model<unknown>, name: string): string;
|
|
117
|
+
declare function toSource(models: Record<string, Model<unknown>>): string;
|
|
114
118
|
|
|
115
119
|
/** `createdAt` → `created_at`, `userID` → `user_id`. */
|
|
116
120
|
declare function toSnake(s: string): string;
|
package/dist/index.js
CHANGED
|
@@ -289,8 +289,10 @@ function singular(s) {
|
|
|
289
289
|
if (/[^s]s$/.test(s)) return s.slice(0, -1);
|
|
290
290
|
return s;
|
|
291
291
|
}
|
|
292
|
-
function toSource(
|
|
292
|
+
function toSource(modelOrModels, name) {
|
|
293
|
+
const roots = isModel(modelOrModels) ? [[name ?? "Model", modelOrModels]] : Object.entries(modelOrModels);
|
|
293
294
|
const names = /* @__PURE__ */ new Map();
|
|
295
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
294
296
|
const used = /* @__PURE__ */ new Set();
|
|
295
297
|
const blocks = [];
|
|
296
298
|
const unique = (base) => {
|
|
@@ -300,10 +302,13 @@ function toSource(model, name) {
|
|
|
300
302
|
return n;
|
|
301
303
|
};
|
|
302
304
|
const emit = (m, want) => {
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
let id = names.get(m);
|
|
306
|
+
if (id && emitted.has(m)) return id;
|
|
307
|
+
if (!id) {
|
|
308
|
+
id = unique(want);
|
|
309
|
+
names.set(m, id);
|
|
310
|
+
}
|
|
311
|
+
emitted.add(m);
|
|
307
312
|
const lines = [];
|
|
308
313
|
for (const [field, spec] of Object.entries(m.shape)) {
|
|
309
314
|
const key = IDENT.test(field) ? field : JSON.stringify(field);
|
|
@@ -331,7 +336,8 @@ export type ${id} = Infer<typeof ${id}>;`);
|
|
|
331
336
|
if (o.from !== void 0) parts.push(`from: ${JSON.stringify(o.from)}`);
|
|
332
337
|
return `{ ${parts.join(", ")} }`;
|
|
333
338
|
};
|
|
334
|
-
|
|
339
|
+
for (const [n, m] of roots) if (!names.has(m)) names.set(m, unique(pascal(n)));
|
|
340
|
+
for (const [, m] of roots) emit(m, names.get(m));
|
|
335
341
|
return `import { defineModel, type Infer } from "@flattype/models";
|
|
336
342
|
|
|
337
343
|
${blocks.join("\n\n")}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flattype/models",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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.
|
|
37
|
-
"@flattype/shared": "1.
|
|
36
|
+
"@flattype/core": "1.8.0",
|
|
37
|
+
"@flattype/shared": "1.8.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|