@junejs/server 1.0.0-dev.29 → 1.0.0-dev.42
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/dist/agent-compile.d.ts +8 -9
- package/dist/agent-discover.d.ts +3 -4
- package/dist/agent-durable.d.ts +38 -20
- package/dist/agent-durable.js +159 -13
- package/dist/agent-native.d.ts +15 -12
- package/dist/agent-native.js +72 -8
- package/dist/app.d.ts +4 -5
- package/dist/app.js +2 -1
- package/dist/blob.d.ts +4 -5
- package/dist/build.d.ts +6 -6
- package/dist/build.js +38 -7
- package/dist/client-bundle.d.ts +8 -9
- package/dist/{config-freeze-DwI-Rmvq.d.ts → config-freeze-ZWC_Qd4h.d.ts} +1 -1
- package/dist/config-freeze.d.ts +1 -1
- package/dist/config-loader.d.ts +3 -4
- package/dist/connection-auth.d.ts +8 -9
- package/dist/content.d.ts +9 -10
- package/dist/core-version.d.ts +3 -4
- package/dist/core-version.js +1 -1
- package/dist/css-modules-ambient.d.ts +2 -1
- package/dist/css-modules.d.ts +10 -11
- package/dist/css.d.ts +9 -10
- package/dist/d1-migrate.d.ts +6 -7
- package/dist/d1-migrate.js +3 -2
- package/dist/db.d.ts +2 -2
- package/dist/deploy.d.ts +3 -4
- package/dist/deploy.js +3 -2
- package/dist/dev-reload.d.ts +3 -4
- package/dist/dev.d.ts +4 -5
- package/dist/favicon.d.ts +16 -17
- package/dist/host.d.ts +5 -5
- package/dist/icon-letter.d.ts +2 -3
- package/dist/index.d.ts +4 -4
- package/dist/instrumentation.d.ts +2 -3
- package/dist/instrumentation.js +2 -1
- package/dist/island-registry.d.ts +6 -7
- package/dist/isolate-local.d.ts +4 -5
- package/dist/kv.d.ts +3 -4
- package/dist/llms-links.d.ts +8 -5
- package/dist/llms-links.js +31 -1
- package/dist/{manifest-gcNWqWoI.d.ts → manifest-DSNX4I-G.d.ts} +2 -2
- package/dist/manifest.d.ts +1 -1
- package/dist/negotiate.d.ts +4 -3
- package/dist/negotiate.js +24 -14
- package/dist/{pipeline-cCR6MGNK.d.ts → pipeline-BHUjBO0G.d.ts} +5 -1
- package/dist/pipeline.d.ts +2 -2
- package/dist/pipeline.js +260 -41
- package/dist/resources.d.ts +5 -6
- package/dist/router.d.ts +12 -13
- package/dist/rsc-build.d.ts +6 -7
- package/dist/rsc-bundle.d.ts +6 -7
- package/dist/rsc-manifest.d.ts +5 -6
- package/dist/rsc-runtime/flight-render-routes.d.ts +3 -4
- package/dist/rsc-runtime/flight-render.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.js +3 -2
- package/dist/rsc-runtime/rsdw-shims.d.ts +2 -1
- package/dist/segment.d.ts +4 -5
- package/dist/sqlite-driver.d.ts +10 -11
- package/dist/static-files.d.ts +4 -5
- package/dist/tsconfig-jsx.d.ts +4 -5
- package/dist/turn-events.d.ts +2 -3
- package/dist/web-manifest.d.ts +3 -4
- package/dist/web-manifest.js +2 -6
- package/dist/{worker-BkFHGtxp.d.ts → worker-Dv4l6Ybs.d.ts} +4 -2
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +35 -18
- package/package.json +5 -5
- package/src/agent-durable.ts +203 -21
- package/src/agent-native.ts +94 -7
- package/src/build.ts +67 -5
- package/src/core-version.ts +1 -1
- package/src/llms-links.ts +46 -1
- package/src/negotiate.ts +39 -15
- package/src/pipeline.ts +433 -45
- package/src/web-manifest.ts +2 -6
- package/src/worker.ts +55 -17
package/src/build.ts
CHANGED
|
@@ -314,7 +314,9 @@ export async function juneBuild(
|
|
|
314
314
|
// The Link header is frozen here from the same builder the pipeline uses, so
|
|
315
315
|
// the static and dynamic surfaces advertise identically. The adapter wraps the
|
|
316
316
|
// portable pipeline for its target (workers() → withAssets).
|
|
317
|
-
|
|
317
|
+
// (catalogs: the same publication rule as the pipeline's catalogOrigin — a
|
|
318
|
+
// basePath site publishes no /.well-known catalog, so it links none.)
|
|
319
|
+
const linkHeader = buildLinkHeader(frozen.agent, { catalogs: !frozen.document.basePath });
|
|
318
320
|
const adapterEntry = adapter.entry({ linkHeader });
|
|
319
321
|
|
|
320
322
|
// Resources (when declared) are bound from the worker's env (env.DB → D1) by
|
|
@@ -415,6 +417,10 @@ export class JuneAgentDO extends DurableObject {
|
|
|
415
417
|
fetch(req: Request): Promise<Response> {
|
|
416
418
|
return this.#agent.fetch(req);
|
|
417
419
|
}
|
|
420
|
+
// Delivers input announcements left undelivered — a failed hook's retry, or the watchdog (#260).
|
|
421
|
+
alarm(): Promise<void> {
|
|
422
|
+
return this.#agent.alarm();
|
|
423
|
+
}
|
|
418
424
|
}
|
|
419
425
|
`
|
|
420
426
|
: "";
|
|
@@ -506,7 +512,16 @@ ${doClass}`;
|
|
|
506
512
|
if (cssAsset) manifest.document.styles = `/${cssAsset}`; // prerendered HTML links the hashed sheet
|
|
507
513
|
if (clientAsset) manifest.document.clientScript = `/${clientAsset}`;
|
|
508
514
|
if (moduleCssAsset) manifest.document.moduleStyles = `/${moduleCssAsset}`;
|
|
509
|
-
|
|
515
|
+
// A static site has no server: /mcp (and the WebMCP tools that proxy to it)
|
|
516
|
+
// and the HTTP API (POST /api/<id>, /openapi.json) do not exist there. Render
|
|
517
|
+
// with both projected out, so the published files advertise only what is
|
|
518
|
+
// deployed: llms.txt drops its MCP and HTTP API sections, the catalogs and the
|
|
519
|
+
// generated skill list neither, pages register no WebMCP tools.
|
|
520
|
+
const isStatic = adapter.capabilities.runtime === "static";
|
|
521
|
+
if (isStatic) manifest.agent = { ...manifest.agent, mcp: false, api: false, webmcp: false };
|
|
522
|
+
// The static() target prerenders its sitemap too; that build is where the
|
|
523
|
+
// route contract lets `staticPaths` run, so its sitemap lists those pages.
|
|
524
|
+
const worker = createWorker(manifest, { staticBuild: isStatic });
|
|
510
525
|
let hasAssets = false;
|
|
511
526
|
|
|
512
527
|
// ---- public/ → assets/ : verbatim static files (favicon, images, fonts) ----
|
|
@@ -567,7 +582,6 @@ ${doClass}`;
|
|
|
567
582
|
// dynamic routes via their staticPaths, and write <stem>/index.html so clean URLs
|
|
568
583
|
// resolve on a dumb file host with no rewrite server. Other targets keep the
|
|
569
584
|
// opt-in `prerender: true` behavior and the flat <stem>.html naming (byte-identical).
|
|
570
|
-
const isStatic = adapter.capabilities.runtime === "static";
|
|
571
585
|
const i18n = frozen.i18n;
|
|
572
586
|
|
|
573
587
|
// Render ONE route pathname through the worker → HTML (+ .md/.json projections).
|
|
@@ -604,6 +618,17 @@ ${doClass}`;
|
|
|
604
618
|
|
|
605
619
|
// Locale variants a static route is emitted at: no i18n → [path]; with i18n → one
|
|
606
620
|
// per locale (defaultLocale keeps the bare path, others get their localeHref prefix).
|
|
621
|
+
// A static build is ONE file tree on ONE host: a locale on its own domain has no
|
|
622
|
+
// path in it (and a basePath can't apply there), so say so instead of failing
|
|
623
|
+
// later with a prerender 404.
|
|
624
|
+
if (isStatic && i18n) {
|
|
625
|
+
const onDomain = Object.keys(i18n.locales).filter((l) => i18n.locales[l]!.domain);
|
|
626
|
+
if (onDomain.length) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`[june build] the static() target serves one host, so i18n locales with their own domain are not supported (${onDomain.join(", ")}). Give them a path prefix instead, e.g. { path: "/${onDomain[0]}" }.`,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
607
632
|
const localeVariants = (path: string): string[] =>
|
|
608
633
|
isStatic && i18n
|
|
609
634
|
? [...new Set(Object.keys(i18n.locales).map((l) => localeHref(i18n, path, l)))]
|
|
@@ -624,6 +649,9 @@ ${doClass}`;
|
|
|
624
649
|
const sp = dyn.def.staticPaths;
|
|
625
650
|
if (!sp) continue;
|
|
626
651
|
const paths = typeof sp === "function" ? await sp() : sp;
|
|
652
|
+
// Resolve a producer ONCE: the prerendered sitemap below reads this same
|
|
653
|
+
// def, so it lists exactly the set prerendered here without a second call.
|
|
654
|
+
dyn.def.staticPaths = paths;
|
|
627
655
|
for (const p of paths) await prerenderOne(p, dyn.def);
|
|
628
656
|
}
|
|
629
657
|
// Framework surfaces the worker would otherwise generate on the fly. Guarded on
|
|
@@ -635,11 +663,45 @@ ${doClass}`;
|
|
|
635
663
|
["/llms.txt", "llms.txt"],
|
|
636
664
|
["/sitemap.xml", "sitemap.xml"],
|
|
637
665
|
];
|
|
638
|
-
|
|
666
|
+
// A sitemap needs absolute URLs, and the prerender host is a placeholder: with
|
|
667
|
+
// no public origin configured the pipeline answers 404 for it (and the loop
|
|
668
|
+
// below skips it), so say why rather than silently ship no sitemap.
|
|
669
|
+
if (manifest.agent.discovery && !manifest.document.site.url && !manifest.document.deployOrigin) {
|
|
670
|
+
console.warn(
|
|
671
|
+
"[june build] no sitemap.xml: set site.url (or deploy.domain) in june.config.ts so its URLs are absolute.",
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
// The agent catalogs (ARD, Agent Skills) — JSON/Markdown files a static host
|
|
675
|
+
// can serve as-is — only when this site publishes them: agent discovery on, a
|
|
676
|
+
// root deploy (a basePath site doesn't own /.well-known), and a known public
|
|
677
|
+
// origin (the files name absolute URLs). Same rule as the pipeline's
|
|
678
|
+
// catalogOrigin, stated here too so an app catch-all route can never answer
|
|
679
|
+
// these paths into the build. The generated skill's path carries its name, so
|
|
680
|
+
// it is read back from the index.
|
|
681
|
+
const doc = manifest.document;
|
|
682
|
+
if (manifest.agent.discovery && !doc.basePath && (doc.site.url || doc.deployOrigin)) {
|
|
683
|
+
extra.push(
|
|
684
|
+
["/.well-known/ai-catalog.json", ".well-known/ai-catalog.json"],
|
|
685
|
+
["/.well-known/ard.json", ".well-known/ard.json"],
|
|
686
|
+
["/.well-known/agent-skills/index.json", ".well-known/agent-skills/index.json"],
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
for (let i = 0; i < extra.length; i++) {
|
|
690
|
+
const [reqPath, file] = extra[i]!;
|
|
639
691
|
const res = await worker.fetch(new Request(`${PRERENDER_ORIGIN}${reqPath}`));
|
|
640
692
|
if (!res.ok) continue;
|
|
641
|
-
|
|
693
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
694
|
+
await mkdir(dirname(join(assetsDir, file)), { recursive: true });
|
|
695
|
+
await writeFile(join(assetsDir, file), bytes);
|
|
642
696
|
hasAssets = true;
|
|
697
|
+
if (file.endsWith("agent-skills/index.json")) {
|
|
698
|
+
const index = JSON.parse(bytes.toString("utf8")) as { skills?: Array<{ url?: string }> };
|
|
699
|
+
for (const s of index.skills ?? []) {
|
|
700
|
+
if (!s.url) continue;
|
|
701
|
+
const p = new URL(s.url, PRERENDER_ORIGIN).pathname;
|
|
702
|
+
extra.push([p, p.slice(1)]);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
643
705
|
}
|
|
644
706
|
// 404.html — GitHub Pages serves it for any unmatched URL. A deliberately-missing
|
|
645
707
|
// path renders June's not-found HTML (body is written regardless of the 404 status).
|
package/src/core-version.ts
CHANGED
|
@@ -15,7 +15,7 @@ import * as coreRuntime from "@junejs/core/agent-runtime";
|
|
|
15
15
|
|
|
16
16
|
// The contract number this server tree is built against. Bump in lockstep with
|
|
17
17
|
// core's RUNTIME_API_VERSION whenever the server↔core runtime contract changes.
|
|
18
|
-
const EXPECTED_CORE_RUNTIME_API =
|
|
18
|
+
const EXPECTED_CORE_RUNTIME_API = 6;
|
|
19
19
|
|
|
20
20
|
export function assertCoreRuntimeVersion(surface: string): void {
|
|
21
21
|
checkCoreRuntimeVersion(surface, (coreRuntime as { RUNTIME_API_VERSION?: number }).RUNTIME_API_VERSION);
|
package/src/llms-links.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// template an agent can't fetch — lists each real page it serves, or is skipped.
|
|
5
5
|
// Links point at the markdown projection (<path>.md) unless the route turned md off,
|
|
6
6
|
// so an agent reads the page rather than its HTML (llmstxt.org's recommendation).
|
|
7
|
-
import type { LlmsLink } from "@junejs/core/discovery";
|
|
7
|
+
import type { LlmsLink, SitemapPage } from "@junejs/core/discovery";
|
|
8
8
|
import type { LlmsEntry } from "@junejs/core/route";
|
|
9
9
|
|
|
10
10
|
import type { RouteResolver } from "./pipeline";
|
|
@@ -49,3 +49,48 @@ export async function collectLlmsLinks(origin: string, routes: string[], resolve
|
|
|
49
49
|
}
|
|
50
50
|
return links;
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// /sitemap.xml pages, enumerated like /llms.txt. A static route is one page; a
|
|
54
|
+
// dynamic route lists its real pages via its `llms` entries (their `lastModified`
|
|
55
|
+
// becomes <lastmod>) — the runtime-safe hook /llms.txt already runs. A route
|
|
56
|
+
// that names none stays out: a [param] template isn't a URL. Resource routes
|
|
57
|
+
// (route.*) are machine endpoints, not pages. `llms = false` keeps a static
|
|
58
|
+
// route listed (its URL is known without entries); on a dynamic route it leaves
|
|
59
|
+
// no entries, so its pages drop out of the runtime sitemap too.
|
|
60
|
+
// `staticPaths` join in only when opts.staticPaths is set — the static() build's
|
|
61
|
+
// prerender, the one place the route contract runs them. Even then they're
|
|
62
|
+
// skipped under i18n: they arrive locale-prefixed, while the sitemap derives
|
|
63
|
+
// each page's locale variants from its canonical path.
|
|
64
|
+
export async function collectSitemapPages(
|
|
65
|
+
routes: string[],
|
|
66
|
+
resolve: RouteResolver,
|
|
67
|
+
opts: { i18n?: boolean; staticPaths?: boolean } = {},
|
|
68
|
+
): Promise<SitemapPage[]> {
|
|
69
|
+
const pages = new Map<string, SitemapPage>();
|
|
70
|
+
const add = (path: string, lastModified?: string | Date) => {
|
|
71
|
+
const key = path === "/" ? "/" : path.replace(/\/+$/, "");
|
|
72
|
+
const prev = pages.get(key);
|
|
73
|
+
if (!prev) pages.set(key, lastModified ? { path: key, lastModified } : { path: key });
|
|
74
|
+
else if (lastModified && !prev.lastModified) prev.lastModified = lastModified;
|
|
75
|
+
};
|
|
76
|
+
for (const route of routes) {
|
|
77
|
+
const resolved = await resolve(route);
|
|
78
|
+
if (!resolved || !("def" in resolved)) continue;
|
|
79
|
+
const { def } = resolved;
|
|
80
|
+
const dynamic = route.includes("[");
|
|
81
|
+
const declared = def.llms ? (typeof def.llms === "function" ? await def.llms() : def.llms) : undefined;
|
|
82
|
+
const entries: LlmsEntry[] = declared ? (Array.isArray(declared) ? declared : [declared]) : [];
|
|
83
|
+
// A static route is exactly one URL — its own. An entry naming another path
|
|
84
|
+
// (an llms.txt pointer elsewhere) is not a page this route serves.
|
|
85
|
+
if (!dynamic) {
|
|
86
|
+
add(route, entries.find((e) => (e.path ?? route) === route)?.lastModified);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const e of entries) if (e.path) add(e.path, e.lastModified);
|
|
90
|
+
if (opts.staticPaths && !opts.i18n && def.staticPaths) {
|
|
91
|
+
const sp = typeof def.staticPaths === "function" ? await def.staticPaths() : def.staticPaths;
|
|
92
|
+
for (const p of sp) add(p);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...pages.values()];
|
|
96
|
+
}
|
package/src/negotiate.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// header — a link is unambiguous; Accept is a hint. The clean pathname (with
|
|
7
7
|
// the projection extension stripped) is what the router matches.
|
|
8
8
|
|
|
9
|
+
import { FRAGMENT_ACCEPT } from "@junejs/core/nav-protocol";
|
|
9
10
|
import type { RenderTarget } from "@junejs/core/route";
|
|
10
11
|
|
|
11
12
|
// The fragment media type + title header are the client-router wire protocol —
|
|
@@ -18,12 +19,43 @@ const EXT_TARGET: Record<string, RenderTarget> = {
|
|
|
18
19
|
".md": "md",
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// RFC 9110 §12.4.2 qvalue: 0–1 with at most three decimals ("0", "0.5", "1.000").
|
|
23
|
+
const QVALUE = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
|
|
24
|
+
|
|
25
|
+
// Parse an Accept header into media-range → q (RFC 9110 §12.5.1). A q outside the
|
|
26
|
+
// qvalue grammar ("2", "-0.5", "0.1234") is malformed and reads as the default 1:
|
|
27
|
+
// the client still listed the type, so it stays acceptable at the default weight
|
|
28
|
+
// rather than being dropped or excluded. Parameters other than q are ignored.
|
|
29
|
+
function acceptRanges(accept: string): Map<string, number> {
|
|
30
|
+
const ranges = new Map<string, number>();
|
|
31
|
+
for (const part of accept.split(",")) {
|
|
32
|
+
const [type, ...params] = part.split(";").map((s) => s.trim().toLowerCase());
|
|
33
|
+
if (!type) continue;
|
|
34
|
+
const raw = params.find((p) => p.startsWith("q="))?.slice(2).trim();
|
|
35
|
+
ranges.set(type, raw !== undefined && QVALUE.test(raw) ? Number(raw) : 1);
|
|
36
|
+
}
|
|
37
|
+
return ranges;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The projection an Accept header asks for — null when it asks for none of the
|
|
41
|
+
// agent projections (→ the HTML view). ONE decision shared by the pipeline and
|
|
42
|
+
// the deployed worker's asset layer (withAssets), so the two can't disagree.
|
|
43
|
+
// - The client router's fragment media type (exact range, q > 0) wins outright.
|
|
44
|
+
// - Markdown / JSON are chosen when listed with q > 0 and at least as preferred
|
|
45
|
+
// as HTML; HTML's quality is text/html's, else the text/* or */* wildcard's.
|
|
46
|
+
// So `text/markdown, text/html;q=0.9` → md, `text/html, text/markdown;q=0.5`
|
|
47
|
+
// → HTML, and a plain browser Accept (no markdown/json) → HTML.
|
|
48
|
+
// - Ties go to the agent projection (md before json), as they always have.
|
|
49
|
+
export function acceptTarget(accept: string): RenderTarget | null {
|
|
50
|
+
const ranges = acceptRanges(accept);
|
|
51
|
+
if ((ranges.get(FRAGMENT_ACCEPT) ?? 0) > 0) return "fragment";
|
|
52
|
+
const html = ranges.get("text/html") ?? ranges.get("text/*") ?? ranges.get("*/*") ?? 0;
|
|
53
|
+
const md = ranges.get("text/markdown") ?? 0;
|
|
54
|
+
const json = ranges.get("application/json") ?? 0;
|
|
55
|
+
const best = Math.max(md, json);
|
|
56
|
+
if (best <= 0 || best < html) return null;
|
|
57
|
+
return md >= json ? "md" : "json";
|
|
58
|
+
}
|
|
27
59
|
|
|
28
60
|
export type Negotiated = {
|
|
29
61
|
target: RenderTarget;
|
|
@@ -61,15 +93,7 @@ export function negotiate(url: URL, request: Request, basePath?: string): Negoti
|
|
|
61
93
|
pathname = original;
|
|
62
94
|
}
|
|
63
95
|
|
|
64
|
-
if (!target)
|
|
65
|
-
const accept = request.headers.get("accept") ?? "";
|
|
66
|
-
for (const [re, t] of ACCEPT_TARGET) {
|
|
67
|
-
if (re.test(accept)) {
|
|
68
|
-
target = t;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
96
|
+
if (!target) target = acceptTarget(request.headers.get("accept") ?? "");
|
|
73
97
|
|
|
74
98
|
// A speculative request (Sec-Purpose: prefetch / prerender) may never be seen
|
|
75
99
|
// — load()s read it to skip side effects (analytics, rate limits, counters).
|