@czap/astro 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 +21 -0
- package/README.md +19 -0
- package/dist/Satellite.d.ts +42 -0
- package/dist/Satellite.d.ts.map +1 -0
- package/dist/Satellite.js +55 -0
- package/dist/Satellite.js.map +1 -0
- package/dist/client-directives/gpu.d.ts +3 -0
- package/dist/client-directives/gpu.d.ts.map +1 -0
- package/dist/client-directives/gpu.js +5 -0
- package/dist/client-directives/gpu.js.map +1 -0
- package/dist/client-directives/llm.d.ts +3 -0
- package/dist/client-directives/llm.d.ts.map +1 -0
- package/dist/client-directives/llm.js +5 -0
- package/dist/client-directives/llm.js.map +1 -0
- package/dist/client-directives/satellite.d.ts +3 -0
- package/dist/client-directives/satellite.d.ts.map +1 -0
- package/dist/client-directives/satellite.js +5 -0
- package/dist/client-directives/satellite.js.map +1 -0
- package/dist/client-directives/stream.d.ts +3 -0
- package/dist/client-directives/stream.d.ts.map +1 -0
- package/dist/client-directives/stream.js +5 -0
- package/dist/client-directives/stream.js.map +1 -0
- package/dist/client-directives/wasm.d.ts +3 -0
- package/dist/client-directives/wasm.d.ts.map +1 -0
- package/dist/client-directives/wasm.js +6 -0
- package/dist/client-directives/wasm.js.map +1 -0
- package/dist/client-directives/worker.d.ts +3 -0
- package/dist/client-directives/worker.d.ts.map +1 -0
- package/dist/client-directives/worker.js +5 -0
- package/dist/client-directives/worker.js.map +1 -0
- package/dist/detect-upgrade.d.ts +16 -0
- package/dist/detect-upgrade.d.ts.map +1 -0
- package/dist/detect-upgrade.js +105 -0
- package/dist/detect-upgrade.js.map +1 -0
- package/dist/headers.d.ts +45 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +64 -0
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +76 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +240 -0
- package/dist/integration.js.map +1 -0
- package/dist/middleware.d.ts +69 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +75 -0
- package/dist/middleware.js.map +1 -0
- package/dist/quantize.d.ts +50 -0
- package/dist/quantize.d.ts.map +1 -0
- package/dist/quantize.js +122 -0
- package/dist/quantize.js.map +1 -0
- package/dist/runtime/boundary.d.ts +123 -0
- package/dist/runtime/boundary.d.ts.map +1 -0
- package/dist/runtime/boundary.js +164 -0
- package/dist/runtime/boundary.js.map +1 -0
- package/dist/runtime/globals.d.ts +32 -0
- package/dist/runtime/globals.d.ts.map +1 -0
- package/dist/runtime/globals.js +45 -0
- package/dist/runtime/globals.js.map +1 -0
- package/dist/runtime/gpu.d.ts +15 -0
- package/dist/runtime/gpu.d.ts.map +1 -0
- package/dist/runtime/gpu.js +266 -0
- package/dist/runtime/gpu.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/llm-receipt-tracker.d.ts +21 -0
- package/dist/runtime/llm-receipt-tracker.d.ts.map +1 -0
- package/dist/runtime/llm-receipt-tracker.js +60 -0
- package/dist/runtime/llm-receipt-tracker.js.map +1 -0
- package/dist/runtime/llm-render-pipeline.d.ts +89 -0
- package/dist/runtime/llm-render-pipeline.d.ts.map +1 -0
- package/dist/runtime/llm-render-pipeline.js +241 -0
- package/dist/runtime/llm-render-pipeline.js.map +1 -0
- package/dist/runtime/llm-session.d.ts +126 -0
- package/dist/runtime/llm-session.d.ts.map +1 -0
- package/dist/runtime/llm-session.js +385 -0
- package/dist/runtime/llm-session.js.map +1 -0
- package/dist/runtime/llm.d.ts +16 -0
- package/dist/runtime/llm.d.ts.map +1 -0
- package/dist/runtime/llm.js +273 -0
- package/dist/runtime/llm.js.map +1 -0
- package/dist/runtime/policy.d.ts +100 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +147 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/receipt-chain.d.ts +22 -0
- package/dist/runtime/receipt-chain.d.ts.map +1 -0
- package/dist/runtime/receipt-chain.js +80 -0
- package/dist/runtime/receipt-chain.js.map +1 -0
- package/dist/runtime/runtime-session.d.ts +34 -0
- package/dist/runtime/runtime-session.d.ts.map +1 -0
- package/dist/runtime/runtime-session.js +102 -0
- package/dist/runtime/runtime-session.js.map +1 -0
- package/dist/runtime/satellite.d.ts +13 -0
- package/dist/runtime/satellite.d.ts.map +1 -0
- package/dist/runtime/satellite.js +59 -0
- package/dist/runtime/satellite.js.map +1 -0
- package/dist/runtime/slots.d.ts +34 -0
- package/dist/runtime/slots.d.ts.map +1 -0
- package/dist/runtime/slots.js +108 -0
- package/dist/runtime/slots.js.map +1 -0
- package/dist/runtime/stream-session.d.ts +47 -0
- package/dist/runtime/stream-session.d.ts.map +1 -0
- package/dist/runtime/stream-session.js +82 -0
- package/dist/runtime/stream-session.js.map +1 -0
- package/dist/runtime/stream.d.ts +9 -0
- package/dist/runtime/stream.d.ts.map +1 -0
- package/dist/runtime/stream.js +308 -0
- package/dist/runtime/stream.js.map +1 -0
- package/dist/runtime/url-policy.d.ts +28 -0
- package/dist/runtime/url-policy.d.ts.map +1 -0
- package/dist/runtime/url-policy.js +87 -0
- package/dist/runtime/url-policy.js.map +1 -0
- package/dist/runtime/wasm.d.ts +20 -0
- package/dist/runtime/wasm.d.ts.map +1 -0
- package/dist/runtime/wasm.js +70 -0
- package/dist/runtime/wasm.js.map +1 -0
- package/dist/runtime/worker.d.ts +11 -0
- package/dist/runtime/worker.d.ts.map +1 -0
- package/dist/runtime/worker.js +249 -0
- package/dist/runtime/worker.js.map +1 -0
- package/package.json +106 -0
- package/src/Satellite.astro +39 -0
- package/src/Satellite.ts +84 -0
- package/src/client-directives/gpu.ts +5 -0
- package/src/client-directives/llm.ts +5 -0
- package/src/client-directives/satellite.ts +5 -0
- package/src/client-directives/stream.ts +5 -0
- package/src/client-directives/wasm.ts +6 -0
- package/src/client-directives/worker.ts +5 -0
- package/src/detect-upgrade.ts +105 -0
- package/src/headers.ts +84 -0
- package/src/index.ts +30 -0
- package/src/integration.ts +309 -0
- package/src/middleware.ts +133 -0
- package/src/quantize.ts +173 -0
- package/src/runtime/boundary.ts +263 -0
- package/src/runtime/globals.ts +57 -0
- package/src/runtime/gpu.ts +291 -0
- package/src/runtime/index.ts +12 -0
- package/src/runtime/llm-receipt-tracker.ts +88 -0
- package/src/runtime/llm-render-pipeline.ts +366 -0
- package/src/runtime/llm-session.ts +548 -0
- package/src/runtime/llm.ts +344 -0
- package/src/runtime/policy.ts +229 -0
- package/src/runtime/receipt-chain.ts +106 -0
- package/src/runtime/runtime-session.ts +139 -0
- package/src/runtime/satellite.ts +80 -0
- package/src/runtime/slots.ts +136 -0
- package/src/runtime/stream-session.ts +125 -0
- package/src/runtime/stream.ts +407 -0
- package/src/runtime/url-policy.ts +107 -0
- package/src/runtime/wasm.ts +85 -0
- package/src/runtime/worker.ts +307 -0
package/src/headers.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP header helpers shipped by `@czap/astro`.
|
|
3
|
+
*
|
|
4
|
+
* Encodes the two concerns the integration cares about: asking browsers
|
|
5
|
+
* for the Client Hints czap uses (tier detection), and shipping the
|
|
6
|
+
* COOP/COEP pair required by `SharedArrayBuffer`-backed workers.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default `Accept-CH` / `Critical-CH` response headers czap requests
|
|
13
|
+
* so the browser sends viewport width, device memory, motion
|
|
14
|
+
* preference, and DPR on the next navigation.
|
|
15
|
+
*/
|
|
16
|
+
export const CLIENT_HINTS_HEADERS: Record<string, string> = {
|
|
17
|
+
'Accept-CH': 'Sec-CH-Viewport-Width, Sec-CH-Device-Memory, Sec-CH-Prefers-Reduced-Motion, Sec-CH-DPR',
|
|
18
|
+
'Critical-CH': 'Sec-CH-Viewport-Width',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* COOP/COEP header pair required for `SharedArrayBuffer` (used by
|
|
23
|
+
* `@czap/worker`'s SPSC ring). Applied only when the integration is
|
|
24
|
+
* configured with `workers: { enabled: true }`.
|
|
25
|
+
*/
|
|
26
|
+
export const CROSS_ORIGIN_HEADERS: Record<string, string> = {
|
|
27
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
28
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the `[header, value]` entries czap wants to emit for a given
|
|
33
|
+
* feature toggle set. Used by dev-server middleware and edge adapters
|
|
34
|
+
* that prefer tuple iteration over the `Headers` API.
|
|
35
|
+
*/
|
|
36
|
+
export function getCzapHeaderEntries(options: {
|
|
37
|
+
readonly detectEnabled: boolean;
|
|
38
|
+
readonly workersEnabled: boolean;
|
|
39
|
+
readonly acceptCH?: string;
|
|
40
|
+
readonly criticalCH?: string;
|
|
41
|
+
}): Array<readonly [string, string]> {
|
|
42
|
+
const entries: Array<readonly [string, string]> = [];
|
|
43
|
+
|
|
44
|
+
if (options.detectEnabled) {
|
|
45
|
+
const acceptCH = options.acceptCH ?? CLIENT_HINTS_HEADERS['Accept-CH'];
|
|
46
|
+
const criticalCH = options.criticalCH ?? CLIENT_HINTS_HEADERS['Critical-CH'];
|
|
47
|
+
if (acceptCH) {
|
|
48
|
+
entries.push(['Accept-CH', acceptCH]);
|
|
49
|
+
}
|
|
50
|
+
if (criticalCH) {
|
|
51
|
+
entries.push(['Critical-CH', criticalCH]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options.workersEnabled) {
|
|
56
|
+
for (const [header, value] of Object.entries(CROSS_ORIGIN_HEADERS)) {
|
|
57
|
+
entries.push([header, value]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return entries;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Apply the czap header set to an existing {@link Headers} bag and
|
|
66
|
+
* return it (for chaining). Convenience wrapper over
|
|
67
|
+
* {@link getCzapHeaderEntries} for middleware that already has a
|
|
68
|
+
* `Headers` object in hand.
|
|
69
|
+
*/
|
|
70
|
+
export function applyCzapHeaders(
|
|
71
|
+
headers: Headers,
|
|
72
|
+
options: {
|
|
73
|
+
readonly detectEnabled: boolean;
|
|
74
|
+
readonly workersEnabled: boolean;
|
|
75
|
+
readonly acceptCH?: string;
|
|
76
|
+
readonly criticalCH?: string;
|
|
77
|
+
},
|
|
78
|
+
): Headers {
|
|
79
|
+
for (const [header, value] of getCzapHeaderEntries(options)) {
|
|
80
|
+
headers.set(header, value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return headers;
|
|
84
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@czap/astro` — **LiteShip** on Astro 6: constraint-shaped adaptive
|
|
3
|
+
* projection hosted as islands and directives.
|
|
4
|
+
*
|
|
5
|
+
* Provides the Astro `Integration` that registers `@czap/vite`,
|
|
6
|
+
* injects client tier detection, **rigs** the `client:satellite` directive,
|
|
7
|
+
* and exposes `Satellite` for shells with server-resolved bearings.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // astro.config.mjs
|
|
12
|
+
* import { defineConfig } from 'astro/config';
|
|
13
|
+
* import { integration as czap } from '@czap/astro';
|
|
14
|
+
*
|
|
15
|
+
* const config = defineConfig({
|
|
16
|
+
* integrations: [czap({ themes: ['./themes/default.ts'] })],
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type { IntegrationConfig } from './integration.js';
|
|
24
|
+
export { integration } from './integration.js';
|
|
25
|
+
export type { ServerIslandContext, QuantizeProps } from './quantize.js';
|
|
26
|
+
export { resolveInitialState } from './quantize.js';
|
|
27
|
+
export { satelliteAttrs, resolveInitialStateFallback } from './Satellite.js';
|
|
28
|
+
export type { SatelliteProps } from './Satellite.js';
|
|
29
|
+
export { czapMiddleware } from './middleware.js';
|
|
30
|
+
export type { CzapLocals, CzapMiddlewareConfig } from './middleware.js';
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro 6 `AstroIntegration` for czap.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `@czap/vite` plugin, injects the detect/boot scripts,
|
|
5
|
+
* registers every client directive (`client:satellite`,
|
|
6
|
+
* `client:stream`, `client:llm`, `client:worker`, `client:gpu`,
|
|
7
|
+
* `client:wasm`) that the host opts into, and turns on Astro's
|
|
8
|
+
* `serverIslands` experimental flag when requested.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AstroIntegration } from 'astro';
|
|
14
|
+
import { plugin } from '@czap/vite';
|
|
15
|
+
import type { PluginConfig } from '@czap/vite';
|
|
16
|
+
import { DETECT_UPGRADE_SCRIPT } from './detect-upgrade.js';
|
|
17
|
+
import { getCzapHeaderEntries } from './headers.js';
|
|
18
|
+
import type { RuntimeEndpointPolicy } from '@czap/web';
|
|
19
|
+
import {
|
|
20
|
+
normalizeRuntimeSecurityPolicy,
|
|
21
|
+
type RuntimeHtmlPolicy,
|
|
22
|
+
type RuntimeSecurityPolicy,
|
|
23
|
+
} from './runtime/policy.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Types
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Options passed to {@link integration} from `astro.config.mjs`. Every
|
|
31
|
+
* field is optional; omitted features fall back to conservative
|
|
32
|
+
* defaults (detect enabled, stream/llm/gpu enabled, workers/wasm/server
|
|
33
|
+
* islands opt-in).
|
|
34
|
+
*/
|
|
35
|
+
export interface IntegrationConfig {
|
|
36
|
+
/** Overrides passed through to `@czap/vite`'s plugin. */
|
|
37
|
+
readonly vite?: PluginConfig;
|
|
38
|
+
/** Enable the inline detect script (default `true`). */
|
|
39
|
+
readonly detect?: boolean;
|
|
40
|
+
/** Turn on Astro's experimental server-islands flag (default `false`). */
|
|
41
|
+
readonly serverIslands?: boolean;
|
|
42
|
+
/** WASM runtime configuration. */
|
|
43
|
+
readonly wasm?: { readonly enabled?: boolean; readonly path?: string };
|
|
44
|
+
/** GPU runtime configuration. */
|
|
45
|
+
readonly gpu?: { readonly enabled?: boolean; readonly preferWebGPU?: boolean };
|
|
46
|
+
/** Off-thread worker runtime configuration. */
|
|
47
|
+
readonly workers?: { readonly enabled?: boolean };
|
|
48
|
+
/** SSE streaming runtime configuration. */
|
|
49
|
+
readonly stream?: { readonly enabled?: boolean };
|
|
50
|
+
/** LLM streaming runtime configuration. */
|
|
51
|
+
readonly llm?: { readonly enabled?: boolean };
|
|
52
|
+
/** Security policies applied to runtime fetch/HTML boundaries. */
|
|
53
|
+
readonly security?: {
|
|
54
|
+
readonly endpointPolicy?: RuntimeEndpointPolicy;
|
|
55
|
+
readonly htmlPolicy?: RuntimeHtmlPolicy;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Detect Script
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Inline script that runs device detection on page load and stores
|
|
65
|
+
* the result as CSS custom properties on the <html> element and
|
|
66
|
+
* as a global for runtime access.
|
|
67
|
+
*/
|
|
68
|
+
const DETECT_INLINE_SCRIPT = `
|
|
69
|
+
(function(){
|
|
70
|
+
function writeDetectState(next) {
|
|
71
|
+
var safe = Object.freeze(Object.assign({}, next));
|
|
72
|
+
try {
|
|
73
|
+
Object.defineProperty(window, '__CZAP_DETECT__', {
|
|
74
|
+
value: safe,
|
|
75
|
+
configurable: true,
|
|
76
|
+
enumerable: false,
|
|
77
|
+
writable: false
|
|
78
|
+
});
|
|
79
|
+
} catch (_) {
|
|
80
|
+
try {
|
|
81
|
+
window.__CZAP_DETECT__ = safe;
|
|
82
|
+
} catch (_) {}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
var h = document.documentElement;
|
|
88
|
+
var w = window.innerWidth || 0;
|
|
89
|
+
var cores = navigator.hardwareConcurrency || 2;
|
|
90
|
+
var mem = navigator.deviceMemory || 4;
|
|
91
|
+
var touch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
|
|
92
|
+
var motion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
93
|
+
var dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
94
|
+
var dpr = window.devicePixelRatio || 1;
|
|
95
|
+
|
|
96
|
+
h.style.setProperty('--czap-vw', w + 'px');
|
|
97
|
+
h.style.setProperty('--czap-cores', String(cores));
|
|
98
|
+
h.style.setProperty('--czap-dpr', String(dpr));
|
|
99
|
+
h.setAttribute('data-czap-touch', String(touch));
|
|
100
|
+
h.setAttribute('data-czap-motion', motion ? 'reduce' : 'no-preference');
|
|
101
|
+
h.setAttribute('data-czap-scheme', dark ? 'dark' : 'light');
|
|
102
|
+
|
|
103
|
+
// Provisional tier -- conservative, no GPU probe available inline.
|
|
104
|
+
// Full detect package overrides on hydration via data-czap-tier attribute.
|
|
105
|
+
var tier = 'reactive';
|
|
106
|
+
if (motion) tier = 'static';
|
|
107
|
+
else if (cores <= 2 || mem <= 2) tier = 'styled';
|
|
108
|
+
h.setAttribute('data-czap-tier', tier);
|
|
109
|
+
h.setAttribute('data-czap-tier-provisional', 'true');
|
|
110
|
+
|
|
111
|
+
writeDetectState({
|
|
112
|
+
tier: tier,
|
|
113
|
+
provisional: true
|
|
114
|
+
});
|
|
115
|
+
} catch(e) {}
|
|
116
|
+
})();
|
|
117
|
+
`.trim();
|
|
118
|
+
|
|
119
|
+
function serializeInlineRuntimePolicy(policy: RuntimeSecurityPolicy): string {
|
|
120
|
+
return JSON.stringify(policy).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function runtimeBootstrapScript(policy: RuntimeSecurityPolicy): string {
|
|
124
|
+
return `
|
|
125
|
+
import { bootstrapSlots, configureRuntimePolicy, installSwapReinit } from '@czap/astro/runtime';
|
|
126
|
+
|
|
127
|
+
configureRuntimePolicy(${serializeInlineRuntimePolicy(policy)});
|
|
128
|
+
bootstrapSlots();
|
|
129
|
+
installSwapReinit();
|
|
130
|
+
`.trim();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const WASM_RUNTIME_SCRIPT = `
|
|
134
|
+
import { wasmUrl } from 'virtual:czap/wasm-url';
|
|
135
|
+
import { configureWasmRuntime } from '@czap/astro/runtime';
|
|
136
|
+
|
|
137
|
+
configureWasmRuntime(wasmUrl);
|
|
138
|
+
`.trim();
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build an `updateConfig` payload that toggles a single experimental flag
|
|
142
|
+
* not yet present in Astro's declared `experimental` shape.
|
|
143
|
+
*
|
|
144
|
+
* The `experimental` field on `AstroConfig` is strictly keyed, so adding
|
|
145
|
+
* an unknown flag requires a widening bridge. Containing that bridge
|
|
146
|
+
* here keeps the cast off individual call sites.
|
|
147
|
+
*/
|
|
148
|
+
function withExperimentalFlag(flag: string, value: boolean): { experimental: Record<string, unknown> } {
|
|
149
|
+
return { experimental: { [flag]: value } };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Integration
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build the czap `AstroIntegration`.
|
|
158
|
+
*
|
|
159
|
+
* Plug the returned object into `astro.config.mjs`'s `integrations`
|
|
160
|
+
* array. The integration wires Astro's `astro:config:setup`,
|
|
161
|
+
* `astro:config:done`, `astro:server:setup`, and `astro:build:done`
|
|
162
|
+
* hooks.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* // astro.config.mjs
|
|
167
|
+
* import { integration as czap } from '@czap/astro';
|
|
168
|
+
*
|
|
169
|
+
* const config = defineConfig({
|
|
170
|
+
* integrations: [czap({ detect: true, workers: { enabled: true } })],
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export function integration(config?: IntegrationConfig): AstroIntegration {
|
|
175
|
+
const detectEnabled = config?.detect !== false;
|
|
176
|
+
const serverIslandsEnabled = config?.serverIslands === true;
|
|
177
|
+
const workersEnabled = config?.workers?.enabled === true;
|
|
178
|
+
const gpuEnabled = config?.gpu?.enabled !== false;
|
|
179
|
+
const streamEnabled = config?.stream?.enabled !== false;
|
|
180
|
+
const llmEnabled = config?.llm?.enabled !== false;
|
|
181
|
+
const wasmEnabled = config?.wasm?.enabled === true;
|
|
182
|
+
const runtimePolicy = normalizeRuntimeSecurityPolicy({
|
|
183
|
+
endpointPolicy: config?.security?.endpointPolicy,
|
|
184
|
+
htmlPolicy: config?.security?.htmlPolicy,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
name: '@czap/astro',
|
|
189
|
+
|
|
190
|
+
hooks: {
|
|
191
|
+
'astro:config:setup': ({ updateConfig, addClientDirective, injectScript, logger }) => {
|
|
192
|
+
type AstroViteConfig = Parameters<typeof updateConfig>[0]['vite'];
|
|
193
|
+
logger.info('Setting up @czap integration');
|
|
194
|
+
|
|
195
|
+
// Astro may carry a different Vite type graph than @czap/vite. The plugin
|
|
196
|
+
// runtime contract is still compatible, so the host integration owns the
|
|
197
|
+
// version bridge here instead of leaking duplicate plugin shapes downstream.
|
|
198
|
+
const astroViteConfig = {
|
|
199
|
+
plugins: [
|
|
200
|
+
plugin({
|
|
201
|
+
...(config?.vite ?? {}),
|
|
202
|
+
...(wasmEnabled ? { wasm: { enabled: true, path: config?.wasm?.path } } : {}),
|
|
203
|
+
}),
|
|
204
|
+
],
|
|
205
|
+
} as AstroViteConfig;
|
|
206
|
+
|
|
207
|
+
updateConfig({
|
|
208
|
+
vite: astroViteConfig,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Register client directives
|
|
212
|
+
addClientDirective({
|
|
213
|
+
name: 'satellite',
|
|
214
|
+
entrypoint: '@czap/astro/client-directives/satellite',
|
|
215
|
+
});
|
|
216
|
+
logger.info('Registered satellite client directive');
|
|
217
|
+
|
|
218
|
+
if (streamEnabled) {
|
|
219
|
+
addClientDirective({
|
|
220
|
+
name: 'stream',
|
|
221
|
+
entrypoint: '@czap/astro/client-directives/stream',
|
|
222
|
+
});
|
|
223
|
+
logger.info('Registered stream client directive');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (llmEnabled) {
|
|
227
|
+
addClientDirective({
|
|
228
|
+
name: 'llm',
|
|
229
|
+
entrypoint: '@czap/astro/client-directives/llm',
|
|
230
|
+
});
|
|
231
|
+
logger.info('Registered llm client directive');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (workersEnabled) {
|
|
235
|
+
addClientDirective({
|
|
236
|
+
name: 'worker',
|
|
237
|
+
entrypoint: '@czap/astro/client-directives/worker',
|
|
238
|
+
});
|
|
239
|
+
logger.info('Registered worker client directive');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (gpuEnabled) {
|
|
243
|
+
addClientDirective({
|
|
244
|
+
name: 'gpu',
|
|
245
|
+
entrypoint: '@czap/astro/client-directives/gpu',
|
|
246
|
+
});
|
|
247
|
+
logger.info('Registered gpu client directive');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (wasmEnabled) {
|
|
251
|
+
addClientDirective({
|
|
252
|
+
name: 'wasm',
|
|
253
|
+
entrypoint: '@czap/astro/client-directives/wasm',
|
|
254
|
+
});
|
|
255
|
+
logger.info('Registered wasm client directive');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Inject detect script for client-side capability detection
|
|
259
|
+
if (detectEnabled) {
|
|
260
|
+
injectScript('head-inline', DETECT_INLINE_SCRIPT);
|
|
261
|
+
logger.info('Injected detect script');
|
|
262
|
+
|
|
263
|
+
// Inject GPU probe upgrade (deferred, non-blocking)
|
|
264
|
+
if (gpuEnabled) {
|
|
265
|
+
injectScript('page', DETECT_UPGRADE_SCRIPT);
|
|
266
|
+
logger.info('Injected GPU probe upgrade');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
injectScript('page', runtimeBootstrapScript(runtimePolicy));
|
|
271
|
+
|
|
272
|
+
if (wasmEnabled) {
|
|
273
|
+
injectScript('page', WASM_RUNTIME_SCRIPT);
|
|
274
|
+
logger.info('Injected wasm runtime bootstrap');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Configure server islands if enabled.
|
|
278
|
+
// `serverIslands` is not in Astro 6's declared experimental keys yet;
|
|
279
|
+
// enablement is delegated via a named bridge so only the one call site
|
|
280
|
+
// opts out of the declared-config shape.
|
|
281
|
+
if (serverIslandsEnabled) {
|
|
282
|
+
updateConfig(withExperimentalFlag('serverIslands', true));
|
|
283
|
+
logger.info('Enabled server islands');
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
'astro:config:done': ({ config: astroConfig, logger }) => {
|
|
288
|
+
logger.info(`@czap configured for ${astroConfig.output} output`);
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
'astro:server:setup': ({ server, logger }) => {
|
|
292
|
+
logger.info('@czap dev server middleware active');
|
|
293
|
+
|
|
294
|
+
if (detectEnabled || workersEnabled) {
|
|
295
|
+
server.middlewares.use((_req: unknown, res: { setHeader(k: string, v: string): void }, next: () => void) => {
|
|
296
|
+
for (const [header, value] of getCzapHeaderEntries({ detectEnabled, workersEnabled })) {
|
|
297
|
+
res.setHeader(header, value);
|
|
298
|
+
}
|
|
299
|
+
next();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
'astro:build:done': ({ logger }) => {
|
|
305
|
+
logger.info('@czap build integration complete');
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge middleware -- Client Hints parsing, tier detection, response headers.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic handler compatible with Astro middleware,
|
|
5
|
+
* Cloudflare Workers, and Express/Vite dev server.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ClientHints, createEdgeHostAdapter, EdgeTier } from '@czap/edge';
|
|
11
|
+
import type { CompiledOutputs, EdgeHostAdapterConfig, ThemeCompileResult } from '@czap/edge';
|
|
12
|
+
import type { ExtendedDeviceCapabilities } from '@czap/detect';
|
|
13
|
+
import { applyCzapHeaders } from './headers.js';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shape of `context.locals.czap` injected by {@link czapMiddleware}.
|
|
21
|
+
* Astro components (and downstream middleware) read this to drive
|
|
22
|
+
* adaptive rendering decisions.
|
|
23
|
+
*/
|
|
24
|
+
export interface CzapLocals {
|
|
25
|
+
/** Resolved tiers (capability, motion, design). */
|
|
26
|
+
readonly tier: {
|
|
27
|
+
readonly cap: string;
|
|
28
|
+
readonly motion: string;
|
|
29
|
+
readonly design: string;
|
|
30
|
+
};
|
|
31
|
+
/** Parsed device capabilities. */
|
|
32
|
+
readonly capabilities: ExtendedDeviceCapabilities;
|
|
33
|
+
/** Edge-host resolution result, present when an edge adapter is configured. */
|
|
34
|
+
readonly edge?: {
|
|
35
|
+
readonly theme?: ThemeCompileResult;
|
|
36
|
+
readonly compiledOutputs?: CompiledOutputs;
|
|
37
|
+
readonly htmlAttributes: string;
|
|
38
|
+
readonly cacheStatus: 'disabled' | 'hit' | 'miss';
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Options accepted by {@link czapMiddleware}.
|
|
44
|
+
*
|
|
45
|
+
* Omit `edge` to run in pure Client-Hints mode. Pass `edge` when you
|
|
46
|
+
* have an `@czap/edge` host adapter (KV cache, theme compilation).
|
|
47
|
+
*/
|
|
48
|
+
export interface CzapMiddlewareConfig {
|
|
49
|
+
/** Edge host adapter configuration (KV cache, theme compilation). */
|
|
50
|
+
readonly edge?: EdgeHostAdapterConfig;
|
|
51
|
+
/** Whether to include the Client Hints request headers (default `true`). */
|
|
52
|
+
readonly detect?: boolean;
|
|
53
|
+
/** Whether to emit COOP/COEP headers for worker features. */
|
|
54
|
+
readonly workers?: { readonly enabled?: boolean };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface MiddlewareContext {
|
|
58
|
+
readonly request: Request;
|
|
59
|
+
locals: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Middleware
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create the czap edge middleware.
|
|
68
|
+
*
|
|
69
|
+
* Parses Client Hints from request headers, computes tier detection,
|
|
70
|
+
* injects results into `context.locals.czap`, and sets Client Hints
|
|
71
|
+
* response headers (`Accept-CH`, `Critical-CH`).
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* // Astro middleware (src/middleware.ts)
|
|
76
|
+
* import { czapMiddleware } from '@czap/astro';
|
|
77
|
+
* export const onRequest = czapMiddleware();
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function czapMiddleware(
|
|
81
|
+
config?: CzapMiddlewareConfig,
|
|
82
|
+
): (context: MiddlewareContext, next: () => Promise<Response>) => Promise<Response> {
|
|
83
|
+
const edgeConfig = config?.edge;
|
|
84
|
+
let edgeAdapter: ReturnType<typeof createEdgeHostAdapter> | null = null;
|
|
85
|
+
if (edgeConfig) {
|
|
86
|
+
edgeAdapter = createEdgeHostAdapter(edgeConfig);
|
|
87
|
+
}
|
|
88
|
+
const detectEnabled = config?.detect !== false;
|
|
89
|
+
const workersEnabled = config?.workers?.enabled === true;
|
|
90
|
+
|
|
91
|
+
return async (context: MiddlewareContext, next: () => Promise<Response>): Promise<Response> => {
|
|
92
|
+
const edgeResolution = edgeAdapter ? await edgeAdapter.resolve(context.request.headers) : null;
|
|
93
|
+
const capabilities = edgeResolution?.capabilities ?? ClientHints.parseClientHints(context.request.headers);
|
|
94
|
+
const tier = edgeResolution?.tier ?? EdgeTier.detectTier(context.request.headers);
|
|
95
|
+
|
|
96
|
+
// Inject into locals for component access
|
|
97
|
+
context.locals.czap = {
|
|
98
|
+
tier: {
|
|
99
|
+
cap: tier.capLevel,
|
|
100
|
+
motion: tier.motionTier,
|
|
101
|
+
design: tier.designTier,
|
|
102
|
+
},
|
|
103
|
+
capabilities,
|
|
104
|
+
...(edgeResolution
|
|
105
|
+
? {
|
|
106
|
+
edge: {
|
|
107
|
+
theme: edgeResolution.theme,
|
|
108
|
+
compiledOutputs: edgeResolution.compiledOutputs,
|
|
109
|
+
htmlAttributes: edgeResolution.htmlAttributes,
|
|
110
|
+
cacheStatus: edgeResolution.cacheStatus,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
: {}),
|
|
114
|
+
} satisfies CzapLocals;
|
|
115
|
+
|
|
116
|
+
// Continue to the route handler
|
|
117
|
+
const response = await next();
|
|
118
|
+
|
|
119
|
+
// Add Client Hints request headers to the response
|
|
120
|
+
const headers = applyCzapHeaders(new Headers(response.headers), {
|
|
121
|
+
detectEnabled,
|
|
122
|
+
workersEnabled,
|
|
123
|
+
acceptCH: edgeResolution?.responseHeaders.acceptCH ?? ClientHints.acceptCHHeader(),
|
|
124
|
+
criticalCH: edgeResolution?.responseHeaders.criticalCH ?? ClientHints.criticalCHHeader(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return new Response(response.body, {
|
|
128
|
+
status: response.status,
|
|
129
|
+
statusText: response.statusText,
|
|
130
|
+
headers,
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|