gorsee 0.2.5 → 0.2.7
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 +23 -0
- package/dist-pkg/ai/mcp.d.ts +1 -1
- package/dist-pkg/ai/mcp.js +24 -3
- package/dist-pkg/cli/cmd-create.js +8 -8
- package/dist-pkg/deploy/cloudflare.js +22 -4
- package/dist-pkg/i18n/index.js +1 -1
- package/dist-pkg/plugins/drizzle.js +4 -1
- package/dist-pkg/plugins/index.js +15 -3
- package/dist-pkg/plugins/prisma.js +4 -1
- package/dist-pkg/reactive/live.js +15 -3
- package/dist-pkg/reactive/optimistic.js +17 -4
- package/dist-pkg/reactive/resource.js +64 -8
- package/dist-pkg/runtime/client.js +15 -4
- package/dist-pkg/runtime/event-replay.js +16 -3
- package/dist-pkg/runtime/form.js +7 -1
- package/dist-pkg/runtime/island-hydrator.js +33 -8
- package/dist-pkg/runtime/jsx-runtime.d.ts +2 -2
- package/dist-pkg/runtime/jsx-runtime.js +7 -4
- package/dist-pkg/runtime/router.js +42 -31
- package/dist-pkg/server/compress.js +31 -3
- package/dist-pkg/server/jobs.js +4 -2
- package/dist-pkg/server/middleware.js +6 -1
- package/dist-pkg/server/not-found.js +4 -1
- package/dist-pkg/server/page-render.js +8 -5
- package/dist-pkg/server/request-policy.js +1 -1
- package/dist-pkg/server/request-preflight.js +2 -1
- package/dist-pkg/server/request-security-policy.js +1 -1
- package/dist-pkg/server/route-response.js +6 -3
- package/dist-pkg/server/static-file.js +2 -2
- package/dist-pkg/testing/index.js +4 -1
- package/package.json +11 -2
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
|
|
package/dist-pkg/ai/mcp.d.ts
CHANGED
package/dist-pkg/ai/mcp.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 {
|
|
@@ -191,7 +191,7 @@ dist/
|
|
|
191
191
|
*.sqlite
|
|
192
192
|
*.db
|
|
193
193
|
.DS_Store
|
|
194
|
-
`, PACKAGE_MANAGER = "bun@1.3.9", CREATE_TEMPLATES = ["basic", "secure-saas", "content-site", "agent-aware-ops", "workspace-monorepo"],
|
|
194
|
+
`, PACKAGE_MANAGER = "bun@1.3.9", REPO_ROOT = fileURLToPath(new URL("../../", import.meta.url)), FRAMEWORK_VERSION = JSON.parse(await readFile(join(REPO_ROOT, "package.json"), "utf-8")), FRAMEWORK_PACKAGE_VERSION = FRAMEWORK_VERSION.version, CREATE_TEMPLATES = ["basic", "secure-saas", "content-site", "agent-aware-ops", "workspace-monorepo"], TEMPLATE_DEFINITIONS = {
|
|
195
195
|
"secure-saas": {
|
|
196
196
|
sourceDir: join(REPO_ROOT, "examples/secure-saas"),
|
|
197
197
|
readmeDescription: "Authenticated SaaS starter with protected route groups, explicit auth middleware, and private cache semantics.",
|
|
@@ -244,7 +244,7 @@ dist/
|
|
|
244
244
|
webPackageJson.packageManager = PACKAGE_MANAGER;
|
|
245
245
|
webPackageJson.scripts = createPackageScripts();
|
|
246
246
|
webPackageJson.dependencies = {
|
|
247
|
-
gorsee:
|
|
247
|
+
gorsee: FRAMEWORK_PACKAGE_VERSION,
|
|
248
248
|
"@workspace/shared": "workspace:*"
|
|
249
249
|
};
|
|
250
250
|
await writeFile(webPackagePath, JSON.stringify(webPackageJson, null, 2) + `
|
|
@@ -263,10 +263,10 @@ dist/
|
|
|
263
263
|
};
|
|
264
264
|
function createPackageScripts() {
|
|
265
265
|
return {
|
|
266
|
-
dev: "gorsee dev",
|
|
267
|
-
build: "gorsee build",
|
|
268
|
-
start: "gorsee start",
|
|
269
|
-
check: "gorsee check"
|
|
266
|
+
dev: "bun run ./node_modules/gorsee/dist-pkg/bin/gorsee.js dev",
|
|
267
|
+
build: "bun run ./node_modules/gorsee/dist-pkg/bin/gorsee.js build",
|
|
268
|
+
start: "bun run ./node_modules/gorsee/dist-pkg/bin/gorsee.js start",
|
|
269
|
+
check: "bun run ./node_modules/gorsee/dist-pkg/bin/gorsee.js check"
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
272
|
function normalizePackageName(name) {
|
|
@@ -337,7 +337,7 @@ async function normalizeSinglePackageManifest(packageJsonPath, packageName) {
|
|
|
337
337
|
packageJson.scripts = createPackageScripts();
|
|
338
338
|
packageJson.dependencies = {
|
|
339
339
|
...packageJson.dependencies ?? {},
|
|
340
|
-
gorsee:
|
|
340
|
+
gorsee: FRAMEWORK_PACKAGE_VERSION
|
|
341
341
|
};
|
|
342
342
|
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + `
|
|
343
343
|
`);
|
|
@@ -408,7 +408,7 @@ async function createBasicProject(dir, name) {
|
|
|
408
408
|
packageManager: PACKAGE_MANAGER,
|
|
409
409
|
scripts: createPackageScripts(),
|
|
410
410
|
dependencies: {
|
|
411
|
-
gorsee:
|
|
411
|
+
gorsee: FRAMEWORK_PACKAGE_VERSION
|
|
412
412
|
},
|
|
413
413
|
devDependencies: {
|
|
414
414
|
"@types/bun": "1.3.10"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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" },
|
package/dist-pkg/i18n/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/dist-pkg/runtime/form.js
CHANGED
|
@@ -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
|
-
}),
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 |
|
|
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 |
|
|
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
|
-
|
|
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(
|
|
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 ===
|
|
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
|
|
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
|
-
|
|
381
|
+
const normalizedURL = normalizePrefetchURL(url);
|
|
382
|
+
if (prefetchCache.has(normalizedURL))
|
|
378
383
|
return;
|
|
379
|
-
prefetchCache.add(
|
|
384
|
+
prefetchCache.add(normalizedURL);
|
|
380
385
|
const link = document.createElement("link");
|
|
381
386
|
link.rel = "prefetch";
|
|
382
|
-
link.href =
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
27
|
-
if (
|
|
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 (
|
|
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");
|
package/dist-pkg/server/jobs.js
CHANGED
|
@@ -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
|
|
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
|
|
25
|
-
if (typeof
|
|
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
|
|
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),
|
|
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(
|
|
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,
|
|
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.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|