@junejs/server 0.1.0-dev.4 → 0.1.0-dev.5
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/adapter.d.ts +55 -0
- package/dist/adapter.js +204 -0
- package/dist/agent-discover.d.ts +5 -0
- package/dist/agent-discover.js +75 -0
- package/dist/agent-durable.d.ts +60 -0
- package/dist/agent-durable.js +106 -0
- package/dist/agent-native.d.ts +40 -0
- package/dist/agent-native.js +179 -0
- package/dist/app.d.ts +15 -0
- package/dist/app.js +218 -0
- package/dist/blob.d.ts +23 -0
- package/dist/blob.js +78 -0
- package/dist/build.d.ts +40 -0
- package/dist/build.js +551 -0
- package/dist/client-bundle.d.ts +15 -0
- package/dist/client-bundle.js +91 -0
- package/dist/config-loader.d.ts +9 -0
- package/dist/config-loader.js +25 -0
- package/dist/content.d.ts +39 -0
- package/dist/content.js +201 -0
- package/dist/css-modules-ambient.d.ts +10 -0
- package/dist/css-modules.d.ts +24 -0
- package/dist/css-modules.js +127 -0
- package/dist/css.d.ts +14 -0
- package/dist/css.js +114 -0
- package/dist/d1-BkdUg9kg.d.ts +24 -0
- package/dist/d1-migrate.d.ts +31 -0
- package/dist/d1-migrate.js +156 -0
- package/dist/d1.d.ts +2 -0
- package/dist/d1.js +32 -0
- package/dist/db.d.ts +11 -0
- package/dist/db.js +20 -0
- package/dist/deploy.d.ts +25 -0
- package/dist/deploy.js +158 -0
- package/dist/dev-reload.d.ts +5 -0
- package/dist/dev-reload.js +123 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.js +55 -0
- package/dist/host.d.ts +25 -0
- package/dist/host.js +107 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/instrumentation.d.ts +4 -0
- package/dist/instrumentation.js +15 -0
- package/dist/island-registry.d.ts +8 -0
- package/dist/island-registry.js +117 -0
- package/dist/kv.d.ts +8 -0
- package/dist/kv.js +35 -0
- package/dist/migrate-BbWt-PuB.d.ts +32 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +115 -0
- package/dist/mysql-driver-BsrWVqbp.d.ts +12 -0
- package/dist/mysql-driver.d.ts +2 -0
- package/dist/mysql-driver.js +52 -0
- package/dist/negotiate.d.ts +11 -0
- package/dist/negotiate.js +39 -0
- package/dist/pipeline-B2V6tKfO.d.ts +49 -0
- package/dist/pipeline.d.ts +2 -0
- package/dist/pipeline.js +235 -0
- package/dist/postgres-driver-DkkBFikA.d.ts +15 -0
- package/dist/postgres-driver.d.ts +2 -0
- package/dist/postgres-driver.js +50 -0
- package/dist/resources.d.ts +15 -0
- package/dist/resources.js +31 -0
- package/dist/router.d.ts +32 -0
- package/dist/router.js +183 -0
- package/dist/rsc-build.d.ts +17 -0
- package/dist/rsc-build.js +68 -0
- package/dist/rsc-bundle.d.ts +8 -0
- package/dist/rsc-bundle.js +39 -0
- package/dist/rsc-manifest.d.ts +8 -0
- package/dist/rsc-manifest.js +67 -0
- package/dist/rsc-runtime/flight-render-routes.d.ts +5 -0
- package/dist/rsc-runtime/flight-render-routes.js +26 -0
- package/dist/rsc-runtime/flight-render.d.ts +4 -0
- package/dist/rsc-runtime/flight-render.js +21 -0
- package/dist/rsc-runtime/flight-to-html.d.ts +4 -0
- package/dist/rsc-runtime/flight-to-html.js +32 -0
- package/dist/rsc-runtime/rsc-worker.d.ts +6 -0
- package/dist/rsc-runtime/rsc-worker.js +46 -0
- package/dist/rsc-runtime/rsdw-shims.d.ts +42 -0
- package/dist/segment.d.ts +14 -0
- package/dist/segment.js +33 -0
- package/dist/sqlite-driver.d.ts +25 -0
- package/dist/sqlite-driver.js +102 -0
- package/dist/tsconfig-jsx.d.ts +15 -0
- package/dist/tsconfig-jsx.js +35 -0
- package/dist/turso-driver-C1wEigMZ.d.ts +22 -0
- package/dist/turso-driver.d.ts +2 -0
- package/dist/turso-driver.js +68 -0
- package/dist/worker.d.ts +55 -0
- package/dist/worker.js +209 -0
- package/package.json +115 -23
- package/src/adapter.ts +4 -4
- package/src/client-bundle.ts +3 -1
- package/src/rsc-bundle.ts +3 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BrandedRoute, RouteContext } from "@junejs/core/route";
|
|
2
|
+
import { AgentConfig } from "@junejs/core/config";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { DocumentConfig } from "@junejs/core/document";
|
|
5
|
+
import { I18nConfig } from "@junejs/core/i18n";
|
|
6
|
+
import { Resources } from "@junejs/core/resources";
|
|
7
|
+
//#region src/pipeline.d.ts
|
|
8
|
+
type LayoutComponent = React.ComponentType<{
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}>;
|
|
11
|
+
type LoadingComponent = React.ComponentType;
|
|
12
|
+
type Resolved = {
|
|
13
|
+
def: BrandedRoute;
|
|
14
|
+
params: Record<string, string>;
|
|
15
|
+
chain: LayoutComponent[];
|
|
16
|
+
loading?: React.ComponentType;
|
|
17
|
+
boundaryIndex?: number | null;
|
|
18
|
+
boundaryKey?: string | null;
|
|
19
|
+
};
|
|
20
|
+
type ResourceHandler = (request: Request, ctx: RouteContext) => Response | Promise<Response>;
|
|
21
|
+
type ResolvedResource = {
|
|
22
|
+
handler: ResourceHandler;
|
|
23
|
+
params: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
type RouteResolver = (pathname: string) => Promise<Resolved | ResolvedResource | null>;
|
|
26
|
+
type PipelineConfig = {
|
|
27
|
+
docConfig: DocumentConfig;
|
|
28
|
+
agent: AgentConfig;
|
|
29
|
+
agentSurface?: MiddlewareHandler;
|
|
30
|
+
i18n?: I18nConfig;
|
|
31
|
+
routeList: () => Promise<string[]> | string[];
|
|
32
|
+
resolve: RouteResolver;
|
|
33
|
+
resources?: () => Promise<Resources> | Resources;
|
|
34
|
+
earlyHints?: string[];
|
|
35
|
+
htmlCacheControl?: string;
|
|
36
|
+
notFoundComponent?: React.ComponentType<{
|
|
37
|
+
pathname: string;
|
|
38
|
+
}>;
|
|
39
|
+
extra?: ExtraHandler;
|
|
40
|
+
};
|
|
41
|
+
type MiddlewareHandler = (request: Request, url: URL) => Promise<Response | null> | Response | null;
|
|
42
|
+
/** @deprecated the former name (app/_extra); use {@link MiddlewareHandler}. */
|
|
43
|
+
type ExtraHandler = MiddlewareHandler;
|
|
44
|
+
type Pipeline = {
|
|
45
|
+
fetch(request: Request): Promise<Response>;
|
|
46
|
+
};
|
|
47
|
+
declare function createPipeline(cfg: PipelineConfig): Pipeline;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { Pipeline as a, ResolvedResource as c, createPipeline as d, MiddlewareHandler as i, ResourceHandler as l, LayoutComponent as n, PipelineConfig as o, LoadingComponent as r, Resolved as s, ExtraHandler as t, RouteResolver as u };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as Pipeline, c as ResolvedResource, d as createPipeline, i as MiddlewareHandler, l as ResourceHandler, n as LayoutComponent, o as PipelineConfig, r as LoadingComponent, s as Resolved, t as ExtraHandler, u as RouteResolver } from "./pipeline-B2V6tKfO.js";
|
|
2
|
+
export { ExtraHandler, LayoutComponent, LoadingComponent, MiddlewareHandler, Pipeline, PipelineConfig, Resolved, ResolvedResource, ResourceHandler, RouteResolver, createPipeline };
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { SEGMENT_HEADER, TITLE_HEADER, encodeTitle, negotiate } from "./negotiate.js";
|
|
2
|
+
import { LoaderDataContext } from "@junejs/core/route";
|
|
3
|
+
import React, { Suspense, use } from "react";
|
|
4
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
5
|
+
import { Document } from "@junejs/core/document";
|
|
6
|
+
import { apiCatalog, buildLinkHeader, llmsTxt, mcpServerCard, robotsTxt, sitemapXml } from "@junejs/core/discovery";
|
|
7
|
+
import { mcpHandler, mcpTools } from "@junejs/core/mcp";
|
|
8
|
+
import { ensureScope, runInScope, setRequestLocale } from "@junejs/db";
|
|
9
|
+
import { LOCALE_COOKIE, localeAlternates, localeDir, matchPinnedLocale, negotiateLocale } from "@junejs/core/i18n";
|
|
10
|
+
//#region src/pipeline.ts
|
|
11
|
+
function readCookie(request, name) {
|
|
12
|
+
const header = request.headers.get("cookie");
|
|
13
|
+
if (!header) return void 0;
|
|
14
|
+
for (const part of header.split(";")) {
|
|
15
|
+
const eq = part.indexOf("=");
|
|
16
|
+
if (eq === -1) continue;
|
|
17
|
+
if (part.slice(0, eq).trim() === name) return decodeURIComponent(part.slice(eq + 1).trim());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const DefaultNotFound = ({ pathname }) => React.createElement("main", null, React.createElement("h1", null, "404 — Not found"), React.createElement("p", null, pathname));
|
|
21
|
+
function text(body, contentType, init) {
|
|
22
|
+
const headers = new Headers(init?.headers);
|
|
23
|
+
headers.set("content-type", contentType);
|
|
24
|
+
return new Response(body, {
|
|
25
|
+
...init,
|
|
26
|
+
headers
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function provideLoaderData(data, node) {
|
|
30
|
+
return React.createElement(LoaderDataContext.Provider, { value: data }, node);
|
|
31
|
+
}
|
|
32
|
+
function StreamedView({ loadPromise, def, ctx }) {
|
|
33
|
+
const data = use(loadPromise);
|
|
34
|
+
return provideLoaderData(data, def.view ? def.view(data, ctx) : null);
|
|
35
|
+
}
|
|
36
|
+
function letterFavicon(siteName) {
|
|
37
|
+
return text(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#1d1d1f"/><text x="32" y="32" dy=".36em" text-anchor="middle" font-family="ui-sans-serif, system-ui, sans-serif" font-size="34" font-weight="600" fill="#fbfbf8">${((siteName ?? "").trim().charAt(0) || "•").toUpperCase().replace(/[<>&"']/g, "") || "•"}</text></svg>\n`, "image/svg+xml", { headers: { "cache-control": "public, max-age=86400" } });
|
|
38
|
+
}
|
|
39
|
+
function createPipeline(cfg) {
|
|
40
|
+
const { docConfig, agent } = cfg;
|
|
41
|
+
const NotFound = cfg.notFoundComponent ?? DefaultNotFound;
|
|
42
|
+
function htmlHeaders() {
|
|
43
|
+
const headers = new Headers({ "content-type": "text/html; charset=utf-8" });
|
|
44
|
+
const links = [buildLinkHeader(agent), ...cfg.earlyHints ?? []].filter(Boolean);
|
|
45
|
+
if (links.length) headers.set("link", links.join(", "));
|
|
46
|
+
if (cfg.htmlCacheControl) headers.set("cache-control", cfg.htmlCacheControl);
|
|
47
|
+
return headers;
|
|
48
|
+
}
|
|
49
|
+
function langDir(locale) {
|
|
50
|
+
const lang = locale ?? docConfig.site.lang ?? "en";
|
|
51
|
+
return {
|
|
52
|
+
lang,
|
|
53
|
+
dir: localeDir(lang, cfg.i18n)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function alternatesFor(ctx) {
|
|
57
|
+
if (!cfg.i18n) return void 0;
|
|
58
|
+
const pinned = matchPinnedLocale(cfg.i18n, ctx.url.host, ctx.url.pathname);
|
|
59
|
+
const routePath = pinned ? pinned.pathname : ctx.url.pathname;
|
|
60
|
+
return localeAlternates(cfg.i18n, routePath, {
|
|
61
|
+
currentHost: ctx.url.host,
|
|
62
|
+
protocol: ctx.url.protocol.replace(":", "")
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function renderDocument(node, metadata, status, chain, locale, boundaryKey, alternates) {
|
|
66
|
+
const wrapped = chain.reduceRight((acc, L) => React.createElement(L, null, acc), node);
|
|
67
|
+
const stream = await renderToReadableStream(React.createElement(Document, {
|
|
68
|
+
config: docConfigForRender(),
|
|
69
|
+
metadata,
|
|
70
|
+
children: wrapped,
|
|
71
|
+
shellKey: boundaryKey,
|
|
72
|
+
alternates,
|
|
73
|
+
...langDir(locale)
|
|
74
|
+
}));
|
|
75
|
+
await stream.allReady;
|
|
76
|
+
return new Response(stream, {
|
|
77
|
+
status,
|
|
78
|
+
headers: htmlHeaders()
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function renderFragment(node, metadata, chain, boundaryIndex, boundaryKey) {
|
|
82
|
+
const segmented = typeof boundaryIndex === "number" && boundaryKey != null;
|
|
83
|
+
const stream = await renderToReadableStream((segmented ? chain.slice(boundaryIndex + 1) : chain).reduceRight((acc, L) => React.createElement(L, null, acc), node));
|
|
84
|
+
await stream.allReady;
|
|
85
|
+
const html = await new Response(stream).text();
|
|
86
|
+
const headers = new Headers({ "content-type": "text/html; charset=utf-8" });
|
|
87
|
+
const title = typeof metadata?.title === "string" ? metadata.title : void 0;
|
|
88
|
+
if (title) headers.set(TITLE_HEADER, encodeTitle(title));
|
|
89
|
+
if (segmented) headers.set(SEGMENT_HEADER, boundaryKey);
|
|
90
|
+
return new Response(html, {
|
|
91
|
+
status: 200,
|
|
92
|
+
headers
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function docConfigForRender() {
|
|
96
|
+
const webmcpTools = agent.webmcp && agent.mcp ? mcpTools() : null;
|
|
97
|
+
return webmcpTools?.length ? {
|
|
98
|
+
...docConfig,
|
|
99
|
+
webmcpTools
|
|
100
|
+
} : docConfig;
|
|
101
|
+
}
|
|
102
|
+
async function renderStreamingDocument(resolved, loadPromise, ctx) {
|
|
103
|
+
const { def, chain, loading: Loading, boundaryKey } = resolved;
|
|
104
|
+
const leaf = React.createElement(StreamedView, {
|
|
105
|
+
loadPromise,
|
|
106
|
+
def,
|
|
107
|
+
ctx
|
|
108
|
+
});
|
|
109
|
+
const boundary = React.createElement(Suspense, { fallback: Loading ? React.createElement(Loading) : null }, leaf);
|
|
110
|
+
const wrapped = chain.reduceRight((acc, L) => React.createElement(L, null, acc), boundary);
|
|
111
|
+
const metadata = typeof def.metadata === "object" ? def.metadata : void 0;
|
|
112
|
+
const stream = await renderToReadableStream(React.createElement(Document, {
|
|
113
|
+
config: docConfigForRender(),
|
|
114
|
+
metadata,
|
|
115
|
+
children: wrapped,
|
|
116
|
+
shellKey: boundaryKey,
|
|
117
|
+
alternates: alternatesFor(ctx),
|
|
118
|
+
...langDir(ctx.locale)
|
|
119
|
+
}), { onError: (e) => console.error("[june] streaming render error:", e) });
|
|
120
|
+
return new Response(stream, {
|
|
121
|
+
status: 200,
|
|
122
|
+
headers: htmlHeaders()
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function notFoundResponse(target, pathname, locale) {
|
|
126
|
+
return target !== "view" ? Response.json({
|
|
127
|
+
error: "Not Found",
|
|
128
|
+
path: pathname
|
|
129
|
+
}, { status: 404 }) : renderDocument(React.createElement(NotFound, { pathname }), {
|
|
130
|
+
title: "Not found",
|
|
131
|
+
robots: "noindex"
|
|
132
|
+
}, 404, [], locale);
|
|
133
|
+
}
|
|
134
|
+
function resolveMeta(def, data, ctx) {
|
|
135
|
+
return typeof def.metadata === "function" ? def.metadata(data, ctx) : def.metadata;
|
|
136
|
+
}
|
|
137
|
+
async function renderMarkdown(def, data, ctx) {
|
|
138
|
+
const jsonData = typeof def.json === "function" ? await def.json(data, ctx) : def.json === false ? null : data;
|
|
139
|
+
const body = typeof def.md === "function" ? await def.md(data, ctx) : "```json\n" + JSON.stringify(jsonData, null, 2) + "\n```\n";
|
|
140
|
+
return text(body, "text/markdown; charset=utf-8", { headers: { "x-markdown-tokens": String(Math.ceil(body.length / 4)) } });
|
|
141
|
+
}
|
|
142
|
+
async function renderProjection(resolved, target, data, ctx) {
|
|
143
|
+
const { def, chain, boundaryIndex, boundaryKey } = resolved;
|
|
144
|
+
if (target !== "fragment" && def[target] === false) return notFoundResponse(target, ctx.url.pathname, ctx.locale);
|
|
145
|
+
const res = await renderTarget(target, def, data, ctx, chain, boundaryIndex, boundaryKey);
|
|
146
|
+
res.headers.set("vary", "accept");
|
|
147
|
+
return res;
|
|
148
|
+
}
|
|
149
|
+
async function renderTarget(target, def, data, ctx, chain, boundaryIndex, boundaryKey) {
|
|
150
|
+
if (target === "md") return renderMarkdown(def, data, ctx);
|
|
151
|
+
if (target === "json") {
|
|
152
|
+
const payload = typeof def.json === "function" ? await def.json(data, ctx) : data;
|
|
153
|
+
return Response.json(payload);
|
|
154
|
+
}
|
|
155
|
+
const node = provideLoaderData(data, def.view ? def.view(data, ctx) : null);
|
|
156
|
+
const meta = resolveMeta(def, data, ctx);
|
|
157
|
+
if (target === "fragment") return renderFragment(node, meta, chain, boundaryIndex, boundaryKey);
|
|
158
|
+
return renderDocument(node, meta, 200, chain, ctx.locale, boundaryKey, alternatesFor(ctx));
|
|
159
|
+
}
|
|
160
|
+
async function discovery(url) {
|
|
161
|
+
switch (url.pathname) {
|
|
162
|
+
case "/llms.txt": return text(llmsTxt(url.origin, await cfg.routeList(), agent, docConfig.site), "text/markdown; charset=utf-8");
|
|
163
|
+
case "/robots.txt": return text(robotsTxt(url.origin), "text/plain; charset=utf-8");
|
|
164
|
+
case "/sitemap.xml": return text(sitemapXml(url.origin, await cfg.routeList(), cfg.i18n), "application/xml; charset=utf-8");
|
|
165
|
+
case "/.well-known/api-catalog": return text(JSON.stringify(apiCatalog(url.origin, agent)), "application/linkset+json");
|
|
166
|
+
case "/.well-known/mcp/server-card.json": return agent.mcp ? Response.json(mcpServerCard(url.origin)) : null;
|
|
167
|
+
default: return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function handleRequest(request) {
|
|
171
|
+
const url = new URL(request.url);
|
|
172
|
+
if (url.pathname === "/mcp") {
|
|
173
|
+
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
174
|
+
return mcpHandler(request, { request });
|
|
175
|
+
}
|
|
176
|
+
if (request.method === "GET" && agent.discovery) {
|
|
177
|
+
const d = await discovery(url);
|
|
178
|
+
if (d) return d;
|
|
179
|
+
}
|
|
180
|
+
if (agent.runtime.enabled && cfg.agentSurface) {
|
|
181
|
+
const a = await cfg.agentSurface(request, url);
|
|
182
|
+
if (a) return a;
|
|
183
|
+
}
|
|
184
|
+
if (cfg.extra) {
|
|
185
|
+
const out = await cfg.extra(request, url);
|
|
186
|
+
if (out) return out;
|
|
187
|
+
}
|
|
188
|
+
if (request.method === "GET" && !docConfig.site.icon && (url.pathname === "/favicon.svg" || url.pathname === "/favicon.ico")) return letterFavicon(docConfig.site.name);
|
|
189
|
+
let locale;
|
|
190
|
+
let routeBase = url.pathname;
|
|
191
|
+
if (cfg.i18n) {
|
|
192
|
+
const pinned = matchPinnedLocale(cfg.i18n, url.host, url.pathname);
|
|
193
|
+
if (pinned) {
|
|
194
|
+
locale = pinned.locale;
|
|
195
|
+
routeBase = pinned.pathname;
|
|
196
|
+
} else {
|
|
197
|
+
const override = cfg.i18n.resolveLocale?.({
|
|
198
|
+
url,
|
|
199
|
+
headers: request.headers
|
|
200
|
+
});
|
|
201
|
+
locale = override && cfg.i18n.locales[override] ? override : negotiateLocale(cfg.i18n, {
|
|
202
|
+
acceptLanguage: request.headers.get("accept-language"),
|
|
203
|
+
cookie: readCookie(request, LOCALE_COOKIE)
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (locale) setRequestLocale(locale);
|
|
207
|
+
}
|
|
208
|
+
const { target, pathname, speculative } = negotiate(url, request, routeBase);
|
|
209
|
+
const resolved = await cfg.resolve(pathname);
|
|
210
|
+
if (!resolved) return notFoundResponse(target, pathname, locale);
|
|
211
|
+
const ctx = {
|
|
212
|
+
request,
|
|
213
|
+
url,
|
|
214
|
+
params: resolved.params,
|
|
215
|
+
target,
|
|
216
|
+
locale,
|
|
217
|
+
speculative
|
|
218
|
+
};
|
|
219
|
+
if ("handler" in resolved) return resolved.handler(request, ctx);
|
|
220
|
+
if (target === "view" && resolved.loading && typeof resolved.def.metadata !== "function") return renderStreamingDocument(resolved, Promise.resolve(resolved.def.load ? resolved.def.load(ctx) : void 0), ctx);
|
|
221
|
+
let data;
|
|
222
|
+
try {
|
|
223
|
+
data = resolved.def.load ? await resolved.def.load(ctx) : void 0;
|
|
224
|
+
} catch {
|
|
225
|
+
return notFoundResponse(target, pathname, locale);
|
|
226
|
+
}
|
|
227
|
+
return renderProjection(resolved, target, data, ctx);
|
|
228
|
+
}
|
|
229
|
+
return { async fetch(request) {
|
|
230
|
+
await ensureScope();
|
|
231
|
+
return runInScope({ resources: cfg.resources ? await cfg.resources() : {} }, () => handleRequest(request));
|
|
232
|
+
} };
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { createPipeline };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DbFactory, JuneDb } from "@junejs/core/resources";
|
|
2
|
+
//#region src/postgres-driver.d.ts
|
|
3
|
+
interface PgClientLike {
|
|
4
|
+
query(sql: string, params?: unknown[]): Promise<{
|
|
5
|
+
rows: unknown[];
|
|
6
|
+
rowCount: number | null;
|
|
7
|
+
}>;
|
|
8
|
+
end(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
declare function pgJuneDb(client: PgClientLike): JuneDb;
|
|
11
|
+
declare function postgres(opts: {
|
|
12
|
+
url: string;
|
|
13
|
+
}): DbFactory;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { pgJuneDb as n, postgres as r, PgClientLike as t };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/postgres-driver.ts
|
|
2
|
+
function pgJuneDb(client) {
|
|
3
|
+
const self = {
|
|
4
|
+
dialect: "postgres",
|
|
5
|
+
async query(sql, params = []) {
|
|
6
|
+
return (await client.query(sql, params)).rows;
|
|
7
|
+
},
|
|
8
|
+
async get(sql, params = []) {
|
|
9
|
+
return (await client.query(sql, params)).rows[0] ?? void 0;
|
|
10
|
+
},
|
|
11
|
+
async run(sql, params = []) {
|
|
12
|
+
return {
|
|
13
|
+
changes: (await client.query(sql, params)).rowCount ?? 0,
|
|
14
|
+
lastInsertRowid: 0
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
async exec(sql) {
|
|
18
|
+
await client.query(sql);
|
|
19
|
+
},
|
|
20
|
+
async transaction(fn) {
|
|
21
|
+
await client.query("begin");
|
|
22
|
+
try {
|
|
23
|
+
const out = await fn(self);
|
|
24
|
+
await client.query("commit");
|
|
25
|
+
return out;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
await client.query("rollback");
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
async close() {
|
|
32
|
+
await client.end();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return self;
|
|
36
|
+
}
|
|
37
|
+
function postgres(opts) {
|
|
38
|
+
return {
|
|
39
|
+
kind: "postgres",
|
|
40
|
+
open: async () => {
|
|
41
|
+
const mod = await import("pg");
|
|
42
|
+
const Client = (mod.default ?? mod).Client;
|
|
43
|
+
const client = new Client({ connectionString: opts.url });
|
|
44
|
+
await client.connect();
|
|
45
|
+
return pgJuneDb(client);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { pgJuneDb, postgres };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t as D1Database } from "./d1-BkdUg9kg.js";
|
|
2
|
+
import { ResourceConfig, Resources } from "@junejs/core/resources";
|
|
3
|
+
//#region src/resources.d.ts
|
|
4
|
+
declare function memoizeResources(config?: ResourceConfig): () => Promise<Resources>;
|
|
5
|
+
type WorkerEnv = {
|
|
6
|
+
DB?: D1Database;
|
|
7
|
+
} & Record<string, unknown>;
|
|
8
|
+
type ResourceFlags = {
|
|
9
|
+
db?: boolean;
|
|
10
|
+
kv?: boolean;
|
|
11
|
+
blob?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare function bindWorkerResources(flags: ResourceFlags): (env?: unknown) => Promise<Resources>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { ResourceFlags, WorkerEnv, bindWorkerResources, memoizeResources };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { d1 } from "./d1.js";
|
|
2
|
+
//#region src/resources.ts
|
|
3
|
+
function memoizeResources(config) {
|
|
4
|
+
if (!config || !config.db && !config.kv && !config.blob) {
|
|
5
|
+
const empty = {};
|
|
6
|
+
return () => Promise.resolve(empty);
|
|
7
|
+
}
|
|
8
|
+
let opened = null;
|
|
9
|
+
return () => {
|
|
10
|
+
if (!opened) opened = (async () => ({
|
|
11
|
+
db: config.db ? await config.db.open() : void 0,
|
|
12
|
+
kv: config.kv ? await config.kv.open() : void 0,
|
|
13
|
+
blob: config.blob ? await config.blob.open() : void 0
|
|
14
|
+
}))();
|
|
15
|
+
return opened;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function bindWorkerResources(flags) {
|
|
19
|
+
if (!flags.db && !flags.kv && !flags.blob) {
|
|
20
|
+
const empty = {};
|
|
21
|
+
return () => Promise.resolve(empty);
|
|
22
|
+
}
|
|
23
|
+
let opened = null;
|
|
24
|
+
return (rawEnv) => {
|
|
25
|
+
const env = rawEnv;
|
|
26
|
+
if (!opened) opened = (async () => ({ db: flags.db && env?.DB ? await d1(env.DB).open() : void 0 }))();
|
|
27
|
+
return opened;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { bindWorkerResources, memoizeResources };
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/router.d.ts
|
|
2
|
+
type RouteMatch = {
|
|
3
|
+
file: string;
|
|
4
|
+
params: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
type SegmentMatch = {
|
|
7
|
+
dir: string;
|
|
8
|
+
layout?: string;
|
|
9
|
+
loading?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
notFound?: string;
|
|
12
|
+
};
|
|
13
|
+
type RouteTreeMatch = {
|
|
14
|
+
file: string;
|
|
15
|
+
params: Record<string, string>;
|
|
16
|
+
segments: SegmentMatch[];
|
|
17
|
+
};
|
|
18
|
+
declare function findMiddlewareFile(appDir: string): string | null;
|
|
19
|
+
type MatchOptions = {
|
|
20
|
+
pageConvention?: boolean;
|
|
21
|
+
};
|
|
22
|
+
declare function isResourceFile(file: string): boolean;
|
|
23
|
+
declare function matchRouteTree(appDir: string, pathname: string, options?: MatchOptions): Promise<RouteTreeMatch | null>;
|
|
24
|
+
declare function resolveNotFound(appDir: string, pathname: string): Promise<{
|
|
25
|
+
segments: SegmentMatch[];
|
|
26
|
+
notFound?: string;
|
|
27
|
+
}>;
|
|
28
|
+
declare function routeFiles(appDir: string, options?: MatchOptions): Promise<string[]>;
|
|
29
|
+
declare function listRoutes(appDir: string, options?: MatchOptions): Promise<string[]>;
|
|
30
|
+
declare function matchRoute(appDir: string, pathname: string, options?: MatchOptions): Promise<RouteMatch | null>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { MatchOptions, RouteMatch, RouteTreeMatch, SegmentMatch, findMiddlewareFile, isResourceFile, listRoutes, matchRoute, matchRouteTree, resolveNotFound, routeFiles };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
//#region src/router.ts
|
|
5
|
+
const routeExtensions = /* @__PURE__ */ new Set([
|
|
6
|
+
".tsx",
|
|
7
|
+
".jsx",
|
|
8
|
+
".ts",
|
|
9
|
+
".js"
|
|
10
|
+
]);
|
|
11
|
+
function findMiddlewareFile(appDir) {
|
|
12
|
+
for (const ext of routeExtensions) {
|
|
13
|
+
const f = join(appDir, `_middleware${ext}`);
|
|
14
|
+
if (existsSync(f)) return f;
|
|
15
|
+
}
|
|
16
|
+
for (const ext of routeExtensions) {
|
|
17
|
+
const f = join(appDir, `_extra${ext}`);
|
|
18
|
+
if (existsSync(f)) {
|
|
19
|
+
console.warn("[june] app/_extra is deprecated — rename it to app/_middleware.");
|
|
20
|
+
return f;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function baseName(file) {
|
|
26
|
+
return (file.split(sep).pop() ?? "").replace(/\.[^.]+$/, "");
|
|
27
|
+
}
|
|
28
|
+
function isPageFile(file) {
|
|
29
|
+
const base = baseName(file);
|
|
30
|
+
return base === "page" || base === "index";
|
|
31
|
+
}
|
|
32
|
+
function isResourceFile(file) {
|
|
33
|
+
return baseName(file) === "route";
|
|
34
|
+
}
|
|
35
|
+
const SPECIAL_FILES = /* @__PURE__ */ new Set([
|
|
36
|
+
"layout",
|
|
37
|
+
"loading",
|
|
38
|
+
"error",
|
|
39
|
+
"not-found"
|
|
40
|
+
]);
|
|
41
|
+
function isSpecialFile(file) {
|
|
42
|
+
return SPECIAL_FILES.has(baseName(file));
|
|
43
|
+
}
|
|
44
|
+
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, "");
|
|
50
|
+
async function listDir(dir) {
|
|
51
|
+
return (await readdir(dir, { withFileTypes: true }).catch(() => [])).filter((e) => !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => ({
|
|
52
|
+
name: e.name,
|
|
53
|
+
dir: e.isDirectory()
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
function fileFor(entries, dir, base) {
|
|
57
|
+
for (const ext of [
|
|
58
|
+
".tsx",
|
|
59
|
+
".jsx",
|
|
60
|
+
".ts",
|
|
61
|
+
".js"
|
|
62
|
+
]) if (entries.some((e) => !e.dir && e.name === base + ext)) return join(dir, base + ext);
|
|
63
|
+
}
|
|
64
|
+
function segmentAt(dir, entries) {
|
|
65
|
+
return {
|
|
66
|
+
dir,
|
|
67
|
+
layout: fileFor(entries, dir, "layout"),
|
|
68
|
+
loading: fileFor(entries, dir, "loading"),
|
|
69
|
+
error: fileFor(entries, dir, "error"),
|
|
70
|
+
notFound: fileFor(entries, dir, "not-found")
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function walk(dir) {
|
|
74
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
75
|
+
return (await Promise.all(entries.map((entry) => {
|
|
76
|
+
const path = join(dir, entry.name);
|
|
77
|
+
return entry.isDirectory() ? walk(path) : [path];
|
|
78
|
+
}))).flat();
|
|
79
|
+
}
|
|
80
|
+
function routePath(appDir, file) {
|
|
81
|
+
const parts = relative(appDir, file).split(sep).join("/").replace(/\.[^.]+$/, "").split("/").filter((p) => !isRouteGroup(p));
|
|
82
|
+
if (parts.at(-1) === "page" || parts.at(-1) === "index" || parts.at(-1) === "route") parts.pop();
|
|
83
|
+
return `/${parts.join("/")}`.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
84
|
+
}
|
|
85
|
+
async function matchRouteTree(appDir, pathname, options = {}) {
|
|
86
|
+
const urlSegments = pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
87
|
+
async function descend(dir, rest, params, chain) {
|
|
88
|
+
const entries = await listDir(dir);
|
|
89
|
+
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,
|
|
94
|
+
params,
|
|
95
|
+
segments
|
|
96
|
+
};
|
|
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
|
+
}
|
|
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;
|
|
111
|
+
}
|
|
112
|
+
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);
|
|
116
|
+
if (hit) return hit;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
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, {
|
|
128
|
+
...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), [], {
|
|
136
|
+
...params,
|
|
137
|
+
[paramName(e.name)]: rest.join("/")
|
|
138
|
+
}, segments);
|
|
139
|
+
if (hit) return hit;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return descend(appDir, urlSegments, {}, []);
|
|
144
|
+
}
|
|
145
|
+
async function resolveNotFound(appDir, pathname) {
|
|
146
|
+
const urlSegments = pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
147
|
+
const segments = [];
|
|
148
|
+
let dir = appDir;
|
|
149
|
+
for (let i = 0; i <= urlSegments.length; i++) {
|
|
150
|
+
const entries = await listDir(dir);
|
|
151
|
+
segments.push(segmentAt(dir, entries));
|
|
152
|
+
if (i === urlSegments.length) break;
|
|
153
|
+
const next = entries.find((e) => e.dir && e.name === urlSegments[i]);
|
|
154
|
+
if (!next) break;
|
|
155
|
+
dir = join(dir, next.name);
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
segments,
|
|
159
|
+
notFound: [...segments].reverse().find((s) => s.notFound)?.notFound
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function routeFiles(appDir, options = {}) {
|
|
163
|
+
return (await walk(appDir)).filter((file) => {
|
|
164
|
+
if (!routeExtensions.has(file.match(/\.[^.]+$/)?.[0] ?? "")) return false;
|
|
165
|
+
if (isSpecialFile(file)) return false;
|
|
166
|
+
if (file.split(sep).some((p) => p.startsWith("_"))) return false;
|
|
167
|
+
if (options.pageConvention && !isPageFile(file) && !isResourceFile(file)) return false;
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async function listRoutes(appDir, options = {}) {
|
|
172
|
+
const files = (await routeFiles(appDir, options)).filter((file) => !isResourceFile(file));
|
|
173
|
+
return [...new Set(files.map((file) => routePath(appDir, file)))].sort();
|
|
174
|
+
}
|
|
175
|
+
async function matchRoute(appDir, pathname, options = {}) {
|
|
176
|
+
const tree = await matchRouteTree(appDir, pathname, options);
|
|
177
|
+
return tree ? {
|
|
178
|
+
file: tree.file,
|
|
179
|
+
params: tree.params
|
|
180
|
+
} : null;
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { findMiddlewareFile, isResourceFile, listRoutes, matchRoute, matchRouteTree, resolveNotFound, routeFiles };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DocumentConfig } from "@junejs/core/document";
|
|
2
|
+
//#region src/rsc-build.d.ts
|
|
3
|
+
type RscRoute = {
|
|
4
|
+
path: string;
|
|
5
|
+
file: string;
|
|
6
|
+
};
|
|
7
|
+
declare function findRscRoutes(appDir: string): RscRoute[];
|
|
8
|
+
type RscBuildResult = {
|
|
9
|
+
worker: string;
|
|
10
|
+
server: string;
|
|
11
|
+
paths: string[];
|
|
12
|
+
} | null;
|
|
13
|
+
declare function buildRsc(appRoot: string, outDir: string, docConfig: DocumentConfig): Promise<RscBuildResult>;
|
|
14
|
+
type Fetcher = (request: Request) => Promise<Response>;
|
|
15
|
+
declare function createRscDispatch(ssrFetch: Fetcher, rscFetch: Fetcher, rscPaths: Iterable<string>): Fetcher;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { RscBuildResult, RscRoute, buildRsc, createRscDispatch, findRscRoutes };
|