@czap/edge 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Eassa Ayoub <eassa@heyoub.dev>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @czap/edge
2
+
3
+ CDN-edge: Client Hints, tier detection, KV boundary cache, theme compilation.
4
+
5
+ ## Docs
6
+
7
+ - [Naming & vocabulary](../../docs/GLOSSARY.md) — LiteShip, CZAP, `@czap/*`
8
+
9
+ - [API reference](https://github.com/heyoub/LiteShip/tree/main/docs/api/edge/) — generated from source TSDoc
10
+ - [Architecture index](https://github.com/heyoub/LiteShip/blob/main/docs/ARCHITECTURE.md)
11
+ - [ADRs](https://github.com/heyoub/LiteShip/tree/main/docs/adr/)
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @czap/edge
17
+ ```
18
+
19
+ ## Part of [LiteShip](https://github.com/heyoub/LiteShip#readme)
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Client Hints header parsing for edge-side device capability detection.
3
+ *
4
+ * Converts HTTP Client Hints headers into the same `ExtendedDeviceCapabilities`
5
+ * structure that `@czap/detect` uses, enabling reuse of the pure tier mapping
6
+ * functions at the edge without browser APIs.
7
+ *
8
+ * @module
9
+ */
10
+ import type { ExtendedDeviceCapabilities } from '@czap/detect';
11
+ /**
12
+ * Plain-object header bag accepted by {@link ClientHints.parseClientHints}.
13
+ *
14
+ * All names are lowercased because Client Hints headers are always lowercase
15
+ * in spec. Values that are missing simply fall back to conservative
16
+ * defaults during parsing.
17
+ */
18
+ export interface ClientHintsHeaders {
19
+ /** `Sec-CH-UA-Platform` (e.g. `"macOS"`, `"Windows"`). */
20
+ readonly 'sec-ch-ua-platform'?: string;
21
+ /** `Sec-CH-Device-Memory` in GiB (one of the standard buckets). */
22
+ readonly 'sec-ch-device-memory'?: string;
23
+ /** `Sec-CH-DPR` — devicePixelRatio as a decimal string. */
24
+ readonly 'sec-ch-dpr'?: string;
25
+ /** `Sec-CH-Viewport-Width` in CSS pixels. */
26
+ readonly 'sec-ch-viewport-width'?: string;
27
+ /** `Sec-CH-Viewport-Height` in CSS pixels. */
28
+ readonly 'sec-ch-viewport-height'?: string;
29
+ /** `Sec-CH-Prefers-Reduced-Motion` (`reduce` / `no-preference`). */
30
+ readonly 'sec-ch-prefers-reduced-motion'?: string;
31
+ /** `Sec-CH-Prefers-Color-Scheme` (`light` / `dark`). */
32
+ readonly 'sec-ch-prefers-color-scheme'?: string;
33
+ /** `Sec-CH-UA-Mobile` as a structured boolean (`?1` / `?0`). */
34
+ readonly 'sec-ch-ua-mobile'?: string;
35
+ /** `Sec-CH-UA` — full user-agent brand list. */
36
+ readonly 'sec-ch-ua'?: string;
37
+ /** `Save-Data` (`on`). */
38
+ readonly 'save-data'?: string;
39
+ /** `Downlink` estimate in Mb/s. */
40
+ readonly downlink?: string;
41
+ /** `ECT` effective connection type. */
42
+ readonly ect?: string;
43
+ /** `RTT` round-trip-time estimate in ms. */
44
+ readonly rtt?: string;
45
+ /** `User-Agent` fallback for GPU-tier heuristics. */
46
+ readonly 'user-agent'?: string;
47
+ }
48
+ /**
49
+ * Parse Client Hints headers into an {@link ExtendedDeviceCapabilities} structure.
50
+ *
51
+ * For properties that cannot be determined from headers (GPU tier, WebGPU
52
+ * support, CPU cores), conservative defaults are used.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { ClientHints } from '@czap/edge';
57
+ *
58
+ * const caps = ClientHints.parseClientHints({
59
+ * 'sec-ch-device-memory': '8',
60
+ * 'sec-ch-dpr': '2',
61
+ * 'sec-ch-viewport-width': '1440',
62
+ * 'sec-ch-prefers-color-scheme': 'dark',
63
+ * 'sec-ch-ua-mobile': '?0',
64
+ * });
65
+ * console.log(caps.memory); // 8
66
+ * console.log(caps.devicePixelRatio); // 2
67
+ * console.log(caps.prefersColorScheme); // 'dark'
68
+ * ```
69
+ *
70
+ * @param headers - Client Hints headers (plain object or Web API Headers)
71
+ * @returns An {@link ExtendedDeviceCapabilities} structure
72
+ */
73
+ declare function parseClientHints(headers: ClientHintsHeaders | Headers): ExtendedDeviceCapabilities;
74
+ /**
75
+ * Generate the `Accept-CH` header value for requesting all useful Client Hints
76
+ * on subsequent requests.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import { ClientHints } from '@czap/edge';
81
+ *
82
+ * const response = new Response('OK', {
83
+ * headers: { 'Accept-CH': ClientHints.acceptCHHeader() },
84
+ * });
85
+ * ```
86
+ *
87
+ * @returns A comma-separated list of Client Hint header names
88
+ */
89
+ declare function acceptCHHeader(): string;
90
+ /**
91
+ * Generate the `Critical-CH` header value for hints needed on the very first
92
+ * request (triggers a browser retry if missing).
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { ClientHints } from '@czap/edge';
97
+ *
98
+ * const response = new Response('OK', {
99
+ * headers: {
100
+ * 'Accept-CH': ClientHints.acceptCHHeader(),
101
+ * 'Critical-CH': ClientHints.criticalCHHeader(),
102
+ * },
103
+ * });
104
+ * ```
105
+ *
106
+ * @returns A comma-separated list of critical Client Hint header names
107
+ */
108
+ declare function criticalCHHeader(): string;
109
+ /**
110
+ * Client Hints namespace.
111
+ *
112
+ * Parses HTTP Client Hints headers into the same
113
+ * {@link ExtendedDeviceCapabilities} structure used by `@czap/detect`,
114
+ * enabling server-side / edge-side tier mapping without browser APIs.
115
+ * Also generates the `Accept-CH` and `Critical-CH` response headers needed
116
+ * to request hints from the browser.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * import { ClientHints } from '@czap/edge';
121
+ *
122
+ * // In an edge handler:
123
+ * const caps = ClientHints.parseClientHints(request.headers);
124
+ * const response = new Response(body, {
125
+ * headers: {
126
+ * 'Accept-CH': ClientHints.acceptCHHeader(),
127
+ * 'Critical-CH': ClientHints.criticalCHHeader(),
128
+ * },
129
+ * });
130
+ * ```
131
+ */
132
+ export declare const ClientHints: {
133
+ /** Parse Client Hints headers into {@link ExtendedDeviceCapabilities}. */
134
+ readonly parseClientHints: typeof parseClientHints;
135
+ /** Produce the `Accept-CH` response header value listing all useful hints. */
136
+ readonly acceptCHHeader: typeof acceptCHHeader;
137
+ /** Produce the `Critical-CH` response header value listing boot-required hints. */
138
+ readonly criticalCHHeader: typeof criticalCHHeader;
139
+ };
140
+ export declare namespace ClientHints {
141
+ /** Alias for {@link ClientHintsHeaders} — plain-object header bag shape. */
142
+ type Headers = ClientHintsHeaders;
143
+ }
144
+ export {};
145
+ //# sourceMappingURL=client-hints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-hints.d.ts","sourceRoot":"","sources":["../src/client-hints.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAW,MAAM,cAAc,CAAC;AAMxE;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,8CAA8C;IAC9C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAClD,wDAAwD;IACxD,QAAQ,CAAC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAChD,gEAAgE;IAChE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,gDAAgD;IAChD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAmHD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,GAAG,0BAA0B,CA+D3F;AAED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,cAAc,IAAI,MAAM,CAEhC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,iBAAS,gBAAgB,IAAI,MAAM,CAElC;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW;IACtB,0EAA0E;;IAE1E,8EAA8E;;IAE9E,mFAAmF;;CAE3E,CAAC;AAEX,MAAM,CAAC,OAAO,WAAW,WAAW,CAAC;IACnC,4EAA4E;IAC5E,KAAY,OAAO,GAAG,kBAAkB,CAAC;CAC1C"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Client Hints header parsing for edge-side device capability detection.
3
+ *
4
+ * Converts HTTP Client Hints headers into the same `ExtendedDeviceCapabilities`
5
+ * structure that `@czap/detect` uses, enabling reuse of the pure tier mapping
6
+ * functions at the edge without browser APIs.
7
+ *
8
+ * @module
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // Internal helpers
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Type guard for Web API Headers objects (fetch Headers).
15
+ * Checks for the `get` method that distinguishes Headers from plain objects.
16
+ */
17
+ function isWebHeaders(value) {
18
+ return typeof value.get === 'function';
19
+ }
20
+ /**
21
+ * Normalise a Headers-like input (Web API Headers or plain object) into
22
+ * a case-insensitive getter function.
23
+ */
24
+ function headerGetter(headers) {
25
+ if (isWebHeaders(headers)) {
26
+ return (name) => headers.get(name) ?? undefined;
27
+ }
28
+ // Client Hints headers are always lowercase in spec, but normalise anyway
29
+ const lower = {};
30
+ for (const [k, v] of Object.entries(headers)) {
31
+ if (v !== undefined)
32
+ lower[k.toLowerCase()] = v;
33
+ }
34
+ return (name) => lower[name.toLowerCase()];
35
+ }
36
+ /**
37
+ * Parse a numeric header, returning undefined for missing / malformed values.
38
+ */
39
+ function parseFloat_(get, name) {
40
+ const raw = get(name);
41
+ if (raw === undefined || raw === '')
42
+ return undefined;
43
+ const n = Number.parseFloat(raw);
44
+ return Number.isFinite(n) ? n : undefined;
45
+ }
46
+ /**
47
+ * Clamp device memory to the set of valid values browsers actually report.
48
+ */
49
+ function clampMemory(raw) {
50
+ const buckets = [0.25, 0.5, 1, 2, 4, 8];
51
+ let closest = buckets[0];
52
+ for (const b of buckets) {
53
+ if (Math.abs(b - raw) < Math.abs(closest - raw))
54
+ closest = b;
55
+ }
56
+ return closest;
57
+ }
58
+ /**
59
+ * Crude GPU tier heuristic from User-Agent string.
60
+ * Without WebGL renderer info we can only make rough guesses.
61
+ */
62
+ function gpuTierFromUA(ua) {
63
+ if (!ua)
64
+ return 1;
65
+ const lower = ua.toLowerCase();
66
+ // Very low-end indicators
67
+ if (/kaios|nokia|feature/i.test(lower))
68
+ return 0;
69
+ // High-end mobile
70
+ if (/iphone\s*1[4-9]|iphone\s*[2-9]\d/i.test(lower))
71
+ return 2;
72
+ if (/sm-s9|sm-s2[4-9]|pixel\s*[8-9]/i.test(lower))
73
+ return 2;
74
+ // Desktop with common high-end hints
75
+ if (/windows nt.*win64|macintosh.*mac os x 1[4-9]/i.test(lower))
76
+ return 2;
77
+ // Default to low-mid -- conservative
78
+ return 1;
79
+ }
80
+ /**
81
+ * Map the ECT (effective connection type) string to a normalised form.
82
+ */
83
+ function normaliseECT(ect) {
84
+ if (!ect)
85
+ return '4g';
86
+ const lower = ect.toLowerCase().trim();
87
+ if (['slow-2g', '2g', '3g', '4g'].includes(lower))
88
+ return lower;
89
+ return '4g';
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Accept-CH / Critical-CH header values
93
+ // ---------------------------------------------------------------------------
94
+ const ALL_HINTS = [
95
+ 'Sec-CH-Device-Memory',
96
+ 'Sec-CH-DPR',
97
+ 'Sec-CH-Viewport-Width',
98
+ 'Sec-CH-Viewport-Height',
99
+ 'Sec-CH-Prefers-Reduced-Motion',
100
+ 'Sec-CH-Prefers-Color-Scheme',
101
+ 'Sec-CH-UA-Mobile',
102
+ 'Sec-CH-UA',
103
+ 'Sec-CH-UA-Platform',
104
+ 'Save-Data',
105
+ 'Downlink',
106
+ 'ECT',
107
+ 'RTT',
108
+ ];
109
+ const CRITICAL_HINTS = [
110
+ 'Sec-CH-Prefers-Reduced-Motion',
111
+ 'Sec-CH-Prefers-Color-Scheme',
112
+ 'Sec-CH-UA-Mobile',
113
+ 'Sec-CH-Device-Memory',
114
+ ];
115
+ // ---------------------------------------------------------------------------
116
+ // Public API -- namespace object pattern
117
+ // ---------------------------------------------------------------------------
118
+ /**
119
+ * Parse Client Hints headers into an {@link ExtendedDeviceCapabilities} structure.
120
+ *
121
+ * For properties that cannot be determined from headers (GPU tier, WebGPU
122
+ * support, CPU cores), conservative defaults are used.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * import { ClientHints } from '@czap/edge';
127
+ *
128
+ * const caps = ClientHints.parseClientHints({
129
+ * 'sec-ch-device-memory': '8',
130
+ * 'sec-ch-dpr': '2',
131
+ * 'sec-ch-viewport-width': '1440',
132
+ * 'sec-ch-prefers-color-scheme': 'dark',
133
+ * 'sec-ch-ua-mobile': '?0',
134
+ * });
135
+ * console.log(caps.memory); // 8
136
+ * console.log(caps.devicePixelRatio); // 2
137
+ * console.log(caps.prefersColorScheme); // 'dark'
138
+ * ```
139
+ *
140
+ * @param headers - Client Hints headers (plain object or Web API Headers)
141
+ * @returns An {@link ExtendedDeviceCapabilities} structure
142
+ */
143
+ function parseClientHints(headers) {
144
+ const get = headerGetter(headers);
145
+ // Memory
146
+ const rawMemory = parseFloat_(get, 'sec-ch-device-memory');
147
+ const memory = rawMemory !== undefined ? clampMemory(rawMemory) : 4;
148
+ // DPR
149
+ const dpr = parseFloat_(get, 'sec-ch-dpr') ?? 1;
150
+ // Viewport
151
+ const viewportWidth = parseFloat_(get, 'sec-ch-viewport-width') ?? 1920;
152
+ const viewportHeight = parseFloat_(get, 'sec-ch-viewport-height') ?? 1080;
153
+ // Preferences
154
+ const reducedMotionRaw = get('sec-ch-prefers-reduced-motion');
155
+ const prefersReducedMotion = reducedMotionRaw === 'reduce' || reducedMotionRaw === '"reduce"';
156
+ const colorSchemeRaw = get('sec-ch-prefers-color-scheme');
157
+ const prefersColorScheme = colorSchemeRaw === 'dark' || colorSchemeRaw === '"dark"' ? 'dark' : 'light';
158
+ // Touch (mobile hint)
159
+ const mobileRaw = get('sec-ch-ua-mobile');
160
+ const touchPrimary = mobileRaw === '?1' || mobileRaw === 'true';
161
+ // Save-Data
162
+ const saveDataRaw = get('save-data');
163
+ const saveData = saveDataRaw === 'on' || saveDataRaw === '1' || saveDataRaw === 'true';
164
+ // Network
165
+ const downlink = parseFloat_(get, 'downlink') ?? 10;
166
+ const ect = normaliseECT(get('ect'));
167
+ // GPU tier heuristic from UA
168
+ const gpu = gpuTierFromUA(get('user-agent'));
169
+ return {
170
+ // Base DeviceCapabilities
171
+ gpu,
172
+ cores: 4, // Conservative default -- not available via Client Hints
173
+ memory,
174
+ webgpu: false, // Cannot determine from headers
175
+ touchPrimary,
176
+ prefersReducedMotion,
177
+ prefersColorScheme,
178
+ viewportWidth,
179
+ viewportHeight,
180
+ devicePixelRatio: dpr,
181
+ connection: {
182
+ effectiveType: ect,
183
+ downlink,
184
+ saveData,
185
+ },
186
+ // Extended properties -- conservative defaults for edge
187
+ prefersContrast: 'no-preference',
188
+ forcedColors: false,
189
+ prefersReducedTransparency: false,
190
+ dynamicRange: 'standard',
191
+ colorGamut: 'srgb',
192
+ updateRate: 'fast',
193
+ };
194
+ }
195
+ /**
196
+ * Generate the `Accept-CH` header value for requesting all useful Client Hints
197
+ * on subsequent requests.
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * import { ClientHints } from '@czap/edge';
202
+ *
203
+ * const response = new Response('OK', {
204
+ * headers: { 'Accept-CH': ClientHints.acceptCHHeader() },
205
+ * });
206
+ * ```
207
+ *
208
+ * @returns A comma-separated list of Client Hint header names
209
+ */
210
+ function acceptCHHeader() {
211
+ return ALL_HINTS.join(', ');
212
+ }
213
+ /**
214
+ * Generate the `Critical-CH` header value for hints needed on the very first
215
+ * request (triggers a browser retry if missing).
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * import { ClientHints } from '@czap/edge';
220
+ *
221
+ * const response = new Response('OK', {
222
+ * headers: {
223
+ * 'Accept-CH': ClientHints.acceptCHHeader(),
224
+ * 'Critical-CH': ClientHints.criticalCHHeader(),
225
+ * },
226
+ * });
227
+ * ```
228
+ *
229
+ * @returns A comma-separated list of critical Client Hint header names
230
+ */
231
+ function criticalCHHeader() {
232
+ return CRITICAL_HINTS.join(', ');
233
+ }
234
+ // ---------------------------------------------------------------------------
235
+ // Namespace export
236
+ // ---------------------------------------------------------------------------
237
+ /**
238
+ * Client Hints namespace.
239
+ *
240
+ * Parses HTTP Client Hints headers into the same
241
+ * {@link ExtendedDeviceCapabilities} structure used by `@czap/detect`,
242
+ * enabling server-side / edge-side tier mapping without browser APIs.
243
+ * Also generates the `Accept-CH` and `Critical-CH` response headers needed
244
+ * to request hints from the browser.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * import { ClientHints } from '@czap/edge';
249
+ *
250
+ * // In an edge handler:
251
+ * const caps = ClientHints.parseClientHints(request.headers);
252
+ * const response = new Response(body, {
253
+ * headers: {
254
+ * 'Accept-CH': ClientHints.acceptCHHeader(),
255
+ * 'Critical-CH': ClientHints.criticalCHHeader(),
256
+ * },
257
+ * });
258
+ * ```
259
+ */
260
+ export const ClientHints = {
261
+ /** Parse Client Hints headers into {@link ExtendedDeviceCapabilities}. */
262
+ parseClientHints,
263
+ /** Produce the `Accept-CH` response header value listing all useful hints. */
264
+ acceptCHHeader,
265
+ /** Produce the `Critical-CH` response header value listing boot-required hints. */
266
+ criticalCHHeader,
267
+ };
268
+ //# sourceMappingURL=client-hints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-hints.js","sourceRoot":"","sources":["../src/client-hints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Edge-side tier detection -- wraps the pure tier mapping functions from
3
+ * `@czap/detect` for use with HTTP Client Hints headers at the edge.
4
+ *
5
+ * @module
6
+ */
7
+ import type { CapLevel } from '@czap/core';
8
+ import type { DesignTier, MotionTier } from '@czap/detect';
9
+ import type { ClientHintsHeaders } from './client-hints.js';
10
+ /**
11
+ * Outcome of an edge-side tier detection sweep.
12
+ *
13
+ * All three fields use the same branded tier types as the client runtime,
14
+ * so downstream boundary evaluation and output gating reuse the exact
15
+ * code paths from `@czap/detect`.
16
+ */
17
+ export interface EdgeTierResult {
18
+ /** Highest {@link CapLevel} the device qualifies for. */
19
+ readonly capLevel: CapLevel;
20
+ /** Motion complexity tier permitted for this device. */
21
+ readonly motionTier: MotionTier;
22
+ /** Visual fidelity tier permitted for this device. */
23
+ readonly designTier: DesignTier;
24
+ }
25
+ /**
26
+ * Detect capability tiers from HTTP headers using Client Hints parsing
27
+ * and the same pure tier mapping functions used on the client.
28
+ */
29
+ declare function detectTier(headers: Headers | ClientHintsHeaders): EdgeTierResult;
30
+ /**
31
+ * Generate HTML data attribute string for injection into the `<html>` element.
32
+ *
33
+ * @example
34
+ * ```
35
+ * tierDataAttributes(result)
36
+ * // => 'data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced"'
37
+ * ```
38
+ */
39
+ declare function tierDataAttributes(result: EdgeTierResult): string;
40
+ /**
41
+ * Edge tier detection namespace.
42
+ *
43
+ * Pairs {@link ClientHints.parseClientHints} with the pure tier-mapping
44
+ * functions from `@czap/detect` so the edge and the browser produce the
45
+ * same `capLevel`/`motionTier`/`designTier` triple for a given device.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * import { EdgeTier } from '@czap/edge';
50
+ *
51
+ * const result = EdgeTier.detectTier(request.headers);
52
+ * const html = `<html ${EdgeTier.tierDataAttributes(result)}>`;
53
+ * // `<html data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced">`
54
+ * ```
55
+ */
56
+ export declare const EdgeTier: {
57
+ /** Detect {@link EdgeTierResult} from a `Headers`-like bag. */
58
+ readonly detectTier: typeof detectTier;
59
+ /** Render an `EdgeTierResult` into `data-czap-*` attributes for the root HTML element. */
60
+ readonly tierDataAttributes: typeof tierDataAttributes;
61
+ };
62
+ export declare namespace EdgeTier {
63
+ /** Alias for {@link EdgeTierResult}. */
64
+ type Result = EdgeTierResult;
65
+ }
66
+ export {};
67
+ //# sourceMappingURL=edge-tier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-tier.d.ts","sourceRoot":"","sources":["../src/edge-tier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM5D;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAMD;;;GAGG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,GAAG,cAAc,CAMzE;AAED;;;;;;;;GAQG;AACH,iBAAS,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE1D;AAMD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ;IACnB,+DAA+D;;IAE/D,0FAA0F;;CAElF,CAAC;AAEX,MAAM,CAAC,OAAO,WAAW,QAAQ,CAAC;IAChC,wCAAwC;IACxC,KAAY,MAAM,GAAG,cAAc,CAAC;CACrC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Edge-side tier detection -- wraps the pure tier mapping functions from
3
+ * `@czap/detect` for use with HTTP Client Hints headers at the edge.
4
+ *
5
+ * @module
6
+ */
7
+ import { tierFromCapabilities, motionTierFromCapabilities, designTierFromCapabilities } from '@czap/detect';
8
+ import { ClientHints } from './client-hints.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Public API
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Detect capability tiers from HTTP headers using Client Hints parsing
14
+ * and the same pure tier mapping functions used on the client.
15
+ */
16
+ function detectTier(headers) {
17
+ const caps = ClientHints.parseClientHints(headers);
18
+ const capLevel = tierFromCapabilities(caps);
19
+ const motionTier = motionTierFromCapabilities(caps);
20
+ const designTier = designTierFromCapabilities(caps);
21
+ return { capLevel, motionTier, designTier };
22
+ }
23
+ /**
24
+ * Generate HTML data attribute string for injection into the `<html>` element.
25
+ *
26
+ * @example
27
+ * ```
28
+ * tierDataAttributes(result)
29
+ * // => 'data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced"'
30
+ * ```
31
+ */
32
+ function tierDataAttributes(result) {
33
+ return `data-czap-cap="${result.capLevel}" data-czap-motion="${result.motionTier}" data-czap-design="${result.designTier}"`;
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Namespace export
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Edge tier detection namespace.
40
+ *
41
+ * Pairs {@link ClientHints.parseClientHints} with the pure tier-mapping
42
+ * functions from `@czap/detect` so the edge and the browser produce the
43
+ * same `capLevel`/`motionTier`/`designTier` triple for a given device.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { EdgeTier } from '@czap/edge';
48
+ *
49
+ * const result = EdgeTier.detectTier(request.headers);
50
+ * const html = `<html ${EdgeTier.tierDataAttributes(result)}>`;
51
+ * // `<html data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced">`
52
+ * ```
53
+ */
54
+ export const EdgeTier = {
55
+ /** Detect {@link EdgeTierResult} from a `Headers`-like bag. */
56
+ detectTier,
57
+ /** Render an `EdgeTierResult` into `data-czap-*` attributes for the root HTML element. */
58
+ tierDataAttributes,
59
+ };
60
+ //# sourceMappingURL=edge-tier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-tier.js","sourceRoot":"","sources":["../src/edge-tier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE5G,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuBhD,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;GAGG;AACH,SAAS,UAAU,CAAC,OAAqC;IACvD,MAAM,IAAI,GAAG,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,MAAsB;IAChD,OAAO,kBAAkB,MAAM,CAAC,QAAQ,uBAAuB,MAAM,CAAC,UAAU,uBAAuB,MAAM,CAAC,UAAU,GAAG,CAAC;AAC9H,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,+DAA+D;IAC/D,UAAU;IACV,0FAA0F;IAC1F,kBAAkB;CACV,CAAC"}