@junejs/server 0.1.3 → 0.2.1
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/assets/icon-latin.LICENSE.txt +92 -0
- package/assets/icon-latin.ttf +0 -0
- package/dist/adapter-D0UnYBuN.d.ts +64 -0
- package/dist/adapter.d.ts +2 -56
- package/dist/adapter.js +107 -6
- package/dist/agent-compile.d.ts +38 -0
- package/dist/agent-compile.js +145 -0
- package/dist/agent-discover.d.ts +4 -4
- package/dist/agent-discover.js +30 -44
- package/dist/agent-durable.d.ts +94 -19
- package/dist/agent-durable.js +647 -32
- package/dist/agent-native.d.ts +44 -13
- package/dist/agent-native.js +264 -27
- package/dist/app.d.ts +5 -5
- package/dist/app.js +42 -10
- package/dist/assets.d.ts +23 -0
- package/dist/assets.js +123 -0
- package/dist/blob.d.ts +4 -5
- package/dist/build.d.ts +8 -30
- package/dist/build.js +143 -298
- package/dist/client-bundle.d.ts +8 -9
- package/dist/client-bundle.js +1 -1
- package/dist/config-freeze-r5p-vTWv.d.ts +17 -0
- package/dist/config-freeze.d.ts +2 -0
- package/dist/config-freeze.js +54 -0
- package/dist/config-loader.d.ts +3 -4
- package/dist/connection-auth.d.ts +38 -0
- package/dist/connection-auth.js +31 -0
- package/dist/content-freeze-CyiW2fRK.d.ts +4 -0
- package/dist/content-freeze.d.ts +2 -0
- package/dist/content-freeze.js +83 -0
- package/dist/content.d.ts +22 -9
- package/dist/content.js +43 -3
- package/dist/core-version.d.ts +4 -0
- package/dist/core-version.js +12 -0
- package/dist/css-modules-ambient.d.ts +2 -1
- package/dist/css-modules.d.ts +10 -11
- package/dist/css.d.ts +9 -10
- package/dist/d1-migrate.d.ts +6 -7
- package/dist/d1-migrate.js +3 -2
- package/dist/db.d.ts +2 -2
- package/dist/deploy.d.ts +3 -4
- package/dist/deploy.js +3 -2
- package/dist/dev-reload.d.ts +3 -4
- package/dist/dev.d.ts +8 -5
- package/dist/dev.js +40 -9
- package/dist/favicon.d.ts +37 -0
- package/dist/favicon.js +248 -0
- package/dist/host.d.ts +6 -5
- package/dist/host.js +2 -1
- package/dist/icon-letter.d.ts +3 -0
- package/dist/icon-letter.js +8 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +9 -3
- package/dist/instrumentation.d.ts +2 -3
- package/dist/instrumentation.js +2 -1
- package/dist/island-registry.d.ts +6 -7
- package/dist/isolate-local.d.ts +5 -0
- package/dist/isolate-local.js +12 -0
- package/dist/kv.d.ts +3 -4
- package/dist/llms-links.d.ts +9 -0
- package/dist/llms-links.js +65 -0
- package/dist/manifest-CwTT1zVs.d.ts +11 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +113 -0
- package/dist/negotiate.d.ts +4 -3
- package/dist/negotiate.js +24 -14
- package/dist/{pipeline-CCp9idQ4.d.ts → pipeline-BHUjBO0G.d.ts} +13 -1
- package/dist/pipeline.d.ts +2 -2
- package/dist/pipeline.js +286 -35
- package/dist/resources.d.ts +5 -6
- package/dist/route-rank.d.ts +6 -7
- package/dist/route-scan-BMbBzPIB.d.ts +20 -0
- package/dist/route-scan.d.ts +2 -0
- package/dist/route-scan.js +100 -0
- package/dist/router.d.ts +12 -13
- package/dist/router.js +51 -38
- package/dist/rsc-build.d.ts +6 -7
- package/dist/rsc-bundle.d.ts +6 -7
- package/dist/rsc-manifest.d.ts +5 -6
- package/dist/rsc-runtime/flight-render-routes.d.ts +3 -4
- package/dist/rsc-runtime/flight-render.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.js +3 -2
- package/dist/rsc-runtime/rsc-worker.js +11 -8
- package/dist/rsc-runtime/rsdw-shims.d.ts +2 -1
- package/dist/segment.d.ts +4 -5
- package/dist/sqlite-driver.d.ts +11 -11
- package/dist/sqlite-driver.js +17 -4
- package/dist/static-files.d.ts +4 -5
- package/dist/tsconfig-jsx.d.ts +4 -4
- package/dist/tsconfig-jsx.js +1 -1
- package/dist/turn-events.d.ts +4 -0
- package/dist/turn-events.js +46 -0
- package/dist/web-manifest.d.ts +5 -0
- package/dist/web-manifest.js +42 -0
- package/dist/worker-2qbQjj2c.d.ts +64 -0
- package/dist/worker.d.ts +2 -57
- package/dist/worker.js +69 -27
- package/package.json +28 -9
- package/src/adapter.ts +149 -4
- package/src/agent-compile.ts +271 -0
- package/src/agent-discover.ts +77 -63
- package/src/agent-durable.ts +998 -30
- package/src/agent-native.ts +370 -35
- package/src/app.ts +75 -6
- package/src/assets.ts +200 -0
- package/src/build.ts +241 -449
- package/src/config-freeze.ts +109 -0
- package/src/connection-auth.ts +101 -0
- package/src/content-freeze.ts +151 -0
- package/src/content.ts +55 -4
- package/src/core-version.ts +34 -0
- package/src/dev.ts +41 -8
- package/src/favicon.ts +325 -0
- package/src/host.ts +4 -3
- package/src/icon-letter.ts +14 -0
- package/src/index.ts +22 -0
- package/src/isolate-local.ts +33 -0
- package/src/llms-links.ts +96 -0
- package/src/manifest.ts +130 -0
- package/src/negotiate.ts +39 -15
- package/src/pipeline.ts +494 -44
- package/src/route-scan.ts +161 -0
- package/src/router.ts +66 -54
- package/src/rsc-runtime/rsc-worker.tsx +5 -4
- package/src/sqlite-driver.ts +36 -6
- package/src/tsconfig-jsx.ts +2 -2
- package/src/turn-events.ts +43 -0
- package/src/web-manifest.ts +38 -0
- package/src/worker.ts +131 -35
package/dist/worker.d.ts
CHANGED
|
@@ -1,57 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { AgentConfig } from "@junejs/core/config";
|
|
4
|
-
import React from "react";
|
|
5
|
-
import { DocumentConfig } from "@junejs/core/document";
|
|
6
|
-
import { I18nConfig } from "@junejs/core/i18n";
|
|
7
|
-
import { Resources } from "@junejs/core/resources";
|
|
8
|
-
//#region src/worker.d.ts
|
|
9
|
-
type WorkerManifest = {
|
|
10
|
-
routes: Record<string, BrandedRoute>;
|
|
11
|
-
dynamicRoutes?: Array<{
|
|
12
|
-
pattern: string;
|
|
13
|
-
def: BrandedRoute;
|
|
14
|
-
}>;
|
|
15
|
-
resourceRoutes?: Array<{
|
|
16
|
-
pattern: string;
|
|
17
|
-
handler: ResourceHandler;
|
|
18
|
-
}>;
|
|
19
|
-
generatedRoutes?: string[];
|
|
20
|
-
layoutChains?: Record<string, LayoutComponent[]>;
|
|
21
|
-
layoutBoundaries?: Record<string, {
|
|
22
|
-
index: number;
|
|
23
|
-
key: string;
|
|
24
|
-
}>;
|
|
25
|
-
loadings?: Record<string, LoadingComponent>;
|
|
26
|
-
document: DocumentConfig;
|
|
27
|
-
agent: AgentConfig;
|
|
28
|
-
agentName?: string;
|
|
29
|
-
i18n?: I18nConfig;
|
|
30
|
-
earlyHints?: string[];
|
|
31
|
-
htmlCacheControl?: string;
|
|
32
|
-
notFound?: React.ComponentType<{
|
|
33
|
-
pathname: string;
|
|
34
|
-
}>;
|
|
35
|
-
extra?: ExtraHandler;
|
|
36
|
-
resources?: (env?: unknown) => Promise<Resources> | Resources;
|
|
37
|
-
services?: (env?: unknown) => unknown;
|
|
38
|
-
};
|
|
39
|
-
declare function createWorker(manifest: WorkerManifest): {
|
|
40
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
41
|
-
};
|
|
42
|
-
type AssetEnv = {
|
|
43
|
-
ASSETS?: {
|
|
44
|
-
fetch(request: Request): Promise<Response>;
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
type FetchPipeline = {
|
|
48
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
49
|
-
};
|
|
50
|
-
declare function withAssets(pipeline: FetchPipeline, opts?: {
|
|
51
|
-
link?: string | null;
|
|
52
|
-
}): {
|
|
53
|
-
fetch(request: Request, env?: AssetEnv): Promise<Response>;
|
|
54
|
-
};
|
|
55
|
-
declare function withDenoAssets(pipeline: FetchPipeline): (request: Request) => Promise<Response>;
|
|
56
|
-
//#endregion
|
|
57
|
-
export { WorkerManifest, createWorker, withAssets, withDenoAssets };
|
|
1
|
+
import { a as withDenoAssets, i as withAssets, n as WorkerManifest, r as createWorker, t as WorkerExecutionContext } from "./worker-2qbQjj2c.js";
|
|
2
|
+
export { WorkerExecutionContext, WorkerManifest, createWorker, withAssets, withDenoAssets };
|
package/dist/worker.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { durableAgentSurface } from "./agent-durable.js";
|
|
1
|
+
import { durableAgentSurface, durableChannelSurface } from "./agent-durable.js";
|
|
2
2
|
import { compareRoutePatterns, parseSegment } from "./route-rank.js";
|
|
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 = [];
|
|
@@ -28,7 +30,7 @@ function compilePattern(pattern) {
|
|
|
28
30
|
names
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
|
-
function createWorker(manifest) {
|
|
33
|
+
function createWorker(manifest, opts = {}) {
|
|
32
34
|
const generated = new Set(manifest.generatedRoutes ?? []);
|
|
33
35
|
const row = (pattern) => ({
|
|
34
36
|
...compilePattern(pattern),
|
|
@@ -66,15 +68,35 @@ function createWorker(manifest) {
|
|
|
66
68
|
};
|
|
67
69
|
};
|
|
68
70
|
let currentEnv;
|
|
71
|
+
const requestLocals = /* @__PURE__ */ new WeakMap();
|
|
69
72
|
const provider = manifest.resources;
|
|
70
73
|
const servicesProvider = manifest.services;
|
|
71
74
|
let servicesCache;
|
|
72
75
|
const agentSurface = manifest.agent.runtime.enabled && manifest.agentName ? (() => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const agentName = manifest.agentName;
|
|
77
|
+
const wantChannels = manifest.agent.runtime.channels !== false && (manifest.agentChannels?.length ?? 0) > 0;
|
|
78
|
+
return async (req) => {
|
|
79
|
+
const locals = requestLocals.get(req);
|
|
80
|
+
const ns = () => (locals?.env)?.AGENT;
|
|
81
|
+
const chatRes = await durableAgentSurface(ns, {
|
|
82
|
+
agentName,
|
|
83
|
+
chatPath: manifest.agent.runtime.chat.path
|
|
84
|
+
})(req);
|
|
85
|
+
if (chatRes) return chatRes;
|
|
86
|
+
if (!wantChannels) return null;
|
|
87
|
+
return durableChannelSurface(ns, {
|
|
88
|
+
agentName,
|
|
89
|
+
channels: manifest.agentChannels,
|
|
90
|
+
env: locals?.env,
|
|
91
|
+
services: servicesProvider,
|
|
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
|
|
98
|
+
})(req);
|
|
99
|
+
};
|
|
78
100
|
})() : void 0;
|
|
79
101
|
const pipeline = createPipeline({
|
|
80
102
|
docConfig: manifest.document,
|
|
@@ -82,6 +104,7 @@ function createWorker(manifest) {
|
|
|
82
104
|
agentSurface,
|
|
83
105
|
i18n: manifest.i18n,
|
|
84
106
|
routeList: () => routeList,
|
|
107
|
+
staticBuild: opts.staticBuild,
|
|
85
108
|
earlyHints: manifest.earlyHints,
|
|
86
109
|
htmlCacheControl: manifest.htmlCacheControl,
|
|
87
110
|
notFoundComponent: manifest.notFound,
|
|
@@ -106,34 +129,51 @@ function createWorker(manifest) {
|
|
|
106
129
|
return null;
|
|
107
130
|
}
|
|
108
131
|
});
|
|
109
|
-
return { fetch(request, env) {
|
|
132
|
+
return { fetch(request, env, ctx) {
|
|
110
133
|
currentEnv = env;
|
|
134
|
+
requestLocals.set(request, {
|
|
135
|
+
env,
|
|
136
|
+
ctx
|
|
137
|
+
});
|
|
111
138
|
return pipeline.fetch(request);
|
|
112
139
|
} };
|
|
113
140
|
}
|
|
114
141
|
const estimateTokens = (s) => String(Math.ceil(s.length / 4));
|
|
142
|
+
function varyAccept(headers) {
|
|
143
|
+
const vary = headers.get("vary");
|
|
144
|
+
if (!vary) headers.set("vary", "accept");
|
|
145
|
+
else if (vary.trim() !== "*" && !vary.split(",").some((v) => v.trim().toLowerCase() === "accept")) headers.set("vary", `${vary}, accept`);
|
|
146
|
+
}
|
|
115
147
|
function withAssets(pipeline, opts = {}) {
|
|
116
|
-
return { async fetch(request, env) {
|
|
148
|
+
return { async fetch(request, env, ctx) {
|
|
117
149
|
const assets = env?.ASSETS;
|
|
118
150
|
const url = new URL(request.url);
|
|
119
151
|
const isPagePath = !/\.[a-z0-9]+$/i.test(url.pathname);
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
const target = isPagePath ? acceptTarget(request.headers.get("accept") ?? "") : null;
|
|
153
|
+
if (target && (request.method === "GET" || request.method === "HEAD")) {
|
|
154
|
+
if (assets && request.method === "GET" && (target === "md" || target === "json")) {
|
|
155
|
+
const base = url.pathname === "/" ? "/index" : url.pathname.replace(/\/+$/, "");
|
|
156
|
+
const assetUrl = new URL(url);
|
|
157
|
+
assetUrl.pathname = `${base}.${target}`;
|
|
158
|
+
const a = await assets.fetch(new Request(assetUrl.toString(), { headers: request.headers }));
|
|
159
|
+
if (a.ok) {
|
|
160
|
+
const headers = new Headers(a.headers);
|
|
161
|
+
if (opts.link) headers.set("link", opts.link);
|
|
162
|
+
varyAccept(headers);
|
|
163
|
+
if (target === "json") return new Response(a.body, {
|
|
164
|
+
status: 200,
|
|
165
|
+
headers
|
|
166
|
+
});
|
|
167
|
+
const body = await a.text();
|
|
168
|
+
headers.set("content-type", "text/markdown; charset=utf-8");
|
|
169
|
+
headers.set("x-markdown-tokens", estimateTokens(body));
|
|
170
|
+
return new Response(body, {
|
|
171
|
+
status: 200,
|
|
172
|
+
headers
|
|
173
|
+
});
|
|
174
|
+
}
|
|
136
175
|
}
|
|
176
|
+
return pipeline.fetch(request, env, ctx);
|
|
137
177
|
}
|
|
138
178
|
if (assets && (request.method === "GET" || request.method === "HEAD")) {
|
|
139
179
|
const a = await assets.fetch(request);
|
|
@@ -141,10 +181,12 @@ function withAssets(pipeline, opts = {}) {
|
|
|
141
181
|
const ct = a.headers.get("content-type") ?? "";
|
|
142
182
|
const addLink = !!opts.link && ct.includes("text/html") && !a.headers.has("link");
|
|
143
183
|
const immutable = /\.[a-f0-9]{8,}\.(css|js)$/.test(url.pathname);
|
|
144
|
-
|
|
184
|
+
const negotiated = isPagePath && ct.includes("text/html");
|
|
185
|
+
if (addLink || immutable || negotiated) {
|
|
145
186
|
const headers = new Headers(a.headers);
|
|
146
187
|
if (addLink) headers.set("link", opts.link);
|
|
147
188
|
if (immutable) headers.set("cache-control", "public, max-age=31536000, immutable");
|
|
189
|
+
if (negotiated) varyAccept(headers);
|
|
148
190
|
return new Response(a.body, {
|
|
149
191
|
status: a.status,
|
|
150
192
|
headers
|
|
@@ -153,7 +195,7 @@ function withAssets(pipeline, opts = {}) {
|
|
|
153
195
|
return a;
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
|
-
return pipeline.fetch(request, env);
|
|
198
|
+
return pipeline.fetch(request, env, ctx);
|
|
157
199
|
} };
|
|
158
200
|
}
|
|
159
201
|
function withDenoAssets(pipeline) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
"types": "./dist/agent-discover.d.ts",
|
|
37
37
|
"default": "./dist/agent-discover.js"
|
|
38
38
|
},
|
|
39
|
+
"./agent-compile": {
|
|
40
|
+
"source": "./src/agent-compile.ts",
|
|
41
|
+
"bun": "./src/agent-compile.ts",
|
|
42
|
+
"types": "./dist/agent-compile.d.ts",
|
|
43
|
+
"default": "./dist/agent-compile.js"
|
|
44
|
+
},
|
|
39
45
|
"./db": {
|
|
40
46
|
"source": "./src/db.ts",
|
|
41
47
|
"bun": "./src/db.ts",
|
|
@@ -54,6 +60,12 @@
|
|
|
54
60
|
"types": "./dist/blob.d.ts",
|
|
55
61
|
"default": "./dist/blob.js"
|
|
56
62
|
},
|
|
63
|
+
"./assets": {
|
|
64
|
+
"source": "./src/assets.ts",
|
|
65
|
+
"bun": "./src/assets.ts",
|
|
66
|
+
"types": "./dist/assets.d.ts",
|
|
67
|
+
"default": "./dist/assets.js"
|
|
68
|
+
},
|
|
57
69
|
"./resources": {
|
|
58
70
|
"source": "./src/resources.ts",
|
|
59
71
|
"bun": "./src/resources.ts",
|
|
@@ -117,7 +129,8 @@
|
|
|
117
129
|
},
|
|
118
130
|
"files": [
|
|
119
131
|
"src",
|
|
120
|
-
"dist"
|
|
132
|
+
"dist",
|
|
133
|
+
"assets"
|
|
121
134
|
],
|
|
122
135
|
"scripts": {
|
|
123
136
|
"test": "bun test",
|
|
@@ -125,20 +138,22 @@
|
|
|
125
138
|
"build": "tsdown"
|
|
126
139
|
},
|
|
127
140
|
"dependencies": {
|
|
128
|
-
"@junejs/
|
|
129
|
-
"@junejs/db": ">=0.0.33",
|
|
141
|
+
"@junejs/db": "^0.2.1",
|
|
130
142
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
143
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
131
144
|
"lightningcss": "^1.27.0",
|
|
132
|
-
"oxc-parser": "^0.
|
|
145
|
+
"oxc-parser": "^0.152.0",
|
|
133
146
|
"rolldown": "^1.1.0"
|
|
134
147
|
},
|
|
135
148
|
"peerDependencies": {
|
|
149
|
+
"@junejs/core": "^0.2.1",
|
|
136
150
|
"react": "^19.2.0",
|
|
137
151
|
"react-dom": "^19.2.0",
|
|
138
|
-
"react-server-dom-webpack": "19.
|
|
152
|
+
"react-server-dom-webpack": "19.3.0",
|
|
139
153
|
"pg": "^8.0.0",
|
|
140
154
|
"mysql2": "^3.0.0",
|
|
141
|
-
"@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0"
|
|
155
|
+
"@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0",
|
|
156
|
+
"@anthropic-ai/sdk": ">=0.60.0"
|
|
142
157
|
},
|
|
143
158
|
"peerDependenciesMeta": {
|
|
144
159
|
"react-server-dom-webpack": {
|
|
@@ -152,17 +167,21 @@
|
|
|
152
167
|
},
|
|
153
168
|
"@libsql/client": {
|
|
154
169
|
"optional": true
|
|
170
|
+
},
|
|
171
|
+
"@anthropic-ai/sdk": {
|
|
172
|
+
"optional": true
|
|
155
173
|
}
|
|
156
174
|
},
|
|
157
175
|
"repository": {
|
|
158
176
|
"type": "git",
|
|
159
|
-
"url": "https://github.com/junebuild/june"
|
|
177
|
+
"url": "git+https://github.com/junebuild/june.git",
|
|
178
|
+
"directory": "packages/june"
|
|
160
179
|
},
|
|
161
180
|
"devDependencies": {
|
|
162
181
|
"@libsql/client": "^0.17.3",
|
|
163
182
|
"@types/pg": "^8.20.0",
|
|
164
183
|
"mysql2": "^3.22.5",
|
|
165
184
|
"pg": "^8.21.0",
|
|
166
|
-
"react-server-dom-webpack": "19.
|
|
185
|
+
"react-server-dom-webpack": "19.3.0"
|
|
167
186
|
}
|
|
168
187
|
}
|
package/src/adapter.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
// `workers()` is the zero-config default (this file, no import). Other targets
|
|
8
8
|
// are explicit `deploy: { adapter: vercel() }` from their own package.
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { cp, copyFile, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { cp, copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
11
11
|
import { basename, dirname, join } from "node:path";
|
|
12
12
|
|
|
13
13
|
import type { JuneConfig } from "@junejs/core/config";
|
|
14
|
+
import { stripJsonc } from "./tsconfig-jsx";
|
|
14
15
|
|
|
15
16
|
export type AdapterCapabilities = {
|
|
16
17
|
runtime: "edge" | "node" | "serverless" | "native" | "static";
|
|
@@ -20,6 +21,9 @@ export type AdapterCapabilities = {
|
|
|
20
21
|
persistentConnections: boolean;
|
|
21
22
|
// Who serves prerendered static files: the platform CDN, the server, or none.
|
|
22
23
|
assets: "platform" | "server" | "none";
|
|
24
|
+
// Durable Objects (the per-session agent actor). Only workers() today; the
|
|
25
|
+
// build skips the durable-agent mount (with a notice) when absent.
|
|
26
|
+
durableObjects?: boolean;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
// What the adapter contributes to the generated worker entry: extra imports and
|
|
@@ -37,6 +41,11 @@ export type ResourcePlan = {
|
|
|
37
41
|
// A declared `db` → a D1 binding named `binding`. The runtime provider
|
|
38
42
|
// (bindWorkerResources) reads env[binding]; keep in sync (default "DB").
|
|
39
43
|
db?: { binding: string; databaseName: string };
|
|
44
|
+
// A durable agent (an app/agent directory on a DO-capable target) → the DO
|
|
45
|
+
// namespace binding createWorker reads (env.AGENT) and the class the
|
|
46
|
+
// generated entry exports. workers() emits durable_objects + the SQLite-class
|
|
47
|
+
// migration from it.
|
|
48
|
+
agent?: { binding: string; className: string };
|
|
40
49
|
};
|
|
41
50
|
|
|
42
51
|
export type AdapterEmitContext = {
|
|
@@ -53,6 +62,9 @@ export type AdapterEmitContext = {
|
|
|
53
62
|
// copied into outDir/assets. Adapters that build a separate static tier (vercel)
|
|
54
63
|
// place these there; whole-assets targets (workers/static/deno) ignore it.
|
|
55
64
|
publicFiles?: string[];
|
|
65
|
+
// Extensions (lowercase, no dot: "wasm", "otf") of the binary files the worker bundle
|
|
66
|
+
// imports and leaves external for the platform's bundler to load.
|
|
67
|
+
binaryImports?: string[];
|
|
56
68
|
};
|
|
57
69
|
|
|
58
70
|
export interface JuneAdapter {
|
|
@@ -77,13 +89,97 @@ export interface JuneAdapter {
|
|
|
77
89
|
emit(ctx: AdapterEmitContext): Promise<void>;
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
// Whether an app-owned wrangler config actually BINDS the generated DO class
|
|
93
|
+
// under the binding name createWorker reads (env.AGENT) — not merely mentions
|
|
94
|
+
// the class (a migrations entry does that too, and a binding under another
|
|
95
|
+
// name never reaches the chat route). JSONC is parsed for the real
|
|
96
|
+
// durable_objects.bindings entry; TOML is scanned per
|
|
97
|
+
// [[durable_objects.bindings]] table with comments stripped, so a
|
|
98
|
+
// commented-out binding never counts. The TOML scan covers wrangler's
|
|
99
|
+
// canonical table form; an exotic inline-table config may draw a spurious
|
|
100
|
+
// (advisory) warning, never a suppressed one. Pure — unit-tested directly.
|
|
101
|
+
export function hasDurableBinding(text: string, file: string, binding: string, className: string): boolean {
|
|
102
|
+
if (file.endsWith(".toml")) {
|
|
103
|
+
const lines = stripTomlComments(text).split("\n");
|
|
104
|
+
let inBindings = false;
|
|
105
|
+
let name: string | undefined;
|
|
106
|
+
let cls: string | undefined;
|
|
107
|
+
const matches = () => name === binding && cls === className;
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const t = line.trim();
|
|
110
|
+
if (t.startsWith("[")) {
|
|
111
|
+
if (inBindings && matches()) return true; // the table that just ended bound it
|
|
112
|
+
inBindings = /^\[\[\s*durable_objects\.bindings\s*\]\]$/.test(t);
|
|
113
|
+
name = cls = undefined;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!inBindings) continue;
|
|
117
|
+
const kv = t.match(/^(name|class_name)\s*=\s*"([^"]*)"/);
|
|
118
|
+
if (kv) kv[1] === "name" ? (name = kv[2]) : (cls = kv[2]);
|
|
119
|
+
}
|
|
120
|
+
return inBindings && matches();
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(stripJsonc(text)) as { durable_objects?: { bindings?: { name?: string; class_name?: string }[] } };
|
|
124
|
+
return (parsed.durable_objects?.bindings ?? []).some((b) => b.name === binding && b.class_name === className);
|
|
125
|
+
} catch {
|
|
126
|
+
return text.includes(className); // unparsable — loose fallback, never crash the build
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Does an app-owned wrangler config give `.ext` files a loader? Any rule with a glob ending in
|
|
131
|
+
// the extension (`**/*.otf`, `**/*.{otf,ttf}`) counts, whatever its type. The warning this
|
|
132
|
+
// feeds is advisory, so a glob it can't read may draw a spurious warning, never suppress one;
|
|
133
|
+
// comments are stripped first, so a commented-out rule doesn't count. undefined: the JSONC
|
|
134
|
+
// couldn't be parsed, so the answer is unknown. Pure — unit-tested.
|
|
135
|
+
export function hasLoaderRule(text: string, file: string, ext: string): boolean | undefined {
|
|
136
|
+
const names = (glob: string) => new RegExp(`\\.(${ext}|\\{[^}]*\\b${ext}\\b[^}]*\\})$`, "i").test(glob);
|
|
137
|
+
if (file.endsWith(".toml")) {
|
|
138
|
+
// The globs = [...] arrays of [[rules]] tables, with '#' comments removed.
|
|
139
|
+
const body = stripTomlComments(text);
|
|
140
|
+
let inRules = false;
|
|
141
|
+
for (const line of body.split("\n")) {
|
|
142
|
+
const t = line.trim();
|
|
143
|
+
if (t.startsWith("[")) {
|
|
144
|
+
inRules = /^\[\[\s*rules\s*\]\]$/.test(t);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const globs = inRules ? t.match(/^globs\s*=\s*\[(.*)\]/) : null;
|
|
148
|
+
if (globs && [...globs[1]!.matchAll(/"([^"]*)"/g)].some((m) => names(m[1]!))) return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(stripJsonc(text)) as { rules?: { globs?: string[] }[] };
|
|
154
|
+
return (parsed.rules ?? []).some((r) => (r.globs ?? []).some(names));
|
|
155
|
+
} catch {
|
|
156
|
+
return undefined; // unparsable — the caller warns that it couldn't check, never crashes the build
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// '#' to end of line, unless inside a double-quoted string.
|
|
161
|
+
function stripTomlComments(text: string): string {
|
|
162
|
+
return text
|
|
163
|
+
.split("\n")
|
|
164
|
+
.map((line) => {
|
|
165
|
+
let inString = false;
|
|
166
|
+
for (let i = 0; i < line.length; i++) {
|
|
167
|
+
const ch = line[i];
|
|
168
|
+
if (ch === '"' && line[i - 1] !== "\\") inString = !inString;
|
|
169
|
+
else if (ch === "#" && !inString) return line.slice(0, i);
|
|
170
|
+
}
|
|
171
|
+
return line;
|
|
172
|
+
})
|
|
173
|
+
.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
80
176
|
// The built-in default. Reproduces exactly what build.ts emitted inline:
|
|
81
177
|
// withAssets(pipeline) as the entry, and a wrangler.jsonc with the assets
|
|
82
178
|
// binding (run_worker_first) + optional custom domain.
|
|
83
179
|
export function workers(opts?: { name?: string; domain?: string }): JuneAdapter {
|
|
84
180
|
return {
|
|
85
181
|
name: "workers",
|
|
86
|
-
capabilities: { runtime: "edge", persistentConnections: true, assets: "platform" },
|
|
182
|
+
capabilities: { runtime: "edge", persistentConnections: true, assets: "platform", durableObjects: true },
|
|
87
183
|
conditions: ["source", "workerd", "edge", "import", "default"],
|
|
88
184
|
// workers-og is a workerd-native WASM package: rolldown cannot bundle its
|
|
89
185
|
// .wasm assets (wrangler's own bundler applies the CompiledWasm rule). Mark
|
|
@@ -98,9 +194,46 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
|
|
|
98
194
|
};
|
|
99
195
|
},
|
|
100
196
|
|
|
101
|
-
async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
|
|
197
|
+
async emit({ appRoot, outDir, hasAssets, config, plan, defaultName, binaryImports }) {
|
|
198
|
+
// Wrangler loads .wasm by default (CompiledWasm) and has no loader for the other
|
|
199
|
+
// binaries build.ts leaves external, so a font package's .otf failed the deploy with
|
|
200
|
+
// "No loader is configured" (#379). Each one the bundle imports gets a Data rule: the
|
|
201
|
+
// module is the file's bytes, which is what a font or image package's workerd entry
|
|
202
|
+
// expects.
|
|
203
|
+
const dataExts = (binaryImports ?? []).filter((e) => e !== "wasm");
|
|
102
204
|
// An app that manages its own wrangler config wins — don't overwrite it.
|
|
103
|
-
|
|
205
|
+
// But a durable agent NEEDS its DO binding: warn when the app-owned config
|
|
206
|
+
// doesn't BIND the generated class under the name createWorker reads
|
|
207
|
+
// (hasDurableBinding above — a migrations-only mention, a binding under
|
|
208
|
+
// another name, or a commented-out table must all still warn), instead of
|
|
209
|
+
// silently shipping a worker whose chat surface 404s.
|
|
210
|
+
const own = ["wrangler.toml", "wrangler.jsonc"].map((f) => join(appRoot, f)).find(existsSync);
|
|
211
|
+
if (own) {
|
|
212
|
+
if (plan.agent) {
|
|
213
|
+
const cls = plan.agent.className;
|
|
214
|
+
const bound = hasDurableBinding(await readFile(own, "utf8"), own, plan.agent.binding, cls);
|
|
215
|
+
if (!bound) {
|
|
216
|
+
const snippet = own.endsWith(".toml")
|
|
217
|
+
? `[[durable_objects.bindings]]\nname = "${plan.agent.binding}"\nclass_name = "${cls}"\n\n[[migrations]]\ntag = "v1"\nnew_sqlite_classes = ["${cls}"]`
|
|
218
|
+
: `"durable_objects": { "bindings": [{ "name": "${plan.agent.binding}", "class_name": "${cls}" }] },\n"migrations": [{ "tag": "v1", "new_sqlite_classes": ["${cls}"] }]`;
|
|
219
|
+
console.warn(`[june build] durable agent built, but ${basename(own)} doesn't bind it — add:\n${snippet}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const text = dataExts.length ? await readFile(own, "utf8") : "";
|
|
223
|
+
const found = dataExts.map((ext) => [ext, hasLoaderRule(text, own, ext)] as const);
|
|
224
|
+
const unloaded = found.filter(([, has]) => has === false).map(([ext]) => ext);
|
|
225
|
+
const unknown = found.filter(([, has]) => has === undefined).map(([ext]) => ext);
|
|
226
|
+
if (unknown.length) {
|
|
227
|
+
const globs = unknown.map((e) => `"**/*.${e}"`).join(", ");
|
|
228
|
+
console.warn(`[june build] the worker imports ${unknown.map((e) => `.${e}`).join(", ")} files, and ${basename(own)} couldn't be parsed to check for a rule that loads them — make sure it has one, or the deploy fails with "No loader is configured":\n"rules": [{ "type": "Data", "globs": [${globs}], "fallthrough": true }]`);
|
|
229
|
+
}
|
|
230
|
+
if (unloaded.length) {
|
|
231
|
+
const globs = unloaded.map((e) => `"**/*.${e}"`).join(", ");
|
|
232
|
+
const snippet = own.endsWith(".toml")
|
|
233
|
+
? `[[rules]]\ntype = "Data"\nglobs = [${globs}]\nfallthrough = true`
|
|
234
|
+
: `"rules": [{ "type": "Data", "globs": [${globs}], "fallthrough": true }]`;
|
|
235
|
+
console.warn(`[june build] the worker imports ${unloaded.map((e) => `.${e}`).join(", ")} files, but ${basename(own)} has no rule to load them (the deploy will fail with "No loader is configured") — add:\n${snippet}`);
|
|
236
|
+
}
|
|
104
237
|
return;
|
|
105
238
|
}
|
|
106
239
|
const deployCfg = config.deploy;
|
|
@@ -133,6 +266,18 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
|
|
|
133
266
|
],
|
|
134
267
|
}
|
|
135
268
|
: {}),
|
|
269
|
+
// The durable agent: one SQLite-backed DO class = the per-session
|
|
270
|
+
// AgentSession actor (its ctx.storage.sql is the durable loop's
|
|
271
|
+
// synchronous store). The binding name is what createWorker reads.
|
|
272
|
+
...(plan.agent
|
|
273
|
+
? {
|
|
274
|
+
durable_objects: { bindings: [{ name: plan.agent.binding, class_name: plan.agent.className }] },
|
|
275
|
+
migrations: [{ tag: "v1", new_sqlite_classes: [plan.agent.className] }],
|
|
276
|
+
}
|
|
277
|
+
: {}),
|
|
278
|
+
// Binary files the bundle imports that wrangler has no default loader for.
|
|
279
|
+
// fallthrough: without it wrangler drops its own default Data rule (**/*.bin).
|
|
280
|
+
...(dataExts.length ? { rules: [{ type: "Data", globs: dataExts.map((e) => `**/*.${e}`), fallthrough: true }] } : {}),
|
|
136
281
|
// config deploy.domain → a Workers custom domain; without it the
|
|
137
282
|
// regenerated file would silently drop a hand-attached domain.
|
|
138
283
|
...(opts?.domain ?? deployCfg?.domain
|