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.
Files changed (2) hide show
  1. package/README.md +122 -6
  2. 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 fetchAll(): Promise<[User[], number]> {
142
- const res = await fetch("/api/users");
143
- if (!res.ok) throw new Error("Failed to fetch users");
144
- const data = (await res.json()) as User[];
145
- return [data, data.length];
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.refetch();
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "live-cache",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A client-side cache + controller library for stateful resources",
5
5
  "repository": {
6
6
  "type": "git",