gorsee 0.2.5 → 0.2.6

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/README.md CHANGED
@@ -30,12 +30,16 @@ Read the strategic docs:
30
30
  - [Reactive Debugging](./docs/REACTIVE_DEBUGGING.md)
31
31
  - [Reactive Measurement Gaps](./docs/REACTIVE_MEASUREMENT_GAPS.md)
32
32
  - [Benchmark Policy](./docs/BENCHMARK_POLICY.md)
33
+ - [Benchmark Contract](./docs/BENCHMARK_CONTRACT.json)
33
34
  - [Benchmark Methodology](./docs/BENCHMARK_METHODOLOGY.md)
34
35
  - [SSR Benchmark Proof](./docs/SSR_BENCHMARK_PROOF.md)
35
36
  - [DOM Benchmark Proof](./docs/DOM_BENCHMARK_PROOF.md)
36
37
  - [Benchmark Artifacts](./docs/BENCHMARK_ARTIFACTS.md)
37
38
  - [Benchmark Release Discipline](./docs/BENCHMARK_RELEASE_DISCIPLINE.md)
38
39
  - [Build Diagnostics](./docs/BUILD_DIAGNOSTICS.md)
40
+ - [Deploy Contract](./docs/DEPLOY_CONTRACT.json)
41
+ - [Diagnostics Contract](./docs/DIAGNOSTICS_CONTRACT.json)
42
+ - [Runtime Security Contract](./docs/RUNTIME_SECURITY_CONTRACT.json)
39
43
  - [Runtime Failures](./docs/RUNTIME_FAILURES.md)
40
44
  - [Cache Invalidation](./docs/CACHE_INVALIDATION.md)
41
45
  - [Streaming and Hydration Failures](./docs/STREAMING_HYDRATION_FAILURES.md)
@@ -51,6 +55,8 @@ Read the strategic docs:
51
55
  - [AI Debugging Workflows](./docs/AI_DEBUGGING_WORKFLOWS.md)
52
56
  - [Starter Onboarding](./docs/STARTER_ONBOARDING.md)
53
57
  - [Market-Ready Proof](./docs/MARKET_READY_PROOF.md)
58
+ - [Adoption Proof Manifest](./docs/ADOPTION_PROOF_MANIFEST.json)
59
+ - [Release Contract](./docs/RELEASE_CONTRACT.json)
54
60
  - [Migration Guide](./docs/MIGRATION_GUIDE.md)
55
61
  - [Upgrade Playbook](./docs/UPGRADE_PLAYBOOK.md)
56
62
  - [Deploy Target Guide](./docs/DEPLOY_TARGET_GUIDE.md)
@@ -59,9 +65,12 @@ Read the strategic docs:
59
65
  - [Recipe Boundaries](./docs/RECIPE_BOUNDARIES.md)
60
66
  - [Workspace Adoption](./docs/WORKSPACE_ADOPTION.md)
61
67
  - [Downstream Testing](./docs/DOWNSTREAM_TESTING.md)
68
+ - [Test Coverage Audit](./docs/TEST_COVERAGE_AUDIT.md)
62
69
  - [Team Failures](./docs/TEAM_FAILURES.md)
63
70
  - [Maturity Policy](./docs/MATURITY_POLICY.md)
71
+ - [Top-Tier Exit Gate](./docs/TOP_TIER_EXIT_GATE.md)
64
72
  - [Dependency Policy](./docs/DEPENDENCY_POLICY.md)
73
+ - [Dependency Contract](./docs/DEPENDENCY_CONTRACT.json)
65
74
  - [Compatibility Guardrails](./docs/COMPATIBILITY_GUARDRAILS.md)
66
75
  - [Ambiguity Policy](./docs/AMBIGUITY_POLICY.md)
67
76
  - [DX Feedback Loop](./docs/DX_FEEDBACK_LOOP.md)
@@ -107,6 +116,20 @@ What that means in practice:
107
116
  - deterministic plugin platform with capabilities, dependency ordering, config validation, and conformance testing
108
117
  - AI diagnostics, saved reactive-trace ingestion, context bundles, IDE projections, and MCP integration built into the framework lifecycle
109
118
  - CLI enforcement through `gorsee check`, release gates, deploy contracts, and policy docs
119
+ - machine-readable public API stability enforcement through `bun run api:policy`
120
+ - machine-readable adoption and market-ready proof enforcement through `bun run adoption:policy`
121
+ - machine-readable dependency/runtime support enforcement through `bun run dependency:policy`
122
+ - machine-readable deploy/runtime profile enforcement through `bun run deploy:policy`
123
+ - machine-readable benchmark evidence enforcement through `bun run benchmarks:policy`
124
+ - machine-readable runtime/security contract enforcement through `bun run runtime:security:policy`
125
+ - machine-readable diagnostics and triage contract enforcement through `bun run runtime:policy`
126
+ - critical runtime/release regression enforcement through `bun run critical:surface` and `bun run test:critical-surface`
127
+ - repository-level coverage enforcement through `bun run coverage:audit`
128
+
129
+ Critical Surface Suite:
130
+
131
+ - `bun run critical:surface` keeps the documented regression gate aligned across package scripts, CI, support claims, and coverage docs.
132
+ - `bun run test:critical-surface` runs the highest-risk runtime/security/reactive/AI/publish regressions that must fail closed before release.
110
133
 
111
134
  ## Core Ideas
112
135
 
@@ -3,7 +3,7 @@ interface JSONRPCRequest {
3
3
  jsonrpc: "2.0";
4
4
  id?: string | number | null;
5
5
  method: string;
6
- params?: any;
6
+ params?: unknown;
7
7
  }
8
8
  interface JSONRPCResponse {
9
9
  jsonrpc: "2.0";
@@ -1,5 +1,5 @@
1
1
  import { buildAIHealthReport, readAIDiagnosticsSnapshot, readAIEvents } from "./store.js";
2
- import { MAX_AI_JSON_BYTES, safeJSONParse } from "./json.js";
2
+ import { isRecord, MAX_AI_JSON_BYTES, safeJSONParse } from "./json.js";
3
3
  export function createAIMCPServer(options) {
4
4
  async function handleRequest(request) {
5
5
  switch (request.method) {
@@ -44,7 +44,12 @@ export function createAIMCPServer(options) {
44
44
  ]
45
45
  });
46
46
  case "tools/call":
47
- return ok(request.id, await callTool(request.params));
47
+ try {
48
+ return ok(request.id, await callTool(request.params));
49
+ } catch (error) {
50
+ const message = error instanceof Error ? error.message : "Tool invocation failed";
51
+ return err(request.id, -32602, message);
52
+ }
48
53
  default:
49
54
  return err(request.id, -32601, `Method not found: ${request.method}`);
50
55
  }
@@ -72,7 +77,7 @@ export function createAIMCPServer(options) {
72
77
  serve
73
78
  };
74
79
  async function callTool(params) {
75
- const name = params?.name, args = params?.arguments, limit = Math.max(1, Math.min(args?.limit ?? options.limit ?? 50, 500));
80
+ const { name, limit } = parseToolCall(params, options.limit);
76
81
  if (name === "gorsee_ai_recent_events") {
77
82
  const events = await readAIEvents(options.paths.eventsPath, { limit });
78
83
  return text(JSON.stringify(events, null, 2));
@@ -88,6 +93,22 @@ export function createAIMCPServer(options) {
88
93
  throw Error(`Unknown tool: ${name}`);
89
94
  }
90
95
  }
96
+ function parseToolCall(params, defaultLimit) {
97
+ if (!isRecord(params))
98
+ throw Error("Invalid params: expected tool call object");
99
+ const { name, arguments: args } = params;
100
+ if (typeof name !== "string" || name.length === 0)
101
+ throw Error("Invalid params: tool name is required");
102
+ if (args !== void 0 && !isRecord(args))
103
+ throw Error("Invalid params: arguments must be an object");
104
+ const rawLimit = args?.limit;
105
+ if (rawLimit !== void 0 && typeof rawLimit !== "number")
106
+ throw Error("Invalid params: limit must be a number");
107
+ return {
108
+ name,
109
+ limit: Math.max(1, Math.min(rawLimit ?? defaultLimit ?? 50, 500))
110
+ };
111
+ }
91
112
  export function createLineReader(input) {
92
113
  const decoder = new TextDecoder;
93
114
  return {
@@ -1,3 +1,9 @@
1
+ function getStaticContent(env) {
2
+ const value = env.__STATIC_CONTENT;
3
+ if (typeof value !== "object" || value === null || !("get" in value) || typeof value.get !== "function")
4
+ return;
5
+ return value;
6
+ }
1
7
  export function generateWranglerConfig(name) {
2
8
  const today = new Date().toISOString().split("T")[0];
3
9
  return `# Gorsee.js \u2014 Cloudflare Workers configuration
@@ -34,6 +40,18 @@ export function generateCloudflareEntry() {
34
40
  return `// Gorsee.js \u2014 Cloudflare Worker entry
35
41
  // Auto-generated by \`gorsee deploy cloudflare\`
36
42
 
43
+ interface CloudflareStaticContent {
44
+ get(path: string): Promise<BodyInit | null | undefined>
45
+ }
46
+
47
+ function getStaticContent(env: Record<string, unknown>): CloudflareStaticContent | undefined {
48
+ const value = env.__STATIC_CONTENT
49
+ if (typeof value !== "object" || value === null || !("get" in value) || typeof value.get !== "function") {
50
+ return undefined
51
+ }
52
+ return value as CloudflareStaticContent
53
+ }
54
+
37
55
  export default {
38
56
  async fetch(
39
57
  request: Request,
@@ -42,12 +60,13 @@ export default {
42
60
  ): Promise<Response> {
43
61
  const url = new URL(request.url)
44
62
  const appOrigin = (env.APP_ORIGIN as string | undefined) ?? url.origin
63
+ // __STATIC_CONTENT is injected by Cloudflare for static asset access.
64
+ const staticContent = getStaticContent(env)
45
65
 
46
66
  // Serve static client assets from KV/site
47
67
  if (url.pathname.startsWith("/_gorsee/")) {
48
68
  const assetPath = url.pathname.slice("/_gorsee/".length)
49
- // @ts-expect-error \u2014 __STATIC_CONTENT is injected by Cloudflare
50
- const asset = await (env.__STATIC_CONTENT as any)?.get(assetPath)
69
+ const asset = await staticContent?.get(assetPath)
51
70
  if (asset) {
52
71
  return new Response(asset, {
53
72
  headers: {
@@ -61,8 +80,7 @@ export default {
61
80
  // Serve public static files
62
81
  const staticExts = [".css", ".ico", ".svg", ".png", ".jpg", ".woff2", ".txt"]
63
82
  if (staticExts.some((ext) => url.pathname.endsWith(ext))) {
64
- // @ts-expect-error \u2014 __STATIC_CONTENT is injected by Cloudflare
65
- const asset = await (env.__STATIC_CONTENT as any)?.get(url.pathname.slice(1))
83
+ const asset = await staticContent?.get(url.pathname.slice(1))
66
84
  if (asset) {
67
85
  return new Response(asset, {
68
86
  headers: { "Cache-Control": "public, max-age=3600" },
@@ -95,7 +95,7 @@ export function stripLocalePrefix(pathname, supportedLocales = Object.keys(dicti
95
95
  return pathname.replace(new RegExp(`^/${locale}(?=/|$)`), "") || "/";
96
96
  }
97
97
  export function withLocalePath(pathname, locale, options = {}) {
98
- const resolvedDefault = options.defaultLocale ?? defaultLocale, strategy = options.strategy ?? routeStrategy, normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
98
+ const resolvedDefault = options.defaultLocale ?? defaultLocale, strategy = options.strategy ?? routeStrategy, supportedLocales = Object.keys(dictionaries), normalized = stripLocalePrefix(pathname.startsWith("/") ? pathname : `/${pathname}`, supportedLocales);
99
99
  if (strategy === "none")
100
100
  return normalized;
101
101
  if (strategy === "prefix-except-default" && locale === resolvedDefault)
@@ -23,6 +23,9 @@ export default defineConfig({
23
23
  })
24
24
  `;
25
25
  }
26
+ function isDrizzleClosable(value) {
27
+ return typeof value === "object" && value !== null && "close" in value && typeof value.close === "function";
28
+ }
26
29
  export function drizzlePlugin(config) {
27
30
  return definePlugin({
28
31
  name: "gorsee-drizzle",
@@ -42,7 +45,7 @@ export function drizzlePlugin(config) {
42
45
  app.addMiddleware(drizzleMiddleware(drizzleInstance));
43
46
  },
44
47
  async teardown() {
45
- if (drizzleInstance && typeof drizzleInstance.close === "function")
48
+ if (isDrizzleClosable(drizzleInstance))
46
49
  drizzleInstance.close();
47
50
  drizzleInstance = null;
48
51
  }
@@ -124,9 +124,19 @@ export function createPluginRunner(config = {}) {
124
124
  await runPluginPhase(plugin, phase);
125
125
  },
126
126
  async teardownAll() {
127
- for (const plugin of [...getOrderedPlugins()].reverse())
128
- if (plugin.teardown)
127
+ const failures = [];
128
+ for (const plugin of [...getOrderedPlugins()].reverse()) {
129
+ if (!plugin.teardown)
130
+ continue;
131
+ try {
129
132
  await plugin.teardown();
133
+ } catch (error) {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ failures.push(`${plugin.name}: ${message}`);
136
+ }
137
+ }
138
+ if (failures.length > 0)
139
+ throw Error(`Plugin teardown failed: ${failures.join("; ")}`);
130
140
  },
131
141
  getMiddlewares: () => [...middlewares],
132
142
  getBuildPlugins(_target) {
@@ -134,7 +144,9 @@ export function createPluginRunner(config = {}) {
134
144
  for (const plugin of getOrderedPlugins())
135
145
  if (plugin.buildPlugins)
136
146
  result.push(...plugin.buildPlugins());
137
- return result;
147
+ if (!_target)
148
+ return result;
149
+ return result.filter((plugin) => _target === "bun" ? Boolean(plugin.bun) : Boolean(plugin.rolldown));
138
150
  },
139
151
  getRoutes: () => new Map(routes),
140
152
  getPluginDescriptors() {
@@ -30,6 +30,9 @@ datasource db {
30
30
  // }
31
31
  `;
32
32
  }
33
+ function isPrismaDisconnectable(value) {
34
+ return typeof value === "object" && value !== null && "$disconnect" in value && typeof value.$disconnect === "function";
35
+ }
33
36
  export function prismaPlugin(config = {}) {
34
37
  return definePlugin({
35
38
  name: "gorsee-prisma",
@@ -49,7 +52,7 @@ export function prismaPlugin(config = {}) {
49
52
  app.addMiddleware(prismaMiddleware(prismaClient));
50
53
  },
51
54
  async teardown() {
52
- if (prismaClient && typeof prismaClient.$disconnect === "function")
55
+ if (isPrismaDisconnectable(prismaClient))
53
56
  await prismaClient.$disconnect();
54
57
  prismaClient = null;
55
58
  }
@@ -7,7 +7,7 @@ export function createLive(options) {
7
7
  reconnect = !0,
8
8
  reconnectDelay = 1000
9
9
  } = options, [value, setValue] = createSignal(initialValue), [connected, setConnected] = createSignal(!1);
10
- let ws = null, closed = !1, retryCount = 0;
10
+ let ws = null, closed = !1, retryCount = 0, reconnectScheduled = !1, reconnectToken = 0;
11
11
  function handleMessage(event) {
12
12
  try {
13
13
  const parsed = JSON.parse(String(event.data)), result = transform ? transform(parsed) : parsed;
@@ -16,6 +16,8 @@ export function createLive(options) {
16
16
  }
17
17
  function handleOpen() {
18
18
  retryCount = 0;
19
+ reconnectScheduled = !1;
20
+ reconnectToken++;
19
21
  setConnected(!0);
20
22
  }
21
23
  function handleClose() {
@@ -25,9 +27,17 @@ export function createLive(options) {
25
27
  scheduleReconnect();
26
28
  }
27
29
  function scheduleReconnect() {
28
- const delay = Math.min(reconnectDelay * 2 ** retryCount, 30000);
30
+ if (reconnectScheduled || closed)
31
+ return;
32
+ reconnectScheduled = !0;
33
+ const token = ++reconnectToken, delay = Math.min(reconnectDelay * 2 ** retryCount, 30000);
29
34
  retryCount++;
30
- setTimeout(connect, delay);
35
+ setTimeout(() => {
36
+ if (closed || !reconnectScheduled || token !== reconnectToken)
37
+ return;
38
+ reconnectScheduled = !1;
39
+ connect();
40
+ }, delay);
31
41
  }
32
42
  function connect() {
33
43
  if (closed)
@@ -46,6 +56,8 @@ export function createLive(options) {
46
56
  }
47
57
  function close() {
48
58
  closed = !0;
59
+ reconnectScheduled = !1;
60
+ reconnectToken++;
49
61
  if (ws) {
50
62
  ws.close();
51
63
  ws = null;
@@ -10,12 +10,17 @@ import {
10
10
  } from "./diagnostics.js";
11
11
  export function createMutation(options) {
12
12
  const mutationNodeId = trackMutationCreated(options.label), [data, setData] = createSignal(void 0), [error, setError] = createSignal(void 0), [isPending, setIsPending] = createSignal(!1);
13
+ let pendingCount = 0, mutationEpoch = 0;
13
14
  async function mutate(variables) {
15
+ const epoch = mutationEpoch;
14
16
  trackMutationStart(mutationNodeId, options.label);
17
+ pendingCount++;
15
18
  setIsPending(!0);
16
19
  setError(void 0);
17
20
  try {
18
21
  const result = await options.mutationFn(variables);
22
+ if (epoch !== mutationEpoch)
23
+ return result;
19
24
  setData(result);
20
25
  trackMutationSuccess(mutationNodeId, options.label);
21
26
  options.onSuccess?.(result, variables);
@@ -23,6 +28,8 @@ export function createMutation(options) {
23
28
  return result;
24
29
  } catch (err) {
25
30
  const e = err instanceof Error ? err : Error(String(err));
31
+ if (epoch !== mutationEpoch)
32
+ throw e;
26
33
  setError(e);
27
34
  trackMutationError(mutationNodeId, options.label, e.message);
28
35
  options.onError?.(e, variables);
@@ -30,22 +37,28 @@ export function createMutation(options) {
30
37
  throw e;
31
38
  } finally {
32
39
  trackMutationSettled(mutationNodeId, options.label);
33
- setIsPending(!1);
40
+ if (epoch === mutationEpoch) {
41
+ pendingCount = Math.max(0, pendingCount - 1);
42
+ setIsPending(pendingCount > 0);
43
+ }
34
44
  }
35
45
  }
36
46
  async function optimistic(signal, setter, update, variables) {
37
- const previous = signal();
38
- setter(update(previous, variables));
47
+ const previous = signal(), optimisticValue = update(previous, variables);
48
+ setter(optimisticValue);
39
49
  try {
40
50
  return await mutate(variables);
41
51
  } catch (err) {
42
- setter(previous);
52
+ if (Object.is(signal(), optimisticValue))
53
+ setter(previous);
43
54
  trackMutationRollback(mutationNodeId, options.label);
44
55
  throw err;
45
56
  }
46
57
  }
47
58
  function reset() {
48
59
  trackMutationReset(mutationNodeId, options.label);
60
+ mutationEpoch++;
61
+ pendingCount = 0;
49
62
  setData(void 0);
50
63
  setError(void 0);
51
64
  setIsPending(!1);
@@ -8,18 +8,38 @@ import {
8
8
  trackResourceMutate,
9
9
  trackResourceRefetch
10
10
  } from "./diagnostics.js";
11
- const resourceCache = new Map;
11
+ const resourceCache = new Map, resourceCacheEpochs = new Map;
12
+ function getCacheEpoch(key) {
13
+ return resourceCacheEpochs.get(key) ?? 0;
14
+ }
15
+ function bumpCacheEpoch(key) {
16
+ const nextEpoch = getCacheEpoch(key) + 1;
17
+ resourceCacheEpochs.set(key, nextEpoch);
18
+ return nextEpoch;
19
+ }
12
20
  export function invalidateResource(key) {
21
+ bumpCacheEpoch(key);
13
22
  resourceCache.delete(key);
14
23
  trackResourceInvalidation(key, "resource.invalidate");
15
24
  }
16
25
  export function invalidateAll() {
17
- for (const key of resourceCache.keys())
26
+ for (const key of resourceCache.keys()) {
27
+ bumpCacheEpoch(key);
18
28
  trackResourceInvalidation(key, "resource.invalidateAll");
29
+ }
19
30
  resourceCache.clear();
20
31
  }
21
32
  export function createResource(fetcher, options) {
22
33
  const retryCount = options?.retry ?? 0, retryDelay = options?.retryDelay ?? 1000, cacheKey = options?.key, staleTime = options?.staleTime, diagnosticsLabel = options?.label ?? cacheKey, resourceNodeId = trackResourceCreated(diagnosticsLabel, cacheKey), cached = cacheKey ? resourceCache.get(cacheKey) : void 0, hasValidCache = cached && staleTime && Date.now() - cached.fetchedAt < staleTime, initialValue = hasValidCache ? cached.data : options?.initialData, [data, setData] = createSignal(initialValue), [loading, setLoading] = createSignal(!hasValidCache), [error, setError] = createSignal(void 0);
34
+ let currentLoadId = 0;
35
+ function writeCachedValue(value) {
36
+ if (!cacheKey)
37
+ return;
38
+ resourceCache.set(cacheKey, {
39
+ data: value,
40
+ fetchedAt: Date.now()
41
+ });
42
+ }
23
43
  async function fetchWithRetry(attempt) {
24
44
  try {
25
45
  return await fetcher();
@@ -31,16 +51,30 @@ export function createResource(fetcher, options) {
31
51
  throw err;
32
52
  }
33
53
  }
34
- const load = () => {
35
- if (cacheKey) {
54
+ const load = (forceFresh = !1) => {
55
+ const loadId = ++currentLoadId;
56
+ if (cacheKey && !forceFresh) {
36
57
  const entry = resourceCache.get(cacheKey);
37
58
  if (entry?.promise) {
59
+ const cacheEpoch = getCacheEpoch(cacheKey);
38
60
  trackResourceLoadStart(resourceNodeId, diagnosticsLabel, cacheKey, "deduped");
39
61
  entry.promise.then((result) => {
62
+ if (loadId !== currentLoadId)
63
+ return;
64
+ if (getCacheEpoch(cacheKey) !== cacheEpoch) {
65
+ setLoading(!1);
66
+ return;
67
+ }
40
68
  setData(() => result);
41
69
  setLoading(!1);
42
70
  trackResourceLoadSuccess(resourceNodeId, diagnosticsLabel, cacheKey);
43
71
  }).catch((err) => {
72
+ if (loadId !== currentLoadId)
73
+ return;
74
+ if (getCacheEpoch(cacheKey) !== cacheEpoch) {
75
+ setLoading(!1);
76
+ return;
77
+ }
44
78
  setError(err instanceof Error ? err : Error(String(err)));
45
79
  setLoading(!1);
46
80
  trackResourceLoadError(resourceNodeId, diagnosticsLabel, cacheKey, err instanceof Error ? err.message : String(err));
@@ -48,6 +82,7 @@ export function createResource(fetcher, options) {
48
82
  return;
49
83
  }
50
84
  }
85
+ const cacheEpoch = cacheKey ? bumpCacheEpoch(cacheKey) : 0;
51
86
  setLoading(!0);
52
87
  setError(void 0);
53
88
  trackResourceLoadStart(resourceNodeId, diagnosticsLabel, cacheKey);
@@ -58,12 +93,24 @@ export function createResource(fetcher, options) {
58
93
  resourceCache.set(cacheKey, entry);
59
94
  }
60
95
  promise.then((result) => {
96
+ if (loadId !== currentLoadId)
97
+ return;
98
+ if (cacheKey && getCacheEpoch(cacheKey) !== cacheEpoch) {
99
+ setLoading(!1);
100
+ return;
101
+ }
61
102
  setData(() => result);
62
103
  setLoading(!1);
63
104
  trackResourceLoadSuccess(resourceNodeId, diagnosticsLabel, cacheKey);
64
105
  if (cacheKey)
65
106
  resourceCache.set(cacheKey, { data: result, fetchedAt: Date.now() });
66
107
  }).catch((err) => {
108
+ if (loadId !== currentLoadId)
109
+ return;
110
+ if (cacheKey && getCacheEpoch(cacheKey) !== cacheEpoch) {
111
+ setLoading(!1);
112
+ return;
113
+ }
67
114
  setError(err instanceof Error ? err : Error(String(err)));
68
115
  setLoading(!1);
69
116
  trackResourceLoadError(resourceNodeId, diagnosticsLabel, cacheKey, err instanceof Error ? err.message : String(err));
@@ -83,14 +130,23 @@ export function createResource(fetcher, options) {
83
130
  error,
84
131
  refetch: () => {
85
132
  trackResourceRefetch(resourceNodeId, diagnosticsLabel, cacheKey);
86
- load();
133
+ load(!0);
87
134
  },
88
135
  mutate: (value) => {
89
136
  trackResourceMutate(resourceNodeId, diagnosticsLabel, cacheKey);
90
- if (typeof value === "function")
91
- setData(value);
92
- else
137
+ currentLoadId++;
138
+ if (cacheKey)
139
+ bumpCacheEpoch(cacheKey);
140
+ setLoading(!1);
141
+ setError(void 0);
142
+ if (typeof value === "function") {
143
+ const nextValue = value(data());
144
+ setData(() => nextValue);
145
+ writeCachedValue(nextValue);
146
+ } else {
93
147
  setData(() => value);
148
+ writeCachedValue(value);
149
+ }
94
150
  }
95
151
  }
96
152
  ];
@@ -1,15 +1,18 @@
1
1
  import { jsx } from "./jsx-runtime.js";
2
- import { enterHydration, exitHydration } from "./hydration.js";
2
+ import { enterHydration, exitHydration, isHydrating } from "./hydration.js";
3
3
  import { replayEvents } from "./event-replay.js";
4
4
  import { isRenderableThunk } from "./html-escape.js";
5
5
  function isVNodeLike(value) {
6
6
  return typeof value === "object" && value !== null && "type" in value && "props" in value;
7
7
  }
8
+ function renderVNodeLike(vnode) {
9
+ return jsx(vnode.type, vnode.props);
10
+ }
8
11
  function insertResult(container, result) {
9
12
  if (result instanceof Node)
10
13
  container.appendChild(result);
11
14
  else if (typeof result === "object" && result !== null && isVNodeLike(result))
12
- container.appendChild(jsx(result.type, result.props));
15
+ container.appendChild(renderVNodeLike(result));
13
16
  else if (Array.isArray(result))
14
17
  for (const child of result)
15
18
  insertResult(container, child);
@@ -24,8 +27,16 @@ export function render(component, container) {
24
27
  }
25
28
  export function hydrate(component, container) {
26
29
  enterHydration(container);
27
- component();
28
- if (exitHydration().recoverableMismatch)
30
+ let diagnostics;
31
+ try {
32
+ component();
33
+ diagnostics = exitHydration();
34
+ } catch (error) {
35
+ if (isHydrating())
36
+ exitHydration();
37
+ throw error;
38
+ }
39
+ if (diagnostics.recoverableMismatch)
29
40
  render(component, container);
30
41
  replayEvents(container);
31
42
  }
@@ -25,11 +25,24 @@ export const EVENT_REPLAY_SCRIPT = `<script data-g-event-replay>
25
25
  })();
26
26
  </script>`;
27
27
  function getEventReplay() {
28
- return globalThis.__gorsee_events;
28
+ const value = globalThis.__gorsee_events;
29
+ if (!value || typeof value !== "object")
30
+ return;
31
+ return value;
29
32
  }
30
33
  export function replayEvents(root) {
31
- getEventReplay()?.replay(root);
34
+ const replay = getEventReplay()?.replay;
35
+ if (typeof replay !== "function")
36
+ return;
37
+ try {
38
+ replay(root);
39
+ } catch {}
32
40
  }
33
41
  export function stopEventCapture() {
34
- getEventReplay()?.stop();
42
+ const stop = getEventReplay()?.stop;
43
+ if (typeof stop !== "function")
44
+ return;
45
+ try {
46
+ stop();
47
+ } catch {}
35
48
  }
@@ -1,4 +1,7 @@
1
1
  import { createSignal } from "../reactive/signal.js";
2
+ function isPlainFormActionResult(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
2
5
  function normalizeFormInput(input) {
3
6
  return input instanceof FormData ? input : new URLSearchParams(input);
4
7
  }
@@ -27,7 +30,10 @@ export function useFormAction(actionUrl) {
27
30
  headers: {
28
31
  Accept: "application/json"
29
32
  }
30
- }), rawResult = await res.json(), result = {
33
+ }), decoded = await res.json();
34
+ if (!isPlainFormActionResult(decoded))
35
+ throw Error("Malformed form action response");
36
+ const rawResult = decoded, result = {
31
37
  ok: rawResult.ok ?? res.ok,
32
38
  status: rawResult.status ?? res.status,
33
39
  data: rawResult.data,
@@ -1,12 +1,16 @@
1
1
  import { enterHydration, exitHydration } from "./hydration.js";
2
2
  import { replayEvents } from "./event-replay.js";
3
- const islandRegistry = new Map;
3
+ const islandRegistry = new Map, hydrationStarted = new WeakSet;
4
4
  export function registerIsland(name, loader) {
5
5
  islandRegistry.set(name, loader);
6
6
  }
7
7
  function parseIslandProps(raw) {
8
8
  try {
9
- return JSON.parse(raw);
9
+ const parsed = JSON.parse(raw);
10
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
11
+ return parsed;
12
+ console.warn(`[gorsee] Island props payload must decode to an object: ${raw}`);
13
+ return {};
10
14
  } catch {
11
15
  console.warn(`[gorsee] Failed to parse island props: ${raw}`);
12
16
  return {};
@@ -21,11 +25,30 @@ async function hydrateOne(el) {
21
25
  console.warn(`[gorsee] Island "${name}" not found in registry`);
22
26
  return;
23
27
  }
24
- const component = (await loader()).default, rawProps = el.getAttribute("data-props") ?? "{}", props = parseIslandProps(rawProps);
25
- enterHydration(el);
26
- component(props);
27
- exitHydration();
28
- replayEvents(el);
28
+ if (hydrationStarted.has(el))
29
+ return;
30
+ hydrationStarted.add(el);
31
+ let enteredHydration = !1;
32
+ try {
33
+ const component = (await loader()).default;
34
+ if (typeof component !== "function") {
35
+ hydrationStarted.delete(el);
36
+ console.warn(`[gorsee] Island "${name}" loader did not return a default component`);
37
+ return;
38
+ }
39
+ const rawProps = el.getAttribute("data-props") ?? "{}", props = parseIslandProps(rawProps);
40
+ enterHydration(el);
41
+ enteredHydration = !0;
42
+ component(props);
43
+ exitHydration();
44
+ enteredHydration = !1;
45
+ replayEvents(el);
46
+ } catch {
47
+ if (enteredHydration)
48
+ exitHydration();
49
+ hydrationStarted.delete(el);
50
+ console.warn(`[gorsee] Island "${name}" hydration failed`);
51
+ }
29
52
  }
30
53
  export function hydrateIslands() {
31
54
  const islands = document.querySelectorAll("[data-island]");
@@ -38,9 +61,11 @@ export function hydrateIslands() {
38
61
  }
39
62
  }
40
63
  function observeLazy(el) {
64
+ let hydrated = !1;
41
65
  const observer = new IntersectionObserver((entries) => {
42
66
  for (const entry of entries)
43
- if (entry.isIntersecting) {
67
+ if (!hydrated && entry.isIntersecting) {
68
+ hydrated = !0;
44
69
  observer.unobserve(el);
45
70
  hydrateOne(el);
46
71
  }
@@ -3,10 +3,10 @@ export type GorseeNode = Node | string | number | boolean | null | undefined | G
3
3
  export type Component = (props: Record<string, unknown>) => GorseeRenderable;
4
4
  export declare const Fragment: unique symbol;
5
5
  export interface JSXElement {
6
- type: string | Component | typeof Fragment;
6
+ type: string | Component | symbol;
7
7
  props: Record<string, unknown>;
8
8
  children: unknown[];
9
9
  }
10
- export declare function jsx(type: string | Component | typeof Fragment, props: Record<string, unknown> | null): Node | DocumentFragment;
10
+ export declare function jsx(type: string | Component | symbol, props: Record<string, unknown> | null): Node | DocumentFragment;
11
11
  export declare const jsxs: typeof jsx;
12
12
  export declare const jsxDEV: typeof jsx;
@@ -5,6 +5,9 @@ export const Fragment = Symbol("Fragment");
5
5
  function isVNodeLike(value) {
6
6
  return typeof value === "object" && value !== null && "type" in value && "props" in value;
7
7
  }
8
+ function renderVNodeLike(vnode) {
9
+ return jsx(vnode.type, vnode.props);
10
+ }
8
11
  function createTextNode(value) {
9
12
  return document.createTextNode(String(value ?? ""));
10
13
  }
@@ -59,7 +62,7 @@ function hydrateChild(parent, child) {
59
62
  return;
60
63
  }
61
64
  if (isVNodeLike(child)) {
62
- jsx(child.type, child.props);
65
+ renderVNodeLike(child);
63
66
  return;
64
67
  }
65
68
  if (typeof child === "function" && isSignal(child)) {
@@ -90,7 +93,7 @@ function insertChild(parent, child) {
90
93
  return;
91
94
  }
92
95
  if (isVNodeLike(child)) {
93
- parent.appendChild(jsx(child.type, child.props));
96
+ parent.appendChild(renderVNodeLike(child));
94
97
  return;
95
98
  }
96
99
  if (child instanceof Node) {
@@ -113,7 +116,7 @@ function insertChild(parent, child) {
113
116
  }
114
117
  export function jsx(type, props) {
115
118
  const allProps = props ?? {}, children = allProps.children, hydrating = isHydrating();
116
- if (type === Fragment) {
119
+ if (typeof type === "symbol") {
117
120
  if (hydrating) {
118
121
  if (children != null)
119
122
  hydrateChild(document.createDocumentFragment(), children);
@@ -129,7 +132,7 @@ export function jsx(type, props) {
129
132
  if (result instanceof Node)
130
133
  return result;
131
134
  if (isVNodeLike(result))
132
- return jsx(result.type, result.props);
135
+ return renderVNodeLike(result);
133
136
  if (isRenderableThunk(result)) {
134
137
  const resolved = result();
135
138
  if (hydrating) {
@@ -1,7 +1,7 @@
1
1
  import { hydrate } from "./client.js";
2
2
  let currentPath = "";
3
3
  const subscribers = [], beforeNavigateHooks = [];
4
- let navigating = !1, loadingElement = null, activeNavigationController = null, latestNavigationToken = 0, pendingPopScrollY = null, activeNavigationUrl = null, activeNavigationPushState = !0, lastFocusedPreserveKey = null, currentRouteScript = null;
4
+ let navigating = !1, loadingElement = null, activeNavigationController = null, latestNavigationToken = 0, pendingPopScrollY = null, activeNavigationUrl = null, activeNavigationPushState = !0, lastFocusedPreserveKey = null, currentRouteScript = null, initializedDocument = null, initializedWindow = null;
5
5
  export function createHTMLFragment(html) {
6
6
  const template = document.createElement("template");
7
7
  template.innerHTML = html;
@@ -373,13 +373,18 @@ export async function applyHMRUpdate(update) {
373
373
  }
374
374
  const prefetchCache = new Set, observedViewportPrefetchAnchors = new WeakSet;
375
375
  let viewportPrefetchObserver = null;
376
+ function normalizePrefetchURL(url) {
377
+ const [withoutHash] = url.split("#", 1);
378
+ return withoutHash || url;
379
+ }
376
380
  export function prefetch(url) {
377
- if (prefetchCache.has(url))
381
+ const normalizedURL = normalizePrefetchURL(url);
382
+ if (prefetchCache.has(normalizedURL))
378
383
  return;
379
- prefetchCache.add(url);
384
+ prefetchCache.add(normalizedURL);
380
385
  const link = document.createElement("link");
381
386
  link.rel = "prefetch";
382
- link.href = url;
387
+ link.href = normalizedURL;
383
388
  link.setAttribute("as", "fetch");
384
389
  link.setAttribute("crossorigin", "");
385
390
  document.head.appendChild(link);
@@ -425,7 +430,7 @@ function shouldHandleClick(e, anchor) {
425
430
  return !1;
426
431
  if (anchor.hasAttribute("download"))
427
432
  return !1;
428
- if (anchor.pathname === location.pathname && anchor.hash)
433
+ if (anchor.pathname === location.pathname && anchor.search === location.search && anchor.hash)
429
434
  return !1;
430
435
  return !0;
431
436
  }
@@ -446,31 +451,37 @@ export function initRouter() {
446
451
  gorseeScrollY: readCurrentScrollY()
447
452
  }, "", readLocationPath());
448
453
  }
449
- document.addEventListener("click", (e) => {
450
- const anchor = e.target.closest?.("a[href]");
451
- if (!anchor)
452
- return;
453
- if (anchor.dataset.gNoRouter !== void 0)
454
- return;
455
- if (shouldHandleClick(e, anchor)) {
456
- e.preventDefault();
457
- navigate(anchor.pathname + anchor.search);
458
- }
459
- });
460
- document.addEventListener("focusin", (e) => {
461
- const target = e.target, container = document.getElementById("app");
462
- if (!target || !container || !container.contains(target))
463
- return;
464
- const key = getFocusablePreserveKey(target);
465
- if (key)
466
- lastFocusedPreserveKey = key;
467
- });
468
- window.addEventListener("popstate", (e) => {
469
- const nextPath = readLocationPath();
470
- if (e.state?.gorsee || currentPath !== nextPath) {
471
- pendingPopScrollY = typeof e.state?.gorseeScrollY === "number" ? e.state.gorseeScrollY : 0;
472
- navigate(nextPath, !1);
473
- }
474
- });
454
+ if (initializedDocument !== document) {
455
+ initializedDocument = document;
456
+ document.addEventListener("click", (e) => {
457
+ const anchor = e.target.closest?.("a[href]");
458
+ if (!anchor)
459
+ return;
460
+ if (anchor.dataset.gNoRouter !== void 0)
461
+ return;
462
+ if (shouldHandleClick(e, anchor)) {
463
+ e.preventDefault();
464
+ navigate(anchor.pathname + anchor.search);
465
+ }
466
+ });
467
+ document.addEventListener("focusin", (e) => {
468
+ const target = e.target, container = document.getElementById("app");
469
+ if (!target || !container || !container.contains(target))
470
+ return;
471
+ const key = getFocusablePreserveKey(target);
472
+ if (key)
473
+ lastFocusedPreserveKey = key;
474
+ });
475
+ }
476
+ if (initializedWindow !== window) {
477
+ initializedWindow = window;
478
+ window.addEventListener("popstate", (e) => {
479
+ const nextPath = readLocationPath();
480
+ if (e.state?.gorsee || currentPath !== nextPath) {
481
+ pendingPopScrollY = typeof e.state?.gorseeScrollY === "number" ? e.state.gorseeScrollY : 0;
482
+ navigate(nextPath, !1);
483
+ }
484
+ });
485
+ }
475
486
  observeViewportPrefetchAnchors(document);
476
487
  }
@@ -14,6 +14,34 @@ function isCompressible(contentType) {
14
14
  const type = contentType.split(";")[0].trim();
15
15
  return COMPRESSIBLE_TYPES.has(type);
16
16
  }
17
+ function parseAcceptedEncodings(header) {
18
+ const accepted = new Map;
19
+ for (const rawEntry of header.split(",")) {
20
+ const entry = rawEntry.trim();
21
+ if (!entry)
22
+ continue;
23
+ const [rawName, ...params] = entry.split(";").map((part) => part.trim()), name = rawName?.toLowerCase();
24
+ if (!name)
25
+ continue;
26
+ const qParam = params.find((part) => part.startsWith("q=")), qValue = qParam ? Number(qParam.slice(2)) : 1;
27
+ if (!Number.isFinite(qValue))
28
+ continue;
29
+ accepted.set(name, Math.max(0, Math.min(qValue, 1)));
30
+ }
31
+ return accepted;
32
+ }
33
+ function resolveEncoding(header) {
34
+ const acceptedEncodings = parseAcceptedEncodings(header), wildcardWeight = acceptedEncodings.get("*"), candidates = ["gzip", "deflate"];
35
+ let best = null;
36
+ for (const encoding of candidates) {
37
+ const weight = acceptedEncodings.get(encoding) ?? wildcardWeight ?? 0;
38
+ if (weight <= 0)
39
+ continue;
40
+ if (!best || weight > best.weight)
41
+ best = { encoding, weight };
42
+ }
43
+ return best?.encoding ?? null;
44
+ }
17
45
  export function compress() {
18
46
  return async (_ctx, next) => {
19
47
  const response = await next(), contentType = response.headers.get("content-type");
@@ -23,8 +51,8 @@ export function compress() {
23
51
  return response;
24
52
  if (!response.body)
25
53
  return response;
26
- const acceptEncoding = _ctx.request.headers.get("accept-encoding") ?? "";
27
- if (acceptEncoding.includes("gzip")) {
54
+ const selectedEncoding = resolveEncoding(_ctx.request.headers.get("accept-encoding") ?? "");
55
+ if (selectedEncoding === "gzip") {
28
56
  const compressed = response.body.pipeThrough(new CompressionStream("gzip")), headers = new Headers(response.headers);
29
57
  headers.set("Content-Encoding", "gzip");
30
58
  headers.delete("Content-Length");
@@ -34,7 +62,7 @@ export function compress() {
34
62
  headers
35
63
  });
36
64
  }
37
- if (acceptEncoding.includes("deflate")) {
65
+ if (selectedEncoding === "deflate") {
38
66
  const compressed = response.body.pipeThrough(new CompressionStream("deflate")), headers = new Headers(response.headers);
39
67
  headers.set("Content-Encoding", "deflate");
40
68
  headers.delete("Content-Length");
@@ -3,8 +3,9 @@ export function defineJob(name, handler) {
3
3
  }
4
4
  export function createMemoryJobQueue() {
5
5
  const jobs = [];
6
+ let sequence = 0;
6
7
  function sortJobs() {
7
- jobs.sort((a, b) => a.runAt - b.runAt);
8
+ jobs.sort((a, b) => a.runAt - b.runAt || a.sequence - b.sequence);
8
9
  }
9
10
  return {
10
11
  async enqueue(job, payload, options = {}) {
@@ -16,7 +17,8 @@ export function createMemoryJobQueue() {
16
17
  attempts: 0,
17
18
  maxAttempts: options.maxAttempts ?? 3,
18
19
  backoffMs: options.backoffMs ?? 1000,
19
- job
20
+ job,
21
+ sequence: sequence++
20
22
  };
21
23
  jobs.push(enqueued);
22
24
  sortJobs();
@@ -115,8 +115,13 @@ export async function runMiddlewareChain(middlewares, ctx, handler) {
115
115
  }, response = await next();
116
116
  for (const [key, value] of ctx.responseHeaders.entries())
117
117
  if (key.toLowerCase() === "set-cookie")
118
- response.headers.append(key, value);
118
+ appendUniqueSetCookie(response.headers, value);
119
119
  else
120
120
  response.headers.set(key, value);
121
121
  return response;
122
122
  }
123
+ function appendUniqueSetCookie(headers, value) {
124
+ if ((typeof headers.getSetCookie === "function" ? headers.getSetCookie() : []).includes(value))
125
+ return;
126
+ headers.append("Set-Cookie", value);
127
+ }
@@ -1,6 +1,9 @@
1
1
  import { join } from "node:path";
2
2
  import { stat } from "node:fs/promises";
3
3
  import { wrapHTML } from "./html-shell.js";
4
+ function toRuntimeComponent(component) {
5
+ return component;
6
+ }
4
7
  export async function renderNotFoundPage(routesDir, nonce, options = {}) {
5
8
  const { title = "404 - Not Found", bodyPrefix, bodySuffix, headElements } = options;
6
9
  try {
@@ -10,7 +13,7 @@ export async function renderNotFoundPage(routesDir, nonce, options = {}) {
10
13
  continue;
11
14
  const mod = await import(notFoundPath);
12
15
  if (typeof mod.default === "function") {
13
- const { ssrJsx, renderToString } = await import("../runtime/server.js"), vnode = ssrJsx(mod.default, {}), body = renderToString(vnode);
16
+ const { ssrJsx, renderToString } = await import("../runtime/server.js"), vnode = ssrJsx(toRuntimeComponent(mod.default), {}), body = renderToString(vnode);
14
17
  return wrapHTML(body, nonce, { title, bodyPrefix, bodySuffix, headElements });
15
18
  }
16
19
  }
@@ -1,5 +1,8 @@
1
1
  import { resetServerHead, getServerHead } from "../runtime/head.js";
2
2
  import { renderToString, ssrJsx } from "../runtime/server.js";
3
+ function toRuntimeComponent(component) {
4
+ return component;
5
+ }
3
6
  function resolvePageDataHook(mod) {
4
7
  if (typeof mod.load === "function")
5
8
  return mod.load;
@@ -8,7 +11,7 @@ function resolvePageDataHook(mod) {
8
11
  return;
9
12
  }
10
13
  export async function resolvePageRoute(mod, match, ctx) {
11
- const component = mod.default;
14
+ const component = typeof mod.default === "function" ? mod.default : void 0;
12
15
  if (typeof component !== "function")
13
16
  return null;
14
17
  const layoutImportPromises = (match.route.layoutPaths ?? []).map((layoutPath) => import(layoutPath)), pageLoaderPromise = resolvePageDataHook(mod)?.(ctx), [layoutMods, loaderData] = await Promise.all([
@@ -21,9 +24,9 @@ export async function resolvePageRoute(mod, match, ctx) {
21
24
  cssFiles.push(...mod.css);
22
25
  let pageComponent = component;
23
26
  for (let i = layoutMods.length - 1;i >= 0; i--) {
24
- const Layout = layoutMods[i].default;
25
- if (typeof Layout === "function") {
26
- const inner = pageComponent, layoutData = layoutLoaderResults[i];
27
+ const layoutDefault = layoutMods[i]?.default;
28
+ if (typeof layoutDefault === "function") {
29
+ const Layout = layoutDefault, inner = pageComponent, layoutData = layoutLoaderResults[i];
27
30
  pageComponent = (props) => Layout({ ...props, data: layoutData, children: () => inner(props) });
28
31
  }
29
32
  }
@@ -40,7 +43,7 @@ export function createClientScriptPath(entryFile) {
40
43
  }
41
44
  export function renderPageDocument(pageComponent, ctx, params, loaderData) {
42
45
  resetServerHead();
43
- const vnode = ssrJsx(pageComponent, { params, ctx, data: loaderData }), html = renderToString(vnode), headElements = getServerHead();
46
+ const pageProps = { params, ctx, data: loaderData }, vnode = ssrJsx(toRuntimeComponent(pageComponent), pageProps), html = renderToString(vnode), headElements = getServerHead();
44
47
  return {
45
48
  html,
46
49
  headElements,
@@ -2,7 +2,7 @@ import { isAllowedRequestOrigin } from "./middleware.js";
2
2
  import { isPartialNavigationRequest } from "./partial-navigation.js";
3
3
  import { RPC_ACCEPTED_CONTENT_TYPES } from "./rpc.js";
4
4
  export function resolveRequestMetadata(request, options) {
5
- const url = new URL(request.url), endpointContract = resolveRequestExecutionPolicy(options.kind), urlProtocol = url.protocol.replace(/:$/, ""), trustedForwardedHops = options.trustForwardedHeaders === !0 ? Math.max(1, options.trustedForwardedHops ?? 1) : 0, forwarded = parseForwardedHeader(request.headers.get("forwarded"), trustedForwardedHops), forwardedHost = normalizeForwardedHost(getTrustedForwardedValue(request.headers.get("x-forwarded-host"), trustedForwardedHops) ?? forwarded.host) ?? forwarded.host ?? void 0, forwardedProto = normalizeForwardedProto(getTrustedForwardedValue(request.headers.get("x-forwarded-proto"), trustedForwardedHops) ?? forwarded.proto) ?? forwarded.proto ?? void 0, forwardedFor = normalizeForwardedFor(getTrustedForwardedValue(request.headers.get("x-forwarded-for"), trustedForwardedHops) ?? forwarded.for) ?? forwarded.for ?? void 0, proxyTrusted = options.trustForwardedHeaders === !0, effectiveHost = proxyTrusted ? forwardedHost ?? (request.headers.get("host") ?? url.host) : request.headers.get("host") ?? url.host, effectiveProto = proxyTrusted ? forwardedProto ?? urlProtocol : urlProtocol;
5
+ const url = new URL(request.url), endpointContract = resolveRequestExecutionPolicy(options.kind), urlProtocol = url.protocol.replace(/:$/, ""), trustedForwardedHops = options.trustForwardedHeaders === !0 ? Math.max(1, options.trustedForwardedHops ?? 1) : 0, forwarded = parseForwardedHeader(request.headers.get("forwarded"), trustedForwardedHops), trustedForwardedHost = normalizeForwardedHost(getTrustedForwardedValue(request.headers.get("x-forwarded-host"), trustedForwardedHops)), trustedForwardedProto = normalizeForwardedProto(getTrustedForwardedValue(request.headers.get("x-forwarded-proto"), trustedForwardedHops)), trustedForwardedFor = normalizeForwardedFor(getTrustedForwardedValue(request.headers.get("x-forwarded-for"), trustedForwardedHops)), forwardedHost = forwarded.host ?? trustedForwardedHost ?? void 0, forwardedProto = forwarded.proto ?? trustedForwardedProto ?? void 0, forwardedFor = forwarded.for ?? trustedForwardedFor ?? void 0, proxyTrusted = options.trustForwardedHeaders === !0, effectiveHost = proxyTrusted ? forwardedHost ?? (request.headers.get("host") ?? url.host) : request.headers.get("host") ?? url.host, effectiveProto = proxyTrusted ? forwardedProto ?? urlProtocol : urlProtocol;
6
6
  return {
7
7
  request,
8
8
  url,
@@ -7,10 +7,11 @@ export async function createRateLimitResponse(rateLimiter, key) {
7
7
  const result = await rateLimiter.check(key);
8
8
  if (result.allowed)
9
9
  return null;
10
+ const retryAfterSeconds = Math.max(0, Math.ceil((result.resetAt - Date.now()) / 1000));
10
11
  return new Response("Too Many Requests", {
11
12
  status: 429,
12
13
  headers: {
13
- "Retry-After": String(Math.ceil((result.resetAt - Date.now()) / 1000))
14
+ "Retry-After": String(retryAfterSeconds)
14
15
  }
15
16
  });
16
17
  }
@@ -12,7 +12,7 @@ export function createRequestSecurityPolicy(options = {}) {
12
12
  trustForwardedHeaders: options.trustForwardedHeaders === !0,
13
13
  trustedForwardedHops: options.trustForwardedHeaders === !0 ? Math.max(1, options.trustedForwardedHops ?? 1) : 0,
14
14
  trustedHosts: [...trustedHosts],
15
- enforceTrustedHosts: options.enforceTrustedHosts ?? explicitTrustedHosts.length > 0
15
+ enforceTrustedHosts: options.enforceTrustedHosts ?? (explicitTrustedHosts.length > 0 || options.trustForwardedHeaders === !0 && trustedHosts.size > 0)
16
16
  };
17
17
  }
18
18
  export function validateRequestSecurityPolicy(metadata, executionPolicy, securityPolicy) {
@@ -6,6 +6,9 @@ import {
6
6
  renderPageDocument
7
7
  } from "./page-render.js";
8
8
  import { partialNavigationHeaders } from "./partial-navigation.js";
9
+ function toRuntimeComponent(component) {
10
+ return component;
11
+ }
9
12
  export async function renderRoutePageResponse(options) {
10
13
  const { match, ctx, resolved, clientScript, nonce, secHeaders = {}, wrapHTML } = options, { pageComponent, loaderData, cssFiles, renderMode } = resolved, pageProps = { params: match.params, ctx, data: loaderData }, htmlOptions = {
11
14
  clientScript,
@@ -17,7 +20,7 @@ export async function renderRoutePageResponse(options) {
17
20
  if (!wrapHTML)
18
21
  throw Error("wrapHTML is required for streaming page responses");
19
22
  resetServerHead();
20
- const vnode = streamJsx(pageComponent, pageProps), stream = renderToStream(vnode, {
23
+ const vnode = streamJsx(toRuntimeComponent(pageComponent), pageProps), stream = renderToStream(vnode, {
21
24
  shell: (body) => wrapHTML(body, nonce, {
22
25
  ...htmlOptions,
23
26
  headElements: getServerHead()
@@ -47,10 +50,10 @@ export function renderRoutePartialResponse(options) {
47
50
  });
48
51
  }
49
52
  export async function renderRouteErrorBoundaryResponse(errorPath, error, options) {
50
- const { match, nonce, secHeaders = {}, wrapHTML } = options, ErrorComponent = (await import(errorPath)).default;
53
+ const { match, nonce, secHeaders = {}, wrapHTML } = options, errorMod = await import(errorPath), ErrorComponent = typeof errorMod.default === "function" ? errorMod.default : void 0;
51
54
  if (typeof ErrorComponent !== "function")
52
55
  throw error;
53
- const vnode = ssrJsx(ErrorComponent, { error, params: match.params }), body = renderToString(vnode), html = wrapHTML(body, nonce, { title: "Error" });
56
+ const vnode = ssrJsx(toRuntimeComponent(ErrorComponent), { error, params: match.params }), body = renderToString(vnode), html = wrapHTML(body, nonce, { title: "Error" });
54
57
  return new Response(html, {
55
58
  status: 500,
56
59
  headers: { "Content-Type": "text/html", ...secHeaders }
@@ -25,10 +25,10 @@ export async function serveStaticFile(rootDir, relativePath, options = {}) {
25
25
  headers["Cache-Control"] = cacheControl;
26
26
  if (etag) {
27
27
  const tag = await fileETag(filePath);
28
- if (tag && request && isNotModified(request, tag))
29
- return new Response(null, { status: 304, headers: extraHeaders });
30
28
  if (tag)
31
29
  headers.ETag = tag;
30
+ if (tag && request && isNotModified(request, tag))
31
+ return new Response(null, { status: 304, headers });
32
32
  }
33
33
  return new Response(fileBuffer, { headers });
34
34
  } catch {
@@ -10,6 +10,9 @@ import { createContext, runMiddlewareChain } from "../server/middleware.js";
10
10
  import { createPluginRunner } from "../plugins/index.js";
11
11
  import { renderToString, ssrJsx } from "../runtime/server.js";
12
12
  import { navigate } from "../runtime/router.js";
13
+ function toRuntimeComponent(component) {
14
+ return component;
15
+ }
13
16
  export function createTestRequest(path, options = {}) {
14
17
  const { method = "GET", headers = {}, body } = options, url = `http://localhost${path}`, init = { method, headers };
15
18
  if (body)
@@ -28,7 +31,7 @@ export async function runTestMiddleware(middleware, ctx, handler) {
28
31
  return runMiddlewareChain([middleware], ctx, handler ?? (async () => new Response("OK")));
29
32
  }
30
33
  export function renderComponent(component, props = {}) {
31
- const vnode = ssrJsx(component, props);
34
+ const vnode = ssrJsx(toRuntimeComponent(component), props);
32
35
  return renderToString(vnode);
33
36
  }
34
37
  export async function testLoader(loader, path, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gorsee",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "AI-first reactive full-stack TypeScript framework for deterministic human and agent collaboration",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.9",
@@ -64,15 +64,21 @@
64
64
  ],
65
65
  "scripts": {
66
66
  "test": "bun test",
67
+ "test:critical-surface": "bun test tests/server/compress.test.ts tests/server/request-security-policy.test.ts tests/server/request-preflight.test.ts tests/server/route-request-security.test.ts tests/reactive/race-contracts.test.ts tests/runtime/client-runtime-negative.test.ts tests/runtime/client-hydration-recovery.test.ts tests/runtime/router-navigation.test.ts tests/ai/mcp.test.ts tests/cli/install-matrix.test.ts tests/cli/release-surface.test.ts",
67
68
  "test:security": "bun test tests/security tests/server/request-policy.test.ts tests/server/request-security-policy.test.ts tests/server/request-preflight.test.ts tests/server/route-request-security.test.ts tests/server/runtime-dispatch.test.ts tests/dev/hmr-security.test.ts tests/integration/dev-prod-parity.test.ts tests/integration/production-runtime.test.ts",
68
69
  "test:confidence": "bun test tests/build/entrypoints-boundary.test.ts tests/integration/dev-prod-auth-cache-parity.test.ts tests/integration/dev-prod-cache-rpc-parity.test.ts tests/integration/production-large-app-runtime.test.ts tests/integration/canonical-recipes-runtime.test.ts",
69
70
  "test:provider-smoke": "bun test tests/deploy/provider-smoke.test.ts",
70
71
  "test:browser-smoke": "node scripts/browser-smoke.mjs",
71
72
  "product:policy": "node scripts/product-policy-check.mjs",
73
+ "dependency:policy": "node scripts/dependency-contract-check.mjs",
74
+ "deploy:policy": "node scripts/deploy-contract-check.mjs",
75
+ "api:policy": "node scripts/api-stability-check.mjs",
76
+ "adoption:policy": "node scripts/adoption-proof-check.mjs",
72
77
  "ai:policy": "node scripts/ai-policy-check.mjs",
73
78
  "dx:policy": "node scripts/dx-policy-check.mjs",
74
79
  "maturity:policy": "node scripts/maturity-policy-check.mjs",
75
80
  "runtime:policy": "node scripts/runtime-diagnostics-policy-check.mjs",
81
+ "runtime:security:policy": "node scripts/runtime-security-contract-check.mjs",
76
82
  "compiler:audit": "node scripts/compiler-platform-audit.mjs",
77
83
  "compiler:parity": "node scripts/compiler-backend-parity.mjs",
78
84
  "compiler:canary": "node scripts/compiler-backend-parity.mjs && bun test tests/cli/programmatic-runtime.test.ts tests/compiler/init.test.ts",
@@ -93,9 +99,12 @@
93
99
  "benchmarks:realworld:check": "node scripts/realworld-benchmark-check.mjs",
94
100
  "examples:policy": "node scripts/examples-policy-check.mjs",
95
101
  "proof:policy": "node scripts/proof-surface-check.mjs",
102
+ "critical:surface": "node scripts/critical-surface-check.mjs",
103
+ "top-tier:exit": "node scripts/top-tier-exit-check.mjs",
104
+ "coverage:audit": "node scripts/coverage-audit-check.mjs",
96
105
  "repo:policy": "node scripts/repo-policy-check.mjs",
97
106
  "ci:policy": "node scripts/ci-policy-check.mjs",
98
- "verify:security": "bun run check && bun run product:policy && bun run ai:policy && bun run dx:policy && bun run maturity:policy && bun run runtime:policy && bun run benchmarks:policy && bun run benchmarks:realworld:check && bun run examples:policy && bun run proof:policy && bun run repo:policy && bun run ci:policy && bun run compiler:promotion:check && bun run build:promotion:check && bun run backend:switch:evidence:check && bun run backend:default-switch:review:check && bun run backend:candidate:rollout:check && bun run compiler:default:rehearsal:check && bun run build:default:rehearsal:check && bun run test:security && bun run test:confidence",
107
+ "verify:security": "bun run check && bun run product:policy && bun run dependency:policy && bun run deploy:policy && bun run api:policy && bun run adoption:policy && bun run ai:policy && bun run dx:policy && bun run maturity:policy && bun run top-tier:exit && bun run runtime:policy && bun run runtime:security:policy && bun run benchmarks:policy && bun run benchmarks:realworld:check && bun run examples:policy && bun run proof:policy && bun run critical:surface && bun run coverage:audit && bun run repo:policy && bun run ci:policy && bun run compiler:promotion:check && bun run build:promotion:check && bun run backend:switch:evidence:check && bun run backend:default-switch:review:check && bun run backend:candidate:rollout:check && bun run compiler:default:rehearsal:check && bun run build:default:rehearsal:check && bun run test:security && bun run test:critical-surface && bun run test:confidence",
99
108
  "check": "tsc --noEmit",
100
109
  "install:matrix": "node scripts/install-matrix-check.mjs",
101
110
  "dev": "bun run src/dev.ts",