@sweidos/eidos 1.0.34 → 1.2.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 +171 -89
- package/dist/action.js +197 -91
- package/dist/async-storage-adapter.js +15 -12
- package/dist/cli.js +102 -0
- package/dist/devtools.js +1009 -551
- package/dist/eidos-sw.js +280 -188
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +135 -18
- package/dist/index.js +46 -42
- package/dist/nextjs.js +1 -10
- package/dist/push.cjs +120 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +113 -0
- package/dist/query.cjs +131 -0
- package/dist/query.js +121 -41
- package/dist/queue-storage.js +5 -4
- package/dist/react/Devtools.d.ts +1 -1
- package/dist/react/Provider.js +11 -7
- package/dist/react/hooks.js +48 -38
- package/dist/react-native.js +47 -53
- package/dist/replay.js +15 -0
- package/dist/resource.js +77 -79
- package/dist/runtime.js +39 -28
- package/dist/store-slices.js +43 -0
- package/dist/store.js +32 -49
- package/dist/stores.js +25 -22
- package/dist/sveltekit.js +22 -6
- package/dist/sw-bridge.js +64 -49
- package/dist/testing.cjs +165 -0
- package/dist/testing.js +140 -70
- package/dist/version.js +4 -3
- package/dist/vite.cjs +48 -0
- package/dist/vite.js +45 -29
- package/package.json +57 -28
- package/dist/action.js.map +0 -1
- package/dist/async-storage-adapter.js.map +0 -1
- package/dist/eidos.cjs.js +0 -14
- package/dist/eidos.cjs.js.map +0 -1
- package/dist/idb.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query.cjs.js +0 -48
- package/dist/queue-storage.js.map +0 -1
- package/dist/react/Provider.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/resource.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/stores.js.map +0 -1
- package/dist/sw-bridge.js.map +0 -1
- package/dist/testing.cjs.js +0 -86
- package/dist/version.js.map +0 -1
- package/dist/vite.cjs.js +0 -31
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
Declare what your app needs offline. Eidos picks the cache strategy, registers the Service Worker, and persists your action queue to IndexedDB — automatically.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import { resource, action } from '@sweidos/eidos'
|
|
15
|
+
import { resource, action } from '@sweidos/eidos';
|
|
16
16
|
|
|
17
|
-
const products = resource('/api/products', { offline: true })
|
|
18
|
-
const createOrder = action(orderApi.create, { reliability: 'neverLose' })
|
|
17
|
+
const products = resource('/api/products', { offline: true });
|
|
18
|
+
const createOrder = action(orderApi.create, { reliability: 'neverLose' });
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
No service worker file to write. No cache strategy to configure. No retry logic to implement.
|
|
@@ -60,12 +60,12 @@ npm install @sweidos/eidos
|
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
62
|
// vite.config.ts
|
|
63
|
-
import { eidos } from '@sweidos/eidos/vite'
|
|
64
|
-
import { defineConfig } from 'vite'
|
|
63
|
+
import { eidos } from '@sweidos/eidos/vite';
|
|
64
|
+
import { defineConfig } from 'vite';
|
|
65
65
|
|
|
66
66
|
export default defineConfig({
|
|
67
67
|
plugins: [eidos()],
|
|
68
|
-
})
|
|
68
|
+
});
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
> **Without Vite** — copy manually: `cp node_modules/@sweidos/eidos/dist/eidos-sw.js public/`
|
|
@@ -74,39 +74,39 @@ export default defineConfig({
|
|
|
74
74
|
|
|
75
75
|
```tsx
|
|
76
76
|
// main.tsx
|
|
77
|
-
import { EidosProvider } from '@sweidos/eidos'
|
|
78
|
-
import { createRoot } from 'react-dom/client'
|
|
79
|
-
import { App } from './App'
|
|
77
|
+
import { EidosProvider } from '@sweidos/eidos';
|
|
78
|
+
import { createRoot } from 'react-dom/client';
|
|
79
|
+
import { App } from './App';
|
|
80
80
|
|
|
81
81
|
createRoot(document.getElementById('root')!).render(
|
|
82
82
|
<EidosProvider swPath="/eidos-sw.js">
|
|
83
83
|
<App />
|
|
84
|
-
</EidosProvider
|
|
85
|
-
)
|
|
84
|
+
</EidosProvider>,
|
|
85
|
+
);
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
89
|
// src/lib/eidos.ts ← module scope required for queue replay after reload
|
|
90
|
-
import { resource, action } from '@sweidos/eidos'
|
|
90
|
+
import { resource, action } from '@sweidos/eidos';
|
|
91
91
|
|
|
92
|
-
export const products = resource('/api/products', { offline: true })
|
|
92
|
+
export const products = resource('/api/products', { offline: true });
|
|
93
93
|
|
|
94
94
|
export const createOrder = action(
|
|
95
95
|
async (payload: OrderPayload) => {
|
|
96
|
-
const res = await fetch('/api/orders', { method: 'POST', body: JSON.stringify(payload) })
|
|
97
|
-
return res.json()
|
|
96
|
+
const res = await fetch('/api/orders', { method: 'POST', body: JSON.stringify(payload) });
|
|
97
|
+
return res.json();
|
|
98
98
|
},
|
|
99
99
|
{ reliability: 'neverLose', name: 'createOrder' },
|
|
100
|
-
)
|
|
100
|
+
);
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
```tsx
|
|
104
104
|
// In components — works the same online and offline
|
|
105
|
-
const result = await createOrder({ productId: 1, qty: 2 })
|
|
105
|
+
const result = await createOrder({ productId: 1, qty: 2 });
|
|
106
106
|
|
|
107
107
|
if ('queued' in result) {
|
|
108
108
|
// Saved to IndexedDB — replays automatically on reconnect
|
|
109
|
-
console.log(result.message)
|
|
109
|
+
console.log(result.message);
|
|
110
110
|
}
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -114,35 +114,35 @@ if ('queued' in result) {
|
|
|
114
114
|
|
|
115
115
|
## What you get
|
|
116
116
|
|
|
117
|
-
| Feature
|
|
118
|
-
|
|
119
|
-
| **Auto strategy selection** | `offline: true` → StaleWhileRevalidate. No config needed. Override when you want.
|
|
120
|
-
| **Persistent action queue** | Failed writes go to IndexedDB and replay with exponential backoff on reconnect.
|
|
121
|
-
| **Request deduplication**
|
|
122
|
-
| **Optimistic updates**
|
|
123
|
-
| **Conflict resolution**
|
|
124
|
-
| **Queue prioritization**
|
|
125
|
-
| **Cache warming**
|
|
126
|
-
| **URL patterns**
|
|
127
|
-
| **Background Sync**
|
|
128
|
-
| **Devtools panel**
|
|
129
|
-
| **Testing helpers**
|
|
130
|
-
| **OpenAPI codegen**
|
|
117
|
+
| Feature | Description |
|
|
118
|
+
| --------------------------- | ------------------------------------------------------------------------------------ |
|
|
119
|
+
| **Auto strategy selection** | `offline: true` → StaleWhileRevalidate. No config needed. Override when you want. |
|
|
120
|
+
| **Persistent action queue** | Failed writes go to IndexedDB and replay with exponential backoff on reconnect. |
|
|
121
|
+
| **Request deduplication** | Concurrent `resource.fetch()` calls share one in-flight request. |
|
|
122
|
+
| **Optimistic updates** | `onOptimistic` / `onRollback` callbacks for instant UI feedback. |
|
|
123
|
+
| **Conflict resolution** | `onConflict` decides per 4xx whether to retry or drop a queued action. |
|
|
124
|
+
| **Queue prioritization** | `priority: 'high' \| 'normal' \| 'low'` — high items replay before normal. |
|
|
125
|
+
| **Cache warming** | `warmCache(handles[])` bulk-prefetches resources on login/init. |
|
|
126
|
+
| **URL patterns** | `/api/products/*`, `/api/users/:id`, `**` wildcards — SW intercepts all matches. |
|
|
127
|
+
| **Background Sync** | Registers a `sync` tag so queued actions replay even after tab close. |
|
|
128
|
+
| **Devtools panel** | `<EidosDevtools />` — live queue, cache state, offline toggle, no CSS import. |
|
|
129
|
+
| **Testing helpers** | `mockOffline`, `drainQueue`, `resetEidos`, `getCachedEntry` for Vitest/Jest. |
|
|
130
|
+
| **OpenAPI codegen** | `npx eidos-gen openapi.json` generates typed `resource()` + `action()` declarations. |
|
|
131
131
|
|
|
132
132
|
---
|
|
133
133
|
|
|
134
134
|
## Framework support
|
|
135
135
|
|
|
136
|
-
| Framework
|
|
137
|
-
|
|
138
|
-
| **React**
|
|
139
|
-
| **Next.js App Router** | `@sweidos/eidos/nextjs`
|
|
140
|
-
| **SvelteKit**
|
|
141
|
-
| **Vue**
|
|
142
|
-
| **React Native**
|
|
143
|
-
| **Vanilla JS**
|
|
144
|
-
| **Vite**
|
|
145
|
-
| **TanStack Query**
|
|
136
|
+
| Framework | Import path | Notes |
|
|
137
|
+
| ---------------------- | ----------------------------- | -------------------------------------------------------------- |
|
|
138
|
+
| **React** | `@sweidos/eidos` | Hooks + `EidosProvider` |
|
|
139
|
+
| **Next.js App Router** | `@sweidos/eidos/nextjs` | Pre-marked `'use client'` — no wrapper needed |
|
|
140
|
+
| **SvelteKit** | `@sweidos/eidos/sveltekit` | `initEidosSvelteKit()` in `onMount`, framework-agnostic stores |
|
|
141
|
+
| **Vue** | `@sweidos/eidos` | Framework-agnostic stores via `eidosStatus.subscribe()` |
|
|
142
|
+
| **React Native** | `@sweidos/eidos/react-native` | AsyncStorage-backed queue, same `action()` API |
|
|
143
|
+
| **Vanilla JS** | `@sweidos/eidos` | `eidosStatus`, `eidosQueue`, `eidosQueueStats` stores |
|
|
144
|
+
| **Vite** | `@sweidos/eidos/vite` | Plugin auto-copies `eidos-sw.js` on every build |
|
|
145
|
+
| **TanStack Query** | `@sweidos/eidos/query` | `useEidosQuery`, `useEidosMutation`, `withEidosQueryClient` |
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
@@ -169,11 +169,11 @@ products.query() // { queryKey, queryFn } for useQuery
|
|
|
169
169
|
|
|
170
170
|
**Auto-selected strategy:**
|
|
171
171
|
|
|
172
|
-
| Config
|
|
173
|
-
|
|
174
|
-
| `offline: true`
|
|
175
|
-
| `offline: true, strategy: 'cache-first'`
|
|
176
|
-
| `offline: true, strategy: 'network-first'` | NetworkFirst
|
|
172
|
+
| Config | Strategy | Use when |
|
|
173
|
+
| ------------------------------------------ | -------------------- | ----------------------------------- |
|
|
174
|
+
| `offline: true` | StaleWhileRevalidate | Default — fast + background refresh |
|
|
175
|
+
| `offline: true, strategy: 'cache-first'` | CacheFirst | Static assets, config data |
|
|
176
|
+
| `offline: true, strategy: 'network-first'` | NetworkFirst | Always-fresh with offline fallback |
|
|
177
177
|
|
|
178
178
|
URL patterns work on any handle: `/api/products/*`, `/api/users/:id`, `**`
|
|
179
179
|
|
|
@@ -194,11 +194,11 @@ const createOrder = action(async (payload: OrderPayload) => { ... }, {
|
|
|
194
194
|
### React hooks
|
|
195
195
|
|
|
196
196
|
```ts
|
|
197
|
-
const { isOnline, swStatus }
|
|
198
|
-
const { pending, failed }
|
|
199
|
-
const entry
|
|
200
|
-
const item
|
|
201
|
-
useEidosOnDrain(() => toast('All offline actions synced!'))
|
|
197
|
+
const { isOnline, swStatus } = useEidosStatus();
|
|
198
|
+
const { pending, failed } = useEidosQueueStats();
|
|
199
|
+
const entry = useEidosResource('/api/products');
|
|
200
|
+
const item = useEidosAction(queuedResult.id);
|
|
201
|
+
useEidosOnDrain(() => toast('All offline actions synced!'));
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
### Framework-agnostic stores
|
|
@@ -217,45 +217,125 @@ eidosResource('/api/products').getState() // ResourceEntry | undefined
|
|
|
217
217
|
|
|
218
218
|
```ts
|
|
219
219
|
// main.tsx — register once
|
|
220
|
-
withEidosQueryClient(queryClient)
|
|
220
|
+
withEidosQueryClient(queryClient);
|
|
221
221
|
|
|
222
222
|
// In components
|
|
223
|
-
const { data, isPending } = useEidosQuery<Product[]>(products)
|
|
223
|
+
const { data, isPending } = useEidosQuery<Product[]>(products);
|
|
224
224
|
|
|
225
225
|
const mutation = useEidosMutation(createOrder, {
|
|
226
226
|
invalidates: [products], // clears cache + invalidates TQ on success
|
|
227
227
|
onSuccess(data) {
|
|
228
|
-
if ('queued' in data) toast('Saved offline')
|
|
229
|
-
else toast(`Order #${data.id} created`)
|
|
228
|
+
if ('queued' in data) toast('Saved offline');
|
|
229
|
+
else toast(`Order #${data.id} created`);
|
|
230
230
|
},
|
|
231
|
-
})
|
|
231
|
+
});
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
---
|
|
235
235
|
|
|
236
|
+
## Push Notifications
|
|
237
|
+
|
|
238
|
+
Headless, framework-agnostic Web Push. Tree-shaken via a separate subpath — adds zero bytes unless imported.
|
|
239
|
+
|
|
240
|
+
**1. Generate VAPID keys (one-time):**
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
npx @sweidos/eidos generate-vapid-keys
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Detects your framework (Vite/Next/SvelteKit/Nuxt) and writes a correctly-prefixed
|
|
247
|
+
public key + an unprefixed private key to `.env.local`:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
VITE_EIDOS_VAPID_PUBLIC_KEY=...
|
|
251
|
+
EIDOS_VAPID_PRIVATE_KEY=...
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Give `EIDOS_VAPID_PRIVATE_KEY` (and the public key) to your backend. What the
|
|
255
|
+
backend does with them — language, storage, send timing — is entirely its own
|
|
256
|
+
concern; Eidos never talks to it directly.
|
|
257
|
+
|
|
258
|
+
**2. Register handlers once at app init (any tab, no permission prompt):**
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { registerPushHandlers } from '@sweidos/eidos/push';
|
|
262
|
+
|
|
263
|
+
registerPushHandlers({
|
|
264
|
+
onNotificationClick: (data) => router.push(data.url),
|
|
265
|
+
onSubscriptionExpired: (sub) =>
|
|
266
|
+
fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**3. Subscribe from a user gesture (e.g. an "Enable notifications" button):**
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { subscribeToPush, isPushSupported, getPushPermissionState } from '@sweidos/eidos/push';
|
|
274
|
+
|
|
275
|
+
async function onEnableClick() {
|
|
276
|
+
const result = await subscribeToPush({
|
|
277
|
+
vapidPublicKey: import.meta.env.VITE_EIDOS_VAPID_PUBLIC_KEY,
|
|
278
|
+
onSubscribe: (sub) =>
|
|
279
|
+
fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (result.status === 'subscribed') toast('Notifications enabled');
|
|
283
|
+
else if (result.status === 'denied') toast('Permission denied');
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`isPushSupported()` / `getPushPermissionState()` / `getPushUnsupportedReason()`
|
|
288
|
+
let you hide the button when push is unavailable (e.g. iOS Safari outside an
|
|
289
|
+
installed PWA returns `'ios-not-installed'`).
|
|
290
|
+
|
|
291
|
+
### Server payload schema
|
|
292
|
+
|
|
293
|
+
The service worker shows whatever your server sends — Eidos never renders UI:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"title": "Order shipped",
|
|
298
|
+
"body": "Your order #1234 is on its way",
|
|
299
|
+
"icon": "/icon.png",
|
|
300
|
+
"badge": "/badge.png",
|
|
301
|
+
"tag": "order-1234",
|
|
302
|
+
"data": { "url": "/orders/1234" }
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Click behavior: if the app is open, `data` is delivered to `onNotificationClick`
|
|
307
|
+
for client-side routing; otherwise the SW opens `data.url` directly.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
236
311
|
## Testing
|
|
237
312
|
|
|
238
313
|
```ts
|
|
239
314
|
import {
|
|
240
|
-
mockOffline,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
315
|
+
mockOffline,
|
|
316
|
+
mockOnline,
|
|
317
|
+
drainQueue,
|
|
318
|
+
waitForQueueDrain,
|
|
319
|
+
getCachedEntry,
|
|
320
|
+
clearEidosCache,
|
|
321
|
+
resetEidos,
|
|
322
|
+
getEidosState,
|
|
323
|
+
} from '@sweidos/eidos/testing';
|
|
324
|
+
|
|
325
|
+
beforeEach(() => resetEidos());
|
|
246
326
|
|
|
247
327
|
it('queues action while offline', async () => {
|
|
248
|
-
mockOffline()
|
|
249
|
-
await createOrder({ productId: 1, qty: 2 })
|
|
250
|
-
expect(getEidosState().queue).toHaveLength(1)
|
|
251
|
-
})
|
|
328
|
+
mockOffline();
|
|
329
|
+
await createOrder({ productId: 1, qty: 2 });
|
|
330
|
+
expect(getEidosState().queue).toHaveLength(1);
|
|
331
|
+
});
|
|
252
332
|
|
|
253
333
|
it('replays on reconnect', async () => {
|
|
254
|
-
mockOffline()
|
|
255
|
-
await createOrder({ productId: 1, qty: 2 })
|
|
256
|
-
const result = await drainQueue()
|
|
257
|
-
expect(result.succeeded).toBe(1)
|
|
258
|
-
})
|
|
334
|
+
mockOffline();
|
|
335
|
+
await createOrder({ productId: 1, qty: 2 });
|
|
336
|
+
const result = await drainQueue();
|
|
337
|
+
expect(result.succeeded).toBe(1);
|
|
338
|
+
});
|
|
259
339
|
```
|
|
260
340
|
|
|
261
341
|
---
|
|
@@ -274,10 +354,12 @@ Handles path params, `$ref` resolution, request/response types, DELETE body omis
|
|
|
274
354
|
## Devtools
|
|
275
355
|
|
|
276
356
|
```tsx
|
|
277
|
-
import { EidosDevtools } from '@sweidos/eidos/devtools'
|
|
357
|
+
import { EidosDevtools } from '@sweidos/eidos/devtools';
|
|
278
358
|
|
|
279
359
|
// Drop anywhere — bottom-right floating panel, no CSS import
|
|
280
|
-
{
|
|
360
|
+
{
|
|
361
|
+
process.env.NODE_ENV === 'development' && <EidosDevtools />;
|
|
362
|
+
}
|
|
281
363
|
```
|
|
282
364
|
|
|
283
365
|
Panel shows: live queue state · cache entries · SW status · offline simulation toggle.
|
|
@@ -296,30 +378,30 @@ Panel shows: live queue state · cache entries · SW status · offline simulatio
|
|
|
296
378
|
|
|
297
379
|
## Known limitations
|
|
298
380
|
|
|
299
|
-
| Limitation
|
|
300
|
-
|
|
301
|
-
| GET-only caching
|
|
302
|
-
| Module-scope actions
|
|
303
|
-
| Single SW
|
|
304
|
-
| React Native resources | In-memory only — no Cache API or SW in RN. Action queue fully persists.
|
|
381
|
+
| Limitation | Detail |
|
|
382
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------- |
|
|
383
|
+
| GET-only caching | SW intercepts `GET` only. Mutations go through `action()`. |
|
|
384
|
+
| Module-scope actions | `action()` must be at module scope so functions are registered before a reload triggers replay. |
|
|
385
|
+
| Single SW | Assumes one SW at the configured `swPath`. |
|
|
386
|
+
| React Native resources | In-memory only — no Cache API or SW in RN. Action queue fully persists. |
|
|
305
387
|
|
|
306
388
|
---
|
|
307
389
|
|
|
308
390
|
## How it compares
|
|
309
391
|
|
|
310
|
-
|
|
|
311
|
-
|
|
312
|
-
| Service worker setup
|
|
313
|
-
| Caching strategy
|
|
314
|
-
| Offline writes
|
|
315
|
-
| Framework support
|
|
316
|
-
| TanStack Query bridge
|
|
317
|
-
| Bundle size (core,
|
|
392
|
+
| | **Eidos** | Workbox | RTK Query / TanStack Query |
|
|
393
|
+
| -------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------- |
|
|
394
|
+
| Service worker setup | Generated for you — `resource()`/`action()` declarations drive the SW | Hand-write `routing` + `strategies` config | None — no SW |
|
|
395
|
+
| Caching strategy | Auto-derived from intent (`offline: true` → SWR, etc.), inspectable via devtools | Manually chosen per route | Configurable `staleTime`/`gcTime`, no Cache Storage integration |
|
|
396
|
+
| Offline writes | `action()` + `reliability: 'neverLose'` → IndexedDB queue, auto-replay, exponential backoff | Background Sync plugin, you wire the queue | No built-in offline mutation queue |
|
|
397
|
+
| Framework support | React, Svelte, Vue, Next.js, React Native, vanilla JS | Framework-agnostic (SW only) | Per-library (RTK Query = Redux, TanStack = many) |
|
|
398
|
+
| TanStack Query bridge | `@sweidos/eidos/query` — drop-in `useEidosQuery`/`useEidosMutation` | — | Native |
|
|
399
|
+
| Bundle size (core, brotli) | ~5.4 kB | ~3-6 kB (modular) | ~13 kB (TanStack Query core) |
|
|
318
400
|
|
|
319
401
|
Eidos isn't a replacement for TanStack Query — `@sweidos/eidos/query` is a thin
|
|
320
402
|
adapter so you keep TQ's cache/devtools while Eidos owns the offline layer
|
|
321
403
|
(SW caching + IndexedDB write queue). Workbox is a lower-level toolkit Eidos
|
|
322
|
-
generates strategies
|
|
404
|
+
generates strategies _for_; Eidos picks and configures the strategy from your
|
|
323
405
|
`resource()`/`action()` declarations instead of you writing `workbox-*` config
|
|
324
406
|
by hand.
|
|
325
407
|
|