@stiviar/modeled-react 1.0.2 → 1.0.3
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 +48 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @stiviar/modeled-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A declarative, type-safe API layer for React. Register your API once — as a type, a URL, and a Zod schema — and get a fully typed data-fetching hook with built-in caching and runtime validation.
|
|
4
|
+
|
|
5
|
+
No codegen, no backend language restrictions, no manual `any` casting between fetch and component. Just describe the shape of what you're calling, and `modeled-react` handles the rest.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -10,23 +12,42 @@ npm i @stiviar/modeled-react
|
|
|
10
12
|
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
13
|
-
Define your models once:
|
|
15
|
+
Define your models once — covering a simple list, a single resource by id, a filtered list, and an OData-style query:
|
|
14
16
|
|
|
15
17
|
```ts
|
|
16
18
|
// api.ts
|
|
17
19
|
import { register } from "@stiviar/modeled-react";
|
|
18
|
-
import {
|
|
20
|
+
import { Todo, TodoSchema, Todos, TodosSchema } from "./types";
|
|
19
21
|
|
|
20
22
|
export const api = register<{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
todos: { type: Todos }; // simple endpoint — no params
|
|
24
|
+
todo: { type: Todo; params: { id: string } }; // single resource, accessed by id
|
|
25
|
+
todosByStatus: { type: Todos; params: { completed: boolean } }; // filtered list, plain query params
|
|
26
|
+
todosOData: {
|
|
27
|
+
type: Todos;
|
|
28
|
+
params: { $filter?: string; $orderby?: string; $top?: number };
|
|
29
|
+
}; // OData-style query
|
|
23
30
|
}>({
|
|
24
|
-
users: {
|
|
25
|
-
url: "https://api.example.com/users",
|
|
26
|
-
schema: UsersSchema,
|
|
27
|
-
},
|
|
28
31
|
todos: {
|
|
29
32
|
url: "https://api.example.com/todos",
|
|
33
|
+
schema: TodosSchema, // schema is type checked agains the registered type - no mismatch possible
|
|
34
|
+
},
|
|
35
|
+
todo: {
|
|
36
|
+
url: (p) => `https://api.example.com/todos/${p.id}`, // type of p: {id: string}
|
|
37
|
+
schema: TodoSchema,
|
|
38
|
+
},
|
|
39
|
+
todosByStatus: {
|
|
40
|
+
url: (p) => `https://api.example.com/todos?completed=${p.completed}`, // type of p: {completed: boolean}
|
|
41
|
+
schema: TodosSchema,
|
|
42
|
+
},
|
|
43
|
+
todosOData: {
|
|
44
|
+
url: (p) => {
|
|
45
|
+
// type of p: { $filter?: string; $orderby?: string; $top?: number }
|
|
46
|
+
const query = new URLSearchParams(
|
|
47
|
+
Object.entries(p).map(([k, v]) => [k, String(v)]),
|
|
48
|
+
);
|
|
49
|
+
return `https://api.example.com/odata/todos?${query.toString()}`;
|
|
50
|
+
},
|
|
30
51
|
schema: TodosSchema,
|
|
31
52
|
},
|
|
32
53
|
});
|
|
@@ -41,16 +62,27 @@ Use it in a component:
|
|
|
41
62
|
import { useData } from "./api";
|
|
42
63
|
|
|
43
64
|
export function Component() {
|
|
44
|
-
const { data
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
const { data: allTodos } = useData("todos");
|
|
66
|
+
const { data: oneTodo } = useData("todo", { id: "42" });
|
|
67
|
+
const { data: activeTodos } = useData("todosByStatus", { completed: false });
|
|
68
|
+
const { data: topTodos } = useData("todosOData", {
|
|
69
|
+
$filter: "completed eq false",
|
|
70
|
+
$orderby: "createdAt desc",
|
|
71
|
+
$top: 10,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return <div>{allTodos?.length} todos total</div>;
|
|
50
75
|
}
|
|
51
76
|
```
|
|
52
77
|
|
|
53
|
-
Types declared for a model flow through the whole codebase — hover `data` in your IDE and see the inferred type, with a compile-time error if your schema and type ever drift apart.
|
|
78
|
+
Types declared for a model flow through the whole codebase — hover `data` in your IDE and see the inferred type, with a compile-time error if your schema and type ever drift apart. Every response is also validated at runtime against the same schema, so a change on the backend fails loudly instead of silently breaking the UI.
|
|
79
|
+
|
|
80
|
+
## Features
|
|
81
|
+
|
|
82
|
+
- Full type safety, no manual casting
|
|
83
|
+
- Runtime validation on every fetch
|
|
84
|
+
- Built-in caching (TanStack Query under the hood)
|
|
85
|
+
- Typed error handling
|
|
54
86
|
|
|
55
87
|
## Testing
|
|
56
88
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stiviar/modeled-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Declarative, type-safe API layer for frontend apps. Register your API once, get validated data, caching, and testable contracts everywhere",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest",
|
|
28
28
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
29
|
-
"patch": "npm version patch && npm run build && npm publish"
|
|
29
|
+
"patch": "npm run test && npm version patch && npm run build && npm publish"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@testing-library/jest-dom": "^7.0.0",
|