live-cache 0.2.2 → 0.2.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 +122 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@ A lightweight, type-safe client-side database library for JavaScript written in
|
|
|
14
14
|
- ♻️ Pluggable invalidation strategies (timeouts, focus, websockets)
|
|
15
15
|
- 🎨 Beautiful examples included
|
|
16
16
|
|
|
17
|
+
## Examples
|
|
18
|
+
|
|
19
|
+
See the `examples/` folder for ready-to-run demos:
|
|
20
|
+
- `examples/react`: PokéAPI explorer built with controllers + `useController`
|
|
21
|
+
- `examples/vanilla-js`: Simple browser demo using the UMD build
|
|
22
|
+
|
|
17
23
|
## Installation
|
|
18
24
|
|
|
19
25
|
```bash
|
|
@@ -132,17 +138,29 @@ Use `Controller<T, Name>` for **server-backed** resources: it wraps a `Collectio
|
|
|
132
138
|
|
|
133
139
|
`commit()` is the important part: it **publishes** the latest snapshot to subscribers and **persists** the snapshot using the configured `StorageManager`.
|
|
134
140
|
|
|
141
|
+
The `fetch(where?)` method can fetch all data or query-specific data based on the `where` parameter:
|
|
142
|
+
|
|
135
143
|
```ts
|
|
136
144
|
import { Controller } from "live-cache";
|
|
137
145
|
|
|
138
146
|
type User = { id: number; name: string };
|
|
139
147
|
|
|
140
148
|
class UsersController extends Controller<User, "users"> {
|
|
141
|
-
async
|
|
142
|
-
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
async fetch(where?: string | Partial<User>): Promise<[User[], number]> {
|
|
150
|
+
// Fetch all users if no where clause
|
|
151
|
+
if (!where) {
|
|
152
|
+
const res = await fetch("/api/users");
|
|
153
|
+
if (!res.ok) throw new Error("Failed to fetch users");
|
|
154
|
+
const data = (await res.json()) as User[];
|
|
155
|
+
return [data, data.length];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Fetch specific user by id or name
|
|
159
|
+
const id = typeof where === "string" ? where : where.id;
|
|
160
|
+
const res = await fetch(`/api/users/${id}`);
|
|
161
|
+
if (!res.ok) throw new Error("Failed to fetch user");
|
|
162
|
+
const data = (await res.json()) as User;
|
|
163
|
+
return [[data], 1];
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
/**
|
|
@@ -151,7 +169,7 @@ class UsersController extends Controller<User, "users"> {
|
|
|
151
169
|
*/
|
|
152
170
|
invalidate() {
|
|
153
171
|
this.abort();
|
|
154
|
-
void this.
|
|
172
|
+
void this.update();
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
async renameUser(id: number, name: string) {
|
|
@@ -163,6 +181,68 @@ class UsersController extends Controller<User, "users"> {
|
|
|
163
181
|
}
|
|
164
182
|
```
|
|
165
183
|
|
|
184
|
+
### Real-world example: PokéAPI integration
|
|
185
|
+
|
|
186
|
+
Here's a complete example from the `examples/react` demo showing how to build controllers for a public API:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { Controller } from "live-cache";
|
|
190
|
+
|
|
191
|
+
const API_BASE = "https://pokeapi.co/api/v2";
|
|
192
|
+
|
|
193
|
+
// Controller for fetching the list of Pokémon
|
|
194
|
+
class PokemonListController extends Controller<{ name: string; url: string }, "pokemonList"> {
|
|
195
|
+
constructor(name, options) {
|
|
196
|
+
super(name, options);
|
|
197
|
+
this.limit = 24;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async fetch() {
|
|
201
|
+
this.abort();
|
|
202
|
+
const response = await fetch(`${API_BASE}/pokemon?limit=${this.limit}`, {
|
|
203
|
+
signal: this.abortController?.signal,
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) throw new Error(`GET /pokemon failed (${response.status})`);
|
|
206
|
+
const data = await response.json();
|
|
207
|
+
return [data.results ?? [], data.count ?? 0];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
invalidate() {
|
|
211
|
+
this.abort();
|
|
212
|
+
void this.update();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Controller for fetching individual Pokémon details
|
|
217
|
+
class PokemonDetailsController extends Controller<any, "pokemonDetails"> {
|
|
218
|
+
resolveQuery(where) {
|
|
219
|
+
if (!where) return null;
|
|
220
|
+
if (typeof where === "string") return where;
|
|
221
|
+
if (where.name) return String(where.name);
|
|
222
|
+
if (where.id !== undefined) return String(where.id);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async fetch(where) {
|
|
227
|
+
const query = this.resolveQuery(where);
|
|
228
|
+
if (!query) return [[], 0];
|
|
229
|
+
|
|
230
|
+
this.abort();
|
|
231
|
+
const response = await fetch(`${API_BASE}/pokemon/${query}`, {
|
|
232
|
+
signal: this.abortController?.signal,
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) throw new Error(`GET /pokemon/${query} failed (${response.status})`);
|
|
235
|
+
const data = await response.json();
|
|
236
|
+
return [[data], 1];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
invalidate() {
|
|
240
|
+
this.abort();
|
|
241
|
+
void this.update(this.lastQuery);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
166
246
|
### Persistence (`StorageManager`)
|
|
167
247
|
|
|
168
248
|
Controllers persist snapshots through a `StorageManager` (array-of-models, not a JSON string).
|
|
@@ -213,6 +293,8 @@ Use `ContextProvider` to provide an `ObjectStore`, `useRegister()` to register c
|
|
|
213
293
|
`controller.invalidator.registerInvalidation()` on mount and
|
|
214
294
|
`controller.invalidator.unregisterInvalidation()` on unmount.
|
|
215
295
|
|
|
296
|
+
### Basic example
|
|
297
|
+
|
|
216
298
|
```tsx
|
|
217
299
|
import React from "react";
|
|
218
300
|
import { ContextProvider, useRegister, useController } from "live-cache";
|
|
@@ -251,6 +333,40 @@ export default function Root() {
|
|
|
251
333
|
}
|
|
252
334
|
```
|
|
253
335
|
|
|
336
|
+
### Query-based fetching example
|
|
337
|
+
|
|
338
|
+
You can pass a `where` clause to `useController()` to fetch specific data:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
import { useController } from "live-cache";
|
|
342
|
+
import { useMemo } from "react";
|
|
343
|
+
|
|
344
|
+
function PokemonDetails({ query }) {
|
|
345
|
+
// Convert query string to where clause
|
|
346
|
+
const where = useMemo(() => ({ name: query }), [query]);
|
|
347
|
+
|
|
348
|
+
const { data, loading, error } = useController(
|
|
349
|
+
"pokemonDetails",
|
|
350
|
+
where,
|
|
351
|
+
{ initialise: !!where }
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const pokemon = data[0];
|
|
355
|
+
if (loading) return <div>Loading Pokémon…</div>;
|
|
356
|
+
if (error) return <div>Error: {String(error)}</div>;
|
|
357
|
+
if (!pokemon) return null;
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div>
|
|
361
|
+
<h2>{pokemon.name}</h2>
|
|
362
|
+
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
|
|
363
|
+
</div>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
See `examples/react` for a complete PokéAPI explorer implementation with multiple components using controllers.
|
|
369
|
+
|
|
254
370
|
## Cache invalidation recipes
|
|
255
371
|
|
|
256
372
|
These show **framework-agnostic** controller patterns and a **React** wiring example for each.
|