New:Socket for Asana Is Now Available.Learn more
Get Started

devframe

Package Overview
Dependencies
Maintainers
1
Versions
63
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.9.1
to
0.9.2
+41
dist/_shared-BAGBgTUO.d.mts
import { d as ConnectionMeta, l as McpRouteOptions, n as DevframeDefinition, r as DevframeDeploymentKind } from "./devframe-mbfgpQQC.mjs";
//#region src/adapters/_shared.d.ts
/**
* Resolve the mount base path for a devframe's SPA. Hosted adapters
* (`vite`, `embedded`) default to `/__<id>/` so they don't collide
* with the host app; standalone adapters (`cli`, `build`)
* default to `/` because they own the origin.
*
* The devframe author can override with `basePath` on the definition.
*/
declare function resolveBasePath(def: DevframeDefinition, kind: DevframeDeploymentKind): string;
declare function normalizeBasePath(base: string): string;
interface ResolveDevServerPortOptions {
/** Bind host (passed to `get-port-please` for in-use detection). */
host?: string;
/** Override the preferred port. Default: `def.cli?.port ?? 9999`. */
defaultPort?: number;
}
/**
* Resolve the listening port for `createDevServer` (and `createHandler`'s
* side-car tiers), 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.
*/
declare function resolveDevServerPort(def: DevframeDefinition, options?: ResolveDevServerPortOptions): Promise<number>;
/**
* 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 `createDevServer`), or `undefined` when the route is
* disabled.
*
* Hosted bridges that hand-roll their connection meta 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).
*/
declare function resolveMcpConnectionMeta(def: DevframeDefinition, mcp: boolean | McpRouteOptions | undefined, port?: number): ConnectionMeta['mcp'];
//#endregion
export { resolveMcpConnectionMeta as a, resolveDevServerPort as i, normalizeBasePath as n, resolveBasePath as r, ResolveDevServerPortOptions as t };
import { n as randomToken } from "./crypto-token-XCqTSMg9.mjs";
import { t as createStorage } from "./storage-BNqSAOxA.mjs";
import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-3Q6aDKWk.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();
const wsEndpointListeners = /* @__PURE__ */ new Set();
function revokeRemoteToken(token) {
if (!remoteTokens.delete(token)) return;
revokeActiveConnectionsForToken(context, token);
}
const internalContext = {
storage: { auth: storage },
revokeAuthToken: (token) => revokeAuthToken(context, storage, token),
setWsEndpoint(endpoint) {
internalContext.wsEndpoint = endpoint;
for (const listener of wsEndpointListeners) listener();
},
onWsEndpointChange(cb) {
wsEndpointListeners.add(cb);
return () => wsEndpointListeners.delete(cb);
},
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 { g as DevframeNodeContext, gt as SharedState } from "./devframe-mbfgpQQC.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;
};
/**
* Set {@link DevframeInternalContext.wsEndpoint} and notify subscribers —
* the WS-binding tiers (side-car, shared-server, and the `unbound` tier's
* `attach()`) call this once the socket is bound (or `undefined` once torn
* down) instead of assigning the field directly, so anything that already
* projected the endpoint (a hub's remote-dock URLs, registered before an
* async bind resolves) gets a chance to re-project it.
*/
setWsEndpoint: (endpoint: {
url: string;
} | undefined) => void;
/**
* Subscribe to every {@link DevframeInternalContext.setWsEndpoint} call.
* Returns an unsubscribe function. The hub context uses this to refresh
* the `devframe:docks` shared state so a remote dock registered before the
* WS port resolves still ends up with a live connection URL.
*/
onWsEndpointChange: (cb: () => void) => () => void;
}
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 { i as defineDiagnostics } from "./nostics-CzECRXpE.mjs";
import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
import { defineRpcFunction } from "./index.mjs";
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { DEVFRAME_SERVICES_STATE_KEY } from "./constants.mjs";
import { t as diagnostics$1 } from "./diagnostics-DI1HGj2I.mjs";
import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-DVDLqjvj.mjs";
import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-CqxiyltC.mjs";
import { t as createStorage } from "./storage-BNqSAOxA.mjs";
import { createRequire } from "node:module";
import { createSharedState } from "devframe/utils/shared-state";
import process from "node:process";
import { pathToFileURL } from "node:url";
import { isAbsolute, join } from "pathe";
import { existsSync } from "node:fs";
//#region src/node/host-diagnostics.ts
var DevframeDiagnosticsHost = class {
context;
_registry = {};
logger = new Proxy({}, { get: (_, code) => this._registry[code] });
defineDiagnostics = defineDiagnostics;
constructor(context, initialDefinitions = []) {
this.context = context;
for (const d of initialDefinitions) this.register(d);
}
register(diagnostics) {
Object.assign(this._registry, diagnostics);
}
};
//#endregion
//#region src/node/rpc-shared-state.ts
const debug$2 = 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$2("patch", {
key,
syncId
});
rpc.broadcast({
method: DEVFRAME_EVENTS.broadcast.clientStatePatch,
args: [
key,
patches,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
} else {
debug$2("updated", {
key,
syncId
});
rpc.broadcast({
method: DEVFRAME_EVENTS.broadcast.clientStateUpdated,
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$2("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/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/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$1 = 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$1("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_EVENTS.broadcast.streamingChunk,
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_EVENTS.broadcast.streamingEnd,
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_EVENTS.broadcast.streamingChunk,
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_EVENTS.broadcast.streamingEnd,
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_EVENTS.broadcast.streamingUploadCancel,
args: [name, reader.id],
event: true,
optional: true,
filter: (client) => client.$meta === targetMeta
});
}
});
inboundRecord = { reader };
state.inbound.set(reader.id, inboundRecord);
debug$1("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 `createContextRpcServer` 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/services-install.ts
/**
* Turn a `resolveFrom` value (a file path, a file URL like `import.meta.url`,
* or a directory) into something `createRequire` accepts — a directory gets a
* synthetic filename appended so resolution starts inside it.
*/
function toRequireBase(resolveFrom) {
if (resolveFrom.startsWith("file://")) return resolveFrom;
if ((resolveFrom.split(/[/\\]/).pop() ?? "").includes(".")) return resolveFrom;
return join(resolveFrom, "_devframe_resolve.js");
}
/**
* Normalize an `install()` `resolveFrom` into a resolution base. Paths and
* file URLs pass through (the common case: the declaring plugin's
* `importMetaUrl`, so a service it declares resolves against the plugin's own
* dependencies); a bare npm package name resolves to that package's location
* from `cwd`. An unresolvable package name reads as no base (the caller's
* workspace fallbacks apply).
*/
function expandResolveFrom(resolveFrom, cwd) {
if (resolveFrom.startsWith("file://") || resolveFrom.startsWith(".") || isAbsolute(resolveFrom)) return resolveFrom;
const require = createRequire(join(cwd, "_devframe_resolve.js"));
try {
return require.resolve(`${resolveFrom}/package.json`);
} catch {}
try {
return require.resolve(resolveFrom);
} catch {}
}
/**
* Import a service package's module, trying each `resolveFrom` candidate in
* order (so a plugin-declared service resolves against the plugin's own
* dependency tree first, then the workspace fallback). Throws the last
* resolution error when no candidate succeeds.
*/
async function importServicePackage(pkg, resolveFroms) {
const candidates = [...new Set(resolveFroms.filter((x) => typeof x === "string" && x.length > 0))];
let lastError = /* @__PURE__ */ new Error(`no resolution base available for "${pkg}"`);
for (const from of candidates) {
let resolved;
try {
resolved = createRequire(toRequireBase(from)).resolve(pkg);
} catch (error) {
lastError = error;
continue;
}
return await import(pathToFileURL(resolved).href);
}
throw lastError;
}
function parseVersion(input) {
const [core, ...prerelease] = input.trim().replace(/^v/, "").split("-");
if (!core) return void 0;
const parts = core.split(".").map((part) => Number.parseInt(part, 10));
if (parts.length === 0 || parts.some((part) => Number.isNaN(part) || part < 0)) return void 0;
while (parts.length < 3) parts.push(0);
return {
parts,
...prerelease.length ? { prerelease: prerelease.join("-") } : {}
};
}
function compareVersions(a, b) {
for (let i = 0; i < 3; i++) {
const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);
if (diff !== 0) return diff;
}
if (a.prerelease && !b.prerelease) return -1;
if (!a.prerelease && b.prerelease) return 1;
if (a.prerelease && b.prerelease) return a.prerelease < b.prerelease ? -1 : a.prerelease > b.prerelease ? 1 : 0;
return 0;
}
function satisfiesComparator(version, comparator) {
const raw = comparator.trim();
if (!raw || raw === "*" || raw === "x") return true;
const operatorMatch = raw.match(/^([\^~]|>=|<=|[><=])?(.+)$/);
if (!operatorMatch) return false;
const operator = operatorMatch[1];
const rest = operatorMatch[2].trim();
const segments = rest.replace(/\.[x*]/gi, "").split(".").filter(Boolean);
const base = parseVersion(rest.replace(/[x*]/gi, "0"));
if (!base) return false;
switch (operator) {
case ">": return compareVersions(version, base) > 0;
case ">=": return compareVersions(version, base) >= 0;
case "<": return compareVersions(version, base) < 0;
case "<=": return compareVersions(version, base) <= 0;
case "^": {
if (compareVersions(version, base) < 0) return false;
const fixedIndex = base.parts.findIndex((part) => part !== 0);
const lockUpTo = fixedIndex === -1 ? base.parts.length - 1 : fixedIndex;
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return true;
}
case "~": {
if (compareVersions(version, base) < 0) return false;
const lockUpTo = segments.length >= 2 ? 1 : 0;
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return true;
}
default:
for (let i = 0; i < Math.max(segments.length, 3); i++) if (i < segments.length && (version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return segments.length >= 3 ? compareVersions(version, base) === 0 : true;
}
}
/**
* Pragmatic semver range check for service version declarations — supports
* the common forms (`1.2.3`, `^1.2.3`, `~1.2`, `>=1 <3`, `1.x`, `*`, and
* `||`-joined alternatives) without pulling in a semver dependency. An
* unparseable version or range reads as **not satisfied**.
*/
function satisfiesVersionRange(version, range) {
const parsed = parseVersion(version);
if (!parsed) return false;
const alternatives = range.split("||").map((alt) => alt.trim()).filter(Boolean);
if (alternatives.length === 0) return true;
return alternatives.some((alternative) => alternative.split(/\s+/).every((comparator) => satisfiesComparator(parsed, comparator)));
}
function isPlainObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Deep-merge two values with the service option-set rules (see below). */
function deepMergeTwo(a, b) {
if (Array.isArray(a) && Array.isArray(b)) return [.../* @__PURE__ */ new Set([...a, ...b])];
if (isPlainObject(a) && isPlainObject(b)) {
const out = { ...a };
for (const key of Object.keys(b)) out[key] = key in a ? deepMergeTwo(a[key], b[key]) : b[key];
return out;
}
return b;
}
/**
* Default option-set merge when a service declares no `mergeOptions`:
* deep-merge in declaration order — objects recurse, arrays union-dedupe,
* scalars take the later value. Covers the built-in services (`roots` /
* `langs` union, `themes` per-key last-wins) without a custom hook.
*/
function deepMergeOptionSets(sets) {
return sets.reduce((merged, set) => deepMergeTwo(merged, set));
}
//#endregion
//#region src/node/host-services.ts
const debug = createDebug("devframe:services");
function isServiceDefinition(input) {
return typeof input.setup === "function";
}
function validateServiceInput(input) {
if (!input || typeof input.package !== "string" || input.package.length === 0) throw diagnostics$1.DF0070({
package: String(input?.package ?? input),
reason: "the input has no `package` name"
});
if (isServiceDefinition(input)) validateServiceDefinition(input);
}
function validateServiceDefinition(def) {
if (typeof def.version !== "string" || def.version.length === 0) throw diagnostics$1.DF0070({
package: def.package,
reason: "the definition has no `version`"
});
if (typeof def.scope !== "string" || def.scope.length === 0) throw diagnostics$1.DF0070({
package: def.package,
reason: "the definition has no RPC `scope` namespace"
});
}
/**
* 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.
*
* On top of the in-process `provide`/`get` tier, this host implements the
* **wire-service** lifecycle: `install()` queues definitions/descriptors,
* `ready()` fires the collect-then-setup barrier — importing descriptor
* packages, merging option sets per service, constructing each service once,
* providing its node API under the package name, and advertising it to
* clients through the `devframe:services` shared state.
*/
var DevframeServicesHostImpl = class {
context;
services = /* @__PURE__ */ new Map();
listeners = /* @__PURE__ */ new Map();
pending = /* @__PURE__ */ new Map();
installed = /* @__PURE__ */ new Map();
readyPromise;
constructor(context) {
this.context = context;
}
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());
}
install(input, options) {
validateServiceInput(input);
const promise = new Promise((resolve, reject) => {
const entry = {
input,
resolveFrom: options?.resolveFrom,
resolve,
reject
};
if (this.readyPromise) this.flushPackage(input.package, [entry]).catch(() => {});
else {
let entries = this.pending.get(input.package);
if (!entries) {
entries = [];
this.pending.set(input.package, entries);
}
entries.push(entry);
}
});
promise.catch(() => {});
return promise;
}
ready() {
if (this.readyPromise) return this.readyPromise;
this.readyPromise = this.flushAll();
return this.readyPromise;
}
async flushAll() {
if (this.context) await this.advertisementState();
const groups = Array.from(this.pending.entries());
this.pending.clear();
for (const [pkg, entries] of groups) await this.flushPackage(pkg, entries);
}
async flushPackage(pkg, entries) {
try {
const api = await this.installPackage(pkg, entries);
for (const entry of entries) entry.resolve(api);
return api;
} catch (error) {
for (const entry of entries) entry.reject(error);
throw error;
}
}
async installPackage(pkg, entries) {
if (this.installed.has(pkg)) {
diagnostics$1.DF0066({ package: pkg });
return this.installed.get(pkg);
}
let def = entries.filter((entry) => isServiceDefinition(entry.input))[0]?.input;
if (!def) {
const required = entries.map((entry) => entry.input).some((descriptor) => descriptor.required === true);
const cwd = this.context?.cwd ?? process.cwd();
const resolveFroms = [
...entries.map((entry) => entry.resolveFrom && expandResolveFrom(entry.resolveFrom, cwd)),
this.context?.workspaceRoot,
cwd
];
let mod;
try {
mod = await importServicePackage(pkg, resolveFroms);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
if (required) throw diagnostics$1.DF0067({
package: pkg,
reason,
cause: error
});
debug("optional service %s not importable, skipping: %s", pkg, reason);
return;
}
const factory = mod.default;
if (typeof factory !== "function") throw diagnostics$1.DF0070({
package: pkg,
reason: "its default export is not a factory function"
});
def = await factory();
if (!def || typeof def.setup !== "function") throw diagnostics$1.DF0070({
package: pkg,
reason: "its factory did not return a definition with a `setup` function"
});
if (typeof def.package !== "string" || def.package.length === 0) def = {
...def,
package: pkg
};
validateServiceDefinition(def);
}
for (const entry of entries) {
const descriptor = entry.input;
if (isServiceDefinition(entry.input) || typeof descriptor.version !== "string") continue;
if (satisfiesVersionRange(def.version, descriptor.version)) continue;
if (descriptor.required === true) throw diagnostics$1.DF0068({
package: pkg,
required: descriptor.version,
installed: def.version
});
diagnostics$1.DF0069({
package: pkg,
required: descriptor.version,
installed: def.version
});
}
const sets = entries.map((entry) => entry.input.options).filter((options) => options !== void 0);
const options = def.mergeOptions ? def.mergeOptions(sets) : sets.length > 0 ? deepMergeOptionSets(sets) : void 0;
if (!this.context) throw diagnostics$1.DF0070({
package: pkg,
reason: "this services host has no node context to install into"
});
debug("installing service %s@%s (scope %s)", def.package, def.version, def.scope);
const scoped = this.context.scope(def.scope);
const api = await def.setup(scoped, options === void 0 ? {} : { options });
this.installed.set(def.package, api);
this.provide(def.package, api);
await this.advertise(def);
return api;
}
advertisementState() {
return this.context.rpc.sharedState.get(DEVFRAME_SERVICES_STATE_KEY, { initialValue: {} });
}
async advertise(def) {
const state = await this.advertisementState();
const { package: pkg, version, scope, meta } = def;
state.mutate((value) => {
value[pkg] = {
package: pkg,
version,
scope,
...meta ? { meta } : {}
};
});
}
};
//#endregion
//#region src/node/host-views.ts
var DevframeViewHost = class {
context;
importMetaUrl;
/**
* @internal
*/
buildStaticDirs = [];
constructor(context, importMetaUrl) {
this.context = context;
this.importMetaUrl = importMetaUrl;
}
hostStatic(baseUrl, source, defaultResolveFrom = this.importMetaUrl) {
const resolved = resolveStaticAssetsSource(source, this.context.host.getStorageDir("project"), defaultResolveFrom);
if (typeof resolved === "string" && !existsSync(resolved)) throw diagnostics$1.DF0008({ distDir: resolved });
this.buildStaticDirs.push({
baseUrl,
source
});
this.context.host.mountStatic(baseUrl, resolved);
}
};
//#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.
*/
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 dir = context.host.getStorageDir(STORAGE_SCOPE[scope]);
const filepath = join(dir, "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, importMetaUrl, builtinRpcDeclarations = [] } = options;
const context = {
cwd,
workspaceRoot,
mode,
host,
rpc: void 0,
views: void 0,
diagnostics: void 0,
agent: void 0,
services: void 0,
staticConfig: {},
scope: void 0
};
const rpcHost = new RpcFunctionsHostImpl(context);
const viewsHost = new DevframeViewHost(context, importMetaUrl);
const diagnosticsHost = new DevframeDiagnosticsHost(context, [diagnostics$1, diagnostics]);
context.rpc = rpcHost;
context.views = viewsHost;
context.diagnostics = diagnosticsHost;
context.services = new DevframeServicesHostImpl(context);
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
export { createHostContext as t };
import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { t as createHostContext } from "./context-riPPHGiu.mjs";
import { t as resolveStaticAssetsSource } from "./remote-assets-CqxiyltC.mjs";
import { i as resolveMcpConnectionMeta, n as resolveBasePath, r as resolveDevServerPort, t as normalizeBasePath } from "./_shared-BM3PdYli.mjs";
import { t as createH3DevframeHost } from "./host-h3-fRbF9yor.mjs";
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-GqTY93gC.mjs";
import { open } from "./utils/open.mjs";
import { mountStaticHandler } from "./utils/serve-static.mjs";
import { createServer } from "node:http";
import process from "node:process";
import { resolve } from "pathe";
import { joinURL, withBase } from "ufo";
import { H3, toNodeHandler } from "h3";
//#region src/adapters/initiate.ts
const INSTANCE_INTERNALS = /* @__PURE__ */ new WeakMap();
/** @internal */
function getInstanceInternals(handler) {
return INSTANCE_INTERNALS.get(handler) ?? {};
}
/**
* Serve a devframe through one framework-agnostic, web-standard handler —
* the SPA, `__connection.json` discovery, the WebSocket RPC endpoint, the
* auth gate, and the optional MCP route, all under a single mount base.
* Mount `handler` on any framework's catch-all route (or `nodeMiddleware` on
* a connect stack) and the devframe is live inside that app.
*
* The factory is synchronous and kicks off initialization eagerly;
* `handler`/`nodeMiddleware` await readiness internally. Nothing binds a port
* on its own: the WebSocket resolves in precedence order — `ws.port` (pinned
* side-car) > `server` (shared upgrade at `<base>__ws`) > `ws.sidecar`
* (auto-port side-car) > the host driving upgrades itself through
* {@link DevframeInstance.attach} — while `ws.url`, when set, overrides the
* advertised* endpoint (the tunnel pattern) and on its own hands the whole
* transport to an external server. `__connection.json` reflects whichever
* combination is active.
*/
function initDevframe(def, options) {
const base = normalizeBasePath(options.base);
const distDir = options.distDir === false ? void 0 : options.distDir ?? def.cli?.distDir;
const app = options.app ?? new H3();
const host = options.host ?? def.cli?.host ?? "localhost";
const shell = createInstanceShell({
base,
app,
host,
origin: options.origin,
auth: options.auth !== void 0 ? options.auth : def.cli?.auth,
server: options.server,
ws: options.ws ?? def.cli?.ws,
sse: options.sse ?? def.cli?.sse,
allowedOrigins: options.allowedOrigins,
destroyUnmatchedUpgrades: options.destroyUnmatchedUpgrades,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect,
register: resolveInstanceRegister(options.register, {
id: def.id,
name: def.name
}),
resolveSidecarPort: (sidecarHost) => resolveDevServerPort(def, { host: sidecarHost }),
onMetaUnavailable: () => {
throw diagnostics.DF0054({ id: def.id });
},
async init(api) {
const h3Host = createH3DevframeHost({
origin: () => api.origin() ?? "http://localhost",
appName: def.id,
mount: (mountBase, dir) => {
mountStaticHandler(app, mountBase, dir);
}
});
const hostImpl = options.getStorageDir ? {
...h3Host,
getStorageDir: options.getStorageDir
} : h3Host;
const context = await createHostContext({
cwd: process.cwd(),
mode: "dev",
host: hostImpl,
importMetaUrl: def.importMetaUrl
});
const setupInfo = { flags: options.flags ?? {} };
for (const input of def.services ?? []) context.services.install(input, { resolveFrom: def.importMetaUrl });
await context.services.ready();
await def.setup(context, setupInfo);
const mcpOption = options.mcp ?? def.cli?.mcp;
const mcpMeta = resolveMcpConnectionMeta(def, mcpOption);
let mcpDispose;
if (mcpMeta) {
const mcpConfig = mcpOption === true || mcpOption === void 0 ? {} : mcpOption;
const mcpPath = joinURL(base, mcpMeta.path);
let mountMcpHttp;
try {
({mountMcpHttp} = await import("./http-POepPnaQ.mjs").then((n) => n.t));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport: "http",
reason,
cause: error
});
}
mcpDispose = mountMcpHttp(app, context, mcpPath, {
serverName: `${def.id} (devframe)`,
serverVersion: def.version ?? "0.0.0",
exposeSharedState: true,
allowedOrigins: mcpConfig.allowedOrigins
}).dispose;
}
return {
context,
...mcpMeta ? { mcp: mcpMeta } : {},
...mcpDispose ? { dispose: mcpDispose } : {}
};
},
mount(context, meta) {
app.use(joinURL(base, DEVFRAME_CONNECTION_META_FILENAME), () => meta);
if (distDir) {
const source = resolveStaticAssetsSource(distDir, context.host.getStorageDir("project"), def.importMetaUrl);
mountStaticHandler(app, base, typeof source === "string" ? resolve(source) : source);
}
}
});
const instance = {
base: shell.base,
handler: shell.handler,
nodeMiddleware: shell.nodeMiddleware,
attach: shell.attach,
handleUpgrade: shell.handleUpgrade,
ready: shell.ready,
context: shell.context,
connectionMeta: shell.connectionMeta,
close: shell.close
};
INSTANCE_INTERNALS.set(instance, shell.internals);
return instance;
}
//#endregion
//#region src/adapters/dev.ts
/**
* 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
* `devframeViteBridge` from `@devframes/vite`.
*
* 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 = {}) {
if (def.capabilities?.dev === false && !options.force) throw diagnostics.DF0058({ id: def.id });
const host = options.host ?? def.cli?.host ?? "localhost";
const requestedPort = 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 server = createServer(toNodeHandler(app));
try {
await new Promise((resolveListen, rejectListen) => {
const onError = (error) => rejectListen(error);
server.once("error", onError);
server.listen(requestedPort, host, () => {
server.removeListener("error", onError);
resolveListen();
});
});
} catch (error) {
throw diagnostics.DF0052({
host,
port: requestedPort,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
const address = server.address();
const port = typeof address === "object" && address ? address.port : requestedPort;
const origin = normalizeHttpServerUrl(host, port);
const devframe = initDevframe(def, {
base: basePath,
distDir: options.distDir,
app,
server,
host,
origin,
ws: options.ws,
allowedOrigins: options.allowedOrigins,
sse: options.sse,
auth: flags.auth === false ? false : options.auth,
mcp: options.mcp,
flags,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect,
register: true,
destroyUnmatchedUpgrades: true
});
try {
await devframe.ready;
} catch (error) {
await new Promise((resolveClose) => server.close(() => resolveClose()));
throw error;
}
const internals = getInstanceInternals(devframe);
const transport = internals.started;
await options.onReady?.({
origin,
port,
app
});
await maybeOpenBrowser(def, flags, `${origin}${basePath}`, options.openBrowser, internals.authHandler);
return {
origin,
port,
app,
ws: transport.ws,
rpcGroup: transport.rpcGroup,
connectionMeta: transport.connectionMeta,
async close() {
await devframe.close();
await new Promise((resolveClose) => server.close(() => resolveClose()));
}
};
}
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 { getInstanceInternals as n, initDevframe as r, createDevServer as t };

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

import { i as defineDiagnostics } from "./nostics-CzECRXpE.mjs";
//#region src/node/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
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 `port` on `devframeViteBridge` (`@devframes/vite`)."
},
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."
},
DF0052: {
why: (p) => `Failed to listen on ${p.host}:${p.port}: ${p.reason}`,
fix: "The port is likely already taken by another process (often a previous devframe instance). Free it, or pick another via `--port`, `cli.port` / `cli.portRange` on the definition, or `port` on `devframeViteBridge` (`@devframes/vite`). The original node error is available as `error.cause`."
},
DF0054: {
why: (p) => `connectionMeta() was called before initDevframe("${p.id}") finished initializing.`,
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()` — the WebSocket binding it describes is only known once initialization completes."
},
DF0055: {
why: (p) => `This instance already owns its WebSocket transport (${p.tier}), so it cannot take over the host's upgrade events.`,
fix: "Drop `handleUpgrade`/`attach` and let the configured transport serve the socket, or remove `server` / `ws.port` / `ws.sidecar` from the options so the instance leaves the binding to you."
},
DF0056: {
why: (p) => `This instance advertises an external WebSocket endpoint (${p.url}), so it serves no socket of its own.`,
fix: "The server behind `ws.url` owns the transport (and its auth). Drop `ws.url` to have the instance serve the socket, or pair it with `server` / `ws.port` / `ws.sidecar` for the tunnel pattern, where a local binding is advertised through the relay."
},
DF0057: {
why: () => "This instance disables its WebSocket transport (`ws: false`), so there is no socket to drive upgrades into.",
fix: "Clients connect over the SSE endpoint instead — no upgrade wiring is needed. Remove `ws: false` if the instance should serve a WebSocket after all."
},
DF0058: {
why: (p) => `"${p.id}" declares \`capabilities.dev: false\` — it does not support a live dev server (its value is a static export only).`,
fix: "Pass `{ force: true }` to `createDevServer()` to run it anyway, or drop `capabilities.dev: false` on the definition."
},
DF0059: {
why: (p) => `Failed to fetch the file listing for "${p.package}@${p.version}" from ${p.provider}: ${p.reason}`,
fix: "Requests fall back to probing the provider per file. Check network access to the provider, or install the assets package locally so no listing is needed."
},
DF0060: {
why: (p) => `Failed to fetch a remote asset of "${p.package}" (${p.url}): ${p.reason}`,
fix: "Install the assets package locally (`npm install <package>`) to serve it with zero network, or check network access to the configured provider."
},
DF0061: {
why: (p) => `The locally installed "${p.package}@${p.installed}" is a different major version than the required "${p.required}".`,
fix: "Align the installed assets package with the version its node package declares — they are published in lockstep."
},
DF0062: {
why: (p) => `The locally installed "${p.package}@${p.installed}" differs from the required "${p.required}" — serving the installed one.`,
fix: "Install the exact declared version to serve byte-identical assets."
},
DF0063: {
why: (p) => `Failed to persist a remote asset into the cache at "${p.filepath}": ${p.reason}`,
fix: "The response was still served; only caching failed. Check that the cache directory is writable and has free space."
},
DF0064: {
why: (p) => `Failed to materialize the remote assets of "${p.package}@${p.version}": ${p.reason}`,
fix: "Static builds need every asset file up front. Install the assets package locally, or ensure the provider (and its file-listing API) is reachable during the build."
},
DF0065: {
why: (p) => `Invalid remote-assets ${p.field} "${p.value}".`,
fix: "A remote-assets `package` must be a valid npm package name and `version` an exact semver version (e.g. `1.2.3`) — they are interpolated into CDN URLs and the cache path."
},
DF0066: {
why: (p) => `Service "${p.package}" is already installed — keeping the first installation and ignoring this one's options.`,
fix: "Option sets only merge before `ctx.services.ready()` fires. Install the service (or declare it in `DevframeDefinition.services`) before the barrier so its options join the merge."
},
DF0067: {
why: (p) => `Failed to import the required service package "${p.package}": ${p.reason}`,
fix: "Install the service package next to whoever declares it (a plugin declaring it in `services` should list it in its own dependencies), or drop `required: true` to degrade gracefully when it is absent."
},
DF0068: {
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the required range "${p.required}".`,
fix: "Align the installed service package with the range its declarer requires, or drop `required: true` to downgrade the mismatch to a warning."
},
DF0069: {
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the declared range "${p.required}" — installing it anyway.`,
fix: "The advertised meta carries the real version, so clients can gate on it. Align the installed service package with the declared range to silence this warning."
},
DF0070: {
why: (p) => `Invalid service "${p.package}": ${p.reason}`,
fix: "A service package's default export must be a factory returning a `DevframeServiceDefinition` — an object with `package`, `version`, `scope`, and a `setup` function."
}
}
});
//#endregion
export { diagnostics as t };
//#region ../../node_modules/.pnpm/ohash@2.0.12/node_modules/ohash/dist/_chunks/is-equal.mjs
function serialize(input) {
if (typeof input === "string") return `'${input}'`;
return new Serializer().serialize(input);
}
const asciiOrder = " _-,;:!?.'\"()[]{}@*/\\&#%`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz";
const asciiWeights = /*@__PURE__*/ (function() {
const weights = /* @__PURE__ */ new Uint8Array(128);
for (let i = 0; i < 69; i++) weights[asciiOrder.charCodeAt(i)] = i + 1;
for (let code = 65; code <= 90; code++) weights[code] = weights[code + 32];
return weights;
})();
function compareStrings(a, b) {
if (a === b) return 0;
const length = Math.min(a.length, b.length);
let tieBreaker = 0;
for (let i = 0; i < length; i++) {
const codeA = a.charCodeAt(i);
const codeB = b.charCodeAt(i);
if (codeA === codeB) continue;
const weightA = codeA < 128 && asciiWeights[codeA] ? asciiWeights[codeA] : codeA + 128;
const weightB = codeB < 128 && asciiWeights[codeB] ? asciiWeights[codeB] : codeB + 128;
if (weightA !== weightB) return weightA < weightB ? -1 : 1;
if (tieBreaker === 0) tieBreaker = codeA > codeB ? -1 : 1;
}
if (a.length !== b.length) return a.length < b.length ? -1 : 1;
return tieBreaker;
}
const Serializer = /*@__PURE__*/ (function() {
class Serializer {
#context = /* @__PURE__ */ new Map();
compare(a, b) {
const typeA = typeof a;
const typeB = typeof b;
if (typeA === "string" && typeB === "string") return compareStrings(a, b);
if (typeA === "number" && typeB === "number") return a - b;
return compareStrings(this.serialize(a, true), this.serialize(b, true));
}
serialize(value, noQuotes) {
if (value === null) return "null";
switch (typeof value) {
case "string": return noQuotes ? value : `'${value}'`;
case "bigint": return `${value}n`;
case "object": return this.$object(value);
case "function": return this.$function(value);
}
return String(value);
}
serializeObject(object) {
const objString = Object.prototype.toString.call(object);
if (objString !== "[object Object]") return this.serializeBuiltInType(objString.length < 10 ? `unknown:${objString}` : objString.slice(8, -1), object);
const constructor = object.constructor;
const objName = constructor === Object || constructor === void 0 ? "" : constructor.name;
if (objName !== "" && globalThis[objName] === constructor) return this.serializeBuiltInType(objName, object);
if ("toJSON" in object && typeof object.toJSON === "function") {
const json = object.toJSON();
return objName + (json !== null && typeof json === "object" ? this.$object(json) : `(${this.serialize(json)})`);
}
const keys = Object.keys(object).sort(compareStrings);
let content = `${objName}{`;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
content += `${key}:${this.serialize(object[key])}`;
if (i < keys.length - 1) content += ",";
}
return content + "}";
}
serializeBuiltInType(type, object) {
const handler = this["$" + type];
if (handler) return handler.call(this, object);
if (typeof object.entries === "function") return this.serializeObjectEntries(type, object.entries());
throw new Error(`Cannot serialize ${type}`);
}
serializeObjectEntries(type, entries) {
const sortedEntries = Array.from(entries).sort((a, b) => this.compare(a[0], b[0]));
let content = `${type}{`;
for (let i = 0; i < sortedEntries.length; i++) {
const [key, value] = sortedEntries[i];
content += `${this.serialize(key, true)}:${this.serialize(value)}`;
if (i < sortedEntries.length - 1) content += ",";
}
return content + "}";
}
$object(object) {
let content = this.#context.get(object);
if (content === void 0) {
this.#context.set(object, `#${this.#context.size}`);
content = this.serializeObject(object);
this.#context.set(object, content);
}
return content;
}
$function(fn) {
const fnStr = Function.prototype.toString.call(fn);
if (fnStr.slice(-15) === "[native code] }") return `${fn.name || ""}()[native]`;
return `${fn.name}(${fn.length})${fnStr.replace(/\s*\n\s*/g, "")}`;
}
$Array(arr) {
let content = "[";
for (let i = 0; i < arr.length; i++) {
content += this.serialize(arr[i]);
if (i < arr.length - 1) content += ",";
}
return content + "]";
}
$Date(date) {
try {
return `Date(${date.toISOString()})`;
} catch {
return `Date(null)`;
}
}
$ArrayBuffer(arr) {
return `ArrayBuffer[${new Uint8Array(arr).join(",")}]`;
}
$Set(set) {
return `Set${this.$Array(Array.from(set).sort((a, b) => this.compare(a, b)))}`;
}
$Map(map) {
return this.serializeObjectEntries("Map", map.entries());
}
}
for (const type of [
"Error",
"RegExp",
"URL"
]) Serializer.prototype["$" + type] = function(val) {
return `${type}(${val})`;
};
for (const type of [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array"
]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join(",")}]`;
};
for (const type of ["BigInt64Array", "BigUint64Array"]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join("n,")}${arr.length > 0 ? "n" : ""}]`;
};
return Serializer;
})();
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.12/node_modules/ohash/dist/crypto/js/index.mjs
const H = [
1779033703,
-1150833019,
1013904242,
-1521486534,
1359893119,
-1694144372,
528734635,
1541459225
];
const K = [
1116352408,
1899447441,
-1245643825,
-373957723,
961987163,
1508970993,
-1841331548,
-1424204075,
-670586216,
310598401,
607225278,
1426881987,
1925078388,
-2132889090,
-1680079193,
-1046744716,
-459576895,
-272742522,
264347078,
604807628,
770255983,
1249150122,
1555081692,
1996064986,
-1740746414,
-1473132947,
-1341970488,
-1084653625,
-958395405,
-710438585,
113926993,
338241895,
666307205,
773529912,
1294757372,
1396182291,
1695183700,
1986661051,
-2117940946,
-1838011259,
-1564481375,
-1474664885,
-1035236496,
-949202525,
-778901479,
-694614492,
-200395387,
275423344,
430227734,
506948616,
659060556,
883997877,
958139571,
1322822218,
1537002063,
1747873779,
1955562222,
2024104815,
-2067236844,
-1933114872,
-1866530822,
-1538233109,
-1090935817,
-965641998
];
const base64KeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const W = [];
var SHA256 = class {
_data = new WordArray();
_hash = new WordArray([...H]);
_nDataBytes = 0;
_minBufferSize = 0;
finalize(messageUpdate) {
if (messageUpdate) this._append(messageUpdate);
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = this._data.sigBytes * 8;
this._data.words[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor(nBitsTotal / 4294967296);
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal;
this._data.sigBytes = this._data.words.length * 4;
this._process();
return this._hash;
}
_doProcessBlock(M, offset) {
const H = this._hash.words;
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
let e = H[4];
let f = H[5];
let g = H[6];
let h = H[7];
for (let i = 0; i < 64; i++) {
if (i < 16) W[i] = M[offset + i] | 0;
else {
const gamma0x = W[i - 15];
const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0x >>> 3;
const gamma1x = W[i - 2];
const gamma1 = (gamma1x << 15 | gamma1x >>> 17) ^ (gamma1x << 13 | gamma1x >>> 19) ^ gamma1x >>> 10;
W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
}
const ch = e & f ^ ~e & g;
const maj = a & b ^ a & c ^ b & c;
const sigma0 = (a << 30 | a >>> 2) ^ (a << 19 | a >>> 13) ^ (a << 10 | a >>> 22);
const sigma1 = (e << 26 | e >>> 6) ^ (e << 21 | e >>> 11) ^ (e << 7 | e >>> 25);
const t1 = h + sigma1 + ch + K[i] + W[i];
const t2 = sigma0 + maj;
h = g;
g = f;
f = e;
e = d + t1 | 0;
d = c;
c = b;
b = a;
a = t1 + t2 | 0;
}
H[0] = H[0] + a | 0;
H[1] = H[1] + b | 0;
H[2] = H[2] + c | 0;
H[3] = H[3] + d | 0;
H[4] = H[4] + e | 0;
H[5] = H[5] + f | 0;
H[6] = H[6] + g | 0;
H[7] = H[7] + h | 0;
}
_append(data) {
if (typeof data === "string") data = WordArray.fromUtf8(data);
this._data.concat(data);
this._nDataBytes += data.sigBytes;
}
_process(doFlush) {
let processedWords;
let nBlocksReady = this._data.sigBytes / 64;
if (doFlush) nBlocksReady = Math.ceil(nBlocksReady);
else nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
const nWordsReady = nBlocksReady * 16;
const nBytesReady = Math.min(nWordsReady * 4, this._data.sigBytes);
if (nWordsReady) {
for (let offset = 0; offset < nWordsReady; offset += 16) this._doProcessBlock(this._data.words, offset);
processedWords = this._data.words.splice(0, nWordsReady);
this._data.sigBytes -= nBytesReady;
}
return new WordArray(processedWords, nBytesReady);
}
};
var WordArray = class WordArray {
words;
sigBytes;
constructor(words, sigBytes) {
words = this.words = words || [];
this.sigBytes = sigBytes === void 0 ? words.length * 4 : sigBytes;
}
static fromUtf8(input) {
const str = unescape(encodeURIComponent(input));
const strlen = str.length;
const words = [];
for (let i = 0; i < strlen; i++) words[i >>> 2] |= (str.charCodeAt(i) & 255) << 24 - i % 4 * 8;
return new WordArray(words, strlen);
}
toBase64() {
const base64Chars = [];
for (let i = 0; i < this.sigBytes; i += 3) {
const byte1 = this.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
const byte2 = this.words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255;
const byte3 = this.words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255;
const triplet = byte1 << 16 | byte2 << 8 | byte3;
for (let j = 0; j < 4 && i * 8 + j * 6 < this.sigBytes * 8; j++) base64Chars.push(base64KeyStr.charAt(triplet >>> 6 * (3 - j) & 63));
}
return base64Chars.join("");
}
concat(wordArray) {
this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8;
this.words.length = Math.ceil(this.sigBytes / 4);
if (this.sigBytes % 4) for (let i = 0; i < wordArray.sigBytes; i++) {
const thatByte = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
this.words[this.sigBytes + i >>> 2] |= thatByte << 24 - (this.sigBytes + i) % 4 * 8;
}
else for (let j = 0; j < wordArray.sigBytes; j += 4) this.words[this.sigBytes + j >>> 2] = wordArray.words[j >>> 2];
this.sigBytes += wordArray.sigBytes;
}
};
function digest(message) {
return new SHA256().finalize(message).toBase64();
}
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.12/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
export { hash as t };
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
//#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 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}
*/
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`.
*/
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(DEVFRAME_EVENTS.bus.agentManifestChanged);
});
}
registerTool(input) {
this._validateToolId(input.id);
const tool = this._projectTool(input);
this.tools.set(tool.id, {
tool,
handler: input.handler
});
this.events.emit(DEVFRAME_EVENTS.bus.agentToolRegistered, tool);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
return { unregister: () => this.unregisterTool(tool.id) };
}
unregisterTool(id) {
const existed = this.tools.delete(id);
if (existed) {
this.events.emit(DEVFRAME_EVENTS.bus.agentToolUnregistered, id);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
return existed;
}
registerToolProvider(provider) {
this.providers.add(provider);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
const notifyChanged = () => {
if (this.providers.has(provider)) this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
};
return {
notifyChanged,
unregister: () => {
if (this.providers.delete(provider)) this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
};
}
registerResource(input) {
if (this.resources.has(input.id)) throw diagnostics.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(DEVFRAME_EVENTS.bus.agentResourceRegistered, resource);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
return { unregister: () => this.unregisterResource(resource.id) };
}
unregisterResource(id) {
const existed = this.resources.delete(id);
if (existed) {
this.events.emit(DEVFRAME_EVENTS.bus.agentResourceUnregistered, id);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
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.DF0015({ id });
if (this.context.rpc.definitions.get(id)?.agent) throw diagnostics.DF0015({ id });
}
_projectTool(input) {
if (!input.description || typeof input.description !== "string") throw diagnostics.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.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
export { coerceAgentPositionalArgs as n, createEventEmitter as r, DevframeAgentHost as t };
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { t as Diagnostic } from "./nostics-CzECRXpE.mjs";
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { i as isAllowedOrigin } from "./ws-server-BdSLrhxE.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { t as createHostContext } from "./context-riPPHGiu.mjs";
import { t as toAgentToolName } from "./agent-tool-name-EgfoFO8C.mjs";
import { randomUUID } from "node:crypto";
import process from "node:process";
import { join } from "pathe";
import { homedir } from "node:os";
import { defineHandler } from "h3";
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(DEVFRAME_EVENTS.bus.agentManifestChanged, () => {
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.
*/
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`);
}
},
importMetaUrl: definition.importMetaUrl
});
for (const input of definition.services ?? []) ctx.services.install(input, { resolveFrom: definition.importMetaUrl });
await ctx.services.ready();
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 guards every request:
* loopback-default DNS-rebinding protection that — unlike the WS upgrade's
* `isAllowedOrigin` — also rejects `Origin`-less requests, so a route-based
* endpoint isn't reachable by an arbitrary local process.
*/
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 && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden: origin required", { 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
//#region src/adapters/mcp/http.ts
var http_exports = /* @__PURE__ */ __exportAll({ mountMcpHttp: () => mountMcpHttp });
/**
* 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).
*/
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 { createMcpServer as i, mountMcpHttp as n, createMcpFetchHandler as r, http_exports as t };
import { _ as DevframeNodeRpcSession, g as DevframeNodeContext, gt as SharedState } from "./devframe-mbfgpQQC.mjs";
import { n as InternalAnonymousAuthStorage } from "./context-al99DDJ8.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) in
* the URL **fragment**. 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.
*
* The code rides the fragment (`#devframe_otp=…`), not the query string, so it
* is never sent to the server, written to an access log, or leaked in a
* `Referer` header — the browser client reads it locally (see
* `consumeOtpFromUrl`). Any existing fragment parameters are preserved.
*/
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 { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import process from "node:process";
import { join } from "pathe";
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
//#region src/node/instance-registry.ts
var instance_registry_exports = /* @__PURE__ */ __exportAll({
listLiveDevframeInstances: () => listLiveDevframeInstances,
probeDevframeOrigin: () => probeDevframeOrigin,
readDevframeInstances: () => readDevframeInstances,
registerDevframeInstance: () => registerDevframeInstance
});
/** 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.
*/
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}.
*/
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.
*/
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 { registerDevframeInstance as i, listLiveDevframeInstances as n, probeDevframeOrigin as r, instance_registry_exports as t };
import "./constants.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { t as getInternalContext } from "./context-af2w_F0_.mjs";
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
import { createServer } from "node:http";
import process from "node:process";
import { isIP } from "node:net";
import { joinURL, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
import { H3, defineHandler, toNodeHandler } from "h3";
//#region src/node/utils.ts
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/instance-shell.ts
/**
* Compose an h3 + WebSocket RPC server for a devframe context — the low-level
* "listen on a port (or share one) + attach the WS transport" binding the
* side-car and shared-server tiers below are built on. Owns and listens on a
* fresh `node:http` server unless `server` is supplied, in which case it only
* attaches the upgrade listener and leaves that server's lifecycle to its
* owner.
*/
async function bindHttpAndWs(options) {
const { context, port, core } = options;
const bindHost = options.host;
const app = new H3();
const ownsHttpServer = !options.server;
const httpServer = options.server ?? createServer(toNodeHandler(app));
const rpcHost = context.rpc;
const websocket = options.websocket !== false;
let ws;
let closeWs = async () => {};
if (websocket) {
const { attachWsRpcTransport } = await import("./rpc/transports/ws-server.mjs");
const transport = attachWsRpcTransport(core.rpcGroup, {
server: httpServer,
path: options.path,
destroyUnmatched: options.destroyUnmatched ?? ownsHttpServer,
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
ws = transport.ws;
closeWs = transport.close;
}
if (ownsHttpServer) try {
await new Promise((resolve, reject) => {
const onError = (error) => reject(error);
httpServer.once("error", onError);
httpServer.listen(port, bindHost, () => {
httpServer.removeListener("error", onError);
resolve();
});
});
} catch (error) {
await closeWs().catch(() => {});
throw diagnostics.DF0052({
host: bindHost,
port,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
const address = httpServer.address();
const resolvedPort = typeof address === "object" && address ? address.port : port;
const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
const internal = getInternalContext(context);
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${resolvedPort}${options.path ?? ""}`;
if (websocket) internal.setWsEndpoint({ url: wsUrl });
function connectionMeta() {
const jsonSerializableMethods = [];
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
return {
backend: "websocket",
websocket: { path: options.path },
jsonSerializableMethods
};
}
return {
origin,
port: resolvedPort,
app,
ws,
rpcGroup: core.rpcGroup,
connectionMeta,
async close() {
await closeWs();
if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
if (websocket && getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).setWsEndpoint(void 0);
}
};
}
/**
* Translate the public `register?: boolean | Partial<DevframeInstanceRecord>`
* option into a shell {@link InstanceRegisterConfig}, or `undefined` when
* registration is opted out. The object form supplies record overrides on top
* of the caller-provided identity defaults.
*/
function resolveInstanceRegister(option, defaults) {
if (!option) return void 0;
return {
id: defaults.id,
...defaults.name !== void 0 ? { name: defaults.name } : {},
...defaults.rootDir !== void 0 ? { rootDir: defaults.rootDir } : {},
...typeof option === "object" ? { overrides: option } : {}
};
}
/** Compare two URL paths ignoring a trailing slash. */
function samePath(a, b) {
return withoutTrailingSlash(a) === withoutTrailingSlash(b);
}
/**
* Copy a web `Response` from a fetch-style transport handler onto the h3
* event's response and return its body — mirroring the MCP route's bridge.
* Returning the body (a `ReadableStream`, or `''` for an empty one — h3
* middleware only falls through on `undefined`) terminates the chain with
* the status/headers set here instead of continuing to the SPA catch-all.
*/
function respondWith(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 ?? "";
}
/**
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
* one h3 app, one lazily-derived public origin (and the auth banner that waits
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
* serves them. Each factory supplies only what makes it itself — its context,
* its routes, its diagnostics — through `init` / `mount`.
*
* Nothing here listens on a port unless a side-car was explicitly requested:
* the default tier leaves the socket `unbound`, so a host chains it onto its
* own server through {@link InstanceShell.attach} /
* {@link InstanceShell.handleUpgrade}.
*
* @internal
*/
function createInstanceShell(options) {
const base = options.base;
const baseNoSlash = withoutTrailingSlash(base);
const app = options.app ?? new H3();
const wsDisabled = options.ws === false;
const ws = options.ws === false ? {} : options.ws ?? {};
const route = withoutLeadingSlash(ws.route ?? "__ws");
/** Where an upgrade lands on the host's own origin. */
const routePath = joinURL(base, route);
/** What `__connection.json` advertises for a same-origin socket. */
const advertisedPath = options.absoluteWsPath ? routePath : route;
const sidecarRequested = ws.port != null || ws.sidecar === true;
const tier = wsDisabled ? "disabled" : sidecarRequested ? "sidecar" : options.server ? "server" : ws.url ? "external" : "unbound";
const sseEnabled = options.sse !== false && tier !== "external";
const sseRoute = withoutLeadingSlash((typeof options.sse === "object" ? options.sse.route : void 0) ?? "__sse");
const sseRoutePath = joinURL(base, sseRoute);
const advertisedSsePath = options.absoluteWsPath ? sseRoutePath : sseRoute;
let derivedOrigin;
function currentOrigin() {
return (typeof options.origin === "function" ? options.origin() : options.origin) || derivedOrigin;
}
let authHandler;
let bannerPrinted = false;
function maybePrintBanner() {
if (bannerPrinted || !authHandler || !currentOrigin()) return;
bannerPrinted = true;
authHandler.printBanner();
}
let meta;
let registration;
let registerPromise;
/**
* Publish the instance in the global registry the moment both its origin
* and connection meta are known — at init end for a pinned origin, or on
* the first request for a derived one. Registration never throws (the
* registry writer degrades to a coded warning), so failures never surface.
*/
function maybeRegister() {
const cfg = options.register;
const origin = currentOrigin();
if (!cfg || registerPromise || !origin || !meta) return;
const resolvedMeta = meta;
registerPromise = import("./instance-registry-Dy28drtm.mjs").then((n) => n.t).then(({ registerDevframeInstance }) => {
let port = 0;
try {
const url = new URL(origin);
port = Number(url.port) || (url.protocol === "https:" ? 443 : 80);
} catch {}
registration = registerDevframeInstance({
pid: process.pid,
port,
origin,
basePath: base,
id: cfg.id,
...cfg.name !== void 0 ? { name: cfg.name } : {},
rootDir: cfg.rootDir ?? process.cwd(),
mcp: resolvedMeta.mcp ? { path: joinURL(base, resolvedMeta.mcp.path) } : null,
startedAt: Date.now(),
...cfg.overrides
});
}).catch(() => {});
}
function noteOrigin(origin) {
derivedOrigin ??= origin;
maybePrintBanner();
maybeRegister();
}
let started;
let transport;
let dispose;
let ctx;
const api = {
base,
app,
origin: currentOrigin,
connectionMeta: () => meta
};
/**
* Auth resolution: gate by default, `false` opts out, a handler object
* installs a custom scheme. The `external` tier has no local transport to
* gate — the server behind `ws.url` owns auth — so it resolves to nothing.
*/
function resolveAuth() {
if (options.auth === false) return false;
if (typeof options.auth === "object") {
authHandler = options.auth;
return options.auth;
}
authHandler = createInteractiveAuth(ctx);
return authHandler;
}
/**
* The context's RPC core (birpc group, session lifecycle, auth gate) —
* one per instance, shared by every transport binding (WS and SSE), so a
* WS peer and an SSE session live in the same session/broadcast space.
* Built lazily: an `unbound` host that never wires a transport pays
* nothing for it, not even the imports. `resolvedAuth` and `ctx` are
* assigned during `init()` before any caller can reach this.
*/
let resolvedAuth = false;
let corePromise;
function ensureCore() {
corePromise ??= import("./rpc-core-DRYnbxdD.mjs").then((n) => n.n).then(({ createContextRpcServer }) => createContextRpcServer({
context: ctx,
auth: resolvedAuth,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect
}));
return corePromise;
}
/**
* The SSE transport, built on the first request to its route so an
* instance nobody dials over SSE never loads it.
*/
let ssePromise;
function ensureSse() {
ssePromise ??= (async () => {
const [core, { attachSseRpcTransport }] = await Promise.all([ensureCore(), import("./rpc/transports/sse-server.mjs")]);
return attachSseRpcTransport(core.rpcGroup, {
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
})();
return ssePromise;
}
/**
* A side-car server on its own port. `getPort` probes and the bind can
* still race (or disagree across the v4/v6 duals of `localhost`), so an
* auto-port side-car retries on a fresh random port instead of failing
* init; a pinned `ws.port` is honored as given and fails loudly.
*/
async function startSidecar(core) {
const sidecarHost = options.host ?? "localhost";
const start = (port) => bindHttpAndWs({
context: ctx,
core,
host: sidecarHost,
port,
path: withLeadingSlash(route),
allowedOrigins: options.allowedOrigins
});
if (ws.port != null) return await start(ws.port);
const { getPort } = await import("./dist-CZXfGEkd.mjs").then((n) => n.t);
let lastError;
for (let attempt = 0; attempt < 3; attempt++) {
const port = attempt === 0 && options.resolveSidecarPort ? await options.resolveSidecarPort(sidecarHost) : await getPort({
random: true,
host: sidecarHost
});
try {
return await start(port);
} catch (error) {
lastError = error;
}
}
throw lastError;
}
async function init() {
const result = await options.init(api);
ctx = result.context;
dispose = result.dispose;
resolvedAuth = tier === "external" ? false : resolveAuth();
let websocketMeta;
if (tier === "sidecar") {
started = await startSidecar(await ensureCore());
websocketMeta = {
port: started.port,
path: route
};
} else if (tier === "server") {
started = await bindHttpAndWs({
context: ctx,
core: await ensureCore(),
host: options.host ?? "localhost",
port: 0,
server: options.server,
path: routePath,
allowedOrigins: options.allowedOrigins,
destroyUnmatched: options.destroyUnmatchedUpgrades
});
websocketMeta = { path: advertisedPath };
} else if (tier === "external") websocketMeta = ws.url;
else if (tier === "unbound") websocketMeta = { path: advertisedPath };
if (!wsDisabled && ws.url) websocketMeta = ws.url;
if (sseEnabled) app.use(sseRoutePath, defineHandler(async (event) => respondWith(event, await (await ensureSse()).handler(event.req))));
meta = {
backend: wsDisabled ? sseEnabled ? "sse" : "none" : "websocket",
...websocketMeta !== void 0 ? { websocket: websocketMeta } : {},
...sseEnabled ? { sse: { path: advertisedSsePath } } : {},
...result.mcp ? { mcp: result.mcp } : {}
};
if (Object.keys(ctx.staticConfig).length > 0) meta.configs = ctx.staticConfig;
await options.mount?.(ctx, meta, api);
maybePrintBanner();
maybeRegister();
}
const initPromise = init();
initPromise.catch(() => {});
const contextPromise = initPromise.then(() => ctx);
contextPromise.catch(() => {});
/**
* The `unbound` tier: the RPC core and its crossws adapter, bound to
* nothing. Built on the first `attach` / `handleUpgrade` — a host that
* never wires the socket (or whose runtime brings its own WS transport)
* pays nothing for it, not even the adapter's imports.
*/
let transportPromise;
function ensureTransport() {
transportPromise ??= initPromise.then(async () => {
const [core, { attachWsRpcTransport }] = await Promise.all([ensureCore(), import("./rpc/transports/ws-server.mjs")]);
transport = attachWsRpcTransport(core.rpcGroup, {
unbound: true,
path: routePath,
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
return transport;
});
return transportPromise;
}
async function handleRequest(request) {
await initPromise;
noteOrigin(new URL(request.url).origin);
const response = await app.fetch(request);
if (response.status === 404) return new Response(null, { status: 404 });
return response;
}
let nodeHandler;
function nodeMiddleware(req, res, next) {
let pathname = req.url ?? "/";
try {
pathname = new URL(pathname, "http://localhost").pathname;
} catch {}
if (!(samePath(pathname, baseNoSlash) || pathname.startsWith(base))) {
if (next) {
next();
return;
}
res.statusCode = 404;
res.end();
return;
}
initPromise.then(async () => {
const host = req.headers.host;
if (host) {
const encrypted = req.socket.encrypted;
noteOrigin(`${encrypted ? "https" : "http"}://${host}`);
}
if (!nodeHandler) {
const { toNodeHandler } = await import("h3/node");
nodeHandler = toNodeHandler(app);
}
return nodeHandler(req, res);
}).catch((err) => {
if (next) {
next(err);
return;
}
res.statusCode = 500;
res.end();
});
}
/** The `unbound` tier is the only one whose socket the host may drive. */
function assertUnbound() {
if (tier === "disabled") throw diagnostics.DF0057();
if (tier === "external") throw diagnostics.DF0056({ url: ws.url });
if (tier !== "unbound") throw diagnostics.DF0055({ tier });
}
/**
* Publish the socket's absolute URL on the context, so surfaces that hand
* out a complete endpoint (the hub's remote docks) work on this tier too.
* {@link bindHttpAndWs} does the same for the tiers it owns.
*/
function publishWsEndpoint(server) {
const record = () => {
const address = server.address();
if (typeof address !== "object" || !address) return;
const host = options.host ?? (address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address);
getInternalContext(ctx).setWsEndpoint({ url: `ws://${formatHostForUrl(host)}:${address.port}${routePath}` });
};
if (server.listening) record();
else server.once("listening", record);
}
function handleUpgrade(req, socket, head) {
assertUnbound();
if (transport) {
transport.handleUpgrade(req, socket, head);
return;
}
ensureTransport().then((live) => live.handleUpgrade(req, socket, head)).catch(() => socket.destroy());
}
function attach(server) {
assertUnbound();
server.on("upgrade", handleUpgrade);
ensureTransport().then(() => publishWsEndpoint(server)).catch(() => {});
return () => server.off("upgrade", handleUpgrade);
}
return {
base,
handler: handleRequest,
nodeMiddleware,
ready: initPromise,
context: contextPromise,
connectionMeta: () => meta ?? options.onMetaUnavailable(),
handleUpgrade,
attach,
async close() {
await initPromise.catch(() => {});
await registerPromise?.catch(() => {});
registration?.unregister();
await dispose?.();
await ssePromise?.then((live) => live.close()).catch(() => {});
await started?.close();
await transportPromise?.then((live) => live.close()).catch(() => {});
},
internals: {
get started() {
return started;
},
get authHandler() {
return authHandler;
}
}
};
}
//#endregion
export { normalizeHttpServerUrl as i, resolveInstanceRegister as n, samePath as r, createInstanceShell as t };
import { E as DevframeRpcServerFunctions, T as DevframeRpcClientFunctions, _ as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, s as DevframeSseOptions, u as DevframeAuthHandler } from "./devframe-mbfgpQQC.mjs";
import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "./ws-server-D1d3QM9f.mjs";
import "./index-Dipgo9ji.mjs";
import { BirpcGroup } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Buffer } from "node:buffer";
import { IncomingMessage, Server, ServerResponse } from "node:http";
import { Duplex } from "node:stream";
import { H3 } from "h3";
//#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.
*/
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}.
*/
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.
*/
declare function registerDevframeInstance(record: DevframeInstanceRecord, options?: {
instancesDir?: string;
}): DevframeInstanceRegistration;
/**
* 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.
*/
declare function listLiveDevframeInstances(options?: {
instancesDir?: string;
timeoutMs?: number;
}): Promise<{
live: DevframeInstanceRecord[];
pruned: DevframeInstanceRecord[];
}>;
//#endregion
//#region src/node/instance-shell.d.ts
/**
* The live handle for a bound HTTP + WebSocket RPC server — what the
* side-car / shared-server tiers produce and what {@link createDevServer}
* re-exposes through its own return contract.
*/
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). Absent when the WebSocket transport is disabled (`ws: false`).
*/
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.
*/
connectionMeta: () => ConnectionMeta;
close: () => Promise<void>;
}
/**
* How the instance's RPC socket is bound:
*
* - `sidecar` — its own HTTP+WS server on a dedicated port (`ws.port` /
* `ws.sidecar`), advertised with that port.
* - `server` — a shared upgrade route on the host's `node:http` server.
* - `external` — no local transport: `ws.url` alone names a server that owns
* both the socket and its auth.
* - `unbound` — the transport exists but nothing is bound to it yet; the host
* drives it through {@link InstanceShell.handleUpgrade} /
* {@link InstanceShell.attach}.
* - `disabled` — `ws: false`: no WebSocket at all; clients connect over the
* SSE endpoint instead (`backend: 'sse'`).
*/
type InstanceWsTier = 'sidecar' | 'server' | 'external' | 'unbound' | 'disabled';
/** The live shell surface an `init` / `mount` callback can reach. */
interface InstanceShellApi {
/** The normalized mount base, with leading and trailing slash. */
base: string;
/** The h3 app every route is mounted on. */
app: H3;
/** The public origin, once known (pinned, or derived from the first request). */
origin: () => string | undefined;
/** The connection meta, once the transport has resolved. */
connectionMeta: () => ConnectionMeta | undefined;
}
/** What an instance's own initialization contributes to the shell. */
interface InstanceShellInit<TContext extends DevframeNodeContext> {
/** The context every mounted surface shares. */
context: TContext;
/** The `mcp` entry to advertise, when an MCP route was mounted. */
mcp?: ConnectionMeta['mcp'];
/** Torn down before the transport on `close()` (e.g. MCP sessions). */
dispose?: () => Promise<void>;
}
interface CreateInstanceShellOptions<TContext extends DevframeNodeContext> {
/** Normalized mount base (leading and trailing slash). */
base: string;
/** h3 app to mount on. A fresh one is created when omitted. */
app?: H3;
/** Public origin, or a getter. Derived from the first request when omitted. */
origin?: string | (() => string);
/** Resolved auth intent: `undefined`/`true` gates, `false` opts out, a handler installs a scheme. */
auth?: boolean | DevframeAuthHandler;
/** Host `node:http` server to share the WS upgrade with. */
server?: Server;
/** Explicit WebSocket control — see {@link DevframeWsOptions}. `false` disables the socket (SSE-only). */
ws?: DevframeWsOptions | false;
/** SSE endpoint control — enabled by default; `false` disables, an object renames the route. */
sse?: boolean | DevframeSseOptions;
/** Bind host for a side-car WebSocket server. Default: `localhost`. */
host?: string;
/** Extra WS-upgrade origins beyond the loopback default; `false` disables the gate. */
allowedOrigins?: readonly string[] | WsOriginRegistry | false;
/** Destroy off-route upgrades on a shared `server`. */
destroyUnmatchedUpgrades?: boolean;
onPeerConnect?: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
onPeerDisconnect?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
/**
* Advertise the WS and SSE routes as base-absolute paths (`<base>__ws` /
* `<base>__sse`) instead of the base-relative default. A hub serves one
* meta document from several bases, so its clients need the absolute form
* to resolve the same endpoints.
*/
absoluteWsPath?: boolean;
/** Pick the first port a `ws.sidecar` server tries. Default: a random free port. */
resolveSidecarPort?: (host: string) => Promise<number>;
/**
* Publish this instance in the global registry (`~/.devframe/instances/`)
* once its public origin is known — a dynamic import so the registry code
* stays out of instances that opt out. Omit to skip registration.
*/
register?: InstanceRegisterConfig;
/** Create the context and mount everything that must precede the transport. */
init: (api: InstanceShellApi) => Promise<InstanceShellInit<TContext>>;
/** Mount the routes that describe the resolved transport (discovery, SPA). */
mount?: (context: TContext, meta: ConnectionMeta, api: InstanceShellApi) => void | Promise<void>;
/** Throw the instance's own diagnostic for `connectionMeta()` before readiness. */
onMetaUnavailable: () => never;
}
/**
* The identity a shell needs to publish itself in the global instance
* registry — the parts it can't derive on its own. The shell fills in
* `pid` / `origin` / `port` / `basePath` / `mcp` / `startedAt` once the
* origin resolves, then merges {@link InstanceRegisterConfig.overrides} last.
*/
interface InstanceRegisterConfig {
/** Definition id (or a synthetic one for a hub). */
id: string;
/** Display name. */
name?: string;
/** Working directory the instance runs from. Default: `process.cwd()`. */
rootDir?: string;
/** Fields overriding the shell-derived record (from the public option's object form). */
overrides?: Partial<DevframeInstanceRecord>;
}
/**
* Translate the public `register?: boolean | Partial<DevframeInstanceRecord>`
* option into a shell {@link InstanceRegisterConfig}, or `undefined` when
* registration is opted out. The object form supplies record overrides on top
* of the caller-provided identity defaults.
*/
declare function resolveInstanceRegister(option: boolean | Partial<DevframeInstanceRecord> | undefined, defaults: {
id: string;
name?: string;
rootDir?: string;
}): InstanceRegisterConfig | undefined;
/** Live internals the first-party adapters read off an instance. */
interface InstanceShellInternals {
readonly started?: StartedServer;
readonly authHandler?: DevframeAuthHandler;
}
interface InstanceShell<TContext extends DevframeNodeContext> {
base: string;
handler: (request: Request) => Promise<Response>;
nodeMiddleware: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
ready: Promise<void>;
context: Promise<TContext>;
connectionMeta: () => ConnectionMeta;
/** Complete a host server's `upgrade` event on the instance's socket. */
handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
/** Route a host server's `upgrade` events to the instance's socket. */
attach: (server: Server) => () => void;
close: () => Promise<void>;
internals: InstanceShellInternals;
}
/** Compare two URL paths ignoring a trailing slash. */
declare function samePath(a: string, b: string): boolean;
/**
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
* one h3 app, one lazily-derived public origin (and the auth banner that waits
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
* serves them. Each factory supplies only what makes it itself — its context,
* its routes, its diagnostics — through `init` / `mount`.
*
* Nothing here listens on a port unless a side-car was explicitly requested:
* the default tier leaves the socket `unbound`, so a host chains it onto its
* own server through {@link InstanceShell.attach} /
* {@link InstanceShell.handleUpgrade}.
*
* @internal
*/
declare function createInstanceShell<TContext extends DevframeNodeContext>(options: CreateInstanceShellOptions<TContext>): InstanceShell<TContext>;
//#endregion
export { InstanceShellInit as a, StartedServer as c, samePath as d, DevframeInstanceRecord as f, registerDevframeInstance as h, InstanceShellApi as i, createInstanceShell as l, listLiveDevframeInstances as m, InstanceRegisterConfig as n, InstanceShellInternals as o, DevframeInstanceRegistration as p, InstanceShell as r, InstanceWsTier as s, CreateInstanceShellOptions as t, resolveInstanceRegister as u };
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { createRequire } from "node:module";
import { isatty } from "node:tty";
import { formatWithOptions, inspect } from "node:util";
import { dirname, extname, join, normalize, sep } from "pathe";
import { createReadStream, existsSync } from "node:fs";
import { Buffer } from "node:buffer";
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { Readable } from "node:stream";
import { lookup } from "mrmime";
//#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/utils/remote-assets.ts
const debugFetch = createDebug("devframe:remote-assets:fetch");
const debugCache = createDebug("devframe:remote-assets:cache");
const MANIFEST_FILENAME = ".manifest.json";
/**
* Upstream response headers replayed to the browser. Everything outside this
* list is dropped, because it describes the *provider's* transfer rather than
* the file: hop-by-hop and encoding headers no longer match the body `fetch`
* already decoded, and a CDN's policy headers (`set-cookie`, `cache-control`,
* framing/CSP) belong to its origin — replaying them under the dev server's
* origin could just as well break the iframe these assets render in.
*/
const PROXIED_HEADERS = [
"content-language",
"etag",
"last-modified"
];
const CACHE_CONTROL_HEADER = "no-store";
/** Flatten a jsDelivr (`name`/`files`) or unpkg (`path`/`files`) file tree. */
function flattenTree(nodes, style) {
const out = [];
const walk = (list, prefix) => {
for (const node of list) if (style === "path") {
if (node.type === "file") out.push((node.path ?? "").replace(/^\//, ""));
else walk(node.files ?? [], "");
} else if (node.type === "file") out.push(prefix + (node.name ?? ""));
else if (node.files) walk(node.files, `${prefix}${node.name}/`);
};
walk(nodes, "");
return out;
}
const providers = {
jsdelivr: {
fileUrl: (pkg, version, filePath) => `https://cdn.jsdelivr.net/npm/${pkg}@${version}/${filePath}`,
listFiles: async (pkg, version, fetchImpl) => {
const url = `https://data.jsdelivr.com/v1/packages/npm/${pkg}@${version}`;
debugFetch("listing files for %s@%s from %s", pkg, version, url);
const res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
return flattenTree((await res.json()).files ?? [], "name");
}
},
unpkg: {
fileUrl: (pkg, version, filePath) => `https://unpkg.com/${pkg}@${version}/${filePath}`,
listFiles: async (pkg, version, fetchImpl) => {
const url = `https://unpkg.com/${pkg}@${version}/?meta`;
debugFetch("listing files for %s@%s from %s", pkg, version, url);
const res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
return flattenTree([await res.json()], "path");
}
}
};
function resolveProvider(assets) {
const p = assets.provider ?? "jsdelivr";
return typeof p === "string" ? {
provider: providers[p],
name: p
} : {
provider: p,
name: "custom"
};
}
/**
* Resolve a locally installed copy of `assets.package` from
* `assets.resolveFrom`'s dependency graph and return its assets directory,
* or `undefined` when the package (or directory) is absent. A different
* installed version warns (`DF0062`); a different major throws (`DF0061`).
*/
function resolveInstalled(assets) {
if (assets.resolveFrom == null) return void 0;
let pkgJsonPath;
let installed;
try {
const requireFrom = createRequire(assets.resolveFrom);
pkgJsonPath = requireFrom.resolve(`${assets.package}/package.json`);
installed = requireFrom(`${assets.package}/package.json`).version;
} catch {
return;
}
if (typeof installed !== "string") return void 0;
if (installed !== assets.version) {
const major = (v) => v.trim().split(".")[0] ?? v;
if (major(installed) !== major(assets.version)) throw diagnostics.DF0061({
package: assets.package,
required: assets.version,
installed
});
diagnostics.DF0062({
package: assets.package,
required: assets.version,
installed
});
}
const dir = join(dirname(pkgJsonPath), assets.path ?? "dist");
return existsSync(dir) ? dir : void 0;
}
function contentTypeFor(filePath) {
const type = lookup(filePath);
if (!type) return "application/octet-stream";
return type === "text/html" ? "text/html; charset=utf-8" : type;
}
/**
* Headers for a file streamed through from the provider. `Content-Type` and
* `Cache-Control` are ours, so a file looks identical whether it came from the
* provider or from the cache ({@link createStore}'s `serveCached`).
*/
function proxyHeaders(filePath, upstream) {
const headers = new Headers({
"Content-Type": contentTypeFor(filePath),
"Cache-Control": CACHE_CONTROL_HEADER
});
const encoding = upstream.get("content-encoding");
const length = upstream.get("content-length");
if (length && (!encoding || encoding === "identity")) headers.set("Content-Length", length);
for (const name of PROXIED_HEADERS) {
const value = upstream.get(name);
if (value != null) headers.set(name, value);
}
return headers;
}
/** Clean a request path into a safe package-relative POSIX path, or `null` if it escapes root. */
function cleanRequestPath(urlPath) {
let cleaned;
try {
cleaned = decodeURIComponent(urlPath || "/");
} catch {
return null;
}
cleaned = cleaned.replace(/[?#].*$/, "").replace(/^\/+|\/+$/g, "");
const normalized = normalize(cleaned);
if (normalized === ".." || normalized.startsWith(`..${sep}`) || normalized.startsWith("/")) return null;
return normalized === "." ? "" : normalized;
}
/** Candidate files for a request, in order: direct hit, index, `.html`, SPA fallback. */
function candidatePaths(prefix, cleaned) {
const candidates = [];
if (cleaned) candidates.push(prefix + cleaned);
candidates.push(`${prefix}${cleaned ? `${cleaned}/` : ""}index.html`);
if (cleaned && !extname(cleaned)) candidates.push(`${prefix + cleaned}.html`);
if (!/\.[a-z0-9]+$/i.test(cleaned) && !candidates.includes(`${prefix}index.html`)) candidates.push(`${prefix}index.html`);
return candidates;
}
function createStore(assets, cacheDir) {
const normalized = {
...assets,
path: assets.path ?? "dist"
};
const { provider, name: providerName } = resolveProvider(assets);
const fetchImpl = assets.fetch ?? globalThis.fetch;
const prefix = `${normalized.path}/`;
let manifestPromise;
let manifestReported = false;
async function loadManifest() {
const manifestFile = join(cacheDir, MANIFEST_FILENAME);
if (existsSync(manifestFile)) try {
return new Set(JSON.parse(await readFile(manifestFile, "utf8")));
} catch {}
if (assets.offline || !provider.listFiles) return null;
try {
const files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
await mkdir(cacheDir, { recursive: true });
await writeFile(manifestFile, JSON.stringify(files), "utf8").catch(() => {});
return new Set(files);
} catch (error) {
if (!manifestReported) {
manifestReported = true;
diagnostics.DF0059({
package: normalized.package,
version: normalized.version,
provider: providerName,
reason: errText(error),
cause: error
});
}
return null;
}
}
async function serveCached(filePath) {
const abs = join(cacheDir, filePath);
let size;
try {
const s = await stat(abs);
if (!s.isFile()) return null;
size = s.size;
} catch {
return null;
}
debugCache("serving %s from cache (%d bytes)", filePath, size);
return new Response(Readable.toWeb(createReadStream(abs)), { headers: {
"Content-Type": contentTypeFor(filePath),
"Content-Length": String(size),
"Cache-Control": CACHE_CONTROL_HEADER
} });
}
/** Persist `body` to the cache at `filePath` (tmp + rename); failures warn (`DF0063`). */
async function persist(filePath, body) {
const target = join(cacheDir, filePath);
const tmp = `${target}.${Math.random().toString(36).slice(2)}.tmp`;
try {
await mkdir(dirname(target), { recursive: true });
await writeFile(tmp, Buffer.from(await new Response(body).arrayBuffer()));
await rename(tmp, target);
} catch (error) {
await rm(tmp, { force: true }).catch(() => {});
diagnostics.DF0063({
filepath: target,
reason: errText(error),
cause: error
});
}
}
/** Fetch `filePath` through the provider: `null` on 404, a `Response` on 200, throws (`DF0060`) otherwise. */
async function serveRemote(filePath) {
const url = provider.fileUrl(normalized.package, normalized.version, filePath);
let res;
try {
debugFetch("fetching %s from %s", filePath, url);
res = await fetchImpl(url);
} catch (error) {
throw diagnostics.DF0060({
url,
package: normalized.package,
reason: errText(error),
cause: error
});
}
if (res.status === 404) {
await res.body?.cancel().catch(() => {});
return null;
}
if (!res.ok || !res.body) {
await res.body?.cancel().catch(() => {});
throw diagnostics.DF0060({
url,
package: normalized.package,
reason: `HTTP ${res.status}`
});
}
const [toClient, toCache] = res.body.tee();
persist(filePath, toCache);
return new Response(toClient, {
status: res.status,
statusText: res.statusText,
headers: proxyHeaders(filePath, res.headers)
});
}
async function serve(urlPath) {
const cleaned = cleanRequestPath(urlPath);
if (cleaned === null) return null;
const candidates = candidatePaths(prefix, cleaned);
manifestPromise ??= loadManifest();
const manifest = await manifestPromise;
if (manifest) {
const filePath = candidates.find((c) => manifest.has(c));
if (!filePath) return null;
return await serveCached(filePath) ?? (assets.offline ? Promise.reject(diagnostics.DF0060({
url: filePath,
package: normalized.package,
reason: "offline: true and the file is not in the cache"
})) : serveRemote(filePath));
}
for (const candidate of candidates) {
const cached = await serveCached(candidate);
if (cached) return cached;
}
if (assets.offline) return null;
for (const candidate of candidates) {
const remote = await serveRemote(candidate);
if (remote) return remote;
}
return null;
}
async function materialize(targetDir) {
const fail = (reason, cause) => {
throw diagnostics.DF0064({
package: normalized.package,
version: normalized.version,
reason,
cause
});
};
if (!provider.listFiles) fail("the configured provider has no file listing (`listFiles`)");
let files;
try {
files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
} catch (error) {
return fail(errText(error), error);
}
for (const filePath of files.filter((f) => f.startsWith(prefix))) {
const target = join(targetDir, filePath.slice(prefix.length));
const url = provider.fileUrl(normalized.package, normalized.version, filePath);
let res;
try {
res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
} catch (error) {
return fail(`failed to download ${filePath}: ${errText(error)}`, error);
}
await mkdir(dirname(target), { recursive: true });
await writeFile(target, Buffer.from(await res.arrayBuffer()));
}
}
return {
assets: normalized,
serve,
materialize
};
}
function errText(error) {
return error instanceof Error ? error.message : String(error);
}
const PACKAGE_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
const VERSION_RE = /^\d+\.\d+\.\d+(?:-[a-z0-9-]+(?:\.[a-z0-9-]+)*)?(?:\+[a-z0-9-]+(?:\.[a-z0-9-]+)*)?$/i;
/** Reject a {@link RemoteAssets} with an unsafe package name or version (`DF0065`). */
function assertValidRemoteAssets(assets) {
if (assets.package.length > 214 || !PACKAGE_NAME_RE.test(assets.package)) throw diagnostics.DF0065({
field: "package",
value: assets.package
});
if (!VERSION_RE.test(assets.version)) throw diagnostics.DF0065({
field: "version",
value: assets.version
});
}
/**
* Normalize a {@link StaticAssetsSource} into something servable: a local
* directory (strings pass through; a remote source short-circuits to a
* locally installed copy of its package when present) or a caching
* {@link RemoteAssetsStore} back-proxy. Remote caches live under
* `<projectStorageDir>/.remote-assets/<package>@<version>/`.
*
* A remote source's `package`/`version` are validated first (`DF0065`) — both
* are interpolated into CDN URLs and the cache path.
*
* `defaultResolveFrom` (typically the declaring devframe's `importMetaUrl`)
* supplies a `resolveFrom` base for a remote source that doesn't set one:
* it is applied only when `source.resolveFrom` is `undefined`, so an explicit
* per-source string still wins and an explicit `null` still opts out of the
* installed-copy lookup.
*/
function resolveStaticAssetsSource(source, projectStorageDir, defaultResolveFrom) {
if (typeof source === "string") return source;
assertValidRemoteAssets(source);
const resolved = source.resolveFrom === void 0 && defaultResolveFrom != null ? {
...source,
resolveFrom: defaultResolveFrom
} : source;
return resolveInstalled(resolved) ?? createStore(resolved, join(projectStorageDir, ".remote-assets", `${resolved.package.replace(/\//g, "+")}@${resolved.version}`));
}
//#endregion
export { createDebug as n, resolveStaticAssetsSource as t };
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { createRpcServer } from "./rpc/server.mjs";
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { AsyncLocalStorage } from "node:async_hooks";
//#region src/node/rpc-core.ts
var rpc_core_exports = /* @__PURE__ */ __exportAll({ createContextRpcServer: () => createContextRpcServer });
/**
* Bind a devframe context's registered RPC functions to a birpc group,
* transport-agnostically — the shared core under the instance shell's own
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
* `createHandler`.
*
* Owns everything about serving RPC that is independent of *how* peers
* connect: the auth handler's function registration, the
* `AsyncLocalStorage`-based session resolver (so
* `ctx.rpc.getCurrentRpcSession()` works inside handlers), the
* `authorize` gate, and the `auth: false` auto-trust handshake shim.
*/
function createContextRpcServer(options) {
const { context } = options;
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);
});
};
}
} });
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 };
}
});
const onConnected = authHandler || options.onPeerConnect ? (connection, meta) => {
const session = {
meta,
rpc: rpcGroup.clients.find((client) => client.$meta === meta)
};
authHandler?.onConnect(connection, session);
options.onPeerConnect?.(connection, session);
} : void 0;
const onDisconnected = (connection, meta) => {
options.onPeerDisconnect?.(connection, meta);
rpcHost._emitSessionDisconnected(meta);
};
return {
rpcGroup,
authHandler,
onConnected,
onDisconnected
};
}
//#endregion
export { rpc_core_exports as n, createContextRpcServer as t };
import { E as DevframeRpcServerFunctions, T as DevframeRpcClientFunctions, _ as DevframeNodeRpcSession, g as DevframeNodeContext, u as DevframeAuthHandler } from "./devframe-mbfgpQQC.mjs";
import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-D1d3QM9f.mjs";
import "./index-Dipgo9ji.mjs";
import { BirpcGroup, EventOptions } from "birpc";
//#region src/node/rpc-core.d.ts
interface CreateContextRpcServerOptions {
context: DevframeNodeContext;
/**
* Auth intent: `true`/omitted gates by default, `false` opts out (auto-trust
* handshake shim), a {@link DevframeAuthHandler} installs a custom scheme.
*/
auth?: boolean | DevframeAuthHandler;
/** Lower-level per-call gate by method name and session, without a full handler. */
authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean;
/** Called once per new RPC connection, right after its session is created. */
onPeerConnect?: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
/** Called once per closed RPC connection, after the transport's disconnect bookkeeping. */
onPeerDisconnect?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
/** Forwarded verbatim to birpc's `rpcOptions` so a host keeps seeing RPC failures. */
rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>;
}
interface ContextRpcServer {
rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
/** The resolved auth handler when `auth` was passed as one. */
authHandler?: DevframeAuthHandler;
/**
* Connection lifecycle handlers to wire into a transport binding
* (`attachWsRpcTransport`'s `onConnected` / `onDisconnected`, or any other
* crossws adapter's peer hooks via `createWsRpcPeerHooks`).
*/
onConnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
onDisconnected: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
}
/**
* Bind a devframe context's registered RPC functions to a birpc group,
* transport-agnostically — the shared core under the instance shell's own
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
* `createHandler`.
*
* Owns everything about serving RPC that is independent of *how* peers
* connect: the auth handler's function registration, the
* `AsyncLocalStorage`-based session resolver (so
* `ctx.rpc.getCurrentRpcSession()` works inside handlers), the
* `authorize` gate, and the `auth: false` auto-trust handshake shim.
*/
declare function createContextRpcServer(options: CreateContextRpcServerOptions): ContextRpcServer;
//#endregion
export { CreateContextRpcServerOptions as n, createContextRpcServer as r, ContextRpcServer as t };
import { n as WsOriginRegistry } from "../../ws-server-D1d3QM9f.mjs";
import { t as ContextRpcServer } from "../../rpc-core-dV69u4lY.mjs";
//#region src/rpc/transports/ws-deno.d.ts
interface AttachDenoWsTransportOptions {
/** Same contract as `WsRpcTransportOptions.allowedOrigins`. */
allowedOrigins?: readonly string[] | WsOriginRegistry | false;
}
interface DenoWsTier {
/**
* Complete a WS upgrade request — `Deno.serve`'s handler info as the 2nd
* argument. Unlike Bun, Deno's adapter attaches the socket to the returned
* `Response` itself, so there is no `websocket` handler object to register.
*/
handleUpgrade: (request: Request, info: unknown) => Promise<Response>;
close: () => Promise<void>;
}
/**
* The Deno fetch-upgrade WebSocket tier for `initDevframe` / `initHub` — the
* same RPC peer wiring as `attachWsRpcTransport`, driven by crossws's Deno
* adapter so upgrades complete through `handleUpgrade(request, info)` on the
* app's own origin, with no side-car server. Load it dynamically so the Deno
* adapter never enters a Node-only bundle path.
*/
declare function attachDenoWsTransport(core: ContextRpcServer, options?: AttachDenoWsTransportOptions): Promise<DenoWsTier>;
//#endregion
export { AttachDenoWsTransportOptions, DenoWsTier, attachDenoWsTransport };
import { i as isAllowedOrigin, r as createWsRpcPeerHooks } from "../../ws-server-BdSLrhxE.mjs";
//#region src/rpc/transports/ws-deno.ts
/**
* The Deno fetch-upgrade WebSocket tier for `initDevframe` / `initHub` — the
* same RPC peer wiring as `attachWsRpcTransport`, driven by crossws's Deno
* adapter so upgrades complete through `handleUpgrade(request, info)` on the
* app's own origin, with no side-car server. Load it dynamically so the Deno
* adapter never enters a Node-only bundle path.
*/
async function attachDenoWsTransport(core, options = {}) {
const { default: denoAdapter } = await import("crossws/adapters/deno");
const { allowedOrigins } = options;
const ws = denoAdapter({ hooks: {
...createWsRpcPeerHooks(core.rpcGroup, {
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
}),
upgrade(request) {
const origin = request.headers.get("origin") ?? void 0;
const allowed = allowedOrigins && !Array.isArray(allowedOrigins) ? allowedOrigins.isAllowed(origin) : isAllowedOrigin(origin, allowedOrigins || []);
if (allowedOrigins !== false && !allowed) return new Response("Forbidden", { status: 403 });
}
} });
return {
handleUpgrade: (request, info) => ws.handleUpgrade(request, info),
close: () => ws.close()
};
}
//#endregion
export { attachDenoWsTransport };
import { createEventEmitter } from "./utils/events.mjs";
import { nanoid } from "./utils/nanoid.mjs";
//#region ../../node_modules/.pnpm/immer@11.1.17/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 = [
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) {
{
const e = errors[error];
const msg = isFunction(e) ? e.apply(null, args) : e;
throw new Error(`[Immer] ${msg}`);
}
}
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 (isNaN(parseInt(prop))) die(13);
return arrayTraps.set.call(this, state, prop, void 0);
};
arrayTraps.set = function(state, prop, value) {
if (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;
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/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
export { createSharedState as t };
import { n as validateDefinitions, r as getRpcHandler, s as hash } from "./validation-YrZH6atx.mjs";
import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
import { DEVFRAME_RPC_DUMP_DIRNAME } from "./constants.mjs";
//#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/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/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 { reviveDumpError as a, getDefinitionsWithDumps as i, createClientFromDump as n, serializeDumpError as o, dumpFunctions as r, collectStaticRpcDump as t };
import { t as diagnostics } from "./diagnostics-DI1HGj2I.mjs";
import { createSharedState } from "devframe/utils/shared-state";
import process from "node:process";
import { dirname } from "pathe";
import fs from "node:fs";
//#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 safeJsonParse(text) {
return JSON.parse(text, (key, value) => {
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) return void 0;
return value;
});
}
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 = safeJsonParse(fs.readFileSync(options.filepath, "utf-8"));
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.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 { createStorage as t };
import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
import { createHash } from "node:crypto";
//#region ../../node_modules/.pnpm/ohash@2.0.12/node_modules/ohash/dist/_chunks/is-equal.mjs
function serialize(input) {
if (typeof input === "string") return `'${input}'`;
return new Serializer().serialize(input);
}
const asciiOrder = " _-,;:!?.'\"()[]{}@*/\\&#%`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz";
const asciiWeights = /*@__PURE__*/ (function() {
const weights = /* @__PURE__ */ new Uint8Array(128);
for (let i = 0; i < 69; i++) weights[asciiOrder.charCodeAt(i)] = i + 1;
for (let code = 65; code <= 90; code++) weights[code] = weights[code + 32];
return weights;
})();
function compareStrings(a, b) {
if (a === b) return 0;
const length = Math.min(a.length, b.length);
let tieBreaker = 0;
for (let i = 0; i < length; i++) {
const codeA = a.charCodeAt(i);
const codeB = b.charCodeAt(i);
if (codeA === codeB) continue;
const weightA = codeA < 128 && asciiWeights[codeA] ? asciiWeights[codeA] : codeA + 128;
const weightB = codeB < 128 && asciiWeights[codeB] ? asciiWeights[codeB] : codeB + 128;
if (weightA !== weightB) return weightA < weightB ? -1 : 1;
if (tieBreaker === 0) tieBreaker = codeA > codeB ? -1 : 1;
}
if (a.length !== b.length) return a.length < b.length ? -1 : 1;
return tieBreaker;
}
const Serializer = /*@__PURE__*/ (function() {
class Serializer {
#context = /* @__PURE__ */ new Map();
compare(a, b) {
const typeA = typeof a;
const typeB = typeof b;
if (typeA === "string" && typeB === "string") return compareStrings(a, b);
if (typeA === "number" && typeB === "number") return a - b;
return compareStrings(this.serialize(a, true), this.serialize(b, true));
}
serialize(value, noQuotes) {
if (value === null) return "null";
switch (typeof value) {
case "string": return noQuotes ? value : `'${value}'`;
case "bigint": return `${value}n`;
case "object": return this.$object(value);
case "function": return this.$function(value);
}
return String(value);
}
serializeObject(object) {
const objString = Object.prototype.toString.call(object);
if (objString !== "[object Object]") return this.serializeBuiltInType(objString.length < 10 ? `unknown:${objString}` : objString.slice(8, -1), object);
const constructor = object.constructor;
const objName = constructor === Object || constructor === void 0 ? "" : constructor.name;
if (objName !== "" && globalThis[objName] === constructor) return this.serializeBuiltInType(objName, object);
if ("toJSON" in object && typeof object.toJSON === "function") {
const json = object.toJSON();
return objName + (json !== null && typeof json === "object" ? this.$object(json) : `(${this.serialize(json)})`);
}
const keys = Object.keys(object).sort(compareStrings);
let content = `${objName}{`;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
content += `${key}:${this.serialize(object[key])}`;
if (i < keys.length - 1) content += ",";
}
return content + "}";
}
serializeBuiltInType(type, object) {
const handler = this["$" + type];
if (handler) return handler.call(this, object);
if (typeof object.entries === "function") return this.serializeObjectEntries(type, object.entries());
throw new Error(`Cannot serialize ${type}`);
}
serializeObjectEntries(type, entries) {
const sortedEntries = Array.from(entries).sort((a, b) => this.compare(a[0], b[0]));
let content = `${type}{`;
for (let i = 0; i < sortedEntries.length; i++) {
const [key, value] = sortedEntries[i];
content += `${this.serialize(key, true)}:${this.serialize(value)}`;
if (i < sortedEntries.length - 1) content += ",";
}
return content + "}";
}
$object(object) {
let content = this.#context.get(object);
if (content === void 0) {
this.#context.set(object, `#${this.#context.size}`);
content = this.serializeObject(object);
this.#context.set(object, content);
}
return content;
}
$function(fn) {
const fnStr = Function.prototype.toString.call(fn);
if (fnStr.slice(-15) === "[native code] }") return `${fn.name || ""}()[native]`;
return `${fn.name}(${fn.length})${fnStr.replace(/\s*\n\s*/g, "")}`;
}
$Array(arr) {
let content = "[";
for (let i = 0; i < arr.length; i++) {
content += this.serialize(arr[i]);
if (i < arr.length - 1) content += ",";
}
return content + "]";
}
$Date(date) {
try {
return `Date(${date.toISOString()})`;
} catch {
return `Date(null)`;
}
}
$ArrayBuffer(arr) {
return `ArrayBuffer[${new Uint8Array(arr).join(",")}]`;
}
$Set(set) {
return `Set${this.$Array(Array.from(set).sort((a, b) => this.compare(a, b)))}`;
}
$Map(map) {
return this.serializeObjectEntries("Map", map.entries());
}
}
for (const type of [
"Error",
"RegExp",
"URL"
]) Serializer.prototype["$" + type] = function(val) {
return `${type}(${val})`;
};
for (const type of [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array"
]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join(",")}]`;
};
for (const type of ["BigInt64Array", "BigUint64Array"]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join("n,")}${arr.length > 0 ? "n" : ""}]`;
};
return Serializer;
})();
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.12/node_modules/ohash/dist/crypto/node/index.mjs
const fastHash = /*@__PURE__*/ (() => globalThis.process?.getBuiltinModule?.("crypto")?.hash)();
const algorithm = "sha256";
const encoding = "base64url";
function digest(data) {
if (fastHash) return fastHash(algorithm, data, encoding);
const h = createHash(algorithm).update(data);
return globalThis.process?.versions?.webcontainer ? h.digest().toString(encoding) : h.digest(encoding);
}
//#endregion
//#region ../../node_modules/.pnpm/ohash@2.0.12/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 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/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
export { validateRpcArgs as a, getRpcResolvedSetupResult as i, validateDefinitions as n, validateRpcReturn as o, getRpcHandler as r, hash as s, validateDefinition as t };
+1
-1

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

import { n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
import { n as DevframeDefinition } from "../devframe-mbfgpQQC.mjs";
import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";

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

import { s as colors } from "../nostics-CzECRXpE.mjs";
import { n as strictJsonStringify } from "../serialization-BGzEwAdr.mjs";
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
import { t as collectStaticRpcDump } from "../static-BPZ1_OPA.mjs";
import { t as collectStaticRpcDump } from "../static-DV_qUSRS.mjs";
import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
import { t as diagnostics } from "../diagnostics-Di8ytitn.mjs";
import { t as createHostContext } from "../context-Bdkakk8S.mjs";
import { t as resolveStaticAssetsSource } from "../remote-assets-DRfY2RjX.mjs";
import { t as diagnostics } from "../diagnostics-DI1HGj2I.mjs";
import { t as createHostContext } from "../context-riPPHGiu.mjs";
import { t as resolveStaticAssetsSource } from "../remote-assets-CqxiyltC.mjs";
import { t as createH3DevframeHost } from "../host-h3-fRbF9yor.mjs";

@@ -37,3 +37,3 @@ import process from "node:process";

});
const resolved = resolveStaticAssetsSource(distSource, host.getStorageDir("project"));
const resolved = resolveStaticAssetsSource(distSource, host.getStorageDir("project"), d.importMetaUrl);
if (typeof resolved === "string") {

@@ -49,7 +49,8 @@ console.log(colors.cyan`[devframe] copying SPA from ${resolved} -> ${outDir}`);

mode: "build",
host
host,
importMetaUrl: d.importMetaUrl
});
for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.packageName });
for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.importMetaUrl });
await ctx.services.ready();
await d.setup(ctx);
await ctx.services.ready();
await fs$1.mkdir(resolve(outDir, DEVFRAME_RPC_DUMP_DIRNAME), { recursive: true });

@@ -56,0 +57,0 @@ const jsonSerializableMethods = [];

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

import { Ft as CliFlagsSchema, It as InferCliFlags, Lt as defineCliFlags, Rt as parseCliFlags, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
import { Ft as CliFlagsSchema, It as InferCliFlags, Lt as defineCliFlags, Rt as parseCliFlags, n as DevframeDefinition } from "../devframe-mbfgpQQC.mjs";
import { CAC } from "cac";

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

import { s as colors } from "../nostics-CzECRXpE.mjs";
import { r as resolveDevServerPort } from "../_shared-BM3PdYli.mjs";
import { createBuild } from "./build.mjs";
import { t as createDevServer } from "../dev-C8VHtuPP.mjs";
import { t as createDevServer } from "../dev-BKm_HisC.mjs";
import process from "node:process";

@@ -6,0 +6,0 @@ import cac$1 from "cac";

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

import { _ as DevframeNodeRpcSession, c as DevframeWsOptions, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-BlLEZR-x.mjs";
import { _ as DevframeNodeRpcSession, c as DevframeWsOptions, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-mbfgpQQC.mjs";
import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";
import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";
import { c as StartedServer } from "../instance-shell-0iw-cQ5V.mjs";
import { a as resolveMcpConnectionMeta, i as resolveDevServerPort, t as ResolveDevServerPortOptions } from "../_shared-DqbKCAew.mjs";
import { c as StartedServer } from "../instance-shell-mU5j5xWn.mjs";
import { a as resolveMcpConnectionMeta, i as resolveDevServerPort, t as ResolveDevServerPortOptions } from "../_shared-BAGBgTUO.mjs";
import { H3 } from "h3";

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

import { i as resolveMcpConnectionMeta, r as resolveDevServerPort } from "../_shared-BM3PdYli.mjs";
import { t as createDevServer } from "../dev-C8VHtuPP.mjs";
import { t as createDevServer } from "../dev-BKm_HisC.mjs";
export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };

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

import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-mbfgpQQC.mjs";
//#region src/adapters/embedded.d.ts

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

@@ -12,3 +12,4 @@ //#region src/adapters/embedded.ts

async function createEmbedded(d, options) {
for (const input of d.services ?? []) options.ctx.services.install(input, { resolveFrom: d.packageName });
for (const input of d.services ?? []) options.ctx.services.install(input, { resolveFrom: d.importMetaUrl });
await options.ctx.services.ready();
await d.setup(options.ctx);

@@ -15,0 +16,0 @@ }

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

import { $ as DevframeStorageScope, _ as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-BlLEZR-x.mjs";
import { $ as DevframeStorageScope, _ as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-mbfgpQQC.mjs";
import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";
import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";
import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-0iw-cQ5V.mjs";
import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-mU5j5xWn.mjs";
import { Buffer } from "node:buffer";

@@ -6,0 +6,0 @@ import { IncomingMessage, Server, ServerResponse } from "node:http";

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

import { n as getInstanceInternals, r as initDevframe } from "../dev-C8VHtuPP.mjs";
import { n as getInstanceInternals, r as initDevframe } from "../dev-BKm_HisC.mjs";
export { getInstanceInternals, initDevframe };

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

import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-mbfgpQQC.mjs";
import { H3 } from "h3";

@@ -3,0 +3,0 @@ import "@modelcontextprotocol/server";

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

import { i as createMcpServer, n as mountMcpHttp, r as createMcpFetchHandler } from "../http-BbHCU5Zs.mjs";
import { i as createMcpServer, n as mountMcpHttp, r as createMcpFetchHandler } from "../http-POepPnaQ.mjs";
export { createMcpFetchHandler, createMcpServer, mountMcpHttp };
import { t as Diagnostic } from "../nostics-CzECRXpE.mjs";
import { t as diagnostics } from "../diagnostics-Di8ytitn.mjs";
import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-CHunhMi5.mjs";
import { t as diagnostics } from "../diagnostics-DI1HGj2I.mjs";
import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-Dy28drtm.mjs";
import { t as toAgentToolName } from "../agent-tool-name-EgfoFO8C.mjs";

@@ -5,0 +5,0 @@ import process from "node:process";

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

import { E as DevframeRpcServerFunctions, H as DevframeSettings, J as ScopedServerFunctions, M as DevframeServiceMeta, Mt as EventEmitter, P as DevframeServiceScopeOf, R as DevframeServicesState, T as DevframeRpcClientFunctions, X as SettingsForNamespace, Y as ScopedSharedStates, b as RpcSharedStateGetOptions, d as ConnectionMeta, gt as SharedState, ot as StreamReader, q as ScopedRpcFn, st as StreamSink, x as RpcSharedStateHost } from "../devframe-BlLEZR-x.mjs";
import { E as DevframeRpcServerFunctions, H as DevframeSettings, J as ScopedServerFunctions, M as DevframeServiceMeta, Mt as EventEmitter, P as DevframeServiceScopeOf, R as DevframeServicesState, T as DevframeRpcClientFunctions, X as SettingsForNamespace, Y as ScopedSharedStates, b as RpcSharedStateGetOptions, d as ConnectionMeta, gt as SharedState, ot as StreamReader, q as ScopedRpcFn, st as StreamSink, x as RpcSharedStateHost } from "../devframe-mbfgpQQC.mjs";
import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-tdrk1bzV.mjs";

@@ -3,0 +3,0 @@ import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-Djq7CBXh.mjs";

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

import { $ as DevframeStorageScope, A as DevframeServiceId, At as DevframeAgentHost, B as DevframeScopedNodeRpc, C as RpcStreamingChannelOptions, Ct as AgentResource, D as DevframeRpcSharedStates, Dt as AgentToolInput, E as DevframeRpcServerFunctions, Et as AgentTool, F as DevframeServicesHost, G as ScopedBroadcastOptions, H as DevframeSettings, I as DevframeServicesRegistry, J as ScopedServerFunctions, K as ScopedClientFunctions, L as DevframeServicesScopeRegistry, M as DevframeServiceMeta, Mt as EventEmitter, N as DevframeServiceOf, Nt as EventUnsubscribe, O as DevframeServiceDefinition, Ot as AgentToolProvider, P as DevframeServiceScopeOf, Pt as EventsMap, Q as DevframeHost, R as DevframeServicesState, S as RpcStreamingChannel, St as AgentManifest, T as DevframeRpcClientFunctions, Tt as AgentResourceInput, U as DevframeSettingsRegistry, V as DevframeScopedStreamingHost, W as DevframeSettingsStore, X as SettingsForNamespace, Y as ScopedSharedStates, Z as DevframeViewHost, _ as DevframeNodeRpcSession, a as DevframeDuplicationStrategy, b as RpcSharedStateGetOptions, c as DevframeWsOptions, d as ConnectionMeta, et as DevframeDiagnosticsHost, f as ConnectionMetaSse, g as DevframeNodeContext, h as DevframeConnectionConfigsRegistry, i as DevframeDockDefaults, j as DevframeServiceInput, jt as DevframeAgentHostEvents, k as DevframeServiceDescriptor, kt as AgentToolProviderHandle, l as McpRouteOptions, m as DevframeCapabilities, n as DevframeDefinition, o as DevframeSetupInfo, p as ConnectionMetaWebsocket, q as ScopedRpcFn, r as DevframeDeploymentKind, s as DevframeSseOptions, t as DevframeCliOptions, tt as DevframeDiagnosticsLogger, v as RpcBroadcastOptions, w as RpcStreamingHost, wt as AgentResourceContent, x as RpcSharedStateHost, xt as AgentHandle, y as RpcFunctionsHost, z as DevframeScopedNodeContext } from "./devframe-BlLEZR-x.mjs";
import { $ as DevframeStorageScope, A as DevframeServiceId, At as DevframeAgentHost, B as DevframeScopedNodeRpc, C as RpcStreamingChannelOptions, Ct as AgentResource, D as DevframeRpcSharedStates, Dt as AgentToolInput, E as DevframeRpcServerFunctions, Et as AgentTool, F as DevframeServicesHost, G as ScopedBroadcastOptions, H as DevframeSettings, I as DevframeServicesRegistry, J as ScopedServerFunctions, K as ScopedClientFunctions, L as DevframeServicesScopeRegistry, M as DevframeServiceMeta, Mt as EventEmitter, N as DevframeServiceOf, Nt as EventUnsubscribe, O as DevframeServiceDefinition, Ot as AgentToolProvider, P as DevframeServiceScopeOf, Pt as EventsMap, Q as DevframeHost, R as DevframeServicesState, S as RpcStreamingChannel, St as AgentManifest, T as DevframeRpcClientFunctions, Tt as AgentResourceInput, U as DevframeSettingsRegistry, V as DevframeScopedStreamingHost, W as DevframeSettingsStore, X as SettingsForNamespace, Y as ScopedSharedStates, Z as DevframeViewHost, _ as DevframeNodeRpcSession, a as DevframeDuplicationStrategy, b as RpcSharedStateGetOptions, c as DevframeWsOptions, d as ConnectionMeta, et as DevframeDiagnosticsHost, f as ConnectionMetaSse, g as DevframeNodeContext, h as DevframeConnectionConfigsRegistry, i as DevframeDockDefaults, j as DevframeServiceInput, jt as DevframeAgentHostEvents, k as DevframeServiceDescriptor, kt as AgentToolProviderHandle, l as McpRouteOptions, m as DevframeCapabilities, n as DevframeDefinition, o as DevframeSetupInfo, p as ConnectionMetaWebsocket, q as ScopedRpcFn, r as DevframeDeploymentKind, s as DevframeSseOptions, t as DevframeCliOptions, tt as DevframeDiagnosticsLogger, v as RpcBroadcastOptions, w as RpcStreamingHost, wt as AgentResourceContent, x as RpcSharedStateHost, xt as AgentHandle, y as RpcFunctionsHost, z as DevframeScopedNodeContext } from "./devframe-mbfgpQQC.mjs";
import "./index-tdrk1bzV.mjs";

@@ -3,0 +3,0 @@ import { C as RpcFunctionType, T as RpcReturnSchema, _ as RpcFunctionDefinition, g as RpcFunctionAgentOptions, i as RpcArgsSchema } from "./types-Djq7CBXh.mjs";

@@ -1,7 +0,7 @@

import { At as DevframeAgentHost$1, Ct as AgentResource, Dt as AgentToolInput, Et as AgentTool, Mt as EventEmitter, Ot as AgentToolProvider, Q as DevframeHost, St as AgentManifest, Tt as AgentResourceInput, g as DevframeNodeContext, jt as DevframeAgentHostEvents, kt as AgentToolProviderHandle, wt as AgentResourceContent, xt as AgentHandle } from "../devframe-BlLEZR-x.mjs";
import { At as DevframeAgentHost$1, Ct as AgentResource, Dt as AgentToolInput, Et as AgentTool, Mt as EventEmitter, Ot as AgentToolProvider, Q as DevframeHost, St as AgentManifest, Tt as AgentResourceInput, g as DevframeNodeContext, jt as DevframeAgentHostEvents, kt as AgentToolProviderHandle, wt as AgentResourceContent, xt as AgentHandle } from "../devframe-mbfgpQQC.mjs";
import { v as RpcFunctionDefinitionAny } from "../types-Djq7CBXh.mjs";
import { a as RemoteAssetsStore } from "../remote-assets-Bg4gCUZ_.mjs";
import { a as InstanceShellInit, c as StartedServer, d as samePath, f as DevframeInstanceRecord, h as registerDevframeInstance, i as InstanceShellApi, l as createInstanceShell, m as listLiveDevframeInstances, n as InstanceRegisterConfig, o as InstanceShellInternals, p as DevframeInstanceRegistration, r as InstanceShell, s as InstanceWsTier, t as CreateInstanceShellOptions, u as resolveInstanceRegister } from "../instance-shell-0iw-cQ5V.mjs";
import { n as normalizeBasePath, r as resolveBasePath } from "../_shared-DqbKCAew.mjs";
import { n as CreateContextRpcServerOptions, r as createContextRpcServer, t as ContextRpcServer } from "../rpc-core-CR8o34O9.mjs";
import { a as InstanceShellInit, c as StartedServer, d as samePath, f as DevframeInstanceRecord, h as registerDevframeInstance, i as InstanceShellApi, l as createInstanceShell, m as listLiveDevframeInstances, n as InstanceRegisterConfig, o as InstanceShellInternals, p as DevframeInstanceRegistration, r as InstanceShell, s as InstanceWsTier, t as CreateInstanceShellOptions, u as resolveInstanceRegister } from "../instance-shell-mU5j5xWn.mjs";
import { n as normalizeBasePath, r as resolveBasePath } from "../_shared-BAGBgTUO.mjs";
import { n as CreateContextRpcServerOptions, r as createContextRpcServer, t as ContextRpcServer } from "../rpc-core-dV69u4lY.mjs";
//#region src/node/agent-args.d.ts

@@ -319,8 +319,2 @@ /**

};
readonly DF0071: {
readonly why: (p: {
reason: string;
}) => string;
readonly fix: "Call `ctx.services.ready()` explicitly after every devframe's setup has run (the first-party adapters do) so installation errors surface at startup instead of at connect time.";
};
}, readonly [(d: import("nostics").Diagnostic, { method }?: {

@@ -327,0 +321,0 @@ method?: "log" | "warn" | "error";

import { n as peekRpcWireFrame, t as createRpcWireCodec } from "../wire-codec-0K-o5MYW.mjs";
import { t as diagnostics } from "../diagnostics-Di8ytitn.mjs";
import { n as coerceAgentPositionalArgs, t as DevframeAgentHost } from "../host-agent-BDdcOXhG.mjs";
import { t as diagnostics } from "../diagnostics-DI1HGj2I.mjs";
import { n as coerceAgentPositionalArgs, t as DevframeAgentHost } from "../host-agent-DVDLqjvj.mjs";
import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-BM3PdYli.mjs";
import { t as createH3DevframeHost } from "../host-h3-fRbF9yor.mjs";
import { i as registerDevframeInstance, n as listLiveDevframeInstances } from "../instance-registry-CHunhMi5.mjs";
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, r as samePath, t as createInstanceShell } from "../instance-shell-DbDoozNM.mjs";
import { t as createContextRpcServer } from "../rpc-core-Dru9uoM0.mjs";
import { i as registerDevframeInstance, n as listLiveDevframeInstances } from "../instance-registry-Dy28drtm.mjs";
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, r as samePath, t as createInstanceShell } from "../instance-shell-GqTY93gC.mjs";
import { t as createContextRpcServer } from "../rpc-core-DRYnbxdD.mjs";
export { DevframeAgentHost, coerceAgentPositionalArgs, createContextRpcServer, createH3DevframeHost, createInstanceShell, createRpcWireCodec, diagnostics, listLiveDevframeInstances, normalizeBasePath, normalizeHttpServerUrl, peekRpcWireFrame, registerDevframeInstance, resolveBasePath, resolveInstanceRegister, samePath };

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

import { u as DevframeAuthHandler } from "../devframe-BlLEZR-x.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-BKFT9-jA.mjs";
import { u as DevframeAuthHandler } from "../devframe-mbfgpQQC.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-Dipgo9ji.mjs";
export { DevframeAuthHandler, buildOtpAuthUrl, exchangeTempAuthCode, getTempAuthCode, refreshTempAuthCode, revokeActiveConnectionsForToken, revokeAuthToken, verifyAuthToken };

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

import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-dqyXS6o3.mjs";
import { n as normalizeBasePath, r as resolveBasePath } from "../_shared-DqbKCAew.mjs";
import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-al99DDJ8.mjs";
import { n as normalizeBasePath, r as resolveBasePath } from "../_shared-BAGBgTUO.mjs";
export { type DevframeInternalContext, type InternalAnonymousAuthStorage, type RemoteTokenRecord, getInternalContext, internalContextMap, normalizeBasePath, resolveBasePath };
import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-BM3PdYli.mjs";
import { n as internalContextMap, t as getInternalContext } from "../context-CXhjygJU.mjs";
import { n as internalContextMap, t as getInternalContext } from "../context-af2w_F0_.mjs";
export { getInternalContext, internalContextMap, normalizeBasePath, resolveBasePath };

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

import { Q as DevframeHost, g as DevframeNodeContext, gt as SharedState, y as RpcFunctionsHost } from "../devframe-BlLEZR-x.mjs";
import { Q as DevframeHost, g as DevframeNodeContext, gt as SharedState, y as RpcFunctionsHost } from "../devframe-mbfgpQQC.mjs";
import "../index-tdrk1bzV.mjs";

@@ -12,2 +12,11 @@ import { v as RpcFunctionDefinitionAny } from "../types-Djq7CBXh.mjs";

/**
* `import.meta.url` of the module that defines the devframe this context
* serves (from `DevframeDefinition.importMetaUrl`). Supplies the default
* `resolveFrom` base for remote {@link DevframeViewHost.hostStatic} sources
* that don't set one, so a locally installed copy of an assets package is
* served with zero network. An internal plumbing detail — it isn't part of
* the public {@link DevframeNodeContext} surface.
*/
importMetaUrl?: string;
/**
* Built-in RPC declarations to register on the host. Framework

@@ -14,0 +23,0 @@ * adapters (vite, rolldown, cli) can pass the ones they need; the

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

import { t as createHostContext } from "../context-Bdkakk8S.mjs";
import { t as createStorage } from "../storage-D4a99Ung.mjs";
import { t as createHostContext } from "../context-riPPHGiu.mjs";
import { t as createStorage } from "../storage-BNqSAOxA.mjs";
export { createHostContext, createStorage };

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

import "../devframe-BlLEZR-x.mjs";
import "../devframe-mbfgpQQC.mjs";
import "../index-tdrk1bzV.mjs";

@@ -36,2 +36,6 @@ import { E as Thenable, S as RpcFunctionSetupResult, c as RpcDump, g as RpcFunctionAgentOptions } from "../types-Djq7CBXh.mjs";

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead — one
* host-level installation shared by every plugin and feature-detectable from
* clients, with workspace-root path containment on top of the editor gating.
*/

@@ -63,2 +67,6 @@ declare const openInEditor: {

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead — one
* host-level installation shared by every plugin and feature-detectable from
* clients, with workspace-root path containment.
*/

@@ -89,2 +97,4 @@ declare const openInFinder: {

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead.
*/

@@ -91,0 +101,0 @@ declare const commonRpcFunctions: readonly [{

@@ -59,2 +59,6 @@ import { n as defineRpcFunction } from "../define-BLWPsH6y.mjs";

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead — one
* host-level installation shared by every plugin and feature-detectable from
* clients, with workspace-root path containment on top of the editor gating.
*/

@@ -82,2 +86,6 @@ const openInEditor = defineRpcFunction({

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead — one
* host-level installation shared by every plugin and feature-detectable from
* clients, with workspace-root path containment.
*/

@@ -104,2 +112,4 @@ const openInFinder = defineRpcFunction({

* ```
*
* @deprecated Use the `@devframes/service-open` wire service instead.
*/

@@ -106,0 +116,0 @@ const commonRpcFunctions = [openInEditor, openInFinder];

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

import { g as DevframeNodeContext, u as DevframeAuthHandler } from "../devframe-BlLEZR-x.mjs";
import "../index-BKFT9-jA.mjs";
import { g as DevframeNodeContext, u as DevframeAuthHandler } from "../devframe-mbfgpQQC.mjs";
import "../index-Dipgo9ji.mjs";
//#region src/recipes/interactive-auth.d.ts

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

@@ -5,3 +5,3 @@ import { s as colors } from "../nostics-CzECRXpE.mjs";

import { a as verifyAuthToken, n as exchangeTempAuthCode, r as getTempAuthCode, t as buildOtpAuthUrl } from "../state-CK9LjrnT.mjs";
import { t as getInternalContext } from "../context-CXhjygJU.mjs";
import { t as getInternalContext } from "../context-af2w_F0_.mjs";
import { t as s } from "../simple-schema-DQPZrAaZ.mjs";

@@ -8,0 +8,0 @@ //#region src/recipes/interactive-auth.ts

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

import { a as reviveDumpError, i as getDefinitionsWithDumps, n as createClientFromDump, o as serializeDumpError, r as dumpFunctions, t as collectStaticRpcDump } from "../static-BPZ1_OPA.mjs";
import { a as reviveDumpError, i as getDefinitionsWithDumps, n as createClientFromDump, o as serializeDumpError, r as dumpFunctions, t as collectStaticRpcDump } from "../static-DV_qUSRS.mjs";
export { collectStaticRpcDump, createClientFromDump, dumpFunctions, getDefinitionsWithDumps, reviveDumpError, serializeDumpError };

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

import { a as validateRpcArgs, i as getRpcResolvedSetupResult, n as validateDefinitions, o as validateRpcReturn, r as getRpcHandler, s as hash, t as validateDefinition } from "../validation-CpXFB6Dz.mjs";
import { a as validateRpcArgs, i as getRpcResolvedSetupResult, n as validateDefinitions, o as validateRpcReturn, r as getRpcHandler, s as hash, t as validateDefinition } from "../validation-YrZH6atx.mjs";
import { t as diagnostics } from "../diagnostics-CD8nlgll.mjs";

@@ -3,0 +3,0 @@ import { n as defineRpcFunction, t as createDefineWrapperWithContext } from "../define-BLWPsH6y.mjs";

import { n as WsOriginRegistry } from "../../ws-server-D1d3QM9f.mjs";
import { t as ContextRpcServer } from "../../rpc-core-CR8o34O9.mjs";
import { t as ContextRpcServer } from "../../rpc-core-dV69u4lY.mjs";
//#region src/rpc/transports/ws-bun.d.ts

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

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

import { $ as DevframeStorageScope, A as DevframeServiceId, At as DevframeAgentHost, B as DevframeScopedNodeRpc, C as RpcStreamingChannelOptions, Ct as AgentResource, D as DevframeRpcSharedStates, Dt as AgentToolInput, E as DevframeRpcServerFunctions, Et as AgentTool, F as DevframeServicesHost, G as ScopedBroadcastOptions, H as DevframeSettings, I as DevframeServicesRegistry, J as ScopedServerFunctions, K as ScopedClientFunctions, L as DevframeServicesScopeRegistry, M as DevframeServiceMeta, Mt as EventEmitter, N as DevframeServiceOf, Nt as EventUnsubscribe, O as DevframeServiceDefinition, Ot as AgentToolProvider, P as DevframeServiceScopeOf, Pt as EventsMap, Q as DevframeHost, R as DevframeServicesState, S as RpcStreamingChannel, St as AgentManifest, T as DevframeRpcClientFunctions, Tt as AgentResourceInput, U as DevframeSettingsRegistry, V as DevframeScopedStreamingHost, W as DevframeSettingsStore, X as SettingsForNamespace, Y as ScopedSharedStates, Z as DevframeViewHost, _ as DevframeNodeRpcSession, a as DevframeDuplicationStrategy, b as RpcSharedStateGetOptions, c as DevframeWsOptions, d as ConnectionMeta, et as DevframeDiagnosticsHost, f as ConnectionMetaSse, g as DevframeNodeContext, h as DevframeConnectionConfigsRegistry, i as DevframeDockDefaults, j as DevframeServiceInput, jt as DevframeAgentHostEvents, k as DevframeServiceDescriptor, kt as AgentToolProviderHandle, l as McpRouteOptions, m as DevframeCapabilities, n as DevframeDefinition, o as DevframeSetupInfo, p as ConnectionMetaWebsocket, q as ScopedRpcFn, r as DevframeDeploymentKind, s as DevframeSseOptions, t as DevframeCliOptions, tt as DevframeDiagnosticsLogger, v as RpcBroadcastOptions, w as RpcStreamingHost, wt as AgentResourceContent, x as RpcSharedStateHost, xt as AgentHandle, y as RpcFunctionsHost, z as DevframeScopedNodeContext } from "../devframe-BlLEZR-x.mjs";
import { $ as DevframeStorageScope, A as DevframeServiceId, At as DevframeAgentHost, B as DevframeScopedNodeRpc, C as RpcStreamingChannelOptions, Ct as AgentResource, D as DevframeRpcSharedStates, Dt as AgentToolInput, E as DevframeRpcServerFunctions, Et as AgentTool, F as DevframeServicesHost, G as ScopedBroadcastOptions, H as DevframeSettings, I as DevframeServicesRegistry, J as ScopedServerFunctions, K as ScopedClientFunctions, L as DevframeServicesScopeRegistry, M as DevframeServiceMeta, Mt as EventEmitter, N as DevframeServiceOf, Nt as EventUnsubscribe, O as DevframeServiceDefinition, Ot as AgentToolProvider, P as DevframeServiceScopeOf, Pt as EventsMap, Q as DevframeHost, R as DevframeServicesState, S as RpcStreamingChannel, St as AgentManifest, T as DevframeRpcClientFunctions, Tt as AgentResourceInput, U as DevframeSettingsRegistry, V as DevframeScopedStreamingHost, W as DevframeSettingsStore, X as SettingsForNamespace, Y as ScopedSharedStates, Z as DevframeViewHost, _ as DevframeNodeRpcSession, a as DevframeDuplicationStrategy, b as RpcSharedStateGetOptions, c as DevframeWsOptions, d as ConnectionMeta, et as DevframeDiagnosticsHost, f as ConnectionMetaSse, g as DevframeNodeContext, h as DevframeConnectionConfigsRegistry, i as DevframeDockDefaults, j as DevframeServiceInput, jt as DevframeAgentHostEvents, k as DevframeServiceDescriptor, kt as AgentToolProviderHandle, l as McpRouteOptions, m as DevframeCapabilities, n as DevframeDefinition, o as DevframeSetupInfo, p as ConnectionMetaWebsocket, q as ScopedRpcFn, r as DevframeDeploymentKind, s as DevframeSseOptions, t as DevframeCliOptions, tt as DevframeDiagnosticsLogger, v as RpcBroadcastOptions, w as RpcStreamingHost, wt as AgentResourceContent, x as RpcSharedStateHost, xt as AgentHandle, y as RpcFunctionsHost, z as DevframeScopedNodeContext } from "../devframe-mbfgpQQC.mjs";
import { g as RpcFunctionAgentOptions } from "../types-Djq7CBXh.mjs";

@@ -3,0 +3,0 @@ import { d as DevframeRpcConnection, f as DevframeRpcConnectionRequest, p as DevframeRpcTransportKind, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";

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

import { Mt as EventEmitter, Pt as EventsMap } from "../devframe-BlLEZR-x.mjs";
import { Mt as EventEmitter, Pt as EventsMap } from "../devframe-mbfgpQQC.mjs";
//#region src/utils/events.d.ts

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

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

import { t as hash } from "../hash-KtDZYXDN.mjs";
import { t as hash } from "../hash-DdPnc4k3.mjs";
export { hash };

@@ -12,5 +12,11 @@ import { a as RemoteAssetsStore, o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";

* are interpolated into CDN URLs and the cache path.
*
* `defaultResolveFrom` (typically the declaring devframe's `importMetaUrl`)
* supplies a `resolveFrom` base for a remote source that doesn't set one:
* it is applied only when `source.resolveFrom` is `undefined`, so an explicit
* per-source string still wins and an explicit `null` still opts out of the
* installed-copy lookup.
*/
declare function resolveStaticAssetsSource(source: StaticAssetsSource, projectStorageDir: string): string | RemoteAssetsStore;
declare function resolveStaticAssetsSource(source: StaticAssetsSource, projectStorageDir: string, defaultResolveFrom?: string | null): string | RemoteAssetsStore;
//#endregion
export { resolveStaticAssetsSource };

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

import { t as resolveStaticAssetsSource } from "../remote-assets-DRfY2RjX.mjs";
import { t as resolveStaticAssetsSource } from "../remote-assets-CqxiyltC.mjs";
export { resolveStaticAssetsSource };

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

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

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

import { t as createSharedState } from "../shared-state-fRRbRUtD.mjs";
import { t as createSharedState } from "../shared-state-DoXutg_U.mjs";
export { createSharedState };

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

import { at as StreamErrorPayload, ct as StreamSinkEvents, it as CreateStreamSinkOptions, lt as createStreamReader, nt as BufferedChunk, ot as StreamReader, rt as CreateStreamReaderOptions, st as StreamSink, ut as createStreamSink } from "../devframe-BlLEZR-x.mjs";
import { at as StreamErrorPayload, ct as StreamSinkEvents, it as CreateStreamSinkOptions, lt as createStreamReader, nt as BufferedChunk, ot as StreamReader, rt as CreateStreamReaderOptions, st as StreamSink, ut as createStreamSink } from "../devframe-mbfgpQQC.mjs";
export { BufferedChunk, CreateStreamReaderOptions, CreateStreamSinkOptions, StreamErrorPayload, StreamReader, StreamSink, StreamSinkEvents, createStreamReader, createStreamSink };
{
"name": "devframe",
"type": "module",
"version": "0.9.1",
"version": "0.9.2",
"description": "Framework for building one portable devtool integration that runs in any viewer.",

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

"./rpc/transports/ws-bun": "./dist/rpc/transports/ws-bun.mjs",
"./rpc/transports/ws-deno": "./dist/rpc/transports/ws-deno.mjs",
"./rpc/transports/ws-client": "./dist/rpc/transports/ws-client.mjs",

@@ -107,7 +108,7 @@ "./rpc/transports/ws-server": "./dist/rpc/transports/ws-server.mjs",

"get-port-please": "^3.2.0",
"immer": "^11.1.16",
"immer": "^11.1.17",
"launch-editor": "^2.14.1",
"mlly": "^1.8.2",
"obug": "^2.1.4",
"ohash": "^2.0.11",
"ohash": "^2.0.12",
"p-limit": "^7.3.1",

@@ -114,0 +115,0 @@ "perfect-debounce": "^2.1.0",

@@ -561,3 +561,3 @@ ---

For "open file in editor" + "reveal in finder", prefer the prebuilt `commonRpcFunctions` RPC recipe (`devframe/recipes/common-rpc-functions`) - it wires the two utilities into named RPC functions ready to register.
For "open file in editor" + "reveal in finder", prefer the `@devframes/service-open` wire service (declare `services: [{ package: '@devframes/service-open' }]` on the definition, gate client UI on `rpc.services.has(...)`) - one host-level installation shared by every plugin, with workspace-root path containment. The older `commonRpcFunctions` recipe (`devframe/recipes/common-rpc-functions`) still works but is deprecated.

@@ -564,0 +564,0 @@ ## Security (secure by default)

import { d as ConnectionMeta, l as McpRouteOptions, n as DevframeDefinition, r as DevframeDeploymentKind } from "./devframe-BlLEZR-x.mjs";
//#region src/adapters/_shared.d.ts
/**
* Resolve the mount base path for a devframe's SPA. Hosted adapters
* (`vite`, `embedded`) default to `/__<id>/` so they don't collide
* with the host app; standalone adapters (`cli`, `build`)
* default to `/` because they own the origin.
*
* The devframe author can override with `basePath` on the definition.
*/
declare function resolveBasePath(def: DevframeDefinition, kind: DevframeDeploymentKind): string;
declare function normalizeBasePath(base: string): string;
interface ResolveDevServerPortOptions {
/** Bind host (passed to `get-port-please` for in-use detection). */
host?: string;
/** Override the preferred port. Default: `def.cli?.port ?? 9999`. */
defaultPort?: number;
}
/**
* Resolve the listening port for `createDevServer` (and `createHandler`'s
* side-car tiers), 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.
*/
declare function resolveDevServerPort(def: DevframeDefinition, options?: ResolveDevServerPortOptions): Promise<number>;
/**
* 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 `createDevServer`), or `undefined` when the route is
* disabled.
*
* Hosted bridges that hand-roll their connection meta 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).
*/
declare function resolveMcpConnectionMeta(def: DevframeDefinition, mcp: boolean | McpRouteOptions | undefined, port?: number): ConnectionMeta['mcp'];
//#endregion
export { resolveMcpConnectionMeta as a, resolveDevServerPort as i, normalizeBasePath as n, resolveBasePath as r, ResolveDevServerPortOptions as t };
import { i as defineDiagnostics } from "./nostics-CzECRXpE.mjs";
import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
import { defineRpcFunction } from "./index.mjs";
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { DEVFRAME_SERVICES_STATE_KEY } from "./constants.mjs";
import { t as diagnostics$1 } from "./diagnostics-Di8ytitn.mjs";
import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-BDdcOXhG.mjs";
import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-DRfY2RjX.mjs";
import { t as createStorage } from "./storage-D4a99Ung.mjs";
import { createRequire } from "node:module";
import { createSharedState } from "devframe/utils/shared-state";
import process from "node:process";
import { pathToFileURL } from "node:url";
import { isAbsolute, join } from "pathe";
import { existsSync } from "node:fs";
//#region src/node/host-diagnostics.ts
var DevframeDiagnosticsHost = class {
context;
_registry = {};
logger = new Proxy({}, { get: (_, code) => this._registry[code] });
defineDiagnostics = defineDiagnostics;
constructor(context, initialDefinitions = []) {
this.context = context;
for (const d of initialDefinitions) this.register(d);
}
register(diagnostics) {
Object.assign(this._registry, diagnostics);
}
};
//#endregion
//#region src/node/rpc-shared-state.ts
const debug$2 = 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$2("patch", {
key,
syncId
});
rpc.broadcast({
method: DEVFRAME_EVENTS.broadcast.clientStatePatch,
args: [
key,
patches,
syncId
],
filter: (client) => client.$meta.subscribedStates.has(key)
});
} else {
debug$2("updated", {
key,
syncId
});
rpc.broadcast({
method: DEVFRAME_EVENTS.broadcast.clientStateUpdated,
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$2("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/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/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$1 = 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$1("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_EVENTS.broadcast.streamingChunk,
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_EVENTS.broadcast.streamingEnd,
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_EVENTS.broadcast.streamingChunk,
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_EVENTS.broadcast.streamingEnd,
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_EVENTS.broadcast.streamingUploadCancel,
args: [name, reader.id],
event: true,
optional: true,
filter: (client) => client.$meta === targetMeta
});
}
});
inboundRecord = { reader };
state.inbound.set(reader.id, inboundRecord);
debug$1("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 `createContextRpcServer` 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/services-install.ts
/**
* Turn a `resolveFrom` value (a file path, a file URL like `import.meta.url`,
* or a directory) into something `createRequire` accepts — a directory gets a
* synthetic filename appended so resolution starts inside it.
*/
function toRequireBase(resolveFrom) {
if (resolveFrom.startsWith("file://")) return resolveFrom;
if ((resolveFrom.split(/[/\\]/).pop() ?? "").includes(".")) return resolveFrom;
return join(resolveFrom, "_devframe_resolve.js");
}
/**
* Normalize an `install()` `resolveFrom` into a resolution base. Paths and
* file URLs pass through; a bare npm package name (the common case: the
* declaring plugin's `packageName`) resolves to that package's location from
* `cwd`, so a service it declares resolves against the plugin's own
* dependencies. An unresolvable package name reads as no base (the caller's
* workspace fallbacks apply).
*/
function expandResolveFrom(resolveFrom, cwd) {
if (resolveFrom.startsWith("file://") || resolveFrom.startsWith(".") || isAbsolute(resolveFrom)) return resolveFrom;
const require = createRequire(join(cwd, "_devframe_resolve.js"));
try {
return require.resolve(`${resolveFrom}/package.json`);
} catch {}
try {
return require.resolve(resolveFrom);
} catch {}
}
/**
* Import a service package's module, trying each `resolveFrom` candidate in
* order (so a plugin-declared service resolves against the plugin's own
* dependency tree first, then the workspace fallback). Throws the last
* resolution error when no candidate succeeds.
*/
async function importServicePackage(pkg, resolveFroms) {
const candidates = [...new Set(resolveFroms.filter((x) => typeof x === "string" && x.length > 0))];
let lastError = /* @__PURE__ */ new Error(`no resolution base available for "${pkg}"`);
for (const from of candidates) {
let resolved;
try {
resolved = createRequire(toRequireBase(from)).resolve(pkg);
} catch (error) {
lastError = error;
continue;
}
return await import(pathToFileURL(resolved).href);
}
throw lastError;
}
function parseVersion(input) {
const [core, ...prerelease] = input.trim().replace(/^v/, "").split("-");
if (!core) return void 0;
const parts = core.split(".").map((part) => Number.parseInt(part, 10));
if (parts.length === 0 || parts.some((part) => Number.isNaN(part) || part < 0)) return void 0;
while (parts.length < 3) parts.push(0);
return {
parts,
...prerelease.length ? { prerelease: prerelease.join("-") } : {}
};
}
function compareVersions(a, b) {
for (let i = 0; i < 3; i++) {
const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);
if (diff !== 0) return diff;
}
if (a.prerelease && !b.prerelease) return -1;
if (!a.prerelease && b.prerelease) return 1;
if (a.prerelease && b.prerelease) return a.prerelease < b.prerelease ? -1 : a.prerelease > b.prerelease ? 1 : 0;
return 0;
}
function satisfiesComparator(version, comparator) {
const raw = comparator.trim();
if (!raw || raw === "*" || raw === "x") return true;
const operatorMatch = raw.match(/^([\^~]|>=|<=|[><=])?(.+)$/);
if (!operatorMatch) return false;
const operator = operatorMatch[1];
const rest = operatorMatch[2].trim();
const segments = rest.replace(/\.[x*]/gi, "").split(".").filter(Boolean);
const base = parseVersion(rest.replace(/[x*]/gi, "0"));
if (!base) return false;
switch (operator) {
case ">": return compareVersions(version, base) > 0;
case ">=": return compareVersions(version, base) >= 0;
case "<": return compareVersions(version, base) < 0;
case "<=": return compareVersions(version, base) <= 0;
case "^": {
if (compareVersions(version, base) < 0) return false;
const fixedIndex = base.parts.findIndex((part) => part !== 0);
const lockUpTo = fixedIndex === -1 ? base.parts.length - 1 : fixedIndex;
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return true;
}
case "~": {
if (compareVersions(version, base) < 0) return false;
const lockUpTo = segments.length >= 2 ? 1 : 0;
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return true;
}
default:
for (let i = 0; i < Math.max(segments.length, 3); i++) if (i < segments.length && (version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
return segments.length >= 3 ? compareVersions(version, base) === 0 : true;
}
}
/**
* Pragmatic semver range check for service version declarations — supports
* the common forms (`1.2.3`, `^1.2.3`, `~1.2`, `>=1 <3`, `1.x`, `*`, and
* `||`-joined alternatives) without pulling in a semver dependency. An
* unparseable version or range reads as **not satisfied**.
*/
function satisfiesVersionRange(version, range) {
const parsed = parseVersion(version);
if (!parsed) return false;
const alternatives = range.split("||").map((alt) => alt.trim()).filter(Boolean);
if (alternatives.length === 0) return true;
return alternatives.some((alternative) => alternative.split(/\s+/).every((comparator) => satisfiesComparator(parsed, comparator)));
}
/**
* Default option-set merge when a service declares no `mergeOptions`:
* shallow-merge plain objects in declaration order (later sets win); any
* non-object set collapses the merge to "last one wins".
*/
function shallowMergeOptionSets(sets) {
if (sets.some((set) => typeof set !== "object" || set === null || Array.isArray(set))) return sets[sets.length - 1];
return Object.assign({}, ...sets);
}
//#endregion
//#region src/node/host-services.ts
const debug = createDebug("devframe:services");
function isServiceDefinition(input) {
return typeof input.setup === "function";
}
function validateServiceInput(input) {
if (!input || typeof input.package !== "string" || input.package.length === 0) throw diagnostics$1.DF0070({
package: String(input?.package ?? input),
reason: "the input has no `package` name"
});
if (isServiceDefinition(input)) validateServiceDefinition(input);
}
function validateServiceDefinition(def) {
if (typeof def.version !== "string" || def.version.length === 0) throw diagnostics$1.DF0070({
package: def.package,
reason: "the definition has no `version`"
});
if (typeof def.scope !== "string" || def.scope.length === 0) throw diagnostics$1.DF0070({
package: def.package,
reason: "the definition has no RPC `scope` namespace"
});
}
/**
* 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.
*
* On top of the in-process `provide`/`get` tier, this host implements the
* **wire-service** lifecycle: `install()` queues definitions/descriptors,
* `ready()` fires the collect-then-setup barrier — importing descriptor
* packages, merging option sets per service, constructing each service once,
* providing its node API under the package name, and advertising it to
* clients through the `devframe:services` shared state.
*/
var DevframeServicesHostImpl = class {
context;
services = /* @__PURE__ */ new Map();
listeners = /* @__PURE__ */ new Map();
pending = /* @__PURE__ */ new Map();
installed = /* @__PURE__ */ new Map();
readyPromise;
constructor(context) {
this.context = context;
}
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());
}
install(input, options) {
validateServiceInput(input);
const promise = new Promise((resolve, reject) => {
const entry = {
input,
resolveFrom: options?.resolveFrom,
resolve,
reject
};
if (this.readyPromise) this.flushPackage(input.package, [entry]).catch(() => {});
else {
let entries = this.pending.get(input.package);
if (!entries) {
entries = [];
this.pending.set(input.package, entries);
}
entries.push(entry);
}
});
promise.catch(() => {});
return promise;
}
ready() {
if (this.readyPromise) return this.readyPromise;
this.readyPromise = this.flushAll();
return this.readyPromise;
}
async flushAll() {
if (this.context) await this.advertisementState();
const groups = Array.from(this.pending.entries());
this.pending.clear();
for (const [pkg, entries] of groups) await this.flushPackage(pkg, entries);
}
async flushPackage(pkg, entries) {
try {
const api = await this.installPackage(pkg, entries);
for (const entry of entries) entry.resolve(api);
return api;
} catch (error) {
for (const entry of entries) entry.reject(error);
throw error;
}
}
async installPackage(pkg, entries) {
if (this.installed.has(pkg)) {
diagnostics$1.DF0066({ package: pkg });
return this.installed.get(pkg);
}
let def = entries.filter((entry) => isServiceDefinition(entry.input))[0]?.input;
if (!def) {
const required = entries.map((entry) => entry.input).some((descriptor) => descriptor.required === true);
const cwd = this.context?.cwd ?? process.cwd();
const resolveFroms = [
...entries.map((entry) => entry.resolveFrom && expandResolveFrom(entry.resolveFrom, cwd)),
this.context?.workspaceRoot,
cwd
];
let mod;
try {
mod = await importServicePackage(pkg, resolveFroms);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
if (required) throw diagnostics$1.DF0067({
package: pkg,
reason,
cause: error
});
debug("optional service %s not importable, skipping: %s", pkg, reason);
return;
}
const factory = mod.default;
if (typeof factory !== "function") throw diagnostics$1.DF0070({
package: pkg,
reason: "its default export is not a factory function"
});
def = await factory();
if (!def || typeof def.setup !== "function") throw diagnostics$1.DF0070({
package: pkg,
reason: "its factory did not return a definition with a `setup` function"
});
if (typeof def.package !== "string" || def.package.length === 0) def = {
...def,
package: pkg
};
validateServiceDefinition(def);
}
for (const entry of entries) {
const descriptor = entry.input;
if (isServiceDefinition(entry.input) || typeof descriptor.version !== "string") continue;
if (satisfiesVersionRange(def.version, descriptor.version)) continue;
if (descriptor.required === true) throw diagnostics$1.DF0068({
package: pkg,
required: descriptor.version,
installed: def.version
});
diagnostics$1.DF0069({
package: pkg,
required: descriptor.version,
installed: def.version
});
}
const sets = entries.map((entry) => entry.input.options).filter((options) => options !== void 0);
const options = def.mergeOptions ? def.mergeOptions(sets) : sets.length > 0 ? shallowMergeOptionSets(sets) : void 0;
if (!this.context) throw diagnostics$1.DF0070({
package: pkg,
reason: "this services host has no node context to install into"
});
debug("installing service %s@%s (scope %s)", def.package, def.version, def.scope);
const scoped = this.context.scope(def.scope);
const api = await def.setup(scoped, options === void 0 ? {} : { options });
this.installed.set(def.package, api);
this.provide(def.package, api);
await this.advertise(def);
return api;
}
advertisementState() {
return this.context.rpc.sharedState.get(DEVFRAME_SERVICES_STATE_KEY, { initialValue: {} });
}
async advertise(def) {
const state = await this.advertisementState();
const { package: pkg, version, scope, meta } = def;
state.mutate((value) => {
value[pkg] = {
package: pkg,
version,
scope,
...meta ? { meta } : {}
};
});
}
};
//#endregion
//#region src/node/host-views.ts
var DevframeViewHost = class {
context;
/**
* @internal
*/
buildStaticDirs = [];
constructor(context) {
this.context = context;
}
hostStatic(baseUrl, source) {
const resolved = resolveStaticAssetsSource(source, this.context.host.getStorageDir("project"));
if (typeof resolved === "string" && !existsSync(resolved)) throw diagnostics$1.DF0008({ distDir: resolved });
this.buildStaticDirs.push({
baseUrl,
source
});
this.context.host.mountStatic(baseUrl, resolved);
}
};
//#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.
*/
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 dir = context.host.getStorageDir(STORAGE_SCOPE[scope]);
const filepath = join(dir, "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,
staticConfig: {},
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);
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
export { createHostContext as t };
import { n as randomToken } from "./crypto-token-XCqTSMg9.mjs";
import { t as createStorage } from "./storage-D4a99Ung.mjs";
import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-3Q6aDKWk.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();
const wsEndpointListeners = /* @__PURE__ */ new Set();
function revokeRemoteToken(token) {
if (!remoteTokens.delete(token)) return;
revokeActiveConnectionsForToken(context, token);
}
const internalContext = {
storage: { auth: storage },
revokeAuthToken: (token) => revokeAuthToken(context, storage, token),
setWsEndpoint(endpoint) {
internalContext.wsEndpoint = endpoint;
for (const listener of wsEndpointListeners) listener();
},
onWsEndpointChange(cb) {
wsEndpointListeners.add(cb);
return () => wsEndpointListeners.delete(cb);
},
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 { g as DevframeNodeContext, gt as SharedState } from "./devframe-BlLEZR-x.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;
};
/**
* Set {@link DevframeInternalContext.wsEndpoint} and notify subscribers —
* the WS-binding tiers (side-car, shared-server, and the `unbound` tier's
* `attach()`) call this once the socket is bound (or `undefined` once torn
* down) instead of assigning the field directly, so anything that already
* projected the endpoint (a hub's remote-dock URLs, registered before an
* async bind resolves) gets a chance to re-project it.
*/
setWsEndpoint: (endpoint: {
url: string;
} | undefined) => void;
/**
* Subscribe to every {@link DevframeInternalContext.setWsEndpoint} call.
* Returns an unsubscribe function. The hub context uses this to refresh
* the `devframe:docks` shared state so a remote dock registered before the
* WS port resolves still ends up with a live connection URL.
*/
onWsEndpointChange: (cb: () => void) => () => void;
}
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 { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { t as createHostContext } from "./context-Bdkakk8S.mjs";
import { t as resolveStaticAssetsSource } from "./remote-assets-DRfY2RjX.mjs";
import { i as resolveMcpConnectionMeta, n as resolveBasePath, r as resolveDevServerPort, t as normalizeBasePath } from "./_shared-BM3PdYli.mjs";
import { t as createH3DevframeHost } from "./host-h3-fRbF9yor.mjs";
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-DbDoozNM.mjs";
import { open } from "./utils/open.mjs";
import { mountStaticHandler } from "./utils/serve-static.mjs";
import { createServer } from "node:http";
import process from "node:process";
import { resolve } from "pathe";
import { joinURL, withBase } from "ufo";
import { H3, toNodeHandler } from "h3";
//#region src/adapters/initiate.ts
const INSTANCE_INTERNALS = /* @__PURE__ */ new WeakMap();
/** @internal */
function getInstanceInternals(handler) {
return INSTANCE_INTERNALS.get(handler) ?? {};
}
/**
* Serve a devframe through one framework-agnostic, web-standard handler —
* the SPA, `__connection.json` discovery, the WebSocket RPC endpoint, the
* auth gate, and the optional MCP route, all under a single mount base.
* Mount `handler` on any framework's catch-all route (or `nodeMiddleware` on
* a connect stack) and the devframe is live inside that app.
*
* The factory is synchronous and kicks off initialization eagerly;
* `handler`/`nodeMiddleware` await readiness internally. Nothing binds a port
* on its own: the WebSocket resolves in precedence order — `ws.port` (pinned
* side-car) > `server` (shared upgrade at `<base>__ws`) > `ws.sidecar`
* (auto-port side-car) > the host driving upgrades itself through
* {@link DevframeInstance.attach} — while `ws.url`, when set, overrides the
* advertised* endpoint (the tunnel pattern) and on its own hands the whole
* transport to an external server. `__connection.json` reflects whichever
* combination is active.
*/
function initDevframe(def, options) {
const base = normalizeBasePath(options.base);
const distDir = options.distDir === false ? void 0 : options.distDir ?? def.cli?.distDir;
const app = options.app ?? new H3();
const host = options.host ?? def.cli?.host ?? "localhost";
const shell = createInstanceShell({
base,
app,
host,
origin: options.origin,
auth: options.auth !== void 0 ? options.auth : def.cli?.auth,
server: options.server,
ws: options.ws ?? def.cli?.ws,
sse: options.sse ?? def.cli?.sse,
allowedOrigins: options.allowedOrigins,
destroyUnmatchedUpgrades: options.destroyUnmatchedUpgrades,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect,
register: resolveInstanceRegister(options.register, {
id: def.id,
name: def.name
}),
resolveSidecarPort: (sidecarHost) => resolveDevServerPort(def, { host: sidecarHost }),
onMetaUnavailable: () => {
throw diagnostics.DF0054({ id: def.id });
},
async init(api) {
const h3Host = createH3DevframeHost({
origin: () => api.origin() ?? "http://localhost",
appName: def.id,
mount: (mountBase, dir) => {
mountStaticHandler(app, mountBase, dir);
}
});
const hostImpl = options.getStorageDir ? {
...h3Host,
getStorageDir: options.getStorageDir
} : h3Host;
const context = await createHostContext({
cwd: process.cwd(),
mode: "dev",
host: hostImpl
});
const setupInfo = { flags: options.flags ?? {} };
for (const input of def.services ?? []) context.services.install(input, { resolveFrom: def.packageName });
await def.setup(context, setupInfo);
await context.services.ready();
const mcpOption = options.mcp ?? def.cli?.mcp;
const mcpMeta = resolveMcpConnectionMeta(def, mcpOption);
let mcpDispose;
if (mcpMeta) {
const mcpConfig = mcpOption === true || mcpOption === void 0 ? {} : mcpOption;
const mcpPath = joinURL(base, mcpMeta.path);
let mountMcpHttp;
try {
({mountMcpHttp} = await import("./http-BbHCU5Zs.mjs").then((n) => n.t));
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw diagnostics.DF0017({
transport: "http",
reason,
cause: error
});
}
mcpDispose = mountMcpHttp(app, context, mcpPath, {
serverName: `${def.id} (devframe)`,
serverVersion: def.version ?? "0.0.0",
exposeSharedState: true,
allowedOrigins: mcpConfig.allowedOrigins
}).dispose;
}
return {
context,
...mcpMeta ? { mcp: mcpMeta } : {},
...mcpDispose ? { dispose: mcpDispose } : {}
};
},
mount(context, meta) {
app.use(joinURL(base, DEVFRAME_CONNECTION_META_FILENAME), () => meta);
if (distDir) {
const source = resolveStaticAssetsSource(distDir, context.host.getStorageDir("project"));
mountStaticHandler(app, base, typeof source === "string" ? resolve(source) : source);
}
}
});
const instance = {
base: shell.base,
handler: shell.handler,
nodeMiddleware: shell.nodeMiddleware,
attach: shell.attach,
handleUpgrade: shell.handleUpgrade,
ready: shell.ready,
context: shell.context,
connectionMeta: shell.connectionMeta,
close: shell.close
};
INSTANCE_INTERNALS.set(instance, shell.internals);
return instance;
}
//#endregion
//#region src/adapters/dev.ts
/**
* 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
* `devframeViteBridge` from `@devframes/vite`.
*
* 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 = {}) {
if (def.capabilities?.dev === false && !options.force) throw diagnostics.DF0058({ id: def.id });
const host = options.host ?? def.cli?.host ?? "localhost";
const requestedPort = 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 server = createServer(toNodeHandler(app));
try {
await new Promise((resolveListen, rejectListen) => {
const onError = (error) => rejectListen(error);
server.once("error", onError);
server.listen(requestedPort, host, () => {
server.removeListener("error", onError);
resolveListen();
});
});
} catch (error) {
throw diagnostics.DF0052({
host,
port: requestedPort,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
const address = server.address();
const port = typeof address === "object" && address ? address.port : requestedPort;
const origin = normalizeHttpServerUrl(host, port);
const devframe = initDevframe(def, {
base: basePath,
distDir: options.distDir,
app,
server,
host,
origin,
ws: options.ws,
allowedOrigins: options.allowedOrigins,
sse: options.sse,
auth: flags.auth === false ? false : options.auth,
mcp: options.mcp,
flags,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect,
register: true,
destroyUnmatchedUpgrades: true
});
try {
await devframe.ready;
} catch (error) {
await new Promise((resolveClose) => server.close(() => resolveClose()));
throw error;
}
const internals = getInstanceInternals(devframe);
const transport = internals.started;
await options.onReady?.({
origin,
port,
app
});
await maybeOpenBrowser(def, flags, `${origin}${basePath}`, options.openBrowser, internals.authHandler);
return {
origin,
port,
app,
ws: transport.ws,
rpcGroup: transport.rpcGroup,
connectionMeta: transport.connectionMeta,
async close() {
await devframe.close();
await new Promise((resolveClose) => server.close(() => resolveClose()));
}
};
}
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 { getInstanceInternals as n, initDevframe as r, createDevServer as t };

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

import { i as defineDiagnostics } from "./nostics-CzECRXpE.mjs";
//#region src/node/diagnostics.ts
const diagnostics = defineDiagnostics({
docsBase: "https://devfra.me/errors",
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 `port` on `devframeViteBridge` (`@devframes/vite`)."
},
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."
},
DF0052: {
why: (p) => `Failed to listen on ${p.host}:${p.port}: ${p.reason}`,
fix: "The port is likely already taken by another process (often a previous devframe instance). Free it, or pick another via `--port`, `cli.port` / `cli.portRange` on the definition, or `port` on `devframeViteBridge` (`@devframes/vite`). The original node error is available as `error.cause`."
},
DF0054: {
why: (p) => `connectionMeta() was called before initDevframe("${p.id}") finished initializing.`,
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()` — the WebSocket binding it describes is only known once initialization completes."
},
DF0055: {
why: (p) => `This instance already owns its WebSocket transport (${p.tier}), so it cannot take over the host's upgrade events.`,
fix: "Drop `handleUpgrade`/`attach` and let the configured transport serve the socket, or remove `server` / `ws.port` / `ws.sidecar` from the options so the instance leaves the binding to you."
},
DF0056: {
why: (p) => `This instance advertises an external WebSocket endpoint (${p.url}), so it serves no socket of its own.`,
fix: "The server behind `ws.url` owns the transport (and its auth). Drop `ws.url` to have the instance serve the socket, or pair it with `server` / `ws.port` / `ws.sidecar` for the tunnel pattern, where a local binding is advertised through the relay."
},
DF0057: {
why: () => "This instance disables its WebSocket transport (`ws: false`), so there is no socket to drive upgrades into.",
fix: "Clients connect over the SSE endpoint instead — no upgrade wiring is needed. Remove `ws: false` if the instance should serve a WebSocket after all."
},
DF0058: {
why: (p) => `"${p.id}" declares \`capabilities.dev: false\` — it does not support a live dev server (its value is a static export only).`,
fix: "Pass `{ force: true }` to `createDevServer()` to run it anyway, or drop `capabilities.dev: false` on the definition."
},
DF0059: {
why: (p) => `Failed to fetch the file listing for "${p.package}@${p.version}" from ${p.provider}: ${p.reason}`,
fix: "Requests fall back to probing the provider per file. Check network access to the provider, or install the assets package locally so no listing is needed."
},
DF0060: {
why: (p) => `Failed to fetch a remote asset of "${p.package}" (${p.url}): ${p.reason}`,
fix: "Install the assets package locally (`npm install <package>`) to serve it with zero network, or check network access to the configured provider."
},
DF0061: {
why: (p) => `The locally installed "${p.package}@${p.installed}" is a different major version than the required "${p.required}".`,
fix: "Align the installed assets package with the version its node package declares — they are published in lockstep."
},
DF0062: {
why: (p) => `The locally installed "${p.package}@${p.installed}" differs from the required "${p.required}" — serving the installed one.`,
fix: "Install the exact declared version to serve byte-identical assets."
},
DF0063: {
why: (p) => `Failed to persist a remote asset into the cache at "${p.filepath}": ${p.reason}`,
fix: "The response was still served; only caching failed. Check that the cache directory is writable and has free space."
},
DF0064: {
why: (p) => `Failed to materialize the remote assets of "${p.package}@${p.version}": ${p.reason}`,
fix: "Static builds need every asset file up front. Install the assets package locally, or ensure the provider (and its file-listing API) is reachable during the build."
},
DF0065: {
why: (p) => `Invalid remote-assets ${p.field} "${p.value}".`,
fix: "A remote-assets `package` must be a valid npm package name and `version` an exact semver version (e.g. `1.2.3`) — they are interpolated into CDN URLs and the cache path."
},
DF0066: {
why: (p) => `Service "${p.package}" is already installed — keeping the first installation and ignoring this one's options.`,
fix: "Option sets only merge before `ctx.services.ready()` fires. Install the service (or declare it in `DevframeDefinition.services`) before the barrier so its options join the merge."
},
DF0067: {
why: (p) => `Failed to import the required service package "${p.package}": ${p.reason}`,
fix: "Install the service package next to whoever declares it (a plugin declaring it in `services` should list it in its own dependencies), or drop `required: true` to degrade gracefully when it is absent."
},
DF0068: {
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the required range "${p.required}".`,
fix: "Align the installed service package with the range its declarer requires, or drop `required: true` to downgrade the mismatch to a warning."
},
DF0069: {
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the declared range "${p.required}" — installing it anyway.`,
fix: "The advertised meta carries the real version, so clients can gate on it. Align the installed service package with the declared range to silence this warning."
},
DF0070: {
why: (p) => `Invalid service "${p.package}": ${p.reason}`,
fix: "A service package's default export must be a factory returning a `DevframeServiceDefinition` — an object with `package`, `version`, `scope`, and a `setup` function."
},
DF0071: {
why: (p) => `Deferred service installation failed while flushing on the first client connection: ${p.reason}`,
fix: "Call `ctx.services.ready()` explicitly after every devframe's setup has run (the first-party adapters do) so installation errors surface at startup instead of at connect time."
}
}
});
//#endregion
export { diagnostics as t };
//#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/js/index.mjs
const z = [
1779033703,
-1150833019,
1013904242,
-1521486534,
1359893119,
-1694144372,
528734635,
1541459225
];
const R = [
1116352408,
1899447441,
-1245643825,
-373957723,
961987163,
1508970993,
-1841331548,
-1424204075,
-670586216,
310598401,
607225278,
1426881987,
1925078388,
-2132889090,
-1680079193,
-1046744716,
-459576895,
-272742522,
264347078,
604807628,
770255983,
1249150122,
1555081692,
1996064986,
-1740746414,
-1473132947,
-1341970488,
-1084653625,
-958395405,
-710438585,
113926993,
338241895,
666307205,
773529912,
1294757372,
1396182291,
1695183700,
1986661051,
-2117940946,
-1838011259,
-1564481375,
-1474664885,
-1035236496,
-949202525,
-778901479,
-694614492,
-200395387,
275423344,
430227734,
506948616,
659060556,
883997877,
958139571,
1322822218,
1537002063,
1747873779,
1955562222,
2024104815,
-2067236844,
-1933114872,
-1866530822,
-1538233109,
-1090935817,
-965641998
];
const S = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const r = [];
var k = class {
_data = new l();
_hash = new l([...z]);
_nDataBytes = 0;
_minBufferSize = 0;
finalize(e) {
e && this._append(e);
const s = this._nDataBytes * 8, t = this._data.sigBytes * 8;
return this._data.words[t >>> 5] |= 128 << 24 - t % 32, this._data.words[(t + 64 >>> 9 << 4) + 14] = Math.floor(s / 4294967296), this._data.words[(t + 64 >>> 9 << 4) + 15] = s, this._data.sigBytes = this._data.words.length * 4, this._process(), this._hash;
}
_doProcessBlock(e, s) {
const t = this._hash.words;
let i = t[0], o = t[1], a = t[2], c = t[3], h = t[4], g = t[5], f = t[6], y = t[7];
for (let n = 0; n < 64; n++) {
if (n < 16) r[n] = e[s + n] | 0;
else {
const d = r[n - 15], j = (d << 25 | d >>> 7) ^ (d << 14 | d >>> 18) ^ d >>> 3, B = r[n - 2], x = (B << 15 | B >>> 17) ^ (B << 13 | B >>> 19) ^ B >>> 10;
r[n] = j + r[n - 7] + x + r[n - 16];
}
const m = h & g ^ ~h & f, p = i & o ^ i & a ^ o & a, u = (i << 30 | i >>> 2) ^ (i << 19 | i >>> 13) ^ (i << 10 | i >>> 22), b = (h << 26 | h >>> 6) ^ (h << 21 | h >>> 11) ^ (h << 7 | h >>> 25), w = y + b + m + R[n] + r[n], M = u + p;
y = f, f = g, g = h, h = c + w | 0, c = a, a = o, o = i, i = w + M | 0;
}
t[0] = t[0] + i | 0, t[1] = t[1] + o | 0, t[2] = t[2] + a | 0, t[3] = t[3] + c | 0, t[4] = t[4] + h | 0, t[5] = t[5] + g | 0, t[6] = t[6] + f | 0, t[7] = t[7] + y | 0;
}
_append(e) {
typeof e == "string" && (e = l.fromUtf8(e)), this._data.concat(e), this._nDataBytes += e.sigBytes;
}
_process(e) {
let s, t = this._data.sigBytes / 64;
e ? t = Math.ceil(t) : t = Math.max((t | 0) - this._minBufferSize, 0);
const i = t * 16, o = Math.min(i * 4, this._data.sigBytes);
if (i) {
for (let a = 0; a < i; a += 16) this._doProcessBlock(this._data.words, a);
s = this._data.words.splice(0, i), this._data.sigBytes -= o;
}
return new l(s, o);
}
};
var l = class l {
words;
sigBytes;
constructor(e, s) {
e = this.words = e || [], this.sigBytes = s === void 0 ? e.length * 4 : s;
}
static fromUtf8(e) {
const s = unescape(encodeURIComponent(e)), t = s.length, i = [];
for (let o = 0; o < t; o++) i[o >>> 2] |= (s.charCodeAt(o) & 255) << 24 - o % 4 * 8;
return new l(i, t);
}
toBase64() {
const e = [];
for (let s = 0; s < this.sigBytes; s += 3) {
const t = this.words[s >>> 2] >>> 24 - s % 4 * 8 & 255, i = this.words[s + 1 >>> 2] >>> 24 - (s + 1) % 4 * 8 & 255, o = this.words[s + 2 >>> 2] >>> 24 - (s + 2) % 4 * 8 & 255, a = t << 16 | i << 8 | o;
for (let c = 0; c < 4 && s * 8 + c * 6 < this.sigBytes * 8; c++) e.push(S.charAt(a >>> 6 * (3 - c) & 63));
}
return e.join("");
}
concat(e) {
if (this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8, this.words.length = Math.ceil(this.sigBytes / 4), this.sigBytes % 4) for (let s = 0; s < e.sigBytes; s++) {
const t = e.words[s >>> 2] >>> 24 - s % 4 * 8 & 255;
this.words[this.sigBytes + s >>> 2] |= t << 24 - (this.sigBytes + s) % 4 * 8;
}
else for (let s = 0; s < e.sigBytes; s += 4) this.words[this.sigBytes + s >>> 2] = e.words[s >>> 2];
this.sigBytes += e.sigBytes;
}
};
function digest(_) {
return new k().finalize(_).toBase64();
}
//#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
export { hash as t };
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
//#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 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}
*/
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`.
*/
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(DEVFRAME_EVENTS.bus.agentManifestChanged);
});
}
registerTool(input) {
this._validateToolId(input.id);
const tool = this._projectTool(input);
this.tools.set(tool.id, {
tool,
handler: input.handler
});
this.events.emit(DEVFRAME_EVENTS.bus.agentToolRegistered, tool);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
return { unregister: () => this.unregisterTool(tool.id) };
}
unregisterTool(id) {
const existed = this.tools.delete(id);
if (existed) {
this.events.emit(DEVFRAME_EVENTS.bus.agentToolUnregistered, id);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
return existed;
}
registerToolProvider(provider) {
this.providers.add(provider);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
const notifyChanged = () => {
if (this.providers.has(provider)) this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
};
return {
notifyChanged,
unregister: () => {
if (this.providers.delete(provider)) this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
};
}
registerResource(input) {
if (this.resources.has(input.id)) throw diagnostics.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(DEVFRAME_EVENTS.bus.agentResourceRegistered, resource);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
return { unregister: () => this.unregisterResource(resource.id) };
}
unregisterResource(id) {
const existed = this.resources.delete(id);
if (existed) {
this.events.emit(DEVFRAME_EVENTS.bus.agentResourceUnregistered, id);
this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged);
}
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.DF0015({ id });
if (this.context.rpc.definitions.get(id)?.agent) throw diagnostics.DF0015({ id });
}
_projectTool(input) {
if (!input.description || typeof input.description !== "string") throw diagnostics.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.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
export { coerceAgentPositionalArgs as n, createEventEmitter as r, DevframeAgentHost as t };
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { t as Diagnostic } from "./nostics-CzECRXpE.mjs";
import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
import { i as isAllowedOrigin } from "./ws-server-BdSLrhxE.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { t as createHostContext } from "./context-Bdkakk8S.mjs";
import { t as toAgentToolName } from "./agent-tool-name-EgfoFO8C.mjs";
import { randomUUID } from "node:crypto";
import process from "node:process";
import { join } from "pathe";
import { homedir } from "node:os";
import { defineHandler } from "h3";
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(DEVFRAME_EVENTS.bus.agentManifestChanged, () => {
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.
*/
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`);
}
}
});
for (const input of definition.services ?? []) ctx.services.install(input, { resolveFrom: definition.packageName });
await definition.setup(ctx);
await ctx.services.ready();
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 guards every request:
* loopback-default DNS-rebinding protection that — unlike the WS upgrade's
* `isAllowedOrigin` — also rejects `Origin`-less requests, so a route-based
* endpoint isn't reachable by an arbitrary local process.
*/
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 && (origin === void 0 || !isAllowedOrigin(origin, allowedOrigins ?? []))) return new Response("Forbidden: origin required", { 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
//#region src/adapters/mcp/http.ts
var http_exports = /* @__PURE__ */ __exportAll({ mountMcpHttp: () => mountMcpHttp });
/**
* 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).
*/
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 { createMcpServer as i, mountMcpHttp as n, createMcpFetchHandler as r, http_exports as t };
import { _ as DevframeNodeRpcSession, g as DevframeNodeContext, gt as SharedState } from "./devframe-BlLEZR-x.mjs";
import { n as InternalAnonymousAuthStorage } from "./context-dqyXS6o3.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) in
* the URL **fragment**. 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.
*
* The code rides the fragment (`#devframe_otp=…`), not the query string, so it
* is never sent to the server, written to an access log, or leaked in a
* `Referer` header — the browser client reads it locally (see
* `consumeOtpFromUrl`). Any existing fragment parameters are preserved.
*/
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 { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import process from "node:process";
import { join } from "pathe";
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
//#region src/node/instance-registry.ts
var instance_registry_exports = /* @__PURE__ */ __exportAll({
listLiveDevframeInstances: () => listLiveDevframeInstances,
probeDevframeOrigin: () => probeDevframeOrigin,
readDevframeInstances: () => readDevframeInstances,
registerDevframeInstance: () => registerDevframeInstance
});
/** 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.
*/
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}.
*/
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.
*/
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 { registerDevframeInstance as i, listLiveDevframeInstances as n, probeDevframeOrigin as r, instance_registry_exports as t };
import { E as DevframeRpcServerFunctions, T as DevframeRpcClientFunctions, _ as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, s as DevframeSseOptions, u as DevframeAuthHandler } from "./devframe-BlLEZR-x.mjs";
import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "./ws-server-D1d3QM9f.mjs";
import "./index-BKFT9-jA.mjs";
import { BirpcGroup } from "birpc";
import { NodeAdapter } from "crossws/adapters/node";
import { Buffer } from "node:buffer";
import { IncomingMessage, Server, ServerResponse } from "node:http";
import { Duplex } from "node:stream";
import { H3 } from "h3";
//#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.
*/
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}.
*/
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.
*/
declare function registerDevframeInstance(record: DevframeInstanceRecord, options?: {
instancesDir?: string;
}): DevframeInstanceRegistration;
/**
* 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.
*/
declare function listLiveDevframeInstances(options?: {
instancesDir?: string;
timeoutMs?: number;
}): Promise<{
live: DevframeInstanceRecord[];
pruned: DevframeInstanceRecord[];
}>;
//#endregion
//#region src/node/instance-shell.d.ts
/**
* The live handle for a bound HTTP + WebSocket RPC server — what the
* side-car / shared-server tiers produce and what {@link createDevServer}
* re-exposes through its own return contract.
*/
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). Absent when the WebSocket transport is disabled (`ws: false`).
*/
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.
*/
connectionMeta: () => ConnectionMeta;
close: () => Promise<void>;
}
/**
* How the instance's RPC socket is bound:
*
* - `sidecar` — its own HTTP+WS server on a dedicated port (`ws.port` /
* `ws.sidecar`), advertised with that port.
* - `server` — a shared upgrade route on the host's `node:http` server.
* - `external` — no local transport: `ws.url` alone names a server that owns
* both the socket and its auth.
* - `unbound` — the transport exists but nothing is bound to it yet; the host
* drives it through {@link InstanceShell.handleUpgrade} /
* {@link InstanceShell.attach}.
* - `disabled` — `ws: false`: no WebSocket at all; clients connect over the
* SSE endpoint instead (`backend: 'sse'`).
*/
type InstanceWsTier = 'sidecar' | 'server' | 'external' | 'unbound' | 'disabled';
/** The live shell surface an `init` / `mount` callback can reach. */
interface InstanceShellApi {
/** The normalized mount base, with leading and trailing slash. */
base: string;
/** The h3 app every route is mounted on. */
app: H3;
/** The public origin, once known (pinned, or derived from the first request). */
origin: () => string | undefined;
/** The connection meta, once the transport has resolved. */
connectionMeta: () => ConnectionMeta | undefined;
}
/** What an instance's own initialization contributes to the shell. */
interface InstanceShellInit<TContext extends DevframeNodeContext> {
/** The context every mounted surface shares. */
context: TContext;
/** The `mcp` entry to advertise, when an MCP route was mounted. */
mcp?: ConnectionMeta['mcp'];
/** Torn down before the transport on `close()` (e.g. MCP sessions). */
dispose?: () => Promise<void>;
}
interface CreateInstanceShellOptions<TContext extends DevframeNodeContext> {
/** Normalized mount base (leading and trailing slash). */
base: string;
/** h3 app to mount on. A fresh one is created when omitted. */
app?: H3;
/** Public origin, or a getter. Derived from the first request when omitted. */
origin?: string | (() => string);
/** Resolved auth intent: `undefined`/`true` gates, `false` opts out, a handler installs a scheme. */
auth?: boolean | DevframeAuthHandler;
/** Host `node:http` server to share the WS upgrade with. */
server?: Server;
/** Explicit WebSocket control — see {@link DevframeWsOptions}. `false` disables the socket (SSE-only). */
ws?: DevframeWsOptions | false;
/** SSE endpoint control — enabled by default; `false` disables, an object renames the route. */
sse?: boolean | DevframeSseOptions;
/** Bind host for a side-car WebSocket server. Default: `localhost`. */
host?: string;
/** Extra WS-upgrade origins beyond the loopback default; `false` disables the gate. */
allowedOrigins?: readonly string[] | WsOriginRegistry | false;
/** Destroy off-route upgrades on a shared `server`. */
destroyUnmatchedUpgrades?: boolean;
onPeerConnect?: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
onPeerDisconnect?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
/**
* Advertise the WS and SSE routes as base-absolute paths (`<base>__ws` /
* `<base>__sse`) instead of the base-relative default. A hub serves one
* meta document from several bases, so its clients need the absolute form
* to resolve the same endpoints.
*/
absoluteWsPath?: boolean;
/** Pick the first port a `ws.sidecar` server tries. Default: a random free port. */
resolveSidecarPort?: (host: string) => Promise<number>;
/**
* Publish this instance in the global registry (`~/.devframe/instances/`)
* once its public origin is known — a dynamic import so the registry code
* stays out of instances that opt out. Omit to skip registration.
*/
register?: InstanceRegisterConfig;
/** Create the context and mount everything that must precede the transport. */
init: (api: InstanceShellApi) => Promise<InstanceShellInit<TContext>>;
/** Mount the routes that describe the resolved transport (discovery, SPA). */
mount?: (context: TContext, meta: ConnectionMeta, api: InstanceShellApi) => void | Promise<void>;
/** Throw the instance's own diagnostic for `connectionMeta()` before readiness. */
onMetaUnavailable: () => never;
}
/**
* The identity a shell needs to publish itself in the global instance
* registry — the parts it can't derive on its own. The shell fills in
* `pid` / `origin` / `port` / `basePath` / `mcp` / `startedAt` once the
* origin resolves, then merges {@link InstanceRegisterConfig.overrides} last.
*/
interface InstanceRegisterConfig {
/** Definition id (or a synthetic one for a hub). */
id: string;
/** Display name. */
name?: string;
/** Working directory the instance runs from. Default: `process.cwd()`. */
rootDir?: string;
/** Fields overriding the shell-derived record (from the public option's object form). */
overrides?: Partial<DevframeInstanceRecord>;
}
/**
* Translate the public `register?: boolean | Partial<DevframeInstanceRecord>`
* option into a shell {@link InstanceRegisterConfig}, or `undefined` when
* registration is opted out. The object form supplies record overrides on top
* of the caller-provided identity defaults.
*/
declare function resolveInstanceRegister(option: boolean | Partial<DevframeInstanceRecord> | undefined, defaults: {
id: string;
name?: string;
rootDir?: string;
}): InstanceRegisterConfig | undefined;
/** Live internals the first-party adapters read off an instance. */
interface InstanceShellInternals {
readonly started?: StartedServer;
readonly authHandler?: DevframeAuthHandler;
}
interface InstanceShell<TContext extends DevframeNodeContext> {
base: string;
handler: (request: Request) => Promise<Response>;
nodeMiddleware: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
ready: Promise<void>;
context: Promise<TContext>;
connectionMeta: () => ConnectionMeta;
/** Complete a host server's `upgrade` event on the instance's socket. */
handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
/** Route a host server's `upgrade` events to the instance's socket. */
attach: (server: Server) => () => void;
close: () => Promise<void>;
internals: InstanceShellInternals;
}
/** Compare two URL paths ignoring a trailing slash. */
declare function samePath(a: string, b: string): boolean;
/**
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
* one h3 app, one lazily-derived public origin (and the auth banner that waits
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
* serves them. Each factory supplies only what makes it itself — its context,
* its routes, its diagnostics — through `init` / `mount`.
*
* Nothing here listens on a port unless a side-car was explicitly requested:
* the default tier leaves the socket `unbound`, so a host chains it onto its
* own server through {@link InstanceShell.attach} /
* {@link InstanceShell.handleUpgrade}.
*
* @internal
*/
declare function createInstanceShell<TContext extends DevframeNodeContext>(options: CreateInstanceShellOptions<TContext>): InstanceShell<TContext>;
//#endregion
export { InstanceShellInit as a, StartedServer as c, samePath as d, DevframeInstanceRecord as f, registerDevframeInstance as h, InstanceShellApi as i, createInstanceShell as l, listLiveDevframeInstances as m, InstanceRegisterConfig as n, InstanceShellInternals as o, DevframeInstanceRegistration as p, InstanceShell as r, InstanceWsTier as s, CreateInstanceShellOptions as t, resolveInstanceRegister as u };
import "./constants.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { t as getInternalContext } from "./context-CXhjygJU.mjs";
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
import { createServer } from "node:http";
import process from "node:process";
import { isIP } from "node:net";
import { joinURL, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
import { H3, defineHandler, toNodeHandler } from "h3";
//#region src/node/utils.ts
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/instance-shell.ts
/**
* Compose an h3 + WebSocket RPC server for a devframe context — the low-level
* "listen on a port (or share one) + attach the WS transport" binding the
* side-car and shared-server tiers below are built on. Owns and listens on a
* fresh `node:http` server unless `server` is supplied, in which case it only
* attaches the upgrade listener and leaves that server's lifecycle to its
* owner.
*/
async function bindHttpAndWs(options) {
const { context, port, core } = options;
const bindHost = options.host;
const app = new H3();
const ownsHttpServer = !options.server;
const httpServer = options.server ?? createServer(toNodeHandler(app));
const rpcHost = context.rpc;
const websocket = options.websocket !== false;
let ws;
let closeWs = async () => {};
if (websocket) {
const { attachWsRpcTransport } = await import("./rpc/transports/ws-server.mjs");
const transport = attachWsRpcTransport(core.rpcGroup, {
server: httpServer,
path: options.path,
destroyUnmatched: options.destroyUnmatched ?? ownsHttpServer,
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
ws = transport.ws;
closeWs = transport.close;
}
if (ownsHttpServer) try {
await new Promise((resolve, reject) => {
const onError = (error) => reject(error);
httpServer.once("error", onError);
httpServer.listen(port, bindHost, () => {
httpServer.removeListener("error", onError);
resolve();
});
});
} catch (error) {
await closeWs().catch(() => {});
throw diagnostics.DF0052({
host: bindHost,
port,
reason: error instanceof Error ? error.message : String(error),
cause: error
});
}
const address = httpServer.address();
const resolvedPort = typeof address === "object" && address ? address.port : port;
const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
const internal = getInternalContext(context);
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${resolvedPort}${options.path ?? ""}`;
if (websocket) internal.setWsEndpoint({ url: wsUrl });
function connectionMeta() {
const jsonSerializableMethods = [];
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
return {
backend: "websocket",
websocket: { path: options.path },
jsonSerializableMethods
};
}
return {
origin,
port: resolvedPort,
app,
ws,
rpcGroup: core.rpcGroup,
connectionMeta,
async close() {
await closeWs();
if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
if (websocket && getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).setWsEndpoint(void 0);
}
};
}
/**
* Translate the public `register?: boolean | Partial<DevframeInstanceRecord>`
* option into a shell {@link InstanceRegisterConfig}, or `undefined` when
* registration is opted out. The object form supplies record overrides on top
* of the caller-provided identity defaults.
*/
function resolveInstanceRegister(option, defaults) {
if (!option) return void 0;
return {
id: defaults.id,
...defaults.name !== void 0 ? { name: defaults.name } : {},
...defaults.rootDir !== void 0 ? { rootDir: defaults.rootDir } : {},
...typeof option === "object" ? { overrides: option } : {}
};
}
/** Compare two URL paths ignoring a trailing slash. */
function samePath(a, b) {
return withoutTrailingSlash(a) === withoutTrailingSlash(b);
}
/**
* Copy a web `Response` from a fetch-style transport handler onto the h3
* event's response and return its body — mirroring the MCP route's bridge.
* Returning the body (a `ReadableStream`, or `''` for an empty one — h3
* middleware only falls through on `undefined`) terminates the chain with
* the status/headers set here instead of continuing to the SPA catch-all.
*/
function respondWith(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 ?? "";
}
/**
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
* one h3 app, one lazily-derived public origin (and the auth banner that waits
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
* serves them. Each factory supplies only what makes it itself — its context,
* its routes, its diagnostics — through `init` / `mount`.
*
* Nothing here listens on a port unless a side-car was explicitly requested:
* the default tier leaves the socket `unbound`, so a host chains it onto its
* own server through {@link InstanceShell.attach} /
* {@link InstanceShell.handleUpgrade}.
*
* @internal
*/
function createInstanceShell(options) {
const base = options.base;
const baseNoSlash = withoutTrailingSlash(base);
const app = options.app ?? new H3();
const wsDisabled = options.ws === false;
const ws = options.ws === false ? {} : options.ws ?? {};
const route = withoutLeadingSlash(ws.route ?? "__ws");
/** Where an upgrade lands on the host's own origin. */
const routePath = joinURL(base, route);
/** What `__connection.json` advertises for a same-origin socket. */
const advertisedPath = options.absoluteWsPath ? routePath : route;
const sidecarRequested = ws.port != null || ws.sidecar === true;
const tier = wsDisabled ? "disabled" : sidecarRequested ? "sidecar" : options.server ? "server" : ws.url ? "external" : "unbound";
const sseEnabled = options.sse !== false && tier !== "external";
const sseRoute = withoutLeadingSlash((typeof options.sse === "object" ? options.sse.route : void 0) ?? "__sse");
const sseRoutePath = joinURL(base, sseRoute);
const advertisedSsePath = options.absoluteWsPath ? sseRoutePath : sseRoute;
let derivedOrigin;
function currentOrigin() {
return (typeof options.origin === "function" ? options.origin() : options.origin) || derivedOrigin;
}
let authHandler;
let bannerPrinted = false;
function maybePrintBanner() {
if (bannerPrinted || !authHandler || !currentOrigin()) return;
bannerPrinted = true;
authHandler.printBanner();
}
let meta;
let registration;
let registerPromise;
/**
* Publish the instance in the global registry the moment both its origin
* and connection meta are known — at init end for a pinned origin, or on
* the first request for a derived one. Registration never throws (the
* registry writer degrades to a coded warning), so failures never surface.
*/
function maybeRegister() {
const cfg = options.register;
const origin = currentOrigin();
if (!cfg || registerPromise || !origin || !meta) return;
const resolvedMeta = meta;
registerPromise = import("./instance-registry-CHunhMi5.mjs").then((n) => n.t).then(({ registerDevframeInstance }) => {
let port = 0;
try {
const url = new URL(origin);
port = Number(url.port) || (url.protocol === "https:" ? 443 : 80);
} catch {}
registration = registerDevframeInstance({
pid: process.pid,
port,
origin,
basePath: base,
id: cfg.id,
...cfg.name !== void 0 ? { name: cfg.name } : {},
rootDir: cfg.rootDir ?? process.cwd(),
mcp: resolvedMeta.mcp ? { path: joinURL(base, resolvedMeta.mcp.path) } : null,
startedAt: Date.now(),
...cfg.overrides
});
}).catch(() => {});
}
function noteOrigin(origin) {
derivedOrigin ??= origin;
maybePrintBanner();
maybeRegister();
}
let started;
let transport;
let dispose;
let ctx;
const api = {
base,
app,
origin: currentOrigin,
connectionMeta: () => meta
};
/**
* Auth resolution: gate by default, `false` opts out, a handler object
* installs a custom scheme. The `external` tier has no local transport to
* gate — the server behind `ws.url` owns auth — so it resolves to nothing.
*/
function resolveAuth() {
if (options.auth === false) return false;
if (typeof options.auth === "object") {
authHandler = options.auth;
return options.auth;
}
authHandler = createInteractiveAuth(ctx);
return authHandler;
}
/**
* The context's RPC core (birpc group, session lifecycle, auth gate) —
* one per instance, shared by every transport binding (WS and SSE), so a
* WS peer and an SSE session live in the same session/broadcast space.
* Built lazily: an `unbound` host that never wires a transport pays
* nothing for it, not even the imports. `resolvedAuth` and `ctx` are
* assigned during `init()` before any caller can reach this.
*/
let resolvedAuth = false;
let corePromise;
function ensureCore() {
corePromise ??= import("./rpc-core-Dru9uoM0.mjs").then((n) => n.n).then(({ createContextRpcServer }) => createContextRpcServer({
context: ctx,
auth: resolvedAuth,
onPeerConnect: options.onPeerConnect,
onPeerDisconnect: options.onPeerDisconnect
}));
return corePromise;
}
/**
* The SSE transport, built on the first request to its route so an
* instance nobody dials over SSE never loads it.
*/
let ssePromise;
function ensureSse() {
ssePromise ??= (async () => {
const [core, { attachSseRpcTransport }] = await Promise.all([ensureCore(), import("./rpc/transports/sse-server.mjs")]);
return attachSseRpcTransport(core.rpcGroup, {
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
})();
return ssePromise;
}
/**
* A side-car server on its own port. `getPort` probes and the bind can
* still race (or disagree across the v4/v6 duals of `localhost`), so an
* auto-port side-car retries on a fresh random port instead of failing
* init; a pinned `ws.port` is honored as given and fails loudly.
*/
async function startSidecar(core) {
const sidecarHost = options.host ?? "localhost";
const start = (port) => bindHttpAndWs({
context: ctx,
core,
host: sidecarHost,
port,
path: withLeadingSlash(route),
allowedOrigins: options.allowedOrigins
});
if (ws.port != null) return await start(ws.port);
const { getPort } = await import("./dist-CZXfGEkd.mjs").then((n) => n.t);
let lastError;
for (let attempt = 0; attempt < 3; attempt++) {
const port = attempt === 0 && options.resolveSidecarPort ? await options.resolveSidecarPort(sidecarHost) : await getPort({
random: true,
host: sidecarHost
});
try {
return await start(port);
} catch (error) {
lastError = error;
}
}
throw lastError;
}
async function init() {
const result = await options.init(api);
ctx = result.context;
dispose = result.dispose;
resolvedAuth = tier === "external" ? false : resolveAuth();
let websocketMeta;
if (tier === "sidecar") {
started = await startSidecar(await ensureCore());
websocketMeta = {
port: started.port,
path: route
};
} else if (tier === "server") {
started = await bindHttpAndWs({
context: ctx,
core: await ensureCore(),
host: options.host ?? "localhost",
port: 0,
server: options.server,
path: routePath,
allowedOrigins: options.allowedOrigins,
destroyUnmatched: options.destroyUnmatchedUpgrades
});
websocketMeta = { path: advertisedPath };
} else if (tier === "external") websocketMeta = ws.url;
else if (tier === "unbound") websocketMeta = { path: advertisedPath };
if (!wsDisabled && ws.url) websocketMeta = ws.url;
if (sseEnabled) app.use(sseRoutePath, defineHandler(async (event) => respondWith(event, await (await ensureSse()).handler(event.req))));
meta = {
backend: wsDisabled ? sseEnabled ? "sse" : "none" : "websocket",
...websocketMeta !== void 0 ? { websocket: websocketMeta } : {},
...sseEnabled ? { sse: { path: advertisedSsePath } } : {},
...result.mcp ? { mcp: result.mcp } : {}
};
if (Object.keys(ctx.staticConfig).length > 0) meta.configs = ctx.staticConfig;
await options.mount?.(ctx, meta, api);
maybePrintBanner();
maybeRegister();
}
const initPromise = init();
initPromise.catch(() => {});
const contextPromise = initPromise.then(() => ctx);
contextPromise.catch(() => {});
/**
* The `unbound` tier: the RPC core and its crossws adapter, bound to
* nothing. Built on the first `attach` / `handleUpgrade` — a host that
* never wires the socket (or whose runtime brings its own WS transport)
* pays nothing for it, not even the adapter's imports.
*/
let transportPromise;
function ensureTransport() {
transportPromise ??= initPromise.then(async () => {
const [core, { attachWsRpcTransport }] = await Promise.all([ensureCore(), import("./rpc/transports/ws-server.mjs")]);
transport = attachWsRpcTransport(core.rpcGroup, {
unbound: true,
path: routePath,
allowedOrigins: options.allowedOrigins,
onConnected: core.onConnected,
onDisconnected: core.onDisconnected
});
return transport;
});
return transportPromise;
}
async function handleRequest(request) {
await initPromise;
noteOrigin(new URL(request.url).origin);
const response = await app.fetch(request);
if (response.status === 404) return new Response(null, { status: 404 });
return response;
}
let nodeHandler;
function nodeMiddleware(req, res, next) {
let pathname = req.url ?? "/";
try {
pathname = new URL(pathname, "http://localhost").pathname;
} catch {}
if (!(samePath(pathname, baseNoSlash) || pathname.startsWith(base))) {
if (next) {
next();
return;
}
res.statusCode = 404;
res.end();
return;
}
initPromise.then(async () => {
const host = req.headers.host;
if (host) {
const encrypted = req.socket.encrypted;
noteOrigin(`${encrypted ? "https" : "http"}://${host}`);
}
if (!nodeHandler) {
const { toNodeHandler } = await import("h3/node");
nodeHandler = toNodeHandler(app);
}
return nodeHandler(req, res);
}).catch((err) => {
if (next) {
next(err);
return;
}
res.statusCode = 500;
res.end();
});
}
/** The `unbound` tier is the only one whose socket the host may drive. */
function assertUnbound() {
if (tier === "disabled") throw diagnostics.DF0057();
if (tier === "external") throw diagnostics.DF0056({ url: ws.url });
if (tier !== "unbound") throw diagnostics.DF0055({ tier });
}
/**
* Publish the socket's absolute URL on the context, so surfaces that hand
* out a complete endpoint (the hub's remote docks) work on this tier too.
* {@link bindHttpAndWs} does the same for the tiers it owns.
*/
function publishWsEndpoint(server) {
const record = () => {
const address = server.address();
if (typeof address !== "object" || !address) return;
const host = options.host ?? (address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address);
getInternalContext(ctx).setWsEndpoint({ url: `ws://${formatHostForUrl(host)}:${address.port}${routePath}` });
};
if (server.listening) record();
else server.once("listening", record);
}
function handleUpgrade(req, socket, head) {
assertUnbound();
if (transport) {
transport.handleUpgrade(req, socket, head);
return;
}
ensureTransport().then((live) => live.handleUpgrade(req, socket, head)).catch(() => socket.destroy());
}
function attach(server) {
assertUnbound();
server.on("upgrade", handleUpgrade);
ensureTransport().then(() => publishWsEndpoint(server)).catch(() => {});
return () => server.off("upgrade", handleUpgrade);
}
return {
base,
handler: handleRequest,
nodeMiddleware,
ready: initPromise,
context: contextPromise,
connectionMeta: () => meta ?? options.onMetaUnavailable(),
handleUpgrade,
attach,
async close() {
await initPromise.catch(() => {});
await registerPromise?.catch(() => {});
registration?.unregister();
await dispose?.();
await ssePromise?.then((live) => live.close()).catch(() => {});
await started?.close();
await transportPromise?.then((live) => live.close()).catch(() => {});
},
internals: {
get started() {
return started;
},
get authHandler() {
return authHandler;
}
}
};
}
//#endregion
export { normalizeHttpServerUrl as i, resolveInstanceRegister as n, samePath as r, createInstanceShell as t };
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { createRequire } from "node:module";
import { isatty } from "node:tty";
import { formatWithOptions, inspect } from "node:util";
import { dirname, extname, join, normalize, sep } from "pathe";
import { createReadStream, existsSync } from "node:fs";
import { Buffer } from "node:buffer";
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { Readable } from "node:stream";
import { lookup } from "mrmime";
//#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/utils/remote-assets.ts
const debugFetch = createDebug("devframe:remote-assets:fetch");
const debugCache = createDebug("devframe:remote-assets:cache");
const MANIFEST_FILENAME = ".manifest.json";
/**
* Upstream response headers replayed to the browser. Everything outside this
* list is dropped, because it describes the *provider's* transfer rather than
* the file: hop-by-hop and encoding headers no longer match the body `fetch`
* already decoded, and a CDN's policy headers (`set-cookie`, `cache-control`,
* framing/CSP) belong to its origin — replaying them under the dev server's
* origin could just as well break the iframe these assets render in.
*/
const PROXIED_HEADERS = [
"content-language",
"etag",
"last-modified"
];
const CACHE_CONTROL_HEADER = "no-store";
/** Flatten a jsDelivr (`name`/`files`) or unpkg (`path`/`files`) file tree. */
function flattenTree(nodes, style) {
const out = [];
const walk = (list, prefix) => {
for (const node of list) if (style === "path") {
if (node.type === "file") out.push((node.path ?? "").replace(/^\//, ""));
else walk(node.files ?? [], "");
} else if (node.type === "file") out.push(prefix + (node.name ?? ""));
else if (node.files) walk(node.files, `${prefix}${node.name}/`);
};
walk(nodes, "");
return out;
}
const providers = {
jsdelivr: {
fileUrl: (pkg, version, filePath) => `https://cdn.jsdelivr.net/npm/${pkg}@${version}/${filePath}`,
listFiles: async (pkg, version, fetchImpl) => {
const url = `https://data.jsdelivr.com/v1/packages/npm/${pkg}@${version}`;
debugFetch("listing files for %s@%s from %s", pkg, version, url);
const res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
return flattenTree((await res.json()).files ?? [], "name");
}
},
unpkg: {
fileUrl: (pkg, version, filePath) => `https://unpkg.com/${pkg}@${version}/${filePath}`,
listFiles: async (pkg, version, fetchImpl) => {
const url = `https://unpkg.com/${pkg}@${version}/?meta`;
debugFetch("listing files for %s@%s from %s", pkg, version, url);
const res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
return flattenTree([await res.json()], "path");
}
}
};
function resolveProvider(assets) {
const p = assets.provider ?? "jsdelivr";
return typeof p === "string" ? {
provider: providers[p],
name: p
} : {
provider: p,
name: "custom"
};
}
/**
* Resolve a locally installed copy of `assets.package` from
* `assets.resolveFrom`'s dependency graph and return its assets directory,
* or `undefined` when the package (or directory) is absent. A different
* installed version warns (`DF0062`); a different major throws (`DF0061`).
*/
function resolveInstalled(assets) {
if (assets.resolveFrom == null) return void 0;
let pkgJsonPath;
let installed;
try {
const requireFrom = createRequire(assets.resolveFrom);
pkgJsonPath = requireFrom.resolve(`${assets.package}/package.json`);
installed = requireFrom(`${assets.package}/package.json`).version;
} catch {
return;
}
if (typeof installed !== "string") return void 0;
if (installed !== assets.version) {
const major = (v) => v.trim().split(".")[0] ?? v;
if (major(installed) !== major(assets.version)) throw diagnostics.DF0061({
package: assets.package,
required: assets.version,
installed
});
diagnostics.DF0062({
package: assets.package,
required: assets.version,
installed
});
}
const dir = join(dirname(pkgJsonPath), assets.path ?? "dist");
return existsSync(dir) ? dir : void 0;
}
function contentTypeFor(filePath) {
const type = lookup(filePath);
if (!type) return "application/octet-stream";
return type === "text/html" ? "text/html; charset=utf-8" : type;
}
/**
* Headers for a file streamed through from the provider. `Content-Type` and
* `Cache-Control` are ours, so a file looks identical whether it came from the
* provider or from the cache ({@link createStore}'s `serveCached`).
*/
function proxyHeaders(filePath, upstream) {
const headers = new Headers({
"Content-Type": contentTypeFor(filePath),
"Cache-Control": CACHE_CONTROL_HEADER
});
const encoding = upstream.get("content-encoding");
const length = upstream.get("content-length");
if (length && (!encoding || encoding === "identity")) headers.set("Content-Length", length);
for (const name of PROXIED_HEADERS) {
const value = upstream.get(name);
if (value != null) headers.set(name, value);
}
return headers;
}
/** Clean a request path into a safe package-relative POSIX path, or `null` if it escapes root. */
function cleanRequestPath(urlPath) {
let cleaned;
try {
cleaned = decodeURIComponent(urlPath || "/");
} catch {
return null;
}
cleaned = cleaned.replace(/[?#].*$/, "").replace(/^\/+|\/+$/g, "");
const normalized = normalize(cleaned);
if (normalized === ".." || normalized.startsWith(`..${sep}`) || normalized.startsWith("/")) return null;
return normalized === "." ? "" : normalized;
}
/** Candidate files for a request, in order: direct hit, index, `.html`, SPA fallback. */
function candidatePaths(prefix, cleaned) {
const candidates = [];
if (cleaned) candidates.push(prefix + cleaned);
candidates.push(`${prefix}${cleaned ? `${cleaned}/` : ""}index.html`);
if (cleaned && !extname(cleaned)) candidates.push(`${prefix + cleaned}.html`);
if (!/\.[a-z0-9]+$/i.test(cleaned) && !candidates.includes(`${prefix}index.html`)) candidates.push(`${prefix}index.html`);
return candidates;
}
function createStore(assets, cacheDir) {
const normalized = {
...assets,
path: assets.path ?? "dist"
};
const { provider, name: providerName } = resolveProvider(assets);
const fetchImpl = assets.fetch ?? globalThis.fetch;
const prefix = `${normalized.path}/`;
let manifestPromise;
let manifestReported = false;
async function loadManifest() {
const manifestFile = join(cacheDir, MANIFEST_FILENAME);
if (existsSync(manifestFile)) try {
return new Set(JSON.parse(await readFile(manifestFile, "utf8")));
} catch {}
if (assets.offline || !provider.listFiles) return null;
try {
const files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
await mkdir(cacheDir, { recursive: true });
await writeFile(manifestFile, JSON.stringify(files), "utf8").catch(() => {});
return new Set(files);
} catch (error) {
if (!manifestReported) {
manifestReported = true;
diagnostics.DF0059({
package: normalized.package,
version: normalized.version,
provider: providerName,
reason: errText(error),
cause: error
});
}
return null;
}
}
async function serveCached(filePath) {
const abs = join(cacheDir, filePath);
let size;
try {
const s = await stat(abs);
if (!s.isFile()) return null;
size = s.size;
} catch {
return null;
}
debugCache("serving %s from cache (%d bytes)", filePath, size);
return new Response(Readable.toWeb(createReadStream(abs)), { headers: {
"Content-Type": contentTypeFor(filePath),
"Content-Length": String(size),
"Cache-Control": CACHE_CONTROL_HEADER
} });
}
/** Persist `body` to the cache at `filePath` (tmp + rename); failures warn (`DF0063`). */
async function persist(filePath, body) {
const target = join(cacheDir, filePath);
const tmp = `${target}.${Math.random().toString(36).slice(2)}.tmp`;
try {
await mkdir(dirname(target), { recursive: true });
await writeFile(tmp, Buffer.from(await new Response(body).arrayBuffer()));
await rename(tmp, target);
} catch (error) {
await rm(tmp, { force: true }).catch(() => {});
diagnostics.DF0063({
filepath: target,
reason: errText(error),
cause: error
});
}
}
/** Fetch `filePath` through the provider: `null` on 404, a `Response` on 200, throws (`DF0060`) otherwise. */
async function serveRemote(filePath) {
const url = provider.fileUrl(normalized.package, normalized.version, filePath);
let res;
try {
debugFetch("fetching %s from %s", filePath, url);
res = await fetchImpl(url);
} catch (error) {
throw diagnostics.DF0060({
url,
package: normalized.package,
reason: errText(error),
cause: error
});
}
if (res.status === 404) {
await res.body?.cancel().catch(() => {});
return null;
}
if (!res.ok || !res.body) {
await res.body?.cancel().catch(() => {});
throw diagnostics.DF0060({
url,
package: normalized.package,
reason: `HTTP ${res.status}`
});
}
const [toClient, toCache] = res.body.tee();
persist(filePath, toCache);
return new Response(toClient, {
status: res.status,
statusText: res.statusText,
headers: proxyHeaders(filePath, res.headers)
});
}
async function serve(urlPath) {
const cleaned = cleanRequestPath(urlPath);
if (cleaned === null) return null;
const candidates = candidatePaths(prefix, cleaned);
manifestPromise ??= loadManifest();
const manifest = await manifestPromise;
if (manifest) {
const filePath = candidates.find((c) => manifest.has(c));
if (!filePath) return null;
return await serveCached(filePath) ?? (assets.offline ? Promise.reject(diagnostics.DF0060({
url: filePath,
package: normalized.package,
reason: "offline: true and the file is not in the cache"
})) : serveRemote(filePath));
}
for (const candidate of candidates) {
const cached = await serveCached(candidate);
if (cached) return cached;
}
if (assets.offline) return null;
for (const candidate of candidates) {
const remote = await serveRemote(candidate);
if (remote) return remote;
}
return null;
}
async function materialize(targetDir) {
const fail = (reason, cause) => {
throw diagnostics.DF0064({
package: normalized.package,
version: normalized.version,
reason,
cause
});
};
if (!provider.listFiles) fail("the configured provider has no file listing (`listFiles`)");
let files;
try {
files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
} catch (error) {
return fail(errText(error), error);
}
for (const filePath of files.filter((f) => f.startsWith(prefix))) {
const target = join(targetDir, filePath.slice(prefix.length));
const url = provider.fileUrl(normalized.package, normalized.version, filePath);
let res;
try {
res = await fetchImpl(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
} catch (error) {
return fail(`failed to download ${filePath}: ${errText(error)}`, error);
}
await mkdir(dirname(target), { recursive: true });
await writeFile(target, Buffer.from(await res.arrayBuffer()));
}
}
return {
assets: normalized,
serve,
materialize
};
}
function errText(error) {
return error instanceof Error ? error.message : String(error);
}
const PACKAGE_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
const VERSION_RE = /^\d+\.\d+\.\d+(?:-[a-z0-9-]+(?:\.[a-z0-9-]+)*)?(?:\+[a-z0-9-]+(?:\.[a-z0-9-]+)*)?$/i;
/** Reject a {@link RemoteAssets} with an unsafe package name or version (`DF0065`). */
function assertValidRemoteAssets(assets) {
if (assets.package.length > 214 || !PACKAGE_NAME_RE.test(assets.package)) throw diagnostics.DF0065({
field: "package",
value: assets.package
});
if (!VERSION_RE.test(assets.version)) throw diagnostics.DF0065({
field: "version",
value: assets.version
});
}
/**
* Normalize a {@link StaticAssetsSource} into something servable: a local
* directory (strings pass through; a remote source short-circuits to a
* locally installed copy of its package when present) or a caching
* {@link RemoteAssetsStore} back-proxy. Remote caches live under
* `<projectStorageDir>/.remote-assets/<package>@<version>/`.
*
* A remote source's `package`/`version` are validated first (`DF0065`) — both
* are interpolated into CDN URLs and the cache path.
*/
function resolveStaticAssetsSource(source, projectStorageDir) {
if (typeof source === "string") return source;
assertValidRemoteAssets(source);
return resolveInstalled(source) ?? createStore(source, join(projectStorageDir, ".remote-assets", `${source.package.replace(/\//g, "+")}@${source.version}`));
}
//#endregion
export { createDebug as n, resolveStaticAssetsSource as t };
import { E as DevframeRpcServerFunctions, T as DevframeRpcClientFunctions, _ as DevframeNodeRpcSession, g as DevframeNodeContext, u as DevframeAuthHandler } from "./devframe-BlLEZR-x.mjs";
import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-D1d3QM9f.mjs";
import "./index-BKFT9-jA.mjs";
import { BirpcGroup, EventOptions } from "birpc";
//#region src/node/rpc-core.d.ts
interface CreateContextRpcServerOptions {
context: DevframeNodeContext;
/**
* Auth intent: `true`/omitted gates by default, `false` opts out (auto-trust
* handshake shim), a {@link DevframeAuthHandler} installs a custom scheme.
*/
auth?: boolean | DevframeAuthHandler;
/** Lower-level per-call gate by method name and session, without a full handler. */
authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean;
/** Called once per new RPC connection, right after its session is created. */
onPeerConnect?: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
/** Called once per closed RPC connection, after the transport's disconnect bookkeeping. */
onPeerDisconnect?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
/** Forwarded verbatim to birpc's `rpcOptions` so a host keeps seeing RPC failures. */
rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>;
}
interface ContextRpcServer {
rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
/** The resolved auth handler when `auth` was passed as one. */
authHandler?: DevframeAuthHandler;
/**
* Connection lifecycle handlers to wire into a transport binding
* (`attachWsRpcTransport`'s `onConnected` / `onDisconnected`, or any other
* crossws adapter's peer hooks via `createWsRpcPeerHooks`).
*/
onConnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
onDisconnected: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
}
/**
* Bind a devframe context's registered RPC functions to a birpc group,
* transport-agnostically — the shared core under the instance shell's own
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
* `createHandler`.
*
* Owns everything about serving RPC that is independent of *how* peers
* connect: the auth handler's function registration, the
* `AsyncLocalStorage`-based session resolver (so
* `ctx.rpc.getCurrentRpcSession()` works inside handlers), the
* `authorize` gate, and the `auth: false` auto-trust handshake shim.
*/
declare function createContextRpcServer(options: CreateContextRpcServerOptions): ContextRpcServer;
//#endregion
export { CreateContextRpcServerOptions as n, createContextRpcServer as r, ContextRpcServer as t };
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
import { createRpcServer } from "./rpc/server.mjs";
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { AsyncLocalStorage } from "node:async_hooks";
//#region src/node/rpc-core.ts
var rpc_core_exports = /* @__PURE__ */ __exportAll({ createContextRpcServer: () => createContextRpcServer });
/**
* Bind a devframe context's registered RPC functions to a birpc group,
* transport-agnostically — the shared core under the instance shell's own
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
* `createHandler`.
*
* Owns everything about serving RPC that is independent of *how* peers
* connect: the auth handler's function registration, the
* `AsyncLocalStorage`-based session resolver (so
* `ctx.rpc.getCurrentRpcSession()` works inside handlers), the
* `authorize` gate, and the `auth: false` auto-trust handshake shim.
*/
function createContextRpcServer(options) {
const { context } = options;
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);
});
};
}
} });
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 };
}
});
const onConnected = (connection, meta) => {
Promise.resolve().then(() => context.services.ready?.()).catch((error) => {
const reason = error instanceof Error ? error.message : String(error);
diagnostics.DF0071({
reason,
cause: error
}, { method: "error" });
});
const session = {
meta,
rpc: rpcGroup.clients.find((client) => client.$meta === meta)
};
authHandler?.onConnect(connection, session);
options.onPeerConnect?.(connection, session);
};
const onDisconnected = (connection, meta) => {
options.onPeerDisconnect?.(connection, meta);
rpcHost._emitSessionDisconnected(meta);
};
return {
rpcGroup,
authHandler,
onConnected,
onDisconnected
};
}
//#endregion
export { rpc_core_exports as n, createContextRpcServer as t };
import { createEventEmitter } from "./utils/events.mjs";
import { nanoid } from "./utils/nanoid.mjs";
//#region ../../node_modules/.pnpm/immer@11.1.16/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 = [
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) {
{
const e = errors[error];
const msg = isFunction(e) ? e.apply(null, args) : e;
throw new Error(`[Immer] ${msg}`);
}
}
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 (isNaN(parseInt(prop))) die(13);
return arrayTraps.set.call(this, state, prop, void 0);
};
arrayTraps.set = function(state, prop, value) {
if (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;
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/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
export { createSharedState as t };
import { n as validateDefinitions, r as getRpcHandler, s as hash } from "./validation-CpXFB6Dz.mjs";
import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
import { DEVFRAME_RPC_DUMP_DIRNAME } from "./constants.mjs";
//#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/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/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 { reviveDumpError as a, getDefinitionsWithDumps as i, createClientFromDump as n, serializeDumpError as o, dumpFunctions as r, collectStaticRpcDump as t };
import { t as diagnostics } from "./diagnostics-Di8ytitn.mjs";
import { createSharedState } from "devframe/utils/shared-state";
import process from "node:process";
import { dirname } from "pathe";
import fs from "node:fs";
//#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 safeJsonParse(text) {
return JSON.parse(text, (key, value) => {
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) return void 0;
return value;
});
}
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 = safeJsonParse(fs.readFileSync(options.filepath, "utf-8"));
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.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 { createStorage as t };
import { t as diagnostics } from "./diagnostics-CD8nlgll.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 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/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
export { validateRpcArgs as a, getRpcResolvedSetupResult as i, validateDefinitions as n, validateRpcReturn as o, getRpcHandler as r, hash as s, validateDefinition as t };

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