@mittwald/react-ghostmaker 1.0.3 → 1.1.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 +28 -14
- package/dist/esm/queries.js +9 -6
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -7,10 +7,6 @@ models that seamlessly integrate with React Suspense and TanStack Query. These
|
|
|
7
7
|
ghosts automatically handle async operations, caching, and loading states,
|
|
8
8
|
making your code cleaner and more declarative.
|
|
9
9
|
|
|
10
|
-
**🔑 Say goodbye to query key management!** Ghost Maker automatically generates
|
|
11
|
-
and manages TanStack Query keys based on your method chains, so you can focus on
|
|
12
|
-
your business logic instead of cache coordination.
|
|
13
|
-
|
|
14
10
|
## 📖 Table of Contents
|
|
15
11
|
|
|
16
12
|
- [✨ Features](#-features)
|
|
@@ -28,7 +24,9 @@ your business logic instead of cache coordination.
|
|
|
28
24
|
|
|
29
25
|
## ✨ Features
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
🎭 **Ghost Proxies**: Transform any object—models, existing API clients,
|
|
28
|
+
services, or utilities into a suspense-ready ghost
|
|
29
|
+
|
|
32
30
|
- ⚡ **Lazy Execution**: Ghosts don't execute until `.use()` or `.render()` is
|
|
33
31
|
called
|
|
34
32
|
- 🎯 **Precise Loading States**: Control exactly where Suspense boundaries
|
|
@@ -171,18 +169,38 @@ function TraditionalApp() {
|
|
|
171
169
|
|
|
172
170
|
### Creating Your First Ghost
|
|
173
171
|
|
|
172
|
+
React Ghost Maker can turn any object into a ghost—not just domain models, but
|
|
173
|
+
also existing API clients, service objects, or utility classes. This makes it
|
|
174
|
+
easy to add Suspense and caching to legacy code or third-party libraries.
|
|
175
|
+
|
|
174
176
|
```tsx
|
|
175
177
|
import { makeGhost } from "@mittwald/react-ghostmaker";
|
|
176
178
|
import { Suspense } from "react";
|
|
177
179
|
|
|
178
|
-
//
|
|
180
|
+
// Example: Wrapping an API client
|
|
181
|
+
class BlogApiClient {
|
|
182
|
+
async fetchBlog(id: string) {
|
|
183
|
+
const response = await fetch(`/api/blogs/${id}`);
|
|
184
|
+
return response.json();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const BlogApiGhost = makeGhost(new BlogApiClient());
|
|
189
|
+
|
|
190
|
+
function BlogView() {
|
|
191
|
+
// Use ghostified API client
|
|
192
|
+
const blog = BlogApiGhost.fetchBlog("123").use();
|
|
193
|
+
return <article>{blog.title}</article>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ...existing code...
|
|
197
|
+
|
|
198
|
+
// You can also use domain models as shown below:
|
|
179
199
|
class Blog {
|
|
180
200
|
constructor(public id: string) {}
|
|
181
|
-
|
|
182
201
|
static ofId(id: string): Blog {
|
|
183
202
|
return new Blog(id);
|
|
184
203
|
}
|
|
185
|
-
|
|
186
204
|
async getDetailed() {
|
|
187
205
|
const response = await fetch(`/api/blogs/${this.id}`);
|
|
188
206
|
const data = await response.json();
|
|
@@ -196,23 +214,18 @@ class DetailedBlog extends Blog {
|
|
|
196
214
|
this.title = data.title;
|
|
197
215
|
this.author = data.author;
|
|
198
216
|
}
|
|
199
|
-
|
|
200
217
|
public readonly title: string;
|
|
201
218
|
public readonly author: string;
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
// Create a ghost
|
|
205
221
|
const BlogGhost = makeGhost(Blog);
|
|
206
222
|
|
|
207
|
-
|
|
208
|
-
function BlogView() {
|
|
223
|
+
function BlogViewModel() {
|
|
209
224
|
const blogGhost = BlogGhost.ofId("123");
|
|
210
|
-
|
|
211
225
|
const { value: blogTitle, invalidate } = blogGhost
|
|
212
226
|
.getDetailed()
|
|
213
227
|
.title.transform((title) => title.toUpperCase())
|
|
214
228
|
.useGhost();
|
|
215
|
-
|
|
216
229
|
return (
|
|
217
230
|
<article>
|
|
218
231
|
<h2>{blogTitle}</h2>
|
|
@@ -226,6 +239,7 @@ function App() {
|
|
|
226
239
|
return (
|
|
227
240
|
<Suspense fallback={<div>Loading blog...</div>}>
|
|
228
241
|
<BlogView />
|
|
242
|
+
<BlogViewModel />
|
|
229
243
|
</Suspense>
|
|
230
244
|
);
|
|
231
245
|
}
|
package/dist/esm/queries.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import is from "@sindresorhus/is";
|
|
2
2
|
import { modelIdentifiers } from "./modelIdentifier";
|
|
3
3
|
import { hashObject } from "./hash";
|
|
4
|
-
const
|
|
5
|
-
if (is.primitive(something)) {
|
|
6
|
-
return String(something);
|
|
7
|
-
}
|
|
4
|
+
const getObjectName = (something) => {
|
|
8
5
|
const isObject = is.object(something);
|
|
9
6
|
const isClass = is.class(something);
|
|
10
7
|
const isFunction = is.function(something);
|
|
11
|
-
|
|
8
|
+
return isFunction
|
|
12
9
|
? something.name
|
|
13
10
|
: isClass || isObject
|
|
14
11
|
? something.constructor.name
|
|
15
12
|
: "unknown";
|
|
13
|
+
};
|
|
14
|
+
const getTargetQueryKey = (something) => {
|
|
15
|
+
if (is.primitive(something)) {
|
|
16
|
+
return String(something);
|
|
17
|
+
}
|
|
18
|
+
const objectName = getObjectName(something);
|
|
16
19
|
const thisModelIdentifiers = modelIdentifiers
|
|
17
20
|
.map((fn) => fn(something))
|
|
18
21
|
.filter(is.string)
|
|
@@ -26,7 +29,7 @@ const getArgKey = (arg) => {
|
|
|
26
29
|
if (is.primitive(arg)) {
|
|
27
30
|
return String(arg);
|
|
28
31
|
}
|
|
29
|
-
return hashObject(arg)
|
|
32
|
+
return `${getObjectName(arg)}(hash:${hashObject(arg)})`;
|
|
30
33
|
};
|
|
31
34
|
export const queries = {
|
|
32
35
|
ghostmaker: () => ["react-ghostmaker"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/react-ghostmaker",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "Mittwald CM Service GmbH & Co. KG <opensource@mittwald.de>",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Marco Falkenberg <m.falkenberg@mittwald.de>"
|
|
@@ -41,19 +41,19 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@eslint/eslintrc": "^3.3.1",
|
|
44
|
-
"@eslint/js": "^9.
|
|
44
|
+
"@eslint/js": "^9.39.1",
|
|
45
45
|
"@tanstack/react-query": "^5.90.5",
|
|
46
46
|
"@testing-library/dom": "^10.4.1",
|
|
47
47
|
"@testing-library/react": "^16.3.0",
|
|
48
48
|
"@types/react": "^19.2.2",
|
|
49
49
|
"@types/react-dom": "^19.2.2",
|
|
50
|
-
"@typescript-eslint/eslint-plugin": "^8.46.
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
51
51
|
"@typescript-eslint/parser": "^8.46.1",
|
|
52
52
|
"eslint": "^9.37.0",
|
|
53
53
|
"eslint-config-prettier": "^10.1.8",
|
|
54
54
|
"eslint-plugin-json": "^4.0.1",
|
|
55
55
|
"eslint-plugin-prettier": "^5.5.4",
|
|
56
|
-
"globals": "^16.
|
|
56
|
+
"globals": "^16.5.0",
|
|
57
57
|
"happy-dom": "^20.0.2",
|
|
58
58
|
"license-checker-rseidelsohn": "^4.4.2",
|
|
59
59
|
"prettier": "^3.6.2",
|