@sweidos/eidos 1.0.17 → 1.0.20

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 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** — automate this with the [Vite plugin snippet](#vite-plugin).
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
@@ -499,26 +508,96 @@ eidos/
499
508
 
500
509
  ## Vite Plugin
501
510
 
502
- Automatically copy `eidos-sw.js` into `public/` on build:
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.
503
512
 
504
513
  ```ts
505
514
  // vite.config.ts
506
- import { copyFileSync } from 'fs'
507
- import { resolve } from 'path'
508
-
509
- function eidosPlugin() {
510
- return {
511
- name: 'eidos-sw',
512
- buildStart() {
513
- copyFileSync(
514
- resolve('./node_modules/@sweidos/eidos/dist/eidos-sw.js'),
515
- resolve('./public/eidos-sw.js'),
516
- )
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!`)
517
588
  },
518
- }
589
+ })
590
+
591
+ return <button onClick={() => mutation.mutate({ productId: 1, qty: 2 })}>Buy</button>
519
592
  }
520
593
  ```
521
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
+
522
601
  ---
523
602
 
524
603
  ## Known Limitations
@@ -529,6 +608,7 @@ function eidosPlugin() {
529
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. |
530
609
  | Module-scope actions | `action()` must be called at module scope so functions are registered before a page reload triggers queue replay. |
531
610
  | Single SW | `EidosProvider` assumes one SW at `/eidos-sw.js`. Multiple registrations are unsupported. |
611
+ | React in main bundle | The published ESM bundle is a single file — consumers who import `resource()` or `eidosStore` (no React) still pull in the `react` import declaration. Since `react` is an optional peer dep, bundlers handle it correctly (import is skipped if unused), but Vue/Svelte projects should ensure `react` is available as a dep. A future `preserveModules` refactor will fix this properly. |
532
612
 
533
613
  ---
534
614
 
@@ -541,9 +621,27 @@ function eidosPlugin() {
541
621
  - [x] URL pattern matching (`*`, `**`, `:param`)
542
622
  - [x] Cross-origin resource support
543
623
  - [x] Background Sync API integration
544
- - [ ] Vite plugin (first-class, published separately)
624
+ - [x] Vite plugin (`@sweidos/eidos/vite` subpath — ships in the main package)
545
625
  - [x] Vue / Svelte bindings (framework-agnostic reactive stores)
546
- - [ ] TanStack Query integration package
626
+ - [x] TanStack Query integration (`@sweidos/eidos/query` subpath — `useEidosQuery`, `useEidosMutation`, `withEidosQueryClient`)
627
+
628
+ **Core reliability**
629
+ - [ ] Optimistic updates — `onOptimistic` / `onRollback` callbacks on `action()` for instant UI feedback before server confirms
630
+ - [ ] Conflict resolution hook — `onConflict` callback when replaying a queued action returns 4xx; decide per-item: retry, skip, or merge
631
+ - [ ] Queue prioritization — `priority: 'high' | 'normal' | 'low'` on `action()`; high-priority items replay first
632
+
633
+ **DX / Tooling**
634
+ - [ ] Devtools panel component — drop-in `<EidosDevtools />` showing cache entries, queue state, replay status, and offline toggle
635
+ - [ ] Testing utilities (`@sweidos/eidos/testing`) — `mockOffline()`, `drainQueue()`, `getCachedEntry(url)` for Vitest / Playwright
636
+ - [ ] SvelteKit / Next.js adapters — SSR-aware init helpers that skip SW registration server-side
637
+
638
+ **Performance**
639
+ - [ ] Request deduplication — multiple simultaneous `resource.fetch()` calls share one in-flight network request
640
+ - [ ] Cache warming — `warmCache(handles[])` bulk-prefetches a list of resources on init (e.g. on login)
641
+
642
+ **Ecosystem**
643
+ - [ ] React Native support — AsyncStorage + fetch-based backend (no Cache API / SW); same `resource` / `action` API surface
644
+ - [ ] OpenAPI codegen CLI — `npx eidos-gen ./openapi.json` generates typed `resource()` and `action()` declarations
547
645
 
548
646
  ---
549
647