@sweidos/eidos 0.2.0 → 1.0.1
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 +157 -167
- package/dist/eidos.cjs.js +50 -40
- package/dist/eidos.cjs.js.map +1 -1
- package/dist/eidos.es.js +50 -40
- package/dist/eidos.es.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,63 +1,55 @@
|
|
|
1
1
|
# Eidos
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@sweidos/eidos)
|
|
4
|
+
[](https://github.com/iamadi11/eidos/actions/workflows/deploy.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
> Describe intent. The runtime figures out how.
|
|
4
8
|
|
|
5
|
-
Eidos is a small, opinionated abstraction layer for building offline-first web
|
|
9
|
+
Eidos is a small, opinionated abstraction layer for building offline-first web apps. Instead of configuring Service Workers, Cache API strategies, and IndexedDB queues by hand, you declare **what you want** and the runtime handles the rest.
|
|
6
10
|
|
|
7
11
|
```ts
|
|
8
12
|
import { resource, action } from '@sweidos/eidos'
|
|
9
13
|
|
|
10
|
-
// "I want this resource
|
|
11
|
-
const products = resource('/api/products', {
|
|
12
|
-
offline: true,
|
|
13
|
-
})
|
|
14
|
+
// "I want this resource available offline."
|
|
15
|
+
const products = resource('/api/products', { offline: true })
|
|
14
16
|
|
|
15
17
|
// "I never want to lose this action."
|
|
16
|
-
const createOrder = action(orderApi.create, {
|
|
17
|
-
reliability: 'neverLose',
|
|
18
|
-
})
|
|
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.
|
|
22
|
+
|
|
23
|
+
**[→ Live playground](https://playground-iamadi11s-projects.vercel.app)**
|
|
22
24
|
|
|
23
25
|
---
|
|
24
26
|
|
|
25
27
|
## The Problem
|
|
26
28
|
|
|
27
|
-
Building offline-capable
|
|
29
|
+
Building offline-capable apps today requires deep knowledge of:
|
|
28
30
|
|
|
29
31
|
- Service Worker registration and lifecycle management
|
|
30
|
-
- Cache API
|
|
32
|
+
- Cache API strategies (cache-first, network-first, stale-while-revalidate)
|
|
31
33
|
- Fetch event interception and URL routing
|
|
32
|
-
- IndexedDB schema design for persistent
|
|
33
|
-
-
|
|
34
|
+
- IndexedDB schema design for persistent queues
|
|
35
|
+
- Exponential backoff and retry logic
|
|
34
36
|
- Cache versioning and stale entry cleanup
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## The Vision
|
|
38
|
+
Every team re-implements this surface area from scratch.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
## The Solution
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
// Before
|
|
44
|
-
// workbox-config.js
|
|
43
|
+
// Before — workbox-config.js + service-worker.js (40+ lines)
|
|
45
44
|
registerRoute(
|
|
46
45
|
({ url }) => url.pathname === '/api/products',
|
|
47
|
-
new StaleWhileRevalidate({
|
|
48
|
-
cacheName: 'api-cache',
|
|
49
|
-
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
50
|
-
}),
|
|
46
|
+
new StaleWhileRevalidate({ cacheName: 'api-cache', plugins: [...] }),
|
|
51
47
|
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
self.addEventListener('sync', (event) => {
|
|
55
|
-
if (event.tag === 'create-order') {
|
|
56
|
-
event.waitUntil(replayOrders())
|
|
57
|
-
}
|
|
48
|
+
self.addEventListener('sync', event => {
|
|
49
|
+
if (event.tag === 'create-order') event.waitUntil(replayOrders())
|
|
58
50
|
})
|
|
59
51
|
|
|
60
|
-
// After
|
|
52
|
+
// After — eidos (2 lines)
|
|
61
53
|
resource('/api/products', { offline: true })
|
|
62
54
|
action(createOrder, { reliability: 'neverLose' })
|
|
63
55
|
```
|
|
@@ -66,28 +58,27 @@ action(createOrder, { reliability: 'neverLose' })
|
|
|
66
58
|
|
|
67
59
|
## Quick Start
|
|
68
60
|
|
|
69
|
-
### Install
|
|
61
|
+
### 1. Install
|
|
70
62
|
|
|
71
63
|
```bash
|
|
72
|
-
npm install eidos
|
|
64
|
+
npm install @sweidos/eidos
|
|
73
65
|
# or
|
|
74
|
-
pnpm add eidos
|
|
66
|
+
pnpm add @sweidos/eidos
|
|
75
67
|
```
|
|
76
68
|
|
|
77
|
-
### Add the service worker
|
|
78
|
-
|
|
79
|
-
Copy `eidos-sw.js` to your project's `public/` directory:
|
|
69
|
+
### 2. Add the service worker
|
|
80
70
|
|
|
81
71
|
```bash
|
|
82
|
-
cp node_modules/eidos/dist/eidos-sw.js public/eidos-sw.js
|
|
72
|
+
cp node_modules/@sweidos/eidos/dist/eidos-sw.js public/eidos-sw.js
|
|
83
73
|
```
|
|
84
74
|
|
|
85
|
-
> **Vite users** —
|
|
75
|
+
> **Vite users** — automate this with the [Vite plugin snippet](#vite-plugin).
|
|
86
76
|
|
|
87
|
-
### Wrap your app
|
|
77
|
+
### 3. Wrap your app
|
|
88
78
|
|
|
89
79
|
```tsx
|
|
90
80
|
import { EidosProvider } from '@sweidos/eidos'
|
|
81
|
+
import { createRoot } from 'react-dom/client'
|
|
91
82
|
|
|
92
83
|
createRoot(document.getElementById('root')!).render(
|
|
93
84
|
<EidosProvider swPath="/eidos-sw.js">
|
|
@@ -96,14 +87,17 @@ createRoot(document.getElementById('root')!).render(
|
|
|
96
87
|
)
|
|
97
88
|
```
|
|
98
89
|
|
|
99
|
-
### Declare resources and actions
|
|
90
|
+
### 4. Declare resources and actions at module scope
|
|
100
91
|
|
|
101
92
|
```ts
|
|
102
|
-
// src/lib/eidos.ts
|
|
93
|
+
// src/lib/eidos.ts
|
|
94
|
+
// Module scope is required — actions must be registered before page reload
|
|
95
|
+
// for queue replay to work.
|
|
103
96
|
import { resource, action } from '@sweidos/eidos'
|
|
104
97
|
|
|
105
98
|
export const products = resource('/api/products', {
|
|
106
|
-
offline: true,
|
|
99
|
+
offline: true, // → StaleWhileRevalidate auto-selected
|
|
100
|
+
maxAge: 5 * 60 * 1000, // optional: treat cache as stale after 5 min
|
|
107
101
|
})
|
|
108
102
|
|
|
109
103
|
export const createOrder = action(
|
|
@@ -114,24 +108,25 @@ export const createOrder = action(
|
|
|
114
108
|
})
|
|
115
109
|
return res.json()
|
|
116
110
|
},
|
|
117
|
-
{ reliability: 'neverLose' },
|
|
111
|
+
{ reliability: 'neverLose', name: 'createOrder' },
|
|
118
112
|
)
|
|
119
113
|
```
|
|
120
114
|
|
|
121
|
-
### Use in components
|
|
115
|
+
### 5. Use in components
|
|
122
116
|
|
|
123
117
|
```tsx
|
|
124
|
-
//
|
|
125
|
-
const { data } = useQuery(products.query())
|
|
118
|
+
// TanStack Query
|
|
119
|
+
const { data } = useQuery(products.query<Product[]>())
|
|
126
120
|
|
|
127
|
-
// Or plain
|
|
128
|
-
const data = await products.json()
|
|
121
|
+
// Or plain async
|
|
122
|
+
const data = await products.json<Product[]>()
|
|
129
123
|
|
|
130
124
|
// Actions work identically online and offline
|
|
131
125
|
const result = await createOrder({ productId: 1, qty: 2 })
|
|
132
126
|
|
|
133
127
|
if ('queued' in result) {
|
|
134
|
-
|
|
128
|
+
// Persisted to IndexedDB — will replay automatically on reconnect
|
|
129
|
+
console.log(result.message)
|
|
135
130
|
}
|
|
136
131
|
```
|
|
137
132
|
|
|
@@ -141,114 +136,134 @@ if ('queued' in result) {
|
|
|
141
136
|
|
|
142
137
|
### `resource(url, config)`
|
|
143
138
|
|
|
144
|
-
Registers a URL as an offline-capable resource. Returns a
|
|
139
|
+
Registers a URL as an offline-capable resource. Returns a `ResourceHandle`.
|
|
145
140
|
|
|
146
141
|
```ts
|
|
147
|
-
const
|
|
148
|
-
offline: true, // required
|
|
142
|
+
const handle = resource('/api/products', {
|
|
143
|
+
offline: true, // required — enables SW interception
|
|
149
144
|
strategy?: 'cache-first' | 'stale-while-revalidate' | 'network-first',
|
|
150
|
-
cacheName?: string, // custom
|
|
145
|
+
cacheName?: string, // custom Cache Storage bucket (default: 'eidos-resources-v1')
|
|
146
|
+
maxAge?: number, // TTL in ms — expired entries are re-fetched from network
|
|
151
147
|
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Auto-selected strategy:**
|
|
151
|
+
|
|
152
|
+
| Config | Strategy | When to use |
|
|
153
|
+
|--------|----------|-------------|
|
|
154
|
+
| `offline: true` | `StaleWhileRevalidate` | Default — instant response + background refresh |
|
|
155
|
+
| `offline: true, strategy: 'cache-first'` | `CacheFirst` | Static assets, rarely-changing data |
|
|
156
|
+
| `offline: true, strategy: 'network-first'` | `NetworkFirst` | Always-fresh data with offline fallback |
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
products.strategy // generated GeneratedStrategy object
|
|
163
|
-
products.config // the config you passed in
|
|
158
|
+
**Handle methods:**
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
handle.fetch() // Promise<Response> — fetches, respects maxAge
|
|
162
|
+
handle.json<T>() // Promise<T> — fetch() + response.json()
|
|
163
|
+
handle.query<T>() // { queryKey, queryFn } — TanStack Query compatible
|
|
164
|
+
handle.prefetch() // Promise<void> — warm the cache
|
|
165
|
+
handle.invalidate() // Promise<void> — evict cached entries
|
|
166
|
+
handle.unregister() // void — remove from SW registry (required to re-register with different config)
|
|
164
167
|
```
|
|
165
168
|
|
|
166
|
-
**
|
|
169
|
+
**Handle properties:**
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
handle.url // '/api/products'
|
|
173
|
+
handle.config // the config you passed in
|
|
174
|
+
handle.strategy // { name, swStrategy, cacheName, reasoning, behavior, equivalentCode }
|
|
175
|
+
```
|
|
167
176
|
|
|
168
|
-
|
|
169
|
-
|---|---|---|
|
|
170
|
-
| `offline: true` | `StaleWhileRevalidate` | Best balance of speed and freshness for resilient resources |
|
|
171
|
-
| `offline: true, strategy: 'cache-first'` | `CacheFirst` | Maximum speed, data rarely changes |
|
|
172
|
-
| `offline: true, strategy: 'network-first'` | `NetworkFirst` | Freshness critical, cache as fallback only |
|
|
177
|
+
---
|
|
173
178
|
|
|
174
179
|
### `action(fn, config)`
|
|
175
180
|
|
|
176
|
-
Wraps
|
|
181
|
+
Wraps any async function with reliability guarantees. The wrapped function is a drop-in replacement.
|
|
177
182
|
|
|
178
183
|
```ts
|
|
179
184
|
const createOrder = action(
|
|
180
|
-
async (payload: OrderPayload): Promise<Order> => {
|
|
181
|
-
// your existing async function, unchanged
|
|
182
|
-
},
|
|
185
|
+
async (payload: OrderPayload): Promise<Order> => { /* your fn */ },
|
|
183
186
|
{
|
|
184
|
-
reliability: 'neverLose',
|
|
185
|
-
maxRetries?: number,
|
|
186
|
-
name?: string,
|
|
187
|
+
reliability: 'neverLose', // persist to IndexedDB + replay on reconnect
|
|
188
|
+
maxRetries?: number, // default: 3
|
|
189
|
+
name?: string, // label in devtools
|
|
187
190
|
}
|
|
188
191
|
)
|
|
189
192
|
|
|
190
|
-
// Returns TReturn when successful, QueuedResult when queued
|
|
191
193
|
const result = await createOrder(payload)
|
|
194
|
+
// → Order when successful
|
|
195
|
+
// → { queued: true, id, message } when offline or network fails
|
|
192
196
|
```
|
|
193
197
|
|
|
194
198
|
**Reliability modes:**
|
|
195
199
|
|
|
196
200
|
| Mode | Behaviour |
|
|
197
|
-
|
|
198
|
-
| `best-effort` |
|
|
199
|
-
| `neverLose` | Persist args to IndexedDB before executing. Replay on reconnect. |
|
|
201
|
+
|------|-----------|
|
|
202
|
+
| `best-effort` | Execute directly. No persistence, no retry. |
|
|
203
|
+
| `neverLose` | Persist args to IndexedDB before executing. Replay on reconnect with exponential backoff. |
|
|
204
|
+
|
|
205
|
+
**Exponential backoff:** `neverLose` actions that fail are retried with `2s × 2^retryCount` delay (capped at 5 min, ±20% jitter). Items not yet due are skipped on each replay pass.
|
|
206
|
+
|
|
207
|
+
---
|
|
200
208
|
|
|
201
209
|
### `replayQueue()`
|
|
202
210
|
|
|
203
|
-
Manually trigger queue replay. Called automatically on
|
|
211
|
+
Manually trigger queue replay. Called automatically on reconnect when `autoReplay: true`.
|
|
204
212
|
|
|
205
213
|
```ts
|
|
206
214
|
import { replayQueue } from '@sweidos/eidos'
|
|
207
215
|
|
|
208
|
-
|
|
216
|
+
// Manual trigger — e.g. after a user clicks "Retry"
|
|
217
|
+
await replayQueue()
|
|
209
218
|
```
|
|
210
219
|
|
|
220
|
+
---
|
|
221
|
+
|
|
211
222
|
### `EidosProvider`
|
|
212
223
|
|
|
213
|
-
|
|
224
|
+
React root component. Registers the SW and initialises the runtime.
|
|
214
225
|
|
|
215
226
|
```tsx
|
|
216
227
|
<EidosProvider
|
|
217
|
-
swPath="/eidos-sw.js"
|
|
218
|
-
autoReplay={true}
|
|
228
|
+
swPath="/eidos-sw.js" // default
|
|
229
|
+
autoReplay={true} // replay queue on reconnect, default: true
|
|
219
230
|
>
|
|
220
231
|
<App />
|
|
221
232
|
</EidosProvider>
|
|
222
233
|
```
|
|
223
234
|
|
|
235
|
+
---
|
|
236
|
+
|
|
224
237
|
### React Hooks
|
|
225
238
|
|
|
226
239
|
```ts
|
|
227
240
|
import { useEidosStatus, useEidosResource, useEidosQueue } from '@sweidos/eidos'
|
|
228
241
|
|
|
229
|
-
// Online + SW
|
|
230
|
-
const { isOnline, swStatus } = useEidosStatus()
|
|
242
|
+
// Online status + SW lifecycle — cheap subscription, safe in headers
|
|
243
|
+
const { isOnline, swStatus, swError } = useEidosStatus()
|
|
231
244
|
|
|
232
|
-
// Live state for a single resource
|
|
245
|
+
// Live cache state for a single resource URL
|
|
233
246
|
const entry = useEidosResource('/api/products')
|
|
234
|
-
// → { status, cacheHits, cachedAt, strategy, ... }
|
|
247
|
+
// entry → { status, cacheHits, cacheMisses, cachedAt, strategy, config, ... }
|
|
235
248
|
|
|
236
|
-
// The full action queue
|
|
249
|
+
// The full action queue, reactive
|
|
237
250
|
const queue = useEidosQueue()
|
|
238
251
|
|
|
239
|
-
// Full store
|
|
252
|
+
// Full Zustand store — use sparingly
|
|
240
253
|
const state = useEidos()
|
|
241
254
|
```
|
|
242
255
|
|
|
256
|
+
---
|
|
257
|
+
|
|
243
258
|
### `setOfflineSimulation(enabled)`
|
|
244
259
|
|
|
245
|
-
Toggle offline simulation
|
|
260
|
+
Toggle offline simulation without physically disconnecting the network.
|
|
246
261
|
|
|
247
262
|
```ts
|
|
248
263
|
import { setOfflineSimulation } from '@sweidos/eidos'
|
|
249
264
|
|
|
250
|
-
setOfflineSimulation(true) //
|
|
251
|
-
setOfflineSimulation(false) // restore normal
|
|
265
|
+
setOfflineSimulation(true) // SW serves only cached responses
|
|
266
|
+
setOfflineSimulation(false) // restore normal behaviour
|
|
252
267
|
```
|
|
253
268
|
|
|
254
269
|
---
|
|
@@ -260,43 +275,43 @@ setOfflineSimulation(false) // restore normal
|
|
|
260
275
|
│ Application Layer │
|
|
261
276
|
│ resource() · action() · EidosProvider │ ← you write this
|
|
262
277
|
└────────────────┬────────────────────────────┘
|
|
263
|
-
│ postMessage
|
|
278
|
+
│ EIDOS_REGISTER_RESOURCE (postMessage)
|
|
264
279
|
┌────────────────▼────────────────────────────┐
|
|
265
|
-
│ Runtime Layer
|
|
266
|
-
│ Strategy derivation · Zustand store │
|
|
267
|
-
│ SW bridge · IDB queue
|
|
280
|
+
│ Runtime Layer (@sweidos/eidos) │
|
|
281
|
+
│ Strategy derivation · Zustand store │
|
|
282
|
+
│ SW bridge · IDB queue · exponential backoff │
|
|
268
283
|
└────────────────┬────────────────────────────┘
|
|
269
284
|
│ fetch intercept
|
|
270
285
|
┌────────────────▼────────────────────────────┐
|
|
271
|
-
│ Worker Layer
|
|
272
|
-
│ CacheFirst · StaleWhileRevalidate │
|
|
286
|
+
│ Worker Layer (eidos-sw.js) │
|
|
287
|
+
│ CacheFirst · StaleWhileRevalidate │
|
|
273
288
|
│ NetworkFirst · Offline simulation │
|
|
274
289
|
└────────────────┬────────────────────────────┘
|
|
275
290
|
│ Cache API · IndexedDB
|
|
276
291
|
┌────────────────▼────────────────────────────┐
|
|
277
292
|
│ Storage Layer │
|
|
278
|
-
│ Cache Storage · IndexedDB (action queue) │
|
|
293
|
+
│ Cache Storage · IndexedDB (action queue) │
|
|
279
294
|
└─────────────────────────────────────────────┘
|
|
280
295
|
```
|
|
281
296
|
|
|
282
|
-
###
|
|
297
|
+
### SW message protocol
|
|
283
298
|
|
|
284
|
-
|
|
299
|
+
**App → SW:**
|
|
285
300
|
|
|
286
301
|
| Message | Purpose |
|
|
287
|
-
|
|
288
|
-
| `EIDOS_REGISTER_RESOURCE` |
|
|
302
|
+
|---------|---------|
|
|
303
|
+
| `EIDOS_REGISTER_RESOURCE` | Register a fetch-intercept rule |
|
|
289
304
|
| `EIDOS_UNREGISTER_RESOURCE` | Remove a rule |
|
|
290
|
-
| `EIDOS_CLEAR_CACHE` | Evict cache entries |
|
|
291
|
-
| `EIDOS_SIMULATE_OFFLINE` | Toggle offline simulation |
|
|
305
|
+
| `EIDOS_CLEAR_CACHE` | Evict cache entries for a URL |
|
|
306
|
+
| `EIDOS_SIMULATE_OFFLINE` | Toggle offline simulation mode |
|
|
292
307
|
| `EIDOS_PING` | Health check |
|
|
293
308
|
|
|
294
|
-
|
|
309
|
+
**SW → App:**
|
|
295
310
|
|
|
296
311
|
| Message | Purpose |
|
|
297
|
-
|
|
298
|
-
| `EIDOS_CACHE_HIT` |
|
|
299
|
-
| `EIDOS_CACHE_UPDATED` | Cache entry
|
|
312
|
+
|---------|---------|
|
|
313
|
+
| `EIDOS_CACHE_HIT` | Cached response was served |
|
|
314
|
+
| `EIDOS_CACHE_UPDATED` | Cache entry refreshed from network |
|
|
300
315
|
| `EIDOS_NETWORK_ERROR` | Network request failed |
|
|
301
316
|
| `EIDOS_CACHE_CLEARED` | Cache was cleared |
|
|
302
317
|
|
|
@@ -306,51 +321,35 @@ Messages received from the SW:
|
|
|
306
321
|
|
|
307
322
|
```
|
|
308
323
|
eidos/
|
|
324
|
+
├── api/ Vercel serverless functions (demo endpoints)
|
|
309
325
|
├── packages/
|
|
310
|
-
│ ├── core/
|
|
326
|
+
│ ├── core/ @sweidos/eidos npm package
|
|
311
327
|
│ │ └── src/
|
|
312
328
|
│ │ ├── types.ts
|
|
313
|
-
│ │ ├── resource.ts resource()
|
|
314
|
-
│ │ ├── action.ts action() + queue replay
|
|
315
|
-
│ │ ├── runtime.ts
|
|
329
|
+
│ │ ├── resource.ts resource() — caching + handle
|
|
330
|
+
│ │ ├── action.ts action() + exponential backoff queue replay
|
|
331
|
+
│ │ ├── runtime.ts initEidos + SW registration
|
|
316
332
|
│ │ ├── store.ts Zustand store
|
|
317
333
|
│ │ ├── sw-bridge.ts postMessage channel
|
|
318
|
-
│ │ ├── idb.ts IndexedDB wrapper
|
|
334
|
+
│ │ ├── idb.ts IndexedDB CRUD wrapper
|
|
319
335
|
│ │ └── react/ EidosProvider + hooks
|
|
320
|
-
│ └── worker/
|
|
321
|
-
│ └── src/sw.ts
|
|
336
|
+
│ └── worker/ SW typed source
|
|
337
|
+
│ └── src/sw.ts → compiles to eidos-sw.js
|
|
322
338
|
├── apps/
|
|
323
|
-
│ └── playground/
|
|
339
|
+
│ └── playground/ Interactive demo dashboard
|
|
324
340
|
│ └── public/
|
|
325
|
-
│ └── eidos-sw.js
|
|
326
|
-
└──
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## Dev Dashboard
|
|
332
|
-
|
|
333
|
-
The playground at `apps/playground` is a full interactive dashboard that demonstrates every feature:
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
pnpm dev # → http://localhost:3000
|
|
341
|
+
│ └── eidos-sw.js compiled service worker
|
|
342
|
+
└── .github/workflows/ CI/CD — deploy + npm release on push to main
|
|
337
343
|
```
|
|
338
344
|
|
|
339
|
-
It includes:
|
|
340
|
-
|
|
341
|
-
- **Overview** — live status + interactive products/orders demos
|
|
342
|
-
- **Resources** — every registered resource with cache stats and strategy detail
|
|
343
|
-
- **Action Queue** — live queue with per-item status and replay controls
|
|
344
|
-
- **Intent Inspector** — step-by-step trace from intent declaration to SW rule
|
|
345
|
-
- **How It Works** — architecture diagrams and lifecycle walkthroughs
|
|
346
|
-
|
|
347
345
|
---
|
|
348
346
|
|
|
349
347
|
## Vite Plugin
|
|
350
348
|
|
|
351
|
-
|
|
349
|
+
Automatically copy `eidos-sw.js` into `public/` on build:
|
|
352
350
|
|
|
353
351
|
```ts
|
|
352
|
+
// vite.config.ts
|
|
354
353
|
import { copyFileSync } from 'fs'
|
|
355
354
|
import { resolve } from 'path'
|
|
356
355
|
|
|
@@ -359,7 +358,7 @@ function eidosPlugin() {
|
|
|
359
358
|
name: 'eidos-sw',
|
|
360
359
|
buildStart() {
|
|
361
360
|
copyFileSync(
|
|
362
|
-
resolve('./node_modules/eidos/dist/eidos-sw.js'),
|
|
361
|
+
resolve('./node_modules/@sweidos/eidos/dist/eidos-sw.js'),
|
|
363
362
|
resolve('./public/eidos-sw.js'),
|
|
364
363
|
)
|
|
365
364
|
},
|
|
@@ -371,26 +370,26 @@ function eidosPlugin() {
|
|
|
371
370
|
|
|
372
371
|
## Known Limitations
|
|
373
372
|
|
|
374
|
-
These are real limitations in v0.1. They are documented so you know exactly what you're getting.
|
|
375
|
-
|
|
376
373
|
| Limitation | Detail |
|
|
377
|
-
|
|
378
|
-
| GET-only caching |
|
|
379
|
-
| Pathname matching | Resources match by pathname
|
|
380
|
-
| Module-scope actions | `action()` must be called at module scope
|
|
381
|
-
|
|
|
382
|
-
| Single SW | `EidosProvider` assumes `/eidos-sw.js`. Multiple SW registrations in one app are unsupported. |
|
|
374
|
+
|------------|--------|
|
|
375
|
+
| GET-only caching | SW intercepts `GET` only. `POST`/`PUT`/`DELETE` are not cached (but *are* queued via `action()`). |
|
|
376
|
+
| Pathname matching | Resources match by pathname. `/api/products?page=2` and `/api/products` share the same SW rule but are cached separately. |
|
|
377
|
+
| Module-scope actions | `action()` must be called at module scope so functions are registered before a page reload triggers queue replay. |
|
|
378
|
+
| Single SW | `EidosProvider` assumes one SW at `/eidos-sw.js`. Multiple registrations are unsupported. |
|
|
383
379
|
|
|
384
380
|
---
|
|
385
381
|
|
|
386
382
|
## Roadmap
|
|
387
383
|
|
|
384
|
+
- [x] Cache TTL / `maxAge` expiration
|
|
385
|
+
- [x] Exponential backoff with jitter for queue replay
|
|
386
|
+
- [x] Per-resource `cacheName` override
|
|
387
|
+
- [x] `resource.unregister()` for cleanup
|
|
388
388
|
- [ ] URL pattern matching (wildcards, regex)
|
|
389
|
-
- [ ] Cache TTL / expiration
|
|
390
389
|
- [ ] Cross-origin resource support
|
|
391
|
-
- [ ] Background Sync integration
|
|
390
|
+
- [ ] Background Sync API integration
|
|
392
391
|
- [ ] Vite plugin (first-class, published separately)
|
|
393
|
-
- [ ]
|
|
392
|
+
- [ ] Vue / Svelte bindings
|
|
394
393
|
- [ ] TanStack Query integration package
|
|
395
394
|
|
|
396
395
|
---
|
|
@@ -398,22 +397,13 @@ These are real limitations in v0.1. They are documented so you know exactly what
|
|
|
398
397
|
## Contributing
|
|
399
398
|
|
|
400
399
|
```bash
|
|
401
|
-
#
|
|
402
|
-
pnpm
|
|
403
|
-
|
|
404
|
-
#
|
|
405
|
-
pnpm dev
|
|
406
|
-
|
|
407
|
-
# Type-check everything
|
|
408
|
-
pnpm type-check
|
|
409
|
-
|
|
410
|
-
# Build the core package
|
|
411
|
-
pnpm build:core
|
|
400
|
+
pnpm install # install all workspace deps
|
|
401
|
+
pnpm dev # run playground at localhost:3000
|
|
402
|
+
pnpm type-check # typecheck all packages
|
|
403
|
+
pnpm --filter @sweidos/eidos build # build core package
|
|
412
404
|
```
|
|
413
405
|
|
|
414
|
-
The project uses pnpm workspaces. TypeScript strict mode
|
|
415
|
-
|
|
416
|
-
The naming (`Eidos`) is a placeholder. All references are easy to find/replace — the package name, SW filename, and message prefix are the only places the name appears.
|
|
406
|
+
The project uses pnpm workspaces. TypeScript strict mode throughout.
|
|
417
407
|
|
|
418
408
|
---
|
|
419
409
|
|