@sweidos/eidos 1.0.16 → 1.0.19
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 +199 -22
- package/dist/eidos-sw.js +164 -180
- package/dist/eidos.cjs.js +37 -0
- package/dist/eidos.cjs.js.map +1 -1
- package/dist/eidos.es.js +37 -0
- package/dist/eidos.es.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/query.cjs.js +48 -0
- package/dist/query.d.ts +81 -0
- package/dist/query.js +48 -0
- package/dist/vite.cjs.js +31 -0
- package/dist/vite.d.ts +28 -0
- package/dist/vite.js +31 -0
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ pnpm add @sweidos/eidos
|
|
|
72
72
|
cp node_modules/@sweidos/eidos/dist/eidos-sw.js public/eidos-sw.js
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
> **Vite users** —
|
|
75
|
+
> **Vite users** — use the [first-class Vite plugin](#vite-plugin) to automate this.
|
|
76
76
|
|
|
77
77
|
### 3. Wrap your app
|
|
78
78
|
|
|
@@ -115,7 +115,16 @@ export const createOrder = action(
|
|
|
115
115
|
### 5. Use in components
|
|
116
116
|
|
|
117
117
|
```tsx
|
|
118
|
-
// TanStack Query
|
|
118
|
+
// TanStack Query — first-class hooks
|
|
119
|
+
import { useEidosQuery, useEidosMutation } from '@sweidos/eidos/query'
|
|
120
|
+
|
|
121
|
+
const { data, isPending } = useEidosQuery<Product[]>(products)
|
|
122
|
+
|
|
123
|
+
const mutation = useEidosMutation(createOrder, {
|
|
124
|
+
invalidates: [products], // clears cache + refetches on success
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Or with plain useQuery
|
|
119
128
|
const { data } = useQuery(products.query<Product[]>())
|
|
120
129
|
|
|
121
130
|
// Or plain async
|
|
@@ -145,6 +154,15 @@ const handle = resource('/api/products', {
|
|
|
145
154
|
cacheName?: string, // custom Cache Storage bucket (default: 'eidos-resources-v1')
|
|
146
155
|
maxAge?: number, // TTL in ms — expired entries are re-fetched from network
|
|
147
156
|
})
|
|
157
|
+
|
|
158
|
+
// URL patterns — SW intercepts all matching requests automatically
|
|
159
|
+
resource('/api/products/*', { offline: true }) // single segment: /api/products/123
|
|
160
|
+
resource('/api/products/**', { offline: true }) // multi-segment: /api/products/123/reviews
|
|
161
|
+
resource('/api/users/:id', { offline: true }) // named segment: /api/users/abc
|
|
162
|
+
|
|
163
|
+
// Cross-origin resources — pass the full URL (including origin)
|
|
164
|
+
resource('https://api.example.com/products', { offline: true })
|
|
165
|
+
resource('https://cdn.example.com/assets/*', { offline: true }) // patterns work too
|
|
148
166
|
```
|
|
149
167
|
|
|
150
168
|
**Auto-selected strategy:**
|
|
@@ -287,6 +305,79 @@ const state = useEidos()
|
|
|
287
305
|
|
|
288
306
|
---
|
|
289
307
|
|
|
308
|
+
### Vue / Svelte / Vanilla JS Stores
|
|
309
|
+
|
|
310
|
+
Framework-agnostic reactive stores — no React dependency, zero extra peer deps. These implement the [Svelte store contract](https://svelte.dev/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values) (`subscribe(run): unsubscribe`) so they work natively with Svelte's `$` prefix. They also wire up cleanly in Vue composables and plain JS.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import {
|
|
314
|
+
eidosQueue, eidosStatus, eidosQueueStats,
|
|
315
|
+
eidosResource, eidosAction, eidosStore,
|
|
316
|
+
} from '@sweidos/eidos'
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Svelte:**
|
|
320
|
+
|
|
321
|
+
```svelte
|
|
322
|
+
<script>
|
|
323
|
+
import { eidosQueue, eidosStatus, eidosQueueStats, eidosResource } from '@sweidos/eidos'
|
|
324
|
+
// Use $ prefix — Svelte auto-subscribes and unsubscribes
|
|
325
|
+
</script>
|
|
326
|
+
|
|
327
|
+
<p>Online: {$eidosStatus.isOnline}</p>
|
|
328
|
+
<p>Pending: {$eidosQueueStats.pending}</p>
|
|
329
|
+
<p>Cache hits: {$eidosResource('/api/products')?.cacheHits ?? 0}</p>
|
|
330
|
+
|
|
331
|
+
{#each $eidosQueue as item}
|
|
332
|
+
<div>{item.actionName} — {item.status}</div>
|
|
333
|
+
{/each}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Vue (Composition API):**
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
import { ref, onUnmounted } from 'vue'
|
|
340
|
+
import { eidosStatus, eidosQueue } from '@sweidos/eidos'
|
|
341
|
+
|
|
342
|
+
export function useEidosStatusVue() {
|
|
343
|
+
const status = ref(eidosStatus.getState())
|
|
344
|
+
const unsub = eidosStatus.subscribe((v) => { status.value = v })
|
|
345
|
+
onUnmounted(unsub)
|
|
346
|
+
return status
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function useEidosQueueVue() {
|
|
350
|
+
const queue = ref(eidosQueue.getState())
|
|
351
|
+
const unsub = eidosQueue.subscribe((v) => { queue.value = v })
|
|
352
|
+
onUnmounted(unsub)
|
|
353
|
+
return queue
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Vanilla JS:**
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { eidosStatus, eidosResource } from '@sweidos/eidos'
|
|
361
|
+
|
|
362
|
+
const unsub = eidosStatus.subscribe(({ isOnline }) => {
|
|
363
|
+
document.title = isOnline ? 'App' : 'App (offline)'
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Read current value once without subscribing
|
|
367
|
+
const hits = eidosResource('/api/products').getState()?.cacheHits ?? 0
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
| Store | Type | Emits when |
|
|
371
|
+
|-------|------|-----------|
|
|
372
|
+
| `eidosQueue` | `ActionQueueItem[]` | Any queue mutation |
|
|
373
|
+
| `eidosStatus` | `{ isOnline, swStatus, swError }` | Online or SW status changes |
|
|
374
|
+
| `eidosQueueStats` | `{ pending, failed, replaying, total }` | Any queue mutation |
|
|
375
|
+
| `eidosResource(url)` | `ResourceEntry \| undefined` | Resource registered or updated |
|
|
376
|
+
| `eidosAction(id)` | `ActionQueueItem \| undefined` | Item status changes or removal |
|
|
377
|
+
| `eidosStore` | `EidosStore` | Any state change |
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
290
381
|
### `setOfflineSimulation(enabled)`
|
|
291
382
|
|
|
292
383
|
Toggle offline simulation without physically disconnecting the network.
|
|
@@ -300,6 +391,21 @@ setOfflineSimulation(false) // restore normal behaviour
|
|
|
300
391
|
|
|
301
392
|
---
|
|
302
393
|
|
|
394
|
+
### `isBgSyncSupported()`
|
|
395
|
+
|
|
396
|
+
Returns `true` when the active SW registration supports the Background Sync API (Chrome 49+, Edge 79+, Safari 16+). Use to conditionally surface sync status in your UI.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import { isBgSyncSupported } from '@sweidos/eidos'
|
|
400
|
+
|
|
401
|
+
if (isBgSyncSupported()) {
|
|
402
|
+
// browser will fire 'eidos-queue-replay' sync tag when connectivity returns,
|
|
403
|
+
// even if the user briefly navigated away from the page
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
303
409
|
## Performance
|
|
304
410
|
|
|
305
411
|
Performance is a first-class concern in Eidos. Every design decision optimises for low overhead.
|
|
@@ -369,6 +475,7 @@ Performance is a first-class concern in Eidos. Every design decision optimises f
|
|
|
369
475
|
| `EIDOS_CACHE_UPDATED` | Cache entry refreshed from network |
|
|
370
476
|
| `EIDOS_NETWORK_ERROR` | Network request failed |
|
|
371
477
|
| `EIDOS_CACHE_CLEARED` | Cache was cleared |
|
|
478
|
+
| `EIDOS_BACKGROUND_SYNC` | Browser fired `sync` event — runtime calls `replayQueue()` |
|
|
372
479
|
|
|
373
480
|
---
|
|
374
481
|
|
|
@@ -401,26 +508,96 @@ eidos/
|
|
|
401
508
|
|
|
402
509
|
## Vite Plugin
|
|
403
510
|
|
|
404
|
-
|
|
511
|
+
`@sweidos/eidos` ships a first-class Vite plugin via the `@sweidos/eidos/vite` subpath. It automatically copies `eidos-sw.js` from the installed package into your `public/` directory on every build and dev-server start — keeping the SW in sync with the installed version.
|
|
405
512
|
|
|
406
513
|
```ts
|
|
407
514
|
// vite.config.ts
|
|
408
|
-
import {
|
|
409
|
-
import {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
515
|
+
import { eidos } from '@sweidos/eidos/vite'
|
|
516
|
+
import { defineConfig } from 'vite'
|
|
517
|
+
|
|
518
|
+
export default defineConfig({
|
|
519
|
+
plugins: [eidos()],
|
|
520
|
+
})
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Options:**
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
eidos({
|
|
527
|
+
swDest: 'public/eidos-sw.js', // default — relative to project root
|
|
528
|
+
})
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
No more manual `cp` step. The plugin runs on `buildStart` (prod builds) and `configureServer` (dev).
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## TanStack Query Integration
|
|
536
|
+
|
|
537
|
+
`@sweidos/eidos/query` provides first-class hooks for [TanStack Query v5](https://tanstack.com/query/latest). Requires `@tanstack/react-query` — already optional in Eidos, just install it.
|
|
538
|
+
|
|
539
|
+
### Setup (once)
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
// main.tsx
|
|
543
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
544
|
+
import { withEidosQueryClient } from '@sweidos/eidos/query'
|
|
545
|
+
|
|
546
|
+
const queryClient = new QueryClient()
|
|
547
|
+
withEidosQueryClient(queryClient) // bridges handle.invalidate() → TQ cache
|
|
548
|
+
|
|
549
|
+
root.render(
|
|
550
|
+
<QueryClientProvider client={queryClient}>
|
|
551
|
+
<EidosProvider swPath="/eidos-sw.js">
|
|
552
|
+
<App />
|
|
553
|
+
</EidosProvider>
|
|
554
|
+
</QueryClientProvider>
|
|
555
|
+
)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### `useEidosQuery(handle, options?)`
|
|
559
|
+
|
|
560
|
+
Wraps `useQuery` with Eidos-smart defaults:
|
|
561
|
+
- `networkMode: 'always'` — Eidos owns offline; queries run even when `navigator.onLine` is false
|
|
562
|
+
- `retry: false` — Eidos handles retries at the SW/replay layer
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
import { useEidosQuery } from '@sweidos/eidos/query'
|
|
566
|
+
|
|
567
|
+
function ProductList() {
|
|
568
|
+
const { data, isPending, isError } = useEidosQuery<Product[]>(products)
|
|
569
|
+
// ...
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### `useEidosMutation(handle, options?)`
|
|
574
|
+
|
|
575
|
+
Wraps `useMutation` for a single-argument action handle:
|
|
576
|
+
- `networkMode: 'always'` — action queues offline automatically
|
|
577
|
+
- `invalidates` — clears Eidos cache + invalidates TQ entries on success
|
|
578
|
+
|
|
579
|
+
```tsx
|
|
580
|
+
import { useEidosMutation } from '@sweidos/eidos/query'
|
|
581
|
+
|
|
582
|
+
function OrderForm() {
|
|
583
|
+
const mutation = useEidosMutation(createOrder, {
|
|
584
|
+
invalidates: [products], // refetch product list after order
|
|
585
|
+
onSuccess(data) {
|
|
586
|
+
if ('queued' in data) toast('Saved offline — will sync when back online')
|
|
587
|
+
else toast(`Order #${data.id} created!`)
|
|
419
588
|
},
|
|
420
|
-
}
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
return <button onClick={() => mutation.mutate({ productId: 1, qty: 2 })}>Buy</button>
|
|
421
592
|
}
|
|
422
593
|
```
|
|
423
594
|
|
|
595
|
+
### `withEidosQueryClient(client)`
|
|
596
|
+
|
|
597
|
+
Registers a `QueryClient` with Eidos. After calling this:
|
|
598
|
+
- `handle.invalidate()` also calls `queryClient.invalidateQueries({ queryKey: ['eidos', url] })`
|
|
599
|
+
- Both systems stay in sync automatically, even when cache is cleared outside of mutations
|
|
600
|
+
|
|
424
601
|
---
|
|
425
602
|
|
|
426
603
|
## Known Limitations
|
|
@@ -428,7 +605,7 @@ function eidosPlugin() {
|
|
|
428
605
|
| Limitation | Detail |
|
|
429
606
|
|------------|--------|
|
|
430
607
|
| GET-only caching | SW intercepts `GET` only. `POST`/`PUT`/`DELETE` are not cached (but *are* queued via `action()`). |
|
|
431
|
-
|
|
|
608
|
+
| Query string ignored | Resources match by pathname (or full URL for cross-origin). `/api/products?page=2` and `/api/products` share the same SW rule but are cached as separate entries. |
|
|
432
609
|
| Module-scope actions | `action()` must be called at module scope so functions are registered before a page reload triggers queue replay. |
|
|
433
610
|
| Single SW | `EidosProvider` assumes one SW at `/eidos-sw.js`. Multiple registrations are unsupported. |
|
|
434
611
|
|
|
@@ -440,12 +617,12 @@ function eidosPlugin() {
|
|
|
440
617
|
- [x] Exponential backoff with jitter for queue replay
|
|
441
618
|
- [x] Per-resource `cacheName` override
|
|
442
619
|
- [x] `resource.unregister()` for cleanup
|
|
443
|
-
- [
|
|
444
|
-
- [
|
|
445
|
-
- [
|
|
446
|
-
- [
|
|
447
|
-
- [
|
|
448
|
-
- [
|
|
620
|
+
- [x] URL pattern matching (`*`, `**`, `:param`)
|
|
621
|
+
- [x] Cross-origin resource support
|
|
622
|
+
- [x] Background Sync API integration
|
|
623
|
+
- [x] Vite plugin (`@sweidos/eidos/vite` subpath — ships in the main package)
|
|
624
|
+
- [x] Vue / Svelte bindings (framework-agnostic reactive stores)
|
|
625
|
+
- [x] TanStack Query integration (`@sweidos/eidos/query` subpath — `useEidosQuery`, `useEidosMutation`, `withEidosQueryClient`)
|
|
449
626
|
|
|
450
627
|
---
|
|
451
628
|
|