@junejs/server 0.1.3 → 0.2.0

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 (128) hide show
  1. package/assets/icon-latin.LICENSE.txt +92 -0
  2. package/assets/icon-latin.ttf +0 -0
  3. package/dist/adapter-DcZ-W1-7.d.ts +62 -0
  4. package/dist/adapter.d.ts +2 -56
  5. package/dist/adapter.js +62 -5
  6. package/dist/agent-compile.d.ts +38 -0
  7. package/dist/agent-compile.js +145 -0
  8. package/dist/agent-discover.d.ts +4 -4
  9. package/dist/agent-discover.js +30 -44
  10. package/dist/agent-durable.d.ts +94 -19
  11. package/dist/agent-durable.js +647 -32
  12. package/dist/agent-native.d.ts +44 -13
  13. package/dist/agent-native.js +264 -27
  14. package/dist/app.d.ts +5 -5
  15. package/dist/app.js +42 -10
  16. package/dist/blob.d.ts +4 -5
  17. package/dist/build.d.ts +8 -30
  18. package/dist/build.js +135 -296
  19. package/dist/client-bundle.d.ts +8 -9
  20. package/dist/client-bundle.js +1 -1
  21. package/dist/config-freeze-CyXvNqi2.d.ts +17 -0
  22. package/dist/config-freeze.d.ts +2 -0
  23. package/dist/config-freeze.js +54 -0
  24. package/dist/config-loader.d.ts +3 -4
  25. package/dist/connection-auth.d.ts +38 -0
  26. package/dist/connection-auth.js +31 -0
  27. package/dist/content-freeze-CyiW2fRK.d.ts +4 -0
  28. package/dist/content-freeze.d.ts +2 -0
  29. package/dist/content-freeze.js +83 -0
  30. package/dist/content.d.ts +22 -9
  31. package/dist/content.js +43 -3
  32. package/dist/core-version.d.ts +4 -0
  33. package/dist/core-version.js +12 -0
  34. package/dist/css-modules-ambient.d.ts +2 -1
  35. package/dist/css-modules.d.ts +10 -11
  36. package/dist/css.d.ts +9 -10
  37. package/dist/d1-migrate.d.ts +6 -7
  38. package/dist/d1-migrate.js +3 -2
  39. package/dist/db.d.ts +2 -2
  40. package/dist/deploy.d.ts +3 -4
  41. package/dist/deploy.js +3 -2
  42. package/dist/dev-reload.d.ts +3 -4
  43. package/dist/dev.d.ts +8 -5
  44. package/dist/dev.js +40 -9
  45. package/dist/favicon.d.ts +37 -0
  46. package/dist/favicon.js +248 -0
  47. package/dist/host.d.ts +6 -5
  48. package/dist/host.js +2 -1
  49. package/dist/icon-letter.d.ts +3 -0
  50. package/dist/icon-letter.js +8 -0
  51. package/dist/index.d.ts +11 -5
  52. package/dist/index.js +9 -3
  53. package/dist/instrumentation.d.ts +2 -3
  54. package/dist/instrumentation.js +2 -1
  55. package/dist/island-registry.d.ts +6 -7
  56. package/dist/isolate-local.d.ts +5 -0
  57. package/dist/isolate-local.js +12 -0
  58. package/dist/kv.d.ts +3 -4
  59. package/dist/llms-links.d.ts +9 -0
  60. package/dist/llms-links.js +65 -0
  61. package/dist/manifest-CwTT1zVs.d.ts +11 -0
  62. package/dist/manifest.d.ts +2 -0
  63. package/dist/manifest.js +113 -0
  64. package/dist/negotiate.d.ts +4 -3
  65. package/dist/negotiate.js +24 -14
  66. package/dist/{pipeline-CCp9idQ4.d.ts → pipeline-BHUjBO0G.d.ts} +13 -1
  67. package/dist/pipeline.d.ts +2 -2
  68. package/dist/pipeline.js +286 -35
  69. package/dist/resources.d.ts +5 -6
  70. package/dist/route-rank.d.ts +6 -7
  71. package/dist/route-scan-BMbBzPIB.d.ts +20 -0
  72. package/dist/route-scan.d.ts +2 -0
  73. package/dist/route-scan.js +100 -0
  74. package/dist/router.d.ts +12 -13
  75. package/dist/router.js +51 -38
  76. package/dist/rsc-build.d.ts +6 -7
  77. package/dist/rsc-bundle.d.ts +6 -7
  78. package/dist/rsc-manifest.d.ts +5 -6
  79. package/dist/rsc-runtime/flight-render-routes.d.ts +3 -4
  80. package/dist/rsc-runtime/flight-render.d.ts +2 -3
  81. package/dist/rsc-runtime/flight-to-html.d.ts +2 -3
  82. package/dist/rsc-runtime/flight-to-html.js +3 -2
  83. package/dist/rsc-runtime/rsc-worker.js +11 -8
  84. package/dist/rsc-runtime/rsdw-shims.d.ts +2 -1
  85. package/dist/segment.d.ts +4 -5
  86. package/dist/sqlite-driver.d.ts +11 -11
  87. package/dist/sqlite-driver.js +17 -4
  88. package/dist/static-files.d.ts +4 -5
  89. package/dist/tsconfig-jsx.d.ts +4 -4
  90. package/dist/tsconfig-jsx.js +1 -1
  91. package/dist/turn-events.d.ts +4 -0
  92. package/dist/turn-events.js +46 -0
  93. package/dist/web-manifest.d.ts +5 -0
  94. package/dist/web-manifest.js +42 -0
  95. package/dist/worker-2qbQjj2c.d.ts +64 -0
  96. package/dist/worker.d.ts +2 -57
  97. package/dist/worker.js +69 -27
  98. package/package.json +22 -9
  99. package/src/adapter.ts +84 -3
  100. package/src/agent-compile.ts +271 -0
  101. package/src/agent-discover.ts +77 -63
  102. package/src/agent-durable.ts +998 -30
  103. package/src/agent-native.ts +370 -35
  104. package/src/app.ts +75 -6
  105. package/src/build.ts +230 -446
  106. package/src/config-freeze.ts +109 -0
  107. package/src/connection-auth.ts +101 -0
  108. package/src/content-freeze.ts +151 -0
  109. package/src/content.ts +55 -4
  110. package/src/core-version.ts +34 -0
  111. package/src/dev.ts +41 -8
  112. package/src/favicon.ts +325 -0
  113. package/src/host.ts +4 -3
  114. package/src/icon-letter.ts +14 -0
  115. package/src/index.ts +22 -0
  116. package/src/isolate-local.ts +33 -0
  117. package/src/llms-links.ts +96 -0
  118. package/src/manifest.ts +130 -0
  119. package/src/negotiate.ts +39 -15
  120. package/src/pipeline.ts +494 -44
  121. package/src/route-scan.ts +161 -0
  122. package/src/router.ts +66 -54
  123. package/src/rsc-runtime/rsc-worker.tsx +5 -4
  124. package/src/sqlite-driver.ts +36 -6
  125. package/src/tsconfig-jsx.ts +2 -2
  126. package/src/turn-events.ts +43 -0
  127. package/src/web-manifest.ts +38 -0
  128. package/src/worker.ts +131 -35
package/dist/worker.d.ts CHANGED
@@ -1,57 +1,2 @@
1
- import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-CCp9idQ4.js";
2
- import { BrandedRoute } from "@junejs/core/route";
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 route = durableAgentSurface(() => currentEnv?.AGENT, {
74
- agentName: manifest.agentName,
75
- chatPath: manifest.agent.runtime.chat.path
76
- });
77
- return (req) => route(req);
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 accept = request.headers.get("accept") ?? "";
121
- if (assets && request.method === "GET" && isPagePath && /text\/markdown/.test(accept)) {
122
- const base = url.pathname === "/" ? "/index" : url.pathname.replace(/\/+$/, "");
123
- const mdUrl = new URL(url);
124
- mdUrl.pathname = `${base}.md`;
125
- const a = await assets.fetch(new Request(mdUrl.toString(), { headers: request.headers }));
126
- if (a.ok) {
127
- const body = await a.text();
128
- const headers = new Headers(a.headers);
129
- headers.set("content-type", "text/markdown; charset=utf-8");
130
- headers.set("x-markdown-tokens", estimateTokens(body));
131
- if (opts.link) headers.set("link", opts.link);
132
- return new Response(body, {
133
- status: 200,
134
- headers
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
- if (addLink || immutable) {
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",
3
+ "version": "0.2.0",
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",
@@ -117,7 +123,8 @@
117
123
  },
118
124
  "files": [
119
125
  "src",
120
- "dist"
126
+ "dist",
127
+ "assets"
121
128
  ],
122
129
  "scripts": {
123
130
  "test": "bun test",
@@ -125,20 +132,22 @@
125
132
  "build": "tsdown"
126
133
  },
127
134
  "dependencies": {
128
- "@junejs/core": ">=0.1.0",
129
- "@junejs/db": ">=0.0.33",
135
+ "@junejs/db": "^0.2.0",
130
136
  "@momiji-rs/sparkdown": "^0.0.4",
137
+ "@resvg/resvg-wasm": "^2.6.2",
131
138
  "lightningcss": "^1.27.0",
132
- "oxc-parser": "^0.137.0",
139
+ "oxc-parser": "^0.152.0",
133
140
  "rolldown": "^1.1.0"
134
141
  },
135
142
  "peerDependencies": {
143
+ "@junejs/core": "^0.2.0",
136
144
  "react": "^19.2.0",
137
145
  "react-dom": "^19.2.0",
138
- "react-server-dom-webpack": "19.2.7",
146
+ "react-server-dom-webpack": "19.3.0",
139
147
  "pg": "^8.0.0",
140
148
  "mysql2": "^3.0.0",
141
- "@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0"
149
+ "@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0",
150
+ "@anthropic-ai/sdk": ">=0.60.0"
142
151
  },
143
152
  "peerDependenciesMeta": {
144
153
  "react-server-dom-webpack": {
@@ -152,17 +161,21 @@
152
161
  },
153
162
  "@libsql/client": {
154
163
  "optional": true
164
+ },
165
+ "@anthropic-ai/sdk": {
166
+ "optional": true
155
167
  }
156
168
  },
157
169
  "repository": {
158
170
  "type": "git",
159
- "url": "https://github.com/junebuild/june"
171
+ "url": "git+https://github.com/junebuild/june.git",
172
+ "directory": "packages/june"
160
173
  },
161
174
  "devDependencies": {
162
175
  "@libsql/client": "^0.17.3",
163
176
  "@types/pg": "^8.20.0",
164
177
  "mysql2": "^3.22.5",
165
178
  "pg": "^8.21.0",
166
- "react-server-dom-webpack": "19.2.7"
179
+ "react-server-dom-webpack": "19.3.0"
167
180
  }
168
181
  }
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 = {
@@ -77,13 +86,60 @@ export interface JuneAdapter {
77
86
  emit(ctx: AdapterEmitContext): Promise<void>;
78
87
  }
79
88
 
89
+ // Whether an app-owned wrangler config actually BINDS the generated DO class
90
+ // under the binding name createWorker reads (env.AGENT) — not merely mentions
91
+ // the class (a migrations entry does that too, and a binding under another
92
+ // name never reaches the chat route). JSONC is parsed for the real
93
+ // durable_objects.bindings entry; TOML is scanned per
94
+ // [[durable_objects.bindings]] table with comments stripped, so a
95
+ // commented-out binding never counts. The TOML scan covers wrangler's
96
+ // canonical table form; an exotic inline-table config may draw a spurious
97
+ // (advisory) warning, never a suppressed one. Pure — unit-tested directly.
98
+ export function hasDurableBinding(text: string, file: string, binding: string, className: string): boolean {
99
+ if (file.endsWith(".toml")) {
100
+ // Strip comments: '#' to end of line, unless inside a double-quoted string.
101
+ const lines = text.split("\n").map((line) => {
102
+ let inString = false;
103
+ for (let i = 0; i < line.length; i++) {
104
+ const ch = line[i];
105
+ if (ch === '"' && line[i - 1] !== "\\") inString = !inString;
106
+ else if (ch === "#" && !inString) return line.slice(0, i);
107
+ }
108
+ return line;
109
+ });
110
+ let inBindings = false;
111
+ let name: string | undefined;
112
+ let cls: string | undefined;
113
+ const matches = () => name === binding && cls === className;
114
+ for (const line of lines) {
115
+ const t = line.trim();
116
+ if (t.startsWith("[")) {
117
+ if (inBindings && matches()) return true; // the table that just ended bound it
118
+ inBindings = /^\[\[\s*durable_objects\.bindings\s*\]\]$/.test(t);
119
+ name = cls = undefined;
120
+ continue;
121
+ }
122
+ if (!inBindings) continue;
123
+ const kv = t.match(/^(name|class_name)\s*=\s*"([^"]*)"/);
124
+ if (kv) kv[1] === "name" ? (name = kv[2]) : (cls = kv[2]);
125
+ }
126
+ return inBindings && matches();
127
+ }
128
+ try {
129
+ const parsed = JSON.parse(stripJsonc(text)) as { durable_objects?: { bindings?: { name?: string; class_name?: string }[] } };
130
+ return (parsed.durable_objects?.bindings ?? []).some((b) => b.name === binding && b.class_name === className);
131
+ } catch {
132
+ return text.includes(className); // unparsable — loose fallback, never crash the build
133
+ }
134
+ }
135
+
80
136
  // The built-in default. Reproduces exactly what build.ts emitted inline:
81
137
  // withAssets(pipeline) as the entry, and a wrangler.jsonc with the assets
82
138
  // binding (run_worker_first) + optional custom domain.
83
139
  export function workers(opts?: { name?: string; domain?: string }): JuneAdapter {
84
140
  return {
85
141
  name: "workers",
86
- capabilities: { runtime: "edge", persistentConnections: true, assets: "platform" },
142
+ capabilities: { runtime: "edge", persistentConnections: true, assets: "platform", durableObjects: true },
87
143
  conditions: ["source", "workerd", "edge", "import", "default"],
88
144
  // workers-og is a workerd-native WASM package: rolldown cannot bundle its
89
145
  // .wasm assets (wrangler's own bundler applies the CompiledWasm rule). Mark
@@ -100,7 +156,23 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
100
156
 
101
157
  async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
102
158
  // An app that manages its own wrangler config wins — don't overwrite it.
103
- if (existsSync(join(appRoot, "wrangler.toml")) || existsSync(join(appRoot, "wrangler.jsonc"))) {
159
+ // But a durable agent NEEDS its DO binding: warn when the app-owned config
160
+ // doesn't BIND the generated class under the name createWorker reads
161
+ // (hasDurableBinding above — a migrations-only mention, a binding under
162
+ // another name, or a commented-out table must all still warn), instead of
163
+ // silently shipping a worker whose chat surface 404s.
164
+ const own = ["wrangler.toml", "wrangler.jsonc"].map((f) => join(appRoot, f)).find(existsSync);
165
+ if (own) {
166
+ if (plan.agent) {
167
+ const cls = plan.agent.className;
168
+ const bound = hasDurableBinding(await readFile(own, "utf8"), own, plan.agent.binding, cls);
169
+ if (!bound) {
170
+ const snippet = own.endsWith(".toml")
171
+ ? `[[durable_objects.bindings]]\nname = "${plan.agent.binding}"\nclass_name = "${cls}"\n\n[[migrations]]\ntag = "v1"\nnew_sqlite_classes = ["${cls}"]`
172
+ : `"durable_objects": { "bindings": [{ "name": "${plan.agent.binding}", "class_name": "${cls}" }] },\n"migrations": [{ "tag": "v1", "new_sqlite_classes": ["${cls}"] }]`;
173
+ console.warn(`[june build] durable agent built, but ${basename(own)} doesn't bind it — add:\n${snippet}`);
174
+ }
175
+ }
104
176
  return;
105
177
  }
106
178
  const deployCfg = config.deploy;
@@ -133,6 +205,15 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
133
205
  ],
134
206
  }
135
207
  : {}),
208
+ // The durable agent: one SQLite-backed DO class = the per-session
209
+ // AgentSession actor (its ctx.storage.sql is the durable loop's
210
+ // synchronous store). The binding name is what createWorker reads.
211
+ ...(plan.agent
212
+ ? {
213
+ durable_objects: { bindings: [{ name: plan.agent.binding, class_name: plan.agent.className }] },
214
+ migrations: [{ tag: "v1", new_sqlite_classes: [plan.agent.className] }],
215
+ }
216
+ : {}),
136
217
  // config deploy.domain → a Workers custom domain; without it the
137
218
  // regenerated file would silently drop a hand-attached domain.
138
219
  ...(opts?.domain ?? deployCfg?.domain