@junejs/server 1.0.0-dev.42 → 1.0.0-dev.47
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-durable.d.ts +4 -0
- package/dist/agent-durable.js +22 -4
- package/dist/agent-native.d.ts +18 -2
- package/dist/agent-native.js +62 -8
- package/dist/app.d.ts +1 -0
- package/dist/app.js +18 -2
- package/dist/build.d.ts +3 -3
- package/dist/build.js +4 -2
- package/dist/{config-freeze-ZWC_Qd4h.d.ts → config-freeze-CyXvNqi2.d.ts} +1 -1
- package/dist/config-freeze.d.ts +1 -1
- package/dist/core-version.js +1 -1
- package/dist/dev.d.ts +6 -2
- package/dist/dev.js +40 -9
- package/dist/host.d.ts +1 -0
- package/dist/host.js +2 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/{manifest-DSNX4I-G.d.ts → manifest-CwTT1zVs.d.ts} +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +3 -1
- package/dist/pipeline.js +15 -6
- package/dist/route-rank.d.ts +10 -0
- package/dist/route-rank.js +38 -0
- package/dist/route-scan-BMbBzPIB.d.ts +20 -0
- package/dist/route-scan.d.ts +2 -2
- package/dist/route-scan.js +47 -2
- package/dist/router.js +58 -44
- package/dist/sqlite-driver.d.ts +1 -0
- package/dist/sqlite-driver.js +17 -4
- package/dist/{worker-Dv4l6Ybs.d.ts → worker-2qbQjj2c.d.ts} +1 -0
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +59 -77
- package/package.json +3 -3
- package/src/agent-durable.ts +24 -3
- package/src/agent-native.ts +99 -13
- package/src/app.ts +34 -2
- package/src/build.ts +7 -1
- package/src/core-version.ts +1 -1
- package/src/dev.ts +41 -8
- package/src/host.ts +4 -3
- package/src/manifest.ts +2 -0
- package/src/pipeline.ts +20 -4
- package/src/route-rank.ts +60 -0
- package/src/route-scan.ts +78 -1
- package/src/router.ts +75 -58
- package/src/sqlite-driver.ts +36 -6
- package/src/worker.ts +76 -82
- package/dist/route-scan-DMlwoESy.d.ts +0 -13
package/dist/manifest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findMiddlewareFile } from "./router.js";
|
|
2
|
+
import { scanAppRoutes } from "./route-scan.js";
|
|
2
3
|
import { resolveBoundary } from "./segment.js";
|
|
3
4
|
import { freezeConfig } from "./config-freeze.js";
|
|
4
|
-
import { scanAppRoutes } from "./route-scan.js";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
@@ -47,6 +47,7 @@ async function buildManifest(appRoot) {
|
|
|
47
47
|
const routes = {};
|
|
48
48
|
const dynamicRoutes = [];
|
|
49
49
|
const resourceRoutes = [];
|
|
50
|
+
const generatedRoutes = scanned.filter((r) => r.generated).map((r) => r.path);
|
|
50
51
|
const layoutChains = {};
|
|
51
52
|
const layoutBoundaries = {};
|
|
52
53
|
const loadings = {};
|
|
@@ -96,6 +97,7 @@ async function buildManifest(appRoot) {
|
|
|
96
97
|
routes,
|
|
97
98
|
dynamicRoutes,
|
|
98
99
|
resourceRoutes,
|
|
100
|
+
...generatedRoutes.length ? { generatedRoutes } : {},
|
|
99
101
|
layoutChains,
|
|
100
102
|
layoutBoundaries,
|
|
101
103
|
loadings,
|
package/dist/pipeline.js
CHANGED
|
@@ -9,8 +9,8 @@ import React, { Suspense, use } from "react";
|
|
|
9
9
|
import { renderToReadableStream } from "react-dom/server";
|
|
10
10
|
import { Document, PRERENDER_ORIGIN, documentTitle, pageCanonical } from "@junejs/core/document";
|
|
11
11
|
import { AGENT_SKILLS_INDEX_PATH, AI_CATALOG_PATH, API_CATALOG_CONTENT_TYPE, ARD_PATH, MCP_SERVER_CARD_TYPE, agentSkillsIndex, aiCatalog, apiCatalog, buildLinkHeader, llmsTxt, mcpServerCard, robotsTxt, siteSkill, sitemapXml } from "@junejs/core/discovery";
|
|
12
|
-
import { mcpHandler, mcpServerIdentity, mcpTools } from "@junejs/core/mcp";
|
|
13
|
-
import { OPENAPI_MEDIA_TYPE, apiActionId, apiHandler, apiNamespaceResponse, isApiNamespace, openApiDocument } from "@junejs/core/api";
|
|
12
|
+
import { mcpForbidden, mcpHandler, mcpServerIdentity, mcpTools, originRejection } from "@junejs/core/mcp";
|
|
13
|
+
import { OPENAPI_MEDIA_TYPE, apiActionId, apiForbidden, apiHandler, apiNamespaceResponse, isApiNamespace, openApiDocument } from "@junejs/core/api";
|
|
14
14
|
import { LOCALE_COOKIE, localeAlternates, localeDir, matchPinnedLocale, negotiateLocale } from "@junejs/core/i18n";
|
|
15
15
|
//#region src/pipeline.ts
|
|
16
16
|
function readCookie(request, name) {
|
|
@@ -368,18 +368,25 @@ function createPipeline(cfg) {
|
|
|
368
368
|
default: return null;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
+
const originPolicy = {
|
|
372
|
+
allowedOrigins: agent.allowedOrigins,
|
|
373
|
+
allowedHosts: agent.allowedHosts
|
|
374
|
+
};
|
|
375
|
+
const callerIdentity = async (request) => request.method === "POST" && cfg.identity ? await cfg.identity(request) : void 0;
|
|
371
376
|
async function handleRequest(request) {
|
|
372
377
|
const url = new URL(request.url);
|
|
373
378
|
if (url.pathname === "/mcp") {
|
|
374
379
|
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
375
|
-
const
|
|
380
|
+
const refused = originRejection(request, originPolicy);
|
|
381
|
+
if (refused !== void 0) return mcpForbidden(refused);
|
|
382
|
+
const identity = await callerIdentity(request);
|
|
376
383
|
return mcpHandler(request, {
|
|
377
384
|
request,
|
|
378
385
|
...identity
|
|
379
386
|
}, mcpServerIdentity(url.origin, {
|
|
380
387
|
site: docConfig.site,
|
|
381
388
|
agent
|
|
382
|
-
}));
|
|
389
|
+
}), originPolicy);
|
|
383
390
|
}
|
|
384
391
|
if (agent.api) {
|
|
385
392
|
if (url.pathname === "/openapi.json" && (request.method === "GET" || request.method === "HEAD")) {
|
|
@@ -394,11 +401,13 @@ function createPipeline(cfg) {
|
|
|
394
401
|
}
|
|
395
402
|
const actionId = apiActionId(url.pathname);
|
|
396
403
|
if (actionId) {
|
|
397
|
-
const
|
|
404
|
+
const refused = originRejection(request, originPolicy);
|
|
405
|
+
if (refused !== void 0) return apiForbidden(request, refused);
|
|
406
|
+
const identity = await callerIdentity(request);
|
|
398
407
|
return apiHandler(request, actionId, {
|
|
399
408
|
request,
|
|
400
409
|
...identity
|
|
401
|
-
});
|
|
410
|
+
}, originPolicy);
|
|
402
411
|
}
|
|
403
412
|
}
|
|
404
413
|
if (request.method === "OPTIONS" && agent.discovery && agent.mcp && url.pathname === SERVER_CARD_PATH) return new Response(null, {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/route-rank.d.ts
|
|
2
|
+
export type SegmentKind = "static" | "param" | "optional" | "catchAll" | "optionalCatchAll";
|
|
3
|
+
export declare function parseSegment(segment: string): {
|
|
4
|
+
kind: SegmentKind;
|
|
5
|
+
name?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function segmentRank(segment: string): number;
|
|
8
|
+
export declare function compareSegments(a: string, b: string): number;
|
|
9
|
+
export declare function compareRoutePatterns(a: string, b: string): number;
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/route-rank.ts
|
|
2
|
+
const DYNAMIC = /^\[(\[)?(\.\.\.)?([A-Za-z_][A-Za-z0-9_]*)\](\])?$/;
|
|
3
|
+
function parseSegment(segment) {
|
|
4
|
+
const m = segment.match(DYNAMIC);
|
|
5
|
+
if (!m || !!m[1] !== !!m[4]) return { kind: "static" };
|
|
6
|
+
const optional = !!m[1];
|
|
7
|
+
return {
|
|
8
|
+
kind: !!m[2] ? optional ? "optionalCatchAll" : "catchAll" : optional ? "optional" : "param",
|
|
9
|
+
name: m[3]
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const RANK = {
|
|
13
|
+
static: 0,
|
|
14
|
+
param: 1,
|
|
15
|
+
optional: 2,
|
|
16
|
+
catchAll: 3,
|
|
17
|
+
optionalCatchAll: 4
|
|
18
|
+
};
|
|
19
|
+
function segmentRank(segment) {
|
|
20
|
+
return RANK[parseSegment(segment).kind];
|
|
21
|
+
}
|
|
22
|
+
function compareSegments(a, b) {
|
|
23
|
+
return segmentRank(a) - segmentRank(b) || (a < b ? -1 : a > b ? 1 : 0);
|
|
24
|
+
}
|
|
25
|
+
function compareRoutePatterns(a, b) {
|
|
26
|
+
const as = a.split("/").filter(Boolean);
|
|
27
|
+
const bs = b.split("/").filter(Boolean);
|
|
28
|
+
for (let i = 0; i < Math.max(as.length, bs.length); i++) {
|
|
29
|
+
const x = as[i];
|
|
30
|
+
const y = bs[i];
|
|
31
|
+
if (x === void 0) return -1;
|
|
32
|
+
if (y === void 0) return 1;
|
|
33
|
+
if (x !== y) return compareSegments(x, y);
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { compareRoutePatterns, compareSegments, parseSegment, segmentRank };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/route-scan.d.ts
|
|
2
|
+
type RouteEntry = {
|
|
3
|
+
path: string;
|
|
4
|
+
file: string;
|
|
5
|
+
dynamic: boolean;
|
|
6
|
+
resource?: boolean;
|
|
7
|
+
generated?: boolean;
|
|
8
|
+
layouts: string[];
|
|
9
|
+
loading?: string;
|
|
10
|
+
};
|
|
11
|
+
declare function scanRoutes(appDir: string, dir?: string, layouts?: string[], out?: RouteEntry[], loading?: string): Promise<RouteEntry[]>;
|
|
12
|
+
declare function scanAppRoutes(appRoot: string): Promise<RouteEntry[]>;
|
|
13
|
+
declare function routeConflicts(routes: RouteEntry[]): Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
files: string[];
|
|
16
|
+
}>;
|
|
17
|
+
declare function misplacedSegments(routes: RouteEntry[]): RouteEntry[];
|
|
18
|
+
declare function routeProblems(trees: RouteEntry[][], appRoot: string): string | null;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { scanAppRoutes as a, routeProblems as i, misplacedSegments as n, scanRoutes as o, routeConflicts as r, RouteEntry as t };
|
package/dist/route-scan.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { RouteEntry, scanAppRoutes, scanRoutes };
|
|
1
|
+
import { a as scanAppRoutes, i as routeProblems, n as misplacedSegments, o as scanRoutes, r as routeConflicts, t as RouteEntry } from "./route-scan-BMbBzPIB.js";
|
|
2
|
+
export { RouteEntry, misplacedSegments, routeConflicts, routeProblems, scanAppRoutes, scanRoutes };
|
package/dist/route-scan.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseSegment } from "./route-rank.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { readdir } from "node:fs/promises";
|
|
3
4
|
import { basename, join, relative, sep } from "node:path";
|
|
@@ -48,8 +49,52 @@ async function scanAppRoutes(appRoot) {
|
|
|
48
49
|
const juneRoutesDir = join(appRoot, ".june", "routes");
|
|
49
50
|
const appRoutes = await scanRoutes(appDir);
|
|
50
51
|
const frameworkRoutes = existsSync(juneRoutesDir) ? await scanRoutes(juneRoutesDir) : [];
|
|
52
|
+
const problems = routeProblems([appRoutes, frameworkRoutes], appRoot);
|
|
53
|
+
if (problems) throw new Error(problems);
|
|
51
54
|
const appPaths = new Set(appRoutes.map((r) => r.path));
|
|
52
|
-
|
|
55
|
+
const generated = frameworkRoutes.filter((r) => !appPaths.has(r.path)).map((r) => ({
|
|
56
|
+
...r,
|
|
57
|
+
generated: true
|
|
58
|
+
}));
|
|
59
|
+
return [...appRoutes, ...generated].sort((a, b) => a.path.localeCompare(b.path));
|
|
60
|
+
}
|
|
61
|
+
function routeConflicts(routes) {
|
|
62
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const r of routes) {
|
|
64
|
+
const key = `${r.resource ? "route" : "page"} ${r.path}`;
|
|
65
|
+
const hit = byKey.get(key);
|
|
66
|
+
if (hit) hit.files.push(r.file);
|
|
67
|
+
else byKey.set(key, {
|
|
68
|
+
path: r.path,
|
|
69
|
+
files: [r.file]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return [...byKey.values()].filter((c) => c.files.length > 1).map((c) => ({
|
|
73
|
+
path: c.path,
|
|
74
|
+
files: c.files.sort()
|
|
75
|
+
})).sort((a, b) => a.path.localeCompare(b.path));
|
|
76
|
+
}
|
|
77
|
+
const ENDS_PATH = /* @__PURE__ */ new Set([
|
|
78
|
+
"optional",
|
|
79
|
+
"catchAll",
|
|
80
|
+
"optionalCatchAll"
|
|
81
|
+
]);
|
|
82
|
+
const beforeLast = (path) => path.split("/").filter(Boolean).slice(0, -1).map((s) => parseSegment(s).kind);
|
|
83
|
+
function misplacedSegments(routes) {
|
|
84
|
+
return routes.filter((r) => beforeLast(r.path).some((k) => ENDS_PATH.has(k))).sort((a, b) => a.path.localeCompare(b.path) || a.file.localeCompare(b.file));
|
|
85
|
+
}
|
|
86
|
+
function routeProblems(trees, appRoot) {
|
|
87
|
+
const rel = (f) => relative(appRoot, f).split(sep).join("/");
|
|
88
|
+
const sections = [];
|
|
89
|
+
const conflicts = trees.flatMap(routeConflicts);
|
|
90
|
+
if (conflicts.length) sections.push("[june] more than one route file resolves to the same path — keep one per path:\n" + conflicts.map((c) => ` ${c.path}: ${c.files.map(rel).join(", ")}`).join("\n"));
|
|
91
|
+
const misplaced = trees.flatMap(misplacedSegments);
|
|
92
|
+
if (misplaced.length) {
|
|
93
|
+
const lines = misplaced.map((r) => ` ${r.path}: ${rel(r.file)}`);
|
|
94
|
+
if (misplaced.some((r) => beforeLast(r.path).includes("optional"))) lines.push(" Unlike SvelteKit, June does not skip a [[param]] mid-path. For a locale prefix, set i18n.locales in june.config.ts instead of a [[lang]] directory.");
|
|
95
|
+
sections.push("[june] an optional or catch-all segment must be the last segment of a route path — nothing may follow it:\n" + lines.join("\n"));
|
|
96
|
+
}
|
|
97
|
+
return sections.length ? sections.join("\n") : null;
|
|
53
98
|
}
|
|
54
99
|
//#endregion
|
|
55
|
-
export { scanAppRoutes, scanRoutes };
|
|
100
|
+
export { misplacedSegments, routeConflicts, routeProblems, scanAppRoutes, scanRoutes };
|
package/dist/router.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compareSegments, parseSegment } from "./route-rank.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { readdir } from "node:fs/promises";
|
|
3
4
|
import { join, relative, sep } from "node:path";
|
|
@@ -42,11 +43,11 @@ function isSpecialFile(file) {
|
|
|
42
43
|
return SPECIAL_FILES.has(baseName(file));
|
|
43
44
|
}
|
|
44
45
|
const isRouteGroup = (name) => /^\(.+\)$/.test(name);
|
|
45
|
-
const isParamDir = (name) =>
|
|
46
|
-
const isOptionalDir = (name) =>
|
|
47
|
-
const isOptionalCatchAllDir = (name) =>
|
|
48
|
-
const isCatchAllDir = (name) =>
|
|
49
|
-
const paramName = (name) => name.
|
|
46
|
+
const isParamDir = (name) => parseSegment(name).kind === "param";
|
|
47
|
+
const isOptionalDir = (name) => parseSegment(name).kind === "optional";
|
|
48
|
+
const isOptionalCatchAllDir = (name) => parseSegment(name).kind === "optionalCatchAll";
|
|
49
|
+
const isCatchAllDir = (name) => parseSegment(name).kind === "catchAll";
|
|
50
|
+
const paramName = (name) => parseSegment(name).name;
|
|
50
51
|
async function listDir(dir) {
|
|
51
52
|
return (await readdir(dir, { withFileTypes: true }).catch(() => [])).filter((e) => !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => ({
|
|
52
53
|
name: e.name,
|
|
@@ -84,63 +85,76 @@ function routePath(appDir, file) {
|
|
|
84
85
|
}
|
|
85
86
|
async function matchRouteTree(appDir, pathname, options = {}) {
|
|
86
87
|
const urlSegments = pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
87
|
-
async function
|
|
88
|
-
const entries = await listDir(dir);
|
|
88
|
+
async function levelAt(dir, chain) {
|
|
89
|
+
const entries = (await listDir(dir)).sort((a, b) => compareSegments(a.name, b.name));
|
|
89
90
|
const segments = [...chain, segmentAt(dir, entries)];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
const levels = [{
|
|
92
|
+
dir,
|
|
93
|
+
entries,
|
|
94
|
+
segments
|
|
95
|
+
}];
|
|
96
|
+
for (const e of entries) if (e.dir && isRouteGroup(e.name)) levels.push(...await levelAt(join(dir, e.name), segments));
|
|
97
|
+
return levels;
|
|
98
|
+
}
|
|
99
|
+
async function descend(nodes, rest, params) {
|
|
100
|
+
const levels = (await Promise.all(nodes.map((n) => levelAt(n.dir, n.chain)))).flat();
|
|
101
|
+
if (rest.length === 0) for (const base of [["page", "index"], ["route"]]) for (const { dir: d, entries, segments } of levels) {
|
|
102
|
+
const file = base.map((b) => fileFor(entries, d, b)).find(Boolean);
|
|
103
|
+
if (file) return {
|
|
104
|
+
file,
|
|
94
105
|
params,
|
|
95
106
|
segments
|
|
96
107
|
};
|
|
97
|
-
} else if (!options.pageConvention) {
|
|
98
|
-
if (rest.length === 1) {
|
|
99
|
-
const leaf = fileFor(entries, dir, rest[0]);
|
|
100
|
-
if (leaf && !isSpecialFile(leaf) && !isPageFile(leaf)) return {
|
|
101
|
-
file: leaf,
|
|
102
|
-
params,
|
|
103
|
-
segments
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
108
|
}
|
|
107
|
-
for (const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
else if (!options.pageConvention && rest.length === 1) for (const { dir: d, entries, segments } of levels) {
|
|
110
|
+
const leaf = fileFor(entries, d, rest[0]);
|
|
111
|
+
if (leaf && !isSpecialFile(leaf) && !isPageFile(leaf)) return {
|
|
112
|
+
file: leaf,
|
|
113
|
+
params,
|
|
114
|
+
segments
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const children = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const lv of levels) for (const e of lv.entries) {
|
|
119
|
+
if (!e.dir || isRouteGroup(e.name)) continue;
|
|
120
|
+
const node = {
|
|
121
|
+
dir: join(lv.dir, e.name),
|
|
122
|
+
chain: lv.segments
|
|
123
|
+
};
|
|
124
|
+
const same = children.get(e.name);
|
|
125
|
+
if (same) same.push(node);
|
|
126
|
+
else children.set(e.name, [node]);
|
|
111
127
|
}
|
|
128
|
+
const names = [...children.keys()].sort(compareSegments);
|
|
112
129
|
if (rest.length === 0) {
|
|
113
|
-
for (const
|
|
114
|
-
if (!
|
|
115
|
-
const hit = await descend(
|
|
130
|
+
for (const name of names) {
|
|
131
|
+
if (!(isOptionalDir(name) || isOptionalCatchAllDir(name))) continue;
|
|
132
|
+
const hit = await descend(children.get(name), [], params);
|
|
116
133
|
if (hit) return hit;
|
|
117
134
|
}
|
|
118
135
|
return null;
|
|
119
136
|
}
|
|
120
137
|
const [head, ...tail] = rest;
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const e of entries) {
|
|
126
|
-
if (!e.dir || !(isParamDir(e.name) || isOptionalDir(e.name))) continue;
|
|
127
|
-
const hit = await descend(join(dir, e.name), tail, {
|
|
138
|
+
for (const name of names) {
|
|
139
|
+
const into = children.get(name);
|
|
140
|
+
let hit = null;
|
|
141
|
+
if (isParamDir(name) || isOptionalDir(name)) hit = await descend(into, tail, {
|
|
128
142
|
...params,
|
|
129
|
-
[paramName(
|
|
130
|
-
}
|
|
131
|
-
if (
|
|
132
|
-
}
|
|
133
|
-
for (const e of entries) {
|
|
134
|
-
if (!e.dir || !(isCatchAllDir(e.name) || isOptionalCatchAllDir(e.name))) continue;
|
|
135
|
-
const hit = await descend(join(dir, e.name), [], {
|
|
143
|
+
[paramName(name)]: head
|
|
144
|
+
});
|
|
145
|
+
else if (isCatchAllDir(name) || isOptionalCatchAllDir(name)) hit = await descend(into, [], {
|
|
136
146
|
...params,
|
|
137
|
-
[paramName(
|
|
138
|
-
}
|
|
147
|
+
[paramName(name)]: rest.join("/")
|
|
148
|
+
});
|
|
149
|
+
else if (name === head) hit = await descend(into, tail, params);
|
|
139
150
|
if (hit) return hit;
|
|
140
151
|
}
|
|
141
152
|
return null;
|
|
142
153
|
}
|
|
143
|
-
return descend(
|
|
154
|
+
return descend([{
|
|
155
|
+
dir: appDir,
|
|
156
|
+
chain: []
|
|
157
|
+
}], urlSegments, {});
|
|
144
158
|
}
|
|
145
159
|
async function resolveNotFound(appDir, pathname) {
|
|
146
160
|
const urlSegments = pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
package/dist/sqlite-driver.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare function nodeSqliteHelp(nodeVersion: string): string;
|
|
|
19
19
|
export declare function isNodeSqliteExperimentalWarning(type: unknown, message: string): boolean;
|
|
20
20
|
type EmitWarning = (warning: string | Error, ...rest: unknown[]) => void;
|
|
21
21
|
export declare function makeWarningFilter(original: EmitWarning): EmitWarning;
|
|
22
|
+
export declare function useDurableJournal(db: SyncSqlite, path: string): SyncSqlite;
|
|
22
23
|
export declare function openLocalSqliteSync(path: string): Promise<SyncSqlite>;
|
|
23
24
|
export declare function openLocalSqlite(path: string): Promise<JuneDb>;
|
|
24
25
|
//#endregion
|
package/dist/sqlite-driver.js
CHANGED
|
@@ -72,10 +72,23 @@ function makeWarningFilter(original) {
|
|
|
72
72
|
original(warning, ...rest);
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
function useDurableJournal(db, path) {
|
|
76
|
+
if (path === ":memory:" || path === "") return db;
|
|
77
|
+
try {
|
|
78
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
79
|
+
db.exec("PRAGMA synchronous=FULL");
|
|
80
|
+
} catch (err) {
|
|
81
|
+
try {
|
|
82
|
+
db.close();
|
|
83
|
+
} catch {}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
return db;
|
|
87
|
+
}
|
|
75
88
|
async function openLocalSqliteSync(path) {
|
|
76
89
|
if (typeof Bun !== "undefined") {
|
|
77
90
|
const { Database } = await import("bun:sqlite");
|
|
78
|
-
return new Database(path, { create: true });
|
|
91
|
+
return useDurableJournal(new Database(path, { create: true }), path);
|
|
79
92
|
}
|
|
80
93
|
const specifier = "node:sqlite";
|
|
81
94
|
let mod;
|
|
@@ -89,14 +102,14 @@ async function openLocalSqliteSync(path) {
|
|
|
89
102
|
process.emitWarning = originalEmitWarning;
|
|
90
103
|
}
|
|
91
104
|
const db = new mod.DatabaseSync(path);
|
|
92
|
-
return {
|
|
105
|
+
return useDurableJournal({
|
|
93
106
|
query: (sql) => db.prepare(sql),
|
|
94
107
|
exec: (sql) => db.exec(sql),
|
|
95
108
|
close: () => db.close()
|
|
96
|
-
};
|
|
109
|
+
}, path);
|
|
97
110
|
}
|
|
98
111
|
async function openLocalSqlite(path) {
|
|
99
112
|
return asyncSqlite(await openLocalSqliteSync(path));
|
|
100
113
|
}
|
|
101
114
|
//#endregion
|
|
102
|
-
export { NODE_SQLITE_MIN_LTS, NODE_SQLITE_MIN_ODD, isNodeSqliteExperimentalWarning, makeWarningFilter, nodeSqliteHelp, openLocalSqlite, openLocalSqliteSync };
|
|
115
|
+
export { NODE_SQLITE_MIN_LTS, NODE_SQLITE_MIN_ODD, isNodeSqliteExperimentalWarning, makeWarningFilter, nodeSqliteHelp, openLocalSqlite, openLocalSqliteSync, useDurableJournal };
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as withDenoAssets, i as withAssets, n as WorkerManifest, r as createWorker, t as WorkerExecutionContext } from "./worker-
|
|
1
|
+
import { a as withDenoAssets, i as withAssets, n as WorkerManifest, r as createWorker, t as WorkerExecutionContext } from "./worker-2qbQjj2c.js";
|
|
2
2
|
export { WorkerExecutionContext, WorkerManifest, createWorker, withAssets, withDenoAssets };
|
package/dist/worker.js
CHANGED
|
@@ -1,41 +1,25 @@
|
|
|
1
1
|
import { durableAgentSurface, durableChannelSurface } from "./agent-durable.js";
|
|
2
|
+
import { compareRoutePatterns, parseSegment } from "./route-rank.js";
|
|
2
3
|
import { acceptTarget } from "./negotiate.js";
|
|
3
4
|
import { createPipeline } from "./pipeline.js";
|
|
4
5
|
import { RESERVED_PREFIX, contentTypeFor, safeRelativePath } from "./static-files.js";
|
|
6
|
+
import { blobFileStore } from "@junejs/core/agent-files";
|
|
5
7
|
//#region src/worker.ts
|
|
6
8
|
function compilePattern(pattern) {
|
|
7
9
|
const names = [];
|
|
8
10
|
let source = "";
|
|
9
11
|
let allOptional = true;
|
|
10
12
|
for (const seg of pattern.split("/").filter(Boolean)) {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
names.push(ca[1]);
|
|
20
|
-
source += "/(.+)";
|
|
21
|
-
allOptional = false;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const op = seg.match(/^\[\[(\w+)\]\]$/);
|
|
25
|
-
if (op) {
|
|
26
|
-
names.push(op[1]);
|
|
27
|
-
source += "(?:/([^/]+))?";
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
const p = seg.match(/^\[(\w+)\]$/);
|
|
31
|
-
if (p) {
|
|
32
|
-
names.push(p[1]);
|
|
33
|
-
source += "/([^/]+)";
|
|
13
|
+
const { kind, name } = parseSegment(seg);
|
|
14
|
+
if (name) names.push(name);
|
|
15
|
+
if (kind === "optionalCatchAll") source += "(?:/(.+))?";
|
|
16
|
+
else if (kind === "optional") source += "(?:/([^/]+))?";
|
|
17
|
+
else {
|
|
18
|
+
if (kind === "catchAll") source += "/(.+)";
|
|
19
|
+
else if (kind === "param") source += "/([^/]+)";
|
|
20
|
+
else source += "/" + seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34
21
|
allOptional = false;
|
|
35
|
-
continue;
|
|
36
22
|
}
|
|
37
|
-
source += "/" + seg.replace(/[.*+?^${}()|\\]/g, "\\$&");
|
|
38
|
-
allOptional = false;
|
|
39
23
|
}
|
|
40
24
|
if (source === "") return {
|
|
41
25
|
regex: /^\/$/,
|
|
@@ -47,20 +31,42 @@ function compilePattern(pattern) {
|
|
|
47
31
|
};
|
|
48
32
|
}
|
|
49
33
|
function createWorker(manifest, opts = {}) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
pattern
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
const generated = new Set(manifest.generatedRoutes ?? []);
|
|
35
|
+
const row = (pattern) => ({
|
|
36
|
+
...compilePattern(pattern),
|
|
37
|
+
pattern,
|
|
38
|
+
generated: generated.has(pattern)
|
|
39
|
+
});
|
|
40
|
+
const table = [
|
|
41
|
+
...Object.entries(manifest.routes).map(([pattern, def]) => ({
|
|
42
|
+
...row(pattern),
|
|
43
|
+
def
|
|
44
|
+
})),
|
|
45
|
+
...(manifest.dynamicRoutes ?? []).map((d) => ({
|
|
46
|
+
...row(d.pattern),
|
|
47
|
+
def: d.def
|
|
48
|
+
})),
|
|
49
|
+
...(manifest.resourceRoutes ?? []).map((r) => ({
|
|
50
|
+
...row(r.pattern),
|
|
51
|
+
handler: r.handler
|
|
52
|
+
}))
|
|
53
|
+
];
|
|
54
|
+
table.sort((a, b) => Number(a.generated) - Number(b.generated) || compareRoutePatterns(a.pattern, b.pattern) || Number(!!a.handler) - Number(!!b.handler));
|
|
60
55
|
const routeList = [...Object.keys(manifest.routes), ...(manifest.dynamicRoutes ?? []).map((d) => d.pattern)].sort();
|
|
61
56
|
const chainFor = (key) => manifest.layoutChains?.[key] ?? [];
|
|
62
57
|
const loadingFor = (key) => manifest.loadings?.[key];
|
|
63
58
|
const boundaryFor = (key) => manifest.layoutBoundaries?.[key] ?? null;
|
|
59
|
+
const resolvedPage = (def, key, params) => {
|
|
60
|
+
const b = boundaryFor(key);
|
|
61
|
+
return {
|
|
62
|
+
def,
|
|
63
|
+
params,
|
|
64
|
+
chain: chainFor(key),
|
|
65
|
+
boundaryIndex: b?.index ?? null,
|
|
66
|
+
boundaryKey: b?.key ?? null,
|
|
67
|
+
loading: loadingFor(key)
|
|
68
|
+
};
|
|
69
|
+
};
|
|
64
70
|
let currentEnv;
|
|
65
71
|
const requestLocals = /* @__PURE__ */ new WeakMap();
|
|
66
72
|
const provider = manifest.resources;
|
|
@@ -83,7 +89,12 @@ function createWorker(manifest, opts = {}) {
|
|
|
83
89
|
channels: manifest.agentChannels,
|
|
84
90
|
env: locals?.env,
|
|
85
91
|
services: servicesProvider,
|
|
86
|
-
waitUntil: locals?.ctx?.waitUntil?.bind(locals.ctx)
|
|
92
|
+
waitUntil: locals?.ctx?.waitUntil?.bind(locals.ctx),
|
|
93
|
+
files: provider ? blobFileStore(async () => {
|
|
94
|
+
const blob = (await provider(locals?.env)).blob;
|
|
95
|
+
if (!blob) throw new Error(`agent "${agentName}": no blob resource bound in this isolate — attached files can't be read`);
|
|
96
|
+
return blob;
|
|
97
|
+
}) : void 0
|
|
87
98
|
})(req);
|
|
88
99
|
};
|
|
89
100
|
})() : void 0;
|
|
@@ -102,47 +113,18 @@ function createWorker(manifest, opts = {}) {
|
|
|
102
113
|
services: servicesProvider ? () => (servicesCache ??= { v: servicesProvider(currentEnv) }).v : void 0,
|
|
103
114
|
resolve: async (pathname) => {
|
|
104
115
|
const staticDef = manifest.routes[pathname];
|
|
105
|
-
if (staticDef) {
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
def: staticDef,
|
|
109
|
-
params: {},
|
|
110
|
-
chain: chainFor(pathname),
|
|
111
|
-
boundaryIndex: b?.index ?? null,
|
|
112
|
-
boundaryKey: b?.key ?? null,
|
|
113
|
-
loading: loadingFor(pathname)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
for (const d of dynamic) {
|
|
117
|
-
const m = pathname.match(d.regex);
|
|
118
|
-
if (m) {
|
|
119
|
-
const params = Object.fromEntries(d.names.flatMap((n, i) => {
|
|
120
|
-
const v = m[i + 1];
|
|
121
|
-
return v === void 0 ? [] : [[n, decodeURIComponent(v)]];
|
|
122
|
-
}));
|
|
123
|
-
const b = boundaryFor(d.pattern);
|
|
124
|
-
return {
|
|
125
|
-
def: d.def,
|
|
126
|
-
params,
|
|
127
|
-
chain: chainFor(d.pattern),
|
|
128
|
-
boundaryIndex: b?.index ?? null,
|
|
129
|
-
boundaryKey: b?.key ?? null,
|
|
130
|
-
loading: loadingFor(d.pattern)
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
for (const r of resources) {
|
|
116
|
+
if (staticDef && !generated.has(pathname)) return resolvedPage(staticDef, pathname, {});
|
|
117
|
+
for (const r of table) {
|
|
135
118
|
const m = pathname.match(r.regex);
|
|
136
|
-
if (m)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
119
|
+
if (!m) continue;
|
|
120
|
+
const params = Object.fromEntries(r.names.flatMap((n, i) => {
|
|
121
|
+
const v = m[i + 1];
|
|
122
|
+
return v === void 0 ? [] : [[n, decodeURIComponent(v)]];
|
|
123
|
+
}));
|
|
124
|
+
return r.handler ? {
|
|
125
|
+
handler: r.handler,
|
|
126
|
+
params
|
|
127
|
+
} : resolvedPage(r.def, r.pattern, params);
|
|
146
128
|
}
|
|
147
129
|
return null;
|
|
148
130
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.47",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -136,11 +136,11 @@
|
|
|
136
136
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
137
137
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
138
138
|
"lightningcss": "^1.27.0",
|
|
139
|
-
"oxc-parser": "^0.
|
|
139
|
+
"oxc-parser": "^0.152.0",
|
|
140
140
|
"rolldown": "^1.1.0"
|
|
141
141
|
},
|
|
142
142
|
"peerDependencies": {
|
|
143
|
-
"@junejs/core": ">=0.2.0-dev.
|
|
143
|
+
"@junejs/core": ">=0.2.0-dev.66",
|
|
144
144
|
"react": "^19.2.0",
|
|
145
145
|
"react-dom": "^19.2.0",
|
|
146
146
|
"react-server-dom-webpack": "19.3.0",
|