@junejs/server 0.1.0-dev.4 → 0.1.0-dev.7

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 (105) hide show
  1. package/dist/adapter.d.ts +56 -0
  2. package/dist/adapter.js +211 -0
  3. package/dist/agent-discover.d.ts +5 -0
  4. package/dist/agent-discover.js +75 -0
  5. package/dist/agent-durable.d.ts +72 -0
  6. package/dist/agent-durable.js +141 -0
  7. package/dist/agent-native.d.ts +40 -0
  8. package/dist/agent-native.js +179 -0
  9. package/dist/app.d.ts +15 -0
  10. package/dist/app.js +252 -0
  11. package/dist/blob.d.ts +23 -0
  12. package/dist/blob.js +78 -0
  13. package/dist/build.d.ts +40 -0
  14. package/dist/build.js +582 -0
  15. package/dist/client-bundle.d.ts +15 -0
  16. package/dist/client-bundle.js +91 -0
  17. package/dist/config-loader.d.ts +9 -0
  18. package/dist/config-loader.js +25 -0
  19. package/dist/content.d.ts +39 -0
  20. package/dist/content.js +201 -0
  21. package/dist/css-modules-ambient.d.ts +10 -0
  22. package/dist/css-modules.d.ts +24 -0
  23. package/dist/css-modules.js +127 -0
  24. package/dist/css.d.ts +14 -0
  25. package/dist/css.js +114 -0
  26. package/dist/d1-BkdUg9kg.d.ts +24 -0
  27. package/dist/d1-migrate.d.ts +31 -0
  28. package/dist/d1-migrate.js +156 -0
  29. package/dist/d1.d.ts +2 -0
  30. package/dist/d1.js +32 -0
  31. package/dist/db.d.ts +11 -0
  32. package/dist/db.js +20 -0
  33. package/dist/deploy.d.ts +25 -0
  34. package/dist/deploy.js +158 -0
  35. package/dist/dev-reload.d.ts +5 -0
  36. package/dist/dev-reload.js +123 -0
  37. package/dist/dev.d.ts +13 -0
  38. package/dist/dev.js +55 -0
  39. package/dist/host.d.ts +25 -0
  40. package/dist/host.js +107 -0
  41. package/dist/index.d.ts +26 -0
  42. package/dist/index.js +26 -0
  43. package/dist/instrumentation.d.ts +4 -0
  44. package/dist/instrumentation.js +15 -0
  45. package/dist/island-registry.d.ts +8 -0
  46. package/dist/island-registry.js +117 -0
  47. package/dist/kv.d.ts +8 -0
  48. package/dist/kv.js +35 -0
  49. package/dist/migrate-BbWt-PuB.d.ts +32 -0
  50. package/dist/migrate.d.ts +2 -0
  51. package/dist/migrate.js +115 -0
  52. package/dist/mysql-driver-BsrWVqbp.d.ts +12 -0
  53. package/dist/mysql-driver.d.ts +2 -0
  54. package/dist/mysql-driver.js +52 -0
  55. package/dist/negotiate.d.ts +11 -0
  56. package/dist/negotiate.js +39 -0
  57. package/dist/pipeline-B2V6tKfO.d.ts +49 -0
  58. package/dist/pipeline.d.ts +2 -0
  59. package/dist/pipeline.js +235 -0
  60. package/dist/postgres-driver-DkkBFikA.d.ts +15 -0
  61. package/dist/postgres-driver.d.ts +2 -0
  62. package/dist/postgres-driver.js +50 -0
  63. package/dist/resources.d.ts +15 -0
  64. package/dist/resources.js +31 -0
  65. package/dist/router.d.ts +32 -0
  66. package/dist/router.js +183 -0
  67. package/dist/rsc-build.d.ts +17 -0
  68. package/dist/rsc-build.js +68 -0
  69. package/dist/rsc-bundle.d.ts +8 -0
  70. package/dist/rsc-bundle.js +39 -0
  71. package/dist/rsc-manifest.d.ts +8 -0
  72. package/dist/rsc-manifest.js +67 -0
  73. package/dist/rsc-runtime/flight-render-routes.d.ts +5 -0
  74. package/dist/rsc-runtime/flight-render-routes.js +26 -0
  75. package/dist/rsc-runtime/flight-render.d.ts +4 -0
  76. package/dist/rsc-runtime/flight-render.js +21 -0
  77. package/dist/rsc-runtime/flight-to-html.d.ts +4 -0
  78. package/dist/rsc-runtime/flight-to-html.js +32 -0
  79. package/dist/rsc-runtime/rsc-worker.d.ts +6 -0
  80. package/dist/rsc-runtime/rsc-worker.js +46 -0
  81. package/dist/rsc-runtime/rsdw-shims.d.ts +42 -0
  82. package/dist/segment.d.ts +14 -0
  83. package/dist/segment.js +33 -0
  84. package/dist/sqlite-driver.d.ts +25 -0
  85. package/dist/sqlite-driver.js +102 -0
  86. package/dist/static-files.d.ts +6 -0
  87. package/dist/static-files.js +56 -0
  88. package/dist/tsconfig-jsx.d.ts +15 -0
  89. package/dist/tsconfig-jsx.js +35 -0
  90. package/dist/turso-driver-C1wEigMZ.d.ts +22 -0
  91. package/dist/turso-driver.d.ts +2 -0
  92. package/dist/turso-driver.js +68 -0
  93. package/dist/worker.d.ts +55 -0
  94. package/dist/worker.js +211 -0
  95. package/package.json +115 -23
  96. package/src/adapter.ts +20 -7
  97. package/src/agent-discover.ts +5 -2
  98. package/src/agent-durable.ts +108 -4
  99. package/src/app.ts +74 -2
  100. package/src/build.ts +52 -3
  101. package/src/client-bundle.ts +3 -1
  102. package/src/index.ts +4 -0
  103. package/src/rsc-bundle.ts +3 -2
  104. package/src/static-files.ts +84 -0
  105. package/src/worker.ts +46 -29
@@ -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 };
@@ -0,0 +1,235 @@
1
+ import { SEGMENT_HEADER, TITLE_HEADER, encodeTitle, negotiate } from "./negotiate.js";
2
+ import { ensureScope, runInScope, setRequestLocale } from "@junejs/db";
3
+ import { LoaderDataContext } from "@junejs/core/route";
4
+ import React, { Suspense, use } from "react";
5
+ import { renderToReadableStream } from "react-dom/server";
6
+ import { Document } from "@junejs/core/document";
7
+ import { apiCatalog, buildLinkHeader, llmsTxt, mcpServerCard, robotsTxt, sitemapXml } from "@junejs/core/discovery";
8
+ import { mcpHandler, mcpTools } from "@junejs/core/mcp";
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,2 @@
1
+ import { n as pgJuneDb, r as postgres, t as PgClientLike } from "./postgres-driver-DkkBFikA.js";
2
+ export { PgClientLike, pgJuneDb, postgres };
@@ -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 };
@@ -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 };