@junejs/server 0.1.2-dev.9 → 1.0.0-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-durable.js +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/isolate-local.d.ts +6 -0
- package/dist/isolate-local.js +12 -0
- package/dist/{pipeline-CCp9idQ4.d.ts → pipeline-cCR6MGNK.d.ts} +8 -0
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -1
- package/dist/worker.d.ts +1 -1
- package/package.json +5 -4
- package/src/agent-durable.ts +50 -2
- package/src/isolate-local.ts +33 -0
- package/src/pipeline.ts +14 -3
package/dist/agent-durable.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isolateLocal } from "./isolate-local.js";
|
|
1
2
|
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
2
3
|
import { channelDispatch, resolveChannel } from "@junejs/core/agent-config";
|
|
3
4
|
import { AgentSession, ResumeAuthorizationError, withSystem } from "@junejs/core/agent-runtime";
|
|
@@ -325,6 +326,18 @@ function durableAgentSurface(getNamespace, opts) {
|
|
|
325
326
|
return Response.json({ text: await sseTurnFinalText(res) });
|
|
326
327
|
};
|
|
327
328
|
}
|
|
329
|
+
function resolveServices(factory, env, agentName) {
|
|
330
|
+
if (!factory) return void 0;
|
|
331
|
+
if (typeof env !== "object" || env === null) return factory(env);
|
|
332
|
+
const cache = isolateLocal("june.durableChannelSurface.services", () => /* @__PURE__ */ new WeakMap());
|
|
333
|
+
let perAgent = cache.get(env);
|
|
334
|
+
if (!perAgent) {
|
|
335
|
+
perAgent = /* @__PURE__ */ new Map();
|
|
336
|
+
cache.set(env, perAgent);
|
|
337
|
+
}
|
|
338
|
+
if (!perAgent.has(agentName)) perAgent.set(agentName, factory(env));
|
|
339
|
+
return perAgent.get(agentName);
|
|
340
|
+
}
|
|
328
341
|
function durableChannelSurface(getNamespace, opts) {
|
|
329
342
|
return channelDispatch(opts.channels.map((c) => resolveChannel(c, opts.env)), {
|
|
330
343
|
agent: {
|
|
@@ -335,7 +348,7 @@ function durableChannelSurface(getNamespace, opts) {
|
|
|
335
348
|
channels: [],
|
|
336
349
|
connections: []
|
|
337
350
|
},
|
|
338
|
-
services: opts.services
|
|
351
|
+
services: resolveServices(opts.services, opts.env, opts.agentName),
|
|
339
352
|
waitUntil: opts.waitUntil,
|
|
340
353
|
run: async (message, o) => {
|
|
341
354
|
const namespace = getNamespace();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, staticSite, vercel, workers } from "./adapter.js";
|
|
2
2
|
import { CreateAppOptions, JuneApp, createApp } from "./app.js";
|
|
3
3
|
import { R2Bucket, localBlob, r2 } from "./blob.js";
|
|
4
|
-
import { a as Pipeline, d as createPipeline, n as LayoutComponent, o as PipelineConfig, s as Resolved, u as RouteResolver } from "./pipeline-
|
|
4
|
+
import { a as Pipeline, d as createPipeline, n as LayoutComponent, o as PipelineConfig, s as Resolved, u as RouteResolver } from "./pipeline-cCR6MGNK.js";
|
|
5
5
|
import { WorkerManifest, createWorker } from "./worker.js";
|
|
6
6
|
import { BuildResult, buildManifest, freezeConfig, generateContent, juneBuild, scanRoutes } from "./build.js";
|
|
7
7
|
import { loadJuneConfig } from "./config-loader.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/isolate-local.d.ts
|
|
2
|
+
type IsolateLocal = <T>(key: string | symbol, make: () => T) => T;
|
|
3
|
+
declare function makeIsolateLocalFallback(): IsolateLocal;
|
|
4
|
+
declare const isolateLocal: IsolateLocal;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { IsolateLocal, isolateLocal, makeIsolateLocalFallback };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as juneDb from "@junejs/db";
|
|
2
|
+
//#region src/isolate-local.ts
|
|
3
|
+
function makeIsolateLocalFallback() {
|
|
4
|
+
const values = /* @__PURE__ */ new Map();
|
|
5
|
+
return (key, make) => {
|
|
6
|
+
if (!values.has(key)) values.set(key, make());
|
|
7
|
+
return values.get(key);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const isolateLocal = juneDb.isolateLocal ?? makeIsolateLocalFallback();
|
|
11
|
+
//#endregion
|
|
12
|
+
export { isolateLocal, makeIsolateLocalFallback };
|
|
@@ -4,6 +4,7 @@ import React from "react";
|
|
|
4
4
|
import { DocumentConfig } from "@junejs/core/document";
|
|
5
5
|
import { I18nConfig } from "@junejs/core/i18n";
|
|
6
6
|
import { Resources } from "@junejs/core/resources";
|
|
7
|
+
import { Principal, Session } from "@junejs/core/context";
|
|
7
8
|
//#region src/pipeline.d.ts
|
|
8
9
|
type LayoutComponent = React.ComponentType<{
|
|
9
10
|
children: React.ReactNode;
|
|
@@ -32,6 +33,13 @@ type PipelineConfig = {
|
|
|
32
33
|
resolve: RouteResolver;
|
|
33
34
|
resources?: () => Promise<Resources> | Resources;
|
|
34
35
|
services?: () => Promise<unknown> | unknown;
|
|
36
|
+
identity?: (request: Request) => Promise<{
|
|
37
|
+
user?: Principal;
|
|
38
|
+
session?: Session;
|
|
39
|
+
}> | {
|
|
40
|
+
user?: Principal;
|
|
41
|
+
session?: Session;
|
|
42
|
+
};
|
|
35
43
|
earlyHints?: string[];
|
|
36
44
|
htmlCacheControl?: string;
|
|
37
45
|
notFoundComponent?: React.ComponentType<{
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,2 +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-
|
|
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-cCR6MGNK.js";
|
|
2
2
|
export { ExtraHandler, LayoutComponent, LoadingComponent, MiddlewareHandler, Pipeline, PipelineConfig, Resolved, ResolvedResource, ResourceHandler, RouteResolver, createPipeline };
|
package/dist/pipeline.js
CHANGED
|
@@ -171,7 +171,10 @@ function createPipeline(cfg) {
|
|
|
171
171
|
const url = new URL(request.url);
|
|
172
172
|
if (url.pathname === "/mcp") {
|
|
173
173
|
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
174
|
-
return mcpHandler(request, {
|
|
174
|
+
return mcpHandler(request, {
|
|
175
|
+
request,
|
|
176
|
+
...cfg.identity ? await cfg.identity(request) : void 0
|
|
177
|
+
});
|
|
175
178
|
}
|
|
176
179
|
if (request.method === "GET" && agent.discovery) {
|
|
177
180
|
const d = await discovery(url);
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-
|
|
1
|
+
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-cCR6MGNK.js";
|
|
2
2
|
import { BrandedRoute } from "@junejs/core/route";
|
|
3
3
|
import { AgentConfig } from "@junejs/core/config";
|
|
4
4
|
import React from "react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-dev.11",
|
|
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",
|
|
@@ -125,14 +125,14 @@
|
|
|
125
125
|
"build": "tsdown"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@junejs/db": ">=0.0
|
|
128
|
+
"@junejs/db": ">=0.1.0-dev.2",
|
|
129
129
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
130
130
|
"lightningcss": "^1.27.0",
|
|
131
131
|
"oxc-parser": "^0.137.0",
|
|
132
132
|
"rolldown": "^1.1.0"
|
|
133
133
|
},
|
|
134
134
|
"peerDependencies": {
|
|
135
|
-
"@junejs/core": ">=0.
|
|
135
|
+
"@junejs/core": ">=0.2.0-dev.26",
|
|
136
136
|
"react": "^19.2.0",
|
|
137
137
|
"react-dom": "^19.2.0",
|
|
138
138
|
"react-server-dom-webpack": "19.2.7",
|
|
@@ -156,7 +156,8 @@
|
|
|
156
156
|
},
|
|
157
157
|
"repository": {
|
|
158
158
|
"type": "git",
|
|
159
|
-
"url": "https://github.com/junebuild/june"
|
|
159
|
+
"url": "git+https://github.com/junebuild/june.git",
|
|
160
|
+
"directory": "packages/june"
|
|
160
161
|
},
|
|
161
162
|
"devDependencies": {
|
|
162
163
|
"@libsql/client": "^0.17.3",
|
package/src/agent-durable.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
type ChannelFactory,
|
|
42
42
|
} from "@junejs/core/agent-config";
|
|
43
43
|
import { ensureScope, runInScope } from "@junejs/db";
|
|
44
|
+
import { isolateLocal } from "./isolate-local";
|
|
44
45
|
import { assertCoreRuntimeVersion } from "./core-version";
|
|
45
46
|
|
|
46
47
|
// ── minimal structural Cloudflare surface (no @cloudflare/workers-types dep) ──
|
|
@@ -605,6 +606,43 @@ export function durableAgentSurface(
|
|
|
605
606
|
// fails in the background — surfaced via the channel's onError IF one is configured,
|
|
606
607
|
// otherwise swallowed (runBackground never rejects). The app is expected to bind
|
|
607
608
|
// env.AGENT when it mounts channels. Returns null for unclaimed requests.
|
|
609
|
+
// The services bag, memoized per (env object, agentName).
|
|
610
|
+
//
|
|
611
|
+
// Why that key. `env` identity is what makes the memo survive: a worker builds
|
|
612
|
+
// the surface inside `fetch`, so the only thing stable across requests is the
|
|
613
|
+
// env object the host hands in — and NOT the provider function, which the
|
|
614
|
+
// documented shape (`services: (e) => makeServices(e)`) recreates on every
|
|
615
|
+
// fetch. But env alone is too coarse: two surfaces mounted in one worker over
|
|
616
|
+
// the same env would then share whichever bag was built first, making the
|
|
617
|
+
// result depend on mount order. `agentName` is the surface's own stable
|
|
618
|
+
// identity, so it partitions them. (One agent must therefore have ONE services
|
|
619
|
+
// provider — mounting the same agentName twice with different providers is
|
|
620
|
+
// incoherent and keeps the first bag.)
|
|
621
|
+
//
|
|
622
|
+
// Held in an isolateLocal so duplicate module instances (workspace symlinks)
|
|
623
|
+
// share one cache; the outer WeakMap is keyed by env so a host handing out
|
|
624
|
+
// fresh env objects gets fresh bags instead of a leak. A non-object env (tests
|
|
625
|
+
// passing a primitive) skips memoization rather than throwing.
|
|
626
|
+
function resolveServices(
|
|
627
|
+
factory: ((env: unknown) => unknown) | undefined,
|
|
628
|
+
env: unknown,
|
|
629
|
+
agentName: string,
|
|
630
|
+
): unknown {
|
|
631
|
+
if (!factory) return undefined;
|
|
632
|
+
if (typeof env !== "object" || env === null) return factory(env);
|
|
633
|
+
const cache = isolateLocal(
|
|
634
|
+
"june.durableChannelSurface.services",
|
|
635
|
+
() => new WeakMap<object, Map<string, unknown>>(),
|
|
636
|
+
);
|
|
637
|
+
let perAgent = cache.get(env);
|
|
638
|
+
if (!perAgent) {
|
|
639
|
+
perAgent = new Map<string, unknown>();
|
|
640
|
+
cache.set(env, perAgent);
|
|
641
|
+
}
|
|
642
|
+
if (!perAgent.has(agentName)) perAgent.set(agentName, factory(env));
|
|
643
|
+
return perAgent.get(agentName);
|
|
644
|
+
}
|
|
645
|
+
|
|
608
646
|
export function durableChannelSurface(
|
|
609
647
|
getNamespace: () => DurableObjectNamespace | undefined,
|
|
610
648
|
opts: {
|
|
@@ -614,7 +652,17 @@ export function durableChannelSurface(
|
|
|
614
652
|
// The app's services factory — the SAME one AgentDurableObject uses — resolved HERE
|
|
615
653
|
// (worker isolate) and exposed to channel hooks as ctx.services. Channel hooks run at
|
|
616
654
|
// the edge, outside the DO, so they can't read the DO's ambient currentServices(); this
|
|
617
|
-
// gives them the same DI bag.
|
|
655
|
+
// gives them the same DI bag.
|
|
656
|
+
//
|
|
657
|
+
// Called once per (env object, agentName) — NOT once per surface. A worker
|
|
658
|
+
// typically constructs the surface inside `fetch` (env only exists inside an
|
|
659
|
+
// invocation), so resolving per construction rebuilt every client on every
|
|
660
|
+
// request and any cache the bag held could never hit. Memoizing on env
|
|
661
|
+
// identity fixes that; see resolveServices above for why the key is env ×
|
|
662
|
+
// agentName and not the provider function. Practical consequences: the
|
|
663
|
+
// provider must be a function of `env` alone, it runs again whenever the
|
|
664
|
+
// host hands in a different env object (a new isolate, or a test), and one
|
|
665
|
+
// agentName must have one provider.
|
|
618
666
|
services?: (env: unknown) => unknown;
|
|
619
667
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
620
668
|
},
|
|
@@ -624,7 +672,7 @@ export function durableChannelSurface(
|
|
|
624
672
|
// A minimal but complete AgentDefinition — channels only read ctx.agent.name; the
|
|
625
673
|
// full def isn't present in the worker (tools/model live in the DO).
|
|
626
674
|
agent: { name: opts.agentName, instructions: "", tools: [], skills: [], channels: [], connections: [] } satisfies AgentDefinition,
|
|
627
|
-
services: opts.services
|
|
675
|
+
services: resolveServices(opts.services, opts.env, opts.agentName),
|
|
628
676
|
waitUntil: opts.waitUntil,
|
|
629
677
|
run: async (message, o) => {
|
|
630
678
|
const namespace = getNamespace();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// isolate-local.ts — read @junejs/db's `isolateLocal` WITHOUT a named import.
|
|
2
|
+
//
|
|
3
|
+
// Same hazard core-version.ts exists for: a NAMED import of an export the
|
|
4
|
+
// resolved copy doesn't have dies at module LINK time with an unhelpful
|
|
5
|
+
// SyntaxError, before any of our own diagnostics can run. `isolateLocal` is new
|
|
6
|
+
// in @junejs/db 0.1.0-dev.2, and a version range cannot save us here — the
|
|
7
|
+
// floor has to stay satisfiable by the workspace's own db (which is the very
|
|
8
|
+
// version that lacks it), and `bun pm pack` rewrites a `workspace:*` dep to
|
|
9
|
+
// whatever version it resolves, which can be OLDER still.
|
|
10
|
+
//
|
|
11
|
+
// So: namespace import + optional read, with a local fallback. Unlike the core
|
|
12
|
+
// runtime contract, this one does NOT warrant throwing — isolate-scoped
|
|
13
|
+
// memoization is a lifetime optimization, not a correctness contract. The
|
|
14
|
+
// fallback memoizes in this module instance, so callers keep their cache; the
|
|
15
|
+
// only thing lost against an older db is sharing one value across DUPLICATE
|
|
16
|
+
// copies of the module (workspace symlinks), which costs a missed cache hit,
|
|
17
|
+
// never a wrong answer.
|
|
18
|
+
import * as juneDb from "@junejs/db";
|
|
19
|
+
|
|
20
|
+
export type IsolateLocal = <T>(key: string | symbol, make: () => T) => T;
|
|
21
|
+
|
|
22
|
+
// Exported for tests: the ambient read below can only ever see the workspace's
|
|
23
|
+
// own (matching) db, so the degraded path needs a direct handle to be provable.
|
|
24
|
+
export function makeIsolateLocalFallback(): IsolateLocal {
|
|
25
|
+
const values = new Map<string | symbol, unknown>();
|
|
26
|
+
return <T>(key: string | symbol, make: () => T): T => {
|
|
27
|
+
if (!values.has(key)) values.set(key, make());
|
|
28
|
+
return values.get(key) as T;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const isolateLocal: IsolateLocal =
|
|
33
|
+
(juneDb as { isolateLocal?: IsolateLocal }).isolateLocal ?? makeIsolateLocalFallback();
|
package/src/pipeline.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
sitemapXml,
|
|
35
35
|
} from "@junejs/core/discovery";
|
|
36
36
|
import { mcpHandler, mcpTools } from "@junejs/core/mcp";
|
|
37
|
+
import type { Principal, Session } from "@junejs/core/context";
|
|
37
38
|
|
|
38
39
|
import { ensureScope, runInScope, setRequestLocale } from "@junejs/db";
|
|
39
40
|
import type { AgentConfig } from "@junejs/core/config";
|
|
@@ -126,6 +127,14 @@ export type PipelineConfig = {
|
|
|
126
127
|
// `currentServices()` resolves in loaders/views/actions. Absent → currentServices()
|
|
127
128
|
// is undefined. This is the Worker-side twin of a Durable Object seeding services.
|
|
128
129
|
services?: () => Promise<unknown> | unknown;
|
|
130
|
+
// Resolve the request's authenticated identity (the auth integration's job —
|
|
131
|
+
// e.g. Better Auth reading the session cookie). The result rides every
|
|
132
|
+
// ActionContext the pipeline builds, starting with the /mcp mount, so
|
|
133
|
+
// requiresPrincipal actions and per-call connection auth see the SAME
|
|
134
|
+
// principal a UI dispatch would. Absent → anonymous (identity-gated actions
|
|
135
|
+
// reject); a THROWING resolver propagates as a server error — it must never
|
|
136
|
+
// silently degrade to anonymous.
|
|
137
|
+
identity?: (request: Request) => Promise<{ user?: Principal; session?: Session }> | { user?: Principal; session?: Session };
|
|
129
138
|
earlyHints?: string[];
|
|
130
139
|
htmlCacheControl?: string;
|
|
131
140
|
notFoundComponent?: React.ComponentType<{ pathname: string }>;
|
|
@@ -473,9 +482,11 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
|
|
|
473
482
|
if (url.pathname === "/mcp") {
|
|
474
483
|
if (!agent.mcp) return notFoundResponse("view", url.pathname);
|
|
475
484
|
// The agent's tool calls run inside the same request scope, so an action's
|
|
476
|
-
// ambient `db` is the SAME resource the UI uses
|
|
477
|
-
// the same principal
|
|
478
|
-
|
|
485
|
+
// ambient `db` is the SAME resource the UI uses, and — via cfg.identity —
|
|
486
|
+
// the same principal: requiresPrincipal actions and per-call connection
|
|
487
|
+
// auth are live on this surface. ctx carries identity only — not resources.
|
|
488
|
+
const identity = cfg.identity ? await cfg.identity(request) : undefined;
|
|
489
|
+
return mcpHandler(request, { request, ...identity });
|
|
479
490
|
}
|
|
480
491
|
if (request.method === "GET" && agent.discovery) {
|
|
481
492
|
const d = await discovery(url);
|