@pwa-platform/engine-workbox 0.1.0-beta.0 → 0.1.0-beta.2

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.
@@ -1,4 +1,6 @@
1
1
  export { createPrecacheEngine } from "./engine.js";
2
2
  export type { PwaPrecacheActivateResult, PwaPrecacheEngine, PwaPrecacheInstallResult } from "./engine.js";
3
3
  export type { PwaPrecacheEngineOptions, PwaPrecacheManifestEntry } from "./options.js";
4
+ export { createRuntimeCacheEngine } from "./runtime.js";
5
+ export type { PwaRuntimeCacheEngine, PwaRuntimeCacheEngineOptions, PwaRuntimeCacheHit, PwaRuntimeCacheResult } from "./runtime.js";
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC1G,YAAY,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC1G,YAAY,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
@@ -1,4 +1,5 @@
1
1
  // Worker entry (`@pwa-platform/engine-workbox/worker`): runs in a service worker after bundling and depends only
2
- // on workbox-precaching and workbox-core, never on Node modules.
2
+ // on workbox-precaching, workbox-strategies, workbox-expiration and workbox-core, never on Node modules.
3
3
  export { createPrecacheEngine } from "./engine.js";
4
+ export { createRuntimeCacheEngine } from "./runtime.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,iEAAiE;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,yGAAyG;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,36 @@
1
+ export type PwaRuntimeCacheEngineOptions = {
2
+ readonly cacheName: string;
3
+ readonly strategy: "network-first" | "stale-while-revalidate";
4
+ readonly maxEntries: number;
5
+ readonly maxAgeSeconds: number;
6
+ /** Platform admission check; the engine writes to the cache only when it resolves true. */
7
+ readonly admit: (response: Response) => Promise<boolean>;
8
+ /**
9
+ * 1-30 integer seconds. Only meaningful for strategy "network-first": passed straight through to Workbox's
10
+ * `NetworkFirst` so a cache hit is served once the timeout elapses, even while the network request is still
11
+ * pending. Ignored for "stale-while-revalidate", which never waits on the network before serving a cache hit.
12
+ * Omitted entirely (not `undefined`) when unset, so an engine built without it constructs `NetworkFirst`
13
+ * exactly as before this option existed.
14
+ */
15
+ readonly networkTimeoutSeconds?: number;
16
+ };
17
+ export type PwaRuntimeCacheHit = {
18
+ readonly cachedAt: number;
19
+ readonly reason: "network-failed" | "network-timeout" | "stale-while-revalidate";
20
+ };
21
+ export type PwaRuntimeCacheResult = {
22
+ readonly response: Response;
23
+ readonly servedFromCache: PwaRuntimeCacheHit | null;
24
+ };
25
+ /** Runtime-cache port used by the platform worker (sw-runtime); it only acts when `handle` is called. */
26
+ export type PwaRuntimeCacheEngine = {
27
+ /** Handles one fetch event; registers its own background work with event.waitUntil. */
28
+ handle(event: FetchEvent): Promise<PwaRuntimeCacheResult>;
29
+ };
30
+ /**
31
+ * Creates the runtime-cache port for one plan entry. Write-time admission and the read-time expiry stamp are the
32
+ * platform's own; `ExpirationPlugin` only enforces `maxEntries` (LRU) and `purgeOnQuotaError`, never `maxAgeSeconds`
33
+ * (see below).
34
+ */
35
+ export declare function createRuntimeCacheEngine(options: PwaRuntimeCacheEngineOptions): PwaRuntimeCacheEngine;
36
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/worker/runtime.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,wBAAwB,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD;;;;;;OAMG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,wBAAwB,CAAC;CAClF,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,CAAC;AAEzH,yGAAyG;AACzG,MAAM,MAAM,qBAAqB,GAAG;IAClC,uFAAuF;IACvF,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3D,CAAC;AAOF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,4BAA4B,GAAG,qBAAqB,CAyGrG"}
@@ -0,0 +1,107 @@
1
+ import { ExpirationPlugin } from "workbox-expiration";
2
+ import { NetworkFirst, StaleWhileRevalidate } from "workbox-strategies";
3
+ /** Internal header on the cached copy only; stripped before the response reaches the page. Never a public API. */
4
+ const STAMP_HEADER = "x-pwa-platform-cached-at";
5
+ /** Clones `response`'s status, statusText and body with `headers` in place of its own. */
6
+ function withHeaders(response, headers) {
7
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
8
+ }
9
+ /**
10
+ * Creates the runtime-cache port for one plan entry. Write-time admission and the read-time expiry stamp are the
11
+ * platform's own; `ExpirationPlugin` only enforces `maxEntries` (LRU) and `purgeOnQuotaError`, never `maxAgeSeconds`
12
+ * (see below).
13
+ */
14
+ export function createRuntimeCacheEngine(options) {
15
+ const { cacheName, strategy, maxEntries, maxAgeSeconds, admit, networkTimeoutSeconds } = options;
16
+ // Per-request correlation for servedFromCache: a module- or closure-level "last hit" variable would leak between
17
+ // concurrent fetch events on the same engine instance, so hits are keyed by the FetchEvent of the call that
18
+ // produced them and read back once, in `handle`.
19
+ const hits = new WeakMap();
20
+ // Marks the moment a network-first request's own network fetch fails. Workbox's `fetchDidFail` plugin hook fires
21
+ // synchronously, inside `fetch()`'s catch block, only when `fetch()` itself throws (a real network failure) --
22
+ // never for a 4xx/5xx response, and never for the timeout path. NetworkFirst reaches a cache hit through exactly
23
+ // one of two routes (see workbox-strategies@7.4.1 NetworkFirst.js `_handle`): the network-failure catch in
24
+ // `_getNetworkPromise` (which always runs `fetchDidFail` before calling `handler.cacheMatch`), or the timeout
25
+ // callback in `_getTimeoutPromise` (which calls `handler.cacheMatch` directly and never touches `fetchDidFail`).
26
+ // So when `cachedResponseWillBeUsed` fires, checking whether this event's fetch already failed distinguishes the
27
+ // two reliably: it reads a flag set synchronously by Workbox's own callback ordering, rather than racing a
28
+ // second timer of our own against Workbox's `networkTimeoutSeconds` timer.
29
+ const networkFailed = new WeakMap();
30
+ // fetchDidFail is only meaningful when a timeout is in play (see the reason ternary in cachedResponseWillBeUsed
31
+ // below: without a timeout the reason always falls back to "network-failed" regardless of this flag), so it is
32
+ // included in the plugin object only for strategy "network-first" with a timeout configured. An engine built
33
+ // without a timeout is therefore constructed with exactly the plugin list this module had before this option
34
+ // existed (no fetchDidFail key on platformPlugin at all).
35
+ const trackNetworkFailure = strategy === "network-first" && networkTimeoutSeconds !== undefined;
36
+ const platformPlugin = {
37
+ ...(trackNetworkFailure
38
+ ? {
39
+ async fetchDidFail({ event }) {
40
+ if (event instanceof FetchEvent)
41
+ networkFailed.set(event, true);
42
+ },
43
+ }
44
+ : {}),
45
+ // Registering this hook is also what stops Workbox from prepending its own default cacheOkAndOpaque plugin
46
+ // (workbox-strategies only does that when no plugin already defines cacheWillUpdate) — admission here is the
47
+ // only gate, private/opaque/non-200 responses are never written unless `admit` itself accepts them.
48
+ async cacheWillUpdate({ response }) {
49
+ if (!(await admit(response)))
50
+ return null;
51
+ const headers = new Headers(response.headers);
52
+ headers.set(STAMP_HEADER, String(Date.now()));
53
+ return withHeaders(response, headers);
54
+ },
55
+ async cachedResponseWillBeUsed({ request, cachedResponse, event, }) {
56
+ if (cachedResponse === undefined)
57
+ return cachedResponse;
58
+ const stampValue = cachedResponse.headers.get(STAMP_HEADER);
59
+ const stamp = stampValue === null ? Number.NaN : Number(stampValue);
60
+ // Expiry is decided solely by this stamp, never by ExpirationPlugin's own Date-header check: that check
61
+ // vetoes entries at read time using the server's Date header, which would reject a freshly written copy of a
62
+ // CDN-cached (and therefore already-old) response. ExpirationPlugin is configured below without
63
+ // maxAgeSeconds so its own freshness check never runs.
64
+ if (!Number.isFinite(stamp) || Date.now() - stamp > maxAgeSeconds * 1000) {
65
+ const cache = await self.caches.open(cacheName);
66
+ await cache.delete(request);
67
+ return undefined;
68
+ }
69
+ if (event instanceof FetchEvent) {
70
+ const reason = strategy !== "network-first"
71
+ ? "stale-while-revalidate"
72
+ : networkFailed.get(event) === true
73
+ ? "network-failed"
74
+ : networkTimeoutSeconds !== undefined
75
+ ? "network-timeout"
76
+ : "network-failed";
77
+ hits.set(event, { cachedAt: stamp, reason });
78
+ }
79
+ const headers = new Headers(cachedResponse.headers);
80
+ headers.delete(STAMP_HEADER);
81
+ return withHeaders(cachedResponse, headers);
82
+ },
83
+ };
84
+ // maxAgeSeconds intentionally omitted: see the comment in cachedResponseWillBeUsed above.
85
+ const expirationPlugin = new ExpirationPlugin({ maxEntries, purgeOnQuotaError: true });
86
+ // The platform plugin must run before ExpirationPlugin so a request expired by our stamp is deleted (and its
87
+ // cachedResponse turned into undefined) before ExpirationPlugin's own cachedResponseWillBeUsed sees it.
88
+ const plugins = [platformPlugin, expirationPlugin];
89
+ const strategyInstance = strategy === "network-first"
90
+ ? new NetworkFirst(networkTimeoutSeconds === undefined ? { cacheName, plugins } : { cacheName, plugins, networkTimeoutSeconds })
91
+ : new StaleWhileRevalidate({ cacheName, plugins });
92
+ return {
93
+ async handle(event) {
94
+ const [responseDone, handlerDone] = strategyInstance.handleAll({ event, request: event.request });
95
+ // A write failure (including QuotaExceededError, which purgeOnQuotaError above reacts to globally) must
96
+ // never surface as an unhandled rejection or affect the response already given to the page; handlerDone only
97
+ // covers Workbox's own background work (cache writes), which is otherwise unobserved once ignored here.
98
+ event.waitUntil(handlerDone.catch(() => undefined));
99
+ const response = await responseDone;
100
+ const servedFromCache = hits.get(event) ?? null;
101
+ hits.delete(event);
102
+ networkFailed.delete(event);
103
+ return { response, servedFromCache };
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/worker/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAMxE,kHAAkH;AAClH,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAgChD,0FAA0F;AAC1F,SAAS,WAAW,CAAC,QAAkB,EAAE,OAAgB;IACvD,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5G,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAqC;IAC5E,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC;IAEjG,iHAAiH;IACjH,4GAA4G;IAC5G,iDAAiD;IACjD,MAAM,IAAI,GAAG,IAAI,OAAO,EAAkC,CAAC;IAE3D,iHAAiH;IACjH,+GAA+G;IAC/G,iHAAiH;IACjH,2GAA2G;IAC3G,8GAA8G;IAC9G,iHAAiH;IACjH,iHAAiH;IACjH,2GAA2G;IAC3G,2EAA2E;IAC3E,MAAM,aAAa,GAAG,IAAI,OAAO,EAAoB,CAAC;IAEtD,gHAAgH;IAChH,+GAA+G;IAC/G,6GAA6G;IAC7G,6GAA6G;IAC7G,0DAA0D;IAC1D,MAAM,mBAAmB,GAAG,QAAQ,KAAK,eAAe,IAAI,qBAAqB,KAAK,SAAS,CAAC;IAChG,MAAM,cAAc,GAAG;QACrB,GAAG,CAAC,mBAAmB;YACrB,CAAC,CAAC;gBACE,KAAK,CAAC,YAAY,CAAC,EAAE,KAAK,EAAuC;oBAC/D,IAAI,KAAK,YAAY,UAAU;wBAAE,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAClE,CAAC;aACF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,2GAA2G;QAC3G,6GAA6G;QAC7G,oGAAoG;QACpG,KAAK,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAmC;YACjE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC9C,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;QACD,KAAK,CAAC,wBAAwB,CAAC,EAC7B,OAAO,EACP,cAAc,EACd,KAAK,GAKN;YACC,IAAI,cAAc,KAAK,SAAS;gBAAE,OAAO,cAAc,CAAC;YACxD,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC5D,MAAM,KAAK,GAAG,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACpE,wGAAwG;YACxG,6GAA6G;YAC7G,gGAAgG;YAChG,uDAAuD;YACvD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,aAAa,GAAG,IAAI,EAAE,CAAC;gBACzE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAChD,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC5B,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;gBAChC,MAAM,MAAM,GACV,QAAQ,KAAK,eAAe;oBAC1B,CAAC,CAAC,wBAAwB;oBAC1B,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI;wBACjC,CAAC,CAAC,gBAAgB;wBAClB,CAAC,CAAC,qBAAqB,KAAK,SAAS;4BACnC,CAAC,CAAC,iBAAiB;4BACnB,CAAC,CAAC,gBAAgB,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/C,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YACpD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC7B,OAAO,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;KACF,CAAC;IAEF,0FAA0F;IAC1F,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvF,6GAA6G;IAC7G,wGAAwG;IACxG,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAoB,CAAC;IACtE,MAAM,gBAAgB,GACpB,QAAQ,KAAK,eAAe;QAC1B,CAAC,CAAC,IAAI,YAAY,CAAC,qBAAqB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;QAChI,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IAEvD,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG,gBAAgB,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAClG,wGAAwG;YACxG,6GAA6G;YAC7G,wGAAwG;YACxG,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC;YACpC,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnB,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pwa-platform/engine-workbox",
3
- "version": "0.1.0-beta.0",
4
- "description": "Internal Workbox precache engine for PWA Platform.",
3
+ "version": "0.1.0-beta.2",
4
+ "description": "Internal Workbox precache and runtime-cache engine for PWA Platform.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -19,8 +19,10 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "workbox-core": "7.4.1",
22
+ "workbox-expiration": "7.4.1",
22
23
  "workbox-precaching": "7.4.1",
23
- "@pwa-platform/contracts": "0.1.0-beta.0"
24
+ "workbox-strategies": "7.4.1",
25
+ "@pwa-platform/contracts": "0.1.0-beta.2"
24
26
  },
25
27
  "devDependencies": {
26
28
  "@playwright/test": "1.63.0",