🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

devframe

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

devframe - npm Package Compare versions

Comparing version
0.7.16
to
0.8.0
+8
bin/devframe.mjs
#!/usr/bin/env node
import process from 'node:process'
import { runDevframeCli } from '../dist/cli/main.mjs'
runDevframeCli().catch((error) => {
console.error(error)
process.exit(1)
})
//#region src/utils/agent-tool-name.ts
/**
* Maximum tool-name length several MCP clients enforce (the Anthropic API
* pattern is `^[a-zA-Z0-9_-]{1,128}$`).
*/
const MAX_TOOL_NAME_LENGTH = 128;
/**
* Derive the wire-safe agent tool name for an internal tool id.
*
* Devframe tool ids are colon-namespaced — `devframe:<area>:<fn>` for
* built-ins, `devframes:plugin:<slug>:<fn>` for plugin RPCs, and hub
* command ids for command-derived tools. MCP clients constrain tool names
* to `^[a-zA-Z0-9_-]{1,128}$`, so the agent/MCP boundary derives the wire
* name automatically: every run of characters outside `[a-zA-Z0-9_-]`
* becomes a single `_`, truncated to 128 characters. Internal ids never
* change — resolution back to the id happens at the boundary.
*
* ```
* devframe:state:read → devframe_state_read
* devframes:plugin:git:status → devframes_plugin_git_status
* ```
*
* A plain string transform with no node dependency, so browser-side UIs
* that display a tool's id (e.g. the inspect plugin's agent view) can
* import it too and show the name a client actually calls.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
function toAgentToolName(id) {
return id.replace(/[^\w-]+/g, "_").slice(0, MAX_TOOL_NAME_LENGTH);
}
//#endregion
export { toAgentToolName as t };
import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { createBuild } from "./adapters/build.mjs";
import { n as resolveDevServerPort, t as createDevServer } from "./dev-D4M7Veo7.mjs";
import process from "node:process";
import cac$1 from "cac";
//#region src/adapters/flags.ts
/**
* Identity helper that preserves the literal schema-map type — use this
* so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
*
* ```ts
* const appFlags = defineCliFlags({
* depth: v.pipe(v.number(), v.integer()),
* config: v.optional(v.string()),
* })
*
* defineDevframe({
* cli: { flags: appFlags },
* setup(ctx, info) {
* const flags = info.flags as InferCliFlags<typeof appFlags>
* flags.depth // number
* flags.config // string | undefined
* },
* })
* ```
*/
function defineCliFlags(flags) {
return flags;
}
/**
* Best-effort, dependency-free probe of a schema to decide whether the
* corresponding CAC option takes a value. Duck-types the `type` /
* `wrapped` / `inner` / `pipe` fields exposed by valibot and by devframe's
* built-in `s` builder, unwrapping `optional` / `nullable` / `nullish` /
* `pipe` wrappers then matching on the inner kind. Validators that don't
* expose these fields (e.g. zod) fall through to a value-taking option.
*/
function getSchemaKind(schema) {
let current = schema;
while (current) {
const kind = current.type;
if (kind === "optional" || kind === "nullable" || kind === "nullish" || kind === "undefined") {
current = current.wrapped ?? current.inner;
continue;
}
if (kind === "pipe" && Array.isArray(current.pipe) && current.pipe.length > 0) {
current = current.pipe[0];
continue;
}
return kind ?? "unknown";
}
return "unknown";
}
/** Whether the CAC option for this schema should be a boolean flag. */
function isBooleanFlag(schema) {
return getSchemaKind(schema) === "boolean";
}
/** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
function parseCliFlags(schema, raw) {
const flags = {};
const issues = [];
for (const [key, fieldSchema] of Object.entries(schema)) {
const result = fieldSchema["~standard"].validate(raw[key]);
if (result instanceof Promise) {
issues.push(`--${toKebab(key)}: async flag validation is not supported`);
continue;
}
if (result.issues) issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`);
else flags[key] = result.value;
}
for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value;
return issues.length ? {
flags,
issues
} : { flags };
}
function toKebab(camel) {
return camel.replaceAll(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
/** Kebab-case a schema key for CAC option registration. */
function flagKeyToOption(camel) {
return toKebab(camel);
}
//#endregion
//#region src/adapters/cac.ts
/**
* Wrap a {@link DevframeDefinition} in a `cac`-powered command-line
* interface exposing `dev` / `build` / `mcp` subcommands.
*
* Requires the optional `cac` peer dependency.
*/
function createCac(d, options = {}) {
const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
const defaultHost = d.cli?.host ?? "localhost";
const cli = cac$1(d.cli?.command ?? d.id);
const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]");
if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
const optionName = flagKeyToOption(key);
const description = schema.description ?? "";
if (isBooleanFlag(schema)) devCommand.option(`--${optionName}`, description);
else devCommand.option(`--${optionName} <value>`, description);
}
devCommand.action(async (_args, rawFlags) => {
const flags = resolveTypedFlags(d, rawFlags);
const host = flags.host ?? defaultHost;
const port = flags.port ?? await resolveDevServerPort(d, {
host,
defaultPort
});
const mcp = flags.mcp;
await createDevServer(d, {
host,
port,
flags,
mcp,
onReady: options.onReady
});
});
if (d.capabilities?.build !== false) cli.command("build", "Build a self-contained static deploy of the devframe").option("--out-dir <outDir>", "Output directory", { default: "dist-static" }).option("--base <base>", "URL base", { default: "/" }).option("--pretty", "Pretty-print dump JSON (larger on disk)").action(async (flags) => {
await createBuild(d, {
outDir: flags.outDir,
base: flags.base,
pretty: flags.pretty
});
});
cli.command("mcp", "Start an MCP server exposing agent-facing tools (stdio) [experimental]").action(async () => {
const { createMcpServer } = await import("./adapters/mcp.mjs");
await createMcpServer(d, {
transport: "stdio",
onReady: ({ transport }) => {
console.error(`[devframe] "${d.id}" MCP server ready (${transport})`);
}
});
});
d.cli?.configure?.(cli);
options.configureCli?.(cli);
cli.help();
cli.version("0.0.0");
return {
cli,
async parse(argv = process.argv) {
cli.parse(argv, { run: false });
await cli.runMatchedCommand();
}
};
}
function resolveTypedFlags(d, raw) {
if (!d.cli?.flags) return raw;
const { flags, issues } = parseCliFlags(d.cli.flags, raw);
if (issues?.length) {
for (const issue of issues) console.error(colors.red`[devframe] invalid flag — ${issue}`);
process.exit(1);
}
return flags;
}
//#endregion
export { defineCliFlags as n, parseCliFlags as r, createCac as t };
//#region src/cli/main.d.ts
/**
* The `devframe` bin — the framework's own CLI, distinct from the per-app
* CLI shells authors build with `createCac(definition)`. It hosts the
* app-independent commands; today that is `connect`, the MCP connector.
*
* @experimental
*/
declare function runDevframeCli(argv?: string[]): Promise<void>;
//#endregion
export { runDevframeCli };
import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
import { n as probeDevframeOrigin, t as listLiveDevframeInstances } from "../instance-registry-D6fxYu36.mjs";
import { t as toAgentToolName } from "../agent-tool-name-C3b5vEwJ.mjs";
import { Diagnostic } from "nostics";
import process from "node:process";
import { joinURL } from "ufo";
import { cac } from "cac";
//#region src/cli/connect.ts
const INDEX_TOOL = toAgentToolName("devframe:connect:list-instances");
const CALL_TOOL = toAgentToolName("devframe:connect:call-tool");
const MCP_DISABLED_HINT = "This instance runs without an MCP route. Restart it with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again.";
const GATEWAY_TOOLS = [{
name: INDEX_TOOL,
title: "Discover running devframes",
description: "Discover every running devframe dev server on this machine and list each one's MCP tools. Call this FIRST, before assuming which devtools are available — the result names the instance (id, project root, origin) and the port to pass to the call tool. Safe to call freely.",
inputSchema: {
type: "object",
properties: {}
},
annotations: {
readOnlyHint: true,
destructiveHint: false
}
}, {
name: CALL_TOOL,
title: "Call a devframe tool",
description: "Invoke one MCP tool on one running devframe instance discovered via the list-instances tool. Pass the instance's port, the tool name, and the tool's arguments object.",
inputSchema: {
type: "object",
properties: {
port: {
type: "number",
description: "The instance's port, from the list-instances tool."
},
tool: {
type: "string",
description: "Tool name, from the instance's tool list."
},
args: {
type: "object",
description: "Arguments object for the tool. Omit for zero-argument tools."
}
},
required: ["port", "tool"],
additionalProperties: false
}
}];
/**
* Start the devframe MCP connector on stdio: a thin discovery + proxy server
* in the shape Vercel's next-devtools-mcp (https://github.com/vercel/next-devtools-mcp)
* validated — credit due there for the architecture this connector follows.
* It exposes two gateway tools —
* `devframe_connect_list-instances` (discover running devframe instances via
* the instance registry and list each one's MCP tools) and
* `devframe_connect_call-tool` (invoke one tool on one instance over its
* Streamable-HTTP endpoint) — and holds no domain knowledge of its own.
*
* @experimental
*/
async function startConnectServer(options = {}) {
const sdk = await importSdk();
const server = new sdk.Server({
name: "devframe-connect",
version: "0.0.0"
}, { capabilities: { tools: {} } });
server.setRequestHandler("tools/list", async () => ({ tools: GATEWAY_TOOLS }));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === INDEX_TOOL) return textResult(await index(sdk, options));
if (name === CALL_TOOL) return textResult(await call(sdk, options, args ?? {}));
return errorResult({
message: `unknown tool "${name}"`,
fix: `Call ${INDEX_TOOL} or ${CALL_TOOL}.`
});
} catch (error) {
return errorResult(toErrorPayload(error));
}
});
const transport = new sdk.StdioServerTransport();
await server.connect(transport);
return { stop: async () => {
await server.close();
} };
}
async function importSdk() {
try {
const [serverMod, stdioMod, clientMod] = await Promise.all([
import("@modelcontextprotocol/server"),
import("@modelcontextprotocol/server/stdio"),
import("@modelcontextprotocol/client")
]);
return {
Server: serverMod.Server,
StdioServerTransport: stdioMod.StdioServerTransport,
Client: clientMod.Client,
StreamableHTTPClientTransport: clientMod.StreamableHTTPClientTransport
};
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0046({
reason,
cause: error
});
}
}
/** Discover instances: registry (prune-on-read) + explicit port probes. */
async function index(sdk, options) {
const { live } = await listLiveDevframeInstances({
instancesDir: options.instancesDir,
timeoutMs: options.timeoutMs
});
const records = [...live];
for (const port of options.ports ?? []) {
if (records.some((r) => r.port === port)) continue;
const probed = await probePort(port, options.timeoutMs);
if (probed) records.push(probed);
}
const instances = await Promise.all(records.map(async (record) => {
const { mcp, ...rest } = record;
const entry = {
...rest,
mcp: null
};
if (!mcp) {
entry.hint = MCP_DISABLED_HINT;
return entry;
}
const url = `${record.origin}${mcp.path}`;
try {
entry.mcp = {
url,
tools: await listInstanceTools(sdk, url)
};
} catch (error) {
entry.mcp = {
url,
error: error instanceof Error ? error.message : String(error)
};
}
return entry;
}));
return {
instances,
...instances.length === 0 ? { hint: "No running devframe instances found. Start a devframe dev server (with --mcp for tools), or pass --port <n> to devframe connect if the instance predates the registry." } : {}
};
}
/**
* Probe an explicit port for a devframe serving `__connection.json` at `/`,
* reusing the registry's origin-candidate probe (a `localhost`-bound server
* may listen on either address family).
*/
async function probePort(port, timeoutMs) {
const probed = await probeDevframeOrigin(`http://localhost:${port}`, "/", timeoutMs);
if (!probed) return null;
const mcpPath = probed.meta.mcp ? joinURL("/", probed.meta.mcp.path) : null;
return {
pid: -1,
port,
origin: probed.origin,
basePath: "/",
id: `port-${port}`,
rootDir: "",
mcp: mcpPath ? { path: mcpPath } : null,
startedAt: 0
};
}
async function listInstanceTools(sdk, url) {
return withInstanceClient(sdk, url, async (client) => {
return (await client.listTools()).tools.map((tool) => ({
name: tool.name,
description: tool.description
}));
});
}
async function call(sdk, options, args) {
if (typeof args.port !== "number" || typeof args.tool !== "string") throw diagnostics.DF0049();
const { live } = await listLiveDevframeInstances({
instancesDir: options.instancesDir,
timeoutMs: options.timeoutMs
});
const record = live.find((r) => r.port === args.port) ?? await probePort(args.port, options.timeoutMs);
if (!record) throw diagnostics.DF0050({ port: args.port });
if (!record.mcp) throw diagnostics.DF0051({ port: args.port });
return withInstanceClient(sdk, `${record.origin}${record.mcp.path}`, async (client) => {
const result = await client.callTool({
name: args.tool,
arguments: args.args ?? {}
});
return {
instance: {
id: record.id,
port: record.port
},
tool: args.tool,
isError: result.isError ?? false,
content: result.content,
...result.structuredContent ? { structuredContent: result.structuredContent } : {}
};
});
}
async function withInstanceClient(sdk, url, fn) {
const transport = new sdk.StreamableHTTPClientTransport(new URL(url));
const client = new sdk.Client({
name: "devframe-connect",
version: "0.0.0"
});
await client.connect(transport);
try {
return await fn(client);
} finally {
await client.close().catch(() => {});
}
}
function textResult(value) {
return { content: [{
type: "text",
text: JSON.stringify(value, null, 2)
}] };
}
/**
* Project a thrown value into the connector's structured error payload. A
* nostics `Diagnostic` carries its code, `fix`, and docs URL across so the
* calling agent gets the actionable next step.
*/
function toErrorPayload(error) {
if (error instanceof Diagnostic) return {
code: error.code,
message: error.message,
...error.fix ? { fix: error.fix } : {},
...error.docs ? { docs: error.docs } : {}
};
return {
message: error instanceof Error ? error.message : String(error),
...error && typeof error === "object" && "fix" in error && typeof error.fix === "string" ? { fix: error.fix } : {}
};
}
function errorResult(error) {
return {
isError: true,
content: [{
type: "text",
text: JSON.stringify({ error }, null, 2)
}]
};
}
/** Parse the repeatable `--port` flag value(s) from cac into numbers. */
function parsePortsFlag(value) {
return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
}
/** Keep the connector process alive until the stdio transport closes it. */
function keepAlive() {
process.stdin.resume();
}
//#endregion
//#region src/cli/main.ts
/**
* The `devframe` bin — the framework's own CLI, distinct from the per-app
* CLI shells authors build with `createCac(definition)`. It hosts the
* app-independent commands; today that is `connect`, the MCP connector.
*
* @experimental
*/
async function runDevframeCli(argv = process.argv) {
const cli = cac("devframe");
cli.command("connect", "Run the devframe MCP connector on stdio (discovers running devframe dev servers and proxies their tools)").option("--port <port>", "Probe an explicit port besides the instance registry (repeatable)").option("--instances-dir <dir>", "Override the instance registry directory (default: ~/.devframe/instances, or $DEVFRAME_INSTANCES_DIR)").option("--timeout <ms>", "Probe timeout per instance in milliseconds", { default: 1e3 }).action(async (options) => {
await startConnectServer({
ports: parsePortsFlag(options.port),
instancesDir: options.instancesDir,
timeoutMs: options.timeout
});
keepAlive();
});
cli.help();
cli.parse(argv, { run: false });
if (!cli.matchedCommand) {
if (!cli.options.help) cli.outputHelp();
return;
}
await cli.runMatchedCommand();
}
//#endregion
export { runDevframeCli };
import { b as DevframeNodeContext, ht as SharedState } from "./devframe-Dsjn_Xtq.mjs";
//#region src/node/hub-internals/context.d.ts
interface InternalAnonymousAuthStorage {
trusted: Record<string, {
authToken: string;
ua: string;
origin: string;
timestamp: number;
} | undefined>;
}
interface RemoteTokenRecord {
dockId: string;
/** Dock URL origin — matched against WS handshake `Origin` header when `originLock` is on. */
origin: string;
originLock: boolean;
}
interface DevframeInternalContext {
storage: {
auth: SharedState<InternalAnonymousAuthStorage>;
};
/**
* Revoke an auth token: remove from storage and notify all connected clients
* using this token that they are no longer trusted.
*/
revokeAuthToken: (token: string) => Promise<void>;
/**
* Session-only tokens issued to remote-UI iframe docks. Not persisted —
* regenerated on every dev-server restart.
*/
remoteTokens: Map<string, RemoteTokenRecord>;
allocateRemoteToken: (dockId: string, origin: string, originLock: boolean) => string;
revokeRemoteToken: (token: string) => void;
revokeRemoteTokensForDock: (dockId: string) => void;
/**
* Returns true if `token` is a valid remote token and, when `originLock` is
* on, `requestOrigin` matches the recorded dock origin.
*/
isRemoteTokenTrusted: (token: string, requestOrigin?: string) => boolean;
/**
* Populated by `createWsServer` once the WS port is bound. Consumed by the
* docks host when enriching remote iframe URLs with a connection descriptor.
*/
wsEndpoint?: {
/** Full `ws://` or `wss://` URL with host and port. */
url: string;
};
}
declare const internalContextMap: WeakMap<DevframeNodeContext, DevframeInternalContext>;
declare function getInternalContext(context: DevframeNodeContext): DevframeInternalContext;
//#endregion
export { internalContextMap as a, getInternalContext as i, InternalAnonymousAuthStorage as n, RemoteTokenRecord as r, DevframeInternalContext as t };
import { t as createStorage } from "./storage-Dzoc3NVC.mjs";
import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-BtQDKTp7.mjs";
import { join } from "pathe";
//#region src/node/hub-internals/context.ts
const internalContextMap = /* @__PURE__ */ new WeakMap();
function getInternalContext(context) {
if (!internalContextMap.has(context)) {
const storage = createStorage({
filepath: join(context.host.getStorageDir("global"), "auth.json"),
initialValue: { trusted: {} }
});
const remoteTokens = /* @__PURE__ */ new Map();
function revokeRemoteToken(token) {
if (!remoteTokens.delete(token)) return;
revokeActiveConnectionsForToken(context, token);
}
const internalContext = {
storage: { auth: storage },
revokeAuthToken: (token) => revokeAuthToken(context, storage, token),
remoteTokens,
allocateRemoteToken(dockId, origin, originLock) {
const token = randomToken();
remoteTokens.set(token, {
dockId,
origin,
originLock
});
return token;
},
revokeRemoteToken,
revokeRemoteTokensForDock(dockId) {
const tokensToRevoke = [];
for (const [token, record] of remoteTokens) if (record.dockId === dockId) tokensToRevoke.push(token);
for (const token of tokensToRevoke) revokeRemoteToken(token);
},
isRemoteTokenTrusted(token, requestOrigin) {
const record = remoteTokens.get(token);
if (!record) return false;
if (!record.originLock) return true;
return !!requestOrigin && record.origin === requestOrigin;
}
};
internalContextMap.set(context, internalContext);
}
return internalContextMap.get(context);
}
//#endregion
export { internalContextMap as n, getInternalContext as t };
import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-Kz7t5Xab.mjs";
import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
import { r as registerDevframeInstance } from "./instance-registry-D6fxYu36.mjs";
import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-CcAPxuoT.mjs";
import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
import { t as open } from "./open-Deb5xmIT.mjs";
import { mountStaticHandler } from "./utils/serve-static.mjs";
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
import { resolve } from "pathe";
import process$1 from "node:process";
import { networkInterfaces } from "node:os";
import { H3 } from "h3";
import { createServer } from "node:net";
import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash } from "ufo";
//#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs
const unsafePorts = /* @__PURE__ */ new Set([
1,
7,
9,
11,
13,
15,
17,
19,
20,
21,
22,
23,
25,
37,
42,
43,
53,
69,
77,
79,
87,
95,
101,
102,
103,
104,
109,
110,
111,
113,
115,
117,
119,
123,
135,
137,
139,
143,
161,
179,
389,
427,
465,
512,
513,
514,
515,
526,
530,
531,
532,
540,
548,
554,
556,
563,
587,
601,
636,
989,
990,
993,
995,
1719,
1720,
1723,
2049,
3659,
4045,
5060,
5061,
6e3,
6566,
6665,
6666,
6667,
6668,
6669,
6697,
10080
]);
function isUnsafePort(port) {
return unsafePorts.has(port);
}
function isSafePort(port) {
return !isUnsafePort(port);
}
var GetPortError = class extends Error {
constructor(message, opts) {
super(message, opts);
this.message = message;
}
name = "GetPortError";
};
function _log(verbose, message) {
if (verbose) console.log(`[get-port] ${message}`);
}
function _generateRange(from, to) {
if (to < from) return [];
const r = [];
for (let index = from; index <= to; index++) r.push(index);
return r;
}
function _tryPort(port, host) {
return new Promise((resolve) => {
const server = createServer();
server.unref();
server.on("error", () => {
resolve(false);
});
server.listen({
port,
host
}, () => {
const { port: port2 } = server.address();
server.close(() => {
resolve(isSafePort(port2) && port2);
});
});
});
}
function _getLocalHosts(additional) {
const hosts = new Set(additional);
for (const _interface of Object.values(networkInterfaces())) for (const config of _interface || []) if (config.address && !config.internal && !config.address.startsWith("fe80::") && !config.address.startsWith("169.254")) hosts.add(config.address);
return [...hosts];
}
async function _findPort(ports, host) {
for (const port of ports) {
const r = await _tryPort(port, host);
if (r) return r;
}
}
function _fmtOnHost(hostname) {
return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host";
}
const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(?<!-)$/;
function _validateHostname(hostname, _public, verbose) {
if (hostname && !HOSTNAME_RE.test(hostname)) {
const fallbackHost = _public ? "0.0.0.0" : "127.0.0.1";
_log(verbose, `Invalid hostname: ${JSON.stringify(hostname)}. Using ${JSON.stringify(fallbackHost)} as fallback.`);
return fallbackHost;
}
return hostname;
}
async function getPort(_userOptions = {}) {
if (typeof _userOptions === "number" || typeof _userOptions === "string") _userOptions = { port: Number.parseInt(_userOptions + "") || 0 };
const _port = Number(_userOptions.port ?? process.env.PORT);
const _userSpecifiedAnyPort = Boolean(_userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length);
const options = {
random: _port === 0,
ports: [],
portRange: [],
alternativePortRange: _userSpecifiedAnyPort ? [] : [3e3, 3100],
verbose: false,
..._userOptions,
port: _port,
host: _validateHostname(_userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose)
};
if (options.random && !_userSpecifiedAnyPort) return getRandomPort(options.host);
const portsToCheck = [
options.port,
...options.ports,
..._generateRange(...options.portRange)
].filter((port) => {
if (!port) return false;
if (!isSafePort(port)) {
_log(options.verbose, `Ignoring unsafe port: ${port}`);
return false;
}
return true;
});
if (portsToCheck.length === 0) portsToCheck.push(3e3);
let availablePort = await _findPort(portsToCheck, options.host);
if (!availablePort && options.alternativePortRange.length > 0) {
availablePort = await _findPort(_generateRange(...options.alternativePortRange), options.host);
if (portsToCheck.length > 0) {
let message = `Unable to find an available port (tried ${portsToCheck.join("-")} ${_fmtOnHost(options.host)}).`;
if (availablePort) message += ` Using alternative port ${availablePort}.`;
_log(options.verbose, message);
}
}
if (!availablePort && _userOptions.random !== false) {
availablePort = await getRandomPort(options.host);
if (availablePort) _log(options.verbose, `Using random port ${availablePort}`);
}
if (!availablePort) {
const triedRanges = [
options.port,
options.portRange.join("-"),
options.alternativePortRange.join("-")
].filter(Boolean).join(", ");
throw new GetPortError(`Unable to find an available port ${_fmtOnHost(options.host)} (tried ${triedRanges})`);
}
return availablePort;
}
async function getRandomPort(host) {
const port = await checkPort(0, host);
if (port === false) throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`);
return port;
}
async function checkPort(port, host = process.env.HOST, verbose) {
if (!host) host = _getLocalHosts([void 0, "0.0.0.0"]);
if (!Array.isArray(host)) return _tryPort(port, host);
for (const _host of host) {
const _port = await _tryPort(port, _host);
if (_port === false) {
if (port < 1024 && verbose) _log(verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost(_host)}`);
return false;
}
if (port === 0 && _port !== 0) port = _port;
}
return port;
}
//#endregion
//#region src/adapters/dev.ts
const DEFAULT_PORT = 9999;
/**
* Resolve the listening port for {@link createDevServer}, honoring the
* definition's `cli.port` / `cli.portRange` / `cli.random` settings.
* Exposed separately so authors who run their own argv parsing can
* resolve a port up-front (to print it, log it, etc.) before starting
* the server.
*/
async function resolveDevServerPort(def, options = {}) {
const host = options.host ?? def.cli?.host ?? "localhost";
const portOptions = {
port: options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT,
host
};
if (def.cli?.portRange) portOptions.portRange = def.cli.portRange;
if (def.cli?.random) portOptions.random = def.cli.random;
return getPort(portOptions);
}
/**
* Start a devframe dev server for a {@link DevframeDefinition} —
* h3 + WebSocket RPC + (optionally) the author's SPA mounted at the
* resolved base path.
*
* When `distDir` is omitted (and `def.cli?.distDir` is unset) the
* server runs in **bridge mode**: only `__connection.json` and the WS
* endpoint are mounted, with no SPA mount. The SPA is expected to be
* hosted elsewhere (e.g. by a parent Vite/Nuxt dev server) — see
* `viteDevBridge({ devMiddleware })`.
*
* Returns the underlying {@link StartedServer} handle so callers can
* close it gracefully (SIGINT, hot-reload, test teardown).
*
* Use this directly when integrating devframe into an existing CLI
* framework (commander, yargs, hand-rolled CAC). For the all-in-one
* `dev` / `build` / `mcp` shell, reach for {@link createCac} instead.
*/
async function createDevServer(def, options = {}) {
const distDir = options.distDir ?? def.cli?.distDir;
const host = options.host ?? def.cli?.host ?? "localhost";
const port = options.port ?? await resolveDevServerPort(def, { host });
const flags = options.flags ?? {};
const basePath = options.basePath ? normalizeBasePath(options.basePath) : resolveBasePath(def, "standalone");
const app = options.app ?? new H3();
const h3Host = createH3DevframeHost({
origin: normalizeHttpServerUrl(host, port),
appName: def.id,
mount: (base, dir) => {
mountStaticHandler(app, base, dir);
}
});
const ctx = await createHostContext({
cwd: process$1.cwd(),
mode: "dev",
host: h3Host
});
const setupInfo = { flags };
await def.setup(ctx, setupInfo);
const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp);
let mcpDispose;
let mcpMeta;
if (mcpConfig) {
const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp");
const mcpPath = joinURL(basePath, mcpRoute);
let mountMcpHttp;
try {
({mountMcpHttp} = await import("./http-BghOqfTD.mjs"));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport: "http",
reason,
cause: error
});
}
mcpDispose = mountMcpHttp(app, ctx, mcpPath, {
serverName: `${def.id} (devframe)`,
serverVersion: def.version ?? "0.0.0",
exposeSharedState: true,
allowedOrigins: mcpConfig.allowedOrigins
}).dispose;
mcpMeta = { path: mcpRoute };
}
const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath);
const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME);
app.use(connectionMetaPath, () => ({
backend: "websocket",
websocket: meta,
...mcpMeta ? { mcp: mcpMeta } : {}
}));
if (distDir) mountStaticHandler(app, basePath, resolve(distDir));
const authOption = flags.auth === false ? false : options.auth !== void 0 ? options.auth : def.cli?.auth;
let authHandler;
let resolvedAuth;
if (authOption === false) resolvedAuth = false;
else if (typeof authOption === "object") {
authHandler = authOption;
resolvedAuth = authOption;
} else {
authHandler = createInteractiveAuth(ctx);
resolvedAuth = authHandler;
}
const started = await startHttpAndWs({
context: ctx,
host,
port,
app,
path: bindPath,
wsPort,
auth: resolvedAuth,
onReady: async (info) => {
authHandler?.printBanner();
await options.onReady?.(info);
await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser, authHandler);
}
});
const registration = registerDevframeInstance({
pid: process$1.pid,
port: started.port,
origin: normalizeHttpServerUrl(host, started.port),
basePath,
id: def.id,
name: def.name,
rootDir: process$1.cwd(),
mcp: mcpConfig ? { path: joinURL(basePath, withoutLeadingSlash(mcpConfig.path ?? "__mcp")) } : null,
startedAt: Date.now()
});
const closeServer = started.close;
started.close = async () => {
registration.unregister();
await mcpDispose?.();
await closeServer();
};
return started;
}
/**
* Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
* concrete options, or `undefined` when the MCP route is disabled.
*/
function resolveMcpConfig(mcp) {
if (!mcp) return void 0;
return mcp === true ? {} : mcp;
}
/**
* Resolve the `mcp` entry a `__connection.json` should advertise for a dev
* server started with the given `mcp` option (falling back to `def.cli?.mcp`,
* exactly like {@link createDevServer}), or `undefined` when the route is
* disabled.
*
* Hosted bridges that hand-roll their connection meta (`viteDevBridge`,
* `@devframes/next`'s handler) pass the side-car `port`: the advertised path
* becomes absolute (the side-car mounts at `/`) and the client dials
* `<page-host>:<port><path>`. Without `port` the path stays relative, resolved
* against `__connection.json`'s own location (the same-server default).
*
* @experimental
*/
function resolveMcpConnectionMeta(def, mcp, port) {
const config = resolveMcpConfig(mcp ?? def.cli?.mcp);
if (!config) return void 0;
const route = withoutLeadingSlash(config.path ?? "__mcp");
return port != null ? {
path: withLeadingSlash(route),
port
} : { path: route };
}
/**
* Resolve the three WS connection scenarios from the definition / call-site
* config into a concrete server bind path, optional dedicated port, and the
* `__connection.json` descriptor the browser resolves.
*/
function resolveWsConnection(def, options, basePath) {
const ws = options.ws ?? def.cli?.ws ?? {};
const route = withoutLeadingSlash(ws.route ?? "__devframe_ws");
if (ws.url) return {
bindPath: joinURL(basePath, route),
wsPort: void 0,
meta: ws.url
};
if (ws.port != null) return {
bindPath: withLeadingSlash(route),
wsPort: ws.port,
meta: {
port: ws.port,
path: route
}
};
return {
bindPath: joinURL(basePath, route),
wsPort: void 0,
meta: { path: route }
};
}
async function maybeOpenBrowser(def, flags, origin, override, authHandler) {
const flagsOpen = flags.open;
const cliOpen = def.cli?.open;
const resolved = override ?? flagsOpen ?? cliOpen;
if (resolved === void 0 || resolved === false) return;
const target = typeof resolved === "string" ? withBase(resolved, origin) : origin;
const authorizedTarget = authHandler?.buildOpenUrl?.(target) ?? target;
try {
await open(authorizedTarget);
} catch {}
}
//#endregion
export { resolveDevServerPort as n, resolveMcpConnectionMeta as r, createDevServer as t };

Sorry, the diff of this file is too big to display

import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { defineDiagnostics } from "nostics";
//#region src/node/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
reporters: [devframeReporter],
codes: {
DF0006: { why: (p) => `RPC function "${p.name}" is not registered` },
DF0007: { why: "AsyncLocalStorage is not set, it likely to be an internal bug of the Devframe foundation" },
DF0008: { why: (p) => `distDir ${p.distDir} does not exist` },
DF0012: { why: (p) => `Failed to parse storage file: ${p.filepath}, falling back to defaults.` },
DF0013: { why: (p) => `Shared state of "${p.key}" is not found, please provide an initial value for the first time` },
DF0014: {
why: (p) => `RPC function "${p.name}" has an invalid \`agent\` field — \`description\` must be a non-empty string.`,
fix: "Provide a short description (~1–3 sentences) explaining what the tool does and when agents should invoke it."
},
DF0015: {
why: (p) => `Agent tool "${p.id}" is already registered.`,
fix: "Tool ids must be unique across RPC functions with an `agent` field and tools registered via `ctx.agent.registerTool()`."
},
DF0016: { why: (p) => `Agent resource "${p.id}" is already registered.` },
DF0017: { why: (p) => `Failed to start MCP server (${p.transport}): ${p.reason}` },
DF0029: {
why: (p) => `Stream "${p.channel}#${p.id}" dropped ${p.dropped} chunk(s) after exceeding the client high-water mark.`,
fix: "The consumer is too slow for the producer. Raise `highWaterMark` on the subscription, slow the producer, or batch chunks."
},
DF0030: {
why: (p) => `Stream "${p.channel}#${p.id}" is unknown — no producer has called \`channel.start({ id: "${p.id}" })\`.`,
fix: "Ensure the server-side producer is running before clients subscribe, or check for typos in the stream id."
},
DF0031: {
why: (p) => `Cannot write to closed stream "${p.channel}#${p.id}".`,
fix: "Track the producer lifecycle — guard writes with the `stream.signal.aborted` flag."
},
DF0032: {
why: (p) => `Streaming channel "${p.channel}" is already registered.`,
fix: "Each channel name must be unique within a context. Pick a different name or reuse the existing channel handle."
},
DF0033: {
why: (p) => `Failed to start dev RPC bridge for "${p.id}": ${p.reason}`,
fix: "Verify the bridge port is free and the devframe setup function does not throw. Pin a port via `cli.port` / `cli.portRange` on the definition, or via `devMiddleware.port` on `viteDevBridge`."
},
DF0034: {
why: (p) => `Scoped RPC registration for namespace "${p.namespace}" received an already-namespaced function name "${p.name}".`,
fix: "A scoped context auto-namespaces ids. Pass a bare name without a \":\" separator (e.g. `register({ name: \"get-cwd\" })`), or use the unscoped `ctx.base.rpc.register` for a fully-qualified name."
},
DF0035: {
why: (p) => `Failed to persist storage file: ${p.filepath}`,
fix: "Check that the storage directory is writable and has free space."
},
DF0036: {
why: (p) => `RPC call to "${p.name}" was rejected: the caller is not authorized.`,
fix: "Complete the auth handshake (or connect with a static/pre-shared token) before calling a trusted method. Untrusted callers may only call `anonymous:`-prefixed methods — see `isAnonymousRpcMethod`."
},
DF0037: {
why: (p) => `A service is already provided under "${p.id}".`,
fix: "Service ids are unique per context. Revoke the existing provider first (the `provide()` call returns a revoke function), or namespace the id with your plugin id to avoid collisions."
},
DF0042: {
why: (p) => `"${p.id}" declares \`capabilities.build: false\` — its static export is not meaningful (writes are excluded and any live-served data won't be there).`,
fix: "Pass `{ force: true }` to `createBuild()` if the degraded export is still useful to you, or drop `capabilities.build: false` on the definition."
},
DF0045: {
why: (p) => `Failed to update the devframe instance registry at "${p.file}": ${p.reason}`,
fix: "Discovery tooling (`devframe connect`) will not see this instance. Check that the registry directory is writable, point `DEVFRAME_INSTANCES_DIR` at a writable directory, or set `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` to opt out of registration."
},
DF0046: {
why: (p) => `\`devframe connect\` requires the optional peer dependency @modelcontextprotocol/server: ${p.reason}`,
fix: "Install it next to devframe (e.g. `npm install @modelcontextprotocol/server`) and run `devframe connect` again."
},
DF0047: {
why: (p) => `Agent tool "${p.id}" is hidden from the MCP surface: its wire name "${p.name}" collides with the tool "${p.existing}".`,
fix: "Wire names derive from tool ids (characters outside [a-zA-Z0-9_-] become \"_\"). Rename one of the two ids so they sanitize to distinct names."
},
DF0048: {
why: (p) => `Unknown shared-state key "${p.key}".`,
fix: "Call the devframe_state_read tool without arguments to list the available keys, then retry with one of them."
},
DF0049: {
why: "The devframe_connect_call-tool tool requires { port: number, tool: string }.",
fix: "Call devframe_connect_list-instances to get the port and tool names, then retry."
},
DF0050: {
why: (p) => `No running devframe instance on port ${p.port}.`,
fix: "Call devframe_connect_list-instances for the current instance list — the instance may have stopped or changed port."
},
DF0051: {
why: (p) => `The devframe instance on port ${p.port} has no MCP endpoint.`,
fix: "Restart the instance with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again."
}
}
});
//#endregion
export { diagnostics as t };
import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { defineDiagnostics } from "nostics";
//#region src/rpc/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
reporters: [devframeReporter],
codes: {
DF0019: {
why: (p) => `RPC function "${p.name}" has \`agent\` set but \`jsonSerializable\` is not \`true\` — MCP requires JSON-serializable data.`,
fix: "Set `jsonSerializable: true` if the payload is JSON-safe, or remove `agent` to keep it RPC-only."
},
DF0020: {
why: (p) => `RPC function "${p.name}" declares \`jsonSerializable: true\` but the value at "${p.path}" is a ${p.type}.`,
fix: "Either drop `jsonSerializable: true` (falls back to structured-clone) or change the value to a JSON-safe shape."
},
DF0021: {
why: (p) => `RPC function "${p.name}" is already registered`,
fix: "Use the `force` parameter to overwrite an existing registration."
},
DF0022: { why: (p) => `RPC function "${p.name}" is not registered. Use register() to add new functions.` },
DF0023: { why: (p) => `RPC function "${p.name}" is not registered` },
DF0024: { why: (p) => `Either handler or setup function must be provided for RPC function "${p.name}"` },
DF0025: { why: (p) => `Function "${p.name}" not found in dump store` },
DF0026: { why: (p) => `No dump match for "${p.name}" with args: ${p.args}` },
DF0027: { why: (p) => `Function "${p.name}" with type "${p.type}" cannot have dump configuration. Only "static" and "query" types support dumps.` },
DF0028: {
why: (p) => `Function "${p.name}" with type "${p.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,
fix: "Remove `snapshot: true`, or change the function type to `query`."
},
DF0043: {
why: (p) => `RPC function "${p.name}" received an invalid argument at position ${p.index}: ${p.issues}`,
fix: "Pass a value that satisfies the `args` schema declared for this function."
},
DF0044: {
why: (p) => `RPC function "${p.name}" returned a value that failed its \`returns\` schema: ${p.issues}`,
fix: "Make the handler return a value that satisfies the `returns` schema, or relax the schema."
}
}
});
//#endregion
export { diagnostics as t };
import { t as diagnostics } from "./diagnostics-hwjXp_UV.mjs";
import { DEVFRAME_RPC_DUMP_DIRNAME } from "./constants.mjs";
import { createHash } from "node:crypto";
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/shared/ohash.D__AXeF1.mjs
function serialize(o) {
return typeof o == "string" ? `'${o}'` : new c().serialize(o);
}
const c = /*@__PURE__*/ function() {
class o {
#t = /* @__PURE__ */ new Map();
compare(t, r) {
const e = typeof t, n = typeof r;
return e === "string" && n === "string" ? t.localeCompare(r) : e === "number" && n === "number" ? t - r : String.prototype.localeCompare.call(this.serialize(t, true), this.serialize(r, true));
}
serialize(t, r) {
if (t === null) return "null";
switch (typeof t) {
case "string": return r ? t : `'${t}'`;
case "bigint": return `${t}n`;
case "object": return this.$object(t);
case "function": return this.$function(t);
}
return String(t);
}
serializeObject(t) {
const r = Object.prototype.toString.call(t);
if (r !== "[object Object]") return this.serializeBuiltInType(r.length < 10 ? `unknown:${r}` : r.slice(8, -1), t);
const e = t.constructor, n = e === Object || e === void 0 ? "" : e.name;
if (n !== "" && globalThis[n] === e) return this.serializeBuiltInType(n, t);
if (typeof t.toJSON == "function") {
const i = t.toJSON();
return n + (i !== null && typeof i == "object" ? this.$object(i) : `(${this.serialize(i)})`);
}
return this.serializeObjectEntries(n, Object.entries(t));
}
serializeBuiltInType(t, r) {
const e = this["$" + t];
if (e) return e.call(this, r);
if (typeof r?.entries == "function") return this.serializeObjectEntries(t, r.entries());
throw new Error(`Cannot serialize ${t}`);
}
serializeObjectEntries(t, r) {
const e = Array.from(r).sort((i, a) => this.compare(i[0], a[0]));
let n = `${t}{`;
for (let i = 0; i < e.length; i++) {
const [a, l] = e[i];
n += `${this.serialize(a, true)}:${this.serialize(l)}`, i < e.length - 1 && (n += ",");
}
return n + "}";
}
$object(t) {
let r = this.#t.get(t);
return r === void 0 && (this.#t.set(t, `#${this.#t.size}`), r = this.serializeObject(t), this.#t.set(t, r)), r;
}
$function(t) {
const r = Function.prototype.toString.call(t);
return r.slice(-15) === "[native code] }" ? `${t.name || ""}()[native]` : `${t.name}(${t.length})${r.replace(/\s*\n\s*/g, "")}`;
}
$Array(t) {
let r = "[";
for (let e = 0; e < t.length; e++) r += this.serialize(t[e]), e < t.length - 1 && (r += ",");
return r + "]";
}
$Date(t) {
try {
return `Date(${t.toISOString()})`;
} catch {
return "Date(null)";
}
}
$ArrayBuffer(t) {
return `ArrayBuffer[${new Uint8Array(t).join(",")}]`;
}
$Set(t) {
return `Set${this.$Array(Array.from(t).sort((r, e) => this.compare(r, e)))}`;
}
$Map(t) {
return this.serializeObjectEntries("Map", t.entries());
}
}
for (const s of [
"Error",
"RegExp",
"URL"
]) o.prototype["$" + s] = function(t) {
return `${s}(${t})`;
};
for (const s of [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array"
]) o.prototype["$" + s] = function(t) {
return `${s}[${t.join(",")}]`;
};
for (const s of ["BigInt64Array", "BigUint64Array"]) o.prototype["$" + s] = function(t) {
return `${s}[${t.join("n,")}${t.length > 0 ? "n" : ""}]`;
};
return o;
}();
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/crypto/node/index.mjs
const e = globalThis.process?.getBuiltinModule?.("crypto")?.hash;
const r = "sha256";
const s = "base64url";
function digest(t) {
if (e) return e(r, t, s);
const o = createHash(r).update(t);
return globalThis.process?.versions?.webcontainer ? o.digest().toString(s) : o.digest(s);
}
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/index.mjs
function hash$1(input) {
return digest(serialize(input));
}
//#endregion
//#region src/utils/hash.ts
/**
* Stable, deterministic hash of any structured-cloneable value.
*/
function hash(value) {
return hash$1(value);
}
//#endregion
//#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
var Node = class {
value;
next;
constructor(value) {
this.value = value;
}
};
var Queue = class {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) return;
this.#head = this.#head.next;
this.#size--;
if (!this.#head) this.#tail = void 0;
return current.value;
}
peek() {
if (!this.#head) return;
return this.#head.value;
}
clear() {
this.#head = void 0;
this.#tail = void 0;
this.#size = 0;
}
get size() {
return this.#size;
}
*[Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
*drain() {
while (this.#head) yield this.dequeue();
}
};
//#endregion
//#region ../../node_modules/.pnpm/p-limit@7.3.1/node_modules/p-limit/index.js
function pLimit(concurrency) {
let rejectOnClear = false;
if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
validateConcurrency(concurrency);
if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
const queue = new Queue();
let activeCount = 0;
const resumeNext = () => {
if (activeCount < concurrency && queue.size > 0) {
activeCount++;
queue.dequeue().run();
}
};
const next = () => {
activeCount--;
resumeNext();
};
const run = async (function_, resolve, arguments_) => {
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {}
next();
};
const enqueue = (function_, resolve, reject, arguments_) => {
const queueItem = { reject };
new Promise((internalResolve) => {
queueItem.run = internalResolve;
queue.enqueue(queueItem);
}).then(run.bind(void 0, function_, resolve, arguments_));
if (activeCount < concurrency) resumeNext();
};
const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
enqueue(function_, resolve, reject, arguments_);
});
Object.defineProperties(generator, {
activeCount: { get: () => activeCount },
pendingCount: { get: () => queue.size },
clearQueue: { value() {
if (!rejectOnClear) {
queue.clear();
return;
}
const abortError = AbortSignal.abort().reason;
while (queue.size > 0) queue.dequeue().reject(abortError);
} },
concurrency: {
get: () => concurrency,
set(newConcurrency) {
validateConcurrency(newConcurrency);
concurrency = newConcurrency;
queueMicrotask(() => {
while (activeCount < concurrency && queue.size > 0) resumeNext();
});
}
},
map: { async value(iterable, function_) {
const promises = Array.from(iterable, (value, index) => generator(function_, value, index));
return Promise.all(promises);
} }
});
return generator;
}
function validateConcurrency(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
}
//#endregion
//#region src/rpc/validation.ts
/**
* Validates RPC function definitions.
* Action and event functions cannot have dumps (side effects should not be cached).
*
* @throws {Error} If an action or event function has a dump configuration
*/
function validateDefinitions(definitions) {
for (const definition of definitions) {
const type = definition.type || "query";
if ((type === "action" || type === "event") && definition.dump) throw diagnostics.DF0027({
name: definition.name,
type
});
if (definition.snapshot && type !== "query") throw diagnostics.DF0028({
name: definition.name,
type
});
}
}
/**
* Validates a single RPC function definition.
*
* @throws {Error} If an action or event function has a dump configuration
*/
function validateDefinition(definition) {
validateDefinitions([definition]);
}
//#endregion
//#region src/rpc/dump/error.ts
/**
* Normalize a thrown value into a plain object suitable for storage in
* a dump record. Preserves `message`, `name`, `cause`, and any own
* enumerable properties of an `Error` so consumers reading the dump can
* reconstruct a richer Error than just `{ message, name }`.
*
* Non-`Error` throws are wrapped as `{ name: 'Error', message: String(thrown) }`.
*/
function serializeDumpError(error) {
return serializeWithSeen(error, /* @__PURE__ */ new WeakSet());
}
function serializeWithSeen(error, seen) {
if (!(error instanceof Error)) return {
name: "Error",
message: String(error)
};
if (seen.has(error)) return {
name: error.name,
message: error.message
};
seen.add(error);
const out = {
name: error.name,
message: error.message
};
const cause = error.cause;
if (cause !== void 0) out.cause = cause instanceof Error ? serializeWithSeen(cause, seen) : cause;
for (const key of Object.keys(error)) {
if (key === "name" || key === "message" || key === "cause") continue;
out[key] = error[key];
}
return out;
}
/**
* Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
* the plain object stored in a dump record. Preserves `cause`, restores
* the original `name`, and re-attaches any custom own properties.
*/
function reviveDumpError(stored) {
const cause = stored.cause instanceof Error ? stored.cause : isPlainErrorShape(stored.cause) ? reviveDumpError(stored.cause) : stored.cause;
const error = cause !== void 0 ? new Error(stored.message, { cause }) : new Error(stored.message);
error.name = stored.name;
for (const key of Object.keys(stored)) {
if (key === "name" || key === "message" || key === "cause") continue;
error[key] = stored[key];
}
return error;
}
function isPlainErrorShape(value) {
return typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.name === "string";
}
//#endregion
//#region src/rpc/dump/collect.ts
function getDumpRecordKey(functionName, args) {
return `${functionName}---${hash(args)}`;
}
function getDumpFallbackKey(functionName) {
return `${functionName}---fallback`;
}
async function resolveGetter(valueOrGetter) {
return typeof valueOrGetter === "function" ? await valueOrGetter() : valueOrGetter;
}
/**
* Collects pre-computed dumps by executing functions with their defined input combinations.
* Static functions without dump config automatically get `{ inputs: [[]] }`.
*
* @example
* ```ts
* const store = await dumpFunctions([greet], context, { concurrency: 10 })
* ```
*/
async function dumpFunctions(definitions, context, options = {}) {
validateDefinitions(definitions);
const concurrency = options.concurrency === true ? 5 : options.concurrency === false || options.concurrency == null ? 1 : options.concurrency;
const store = {
definitions: {},
records: {}
};
const tasksResolutions = definitions.map((definition) => async () => {
if (definition.type === "event" || definition.type === "action") return;
const setupResult = definition.setup ? await Promise.resolve(definition.setup(context)) : {};
const handler = setupResult.handler || definition.handler;
if (!handler) throw diagnostics.DF0024({ name: definition.name });
let dump = setupResult.dump ?? definition.dump;
if (!dump && definition.type === "static") dump = { inputs: [[]] };
if (!dump && definition.snapshot) dump = async (_ctx, h) => {
const output = await Promise.resolve(h(...[]));
return {
records: [{
inputs: [],
output
}],
fallback: output
};
};
if (!dump) return;
if (typeof dump === "function") dump = await Promise.resolve(dump(context, handler));
store.definitions[definition.name] = {
name: definition.name,
type: definition.type
};
return {
handler,
dump,
definition
};
});
let functionsToDump = [];
if (concurrency <= 1) for (const task of tasksResolutions) {
const resolution = await task();
if (resolution) functionsToDump.push(resolution);
}
else {
const limit = pLimit(concurrency);
functionsToDump = (await Promise.all(tasksResolutions.map((task) => limit(task)))).filter((x) => !!x);
}
const dumpTasks = [];
for (const { definition, handler, dump } of functionsToDump) {
const { inputs, records, fallback } = dump;
if (records) for (const record of records) {
const recordKey = getDumpRecordKey(definition.name, record.inputs);
store.records[recordKey] = record;
}
if ("fallback" in dump) {
const fallbackKey = getDumpFallbackKey(definition.name);
store.records[fallbackKey] = {
inputs: [],
output: fallback
};
}
if (inputs) for (const input of inputs) dumpTasks.push(async () => {
const recordKey = getDumpRecordKey(definition.name, input);
try {
const output = await Promise.resolve(handler(...input));
store.records[recordKey] = {
inputs: input,
output
};
} catch (error) {
store.records[recordKey] = {
inputs: input,
error: serializeDumpError(error)
};
}
});
}
if (concurrency <= 1) for (const task of dumpTasks) await task();
else {
const limit = pLimit(concurrency);
await Promise.all(dumpTasks.map((task) => limit(task)));
}
return store;
}
/**
* Creates a client that serves pre-computed results from a dump store.
* Uses argument hashing to match calls to stored records.
*
* @example
* ```ts
* const client = createClientFromDump(store)
* await client.greet('Alice')
* ```
*/
function createClientFromDump(store, options = {}) {
const { onMiss } = options;
return new Proxy({}, {
get(_, functionName) {
if (!(functionName in store.definitions)) throw diagnostics.DF0025({ name: functionName });
return async (...args) => {
const recordKey = getDumpRecordKey(functionName, args);
const recordOrGetter = store.records[recordKey];
if (recordOrGetter) {
const record = await resolveGetter(recordOrGetter);
if (record.error) throw reviveDumpError(record.error);
if (typeof record.output === "function") return await record.output();
return record.output;
}
onMiss?.(functionName, args);
const fallbackKey = getDumpFallbackKey(functionName);
if (fallbackKey in store.records) {
const fallbackOrGetter = store.records[fallbackKey];
const fallbackRecord = await resolveGetter(fallbackOrGetter);
if (fallbackRecord && typeof fallbackRecord.output === "function") return await fallbackRecord.output();
if (fallbackRecord) return fallbackRecord.output;
}
throw diagnostics.DF0026({
name: functionName,
args: JSON.stringify(args)
});
};
},
has(_, functionName) {
return functionName in store.definitions;
},
ownKeys() {
return Object.keys(store.definitions);
},
getOwnPropertyDescriptor(_, functionName) {
return functionName in store.definitions ? {
configurable: true,
enumerable: true,
value: void 0
} : void 0;
}
});
}
/**
* Filters function definitions to only those with dump definitions.
* Note: Only checks the definition itself, not setup results.
*/
function getDefinitionsWithDumps(definitions) {
return definitions.filter((def) => def.dump !== void 0);
}
//#endregion
//#region src/rpc/validate-io.ts
/**
* Run a single [Standard Schema](https://standardschema.dev) validator,
* awaiting the result when the validator is asynchronous.
*/
async function runStandardSchema(schema, value) {
const result = schema["~standard"].validate(value);
return result instanceof Promise ? await result : result;
}
/**
* Render Standard Schema issues into a single human-readable line for a
* diagnostic message, prefixing each with its dotted path when present.
*/
function formatIssues(issues) {
return issues.map((issue) => {
const path = issue.path?.map((segment) => typeof segment === "object" ? segment.key : segment).join(".");
return path ? `${path}: ${issue.message}` : issue.message;
}).join("; ");
}
/**
* Validate positional arguments against their declared schemas. Only
* indices with a schema are checked; extra arguments pass through
* untouched. Throws `DF0038` on the first failing argument.
*
* Validation guards the payload without rewriting it: the original values
* are handed to the handler unchanged, so a schema that describes a subset
* of an object never silently strips the sender's extra fields (and any
* declared transforms stay a purely type-level concern).
*
* @internal
*/
async function validateRpcArgs(name, argsSchema, args) {
const original = args.slice();
if (!argsSchema || argsSchema.length === 0) return original;
for (let index = 0; index < argsSchema.length; index++) {
const schema = argsSchema[index];
if (!schema) continue;
const result = await runStandardSchema(schema, args[index]);
if (result.issues) throw diagnostics.DF0043({
name,
index,
issues: formatIssues(result.issues)
});
}
return original;
}
/**
* Validate a handler's resolved return value against its declared schema.
* Throws `DF0039` when the value fails the schema, otherwise returns the
* original value unchanged (guard-only, never rewriting the payload — see
* {@link validateRpcArgs}). Passes through when no return schema is set.
*
* @internal
*/
async function validateRpcReturn(name, returnSchema, value) {
if (!returnSchema) return value;
const result = await runStandardSchema(returnSchema, value);
if (result.issues) throw diagnostics.DF0044({
name,
issues: formatIssues(result.issues)
});
return value;
}
//#endregion
//#region src/rpc/handler.ts
async function getRpcResolvedSetupResult(definition, context) {
if (!definition.setup) return {};
if (typeof context === "object" && context !== null) {
definition.__cache ??= /* @__PURE__ */ new WeakMap();
const cache = definition.__cache;
let promise = cache.get(context);
if (!promise) {
promise = Promise.resolve(definition.setup(context));
promise.catch(() => {
if (cache.get(context) === promise) cache.delete(context);
});
cache.set(context, promise);
}
return await promise;
}
if (!definition.__promise) {
const promise = Promise.resolve(definition.setup(context));
promise.catch(() => {
if (definition.__promise === promise) definition.__promise = void 0;
});
definition.__promise = promise;
}
return await definition.__promise;
}
async function getRpcHandler(definition, context) {
let handler = definition.handler;
if (!handler) {
const result = await getRpcResolvedSetupResult(definition, context);
if (!result.handler) throw diagnostics.DF0024({ name: definition.name });
handler = result.handler;
}
const argsSchema = definition.args;
const returnSchema = definition.returns;
if (!argsSchema && !returnSchema) return handler;
const inner = handler;
const validating = async (...args) => {
const validatedArgs = await validateRpcArgs(definition.name, argsSchema, args);
const output = await inner(...validatedArgs);
return await validateRpcReturn(definition.name, returnSchema, output);
};
return validating;
}
//#endregion
//#region src/rpc/dump/static.ts
function makeDumpKey(name) {
return encodeURIComponent(name.replaceAll(":", "~"));
}
function makeStaticPath(name) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.static.json`;
}
function makeQueryRecordPath(name, hash) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.record.${hash}.json`;
}
function makeQueryFallbackPath(name) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.fallback.json`;
}
async function resolveRecord(record) {
return typeof record === "function" ? await record() : record;
}
async function collectStaticRpcDump(definitions, context) {
const manifest = {};
const files = {};
for (const definition of definitions) {
const type = definition.type ?? "query";
const serialization = definition.jsonSerializable === true ? "json" : "structured-clone";
if (type === "static") {
const handler = await getRpcHandler(definition, context);
const path = makeStaticPath(definition.name);
files[path] = {
serialization,
fnName: definition.name,
data: { output: await Promise.resolve(handler()) }
};
manifest[definition.name] = {
type: "static",
path,
serialization
};
continue;
}
if (type !== "query") continue;
const store = await dumpFunctions([definition], context);
if (!(definition.name in store.definitions)) continue;
const queryEntry = {
type: "query",
records: {},
serialization
};
const prefix = `${definition.name}---`;
for (const [recordKey, recordOrGetter] of Object.entries(store.records)) {
if (!recordKey.startsWith(prefix)) continue;
const key = recordKey.slice(prefix.length);
const record = await resolveRecord(recordOrGetter);
if (key === "fallback") {
const path = makeQueryFallbackPath(definition.name);
files[path] = {
serialization,
fnName: definition.name,
data: record
};
queryEntry.fallback = path;
} else {
const path = makeQueryRecordPath(definition.name, key);
files[path] = {
serialization,
fnName: definition.name,
data: record
};
queryEntry.records[key] = path;
}
}
if (!Object.keys(queryEntry.records).length && !queryEntry.fallback) continue;
manifest[definition.name] = queryEntry;
}
return {
manifest,
files
};
}
//#endregion
export { validateRpcReturn as a, getDefinitionsWithDumps as c, validateDefinition as d, validateDefinitions as f, validateRpcArgs as i, reviveDumpError as l, getRpcHandler as n, createClientFromDump as o, hash as p, getRpcResolvedSetupResult as r, dumpFunctions as s, collectStaticRpcDump as t, serializeDumpError as u };
import { isAllowedOrigin } from "./rpc/transports/ws-server.mjs";
import { n as createHostContext } from "./host-h3-Kz7t5Xab.mjs";
import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
import { t as toAgentToolName } from "./agent-tool-name-C3b5vEwJ.mjs";
import { randomUUID } from "node:crypto";
import { Diagnostic } from "nostics";
import { join } from "pathe";
import process from "node:process";
import { homedir } from "node:os";
import { Server, WebStandardStreamableHTTPServerTransport, isInitializeRequest } from "@modelcontextprotocol/server";
//#region src/adapters/mcp/stringify.ts
/**
* JSON-coercing serializer for MCP text payloads.
*
* MCP carries tool results and resource reads as plain text over a
* JSON-RPC transport, so we cannot use the `s:`-prefixed structured-clone
* format the WS RPC transport falls back to for non-JSON values. Instead,
* we coerce common non-JSON types into JSON-friendly forms so the LLM
* client sees something useful instead of `[object Object]`.
*
* Coercions:
* - `BigInt` → `"123n"`
* - `Date` → ISO string (via the native `toJSON`)
* - `Map` → `{ __type: 'Map', entries: [[k, v], …] }`
* - `Set` → `{ __type: 'Set', entries: [v, …] }`
* - `Error` → `{ name, message, stack, cause? }` (cause recurses)
* - `Function` → `"[Function: name]"`
* - `Symbol` → `value.toString()`
* - cycles → `"[Circular]"`
*/
function stringifyForMcp(value) {
if (value === void 0) return "undefined";
if (typeof value === "string") return value;
const seen = /* @__PURE__ */ new WeakSet();
return JSON.stringify(value, (_key, val) => {
if (typeof val === "bigint") return `${val}n`;
if (val instanceof Error) {
const out = {
name: val.name,
message: val.message,
stack: val.stack
};
if (val.cause !== void 0) out.cause = val.cause;
return out;
}
if (val instanceof Map) return {
__type: "Map",
entries: [...val.entries()]
};
if (val instanceof Set) return {
__type: "Set",
entries: [...val]
};
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
if (typeof val === "symbol") return val.toString();
if (val !== null && typeof val === "object") {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
return val;
}, 2);
}
/**
* Format a thrown value for an MCP `isError` text payload.
*
* A nostics `Diagnostic` (every coded devframe error) becomes structured
* JSON — `{ error: { code, message, fix?, docs? } }` — so an agent receives
* the actionable next step (`fix`) and the docs URL instead of a bare
* message string. Other errors surface `Error.name`/`message`, plus one
* level of `cause.message` so context isn't dropped silently.
*/
function formatMcpError(error) {
if (error instanceof Diagnostic) return JSON.stringify({ error: {
code: error.code,
message: error.message,
...error.fix ? { fix: error.fix } : {},
...error.docs ? { docs: error.docs } : {}
} }, null, 2);
if (!(error instanceof Error)) return String(error);
const cause = error.cause;
const causeText = cause instanceof Error ? ` (cause: ${cause.message})` : cause !== void 0 ? ` (cause: ${String(cause)})` : "";
return `${error.name}: ${error.message}${causeText}`;
}
//#endregion
//#region src/adapters/mcp/to-json-schema.ts
const FALLBACK_OBJECT_SCHEMA = Object.freeze({
type: "object",
additionalProperties: true
});
/**
* Convert a Standard Schema to JSON Schema for the agent/MCP surface.
*
* Devframe stays validator-neutral, so conversion uses the schema's own
* [Standard JSON Schema](https://standardschema.dev/) converter
* (`~standard.jsonSchema`) when the validator provides one — zod 4 does,
* for example. Validators without a native converter (e.g. valibot) degrade
* to a permissive object schema rather than pulling in a converter library.
*/
function safeToJsonSchema(schema) {
const standard = schema["~standard"];
if (standard.jsonSchema) try {
return standard.jsonSchema.input({ target: "draft-2020-12" });
} catch {
return FALLBACK_OBJECT_SCHEMA;
}
return FALLBACK_OBJECT_SCHEMA;
}
/**
* JSON Schema for an RPC return value on the agent/MCP surface.
* @internal
*/
function returnToJsonSchema(schema) {
if (!schema) return void 0;
return safeToJsonSchema(schema);
}
/**
* JSON Schema for an RPC function's positional args on the agent/MCP
* surface. Each positional arg is advertised under `arg0` / `arg1` / … —
* matching how the agent bridge coerces the incoming object payload back
* into positional arguments.
*
* Returns `{ type: 'object', properties: {} }` when there are no args.
* @internal
*/
function argsToJsonSchema(args) {
if (!args || args.length === 0) return {
schema: {
type: "object",
properties: {}
},
unwrapped: false
};
const properties = {};
const required = [];
for (let i = 0; i < args.length; i++) {
const key = `arg${i}`;
properties[key] = safeToJsonSchema(args[i]);
required.push(key);
}
return {
schema: {
type: "object",
properties,
required,
additionalProperties: false
},
unwrapped: false
};
}
//#endregion
//#region src/adapters/mcp/build-server.ts
/**
* Wire an MCP {@link Server} to a devframe context. Returns the server
* plus a disposal function for the subscriptions it sets up. The
* transport is the caller's responsibility — `createMcpServer` connects
* stdio; tests can connect an {@link InMemoryTransport} instead.
*
* @internal
*/
function buildMcpServerFromContext(ctx, options) {
const server = new Server({
name: options.serverName,
version: options.serverVersion
}, { capabilities: {
tools: { listChanged: true },
resources: { listChanged: true }
} });
registerToolHandlers(server, ctx, options.exposeSharedState);
registerResourceHandlers(server, ctx, options.exposeSharedState);
const notify = (method) => {
server.notification({ method }).catch(() => {});
};
const offManifest = ctx.agent.events.on("agent:manifest:changed", () => {
notify("notifications/tools/list_changed");
notify("notifications/resources/list_changed");
});
const offKeyAdded = ctx.rpc.sharedState.onKeyAdded(() => {
notify("notifications/resources/list_changed");
});
return {
server,
dispose: () => {
offManifest();
offKeyAdded();
}
};
}
/**
* Build an MCP server over the agent surface of a devframe definition.
* Currently supports `stdio` transport only.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
async function createMcpServer(definition, options = {}) {
const transport = options.transport ?? "stdio";
if (transport !== "stdio") throw diagnostics.DF0017({
transport,
reason: "Only stdio transport is supported in this release."
});
const ctx = await createHostContext({
cwd: process.cwd(),
mode: "dev",
host: {
mountStatic: () => {},
resolveOrigin: () => "mcp://devframe",
getStorageDir: (scope) => {
if (scope === "workspace") return join(process.cwd(), ".devframe");
if (scope === "project") return join(process.cwd(), `node_modules/.${definition.id}/devframe`);
return join(homedir(), `.${definition.id}/devframe`);
}
}
});
await definition.setup(ctx);
const { server, dispose } = buildMcpServerFromContext(ctx, {
serverName: options.serverName ?? `${definition.id} (devframe)`,
serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
exposeSharedState: options.exposeSharedState ?? true
});
const { startStdioTransport } = await import("./transports-vhizgqXM.mjs");
let stop;
try {
stop = await startStdioTransport(server);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport,
reason,
cause: error
});
}
options.onReady?.({ transport: "stdio" });
return { async stop() {
dispose();
await stop();
} };
}
/**
* Id of the built-in shared-state read tool — namespaced like every other
* built-in (`devframe:<area>:<fn>`). Tool-shaped access matters because many
* MCP clients only consume tools — the parallel `devframe://state/<key>`
* resource projection stays for the clients that do read resources.
*/
const READ_STATE_TOOL = "devframe:state:read";
/** Wire name of the built-in shared-state read tool: `devframe_state_read`. */
const READ_STATE_NAME = toAgentToolName(READ_STATE_TOOL);
function sharedStateFilter(exposeSharedState) {
if (exposeSharedState === false) return void 0;
return typeof exposeSharedState === "function" ? exposeSharedState : () => true;
}
function readStateToolProjection() {
return {
name: READ_STATE_NAME,
title: "Read shared state",
description: "Read this devtool's live shared state. Call without arguments to list the available keys, then with a key to get that value as JSON. Safe to call freely.",
inputSchema: {
type: "object",
properties: { key: {
type: "string",
description: "A shared-state key from the key list. Omit to list all keys."
} }
},
annotations: {
title: "Read shared state",
readOnlyHint: true,
destructiveHint: false
}
};
}
async function readStateResult(ctx, filter, key) {
const keys = ctx.rpc.sharedState.keys().filter(filter);
if (key === void 0) return { keys };
if (!keys.includes(key)) throw diagnostics.DF0048({ key });
return {
key,
value: (await ctx.rpc.sharedState.get(key)).value()
};
}
function registerToolHandlers(server, ctx, exposeSharedState) {
const stateFilter = sharedStateFilter(exposeSharedState);
const warnedCollisions = /* @__PURE__ */ new Set();
/**
* Resolve a wire tool name back to the registered {@link AgentTool}.
* Wire-name matching runs first, in manifest order — the same tool the
* list projection advertises under that name — with a raw-id fallback so
* a colon-namespaced id keeps working as a call name.
*/
const resolveTool = (name) => {
return ctx.agent.list().tools.find((tool) => toAgentToolName(tool.id) === name) ?? ctx.agent.getTool(name);
};
server.setRequestHandler("tools/list", async () => {
const byName = /* @__PURE__ */ new Map();
for (const tool of ctx.agent.list().tools) {
const name = toAgentToolName(tool.id);
const existing = byName.get(name);
if (existing) {
if (!warnedCollisions.has(`${name}|${tool.id}`)) {
warnedCollisions.add(`${name}|${tool.id}`);
diagnostics.DF0047({
name,
id: tool.id,
existing: existing.id
});
}
continue;
}
byName.set(name, tool);
}
const tools = [...byName.entries()].map(([name, tool]) => projectTool(name, tool, ctx));
if (stateFilter && !byName.has(READ_STATE_NAME)) tools.push(readStateToolProjection());
return { tools };
});
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
try {
const tool = resolveTool(name);
if (stateFilter && !tool && (name === READ_STATE_NAME || name === READ_STATE_TOOL)) {
const key = args?.key;
const result = await readStateResult(ctx, stateFilter, key);
return {
content: [{
type: "text",
text: stringifyForMcp(result)
}],
structuredContent: result
};
}
const outputSchema = tool ? usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx)) : void 0;
const result = await ctx.agent.invoke(tool?.id ?? name, args ?? {});
return {
content: [{
type: "text",
text: stringifyForMcp(result)
}],
...outputSchema ? { structuredContent: result } : {}
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error invoking "${name}": ${formatMcpError(error)}`
}]
};
}
});
}
function registerResourceHandlers(server, ctx, exposeSharedState) {
server.setRequestHandler("resources/list", async () => {
const resources = ctx.agent.list().resources.map((resource) => ({
uri: resource.uri,
name: resource.name,
description: resource.description,
mimeType: resource.mimeType
}));
if (exposeSharedState !== false) {
const filter = typeof exposeSharedState === "function" ? exposeSharedState : () => true;
for (const key of ctx.rpc.sharedState.keys()) {
if (!filter(key)) continue;
resources.push({
uri: `devframe://state/${encodeURIComponent(key)}`,
name: key,
description: `Shared state: ${key}`,
mimeType: "application/json"
});
}
}
return { resources };
});
server.setRequestHandler("resources/read", async (request) => {
const { uri } = request.params;
const parsed = parseResourceUri(uri);
if (parsed.kind === "resource") {
const content = await ctx.agent.read(parsed.id);
return { contents: [{
uri,
mimeType: content.mimeType ?? "application/json",
text: content.text ?? stringifyForMcp(content.json)
}] };
}
if (parsed.kind === "state") return { contents: [{
uri,
mimeType: "application/json",
text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
}] };
throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
});
}
/**
* MCP constrains a tool's `outputSchema` to a JSON Schema of `type:
* "object"` — clients (the SDK included) reject anything else. Non-object
* return schemas (e.g. a schema for `void` / a bare string) simply project
* no output schema; the text content still carries the result.
*/
function usableOutputSchema(schema) {
return schema && typeof schema === "object" && schema.type === "object" ? schema : void 0;
}
function projectTool(name, tool, ctx) {
const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
const outputSchema = usableOutputSchema(tool.outputSchema ?? computeOutputSchema(tool, ctx));
return {
name,
title: tool.title,
description: tool.description,
inputSchema,
...outputSchema ? { outputSchema } : {},
annotations: {
title: tool.title,
readOnlyHint: tool.safety === "read",
destructiveHint: tool.safety === "destructive"
}
};
}
function computeInputSchema(tool, ctx) {
if (tool.kind === "tool") return argsToJsonSchema(tool.args).schema;
if (tool.kind !== "rpc" || !tool.rpcName) return {
type: "object",
properties: {}
};
const def = ctx.rpc.definitions.get(tool.rpcName);
if (!def) return {
type: "object",
properties: {}
};
const args = def.args;
return argsToJsonSchema(args).schema;
}
function computeOutputSchema(tool, ctx) {
if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
const def = ctx.rpc.definitions.get(tool.rpcName);
if (!def) return void 0;
return returnToJsonSchema(def.returns);
}
function parseResourceUri(uri) {
const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
if (!match) return { kind: "unknown" };
const [, kind, rest] = match;
const decoded = decodeURIComponent(rest);
if (kind === "resource") return {
kind: "resource",
id: decoded
};
return {
kind: "state",
key: decoded
};
}
//#endregion
//#region src/adapters/mcp/fetch.ts
/**
* Build a framework-agnostic MCP Streamable-HTTP endpoint over a devframe
* context: a web-standard `Request → Response` handler any host can mount —
* h3 (see `mountMcpHttp`), a Next.js App Router route, or any other
* fetch-shaped server.
*
* Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
* and MCP server (built from the shared, live `ctx` via
* `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
* `initialize` POST spins up a session; later requests route to it; a `DELETE`
* (or client disconnect) tears it down. The origin gate applies devframe's
* loopback-default DNS-rebinding protection (identical semantics to the WS
* upgrade's `isAllowedOrigin`).
*
* @experimental
*/
function createMcpFetchHandler(ctx, options) {
const sessions = /* @__PURE__ */ new Map();
const allowedOrigins = options.allowedOrigins;
function drop(sessionId) {
const session = sessions.get(sessionId);
if (!session) return;
sessions.delete(sessionId);
session.dispose();
}
async function createSession() {
let session;
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => {
sessions.set(id, session);
},
onsessionclosed: (id) => {
drop(id);
}
});
const { server, dispose } = buildMcpServerFromContext(ctx, {
serverName: options.serverName,
serverVersion: options.serverVersion,
exposeSharedState: options.exposeSharedState
});
session = {
transport,
dispose: async () => {
dispose();
await server.close();
}
};
transport.onclose = () => {
if (transport.sessionId) drop(transport.sessionId);
};
await server.connect(transport);
return session;
}
async function handle(req) {
const origin = req.headers.get("origin") ?? void 0;
if (allowedOrigins !== false && !isAllowedOrigin(origin, allowedOrigins ?? [])) return new Response("Forbidden: origin not allowed", { status: 403 });
const sessionId = req.headers.get("mcp-session-id") ?? void 0;
let session = sessionId ? sessions.get(sessionId) : void 0;
if (!session && req.method === "POST") {
let body;
try {
body = await req.json();
} catch {
body = void 0;
}
if (!sessionId && isInitializeRequest(body)) session = await createSession();
else return new Response(sessionId ? "Not Found: unknown MCP session" : "Bad Request: no valid session ID and not an initialize request", { status: sessionId ? 404 : 400 });
return session.transport.handleRequest(req, { parsedBody: body });
}
if (!session) return new Response(sessionId ? "Not Found: unknown MCP session" : "Bad Request: missing MCP session ID", { status: sessionId ? 404 : 400 });
return session.transport.handleRequest(req);
}
return {
fetch: handle,
dispose: async () => {
const live = [...sessions.values()];
sessions.clear();
await Promise.all(live.map((session) => session.dispose()));
}
};
}
//#endregion
export { createMcpServer as n, createMcpFetchHandler as t };
import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { t as diagnostics } from "./diagnostics-hwjXp_UV.mjs";
import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
import { defineRpcFunction } from "./index.mjs";
import { t as diagnostics$1 } from "./diagnostics-B5-qHeqD.mjs";
import { i as createEventEmitter, n as createSharedState, r as nanoid, t as createStorage } from "./storage-Dzoc3NVC.mjs";
import { defineDiagnostics } from "nostics";
import { isatty } from "node:tty";
import { formatWithOptions, inspect } from "node:util";
import { existsSync } from "node:fs";
import { join } from "pathe";
import process$1 from "node:process";
import { homedir } from "node:os";
//#region src/node/agent-args.ts
/**
* Map the args payload an agent surface receives (MCP sends an object
* keyed `arg0`/`arg1`/…, matching the schema the adapter advertises) onto
* a handler's positional parameters. Shared by the agent host's RPC
* bridge and the hub's command-derived tools so the coercion cannot
* drift between them.
*
* - an array passes through as-is
* - `null`/`undefined` become a zero-argument call
* - with declared schemas, each schema reads its own `argN` key, in order
* - without schemas, `arg0`/`arg1`/… keys are collected when present
* - an empty object becomes a zero-argument call
* - anything else follows the {@link AgentArgsFallback}
*
* @experimental
*/
function coerceAgentPositionalArgs(args, schemas, fallback = "wrap") {
if (Array.isArray(args)) return args;
if (args === void 0 || args === null) return [];
if (typeof args === "object") {
const obj = args;
if (schemas && schemas.length) return schemas.map((_, i) => obj[`arg${i}`]);
if ("arg0" in obj) {
const out = [];
let i = 0;
while (`arg${i}` in obj) {
out.push(obj[`arg${i}`]);
i++;
}
return out;
}
if (Object.keys(obj).length === 0) return [];
}
return fallback === "drop" ? [] : [args];
}
//#endregion
//#region src/node/host-agent.ts
/**
* Framework-neutral host aggregating the agent-exposed surface of a
* devframe. Auto-discovers RPC functions with an `agent` field from
* `ctx.rpc.definitions`, and accepts plugin-registered tools /
* resources via `registerTool` / `registerResource`.
*
* @experimental
*/
var DevframeAgentHost = class {
context;
events = createEventEmitter();
tools = /* @__PURE__ */ new Map();
resources = /* @__PURE__ */ new Map();
providers = /* @__PURE__ */ new Set();
_rpcUnsubscribe;
constructor(context) {
this.context = context;
this._rpcUnsubscribe = context.rpc.onChanged(() => {
this.events.emit("agent:manifest:changed");
});
}
registerTool(input) {
this._validateToolId(input.id);
const tool = this._projectTool(input);
this.tools.set(tool.id, {
tool,
handler: input.handler
});
this.events.emit("agent:tool:registered", tool);
this.events.emit("agent:manifest:changed");
return { unregister: () => this.unregisterTool(tool.id) };
}
unregisterTool(id) {
const existed = this.tools.delete(id);
if (existed) {
this.events.emit("agent:tool:unregistered", id);
this.events.emit("agent:manifest:changed");
}
return existed;
}
registerToolProvider(provider) {
this.providers.add(provider);
this.events.emit("agent:manifest:changed");
const notifyChanged = () => {
if (this.providers.has(provider)) this.events.emit("agent:manifest:changed");
};
return {
notifyChanged,
unregister: () => {
if (this.providers.delete(provider)) this.events.emit("agent:manifest:changed");
}
};
}
registerResource(input) {
if (this.resources.has(input.id)) throw diagnostics$1.DF0016({ id: input.id });
const resource = {
id: input.id,
name: input.name,
description: input.description,
mimeType: input.mimeType ?? "application/json",
uri: input.uri ?? `devframe://resource/${encodeURIComponent(input.id)}`
};
this.resources.set(resource.id, {
resource,
read: input.read
});
this.events.emit("agent:resource:registered", resource);
this.events.emit("agent:manifest:changed");
return { unregister: () => this.unregisterResource(resource.id) };
}
unregisterResource(id) {
const existed = this.resources.delete(id);
if (existed) {
this.events.emit("agent:resource:unregistered", id);
this.events.emit("agent:manifest:changed");
}
return existed;
}
list() {
const rpcTools = this._collectRpcTools();
const plainTools = Array.from(this.tools.values()).map((t) => t.tool);
const resources = Array.from(this.resources.values()).map((r) => r.resource);
const seen = new Set([...rpcTools, ...plainTools].map((t) => t.id));
const providerTools = [];
for (const { tool } of this._collectProviderTools()) {
if (seen.has(tool.id)) continue;
seen.add(tool.id);
providerTools.push(tool);
}
return {
tools: [
...rpcTools,
...plainTools,
...providerTools
],
resources
};
}
getTool(id) {
const plain = this.tools.get(id);
if (plain) return plain.tool;
const rpc = this._collectRpcTools().find((t) => t.id === id);
if (rpc) return rpc;
return this._collectProviderTools().find((t) => t.tool.id === id)?.tool;
}
getResource(id) {
return this.resources.get(id)?.resource;
}
async invoke(id, args) {
const plain = this.tools.get(id);
if (plain?.handler) return await plain.handler(args);
const rpcDef = this._findRpcDefinition(id);
if (rpcDef) {
const positional = coerceAgentPositionalArgs(args, rpcDef.args, "wrap");
return await this.context.rpc.invokeLocal(id, ...positional);
}
const provided = this._collectProviderTools().find((t) => t.tool.id === id);
if (provided) return await provided.input.handler(args);
throw new Error(`[devframe/agent] tool "${id}" not found`);
}
async read(id) {
const entry = this.resources.get(id);
if (!entry) throw new Error(`[devframe/agent] resource "${id}" not found`);
return await entry.read();
}
/** @internal */
_dispose() {
this._rpcUnsubscribe?.();
this._rpcUnsubscribe = void 0;
}
_validateToolId(id) {
if (this.tools.has(id)) throw diagnostics$1.DF0015({ id });
if (this.context.rpc.definitions.get(id)?.agent) throw diagnostics$1.DF0015({ id });
}
_projectTool(input) {
if (!input.description || typeof input.description !== "string") throw diagnostics$1.DF0014({ name: input.id });
return {
id: input.id,
kind: "tool",
title: input.title ?? input.id,
description: input.description,
safety: input.safety ?? "action",
tags: input.tags,
args: input.args,
inputSchema: input.inputSchema,
outputSchema: input.outputSchema,
examples: input.examples
};
}
/** Query every registered provider, projecting inputs to serializable tools. */
_collectProviderTools() {
const out = [];
for (const provider of this.providers) for (const input of provider()) out.push({
input,
tool: this._projectTool(input)
});
return out;
}
_collectRpcTools() {
const out = [];
for (const [name, def] of this.context.rpc.definitions) {
const agent = def.agent;
if (!agent) continue;
if (!agent.description || typeof agent.description !== "string") throw diagnostics$1.DF0014({ name });
const type = def.type ?? "query";
const safety = agent.safety ?? inferSafety(type);
out.push({
id: name,
kind: "rpc",
title: agent.title ?? name,
description: agent.description,
safety,
tags: agent.tags,
rpcName: name,
examples: agent.examples
});
}
return out;
}
_findRpcDefinition(id) {
const def = this.context.rpc.definitions.get(id);
if (def?.agent) return def;
}
};
function inferSafety(type) {
if (type === "static" || type === "query") return "read";
return "action";
}
//#endregion
//#region src/node/host-diagnostics.ts
var DevframeDiagnosticsHost = class {
context;
_registry = {};
logger = new Proxy({}, { get: (_, code) => this._registry[code] });
defineDiagnostics = (opts) => {
return defineDiagnostics({
...opts,
reporters: [devframeReporter, ...opts.reporters ?? []]
});
};
constructor(context, initialDefinitions = []) {
this.context = context;
for (const d of initialDefinitions) this.register(d);
}
register(diagnostics) {
Object.assign(this._registry, diagnostics);
}
};
//#endregion
//#region ../../node_modules/.pnpm/obug@2.1.4/node_modules/obug/dist/core.js
/**
* Coerce `value`.
*/
function coerce(value) {
if (value instanceof Error) return value.stack || value.message;
return value;
}
/**
* Selects a color for a debug namespace
* @return An ANSI color code for the given namespace
*/
function selectColor(colors, namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = (hash << 5) - hash + namespace.charCodeAt(i);
hash |= 0;
}
return colors[Math.abs(hash) % colors.length];
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*/
function matchesTemplate(search, template) {
let searchIndex = 0;
let templateIndex = 0;
let starIndex = -1;
let matchIndex = 0;
while (searchIndex < search.length) if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) if (template[templateIndex] === "*") {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++;
} else {
searchIndex++;
templateIndex++;
}
else if (starIndex !== -1) {
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else return false;
while (templateIndex < template.length && template[templateIndex] === "*") templateIndex++;
return templateIndex === template.length;
}
function humanize(value) {
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
return `${value}ms`;
}
let globalNamespaces = "";
function createDebug$1(namespace, options) {
let prevTime;
let enableOverride;
let namespacesCache;
let enabledCache;
const debug = (...args) => {
if (!debug.enabled) return;
const curr = Date.now();
const diff = curr - (prevTime || curr);
prevTime = curr;
args[0] = coerce(args[0]);
if (typeof args[0] !== "string") args.unshift("%O");
let index = 0;
args[0] = args[0].replace(/%([a-z%])/gi, (match, format) => {
if (match === "%%") return "%";
index++;
const formatter = options.formatters[format];
if (typeof formatter === "function") {
const value = args[index];
match = formatter.call(debug, value);
args.splice(index, 1);
index--;
}
return match;
});
options.formatArgs.call(debug, diff, args);
debug.log(...args);
};
debug.extend = function(namespace, delimiter = ":") {
return createDebug$1(this.namespace + delimiter + namespace, {
useColors: this.useColors,
color: this.color,
formatArgs: this.formatArgs,
formatters: this.formatters,
inspectOpts: this.inspectOpts,
log: this.log,
humanize: this.humanize
});
};
Object.assign(debug, options);
debug.namespace = namespace;
Object.defineProperty(debug, "enabled", {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride != null) return enableOverride;
if (namespacesCache !== globalNamespaces) {
namespacesCache = globalNamespaces;
enabledCache = enabled(namespace);
}
return enabledCache;
},
set: (v) => {
enableOverride = v;
}
});
return debug;
}
let names = [];
let skips = [];
function enable(namespaces) {
globalNamespaces = namespaces;
names = [];
skips = [];
const split = globalNamespaces.trim().replace(/\s+/g, ",").split(",").filter(Boolean);
for (const ns of split) if (ns[0] === "-") skips.push(ns.slice(1));
else names.push(ns);
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*/
function enabled(name) {
for (const skip of skips) if (matchesTemplate(name, skip)) return false;
for (const ns of names) if (matchesTemplate(name, ns)) return true;
return false;
}
//#endregion
//#region ../../node_modules/.pnpm/obug@2.1.4/node_modules/obug/dist/node.js
let env = {};
try {
process.env.DEBUG;
env = process.env;
} catch (_unused) {}
const colors = process.stderr.getColorDepth && process.stderr.getColorDepth(env) > 2 ? [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
] : [
6,
2,
3,
4,
5,
1
];
const inspectOpts = Object.keys(env).filter((key) => /^debug_/i.test(key)).reduce((obj, key) => {
const prop = key.slice(6).toLowerCase().replace(/_([a-z])/g, (_, k) => k.toUpperCase());
let value = env[key];
const lowerCase = typeof value === "string" && value.toLowerCase();
if (value === "null") value = null;
else if (lowerCase === "yes" || lowerCase === "on" || lowerCase === "true" || lowerCase === "enabled") value = true;
else if (lowerCase === "no" || lowerCase === "off" || lowerCase === "false" || lowerCase === "disabled") value = false;
else value = Number(value);
obj[prop] = value;
return obj;
}, Object.create(null));
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
return "colors" in inspectOpts ? Boolean(inspectOpts.colors) : isatty(process.stderr.fd);
}
function getDate() {
if (inspectOpts.hideDate) return "";
return `${(/* @__PURE__ */ new Date()).toISOString()} `;
}
/**
* Adds ANSI color escape codes if enabled.
*/
function formatArgs(diff, args) {
const { namespace: name, useColors } = this;
if (useColors) {
const c = this.color;
const colorCode = `\u001B[3${c < 8 ? c : `8;5;${c}`}`;
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split("\n").join(`\n${prefix}`);
args.push(`${colorCode}m+${this.humanize(diff)}\u001B[0m`);
} else args[0] = `${getDate()}${name} ${args[0]}`;
}
function log(...args) {
process.stderr.write(`${formatWithOptions(this.inspectOpts, ...args)}\n`);
}
const defaultOptions = {
useColors: useColors(),
formatArgs,
formatters: {
/**
* Map %o to `util.inspect()`, all on a single line.
*/
o(v) {
this.inspectOpts.colors = this.useColors;
return inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
},
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
O(v) {
this.inspectOpts.colors = this.useColors;
return inspect(v, this.inspectOpts);
}
},
inspectOpts,
log,
humanize
};
function createDebug(namespace, options) {
var _ref;
const color = (_ref = options && options.color) !== null && _ref !== void 0 ? _ref : selectColor(colors, namespace);
return createDebug$1(namespace, Object.assign(defaultOptions, { color }, options));
}
enable(env.DEBUG || "");
//#endregion
//#region src/node/rpc-shared-state.ts
const debug$1 = createDebug("devframe:rpc:state:changed");
const debugSubscribe = createDebug("devframe:rpc:state:subscribe");
function createRpcSharedStateServerHost(rpc) {
const sharedState = /* @__PURE__ */ new Map();
const stateDisposers = /* @__PURE__ */ new Map();
const keyAddedListeners = /* @__PURE__ */ new Set();
function registerSharedState(key, state) {
const offs = [];
offs.push(state.on("updated", (fullState, patches, syncId) => {
if (patches) {
debug$1("patch", {
key,
syncId
});
rpc.broadcast({
method: "devframe:rpc:client-state:patch",
args: [
key,
patches,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
} else {
debug$1("updated", {
key,
syncId
});
rpc.broadcast({
method: "devframe:rpc:client-state:updated",
args: [
key,
fullState,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
}
}));
return () => {
for (const off of offs) off();
};
}
const host = {
get: async (key, options) => {
if (sharedState.has(key)) return sharedState.get(key);
if (options?.initialValue === void 0 && options?.sharedState === void 0) throw diagnostics$1.DF0013({ key });
debug$1("new-state", key);
const state = options.sharedState ?? createSharedState({
initialValue: options.initialValue,
enablePatches: false
});
stateDisposers.set(key, registerSharedState(key, state));
sharedState.set(key, state);
for (const fn of keyAddedListeners) fn(key);
return state;
},
keys() {
return Array.from(sharedState.keys());
},
onKeyAdded(fn) {
keyAddedListeners.add(fn);
return () => {
keyAddedListeners.delete(fn);
};
},
delete(key) {
const dispose = stateDisposers.get(key);
if (!dispose) return false;
dispose();
stateDisposers.delete(key);
sharedState.delete(key);
return true;
}
};
rpc.register({
name: "devframe:rpc:server-state:subscribe",
type: "event",
handler(key) {
const session = rpc.getCurrentRpcSession();
if (!session) return;
debugSubscribe("subscribe", {
key,
session: session.meta.id
});
session.meta.subscribedStates.add(key);
}
});
rpc.register({
name: "devframe:rpc:server-state:get",
type: "query",
handler: async (key) => {
if (!sharedState.has(key)) return void 0;
return (await host.get(key)).value();
},
dump: () => ({ inputs: host.keys().map((key) => [key]) })
});
rpc.register({
name: "devframe:rpc:server-state:set",
type: "query",
handler: async (key, value, syncId) => {
(await host.get(key, { initialValue: value })).mutate(() => value, syncId);
}
});
rpc.register({
name: "devframe:rpc:server-state:patch",
type: "query",
handler: async (key, patches, syncId) => {
if (!sharedState.has(key)) return;
(await host.get(key)).patch(patches, syncId);
}
});
return host;
}
//#endregion
//#region src/utils/streaming-channel.ts
const DEFAULT_HIGH_WATER_MARK = 256;
var StreamClosedError = class extends Error {
name = "StreamClosedError";
};
/**
* Build a server-side stream sink. RPC-agnostic — the RPC host wires
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
* for late or reconnecting subscribers.
*/
function createStreamSink(options = {}) {
const id = options.id ?? nanoid();
const replayWindow = Math.max(0, options.replayWindow ?? 0);
const events = createEventEmitter();
const controller = new AbortController();
const buffer = [];
let closed = false;
let lastSeq = 0;
function write(chunk) {
if (closed) throw new StreamClosedError(`Cannot write to a closed stream "${id}"`);
lastSeq += 1;
if (replayWindow > 0) {
buffer.push({
seq: lastSeq,
chunk
});
if (buffer.length > replayWindow) buffer.splice(0, buffer.length - replayWindow);
}
events.emit("chunk", lastSeq, chunk);
}
function error(reason) {
if (closed) return;
closed = true;
const payload = toErrorPayload(reason);
controller.abort(reason);
events.emit("end", payload);
}
function close() {
if (closed) return;
closed = true;
if (!controller.signal.aborted) controller.abort("stream closed");
events.emit("end", void 0);
}
function abort(reason) {
if (closed) return;
if (!controller.signal.aborted) controller.abort(reason ?? "aborted");
}
const writable = new WritableStream({
write(chunk) {
write(chunk);
},
close() {
close();
},
abort(reason) {
error(reason);
}
});
return {
id,
signal: controller.signal,
get closed() {
return closed;
},
get lastSeq() {
return lastSeq;
},
write,
error,
close,
abort,
writable,
events,
buffer
};
}
/**
* Build a client-side stream reader. RPC-agnostic — the RPC host calls
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
*/
function createStreamReader(options = {}) {
const id = options.id ?? nanoid();
const highWaterMark = Math.max(1, options.highWaterMark ?? DEFAULT_HIGH_WATER_MARK);
const queue = [];
let lastSeenSeq = 0;
let done = false;
let cancelled = false;
let endError;
let pending;
let pullController;
let readableInstance;
function drainNext() {
if (!pending) return;
if (queue.length > 0) {
const value = queue.shift();
const r = pending;
pending = void 0;
r.resolve({
value,
done: false
});
return;
}
if (done) {
const r = pending;
pending = void 0;
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
r.reject(err);
} else r.resolve({
value: void 0,
done: true
});
}
}
function feedReadable() {
if (!pullController) return;
while (queue.length > 0) {
const v = queue.shift();
try {
pullController.enqueue(v);
} catch {
break;
}
}
if (done && pullController) {
try {
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
pullController.error(err);
} else pullController.close();
} catch {}
pullController = void 0;
}
}
function push(seq, chunk) {
if (done || cancelled) return;
if (seq <= lastSeenSeq) return;
lastSeenSeq = seq;
queue.push(chunk);
if (queue.length > highWaterMark) {
const overflow = queue.length - highWaterMark;
queue.splice(0, overflow);
options.onOverflow?.(overflow);
}
drainNext();
if (readableInstance) feedReadable();
}
function end(error) {
if (done) return;
done = true;
endError = error;
drainNext();
if (readableInstance) feedReadable();
}
function cancel() {
if (cancelled || done) return;
cancelled = true;
options.onCancel?.();
end(void 0);
}
function getReadable() {
if (readableInstance) return readableInstance;
readableInstance = new ReadableStream({
start(controller) {
pullController = controller;
feedReadable();
},
cancel() {
cancel();
}
});
return readableInstance;
}
return {
id,
get cancelled() {
return cancelled;
},
get done() {
return done;
},
get lastSeenSeq() {
return lastSeenSeq;
},
get readable() {
return getReadable();
},
cancel,
_push: push,
_end: end,
[Symbol.asyncIterator]() {
return {
next() {
if (queue.length > 0) return Promise.resolve({
value: queue.shift(),
done: false
});
if (done) {
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
return Promise.reject(err);
}
return Promise.resolve({
value: void 0,
done: true
});
}
return new Promise((resolve, reject) => {
pending = {
resolve,
reject
};
});
},
return() {
cancel();
return Promise.resolve({
value: void 0,
done: true
});
}
};
}
};
}
function toErrorPayload(reason) {
if (reason instanceof Error) return {
name: reason.name || "Error",
message: reason.message
};
if (typeof reason === "string") return {
name: "Error",
message: reason
};
try {
return {
name: "Error",
message: JSON.stringify(reason)
};
} catch {
return {
name: "Error",
message: String(reason)
};
}
}
//#endregion
//#region src/node/rpc-streaming.ts
const debug = createDebug("devframe:rpc:streaming");
const STREAM_KEY_SEPARATOR = "";
function streamKey(channel, id) {
return `${channel}${STREAM_KEY_SEPARATOR}${id}`;
}
/**
* Build the server-side streaming host. Mirrors the layout of
* `createRpcSharedStateServerHost` — registers a fixed set of internal
* RPC methods (`subscribe` / `unsubscribe` / `cancel`) once, then per-channel
* state lives in a `Map<channelName, ChannelState>`.
*/
function createRpcStreamingServerHost(rpc) {
const channels = /* @__PURE__ */ new Map();
function findStream(channelName, id) {
return channels.get(channelName)?.streams.get(id);
}
function freeStreamNow(state, id) {
const record = state.streams.get(id);
if (!record) return;
if (record.retentionTimer) {
clearTimeout(record.retentionTimer);
record.retentionTimer = void 0;
}
for (const off of record.unbinders) off();
state.streams.delete(id);
debug("freed", state.name, id);
}
function maybeFreeStream(state, id) {
const record = state.streams.get(id);
if (!record) return;
if (!record.sink.closed || record.subscribers.size > 0) return;
const retention = state.options.closedStreamRetention;
if (retention <= 0) {
freeStreamNow(state, id);
return;
}
if (record.retentionTimer) return;
record.retentionTimer = setTimeout(freeStreamNow, retention, state, id);
}
function cancelRetention(record) {
if (record.retentionTimer) {
clearTimeout(record.retentionTimer);
record.retentionTimer = void 0;
}
}
rpc.register({
name: "devframe:streaming:subscribe",
type: "event",
handler(channelName, id, opts) {
const state = channels.get(channelName);
if (!state) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
const record = state.streams.get(id);
if (!record) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
const session = rpc.getCurrentRpcSession();
if (!session) return;
const key = streamKey(channelName, id);
session.meta.subscribedStreams ??= /* @__PURE__ */ new Set();
session.meta.subscribedStreams.add(key);
record.subscribers.add(session.meta);
cancelRetention(record);
const afterSeq = opts?.afterSeq ?? 0;
for (const buffered of record.sink.buffer) if (buffered.seq > afterSeq) rpc.broadcast({
method: "devframe:streaming:chunk",
args: [
channelName,
id,
buffered.seq,
buffered.chunk
],
event: true,
optional: true,
filter: (client) => client.$meta === session.meta
});
if (record.sink.closed) rpc.broadcast({
method: "devframe:streaming:end",
args: [
channelName,
id,
void 0
],
event: true,
optional: true,
filter: (client) => client.$meta === session.meta
});
}
});
rpc.register({
name: "devframe:streaming:unsubscribe",
type: "event",
handler(channelName, id) {
const state = channels.get(channelName);
const record = state?.streams.get(id);
const session = rpc.getCurrentRpcSession();
if (!session) return;
session.meta.subscribedStreams?.delete(streamKey(channelName, id));
if (state && record) {
record.subscribers.delete(session.meta);
maybeFreeStream(state, id);
}
}
});
rpc.register({
name: "devframe:streaming:cancel",
type: "event",
handler(channelName, id) {
const record = findStream(channelName, id);
if (!record) return;
const session = rpc.getCurrentRpcSession();
if (!session) return;
record.subscribers.delete(session.meta);
session.meta.subscribedStreams?.delete(streamKey(channelName, id));
if (record.subscribers.size === 0) record.sink.abort("cancelled by client");
}
});
rpc.register({
name: "devframe:streaming:upload-chunk",
type: "event",
handler(channelName, id, seq, chunk) {
const record = channels.get(channelName)?.inbound.get(id);
if (!record) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
if (!record.uploaderMeta) {
const session = rpc.getCurrentRpcSession();
if (session) {
record.uploaderMeta = session.meta;
session.meta.uploadingStreams ??= /* @__PURE__ */ new Set();
session.meta.uploadingStreams.add(streamKey(channelName, id));
}
}
record.reader._push(seq, chunk);
}
});
rpc.register({
name: "devframe:streaming:upload-end",
type: "event",
handler(channelName, id, error) {
const state = channels.get(channelName);
const record = state?.inbound.get(id);
if (!record) return;
record.reader._end(error);
if (record.uploaderMeta) record.uploaderMeta.uploadingStreams?.delete(streamKey(channelName, id));
state?.inbound.delete(id);
}
});
function createChannel(name, opts = {}) {
if (channels.has(name)) throw diagnostics$1.DF0032({ channel: name });
const replayWindow = opts.replayWindow ?? 0;
const state = {
name,
options: {
replayWindow,
closedStreamRetention: opts.closedStreamRetention ?? (replayWindow > 0 ? 3e4 : 0)
},
streams: /* @__PURE__ */ new Map(),
inbound: /* @__PURE__ */ new Map()
};
channels.set(name, state);
function start(startOpts = {}) {
const sink = createStreamSink({
id: startOpts.id,
replayWindow: state.options.replayWindow
});
const record = {
sink,
subscribers: /* @__PURE__ */ new Set(),
unbinders: []
};
state.streams.set(sink.id, record);
record.unbinders.push(sink.events.on("chunk", (seq, chunk) => {
rpc.broadcast({
method: "devframe:streaming:chunk",
args: [
name,
sink.id,
seq,
chunk
],
event: true,
optional: true,
filter: (client) => record.subscribers.has(client.$meta)
});
}));
record.unbinders.push(sink.events.on("end", (error) => {
rpc.broadcast({
method: "devframe:streaming:end",
args: [
name,
sink.id,
error
],
event: true,
optional: true,
filter: (client) => record.subscribers.has(client.$meta)
});
maybeFreeStream(state, sink.id);
}));
return sink;
}
async function pipeFrom(readable, startOpts = {}) {
const sink = start(startOpts);
readable.pipeTo(sink.writable, { signal: sink.signal }).catch(() => {});
return sink;
}
function get(id) {
return state.streams.get(id)?.sink;
}
function ids() {
return Array.from(state.streams.keys());
}
function openInbound(inboundOpts = {}) {
let inboundRecord;
const reader = createStreamReader({
id: inboundOpts.id,
onCancel() {
const targetMeta = inboundRecord?.uploaderMeta;
if (!targetMeta) return;
rpc.broadcast({
method: "devframe:streaming:upload-cancel",
args: [name, reader.id],
event: true,
optional: true,
filter: (client) => client.$meta === targetMeta
});
}
});
inboundRecord = { reader };
state.inbound.set(reader.id, inboundRecord);
debug("opened-inbound", name, reader.id);
return reader;
}
return {
name,
start,
pipeFrom,
get,
ids,
openInbound
};
}
function parseKey(key) {
const sepIdx = key.indexOf(STREAM_KEY_SEPARATOR);
if (sepIdx < 0) return void 0;
return {
channelName: key.slice(0, sepIdx),
id: key.slice(sepIdx + 1)
};
}
return {
create: createChannel,
_onSessionDisconnected(meta) {
if (meta.subscribedStreams) {
for (const key of meta.subscribedStreams) {
const parsed = parseKey(key);
if (!parsed) continue;
const state = channels.get(parsed.channelName);
const record = state?.streams.get(parsed.id);
if (!state || !record) continue;
record.subscribers.delete(meta);
if (record.subscribers.size === 0 && !record.sink.closed) record.sink.abort("all subscribers disconnected");
maybeFreeStream(state, parsed.id);
}
meta.subscribedStreams.clear();
}
if (meta.uploadingStreams) {
for (const key of meta.uploadingStreams) {
const parsed = parseKey(key);
if (!parsed) continue;
const state = channels.get(parsed.channelName);
const record = state?.inbound.get(parsed.id);
if (!state || !record) continue;
record.reader._end({
name: "UploadDisconnected",
message: "Uploader disconnected before completing the stream"
});
state.inbound.delete(parsed.id);
}
meta.uploadingStreams.clear();
}
}
};
}
//#endregion
//#region src/node/host-functions.ts
const debugBroadcast = createDebug("devframe:rpc:broadcast");
/**
* Concrete implementation backing `ctx.rpc`. Internal: consumers should
* depend on the structural {@link RpcFunctionsHost} type, never this class.
* Its `@internal` members (`_rpcGroup`, `_asyncStorage`,
* `_emitSessionDisconnected`) are wired by `startHttpAndWs` and must not
* widen the public surface.
*
* @internal
*/
var RpcFunctionsHostImpl = class extends RpcFunctionsCollectorBase {
/**
* @internal
*/
_rpcGroup = void 0;
_asyncStorage = void 0;
constructor(context) {
super(context);
this.sharedState = createRpcSharedStateServerHost(this);
this.streaming = createRpcStreamingServerHost(this);
}
sharedState;
streaming;
/**
* Adapters call this from their WS `onDisconnected` hook so downstream
* hosts (streaming, …) can free per-session state. Public-ish because
* tests / custom adapters may want to mirror it.
*
* @internal
*/
_emitSessionDisconnected(meta) {
this.streaming._onSessionDisconnected(meta);
}
async invokeLocal(method, ...args) {
if (!this.definitions.has(method)) throw diagnostics$1.DF0006({ name: String(method) });
const handler = await this.getHandler(method);
return await Promise.resolve(handler(...args));
}
async broadcast(options) {
if (!this._rpcGroup) return;
debugBroadcast(JSON.stringify(options.method));
await Promise.allSettled(this._rpcGroup.clients.map((client) => {
if (options.filter?.(client) === false) return void 0;
return client.$callRaw({
optional: true,
event: true,
...options
});
}));
}
getCurrentRpcSession() {
if (!this._asyncStorage) throw diagnostics$1.DF0007();
return this._asyncStorage.getStore();
}
};
//#endregion
//#region src/node/host-services.ts
/**
* Cross-plugin service registry (see `types/services.ts` for the contract).
* Values are held per context instance; `whenAvailable` subscriptions make
* the mechanism robust against setup ordering between provider and consumer.
*/
var DevframeServicesHostImpl = class {
services = /* @__PURE__ */ new Map();
listeners = /* @__PURE__ */ new Map();
provide(id, service) {
const key = id;
if (this.services.has(key)) throw diagnostics$1.DF0037({ id: key });
this.services.set(key, service);
for (const listener of this.listeners.get(key) ?? []) listener(service);
return () => {
if (this.services.get(key) === service) this.services.delete(key);
};
}
get(id) {
return this.services.get(id);
}
has(id) {
return this.services.has(id);
}
whenAvailable(id, callback) {
const key = id;
if (this.services.has(key)) callback(this.services.get(key));
let set = this.listeners.get(key);
if (!set) {
set = /* @__PURE__ */ new Set();
this.listeners.set(key, set);
}
const listener = callback;
set.add(listener);
return () => {
set.delete(listener);
};
}
keys() {
return Array.from(this.services.keys());
}
};
//#endregion
//#region src/node/host-views.ts
var DevframeViewHost = class {
context;
/**
* @internal
*/
buildStaticDirs = [];
constructor(context) {
this.context = context;
}
hostStatic(baseUrl, distDir) {
if (!existsSync(distDir)) throw diagnostics$1.DF0008({ distDir });
this.buildStaticDirs.push({
baseUrl,
distDir
});
this.context.host.mountStatic(baseUrl, distDir);
}
};
//#endregion
//#region src/node/rpc/agent-invoke-tool.ts
const agentInvokeTool = defineRpcFunction({
name: "devframe:agent:invoke-tool",
type: "action",
setup: (ctx) => {
return { async handler(id, args) {
return await ctx.agent.invoke(id, args);
} };
}
});
//#endregion
//#region src/node/rpc/agent-list-resources.ts
const agentListResources = defineRpcFunction({
name: "devframe:agent:list-resources",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler() {
return ctx.agent.list().resources;
} };
}
});
//#endregion
//#region src/node/rpc/index.ts
/**
* Built-in agent introspection RPC functions. Registered automatically
* by `createHostContext`. Not themselves agent-exposed (no `agent`
* field) — they power the MCP adapter and any future agent CLI.
*
* @experimental
*/
const BUILTIN_AGENT_RPC = [
defineRpcFunction({
name: "devframe:agent:list-tools",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler() {
return ctx.agent.list().tools;
} };
}
}),
agentInvokeTool,
agentListResources,
defineRpcFunction({
name: "devframe:agent:read-resource",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler(id) {
return await ctx.agent.read(id);
} };
}
})
];
//#endregion
//#region src/utils/scope.ts
/** Whether a name is already namespaced (contains a `:` separator). */
function isQualifiedName(name) {
return name.includes(":");
}
/**
* Prefix a bare name with `<namespace>:`. Names that already contain a
* `:` are returned unchanged, so callers can reference another scope's
* ids explicitly (e.g. `ctx.rpc.call('other-plugin:fn')`).
*/
function qualifyName(namespace, name) {
return isQualifiedName(name) ? name : `${namespace}:${name}`;
}
//#endregion
//#region src/node/settings.ts
const STORAGE_SCOPE = {
global: "global",
project: "project"
};
function createNodeSettingsStore(context, namespace, scope) {
const stateKey = `devframe:settings:${scope}:${namespace}`;
let statePromise;
function store() {
if (!statePromise) {
const filepath = join(context.host.getStorageDir(STORAGE_SCOPE[scope]), "settings", `${namespace}.json`);
statePromise = context.rpc.sharedState.get(stateKey, { sharedState: createStorage({
filepath,
initialValue: {}
}) });
}
return statePromise;
}
return {
async get(key) {
return (await store()).value()[key];
},
async set(key, value) {
(await store()).mutate((draft) => {
draft[key] = value;
});
},
async delete(key) {
(await store()).mutate((draft) => {
delete draft[key];
});
},
async all() {
return (await store()).value();
},
async onChange(fn) {
return (await store()).on("updated", (full) => fn(full));
}
};
}
/**
* Build the node-side `settings` surface for a scope namespace. `project`
* persists under the host's `workspace` storage dir, `global` under its
* `global` dir. Each is a file-backed, client-synced key-value store.
*/
function createNodeSettings(context, namespace) {
return {
global: createNodeSettingsStore(context, namespace, "global"),
project: createNodeSettingsStore(context, namespace, "project")
};
}
//#endregion
//#region src/node/scope.ts
function prefixDefinition(namespace, fn) {
if (isQualifiedName(fn.name)) throw diagnostics$1.DF0034({
namespace,
name: fn.name
});
return {
...fn,
name: `${namespace}:${fn.name}`
};
}
/**
* Build a namespace-scoped view of a {@link DevframeNodeContext}. Every
* RPC id, shared-state key, and streaming channel passed through the
* returned `rpc` surface is auto-namespaced with `<namespace>:`.
*/
function createScopedNodeContext(context, namespace) {
const base = context.rpc;
const rpc = {
namespace,
register(fn, force) {
base.register(prefixDefinition(namespace, fn), force);
},
update(fn, force) {
base.update(prefixDefinition(namespace, fn), force);
},
call: ((method, ...args) => base.invokeLocal(qualifyName(namespace, method), ...args)),
broadcast: ((options) => base.broadcast({
...options,
method: qualifyName(namespace, options.method)
})),
sharedState: ((key, options) => base.sharedState.get(qualifyName(namespace, key), options)),
streaming: { create: (name, opts) => base.streaming.create(qualifyName(namespace, name), opts) },
getCurrentRpcSession: () => base.getCurrentRpcSession()
};
return {
namespace,
base: context,
cwd: context.cwd,
workspaceRoot: context.workspaceRoot,
mode: context.mode,
host: context.host,
rpc,
settings: createNodeSettings(context, namespace),
views: context.views,
diagnostics: context.diagnostics,
agent: context.agent,
scope: context.scope
};
}
//#endregion
//#region src/node/context.ts
/**
* Framework- and build-tool-agnostic core of the Devframe node context.
* Wires the RPC host, view (HTTP file-serving) host, diagnostics, and
* agent subsystems. Host adapters can wrap this to augment `ctx` with
* extra surfaces — for example, `@vitejs/devtools-kit`'s
* `createKitContext` attaches `docks`, `terminals`, `messages`, and
* `commands` when mounted into Vite DevTools.
*/
async function createHostContext(options) {
const { cwd, workspaceRoot = cwd, mode, host, builtinRpcDeclarations = [] } = options;
const context = {
cwd,
workspaceRoot,
mode,
host,
rpc: void 0,
views: void 0,
diagnostics: void 0,
agent: void 0,
services: void 0,
scope: void 0
};
const rpcHost = new RpcFunctionsHostImpl(context);
const viewsHost = new DevframeViewHost(context);
const diagnosticsHost = new DevframeDiagnosticsHost(context, [diagnostics$1, diagnostics]);
context.rpc = rpcHost;
context.views = viewsHost;
context.diagnostics = diagnosticsHost;
context.services = new DevframeServicesHostImpl();
context.agent = new DevframeAgentHost(context);
const scopedCache = /* @__PURE__ */ new Map();
context.scope = ((namespace) => {
if (!namespace) return context;
let scoped = scopedCache.get(namespace);
if (!scoped) {
scoped = createScopedNodeContext(context, namespace);
scopedCache.set(namespace, scoped);
}
return scoped;
});
for (const fn of BUILTIN_AGENT_RPC) rpcHost.register(fn);
for (const fn of builtinRpcDeclarations) rpcHost.register(fn);
return context;
}
//#endregion
//#region src/node/host-h3.ts
/**
* h3-backed {@link DevframeHost} — used by the standalone CLI adapter.
*/
function createH3DevframeHost(options) {
const workspaceRoot = options.workspaceRoot ?? process$1.cwd();
return {
mountStatic(base, distDir) {
return options.mount?.(base, distDir);
},
resolveOrigin() {
return options.origin;
},
getStorageDir(scope) {
const namespace = `.${options.appName}/devframe`;
if (scope === "workspace") return join(workspaceRoot, ".devframe");
if (scope === "project") return join(workspaceRoot, "node_modules", namespace);
return join(homedir(), namespace);
}
};
}
//#endregion
export { DevframeViewHost as a, createRpcSharedStateServerHost as c, coerceAgentPositionalArgs as d, createNodeSettings as i, DevframeDiagnosticsHost as l, createHostContext as n, DevframeServicesHostImpl as o, createScopedNodeContext as r, createRpcStreamingServerHost as s, createH3DevframeHost as t, DevframeAgentHost as u };
import { t as createMcpFetchHandler } from "./fetch-BZyK4v6W.mjs";
import { defineHandler } from "h3";
//#region src/adapters/mcp/http.ts
/**
* Mount an MCP Streamable-HTTP endpoint on an h3 app at `path` — the h3
* binding over {@link createMcpFetchHandler}, which owns the sessions, the
* origin gate, and the transport plumbing.
*
* The handler is web-standard — it takes the h3 event's web `Request` and
* returns a web `Response` (an SSE `ReadableStream` body for the
* server→client stream). We copy that response onto `event.res` and return
* its body rather than returning the `Response` object directly, so a
* legitimate MCP 404 (unknown session) isn't swallowed by h3's
* "Response-with-404 falls through to the next handler" rule (which would
* otherwise hand the request to the SPA static catch-all).
*
* @experimental
*/
function mountMcpHttp(app, ctx, path, options) {
const handler = createMcpFetchHandler(ctx, options);
app.use(path, defineHandler(async (event) => respond(event, await handler.fetch(event.req))));
return { dispose: handler.dispose };
}
/**
* Copy a web `Response` from the MCP transport onto the h3 event's response
* and return its body. Returning the body (a `ReadableStream` or `null`)
* rather than the `Response` object avoids h3's 404-fall-through behavior.
*/
function respond(event, response) {
event.res.status = response.status;
event.res.statusText = response.statusText;
response.headers.forEach((value, key) => {
event.res.headers.set(key, value);
});
return response.body ?? "";
}
//#endregion
export { mountMcpHttp };
import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-Dsjn_Xtq.mjs";
import { n as InternalAnonymousAuthStorage } from "./context-7BbaIUSI.mjs";
//#region src/node/auth/revoke.d.ts
/**
* Flip `isTrusted` to false on any live WS clients connected with `token`
* and broadcast the `auth:revoked` event so they can react.
*
* Shared between persisted-auth revocation and remote-dock token revocation.
*/
declare function revokeActiveConnectionsForToken(context: DevframeNodeContext, token: string): Promise<void>;
/**
* Revoke an auth token: remove from storage and notify all connected clients
* using this token that they are no longer trusted.
*/
declare function revokeAuthToken(context: DevframeNodeContext, storage: SharedState<InternalAnonymousAuthStorage>, token: string): Promise<void>;
//#endregion
//#region src/node/auth/state.d.ts
/**
* The current one-time authentication code. Display this to the user (e.g. in
* the dev-server terminal) so they can type it into the browser to authenticate.
*/
declare function getTempAuthCode(): string;
/**
* Rotate the authentication code, resetting its expiry window and failed-attempt
* counter. Call this when a new authentication flow begins (e.g. when an
* untrusted client starts authenticating) so the displayed code is freshly
* valid for its full TTL.
*/
declare function refreshTempAuthCode(): string;
/**
* Build a "magic link" authentication URL that embeds a one-time code (OTP) as
* a query parameter. Opening it authenticates the client without typing — print
* it on startup (devframe stays headless, so the host prints its own banner).
* Defaults to the current code; the link is subject to the same TTL.
*/
declare function buildOtpAuthUrl(baseUrl: string, code?: string): string;
/**
* Re-authenticate a connection that presents a previously-issued bearer token.
* Returns `true` and marks the session trusted when the token is known.
*
* Used by the `anonymous:devframe:auth` handler so a client that already
* authenticated (token persisted in the browser) is trusted on reconnect
* without entering the code again.
*/
declare function verifyAuthToken(token: string, session: DevframeNodeRpcSession, storage: SharedState<InternalAnonymousAuthStorage>): boolean;
/**
* Exchange a one-time authentication code for a fresh, node-issued bearer token.
*
* On success this mints a high-entropy token, records it in the trusted store,
* marks the calling session trusted, rotates the code, and returns the token
* for the client to persist. Returns `null` on any failure.
*
* Because the code is short and human-typed, verification is hardened against
* brute force: it enforces a time-to-live, compares in constant time, and
* rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an
* attacker cannot keep guessing against the same code.
*/
declare function exchangeTempAuthCode(code: string, session: DevframeNodeRpcSession, info: {
ua: string;
origin: string;
}, storage: SharedState<InternalAnonymousAuthStorage>): string | null;
//#endregion
export { verifyAuthToken as a, refreshTempAuthCode as i, exchangeTempAuthCode as n, revokeActiveConnectionsForToken as o, getTempAuthCode as r, revokeAuthToken as s, buildOtpAuthUrl as t };
import { C as RpcFunctionType, S as RpcFunctionSetupResult, T as RpcReturnSchema, _ as RpcFunctionDefinition, i as RpcArgsSchema, v as RpcFunctionDefinitionAny, w as RpcFunctionsCollector } from "./types-CnJSgRVa.mjs";
import { a as getDefinitionsWithDumps$1, c as StaticRpcDumpManifest$1, d as StaticRpcDumpManifestValue$1, f as StaticRpcDumpSerialization$1, i as dumpFunctions$1, l as StaticRpcDumpManifestQueryEntry$1, n as serializeDumpError$1, o as StaticRpcDumpCollection$1, p as collectStaticRpcDump$1, r as createClientFromDump$1, s as StaticRpcDumpFile$1, t as reviveDumpError$1, u as StaticRpcDumpManifestStaticEntry$1 } from "./index-vAX6bYuc.mjs";
//#region src/rpc/cache.d.ts
interface RpcCacheOptions {
functions: string[];
keySerializer?: (args: unknown[]) => string;
}
/**
* @experimental API is expected to change.
*/
declare class RpcCacheManager {
private cacheMap;
private options;
private keySerializer;
constructor(options: RpcCacheOptions);
updateOptions(options: Partial<RpcCacheOptions>): void;
cached<T>(m: string, a: unknown[]): T | undefined;
has(m: string, a: unknown[]): boolean;
apply(req: {
m: string;
a: unknown[];
}, res: unknown): void;
validate(m: string): boolean;
clear(fn?: string): void;
}
//#endregion
//#region src/rpc/collector.d.ts
declare class RpcFunctionsCollectorBase<LocalFunctions extends Record<string, any>, SetupContext> implements RpcFunctionsCollector<LocalFunctions, SetupContext> {
readonly context: SetupContext;
readonly definitions: Map<string, RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>>;
readonly functions: LocalFunctions;
private readonly _onChanged;
constructor(context: SetupContext);
register(fn: RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>, force?: boolean): void;
update(fn: RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>, force?: boolean): void;
onChanged(fn: (id?: string) => void): () => void;
getHandler<T extends keyof LocalFunctions>(name: T): Promise<LocalFunctions[T]>;
getSchema<T extends keyof LocalFunctions>(name: T): {
args: RpcArgsSchema | undefined;
returns: RpcReturnSchema | undefined;
};
has(name: string): boolean;
get(name: string): RpcFunctionDefinition<string, any, any, any, any, any, SetupContext> | undefined;
list(): string[];
}
//#endregion
//#region src/rpc/define.d.ts
declare function defineRpcFunction<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends RpcArgsSchema | undefined = undefined, const RS extends RpcReturnSchema | undefined = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS>): RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS>;
declare function createDefineWrapperWithContext<CONTEXT>(): <NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends RpcArgsSchema | undefined = undefined, const RS extends RpcReturnSchema | undefined = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, CONTEXT>) => RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, CONTEXT>;
//#endregion
//#region src/rpc/handler.d.ts
declare function getRpcResolvedSetupResult<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, CONTEXT = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, any, any, CONTEXT>, context: CONTEXT): Promise<RpcFunctionSetupResult<ARGS, RETURN>>;
declare function getRpcHandler<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, CONTEXT = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, any, any, CONTEXT>, context: CONTEXT): Promise<(...args: ARGS) => RETURN>;
//#endregion
//#region src/rpc/serialization.d.ts
/**
* Wire format used by the WS RPC transport.
*
* - **JSON (default, unprefixed):** payload is plain JSON text. Used when
* the dispatched method is declared `jsonSerializable: true`. Encoded
* via {@link strictJsonStringify} (rejects non-JSON values), decoded
* via `JSON.parse`.
* - **Structured-clone (`s:` prefix):** payload is `s:` followed by
* `structured-clone-es` text. Used when the method is declared
* `jsonSerializable: false` (or omitted, the default). Round-trips
* `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
*
* birpc envelopes always start with `{`, so a leading byte that is not
* `s` is unambiguously JSON. Each direction independently chooses its
* encoding from local definitions — request and response are not
* coupled by a mirror rule.
*/
declare const STRUCTURED_CLONE_PREFIX = "s:";
/**
* `JSON.stringify` with a single-pass strict replacer.
*
* Throws `DF0020` synchronously when the value contains a type JSON
* cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
* instances, or `undefined` inside an array (silently becomes `null`).
*
* Native pass-throughs (no extra work needed):
* - circular references — `JSON.stringify` raises `TypeError`.
* - `BigInt` at top level — caught here for a friendlier error path.
*
* Lenient cases (allowed without throwing):
* - `undefined` as an object property — legitimate optional field;
* JSON.stringify just omits it.
* - `undefined` at the root — legitimate "action returned nothing".
* - `Symbol` / `Function` values — semantically "drop me" in JSON.
*
* `fnName` is used only for the diagnostic message — pass the RPC
* function name when calling from a wire serializer / dump writer so
* the error points at the offending function.
*/
declare function strictJsonStringify(value: unknown, fnName?: string): string;
//#endregion
//#region src/rpc/validate-io.d.ts
/**
* Validate positional arguments against their declared schemas. Only
* indices with a schema are checked; extra arguments pass through
* untouched. Throws `DF0038` on the first failing argument.
*
* Validation guards the payload without rewriting it: the original values
* are handed to the handler unchanged, so a schema that describes a subset
* of an object never silently strips the sender's extra fields (and any
* declared transforms stay a purely type-level concern).
*
* @internal
*/
declare function validateRpcArgs(name: string, argsSchema: RpcArgsSchema | undefined, args: readonly unknown[]): Promise<unknown[]>;
/**
* Validate a handler's resolved return value against its declared schema.
* Throws `DF0039` when the value fails the schema, otherwise returns the
* original value unchanged (guard-only, never rewriting the payload — see
* {@link validateRpcArgs}). Passes through when no return schema is set.
*
* @internal
*/
declare function validateRpcReturn(name: string, returnSchema: RpcReturnSchema | undefined, value: unknown): Promise<unknown>;
//#endregion
//#region src/rpc/validation.d.ts
/**
* Validates RPC function definitions.
* Action and event functions cannot have dumps (side effects should not be cached).
*
* @throws {Error} If an action or event function has a dump configuration
*/
declare function validateDefinitions(definitions: readonly RpcFunctionDefinitionAny[]): void;
/**
* Validates a single RPC function definition.
*
* @throws {Error} If an action or event function has a dump configuration
*/
declare function validateDefinition(definition: RpcFunctionDefinitionAny): void;
//#endregion
//#region src/rpc/index.d.ts
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const collectStaticRpcDump: typeof collectStaticRpcDump$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const createClientFromDump: typeof createClientFromDump$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const dumpFunctions: typeof dumpFunctions$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const getDefinitionsWithDumps: typeof getDefinitionsWithDumps$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const reviveDumpError: typeof reviveDumpError$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const serializeDumpError: typeof serializeDumpError$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpCollection = StaticRpcDumpCollection$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpFile = StaticRpcDumpFile$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifest = StaticRpcDumpManifest$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestQueryEntry = StaticRpcDumpManifestQueryEntry$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestStaticEntry = StaticRpcDumpManifestStaticEntry$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestValue = StaticRpcDumpManifestValue$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpSerialization = StaticRpcDumpSerialization$1;
//#endregion
export { defineRpcFunction as C, RpcCacheOptions as E, createDefineWrapperWithContext as S, RpcCacheManager as T, validateRpcReturn as _, StaticRpcDumpManifestStaticEntry as a, getRpcHandler as b, collectStaticRpcDump as c, getDefinitionsWithDumps as d, reviveDumpError as f, validateRpcArgs as g, validateDefinitions as h, StaticRpcDumpManifestQueryEntry as i, createClientFromDump as l, validateDefinition as m, StaticRpcDumpFile as n, StaticRpcDumpManifestValue as o, serializeDumpError as p, StaticRpcDumpManifest as r, StaticRpcDumpSerialization as s, StaticRpcDumpCollection as t, dumpFunctions as u, STRUCTURED_CLONE_PREFIX as v, RpcFunctionsCollectorBase as w, getRpcResolvedSetupResult as x, strictJsonStringify as y };
import { h as RpcDumpStore, l as RpcDumpClientOptions, m as RpcDumpRecordError, n as BirpcReturn, o as RpcDefinitionsToFunctions, u as RpcDumpCollectionOptions, v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
//#region src/rpc/dump/static.d.ts
type StaticRpcDumpSerialization = 'json' | 'structured-clone';
interface StaticRpcDumpManifestStaticEntry {
type: 'static';
path: string;
/** Encoder used when this entry's file was written. Default: `'json'`. */
serialization?: StaticRpcDumpSerialization;
}
interface StaticRpcDumpManifestQueryEntry {
type: 'query';
records: Record<string, string>;
fallback?: string;
/** Encoder used when each record/fallback file was written. Default: `'json'`. */
serialization?: StaticRpcDumpSerialization;
}
type StaticRpcDumpManifestValue = StaticRpcDumpManifestStaticEntry | StaticRpcDumpManifestQueryEntry | any;
type StaticRpcDumpManifest = Record<string, StaticRpcDumpManifestValue>;
interface StaticRpcDumpFile {
/** Whether this file was written via `JSON.stringify` or `structured-clone-es.stringify`. */
serialization: StaticRpcDumpSerialization;
/** Function name the file belongs to — used to scope `DF0019` errors during write. */
fnName: string;
/** Payload to encode. */
data: unknown;
}
interface StaticRpcDumpCollection {
manifest: StaticRpcDumpManifest;
files: Record<string, StaticRpcDumpFile>;
}
declare function collectStaticRpcDump(definitions: Iterable<RpcFunctionDefinitionAny>, context: any): Promise<StaticRpcDumpCollection>;
//#endregion
//#region src/rpc/dump/collect.d.ts
/**
* Collects pre-computed dumps by executing functions with their defined input combinations.
* Static functions without dump config automatically get `{ inputs: [[]] }`.
*
* @example
* ```ts
* const store = await dumpFunctions([greet], context, { concurrency: 10 })
* ```
*/
declare function dumpFunctions<T extends readonly RpcFunctionDefinitionAny[]>(definitions: T, context?: any, options?: RpcDumpCollectionOptions): Promise<RpcDumpStore<RpcDefinitionsToFunctions<T>>>;
/**
* Creates a client that serves pre-computed results from a dump store.
* Uses argument hashing to match calls to stored records.
*
* @example
* ```ts
* const client = createClientFromDump(store)
* await client.greet('Alice')
* ```
*/
declare function createClientFromDump<T extends Record<string, any>>(store: RpcDumpStore<T>, options?: RpcDumpClientOptions): BirpcReturn<T>;
/**
* Filters function definitions to only those with dump definitions.
* Note: Only checks the definition itself, not setup results.
*/
declare function getDefinitionsWithDumps<T extends readonly RpcFunctionDefinitionAny[]>(definitions: T): RpcFunctionDefinitionAny[];
//#endregion
//#region src/rpc/dump/error.d.ts
/**
* Normalize a thrown value into a plain object suitable for storage in
* a dump record. Preserves `message`, `name`, `cause`, and any own
* enumerable properties of an `Error` so consumers reading the dump can
* reconstruct a richer Error than just `{ message, name }`.
*
* Non-`Error` throws are wrapped as `{ name: 'Error', message: String(thrown) }`.
*/
declare function serializeDumpError(error: unknown): RpcDumpRecordError;
/**
* Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
* the plain object stored in a dump record. Preserves `cause`, restores
* the original `name`, and re-attaches any custom own properties.
*/
declare function reviveDumpError(stored: RpcDumpRecordError): Error;
//#endregion
export { getDefinitionsWithDumps as a, StaticRpcDumpManifest as c, StaticRpcDumpManifestValue as d, StaticRpcDumpSerialization as f, dumpFunctions as i, StaticRpcDumpManifestQueryEntry as l, serializeDumpError as n, StaticRpcDumpCollection as o, collectStaticRpcDump as p, createClientFromDump as r, StaticRpcDumpFile as s, reviveDumpError as t, StaticRpcDumpManifestStaticEntry as u };
import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { join } from "pathe";
import process from "node:process";
import { homedir } from "node:os";
//#region src/node/instance-registry.ts
/** Environment variable overriding the registry directory (tests, CI). */
const DEVFRAME_INSTANCES_DIR_ENV = "DEVFRAME_INSTANCES_DIR";
/** Environment variable disabling instance registration entirely. */
const DEVFRAME_DISABLE_INSTANCE_REGISTRY_ENV = "DEVFRAME_DISABLE_INSTANCE_REGISTRY";
/**
* Resolve the registry directory: `~/.devframe/instances/` by default —
* the framework's own global dir, deliberately outside the per-app
* `~/.<appName>/devframe/` storage convention since the registry spans apps —
* overridable via `DEVFRAME_INSTANCES_DIR`.
*/
function resolveInstancesDir(override) {
return override ?? process.env[DEVFRAME_INSTANCES_DIR_ENV] ?? join(homedir(), ".devframe", "instances");
}
function isRegistryDisabled() {
const value = process.env[DEVFRAME_DISABLE_INSTANCE_REGISTRY_ENV];
return value === "1" || value === "true";
}
/**
* Record a running devframe instance in the global instance registry so
* discovery tooling (`devframe connect`, editor integrations) can find it
* without port guessing.
*
* `createDevServer` registers automatically; custom hosts that serve a
* devframe in-process (e.g. `@devframes/next`'s host inside a Next dev
* server) call this explicitly with the origin they are reachable at.
*
* The record is written atomically to `<dir>/<pid>-<port>.json` and removed
* by {@link DevframeInstanceRegistration.unregister}. Records surviving a
* crash are pruned by readers whose liveness probe fails. Registration never
* throws — a write failure degrades to a coded warning (`DF0045`), since a
* dev server must not die over discovery metadata.
*
* @experimental
*/
function registerDevframeInstance(record, options = {}) {
const dir = resolveInstancesDir(options.instancesDir);
const file = join(dir, `${record.pid}-${record.port}.json`);
if (!isRegistryDisabled()) try {
mkdirSync(dir, { recursive: true });
const tmp = join(dir, `.${record.pid}-${record.port}.${Date.now()}.tmp`);
writeFileSync(tmp, `${JSON.stringify(record, null, 2)}\n`);
renameSync(tmp, file);
} catch (error) {
diagnostics.DF0045({
file,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
return {
file,
unregister: () => {
try {
rmSync(file, { force: true });
} catch (error) {
diagnostics.DF0045({
file,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
}
};
}
/**
* Read every record in the registry directory, dropping unparseable files.
* Liveness is the caller's concern — see {@link probeDevframeInstance}.
*
* @experimental
*/
function readDevframeInstances(options = {}) {
const dir = resolveInstancesDir(options.instancesDir);
let files;
try {
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
} catch {
return [];
}
const records = [];
for (const file of files) try {
const parsed = JSON.parse(readFileSync(join(dir, file), "utf8"));
if (typeof parsed?.origin === "string" && typeof parsed?.pid === "number") records.push(parsed);
} catch {}
return records;
}
/**
* Dialable-origin candidates for a recorded origin. A `localhost` bind is
* ambiguous — the server may listen on `127.0.0.1`, `::1`, or both, and
* HTTP clients differ in which family they try — so probe the explicit
* addresses too and adopt whichever answers.
*/
function originCandidates(origin) {
try {
const url = new URL(origin);
if (url.hostname !== "localhost") return [origin];
const port = url.port ? `:${url.port}` : "";
return [
origin,
`${url.protocol}//127.0.0.1${port}`,
`${url.protocol}//[::1]${port}`
];
} catch {
return [origin];
}
}
/**
* Probe `<origin><basePath>__connection.json`, trying each dialable
* candidate for the origin (see {@link originCandidates}). The single
* probe primitive behind both registry liveness checks and the
* connector's explicit `--port` probes.
*
* @internal
*/
async function probeDevframeOrigin(origin, basePath, timeoutMs) {
const base = basePath.endsWith("/") ? basePath : `${basePath}/`;
for (const candidate of originCandidates(origin)) try {
const response = await fetch(`${candidate}${base}__connection.json`, { signal: AbortSignal.timeout(timeoutMs ?? 1e3) });
if (!response.ok) continue;
return {
origin: candidate,
meta: await response.json().catch(() => ({}))
};
} catch {}
return null;
}
/**
* Probe a record's `__connection.json` to check the instance is alive.
* Returns the **dialable origin** that answered (for `localhost` records
* this may be an explicit `127.0.0.1` / `[::1]` origin), or `null` when
* unreachable.
*/
async function probeDevframeInstance(record, options = {}) {
return (await probeDevframeOrigin(record.origin, record.basePath, options.timeoutMs))?.origin ?? null;
}
/**
* Read the registry and split records into live and dead by probing each
* one's `__connection.json`, deleting dead records (prune-on-read). Live
* records carry the dialable origin the probe confirmed (a `localhost`
* record may come back as `127.0.0.1` / `[::1]`).
*
* A liveness probe only proves *something* answers on the record's port, so
* records left behind by killed processes shadow the server currently bound
* there: per `(port, basePath)` only the newest record survives, older
* ghosts are pruned with the dead.
*
* @experimental
*/
async function listLiveDevframeInstances(options = {}) {
const dir = resolveInstancesDir(options.instancesDir);
const records = readDevframeInstances({ instancesDir: dir });
const pruned = [];
const prune = (record) => {
pruned.push(record);
try {
rmSync(join(dir, `${record.pid}-${record.port}.json`), { force: true });
} catch {}
};
const newest = /* @__PURE__ */ new Map();
for (const record of records) {
const key = `${record.port}|${record.basePath}`;
const existing = newest.get(key);
if (!existing) newest.set(key, record);
else if (record.startedAt > existing.startedAt) {
prune(existing);
newest.set(key, record);
} else prune(record);
}
const live = [];
await Promise.all([...newest.values()].map(async (record) => {
const origin = await probeDevframeInstance(record, options);
if (origin) live.push(origin === record.origin ? record : {
...record,
origin
});
else prune(record);
}));
live.sort((a, b) => a.startedAt - b.startedAt);
return {
live,
pruned
};
}
//#endregion
export { probeDevframeOrigin as n, registerDevframeInstance as r, listLiveDevframeInstances as t };
import { t as diagnostics } from "./diagnostics-hwjXp_UV.mjs";
//#region src/rpc/serialization.ts
/**
* Wire format used by the WS RPC transport.
*
* - **JSON (default, unprefixed):** payload is plain JSON text. Used when
* the dispatched method is declared `jsonSerializable: true`. Encoded
* via {@link strictJsonStringify} (rejects non-JSON values), decoded
* via `JSON.parse`.
* - **Structured-clone (`s:` prefix):** payload is `s:` followed by
* `structured-clone-es` text. Used when the method is declared
* `jsonSerializable: false` (or omitted, the default). Round-trips
* `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
*
* birpc envelopes always start with `{`, so a leading byte that is not
* `s` is unambiguously JSON. Each direction independently chooses its
* encoding from local definitions — request and response are not
* coupled by a mirror rule.
*/
const STRUCTURED_CLONE_PREFIX = "s:";
/**
* `JSON.stringify` with a single-pass strict replacer.
*
* Throws `DF0020` synchronously when the value contains a type JSON
* cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
* instances, or `undefined` inside an array (silently becomes `null`).
*
* Native pass-throughs (no extra work needed):
* - circular references — `JSON.stringify` raises `TypeError`.
* - `BigInt` at top level — caught here for a friendlier error path.
*
* Lenient cases (allowed without throwing):
* - `undefined` as an object property — legitimate optional field;
* JSON.stringify just omits it.
* - `undefined` at the root — legitimate "action returned nothing".
* - `Symbol` / `Function` values — semantically "drop me" in JSON.
*
* `fnName` is used only for the diagnostic message — pass the RPC
* function name when calling from a wire serializer / dump writer so
* the error points at the offending function.
*/
function strictJsonStringify(value, fnName = "") {
return JSON.stringify(value, function strictReplacer(key, val) {
const holder = this;
const original = holder != null ? holder[key] : val;
if (original === void 0) {
if (Array.isArray(holder)) throw nonJsonAt(fnName, "undefined", holder, key);
return val;
}
if (original === null) return val;
if (typeof original === "bigint") throw nonJsonAt(fnName, "BigInt", holder, key);
if (typeof original === "object") {
if (original instanceof Map) throw nonJsonAt(fnName, "Map", holder, key);
if (original instanceof Set) throw nonJsonAt(fnName, "Set", holder, key);
if (original instanceof Date) throw nonJsonAt(fnName, "Date", holder, key);
if (Array.isArray(original)) return val;
const proto = Object.getPrototypeOf(original);
if (proto !== null && proto !== Object.prototype) throw nonJsonAt(fnName, original.constructor?.name ?? "class instance", holder, key);
}
return val;
});
}
function nonJsonAt(fnName, type, parent, key) {
const path = formatPath(parent, key);
return diagnostics.DF0020({
name: fnName || "<anonymous>",
type,
path
});
}
function formatPath(parent, key) {
if (Array.isArray(parent)) return `[${key}]`;
if (key === "") return "<root>";
return key;
}
//#endregion
export { strictJsonStringify as n, STRUCTURED_CLONE_PREFIX as t };
import { createRpcServer } from "./rpc/server.mjs";
import { attachWsRpcTransport } from "./rpc/transports/ws-server.mjs";
import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
import { t as getInternalContext } from "./context-C9Cgm1hP.mjs";
import { createServer } from "node:http";
import { AsyncLocalStorage } from "node:async_hooks";
import { H3, toNodeHandler } from "h3";
import { isIP } from "node:net";
//#region src/node/utils.ts
function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
const NON_DIALABLE_HOSTS = /* @__PURE__ */ new Set([
"0.0.0.0",
"127.0.0.1",
"::",
"0000:0000:0000:0000:0000:0000:0000:0000",
""
]);
/** Map a bind host to a host a client can actually connect to. */
function toDialableHost(host) {
return NON_DIALABLE_HOSTS.has(host) ? "localhost" : host;
}
/** Format a bind host for use in a URL authority (dialable, IPv6-bracketed). */
function formatHostForUrl(host) {
const dialable = toDialableHost(host);
return isIP(dialable) === 6 ? `[${dialable}]` : dialable;
}
function normalizeHttpServerUrl(host, port) {
return `http://${formatHostForUrl(host)}:${port}`;
}
//#endregion
//#region src/node/server.ts
/**
* Compose an h3 + WebSocket server for a devframe context. The RPC
* group is bound to `context.rpc.functions`; the WS endpoint lives on
* the same port as the HTTP server.
*/
async function startHttpAndWs(options) {
const { context, port } = options;
const bindHost = options.host ?? "localhost";
const app = options.app ?? new H3();
const ownsHttpServer = !options.server;
const httpServer = options.server ?? createServer(toNodeHandler(app));
const rpcHost = context.rpc;
const asyncStorage = new AsyncLocalStorage();
const authHandler = typeof options.auth === "object" ? options.auth : void 0;
const effectiveAuthorize = options.authorize ?? authHandler?.authorize;
if (authHandler) {
for (const fn of authHandler.rpcFunctions) if (!rpcHost.definitions.has(fn.name)) rpcHost.register(fn);
}
const rpcGroup = createRpcServer(rpcHost.functions, { rpcOptions: {
onFunctionError: options.rpcOptions?.onFunctionError,
onGeneralError: options.rpcOptions?.onGeneralError,
resolver(name, fn) {
const rpc = this;
if (!fn) return void 0;
return async function(...args) {
const meta = rpc.$meta;
if (effectiveAuthorize && !effectiveAuthorize(name, {
meta,
rpc
})) throw diagnostics.DF0036({ name });
return await asyncStorage.run({
rpc,
meta
}, async () => {
return (await fn).apply(this, args);
});
};
}
} });
const separateWsPort = ownsHttpServer && options.wsPort != null && options.wsPort !== port ? options.wsPort : void 0;
const { ws, close: closeWs } = attachWsRpcTransport(rpcGroup, {
...separateWsPort != null ? {
port: separateWsPort,
host: bindHost
} : { server: httpServer },
path: options.path,
destroyUnmatched: ownsHttpServer,
allowedOrigins: options.allowedOrigins,
onConnected: authHandler || options.onPeerConnect ? (peer, meta) => {
const session = {
meta,
rpc: rpcGroup.clients.find((client) => client.$meta === meta)
};
authHandler?.onConnect(peer, session);
options.onPeerConnect?.(peer, session);
} : void 0,
onDisconnected: (_peer, meta) => {
rpcHost._emitSessionDisconnected(meta);
}
});
rpcHost._rpcGroup = rpcGroup;
rpcHost._asyncStorage = asyncStorage;
rpcHost._authDisabled = options.auth === false;
if (options.auth === false && !rpcHost.definitions.has("anonymous:devframe:auth")) rpcHost.register({
name: "anonymous:devframe:auth",
type: "action",
handler: () => {
const session = rpcHost.getCurrentRpcSession();
if (session) session.meta.isTrusted = true;
return { isTrusted: true };
}
});
if (ownsHttpServer) await new Promise((resolveListen) => {
httpServer.listen(port, bindHost, () => resolveListen());
});
const address = httpServer.address();
const resolvedPort = typeof address === "object" && address ? address.port : port;
const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
const internal = getInternalContext(context);
const wsPortForUrl = separateWsPort ?? resolvedPort;
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${wsPortForUrl}${options.path ?? ""}`;
internal.wsEndpoint = { url: wsUrl };
if (options.onReady) await options.onReady({
origin,
port: resolvedPort,
app
});
function connectionMeta() {
const jsonSerializableMethods = [];
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
return {
backend: "websocket",
websocket: separateWsPort != null ? {
port: separateWsPort,
path: options.path
} : { path: options.path },
jsonSerializableMethods
};
}
return {
origin,
port: resolvedPort,
app,
ws,
rpcGroup,
connectionMeta,
async close() {
await closeWs();
if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
if (getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).wsEndpoint = void 0;
}
};
}
//#endregion
export { toDialableHost as a, normalizeHttpServerUrl as i, formatHostForUrl as n, isObject as r, startHttpAndWs as t };
import { $ as DevframeRpcServerFunctions, Q as DevframeRpcClientFunctions, W as DevframeNodeRpcSession, _ as ConnectionMeta, b as DevframeNodeContext, p as DevframeAuthHandler } from "./devframe-Dsjn_Xtq.mjs";
import "./index-CeBbry0R.mjs";
import { Peer } from "crossws";
import { BirpcGroup, EventOptions } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Server } from "node:http";
import { H3 } from "h3";
//#region src/node/server.d.ts
interface StartHttpAndWsOptions {
context: DevframeNodeContext;
host?: string;
port: number;
/**
* Optional h3 app to mount on. When omitted a fresh one is created;
* when provided, callers can add their own routes (static handlers,
* auth middleware, etc.) first.
*/
app?: H3;
/**
* Bind the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`) instead of
* claiming every upgrade on the port. This lets the socket share a server
* with other upgrade handlers (Vite HMR, a host framework's own sockets)
* and is what the SPA's `__connection.json` points at. When omitted, the WS
* server handles every upgrade on the port (legacy behaviour).
*/
path?: string;
/**
* Bind the WS endpoint on its own port instead of sharing the HTTP server's.
* The HTTP/SPA server still listens on `port`; the socket gets a dedicated
* `ws` server on `wsPort` (same `host`). Use this for the "different port"
* connection scenario. Ignored when a `server` is supplied.
*/
wsPort?: number;
/**
* Mount the WS endpoint onto an existing HTTP server, sharing its port,
* rather than creating and listening on a fresh one. Use this to embed
* devframe's RPC socket inside a host server (e.g. a Vite dev server) — pair
* it with `path` so it coexists with the host's routes. The caller owns the
* server's lifecycle: {@link StartedServer.close} detaches devframe's upgrade
* listener but leaves the host server running. When set, `host`/`port` are
* only used to report the resolved origin.
*/
server?: Server;
/**
* Authentication for the server:
*
* - `true` (default) — no gate; every registered method is callable
* regardless of trust (today's behavior, unchanged).
* - `false` — the RPC server is started without a trust handshake.
* Intended for single-user localhost tools where an auth round-trip
* would only get in the way. A noop `anonymous:devframe:auth` handler
* is registered so the browser client's unconditional handshake call
* succeeds and auto-trusts.
* - A {@link DevframeAuthHandler} (e.g. from
* `devframe/recipes/interactive-auth`'s `createInteractiveAuth`) —
* registers its `rpcFunctions`, wires its `authorize` as the resolver
* gate, and wires its `onConnect` on every new peer. This is the
* fully-authenticated server: an untrusted caller can only reach
* `anonymous:`-prefixed methods (see `isAnonymousRpcMethod`).
*/
auth?: boolean | DevframeAuthHandler;
/**
* Lower-level escape hatch: gate individual RPC calls by method name and
* session without a full {@link DevframeAuthHandler}. Ignored when `auth`
* is a handler object (its own `authorize` is used); combine with `auth:
* true` to layer a custom policy on top of an otherwise ungated server.
*/
authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean;
/**
* Called once per new WS connection, right after its session is created
* (before any RPC call is dispatched). Runs after the auth handler's own
* `onConnect` (when `auth` is a {@link DevframeAuthHandler}), so it can
* observe — but not override — the connect-time trust decision.
*/
onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void;
/**
* Forwarded verbatim to the internal `createRpcServer`'s birpc
* `rpcOptions`, alongside the resolver `startHttpAndWs` installs for
* auth/session wiring. Use this so a host that owns its own structured
* diagnostics (e.g. a coded error reporter) keeps seeing RPC failures
* instead of them being silently absorbed by delegating to
* `startHttpAndWs`. Returning `true` from either callback suppresses
* birpc's own error response to the caller — see birpc's
* `EventOptions` for the full contract.
*/
rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>;
/**
* Extra origins to accept on the WS upgrade beyond the loopback default
* (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less request from a
* native client). Add your LAN/tunnel origin here when reaching the tool
* from another host. Pass `false` to disable origin checking entirely
* (not recommended). Default: loopback-only.
*/
allowedOrigins?: readonly string[] | false;
/**
* Called once the WS server is bound so callers can mount static
* handlers whose origin depends on the resolved port, or print their
* own startup banner. Devframe does not print one itself.
*/
onReady?: (info: {
origin: string;
port: number;
app: H3;
}) => void | Promise<void>;
}
interface StartedServer {
/** Listening origin, e.g. `http://localhost:9999`. */
origin: string;
port: number;
app: H3;
/** The crossws node adapter driving the RPC socket (connected peers, pub/sub). */
ws: NodeAdapter;
rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
/**
* The {@link ConnectionMeta} descriptor for this server — the same shape
* a `__connection.json` route should serve so a devframe client's
* `resolveWsUrl` can dial back in. Reflects the `path` / `wsPort` this
* server was started with and the `jsonSerializable` methods currently
* registered on `context.rpc`.
*/
connectionMeta: () => ConnectionMeta;
close: () => Promise<void>;
}
/**
* Compose an h3 + WebSocket server for a devframe context. The RPC
* group is bound to `context.rpc.functions`; the WS endpoint lives on
* the same port as the HTTP server.
*/
declare function startHttpAndWs(options: StartHttpAndWsOptions): Promise<StartedServer>;
//#endregion
export { StartedServer as n, startHttpAndWs as r, StartHttpAndWsOptions as t };
import { StandardSchemaV1 } from "@standard-schema/spec";
//#region src/utils/simple-schema.d.ts
/**
* A tiny, zero-dependency [Standard Schema](https://standardschema.dev/)
* builder.
*
* ⚠️ **Discouraged for app code.** This is a deliberately minimal,
* best-effort validator that exists only so devframe's own first-party
* packages (recipes, built-in plugins) can declare `args`/`returns`/flag
* schemas without taking on a validator dependency. It implements a small
* subset of primitives and approximates refinements — it is not a
* general-purpose validator.
*
* For your own code, prefer a real Standard Schema validator — **valibot**,
* **zod**, or **arktype**. Devframe's RPC and CLI-flag layers accept any of
* them; install the one you like and use it directly:
*
* ```ts
* import * as v from 'valibot' // npm i valibot
*
* defineRpcFunction({
* name: 'greet',
* args: [v.object({ name: v.string() })],
* returns: v.string(),
* handler: ({ name }) => `hi ${name}`,
* })
* ```
*/
/**
* A Standard Schema produced by the {@link s} builder. It carries a
* duck-typed `type` marker (and `wrapped` for wrappers) alongside the
* standard `~standard` prop so the CLI-flags adapter can introspect the
* schema kind without importing any validator.
*/
interface SimpleSchema<Input, Output = Input> extends StandardSchemaV1<Input, Output> {
/** Schema kind marker, e.g. `'string'`, `'boolean'`, `'optional'`. */
readonly type: string;
/** Inner schema for wrapper kinds (`optional` / `nullable`). */
readonly wrapped?: StandardSchemaV1;
/** Optional human description (surfaced as CLI option help). */
readonly description?: string;
}
/** Any string. */
declare function string(): SimpleSchema<string>;
/** A finite number (rejects `NaN`). */
declare function number(): SimpleSchema<number>;
/** A boolean. */
declare function boolean(): SimpleSchema<boolean>;
/** `undefined` — mirrors valibot's `void`. */
declare function voidType(): SimpleSchema<void>;
/** `null`. */
declare function nullType(): SimpleSchema<null>;
/** One of a fixed set of literal values. */
declare function picklist<const T extends readonly (string | number | boolean)[]>(values: T): SimpleSchema<T[number]>;
/** A single literal value (string / number / boolean). */
declare function literal<const T extends string | number | boolean>(value: T): SimpleSchema<T>;
/** A value matching any one of the given schemas. */
declare function union<const T extends readonly StandardSchemaV1[]>(options: T): SimpleSchema<StandardSchemaV1.InferInput<T[number]>, StandardSchemaV1.InferOutput<T[number]>>;
/** A record with string keys whose values each satisfy the value schema. */
declare function record<V extends StandardSchemaV1>(_key: StandardSchemaV1, value: V): SimpleSchema<Record<string, StandardSchemaV1.InferInput<V>>, Record<string, StandardSchemaV1.InferOutput<V>>>;
/** An array whose every element satisfies the item schema. */
declare function array<T extends StandardSchemaV1>(item: T): SimpleSchema<StandardSchemaV1.InferInput<T>[], StandardSchemaV1.InferOutput<T>[]>;
/** Flatten an intersection into a single object literal for readable types. */
type Prettify<T> = { [K in keyof T]: T[K]; } & {};
/**
* Map a shape to its object type, turning fields whose type includes
* `undefined` (i.e. `optional()`) into optional keys — mirroring how
* valibot/zod render `optional` object entries.
*/
type InferField<T extends StandardSchemaV1, Mode extends 'input' | 'output'> = Mode extends 'input' ? StandardSchemaV1.InferInput<T> : StandardSchemaV1.InferOutput<T>;
type InferObject<T extends Record<string, StandardSchemaV1>, Mode extends 'input' | 'output'> = Prettify<{ [K in keyof T as undefined extends InferField<T[K], Mode> ? never : K]: InferField<T[K], Mode>; } & { [K in keyof T as undefined extends InferField<T[K], Mode> ? K : never]?: InferField<T[K], Mode>; }>;
/** An object whose known keys each satisfy their schema (extra keys are kept). */
declare function object<T extends Record<string, StandardSchemaV1>>(shape: T): SimpleSchema<InferObject<T, 'input'>, InferObject<T, 'output'>>;
/** Allow `undefined` in addition to the inner schema. */
declare function optional<T extends StandardSchemaV1>(inner: T): SimpleSchema<StandardSchemaV1.InferInput<T> | undefined, StandardSchemaV1.InferOutput<T> | undefined>;
/** Allow `null` in addition to the inner schema. */
declare function nullable<T extends StandardSchemaV1>(inner: T): SimpleSchema<StandardSchemaV1.InferInput<T> | null, StandardSchemaV1.InferOutput<T> | null>;
/** Attach a human-readable description (used for CLI option help). */
declare function describe<T extends SimpleSchema<any, any>>(schema: T, description: string): T;
/**
* Grouped access to every builder — `s.string()`, `s.object({ ... })`,
* `s.void()`, etc. Handy for a valibot-like `import { s } from
* 'devframe/utils/simple-schema'` call site.
*/
declare const s: {
readonly string: typeof string;
readonly number: typeof number;
readonly boolean: typeof boolean;
readonly void: typeof voidType;
readonly null: typeof nullType;
readonly literal: typeof literal;
readonly picklist: typeof picklist;
readonly union: typeof union;
readonly record: typeof record;
readonly array: typeof array;
readonly object: typeof object;
readonly optional: typeof optional;
readonly nullable: typeof nullable;
readonly describe: typeof describe;
};
//#endregion
export { literal as a, number as c, picklist as d, record as f, voidType as g, union as h, describe as i, object as l, string as m, array as n, nullType as o, s as p, boolean as r, nullable as s, SimpleSchema as t, optional as u };
//#region src/utils/simple-schema.ts
function ok(value) {
return { value };
}
function fail(message, path) {
return { issues: [path ? {
message,
path
} : { message }] };
}
function make(type, validate, extra) {
return {
type,
...extra,
"~standard": {
version: 1,
vendor: "devframe",
validate
}
};
}
/** Run a Standard Schema synchronously, rejecting async validators. */
function runSync(schema, value) {
const result = schema["~standard"].validate(value);
if (result instanceof Promise) throw new TypeError("[devframe/utils/simple-schema] async validators are not supported inside object()/optional()/nullable()");
return result;
}
/** Any string. */
function string() {
return make("string", (v) => typeof v === "string" ? ok(v) : fail("Expected a string"));
}
/** A finite number (rejects `NaN`). */
function number() {
return make("number", (v) => typeof v === "number" && !Number.isNaN(v) ? ok(v) : fail("Expected a number"));
}
/** A boolean. */
function boolean() {
return make("boolean", (v) => typeof v === "boolean" ? ok(v) : fail("Expected a boolean"));
}
/** `undefined` — mirrors valibot's `void`. */
function voidType() {
return make("void", (v) => v === void 0 ? ok(void 0) : fail("Expected undefined"));
}
/** `null`. */
function nullType() {
return make("null", (v) => v === null ? ok(null) : fail("Expected null"));
}
/** One of a fixed set of literal values. */
function picklist(values) {
const set = new Set(values);
return make("picklist", (v) => set.has(v) ? ok(v) : fail(`Expected one of: ${values.join(", ")}`), { values });
}
/** A single literal value (string / number / boolean). */
function literal(value) {
return make("literal", (v) => v === value ? ok(v) : fail(`Expected ${JSON.stringify(value)}`), { value });
}
/** A value matching any one of the given schemas. */
function union(options) {
return make("union", (v) => {
const issues = [];
for (const option of options) {
const result = runSync(option, v);
if (!result.issues) return ok(v);
issues.push(...result.issues);
}
return { issues };
}, { options });
}
/** A record with string keys whose values each satisfy the value schema. */
function record(_key, value) {
return make("record", (v) => {
if (typeof v !== "object" || v === null || Array.isArray(v)) return fail("Expected an object");
const obj = v;
const issues = [];
for (const key of Object.keys(obj)) {
const result = runSync(value, obj[key]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [key, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** An array whose every element satisfies the item schema. */
function array(item) {
return make("array", (v) => {
if (!Array.isArray(v)) return fail("Expected an array");
const issues = [];
for (let i = 0; i < v.length; i++) {
const result = runSync(item, v[i]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [i, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** An object whose known keys each satisfy their schema (extra keys are kept). */
function object(shape) {
const entries = Object.entries(shape);
return make("object", (v) => {
if (typeof v !== "object" || v === null || Array.isArray(v)) return fail("Expected an object");
const obj = v;
const issues = [];
for (const [key, schema] of entries) {
const result = runSync(schema, obj[key]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [key, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** Allow `undefined` in addition to the inner schema. */
function optional(inner) {
return make("optional", (v) => v === void 0 ? ok(void 0) : runSync(inner, v), { wrapped: inner });
}
/** Allow `null` in addition to the inner schema. */
function nullable(inner) {
return make("nullable", (v) => v === null ? ok(null) : runSync(inner, v), { wrapped: inner });
}
/** Attach a human-readable description (used for CLI option help). */
function describe(schema, description) {
return {
...schema,
description
};
}
/**
* Grouped access to every builder — `s.string()`, `s.object({ ... })`,
* `s.void()`, etc. Handy for a valibot-like `import { s } from
* 'devframe/utils/simple-schema'` call site.
*/
const s = {
string,
number,
boolean,
void: voidType,
null: nullType,
literal,
picklist,
union,
record,
array,
object,
optional,
nullable,
describe
};
//#endregion
export { s as t };
import { t as diagnostics } from "./diagnostics-B5-qHeqD.mjs";
import fs from "node:fs";
import { dirname } from "pathe";
import process$1 from "node:process";
import { destr } from "destr";
//#region src/utils/events.ts
/**
* Create event emitter.
*/
function createEventEmitter() {
const _listeners = {};
function emit(event, ...args) {
const callbacks = _listeners[event] || [];
for (let i = 0, length = callbacks.length; i < length; i++) {
const callback = callbacks[i];
if (callback) callback(...args);
}
}
function emitOnce(event, ...args) {
emit(event, ...args);
delete _listeners[event];
}
function on(event, cb) {
(_listeners[event] ||= []).push(cb);
return () => {
_listeners[event] = _listeners[event]?.filter((i) => cb !== i);
};
}
function once(event, cb) {
const unsubscribe = on(event, ((...args) => {
unsubscribe();
return cb(...args);
}));
return unsubscribe;
}
return {
_listeners,
emit,
emitOnce,
on,
once
};
}
//#endregion
//#region ../../node_modules/.pnpm/immer@11.1.15/node_modules/immer/dist/immer.mjs
var NOTHING = Symbol.for("immer-nothing");
var DRAFTABLE = Symbol.for("immer-draftable");
var DRAFT_STATE = Symbol.for("immer-state");
var errors = process.env.NODE_ENV !== "production" ? [
function(plugin) {
return `The plugin for '${plugin}' has not been loaded into Immer. To enable the plugin, import and call \`enable${plugin}()\` when initializing your application.`;
},
function(thing) {
return `produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${thing}'`;
},
"This object has been frozen and should not be mutated",
function(data) {
return "Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " + data;
},
"An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
"Immer forbids circular references",
"The first or second argument to `produce` must be a function",
"The third argument to `produce` must be a function or undefined",
"First argument to `createDraft` must be a plain object, an array, or an immerable object",
"First argument to `finishDraft` must be a draft returned by `createDraft`",
function(thing) {
return `'current' expects a draft, got: ${thing}`;
},
"Object.defineProperty() cannot be used on an Immer draft",
"Object.setPrototypeOf() cannot be used on an Immer draft",
"Immer only supports deleting array indices",
"Immer only supports setting array indices and the 'length' property",
function(thing) {
return `'original' expects a draft, got: ${thing}`;
}
] : [];
function die(error, ...args) {
if (process.env.NODE_ENV !== "production") {
const e = errors[error];
const msg = isFunction(e) ? e.apply(null, args) : e;
throw new Error(`[Immer] ${msg}`);
}
throw new Error(`[Immer] minified error nr: ${error}. Full error at: https://bit.ly/3cXEKWf`);
}
var O = Object;
var getPrototypeOf = O.getPrototypeOf;
var CONSTRUCTOR = "constructor";
var PROTOTYPE = "prototype";
var CONFIGURABLE = "configurable";
var ENUMERABLE = "enumerable";
var WRITABLE = "writable";
var VALUE = "value";
var isDraft = (value) => !!value && !!value[DRAFT_STATE];
function isDraftable(value) {
if (!value) return false;
return isPlainObject(value) || isArray(value) || !!value[DRAFTABLE] || !!value[CONSTRUCTOR]?.[DRAFTABLE] || isMap(value) || isSet(value);
}
var objectCtorString = O[PROTOTYPE][CONSTRUCTOR].toString();
var cachedCtorStrings = /* @__PURE__ */ new WeakMap();
function isPlainObject(value) {
if (!value || !isObjectish(value)) return false;
const proto = getPrototypeOf(value);
if (proto === null || proto === O[PROTOTYPE]) return true;
const Ctor = O.hasOwnProperty.call(proto, CONSTRUCTOR) && proto[CONSTRUCTOR];
if (Ctor === Object) return true;
if (!isFunction(Ctor)) return false;
let ctorString = cachedCtorStrings.get(Ctor);
if (ctorString === void 0) {
ctorString = Function.toString.call(Ctor);
cachedCtorStrings.set(Ctor, ctorString);
}
return ctorString === objectCtorString;
}
function each(obj, iter, strict = true) {
if (getArchtype(obj) === 0) (strict ? Reflect.ownKeys(obj) : O.keys(obj)).forEach((key) => {
iter(key, obj[key], obj);
});
else obj.forEach((entry, index) => iter(index, entry, obj));
}
function getArchtype(thing) {
const state = thing[DRAFT_STATE];
return state ? state.type_ : isArray(thing) ? 1 : isMap(thing) ? 2 : isSet(thing) ? 3 : 0;
}
var has = (thing, prop, type = getArchtype(thing)) => type === 2 ? thing.has(prop) : O[PROTOTYPE].hasOwnProperty.call(thing, prop);
var get = (thing, prop, type = getArchtype(thing)) => type === 2 ? thing.get(prop) : thing[prop];
var set = (thing, propOrOldValue, value, type = getArchtype(thing)) => {
if (type === 2) thing.set(propOrOldValue, value);
else if (type === 3) thing.add(value);
else thing[propOrOldValue] = value;
};
function is(x, y) {
if (x === y) return x !== 0 || 1 / x === 1 / y;
else return x !== x && y !== y;
}
var isArray = Array.isArray;
var isMap = (target) => target instanceof Map;
var isSet = (target) => target instanceof Set;
var isObjectish = (target) => typeof target === "object";
var isFunction = (target) => typeof target === "function";
var isBoolean = (target) => typeof target === "boolean";
function isArrayIndex(value) {
const n = +value;
return Number.isInteger(n) && String(n) === value;
}
var getProxyDraft = (value) => {
if (!isObjectish(value)) return null;
return value?.[DRAFT_STATE];
};
var latest = (state) => state.copy_ || state.base_;
var getFinalValue = (state) => state.modified_ ? state.copy_ : state.base_;
function shallowCopy(base, strict) {
if (isMap(base)) return new Map(base);
if (isSet(base)) return new Set(base);
if (isArray(base)) return Array[PROTOTYPE].slice.call(base);
const isPlain = isPlainObject(base);
if (strict === true || strict === "class_only" && !isPlain) {
const descriptors = O.getOwnPropertyDescriptors(base);
delete descriptors[DRAFT_STATE];
let keys = Reflect.ownKeys(descriptors);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const desc = descriptors[key];
if (desc[WRITABLE] === false) {
desc[WRITABLE] = true;
desc[CONFIGURABLE] = true;
}
if (desc.get || desc.set) descriptors[key] = {
[CONFIGURABLE]: true,
[WRITABLE]: true,
[ENUMERABLE]: desc[ENUMERABLE],
[VALUE]: base[key]
};
}
return O.create(getPrototypeOf(base), descriptors);
} else {
const proto = getPrototypeOf(base);
if (proto !== null && isPlain) return { ...base };
const obj = O.create(proto);
return O.assign(obj, base);
}
}
function freeze(obj, deep = false) {
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj;
if (getArchtype(obj) > 1) O.defineProperties(obj, {
set: dontMutateMethodOverride,
add: dontMutateMethodOverride,
clear: dontMutateMethodOverride,
delete: dontMutateMethodOverride
});
O.freeze(obj);
if (deep) each(obj, (_key, value) => {
freeze(value, true);
}, false);
return obj;
}
function dontMutateFrozenCollections() {
die(2);
}
var dontMutateMethodOverride = { [VALUE]: dontMutateFrozenCollections };
function isFrozen(obj) {
if (obj === null || !isObjectish(obj)) return true;
return O.isFrozen(obj);
}
var PluginMapSet = "MapSet";
var PluginPatches = "Patches";
var PluginArrayMethods = "ArrayMethods";
var plugins = {};
function getPlugin(pluginKey) {
const plugin = plugins[pluginKey];
if (!plugin) die(0, pluginKey);
return plugin;
}
var isPluginLoaded = (pluginKey) => !!plugins[pluginKey];
function loadPlugin(pluginKey, implementation) {
if (!plugins[pluginKey]) plugins[pluginKey] = implementation;
}
var currentScope;
var getCurrentScope = () => currentScope;
var createScope = (parent_, immer_) => ({
drafts_: [],
parent_,
immer_,
canAutoFreeze_: true,
unfinalizedDrafts_: 0,
handledSet_: /* @__PURE__ */ new Set(),
processedForPatches_: /* @__PURE__ */ new Set(),
mapSetPlugin_: isPluginLoaded(PluginMapSet) ? getPlugin(PluginMapSet) : void 0,
arrayMethodsPlugin_: isPluginLoaded(PluginArrayMethods) ? getPlugin(PluginArrayMethods) : void 0
});
function usePatchesInScope(scope, patchListener) {
if (patchListener) {
scope.patchPlugin_ = getPlugin(PluginPatches);
scope.patches_ = [];
scope.inversePatches_ = [];
scope.patchListener_ = patchListener;
}
}
function revokeScope(scope) {
leaveScope(scope);
scope.drafts_.forEach(revokeDraft);
scope.drafts_ = null;
}
function leaveScope(scope) {
if (scope === currentScope) currentScope = scope.parent_;
}
var enterScope = (immer2) => currentScope = createScope(currentScope, immer2);
function revokeDraft(draft) {
const state = draft[DRAFT_STATE];
if (state.type_ === 0 || state.type_ === 1) state.revoke_();
else state.revoked_ = true;
}
function processResult(result, scope) {
scope.unfinalizedDrafts_ = scope.drafts_.length;
const baseDraft = scope.drafts_[0];
if (result !== void 0 && result !== baseDraft) {
if (baseDraft[DRAFT_STATE].modified_) {
revokeScope(scope);
die(4);
}
if (isDraftable(result)) result = finalize(scope, result);
const { patchPlugin_ } = scope;
if (patchPlugin_) patchPlugin_.generateReplacementPatches_(baseDraft[DRAFT_STATE].base_, result, scope);
} else result = finalize(scope, baseDraft);
maybeFreeze(scope, result, true);
revokeScope(scope);
if (scope.patches_) scope.patchListener_(scope.patches_, scope.inversePatches_);
return result !== NOTHING ? result : void 0;
}
function finalize(rootScope, value) {
if (isFrozen(value)) return value;
const state = value[DRAFT_STATE];
if (!state) return handleValue(value, rootScope.handledSet_, rootScope);
if (!isSameScope(state, rootScope)) return value;
if (!state.modified_) return state.base_;
if (!state.finalized_) {
const { callbacks_ } = state;
if (callbacks_) while (callbacks_.length > 0) callbacks_.pop()(rootScope);
generatePatchesAndFinalize(state, rootScope);
}
return state.copy_;
}
function maybeFreeze(scope, value, deep = false) {
if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) freeze(value, deep);
}
function markStateFinalized(state) {
state.finalized_ = true;
state.scope_.unfinalizedDrafts_--;
}
var isSameScope = (state, rootScope) => state.scope_ === rootScope;
var EMPTY_LOCATIONS_RESULT = [];
function updateDraftInParent(parent, draftValue, finalizedValue, originalKey) {
const parentCopy = latest(parent);
const parentType = parent.type_;
if (originalKey !== void 0) {
if (get(parentCopy, originalKey, parentType) === draftValue) {
set(parentCopy, originalKey, finalizedValue, parentType);
return;
}
}
if (!parent.draftLocations_) {
const draftLocations = parent.draftLocations_ = /* @__PURE__ */ new Map();
each(parentCopy, (key, value) => {
if (isDraft(value)) {
const keys = draftLocations.get(value) || [];
keys.push(key);
draftLocations.set(value, keys);
}
});
}
const locations = parent.draftLocations_.get(draftValue) ?? EMPTY_LOCATIONS_RESULT;
for (const location of locations) set(parentCopy, location, finalizedValue, parentType);
}
function registerChildFinalizationCallback(parent, child, key) {
parent.callbacks_.push(function childCleanup(rootScope) {
const state = child;
if (!state || !isSameScope(state, rootScope)) return;
rootScope.mapSetPlugin_?.fixSetContents(state);
const finalizedValue = getFinalValue(state);
updateDraftInParent(parent, state.draft_ ?? state, finalizedValue, key);
generatePatchesAndFinalize(state, rootScope);
});
}
function generatePatchesAndFinalize(state, rootScope) {
if (state.modified_ && !state.finalized_ && (state.type_ === 3 || state.type_ === 1 && state.allIndicesReassigned_ || (state.assigned_?.size ?? 0) > 0)) {
const { patchPlugin_ } = rootScope;
if (patchPlugin_) {
const basePath = patchPlugin_.getPath(state);
if (basePath) patchPlugin_.generatePatches_(state, basePath, rootScope);
}
markStateFinalized(state);
}
}
function handleCrossReference(target, key, value) {
const { scope_ } = target;
if (isDraft(value)) {
const state = value[DRAFT_STATE];
if (isSameScope(state, scope_)) state.callbacks_.push(function crossReferenceCleanup() {
prepareCopy(target);
updateDraftInParent(target, value, getFinalValue(state), key);
});
} else if (isDraftable(value)) target.callbacks_.push(function nestedDraftCleanup() {
const targetCopy = latest(target);
if (target.type_ === 3) {
if (targetCopy.has(value)) handleValue(value, scope_.handledSet_, scope_);
} else if (get(targetCopy, key, target.type_) === value) {
if (scope_.drafts_.length > 1 && (target.assigned_.get(key) ?? false) === true && target.copy_) handleValue(get(target.copy_, key, target.type_), scope_.handledSet_, scope_);
}
});
}
function handleValue(target, handledSet, rootScope) {
if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) return target;
if (isDraft(target) || handledSet.has(target) || !isDraftable(target) || isFrozen(target)) return target;
handledSet.add(target);
each(target, (key, value) => {
if (isDraft(value)) {
const state = value[DRAFT_STATE];
if (isSameScope(state, rootScope)) {
set(target, key, getFinalValue(state), target.type_);
markStateFinalized(state);
}
} else if (isDraftable(value)) handleValue(value, handledSet, rootScope);
});
return target;
}
function createProxyProxy(base, parent) {
const baseIsArray = isArray(base);
const state = {
type_: baseIsArray ? 1 : 0,
scope_: parent ? parent.scope_ : getCurrentScope(),
modified_: false,
finalized_: false,
assigned_: void 0,
parent_: parent,
base_: base,
draft_: null,
copy_: null,
revoke_: null,
isManual_: false,
callbacks_: void 0
};
let target = state;
let traps = objectTraps;
if (baseIsArray) {
target = [state];
traps = arrayTraps;
}
const { revoke, proxy } = Proxy.revocable(target, traps);
state.draft_ = proxy;
state.revoke_ = revoke;
return [proxy, state];
}
var objectTraps = {
get(state, prop) {
if (prop === DRAFT_STATE) return state;
let arrayPlugin = state.scope_.arrayMethodsPlugin_;
const isArrayWithStringProp = state.type_ === 1 && typeof prop === "string";
if (isArrayWithStringProp) {
if (arrayPlugin?.isArrayOperationMethod(prop)) return arrayPlugin.createMethodInterceptor(state, prop);
}
const source = latest(state);
if (!has(source, prop, state.type_)) return readPropFromProto(state, source, prop);
const value = source[prop];
if (state.finalized_ || !isDraftable(value)) return value;
if (isArrayWithStringProp && state.operationMethod && arrayPlugin?.isMutatingArrayMethod(state.operationMethod) && isArrayIndex(prop)) return value;
if (value === peek(state.base_, prop) || isRelocatedBaseRef(state, prop, value)) {
prepareCopy(state);
const childKey = state.type_ === 1 ? +prop : prop;
const childDraft = createProxy(state.scope_, value, state, childKey);
return state.copy_[childKey] = childDraft;
}
return value;
},
has(state, prop) {
return prop in latest(state);
},
ownKeys(state) {
return Reflect.ownKeys(latest(state));
},
set(state, prop, value) {
const desc = getDescriptorFromProto(latest(state), prop);
if (desc?.set) {
desc.set.call(state.draft_, value);
return true;
}
if (!state.modified_) {
const current2 = peek(latest(state), prop);
const currentState = current2?.[DRAFT_STATE];
if (currentState && currentState.base_ === value) {
state.copy_[prop] = value;
state.assigned_.set(prop, false);
return true;
}
if (is(value, current2) && (value !== void 0 || has(state.base_, prop, state.type_))) return true;
prepareCopy(state);
markChanged(state);
}
if (state.copy_[prop] === value && (value !== void 0 || has(state.copy_, prop, state.type_)) || Number.isNaN(value) && Number.isNaN(state.copy_[prop])) return true;
state.copy_[prop] = value;
state.assigned_.set(prop, true);
handleCrossReference(state, prop, value);
return true;
},
deleteProperty(state, prop) {
prepareCopy(state);
if (peek(state.base_, prop) !== void 0 || prop in state.base_) {
state.assigned_.set(prop, false);
markChanged(state);
} else state.assigned_.delete(prop);
if (state.copy_) delete state.copy_[prop];
return true;
},
getOwnPropertyDescriptor(state, prop) {
const owner = latest(state);
const desc = Reflect.getOwnPropertyDescriptor(owner, prop);
if (!desc) return desc;
return {
[WRITABLE]: true,
[CONFIGURABLE]: state.type_ !== 1 || prop !== "length",
[ENUMERABLE]: desc[ENUMERABLE],
[VALUE]: owner[prop]
};
},
defineProperty() {
die(11);
},
getPrototypeOf(state) {
return getPrototypeOf(state.base_);
},
setPrototypeOf() {
die(12);
}
};
var arrayTraps = {};
for (let key in objectTraps) {
let fn = objectTraps[key];
arrayTraps[key] = function() {
const args = arguments;
args[0] = args[0][0];
return fn.apply(this, args);
};
}
arrayTraps.deleteProperty = function(state, prop) {
if (process.env.NODE_ENV !== "production" && isNaN(parseInt(prop))) die(13);
return arrayTraps.set.call(this, state, prop, void 0);
};
arrayTraps.set = function(state, prop, value) {
if (process.env.NODE_ENV !== "production" && prop !== "length" && isNaN(parseInt(prop))) die(14);
return objectTraps.set.call(this, state[0], prop, value, state[0]);
};
function peek(draft, prop) {
const state = draft[DRAFT_STATE];
return (state ? latest(state) : draft)[prop];
}
function isRelocatedBaseRef(state, prop, value) {
if (state.type_ !== 1 || !state.allIndicesReassigned_ || state.assigned_?.get(prop) || !isDraftable(value) || value[DRAFT_STATE]) return false;
return state.baseRefs_.has(value);
}
function readPropFromProto(state, source, prop) {
const desc = getDescriptorFromProto(source, prop);
return desc ? VALUE in desc ? desc[VALUE] : desc.get?.call(state.draft_) : void 0;
}
function getDescriptorFromProto(source, prop) {
if (!(prop in source)) return void 0;
let proto = getPrototypeOf(source);
while (proto) {
const desc = Object.getOwnPropertyDescriptor(proto, prop);
if (desc) return desc;
proto = getPrototypeOf(proto);
}
}
function markChanged(state) {
if (!state.modified_) {
state.modified_ = true;
if (state.parent_) markChanged(state.parent_);
}
}
function prepareCopy(state) {
if (!state.copy_) {
state.assigned_ = /* @__PURE__ */ new Map();
state.copy_ = shallowCopy(state.base_, state.scope_.immer_.useStrictShallowCopy_);
}
}
var Immer2 = class {
constructor(config) {
this.autoFreeze_ = true;
this.useStrictShallowCopy_ = false;
this.useStrictIteration_ = false;
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
this.produce = (base, recipe, patchListener) => {
if (isFunction(base) && !isFunction(recipe)) {
const defaultBase = recipe;
recipe = base;
const self = this;
return function curriedProduce(base2 = defaultBase, ...args) {
return self.produce(base2, (draft) => recipe.call(this, draft, ...args));
};
}
if (!isFunction(recipe)) die(6);
if (patchListener !== void 0 && !isFunction(patchListener)) die(7);
let result;
if (isDraftable(base)) {
const scope = enterScope(this);
const proxy = createProxy(scope, base, void 0);
let hasError = true;
try {
result = recipe(proxy);
hasError = false;
} finally {
if (hasError) revokeScope(scope);
else leaveScope(scope);
}
usePatchesInScope(scope, patchListener);
return processResult(result, scope);
} else if (!base || !isObjectish(base)) {
result = recipe(base);
if (result === void 0) result = base;
if (result === NOTHING) result = void 0;
if (this.autoFreeze_) freeze(result, true);
if (patchListener) {
const p = [];
const ip = [];
getPlugin(PluginPatches).generateReplacementPatches_(base, result, {
patches_: p,
inversePatches_: ip
});
patchListener(p, ip);
}
return result;
} else die(1, base);
};
this.produceWithPatches = (base, recipe) => {
if (isFunction(base)) return (state, ...args) => this.produceWithPatches(state, (draft) => base(draft, ...args));
let patches, inversePatches;
return [
this.produce(base, recipe, (p, ip) => {
patches = p;
inversePatches = ip;
}),
patches,
inversePatches
];
};
if (isBoolean(config?.autoFreeze)) this.setAutoFreeze(config.autoFreeze);
if (isBoolean(config?.useStrictShallowCopy)) this.setUseStrictShallowCopy(config.useStrictShallowCopy);
if (isBoolean(config?.useStrictIteration)) this.setUseStrictIteration(config.useStrictIteration);
}
createDraft(base) {
if (!isDraftable(base)) die(8);
if (isDraft(base)) base = current(base);
const scope = enterScope(this);
const proxy = createProxy(scope, base, void 0);
proxy[DRAFT_STATE].isManual_ = true;
leaveScope(scope);
return proxy;
}
finishDraft(draft, patchListener) {
const state = draft && draft[DRAFT_STATE];
if (!state || !state.isManual_) die(9);
const { scope_: scope } = state;
usePatchesInScope(scope, patchListener);
return processResult(void 0, scope);
}
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is enabled.
*/
setAutoFreeze(value) {
this.autoFreeze_ = value;
}
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
setUseStrictShallowCopy(value) {
this.useStrictShallowCopy_ = value;
}
/**
* Pass false to use faster iteration that skips non-enumerable properties
* but still handles symbols for compatibility.
*
* By default, strict iteration is enabled (includes all own properties).
*/
setUseStrictIteration(value) {
this.useStrictIteration_ = value;
}
shouldUseStrictIteration() {
return this.useStrictIteration_;
}
applyPatches(base, patches) {
let i;
for (i = patches.length - 1; i >= 0; i--) {
const patch = patches[i];
if (patch.path.length === 0 && patch.op === "replace") {
base = patch.value;
break;
}
}
if (i > -1) patches = patches.slice(i + 1);
const applyPatchesImpl = getPlugin(PluginPatches).applyPatches_;
if (isDraft(base)) return applyPatchesImpl(base, patches);
return this.produce(base, (draft) => applyPatchesImpl(draft, patches));
}
};
function createProxy(rootScope, value, parent, key) {
const [draft, state] = isMap(value) ? getPlugin(PluginMapSet).proxyMap_(value, parent) : isSet(value) ? getPlugin(PluginMapSet).proxySet_(value, parent) : createProxyProxy(value, parent);
(parent?.scope_ ?? getCurrentScope()).drafts_.push(draft);
state.callbacks_ = parent?.callbacks_ ?? [];
state.key_ = key;
if (parent && key !== void 0) registerChildFinalizationCallback(parent, state, key);
else state.callbacks_.push(function rootDraftCleanup(rootScope2) {
rootScope2.mapSetPlugin_?.fixSetContents(state);
const { patchPlugin_ } = rootScope2;
if (state.modified_ && patchPlugin_) patchPlugin_.generatePatches_(state, [], rootScope2);
});
return draft;
}
function current(value) {
if (!isDraft(value)) die(10, value);
return currentImpl(value);
}
function currentImpl(value) {
if (!isDraftable(value) || isFrozen(value)) return value;
const state = value[DRAFT_STATE];
let copy;
let strict = true;
if (state) {
if (!state.modified_) return state.base_;
state.finalized_ = true;
copy = shallowCopy(value, state.scope_.immer_.useStrictShallowCopy_);
strict = state.scope_.immer_.shouldUseStrictIteration();
} else copy = shallowCopy(value, true);
each(copy, (key, childValue) => {
set(copy, key, currentImpl(childValue));
}, strict);
if (state) state.finalized_ = false;
return copy;
}
function enablePatches() {
const errorOffset = 16;
if (process.env.NODE_ENV !== "production") errors.push("Sets cannot have \"replace\" patches.", function(op) {
return "Unsupported patch operation: " + op;
}, function(path) {
return "Cannot apply patch, path doesn't resolve: " + path;
}, "Patching reserved attributes like __proto__, prototype and constructor is not allowed");
function getPath(state, path = []) {
if (state.key_ !== void 0) {
const parentCopy = state.parent_.copy_ ?? state.parent_.base_;
const proxyDraft = getProxyDraft(get(parentCopy, state.key_));
const valueAtKey = get(parentCopy, state.key_);
if (valueAtKey === void 0) return null;
if (valueAtKey !== state.draft_ && valueAtKey !== state.base_ && valueAtKey !== state.copy_) return null;
if (proxyDraft != null && proxyDraft.base_ !== state.base_) return null;
const isSet2 = state.parent_.type_ === 3;
let key;
if (isSet2) {
const setParent = state.parent_;
key = Array.from(setParent.drafts_.keys()).indexOf(state.key_);
} else key = state.key_;
if (!(isSet2 && parentCopy.size > key || has(parentCopy, key))) return null;
path.push(key);
}
if (state.parent_) return getPath(state.parent_, path);
path.reverse();
try {
resolvePath(state.copy_, path);
} catch (e) {
return null;
}
return path;
}
function resolvePath(base, path) {
let current2 = base;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
current2 = get(current2, key);
if (!isObjectish(current2) || current2 === null) throw new Error(`Cannot resolve path at '${path.join("/")}'`);
}
return current2;
}
const REPLACE = "replace";
const ADD = "add";
const REMOVE = "remove";
function generatePatches_(state, basePath, scope) {
if (state.scope_.processedForPatches_.has(state)) return;
state.scope_.processedForPatches_.add(state);
const { patches_, inversePatches_ } = scope;
switch (state.type_) {
case 0:
case 2: return generatePatchesFromAssigned(state, basePath, patches_, inversePatches_);
case 1: return generateArrayPatches(state, basePath, patches_, inversePatches_);
case 3: return generateSetPatches(state, basePath, patches_, inversePatches_);
}
}
function generateArrayPatches(state, basePath, patches, inversePatches) {
let { base_, assigned_ } = state;
let copy_ = state.copy_;
if (copy_.length < base_.length) {
[base_, copy_] = [copy_, base_];
[patches, inversePatches] = [inversePatches, patches];
}
const allReassigned = state.allIndicesReassigned_ === true;
for (let i = 0; i < base_.length; i++) {
const copiedItem = copy_[i];
const baseItem = base_[i];
if ((allReassigned || assigned_?.get(i.toString())) && copiedItem !== baseItem) {
const childState = copiedItem?.[DRAFT_STATE];
if (childState && childState.modified_) continue;
const path = basePath.concat([i]);
patches.push({
op: REPLACE,
path,
value: clonePatchValueIfNeeded(copiedItem)
});
inversePatches.push({
op: REPLACE,
path,
value: clonePatchValueIfNeeded(baseItem)
});
}
}
for (let i = base_.length; i < copy_.length; i++) {
const path = basePath.concat([i]);
patches.push({
op: ADD,
path,
value: clonePatchValueIfNeeded(copy_[i])
});
}
for (let i = copy_.length - 1; base_.length <= i; --i) {
const path = basePath.concat([i]);
inversePatches.push({
op: REMOVE,
path
});
}
}
function generatePatchesFromAssigned(state, basePath, patches, inversePatches) {
const { base_, copy_, type_ } = state;
each(state.assigned_, (key, assignedValue) => {
const origValue = get(base_, key, type_);
const value = get(copy_, key, type_);
const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD;
if (origValue === value && op === REPLACE) return;
const path = basePath.concat(key);
patches.push(op === REMOVE ? {
op,
path
} : {
op,
path,
value: clonePatchValueIfNeeded(value)
});
inversePatches.push(op === ADD ? {
op: REMOVE,
path
} : op === REMOVE ? {
op: ADD,
path,
value: clonePatchValueIfNeeded(origValue)
} : {
op: REPLACE,
path,
value: clonePatchValueIfNeeded(origValue)
});
});
}
function generateSetPatches(state, basePath, patches, inversePatches) {
let { base_, copy_ } = state;
let i = 0;
base_.forEach((value) => {
if (!copy_.has(value)) {
const path = basePath.concat([i]);
patches.push({
op: REMOVE,
path,
value
});
inversePatches.unshift({
op: ADD,
path,
value
});
}
i++;
});
i = 0;
copy_.forEach((value) => {
if (!base_.has(value)) {
const path = basePath.concat([i]);
patches.push({
op: ADD,
path,
value
});
inversePatches.unshift({
op: REMOVE,
path,
value
});
}
i++;
});
}
function generateReplacementPatches_(baseValue, replacement, scope) {
const { patches_, inversePatches_ } = scope;
patches_.push({
op: REPLACE,
path: [],
value: replacement === NOTHING ? void 0 : replacement
});
inversePatches_.push({
op: REPLACE,
path: [],
value: baseValue
});
}
function applyPatches_(draft, patches) {
patches.forEach((patch) => {
const { path, op } = patch;
let base = draft;
for (let i = 0; i < path.length - 1; i++) {
const parentType = getArchtype(base);
let p = path[i];
if (typeof p !== "string" && typeof p !== "number") p = "" + p;
if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(19);
if (isFunction(base) && p === PROTOTYPE) die(19);
base = get(base, p);
if (base === null || !isObjectish(base)) die(18, path.join("/"));
}
const type = getArchtype(base);
const value = deepClonePatchValue(patch.value);
const key = path[path.length - 1];
switch (op) {
case REPLACE: switch (type) {
case 2: return base.set(key, value);
case 3: die(errorOffset);
default: return base[key] = value;
}
case ADD: switch (type) {
case 1: return key === "-" ? base.push(value) : base.splice(key, 0, value);
case 2: return base.set(key, value);
case 3: return base.add(value);
default: return base[key] = value;
}
case REMOVE: switch (type) {
case 1: return base.splice(key, 1);
case 2: return base.delete(key);
case 3: return base.delete(patch.value);
default: return delete base[key];
}
default: die(17, op);
}
});
return draft;
}
function deepClonePatchValue(obj) {
if (!isDraftable(obj)) return obj;
if (isArray(obj)) return obj.map(deepClonePatchValue);
if (isMap(obj)) return new Map(Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)]));
if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue));
const cloned = Object.create(getPrototypeOf(obj));
for (const key in obj) cloned[key] = deepClonePatchValue(obj[key]);
if (has(obj, DRAFTABLE)) cloned[DRAFTABLE] = obj[DRAFTABLE];
return cloned;
}
function clonePatchValueIfNeeded(obj) {
if (isDraft(obj)) return deepClonePatchValue(obj);
else return obj;
}
loadPlugin(PluginPatches, {
applyPatches_,
generatePatches_,
generateReplacementPatches_,
getPath
});
}
var immer = new Immer2();
var produce = immer.produce;
var produceWithPatches = /* @__PURE__ */ immer.produceWithPatches.bind(immer);
var applyPatches = /* @__PURE__ */ immer.applyPatches.bind(immer);
//#endregion
//#region src/utils/nanoid.ts
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function nanoid(size = 21) {
let id = "";
let i = size;
while (i--) id += urlAlphabet[Math.random() * 64 | 0];
return id;
}
//#endregion
//#region src/utils/shared-state.ts
/**
* Upper bound on retained syncIds. Loop echoes arrive near-immediately, so a
* generous window preserves de-dup while capping memory on long-lived,
* frequently-mutated states (e.g. a 1s terminal poll).
*/
const MAX_SYNC_IDS = 1e3;
function rememberSyncId(syncIds, syncId) {
syncIds.add(syncId);
if (syncIds.size > MAX_SYNC_IDS) {
const oldest = syncIds.values().next().value;
if (oldest !== void 0) syncIds.delete(oldest);
}
}
function createSharedState(options) {
const { enablePatches: enablePatches$1 = false } = options;
if (enablePatches$1) enablePatches();
const events = createEventEmitter();
let state = options.initialValue;
const syncIds = /* @__PURE__ */ new Set();
return {
on: events.on,
value: () => state,
patch: (patches, syncId = nanoid()) => {
if (syncIds.has(syncId)) return;
enablePatches();
state = applyPatches(state, patches);
rememberSyncId(syncIds, syncId);
events.emit("updated", state, void 0, syncId);
},
mutate: (fn, syncId = nanoid()) => {
if (syncIds.has(syncId)) return;
rememberSyncId(syncIds, syncId);
if (enablePatches$1) {
const [newState, patches] = produceWithPatches(state, fn);
state = newState;
events.emit("updated", state, patches, syncId);
} else {
state = produce(state, fn);
events.emit("updated", state, void 0, syncId);
}
},
syncIds
};
}
//#endregion
//#region ../../node_modules/.pnpm/perfect-debounce@2.1.0/node_modules/perfect-debounce/dist/index.mjs
const DEBOUNCE_DEFAULTS = { trailing: true };
/**
Debounce functions
@param fn - Promise-returning/async function to debounce.
@param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
@returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
@example
```
import { debounce } from 'perfect-debounce';
const expensiveCall = async input => input;
const debouncedFn = debounce(expensiveCall, 200);
for (const number of [1, 2, 3]) {
console.log(await debouncedFn(number));
}
//=> 1
//=> 2
//=> 3
```
*/
function debounce(fn, wait = 25, options = {}) {
options = {
...DEBOUNCE_DEFAULTS,
...options
};
if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
let leadingValue;
let timeout;
let resolveList = [];
let currentPromise;
let trailingArgs;
const applyFn = (_this, args) => {
currentPromise = _applyPromised(fn, _this, args);
currentPromise.finally(() => {
currentPromise = null;
if (options.trailing && trailingArgs && !timeout) {
const promise = applyFn(_this, trailingArgs);
trailingArgs = null;
return promise;
}
});
return currentPromise;
};
const debounced = function(...args) {
if (options.trailing) trailingArgs = args;
if (currentPromise) return currentPromise;
return new Promise((resolve) => {
const shouldCallNow = !timeout && options.leading;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
const promise = options.leading ? leadingValue : applyFn(this, args);
trailingArgs = null;
for (const _resolve of resolveList) _resolve(promise);
resolveList = [];
}, wait);
if (shouldCallNow) {
leadingValue = applyFn(this, args);
resolve(leadingValue);
} else resolveList.push(resolve);
});
};
const _clearTimeout = (timer) => {
if (timer) {
clearTimeout(timer);
timeout = null;
}
};
debounced.isPending = () => !!timeout;
debounced.cancel = () => {
_clearTimeout(timeout);
resolveList = [];
trailingArgs = null;
};
debounced.flush = () => {
_clearTimeout(timeout);
if (!trailingArgs || currentPromise) return;
const args = trailingArgs;
trailingArgs = null;
return applyFn(this, args);
};
return debounced;
}
async function _applyPromised(fn, _this, args) {
return await fn.apply(_this, args);
}
//#endregion
//#region src/node/storage.ts
function createStorage(options) {
const { mergeInitialValue = (initialValue, savedValue) => ({
...initialValue,
...savedValue
}), debounce: debounceTime = 100 } = options;
let initialValue = options.initialValue;
if (fs.existsSync(options.filepath)) try {
const savedValue = destr(fs.readFileSync(options.filepath, "utf-8"), { strict: true });
initialValue = mergeInitialValue ? mergeInitialValue(options.initialValue, savedValue) : savedValue;
} catch (error) {
diagnostics.DF0012({
filepath: options.filepath,
cause: error
}, { method: "warn" });
initialValue = options.initialValue;
}
const state = createSharedState({
initialValue,
enablePatches: false
});
state.on("updated", debounce((newState) => {
try {
const dir = dirname(options.filepath);
fs.mkdirSync(dir, { recursive: true });
const tmp = `${options.filepath}.${process$1.pid}.tmp`;
fs.writeFileSync(tmp, `${JSON.stringify(newState, null, 2)}\n`);
fs.renameSync(tmp, options.filepath);
} catch (error) {
diagnostics.DF0035({
filepath: options.filepath,
cause: error
}, { method: "error" });
}
}, debounceTime));
return state;
}
//#endregion
export { createEventEmitter as i, createSharedState as n, nanoid as r, createStorage as t };
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
//#region src/adapters/mcp/transports.ts
/**
* Start the MCP server on stdio. Returns a stop function.
* @internal
*/
async function startStdioTransport(server) {
const transport = new StdioServerTransport();
await server.connect(transport);
return async () => {
await server.close();
};
}
//#endregion
export { startStdioTransport };
import { StandardSchemaV1 } from "@standard-schema/spec";
import { BirpcFn, BirpcReturn as BirpcReturn$1 } from "birpc";
//#region src/rpc/utils.d.ts
/** Infers a TypeScript argument tuple from a Standard Schema array */
type InferArgsType<S extends RpcArgsSchema | undefined> = S extends readonly [] ? [] : S extends readonly [infer H, ...infer T] ? H extends StandardSchemaV1 ? T extends readonly StandardSchemaV1[] ? [StandardSchemaV1.InferInput<H>, ...InferArgsType<T>] : never : never : never;
/** Infers a TypeScript return type from a Standard Schema */
type InferReturnType<S extends RpcReturnSchema | undefined> = S extends StandardSchemaV1 ? StandardSchemaV1.InferInput<S> : void;
//#endregion
//#region src/rpc/types.d.ts
type Thenable<T> = T | Promise<T>;
type EntriesToObject<T extends readonly [string, any][]> = { [K in T[number] as K[0]]: K[1]; };
/**
* Type of the RPC function,
* - static: A function that returns a static data, no arguments (can be cached and dumped)
* - action: A function that performs an action (no data returned)
* - event: A function that emits an event (no data returned), and does not wait for a response
* - query: A function that queries a resource
*
* By default, the function is a query function.
*/
type RpcFunctionType = 'static' | 'action' | 'event' | 'query';
/**
* Agent exposure settings for an RPC function. When this field is set,
* the function is surfaced to agents (e.g. via the devframe MCP adapter)
* as a callable tool. Functions without an `agent` field are not exposed —
* default-deny.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
interface RpcFunctionAgentOptions {
/**
* Human-readable description shown to the agent. Required — agents
* rely on this to decide when to invoke the tool. Keep it to ~1–3
* sentences explaining what the tool does and when to use it.
*/
description: string;
/**
* Optional human-friendly display title. Maps to the MCP tool `title`
* annotation. Falls back to the RPC function `name` when omitted.
*/
title?: string;
/**
* Safety classification. Drives MCP annotations (`readOnlyHint`,
* `destructiveHint`) downstream.
* - `'read'` — no side effects; safe to call freely.
* - `'action'` — mutates state but not destructive.
* - `'destructive'` — may perform destructive updates.
*
* When omitted it is inferred from the function `type`:
* - `'static'` / `'query'` → `'read'`
* - `'action'` / `'event'` → `'action'`
*/
safety?: 'read' | 'action' | 'destructive';
/** Free-form tags for grouping or filtering. */
tags?: readonly string[];
/**
* Optional example invocations shown to agents. Returned verbatim in
* the agent manifest.
*/
examples?: readonly {
args: unknown[];
description?: string;
}[];
}
/**
* Manages dynamic function registration and provides a type-safe proxy for accessing functions.
*/
interface RpcFunctionsCollector<LocalFunctions, SetupContext = undefined> {
/** User-provided context passed to setup functions */
context: SetupContext;
/** Type-safe proxy for calling registered functions */
readonly functions: LocalFunctions;
/** Map of registered function definitions keyed by function name */
readonly definitions: Map<string, RpcFunctionDefinitionAnyWithContext<SetupContext>>;
/** Register a new function definition. Pass `force` to overwrite an existing one. */
register: (fn: RpcFunctionDefinitionAnyWithContext<SetupContext>, force?: boolean) => void;
/** Update an existing function definition. Pass `force` to register it if it doesn't exist yet. */
update: (fn: RpcFunctionDefinitionAnyWithContext<SetupContext>, force?: boolean) => void;
/** Subscribe to function changes, returns unsubscribe function */
onChanged: (fn: (id?: string) => void) => (() => void);
}
/**
* Result returned by a function's setup method.
*/
interface RpcFunctionSetupResult<ARGS extends any[], RETURN = void> {
/** Function handler */
handler?: (...args: ARGS) => RETURN;
/** Optional dump definition (overrides definition-level dump) */
dump?: RpcDumpDefinition<ARGS, RETURN>;
}
/**
* Positional argument schemas for an RPC function. Each entry is any
* [Standard Schema](https://standardschema.dev)-compliant validator
* (valibot, zod, arktype, …); the entry at index `i` validates argument
* `i` at call time and drives that argument's inferred type.
*/
type RpcArgsSchema = readonly StandardSchemaV1[];
/**
* Return-value schema for an RPC function. Any
* [Standard Schema](https://standardschema.dev)-compliant validator; it
* validates the handler's resolved return value and drives its inferred
* type.
*/
type RpcReturnSchema = StandardSchemaV1;
/**
* Serialized representation of a thrown value in a dump record.
*
* Errors are stored as plain objects so they round-trip through both the
* strict-JSON and structured-clone codecs. `message` and `name` are always
* present; `cause` and any own enumerable properties of the original
* `Error` are preserved on a best-effort basis. Non-`Error` throws are
* normalized to `{ name: 'Error', message: String(thrown) }`.
*/
interface RpcDumpRecordError {
/** Error message (mirrors `Error.message`). */
message: string;
/** Error type name (e.g., "Error", "TypeError"). */
name: string;
/** `Error.cause`, recursively serialized when it is itself an `Error`. */
cause?: unknown;
/** Own enumerable properties of the original error (excluding `message`/`name`/`cause`). */
[key: string]: unknown;
}
/**
* Single record in a dump store with pre-computed results.
*/
interface RpcDumpRecord<ARGS extends any[] = any[], RETURN = any> {
/** Function arguments */
inputs: ARGS;
/** Result (value or lazy function) */
output?: RETURN;
/** Error if execution failed */
error?: RpcDumpRecordError;
}
/**
* Defines argument combinations to pre-compute for a function.
*/
interface RpcDumpDefinition<ARGS extends any[] = any[], RETURN = any> {
/** Argument combinations to pre-compute by executing handler */
inputs?: ARGS[];
/** Pre-computed records to use directly (bypasses handler execution) */
records?: RpcDumpRecord<ARGS, RETURN>[];
/** Fallback value when no match found */
fallback?: RETURN;
}
/**
* Dynamically generates dump definitions based on context.
*/
type RpcDumpGetter<ARGS extends any[] = any[], RETURN = any, CONTEXT = any> = (context: CONTEXT, handler: (...args: ARGS) => RETURN) => Thenable<RpcDumpDefinition<ARGS, RETURN>>;
/**
* Dump configuration (static object or dynamic function).
*/
type RpcDump<ARGS extends any[] = any[], RETURN = any, CONTEXT = any> = RpcDumpDefinition<ARGS, RETURN> | RpcDumpGetter<ARGS, RETURN, CONTEXT>;
/**
* Base function definition metadata.
*/
interface RpcFunctionDefinitionBase {
/** Function name (unique identifier) */
name: string;
/** Function type (static, action, event, or query) */
type?: RpcFunctionType;
/**
* Declares whether this function's args/return are JSON-serializable
* — i.e. no `Map`, `Set`, `Date`, `BigInt`, class instances, circular
* references, `undefined` leaves, `Symbol`, or `Function` values.
*
* - `true` — args and return are encoded with strict `JSON.stringify`
* on the wire and on disk. Misshapen values throw `DF0019` at the
* sender, surfacing the bug *during the offending call* rather than
* silently coercing to `{}` later. Required for `agent` exposure.
* - `false` (default) — payloads use `structured-clone-es`, which
* round-trips Maps/Sets/cycles. Functions in this mode cannot be
* exposed via the `agent` field — registration throws `DF0018`.
*/
jsonSerializable?: boolean;
}
/**
* Dump store containing pre-computed results.
* Flat structure for serialization and efficient lookups.
*/
interface RpcDumpStore<T = any> {
/** Function definitions keyed by name */
definitions: Record<string, RpcFunctionDefinitionBase>;
/** Records keyed by '<function-name>---<hash>' or '<function-name>---fallback' */
records: Record<string, RpcDumpRecord | (() => Promise<RpcDumpRecord>)>;
/** @internal */
_functions?: T;
}
/**
* Dump client options.
*/
interface RpcDumpClientOptions {
/** Called when arguments don't match any pre-computed entry */
onMiss?: (functionName: string, args: any[]) => void;
}
/**
* Options for collecting dumps.
*/
interface RpcDumpCollectionOptions {
/**
* Concurrency control for parallel execution.
* - `false` or `undefined`: sequential execution (default)
* - `true`: parallel execution with concurrency limit of 5
* - `number`: parallel execution with specified concurrency limit
*/
concurrency?: boolean | number | null;
}
/**
* RPC function definition with optional dump support.
*/
type RpcFunctionDefinition<NAME extends string, TYPE extends RpcFunctionType = 'query', ARGS extends any[] = [], RETURN = void, AS extends RpcArgsSchema | undefined = undefined, RS extends RpcReturnSchema | undefined = undefined, CONTEXT = undefined> = [AS, RS] extends [undefined, undefined] ? {
/** Function name (unique identifier) */
name: NAME;
/** Function type (static, action, event, or query) */
type?: TYPE;
/** Whether the function results should be cached */
cacheable?: boolean;
/** Standard Schema array validating (and typing) the arguments */
args?: AS;
/** Standard Schema validating (and typing) the return value */
returns?: RS;
/**
* Declares whether this function's args/return are JSON-serializable
* (no Map/Set/Date/BigInt/cycles/class instances/undefined/Symbol/Function).
*
* - `true` — wire and dump use strict `JSON.stringify`; misshapen
* values throw `DF0019` at the call site. Required for `agent`.
* - `false` (default) — `structured-clone-es` round-trips fancy
* types. Cannot be `agent`-exposed (registration throws `DF0018`).
*/
jsonSerializable?: boolean;
/**
* Expose this function to agents (e.g. via the MCP adapter).
* When omitted, the function is not agent-exposed (default-deny).
*
* @experimental
*/
agent?: RpcFunctionAgentOptions;
/** Setup function called with context to initialize handler and dump */
setup?: (context: CONTEXT) => Thenable<RpcFunctionSetupResult<ARGS, RETURN>>;
/** Function implementation (required if setup doesn't provide one) */
handler?: (...args: ARGS) => RETURN;
/** Dump definition (setup dump takes priority) */
dump?: RpcDump<ARGS, RETURN, CONTEXT>;
/**
* Sugar for "query in dev, single baked snapshot in build": when
* `true` and no `dump` is provided, the build adapter runs the
* handler once with no arguments and stores the result as both a
* no-args record and the fallback so any call variant resolves
* to the same snapshot. Only valid on `query` (or untyped)
* functions — `static` already has equivalent default behavior.
*/
snapshot?: boolean;
/** Per-context setup-result cache, populated by `getRpcResolvedSetupResult`. @internal */
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<ARGS, RETURN>>>;
/** Single-slot fallback for primitive contexts. @internal */
__promise?: Thenable<RpcFunctionSetupResult<ARGS, RETURN>>;
} : {
/** Function name (unique identifier) */
name: NAME;
/** Function type (static, action, event, or query) */
type?: TYPE;
/** Whether the function results should be cached */
cacheable?: boolean;
/** Standard Schema array validating (and typing) the arguments */
args: AS;
/** Standard Schema validating (and typing) the return value */
returns: RS;
/**
* Declares whether this function's args/return are JSON-serializable
* (no Map/Set/Date/BigInt/cycles/class instances/undefined/Symbol/Function).
*
* - `true` — wire and dump use strict `JSON.stringify`; misshapen
* values throw `DF0019` at the call site. Required for `agent`.
* - `false` (default) — `structured-clone-es` round-trips fancy
* types. Cannot be `agent`-exposed (registration throws `DF0018`).
*/
jsonSerializable?: boolean;
/**
* Expose this function to agents (e.g. via the MCP adapter).
* When omitted, the function is not agent-exposed (default-deny).
*
* @experimental
*/
agent?: RpcFunctionAgentOptions;
/** Setup function called with context to initialize handler and dump */
setup?: (context: CONTEXT) => Thenable<RpcFunctionSetupResult<InferArgsType<AS>, Thenable<InferReturnType<RS>>>>;
/**
* Function implementation (required if setup doesn't provide one).
* The declared `returns` schema describes the *resolved* value —
* async handlers return a promise of it (the runtime always awaits).
*/
handler?: (...args: InferArgsType<AS>) => Thenable<InferReturnType<RS>>;
/** Dump definition (setup dump takes priority) */
dump?: RpcDump<InferArgsType<AS>, Thenable<InferReturnType<RS>>, CONTEXT>;
/**
* Sugar for "query in dev, single baked snapshot in build": when
* `true` and no `dump` is provided, the build adapter runs the
* handler once with no arguments and stores the result as both a
* no-args record and the fallback so any call variant resolves
* to the same snapshot. Only valid on `query` (or untyped)
* functions — `static` already has equivalent default behavior.
*/
snapshot?: boolean;
/** Per-context setup-result cache, populated by `getRpcResolvedSetupResult`. @internal */
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<InferArgsType<AS>, Thenable<InferReturnType<RS>>>>>;
/** Single-slot fallback for primitive contexts. @internal */
__promise?: Thenable<RpcFunctionSetupResult<InferArgsType<AS>, Thenable<InferReturnType<RS>>>>;
};
type RpcFunctionDefinitionToFunction<T extends RpcFunctionDefinitionAny> = T extends {
args: infer AS;
returns: infer RS;
} ? AS extends RpcArgsSchema ? RS extends RpcReturnSchema ? (...args: InferArgsType<AS>) => InferReturnType<RS> : never : never : T extends RpcFunctionDefinition<string, any, infer ARGS, infer RETURN, any, any, any> ? (...args: ARGS) => RETURN : never;
type RpcFunctionDefinitionAny = RpcFunctionDefinition<string, any, any, any, any, any, any>;
type RpcFunctionDefinitionAnyWithContext<CONTEXT = undefined> = RpcFunctionDefinition<string, any, any, any, any, any, CONTEXT>;
type RpcDefinitionsToFunctions<T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [T[K]['name'], RpcFunctionDefinitionToFunction<T[K]>]; }>;
/**
* Like {@link RpcDefinitionsToFunctions}, but prefixes every (bare)
* definition name with `<NS>:`. Use this when functions are defined with
* bare names and registered through a scoped context
* (`ctx.scope(NS).rpc.register(...)`), so the augmented registry keys
* match the namespaced ids stored at runtime.
*/
type RpcDefinitionsToFunctionsWithNamespace<NS extends string, T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [`${NS}:${T[K]['name'] & string}`, RpcFunctionDefinitionToFunction<T[K]>]; }>;
type RpcDefinitionsFilter<T extends readonly RpcFunctionDefinitionAny[], Type extends RpcFunctionType> = { [K in keyof T]: T[K] extends {
type: Type;
} ? T[K] : never; };
//#endregion
export { RpcFunctionType as C, Thenable as E, RpcFunctionSetupResult as S, RpcReturnSchema as T, RpcFunctionDefinition as _, RpcDefinitionsFilter as a, RpcFunctionDefinitionBase as b, RpcDump as c, RpcDumpDefinition as d, RpcDumpGetter as f, RpcFunctionAgentOptions as g, RpcDumpStore as h, RpcArgsSchema as i, RpcDumpClientOptions as l, RpcDumpRecordError as m, BirpcReturn$1 as n, RpcDefinitionsToFunctions as o, RpcDumpRecord as p, EntriesToObject as r, RpcDefinitionsToFunctionsWithNamespace as s, BirpcFn as t, RpcDumpCollectionOptions as u, RpcFunctionDefinitionAny as v, RpcFunctionsCollector as w, RpcFunctionDefinitionToFunction as x, RpcFunctionDefinitionAnyWithContext as y };
//#region src/utils/agent-tool-name.d.ts
/**
* Derive the wire-safe agent tool name for an internal tool id.
*
* Devframe tool ids are colon-namespaced — `devframe:<area>:<fn>` for
* built-ins, `devframes:plugin:<slug>:<fn>` for plugin RPCs, and hub
* command ids for command-derived tools. MCP clients constrain tool names
* to `^[a-zA-Z0-9_-]{1,128}$`, so the agent/MCP boundary derives the wire
* name automatically: every run of characters outside `[a-zA-Z0-9_-]`
* becomes a single `_`, truncated to 128 characters. Internal ids never
* change — resolution back to the id happens at the boundary.
*
* ```
* devframe:state:read → devframe_state_read
* devframes:plugin:git:status → devframes_plugin_git_status
* ```
*
* A plain string transform with no node dependency, so browser-side UIs
* that display a tool's id (e.g. the inspect plugin's agent view) can
* import it too and show the name a client actually calls.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
declare function toAgentToolName(id: string): string;
//#endregion
export { toAgentToolName };
//#region src/utils/agent-tool-name.ts
/**
* Maximum tool-name length several MCP clients enforce (the Anthropic API
* pattern is `^[a-zA-Z0-9_-]{1,128}$`).
*/
const MAX_TOOL_NAME_LENGTH = 128;
/**
* Derive the wire-safe agent tool name for an internal tool id.
*
* Devframe tool ids are colon-namespaced — `devframe:<area>:<fn>` for
* built-ins, `devframes:plugin:<slug>:<fn>` for plugin RPCs, and hub
* command ids for command-derived tools. MCP clients constrain tool names
* to `^[a-zA-Z0-9_-]{1,128}$`, so the agent/MCP boundary derives the wire
* name automatically: every run of characters outside `[a-zA-Z0-9_-]`
* becomes a single `_`, truncated to 128 characters. Internal ids never
* change — resolution back to the id happens at the boundary.
*
* ```
* devframe:state:read → devframe_state_read
* devframes:plugin:git:status → devframes_plugin_git_status
* ```
*
* A plain string transform with no node dependency, so browser-side UIs
* that display a tool's id (e.g. the inspect plugin's agent view) can
* import it too and show the name a client actually calls.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
function toAgentToolName(id) {
return id.replace(/[^\w-]+/g, "_").slice(0, MAX_TOOL_NAME_LENGTH);
}
//#endregion
export { toAgentToolName };
import { a as literal, c as number, d as picklist, f as record, g as voidType, h as union, i as describe, l as object, m as string, n as array, o as nullType, p as s, r as boolean, s as nullable, t as SimpleSchema, u as optional } from "../simple-schema-BDzLeJDk.mjs";
export { SimpleSchema, array, boolean, describe, literal, nullType, nullable, number, object, optional, picklist, record, s, string, union, voidType };
//#region src/utils/simple-schema.ts
function ok(value) {
return { value };
}
function fail(message, path) {
return { issues: [path ? {
message,
path
} : { message }] };
}
function make(type, validate, extra) {
return {
type,
...extra,
"~standard": {
version: 1,
vendor: "devframe",
validate
}
};
}
/** Run a Standard Schema synchronously, rejecting async validators. */
function runSync(schema, value) {
const result = schema["~standard"].validate(value);
if (result instanceof Promise) throw new TypeError("[devframe/utils/simple-schema] async validators are not supported inside object()/optional()/nullable()");
return result;
}
/** Any string. */
function string() {
return make("string", (v) => typeof v === "string" ? ok(v) : fail("Expected a string"));
}
/** A finite number (rejects `NaN`). */
function number() {
return make("number", (v) => typeof v === "number" && !Number.isNaN(v) ? ok(v) : fail("Expected a number"));
}
/** A boolean. */
function boolean() {
return make("boolean", (v) => typeof v === "boolean" ? ok(v) : fail("Expected a boolean"));
}
/** `undefined` — mirrors valibot's `void`. */
function voidType() {
return make("void", (v) => v === void 0 ? ok(void 0) : fail("Expected undefined"));
}
/** `null`. */
function nullType() {
return make("null", (v) => v === null ? ok(null) : fail("Expected null"));
}
/** One of a fixed set of literal values. */
function picklist(values) {
const set = new Set(values);
return make("picklist", (v) => set.has(v) ? ok(v) : fail(`Expected one of: ${values.join(", ")}`), { values });
}
/** A single literal value (string / number / boolean). */
function literal(value) {
return make("literal", (v) => v === value ? ok(v) : fail(`Expected ${JSON.stringify(value)}`), { value });
}
/** A value matching any one of the given schemas. */
function union(options) {
return make("union", (v) => {
const issues = [];
for (const option of options) {
const result = runSync(option, v);
if (!result.issues) return ok(v);
issues.push(...result.issues);
}
return { issues };
}, { options });
}
/** A record with string keys whose values each satisfy the value schema. */
function record(_key, value) {
return make("record", (v) => {
if (typeof v !== "object" || v === null || Array.isArray(v)) return fail("Expected an object");
const obj = v;
const issues = [];
for (const key of Object.keys(obj)) {
const result = runSync(value, obj[key]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [key, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** An array whose every element satisfies the item schema. */
function array(item) {
return make("array", (v) => {
if (!Array.isArray(v)) return fail("Expected an array");
const issues = [];
for (let i = 0; i < v.length; i++) {
const result = runSync(item, v[i]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [i, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** An object whose known keys each satisfy their schema (extra keys are kept). */
function object(shape) {
const entries = Object.entries(shape);
return make("object", (v) => {
if (typeof v !== "object" || v === null || Array.isArray(v)) return fail("Expected an object");
const obj = v;
const issues = [];
for (const [key, schema] of entries) {
const result = runSync(schema, obj[key]);
if (result.issues) for (const issue of result.issues) issues.push({
message: issue.message,
path: [key, ...issue.path ?? []]
});
}
return issues.length ? { issues } : ok(v);
});
}
/** Allow `undefined` in addition to the inner schema. */
function optional(inner) {
return make("optional", (v) => v === void 0 ? ok(void 0) : runSync(inner, v), { wrapped: inner });
}
/** Allow `null` in addition to the inner schema. */
function nullable(inner) {
return make("nullable", (v) => v === null ? ok(null) : runSync(inner, v), { wrapped: inner });
}
/** Attach a human-readable description (used for CLI option help). */
function describe(schema, description) {
return {
...schema,
description
};
}
/**
* Grouped access to every builder — `s.string()`, `s.object({ ... })`,
* `s.void()`, etc. Handy for a valibot-like `import { s } from
* 'devframe/utils/simple-schema'` call site.
*/
const s = {
string,
number,
boolean,
void: voidType,
null: nullType,
literal,
picklist,
union,
record,
array,
object,
optional,
nullable,
describe
};
//#endregion
export { array, boolean, describe, literal, nullType, nullable, number, object, optional, picklist, record, s, string, union, voidType };
import { v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
import { ChannelOptions } from "birpc";
//#region src/rpc/transports/ws-client.d.ts
interface WsRpcChannelOptions {
url: string;
onConnected?: (e: Event) => void;
onError?: (e: Error) => void;
onDisconnected?: (e: CloseEvent) => void;
authToken?: string;
/**
* RPC function definitions (or just the `jsonSerializable` flag per
* method) used to dispatch the per-call wire serializer. Pass an
* empty / partial map on clients that don't have the full registry —
* encoding falls back to structured-clone (the safer superset) and
* decoding still routes correctly via the wire prefix.
*/
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
}
/**
* Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
* Pass the result straight to `createRpcClient`'s `channel` option.
*/
declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions;
//#endregion
export { createWsRpcChannel as n, WsRpcChannelOptions as t };
import { v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
import { Peer } from "crossws";
import { BirpcGroup, ChannelOptions } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Server } from "node:http";
import { Server as Server$1, ServerOptions } from "node:https";
//#region src/rpc/transports/ws-server.d.ts
interface DevframeNodeRpcSessionMeta {
id: number;
/** The crossws peer backing this session's socket. */
peer?: Peer;
clientAuthToken?: string;
isTrusted?: boolean;
subscribedStates: Set<string>;
/**
* Streams this session has subscribed to via
* `rpc.streaming.subscribe(channel, id)`. Tracked here for O(1) cleanup
* on disconnect; the wire format is `${channel}\x1F${id}`.
*/
subscribedStreams?: Set<string>;
/**
* Inbound streams this session is currently uploading to (via
* `rpc.streaming.upload(channel, id)`). Tracked for cleanup on
* disconnect; same wire format as `subscribedStreams`.
*/
uploadingStreams?: Set<string>;
}
interface WsRpcTransportOptions {
/**
* Attach to an existing HTTP(S) server, sharing its port. Combine with
* `path` to bind the WS endpoint to a single route so it coexists with
* other upgrade handlers on the same server (e.g. a Vite dev server's HMR
* socket). The shared server's lifecycle is owned by the caller — closing
* this transport detaches the upgrade listener without closing the server.
*/
server?: Server | Server$1;
/** Port for a newly-created standalone WS server. */
port?: number;
/** Host for a newly-created standalone WS server. Defaults to `localhost`. */
host?: string;
/**
* Restrict the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`). When
* sharing a `server`, non-matching upgrade requests are left untouched for
* other listeners to handle, so devframe's socket can sit alongside
* framework sockets (Vite HMR, etc.).
*/
path?: string;
/**
* Destroy upgrade requests that don't match `path` instead of leaving them
* for other listeners. Enable this when devframe owns the shared server
* outright (nothing else handles its upgrades), so an off-route client is
* rejected promptly rather than left hanging. Default: `false`
* (coexist-friendly); servers this transport creates itself always
* destroy unmatched upgrades.
*/
destroyUnmatched?: boolean;
/** When set, a new https.Server is created and the WS endpoint is attached to it. */
https?: ServerOptions;
/**
* Extra origins to accept on the WS upgrade beyond the loopback default.
* Add your LAN/tunnel origin here when reaching the tool from another host.
* Pass `false` to disable origin checking entirely (not recommended).
* Default: loopback-only.
*/
allowedOrigins?: readonly string[] | false;
/**
* RPC function definitions, used by the per-call wire serializer to
* dispatch between strict-JSON and structured-clone encoding based
* on each function's `jsonSerializable` flag.
*
* When omitted, all messages fall back to structured-clone — safe but
* loses dev-time validation for `jsonSerializable: true` declarations.
*/
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
onConnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
onDisconnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
/** Override the default per-call serializer. Most callers should leave this unset. */
serialize?: ChannelOptions['serialize'];
/** Override the default per-call deserializer. Most callers should leave this unset. */
deserialize?: ChannelOptions['deserialize'];
}
interface WsRpcTransport {
/**
* The crossws node adapter driving the socket — exposes the connected
* `peers` and pub/sub. See https://crossws.h3.dev.
*/
ws: NodeAdapter;
/** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
detach: () => void;
/**
* Tear the transport down deterministically: detach from a shared server,
* force-terminate every connected peer, and close any server this
* transport created itself (`port` / `https` modes).
*/
close: () => Promise<void>;
}
declare function isLoopbackHostname(hostname: string): boolean;
/**
* Default origin policy for a localhost dev tool: allow requests with no
* `Origin` header (native, non-browser clients), allow any loopback origin
* (so cross-port localhost dev setups keep working), and allow explicitly
* configured origins. Everything else — a real remote page in the dev's
* browser — is rejected.
*/
declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
/**
* Attach a WebSocket transport to an existing RPC group, powered by
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
* `server` (sharing its port, optionally scoped to a `path`), or let this
* helper create a standalone server from `port` / `host` / `https`.
*
* Returns the crossws node adapter plus `detach` (remove the upgrade
* listener from a shared `server`) and `close` (full deterministic
* teardown).
*/
declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
//#endregion
export { isAllowedOrigin as a, attachWsRpcTransport as i, WsRpcTransport as n, isLoopbackHostname as o, WsRpcTransportOptions as r, DevframeNodeRpcSessionMeta as t };
+1
-1

@@ -1,2 +0,2 @@

import { r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import { r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
//#region src/adapters/build.d.ts

@@ -3,0 +3,0 @@ interface CreateBuildOptions {

@@ -1,8 +0,8 @@

import { t as collectStaticRpcDump } from "../dump-Cz7yVvsB.mjs";
import { t as collectStaticRpcDump } from "../dump-CgZShDRB.mjs";
import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
import { n as strictJsonStringify } from "../serialization-DpLXCy13.mjs";
import { n as strictJsonStringify } from "../serialization-C8Mnw9hK.mjs";
import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
import { a as diagnostics } from "../storage-gEnj9ASQ.mjs";
import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-CgOetlNE.mjs";
import { n as createHostContext, t as createH3DevframeHost } from "../host-h3-Kz7t5Xab.mjs";
import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
import { n as resolveBasePath } from "../_shared-bWRzeSa0.mjs";

@@ -9,0 +9,0 @@ import { existsSync } from "node:fs";

@@ -1,2 +0,2 @@

import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
import { CAC } from "cac";

@@ -3,0 +3,0 @@ import { H3 } from "h3";

@@ -1,2 +0,2 @@

import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CPaOy3LD.mjs";
import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B-R_tDGJ.mjs";
export { createCac, defineCliFlags, parseCliFlags };

@@ -1,2 +0,2 @@

import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags } from "../devframe-Ckhf3kFw.mjs";
import { Ft as InferCliFlags, It as defineCliFlags, Lt as parseCliFlags, Pt as CliFlagsSchema } from "../devframe-Dsjn_Xtq.mjs";
import { CacHandle, CreateCacOptions, createCac } from "./cac.mjs";

@@ -3,0 +3,0 @@ //#region src/adapters/cli.d.ts

@@ -1,2 +0,2 @@

import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CPaOy3LD.mjs";
import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-B-R_tDGJ.mjs";
//#region src/adapters/cli.ts

@@ -3,0 +3,0 @@ /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */

@@ -1,3 +0,3 @@

import { _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-Ckhf3kFw.mjs";
import { n as StartedServer } from "../server-B0wiP_Oi.mjs";
import { _ as ConnectionMeta, d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-Dsjn_Xtq.mjs";
import { n as StartedServer } from "../server-VLQJouOO.mjs";
import { H3 } from "h3";

@@ -4,0 +4,0 @@ //#region src/adapters/dev.d.ts

@@ -1,2 +0,2 @@

import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-BCeaQuGE.mjs";
import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-D4M7Veo7.mjs";
export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };

@@ -1,2 +0,2 @@

import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
//#region src/adapters/embedded.d.ts

@@ -3,0 +3,0 @@ interface CreateEmbeddedOptions {

@@ -1,3 +0,3 @@

import { r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import "@modelcontextprotocol/sdk/server/index.js";
import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
import "@modelcontextprotocol/server";
//#region src/adapters/mcp/build-server.d.ts

@@ -40,2 +40,44 @@ interface CreateMcpServerOptions {

//#endregion
export { type CreateMcpServerOptions, type McpServerHandle, createMcpServer };
//#region src/adapters/mcp/fetch.d.ts
interface CreateMcpFetchHandlerOptions {
/** Name reported in the MCP handshake. */
serverName: string;
/** Version reported in the MCP handshake. */
serverVersion: string;
/** Expose shared-state keys as MCP resources — see `buildMcpServerFromContext`. */
exposeSharedState: boolean | ((key: string) => boolean);
/**
* Origin allow-list beyond the loopback default. `false` disables the
* origin gate entirely. Default: loopback-only (mirrors the WS transport).
*/
allowedOrigins?: readonly string[] | false;
}
interface McpFetchHandler {
/**
* WHATWG-`fetch` handler for the MCP Streamable-HTTP endpoint. Hand every
* method (POST/GET/DELETE) on the endpoint's path to it — routing by path
* is the host's job.
*/
fetch: (request: Request) => Promise<Response>;
/** Tear down every live MCP session (closes servers, drops subscriptions). */
dispose: () => Promise<void>;
}
/**
* Build a framework-agnostic MCP Streamable-HTTP endpoint over a devframe
* context: a web-standard `Request → Response` handler any host can mount —
* h3 (see `mountMcpHttp`), a Next.js App Router route, or any other
* fetch-shaped server.
*
* Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
* and MCP server (built from the shared, live `ctx` via
* `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header: an
* `initialize` POST spins up a session; later requests route to it; a `DELETE`
* (or client disconnect) tears it down. The origin gate applies devframe's
* loopback-default DNS-rebinding protection (identical semantics to the WS
* upgrade's `isAllowedOrigin`).
*
* @experimental
*/
declare function createMcpFetchHandler(ctx: DevframeNodeContext, options: CreateMcpFetchHandlerOptions): McpFetchHandler;
//#endregion
export { type CreateMcpFetchHandlerOptions, type CreateMcpServerOptions, type McpFetchHandler, type McpServerHandle, createMcpFetchHandler, createMcpServer };

@@ -1,2 +0,2 @@

import { n as createMcpServer } from "../build-server-C7vTKjiQ.mjs";
export { createMcpServer };
import { n as createMcpServer, t as createMcpFetchHandler } from "../fetch-BZyK4v6W.mjs";
export { createMcpFetchHandler, createMcpServer };

@@ -1,5 +0,5 @@

import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, kt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-Ckhf3kFw.mjs";
import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CrzNxXKq.mjs";
import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-DgsLFhZg.mjs";
import { t as WsRpcChannelOptions } from "../ws-client-DSXiI2h7.mjs";
import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, jt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-Dsjn_Xtq.mjs";
import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CnJSgRVa.mjs";
import { E as RpcCacheOptions, T as RpcCacheManager } from "../index-Dbw1p5ch.mjs";
import { t as WsRpcChannelOptions } from "../ws-client-B6Tnr0ic.mjs";
import { BirpcOptions, BirpcReturn } from "birpc";

@@ -6,0 +6,0 @@ //#region src/client/connection.d.ts

@@ -1,2 +0,2 @@

import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import { d as McpRouteOptions, p as DevframeAuthHandler, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
//#region src/helpers/vite.d.ts

@@ -3,0 +3,0 @@ interface ViteDevBridgeOptions {

import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constants.mjs";
import { a as diagnostics } from "../storage-gEnj9ASQ.mjs";
import { t as diagnostics } from "../diagnostics-B5-qHeqD.mjs";
import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-bWRzeSa0.mjs";
import { serveStaticNodeMiddleware } from "../utils/serve-static.mjs";
import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-BCeaQuGE.mjs";
import { n as resolveDevServerPort, r as resolveMcpConnectionMeta, t as createDevServer } from "../dev-D4M7Veo7.mjs";
import { resolve } from "pathe";

@@ -7,0 +7,0 @@ //#region src/helpers/vite.ts

@@ -1,8 +0,8 @@

import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "./devframe-Ckhf3kFw.mjs";
import { C as RpcFunctionType, T as RpcReturnSchema, _ as RpcFunctionDefinition, g as RpcFunctionAgentOptions, i as RpcArgsSchema } from "./types-CrzNxXKq.mjs";
import "./index-DgsLFhZg.mjs";
import { t as DevframeNodeRpcSessionMeta } from "./ws-server-9-wn7MNQ.mjs";
import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as DevframeAgentHostEvents, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as AgentToolProvider, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, Mt as EventUnsubscribe, N as ScopedRpcFn, Nt as EventsMap, O as DevframeSettings, Ot as AgentToolProviderHandle, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventEmitter, k as DevframeSettingsRegistry, kt as DevframeAgentHost, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "./devframe-Dsjn_Xtq.mjs";
import { C as RpcFunctionType, T as RpcReturnSchema, _ as RpcFunctionDefinition, g as RpcFunctionAgentOptions, i as RpcArgsSchema } from "./types-CnJSgRVa.mjs";
import "./index-Dbw1p5ch.mjs";
import { t as DevframeNodeRpcSessionMeta } from "./ws-server-D_Vjtums.mjs";
//#region src/define.d.ts
declare const defineRpcFunction: <NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends RpcArgsSchema | undefined = undefined, const RS extends RpcReturnSchema | undefined = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, DevframeNodeContext>) => RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, DevframeNodeContext>;
//#endregion
export { type AgentHandle, type AgentManifest, type AgentResource, type AgentResourceContent, type AgentResourceInput, type AgentTool, type AgentToolInput, type ConnectionMeta, type ConnectionMetaWebsocket, type DevframeAgentHost, type DevframeAgentHostEvents, type DevframeBrowserContext, type DevframeCapabilities, type DevframeCliOptions, type DevframeDefineDiagnosticsOptions, type DevframeDefinition, type DevframeDeploymentKind, type DevframeDiagnosticsDefinition, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, type DevframeDockDefaults, type DevframeDuplicationStrategy, type DevframeHost, type DevframeNodeContext, type DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, type DevframeRuntime, type DevframeScopedNodeContext, type DevframeScopedNodeRpc, type DevframeScopedStreamingHost, type DevframeServiceId, type DevframeServiceOf, type DevframeServicesHost, type DevframeServicesRegistry, type DevframeSettings, type DevframeSettingsRegistry, type DevframeSettingsStore, type DevframeSetupInfo, type DevframeSpaOptions, type DevframeStorageScope, type DevframeViewHost, type DevframeWsOptions, type EntriesToObject, type EventEmitter, type EventUnsubscribe, type EventsMap, type McpRouteOptions, type PartialWithoutId, type RpcBroadcastOptions, type RpcFunctionAgentOptions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost, type ScopedBroadcastOptions, type ScopedClientFunctions, type ScopedRpcFn, type ScopedServerFunctions, type ScopedSharedStates, type SettingsForNamespace, type Thenable, type defineDevframe, defineRpcFunction };
export { type AgentHandle, type AgentManifest, type AgentResource, type AgentResourceContent, type AgentResourceInput, type AgentTool, type AgentToolInput, type AgentToolProvider, type AgentToolProviderHandle, type ConnectionMeta, type ConnectionMetaWebsocket, type DevframeAgentHost, type DevframeAgentHostEvents, type DevframeBrowserContext, type DevframeCapabilities, type DevframeCliOptions, type DevframeDefineDiagnosticsOptions, type DevframeDefinition, type DevframeDeploymentKind, type DevframeDiagnosticsDefinition, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, type DevframeDockDefaults, type DevframeDuplicationStrategy, type DevframeHost, type DevframeNodeContext, type DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, type DevframeRuntime, type DevframeScopedNodeContext, type DevframeScopedNodeRpc, type DevframeScopedStreamingHost, type DevframeServiceId, type DevframeServiceOf, type DevframeServicesHost, type DevframeServicesRegistry, type DevframeSettings, type DevframeSettingsRegistry, type DevframeSettingsStore, type DevframeSetupInfo, type DevframeSpaOptions, type DevframeStorageScope, type DevframeViewHost, type DevframeWsOptions, type EntriesToObject, type EventEmitter, type EventUnsubscribe, type EventsMap, type McpRouteOptions, type PartialWithoutId, type RpcBroadcastOptions, type RpcFunctionAgentOptions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost, type ScopedBroadcastOptions, type ScopedClientFunctions, type ScopedRpcFn, type ScopedServerFunctions, type ScopedSharedStates, type SettingsForNamespace, type Thenable, type defineDevframe, defineRpcFunction };

@@ -1,3 +0,3 @@

import { p as DevframeAuthHandler } from "../devframe-Ckhf3kFw.mjs";
import { a as verifyAuthToken, i as refreshTempAuthCode, n as exchangeTempAuthCode, o as revokeActiveConnectionsForToken, r as getTempAuthCode, s as revokeAuthToken, t as buildOtpAuthUrl } from "../index-8dndvIxR.mjs";
import { p as DevframeAuthHandler } from "../devframe-Dsjn_Xtq.mjs";
import { a as verifyAuthToken, i as refreshTempAuthCode, n as exchangeTempAuthCode, o as revokeActiveConnectionsForToken, r as getTempAuthCode, s as revokeAuthToken, t as buildOtpAuthUrl } from "../index-CeBbry0R.mjs";
export { DevframeAuthHandler, buildOtpAuthUrl, exchangeTempAuthCode, getTempAuthCode, refreshTempAuthCode, revokeActiveConnectionsForToken, revokeAuthToken, verifyAuthToken };

@@ -1,3 +0,3 @@

import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-Ckhf3kFw.mjs";
import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-CzqPrJSz.mjs";
import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-Dsjn_Xtq.mjs";
import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-7BbaIUSI.mjs";
//#region src/adapters/_shared.d.ts

@@ -4,0 +4,0 @@ /**

@@ -1,3 +0,3 @@

import { n as internalContextMap, t as getInternalContext } from "../context-DY72brRI.mjs";
import { n as internalContextMap, t as getInternalContext } from "../context-C9Cgm1hP.mjs";
import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-bWRzeSa0.mjs";
export { getInternalContext, internalContextMap, normalizeBasePath, resolveBasePath };

@@ -1,6 +0,36 @@

import { C as DevframeServicesHost, Ct as AgentResourceContent, Dt as DevframeAgentHost$1, Et as AgentToolInput, H as DevframeDiagnosticsHost$1, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost$1, O as DevframeSettings, Ot as DevframeAgentHostEvents, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, Z as RpcStreamingHost, b as DevframeNodeContext, bt as AgentHandle, ht as SharedState, kt as EventEmitter, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest } from "../devframe-Ckhf3kFw.mjs";
import { v as RpcFunctionDefinitionAny } from "../types-CrzNxXKq.mjs";
import "../index-DgsLFhZg.mjs";
import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-B0wiP_Oi.mjs";
import { At as DevframeAgentHostEvents, C as DevframeServicesHost, Ct as AgentResourceContent, Dt as AgentToolProvider, Et as AgentToolInput, H as DevframeDiagnosticsHost$1, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost$1, O as DevframeSettings, Ot as AgentToolProviderHandle, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, Z as RpcStreamingHost, b as DevframeNodeContext, bt as AgentHandle, ht as SharedState, jt as EventEmitter, kt as DevframeAgentHost$1, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest } from "../devframe-Dsjn_Xtq.mjs";
import { v as RpcFunctionDefinitionAny } from "../types-CnJSgRVa.mjs";
import "../index-Dbw1p5ch.mjs";
import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-VLQJouOO.mjs";
import { BirpcGroup } from "birpc";
//#region src/node/agent-args.d.ts
/**
* How {@link coerceAgentPositionalArgs} treats an args object that carries
* neither declared schemas nor `arg0`/`arg1`/… keys:
*
* - `'wrap'` — pass the object itself as the single positional argument.
* RPC-backed tools use this: an untyped RPC may take one raw object.
* - `'drop'` — call with zero arguments. Command-backed tools use this:
* a handler's positional parameters come solely from its declared
* `agent.args` schemas, so undeclared payload is ignored.
*/
type AgentArgsFallback = 'wrap' | 'drop';
/**
* Map the args payload an agent surface receives (MCP sends an object
* keyed `arg0`/`arg1`/…, matching the schema the adapter advertises) onto
* a handler's positional parameters. Shared by the agent host's RPC
* bridge and the hub's command-derived tools so the coercion cannot
* drift between them.
*
* - an array passes through as-is
* - `null`/`undefined` become a zero-argument call
* - with declared schemas, each schema reads its own `argN` key, in order
* - without schemas, `arg0`/`arg1`/… keys are collected when present
* - an empty object becomes a zero-argument call
* - anything else follows the {@link AgentArgsFallback}
*
* @experimental
*/
declare function coerceAgentPositionalArgs(args: unknown, schemas: readonly unknown[] | undefined, fallback?: AgentArgsFallback): unknown[];
//#endregion
//#region src/node/context.d.ts

@@ -43,2 +73,3 @@ interface CreateHostContextOptions {

private readonly resources;
private readonly providers;
private _rpcUnsubscribe;

@@ -48,2 +79,3 @@ constructor(context: DevframeNodeContext);

unregisterTool(id: string): boolean;
registerToolProvider(provider: AgentToolProvider): AgentToolProviderHandle;
registerResource(input: AgentResourceInput): AgentHandle;

@@ -60,5 +92,6 @@ unregisterResource(id: string): boolean;

private _projectTool;
/** Query every registered provider, projecting inputs to serializable tools. */
private _collectProviderTools;
private _collectRpcTools;
private _findRpcDefinition;
private _coercePositionalArgs;
}

@@ -141,2 +174,67 @@ //#endregion

//#endregion
//#region src/node/instance-registry.d.ts
/**
* One running devframe instance, as recorded in the instance registry.
* Records are self-describing JSON — additive fields are safe.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
interface DevframeInstanceRecord {
/** Process id of the dev server. */
pid: number;
/** Listening port. */
port: number;
/** Dialable HTTP origin, e.g. `http://127.0.0.1:9876`. */
origin: string;
/** Base path the devframe is mounted at (trailing slash). */
basePath: string;
/** Definition id. */
id: string;
/** Definition display name. */
name?: string;
/** Working directory the instance was started from. */
rootDir: string;
/**
* Absolute URL path of the MCP Streamable-HTTP endpoint on `origin`, or
* `null` when the instance runs without an MCP route.
*/
mcp: {
path: string;
} | null;
/** Epoch-ms timestamp of registration. */
startedAt: number;
}
/**
* Handle returned by {@link registerDevframeInstance}.
*
* @experimental
*/
interface DevframeInstanceRegistration {
/** The registry file backing this registration. */
readonly file: string;
/** Remove the record (idempotent). Call on server close. */
unregister: () => void;
}
/**
* Record a running devframe instance in the global instance registry so
* discovery tooling (`devframe connect`, editor integrations) can find it
* without port guessing.
*
* `createDevServer` registers automatically; custom hosts that serve a
* devframe in-process (e.g. `@devframes/next`'s host inside a Next dev
* server) call this explicitly with the origin they are reachable at.
*
* The record is written atomically to `<dir>/<pid>-<port>.json` and removed
* by {@link DevframeInstanceRegistration.unregister}. Records surviving a
* crash are pruned by readers whose liveness probe fails. Registration never
* throws — a write failure degrades to a coded warning (`DF0045`), since a
* dev server must not die over discovery metadata.
*
* @experimental
*/
declare function registerDevframeInstance(record: DevframeInstanceRecord, options?: {
instancesDir?: string;
}): DevframeInstanceRegistration;
//#endregion
//#region src/node/rpc-shared-state.d.ts

@@ -187,2 +285,2 @@ declare function createRpcSharedStateServerHost(rpc: RpcFunctionsHost): RpcSharedStateHost;

//#endregion
export { CreateH3DevframeHostOptions, CreateHostContextOptions, CreateStorageOptions, DevframeAgentHost, DevframeDiagnosticsHost, DevframeServicesHostImpl, DevframeViewHost, type RpcFunctionsHost, StartHttpAndWsOptions, StartedServer, createH3DevframeHost, createHostContext, createNodeSettings, createRpcSharedStateServerHost, createRpcStreamingServerHost, createScopedNodeContext, createStorage, formatHostForUrl, isObject, normalizeHttpServerUrl, startHttpAndWs, toDialableHost };
export { AgentArgsFallback, CreateH3DevframeHostOptions, CreateHostContextOptions, CreateStorageOptions, DevframeAgentHost, DevframeDiagnosticsHost, type DevframeInstanceRecord, type DevframeInstanceRegistration, DevframeServicesHostImpl, DevframeViewHost, type RpcFunctionsHost, StartHttpAndWsOptions, StartedServer, coerceAgentPositionalArgs, createH3DevframeHost, createHostContext, createNodeSettings, createRpcSharedStateServerHost, createRpcStreamingServerHost, createScopedNodeContext, createStorage, formatHostForUrl, isObject, normalizeHttpServerUrl, registerDevframeInstance, startHttpAndWs, toDialableHost };

@@ -1,4 +0,5 @@

import { t as createStorage } from "../storage-gEnj9ASQ.mjs";
import { a as DevframeViewHost, c as createRpcSharedStateServerHost, i as createNodeSettings, l as DevframeDiagnosticsHost, n as createHostContext, o as DevframeServicesHostImpl, r as createScopedNodeContext, s as createRpcStreamingServerHost, t as createH3DevframeHost, u as DevframeAgentHost } from "../host-h3-CgOetlNE.mjs";
import { a as toDialableHost, i as normalizeHttpServerUrl, n as formatHostForUrl, r as isObject, t as startHttpAndWs } from "../server-DykHCza_.mjs";
export { DevframeAgentHost, DevframeDiagnosticsHost, DevframeServicesHostImpl, DevframeViewHost, createH3DevframeHost, createHostContext, createNodeSettings, createRpcSharedStateServerHost, createRpcStreamingServerHost, createScopedNodeContext, createStorage, formatHostForUrl, isObject, normalizeHttpServerUrl, startHttpAndWs, toDialableHost };
import { a as DevframeViewHost, c as createRpcSharedStateServerHost, d as coerceAgentPositionalArgs, i as createNodeSettings, l as DevframeDiagnosticsHost, n as createHostContext, o as DevframeServicesHostImpl, r as createScopedNodeContext, s as createRpcStreamingServerHost, t as createH3DevframeHost, u as DevframeAgentHost } from "../host-h3-Kz7t5Xab.mjs";
import { t as createStorage } from "../storage-Dzoc3NVC.mjs";
import { r as registerDevframeInstance } from "../instance-registry-D6fxYu36.mjs";
import { a as toDialableHost, i as normalizeHttpServerUrl, n as formatHostForUrl, r as isObject, t as startHttpAndWs } from "../server-CcAPxuoT.mjs";
export { DevframeAgentHost, DevframeDiagnosticsHost, DevframeServicesHostImpl, DevframeViewHost, coerceAgentPositionalArgs, createH3DevframeHost, createHostContext, createNodeSettings, createRpcSharedStateServerHost, createRpcStreamingServerHost, createScopedNodeContext, createStorage, formatHostForUrl, isObject, normalizeHttpServerUrl, registerDevframeInstance, startHttpAndWs, toDialableHost };

@@ -1,5 +0,5 @@

import "../devframe-Ckhf3kFw.mjs";
import { E as Thenable, S as RpcFunctionSetupResult, c as RpcDump, g as RpcFunctionAgentOptions } from "../types-CrzNxXKq.mjs";
import "../index-DgsLFhZg.mjs";
import * as v from "valibot";
import "../devframe-Dsjn_Xtq.mjs";
import { E as Thenable, S as RpcFunctionSetupResult, c as RpcDump, g as RpcFunctionAgentOptions } from "../types-CnJSgRVa.mjs";
import "../index-Dbw1p5ch.mjs";
import { t as SimpleSchema } from "../simple-schema-BDzLeJDk.mjs";
//#region src/recipes/common-rpc-functions.d.ts

@@ -14,3 +14,3 @@ /**

type KnownEditor = 'atom' | 'subl' | 'sublime' | 'sublime_text' | 'wstorm' | 'charm' | 'zed' | 'notepad++' | 'vim' | 'mvim' | 'joe' | 'gvim' | 'emacs' | 'emacsclient' | 'rmate' | 'mate' | 'code' | 'code-insiders' | 'codium' | 'vscodium' | 'trae' | 'antigravity' | 'cursor' | 'appcode' | 'clion' | 'idea' | 'phpstorm' | 'pycharm' | 'rubymine' | 'webstorm' | 'goland' | 'rider';
/** Runtime list of every {@link KnownEditor}, in the order `v.picklist` reports them. */
/** Runtime list of every {@link KnownEditor}. */
declare const KNOWN_EDITORS: KnownEditor[];

@@ -42,12 +42,12 @@ /**

cacheable?: boolean;
args: readonly [v.StringSchema<undefined>, v.OptionalSchema<v.PicklistSchema<KnownEditor[], undefined>, undefined>];
returns: v.VoidSchema<undefined>;
args: readonly [SimpleSchema<string, string>, SimpleSchema<KnownEditor | undefined, KnownEditor | undefined>];
returns: SimpleSchema<void, void>;
jsonSerializable?: boolean;
agent?: RpcFunctionAgentOptions;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>>) | undefined;
handler?: ((args_0: string, args_1: KnownEditor | undefined) => void) | undefined;
dump?: RpcDump<[string, KnownEditor | undefined], void, undefined> | undefined;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>>) | undefined;
handler?: ((args_0: string, args_1: KnownEditor | undefined) => Thenable<void>) | undefined;
dump?: RpcDump<[string, KnownEditor | undefined], Thenable<void>, undefined> | undefined;
snapshot?: boolean;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>> | undefined;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>> | undefined;
};

@@ -69,12 +69,12 @@ /**

cacheable?: boolean;
args: readonly [v.StringSchema<undefined>];
returns: v.VoidSchema<undefined>;
args: readonly [SimpleSchema<string, string>];
returns: SimpleSchema<void, void>;
jsonSerializable?: boolean;
agent?: RpcFunctionAgentOptions;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string], void>>) | undefined;
handler?: ((args_0: string) => void) | undefined;
dump?: RpcDump<[string], void, undefined> | undefined;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string], Thenable<void>>>) | undefined;
handler?: ((args_0: string) => Thenable<void>) | undefined;
dump?: RpcDump<[string], Thenable<void>, undefined> | undefined;
snapshot?: boolean;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string], void>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string], void>> | undefined;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string], Thenable<void>>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string], Thenable<void>>> | undefined;
};

@@ -95,12 +95,12 @@ /**

cacheable?: boolean;
args: readonly [v.StringSchema<undefined>, v.OptionalSchema<v.PicklistSchema<KnownEditor[], undefined>, undefined>];
returns: v.VoidSchema<undefined>;
args: readonly [SimpleSchema<string, string>, SimpleSchema<KnownEditor | undefined, KnownEditor | undefined>];
returns: SimpleSchema<void, void>;
jsonSerializable?: boolean;
agent?: RpcFunctionAgentOptions;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>>) | undefined;
handler?: ((args_0: string, args_1: KnownEditor | undefined) => void) | undefined;
dump?: RpcDump<[string, KnownEditor | undefined], void, undefined> | undefined;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>>) | undefined;
handler?: ((args_0: string, args_1: KnownEditor | undefined) => Thenable<void>) | undefined;
dump?: RpcDump<[string, KnownEditor | undefined], Thenable<void>, undefined> | undefined;
snapshot?: boolean;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], void>> | undefined;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string, KnownEditor | undefined], Thenable<void>>> | undefined;
}, {

@@ -110,14 +110,14 @@ name: "devframe:open-in-finder";

cacheable?: boolean;
args: readonly [v.StringSchema<undefined>];
returns: v.VoidSchema<undefined>;
args: readonly [SimpleSchema<string, string>];
returns: SimpleSchema<void, void>;
jsonSerializable?: boolean;
agent?: RpcFunctionAgentOptions;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string], void>>) | undefined;
handler?: ((args_0: string) => void) | undefined;
dump?: RpcDump<[string], void, undefined> | undefined;
setup?: ((context: undefined) => Thenable<RpcFunctionSetupResult<[string], Thenable<void>>>) | undefined;
handler?: ((args_0: string) => Thenable<void>) | undefined;
dump?: RpcDump<[string], Thenable<void>, undefined> | undefined;
snapshot?: boolean;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string], void>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string], void>> | undefined;
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<[string], Thenable<void>>>> | undefined;
__promise?: Thenable<RpcFunctionSetupResult<[string], Thenable<void>>> | undefined;
}];
//#endregion
export { KNOWN_EDITORS, KnownEditor, commonRpcFunctions, openInEditor, openInFinder };
import { n as defineRpcFunction } from "../define-BLWPsH6y.mjs";
import * as v from "valibot";
import { t as s } from "../simple-schema-DQPZrAaZ.mjs";
//#region src/recipes/common-rpc-functions.ts
/** Runtime list of every {@link KnownEditor}, in the order `v.picklist` reports them. */
/** Runtime list of every {@link KnownEditor}. */
const KNOWN_EDITORS = [

@@ -64,4 +64,4 @@ "atom",

jsonSerializable: true,
args: [v.string(), v.optional(v.picklist(KNOWN_EDITORS))],
returns: v.void(),
args: [s.string(), s.optional(s.picklist(KNOWN_EDITORS))],
returns: s.void(),
async handler(filename, editor) {

@@ -87,4 +87,4 @@ const { launchEditor } = await import("../utils/launch-editor.mjs");

jsonSerializable: true,
args: [v.string()],
returns: v.void(),
args: [s.string()],
returns: s.void(),
async handler(path) {

@@ -91,0 +91,0 @@ const { open } = await import("../utils/open.mjs");

@@ -1,3 +0,3 @@

import { b as DevframeNodeContext, p as DevframeAuthHandler } from "../devframe-Ckhf3kFw.mjs";
import "../index-8dndvIxR.mjs";
import { b as DevframeNodeContext, p as DevframeAuthHandler } from "../devframe-Dsjn_Xtq.mjs";
import "../index-CeBbry0R.mjs";
//#region src/recipes/interactive-auth.d.ts

@@ -4,0 +4,0 @@ interface CreateInteractiveAuthOptions {

import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
import { isAnonymousRpcMethod } from "../constants.mjs";
import { n as defineRpcFunction } from "../define-BLWPsH6y.mjs";
import { t as getInternalContext } from "../context-DY72brRI.mjs";
import { t as getInternalContext } from "../context-C9Cgm1hP.mjs";
import { a as verifyAuthToken, n as exchangeTempAuthCode, r as getTempAuthCode, t as buildOtpAuthUrl } from "../state-WX3HT5a3.mjs";
import * as v from "valibot";
import { t as s } from "../simple-schema-DQPZrAaZ.mjs";
//#region src/recipes/interactive-auth.ts

@@ -57,8 +57,8 @@ function defaultBanner(info) {

jsonSerializable: true,
args: [v.object({
authToken: v.string(),
ua: v.string(),
origin: v.string()
args: [s.object({
authToken: s.string(),
ua: s.string(),
origin: s.string()
})],
returns: v.object({ isTrusted: v.boolean() }),
returns: s.object({ isTrusted: s.boolean() }),
handler(params) {

@@ -80,8 +80,8 @@ const session = context.rpc.getCurrentRpcSession();

jsonSerializable: true,
args: [v.object({
code: v.string(),
ua: v.string(),
origin: v.string()
args: [s.object({
code: s.string(),
ua: s.string(),
origin: s.string()
})],
returns: v.object({ authToken: v.nullable(v.string()) }),
returns: s.object({ authToken: s.nullable(s.string()) }),
handler(params) {

@@ -100,3 +100,3 @@ const session = context.rpc.getCurrentRpcSession();

args: [],
returns: v.void(),
returns: s.void(),
async handler() {

@@ -103,0 +103,0 @@ const token = context.rpc.getCurrentRpcSession()?.meta.clientAuthToken;

@@ -1,2 +0,2 @@

import { a as getDefinitionsWithDumps, c as StaticRpcDumpManifest, d as StaticRpcDumpManifestValue, f as StaticRpcDumpSerialization, i as dumpFunctions, l as StaticRpcDumpManifestQueryEntry, n as serializeDumpError, o as StaticRpcDumpCollection, p as collectStaticRpcDump, r as createClientFromDump, s as StaticRpcDumpFile, t as reviveDumpError, u as StaticRpcDumpManifestStaticEntry } from "../index-YDSJgsyG.mjs";
import { a as getDefinitionsWithDumps, c as StaticRpcDumpManifest, d as StaticRpcDumpManifestValue, f as StaticRpcDumpSerialization, i as dumpFunctions, l as StaticRpcDumpManifestQueryEntry, n as serializeDumpError, o as StaticRpcDumpCollection, p as collectStaticRpcDump, r as createClientFromDump, s as StaticRpcDumpFile, t as reviveDumpError, u as StaticRpcDumpManifestStaticEntry } from "../index-vAX6bYuc.mjs";
export { StaticRpcDumpCollection, StaticRpcDumpFile, StaticRpcDumpManifest, StaticRpcDumpManifestQueryEntry, StaticRpcDumpManifestStaticEntry, StaticRpcDumpManifestValue, StaticRpcDumpSerialization, collectStaticRpcDump, createClientFromDump, dumpFunctions, getDefinitionsWithDumps, reviveDumpError, serializeDumpError };

@@ -1,2 +0,2 @@

import { a as dumpFunctions, c as serializeDumpError, i as createClientFromDump, o as getDefinitionsWithDumps, s as reviveDumpError, t as collectStaticRpcDump } from "../dump-Cz7yVvsB.mjs";
import { c as getDefinitionsWithDumps, l as reviveDumpError, o as createClientFromDump, s as dumpFunctions, t as collectStaticRpcDump, u as serializeDumpError } from "../dump-CgZShDRB.mjs";
export { collectStaticRpcDump, createClientFromDump, dumpFunctions, getDefinitionsWithDumps, reviveDumpError, serializeDumpError };

@@ -1,3 +0,3 @@

import { C as RpcFunctionType, E as Thenable, S as RpcFunctionSetupResult, T as RpcReturnSchema, _ as RpcFunctionDefinition, a as RpcDefinitionsFilter, b as RpcFunctionDefinitionBase, c as RpcDump, d as RpcDumpDefinition, f as RpcDumpGetter, g as RpcFunctionAgentOptions, h as RpcDumpStore, i as RpcArgsSchema, l as RpcDumpClientOptions, m as RpcDumpRecordError, n as BirpcReturn, o as RpcDefinitionsToFunctions, p as RpcDumpRecord, r as EntriesToObject, s as RpcDefinitionsToFunctionsWithNamespace, t as BirpcFn, u as RpcDumpCollectionOptions, v as RpcFunctionDefinitionAny, w as RpcFunctionsCollector, x as RpcFunctionDefinitionToFunction, y as RpcFunctionDefinitionAnyWithContext } from "../types-CrzNxXKq.mjs";
import { C as RpcCacheManager, S as RpcFunctionsCollectorBase, _ as strictJsonStringify, a as StaticRpcDumpManifestStaticEntry, b as createDefineWrapperWithContext, c as collectStaticRpcDump, d as getDefinitionsWithDumps, f as reviveDumpError, g as STRUCTURED_CLONE_PREFIX, h as validateDefinitions, i as StaticRpcDumpManifestQueryEntry, l as createClientFromDump, m as validateDefinition, n as StaticRpcDumpFile, o as StaticRpcDumpManifestValue, p as serializeDumpError, r as StaticRpcDumpManifest, s as StaticRpcDumpSerialization, t as StaticRpcDumpCollection, u as dumpFunctions, v as getRpcHandler, w as RpcCacheOptions, x as defineRpcFunction, y as getRpcResolvedSetupResult } from "../index-DgsLFhZg.mjs";
export { type BirpcFn, type BirpcReturn, EntriesToObject, RpcArgsSchema, RpcCacheManager, RpcCacheOptions, RpcDefinitionsFilter, RpcDefinitionsToFunctions, RpcDefinitionsToFunctionsWithNamespace, RpcDump, RpcDumpClientOptions, RpcDumpCollectionOptions, RpcDumpDefinition, RpcDumpGetter, RpcDumpRecord, RpcDumpRecordError, RpcDumpStore, RpcFunctionAgentOptions, RpcFunctionDefinition, RpcFunctionDefinitionAny, RpcFunctionDefinitionAnyWithContext, RpcFunctionDefinitionBase, RpcFunctionDefinitionToFunction, RpcFunctionSetupResult, RpcFunctionType, RpcFunctionsCollector, RpcFunctionsCollectorBase, RpcReturnSchema, STRUCTURED_CLONE_PREFIX, StaticRpcDumpCollection, StaticRpcDumpFile, StaticRpcDumpManifest, StaticRpcDumpManifestQueryEntry, StaticRpcDumpManifestStaticEntry, StaticRpcDumpManifestValue, StaticRpcDumpSerialization, Thenable, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions };
import { C as RpcFunctionType, E as Thenable, S as RpcFunctionSetupResult, T as RpcReturnSchema, _ as RpcFunctionDefinition, a as RpcDefinitionsFilter, b as RpcFunctionDefinitionBase, c as RpcDump, d as RpcDumpDefinition, f as RpcDumpGetter, g as RpcFunctionAgentOptions, h as RpcDumpStore, i as RpcArgsSchema, l as RpcDumpClientOptions, m as RpcDumpRecordError, n as BirpcReturn, o as RpcDefinitionsToFunctions, p as RpcDumpRecord, r as EntriesToObject, s as RpcDefinitionsToFunctionsWithNamespace, t as BirpcFn, u as RpcDumpCollectionOptions, v as RpcFunctionDefinitionAny, w as RpcFunctionsCollector, x as RpcFunctionDefinitionToFunction, y as RpcFunctionDefinitionAnyWithContext } from "../types-CnJSgRVa.mjs";
import { C as defineRpcFunction, E as RpcCacheOptions, S as createDefineWrapperWithContext, T as RpcCacheManager, _ as validateRpcReturn, a as StaticRpcDumpManifestStaticEntry, b as getRpcHandler, c as collectStaticRpcDump, d as getDefinitionsWithDumps, f as reviveDumpError, g as validateRpcArgs, h as validateDefinitions, i as StaticRpcDumpManifestQueryEntry, l as createClientFromDump, m as validateDefinition, n as StaticRpcDumpFile, o as StaticRpcDumpManifestValue, p as serializeDumpError, r as StaticRpcDumpManifest, s as StaticRpcDumpSerialization, t as StaticRpcDumpCollection, u as dumpFunctions, v as STRUCTURED_CLONE_PREFIX, w as RpcFunctionsCollectorBase, x as getRpcResolvedSetupResult, y as strictJsonStringify } from "../index-Dbw1p5ch.mjs";
export { type BirpcFn, type BirpcReturn, EntriesToObject, RpcArgsSchema, RpcCacheManager, RpcCacheOptions, RpcDefinitionsFilter, RpcDefinitionsToFunctions, RpcDefinitionsToFunctionsWithNamespace, RpcDump, RpcDumpClientOptions, RpcDumpCollectionOptions, RpcDumpDefinition, RpcDumpGetter, RpcDumpRecord, RpcDumpRecordError, RpcDumpStore, RpcFunctionAgentOptions, RpcFunctionDefinition, RpcFunctionDefinitionAny, RpcFunctionDefinitionAnyWithContext, RpcFunctionDefinitionBase, RpcFunctionDefinitionToFunction, RpcFunctionSetupResult, RpcFunctionType, RpcFunctionsCollector, RpcFunctionsCollectorBase, RpcReturnSchema, STRUCTURED_CLONE_PREFIX, StaticRpcDumpCollection, StaticRpcDumpFile, StaticRpcDumpManifest, StaticRpcDumpManifestQueryEntry, StaticRpcDumpManifestStaticEntry, StaticRpcDumpManifestValue, StaticRpcDumpSerialization, Thenable, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions, validateRpcArgs, validateRpcReturn };

@@ -1,5 +0,5 @@

import { a as dumpFunctions$1, c as serializeDumpError$1, d as hash, i as createClientFromDump$1, l as validateDefinition, n as getRpcHandler, o as getDefinitionsWithDumps$1, r as getRpcResolvedSetupResult, s as reviveDumpError$1, t as collectStaticRpcDump$1, u as validateDefinitions } from "../dump-Cz7yVvsB.mjs";
import { t as diagnostics } from "../diagnostics-BXwBQmoN.mjs";
import { a as validateRpcReturn, c as getDefinitionsWithDumps$1, d as validateDefinition, f as validateDefinitions, i as validateRpcArgs, l as reviveDumpError$1, n as getRpcHandler, o as createClientFromDump$1, p as hash, r as getRpcResolvedSetupResult, s as dumpFunctions$1, t as collectStaticRpcDump$1, u as serializeDumpError$1 } from "../dump-CgZShDRB.mjs";
import { t as diagnostics } from "../diagnostics-hwjXp_UV.mjs";
import { n as defineRpcFunction, t as createDefineWrapperWithContext } from "../define-BLWPsH6y.mjs";
import { n as strictJsonStringify, t as STRUCTURED_CLONE_PREFIX } from "../serialization-DpLXCy13.mjs";
import { n as strictJsonStringify, t as STRUCTURED_CLONE_PREFIX } from "../serialization-C8Mnw9hK.mjs";
//#region src/rpc/cache.ts

@@ -133,2 +133,2 @@ /**

//#endregion
export { RpcCacheManager, RpcFunctionsCollectorBase, STRUCTURED_CLONE_PREFIX, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions };
export { RpcCacheManager, RpcFunctionsCollectorBase, STRUCTURED_CLONE_PREFIX, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions, validateRpcArgs, validateRpcReturn };

@@ -1,2 +0,2 @@

import { n as createWsRpcChannel, t as WsRpcChannelOptions } from "../../ws-client-DSXiI2h7.mjs";
import { n as createWsRpcChannel, t as WsRpcChannelOptions } from "../../ws-client-B6Tnr0ic.mjs";
export { WsRpcChannelOptions, createWsRpcChannel };
import { DEVFRAME_AUTH_TOKEN_QUERY_PARAM } from "../../constants.mjs";
import { n as strictJsonStringify } from "../../serialization-DpLXCy13.mjs";
import { n as strictJsonStringify } from "../../serialization-C8Mnw9hK.mjs";
import { n as structuredCloneStringify, t as structuredCloneParse } from "../../structured-clone-CbAV5rFI.mjs";

@@ -4,0 +4,0 @@ //#region src/rpc/transports/ws-client.ts

@@ -1,2 +0,2 @@

import { a as isAllowedOrigin, i as attachWsRpcTransport, n as WsRpcTransport, o as isLoopbackHostname, r as WsRpcTransportOptions, t as DevframeNodeRpcSessionMeta } from "../../ws-server-9-wn7MNQ.mjs";
import { a as isAllowedOrigin, i as attachWsRpcTransport, n as WsRpcTransport, o as isLoopbackHostname, r as WsRpcTransportOptions, t as DevframeNodeRpcSessionMeta } from "../../ws-server-D_Vjtums.mjs";
export { DevframeNodeRpcSessionMeta, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport, isAllowedOrigin, isLoopbackHostname };

@@ -1,2 +0,2 @@

import { n as strictJsonStringify } from "../../serialization-DpLXCy13.mjs";
import { n as strictJsonStringify } from "../../serialization-C8Mnw9hK.mjs";
import { n as structuredCloneStringify, t as structuredCloneParse } from "../../structured-clone-CbAV5rFI.mjs";

@@ -3,0 +3,0 @@ import { createServer } from "node:http";

@@ -1,4 +0,4 @@

import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "../devframe-Ckhf3kFw.mjs";
import { g as RpcFunctionAgentOptions } from "../types-CrzNxXKq.mjs";
import { t as DevframeNodeRpcSessionMeta } from "../ws-server-9-wn7MNQ.mjs";
export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, ConnectionMeta, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDockDefaults, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeRuntime, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeServiceId, DevframeServiceOf, DevframeServicesHost, DevframeServicesRegistry, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSpaOptions, DevframeStorageScope, DevframeViewHost, DevframeWsOptions, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, McpRouteOptions, PartialWithoutId, RpcBroadcastOptions, type RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedRpcFn, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, Thenable, defineDevframe };
import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as DevframeAgentHostEvents, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as AgentToolProvider, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, Mt as EventUnsubscribe, N as ScopedRpcFn, Nt as EventsMap, O as DevframeSettings, Ot as AgentToolProviderHandle, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventEmitter, k as DevframeSettingsRegistry, kt as DevframeAgentHost, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "../devframe-Dsjn_Xtq.mjs";
import { g as RpcFunctionAgentOptions } from "../types-CnJSgRVa.mjs";
import { t as DevframeNodeRpcSessionMeta } from "../ws-server-D_Vjtums.mjs";
export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, AgentToolProvider, AgentToolProviderHandle, ConnectionMeta, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDockDefaults, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeRuntime, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeServiceId, DevframeServiceOf, DevframeServicesHost, DevframeServicesRegistry, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSpaOptions, DevframeStorageScope, DevframeViewHost, DevframeWsOptions, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, McpRouteOptions, PartialWithoutId, RpcBroadcastOptions, type RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedRpcFn, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, Thenable, defineDevframe };

@@ -1,2 +0,2 @@

import { jt as EventsMap, kt as EventEmitter } from "../devframe-Ckhf3kFw.mjs";
import { Nt as EventsMap, jt as EventEmitter } from "../devframe-Dsjn_Xtq.mjs";
//#region src/utils/events.d.ts

@@ -3,0 +3,0 @@ /**

@@ -1,2 +0,2 @@

import { _t as SharedStateOptions, dt as ImmutableArray, ft as ImmutableMap, gt as SharedStateEvents, ht as SharedState, mt as ImmutableSet, pt as ImmutableObject, ut as Immutable, vt as SharedStatePatch, yt as createSharedState } from "../devframe-Ckhf3kFw.mjs";
import { _t as SharedStateOptions, dt as ImmutableArray, ft as ImmutableMap, gt as SharedStateEvents, ht as SharedState, mt as ImmutableSet, pt as ImmutableObject, ut as Immutable, vt as SharedStatePatch, yt as createSharedState } from "../devframe-Dsjn_Xtq.mjs";
export { Immutable, ImmutableArray, ImmutableMap, ImmutableObject, ImmutableSet, SharedState, SharedStateEvents, SharedStateOptions, SharedStatePatch, createSharedState };

@@ -1,2 +0,2 @@

import { at as StreamReader, ct as createStreamReader, it as StreamErrorPayload, lt as createStreamSink, nt as CreateStreamReaderOptions, ot as StreamSink, rt as CreateStreamSinkOptions, st as StreamSinkEvents, tt as BufferedChunk } from "../devframe-Ckhf3kFw.mjs";
import { at as StreamReader, ct as createStreamReader, it as StreamErrorPayload, lt as createStreamSink, nt as CreateStreamReaderOptions, ot as StreamSink, rt as CreateStreamSinkOptions, st as StreamSinkEvents, tt as BufferedChunk } from "../devframe-Dsjn_Xtq.mjs";
export { BufferedChunk, CreateStreamReaderOptions, CreateStreamSinkOptions, StreamErrorPayload, StreamReader, StreamSink, StreamSinkEvents, createStreamReader, createStreamSink };
{
"name": "devframe",
"type": "module",
"version": "0.7.16",
"version": "0.8.0",
"description": "Framework for building generic devframes",

@@ -45,2 +45,3 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>",

"./types": "./dist/types/index.mjs",
"./utils/agent-tool-name": "./dist/utils/agent-tool-name.mjs",
"./utils/colors": "./dist/utils/colors.mjs",

@@ -54,2 +55,3 @@ "./utils/crypto-token": "./dist/utils/crypto-token.mjs",

"./utils/promise": "./dist/utils/promise.mjs",
"./utils/simple-schema": "./dist/utils/simple-schema.mjs",
"./utils/scope": "./dist/utils/scope.mjs",

@@ -64,3 +66,7 @@ "./utils/serve-static": "./dist/utils/serve-static.mjs",

"types": "./dist/index.d.mts",
"bin": {
"devframe": "./bin/devframe.mjs"
},
"files": [
"bin",
"dist",

@@ -70,9 +76,13 @@ "skills"

"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"cac": "^7.0.0"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"@modelcontextprotocol/client": {
"optional": true
},
"@modelcontextprotocol/server": {
"optional": true
},
"cac": {

@@ -83,3 +93,3 @@ "optional": true

"dependencies": {
"@valibot/to-json-schema": "^1.7.1",
"@standard-schema/spec": "^1.1.0",
"birpc": "^4.0.0",

@@ -92,7 +102,7 @@ "crossws": "^0.4.10",

"pathe": "^2.0.3",
"ufo": "^1.6.4",
"valibot": "^1.4.2"
"ufo": "^1.6.4"
},
"devDependencies": {
"@modelcontextprotocol/sdk": "^1.30.0",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"cac": "^7.0.0",

@@ -112,2 +122,3 @@ "get-port-please": "^3.2.0",

"ua-parser-modern": "^0.1.1",
"valibot": "^1.4.2",
"whenexpr": "^0.1.2",

@@ -114,0 +125,0 @@ "ws": "^8.21.1"

@@ -488,3 +488,3 @@ ---

`@modelcontextprotocol/sdk` is a peer dependency. The CLI adapter also exposes `my-devframe mcp` — route host logs to stderr (stdout is the MCP transport). Safety classifications (`'read' | 'action' | 'destructive'`) drive MCP hint annotations that agent clients use to prompt for confirmation.
`@modelcontextprotocol/server` is a peer dependency. The CLI adapter also exposes `my-devframe mcp` — route host logs to stderr (stdout is the MCP transport). Safety classifications (`'read' | 'action' | 'destructive'`) drive MCP hint annotations that agent clients use to prompt for confirmation.

@@ -491,0 +491,0 @@ ## Author SPA

import { a as diagnostics } from "./storage-gEnj9ASQ.mjs";
import { n as createHostContext } from "./host-h3-CgOetlNE.mjs";
import { Diagnostic } from "nostics";
import { join } from "pathe";
import process from "node:process";
import { homedir } from "node:os";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { toJsonSchema } from "@valibot/to-json-schema";
//#region src/adapters/mcp/stringify.ts
/**
* JSON-coercing serializer for MCP text payloads.
*
* MCP carries tool results and resource reads as plain text over a
* JSON-RPC transport, so we cannot use the `s:`-prefixed structured-clone
* format the WS RPC transport falls back to for non-JSON values. Instead,
* we coerce common non-JSON types into JSON-friendly forms so the LLM
* client sees something useful instead of `[object Object]`.
*
* Coercions:
* - `BigInt` → `"123n"`
* - `Date` → ISO string (via the native `toJSON`)
* - `Map` → `{ __type: 'Map', entries: [[k, v], …] }`
* - `Set` → `{ __type: 'Set', entries: [v, …] }`
* - `Error` → `{ name, message, stack, cause? }` (cause recurses)
* - `Function` → `"[Function: name]"`
* - `Symbol` → `value.toString()`
* - cycles → `"[Circular]"`
*/
function stringifyForMcp(value) {
if (value === void 0) return "undefined";
if (typeof value === "string") return value;
const seen = /* @__PURE__ */ new WeakSet();
return JSON.stringify(value, (_key, val) => {
if (typeof val === "bigint") return `${val}n`;
if (val instanceof Error) {
const out = {
name: val.name,
message: val.message,
stack: val.stack
};
if (val.cause !== void 0) out.cause = val.cause;
return out;
}
if (val instanceof Map) return {
__type: "Map",
entries: [...val.entries()]
};
if (val instanceof Set) return {
__type: "Set",
entries: [...val]
};
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
if (typeof val === "symbol") return val.toString();
if (val !== null && typeof val === "object") {
if (seen.has(val)) return "[Circular]";
seen.add(val);
}
return val;
}, 2);
}
/**
* Format a thrown value for an MCP `isError` text payload.
*
* A nostics `Diagnostic` (every coded devframe error) becomes structured
* JSON — `{ error: { code, message, fix?, docs? } }` — so an agent receives
* the actionable next step (`fix`) and the docs URL instead of a bare
* message string. Other errors surface `Error.name`/`message`, plus one
* level of `cause.message` so context isn't dropped silently.
*/
function formatMcpError(error) {
if (error instanceof Diagnostic) return JSON.stringify({ error: {
code: error.code,
message: error.message,
...error.fix ? { fix: error.fix } : {},
...error.docs ? { docs: error.docs } : {}
} }, null, 2);
if (!(error instanceof Error)) return String(error);
const cause = error.cause;
const causeText = cause instanceof Error ? ` (cause: ${cause.message})` : cause !== void 0 ? ` (cause: ${String(cause)})` : "";
return `${error.name}: ${error.message}${causeText}`;
}
//#endregion
//#region src/adapters/mcp/to-json-schema.ts
const FALLBACK_OBJECT_SCHEMA = Object.freeze({
type: "object",
additionalProperties: true
});
/**
* Convert a valibot return schema to JSON Schema.
* @internal
*/
function valibotReturnToJsonSchema(schema) {
if (!schema) return void 0;
try {
return toJsonSchema(schema);
} catch {
return FALLBACK_OBJECT_SCHEMA;
}
}
/**
* Convert positional RPC args schemas to a single MCP-friendly object
* schema. When the RPC declares `args: [v.object(...)]`, unwrap the
* single-object schema directly (nicer agent UX than `{ arg0: {...} }`).
*
* Returns `undefined` when there are no args (the MCP SDK treats this
* as `{ type: 'object', properties: {} }`).
* @internal
*/
function valibotArgsToJsonSchema(args) {
if (!args || args.length === 0) return {
schema: {
type: "object",
properties: {}
},
unwrapped: false
};
if (args.length === 1) {
const inner = safeToJsonSchema(args[0]);
if (isObjectJsonSchema(inner)) return {
schema: inner,
unwrapped: true
};
}
const properties = {};
const required = [];
for (let i = 0; i < args.length; i++) {
const key = `arg${i}`;
properties[key] = safeToJsonSchema(args[i]);
required.push(key);
}
return {
schema: {
type: "object",
properties,
required,
additionalProperties: false
},
unwrapped: false
};
}
function safeToJsonSchema(schema) {
try {
return toJsonSchema(schema);
} catch {
return FALLBACK_OBJECT_SCHEMA;
}
}
function isObjectJsonSchema(value) {
return !!value && typeof value === "object" && value.type === "object";
}
//#endregion
//#region src/adapters/mcp/build-server.ts
/**
* Wire an MCP {@link Server} to a devframe context. Returns the server
* plus a disposal function for the subscriptions it sets up. The
* transport is the caller's responsibility — `createMcpServer` connects
* stdio; tests can connect an {@link InMemoryTransport} instead.
*
* @internal
*/
function buildMcpServerFromContext(ctx, options) {
const server = new Server({
name: options.serverName,
version: options.serverVersion
}, { capabilities: {
tools: { listChanged: true },
resources: { listChanged: true }
} });
registerToolHandlers(server, ctx);
registerResourceHandlers(server, ctx, options.exposeSharedState);
const notify = (method) => {
server.notification({ method }).catch(() => {});
};
const offManifest = ctx.agent.events.on("agent:manifest:changed", () => {
notify("notifications/tools/list_changed");
notify("notifications/resources/list_changed");
});
const offKeyAdded = ctx.rpc.sharedState.onKeyAdded(() => {
notify("notifications/resources/list_changed");
});
return {
server,
dispose: () => {
offManifest();
offKeyAdded();
}
};
}
/**
* Build an MCP server over the agent surface of a devframe definition.
* Currently supports `stdio` transport only.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
async function createMcpServer(definition, options = {}) {
const transport = options.transport ?? "stdio";
if (transport !== "stdio") throw diagnostics.DF0017({
transport,
reason: "Only stdio transport is supported in this release."
});
const ctx = await createHostContext({
cwd: process.cwd(),
mode: "dev",
host: {
mountStatic: () => {},
resolveOrigin: () => "mcp://devframe",
getStorageDir: (scope) => {
if (scope === "workspace") return join(process.cwd(), ".devframe");
if (scope === "project") return join(process.cwd(), `node_modules/.${definition.id}/devframe`);
return join(homedir(), `.${definition.id}/devframe`);
}
}
});
await definition.setup(ctx);
const { server, dispose } = buildMcpServerFromContext(ctx, {
serverName: options.serverName ?? `${definition.id} (devframe)`,
serverVersion: options.serverVersion ?? definition.version ?? "0.0.0",
exposeSharedState: options.exposeSharedState ?? true
});
const { startStdioTransport } = await import("./transports-BmDVn4SF.mjs");
let stop;
try {
stop = await startStdioTransport(server);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport,
reason,
cause: error
});
}
options.onReady?.({ transport: "stdio" });
return { async stop() {
dispose();
await stop();
} };
}
function registerToolHandlers(server, ctx) {
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: ctx.agent.list().tools.map((tool) => projectTool(tool, ctx)) };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const tool = ctx.agent.getTool(name);
const outputSchema = tool ? tool.outputSchema ?? computeOutputSchema(tool, ctx) : void 0;
const result = await ctx.agent.invoke(name, args ?? {});
return {
content: [{
type: "text",
text: stringifyForMcp(result)
}],
...outputSchema ? { structuredContent: result } : {}
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error invoking "${name}": ${formatMcpError(error)}`
}]
};
}
});
}
function registerResourceHandlers(server, ctx, exposeSharedState) {
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const resources = ctx.agent.list().resources.map((resource) => ({
uri: resource.uri,
name: resource.name,
description: resource.description,
mimeType: resource.mimeType
}));
if (exposeSharedState !== false) {
const filter = typeof exposeSharedState === "function" ? exposeSharedState : () => true;
for (const key of ctx.rpc.sharedState.keys()) {
if (!filter(key)) continue;
resources.push({
uri: `devframe://state/${encodeURIComponent(key)}`,
name: key,
description: `Shared state: ${key}`,
mimeType: "application/json"
});
}
}
return { resources };
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
const parsed = parseResourceUri(uri);
if (parsed.kind === "resource") {
const content = await ctx.agent.read(parsed.id);
return { contents: [{
uri,
mimeType: content.mimeType ?? "application/json",
text: content.text ?? stringifyForMcp(content.json)
}] };
}
if (parsed.kind === "state") return { contents: [{
uri,
mimeType: "application/json",
text: stringifyForMcp((await ctx.rpc.sharedState.get(parsed.key)).value())
}] };
throw new Error(`[devframe/mcp] unknown resource URI "${uri}"`);
});
}
function projectTool(tool, ctx) {
const inputSchema = tool.inputSchema ?? computeInputSchema(tool, ctx);
const outputSchema = tool.outputSchema ?? computeOutputSchema(tool, ctx);
return {
name: tool.id,
title: tool.title,
description: tool.description,
inputSchema,
...outputSchema ? { outputSchema } : {},
annotations: {
title: tool.title,
readOnlyHint: tool.safety === "read",
destructiveHint: tool.safety === "destructive"
}
};
}
function computeInputSchema(tool, ctx) {
if (tool.kind !== "rpc" || !tool.rpcName) return {
type: "object",
properties: {}
};
const def = ctx.rpc.definitions.get(tool.rpcName);
if (!def) return {
type: "object",
properties: {}
};
const args = def.args;
return valibotArgsToJsonSchema(args).schema;
}
function computeOutputSchema(tool, ctx) {
if (tool.kind !== "rpc" || !tool.rpcName) return void 0;
const def = ctx.rpc.definitions.get(tool.rpcName);
if (!def) return void 0;
return valibotReturnToJsonSchema(def.returns);
}
function parseResourceUri(uri) {
const match = uri.match(/^devframe:\/\/(resource|state)\/(.+)$/);
if (!match) return { kind: "unknown" };
const [, kind, rest] = match;
const decoded = decodeURIComponent(rest);
if (kind === "resource") return {
kind: "resource",
id: decoded
};
return {
kind: "state",
key: decoded
};
}
//#endregion
export { createMcpServer as n, buildMcpServerFromContext as t };
import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { createBuild } from "./adapters/build.mjs";
import { n as resolveDevServerPort, t as createDevServer } from "./dev-BCeaQuGE.mjs";
import process from "node:process";
import cac from "cac";
import { safeParse } from "valibot";
//#region src/adapters/flags.ts
/**
* Identity helper that preserves the literal schema-map type — use this
* so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
*
* ```ts
* const appFlags = defineCliFlags({
* depth: v.pipe(v.number(), v.integer()),
* config: v.optional(v.string()),
* })
*
* defineDevframe({
* cli: { flags: appFlags },
* setup(ctx, info) {
* const flags = info.flags as InferCliFlags<typeof appFlags>
* flags.depth // number
* flags.config // string | undefined
* },
* })
* ```
*/
function defineCliFlags(flags) {
return flags;
}
/**
* Best-effort probe of a valibot schema to decide whether the
* corresponding CAC option takes a value. Unwraps `optional` / `nullable`
* / `nullish` / `default` / `pipe` wrappers then matches on the inner
* type's kind.
*/
function getSchemaKind(schema) {
let current = schema;
while (current) {
const kind = current.type;
if (kind === "optional" || kind === "nullable" || kind === "nullish" || kind === "undefined") {
current = current.wrapped ?? current.inner;
continue;
}
if (kind === "pipe" && Array.isArray(current.pipe) && current.pipe.length > 0) {
current = current.pipe[0];
continue;
}
return kind;
}
return "unknown";
}
/** Whether the CAC option for this schema should be a boolean flag. */
function isBooleanFlag(schema) {
return getSchemaKind(schema) === "boolean";
}
/** Validate and coerce the raw cac-parsed bag against a {@link CliFlagsSchema}. */
function parseCliFlags(schema, raw) {
const flags = {};
const issues = [];
for (const [key, fieldSchema] of Object.entries(schema)) {
const result = safeParse(fieldSchema, raw[key]);
if (result.success) flags[key] = result.output;
else issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`);
}
for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value;
return issues.length ? {
flags,
issues
} : { flags };
}
function toKebab(camel) {
return camel.replaceAll(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
/** Kebab-case a schema key for CAC option registration. */
function flagKeyToOption(camel) {
return toKebab(camel);
}
//#endregion
//#region src/adapters/cac.ts
/**
* Wrap a {@link DevframeDefinition} in a `cac`-powered command-line
* interface exposing `dev` / `build` / `mcp` subcommands.
*
* Requires the optional `cac` peer dependency.
*/
function createCac(d, options = {}) {
const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
const defaultHost = d.cli?.host ?? "localhost";
const cli = cac(d.cli?.command ?? d.id);
const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]");
if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
const optionName = flagKeyToOption(key);
const description = schema.description ?? "";
if (isBooleanFlag(schema)) devCommand.option(`--${optionName}`, description);
else devCommand.option(`--${optionName} <value>`, description);
}
devCommand.action(async (_args, rawFlags) => {
const flags = resolveTypedFlags(d, rawFlags);
const host = flags.host ?? defaultHost;
const port = flags.port ?? await resolveDevServerPort(d, {
host,
defaultPort
});
const mcp = flags.mcp;
await createDevServer(d, {
host,
port,
flags,
mcp,
onReady: options.onReady
});
});
if (d.capabilities?.build !== false) cli.command("build", "Build a self-contained static deploy of the devframe").option("--out-dir <outDir>", "Output directory", { default: "dist-static" }).option("--base <base>", "URL base", { default: "/" }).option("--pretty", "Pretty-print dump JSON (larger on disk)").action(async (flags) => {
await createBuild(d, {
outDir: flags.outDir,
base: flags.base,
pretty: flags.pretty
});
});
cli.command("mcp", "Start an MCP server exposing agent-facing tools (stdio) [experimental]").action(async () => {
const { createMcpServer } = await import("./adapters/mcp.mjs");
await createMcpServer(d, {
transport: "stdio",
onReady: ({ transport }) => {
console.error(`[devframe] "${d.id}" MCP server ready (${transport})`);
}
});
});
d.cli?.configure?.(cli);
options.configureCli?.(cli);
cli.help();
cli.version("0.0.0");
return {
cli,
async parse(argv = process.argv) {
cli.parse(argv, { run: false });
await cli.runMatchedCommand();
}
};
}
function resolveTypedFlags(d, raw) {
if (!d.cli?.flags) return raw;
const { flags, issues } = parseCliFlags(d.cli.flags, raw);
if (issues?.length) {
for (const issue of issues) console.error(colors.red`[devframe] invalid flag — ${issue}`);
process.exit(1);
}
return flags;
}
//#endregion
export { defineCliFlags as n, parseCliFlags as r, createCac as t };
import { b as DevframeNodeContext, ht as SharedState } from "./devframe-Ckhf3kFw.mjs";
//#region src/node/hub-internals/context.d.ts
interface InternalAnonymousAuthStorage {
trusted: Record<string, {
authToken: string;
ua: string;
origin: string;
timestamp: number;
} | undefined>;
}
interface RemoteTokenRecord {
dockId: string;
/** Dock URL origin — matched against WS handshake `Origin` header when `originLock` is on. */
origin: string;
originLock: boolean;
}
interface DevframeInternalContext {
storage: {
auth: SharedState<InternalAnonymousAuthStorage>;
};
/**
* Revoke an auth token: remove from storage and notify all connected clients
* using this token that they are no longer trusted.
*/
revokeAuthToken: (token: string) => Promise<void>;
/**
* Session-only tokens issued to remote-UI iframe docks. Not persisted —
* regenerated on every dev-server restart.
*/
remoteTokens: Map<string, RemoteTokenRecord>;
allocateRemoteToken: (dockId: string, origin: string, originLock: boolean) => string;
revokeRemoteToken: (token: string) => void;
revokeRemoteTokensForDock: (dockId: string) => void;
/**
* Returns true if `token` is a valid remote token and, when `originLock` is
* on, `requestOrigin` matches the recorded dock origin.
*/
isRemoteTokenTrusted: (token: string, requestOrigin?: string) => boolean;
/**
* Populated by `createWsServer` once the WS port is bound. Consumed by the
* docks host when enriching remote iframe URLs with a connection descriptor.
*/
wsEndpoint?: {
/** Full `ws://` or `wss://` URL with host and port. */
url: string;
};
}
declare const internalContextMap: WeakMap<DevframeNodeContext, DevframeInternalContext>;
declare function getInternalContext(context: DevframeNodeContext): DevframeInternalContext;
//#endregion
export { internalContextMap as a, getInternalContext as i, InternalAnonymousAuthStorage as n, RemoteTokenRecord as r, DevframeInternalContext as t };
import { t as createStorage } from "./storage-gEnj9ASQ.mjs";
import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-BtQDKTp7.mjs";
import { join } from "pathe";
//#region src/node/hub-internals/context.ts
const internalContextMap = /* @__PURE__ */ new WeakMap();
function getInternalContext(context) {
if (!internalContextMap.has(context)) {
const storage = createStorage({
filepath: join(context.host.getStorageDir("global"), "auth.json"),
initialValue: { trusted: {} }
});
const remoteTokens = /* @__PURE__ */ new Map();
function revokeRemoteToken(token) {
if (!remoteTokens.delete(token)) return;
revokeActiveConnectionsForToken(context, token);
}
const internalContext = {
storage: { auth: storage },
revokeAuthToken: (token) => revokeAuthToken(context, storage, token),
remoteTokens,
allocateRemoteToken(dockId, origin, originLock) {
const token = randomToken();
remoteTokens.set(token, {
dockId,
origin,
originLock
});
return token;
},
revokeRemoteToken,
revokeRemoteTokensForDock(dockId) {
const tokensToRevoke = [];
for (const [token, record] of remoteTokens) if (record.dockId === dockId) tokensToRevoke.push(token);
for (const token of tokensToRevoke) revokeRemoteToken(token);
},
isRemoteTokenTrusted(token, requestOrigin) {
const record = remoteTokens.get(token);
if (!record) return false;
if (!record.originLock) return true;
return !!requestOrigin && record.origin === requestOrigin;
}
};
internalContextMap.set(context, internalContext);
}
return internalContextMap.get(context);
}
//#endregion
export { internalContextMap as n, getInternalContext as t };
import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
import { a as diagnostics } from "./storage-gEnj9ASQ.mjs";
import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-CgOetlNE.mjs";
import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-DykHCza_.mjs";
import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
import { t as open } from "./open-Deb5xmIT.mjs";
import { mountStaticHandler } from "./utils/serve-static.mjs";
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
import { resolve } from "pathe";
import process$1 from "node:process";
import { networkInterfaces } from "node:os";
import { H3 } from "h3";
import { createServer } from "node:net";
import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash } from "ufo";
//#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs
const unsafePorts = /* @__PURE__ */ new Set([
1,
7,
9,
11,
13,
15,
17,
19,
20,
21,
22,
23,
25,
37,
42,
43,
53,
69,
77,
79,
87,
95,
101,
102,
103,
104,
109,
110,
111,
113,
115,
117,
119,
123,
135,
137,
139,
143,
161,
179,
389,
427,
465,
512,
513,
514,
515,
526,
530,
531,
532,
540,
548,
554,
556,
563,
587,
601,
636,
989,
990,
993,
995,
1719,
1720,
1723,
2049,
3659,
4045,
5060,
5061,
6e3,
6566,
6665,
6666,
6667,
6668,
6669,
6697,
10080
]);
function isUnsafePort(port) {
return unsafePorts.has(port);
}
function isSafePort(port) {
return !isUnsafePort(port);
}
var GetPortError = class extends Error {
constructor(message, opts) {
super(message, opts);
this.message = message;
}
name = "GetPortError";
};
function _log(verbose, message) {
if (verbose) console.log(`[get-port] ${message}`);
}
function _generateRange(from, to) {
if (to < from) return [];
const r = [];
for (let index = from; index <= to; index++) r.push(index);
return r;
}
function _tryPort(port, host) {
return new Promise((resolve) => {
const server = createServer();
server.unref();
server.on("error", () => {
resolve(false);
});
server.listen({
port,
host
}, () => {
const { port: port2 } = server.address();
server.close(() => {
resolve(isSafePort(port2) && port2);
});
});
});
}
function _getLocalHosts(additional) {
const hosts = new Set(additional);
for (const _interface of Object.values(networkInterfaces())) for (const config of _interface || []) if (config.address && !config.internal && !config.address.startsWith("fe80::") && !config.address.startsWith("169.254")) hosts.add(config.address);
return [...hosts];
}
async function _findPort(ports, host) {
for (const port of ports) {
const r = await _tryPort(port, host);
if (r) return r;
}
}
function _fmtOnHost(hostname) {
return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host";
}
const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(?<!-)$/;
function _validateHostname(hostname, _public, verbose) {
if (hostname && !HOSTNAME_RE.test(hostname)) {
const fallbackHost = _public ? "0.0.0.0" : "127.0.0.1";
_log(verbose, `Invalid hostname: ${JSON.stringify(hostname)}. Using ${JSON.stringify(fallbackHost)} as fallback.`);
return fallbackHost;
}
return hostname;
}
async function getPort(_userOptions = {}) {
if (typeof _userOptions === "number" || typeof _userOptions === "string") _userOptions = { port: Number.parseInt(_userOptions + "") || 0 };
const _port = Number(_userOptions.port ?? process.env.PORT);
const _userSpecifiedAnyPort = Boolean(_userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length);
const options = {
random: _port === 0,
ports: [],
portRange: [],
alternativePortRange: _userSpecifiedAnyPort ? [] : [3e3, 3100],
verbose: false,
..._userOptions,
port: _port,
host: _validateHostname(_userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose)
};
if (options.random && !_userSpecifiedAnyPort) return getRandomPort(options.host);
const portsToCheck = [
options.port,
...options.ports,
..._generateRange(...options.portRange)
].filter((port) => {
if (!port) return false;
if (!isSafePort(port)) {
_log(options.verbose, `Ignoring unsafe port: ${port}`);
return false;
}
return true;
});
if (portsToCheck.length === 0) portsToCheck.push(3e3);
let availablePort = await _findPort(portsToCheck, options.host);
if (!availablePort && options.alternativePortRange.length > 0) {
availablePort = await _findPort(_generateRange(...options.alternativePortRange), options.host);
if (portsToCheck.length > 0) {
let message = `Unable to find an available port (tried ${portsToCheck.join("-")} ${_fmtOnHost(options.host)}).`;
if (availablePort) message += ` Using alternative port ${availablePort}.`;
_log(options.verbose, message);
}
}
if (!availablePort && _userOptions.random !== false) {
availablePort = await getRandomPort(options.host);
if (availablePort) _log(options.verbose, `Using random port ${availablePort}`);
}
if (!availablePort) {
const triedRanges = [
options.port,
options.portRange.join("-"),
options.alternativePortRange.join("-")
].filter(Boolean).join(", ");
throw new GetPortError(`Unable to find an available port ${_fmtOnHost(options.host)} (tried ${triedRanges})`);
}
return availablePort;
}
async function getRandomPort(host) {
const port = await checkPort(0, host);
if (port === false) throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`);
return port;
}
async function checkPort(port, host = process.env.HOST, verbose) {
if (!host) host = _getLocalHosts([void 0, "0.0.0.0"]);
if (!Array.isArray(host)) return _tryPort(port, host);
for (const _host of host) {
const _port = await _tryPort(port, _host);
if (_port === false) {
if (port < 1024 && verbose) _log(verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost(_host)}`);
return false;
}
if (port === 0 && _port !== 0) port = _port;
}
return port;
}
//#endregion
//#region src/adapters/dev.ts
const DEFAULT_PORT = 9999;
/**
* Resolve the listening port for {@link createDevServer}, honoring the
* definition's `cli.port` / `cli.portRange` / `cli.random` settings.
* Exposed separately so authors who run their own argv parsing can
* resolve a port up-front (to print it, log it, etc.) before starting
* the server.
*/
async function resolveDevServerPort(def, options = {}) {
const host = options.host ?? def.cli?.host ?? "localhost";
const portOptions = {
port: options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT,
host
};
if (def.cli?.portRange) portOptions.portRange = def.cli.portRange;
if (def.cli?.random) portOptions.random = def.cli.random;
return getPort(portOptions);
}
/**
* Start a devframe dev server for a {@link DevframeDefinition} —
* h3 + WebSocket RPC + (optionally) the author's SPA mounted at the
* resolved base path.
*
* When `distDir` is omitted (and `def.cli?.distDir` is unset) the
* server runs in **bridge mode**: only `__connection.json` and the WS
* endpoint are mounted, with no SPA mount. The SPA is expected to be
* hosted elsewhere (e.g. by a parent Vite/Nuxt dev server) — see
* `viteDevBridge({ devMiddleware })`.
*
* Returns the underlying {@link StartedServer} handle so callers can
* close it gracefully (SIGINT, hot-reload, test teardown).
*
* Use this directly when integrating devframe into an existing CLI
* framework (commander, yargs, hand-rolled CAC). For the all-in-one
* `dev` / `build` / `mcp` shell, reach for {@link createCac} instead.
*/
async function createDevServer(def, options = {}) {
const distDir = options.distDir ?? def.cli?.distDir;
const host = options.host ?? def.cli?.host ?? "localhost";
const port = options.port ?? await resolveDevServerPort(def, { host });
const flags = options.flags ?? {};
const basePath = options.basePath ? normalizeBasePath(options.basePath) : resolveBasePath(def, "standalone");
const app = options.app ?? new H3();
const h3Host = createH3DevframeHost({
origin: normalizeHttpServerUrl(host, port),
appName: def.id,
mount: (base, dir) => {
mountStaticHandler(app, base, dir);
}
});
const ctx = await createHostContext({
cwd: process$1.cwd(),
mode: "dev",
host: h3Host
});
const setupInfo = { flags };
await def.setup(ctx, setupInfo);
const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp);
let mcpDispose;
let mcpMeta;
if (mcpConfig) {
const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp");
const mcpPath = joinURL(basePath, mcpRoute);
let mountMcpHttp;
try {
({mountMcpHttp} = await import("./http-_pPbryTQ.mjs"));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport: "http",
reason,
cause: error
});
}
mcpDispose = mountMcpHttp(app, ctx, mcpPath, {
serverName: `${def.id} (devframe)`,
serverVersion: def.version ?? "0.0.0",
exposeSharedState: true,
allowedOrigins: mcpConfig.allowedOrigins
}).dispose;
mcpMeta = { path: mcpRoute };
}
const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath);
const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME);
app.use(connectionMetaPath, () => ({
backend: "websocket",
websocket: meta,
...mcpMeta ? { mcp: mcpMeta } : {}
}));
if (distDir) mountStaticHandler(app, basePath, resolve(distDir));
const authOption = flags.auth === false ? false : options.auth !== void 0 ? options.auth : def.cli?.auth;
let authHandler;
let resolvedAuth;
if (authOption === false) resolvedAuth = false;
else if (typeof authOption === "object") {
authHandler = authOption;
resolvedAuth = authOption;
} else {
authHandler = createInteractiveAuth(ctx);
resolvedAuth = authHandler;
}
const started = await startHttpAndWs({
context: ctx,
host,
port,
app,
path: bindPath,
wsPort,
auth: resolvedAuth,
onReady: async (info) => {
authHandler?.printBanner();
await options.onReady?.(info);
await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser, authHandler);
}
});
if (mcpDispose) {
const closeServer = started.close;
started.close = async () => {
await mcpDispose();
await closeServer();
};
}
return started;
}
/**
* Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
* concrete options, or `undefined` when the MCP route is disabled.
*/
function resolveMcpConfig(mcp) {
if (!mcp) return void 0;
return mcp === true ? {} : mcp;
}
/**
* Resolve the `mcp` entry a `__connection.json` should advertise for a dev
* server started with the given `mcp` option (falling back to `def.cli?.mcp`,
* exactly like {@link createDevServer}), or `undefined` when the route is
* disabled.
*
* Hosted bridges that hand-roll their connection meta (`viteDevBridge`,
* `@devframes/next`'s handler) pass the side-car `port`: the advertised path
* becomes absolute (the side-car mounts at `/`) and the client dials
* `<page-host>:<port><path>`. Without `port` the path stays relative, resolved
* against `__connection.json`'s own location (the same-server default).
*
* @experimental
*/
function resolveMcpConnectionMeta(def, mcp, port) {
const config = resolveMcpConfig(mcp ?? def.cli?.mcp);
if (!config) return void 0;
const route = withoutLeadingSlash(config.path ?? "__mcp");
return port != null ? {
path: withLeadingSlash(route),
port
} : { path: route };
}
/**
* Resolve the three WS connection scenarios from the definition / call-site
* config into a concrete server bind path, optional dedicated port, and the
* `__connection.json` descriptor the browser resolves.
*/
function resolveWsConnection(def, options, basePath) {
const ws = options.ws ?? def.cli?.ws ?? {};
const route = withoutLeadingSlash(ws.route ?? "__devframe_ws");
if (ws.url) return {
bindPath: joinURL(basePath, route),
wsPort: void 0,
meta: ws.url
};
if (ws.port != null) return {
bindPath: withLeadingSlash(route),
wsPort: ws.port,
meta: {
port: ws.port,
path: route
}
};
return {
bindPath: joinURL(basePath, route),
wsPort: void 0,
meta: { path: route }
};
}
async function maybeOpenBrowser(def, flags, origin, override, authHandler) {
const flagsOpen = flags.open;
const cliOpen = def.cli?.open;
const resolved = override ?? flagsOpen ?? cliOpen;
if (resolved === void 0 || resolved === false) return;
const target = typeof resolved === "string" ? withBase(resolved, origin) : origin;
const authorizedTarget = authHandler?.buildOpenUrl?.(target) ?? target;
try {
await open(authorizedTarget);
} catch {}
}
//#endregion
export { resolveDevServerPort as n, resolveMcpConnectionMeta as r, createDevServer as t };

Sorry, the diff of this file is too big to display

import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { defineDiagnostics } from "nostics";
//#region src/rpc/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
reporters: [devframeReporter],
codes: {
DF0019: {
why: (p) => `RPC function "${p.name}" has \`agent\` set but \`jsonSerializable\` is not \`true\` — MCP requires JSON-serializable data.`,
fix: "Set `jsonSerializable: true` if the payload is JSON-safe, or remove `agent` to keep it RPC-only."
},
DF0020: {
why: (p) => `RPC function "${p.name}" declares \`jsonSerializable: true\` but the value at "${p.path}" is a ${p.type}.`,
fix: "Either drop `jsonSerializable: true` (falls back to structured-clone) or change the value to a JSON-safe shape."
},
DF0021: {
why: (p) => `RPC function "${p.name}" is already registered`,
fix: "Use the `force` parameter to overwrite an existing registration."
},
DF0022: { why: (p) => `RPC function "${p.name}" is not registered. Use register() to add new functions.` },
DF0023: { why: (p) => `RPC function "${p.name}" is not registered` },
DF0024: { why: (p) => `Either handler or setup function must be provided for RPC function "${p.name}"` },
DF0025: { why: (p) => `Function "${p.name}" not found in dump store` },
DF0026: { why: (p) => `No dump match for "${p.name}" with args: ${p.args}` },
DF0027: { why: (p) => `Function "${p.name}" with type "${p.type}" cannot have dump configuration. Only "static" and "query" types support dumps.` },
DF0028: {
why: (p) => `Function "${p.name}" with type "${p.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,
fix: "Remove `snapshot: true`, or change the function type to `query`."
}
}
});
//#endregion
export { diagnostics as t };
import { t as diagnostics } from "./diagnostics-BXwBQmoN.mjs";
import { DEVFRAME_RPC_DUMP_DIRNAME } from "./constants.mjs";
import { createHash } from "node:crypto";
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/shared/ohash.D__AXeF1.mjs
function serialize(o) {
return typeof o == "string" ? `'${o}'` : new c().serialize(o);
}
const c = /*@__PURE__*/ function() {
class o {
#t = /* @__PURE__ */ new Map();
compare(t, r) {
const e = typeof t, n = typeof r;
return e === "string" && n === "string" ? t.localeCompare(r) : e === "number" && n === "number" ? t - r : String.prototype.localeCompare.call(this.serialize(t, true), this.serialize(r, true));
}
serialize(t, r) {
if (t === null) return "null";
switch (typeof t) {
case "string": return r ? t : `'${t}'`;
case "bigint": return `${t}n`;
case "object": return this.$object(t);
case "function": return this.$function(t);
}
return String(t);
}
serializeObject(t) {
const r = Object.prototype.toString.call(t);
if (r !== "[object Object]") return this.serializeBuiltInType(r.length < 10 ? `unknown:${r}` : r.slice(8, -1), t);
const e = t.constructor, n = e === Object || e === void 0 ? "" : e.name;
if (n !== "" && globalThis[n] === e) return this.serializeBuiltInType(n, t);
if (typeof t.toJSON == "function") {
const i = t.toJSON();
return n + (i !== null && typeof i == "object" ? this.$object(i) : `(${this.serialize(i)})`);
}
return this.serializeObjectEntries(n, Object.entries(t));
}
serializeBuiltInType(t, r) {
const e = this["$" + t];
if (e) return e.call(this, r);
if (typeof r?.entries == "function") return this.serializeObjectEntries(t, r.entries());
throw new Error(`Cannot serialize ${t}`);
}
serializeObjectEntries(t, r) {
const e = Array.from(r).sort((i, a) => this.compare(i[0], a[0]));
let n = `${t}{`;
for (let i = 0; i < e.length; i++) {
const [a, l] = e[i];
n += `${this.serialize(a, true)}:${this.serialize(l)}`, i < e.length - 1 && (n += ",");
}
return n + "}";
}
$object(t) {
let r = this.#t.get(t);
return r === void 0 && (this.#t.set(t, `#${this.#t.size}`), r = this.serializeObject(t), this.#t.set(t, r)), r;
}
$function(t) {
const r = Function.prototype.toString.call(t);
return r.slice(-15) === "[native code] }" ? `${t.name || ""}()[native]` : `${t.name}(${t.length})${r.replace(/\s*\n\s*/g, "")}`;
}
$Array(t) {
let r = "[";
for (let e = 0; e < t.length; e++) r += this.serialize(t[e]), e < t.length - 1 && (r += ",");
return r + "]";
}
$Date(t) {
try {
return `Date(${t.toISOString()})`;
} catch {
return "Date(null)";
}
}
$ArrayBuffer(t) {
return `ArrayBuffer[${new Uint8Array(t).join(",")}]`;
}
$Set(t) {
return `Set${this.$Array(Array.from(t).sort((r, e) => this.compare(r, e)))}`;
}
$Map(t) {
return this.serializeObjectEntries("Map", t.entries());
}
}
for (const s of [
"Error",
"RegExp",
"URL"
]) o.prototype["$" + s] = function(t) {
return `${s}(${t})`;
};
for (const s of [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array"
]) o.prototype["$" + s] = function(t) {
return `${s}[${t.join(",")}]`;
};
for (const s of ["BigInt64Array", "BigUint64Array"]) o.prototype["$" + s] = function(t) {
return `${s}[${t.join("n,")}${t.length > 0 ? "n" : ""}]`;
};
return o;
}();
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/crypto/node/index.mjs
const e = globalThis.process?.getBuiltinModule?.("crypto")?.hash;
const r = "sha256";
const s = "base64url";
function digest(t) {
if (e) return e(r, t, s);
const o = createHash(r).update(t);
return globalThis.process?.versions?.webcontainer ? o.digest().toString(s) : o.digest(s);
}
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/index.mjs
function hash$1(input) {
return digest(serialize(input));
}
//#endregion
//#region src/utils/hash.ts
/**
* Stable, deterministic hash of any structured-cloneable value.
*/
function hash(value) {
return hash$1(value);
}
//#endregion
//#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
var Node = class {
value;
next;
constructor(value) {
this.value = value;
}
};
var Queue = class {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) return;
this.#head = this.#head.next;
this.#size--;
if (!this.#head) this.#tail = void 0;
return current.value;
}
peek() {
if (!this.#head) return;
return this.#head.value;
}
clear() {
this.#head = void 0;
this.#tail = void 0;
this.#size = 0;
}
get size() {
return this.#size;
}
*[Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
*drain() {
while (this.#head) yield this.dequeue();
}
};
//#endregion
//#region ../../node_modules/.pnpm/p-limit@7.3.1/node_modules/p-limit/index.js
function pLimit(concurrency) {
let rejectOnClear = false;
if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
validateConcurrency(concurrency);
if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
const queue = new Queue();
let activeCount = 0;
const resumeNext = () => {
if (activeCount < concurrency && queue.size > 0) {
activeCount++;
queue.dequeue().run();
}
};
const next = () => {
activeCount--;
resumeNext();
};
const run = async (function_, resolve, arguments_) => {
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {}
next();
};
const enqueue = (function_, resolve, reject, arguments_) => {
const queueItem = { reject };
new Promise((internalResolve) => {
queueItem.run = internalResolve;
queue.enqueue(queueItem);
}).then(run.bind(void 0, function_, resolve, arguments_));
if (activeCount < concurrency) resumeNext();
};
const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
enqueue(function_, resolve, reject, arguments_);
});
Object.defineProperties(generator, {
activeCount: { get: () => activeCount },
pendingCount: { get: () => queue.size },
clearQueue: { value() {
if (!rejectOnClear) {
queue.clear();
return;
}
const abortError = AbortSignal.abort().reason;
while (queue.size > 0) queue.dequeue().reject(abortError);
} },
concurrency: {
get: () => concurrency,
set(newConcurrency) {
validateConcurrency(newConcurrency);
concurrency = newConcurrency;
queueMicrotask(() => {
while (activeCount < concurrency && queue.size > 0) resumeNext();
});
}
},
map: { async value(iterable, function_) {
const promises = Array.from(iterable, (value, index) => generator(function_, value, index));
return Promise.all(promises);
} }
});
return generator;
}
function validateConcurrency(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
}
//#endregion
//#region src/rpc/validation.ts
/**
* Validates RPC function definitions.
* Action and event functions cannot have dumps (side effects should not be cached).
*
* @throws {Error} If an action or event function has a dump configuration
*/
function validateDefinitions(definitions) {
for (const definition of definitions) {
const type = definition.type || "query";
if ((type === "action" || type === "event") && definition.dump) throw diagnostics.DF0027({
name: definition.name,
type
});
if (definition.snapshot && type !== "query") throw diagnostics.DF0028({
name: definition.name,
type
});
}
}
/**
* Validates a single RPC function definition.
*
* @throws {Error} If an action or event function has a dump configuration
*/
function validateDefinition(definition) {
validateDefinitions([definition]);
}
//#endregion
//#region src/rpc/dump/error.ts
/**
* Normalize a thrown value into a plain object suitable for storage in
* a dump record. Preserves `message`, `name`, `cause`, and any own
* enumerable properties of an `Error` so consumers reading the dump can
* reconstruct a richer Error than just `{ message, name }`.
*
* Non-`Error` throws are wrapped as `{ name: 'Error', message: String(thrown) }`.
*/
function serializeDumpError(error) {
return serializeWithSeen(error, /* @__PURE__ */ new WeakSet());
}
function serializeWithSeen(error, seen) {
if (!(error instanceof Error)) return {
name: "Error",
message: String(error)
};
if (seen.has(error)) return {
name: error.name,
message: error.message
};
seen.add(error);
const out = {
name: error.name,
message: error.message
};
const cause = error.cause;
if (cause !== void 0) out.cause = cause instanceof Error ? serializeWithSeen(cause, seen) : cause;
for (const key of Object.keys(error)) {
if (key === "name" || key === "message" || key === "cause") continue;
out[key] = error[key];
}
return out;
}
/**
* Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
* the plain object stored in a dump record. Preserves `cause`, restores
* the original `name`, and re-attaches any custom own properties.
*/
function reviveDumpError(stored) {
const cause = stored.cause instanceof Error ? stored.cause : isPlainErrorShape(stored.cause) ? reviveDumpError(stored.cause) : stored.cause;
const error = cause !== void 0 ? new Error(stored.message, { cause }) : new Error(stored.message);
error.name = stored.name;
for (const key of Object.keys(stored)) {
if (key === "name" || key === "message" || key === "cause") continue;
error[key] = stored[key];
}
return error;
}
function isPlainErrorShape(value) {
return typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.name === "string";
}
//#endregion
//#region src/rpc/dump/collect.ts
function getDumpRecordKey(functionName, args) {
return `${functionName}---${hash(args)}`;
}
function getDumpFallbackKey(functionName) {
return `${functionName}---fallback`;
}
async function resolveGetter(valueOrGetter) {
return typeof valueOrGetter === "function" ? await valueOrGetter() : valueOrGetter;
}
/**
* Collects pre-computed dumps by executing functions with their defined input combinations.
* Static functions without dump config automatically get `{ inputs: [[]] }`.
*
* @example
* ```ts
* const store = await dumpFunctions([greet], context, { concurrency: 10 })
* ```
*/
async function dumpFunctions(definitions, context, options = {}) {
validateDefinitions(definitions);
const concurrency = options.concurrency === true ? 5 : options.concurrency === false || options.concurrency == null ? 1 : options.concurrency;
const store = {
definitions: {},
records: {}
};
const tasksResolutions = definitions.map((definition) => async () => {
if (definition.type === "event" || definition.type === "action") return;
const setupResult = definition.setup ? await Promise.resolve(definition.setup(context)) : {};
const handler = setupResult.handler || definition.handler;
if (!handler) throw diagnostics.DF0024({ name: definition.name });
let dump = setupResult.dump ?? definition.dump;
if (!dump && definition.type === "static") dump = { inputs: [[]] };
if (!dump && definition.snapshot) dump = async (_ctx, h) => {
const output = await Promise.resolve(h(...[]));
return {
records: [{
inputs: [],
output
}],
fallback: output
};
};
if (!dump) return;
if (typeof dump === "function") dump = await Promise.resolve(dump(context, handler));
store.definitions[definition.name] = {
name: definition.name,
type: definition.type
};
return {
handler,
dump,
definition
};
});
let functionsToDump = [];
if (concurrency <= 1) for (const task of tasksResolutions) {
const resolution = await task();
if (resolution) functionsToDump.push(resolution);
}
else {
const limit = pLimit(concurrency);
functionsToDump = (await Promise.all(tasksResolutions.map((task) => limit(task)))).filter((x) => !!x);
}
const dumpTasks = [];
for (const { definition, handler, dump } of functionsToDump) {
const { inputs, records, fallback } = dump;
if (records) for (const record of records) {
const recordKey = getDumpRecordKey(definition.name, record.inputs);
store.records[recordKey] = record;
}
if ("fallback" in dump) {
const fallbackKey = getDumpFallbackKey(definition.name);
store.records[fallbackKey] = {
inputs: [],
output: fallback
};
}
if (inputs) for (const input of inputs) dumpTasks.push(async () => {
const recordKey = getDumpRecordKey(definition.name, input);
try {
const output = await Promise.resolve(handler(...input));
store.records[recordKey] = {
inputs: input,
output
};
} catch (error) {
store.records[recordKey] = {
inputs: input,
error: serializeDumpError(error)
};
}
});
}
if (concurrency <= 1) for (const task of dumpTasks) await task();
else {
const limit = pLimit(concurrency);
await Promise.all(dumpTasks.map((task) => limit(task)));
}
return store;
}
/**
* Creates a client that serves pre-computed results from a dump store.
* Uses argument hashing to match calls to stored records.
*
* @example
* ```ts
* const client = createClientFromDump(store)
* await client.greet('Alice')
* ```
*/
function createClientFromDump(store, options = {}) {
const { onMiss } = options;
return new Proxy({}, {
get(_, functionName) {
if (!(functionName in store.definitions)) throw diagnostics.DF0025({ name: functionName });
return async (...args) => {
const recordKey = getDumpRecordKey(functionName, args);
const recordOrGetter = store.records[recordKey];
if (recordOrGetter) {
const record = await resolveGetter(recordOrGetter);
if (record.error) throw reviveDumpError(record.error);
if (typeof record.output === "function") return await record.output();
return record.output;
}
onMiss?.(functionName, args);
const fallbackKey = getDumpFallbackKey(functionName);
if (fallbackKey in store.records) {
const fallbackOrGetter = store.records[fallbackKey];
const fallbackRecord = await resolveGetter(fallbackOrGetter);
if (fallbackRecord && typeof fallbackRecord.output === "function") return await fallbackRecord.output();
if (fallbackRecord) return fallbackRecord.output;
}
throw diagnostics.DF0026({
name: functionName,
args: JSON.stringify(args)
});
};
},
has(_, functionName) {
return functionName in store.definitions;
},
ownKeys() {
return Object.keys(store.definitions);
},
getOwnPropertyDescriptor(_, functionName) {
return functionName in store.definitions ? {
configurable: true,
enumerable: true,
value: void 0
} : void 0;
}
});
}
/**
* Filters function definitions to only those with dump definitions.
* Note: Only checks the definition itself, not setup results.
*/
function getDefinitionsWithDumps(definitions) {
return definitions.filter((def) => def.dump !== void 0);
}
//#endregion
//#region src/rpc/handler.ts
async function getRpcResolvedSetupResult(definition, context) {
if (!definition.setup) return {};
if (typeof context === "object" && context !== null) {
definition.__cache ??= /* @__PURE__ */ new WeakMap();
const cache = definition.__cache;
let promise = cache.get(context);
if (!promise) {
promise = Promise.resolve(definition.setup(context));
promise.catch(() => {
if (cache.get(context) === promise) cache.delete(context);
});
cache.set(context, promise);
}
return await promise;
}
if (!definition.__promise) {
const promise = Promise.resolve(definition.setup(context));
promise.catch(() => {
if (definition.__promise === promise) definition.__promise = void 0;
});
definition.__promise = promise;
}
return await definition.__promise;
}
async function getRpcHandler(definition, context) {
if (definition.handler) return definition.handler;
const result = await getRpcResolvedSetupResult(definition, context);
if (!result.handler) throw diagnostics.DF0024({ name: definition.name });
return result.handler;
}
//#endregion
//#region src/rpc/dump/static.ts
function makeDumpKey(name) {
return encodeURIComponent(name.replaceAll(":", "~"));
}
function makeStaticPath(name) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.static.json`;
}
function makeQueryRecordPath(name, hash) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.record.${hash}.json`;
}
function makeQueryFallbackPath(name) {
return `${DEVFRAME_RPC_DUMP_DIRNAME}/${makeDumpKey(name)}.fallback.json`;
}
async function resolveRecord(record) {
return typeof record === "function" ? await record() : record;
}
async function collectStaticRpcDump(definitions, context) {
const manifest = {};
const files = {};
for (const definition of definitions) {
const type = definition.type ?? "query";
const serialization = definition.jsonSerializable === true ? "json" : "structured-clone";
if (type === "static") {
const handler = await getRpcHandler(definition, context);
const path = makeStaticPath(definition.name);
files[path] = {
serialization,
fnName: definition.name,
data: { output: await Promise.resolve(handler()) }
};
manifest[definition.name] = {
type: "static",
path,
serialization
};
continue;
}
if (type !== "query") continue;
const store = await dumpFunctions([definition], context);
if (!(definition.name in store.definitions)) continue;
const queryEntry = {
type: "query",
records: {},
serialization
};
const prefix = `${definition.name}---`;
for (const [recordKey, recordOrGetter] of Object.entries(store.records)) {
if (!recordKey.startsWith(prefix)) continue;
const key = recordKey.slice(prefix.length);
const record = await resolveRecord(recordOrGetter);
if (key === "fallback") {
const path = makeQueryFallbackPath(definition.name);
files[path] = {
serialization,
fnName: definition.name,
data: record
};
queryEntry.fallback = path;
} else {
const path = makeQueryRecordPath(definition.name, key);
files[path] = {
serialization,
fnName: definition.name,
data: record
};
queryEntry.records[key] = path;
}
}
if (!Object.keys(queryEntry.records).length && !queryEntry.fallback) continue;
manifest[definition.name] = queryEntry;
}
return {
manifest,
files
};
}
//#endregion
export { dumpFunctions as a, serializeDumpError as c, hash as d, createClientFromDump as i, validateDefinition as l, getRpcHandler as n, getDefinitionsWithDumps as o, getRpcResolvedSetupResult as r, reviveDumpError as s, collectStaticRpcDump as t, validateDefinitions as u };
import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { t as diagnostics } from "./diagnostics-BXwBQmoN.mjs";
import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
import { defineRpcFunction } from "./index.mjs";
import { a as diagnostics$1, i as createEventEmitter, n as createSharedState, r as nanoid, t as createStorage } from "./storage-gEnj9ASQ.mjs";
import { defineDiagnostics } from "nostics";
import { isatty } from "node:tty";
import { formatWithOptions, inspect } from "node:util";
import { existsSync } from "node:fs";
import { join } from "pathe";
import process$1 from "node:process";
import { homedir } from "node:os";
//#region src/node/host-agent.ts
/**
* Framework-neutral host aggregating the agent-exposed surface of a
* devframe. Auto-discovers RPC functions with an `agent` field from
* `ctx.rpc.definitions`, and accepts plugin-registered tools /
* resources via `registerTool` / `registerResource`.
*
* @experimental
*/
var DevframeAgentHost = class {
context;
events = createEventEmitter();
tools = /* @__PURE__ */ new Map();
resources = /* @__PURE__ */ new Map();
_rpcUnsubscribe;
constructor(context) {
this.context = context;
this._rpcUnsubscribe = context.rpc.onChanged(() => {
this.events.emit("agent:manifest:changed");
});
}
registerTool(input) {
this._validateToolId(input.id);
const tool = this._projectTool(input);
this.tools.set(tool.id, {
tool,
handler: input.handler
});
this.events.emit("agent:tool:registered", tool);
this.events.emit("agent:manifest:changed");
return { unregister: () => this.unregisterTool(tool.id) };
}
unregisterTool(id) {
const existed = this.tools.delete(id);
if (existed) {
this.events.emit("agent:tool:unregistered", id);
this.events.emit("agent:manifest:changed");
}
return existed;
}
registerResource(input) {
if (this.resources.has(input.id)) throw diagnostics$1.DF0016({ id: input.id });
const resource = {
id: input.id,
name: input.name,
description: input.description,
mimeType: input.mimeType ?? "application/json",
uri: input.uri ?? `devframe://resource/${encodeURIComponent(input.id)}`
};
this.resources.set(resource.id, {
resource,
read: input.read
});
this.events.emit("agent:resource:registered", resource);
this.events.emit("agent:manifest:changed");
return { unregister: () => this.unregisterResource(resource.id) };
}
unregisterResource(id) {
const existed = this.resources.delete(id);
if (existed) {
this.events.emit("agent:resource:unregistered", id);
this.events.emit("agent:manifest:changed");
}
return existed;
}
list() {
const rpcTools = this._collectRpcTools();
const plainTools = Array.from(this.tools.values()).map((t) => t.tool);
const resources = Array.from(this.resources.values()).map((r) => r.resource);
return {
tools: [...rpcTools, ...plainTools],
resources
};
}
getTool(id) {
const plain = this.tools.get(id);
if (plain) return plain.tool;
return this._collectRpcTools().find((t) => t.id === id);
}
getResource(id) {
return this.resources.get(id)?.resource;
}
async invoke(id, args) {
const plain = this.tools.get(id);
if (plain?.handler) return await plain.handler(args);
const rpcDef = this._findRpcDefinition(id);
if (rpcDef) {
const positional = this._coercePositionalArgs(args, rpcDef);
return await this.context.rpc.invokeLocal(id, ...positional);
}
throw new Error(`[devframe/agent] tool "${id}" not found`);
}
async read(id) {
const entry = this.resources.get(id);
if (!entry) throw new Error(`[devframe/agent] resource "${id}" not found`);
return await entry.read();
}
/** @internal */
_dispose() {
this._rpcUnsubscribe?.();
this._rpcUnsubscribe = void 0;
}
_validateToolId(id) {
if (this.tools.has(id)) throw diagnostics$1.DF0015({ id });
if (this.context.rpc.definitions.get(id)?.agent) throw diagnostics$1.DF0015({ id });
}
_projectTool(input) {
if (!input.description || typeof input.description !== "string") throw diagnostics$1.DF0014({ name: input.id });
return {
id: input.id,
kind: "tool",
title: input.title ?? input.id,
description: input.description,
safety: input.safety ?? "action",
tags: input.tags,
inputSchema: input.inputSchema,
outputSchema: input.outputSchema,
examples: input.examples
};
}
_collectRpcTools() {
const out = [];
for (const [name, def] of this.context.rpc.definitions) {
const agent = def.agent;
if (!agent) continue;
if (!agent.description || typeof agent.description !== "string") throw diagnostics$1.DF0014({ name });
const type = def.type ?? "query";
const safety = agent.safety ?? inferSafety(type);
out.push({
id: name,
kind: "rpc",
title: agent.title ?? name,
description: agent.description,
safety,
tags: agent.tags,
rpcName: name,
examples: agent.examples
});
}
return out;
}
_findRpcDefinition(id) {
const def = this.context.rpc.definitions.get(id);
if (def?.agent) return def;
}
_coercePositionalArgs(args, def) {
if (Array.isArray(args)) return args;
if (args === void 0 || args === null) return [];
if (args && typeof args === "object") {
const obj = args;
const schemas = def.args;
if (schemas && schemas.length) return schemas.map((_, i) => obj[`arg${i}`]);
if (hasPositionalKeys(obj)) {
const out = [];
let i = 0;
while (`arg${i}` in obj) {
out.push(obj[`arg${i}`]);
i++;
}
return out;
}
}
return [args];
}
};
function inferSafety(type) {
if (type === "static" || type === "query") return "read";
return "action";
}
function hasPositionalKeys(obj) {
return "arg0" in obj;
}
//#endregion
//#region src/node/host-diagnostics.ts
var DevframeDiagnosticsHost = class {
context;
_registry = {};
logger = new Proxy({}, { get: (_, code) => this._registry[code] });
defineDiagnostics = (opts) => {
return defineDiagnostics({
...opts,
reporters: [devframeReporter, ...opts.reporters ?? []]
});
};
constructor(context, initialDefinitions = []) {
this.context = context;
for (const d of initialDefinitions) this.register(d);
}
register(diagnostics) {
Object.assign(this._registry, diagnostics);
}
};
//#endregion
//#region ../../node_modules/.pnpm/obug@2.1.4/node_modules/obug/dist/core.js
/**
* Coerce `value`.
*/
function coerce(value) {
if (value instanceof Error) return value.stack || value.message;
return value;
}
/**
* Selects a color for a debug namespace
* @return An ANSI color code for the given namespace
*/
function selectColor(colors, namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = (hash << 5) - hash + namespace.charCodeAt(i);
hash |= 0;
}
return colors[Math.abs(hash) % colors.length];
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*/
function matchesTemplate(search, template) {
let searchIndex = 0;
let templateIndex = 0;
let starIndex = -1;
let matchIndex = 0;
while (searchIndex < search.length) if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) if (template[templateIndex] === "*") {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++;
} else {
searchIndex++;
templateIndex++;
}
else if (starIndex !== -1) {
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else return false;
while (templateIndex < template.length && template[templateIndex] === "*") templateIndex++;
return templateIndex === template.length;
}
function humanize(value) {
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
return `${value}ms`;
}
let globalNamespaces = "";
function createDebug$1(namespace, options) {
let prevTime;
let enableOverride;
let namespacesCache;
let enabledCache;
const debug = (...args) => {
if (!debug.enabled) return;
const curr = Date.now();
const diff = curr - (prevTime || curr);
prevTime = curr;
args[0] = coerce(args[0]);
if (typeof args[0] !== "string") args.unshift("%O");
let index = 0;
args[0] = args[0].replace(/%([a-z%])/gi, (match, format) => {
if (match === "%%") return "%";
index++;
const formatter = options.formatters[format];
if (typeof formatter === "function") {
const value = args[index];
match = formatter.call(debug, value);
args.splice(index, 1);
index--;
}
return match;
});
options.formatArgs.call(debug, diff, args);
debug.log(...args);
};
debug.extend = function(namespace, delimiter = ":") {
return createDebug$1(this.namespace + delimiter + namespace, {
useColors: this.useColors,
color: this.color,
formatArgs: this.formatArgs,
formatters: this.formatters,
inspectOpts: this.inspectOpts,
log: this.log,
humanize: this.humanize
});
};
Object.assign(debug, options);
debug.namespace = namespace;
Object.defineProperty(debug, "enabled", {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride != null) return enableOverride;
if (namespacesCache !== globalNamespaces) {
namespacesCache = globalNamespaces;
enabledCache = enabled(namespace);
}
return enabledCache;
},
set: (v) => {
enableOverride = v;
}
});
return debug;
}
let names = [];
let skips = [];
function enable(namespaces) {
globalNamespaces = namespaces;
names = [];
skips = [];
const split = globalNamespaces.trim().replace(/\s+/g, ",").split(",").filter(Boolean);
for (const ns of split) if (ns[0] === "-") skips.push(ns.slice(1));
else names.push(ns);
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*/
function enabled(name) {
for (const skip of skips) if (matchesTemplate(name, skip)) return false;
for (const ns of names) if (matchesTemplate(name, ns)) return true;
return false;
}
//#endregion
//#region ../../node_modules/.pnpm/obug@2.1.4/node_modules/obug/dist/node.js
let env = {};
try {
process.env.DEBUG;
env = process.env;
} catch (_unused) {}
const colors = process.stderr.getColorDepth && process.stderr.getColorDepth(env) > 2 ? [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
] : [
6,
2,
3,
4,
5,
1
];
const inspectOpts = Object.keys(env).filter((key) => /^debug_/i.test(key)).reduce((obj, key) => {
const prop = key.slice(6).toLowerCase().replace(/_([a-z])/g, (_, k) => k.toUpperCase());
let value = env[key];
const lowerCase = typeof value === "string" && value.toLowerCase();
if (value === "null") value = null;
else if (lowerCase === "yes" || lowerCase === "on" || lowerCase === "true" || lowerCase === "enabled") value = true;
else if (lowerCase === "no" || lowerCase === "off" || lowerCase === "false" || lowerCase === "disabled") value = false;
else value = Number(value);
obj[prop] = value;
return obj;
}, Object.create(null));
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
return "colors" in inspectOpts ? Boolean(inspectOpts.colors) : isatty(process.stderr.fd);
}
function getDate() {
if (inspectOpts.hideDate) return "";
return `${(/* @__PURE__ */ new Date()).toISOString()} `;
}
/**
* Adds ANSI color escape codes if enabled.
*/
function formatArgs(diff, args) {
const { namespace: name, useColors } = this;
if (useColors) {
const c = this.color;
const colorCode = `\u001B[3${c < 8 ? c : `8;5;${c}`}`;
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split("\n").join(`\n${prefix}`);
args.push(`${colorCode}m+${this.humanize(diff)}\u001B[0m`);
} else args[0] = `${getDate()}${name} ${args[0]}`;
}
function log(...args) {
process.stderr.write(`${formatWithOptions(this.inspectOpts, ...args)}\n`);
}
const defaultOptions = {
useColors: useColors(),
formatArgs,
formatters: {
/**
* Map %o to `util.inspect()`, all on a single line.
*/
o(v) {
this.inspectOpts.colors = this.useColors;
return inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
},
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
O(v) {
this.inspectOpts.colors = this.useColors;
return inspect(v, this.inspectOpts);
}
},
inspectOpts,
log,
humanize
};
function createDebug(namespace, options) {
var _ref;
const color = (_ref = options && options.color) !== null && _ref !== void 0 ? _ref : selectColor(colors, namespace);
return createDebug$1(namespace, Object.assign(defaultOptions, { color }, options));
}
enable(env.DEBUG || "");
//#endregion
//#region src/node/rpc-shared-state.ts
const debug$1 = createDebug("devframe:rpc:state:changed");
const debugSubscribe = createDebug("devframe:rpc:state:subscribe");
function createRpcSharedStateServerHost(rpc) {
const sharedState = /* @__PURE__ */ new Map();
const stateDisposers = /* @__PURE__ */ new Map();
const keyAddedListeners = /* @__PURE__ */ new Set();
function registerSharedState(key, state) {
const offs = [];
offs.push(state.on("updated", (fullState, patches, syncId) => {
if (patches) {
debug$1("patch", {
key,
syncId
});
rpc.broadcast({
method: "devframe:rpc:client-state:patch",
args: [
key,
patches,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
} else {
debug$1("updated", {
key,
syncId
});
rpc.broadcast({
method: "devframe:rpc:client-state:updated",
args: [
key,
fullState,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
}
}));
return () => {
for (const off of offs) off();
};
}
const host = {
get: async (key, options) => {
if (sharedState.has(key)) return sharedState.get(key);
if (options?.initialValue === void 0 && options?.sharedState === void 0) throw diagnostics$1.DF0013({ key });
debug$1("new-state", key);
const state = options.sharedState ?? createSharedState({
initialValue: options.initialValue,
enablePatches: false
});
stateDisposers.set(key, registerSharedState(key, state));
sharedState.set(key, state);
for (const fn of keyAddedListeners) fn(key);
return state;
},
keys() {
return Array.from(sharedState.keys());
},
onKeyAdded(fn) {
keyAddedListeners.add(fn);
return () => {
keyAddedListeners.delete(fn);
};
},
delete(key) {
const dispose = stateDisposers.get(key);
if (!dispose) return false;
dispose();
stateDisposers.delete(key);
sharedState.delete(key);
return true;
}
};
rpc.register({
name: "devframe:rpc:server-state:subscribe",
type: "event",
handler(key) {
const session = rpc.getCurrentRpcSession();
if (!session) return;
debugSubscribe("subscribe", {
key,
session: session.meta.id
});
session.meta.subscribedStates.add(key);
}
});
rpc.register({
name: "devframe:rpc:server-state:get",
type: "query",
handler: async (key) => {
if (!sharedState.has(key)) return void 0;
return (await host.get(key)).value();
},
dump: () => ({ inputs: host.keys().map((key) => [key]) })
});
rpc.register({
name: "devframe:rpc:server-state:set",
type: "query",
handler: async (key, value, syncId) => {
(await host.get(key, { initialValue: value })).mutate(() => value, syncId);
}
});
rpc.register({
name: "devframe:rpc:server-state:patch",
type: "query",
handler: async (key, patches, syncId) => {
if (!sharedState.has(key)) return;
(await host.get(key)).patch(patches, syncId);
}
});
return host;
}
//#endregion
//#region src/utils/streaming-channel.ts
const DEFAULT_HIGH_WATER_MARK = 256;
var StreamClosedError = class extends Error {
name = "StreamClosedError";
};
/**
* Build a server-side stream sink. RPC-agnostic — the RPC host wires
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
* for late or reconnecting subscribers.
*/
function createStreamSink(options = {}) {
const id = options.id ?? nanoid();
const replayWindow = Math.max(0, options.replayWindow ?? 0);
const events = createEventEmitter();
const controller = new AbortController();
const buffer = [];
let closed = false;
let lastSeq = 0;
function write(chunk) {
if (closed) throw new StreamClosedError(`Cannot write to a closed stream "${id}"`);
lastSeq += 1;
if (replayWindow > 0) {
buffer.push({
seq: lastSeq,
chunk
});
if (buffer.length > replayWindow) buffer.splice(0, buffer.length - replayWindow);
}
events.emit("chunk", lastSeq, chunk);
}
function error(reason) {
if (closed) return;
closed = true;
const payload = toErrorPayload(reason);
controller.abort(reason);
events.emit("end", payload);
}
function close() {
if (closed) return;
closed = true;
if (!controller.signal.aborted) controller.abort("stream closed");
events.emit("end", void 0);
}
function abort(reason) {
if (closed) return;
if (!controller.signal.aborted) controller.abort(reason ?? "aborted");
}
const writable = new WritableStream({
write(chunk) {
write(chunk);
},
close() {
close();
},
abort(reason) {
error(reason);
}
});
return {
id,
signal: controller.signal,
get closed() {
return closed;
},
get lastSeq() {
return lastSeq;
},
write,
error,
close,
abort,
writable,
events,
buffer
};
}
/**
* Build a client-side stream reader. RPC-agnostic — the RPC host calls
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
*/
function createStreamReader(options = {}) {
const id = options.id ?? nanoid();
const highWaterMark = Math.max(1, options.highWaterMark ?? DEFAULT_HIGH_WATER_MARK);
const queue = [];
let lastSeenSeq = 0;
let done = false;
let cancelled = false;
let endError;
let pending;
let pullController;
let readableInstance;
function drainNext() {
if (!pending) return;
if (queue.length > 0) {
const value = queue.shift();
const r = pending;
pending = void 0;
r.resolve({
value,
done: false
});
return;
}
if (done) {
const r = pending;
pending = void 0;
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
r.reject(err);
} else r.resolve({
value: void 0,
done: true
});
}
}
function feedReadable() {
if (!pullController) return;
while (queue.length > 0) {
const v = queue.shift();
try {
pullController.enqueue(v);
} catch {
break;
}
}
if (done && pullController) {
try {
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
pullController.error(err);
} else pullController.close();
} catch {}
pullController = void 0;
}
}
function push(seq, chunk) {
if (done || cancelled) return;
if (seq <= lastSeenSeq) return;
lastSeenSeq = seq;
queue.push(chunk);
if (queue.length > highWaterMark) {
const overflow = queue.length - highWaterMark;
queue.splice(0, overflow);
options.onOverflow?.(overflow);
}
drainNext();
if (readableInstance) feedReadable();
}
function end(error) {
if (done) return;
done = true;
endError = error;
drainNext();
if (readableInstance) feedReadable();
}
function cancel() {
if (cancelled || done) return;
cancelled = true;
options.onCancel?.();
end(void 0);
}
function getReadable() {
if (readableInstance) return readableInstance;
readableInstance = new ReadableStream({
start(controller) {
pullController = controller;
feedReadable();
},
cancel() {
cancel();
}
});
return readableInstance;
}
return {
id,
get cancelled() {
return cancelled;
},
get done() {
return done;
},
get lastSeenSeq() {
return lastSeenSeq;
},
get readable() {
return getReadable();
},
cancel,
_push: push,
_end: end,
[Symbol.asyncIterator]() {
return {
next() {
if (queue.length > 0) return Promise.resolve({
value: queue.shift(),
done: false
});
if (done) {
if (endError) {
const err = new Error(endError.message);
err.name = endError.name;
return Promise.reject(err);
}
return Promise.resolve({
value: void 0,
done: true
});
}
return new Promise((resolve, reject) => {
pending = {
resolve,
reject
};
});
},
return() {
cancel();
return Promise.resolve({
value: void 0,
done: true
});
}
};
}
};
}
function toErrorPayload(reason) {
if (reason instanceof Error) return {
name: reason.name || "Error",
message: reason.message
};
if (typeof reason === "string") return {
name: "Error",
message: reason
};
try {
return {
name: "Error",
message: JSON.stringify(reason)
};
} catch {
return {
name: "Error",
message: String(reason)
};
}
}
//#endregion
//#region src/node/rpc-streaming.ts
const debug = createDebug("devframe:rpc:streaming");
const STREAM_KEY_SEPARATOR = "";
function streamKey(channel, id) {
return `${channel}${STREAM_KEY_SEPARATOR}${id}`;
}
/**
* Build the server-side streaming host. Mirrors the layout of
* `createRpcSharedStateServerHost` — registers a fixed set of internal
* RPC methods (`subscribe` / `unsubscribe` / `cancel`) once, then per-channel
* state lives in a `Map<channelName, ChannelState>`.
*/
function createRpcStreamingServerHost(rpc) {
const channels = /* @__PURE__ */ new Map();
function findStream(channelName, id) {
return channels.get(channelName)?.streams.get(id);
}
function freeStreamNow(state, id) {
const record = state.streams.get(id);
if (!record) return;
if (record.retentionTimer) {
clearTimeout(record.retentionTimer);
record.retentionTimer = void 0;
}
for (const off of record.unbinders) off();
state.streams.delete(id);
debug("freed", state.name, id);
}
function maybeFreeStream(state, id) {
const record = state.streams.get(id);
if (!record) return;
if (!record.sink.closed || record.subscribers.size > 0) return;
const retention = state.options.closedStreamRetention;
if (retention <= 0) {
freeStreamNow(state, id);
return;
}
if (record.retentionTimer) return;
record.retentionTimer = setTimeout(freeStreamNow, retention, state, id);
}
function cancelRetention(record) {
if (record.retentionTimer) {
clearTimeout(record.retentionTimer);
record.retentionTimer = void 0;
}
}
rpc.register({
name: "devframe:streaming:subscribe",
type: "event",
handler(channelName, id, opts) {
const state = channels.get(channelName);
if (!state) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
const record = state.streams.get(id);
if (!record) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
const session = rpc.getCurrentRpcSession();
if (!session) return;
const key = streamKey(channelName, id);
session.meta.subscribedStreams ??= /* @__PURE__ */ new Set();
session.meta.subscribedStreams.add(key);
record.subscribers.add(session.meta);
cancelRetention(record);
const afterSeq = opts?.afterSeq ?? 0;
for (const buffered of record.sink.buffer) if (buffered.seq > afterSeq) rpc.broadcast({
method: "devframe:streaming:chunk",
args: [
channelName,
id,
buffered.seq,
buffered.chunk
],
event: true,
optional: true,
filter: (client) => client.$meta === session.meta
});
if (record.sink.closed) rpc.broadcast({
method: "devframe:streaming:end",
args: [
channelName,
id,
void 0
],
event: true,
optional: true,
filter: (client) => client.$meta === session.meta
});
}
});
rpc.register({
name: "devframe:streaming:unsubscribe",
type: "event",
handler(channelName, id) {
const state = channels.get(channelName);
const record = state?.streams.get(id);
const session = rpc.getCurrentRpcSession();
if (!session) return;
session.meta.subscribedStreams?.delete(streamKey(channelName, id));
if (state && record) {
record.subscribers.delete(session.meta);
maybeFreeStream(state, id);
}
}
});
rpc.register({
name: "devframe:streaming:cancel",
type: "event",
handler(channelName, id) {
const record = findStream(channelName, id);
if (!record) return;
const session = rpc.getCurrentRpcSession();
if (!session) return;
record.subscribers.delete(session.meta);
session.meta.subscribedStreams?.delete(streamKey(channelName, id));
if (record.subscribers.size === 0) record.sink.abort("cancelled by client");
}
});
rpc.register({
name: "devframe:streaming:upload-chunk",
type: "event",
handler(channelName, id, seq, chunk) {
const record = channels.get(channelName)?.inbound.get(id);
if (!record) {
diagnostics$1.DF0030({
channel: channelName,
id
}, { method: "error" });
return;
}
if (!record.uploaderMeta) {
const session = rpc.getCurrentRpcSession();
if (session) {
record.uploaderMeta = session.meta;
session.meta.uploadingStreams ??= /* @__PURE__ */ new Set();
session.meta.uploadingStreams.add(streamKey(channelName, id));
}
}
record.reader._push(seq, chunk);
}
});
rpc.register({
name: "devframe:streaming:upload-end",
type: "event",
handler(channelName, id, error) {
const state = channels.get(channelName);
const record = state?.inbound.get(id);
if (!record) return;
record.reader._end(error);
if (record.uploaderMeta) record.uploaderMeta.uploadingStreams?.delete(streamKey(channelName, id));
state?.inbound.delete(id);
}
});
function createChannel(name, opts = {}) {
if (channels.has(name)) throw diagnostics$1.DF0032({ channel: name });
const replayWindow = opts.replayWindow ?? 0;
const state = {
name,
options: {
replayWindow,
closedStreamRetention: opts.closedStreamRetention ?? (replayWindow > 0 ? 3e4 : 0)
},
streams: /* @__PURE__ */ new Map(),
inbound: /* @__PURE__ */ new Map()
};
channels.set(name, state);
function start(startOpts = {}) {
const sink = createStreamSink({
id: startOpts.id,
replayWindow: state.options.replayWindow
});
const record = {
sink,
subscribers: /* @__PURE__ */ new Set(),
unbinders: []
};
state.streams.set(sink.id, record);
record.unbinders.push(sink.events.on("chunk", (seq, chunk) => {
rpc.broadcast({
method: "devframe:streaming:chunk",
args: [
name,
sink.id,
seq,
chunk
],
event: true,
optional: true,
filter: (client) => record.subscribers.has(client.$meta)
});
}));
record.unbinders.push(sink.events.on("end", (error) => {
rpc.broadcast({
method: "devframe:streaming:end",
args: [
name,
sink.id,
error
],
event: true,
optional: true,
filter: (client) => record.subscribers.has(client.$meta)
});
maybeFreeStream(state, sink.id);
}));
return sink;
}
async function pipeFrom(readable, startOpts = {}) {
const sink = start(startOpts);
readable.pipeTo(sink.writable, { signal: sink.signal }).catch(() => {});
return sink;
}
function get(id) {
return state.streams.get(id)?.sink;
}
function ids() {
return Array.from(state.streams.keys());
}
function openInbound(inboundOpts = {}) {
let inboundRecord;
const reader = createStreamReader({
id: inboundOpts.id,
onCancel() {
const targetMeta = inboundRecord?.uploaderMeta;
if (!targetMeta) return;
rpc.broadcast({
method: "devframe:streaming:upload-cancel",
args: [name, reader.id],
event: true,
optional: true,
filter: (client) => client.$meta === targetMeta
});
}
});
inboundRecord = { reader };
state.inbound.set(reader.id, inboundRecord);
debug("opened-inbound", name, reader.id);
return reader;
}
return {
name,
start,
pipeFrom,
get,
ids,
openInbound
};
}
function parseKey(key) {
const sepIdx = key.indexOf(STREAM_KEY_SEPARATOR);
if (sepIdx < 0) return void 0;
return {
channelName: key.slice(0, sepIdx),
id: key.slice(sepIdx + 1)
};
}
return {
create: createChannel,
_onSessionDisconnected(meta) {
if (meta.subscribedStreams) {
for (const key of meta.subscribedStreams) {
const parsed = parseKey(key);
if (!parsed) continue;
const state = channels.get(parsed.channelName);
const record = state?.streams.get(parsed.id);
if (!state || !record) continue;
record.subscribers.delete(meta);
if (record.subscribers.size === 0 && !record.sink.closed) record.sink.abort("all subscribers disconnected");
maybeFreeStream(state, parsed.id);
}
meta.subscribedStreams.clear();
}
if (meta.uploadingStreams) {
for (const key of meta.uploadingStreams) {
const parsed = parseKey(key);
if (!parsed) continue;
const state = channels.get(parsed.channelName);
const record = state?.inbound.get(parsed.id);
if (!state || !record) continue;
record.reader._end({
name: "UploadDisconnected",
message: "Uploader disconnected before completing the stream"
});
state.inbound.delete(parsed.id);
}
meta.uploadingStreams.clear();
}
}
};
}
//#endregion
//#region src/node/host-functions.ts
const debugBroadcast = createDebug("devframe:rpc:broadcast");
/**
* Concrete implementation backing `ctx.rpc`. Internal: consumers should
* depend on the structural {@link RpcFunctionsHost} type, never this class.
* Its `@internal` members (`_rpcGroup`, `_asyncStorage`,
* `_emitSessionDisconnected`) are wired by `startHttpAndWs` and must not
* widen the public surface.
*
* @internal
*/
var RpcFunctionsHostImpl = class extends RpcFunctionsCollectorBase {
/**
* @internal
*/
_rpcGroup = void 0;
_asyncStorage = void 0;
constructor(context) {
super(context);
this.sharedState = createRpcSharedStateServerHost(this);
this.streaming = createRpcStreamingServerHost(this);
}
sharedState;
streaming;
/**
* Adapters call this from their WS `onDisconnected` hook so downstream
* hosts (streaming, …) can free per-session state. Public-ish because
* tests / custom adapters may want to mirror it.
*
* @internal
*/
_emitSessionDisconnected(meta) {
this.streaming._onSessionDisconnected(meta);
}
async invokeLocal(method, ...args) {
if (!this.definitions.has(method)) throw diagnostics$1.DF0006({ name: String(method) });
const handler = await this.getHandler(method);
return await Promise.resolve(handler(...args));
}
async broadcast(options) {
if (!this._rpcGroup) return;
debugBroadcast(JSON.stringify(options.method));
await Promise.allSettled(this._rpcGroup.clients.map((client) => {
if (options.filter?.(client) === false) return void 0;
return client.$callRaw({
optional: true,
event: true,
...options
});
}));
}
getCurrentRpcSession() {
if (!this._asyncStorage) throw diagnostics$1.DF0007();
return this._asyncStorage.getStore();
}
};
//#endregion
//#region src/node/host-services.ts
/**
* Cross-plugin service registry (see `types/services.ts` for the contract).
* Values are held per context instance; `whenAvailable` subscriptions make
* the mechanism robust against setup ordering between provider and consumer.
*/
var DevframeServicesHostImpl = class {
services = /* @__PURE__ */ new Map();
listeners = /* @__PURE__ */ new Map();
provide(id, service) {
const key = id;
if (this.services.has(key)) throw diagnostics$1.DF0037({ id: key });
this.services.set(key, service);
for (const listener of this.listeners.get(key) ?? []) listener(service);
return () => {
if (this.services.get(key) === service) this.services.delete(key);
};
}
get(id) {
return this.services.get(id);
}
has(id) {
return this.services.has(id);
}
whenAvailable(id, callback) {
const key = id;
if (this.services.has(key)) callback(this.services.get(key));
let set = this.listeners.get(key);
if (!set) {
set = /* @__PURE__ */ new Set();
this.listeners.set(key, set);
}
const listener = callback;
set.add(listener);
return () => {
set.delete(listener);
};
}
keys() {
return Array.from(this.services.keys());
}
};
//#endregion
//#region src/node/host-views.ts
var DevframeViewHost = class {
context;
/**
* @internal
*/
buildStaticDirs = [];
constructor(context) {
this.context = context;
}
hostStatic(baseUrl, distDir) {
if (!existsSync(distDir)) throw diagnostics$1.DF0008({ distDir });
this.buildStaticDirs.push({
baseUrl,
distDir
});
this.context.host.mountStatic(baseUrl, distDir);
}
};
//#endregion
//#region src/node/rpc/agent-invoke-tool.ts
const agentInvokeTool = defineRpcFunction({
name: "devframe:agent:invoke-tool",
type: "action",
setup: (ctx) => {
return { async handler(id, args) {
return await ctx.agent.invoke(id, args);
} };
}
});
//#endregion
//#region src/node/rpc/agent-list-resources.ts
const agentListResources = defineRpcFunction({
name: "devframe:agent:list-resources",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler() {
return ctx.agent.list().resources;
} };
}
});
//#endregion
//#region src/node/rpc/index.ts
/**
* Built-in agent introspection RPC functions. Registered automatically
* by `createHostContext`. Not themselves agent-exposed (no `agent`
* field) — they power the MCP adapter and any future agent CLI.
*
* @experimental
*/
const BUILTIN_AGENT_RPC = [
defineRpcFunction({
name: "devframe:agent:list-tools",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler() {
return ctx.agent.list().tools;
} };
}
}),
agentInvokeTool,
agentListResources,
defineRpcFunction({
name: "devframe:agent:read-resource",
type: "query",
jsonSerializable: true,
setup: (ctx) => {
return { async handler(id) {
return await ctx.agent.read(id);
} };
}
})
];
//#endregion
//#region src/utils/scope.ts
/** Whether a name is already namespaced (contains a `:` separator). */
function isQualifiedName(name) {
return name.includes(":");
}
/**
* Prefix a bare name with `<namespace>:`. Names that already contain a
* `:` are returned unchanged, so callers can reference another scope's
* ids explicitly (e.g. `ctx.rpc.call('other-plugin:fn')`).
*/
function qualifyName(namespace, name) {
return isQualifiedName(name) ? name : `${namespace}:${name}`;
}
//#endregion
//#region src/node/settings.ts
const STORAGE_SCOPE = {
global: "global",
project: "project"
};
function createNodeSettingsStore(context, namespace, scope) {
const stateKey = `devframe:settings:${scope}:${namespace}`;
let statePromise;
function store() {
if (!statePromise) {
const filepath = join(context.host.getStorageDir(STORAGE_SCOPE[scope]), "settings", `${namespace}.json`);
statePromise = context.rpc.sharedState.get(stateKey, { sharedState: createStorage({
filepath,
initialValue: {}
}) });
}
return statePromise;
}
return {
async get(key) {
return (await store()).value()[key];
},
async set(key, value) {
(await store()).mutate((draft) => {
draft[key] = value;
});
},
async delete(key) {
(await store()).mutate((draft) => {
delete draft[key];
});
},
async all() {
return (await store()).value();
},
async onChange(fn) {
return (await store()).on("updated", (full) => fn(full));
}
};
}
/**
* Build the node-side `settings` surface for a scope namespace. `project`
* persists under the host's `workspace` storage dir, `global` under its
* `global` dir. Each is a file-backed, client-synced key-value store.
*/
function createNodeSettings(context, namespace) {
return {
global: createNodeSettingsStore(context, namespace, "global"),
project: createNodeSettingsStore(context, namespace, "project")
};
}
//#endregion
//#region src/node/scope.ts
function prefixDefinition(namespace, fn) {
if (isQualifiedName(fn.name)) throw diagnostics$1.DF0034({
namespace,
name: fn.name
});
return {
...fn,
name: `${namespace}:${fn.name}`
};
}
/**
* Build a namespace-scoped view of a {@link DevframeNodeContext}. Every
* RPC id, shared-state key, and streaming channel passed through the
* returned `rpc` surface is auto-namespaced with `<namespace>:`.
*/
function createScopedNodeContext(context, namespace) {
const base = context.rpc;
const rpc = {
namespace,
register(fn, force) {
base.register(prefixDefinition(namespace, fn), force);
},
update(fn, force) {
base.update(prefixDefinition(namespace, fn), force);
},
call: ((method, ...args) => base.invokeLocal(qualifyName(namespace, method), ...args)),
broadcast: ((options) => base.broadcast({
...options,
method: qualifyName(namespace, options.method)
})),
sharedState: ((key, options) => base.sharedState.get(qualifyName(namespace, key), options)),
streaming: { create: (name, opts) => base.streaming.create(qualifyName(namespace, name), opts) },
getCurrentRpcSession: () => base.getCurrentRpcSession()
};
return {
namespace,
base: context,
cwd: context.cwd,
workspaceRoot: context.workspaceRoot,
mode: context.mode,
host: context.host,
rpc,
settings: createNodeSettings(context, namespace),
views: context.views,
diagnostics: context.diagnostics,
agent: context.agent,
scope: context.scope
};
}
//#endregion
//#region src/node/context.ts
/**
* Framework- and build-tool-agnostic core of the Devframe node context.
* Wires the RPC host, view (HTTP file-serving) host, diagnostics, and
* agent subsystems. Host adapters can wrap this to augment `ctx` with
* extra surfaces — for example, `@vitejs/devtools-kit`'s
* `createKitContext` attaches `docks`, `terminals`, `messages`, and
* `commands` when mounted into Vite DevTools.
*/
async function createHostContext(options) {
const { cwd, workspaceRoot = cwd, mode, host, builtinRpcDeclarations = [] } = options;
const context = {
cwd,
workspaceRoot,
mode,
host,
rpc: void 0,
views: void 0,
diagnostics: void 0,
agent: void 0,
services: void 0,
scope: void 0
};
const rpcHost = new RpcFunctionsHostImpl(context);
const viewsHost = new DevframeViewHost(context);
const diagnosticsHost = new DevframeDiagnosticsHost(context, [diagnostics$1, diagnostics]);
context.rpc = rpcHost;
context.views = viewsHost;
context.diagnostics = diagnosticsHost;
context.services = new DevframeServicesHostImpl();
context.agent = new DevframeAgentHost(context);
const scopedCache = /* @__PURE__ */ new Map();
context.scope = ((namespace) => {
if (!namespace) return context;
let scoped = scopedCache.get(namespace);
if (!scoped) {
scoped = createScopedNodeContext(context, namespace);
scopedCache.set(namespace, scoped);
}
return scoped;
});
for (const fn of BUILTIN_AGENT_RPC) rpcHost.register(fn);
for (const fn of builtinRpcDeclarations) rpcHost.register(fn);
return context;
}
//#endregion
//#region src/node/host-h3.ts
/**
* h3-backed {@link DevframeHost} — used by the standalone CLI adapter.
*/
function createH3DevframeHost(options) {
const workspaceRoot = options.workspaceRoot ?? process$1.cwd();
return {
mountStatic(base, distDir) {
return options.mount?.(base, distDir);
},
resolveOrigin() {
return options.origin;
},
getStorageDir(scope) {
const namespace = `.${options.appName}/devframe`;
if (scope === "workspace") return join(workspaceRoot, ".devframe");
if (scope === "project") return join(workspaceRoot, "node_modules", namespace);
return join(homedir(), namespace);
}
};
}
//#endregion
export { DevframeViewHost as a, createRpcSharedStateServerHost as c, createNodeSettings as i, DevframeDiagnosticsHost as l, createHostContext as n, DevframeServicesHostImpl as o, createScopedNodeContext as r, createRpcStreamingServerHost as s, createH3DevframeHost as t, DevframeAgentHost as u };
import { isAllowedOrigin } from "./rpc/transports/ws-server.mjs";
import { t as buildMcpServerFromContext } from "./build-server-C7vTKjiQ.mjs";
import { randomUUID } from "node:crypto";
import { defineHandler } from "h3";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
//#region src/adapters/mcp/http.ts
/**
* Mount an MCP Streamable-HTTP endpoint on an h3 app at `path`.
*
* Each MCP session gets its own {@link WebStandardStreamableHTTPServerTransport}
* and MCP server (built from the shared, live `ctx` via
* `buildMcpServerFromContext`), correlated by the `Mcp-Session-Id` header:
* an `initialize` POST spins up a session; later requests route to it; a
* `DELETE` (or client disconnect) tears it down.
*
* The transport is web-standard — its `handleRequest` takes the h3 event's
* web `Request` and returns a web `Response` (an SSE `ReadableStream` body
* for the server→client stream). We copy that response onto `event.res` and
* return its body rather than returning the `Response` object directly, so a
* legitimate MCP 404 (unknown session) isn't swallowed by h3's
* "Response-with-404 falls through to the next handler" rule (which would
* otherwise hand the request to the SPA static catch-all).
*
* @experimental
*/
function mountMcpHttp(app, ctx, path, options) {
const sessions = /* @__PURE__ */ new Map();
const allowedOrigins = options.allowedOrigins;
function drop(sessionId) {
const session = sessions.get(sessionId);
if (!session) return;
sessions.delete(sessionId);
session.dispose();
}
async function createSession() {
let session;
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => {
sessions.set(id, session);
},
onsessionclosed: (id) => {
drop(id);
}
});
const { server, dispose } = buildMcpServerFromContext(ctx, {
serverName: options.serverName,
serverVersion: options.serverVersion,
exposeSharedState: options.exposeSharedState
});
session = {
transport,
dispose: async () => {
dispose();
await server.close();
}
};
transport.onclose = () => {
if (transport.sessionId) drop(transport.sessionId);
};
await server.connect(transport);
return session;
}
app.use(path, defineHandler(async (event) => {
const req = event.req;
const origin = req.headers.get("origin") ?? void 0;
if (allowedOrigins !== false && !isAllowedOrigin(origin, allowedOrigins ?? [])) {
event.res.status = 403;
return "Forbidden: origin not allowed";
}
const sessionId = req.headers.get("mcp-session-id") ?? void 0;
let session = sessionId ? sessions.get(sessionId) : void 0;
if (!session && req.method === "POST") {
let body;
try {
body = await req.json();
} catch {
body = void 0;
}
if (!sessionId && isInitializeRequest(body)) session = await createSession();
else {
event.res.status = sessionId ? 404 : 400;
return sessionId ? "Not Found: unknown MCP session" : "Bad Request: no valid session ID and not an initialize request";
}
return respond(event, await session.transport.handleRequest(req, { parsedBody: body }));
}
if (!session) {
event.res.status = sessionId ? 404 : 400;
return sessionId ? "Not Found: unknown MCP session" : "Bad Request: missing MCP session ID";
}
return respond(event, await session.transport.handleRequest(req));
}));
return { dispose: async () => {
const live = [...sessions.values()];
sessions.clear();
await Promise.all(live.map((session) => session.dispose()));
} };
}
/**
* Copy a web `Response` from the MCP transport onto the h3 event's response
* and return its body. Returning the body (a `ReadableStream` or `null`)
* rather than the `Response` object avoids h3's 404-fall-through behavior.
*/
function respond(event, response) {
event.res.status = response.status;
event.res.statusText = response.statusText;
response.headers.forEach((value, key) => {
event.res.headers.set(key, value);
});
return response.body ?? "";
}
//#endregion
export { mountMcpHttp };
import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-Ckhf3kFw.mjs";
import { n as InternalAnonymousAuthStorage } from "./context-CzqPrJSz.mjs";
//#region src/node/auth/revoke.d.ts
/**
* Flip `isTrusted` to false on any live WS clients connected with `token`
* and broadcast the `auth:revoked` event so they can react.
*
* Shared between persisted-auth revocation and remote-dock token revocation.
*/
declare function revokeActiveConnectionsForToken(context: DevframeNodeContext, token: string): Promise<void>;
/**
* Revoke an auth token: remove from storage and notify all connected clients
* using this token that they are no longer trusted.
*/
declare function revokeAuthToken(context: DevframeNodeContext, storage: SharedState<InternalAnonymousAuthStorage>, token: string): Promise<void>;
//#endregion
//#region src/node/auth/state.d.ts
/**
* The current one-time authentication code. Display this to the user (e.g. in
* the dev-server terminal) so they can type it into the browser to authenticate.
*/
declare function getTempAuthCode(): string;
/**
* Rotate the authentication code, resetting its expiry window and failed-attempt
* counter. Call this when a new authentication flow begins (e.g. when an
* untrusted client starts authenticating) so the displayed code is freshly
* valid for its full TTL.
*/
declare function refreshTempAuthCode(): string;
/**
* Build a "magic link" authentication URL that embeds a one-time code (OTP) as
* a query parameter. Opening it authenticates the client without typing — print
* it on startup (devframe stays headless, so the host prints its own banner).
* Defaults to the current code; the link is subject to the same TTL.
*/
declare function buildOtpAuthUrl(baseUrl: string, code?: string): string;
/**
* Re-authenticate a connection that presents a previously-issued bearer token.
* Returns `true` and marks the session trusted when the token is known.
*
* Used by the `anonymous:devframe:auth` handler so a client that already
* authenticated (token persisted in the browser) is trusted on reconnect
* without entering the code again.
*/
declare function verifyAuthToken(token: string, session: DevframeNodeRpcSession, storage: SharedState<InternalAnonymousAuthStorage>): boolean;
/**
* Exchange a one-time authentication code for a fresh, node-issued bearer token.
*
* On success this mints a high-entropy token, records it in the trusted store,
* marks the calling session trusted, rotates the code, and returns the token
* for the client to persist. Returns `null` on any failure.
*
* Because the code is short and human-typed, verification is hardened against
* brute force: it enforces a time-to-live, compares in constant time, and
* rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an
* attacker cannot keep guessing against the same code.
*/
declare function exchangeTempAuthCode(code: string, session: DevframeNodeRpcSession, info: {
ua: string;
origin: string;
}, storage: SharedState<InternalAnonymousAuthStorage>): string | null;
//#endregion
export { verifyAuthToken as a, refreshTempAuthCode as i, exchangeTempAuthCode as n, revokeActiveConnectionsForToken as o, getTempAuthCode as r, revokeAuthToken as s, buildOtpAuthUrl as t };
import { C as RpcFunctionType, S as RpcFunctionSetupResult, T as RpcReturnSchema, _ as RpcFunctionDefinition, i as RpcArgsSchema, v as RpcFunctionDefinitionAny, w as RpcFunctionsCollector } from "./types-CrzNxXKq.mjs";
import { a as getDefinitionsWithDumps$1, c as StaticRpcDumpManifest$1, d as StaticRpcDumpManifestValue$1, f as StaticRpcDumpSerialization$1, i as dumpFunctions$1, l as StaticRpcDumpManifestQueryEntry$1, n as serializeDumpError$1, o as StaticRpcDumpCollection$1, p as collectStaticRpcDump$1, r as createClientFromDump$1, s as StaticRpcDumpFile$1, t as reviveDumpError$1, u as StaticRpcDumpManifestStaticEntry$1 } from "./index-YDSJgsyG.mjs";
//#region src/rpc/cache.d.ts
interface RpcCacheOptions {
functions: string[];
keySerializer?: (args: unknown[]) => string;
}
/**
* @experimental API is expected to change.
*/
declare class RpcCacheManager {
private cacheMap;
private options;
private keySerializer;
constructor(options: RpcCacheOptions);
updateOptions(options: Partial<RpcCacheOptions>): void;
cached<T>(m: string, a: unknown[]): T | undefined;
has(m: string, a: unknown[]): boolean;
apply(req: {
m: string;
a: unknown[];
}, res: unknown): void;
validate(m: string): boolean;
clear(fn?: string): void;
}
//#endregion
//#region src/rpc/collector.d.ts
declare class RpcFunctionsCollectorBase<LocalFunctions extends Record<string, any>, SetupContext> implements RpcFunctionsCollector<LocalFunctions, SetupContext> {
readonly context: SetupContext;
readonly definitions: Map<string, RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>>;
readonly functions: LocalFunctions;
private readonly _onChanged;
constructor(context: SetupContext);
register(fn: RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>, force?: boolean): void;
update(fn: RpcFunctionDefinition<string, any, any, any, any, any, SetupContext>, force?: boolean): void;
onChanged(fn: (id?: string) => void): () => void;
getHandler<T extends keyof LocalFunctions>(name: T): Promise<LocalFunctions[T]>;
getSchema<T extends keyof LocalFunctions>(name: T): {
args: RpcArgsSchema | undefined;
returns: RpcReturnSchema | undefined;
};
has(name: string): boolean;
get(name: string): RpcFunctionDefinition<string, any, any, any, any, any, SetupContext> | undefined;
list(): string[];
}
//#endregion
//#region src/rpc/define.d.ts
declare function defineRpcFunction<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends RpcArgsSchema | undefined = undefined, const RS extends RpcReturnSchema | undefined = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS>): RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS>;
declare function createDefineWrapperWithContext<CONTEXT>(): <NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends RpcArgsSchema | undefined = undefined, const RS extends RpcReturnSchema | undefined = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, CONTEXT>) => RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, CONTEXT>;
//#endregion
//#region src/rpc/handler.d.ts
declare function getRpcResolvedSetupResult<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, CONTEXT = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, any, any, CONTEXT>, context: CONTEXT): Promise<RpcFunctionSetupResult<ARGS, RETURN>>;
declare function getRpcHandler<NAME extends string, TYPE extends RpcFunctionType, ARGS extends any[], RETURN = void, CONTEXT = undefined>(definition: RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, any, any, CONTEXT>, context: CONTEXT): Promise<(...args: ARGS) => RETURN>;
//#endregion
//#region src/rpc/serialization.d.ts
/**
* Wire format used by the WS RPC transport.
*
* - **JSON (default, unprefixed):** payload is plain JSON text. Used when
* the dispatched method is declared `jsonSerializable: true`. Encoded
* via {@link strictJsonStringify} (rejects non-JSON values), decoded
* via `JSON.parse`.
* - **Structured-clone (`s:` prefix):** payload is `s:` followed by
* `structured-clone-es` text. Used when the method is declared
* `jsonSerializable: false` (or omitted, the default). Round-trips
* `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
*
* birpc envelopes always start with `{`, so a leading byte that is not
* `s` is unambiguously JSON. Each direction independently chooses its
* encoding from local definitions — request and response are not
* coupled by a mirror rule.
*/
declare const STRUCTURED_CLONE_PREFIX = "s:";
/**
* `JSON.stringify` with a single-pass strict replacer.
*
* Throws `DF0020` synchronously when the value contains a type JSON
* cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
* instances, or `undefined` inside an array (silently becomes `null`).
*
* Native pass-throughs (no extra work needed):
* - circular references — `JSON.stringify` raises `TypeError`.
* - `BigInt` at top level — caught here for a friendlier error path.
*
* Lenient cases (allowed without throwing):
* - `undefined` as an object property — legitimate optional field;
* JSON.stringify just omits it.
* - `undefined` at the root — legitimate "action returned nothing".
* - `Symbol` / `Function` values — semantically "drop me" in JSON.
*
* `fnName` is used only for the diagnostic message — pass the RPC
* function name when calling from a wire serializer / dump writer so
* the error points at the offending function.
*/
declare function strictJsonStringify(value: unknown, fnName?: string): string;
//#endregion
//#region src/rpc/validation.d.ts
/**
* Validates RPC function definitions.
* Action and event functions cannot have dumps (side effects should not be cached).
*
* @throws {Error} If an action or event function has a dump configuration
*/
declare function validateDefinitions(definitions: readonly RpcFunctionDefinitionAny[]): void;
/**
* Validates a single RPC function definition.
*
* @throws {Error} If an action or event function has a dump configuration
*/
declare function validateDefinition(definition: RpcFunctionDefinitionAny): void;
//#endregion
//#region src/rpc/index.d.ts
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const collectStaticRpcDump: typeof collectStaticRpcDump$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const createClientFromDump: typeof createClientFromDump$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const dumpFunctions: typeof dumpFunctions$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const getDefinitionsWithDumps: typeof getDefinitionsWithDumps$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const reviveDumpError: typeof reviveDumpError$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
declare const serializeDumpError: typeof serializeDumpError$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpCollection = StaticRpcDumpCollection$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpFile = StaticRpcDumpFile$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifest = StaticRpcDumpManifest$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestQueryEntry = StaticRpcDumpManifestQueryEntry$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestStaticEntry = StaticRpcDumpManifestStaticEntry$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpManifestValue = StaticRpcDumpManifestValue$1;
/** @deprecated Import from `devframe/rpc/dump` instead. */
type StaticRpcDumpSerialization = StaticRpcDumpSerialization$1;
//#endregion
export { RpcCacheManager as C, RpcFunctionsCollectorBase as S, strictJsonStringify as _, StaticRpcDumpManifestStaticEntry as a, createDefineWrapperWithContext as b, collectStaticRpcDump as c, getDefinitionsWithDumps as d, reviveDumpError as f, STRUCTURED_CLONE_PREFIX as g, validateDefinitions as h, StaticRpcDumpManifestQueryEntry as i, createClientFromDump as l, validateDefinition as m, StaticRpcDumpFile as n, StaticRpcDumpManifestValue as o, serializeDumpError as p, StaticRpcDumpManifest as r, StaticRpcDumpSerialization as s, StaticRpcDumpCollection as t, dumpFunctions as u, getRpcHandler as v, RpcCacheOptions as w, defineRpcFunction as x, getRpcResolvedSetupResult as y };
import { h as RpcDumpStore, l as RpcDumpClientOptions, m as RpcDumpRecordError, n as BirpcReturn, o as RpcDefinitionsToFunctions, u as RpcDumpCollectionOptions, v as RpcFunctionDefinitionAny } from "./types-CrzNxXKq.mjs";
//#region src/rpc/dump/static.d.ts
type StaticRpcDumpSerialization = 'json' | 'structured-clone';
interface StaticRpcDumpManifestStaticEntry {
type: 'static';
path: string;
/** Encoder used when this entry's file was written. Default: `'json'`. */
serialization?: StaticRpcDumpSerialization;
}
interface StaticRpcDumpManifestQueryEntry {
type: 'query';
records: Record<string, string>;
fallback?: string;
/** Encoder used when each record/fallback file was written. Default: `'json'`. */
serialization?: StaticRpcDumpSerialization;
}
type StaticRpcDumpManifestValue = StaticRpcDumpManifestStaticEntry | StaticRpcDumpManifestQueryEntry | any;
type StaticRpcDumpManifest = Record<string, StaticRpcDumpManifestValue>;
interface StaticRpcDumpFile {
/** Whether this file was written via `JSON.stringify` or `structured-clone-es.stringify`. */
serialization: StaticRpcDumpSerialization;
/** Function name the file belongs to — used to scope `DF0019` errors during write. */
fnName: string;
/** Payload to encode. */
data: unknown;
}
interface StaticRpcDumpCollection {
manifest: StaticRpcDumpManifest;
files: Record<string, StaticRpcDumpFile>;
}
declare function collectStaticRpcDump(definitions: Iterable<RpcFunctionDefinitionAny>, context: any): Promise<StaticRpcDumpCollection>;
//#endregion
//#region src/rpc/dump/collect.d.ts
/**
* Collects pre-computed dumps by executing functions with their defined input combinations.
* Static functions without dump config automatically get `{ inputs: [[]] }`.
*
* @example
* ```ts
* const store = await dumpFunctions([greet], context, { concurrency: 10 })
* ```
*/
declare function dumpFunctions<T extends readonly RpcFunctionDefinitionAny[]>(definitions: T, context?: any, options?: RpcDumpCollectionOptions): Promise<RpcDumpStore<RpcDefinitionsToFunctions<T>>>;
/**
* Creates a client that serves pre-computed results from a dump store.
* Uses argument hashing to match calls to stored records.
*
* @example
* ```ts
* const client = createClientFromDump(store)
* await client.greet('Alice')
* ```
*/
declare function createClientFromDump<T extends Record<string, any>>(store: RpcDumpStore<T>, options?: RpcDumpClientOptions): BirpcReturn<T>;
/**
* Filters function definitions to only those with dump definitions.
* Note: Only checks the definition itself, not setup results.
*/
declare function getDefinitionsWithDumps<T extends readonly RpcFunctionDefinitionAny[]>(definitions: T): RpcFunctionDefinitionAny[];
//#endregion
//#region src/rpc/dump/error.d.ts
/**
* Normalize a thrown value into a plain object suitable for storage in
* a dump record. Preserves `message`, `name`, `cause`, and any own
* enumerable properties of an `Error` so consumers reading the dump can
* reconstruct a richer Error than just `{ message, name }`.
*
* Non-`Error` throws are wrapped as `{ name: 'Error', message: String(thrown) }`.
*/
declare function serializeDumpError(error: unknown): RpcDumpRecordError;
/**
* Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
* the plain object stored in a dump record. Preserves `cause`, restores
* the original `name`, and re-attaches any custom own properties.
*/
declare function reviveDumpError(stored: RpcDumpRecordError): Error;
//#endregion
export { getDefinitionsWithDumps as a, StaticRpcDumpManifest as c, StaticRpcDumpManifestValue as d, StaticRpcDumpSerialization as f, dumpFunctions as i, StaticRpcDumpManifestQueryEntry as l, serializeDumpError as n, StaticRpcDumpCollection as o, collectStaticRpcDump as p, createClientFromDump as r, StaticRpcDumpFile as s, reviveDumpError as t, StaticRpcDumpManifestStaticEntry as u };
import { t as diagnostics } from "./diagnostics-BXwBQmoN.mjs";
//#region src/rpc/serialization.ts
/**
* Wire format used by the WS RPC transport.
*
* - **JSON (default, unprefixed):** payload is plain JSON text. Used when
* the dispatched method is declared `jsonSerializable: true`. Encoded
* via {@link strictJsonStringify} (rejects non-JSON values), decoded
* via `JSON.parse`.
* - **Structured-clone (`s:` prefix):** payload is `s:` followed by
* `structured-clone-es` text. Used when the method is declared
* `jsonSerializable: false` (or omitted, the default). Round-trips
* `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
*
* birpc envelopes always start with `{`, so a leading byte that is not
* `s` is unambiguously JSON. Each direction independently chooses its
* encoding from local definitions — request and response are not
* coupled by a mirror rule.
*/
const STRUCTURED_CLONE_PREFIX = "s:";
/**
* `JSON.stringify` with a single-pass strict replacer.
*
* Throws `DF0020` synchronously when the value contains a type JSON
* cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
* instances, or `undefined` inside an array (silently becomes `null`).
*
* Native pass-throughs (no extra work needed):
* - circular references — `JSON.stringify` raises `TypeError`.
* - `BigInt` at top level — caught here for a friendlier error path.
*
* Lenient cases (allowed without throwing):
* - `undefined` as an object property — legitimate optional field;
* JSON.stringify just omits it.
* - `undefined` at the root — legitimate "action returned nothing".
* - `Symbol` / `Function` values — semantically "drop me" in JSON.
*
* `fnName` is used only for the diagnostic message — pass the RPC
* function name when calling from a wire serializer / dump writer so
* the error points at the offending function.
*/
function strictJsonStringify(value, fnName = "") {
return JSON.stringify(value, function strictReplacer(key, val) {
const holder = this;
const original = holder != null ? holder[key] : val;
if (original === void 0) {
if (Array.isArray(holder)) throw nonJsonAt(fnName, "undefined", holder, key);
return val;
}
if (original === null) return val;
if (typeof original === "bigint") throw nonJsonAt(fnName, "BigInt", holder, key);
if (typeof original === "object") {
if (original instanceof Map) throw nonJsonAt(fnName, "Map", holder, key);
if (original instanceof Set) throw nonJsonAt(fnName, "Set", holder, key);
if (original instanceof Date) throw nonJsonAt(fnName, "Date", holder, key);
if (Array.isArray(original)) return val;
const proto = Object.getPrototypeOf(original);
if (proto !== null && proto !== Object.prototype) throw nonJsonAt(fnName, original.constructor?.name ?? "class instance", holder, key);
}
return val;
});
}
function nonJsonAt(fnName, type, parent, key) {
const path = formatPath(parent, key);
return diagnostics.DF0020({
name: fnName || "<anonymous>",
type,
path
});
}
function formatPath(parent, key) {
if (Array.isArray(parent)) return `[${key}]`;
if (key === "") return "<root>";
return key;
}
//#endregion
export { strictJsonStringify as n, STRUCTURED_CLONE_PREFIX as t };
import { $ as DevframeRpcServerFunctions, Q as DevframeRpcClientFunctions, W as DevframeNodeRpcSession, _ as ConnectionMeta, b as DevframeNodeContext, p as DevframeAuthHandler } from "./devframe-Ckhf3kFw.mjs";
import "./index-8dndvIxR.mjs";
import { Peer } from "crossws";
import { BirpcGroup, EventOptions } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Server } from "node:http";
import { H3 } from "h3";
//#region src/node/server.d.ts
interface StartHttpAndWsOptions {
context: DevframeNodeContext;
host?: string;
port: number;
/**
* Optional h3 app to mount on. When omitted a fresh one is created;
* when provided, callers can add their own routes (static handlers,
* auth middleware, etc.) first.
*/
app?: H3;
/**
* Bind the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`) instead of
* claiming every upgrade on the port. This lets the socket share a server
* with other upgrade handlers (Vite HMR, a host framework's own sockets)
* and is what the SPA's `__connection.json` points at. When omitted, the WS
* server handles every upgrade on the port (legacy behaviour).
*/
path?: string;
/**
* Bind the WS endpoint on its own port instead of sharing the HTTP server's.
* The HTTP/SPA server still listens on `port`; the socket gets a dedicated
* `ws` server on `wsPort` (same `host`). Use this for the "different port"
* connection scenario. Ignored when a `server` is supplied.
*/
wsPort?: number;
/**
* Mount the WS endpoint onto an existing HTTP server, sharing its port,
* rather than creating and listening on a fresh one. Use this to embed
* devframe's RPC socket inside a host server (e.g. a Vite dev server) — pair
* it with `path` so it coexists with the host's routes. The caller owns the
* server's lifecycle: {@link StartedServer.close} detaches devframe's upgrade
* listener but leaves the host server running. When set, `host`/`port` are
* only used to report the resolved origin.
*/
server?: Server;
/**
* Authentication for the server:
*
* - `true` (default) — no gate; every registered method is callable
* regardless of trust (today's behavior, unchanged).
* - `false` — the RPC server is started without a trust handshake.
* Intended for single-user localhost tools where an auth round-trip
* would only get in the way. A noop `anonymous:devframe:auth` handler
* is registered so the browser client's unconditional handshake call
* succeeds and auto-trusts.
* - A {@link DevframeAuthHandler} (e.g. from
* `devframe/recipes/interactive-auth`'s `createInteractiveAuth`) —
* registers its `rpcFunctions`, wires its `authorize` as the resolver
* gate, and wires its `onConnect` on every new peer. This is the
* fully-authenticated server: an untrusted caller can only reach
* `anonymous:`-prefixed methods (see `isAnonymousRpcMethod`).
*/
auth?: boolean | DevframeAuthHandler;
/**
* Lower-level escape hatch: gate individual RPC calls by method name and
* session without a full {@link DevframeAuthHandler}. Ignored when `auth`
* is a handler object (its own `authorize` is used); combine with `auth:
* true` to layer a custom policy on top of an otherwise ungated server.
*/
authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean;
/**
* Called once per new WS connection, right after its session is created
* (before any RPC call is dispatched). Runs after the auth handler's own
* `onConnect` (when `auth` is a {@link DevframeAuthHandler}), so it can
* observe — but not override — the connect-time trust decision.
*/
onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void;
/**
* Forwarded verbatim to the internal `createRpcServer`'s birpc
* `rpcOptions`, alongside the resolver `startHttpAndWs` installs for
* auth/session wiring. Use this so a host that owns its own structured
* diagnostics (e.g. a coded error reporter) keeps seeing RPC failures
* instead of them being silently absorbed by delegating to
* `startHttpAndWs`. Returning `true` from either callback suppresses
* birpc's own error response to the caller — see birpc's
* `EventOptions` for the full contract.
*/
rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>;
/**
* Extra origins to accept on the WS upgrade beyond the loopback default
* (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less request from a
* native client). Add your LAN/tunnel origin here when reaching the tool
* from another host. Pass `false` to disable origin checking entirely
* (not recommended). Default: loopback-only.
*/
allowedOrigins?: readonly string[] | false;
/**
* Called once the WS server is bound so callers can mount static
* handlers whose origin depends on the resolved port, or print their
* own startup banner. Devframe does not print one itself.
*/
onReady?: (info: {
origin: string;
port: number;
app: H3;
}) => void | Promise<void>;
}
interface StartedServer {
/** Listening origin, e.g. `http://localhost:9999`. */
origin: string;
port: number;
app: H3;
/** The crossws node adapter driving the RPC socket (connected peers, pub/sub). */
ws: NodeAdapter;
rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
/**
* The {@link ConnectionMeta} descriptor for this server — the same shape
* a `__connection.json` route should serve so a devframe client's
* `resolveWsUrl` can dial back in. Reflects the `path` / `wsPort` this
* server was started with and the `jsonSerializable` methods currently
* registered on `context.rpc`.
*/
connectionMeta: () => ConnectionMeta;
close: () => Promise<void>;
}
/**
* Compose an h3 + WebSocket server for a devframe context. The RPC
* group is bound to `context.rpc.functions`; the WS endpoint lives on
* the same port as the HTTP server.
*/
declare function startHttpAndWs(options: StartHttpAndWsOptions): Promise<StartedServer>;
//#endregion
export { StartedServer as n, startHttpAndWs as r, StartHttpAndWsOptions as t };
import { createRpcServer } from "./rpc/server.mjs";
import { attachWsRpcTransport } from "./rpc/transports/ws-server.mjs";
import { a as diagnostics } from "./storage-gEnj9ASQ.mjs";
import { t as getInternalContext } from "./context-DY72brRI.mjs";
import { createServer } from "node:http";
import { AsyncLocalStorage } from "node:async_hooks";
import { H3, toNodeHandler } from "h3";
import { isIP } from "node:net";
//#region src/node/utils.ts
function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
const NON_DIALABLE_HOSTS = /* @__PURE__ */ new Set([
"0.0.0.0",
"127.0.0.1",
"::",
"0000:0000:0000:0000:0000:0000:0000:0000",
""
]);
/** Map a bind host to a host a client can actually connect to. */
function toDialableHost(host) {
return NON_DIALABLE_HOSTS.has(host) ? "localhost" : host;
}
/** Format a bind host for use in a URL authority (dialable, IPv6-bracketed). */
function formatHostForUrl(host) {
const dialable = toDialableHost(host);
return isIP(dialable) === 6 ? `[${dialable}]` : dialable;
}
function normalizeHttpServerUrl(host, port) {
return `http://${formatHostForUrl(host)}:${port}`;
}
//#endregion
//#region src/node/server.ts
/**
* Compose an h3 + WebSocket server for a devframe context. The RPC
* group is bound to `context.rpc.functions`; the WS endpoint lives on
* the same port as the HTTP server.
*/
async function startHttpAndWs(options) {
const { context, port } = options;
const bindHost = options.host ?? "localhost";
const app = options.app ?? new H3();
const ownsHttpServer = !options.server;
const httpServer = options.server ?? createServer(toNodeHandler(app));
const rpcHost = context.rpc;
const asyncStorage = new AsyncLocalStorage();
const authHandler = typeof options.auth === "object" ? options.auth : void 0;
const effectiveAuthorize = options.authorize ?? authHandler?.authorize;
if (authHandler) {
for (const fn of authHandler.rpcFunctions) if (!rpcHost.definitions.has(fn.name)) rpcHost.register(fn);
}
const rpcGroup = createRpcServer(rpcHost.functions, { rpcOptions: {
onFunctionError: options.rpcOptions?.onFunctionError,
onGeneralError: options.rpcOptions?.onGeneralError,
resolver(name, fn) {
const rpc = this;
if (!fn) return void 0;
return async function(...args) {
const meta = rpc.$meta;
if (effectiveAuthorize && !effectiveAuthorize(name, {
meta,
rpc
})) throw diagnostics.DF0036({ name });
return await asyncStorage.run({
rpc,
meta
}, async () => {
return (await fn).apply(this, args);
});
};
}
} });
const separateWsPort = ownsHttpServer && options.wsPort != null && options.wsPort !== port ? options.wsPort : void 0;
const { ws, close: closeWs } = attachWsRpcTransport(rpcGroup, {
...separateWsPort != null ? {
port: separateWsPort,
host: bindHost
} : { server: httpServer },
path: options.path,
destroyUnmatched: ownsHttpServer,
allowedOrigins: options.allowedOrigins,
onConnected: authHandler || options.onPeerConnect ? (peer, meta) => {
const session = {
meta,
rpc: rpcGroup.clients.find((client) => client.$meta === meta)
};
authHandler?.onConnect(peer, session);
options.onPeerConnect?.(peer, session);
} : void 0,
onDisconnected: (_peer, meta) => {
rpcHost._emitSessionDisconnected(meta);
}
});
rpcHost._rpcGroup = rpcGroup;
rpcHost._asyncStorage = asyncStorage;
rpcHost._authDisabled = options.auth === false;
if (options.auth === false && !rpcHost.definitions.has("anonymous:devframe:auth")) rpcHost.register({
name: "anonymous:devframe:auth",
type: "action",
handler: () => {
const session = rpcHost.getCurrentRpcSession();
if (session) session.meta.isTrusted = true;
return { isTrusted: true };
}
});
if (ownsHttpServer) await new Promise((resolveListen) => {
httpServer.listen(port, bindHost, () => resolveListen());
});
const address = httpServer.address();
const resolvedPort = typeof address === "object" && address ? address.port : port;
const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
const internal = getInternalContext(context);
const wsPortForUrl = separateWsPort ?? resolvedPort;
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${wsPortForUrl}${options.path ?? ""}`;
internal.wsEndpoint = { url: wsUrl };
if (options.onReady) await options.onReady({
origin,
port: resolvedPort,
app
});
function connectionMeta() {
const jsonSerializableMethods = [];
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
return {
backend: "websocket",
websocket: separateWsPort != null ? {
port: separateWsPort,
path: options.path
} : { path: options.path },
jsonSerializableMethods
};
}
return {
origin,
port: resolvedPort,
app,
ws,
rpcGroup,
connectionMeta,
async close() {
await closeWs();
if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
if (getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).wsEndpoint = void 0;
}
};
}
//#endregion
export { toDialableHost as a, normalizeHttpServerUrl as i, formatHostForUrl as n, isObject as r, startHttpAndWs as t };
import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
import { defineDiagnostics } from "nostics";
import fs from "node:fs";
import { dirname } from "pathe";
import process$1 from "node:process";
import { destr } from "destr";
//#region src/node/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
reporters: [devframeReporter],
codes: {
DF0006: { why: (p) => `RPC function "${p.name}" is not registered` },
DF0007: { why: "AsyncLocalStorage is not set, it likely to be an internal bug of the Devframe foundation" },
DF0008: { why: (p) => `distDir ${p.distDir} does not exist` },
DF0012: { why: (p) => `Failed to parse storage file: ${p.filepath}, falling back to defaults.` },
DF0013: { why: (p) => `Shared state of "${p.key}" is not found, please provide an initial value for the first time` },
DF0014: {
why: (p) => `RPC function "${p.name}" has an invalid \`agent\` field — \`description\` must be a non-empty string.`,
fix: "Provide a short description (~1–3 sentences) explaining what the tool does and when agents should invoke it."
},
DF0015: {
why: (p) => `Agent tool "${p.id}" is already registered.`,
fix: "Tool ids must be unique across RPC functions with an `agent` field and tools registered via `ctx.agent.registerTool()`."
},
DF0016: { why: (p) => `Agent resource "${p.id}" is already registered.` },
DF0017: { why: (p) => `Failed to start MCP server (${p.transport}): ${p.reason}` },
DF0029: {
why: (p) => `Stream "${p.channel}#${p.id}" dropped ${p.dropped} chunk(s) after exceeding the client high-water mark.`,
fix: "The consumer is too slow for the producer. Raise `highWaterMark` on the subscription, slow the producer, or batch chunks."
},
DF0030: {
why: (p) => `Stream "${p.channel}#${p.id}" is unknown — no producer has called \`channel.start({ id: "${p.id}" })\`.`,
fix: "Ensure the server-side producer is running before clients subscribe, or check for typos in the stream id."
},
DF0031: {
why: (p) => `Cannot write to closed stream "${p.channel}#${p.id}".`,
fix: "Track the producer lifecycle — guard writes with the `stream.signal.aborted` flag."
},
DF0032: {
why: (p) => `Streaming channel "${p.channel}" is already registered.`,
fix: "Each channel name must be unique within a context. Pick a different name or reuse the existing channel handle."
},
DF0033: {
why: (p) => `Failed to start dev RPC bridge for "${p.id}": ${p.reason}`,
fix: "Verify the bridge port is free and the devframe setup function does not throw. Pin a port via `cli.port` / `cli.portRange` on the definition, or via `devMiddleware.port` on `viteDevBridge`."
},
DF0034: {
why: (p) => `Scoped RPC registration for namespace "${p.namespace}" received an already-namespaced function name "${p.name}".`,
fix: "A scoped context auto-namespaces ids. Pass a bare name without a \":\" separator (e.g. `register({ name: \"get-cwd\" })`), or use the unscoped `ctx.base.rpc.register` for a fully-qualified name."
},
DF0035: {
why: (p) => `Failed to persist storage file: ${p.filepath}`,
fix: "Check that the storage directory is writable and has free space."
},
DF0036: {
why: (p) => `RPC call to "${p.name}" was rejected: the caller is not authorized.`,
fix: "Complete the auth handshake (or connect with a static/pre-shared token) before calling a trusted method. Untrusted callers may only call `anonymous:`-prefixed methods — see `isAnonymousRpcMethod`."
},
DF0037: {
why: (p) => `A service is already provided under "${p.id}".`,
fix: "Service ids are unique per context. Revoke the existing provider first (the `provide()` call returns a revoke function), or namespace the id with your plugin id to avoid collisions."
},
DF0042: {
why: (p) => `"${p.id}" declares \`capabilities.build: false\` — its static export is not meaningful (writes are excluded and any live-served data won't be there).`,
fix: "Pass `{ force: true }` to `createBuild()` if the degraded export is still useful to you, or drop `capabilities.build: false` on the definition."
}
}
});
//#endregion
//#region src/utils/events.ts
/**
* Create event emitter.
*/
function createEventEmitter() {
const _listeners = {};
function emit(event, ...args) {
const callbacks = _listeners[event] || [];
for (let i = 0, length = callbacks.length; i < length; i++) {
const callback = callbacks[i];
if (callback) callback(...args);
}
}
function emitOnce(event, ...args) {
emit(event, ...args);
delete _listeners[event];
}
function on(event, cb) {
(_listeners[event] ||= []).push(cb);
return () => {
_listeners[event] = _listeners[event]?.filter((i) => cb !== i);
};
}
function once(event, cb) {
const unsubscribe = on(event, ((...args) => {
unsubscribe();
return cb(...args);
}));
return unsubscribe;
}
return {
_listeners,
emit,
emitOnce,
on,
once
};
}
//#endregion
//#region ../../node_modules/.pnpm/immer@11.1.15/node_modules/immer/dist/immer.mjs
var NOTHING = Symbol.for("immer-nothing");
var DRAFTABLE = Symbol.for("immer-draftable");
var DRAFT_STATE = Symbol.for("immer-state");
var errors = process.env.NODE_ENV !== "production" ? [
function(plugin) {
return `The plugin for '${plugin}' has not been loaded into Immer. To enable the plugin, import and call \`enable${plugin}()\` when initializing your application.`;
},
function(thing) {
return `produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${thing}'`;
},
"This object has been frozen and should not be mutated",
function(data) {
return "Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " + data;
},
"An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
"Immer forbids circular references",
"The first or second argument to `produce` must be a function",
"The third argument to `produce` must be a function or undefined",
"First argument to `createDraft` must be a plain object, an array, or an immerable object",
"First argument to `finishDraft` must be a draft returned by `createDraft`",
function(thing) {
return `'current' expects a draft, got: ${thing}`;
},
"Object.defineProperty() cannot be used on an Immer draft",
"Object.setPrototypeOf() cannot be used on an Immer draft",
"Immer only supports deleting array indices",
"Immer only supports setting array indices and the 'length' property",
function(thing) {
return `'original' expects a draft, got: ${thing}`;
}
] : [];
function die(error, ...args) {
if (process.env.NODE_ENV !== "production") {
const e = errors[error];
const msg = isFunction(e) ? e.apply(null, args) : e;
throw new Error(`[Immer] ${msg}`);
}
throw new Error(`[Immer] minified error nr: ${error}. Full error at: https://bit.ly/3cXEKWf`);
}
var O = Object;
var getPrototypeOf = O.getPrototypeOf;
var CONSTRUCTOR = "constructor";
var PROTOTYPE = "prototype";
var CONFIGURABLE = "configurable";
var ENUMERABLE = "enumerable";
var WRITABLE = "writable";
var VALUE = "value";
var isDraft = (value) => !!value && !!value[DRAFT_STATE];
function isDraftable(value) {
if (!value) return false;
return isPlainObject(value) || isArray(value) || !!value[DRAFTABLE] || !!value[CONSTRUCTOR]?.[DRAFTABLE] || isMap(value) || isSet(value);
}
var objectCtorString = O[PROTOTYPE][CONSTRUCTOR].toString();
var cachedCtorStrings = /* @__PURE__ */ new WeakMap();
function isPlainObject(value) {
if (!value || !isObjectish(value)) return false;
const proto = getPrototypeOf(value);
if (proto === null || proto === O[PROTOTYPE]) return true;
const Ctor = O.hasOwnProperty.call(proto, CONSTRUCTOR) && proto[CONSTRUCTOR];
if (Ctor === Object) return true;
if (!isFunction(Ctor)) return false;
let ctorString = cachedCtorStrings.get(Ctor);
if (ctorString === void 0) {
ctorString = Function.toString.call(Ctor);
cachedCtorStrings.set(Ctor, ctorString);
}
return ctorString === objectCtorString;
}
function each(obj, iter, strict = true) {
if (getArchtype(obj) === 0) (strict ? Reflect.ownKeys(obj) : O.keys(obj)).forEach((key) => {
iter(key, obj[key], obj);
});
else obj.forEach((entry, index) => iter(index, entry, obj));
}
function getArchtype(thing) {
const state = thing[DRAFT_STATE];
return state ? state.type_ : isArray(thing) ? 1 : isMap(thing) ? 2 : isSet(thing) ? 3 : 0;
}
var has = (thing, prop, type = getArchtype(thing)) => type === 2 ? thing.has(prop) : O[PROTOTYPE].hasOwnProperty.call(thing, prop);
var get = (thing, prop, type = getArchtype(thing)) => type === 2 ? thing.get(prop) : thing[prop];
var set = (thing, propOrOldValue, value, type = getArchtype(thing)) => {
if (type === 2) thing.set(propOrOldValue, value);
else if (type === 3) thing.add(value);
else thing[propOrOldValue] = value;
};
function is(x, y) {
if (x === y) return x !== 0 || 1 / x === 1 / y;
else return x !== x && y !== y;
}
var isArray = Array.isArray;
var isMap = (target) => target instanceof Map;
var isSet = (target) => target instanceof Set;
var isObjectish = (target) => typeof target === "object";
var isFunction = (target) => typeof target === "function";
var isBoolean = (target) => typeof target === "boolean";
function isArrayIndex(value) {
const n = +value;
return Number.isInteger(n) && String(n) === value;
}
var getProxyDraft = (value) => {
if (!isObjectish(value)) return null;
return value?.[DRAFT_STATE];
};
var latest = (state) => state.copy_ || state.base_;
var getFinalValue = (state) => state.modified_ ? state.copy_ : state.base_;
function shallowCopy(base, strict) {
if (isMap(base)) return new Map(base);
if (isSet(base)) return new Set(base);
if (isArray(base)) return Array[PROTOTYPE].slice.call(base);
const isPlain = isPlainObject(base);
if (strict === true || strict === "class_only" && !isPlain) {
const descriptors = O.getOwnPropertyDescriptors(base);
delete descriptors[DRAFT_STATE];
let keys = Reflect.ownKeys(descriptors);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const desc = descriptors[key];
if (desc[WRITABLE] === false) {
desc[WRITABLE] = true;
desc[CONFIGURABLE] = true;
}
if (desc.get || desc.set) descriptors[key] = {
[CONFIGURABLE]: true,
[WRITABLE]: true,
[ENUMERABLE]: desc[ENUMERABLE],
[VALUE]: base[key]
};
}
return O.create(getPrototypeOf(base), descriptors);
} else {
const proto = getPrototypeOf(base);
if (proto !== null && isPlain) return { ...base };
const obj = O.create(proto);
return O.assign(obj, base);
}
}
function freeze(obj, deep = false) {
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj;
if (getArchtype(obj) > 1) O.defineProperties(obj, {
set: dontMutateMethodOverride,
add: dontMutateMethodOverride,
clear: dontMutateMethodOverride,
delete: dontMutateMethodOverride
});
O.freeze(obj);
if (deep) each(obj, (_key, value) => {
freeze(value, true);
}, false);
return obj;
}
function dontMutateFrozenCollections() {
die(2);
}
var dontMutateMethodOverride = { [VALUE]: dontMutateFrozenCollections };
function isFrozen(obj) {
if (obj === null || !isObjectish(obj)) return true;
return O.isFrozen(obj);
}
var PluginMapSet = "MapSet";
var PluginPatches = "Patches";
var PluginArrayMethods = "ArrayMethods";
var plugins = {};
function getPlugin(pluginKey) {
const plugin = plugins[pluginKey];
if (!plugin) die(0, pluginKey);
return plugin;
}
var isPluginLoaded = (pluginKey) => !!plugins[pluginKey];
function loadPlugin(pluginKey, implementation) {
if (!plugins[pluginKey]) plugins[pluginKey] = implementation;
}
var currentScope;
var getCurrentScope = () => currentScope;
var createScope = (parent_, immer_) => ({
drafts_: [],
parent_,
immer_,
canAutoFreeze_: true,
unfinalizedDrafts_: 0,
handledSet_: /* @__PURE__ */ new Set(),
processedForPatches_: /* @__PURE__ */ new Set(),
mapSetPlugin_: isPluginLoaded(PluginMapSet) ? getPlugin(PluginMapSet) : void 0,
arrayMethodsPlugin_: isPluginLoaded(PluginArrayMethods) ? getPlugin(PluginArrayMethods) : void 0
});
function usePatchesInScope(scope, patchListener) {
if (patchListener) {
scope.patchPlugin_ = getPlugin(PluginPatches);
scope.patches_ = [];
scope.inversePatches_ = [];
scope.patchListener_ = patchListener;
}
}
function revokeScope(scope) {
leaveScope(scope);
scope.drafts_.forEach(revokeDraft);
scope.drafts_ = null;
}
function leaveScope(scope) {
if (scope === currentScope) currentScope = scope.parent_;
}
var enterScope = (immer2) => currentScope = createScope(currentScope, immer2);
function revokeDraft(draft) {
const state = draft[DRAFT_STATE];
if (state.type_ === 0 || state.type_ === 1) state.revoke_();
else state.revoked_ = true;
}
function processResult(result, scope) {
scope.unfinalizedDrafts_ = scope.drafts_.length;
const baseDraft = scope.drafts_[0];
if (result !== void 0 && result !== baseDraft) {
if (baseDraft[DRAFT_STATE].modified_) {
revokeScope(scope);
die(4);
}
if (isDraftable(result)) result = finalize(scope, result);
const { patchPlugin_ } = scope;
if (patchPlugin_) patchPlugin_.generateReplacementPatches_(baseDraft[DRAFT_STATE].base_, result, scope);
} else result = finalize(scope, baseDraft);
maybeFreeze(scope, result, true);
revokeScope(scope);
if (scope.patches_) scope.patchListener_(scope.patches_, scope.inversePatches_);
return result !== NOTHING ? result : void 0;
}
function finalize(rootScope, value) {
if (isFrozen(value)) return value;
const state = value[DRAFT_STATE];
if (!state) return handleValue(value, rootScope.handledSet_, rootScope);
if (!isSameScope(state, rootScope)) return value;
if (!state.modified_) return state.base_;
if (!state.finalized_) {
const { callbacks_ } = state;
if (callbacks_) while (callbacks_.length > 0) callbacks_.pop()(rootScope);
generatePatchesAndFinalize(state, rootScope);
}
return state.copy_;
}
function maybeFreeze(scope, value, deep = false) {
if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) freeze(value, deep);
}
function markStateFinalized(state) {
state.finalized_ = true;
state.scope_.unfinalizedDrafts_--;
}
var isSameScope = (state, rootScope) => state.scope_ === rootScope;
var EMPTY_LOCATIONS_RESULT = [];
function updateDraftInParent(parent, draftValue, finalizedValue, originalKey) {
const parentCopy = latest(parent);
const parentType = parent.type_;
if (originalKey !== void 0) {
if (get(parentCopy, originalKey, parentType) === draftValue) {
set(parentCopy, originalKey, finalizedValue, parentType);
return;
}
}
if (!parent.draftLocations_) {
const draftLocations = parent.draftLocations_ = /* @__PURE__ */ new Map();
each(parentCopy, (key, value) => {
if (isDraft(value)) {
const keys = draftLocations.get(value) || [];
keys.push(key);
draftLocations.set(value, keys);
}
});
}
const locations = parent.draftLocations_.get(draftValue) ?? EMPTY_LOCATIONS_RESULT;
for (const location of locations) set(parentCopy, location, finalizedValue, parentType);
}
function registerChildFinalizationCallback(parent, child, key) {
parent.callbacks_.push(function childCleanup(rootScope) {
const state = child;
if (!state || !isSameScope(state, rootScope)) return;
rootScope.mapSetPlugin_?.fixSetContents(state);
const finalizedValue = getFinalValue(state);
updateDraftInParent(parent, state.draft_ ?? state, finalizedValue, key);
generatePatchesAndFinalize(state, rootScope);
});
}
function generatePatchesAndFinalize(state, rootScope) {
if (state.modified_ && !state.finalized_ && (state.type_ === 3 || state.type_ === 1 && state.allIndicesReassigned_ || (state.assigned_?.size ?? 0) > 0)) {
const { patchPlugin_ } = rootScope;
if (patchPlugin_) {
const basePath = patchPlugin_.getPath(state);
if (basePath) patchPlugin_.generatePatches_(state, basePath, rootScope);
}
markStateFinalized(state);
}
}
function handleCrossReference(target, key, value) {
const { scope_ } = target;
if (isDraft(value)) {
const state = value[DRAFT_STATE];
if (isSameScope(state, scope_)) state.callbacks_.push(function crossReferenceCleanup() {
prepareCopy(target);
updateDraftInParent(target, value, getFinalValue(state), key);
});
} else if (isDraftable(value)) target.callbacks_.push(function nestedDraftCleanup() {
const targetCopy = latest(target);
if (target.type_ === 3) {
if (targetCopy.has(value)) handleValue(value, scope_.handledSet_, scope_);
} else if (get(targetCopy, key, target.type_) === value) {
if (scope_.drafts_.length > 1 && (target.assigned_.get(key) ?? false) === true && target.copy_) handleValue(get(target.copy_, key, target.type_), scope_.handledSet_, scope_);
}
});
}
function handleValue(target, handledSet, rootScope) {
if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) return target;
if (isDraft(target) || handledSet.has(target) || !isDraftable(target) || isFrozen(target)) return target;
handledSet.add(target);
each(target, (key, value) => {
if (isDraft(value)) {
const state = value[DRAFT_STATE];
if (isSameScope(state, rootScope)) {
set(target, key, getFinalValue(state), target.type_);
markStateFinalized(state);
}
} else if (isDraftable(value)) handleValue(value, handledSet, rootScope);
});
return target;
}
function createProxyProxy(base, parent) {
const baseIsArray = isArray(base);
const state = {
type_: baseIsArray ? 1 : 0,
scope_: parent ? parent.scope_ : getCurrentScope(),
modified_: false,
finalized_: false,
assigned_: void 0,
parent_: parent,
base_: base,
draft_: null,
copy_: null,
revoke_: null,
isManual_: false,
callbacks_: void 0
};
let target = state;
let traps = objectTraps;
if (baseIsArray) {
target = [state];
traps = arrayTraps;
}
const { revoke, proxy } = Proxy.revocable(target, traps);
state.draft_ = proxy;
state.revoke_ = revoke;
return [proxy, state];
}
var objectTraps = {
get(state, prop) {
if (prop === DRAFT_STATE) return state;
let arrayPlugin = state.scope_.arrayMethodsPlugin_;
const isArrayWithStringProp = state.type_ === 1 && typeof prop === "string";
if (isArrayWithStringProp) {
if (arrayPlugin?.isArrayOperationMethod(prop)) return arrayPlugin.createMethodInterceptor(state, prop);
}
const source = latest(state);
if (!has(source, prop, state.type_)) return readPropFromProto(state, source, prop);
const value = source[prop];
if (state.finalized_ || !isDraftable(value)) return value;
if (isArrayWithStringProp && state.operationMethod && arrayPlugin?.isMutatingArrayMethod(state.operationMethod) && isArrayIndex(prop)) return value;
if (value === peek(state.base_, prop) || isRelocatedBaseRef(state, prop, value)) {
prepareCopy(state);
const childKey = state.type_ === 1 ? +prop : prop;
const childDraft = createProxy(state.scope_, value, state, childKey);
return state.copy_[childKey] = childDraft;
}
return value;
},
has(state, prop) {
return prop in latest(state);
},
ownKeys(state) {
return Reflect.ownKeys(latest(state));
},
set(state, prop, value) {
const desc = getDescriptorFromProto(latest(state), prop);
if (desc?.set) {
desc.set.call(state.draft_, value);
return true;
}
if (!state.modified_) {
const current2 = peek(latest(state), prop);
const currentState = current2?.[DRAFT_STATE];
if (currentState && currentState.base_ === value) {
state.copy_[prop] = value;
state.assigned_.set(prop, false);
return true;
}
if (is(value, current2) && (value !== void 0 || has(state.base_, prop, state.type_))) return true;
prepareCopy(state);
markChanged(state);
}
if (state.copy_[prop] === value && (value !== void 0 || has(state.copy_, prop, state.type_)) || Number.isNaN(value) && Number.isNaN(state.copy_[prop])) return true;
state.copy_[prop] = value;
state.assigned_.set(prop, true);
handleCrossReference(state, prop, value);
return true;
},
deleteProperty(state, prop) {
prepareCopy(state);
if (peek(state.base_, prop) !== void 0 || prop in state.base_) {
state.assigned_.set(prop, false);
markChanged(state);
} else state.assigned_.delete(prop);
if (state.copy_) delete state.copy_[prop];
return true;
},
getOwnPropertyDescriptor(state, prop) {
const owner = latest(state);
const desc = Reflect.getOwnPropertyDescriptor(owner, prop);
if (!desc) return desc;
return {
[WRITABLE]: true,
[CONFIGURABLE]: state.type_ !== 1 || prop !== "length",
[ENUMERABLE]: desc[ENUMERABLE],
[VALUE]: owner[prop]
};
},
defineProperty() {
die(11);
},
getPrototypeOf(state) {
return getPrototypeOf(state.base_);
},
setPrototypeOf() {
die(12);
}
};
var arrayTraps = {};
for (let key in objectTraps) {
let fn = objectTraps[key];
arrayTraps[key] = function() {
const args = arguments;
args[0] = args[0][0];
return fn.apply(this, args);
};
}
arrayTraps.deleteProperty = function(state, prop) {
if (process.env.NODE_ENV !== "production" && isNaN(parseInt(prop))) die(13);
return arrayTraps.set.call(this, state, prop, void 0);
};
arrayTraps.set = function(state, prop, value) {
if (process.env.NODE_ENV !== "production" && prop !== "length" && isNaN(parseInt(prop))) die(14);
return objectTraps.set.call(this, state[0], prop, value, state[0]);
};
function peek(draft, prop) {
const state = draft[DRAFT_STATE];
return (state ? latest(state) : draft)[prop];
}
function isRelocatedBaseRef(state, prop, value) {
if (state.type_ !== 1 || !state.allIndicesReassigned_ || state.assigned_?.get(prop) || !isDraftable(value) || value[DRAFT_STATE]) return false;
return state.baseRefs_.has(value);
}
function readPropFromProto(state, source, prop) {
const desc = getDescriptorFromProto(source, prop);
return desc ? VALUE in desc ? desc[VALUE] : desc.get?.call(state.draft_) : void 0;
}
function getDescriptorFromProto(source, prop) {
if (!(prop in source)) return void 0;
let proto = getPrototypeOf(source);
while (proto) {
const desc = Object.getOwnPropertyDescriptor(proto, prop);
if (desc) return desc;
proto = getPrototypeOf(proto);
}
}
function markChanged(state) {
if (!state.modified_) {
state.modified_ = true;
if (state.parent_) markChanged(state.parent_);
}
}
function prepareCopy(state) {
if (!state.copy_) {
state.assigned_ = /* @__PURE__ */ new Map();
state.copy_ = shallowCopy(state.base_, state.scope_.immer_.useStrictShallowCopy_);
}
}
var Immer2 = class {
constructor(config) {
this.autoFreeze_ = true;
this.useStrictShallowCopy_ = false;
this.useStrictIteration_ = false;
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
this.produce = (base, recipe, patchListener) => {
if (isFunction(base) && !isFunction(recipe)) {
const defaultBase = recipe;
recipe = base;
const self = this;
return function curriedProduce(base2 = defaultBase, ...args) {
return self.produce(base2, (draft) => recipe.call(this, draft, ...args));
};
}
if (!isFunction(recipe)) die(6);
if (patchListener !== void 0 && !isFunction(patchListener)) die(7);
let result;
if (isDraftable(base)) {
const scope = enterScope(this);
const proxy = createProxy(scope, base, void 0);
let hasError = true;
try {
result = recipe(proxy);
hasError = false;
} finally {
if (hasError) revokeScope(scope);
else leaveScope(scope);
}
usePatchesInScope(scope, patchListener);
return processResult(result, scope);
} else if (!base || !isObjectish(base)) {
result = recipe(base);
if (result === void 0) result = base;
if (result === NOTHING) result = void 0;
if (this.autoFreeze_) freeze(result, true);
if (patchListener) {
const p = [];
const ip = [];
getPlugin(PluginPatches).generateReplacementPatches_(base, result, {
patches_: p,
inversePatches_: ip
});
patchListener(p, ip);
}
return result;
} else die(1, base);
};
this.produceWithPatches = (base, recipe) => {
if (isFunction(base)) return (state, ...args) => this.produceWithPatches(state, (draft) => base(draft, ...args));
let patches, inversePatches;
return [
this.produce(base, recipe, (p, ip) => {
patches = p;
inversePatches = ip;
}),
patches,
inversePatches
];
};
if (isBoolean(config?.autoFreeze)) this.setAutoFreeze(config.autoFreeze);
if (isBoolean(config?.useStrictShallowCopy)) this.setUseStrictShallowCopy(config.useStrictShallowCopy);
if (isBoolean(config?.useStrictIteration)) this.setUseStrictIteration(config.useStrictIteration);
}
createDraft(base) {
if (!isDraftable(base)) die(8);
if (isDraft(base)) base = current(base);
const scope = enterScope(this);
const proxy = createProxy(scope, base, void 0);
proxy[DRAFT_STATE].isManual_ = true;
leaveScope(scope);
return proxy;
}
finishDraft(draft, patchListener) {
const state = draft && draft[DRAFT_STATE];
if (!state || !state.isManual_) die(9);
const { scope_: scope } = state;
usePatchesInScope(scope, patchListener);
return processResult(void 0, scope);
}
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is enabled.
*/
setAutoFreeze(value) {
this.autoFreeze_ = value;
}
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
setUseStrictShallowCopy(value) {
this.useStrictShallowCopy_ = value;
}
/**
* Pass false to use faster iteration that skips non-enumerable properties
* but still handles symbols for compatibility.
*
* By default, strict iteration is enabled (includes all own properties).
*/
setUseStrictIteration(value) {
this.useStrictIteration_ = value;
}
shouldUseStrictIteration() {
return this.useStrictIteration_;
}
applyPatches(base, patches) {
let i;
for (i = patches.length - 1; i >= 0; i--) {
const patch = patches[i];
if (patch.path.length === 0 && patch.op === "replace") {
base = patch.value;
break;
}
}
if (i > -1) patches = patches.slice(i + 1);
const applyPatchesImpl = getPlugin(PluginPatches).applyPatches_;
if (isDraft(base)) return applyPatchesImpl(base, patches);
return this.produce(base, (draft) => applyPatchesImpl(draft, patches));
}
};
function createProxy(rootScope, value, parent, key) {
const [draft, state] = isMap(value) ? getPlugin(PluginMapSet).proxyMap_(value, parent) : isSet(value) ? getPlugin(PluginMapSet).proxySet_(value, parent) : createProxyProxy(value, parent);
(parent?.scope_ ?? getCurrentScope()).drafts_.push(draft);
state.callbacks_ = parent?.callbacks_ ?? [];
state.key_ = key;
if (parent && key !== void 0) registerChildFinalizationCallback(parent, state, key);
else state.callbacks_.push(function rootDraftCleanup(rootScope2) {
rootScope2.mapSetPlugin_?.fixSetContents(state);
const { patchPlugin_ } = rootScope2;
if (state.modified_ && patchPlugin_) patchPlugin_.generatePatches_(state, [], rootScope2);
});
return draft;
}
function current(value) {
if (!isDraft(value)) die(10, value);
return currentImpl(value);
}
function currentImpl(value) {
if (!isDraftable(value) || isFrozen(value)) return value;
const state = value[DRAFT_STATE];
let copy;
let strict = true;
if (state) {
if (!state.modified_) return state.base_;
state.finalized_ = true;
copy = shallowCopy(value, state.scope_.immer_.useStrictShallowCopy_);
strict = state.scope_.immer_.shouldUseStrictIteration();
} else copy = shallowCopy(value, true);
each(copy, (key, childValue) => {
set(copy, key, currentImpl(childValue));
}, strict);
if (state) state.finalized_ = false;
return copy;
}
function enablePatches() {
const errorOffset = 16;
if (process.env.NODE_ENV !== "production") errors.push("Sets cannot have \"replace\" patches.", function(op) {
return "Unsupported patch operation: " + op;
}, function(path) {
return "Cannot apply patch, path doesn't resolve: " + path;
}, "Patching reserved attributes like __proto__, prototype and constructor is not allowed");
function getPath(state, path = []) {
if (state.key_ !== void 0) {
const parentCopy = state.parent_.copy_ ?? state.parent_.base_;
const proxyDraft = getProxyDraft(get(parentCopy, state.key_));
const valueAtKey = get(parentCopy, state.key_);
if (valueAtKey === void 0) return null;
if (valueAtKey !== state.draft_ && valueAtKey !== state.base_ && valueAtKey !== state.copy_) return null;
if (proxyDraft != null && proxyDraft.base_ !== state.base_) return null;
const isSet2 = state.parent_.type_ === 3;
let key;
if (isSet2) {
const setParent = state.parent_;
key = Array.from(setParent.drafts_.keys()).indexOf(state.key_);
} else key = state.key_;
if (!(isSet2 && parentCopy.size > key || has(parentCopy, key))) return null;
path.push(key);
}
if (state.parent_) return getPath(state.parent_, path);
path.reverse();
try {
resolvePath(state.copy_, path);
} catch (e) {
return null;
}
return path;
}
function resolvePath(base, path) {
let current2 = base;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
current2 = get(current2, key);
if (!isObjectish(current2) || current2 === null) throw new Error(`Cannot resolve path at '${path.join("/")}'`);
}
return current2;
}
const REPLACE = "replace";
const ADD = "add";
const REMOVE = "remove";
function generatePatches_(state, basePath, scope) {
if (state.scope_.processedForPatches_.has(state)) return;
state.scope_.processedForPatches_.add(state);
const { patches_, inversePatches_ } = scope;
switch (state.type_) {
case 0:
case 2: return generatePatchesFromAssigned(state, basePath, patches_, inversePatches_);
case 1: return generateArrayPatches(state, basePath, patches_, inversePatches_);
case 3: return generateSetPatches(state, basePath, patches_, inversePatches_);
}
}
function generateArrayPatches(state, basePath, patches, inversePatches) {
let { base_, assigned_ } = state;
let copy_ = state.copy_;
if (copy_.length < base_.length) {
[base_, copy_] = [copy_, base_];
[patches, inversePatches] = [inversePatches, patches];
}
const allReassigned = state.allIndicesReassigned_ === true;
for (let i = 0; i < base_.length; i++) {
const copiedItem = copy_[i];
const baseItem = base_[i];
if ((allReassigned || assigned_?.get(i.toString())) && copiedItem !== baseItem) {
const childState = copiedItem?.[DRAFT_STATE];
if (childState && childState.modified_) continue;
const path = basePath.concat([i]);
patches.push({
op: REPLACE,
path,
value: clonePatchValueIfNeeded(copiedItem)
});
inversePatches.push({
op: REPLACE,
path,
value: clonePatchValueIfNeeded(baseItem)
});
}
}
for (let i = base_.length; i < copy_.length; i++) {
const path = basePath.concat([i]);
patches.push({
op: ADD,
path,
value: clonePatchValueIfNeeded(copy_[i])
});
}
for (let i = copy_.length - 1; base_.length <= i; --i) {
const path = basePath.concat([i]);
inversePatches.push({
op: REMOVE,
path
});
}
}
function generatePatchesFromAssigned(state, basePath, patches, inversePatches) {
const { base_, copy_, type_ } = state;
each(state.assigned_, (key, assignedValue) => {
const origValue = get(base_, key, type_);
const value = get(copy_, key, type_);
const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD;
if (origValue === value && op === REPLACE) return;
const path = basePath.concat(key);
patches.push(op === REMOVE ? {
op,
path
} : {
op,
path,
value: clonePatchValueIfNeeded(value)
});
inversePatches.push(op === ADD ? {
op: REMOVE,
path
} : op === REMOVE ? {
op: ADD,
path,
value: clonePatchValueIfNeeded(origValue)
} : {
op: REPLACE,
path,
value: clonePatchValueIfNeeded(origValue)
});
});
}
function generateSetPatches(state, basePath, patches, inversePatches) {
let { base_, copy_ } = state;
let i = 0;
base_.forEach((value) => {
if (!copy_.has(value)) {
const path = basePath.concat([i]);
patches.push({
op: REMOVE,
path,
value
});
inversePatches.unshift({
op: ADD,
path,
value
});
}
i++;
});
i = 0;
copy_.forEach((value) => {
if (!base_.has(value)) {
const path = basePath.concat([i]);
patches.push({
op: ADD,
path,
value
});
inversePatches.unshift({
op: REMOVE,
path,
value
});
}
i++;
});
}
function generateReplacementPatches_(baseValue, replacement, scope) {
const { patches_, inversePatches_ } = scope;
patches_.push({
op: REPLACE,
path: [],
value: replacement === NOTHING ? void 0 : replacement
});
inversePatches_.push({
op: REPLACE,
path: [],
value: baseValue
});
}
function applyPatches_(draft, patches) {
patches.forEach((patch) => {
const { path, op } = patch;
let base = draft;
for (let i = 0; i < path.length - 1; i++) {
const parentType = getArchtype(base);
let p = path[i];
if (typeof p !== "string" && typeof p !== "number") p = "" + p;
if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(19);
if (isFunction(base) && p === PROTOTYPE) die(19);
base = get(base, p);
if (base === null || !isObjectish(base)) die(18, path.join("/"));
}
const type = getArchtype(base);
const value = deepClonePatchValue(patch.value);
const key = path[path.length - 1];
switch (op) {
case REPLACE: switch (type) {
case 2: return base.set(key, value);
case 3: die(errorOffset);
default: return base[key] = value;
}
case ADD: switch (type) {
case 1: return key === "-" ? base.push(value) : base.splice(key, 0, value);
case 2: return base.set(key, value);
case 3: return base.add(value);
default: return base[key] = value;
}
case REMOVE: switch (type) {
case 1: return base.splice(key, 1);
case 2: return base.delete(key);
case 3: return base.delete(patch.value);
default: return delete base[key];
}
default: die(17, op);
}
});
return draft;
}
function deepClonePatchValue(obj) {
if (!isDraftable(obj)) return obj;
if (isArray(obj)) return obj.map(deepClonePatchValue);
if (isMap(obj)) return new Map(Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)]));
if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue));
const cloned = Object.create(getPrototypeOf(obj));
for (const key in obj) cloned[key] = deepClonePatchValue(obj[key]);
if (has(obj, DRAFTABLE)) cloned[DRAFTABLE] = obj[DRAFTABLE];
return cloned;
}
function clonePatchValueIfNeeded(obj) {
if (isDraft(obj)) return deepClonePatchValue(obj);
else return obj;
}
loadPlugin(PluginPatches, {
applyPatches_,
generatePatches_,
generateReplacementPatches_,
getPath
});
}
var immer = new Immer2();
var produce = immer.produce;
var produceWithPatches = /* @__PURE__ */ immer.produceWithPatches.bind(immer);
var applyPatches = /* @__PURE__ */ immer.applyPatches.bind(immer);
//#endregion
//#region src/utils/nanoid.ts
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function nanoid(size = 21) {
let id = "";
let i = size;
while (i--) id += urlAlphabet[Math.random() * 64 | 0];
return id;
}
//#endregion
//#region src/utils/shared-state.ts
/**
* Upper bound on retained syncIds. Loop echoes arrive near-immediately, so a
* generous window preserves de-dup while capping memory on long-lived,
* frequently-mutated states (e.g. a 1s terminal poll).
*/
const MAX_SYNC_IDS = 1e3;
function rememberSyncId(syncIds, syncId) {
syncIds.add(syncId);
if (syncIds.size > MAX_SYNC_IDS) {
const oldest = syncIds.values().next().value;
if (oldest !== void 0) syncIds.delete(oldest);
}
}
function createSharedState(options) {
const { enablePatches: enablePatches$1 = false } = options;
if (enablePatches$1) enablePatches();
const events = createEventEmitter();
let state = options.initialValue;
const syncIds = /* @__PURE__ */ new Set();
return {
on: events.on,
value: () => state,
patch: (patches, syncId = nanoid()) => {
if (syncIds.has(syncId)) return;
enablePatches();
state = applyPatches(state, patches);
rememberSyncId(syncIds, syncId);
events.emit("updated", state, void 0, syncId);
},
mutate: (fn, syncId = nanoid()) => {
if (syncIds.has(syncId)) return;
rememberSyncId(syncIds, syncId);
if (enablePatches$1) {
const [newState, patches] = produceWithPatches(state, fn);
state = newState;
events.emit("updated", state, patches, syncId);
} else {
state = produce(state, fn);
events.emit("updated", state, void 0, syncId);
}
},
syncIds
};
}
//#endregion
//#region ../../node_modules/.pnpm/perfect-debounce@2.1.0/node_modules/perfect-debounce/dist/index.mjs
const DEBOUNCE_DEFAULTS = { trailing: true };
/**
Debounce functions
@param fn - Promise-returning/async function to debounce.
@param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
@returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
@example
```
import { debounce } from 'perfect-debounce';
const expensiveCall = async input => input;
const debouncedFn = debounce(expensiveCall, 200);
for (const number of [1, 2, 3]) {
console.log(await debouncedFn(number));
}
//=> 1
//=> 2
//=> 3
```
*/
function debounce(fn, wait = 25, options = {}) {
options = {
...DEBOUNCE_DEFAULTS,
...options
};
if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
let leadingValue;
let timeout;
let resolveList = [];
let currentPromise;
let trailingArgs;
const applyFn = (_this, args) => {
currentPromise = _applyPromised(fn, _this, args);
currentPromise.finally(() => {
currentPromise = null;
if (options.trailing && trailingArgs && !timeout) {
const promise = applyFn(_this, trailingArgs);
trailingArgs = null;
return promise;
}
});
return currentPromise;
};
const debounced = function(...args) {
if (options.trailing) trailingArgs = args;
if (currentPromise) return currentPromise;
return new Promise((resolve) => {
const shouldCallNow = !timeout && options.leading;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
const promise = options.leading ? leadingValue : applyFn(this, args);
trailingArgs = null;
for (const _resolve of resolveList) _resolve(promise);
resolveList = [];
}, wait);
if (shouldCallNow) {
leadingValue = applyFn(this, args);
resolve(leadingValue);
} else resolveList.push(resolve);
});
};
const _clearTimeout = (timer) => {
if (timer) {
clearTimeout(timer);
timeout = null;
}
};
debounced.isPending = () => !!timeout;
debounced.cancel = () => {
_clearTimeout(timeout);
resolveList = [];
trailingArgs = null;
};
debounced.flush = () => {
_clearTimeout(timeout);
if (!trailingArgs || currentPromise) return;
const args = trailingArgs;
trailingArgs = null;
return applyFn(this, args);
};
return debounced;
}
async function _applyPromised(fn, _this, args) {
return await fn.apply(_this, args);
}
//#endregion
//#region src/node/storage.ts
function createStorage(options) {
const { mergeInitialValue = (initialValue, savedValue) => ({
...initialValue,
...savedValue
}), debounce: debounceTime = 100 } = options;
let initialValue = options.initialValue;
if (fs.existsSync(options.filepath)) try {
const savedValue = destr(fs.readFileSync(options.filepath, "utf-8"), { strict: true });
initialValue = mergeInitialValue ? mergeInitialValue(options.initialValue, savedValue) : savedValue;
} catch (error) {
diagnostics.DF0012({
filepath: options.filepath,
cause: error
}, { method: "warn" });
initialValue = options.initialValue;
}
const state = createSharedState({
initialValue,
enablePatches: false
});
state.on("updated", debounce((newState) => {
try {
const dir = dirname(options.filepath);
fs.mkdirSync(dir, { recursive: true });
const tmp = `${options.filepath}.${process$1.pid}.tmp`;
fs.writeFileSync(tmp, `${JSON.stringify(newState, null, 2)}\n`);
fs.renameSync(tmp, options.filepath);
} catch (error) {
diagnostics.DF0035({
filepath: options.filepath,
cause: error
}, { method: "error" });
}
}, debounceTime));
return state;
}
//#endregion
export { diagnostics as a, createEventEmitter as i, createSharedState as n, nanoid as r, createStorage as t };
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
//#region src/adapters/mcp/transports.ts
/**
* Start the MCP server on stdio. Returns a stop function.
* @internal
*/
async function startStdioTransport(server) {
const transport = new StdioServerTransport();
await server.connect(transport);
return async () => {
await server.close();
};
}
//#endregion
export { startStdioTransport };
import { GenericSchema, InferInput } from "valibot";
import { BirpcFn, BirpcReturn as BirpcReturn$1 } from "birpc";
//#region src/rpc/utils.d.ts
/** Infers TypeScript tuple type from Valibot schema array */
type InferArgsType<S extends RpcArgsSchema | undefined> = S extends readonly [] ? [] : S extends readonly [infer H, ...infer T] ? H extends GenericSchema ? T extends readonly GenericSchema[] ? [InferInput<H>, ...InferArgsType<T>] : never : never : never;
/** Infers TypeScript return type from Valibot return schema */
type InferReturnType<S extends RpcReturnSchema | undefined> = S extends RpcReturnSchema ? InferInput<S> : void;
//#endregion
//#region src/rpc/types.d.ts
type Thenable<T> = T | Promise<T>;
type EntriesToObject<T extends readonly [string, any][]> = { [K in T[number] as K[0]]: K[1]; };
/**
* Type of the RPC function,
* - static: A function that returns a static data, no arguments (can be cached and dumped)
* - action: A function that performs an action (no data returned)
* - event: A function that emits an event (no data returned), and does not wait for a response
* - query: A function that queries a resource
*
* By default, the function is a query function.
*/
type RpcFunctionType = 'static' | 'action' | 'event' | 'query';
/**
* Agent exposure settings for an RPC function. When this field is set,
* the function is surfaced to agents (e.g. via the devframe MCP adapter)
* as a callable tool. Functions without an `agent` field are not exposed —
* default-deny.
*
* @experimental The agent-native surface is experimental and may change
* without a major version bump until it stabilizes.
*/
interface RpcFunctionAgentOptions {
/**
* Human-readable description shown to the agent. Required — agents
* rely on this to decide when to invoke the tool. Keep it to ~1–3
* sentences explaining what the tool does and when to use it.
*/
description: string;
/**
* Optional human-friendly display title. Maps to the MCP tool `title`
* annotation. Falls back to the RPC function `name` when omitted.
*/
title?: string;
/**
* Safety classification. Drives MCP annotations (`readOnlyHint`,
* `destructiveHint`) downstream.
* - `'read'` — no side effects; safe to call freely.
* - `'action'` — mutates state but not destructive.
* - `'destructive'` — may perform destructive updates.
*
* When omitted it is inferred from the function `type`:
* - `'static'` / `'query'` → `'read'`
* - `'action'` / `'event'` → `'action'`
*/
safety?: 'read' | 'action' | 'destructive';
/** Free-form tags for grouping or filtering. */
tags?: readonly string[];
/**
* Optional example invocations shown to agents. Returned verbatim in
* the agent manifest.
*/
examples?: readonly {
args: unknown[];
description?: string;
}[];
}
/**
* Manages dynamic function registration and provides a type-safe proxy for accessing functions.
*/
interface RpcFunctionsCollector<LocalFunctions, SetupContext = undefined> {
/** User-provided context passed to setup functions */
context: SetupContext;
/** Type-safe proxy for calling registered functions */
readonly functions: LocalFunctions;
/** Map of registered function definitions keyed by function name */
readonly definitions: Map<string, RpcFunctionDefinitionAnyWithContext<SetupContext>>;
/** Register a new function definition. Pass `force` to overwrite an existing one. */
register: (fn: RpcFunctionDefinitionAnyWithContext<SetupContext>, force?: boolean) => void;
/** Update an existing function definition. Pass `force` to register it if it doesn't exist yet. */
update: (fn: RpcFunctionDefinitionAnyWithContext<SetupContext>, force?: boolean) => void;
/** Subscribe to function changes, returns unsubscribe function */
onChanged: (fn: (id?: string) => void) => (() => void);
}
/**
* Result returned by a function's setup method.
*/
interface RpcFunctionSetupResult<ARGS extends any[], RETURN = void> {
/** Function handler */
handler?: (...args: ARGS) => RETURN;
/** Optional dump definition (overrides definition-level dump) */
dump?: RpcDumpDefinition<ARGS, RETURN>;
}
/** Valibot schema array for validating function arguments */
type RpcArgsSchema = readonly GenericSchema[];
/** Valibot schema for validating function return value */
type RpcReturnSchema = GenericSchema;
/**
* Serialized representation of a thrown value in a dump record.
*
* Errors are stored as plain objects so they round-trip through both the
* strict-JSON and structured-clone codecs. `message` and `name` are always
* present; `cause` and any own enumerable properties of the original
* `Error` are preserved on a best-effort basis. Non-`Error` throws are
* normalized to `{ name: 'Error', message: String(thrown) }`.
*/
interface RpcDumpRecordError {
/** Error message (mirrors `Error.message`). */
message: string;
/** Error type name (e.g., "Error", "TypeError"). */
name: string;
/** `Error.cause`, recursively serialized when it is itself an `Error`. */
cause?: unknown;
/** Own enumerable properties of the original error (excluding `message`/`name`/`cause`). */
[key: string]: unknown;
}
/**
* Single record in a dump store with pre-computed results.
*/
interface RpcDumpRecord<ARGS extends any[] = any[], RETURN = any> {
/** Function arguments */
inputs: ARGS;
/** Result (value or lazy function) */
output?: RETURN;
/** Error if execution failed */
error?: RpcDumpRecordError;
}
/**
* Defines argument combinations to pre-compute for a function.
*/
interface RpcDumpDefinition<ARGS extends any[] = any[], RETURN = any> {
/** Argument combinations to pre-compute by executing handler */
inputs?: ARGS[];
/** Pre-computed records to use directly (bypasses handler execution) */
records?: RpcDumpRecord<ARGS, RETURN>[];
/** Fallback value when no match found */
fallback?: RETURN;
}
/**
* Dynamically generates dump definitions based on context.
*/
type RpcDumpGetter<ARGS extends any[] = any[], RETURN = any, CONTEXT = any> = (context: CONTEXT, handler: (...args: ARGS) => RETURN) => Thenable<RpcDumpDefinition<ARGS, RETURN>>;
/**
* Dump configuration (static object or dynamic function).
*/
type RpcDump<ARGS extends any[] = any[], RETURN = any, CONTEXT = any> = RpcDumpDefinition<ARGS, RETURN> | RpcDumpGetter<ARGS, RETURN, CONTEXT>;
/**
* Base function definition metadata.
*/
interface RpcFunctionDefinitionBase {
/** Function name (unique identifier) */
name: string;
/** Function type (static, action, event, or query) */
type?: RpcFunctionType;
/**
* Declares whether this function's args/return are JSON-serializable
* — i.e. no `Map`, `Set`, `Date`, `BigInt`, class instances, circular
* references, `undefined` leaves, `Symbol`, or `Function` values.
*
* - `true` — args and return are encoded with strict `JSON.stringify`
* on the wire and on disk. Misshapen values throw `DF0019` at the
* sender, surfacing the bug *during the offending call* rather than
* silently coercing to `{}` later. Required for `agent` exposure.
* - `false` (default) — payloads use `structured-clone-es`, which
* round-trips Maps/Sets/cycles. Functions in this mode cannot be
* exposed via the `agent` field — registration throws `DF0018`.
*/
jsonSerializable?: boolean;
}
/**
* Dump store containing pre-computed results.
* Flat structure for serialization and efficient lookups.
*/
interface RpcDumpStore<T = any> {
/** Function definitions keyed by name */
definitions: Record<string, RpcFunctionDefinitionBase>;
/** Records keyed by '<function-name>---<hash>' or '<function-name>---fallback' */
records: Record<string, RpcDumpRecord | (() => Promise<RpcDumpRecord>)>;
/** @internal */
_functions?: T;
}
/**
* Dump client options.
*/
interface RpcDumpClientOptions {
/** Called when arguments don't match any pre-computed entry */
onMiss?: (functionName: string, args: any[]) => void;
}
/**
* Options for collecting dumps.
*/
interface RpcDumpCollectionOptions {
/**
* Concurrency control for parallel execution.
* - `false` or `undefined`: sequential execution (default)
* - `true`: parallel execution with concurrency limit of 5
* - `number`: parallel execution with specified concurrency limit
*/
concurrency?: boolean | number | null;
}
/**
* RPC function definition with optional dump support.
*/
type RpcFunctionDefinition<NAME extends string, TYPE extends RpcFunctionType = 'query', ARGS extends any[] = [], RETURN = void, AS extends RpcArgsSchema | undefined = undefined, RS extends RpcReturnSchema | undefined = undefined, CONTEXT = undefined> = [AS, RS] extends [undefined, undefined] ? {
/** Function name (unique identifier) */
name: NAME;
/** Function type (static, action, event, or query) */
type?: TYPE;
/** Whether the function results should be cached */
cacheable?: boolean;
/** Valibot schema array for validating function arguments */
args?: AS;
/** Valibot schema for validating function return value */
returns?: RS;
/**
* Declares whether this function's args/return are JSON-serializable
* (no Map/Set/Date/BigInt/cycles/class instances/undefined/Symbol/Function).
*
* - `true` — wire and dump use strict `JSON.stringify`; misshapen
* values throw `DF0019` at the call site. Required for `agent`.
* - `false` (default) — `structured-clone-es` round-trips fancy
* types. Cannot be `agent`-exposed (registration throws `DF0018`).
*/
jsonSerializable?: boolean;
/**
* Expose this function to agents (e.g. via the MCP adapter).
* When omitted, the function is not agent-exposed (default-deny).
*
* @experimental
*/
agent?: RpcFunctionAgentOptions;
/** Setup function called with context to initialize handler and dump */
setup?: (context: CONTEXT) => Thenable<RpcFunctionSetupResult<ARGS, RETURN>>;
/** Function implementation (required if setup doesn't provide one) */
handler?: (...args: ARGS) => RETURN;
/** Dump definition (setup dump takes priority) */
dump?: RpcDump<ARGS, RETURN, CONTEXT>;
/**
* Sugar for "query in dev, single baked snapshot in build": when
* `true` and no `dump` is provided, the build adapter runs the
* handler once with no arguments and stores the result as both a
* no-args record and the fallback so any call variant resolves
* to the same snapshot. Only valid on `query` (or untyped)
* functions — `static` already has equivalent default behavior.
*/
snapshot?: boolean;
/** Per-context setup-result cache, populated by `getRpcResolvedSetupResult`. @internal */
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<ARGS, RETURN>>>;
/** Single-slot fallback for primitive contexts. @internal */
__promise?: Thenable<RpcFunctionSetupResult<ARGS, RETURN>>;
} : {
/** Function name (unique identifier) */
name: NAME;
/** Function type (static, action, event, or query) */
type?: TYPE;
/** Whether the function results should be cached */
cacheable?: boolean;
/** Valibot schema array for validating function arguments */
args: AS;
/** Valibot schema for validating function return value */
returns: RS;
/**
* Declares whether this function's args/return are JSON-serializable
* (no Map/Set/Date/BigInt/cycles/class instances/undefined/Symbol/Function).
*
* - `true` — wire and dump use strict `JSON.stringify`; misshapen
* values throw `DF0019` at the call site. Required for `agent`.
* - `false` (default) — `structured-clone-es` round-trips fancy
* types. Cannot be `agent`-exposed (registration throws `DF0018`).
*/
jsonSerializable?: boolean;
/**
* Expose this function to agents (e.g. via the MCP adapter).
* When omitted, the function is not agent-exposed (default-deny).
*
* @experimental
*/
agent?: RpcFunctionAgentOptions;
/** Setup function called with context to initialize handler and dump */
setup?: (context: CONTEXT) => Thenable<RpcFunctionSetupResult<InferArgsType<AS>, InferReturnType<RS>>>;
/** Function implementation (required if setup doesn't provide one) */
handler?: (...args: InferArgsType<AS>) => InferReturnType<RS>;
/** Dump definition (setup dump takes priority) */
dump?: RpcDump<InferArgsType<AS>, InferReturnType<RS>, CONTEXT>;
/**
* Sugar for "query in dev, single baked snapshot in build": when
* `true` and no `dump` is provided, the build adapter runs the
* handler once with no arguments and stores the result as both a
* no-args record and the fallback so any call variant resolves
* to the same snapshot. Only valid on `query` (or untyped)
* functions — `static` already has equivalent default behavior.
*/
snapshot?: boolean;
/** Per-context setup-result cache, populated by `getRpcResolvedSetupResult`. @internal */
__cache?: WeakMap<object, Thenable<RpcFunctionSetupResult<InferArgsType<AS>, InferReturnType<RS>>>>;
/** Single-slot fallback for primitive contexts. @internal */
__promise?: Thenable<RpcFunctionSetupResult<InferArgsType<AS>, InferReturnType<RS>>>;
};
type RpcFunctionDefinitionToFunction<T extends RpcFunctionDefinitionAny> = T extends {
args: infer AS;
returns: infer RS;
} ? AS extends RpcArgsSchema ? RS extends RpcReturnSchema ? (...args: InferArgsType<AS>) => InferReturnType<RS> : never : never : T extends RpcFunctionDefinition<string, any, infer ARGS, infer RETURN, any, any, any> ? (...args: ARGS) => RETURN : never;
type RpcFunctionDefinitionAny = RpcFunctionDefinition<string, any, any, any, any, any, any>;
type RpcFunctionDefinitionAnyWithContext<CONTEXT = undefined> = RpcFunctionDefinition<string, any, any, any, any, any, CONTEXT>;
type RpcDefinitionsToFunctions<T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [T[K]['name'], RpcFunctionDefinitionToFunction<T[K]>]; }>;
/**
* Like {@link RpcDefinitionsToFunctions}, but prefixes every (bare)
* definition name with `<NS>:`. Use this when functions are defined with
* bare names and registered through a scoped context
* (`ctx.scope(NS).rpc.register(...)`), so the augmented registry keys
* match the namespaced ids stored at runtime.
*/
type RpcDefinitionsToFunctionsWithNamespace<NS extends string, T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [`${NS}:${T[K]['name'] & string}`, RpcFunctionDefinitionToFunction<T[K]>]; }>;
type RpcDefinitionsFilter<T extends readonly RpcFunctionDefinitionAny[], Type extends RpcFunctionType> = { [K in keyof T]: T[K] extends {
type: Type;
} ? T[K] : never; };
//#endregion
export { RpcFunctionType as C, Thenable as E, RpcFunctionSetupResult as S, RpcReturnSchema as T, RpcFunctionDefinition as _, RpcDefinitionsFilter as a, RpcFunctionDefinitionBase as b, RpcDump as c, RpcDumpDefinition as d, RpcDumpGetter as f, RpcFunctionAgentOptions as g, RpcDumpStore as h, RpcArgsSchema as i, RpcDumpClientOptions as l, RpcDumpRecordError as m, BirpcReturn$1 as n, RpcDefinitionsToFunctions as o, RpcDumpRecord as p, EntriesToObject as r, RpcDefinitionsToFunctionsWithNamespace as s, BirpcFn as t, RpcDumpCollectionOptions as u, RpcFunctionDefinitionAny as v, RpcFunctionsCollector as w, RpcFunctionDefinitionToFunction as x, RpcFunctionDefinitionAnyWithContext as y };
import { v as RpcFunctionDefinitionAny } from "./types-CrzNxXKq.mjs";
import { ChannelOptions } from "birpc";
//#region src/rpc/transports/ws-client.d.ts
interface WsRpcChannelOptions {
url: string;
onConnected?: (e: Event) => void;
onError?: (e: Error) => void;
onDisconnected?: (e: CloseEvent) => void;
authToken?: string;
/**
* RPC function definitions (or just the `jsonSerializable` flag per
* method) used to dispatch the per-call wire serializer. Pass an
* empty / partial map on clients that don't have the full registry —
* encoding falls back to structured-clone (the safer superset) and
* decoding still routes correctly via the wire prefix.
*/
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
}
/**
* Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
* Pass the result straight to `createRpcClient`'s `channel` option.
*/
declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions;
//#endregion
export { createWsRpcChannel as n, WsRpcChannelOptions as t };
import { v as RpcFunctionDefinitionAny } from "./types-CrzNxXKq.mjs";
import { Peer } from "crossws";
import { BirpcGroup, ChannelOptions } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Server } from "node:http";
import { Server as Server$1, ServerOptions } from "node:https";
//#region src/rpc/transports/ws-server.d.ts
interface DevframeNodeRpcSessionMeta {
id: number;
/** The crossws peer backing this session's socket. */
peer?: Peer;
clientAuthToken?: string;
isTrusted?: boolean;
subscribedStates: Set<string>;
/**
* Streams this session has subscribed to via
* `rpc.streaming.subscribe(channel, id)`. Tracked here for O(1) cleanup
* on disconnect; the wire format is `${channel}\x1F${id}`.
*/
subscribedStreams?: Set<string>;
/**
* Inbound streams this session is currently uploading to (via
* `rpc.streaming.upload(channel, id)`). Tracked for cleanup on
* disconnect; same wire format as `subscribedStreams`.
*/
uploadingStreams?: Set<string>;
}
interface WsRpcTransportOptions {
/**
* Attach to an existing HTTP(S) server, sharing its port. Combine with
* `path` to bind the WS endpoint to a single route so it coexists with
* other upgrade handlers on the same server (e.g. a Vite dev server's HMR
* socket). The shared server's lifecycle is owned by the caller — closing
* this transport detaches the upgrade listener without closing the server.
*/
server?: Server | Server$1;
/** Port for a newly-created standalone WS server. */
port?: number;
/** Host for a newly-created standalone WS server. Defaults to `localhost`. */
host?: string;
/**
* Restrict the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`). When
* sharing a `server`, non-matching upgrade requests are left untouched for
* other listeners to handle, so devframe's socket can sit alongside
* framework sockets (Vite HMR, etc.).
*/
path?: string;
/**
* Destroy upgrade requests that don't match `path` instead of leaving them
* for other listeners. Enable this when devframe owns the shared server
* outright (nothing else handles its upgrades), so an off-route client is
* rejected promptly rather than left hanging. Default: `false`
* (coexist-friendly); servers this transport creates itself always
* destroy unmatched upgrades.
*/
destroyUnmatched?: boolean;
/** When set, a new https.Server is created and the WS endpoint is attached to it. */
https?: ServerOptions;
/**
* Extra origins to accept on the WS upgrade beyond the loopback default.
* Add your LAN/tunnel origin here when reaching the tool from another host.
* Pass `false` to disable origin checking entirely (not recommended).
* Default: loopback-only.
*/
allowedOrigins?: readonly string[] | false;
/**
* RPC function definitions, used by the per-call wire serializer to
* dispatch between strict-JSON and structured-clone encoding based
* on each function's `jsonSerializable` flag.
*
* When omitted, all messages fall back to structured-clone — safe but
* loses dev-time validation for `jsonSerializable: true` declarations.
*/
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
onConnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
onDisconnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
/** Override the default per-call serializer. Most callers should leave this unset. */
serialize?: ChannelOptions['serialize'];
/** Override the default per-call deserializer. Most callers should leave this unset. */
deserialize?: ChannelOptions['deserialize'];
}
interface WsRpcTransport {
/**
* The crossws node adapter driving the socket — exposes the connected
* `peers` and pub/sub. See https://crossws.h3.dev.
*/
ws: NodeAdapter;
/** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
detach: () => void;
/**
* Tear the transport down deterministically: detach from a shared server,
* force-terminate every connected peer, and close any server this
* transport created itself (`port` / `https` modes).
*/
close: () => Promise<void>;
}
declare function isLoopbackHostname(hostname: string): boolean;
/**
* Default origin policy for a localhost dev tool: allow requests with no
* `Origin` header (native, non-browser clients), allow any loopback origin
* (so cross-port localhost dev setups keep working), and allow explicitly
* configured origins. Everything else — a real remote page in the dev's
* browser — is rejected.
*/
declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
/**
* Attach a WebSocket transport to an existing RPC group, powered by
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
* `server` (sharing its port, optionally scoped to a `path`), or let this
* helper create a standalone server from `port` / `host` / `https`.
*
* Returns the crossws node adapter plus `detach` (remove the upgrade
* listener from a shared `server`) and `close` (full deterministic
* teardown).
*/
declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
//#endregion
export { isAllowedOrigin as a, attachWsRpcTransport as i, WsRpcTransport as n, isLoopbackHostname as o, WsRpcTransportOptions as r, DevframeNodeRpcSessionMeta as t };

Sorry, the diff of this file is too big to display