@devframes/hub 0.8.2 → 0.9.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ import { i as InstallDevframeOptions, n as DevframeHubContext, t as CreateHubContextOptions } from "../context-Dz0j8Eyg.mjs";
2
+ import { DevframeInstanceRecord } from "devframe/internal";
3
+ import { ConnectionMeta, DevframeDefinition, DevframeSseOptions, DevframeStorageScope, DevframeWsOptions, McpRouteOptions } from "devframe/types";
4
+ import { DevframeAuthHandler } from "devframe/node/auth";
5
+ import { WsOriginRegistry } from "devframe/rpc/transports/ws-server";
6
+ import { Buffer } from "node:buffer";
7
+ import { IncomingMessage, Server, ServerResponse } from "node:http";
8
+ import { Duplex } from "node:stream";
9
+ //#region src/node/initiate.d.ts
10
+ /** A `devframes` entry with per-mount dock customization. */
11
+ interface HubDevframeEntry {
12
+ devframe: DevframeDefinition;
13
+ /** Per-mount overrides for the auto-synthesized iframe dock entry. */
14
+ dock?: InstallDevframeOptions['dock'];
15
+ }
16
+ type Thenable<T> = T | Promise<T>;
17
+ type Arrayable<T> = T | readonly T[];
18
+ /** Default mount base for a hub instance — one namespace, one catch-all. */
19
+ declare const DEVFRAMES_HUB_BASE = "/__devframes/";
20
+ /**
21
+ * One dock-type → prebuilt renderer-module registration for
22
+ * {@link InitHubOptions.renderers}. The hub serves the module at
23
+ * `<base>__renderers/<type>.mjs` and publishes it in the renderer manifest
24
+ * (the `devframe:dock-renderers` shared-state slot), so any viewer — the
25
+ * reference UI, a community viewer, a hand-rolled host page — lazily imports
26
+ * it the first time a dock of that `type` needs rendering.
27
+ *
28
+ * The module must be a **self-contained browser ES module** (its framework
29
+ * and styles bundled in) whose {@link DockRendererRegistration.importName}
30
+ * export is a ready `DockRenderer`. Renderer packages ship a node helper
31
+ * returning this shape — e.g. `jsonRenderUiRenderer()` from
32
+ * `@devframes/json-render-ui/hub`.
33
+ */
34
+ interface DockRendererRegistration {
35
+ /** Dock `type` this renderer handles (e.g. `'json-render'`). */
36
+ type: string;
37
+ /** Absolute path of the prebuilt, self-contained browser ES module. */
38
+ file: string;
39
+ /**
40
+ * Named export carrying the renderer.
41
+ *
42
+ * @default 'default'
43
+ */
44
+ importName?: string;
45
+ }
46
+ /**
47
+ * The UI slot of a hub instance — pure data, zero policy. The hub itself is
48
+ * headless: whoever fills this slot decides what a viewer looks like.
49
+ * `@devframes/hub-ui` ships the reference implementation (`createUi()`);
50
+ * Vite DevTools or any community viewer supplies its own object to the same
51
+ * slot and reuses all the infrastructure.
52
+ */
53
+ interface DevframeHubUi {
54
+ /**
55
+ * A standalone viewer SPA (built with relative asset paths) served at the
56
+ * hub base itself — open `<base>` in a tab and the devtools are there.
57
+ */
58
+ viewer?: {
59
+ /** Directory of the prebuilt viewer SPA. */
60
+ distDir: string;
61
+ };
62
+ /**
63
+ * A prebuilt, self-contained script served at `<base>embedded.js` — the
64
+ * floating-devtools bootstrap a host page loads with one
65
+ * `<script type="module" src="<base>embedded.js">` tag. Visibility policy
66
+ * (always-on, keyboard-summoned, …) belongs entirely to this entry.
67
+ */
68
+ embedded?: {
69
+ /** File path of the prebuilt single-file module. */
70
+ entry: string;
71
+ };
72
+ /**
73
+ * Extra UI-owned files the hub serves at `<base><key>`, each produced lazily
74
+ * from memory. Keys are base-relative paths (e.g. `branding.json`); the
75
+ * content-type is inferred from the key's extension. A generic seam a viewer
76
+ * uses to publish small runtime documents (the reference UI serves its
77
+ * branding this way) without teaching the hub anything about their meaning.
78
+ */
79
+ assets?: Record<string, () => string | Uint8Array>;
80
+ }
81
+ type DevframesInput = Array<DevframeDefinition | HubDevframeEntry | Thenable<Arrayable<DevframeDefinition | HubDevframeEntry | null | undefined>> | (() => Thenable<Arrayable<DevframeDefinition | HubDevframeEntry | null | undefined>>)>;
82
+ interface InitHubOptions {
83
+ /**
84
+ * Name for the hub instance, used in logs and diagnostics, and mcp server.
85
+ */
86
+ name?: string;
87
+ /**
88
+ * Version for the hub instance, used in logs and diagnostics, and mcp server.
89
+ */
90
+ version?: string;
91
+ /**
92
+ * Mount base the hub answers under — required so the mount path is
93
+ * explicit at the call site (pass the exported {@link DEVFRAMES_HUB_BASE}
94
+ * for the conventional `/__devframes/`). Every mounted devframe lives at
95
+ * `<base><id>/`, so the host app needs exactly one catch-all route. The
96
+ * resolved value is echoed back as {@link HubInstance.base} so route and
97
+ * middleware code references it instead of repeating the string.
98
+ */
99
+ base: string;
100
+ /**
101
+ * Devframes to mount: each runs its `setup()` against the shared hub
102
+ * context (one merged RPC registry, one WebSocket, one auth gate), serves
103
+ * its SPA at `<base><id>/`, and is auto-registered as an iframe dock.
104
+ * Wrap an entry in `{ devframe, dock }` to customize its synthesized dock
105
+ * (category, icon, a `clientScript` to run in the host page, …).
106
+ */
107
+ devframes?: DevframesInput;
108
+ /**
109
+ * Extra RPC declarations registered at context creation, alongside the
110
+ * hub built-ins — forwarded to `createHubContext`'s
111
+ * `builtinRpcDeclarations`. Declarative mode only (a pre-built `context`
112
+ * already made this choice).
113
+ */
114
+ rpcDeclarations?: CreateHubContextOptions['builtinRpcDeclarations'];
115
+ /**
116
+ * Bring your own hub context instead of `devframes` — for hosts that
117
+ * assemble `createHubContext` + `ctx.install` themselves (with their own
118
+ * `DevframeHost` serving the frames). The instance then serves only the
119
+ * hub-level endpoints (`__connection.json`, `__index.json`,
120
+ * `__client-imports.js`, the WS transport, MCP, and the `ui` slot);
121
+ * serve each frame's meta yourself from {@link HubInstance.connectionMeta}.
122
+ */
123
+ context?: DevframeHubContext;
124
+ /**
125
+ * Runs once the context exists and every `devframes` entry is mounted —
126
+ * register docks, commands, terminals, and messages surfaces here.
127
+ */
128
+ configure?: (ctx: DevframeHubContext) => void | Promise<void>;
129
+ /** See {@link DevframeHubUi} — omitted, the hub stays fully headless. */
130
+ ui?: DevframeHubUi;
131
+ /**
132
+ * Prebuilt dock-renderer modules to serve and advertise — the composition
133
+ * seam that hands a renderer package (e.g. `@devframes/json-render-ui`) to
134
+ * a prebuilt viewer. Each {@link DockRendererRegistration} is served at
135
+ * `<base>__renderers/<type>.mjs` and published in the renderer manifest;
136
+ * clients import a module lazily the first time a dock of its `type`
137
+ * mounts. Renderers registered directly in client code
138
+ * (`createDevframeClientHost({ renderers })`) take precedence.
139
+ *
140
+ * ```ts
141
+ * import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
142
+ *
143
+ * initHub({ ui: createUi(), renderers: [jsonRenderUiRenderer()] })
144
+ * ```
145
+ */
146
+ renderers?: readonly DockRendererRegistration[];
147
+ /**
148
+ * Share the host's `node:http` server for the WebSocket RPC endpoint
149
+ * (upgrade bound at `<base>__ws`). Hosts whose handlers never see upgrades
150
+ * (Next.js route handlers, Nitro, Rsbuild) ask for a side-car instead with
151
+ * `ws: { sidecar: true }`; hosts that get their server later wire
152
+ * {@link HubInstance.attach} / {@link HubInstance.handleUpgrade}.
153
+ */
154
+ server?: Server;
155
+ /**
156
+ * Explicit WebSocket control, same contract as `initDevframe`: the local
157
+ * binding resolves `ws.port` (pinned side-car) > `server` (shared upgrade)
158
+ * > `ws.sidecar` (auto-port side-car) > the host driving upgrades itself,
159
+ * while `url` overrides the advertisement (tunnel pattern) and `route`
160
+ * renames the upgrade segment (default `__ws`). Pass `false` to serve no
161
+ * WebSocket at all — clients connect over SSE instead (`backend: 'sse'`).
162
+ */
163
+ ws?: DevframeWsOptions | false;
164
+ /**
165
+ * SSE RPC endpoint control, same contract as `initDevframe` — enabled by
166
+ * default at `<base>__sse` as the more portable transport alongside the
167
+ * WebSocket. Pass `false` to disable, or a {@link DevframeSseOptions} to
168
+ * rename the route.
169
+ */
170
+ sse?: boolean | DevframeSseOptions;
171
+ /** Bind host for a side-car WebSocket server. Default: `localhost`. */
172
+ host?: string;
173
+ /**
174
+ * The hub's **single Auth**: one gate at the one shared transport covers
175
+ * every mounted frame, the MCP route, and the hub built-ins. Gates by
176
+ * default (devframe's interactive OTP); `false` opts out; a
177
+ * {@link DevframeAuthHandler} installs a custom scheme.
178
+ */
179
+ auth?: boolean | DevframeAuthHandler;
180
+ /**
181
+ * Expose the **aggregate** MCP endpoint at `<base>__mcp` — one
182
+ * Streamable-HTTP server over the shared context's whole tool registry
183
+ * (ids are already namespaced per plugin). Disabled by default.
184
+ *
185
+ * @experimental
186
+ */
187
+ mcp?: boolean | McpRouteOptions;
188
+ /**
189
+ * Public origin the host app is reachable at, or a getter. Derived lazily
190
+ * from the first request when omitted.
191
+ */
192
+ origin?: string | (() => string);
193
+ /**
194
+ * Publish this hub in the global instance registry
195
+ * (`~/.devframe/instances/`) so discovery tooling (`devframe connect`, the
196
+ * inspect plugin's Instances tab) lists it like any standalone devframe.
197
+ * Registration is a dynamic import that fires once the public origin
198
+ * resolves and is torn down on {@link HubInstance.close}. Defaults to off;
199
+ * pass `true` to enable, or an object to override individual record fields
200
+ * (`id`, `name`, `basePath`, …).
201
+ */
202
+ register?: boolean | Partial<DevframeInstanceRecord>;
203
+ /** Working directory for the hub context. Default: `process.cwd()`. */
204
+ cwd?: string;
205
+ /** Override where persisted devframe state lives. */
206
+ getStorageDir?: (scope: DevframeStorageScope) => string;
207
+ /** Extra WS-upgrade origins beyond the loopback default; `false` disables the gate. */
208
+ allowedOrigins?: readonly string[] | WsOriginRegistry | false;
209
+ /** Destroy off-route upgrades on a shared `server` devframe's adapter owns outright. */
210
+ destroyUnmatchedUpgrades?: boolean;
211
+ }
212
+ interface HubInstance {
213
+ /**
214
+ * The normalized mount base this hub answers under (leading and trailing
215
+ * slash, e.g. `/__devframes/`). Reference it when wiring the mount — route
216
+ * guards, middleware path checks — instead of repeating the string literal.
217
+ */
218
+ base: string;
219
+ /**
220
+ * Web-standard request handler for the whole hub — mount it on one
221
+ * catch-all route under {@link HubInstance.base}.
222
+ */
223
+ handler: (request: Request) => Promise<Response>;
224
+ /** Connect/Express-style middleware over the same surface; `next()`s outside the base. */
225
+ nodeMiddleware: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
226
+ /**
227
+ * Route a host server's `upgrade` events to the shared RPC socket,
228
+ * returning a detach function — the manual counterpart to the `server`
229
+ * option, for hosts that get their `node:http` server only after the hub
230
+ * exists. Available on the default tier; a configured transport (`server`,
231
+ * `ws.port`, `ws.sidecar`, `ws.url`) already owns the socket and reports
232
+ * `DF0055` / `DF0056` instead.
233
+ */
234
+ attach: (server: Server) => () => void;
235
+ /**
236
+ * Complete a single `upgrade` event on the shared RPC socket, for hosts
237
+ * that already own an `upgrade` listener. Same availability as
238
+ * {@link HubInstance.attach}.
239
+ */
240
+ handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
241
+ /** Resolves once every frame is mounted and the WebSocket binding is live. */
242
+ ready: Promise<void>;
243
+ /** The shared hub context, once initialized. */
244
+ context: Promise<DevframeHubContext>;
245
+ /** The `ConnectionMeta` served at `<base>__connection.json` (and every frame base). */
246
+ connectionMeta: () => ConnectionMeta;
247
+ /** Tear down: WS transport/side-car, MCP sessions. */
248
+ close: () => Promise<void>;
249
+ }
250
+ /**
251
+ * Initiate a hub instance — the whole multi-devframe devtools surface
252
+ * behind one framework-agnostic, web-standard handler. Every mounted
253
+ * devframe shares one context (merged RPC registry, shared state, docks /
254
+ * terminals / messages / commands), one WebSocket transport, and one Auth;
255
+ * the instance serves each frame's SPA at `<base><id>/`, the discovery
256
+ * endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
257
+ * the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
258
+ * provides — the hub itself stays headless.
259
+ *
260
+ * The factory is synchronous and initializes eagerly, and binds no port of
261
+ * its own: the WebSocket follows `server` / `ws` (see
262
+ * {@link InitHubOptions.ws}), or waits for the host to hand upgrades over
263
+ * through {@link HubInstance.attach}.
264
+ */
265
+ declare function initHub(options: InitHubOptions): HubInstance;
266
+ //#endregion
267
+ export { DEVFRAMES_HUB_BASE, DevframeHubUi, DevframesInput, DockRendererRegistration, HubDevframeEntry, HubInstance, InitHubOptions, initHub };
@@ -0,0 +1,271 @@
1
+ import { DOCK_RENDERERS_STATE_KEY } from "../constants.mjs";
2
+ import { t as createHubContext, v as diagnostics } from "../context-DPdojIW8.mjs";
3
+ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
4
+ import { createH3DevframeHost, createInstanceShell, resolveInstanceRegister } from "devframe/internal";
5
+ import { resolve } from "pathe";
6
+ import process from "node:process";
7
+ import { existsSync } from "node:fs";
8
+ import { cleanDoubleSlashes, joinURL, withLeadingSlash, withTrailingSlash, withoutLeadingSlash } from "ufo";
9
+ import { readFile } from "node:fs/promises";
10
+ import { mountStaticHandler } from "devframe/utils/serve-static";
11
+ import { H3 } from "h3";
12
+ //#region src/node/initiate.ts
13
+ function normalizeDevframeEntry(entry) {
14
+ return "devframe" in entry ? entry : { devframe: entry };
15
+ }
16
+ /** Default mount base for a hub instance — one namespace, one catch-all. */
17
+ const DEVFRAMES_HUB_BASE = "/__devframes/";
18
+ /** Content-type for a UI asset key, inferred from its file extension. */
19
+ function assetContentType(key) {
20
+ if (key.endsWith(".json")) return "application/json; charset=utf-8";
21
+ if (key.endsWith(".js") || key.endsWith(".mjs")) return "text/javascript; charset=utf-8";
22
+ if (key.endsWith(".css")) return "text/css; charset=utf-8";
23
+ if (key.endsWith(".svg")) return "image/svg+xml";
24
+ if (key.endsWith(".html")) return "text/html; charset=utf-8";
25
+ return "application/octet-stream";
26
+ }
27
+ /** Reserved filenames directly under the hub base — a frame id can't shadow them. */
28
+ const RESERVED_HUB_PATHS = [
29
+ DEVFRAME_CONNECTION_META_FILENAME,
30
+ DEVFRAME_DOCK_IMPORTS_FILENAME,
31
+ DEVFRAME_WS_ROUTE,
32
+ DEVFRAME_MCP_ROUTE,
33
+ "__index.json",
34
+ "__renderers",
35
+ "embedded.js"
36
+ ];
37
+ /**
38
+ * Flatten the `devframes` input: await every thenable, call every factory,
39
+ * spread the arrays, and drop the empty slots — so a host can build the list
40
+ * conditionally (`isDev && loadInspect()`) without filtering it first.
41
+ */
42
+ function resolveDevframesInput(input) {
43
+ return Promise.all(input.map(async (entry) => {
44
+ const resolved = await (typeof entry === "function" ? entry() : entry);
45
+ return (Array.isArray(resolved) ? resolved : [resolved]).filter((slot) => slot != null).map(normalizeDevframeEntry);
46
+ })).then((arrays) => arrays.flat());
47
+ }
48
+ function normalizeBase(base) {
49
+ return cleanDoubleSlashes(withTrailingSlash(withLeadingSlash(base)));
50
+ }
51
+ /**
52
+ * Validate the renderer-module registrations fail-fast: route-safe types
53
+ * (each becomes the `<base>__renderers/<type>.mjs` URL segment), one module
54
+ * per type, and an existing bundle file (renderer packages are prebuilt).
55
+ */
56
+ function resolveRendererRegistrations(registrations) {
57
+ const seen = /* @__PURE__ */ new Set();
58
+ return registrations.map((registration) => {
59
+ if (!/^[\w.-]+$/.test(registration.type)) throw diagnostics.DF8110({ type: registration.type });
60
+ if (seen.has(registration.type)) throw diagnostics.DF8108({ type: registration.type });
61
+ seen.add(registration.type);
62
+ const file = resolve(registration.file);
63
+ if (!existsSync(file)) throw diagnostics.DF8109({
64
+ type: registration.type,
65
+ file
66
+ });
67
+ return {
68
+ ...registration,
69
+ file
70
+ };
71
+ });
72
+ }
73
+ /**
74
+ * Render the dock client-script import map as an ES module — one dynamic
75
+ * import thunk per dock that carries a client script (`clientScript` on
76
+ * iframe docks, `action`, `renderer`). External viewers import this module
77
+ * from `<base>__client-imports.js` to load per-dock client code into the
78
+ * host page; `importFrom` values must be URL paths the host serves.
79
+ */
80
+ function renderClientImportsModule(ctx) {
81
+ const entries = [];
82
+ for (const [id, view] of ctx.docks.views) {
83
+ const scripts = [];
84
+ const anyView = view;
85
+ if (anyView.clientScript) scripts.push(anyView.clientScript);
86
+ if (anyView.action) scripts.push(anyView.action);
87
+ if (anyView.renderer) scripts.push(anyView.renderer);
88
+ if (scripts.length === 0) continue;
89
+ const thunks = scripts.map((script) => `() => import(${JSON.stringify(script.importFrom)})`);
90
+ entries.push(` ${JSON.stringify(id)}: [${thunks.join(", ")}],`);
91
+ }
92
+ return `// Generated by @devframes/hub — dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
93
+ }
94
+ /**
95
+ * Initiate a hub instance — the whole multi-devframe devtools surface
96
+ * behind one framework-agnostic, web-standard handler. Every mounted
97
+ * devframe shares one context (merged RPC registry, shared state, docks /
98
+ * terminals / messages / commands), one WebSocket transport, and one Auth;
99
+ * the instance serves each frame's SPA at `<base><id>/`, the discovery
100
+ * endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
101
+ * the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
102
+ * provides — the hub itself stays headless.
103
+ *
104
+ * The factory is synchronous and initializes eagerly, and binds no port of
105
+ * its own: the WebSocket follows `server` / `ws` (see
106
+ * {@link InitHubOptions.ws}), or waits for the host to hand upgrades over
107
+ * through {@link HubInstance.attach}.
108
+ */
109
+ function initHub(options) {
110
+ const base = normalizeBase(options.base);
111
+ const baseNoSlash = base.slice(0, -1);
112
+ const app = new H3();
113
+ const cwd = options.cwd ?? process.cwd();
114
+ const frames = [];
115
+ const rendererRegistrations = resolveRendererRegistrations(options.renderers ?? []);
116
+ const shell = createInstanceShell({
117
+ base,
118
+ app,
119
+ host: options.host,
120
+ origin: options.origin,
121
+ auth: options.auth,
122
+ server: options.server,
123
+ ws: options.ws,
124
+ sse: options.sse,
125
+ allowedOrigins: options.allowedOrigins,
126
+ destroyUnmatchedUpgrades: options.destroyUnmatchedUpgrades,
127
+ register: resolveInstanceRegister(options.register, {
128
+ id: options.name ?? "devframes-hub",
129
+ ...options.name !== void 0 ? { name: options.name } : {},
130
+ rootDir: cwd
131
+ }),
132
+ absoluteWsPath: true,
133
+ resolveSidecarPort: async (sidecarHost) => {
134
+ const { getPort } = await import("../dist-R0T2g9Vw.mjs");
135
+ return getPort({
136
+ port: 9777,
137
+ portRange: [9777, 9877],
138
+ host: sidecarHost
139
+ });
140
+ },
141
+ onMetaUnavailable: () => {
142
+ throw diagnostics.DF8003();
143
+ },
144
+ async init(api) {
145
+ if (options.context && options.devframes?.length) throw diagnostics.DF8002();
146
+ let ctx;
147
+ if (options.context) ctx = options.context;
148
+ else {
149
+ const host = {
150
+ ...createH3DevframeHost({
151
+ origin: () => api.origin() ?? "http://localhost",
152
+ appName: "devframes",
153
+ workspaceRoot: cwd,
154
+ mount: (mountBase, dir) => {
155
+ mountStaticHandler(app, mountBase, dir);
156
+ }
157
+ }),
158
+ ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
159
+ mountConnectionMeta: (frameBase) => {
160
+ app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
161
+ }
162
+ };
163
+ ctx = await createHubContext({
164
+ cwd,
165
+ workspaceRoot: cwd,
166
+ mode: "dev",
167
+ host,
168
+ ...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
169
+ });
170
+ }
171
+ const devframes = await resolveDevframesInput(options.devframes ?? []);
172
+ for (const { devframe: def, dock } of devframes) {
173
+ if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
174
+ if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
175
+ const frameBase = withTrailingSlash(joinURL(base, def.id));
176
+ await ctx.install(def, {
177
+ base: frameBase,
178
+ ...dock ? { dock } : {}
179
+ });
180
+ frames.push({
181
+ id: def.id,
182
+ base: frameBase,
183
+ title: def.name
184
+ });
185
+ }
186
+ await options.configure?.(ctx);
187
+ if (rendererRegistrations.length > 0) {
188
+ const manifest = {};
189
+ for (const registration of rendererRegistrations) manifest[registration.type] = {
190
+ importFrom: joinURL(base, "__renderers", `${registration.type}.mjs`),
191
+ ...registration.importName ? { importName: registration.importName } : {}
192
+ };
193
+ (await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
194
+ }
195
+ const mcpConfig = options.mcp === true ? {} : options.mcp;
196
+ if (!mcpConfig) return { context: ctx };
197
+ const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? DEVFRAME_MCP_ROUTE);
198
+ const { mountMcpHttp } = await import("devframe/adapters/mcp");
199
+ const mounted = mountMcpHttp(app, ctx, joinURL(base, mcpRoute), {
200
+ serverName: options.name ?? "devframes-hub",
201
+ serverVersion: options.version ?? "0.0.0",
202
+ exposeSharedState: true,
203
+ allowedOrigins: mcpConfig.allowedOrigins
204
+ });
205
+ return {
206
+ context: ctx,
207
+ mcp: { path: mcpRoute },
208
+ dispose: mounted.dispose
209
+ };
210
+ },
211
+ mount(ctx, meta) {
212
+ app.use(joinURL(base, DEVFRAME_CONNECTION_META_FILENAME), () => meta);
213
+ const indexDocument = () => ({
214
+ name: options.name,
215
+ version: options.version,
216
+ base,
217
+ frames,
218
+ endpoints: {
219
+ connection: DEVFRAME_CONNECTION_META_FILENAME,
220
+ clientImports: DEVFRAME_DOCK_IMPORTS_FILENAME,
221
+ index: "__index.json",
222
+ websocket: meta.websocket,
223
+ ...meta.mcp ? { mcp: meta.mcp.path } : {},
224
+ ...options.ui?.embedded ? { embedded: "embedded.js" } : {}
225
+ }
226
+ });
227
+ app.use(joinURL(base, "__index.json"), () => indexDocument());
228
+ app.use(joinURL(base, DEVFRAME_DOCK_IMPORTS_FILENAME), (event) => {
229
+ event.res.headers.set("Content-Type", "text/javascript; charset=utf-8");
230
+ event.res.headers.set("Cache-Control", "no-store");
231
+ return renderClientImportsModule(ctx);
232
+ });
233
+ if (options.ui?.embedded) {
234
+ const entry = resolve(options.ui.embedded.entry);
235
+ app.use(joinURL(base, "embedded.js"), async (event) => {
236
+ event.res.headers.set("Content-Type", "text/javascript; charset=utf-8");
237
+ event.res.headers.set("Cache-Control", "no-store");
238
+ return await readFile(entry);
239
+ });
240
+ }
241
+ for (const registration of rendererRegistrations) app.use(joinURL(base, "__renderers", `${registration.type}.mjs`), async (event) => {
242
+ event.res.headers.set("Content-Type", "text/javascript; charset=utf-8");
243
+ event.res.headers.set("Cache-Control", "no-store");
244
+ return await readFile(registration.file);
245
+ });
246
+ for (const [key, produce] of Object.entries(options.ui?.assets ?? {})) app.use(joinURL(base, key), (event) => {
247
+ event.res.headers.set("Content-Type", assetContentType(key));
248
+ event.res.headers.set("Cache-Control", "no-store");
249
+ return produce();
250
+ });
251
+ if (options.ui?.viewer) mountStaticHandler(app, base, resolve(options.ui.viewer.distDir));
252
+ else {
253
+ app.use(baseNoSlash, () => indexDocument());
254
+ app.use(base, () => indexDocument());
255
+ }
256
+ }
257
+ });
258
+ return {
259
+ base: shell.base,
260
+ handler: shell.handler,
261
+ nodeMiddleware: shell.nodeMiddleware,
262
+ attach: shell.attach,
263
+ handleUpgrade: shell.handleUpgrade,
264
+ ready: shell.ready,
265
+ context: shell.context,
266
+ connectionMeta: shell.connectionMeta,
267
+ close: shell.close
268
+ };
269
+ }
270
+ //#endregion
271
+ export { DEVFRAMES_HUB_BASE, initHub };
@@ -0,0 +1,13 @@
1
+ import { s as DevframeCommandShortcutOverrides } from "./commands-CAcTUSlj.mjs";
2
+ //#region src/types/settings.d.ts
3
+ interface DevframeDocksUserSettings {
4
+ docksHidden: string[];
5
+ docksCategoriesHidden: string[];
6
+ docksPinned: string[];
7
+ docksCustomOrder: Record<string, number>;
8
+ showIframeAddressBar: boolean;
9
+ closeOnOutsideClick: boolean;
10
+ commandShortcuts: DevframeCommandShortcutOverrides;
11
+ }
12
+ //#endregion
13
+ export { DevframeDocksUserSettings as t };
@@ -1,4 +1,5 @@
1
- import { A as DevframeViewLauncher, C as DevframeDocksHost, D as DevframeViewGroup, E as DevframeViewCustomRender, F as RemoteDockOptions, I as JsonRenderElement, L as JsonRenderSpec, M as FrameSubTabsConfig, N as NavTarget, O as DevframeViewIframe, P as RemoteConnectionInfo, R as JsonRenderer, S as DevframeDocksActiveState, T as DevframeViewBuiltin, _ as DevframeDockEntryBase, a as DevframeCommandEntry, b as DevframeDockEntryRegistry, c as DevframeCommandShortcutOverrides, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, g as DevframeDockEntry, h as DevframeDockEntriesGrouped, i as DevframeCommandBase, j as DevframeViewLauncherStatus, k as DevframeViewJsonRender, l as DevframeCommandsHost, m as DevframeDockActivation, n as DevframeClientCommand, o as DevframeCommandHandle, p as ClientScriptEntry, r as DevframeCommandAgentOptions, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings, u as DevframeCommandsHostEvents, v as DevframeDockEntryCategory, w as DevframeViewAction, x as DevframeDockUserEntry, y as DevframeDockEntryIcon } from "../settings-Byh48aCd.mjs";
2
- import { C as DevframeMessageShortcutInput, D as DevframeMessagesListDelta, E as DevframeMessagesLevelShortcuts, S as DevframeMessageLevel, T as DevframeMessagesHost, _ as DevframeMessageEntry, a as DevframeChildProcessOutput, b as DevframeMessageFilePosition, c as DevframePtyExecuteOptions, d as DevframeTerminalSessionBase, f as DevframeTerminalStatus, g as DevframeMessageElementPosition, h as DevframeMessageActivateAction, i as DevframeChildProcessExecuteOptions, l as DevframePtyTerminalSession, m as DevframeMessageAction, n as DevframeHubContext, o as DevframeChildProcessResult, p as DevframeTerminalsHost, s as DevframeChildProcessTerminalSession, t as CreateHubContextOptions, u as DevframeTerminalSession, v as DevframeMessageEntryFrom, w as DevframeMessagesClient, x as DevframeMessageHandle, y as DevframeMessageEntryInput } from "../context-BSgzLnsu.mjs";
3
- import { C as RpcStreamingChannel, E as Thenable, S as RpcSharedStateHost, T as RpcStreamingHost, _ as RpcBroadcastOptions, a as DevframeDiagnosticsLogger, b as RpcFunctionsHost, c as DevframeRpcClientFunctions, d as DevframeViewHost, f as EntriesToObject, g as PartialWithoutId, h as EventsMap, i as DevframeDiagnosticsHost, l as DevframeRpcServerFunctions, m as EventUnsubscribe, n as DevframeCapabilities, o as DevframeHost, p as EventEmitter, r as DevframeDiagnosticsDefinition, s as DevframeNodeRpcSession, t as ConnectionMeta, u as DevframeRpcSharedStates, v as RpcDefinitionsFilter, w as RpcStreamingChannelOptions, x as RpcSharedStateGetOptions, y as RpcDefinitionsToFunctions } from "../index-IzywMKfP.mjs";
4
- export { ClientScriptEntry, type ConnectionMeta, type CreateHubContextOptions, type DevframeCapabilities, DevframeChildProcessExecuteOptions, DevframeChildProcessOutput, DevframeChildProcessResult, DevframeChildProcessTerminalSession, DevframeClientCommand, DevframeCommandAgentOptions, DevframeCommandBase, DevframeCommandEntry, DevframeCommandHandle, DevframeCommandKeybinding, DevframeCommandShortcutOverrides, DevframeCommandsHost, DevframeCommandsHostEvents, type DevframeDiagnosticsDefinition, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, DevframeDockActivation, DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockEntryBase, DevframeDockEntryCategory, DevframeDockEntryIcon, DevframeDockEntryRegistry, DevframeDockUserEntry, DevframeDocksActiveState, DevframeDocksHost, DevframeDocksUserSettings, type DevframeHost, type DevframeHubContext, DevframeMessageAction, DevframeMessageActivateAction, DevframeMessageElementPosition, DevframeMessageEntry, DevframeMessageEntryFrom, DevframeMessageEntryInput, DevframeMessageFilePosition, DevframeMessageHandle, DevframeMessageLevel, DevframeMessageShortcutInput, DevframeMessagesClient, DevframeMessagesHost, DevframeMessagesLevelShortcuts, DevframeMessagesListDelta, type DevframeNodeRpcSession, DevframePtyExecuteOptions, DevframePtyTerminalSession, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, DevframeServerCommandEntry, DevframeServerCommandInput, DevframeTerminalSession, DevframeTerminalSessionBase, DevframeTerminalStatus, DevframeTerminalsHost, DevframeViewAction, DevframeViewBuiltin, DevframeViewCustomRender, DevframeViewGroup, type DevframeViewHost, DevframeViewIframe, DevframeViewJsonRender, DevframeViewLauncher, DevframeViewLauncherStatus, type EntriesToObject, type EventEmitter, type EventUnsubscribe, type EventsMap, FrameSubTabsConfig, JsonRenderElement, JsonRenderSpec, JsonRenderer, NavTarget, type PartialWithoutId, RemoteConnectionInfo, RemoteDockOptions, type RpcBroadcastOptions, type RpcDefinitionsFilter, type RpcDefinitionsToFunctions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost, type Thenable };
1
+ import { C as DevframeMessageHandle, D as DevframeMessagesHost, E as DevframeMessagesClient, O as DevframeMessagesLevelShortcuts, S as DevframeMessageFilePosition, T as DevframeMessageShortcutInput, _ as DevframeMessageCommandAction, a as DevframeChildProcessExecuteOptions, b as DevframeMessageEntryFrom, c as DevframeChildProcessTerminalSession, d as DevframeTerminalSession, f as DevframeTerminalSessionBase, g as DevframeMessageActivateAction, h as DevframeMessageAction, k as DevframeMessagesListDelta, l as DevframePtyExecuteOptions, m as DevframeTerminalsHost, n as DevframeHubContext, o as DevframeChildProcessOutput, p as DevframeTerminalStatus, s as DevframeChildProcessResult, t as CreateHubContextOptions, u as DevframePtyTerminalSession, v as DevframeMessageElementPosition, w as DevframeMessageLevel, x as DevframeMessageEntryInput, y as DevframeMessageEntry } from "../context-Dz0j8Eyg.mjs";
2
+ import { A as FrameSubTabsConfig, C as DevframeViewAction, D as DevframeViewIframe, E as DevframeViewGroup, M as RemoteConnectionInfo, N as RemoteDockOptions, O as DevframeViewLauncher, S as DevframeDocksHost, T as DevframeViewCustomRender, _ as DevframeDockEntryCategory, a as DevframeCommandHandle, b as DevframeDockUserEntry, c as DevframeCommandsHost, d as DevframeServerCommandInput, f as ClientScriptEntry, g as DevframeDockEntryBase, h as DevframeDockEntry, i as DevframeCommandEntry, j as NavTarget, k as DevframeViewLauncherStatus, l as DevframeCommandsHostEvents, m as DevframeDockEntriesGrouped, n as DevframeCommandAgentOptions, o as DevframeCommandKeybinding, p as DevframeDockActivation, r as DevframeCommandBase, s as DevframeCommandShortcutOverrides, t as DevframeClientCommand, u as DevframeServerCommandEntry, v as DevframeDockEntryIcon, w as DevframeViewBuiltin, x as DevframeDocksActiveState, y as DevframeDockEntryRegistry } from "../commands-CAcTUSlj.mjs";
3
+ import { t as DevframeDocksUserSettings } from "../settings-B1qJ47d5.mjs";
4
+ import { C as RpcStreamingChannel, E as Thenable, S as RpcSharedStateHost, T as RpcStreamingHost, _ as RpcBroadcastOptions, a as DevframeDiagnosticsLogger, b as RpcFunctionsHost, c as DevframeRpcClientFunctions, d as DevframeViewHost, f as EntriesToObject, g as PartialWithoutId, h as EventsMap, i as DevframeDiagnosticsHost, l as DevframeRpcServerFunctions, m as EventUnsubscribe, n as DevframeCapabilities, o as DevframeHost, p as EventEmitter, r as DevframeDiagnosticsDefinition, s as DevframeNodeRpcSession, t as ConnectionMeta, u as DevframeRpcSharedStates, v as RpcDefinitionsFilter, w as RpcStreamingChannelOptions, x as RpcSharedStateGetOptions, y as RpcDefinitionsToFunctions } from "../index-p4BktGvQ.mjs";
5
+ export { ClientScriptEntry, type ConnectionMeta, type CreateHubContextOptions, type DevframeCapabilities, DevframeChildProcessExecuteOptions, DevframeChildProcessOutput, DevframeChildProcessResult, DevframeChildProcessTerminalSession, DevframeClientCommand, DevframeCommandAgentOptions, DevframeCommandBase, DevframeCommandEntry, DevframeCommandHandle, DevframeCommandKeybinding, DevframeCommandShortcutOverrides, DevframeCommandsHost, DevframeCommandsHostEvents, type DevframeDiagnosticsDefinition, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, DevframeDockActivation, DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockEntryBase, DevframeDockEntryCategory, DevframeDockEntryIcon, DevframeDockEntryRegistry, DevframeDockUserEntry, DevframeDocksActiveState, DevframeDocksHost, DevframeDocksUserSettings, type DevframeHost, type DevframeHubContext, DevframeMessageAction, DevframeMessageActivateAction, DevframeMessageCommandAction, DevframeMessageElementPosition, DevframeMessageEntry, DevframeMessageEntryFrom, DevframeMessageEntryInput, DevframeMessageFilePosition, DevframeMessageHandle, DevframeMessageLevel, DevframeMessageShortcutInput, DevframeMessagesClient, DevframeMessagesHost, DevframeMessagesLevelShortcuts, DevframeMessagesListDelta, type DevframeNodeRpcSession, DevframePtyExecuteOptions, DevframePtyTerminalSession, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, DevframeServerCommandEntry, DevframeServerCommandInput, DevframeTerminalSession, DevframeTerminalSessionBase, DevframeTerminalStatus, DevframeTerminalsHost, DevframeViewAction, DevframeViewBuiltin, DevframeViewCustomRender, DevframeViewGroup, type DevframeViewHost, DevframeViewIframe, DevframeViewLauncher, DevframeViewLauncherStatus, type EntriesToObject, type EventEmitter, type EventUnsubscribe, type EventsMap, FrameSubTabsConfig, NavTarget, type PartialWithoutId, RemoteConnectionInfo, RemoteDockOptions, type RpcBroadcastOptions, type RpcDefinitionsFilter, type RpcDefinitionsToFunctions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost, type Thenable };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@devframes/hub",
3
3
  "type": "module",
4
- "version": "0.8.2",
5
- "description": "Framework-neutral hub layer for devframe — docks, terminals, messages, commands.",
4
+ "version": "0.9.0-beta.1",
5
+ "description": "Hub layer that orchestrates devframe docks, terminals, messages, and commands on any host.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/devframes/devframe#readme",
@@ -24,6 +24,7 @@
24
24
  ".": "./dist/index.mjs",
25
25
  "./client": "./dist/client/index.mjs",
26
26
  "./constants": "./dist/constants.mjs",
27
+ "./initiate": "./dist/node/initiate.mjs",
27
28
  "./node": "./dist/node/index.mjs",
28
29
  "./types": "./dist/types/index.mjs",
29
30
  "./package.json": "./package.json"
@@ -33,23 +34,26 @@
33
34
  "dist"
34
35
  ],
35
36
  "peerDependencies": {
36
- "devframe": "0.8.2"
37
+ "devframe": "0.9.0-beta.1"
37
38
  },
38
39
  "dependencies": {
39
40
  "@standard-schema/spec": "^1.1.0",
40
41
  "destr": "^2.0.5",
42
+ "h3": "^2.0.1-rc.26",
41
43
  "nostics": "^1.2.0",
42
44
  "pathe": "^2.0.3",
43
45
  "perfect-debounce": "^2.1.0",
44
46
  "tinyexec": "^1.3.0",
47
+ "ufo": "^1.6.4",
45
48
  "zigpty": "^0.2.1"
46
49
  },
47
50
  "devDependencies": {
48
- "@types/node": "^26.1.2",
51
+ "@types/node": "^26.2.0",
52
+ "get-port-please": "^3.2.0",
49
53
  "mlly": "^1.8.2",
50
54
  "tsdown": "^0.22.14",
51
55
  "valibot": "^1.4.2",
52
- "devframe": "0.8.2"
56
+ "devframe": "0.9.0-beta.1"
53
57
  },
54
58
  "scripts": {
55
59
  "build": "tsdown",
@@ -1,21 +0,0 @@
1
- import { createDefineWrapperWithContext } from "devframe/rpc";
2
- //#region src/define.ts
3
- const defineHubRpcFunction = createDefineWrapperWithContext();
4
- function defineCommand(command) {
5
- return command;
6
- }
7
- function defineDockEntry(entry) {
8
- return entry;
9
- }
10
- /**
11
- * @deprecated json-render moved out of the hub into the opt-in
12
- * `@devframes/json-render` integration in 0.7. This identity helper is kept
13
- * so existing imports keep compiling — pass your spec directly to
14
- * `createJsonRenderView` (from `@devframes/json-render/node`) instead.
15
- * Removed in 0.8.
16
- */
17
- function defineJsonRenderSpec(spec) {
18
- return spec;
19
- }
20
- //#endregion
21
- export { defineJsonRenderSpec as i, defineDockEntry as n, defineHubRpcFunction as r, defineCommand as t };