@junejs/server 1.0.0-dev.42 → 1.0.0-dev.45
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-native.d.ts +8 -1
- package/dist/agent-native.js +45 -3
- package/dist/app.d.ts +1 -0
- package/dist/app.js +10 -1
- 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/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 +1 -1
- 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/{worker-Dv4l6Ybs.d.ts → worker-2qbQjj2c.d.ts} +1 -0
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +52 -76
- package/package.json +3 -3
- package/src/agent-native.ts +75 -4
- package/src/app.ts +22 -1
- package/src/build.ts +7 -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/worker.ts +66 -82
- package/dist/route-scan-DMlwoESy.d.ts +0 -13
package/src/worker.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
|
22
22
|
import { createPipeline, type ExtraHandler, type LayoutComponent, type LoadingComponent, type Resolved, type ResolvedResource, type ResourceHandler } from "./pipeline";
|
|
23
23
|
import { durableAgentSurface, durableChannelSurface, type DurableObjectNamespace } from "./agent-durable";
|
|
24
24
|
import { acceptTarget } from "./negotiate";
|
|
25
|
+
import { compareRoutePatterns, parseSegment } from "./route-rank";
|
|
25
26
|
import { contentTypeFor, RESERVED_PREFIX, safeRelativePath } from "./static-files";
|
|
26
27
|
|
|
27
28
|
// The slice of workerd's ExecutionContext the worker threads through: webhook
|
|
@@ -37,8 +38,13 @@ export type WorkerManifest = {
|
|
|
37
38
|
// Dynamic patterns in file-route syntax ("/posts/[slug]", "/docs/[...path]").
|
|
38
39
|
dynamicRoutes?: Array<{ pattern: string; def: BrandedRoute }>;
|
|
39
40
|
// Resource routes (app/**/route.*): a raw-Response handler keyed by its pattern
|
|
40
|
-
// (static or dynamic).
|
|
41
|
+
// (static or dynamic). Ranked with the pages by segment shape (route-rank.ts),
|
|
42
|
+
// exactly as `june dev` resolves; a page wins only at the SAME pattern.
|
|
41
43
|
resourceRoutes?: Array<{ pattern: string; handler: ResourceHandler }>;
|
|
44
|
+
// Patterns scanned from .june/routes/ (framework-generated, e.g. Kura). Dev
|
|
45
|
+
// consults app/ first and falls back to .june/routes/, so every one of these
|
|
46
|
+
// ranks after every app/ route. Absent → all routes are app/ routes.
|
|
47
|
+
generatedRoutes?: string[];
|
|
42
48
|
// Layout chains (root→leaf) keyed by route path / dynamic pattern. The build
|
|
43
49
|
// freezes the same chain the dev server loads from app/layout.* files.
|
|
44
50
|
layoutChains?: Record<string, LayoutComponent[]>;
|
|
@@ -83,7 +89,13 @@ export type WorkerManifest = {
|
|
|
83
89
|
services?: (env?: unknown) => unknown;
|
|
84
90
|
};
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
// One row of the worker's route table: a page (def) or a resource route (handler).
|
|
93
|
+
type Compiled = {
|
|
94
|
+
regex: RegExp;
|
|
95
|
+
names: string[];
|
|
96
|
+
pattern: string;
|
|
97
|
+
generated: boolean;
|
|
98
|
+
} & ({ def: BrandedRoute; handler?: undefined } | { def?: undefined; handler: ResourceHandler });
|
|
87
99
|
|
|
88
100
|
// "/posts/[slug]" | "/docs/[...path]" | "/notes/[[tag]]" → matcher. Optional
|
|
89
101
|
// segments ([[x]], [[...x]]) wrap slash + capture together so absence matches;
|
|
@@ -93,34 +105,19 @@ function compilePattern(pattern: string): { regex: RegExp; names: string[] } {
|
|
|
93
105
|
let source = "";
|
|
94
106
|
let allOptional = true;
|
|
95
107
|
for (const seg of pattern.split("/").filter(Boolean)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
// Same grammar as the dev matcher (route-rank.ts parseSegment).
|
|
109
|
+
const { kind, name } = parseSegment(seg);
|
|
110
|
+
if (name) names.push(name);
|
|
111
|
+
if (kind === "optionalCatchAll") source += "(?:/(.+))?";
|
|
112
|
+
else if (kind === "optional") source += "(?:/([^/]+))?";
|
|
113
|
+
else {
|
|
114
|
+
if (kind === "catchAll") source += "/(.+)";
|
|
115
|
+
else if (kind === "param") source += "/([^/]+)";
|
|
116
|
+
// A static segment matches literally: escape every RegExp metacharacter,
|
|
117
|
+
// brackets included (`docs[v2`, `[slug].png` are static names).
|
|
118
|
+
else source += "/" + seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
106
119
|
allOptional = false;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
const op = seg.match(/^\[\[(\w+)\]\]$/);
|
|
110
|
-
if (op) {
|
|
111
|
-
names.push(op[1]!);
|
|
112
|
-
source += "(?:/([^/]+))?";
|
|
113
|
-
continue;
|
|
114
120
|
}
|
|
115
|
-
const p = seg.match(/^\[(\w+)\]$/);
|
|
116
|
-
if (p) {
|
|
117
|
-
names.push(p[1]!);
|
|
118
|
-
source += "/([^/]+)";
|
|
119
|
-
allOptional = false;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
source += "/" + seg.replace(/[.*+?^${}()|\\]/g, "\\$&");
|
|
123
|
-
allOptional = false;
|
|
124
121
|
}
|
|
125
122
|
if (source === "") return { regex: /^\/$/, names };
|
|
126
123
|
return { regex: new RegExp(allOptional ? `^(?:${source}|/)$` : `^${source}$`), names };
|
|
@@ -132,16 +129,23 @@ export function createWorker(
|
|
|
132
129
|
manifest: WorkerManifest,
|
|
133
130
|
opts: { staticBuild?: boolean } = {},
|
|
134
131
|
): { fetch(request: Request, env?: unknown, ctx?: WorkerExecutionContext): Promise<Response> } {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
pattern:
|
|
144
|
-
|
|
132
|
+
// Pages and resource routes in ONE table, ordered the way `june dev` resolves
|
|
133
|
+
// (#312): app/ before .june/routes/, then segment shape (static > [param] >
|
|
134
|
+
// [...catchAll], required before optional), then a page before a resource
|
|
135
|
+
// route at the same pattern. The kind of route never outranks the shape.
|
|
136
|
+
const generated = new Set(manifest.generatedRoutes ?? []);
|
|
137
|
+
const row = (pattern: string) => ({ ...compilePattern(pattern), pattern, generated: generated.has(pattern) });
|
|
138
|
+
const table: Compiled[] = [
|
|
139
|
+
...Object.entries(manifest.routes).map(([pattern, def]) => ({ ...row(pattern), def })),
|
|
140
|
+
...(manifest.dynamicRoutes ?? []).map((d) => ({ ...row(d.pattern), def: d.def })),
|
|
141
|
+
...(manifest.resourceRoutes ?? []).map((r) => ({ ...row(r.pattern), handler: r.handler })),
|
|
142
|
+
];
|
|
143
|
+
table.sort(
|
|
144
|
+
(a, b) =>
|
|
145
|
+
Number(a.generated) - Number(b.generated) ||
|
|
146
|
+
compareRoutePatterns(a.pattern, b.pattern) ||
|
|
147
|
+
Number(!!a.handler) - Number(!!b.handler),
|
|
148
|
+
);
|
|
145
149
|
const routeList = [
|
|
146
150
|
...Object.keys(manifest.routes),
|
|
147
151
|
...(manifest.dynamicRoutes ?? []).map((d) => d.pattern),
|
|
@@ -151,6 +155,17 @@ export function createWorker(
|
|
|
151
155
|
const loadingFor = (key: string): LoadingComponent | undefined => manifest.loadings?.[key];
|
|
152
156
|
const boundaryFor = (key: string): { index: number; key: string } | null =>
|
|
153
157
|
manifest.layoutBoundaries?.[key] ?? null;
|
|
158
|
+
const resolvedPage = (def: BrandedRoute, key: string, params: Record<string, string>): Resolved => {
|
|
159
|
+
const b = boundaryFor(key);
|
|
160
|
+
return {
|
|
161
|
+
def,
|
|
162
|
+
params,
|
|
163
|
+
chain: chainFor(key),
|
|
164
|
+
boundaryIndex: b?.index ?? null,
|
|
165
|
+
boundaryKey: b?.key ?? null,
|
|
166
|
+
loading: loadingFor(key),
|
|
167
|
+
};
|
|
168
|
+
};
|
|
154
169
|
|
|
155
170
|
// The worker's env (D1/KV/R2 bindings) arrives per fetch and is stable across
|
|
156
171
|
// requests in an isolate; we capture the latest and hand it to the env-aware
|
|
@@ -224,52 +239,21 @@ export function createWorker(
|
|
|
224
239
|
resources: provider ? () => provider(currentEnv) : undefined,
|
|
225
240
|
services: servicesProvider ? () => (servicesCache ??= { v: servicesProvider(currentEnv) }).v : undefined,
|
|
226
241
|
resolve: async (pathname): Promise<Resolved | ResolvedResource | null> => {
|
|
242
|
+
// Fast path: an app/ static page outranks every other route for its path.
|
|
227
243
|
const staticDef = manifest.routes[pathname];
|
|
228
|
-
if (staticDef) {
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
def: staticDef,
|
|
232
|
-
params: {},
|
|
233
|
-
chain: chainFor(pathname),
|
|
234
|
-
boundaryIndex: b?.index ?? null,
|
|
235
|
-
boundaryKey: b?.key ?? null,
|
|
236
|
-
loading: loadingFor(pathname),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
for (const d of dynamic) {
|
|
240
|
-
const m = pathname.match(d.regex);
|
|
241
|
-
if (m) {
|
|
242
|
-
// Optional segments leave their capture undefined — the param is
|
|
243
|
-
// then absent, matching the dev matcher's semantics.
|
|
244
|
-
const params = Object.fromEntries(
|
|
245
|
-
d.names.flatMap((n, i) => {
|
|
246
|
-
const v = m[i + 1];
|
|
247
|
-
return v === undefined ? [] : [[n, decodeURIComponent(v)]];
|
|
248
|
-
}),
|
|
249
|
-
);
|
|
250
|
-
const b = boundaryFor(d.pattern);
|
|
251
|
-
return {
|
|
252
|
-
def: d.def,
|
|
253
|
-
params,
|
|
254
|
-
chain: chainFor(d.pattern),
|
|
255
|
-
boundaryIndex: b?.index ?? null,
|
|
256
|
-
boundaryKey: b?.key ?? null,
|
|
257
|
-
loading: loadingFor(d.pattern),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Resource routes after pages (so a page is never shadowed by one).
|
|
262
|
-
for (const r of resources) {
|
|
244
|
+
if (staticDef && !generated.has(pathname)) return resolvedPage(staticDef, pathname, {});
|
|
245
|
+
for (const r of table) {
|
|
263
246
|
const m = pathname.match(r.regex);
|
|
264
|
-
if (m)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
247
|
+
if (!m) continue;
|
|
248
|
+
// Optional segments leave their capture undefined — the param is
|
|
249
|
+
// then absent, matching the dev matcher's semantics.
|
|
250
|
+
const params = Object.fromEntries(
|
|
251
|
+
r.names.flatMap((n, i) => {
|
|
252
|
+
const v = m[i + 1];
|
|
253
|
+
return v === undefined ? [] : [[n, decodeURIComponent(v)]];
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
return r.handler ? { handler: r.handler, params } : resolvedPage(r.def, r.pattern, params);
|
|
273
257
|
}
|
|
274
258
|
return null;
|
|
275
259
|
},
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
//#region src/route-scan.d.ts
|
|
2
|
-
type RouteEntry = {
|
|
3
|
-
path: string;
|
|
4
|
-
file: string;
|
|
5
|
-
dynamic: boolean;
|
|
6
|
-
resource?: boolean;
|
|
7
|
-
layouts: string[];
|
|
8
|
-
loading?: string;
|
|
9
|
-
};
|
|
10
|
-
declare function scanRoutes(appDir: string, dir?: string, layouts?: string[], out?: RouteEntry[], loading?: string): Promise<RouteEntry[]>;
|
|
11
|
-
declare function scanAppRoutes(appRoot: string): Promise<RouteEntry[]>;
|
|
12
|
-
//#endregion
|
|
13
|
-
export { scanAppRoutes as n, scanRoutes as r, RouteEntry as t };
|