@ultimat3/pwa 5.0.1 → 7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/pwa",
3
- "version": "5.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "Generated service worker, web manifest, icons, push and version-skew handling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "5.0.1",
35
- "@ultimat3/seo": "5.0.1"
34
+ "@ultimat3/core": "7.0.0",
35
+ "@ultimat3/seo": "7.0.0"
36
36
  }
37
37
  }
@@ -43,15 +43,14 @@ export function enabledCapabilities(capabilities: ResolvedCapabilities): readonl
43
43
  }
44
44
 
45
45
  /** Manifest members a capability owns. Absent capability → absent member. */
46
- export const CAPABILITY_MANIFEST_KEYS: Readonly<Record<Capability, readonly string[]>> =
47
- Object.freeze({
48
- push: [],
49
- backgroundSync: [],
50
- badging: [],
51
- shareTarget: ['share_target'],
52
- fileHandlers: ['file_handlers'],
53
- protocolHandlers: ['protocol_handlers'],
54
- });
46
+ export const CAPABILITY_MANIFEST_KEYS = Object.freeze<Record<Capability, readonly string[]>>({
47
+ push: [],
48
+ backgroundSync: [],
49
+ badging: [],
50
+ shareTarget: ['share_target'],
51
+ fileHandlers: ['file_handlers'],
52
+ protocolHandlers: ['protocol_handlers'],
53
+ });
55
54
 
56
55
  /**
57
56
  * The service-worker code each capability emits — its listener, and anything that listener alone
@@ -64,13 +63,11 @@ export const CAPABILITY_MANIFEST_KEYS: Readonly<Record<Capability, readonly stri
64
63
  * already serves, so the member is the whole feature and the worker has no branch to add.
65
64
  * `shareTarget` named `/_x/share-target` here for two releases and no block ever emitted it.
66
65
  */
67
- export const CAPABILITY_SW_MARKERS: Readonly<Record<Capability, readonly string[]>> = Object.freeze(
68
- {
69
- push: ["addEventListener('push'", "addEventListener('notificationclick'"],
70
- backgroundSync: ["addEventListener('sync'", 'class PwaSyncError'],
71
- badging: ['navigator.setAppBadge'],
72
- shareTarget: [],
73
- fileHandlers: [],
74
- protocolHandlers: [],
75
- },
76
- );
66
+ export const CAPABILITY_SW_MARKERS = Object.freeze<Record<Capability, readonly string[]>>({
67
+ push: ["addEventListener('push'", "addEventListener('notificationclick'"],
68
+ backgroundSync: ["addEventListener('sync'", 'class PwaSyncError'],
69
+ badging: ['navigator.setAppBadge'],
70
+ shareTarget: [],
71
+ fileHandlers: [],
72
+ protocolHandlers: [],
73
+ });
package/src/icons.ts CHANGED
@@ -87,12 +87,13 @@ export interface ImagePipeline {
87
87
  * Always square PNG: `toManifestIcon` declares `type: 'image/png'`, so any other format
88
88
  * would make the manifest lie about bytes the browser then refuses.
89
89
  *
90
- * `async` on purpose. A `Promise`-typed method that throws synchronously skips every
91
- * `.catch()` a caller wrote; an undecodable source or a named colour must reject instead.
90
+ * `await` inside, not a returned promise: core's own refusals (a named colour, a padding out of
91
+ * range) are thrown before the first `await` in it, and re-raising them here is what keeps every
92
+ * failure of this method a rejection rather than a synchronous throw past a caller's `.catch()`.
92
93
  */
93
94
  export class BuiltinImagePipeline implements ImagePipeline {
94
95
  async resize(source: Uint8Array, transform: ImageTransform): Promise<Uint8Array> {
95
- return transformImageBytes(source, {
96
+ return await transformImageBytes(source, {
96
97
  width: transform.size,
97
98
  height: transform.size,
98
99
  fit: 'contain',
package/src/index.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  /** Public API of `@ultimat3/pwa`. You never open `sw.js`; you call these. */
2
2
 
3
+ /**
4
+ * `PwaRenderMode` and `PwaOfflineStrategy` were this package's own NAMES for tier 0's vocabulary —
5
+ * the alias was the copy. `PwaRoute` takes both in its signature, so the canonical names are
6
+ * re-exported here; a consumer still needs one import, and now it names the real type.
7
+ */
8
+ export type { OfflineStrategy, RenderMode } from '@ultimat3/core';
3
9
  export type { BackgroundSyncOptions, RetryPolicy } from './background-sync';
4
10
  export {
5
11
  backgroundSyncSource,
@@ -111,8 +117,6 @@ export {
111
117
  export type { RouteRule, ServiceWorkerConfig, ServiceWorkerOutput } from './service-worker';
112
118
  export { assertScope, generateServiceWorker, routeRules } from './service-worker';
113
119
  export type {
114
- PwaOfflineStrategy,
115
- PwaRenderMode,
116
120
  PwaRoute,
117
121
  StrategyCache,
118
122
  StrategyEnv,
package/src/strategies.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * its bytes have to be, so the mapping is derived and the override is the exception.
5
5
  */
6
6
 
7
+ import type { OfflineStrategy, RenderMode } from '@ultimat3/core';
7
8
  import { PwaStrategyExhaustedError } from './errors';
8
9
 
9
10
  export type StrategyName =
@@ -19,9 +20,6 @@ export const STRATEGY_NAMES: readonly StrategyName[] = [
19
20
  'network-only',
20
21
  ];
21
22
 
22
- export type PwaRenderMode = 'static' | 'isr' | 'ssr' | 'stream' | 'spa';
23
- export type PwaOfflineStrategy = 'precache' | 'runtime' | 'network-only';
24
-
25
23
  /**
26
24
  * Structural view of `@ultimat3/render`'s `RouteDescriptor`. Tier-4 packages must not
27
25
  * import each other, so route data arrives as data and this is the shape it must have.
@@ -29,8 +27,8 @@ export type PwaOfflineStrategy = 'precache' | 'runtime' | 'network-only';
29
27
  export interface PwaRoute {
30
28
  readonly path: string;
31
29
  readonly surface: 'site' | 'app' | 'api';
32
- readonly mode: PwaRenderMode;
33
- readonly offline: PwaOfflineStrategy;
30
+ readonly mode: RenderMode;
31
+ readonly offline: OfflineStrategy;
34
32
  readonly dynamic?: boolean;
35
33
  /** Explicit per-route override; wins over the derived strategy. */
36
34
  readonly strategy?: StrategyName;
@@ -41,13 +39,24 @@ export interface PwaRoute {
41
39
  readonly dataUrl?: string;
42
40
  }
43
41
 
44
- /** Render mode → runtime strategy. The whole reason `sw.js` is generated, not written. */
45
- export const MODE_STRATEGY: Readonly<Record<PwaRenderMode, StrategyName>> = Object.freeze({
42
+ /**
43
+ * Render mode → runtime strategy. The whole reason `sw.js` is generated, not written.
44
+ *
45
+ * Two separate things make the closed set hold, and the table needed both. `Record<RenderMode, …>`
46
+ * over the TIER-0 union is the exhaustiveness check — this was keyed on a hand-copy, which is how
47
+ * `spa` went on mapping to `cache-first` after `spa` was deleted from the vocabulary: the one
48
+ * strategy that gives an `app/` route a SHARED cache entry, i.e. one member's authed HTML served
49
+ * to the next. And `Object.freeze<T>({…})` with an EXPLICIT type argument, never
50
+ * `const X: T = Object.freeze({…})` — the second form infers `T` from the literal and the
51
+ * annotation only checks assignability afterwards, so the literal's freshness is already gone and
52
+ * an EXTRA key compiles in silence. Named, the literal is contextually typed and a missing key
53
+ * AND an extra key are both build errors.
54
+ */
55
+ export const MODE_STRATEGY = Object.freeze<Record<RenderMode, StrategyName>>({
46
56
  static: 'cache-first',
47
57
  isr: 'stale-while-revalidate',
48
58
  ssr: 'network-first',
49
59
  stream: 'stale-while-revalidate',
50
- spa: 'cache-first',
51
60
  });
52
61
 
53
62
  export function strategyFor(route: PwaRoute): StrategyName {
@@ -139,12 +148,12 @@ export async function networkOnly(
139
148
  }
140
149
  }
141
150
 
142
- export const STRATEGY_FNS: Readonly<
151
+ export const STRATEGY_FNS = Object.freeze<
143
152
  Record<
144
153
  StrategyName,
145
154
  (request: Request, env: StrategyEnv, options: StrategyOptions) => Promise<Response>
146
155
  >
147
- > = Object.freeze({
156
+ >({
148
157
  'cache-first': cacheFirst,
149
158
  'network-first': networkFirst,
150
159
  'stale-while-revalidate': staleWhileRevalidate,
@@ -177,7 +186,7 @@ async function fallbackOrThrow(options: StrategyOptions): Promise<Response> {
177
186
  * service worker is a generated artifact with no bundler in the loop — the shapes are
178
187
  * identical on purpose and `strategies.test.ts` asserts both halves stay in step.
179
188
  */
180
- export const STRATEGY_SOURCE: Readonly<Record<StrategyName, string>> = Object.freeze({
189
+ export const STRATEGY_SOURCE = Object.freeze<Record<StrategyName, string>>({
181
190
  'cache-first': `async function cacheFirst(req,cn,fb){
182
191
  const c=await caches.open(cn);const hit=await c.match(req);if(hit)return hit;
183
192
  try{const r=await fetch(req);if(r.ok)await c.put(req,r.clone());return r}catch(e){if(fb)return fb();throw e}
@@ -199,7 +208,7 @@ export const STRATEGY_SOURCE: Readonly<Record<StrategyName, string>> = Object.fr
199
208
  }`,
200
209
  });
201
210
 
202
- export const STRATEGY_FN_NAMES: Readonly<Record<StrategyName, string>> = Object.freeze({
211
+ export const STRATEGY_FN_NAMES = Object.freeze<Record<StrategyName, string>>({
203
212
  'cache-first': 'cacheFirst',
204
213
  'network-first': 'networkFirst',
205
214
  'stale-while-revalidate': 'staleWhileRevalidate',