@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.
Files changed (48) hide show
  1. package/dist/agent-durable.d.ts +4 -0
  2. package/dist/agent-durable.js +22 -4
  3. package/dist/agent-native.d.ts +18 -2
  4. package/dist/agent-native.js +62 -8
  5. package/dist/app.d.ts +1 -0
  6. package/dist/app.js +18 -2
  7. package/dist/build.d.ts +3 -3
  8. package/dist/build.js +4 -2
  9. package/dist/{config-freeze-ZWC_Qd4h.d.ts → config-freeze-CyXvNqi2.d.ts} +1 -1
  10. package/dist/config-freeze.d.ts +1 -1
  11. package/dist/core-version.js +1 -1
  12. package/dist/dev.d.ts +6 -2
  13. package/dist/dev.js +40 -9
  14. package/dist/host.d.ts +1 -0
  15. package/dist/host.js +2 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +2 -2
  18. package/dist/{manifest-DSNX4I-G.d.ts → manifest-CwTT1zVs.d.ts} +1 -1
  19. package/dist/manifest.d.ts +1 -1
  20. package/dist/manifest.js +3 -1
  21. package/dist/pipeline.js +15 -6
  22. package/dist/route-rank.d.ts +10 -0
  23. package/dist/route-rank.js +38 -0
  24. package/dist/route-scan-BMbBzPIB.d.ts +20 -0
  25. package/dist/route-scan.d.ts +2 -2
  26. package/dist/route-scan.js +47 -2
  27. package/dist/router.js +58 -44
  28. package/dist/sqlite-driver.d.ts +1 -0
  29. package/dist/sqlite-driver.js +17 -4
  30. package/dist/{worker-Dv4l6Ybs.d.ts → worker-2qbQjj2c.d.ts} +1 -0
  31. package/dist/worker.d.ts +1 -1
  32. package/dist/worker.js +59 -77
  33. package/package.json +3 -3
  34. package/src/agent-durable.ts +24 -3
  35. package/src/agent-native.ts +99 -13
  36. package/src/app.ts +34 -2
  37. package/src/build.ts +7 -1
  38. package/src/core-version.ts +1 -1
  39. package/src/dev.ts +41 -8
  40. package/src/host.ts +4 -3
  41. package/src/manifest.ts +2 -0
  42. package/src/pipeline.ts +20 -4
  43. package/src/route-rank.ts +60 -0
  44. package/src/route-scan.ts +78 -1
  45. package/src/router.ts +75 -58
  46. package/src/sqlite-driver.ts +36 -6
  47. package/src/worker.ts +76 -82
  48. 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 identity = cfg.identity ? await cfg.identity(request) : void 0;
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 identity = cfg.identity ? await cfg.identity(request) : void 0;
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 };
@@ -1,2 +1,2 @@
1
- import { n as scanAppRoutes, r as scanRoutes, t as RouteEntry } from "./route-scan-DMlwoESy.js";
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 };
@@ -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
- return [...appRoutes, ...frameworkRoutes.filter((r) => !appPaths.has(r.path))].sort((a, b) => a.path.localeCompare(b.path));
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) => /^\[([A-Za-z_][A-Za-z0-9_]*)\]$/.test(name);
46
- const isOptionalDir = (name) => /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/.test(name);
47
- const isOptionalCatchAllDir = (name) => /^\[\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]\]$/.test(name);
48
- const isCatchAllDir = (name) => /^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/.test(name);
49
- const paramName = (name) => name.replace(/^\[+(\.\.\.)?|\]+$/g, "");
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 descend(dir, rest, params, chain) {
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
- if (rest.length === 0) {
91
- const page = fileFor(entries, dir, "page") ?? fileFor(entries, dir, "index") ?? fileFor(entries, dir, "route");
92
- if (page) return {
93
- file: page,
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 e of entries) {
108
- if (!e.dir || !isRouteGroup(e.name)) continue;
109
- const hit = await descend(join(dir, e.name), rest, params, segments);
110
- if (hit) return hit;
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 e of entries) {
114
- if (!e.dir || !(isOptionalDir(e.name) || isOptionalCatchAllDir(e.name))) continue;
115
- const hit = await descend(join(dir, e.name), [], params, segments);
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
- if (entries.find((e) => e.dir && e.name === head)) {
122
- const hit = await descend(join(dir, head), tail, params, segments);
123
- if (hit) return hit;
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(e.name)]: head
130
- }, segments);
131
- if (hit) return hit;
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(e.name)]: rest.join("/")
138
- }, segments);
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(appDir, urlSegments, {}, []);
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);
@@ -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
@@ -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 };
@@ -20,6 +20,7 @@ type WorkerManifest = {
20
20
  pattern: string;
21
21
  handler: ResourceHandler;
22
22
  }>;
23
+ generatedRoutes?: string[];
23
24
  layoutChains?: Record<string, LayoutComponent[]>;
24
25
  layoutBoundaries?: Record<string, {
25
26
  index: number;
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-Dv4l6Ybs.js";
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 oca = seg.match(/^\[\[\.\.\.(\w+)\]\]$/);
12
- if (oca) {
13
- names.push(oca[1]);
14
- source += "(?:/(.+))?";
15
- continue;
16
- }
17
- const ca = seg.match(/^\[\.\.\.(\w+)\]$/);
18
- if (ca) {
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 dynamic = (manifest.dynamicRoutes ?? []).map((d) => ({
51
- ...compilePattern(d.pattern),
52
- def: d.def,
53
- pattern: d.pattern
54
- }));
55
- const resources = (manifest.resourceRoutes ?? []).map((r) => ({
56
- ...compilePattern(r.pattern),
57
- handler: r.handler,
58
- pattern: r.pattern
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
- const b = boundaryFor(pathname);
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
- const params = Object.fromEntries(r.names.flatMap((n, i) => {
138
- const v = m[i + 1];
139
- return v === void 0 ? [] : [[n, decodeURIComponent(v)]];
140
- }));
141
- return {
142
- handler: r.handler,
143
- params
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.42",
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.151.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.63",
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",