@ultimat3/pwa 1.1.0 → 2.0.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/CLAUDE.md +34 -0
- package/package.json +3 -2
- package/src/service-worker.ts +15 -1
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @ultimat3/pwa — boundary
|
|
2
|
+
|
|
3
|
+
Owns: `sw.js` generation, caching strategies, precache manifest, version skew, web
|
|
4
|
+
manifest, icons, offline fallback, background-sync trigger, push, install prompt.
|
|
5
|
+
|
|
6
|
+
Tier 4. May import tiers 0–3: `core`, `schema`, `i18n`, `money`, `time`, `cache`, `seo`,
|
|
7
|
+
`entity`, `policy`, `http`, `action`, `query`. **Never** `render`, `mcp`, `ai`, `manifest`
|
|
8
|
+
(sideways), never `ui`/`cli` (upward).
|
|
9
|
+
|
|
10
|
+
| Rule | Detail |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `sw.js` | generated, never hand-written, never committed. No file in this package is a SW. |
|
|
13
|
+
| Determinism | no `Date.now()`, no randomness, sort every collection. Same input → same bytes. |
|
|
14
|
+
| Route input | `PwaRoute` is a **structural** view of render's `RouteDescriptor`. Never import render. |
|
|
15
|
+
| Strategy choice | derived from render mode via `MODE_STRATEGY`. Per-route override only. |
|
|
16
|
+
| Precache revision | content hash. Never the build id — that re-downloads everything per deploy. |
|
|
17
|
+
| Precache KEY | the bare URL, always. The revision addresses the FETCH (`?v=<hash>`), never the key: every strategy looks an entry up with `caches.match(req)` and `ignoreSearch` defaults to `false`, so an entry left keyed under `?v=` is a permanent miss — offline serves the fallback document instead of the precached page, and online every precached byte is downloaded twice. `addAll` still does the fetching, because its all-or-nothing failure is what stops a half-populated precache from activating; the install block only re-keys what it stored. `service-worker.test.ts` executes the emitted `sw.js` against stub `caches`/`fetch` rather than asserting its text. |
|
|
18
|
+
| Cache names | always `cacheNamespace(buildId, kind)`. An unkeyed cache name is a rejected change. |
|
|
19
|
+
| Offline fallback | `requireOfflineFallback` runs inside `generateServiceWorker`. Never optional. |
|
|
20
|
+
| Capabilities | gate the manifest member **and** the SW block. Disabled → zero bytes. |
|
|
21
|
+
| Outbox | queue lives in `@ultimat3/realtime`. This package only registers the sync trigger. |
|
|
22
|
+
| Push strings | i18n keys only, rendered per subscriber locale. Never a literal. |
|
|
23
|
+
| Colours | token values passed in via `PwaConfig.tokens`. Never a hex literal in this package — a test fixture asserting the parser is the one exception. |
|
|
24
|
+
| Icons | one source image → `BuiltinImagePipeline` → a square PNG per `ICON_MATRIX` entry, rendered by `@ultimat3/core`'s image pipeline. Never a second scaler, never `sharp`, never a vendor CDN. |
|
|
25
|
+
| Icon source | a **PNG** (core decodes PNG and JPEG only). `x new` scaffolds one; `@ultimat3/cli`'s `dev-assets.ts` reads it at `ICON_SOURCE` and serves every entry at its `outputPath`. This package renders bytes and mounts nothing. |
|
|
26
|
+
| Icon background | `IconSourceConfig.background`, hex or `transparent` — core's grammar has no named colours. Default transparent. |
|
|
27
|
+
| Errors | `errors.ts` subclasses only. `X_NOT_IMPLEMENTED` must carry a real `fix:`. |
|
|
28
|
+
| Errors in `sw.js` | no bundler there, so no `UltimateError` import — the generated source defines its own class (`SYNC_ERROR_CLASS`) carrying `code`, `cause`, `fix`, `docs`. Never emit a bare `throw new Error`; the code stays owned by `errors.ts`. |
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
bun test # from packages/pwa
|
|
32
|
+
bun run typecheck
|
|
33
|
+
bun run --cwd ../.. verify
|
|
34
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/pwa",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Generated service worker, web manifest, icons, push and version-skew handling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"src",
|
|
21
21
|
"!src/**/*.test.ts",
|
|
22
|
+
"CLAUDE.md",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE"
|
|
24
25
|
],
|
|
@@ -30,6 +31,6 @@
|
|
|
30
31
|
"test": "bun test"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@ultimat3/core": "
|
|
34
|
+
"@ultimat3/core": "2.0.0"
|
|
34
35
|
}
|
|
35
36
|
}
|
package/src/service-worker.ts
CHANGED
|
@@ -203,12 +203,26 @@ function serializeRules(rules: readonly RouteRule[]): string {
|
|
|
203
203
|
return `[${rows.join(',')}]`;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* The revision addresses the **fetch**, never the **key**. Every strategy looks an entry up with
|
|
208
|
+
* `caches.match(req)` on the bare URL — `ignoreSearch` defaults to `false` — so an entry left
|
|
209
|
+
* keyed under `?v=<revision>` is a permanent miss: offline serves the fallback document instead
|
|
210
|
+
* of the page that was precached, and online every precached byte is downloaded a second time.
|
|
211
|
+
* `addAll` still does the fetching, because its all-or-nothing failure is what stops a
|
|
212
|
+
* half-populated precache from activating; the second pass only re-keys what it stored.
|
|
213
|
+
*/
|
|
206
214
|
const INSTALL_BLOCK = `
|
|
207
215
|
self.addEventListener('install',(event)=>{
|
|
208
216
|
event.waitUntil((async()=>{
|
|
209
217
|
const cache=await caches.open(PRECACHE);
|
|
210
218
|
// Revision is a content hash: unchanged assets are not re-downloaded across deploys.
|
|
211
|
-
|
|
219
|
+
const fetched=PRECACHE_MANIFEST.map((e)=>new Request(e.url+'?v='+e.revision,{cache:'reload'}));
|
|
220
|
+
await cache.addAll(fetched);
|
|
221
|
+
for(let i=0;i<PRECACHE_MANIFEST.length;i++){
|
|
222
|
+
const stored=await cache.match(fetched[i]);
|
|
223
|
+
if(stored)await cache.put(new Request(PRECACHE_MANIFEST[i].url),stored);
|
|
224
|
+
await cache.delete(fetched[i]);
|
|
225
|
+
}
|
|
212
226
|
})());
|
|
213
227
|
});`.trim();
|
|
214
228
|
|