devframe 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_shared-BAGBgTUO.d.mts → _shared-BPovvJW8.d.mts} +1 -1
- package/dist/adapters/build.d.mts +11 -2
- package/dist/adapters/build.mjs +40 -4
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +1 -1
- package/dist/adapters/dev.d.mts +4 -4
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +3 -3
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/cli/main.mjs +2 -2
- package/dist/client/index.d.mts +5 -5
- package/dist/{context-af2w_F0_.mjs → context-BUJGvlje.mjs} +1 -1
- package/dist/{context-al99DDJ8.d.mts → context-CO7eZHJy.d.mts} +1 -1
- package/dist/{context-riPPHGiu.mjs → context-CQW0lSw8.mjs} +10 -5
- package/dist/{devframe-mbfgpQQC.d.mts → context-_i51nYOs.d.mts} +1199 -1170
- package/dist/{dev-BKm_HisC.mjs → dev-lpIvwagf.mjs} +5 -5
- package/dist/{diagnostics-DI1HGj2I.mjs → diagnostics-BXWW3VK-.mjs} +4 -0
- package/dist/{host-agent-DVDLqjvj.mjs → host-agent-B84av916.mjs} +1 -1
- package/dist/{http-POepPnaQ.mjs → http-BKiqHcXX.mjs} +2 -2
- package/dist/{index-Dipgo9ji.d.mts → index-CEvd-YXD.d.mts} +2 -2
- package/dist/{index-tdrk1bzV.d.mts → index-DOg4S5wk.d.mts} +1 -1
- package/dist/index.d.mts +6 -6
- package/dist/{instance-registry-Dy28drtm.mjs → instance-registry-BjlMQbFB.mjs} +1 -1
- package/dist/{instance-shell-GqTY93gC.mjs → instance-shell-B-uevnK-.mjs} +4 -4
- package/dist/{instance-shell-mU5j5xWn.d.mts → instance-shell-BHvBIGP4.d.mts} +3 -3
- package/dist/internal/index.d.mts +11 -5
- package/dist/internal/index.mjs +5 -5
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +1 -1
- package/dist/node/index.d.mts +3 -3
- package/dist/node/index.mjs +2 -2
- package/dist/recipes/common-rpc-functions.d.mts +3 -3
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +1 -1
- package/dist/{remote-assets-CqxiyltC.mjs → remote-assets-DezQmPQU.mjs} +1 -1
- package/dist/rpc/dump.d.mts +1 -1
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-server.d.mts +2 -2
- package/dist/rpc/transports/ws-bun.d.mts +2 -2
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-deno.d.mts +2 -2
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/{rpc-core-dV69u4lY.d.mts → rpc-core-CDZoGYiD.d.mts} +3 -3
- package/dist/{rpc-core-DRYnbxdD.mjs → rpc-core-DlHd276T.mjs} +1 -1
- package/dist/{sse-client-CFBOIaUi.d.mts → sse-client-CdGD_B8_.d.mts} +1 -1
- package/dist/{storage-BNqSAOxA.mjs → storage-BXNVhKyR.mjs} +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/{types-Djq7CBXh.d.mts → types-BBLeGfpt.d.mts} +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/{ws-client-DhjNyQo3.d.mts → ws-client-Bo2t6hES.d.mts} +1 -1
- package/dist/{ws-server-D1d3QM9f.d.mts → ws-server-DLtEoLdR.d.mts} +1 -1
- package/package.json +1 -1
|
@@ -1,50 +1,11 @@
|
|
|
1
|
-
import { d as RpcFunctionsCollectorBase } from "./index-
|
|
2
|
-
import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-
|
|
3
|
-
import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-
|
|
4
|
-
import { m as defineDiagnostics } from "./nostics-SPGDgHEP.mjs";
|
|
1
|
+
import { d as RpcFunctionsCollectorBase } from "./index-DOg4S5wk.mjs";
|
|
2
|
+
import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-BBLeGfpt.mjs";
|
|
3
|
+
import { d as DevframeRpcConnection, u as DevframeNodeRpcSessionMeta } from "./ws-server-DLtEoLdR.mjs";
|
|
5
4
|
import { a as RemoteAssetsStore, o as StaticAssetsSource } from "./remote-assets-Bg4gCUZ_.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
import { m as defineDiagnostics } from "./nostics-SPGDgHEP.mjs";
|
|
8
6
|
import { BirpcReturn } from "birpc";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Schema map for typed CLI flags. Keys are flag names in camelCase —
|
|
12
|
-
* this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
|
|
13
|
-
* value is any [Standard Schema](https://standardschema.dev/) validator
|
|
14
|
-
* (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
|
|
15
|
-
* derive the CAC option type when the flag is registered and (b) validate
|
|
16
|
-
* the parsed value before it's forwarded to `setup(ctx, { flags })`.
|
|
17
|
-
*/
|
|
18
|
-
type CliFlagsSchema = Record<string, StandardSchemaV1>;
|
|
19
|
-
/**
|
|
20
|
-
* Identity helper that preserves the literal schema-map type — use this
|
|
21
|
-
* so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
|
|
22
|
-
*
|
|
23
|
-
* ```ts
|
|
24
|
-
* const appFlags = defineCliFlags({
|
|
25
|
-
* depth: v.pipe(v.number(), v.integer()),
|
|
26
|
-
* config: v.optional(v.string()),
|
|
27
|
-
* })
|
|
28
|
-
*
|
|
29
|
-
* defineDevframe({
|
|
30
|
-
* cli: { flags: appFlags },
|
|
31
|
-
* setup(ctx, info) {
|
|
32
|
-
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
33
|
-
* flags.depth // number
|
|
34
|
-
* flags.config // string | undefined
|
|
35
|
-
* },
|
|
36
|
-
* })
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
|
|
40
|
-
/** Extract the parsed-output type from a {@link CliFlagsSchema}. */
|
|
41
|
-
type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
|
|
42
|
-
/** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
|
|
43
|
-
declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
|
|
44
|
-
flags: Record<string, unknown>;
|
|
45
|
-
issues?: string[];
|
|
46
|
-
};
|
|
47
|
-
//#endregion
|
|
7
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
8
|
+
import { CAC } from "cac";
|
|
48
9
|
//#region src/types/events.d.ts
|
|
49
10
|
interface EventsMap {
|
|
50
11
|
[event: string]: any;
|
|
@@ -313,235 +274,144 @@ interface DevframeAgentHost {
|
|
|
313
274
|
getResource: (id: string) => AgentResource | undefined;
|
|
314
275
|
}
|
|
315
276
|
//#endregion
|
|
316
|
-
//#region src/
|
|
317
|
-
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
318
|
-
type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
|
|
319
|
-
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
320
|
-
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
321
|
-
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
322
|
-
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
|
|
277
|
+
//#region src/adapters/flags.d.ts
|
|
323
278
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
279
|
+
* Schema map for typed CLI flags. Keys are flag names in camelCase —
|
|
280
|
+
* this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
|
|
281
|
+
* value is any [Standard Schema](https://standardschema.dev/) validator
|
|
282
|
+
* (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
|
|
283
|
+
* derive the CAC option type when the flag is registered and (b) validate
|
|
284
|
+
* the parsed value before it's forwarded to `setup(ctx, { flags })`.
|
|
326
285
|
*/
|
|
327
|
-
|
|
328
|
-
op: 'add' | 'remove' | 'replace';
|
|
329
|
-
path: readonly (string | number)[];
|
|
330
|
-
value?: unknown;
|
|
331
|
-
}
|
|
286
|
+
type CliFlagsSchema = Record<string, StandardSchemaV1>;
|
|
332
287
|
/**
|
|
333
|
-
*
|
|
288
|
+
* Identity helper that preserves the literal schema-map type — use this
|
|
289
|
+
* so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
|
|
290
|
+
*
|
|
291
|
+
* ```ts
|
|
292
|
+
* const appFlags = defineCliFlags({
|
|
293
|
+
* depth: v.pipe(v.number(), v.integer()),
|
|
294
|
+
* config: v.optional(v.string()),
|
|
295
|
+
* })
|
|
296
|
+
*
|
|
297
|
+
* defineDevframe({
|
|
298
|
+
* cli: { flags: appFlags },
|
|
299
|
+
* setup(ctx, info) {
|
|
300
|
+
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
301
|
+
* flags.depth // number
|
|
302
|
+
* flags.config // string | undefined
|
|
303
|
+
* },
|
|
304
|
+
* })
|
|
305
|
+
* ```
|
|
334
306
|
*/
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
307
|
+
declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
|
|
308
|
+
/** Extract the parsed-output type from a {@link CliFlagsSchema}. */
|
|
309
|
+
type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
|
|
310
|
+
/** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
|
|
311
|
+
declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
|
|
312
|
+
flags: Record<string, unknown>;
|
|
313
|
+
issues?: string[];
|
|
314
|
+
};
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/node/auth/handler.d.ts
|
|
317
|
+
/**
|
|
318
|
+
* A ready-made pre-auth RPC handler, as produced by
|
|
319
|
+
* `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
|
|
320
|
+
* everything a host adapter needs to wire an authenticated server:
|
|
321
|
+
* the handshake RPC functions, the resolver gate, the connect-time trust
|
|
322
|
+
* hook, and the startup banner.
|
|
323
|
+
*
|
|
324
|
+
* `initDevframe` / `initHub` accept one of these directly via their `auth` option —
|
|
325
|
+
* see {@link https://devfra.me | devframe}'s server docs — or a host can
|
|
326
|
+
* wire the four pieces itself against a lower-level transport.
|
|
327
|
+
*/
|
|
328
|
+
interface DevframeAuthHandler {
|
|
344
329
|
/**
|
|
345
|
-
*
|
|
330
|
+
* `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
|
|
331
|
+
* handshake) and `devframe:auth:revoke` (self-revoke) — register these on
|
|
332
|
+
* the RPC host (e.g. `rpcHost.register(fn)` for each).
|
|
346
333
|
*/
|
|
347
|
-
|
|
334
|
+
rpcFunctions: RpcFunctionDefinitionAny[];
|
|
348
335
|
/**
|
|
349
|
-
*
|
|
336
|
+
* Resolver gate: whether `methodName` is callable given `session`'s
|
|
337
|
+
* current trust state. Defaults to allowing any `anonymous:`-prefixed
|
|
338
|
+
* method (see `isAnonymousRpcMethod`) plus anything once the session is
|
|
339
|
+
* trusted.
|
|
350
340
|
*/
|
|
351
|
-
|
|
341
|
+
authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
|
|
352
342
|
/**
|
|
353
|
-
*
|
|
343
|
+
* Connect-time trust: reads a bearer token off the connection's initial
|
|
344
|
+
* request (a static/pre-shared token from `clientAuthTokens`, or a token
|
|
345
|
+
* minted by the code exchange) and, when valid, marks the session
|
|
346
|
+
* trusted immediately — before the client's own handshake call.
|
|
354
347
|
*/
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
interface SharedStateEvents<T> {
|
|
358
|
-
updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
|
|
359
|
-
}
|
|
360
|
-
interface SharedStateOptions<T> {
|
|
348
|
+
onConnect: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
|
|
361
349
|
/**
|
|
362
|
-
*
|
|
350
|
+
* Print the current one-time code and its magic-link URL. Devframe stays
|
|
351
|
+
* headless — call this yourself once the server is listening. Safe to
|
|
352
|
+
* call repeatedly; it only prints once per code.
|
|
363
353
|
*/
|
|
364
|
-
|
|
354
|
+
printBanner: () => void;
|
|
365
355
|
/**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
356
|
+
* Rewrite a URL the CLI is about to open in the browser (`--open`) so the
|
|
357
|
+
* tab lands already authenticated — e.g. appending the current OTP as a
|
|
358
|
+
* query param. Called with the fully-resolved target URL; return it
|
|
359
|
+
* unchanged to opt out. Optional: a handler that doesn't need this (e.g.
|
|
360
|
+
* one gated by a pre-shared bearer token instead of an interactive code)
|
|
361
|
+
* can omit it and the URL opens as-is.
|
|
369
362
|
*/
|
|
370
|
-
|
|
363
|
+
buildOpenUrl?: (url: string) => string;
|
|
371
364
|
}
|
|
372
|
-
declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
|
|
373
365
|
//#endregion
|
|
374
|
-
//#region src/
|
|
366
|
+
//#region src/types/diagnostics.d.ts
|
|
375
367
|
/**
|
|
376
|
-
*
|
|
377
|
-
*
|
|
368
|
+
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
|
|
369
|
+
* any registered code name to its `nostics` handle (a callable that builds
|
|
370
|
+
* a diagnostic and routes it through registered reporters). Typed loosely
|
|
371
|
+
* because it spans heterogeneous definitions registered by different
|
|
372
|
+
* integrations.
|
|
378
373
|
*/
|
|
379
|
-
|
|
380
|
-
name: string;
|
|
381
|
-
message: string;
|
|
382
|
-
}
|
|
374
|
+
type DevframeDiagnosticsLogger = Record<string, any>;
|
|
383
375
|
/**
|
|
384
|
-
*
|
|
376
|
+
* Host for structured diagnostics — a thin layer over `nostics` that lets
|
|
377
|
+
* integrations register their own coded errors/warnings into a shared
|
|
378
|
+
* registry without taking a direct dependency on `nostics`.
|
|
385
379
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
380
|
+
* Typical usage from a plugin's `setup(ctx)`:
|
|
381
|
+
*
|
|
382
|
+
* ```ts
|
|
383
|
+
* const myDiagnostics = ctx.diagnostics.defineDiagnostics({
|
|
384
|
+
* docsBase: 'https://example.com/errors',
|
|
385
|
+
* codes: {
|
|
386
|
+
* MYP0001: { why: 'Something went wrong' },
|
|
387
|
+
* },
|
|
388
|
+
* })
|
|
389
|
+
* ctx.diagnostics.register(myDiagnostics)
|
|
390
|
+
*
|
|
391
|
+
* // Through the shared lookup (loose typing):
|
|
392
|
+
* throw ctx.diagnostics.logger.MYP0001()
|
|
393
|
+
*
|
|
394
|
+
* // Or directly on the typed handle returned from `defineDiagnostics`:
|
|
395
|
+
* throw myDiagnostics.MYP0001()
|
|
396
|
+
* ```
|
|
389
397
|
*/
|
|
390
|
-
interface
|
|
391
|
-
seq: number;
|
|
392
|
-
chunk: T;
|
|
393
|
-
}
|
|
394
|
-
interface StreamSinkEvents<T> {
|
|
395
|
-
/** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
|
|
396
|
-
chunk: (seq: number, chunk: T) => void;
|
|
397
|
-
/** Terminal — fired exactly once per sink lifetime. */
|
|
398
|
-
end: (error?: StreamErrorPayload) => void;
|
|
399
|
-
}
|
|
400
|
-
interface CreateStreamSinkOptions {
|
|
401
|
-
id?: string;
|
|
398
|
+
interface DevframeDiagnosticsHost {
|
|
402
399
|
/**
|
|
403
|
-
*
|
|
404
|
-
* `
|
|
400
|
+
* Proxy-backed lookup of every registered diagnostic handle by code name.
|
|
401
|
+
* Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
|
|
402
|
+
* diagnostic and routes it through registered reporters; prefix with
|
|
403
|
+
* `throw` to raise. Loosely typed — for autocompletion, keep a reference
|
|
404
|
+
* to the typed result of `defineDiagnostics()` instead.
|
|
405
405
|
*/
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Server-side producer handle. Two equivalent surfaces share one piece of
|
|
410
|
-
* state: the imperative `write/error/close` triple, and a `WritableStream<T>`
|
|
411
|
-
* for `pipeTo`-style consumption.
|
|
412
|
-
*/
|
|
413
|
-
interface StreamSink<T> {
|
|
414
|
-
/** Stable id used by clients to subscribe. */
|
|
415
|
-
readonly id: string;
|
|
406
|
+
readonly logger: DevframeDiagnosticsLogger;
|
|
416
407
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
408
|
+
* Register additional diagnostic definitions with this host. After
|
|
409
|
+
* registration, codes from the new definition are reachable via
|
|
410
|
+
* `host.logger.CODE`. Plugins that want shared output formatting should
|
|
411
|
+
* build their diagnostics via `host.defineDiagnostics()` first — that
|
|
412
|
+
* factory pre-wires the host's ANSI console reporter.
|
|
420
413
|
*/
|
|
421
|
-
|
|
422
|
-
/** `true` after `close()` / `error()`. Further writes throw. */
|
|
423
|
-
readonly closed: boolean;
|
|
424
|
-
/** Last allocated sequence number. `0` until the first write. */
|
|
425
|
-
readonly lastSeq: number;
|
|
426
|
-
write: (chunk: T) => void;
|
|
427
|
-
error: (reason: unknown) => void;
|
|
428
|
-
close: () => void;
|
|
429
|
-
/** External-cancel path. Aborts the signal so handlers can short-circuit. */
|
|
430
|
-
abort: (reason?: unknown) => void;
|
|
431
|
-
/** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
|
|
432
|
-
readonly writable: WritableStream<T>;
|
|
433
|
-
/**
|
|
434
|
-
* Internal — RPC layer subscribes to receive chunk/end notifications.
|
|
435
|
-
* Not part of the public contract; do not call directly.
|
|
436
|
-
*
|
|
437
|
-
* @internal
|
|
438
|
-
*/
|
|
439
|
-
readonly events: EventEmitter<StreamSinkEvents<T>>;
|
|
440
|
-
/**
|
|
441
|
-
* Internal — replay buffer. RPC layer reads on (re)subscribe to feed
|
|
442
|
-
* missed chunks before going live.
|
|
443
|
-
*
|
|
444
|
-
* @internal
|
|
445
|
-
*/
|
|
446
|
-
readonly buffer: ReadonlyArray<BufferedChunk<T>>;
|
|
447
|
-
}
|
|
448
|
-
interface CreateStreamReaderOptions {
|
|
449
|
-
id?: string;
|
|
450
|
-
/**
|
|
451
|
-
* Maximum number of buffered chunks held client-side while the consumer
|
|
452
|
-
* isn't draining. On overflow, the oldest chunk is dropped.
|
|
453
|
-
*/
|
|
454
|
-
highWaterMark?: number;
|
|
455
|
-
/**
|
|
456
|
-
* Called when the chunk queue overflows the high-water mark. The RPC
|
|
457
|
-
* layer wires this to a coded warning; the primitive itself is
|
|
458
|
-
* RPC-agnostic.
|
|
459
|
-
*/
|
|
460
|
-
onOverflow?: (dropped: number) => void;
|
|
461
|
-
/** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
|
|
462
|
-
onCancel?: () => void;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
|
|
466
|
-
* and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one — they
|
|
467
|
-
* share a single internal queue, so concurrent draining will race.
|
|
468
|
-
*/
|
|
469
|
-
interface StreamReader<T> extends AsyncIterable<T> {
|
|
470
|
-
readonly id: string;
|
|
471
|
-
readonly cancelled: boolean;
|
|
472
|
-
readonly done: boolean;
|
|
473
|
-
/** Highest `seq` observed. Used for replay on reconnect. */
|
|
474
|
-
readonly lastSeenSeq: number;
|
|
475
|
-
/** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
|
|
476
|
-
readonly readable: ReadableStream<T>;
|
|
477
|
-
cancel: () => void;
|
|
478
|
-
/** @internal */
|
|
479
|
-
_push: (seq: number, chunk: T) => void;
|
|
480
|
-
/** @internal */
|
|
481
|
-
_end: (error?: StreamErrorPayload) => void;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Build a server-side stream sink. RPC-agnostic — the RPC host wires
|
|
485
|
-
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
|
|
486
|
-
* for late or reconnecting subscribers.
|
|
487
|
-
*/
|
|
488
|
-
declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
|
|
489
|
-
/**
|
|
490
|
-
* Build a client-side stream reader. RPC-agnostic — the RPC host calls
|
|
491
|
-
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
|
|
492
|
-
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
|
|
493
|
-
*/
|
|
494
|
-
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
495
|
-
//#endregion
|
|
496
|
-
//#region src/types/diagnostics.d.ts
|
|
497
|
-
/**
|
|
498
|
-
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
|
|
499
|
-
* any registered code name to its `nostics` handle (a callable that builds
|
|
500
|
-
* a diagnostic and routes it through registered reporters). Typed loosely
|
|
501
|
-
* because it spans heterogeneous definitions registered by different
|
|
502
|
-
* integrations.
|
|
503
|
-
*/
|
|
504
|
-
type DevframeDiagnosticsLogger = Record<string, any>;
|
|
505
|
-
/**
|
|
506
|
-
* Host for structured diagnostics — a thin layer over `nostics` that lets
|
|
507
|
-
* integrations register their own coded errors/warnings into a shared
|
|
508
|
-
* registry without taking a direct dependency on `nostics`.
|
|
509
|
-
*
|
|
510
|
-
* Typical usage from a plugin's `setup(ctx)`:
|
|
511
|
-
*
|
|
512
|
-
* ```ts
|
|
513
|
-
* const myDiagnostics = ctx.diagnostics.defineDiagnostics({
|
|
514
|
-
* docsBase: 'https://example.com/errors',
|
|
515
|
-
* codes: {
|
|
516
|
-
* MYP0001: { why: 'Something went wrong' },
|
|
517
|
-
* },
|
|
518
|
-
* })
|
|
519
|
-
* ctx.diagnostics.register(myDiagnostics)
|
|
520
|
-
*
|
|
521
|
-
* // Through the shared lookup (loose typing):
|
|
522
|
-
* throw ctx.diagnostics.logger.MYP0001()
|
|
523
|
-
*
|
|
524
|
-
* // Or directly on the typed handle returned from `defineDiagnostics`:
|
|
525
|
-
* throw myDiagnostics.MYP0001()
|
|
526
|
-
* ```
|
|
527
|
-
*/
|
|
528
|
-
interface DevframeDiagnosticsHost {
|
|
529
|
-
/**
|
|
530
|
-
* Proxy-backed lookup of every registered diagnostic handle by code name.
|
|
531
|
-
* Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
|
|
532
|
-
* diagnostic and routes it through registered reporters; prefix with
|
|
533
|
-
* `throw` to raise. Loosely typed — for autocompletion, keep a reference
|
|
534
|
-
* to the typed result of `defineDiagnostics()` instead.
|
|
535
|
-
*/
|
|
536
|
-
readonly logger: DevframeDiagnosticsLogger;
|
|
537
|
-
/**
|
|
538
|
-
* Register additional diagnostic definitions with this host. After
|
|
539
|
-
* registration, codes from the new definition are reachable via
|
|
540
|
-
* `host.logger.CODE`. Plugins that want shared output formatting should
|
|
541
|
-
* build their diagnostics via `host.defineDiagnostics()` first — that
|
|
542
|
-
* factory pre-wires the host's ANSI console reporter.
|
|
543
|
-
*/
|
|
544
|
-
register: (definitions: Record<string, unknown>) => void;
|
|
414
|
+
register: (definitions: Record<string, unknown>) => void;
|
|
545
415
|
/**
|
|
546
416
|
* Build a typed diagnostics object with the host's ANSI console reporter
|
|
547
417
|
* pre-wired. The same `devframe/utils/nostics` `defineDiagnostics` every
|
|
@@ -620,125 +490,309 @@ interface DevframeHost {
|
|
|
620
490
|
*/
|
|
621
491
|
type DevframeStorageScope = 'workspace' | 'project' | 'global';
|
|
622
492
|
//#endregion
|
|
623
|
-
//#region src/types/
|
|
624
|
-
|
|
493
|
+
//#region src/types/rpc-augments.d.ts
|
|
494
|
+
/**
|
|
495
|
+
* To be extended
|
|
496
|
+
*/
|
|
497
|
+
interface DevframeRpcClientFunctions {
|
|
625
498
|
/**
|
|
499
|
+
* Server→client notification that this connection's auth token has been
|
|
500
|
+
* revoked. The client drops to untrusted on receipt. Broadcast by
|
|
501
|
+
* `revokeActiveConnectionsForToken`.
|
|
502
|
+
*
|
|
626
503
|
* @internal
|
|
627
504
|
*/
|
|
628
|
-
|
|
629
|
-
baseUrl: string;
|
|
630
|
-
source: StaticAssetsSource;
|
|
631
|
-
}[];
|
|
505
|
+
'devframe:auth:revoked': () => Promise<void>;
|
|
632
506
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* - In `build` mode, it will copy the static files to the dist directory
|
|
507
|
+
* Streaming chunk pushed from server to subscribed clients. Wired by
|
|
508
|
+
* `RpcStreamingHost`; do not register manually.
|
|
636
509
|
*
|
|
637
|
-
*
|
|
638
|
-
|
|
510
|
+
* @internal
|
|
511
|
+
*/
|
|
512
|
+
'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
513
|
+
/**
|
|
514
|
+
* Streaming terminator pushed from server to subscribed clients. Wired by
|
|
515
|
+
* `RpcStreamingHost`; do not register manually.
|
|
639
516
|
*
|
|
640
|
-
*
|
|
641
|
-
* `importMetaUrl` as the default `resolveFrom` for a remote source that
|
|
642
|
-
* doesn't set one. A shared host that mounts assets on behalf of another
|
|
643
|
-
* devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
|
|
644
|
-
* so the assets resolve against the plugin's dependency graph rather than
|
|
645
|
-
* the host's.
|
|
517
|
+
* @internal
|
|
646
518
|
*/
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
type AnyRpcFn = (...args: any[]) => any;
|
|
652
|
-
/**
|
|
653
|
-
* Resolve a scoped bare name `T` back to its fully-qualified entry in
|
|
654
|
-
* `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
|
|
655
|
-
* guard keeps TypeScript from rejecting a generic index into the
|
|
656
|
-
* key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
|
|
657
|
-
* the registry, while preserving each name's argument/return typing.
|
|
658
|
-
*/
|
|
659
|
-
type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
|
|
660
|
-
/**
|
|
661
|
-
* Augmentable registry mapping a scope namespace to the shape of its
|
|
662
|
-
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
|
|
663
|
-
* gets a fully-typed `settings.global` / `settings.project`:
|
|
664
|
-
*
|
|
665
|
-
* ```ts
|
|
666
|
-
* declare module 'devframe' {
|
|
667
|
-
* interface DevframeSettingsRegistry {
|
|
668
|
-
* 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
|
|
669
|
-
* }
|
|
670
|
-
* }
|
|
671
|
-
* ```
|
|
672
|
-
*/
|
|
673
|
-
interface DevframeSettingsRegistry {}
|
|
674
|
-
/**
|
|
675
|
-
* Resolve the settings shape for a namespace from
|
|
676
|
-
* {@link DevframeSettingsRegistry}, falling back to an open record when
|
|
677
|
-
* the namespace hasn't been augmented.
|
|
678
|
-
*/
|
|
679
|
-
type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
|
|
680
|
-
/**
|
|
681
|
-
* A persisted key-value settings store for one scope (`global` or
|
|
682
|
-
* `project`). Backed by a file on the node side and by the shared-state
|
|
683
|
-
* sync protocol on the client, so a `set` on either side propagates to
|
|
684
|
-
* every connected peer and survives restarts.
|
|
685
|
-
*
|
|
686
|
-
* Every method is async because the underlying shared state is resolved
|
|
687
|
-
* lazily on first access.
|
|
688
|
-
*/
|
|
689
|
-
interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
|
|
690
|
-
/** Read a single setting. Resolves to `undefined` when unset. */
|
|
691
|
-
get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
|
|
692
|
-
/** Write a single setting. */
|
|
693
|
-
set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
|
|
694
|
-
/** Remove a single setting. */
|
|
695
|
-
delete: <K extends keyof T>(key: K) => Promise<void>;
|
|
696
|
-
/** Read the whole settings object (immutable snapshot). */
|
|
697
|
-
all: () => Promise<Readonly<T>>;
|
|
519
|
+
'devframe:streaming:end': (channel: string, id: string, error?: {
|
|
520
|
+
name: string;
|
|
521
|
+
message: string;
|
|
522
|
+
}) => Promise<void>;
|
|
698
523
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
524
|
+
* Server→client cancel for an in-flight upload. Wired by
|
|
525
|
+
* `RpcStreamingHost`; do not register manually.
|
|
526
|
+
*
|
|
527
|
+
* @internal
|
|
701
528
|
*/
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
529
|
+
'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
|
|
530
|
+
/**
|
|
531
|
+
* Full shared-state snapshot pushed from server to subscribed clients.
|
|
532
|
+
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
533
|
+
*
|
|
534
|
+
* @internal
|
|
535
|
+
*/
|
|
536
|
+
'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
|
|
537
|
+
/**
|
|
538
|
+
* Incremental shared-state patch pushed from server to subscribed clients.
|
|
539
|
+
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
540
|
+
*
|
|
541
|
+
* @internal
|
|
542
|
+
*/
|
|
543
|
+
'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
715
544
|
}
|
|
716
545
|
/**
|
|
717
|
-
*
|
|
718
|
-
* namespace, so a scoped `call('get-cwd')` is typed without the
|
|
719
|
-
* `my-plugin:` prefix.
|
|
720
|
-
*/
|
|
721
|
-
type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
|
|
722
|
-
/**
|
|
723
|
-
* Map the client-side RPC registry down to the bare names owned by a
|
|
724
|
-
* namespace, used by scoped `broadcast`.
|
|
725
|
-
*/
|
|
726
|
-
type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
|
|
727
|
-
/**
|
|
728
|
-
* Map the shared-state registry down to the bare keys owned by a
|
|
729
|
-
* namespace, so scoped `sharedState('messages')` is typed without the
|
|
730
|
-
* `my-plugin:` prefix.
|
|
731
|
-
*/
|
|
732
|
-
type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
|
|
733
|
-
/**
|
|
734
|
-
* Broadcast options for a scoped client method (bare name).
|
|
546
|
+
* To be extended
|
|
735
547
|
*/
|
|
736
|
-
interface
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
548
|
+
interface DevframeRpcServerFunctions {
|
|
549
|
+
/**
|
|
550
|
+
* Authenticate a connection with a previously-issued bearer token; resolves
|
|
551
|
+
* whether the connection is now trusted. The interactive handler is provided
|
|
552
|
+
* by the host adapter (e.g. Vite DevTools); the standalone server registers
|
|
553
|
+
* an auto-trust noop when `auth: false`.
|
|
554
|
+
*
|
|
555
|
+
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
556
|
+
* reachable before the connection is trusted — this is the *only* rule the
|
|
557
|
+
* pre-trust gate applies, no per-method allowlist.
|
|
558
|
+
*
|
|
559
|
+
* @internal
|
|
560
|
+
*/
|
|
561
|
+
'anonymous:devframe:auth': (params: {
|
|
562
|
+
authToken: string;
|
|
563
|
+
ua: string;
|
|
564
|
+
origin: string;
|
|
565
|
+
}) => Promise<{
|
|
566
|
+
isTrusted: boolean;
|
|
567
|
+
}>;
|
|
568
|
+
/**
|
|
569
|
+
* Exchange a one-time authentication code (shown by the dev server) for a fresh,
|
|
570
|
+
* node-issued bearer token, returning the token on success or `null`. The
|
|
571
|
+
* handler is provided by the host adapter on top of `exchangeTempAuthCode`.
|
|
572
|
+
*
|
|
573
|
+
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
574
|
+
* reachable before the connection is trusted.
|
|
575
|
+
*
|
|
576
|
+
* @internal
|
|
577
|
+
*/
|
|
578
|
+
'anonymous:devframe:auth:exchange': (params: {
|
|
579
|
+
code: string;
|
|
580
|
+
ua: string;
|
|
581
|
+
origin: string;
|
|
582
|
+
}) => Promise<{
|
|
583
|
+
authToken: string | null;
|
|
584
|
+
}>;
|
|
585
|
+
/**
|
|
586
|
+
* Self-revoke: the caller asks the server to revoke its own bearer token
|
|
587
|
+
* (if any) and drop to untrusted. Requires an already-trusted caller, so
|
|
588
|
+
* unlike the two handshake methods above it does **not** carry the
|
|
589
|
+
* `anonymous:` prefix. Registered by `recipes/interactive-auth`.
|
|
590
|
+
*
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
'devframe:auth:revoke': () => Promise<void>;
|
|
594
|
+
/**
|
|
595
|
+
* Subscribe a client to a shared-state key. Wired by
|
|
596
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
597
|
+
*
|
|
598
|
+
* @internal
|
|
599
|
+
*/
|
|
600
|
+
'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
|
|
601
|
+
/**
|
|
602
|
+
* Read the current value for a shared-state key. Wired by
|
|
603
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
604
|
+
*
|
|
605
|
+
* @internal
|
|
606
|
+
*/
|
|
607
|
+
'devframe:rpc:server-state:get': (key: string) => Promise<any>;
|
|
608
|
+
/**
|
|
609
|
+
* Replace a shared-state value (from the client). Wired by
|
|
610
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
611
|
+
*
|
|
612
|
+
* @internal
|
|
613
|
+
*/
|
|
614
|
+
'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
|
|
615
|
+
/**
|
|
616
|
+
* Apply a patch to a shared-state value (from the client). Wired by
|
|
617
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
618
|
+
*
|
|
619
|
+
* @internal
|
|
620
|
+
*/
|
|
621
|
+
'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
622
|
+
/**
|
|
623
|
+
* Client→server streaming subscription with optional replay cursor.
|
|
624
|
+
* Wired by `RpcStreamingHost`; do not register manually.
|
|
625
|
+
*
|
|
626
|
+
* @internal
|
|
627
|
+
*/
|
|
628
|
+
'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
|
|
629
|
+
afterSeq?: number;
|
|
630
|
+
}) => Promise<void>;
|
|
631
|
+
/**
|
|
632
|
+
* Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
|
|
633
|
+
* do not register manually.
|
|
634
|
+
*
|
|
635
|
+
* @internal
|
|
636
|
+
*/
|
|
637
|
+
'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
|
|
638
|
+
/**
|
|
639
|
+
* Client→server streaming cancellation request. Wired by
|
|
640
|
+
* `RpcStreamingHost`; do not register manually.
|
|
641
|
+
*
|
|
642
|
+
* @internal
|
|
643
|
+
*/
|
|
644
|
+
'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
|
|
645
|
+
/**
|
|
646
|
+
* Client→server upload chunk. Wired by `RpcStreamingHost`; do not
|
|
647
|
+
* register manually.
|
|
648
|
+
*
|
|
649
|
+
* @internal
|
|
650
|
+
*/
|
|
651
|
+
'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
652
|
+
/**
|
|
653
|
+
* Client→server upload terminator. Wired by `RpcStreamingHost`; do not
|
|
654
|
+
* register manually.
|
|
655
|
+
*
|
|
656
|
+
* @internal
|
|
657
|
+
*/
|
|
658
|
+
'devframe:streaming:upload-end': (channel: string, id: string, error?: {
|
|
659
|
+
name: string;
|
|
660
|
+
message: string;
|
|
661
|
+
}) => Promise<void>;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* To be extended
|
|
665
|
+
*/
|
|
666
|
+
interface DevframeRpcSharedStates {
|
|
667
|
+
/**
|
|
668
|
+
* Wire-service advertisements — package name → `{ package, version,
|
|
669
|
+
* scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
|
|
670
|
+
* Written by the node services host at the `ready()` barrier; clients read
|
|
671
|
+
* it through `client.services` (or subscribe to it directly for
|
|
672
|
+
* reactivity). Read-only from the browser.
|
|
673
|
+
*/
|
|
674
|
+
'devframe:services': DevframeServicesState;
|
|
675
|
+
}
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/types/views.d.ts
|
|
678
|
+
interface DevframeViewHost {
|
|
679
|
+
/**
|
|
680
|
+
* @internal
|
|
681
|
+
*/
|
|
682
|
+
buildStaticDirs: {
|
|
683
|
+
baseUrl: string;
|
|
684
|
+
source: StaticAssetsSource;
|
|
685
|
+
}[];
|
|
686
|
+
/**
|
|
687
|
+
* Helper to host static files
|
|
688
|
+
* - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
|
|
689
|
+
* - In `build` mode, it will copy the static files to the dist directory
|
|
690
|
+
*
|
|
691
|
+
* Accepts a local dist directory, or a {@link StaticAssetsSource} remote
|
|
692
|
+
* declaration served through devframe's caching CDN back-proxy.
|
|
693
|
+
*
|
|
694
|
+
* `defaultResolveFrom` overrides, for this call only, the context's own
|
|
695
|
+
* `importMetaUrl` as the default `resolveFrom` for a remote source that
|
|
696
|
+
* doesn't set one. A shared host that mounts assets on behalf of another
|
|
697
|
+
* devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
|
|
698
|
+
* so the assets resolve against the plugin's dependency graph rather than
|
|
699
|
+
* the host's.
|
|
700
|
+
*/
|
|
701
|
+
hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
|
|
702
|
+
}
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/types/scope.d.ts
|
|
705
|
+
type AnyRpcFn = (...args: any[]) => any;
|
|
706
|
+
/**
|
|
707
|
+
* Resolve a scoped bare name `T` back to its fully-qualified entry in
|
|
708
|
+
* `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
|
|
709
|
+
* guard keeps TypeScript from rejecting a generic index into the
|
|
710
|
+
* key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
|
|
711
|
+
* the registry, while preserving each name's argument/return typing.
|
|
712
|
+
*/
|
|
713
|
+
type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
|
|
714
|
+
/**
|
|
715
|
+
* Augmentable registry mapping a scope namespace to the shape of its
|
|
716
|
+
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
|
|
717
|
+
* gets a fully-typed `settings.global` / `settings.project`:
|
|
718
|
+
*
|
|
719
|
+
* ```ts
|
|
720
|
+
* declare module 'devframe' {
|
|
721
|
+
* interface DevframeSettingsRegistry {
|
|
722
|
+
* 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
|
|
723
|
+
* }
|
|
724
|
+
* }
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
interface DevframeSettingsRegistry {}
|
|
728
|
+
/**
|
|
729
|
+
* Resolve the settings shape for a namespace from
|
|
730
|
+
* {@link DevframeSettingsRegistry}, falling back to an open record when
|
|
731
|
+
* the namespace hasn't been augmented.
|
|
732
|
+
*/
|
|
733
|
+
type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
|
|
734
|
+
/**
|
|
735
|
+
* A persisted key-value settings store for one scope (`global` or
|
|
736
|
+
* `project`). Backed by a file on the node side and by the shared-state
|
|
737
|
+
* sync protocol on the client, so a `set` on either side propagates to
|
|
738
|
+
* every connected peer and survives restarts.
|
|
739
|
+
*
|
|
740
|
+
* Every method is async because the underlying shared state is resolved
|
|
741
|
+
* lazily on first access.
|
|
742
|
+
*/
|
|
743
|
+
interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
|
|
744
|
+
/** Read a single setting. Resolves to `undefined` when unset. */
|
|
745
|
+
get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
|
|
746
|
+
/** Write a single setting. */
|
|
747
|
+
set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
|
|
748
|
+
/** Remove a single setting. */
|
|
749
|
+
delete: <K extends keyof T>(key: K) => Promise<void>;
|
|
750
|
+
/** Read the whole settings object (immutable snapshot). */
|
|
751
|
+
all: () => Promise<Readonly<T>>;
|
|
752
|
+
/**
|
|
753
|
+
* Subscribe to settings changes. Resolves to an unsubscribe function.
|
|
754
|
+
* Fires on every local or remote mutation with the full snapshot.
|
|
755
|
+
*/
|
|
756
|
+
onChange: (fn: (value: Readonly<T>) => void) => Promise<() => void>;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* The two settings scopes available on a scoped context.
|
|
760
|
+
*
|
|
761
|
+
* - `project` — per-workspace state, persisted under the host's
|
|
762
|
+
* `workspace` storage dir. Project-local settings.
|
|
763
|
+
* - `global` — per-user state, persisted under the host's `global`
|
|
764
|
+
* storage dir. Machine-wide preferences.
|
|
765
|
+
*/
|
|
766
|
+
interface DevframeSettings<T extends Record<string, any> = Record<string, any>> {
|
|
767
|
+
global: DevframeSettingsStore<T>;
|
|
768
|
+
project: DevframeSettingsStore<T>;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Map the server-side RPC registry down to the bare names owned by a
|
|
772
|
+
* namespace, so a scoped `call('get-cwd')` is typed without the
|
|
773
|
+
* `my-plugin:` prefix.
|
|
774
|
+
*/
|
|
775
|
+
type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
|
|
776
|
+
/**
|
|
777
|
+
* Map the client-side RPC registry down to the bare names owned by a
|
|
778
|
+
* namespace, used by scoped `broadcast`.
|
|
779
|
+
*/
|
|
780
|
+
type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
|
|
781
|
+
/**
|
|
782
|
+
* Map the shared-state registry down to the bare keys owned by a
|
|
783
|
+
* namespace, so scoped `sharedState('messages')` is typed without the
|
|
784
|
+
* `my-plugin:` prefix.
|
|
785
|
+
*/
|
|
786
|
+
type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
|
|
787
|
+
/**
|
|
788
|
+
* Broadcast options for a scoped client method (bare name).
|
|
789
|
+
*/
|
|
790
|
+
interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
|
|
791
|
+
method: METHOD;
|
|
792
|
+
args: Args;
|
|
793
|
+
optional?: boolean;
|
|
794
|
+
event?: boolean;
|
|
795
|
+
}
|
|
742
796
|
/**
|
|
743
797
|
* Node-side streaming host scoped to a namespace. Channel names are
|
|
744
798
|
* auto-prefixed with `<namespace>:`.
|
|
@@ -1023,1014 +1077,989 @@ interface DevframeServicesHost {
|
|
|
1023
1077
|
ready: () => Promise<void>;
|
|
1024
1078
|
}
|
|
1025
1079
|
//#endregion
|
|
1026
|
-
//#region src/types/
|
|
1080
|
+
//#region src/types/devframe.d.ts
|
|
1027
1081
|
/**
|
|
1028
|
-
*
|
|
1082
|
+
* Classification of how a devframe is being deployed. Hosted adapters
|
|
1083
|
+
* (`vite`, `embedded`) share their origin with a host app and must
|
|
1084
|
+
* namespace their mount path under `/__<id>/`. Standalone adapters
|
|
1085
|
+
* (`cli`, `build`) own the origin and default to `/`.
|
|
1029
1086
|
*/
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1087
|
+
type DevframeDeploymentKind = 'standalone' | 'hosted';
|
|
1088
|
+
/**
|
|
1089
|
+
* How a hub deduplicates devframes that share an `id` when more than one
|
|
1090
|
+
* is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
|
|
1091
|
+
*
|
|
1092
|
+
* - `'warn'` (default) — keep the first registration, drop later
|
|
1093
|
+
* duplicates, and emit a warning diagnostic (`DF8105`).
|
|
1094
|
+
* - `'silent'` — drop later duplicates without warning.
|
|
1095
|
+
* - `'throw'` — throw when a duplicate is mounted.
|
|
1096
|
+
* - `'duplicate'` — let every instance coexist under a disambiguated
|
|
1097
|
+
* dock id.
|
|
1098
|
+
*/
|
|
1099
|
+
type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
|
|
1100
|
+
/**
|
|
1101
|
+
* Controls where the browser opens the RPC WebSocket — advertised in
|
|
1102
|
+
* `__connection.json` and used to bind the dev server. The three shapes map
|
|
1103
|
+
* to the three connection scenarios; precedence is `url` > `port` > `route`:
|
|
1104
|
+
*
|
|
1105
|
+
* 1. **Same server, different route** (default) — leave `port`/`url` unset.
|
|
1106
|
+
* The socket shares the HTTP server's port and binds to `route`
|
|
1107
|
+
* (`__ws`). The client connects to its own origin, so the link
|
|
1108
|
+
* survives a reverse proxy that rewrites the host/port/subpath.
|
|
1109
|
+
*
|
|
1110
|
+
* 2. **Different port** — set `port`. The socket binds on its own port on the
|
|
1111
|
+
* same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
|
|
1112
|
+
*
|
|
1113
|
+
* 3. **Remote, different origin** — set `url` to a full `ws://`/`wss://`
|
|
1114
|
+
* endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
|
|
1115
|
+
*/
|
|
1116
|
+
interface DevframeWsOptions {
|
|
1039
1117
|
/**
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
* @internal
|
|
1118
|
+
* Upgrade route segment the socket binds to and is advertised at, relative
|
|
1119
|
+
* to the SPA base. Default: `__ws`.
|
|
1044
1120
|
*/
|
|
1045
|
-
|
|
1121
|
+
route?: string;
|
|
1046
1122
|
/**
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
* @internal
|
|
1123
|
+
* Bind the socket on its own port instead of sharing the HTTP port. The
|
|
1124
|
+
* browser connects to this port on the page's hostname. Implies
|
|
1125
|
+
* {@link DevframeWsOptions.sidecar}.
|
|
1051
1126
|
*/
|
|
1052
|
-
|
|
1053
|
-
name: string;
|
|
1054
|
-
message: string;
|
|
1055
|
-
}) => Promise<void>;
|
|
1127
|
+
port?: number;
|
|
1056
1128
|
/**
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1129
|
+
* Start a side-car WebSocket server on a free port, for hosts whose request
|
|
1130
|
+
* handlers can't accept upgrades (Next.js route handlers, Nitro, Rsbuild).
|
|
1131
|
+
* The resolved port is advertised in `__connection.json`, so the browser
|
|
1132
|
+
* finds it without any further wiring. Set `port` instead to pin it.
|
|
1061
1133
|
*/
|
|
1062
|
-
|
|
1134
|
+
sidecar?: boolean;
|
|
1063
1135
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
* @internal
|
|
1136
|
+
* Advertise a fixed, fully-qualified endpoint on another origin (a full
|
|
1137
|
+
* `ws://`/`wss://` URL). Takes precedence over `port`/`route` in the meta.
|
|
1068
1138
|
*/
|
|
1069
|
-
|
|
1139
|
+
url?: string;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Controls the SSE RPC endpoint — the WebSocket-free transport for hosts
|
|
1143
|
+
* and proxies where the upgrade isn't available. It rides whatever HTTP
|
|
1144
|
+
* surface serves the instance (the same one serving `__connection.json`),
|
|
1145
|
+
* so a relative `route` always resolves; there is no port plumbing of its
|
|
1146
|
+
* own. Enabled by default alongside the WebSocket; pass `sse: false` to
|
|
1147
|
+
* disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
|
|
1148
|
+
*/
|
|
1149
|
+
interface DevframeSseOptions {
|
|
1070
1150
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
* @internal
|
|
1151
|
+
* Route segment the SSE endpoint binds to and is advertised at, relative
|
|
1152
|
+
* to the SPA base. Default: `__sse`.
|
|
1075
1153
|
*/
|
|
1076
|
-
|
|
1154
|
+
route?: string;
|
|
1077
1155
|
}
|
|
1078
1156
|
/**
|
|
1079
|
-
*
|
|
1157
|
+
* Configuration for the route-based MCP server mounted alongside the dev
|
|
1158
|
+
* server (opt-in via {@link DevframeCliOptions.mcp}). The endpoint speaks
|
|
1159
|
+
* the MCP Streamable-HTTP transport over the same origin as the SPA,
|
|
1160
|
+
* exposing the definition's `ctx.agent` tools + shared-state resources to
|
|
1161
|
+
* external MCP clients connected to the *running* server.
|
|
1080
1162
|
*/
|
|
1081
|
-
interface
|
|
1163
|
+
interface McpRouteOptions {
|
|
1082
1164
|
/**
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
* by the host adapter (e.g. Vite DevTools); the standalone server registers
|
|
1086
|
-
* an auto-trust noop when `auth: false`.
|
|
1087
|
-
*
|
|
1088
|
-
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
1089
|
-
* reachable before the connection is trusted — this is the *only* rule the
|
|
1090
|
-
* pre-trust gate applies, no per-method allowlist.
|
|
1091
|
-
*
|
|
1092
|
-
* @internal
|
|
1165
|
+
* Route segment the MCP endpoint binds to, relative to the SPA base.
|
|
1166
|
+
* Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
|
|
1093
1167
|
*/
|
|
1094
|
-
|
|
1095
|
-
authToken: string;
|
|
1096
|
-
ua: string;
|
|
1097
|
-
origin: string;
|
|
1098
|
-
}) => Promise<{
|
|
1099
|
-
isTrusted: boolean;
|
|
1100
|
-
}>;
|
|
1168
|
+
path?: string;
|
|
1101
1169
|
/**
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1107
|
-
* reachable before the connection is trusted.
|
|
1170
|
+
* Extra `Origin` header values to accept beyond the loopback default
|
|
1171
|
+
* (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
|
|
1172
|
+
* Add your LAN/tunnel origin here when reaching the endpoint from another
|
|
1173
|
+
* host, mirroring the WS transport's origin gate. Pass `false` to disable
|
|
1174
|
+
* origin checking entirely (not recommended). Default: loopback-only.
|
|
1108
1175
|
*
|
|
1109
|
-
*
|
|
1176
|
+
* This is the endpoint's DNS-rebinding protection — the shared
|
|
1177
|
+
* `isAllowedOrigin` gate the WS upgrade already uses, applied as external
|
|
1178
|
+
* middleware (the approach the MCP SDK now recommends over its own
|
|
1179
|
+
* deprecated `allowedHosts`/`allowedOrigins` transport flags).
|
|
1110
1180
|
*/
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1181
|
+
allowedOrigins?: readonly string[] | false;
|
|
1182
|
+
}
|
|
1183
|
+
interface DevframeCliOptions {
|
|
1184
|
+
/** Binary name; default: the devframe's `id`. */
|
|
1185
|
+
command?: string;
|
|
1186
|
+
/** Preferred port for the dev server (default 9999). */
|
|
1187
|
+
port?: number;
|
|
1188
|
+
/** Port scan range, forwarded to `get-port-please`. */
|
|
1189
|
+
portRange?: [number, number];
|
|
1190
|
+
/** Prefer a random open port. */
|
|
1191
|
+
random?: boolean;
|
|
1192
|
+
/** Default host to bind to; `--host` overrides. */
|
|
1193
|
+
host?: string;
|
|
1118
1194
|
/**
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
* `anonymous:` prefix. Registered by `recipes/interactive-auth`.
|
|
1123
|
-
*
|
|
1124
|
-
* @internal
|
|
1195
|
+
* Auto-open the browser when the dev server starts.
|
|
1196
|
+
* `true` opens the resolved origin; a string opens that relative path.
|
|
1197
|
+
* The `--open` / `--no-open` flags override this.
|
|
1125
1198
|
*/
|
|
1126
|
-
|
|
1199
|
+
open?: boolean | string;
|
|
1127
1200
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
1201
|
+
* Authentication for the standalone dev server.
|
|
1130
1202
|
*
|
|
1131
|
-
*
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1203
|
+
* - `undefined` / `true` — the standalone adapters (`cli` /
|
|
1204
|
+
* served `build`) auto-wire devframe's interactive OTP auth
|
|
1205
|
+
* (`createInteractiveAuth`): an untrusted client can only reach
|
|
1206
|
+
* `anonymous:` methods until it exchanges the printed one-time code.
|
|
1207
|
+
* The adapter prints the code + magic-link banner once the server is
|
|
1208
|
+
* listening.
|
|
1209
|
+
* - `false` — no gate, for trusted single-user localhost tools where an
|
|
1210
|
+
* auth round-trip only gets in the way (the built-in plugins set this).
|
|
1211
|
+
* The `--no-auth` CLI flag maps here for one-off runs.
|
|
1212
|
+
* - A {@link DevframeAuthHandler} — a custom handler (e.g. a tuned
|
|
1213
|
+
* `createInteractiveAuth`, or an entirely different scheme) passed
|
|
1214
|
+
* straight through to the RPC transport binding.
|
|
1137
1215
|
*
|
|
1138
|
-
*
|
|
1139
|
-
|
|
1140
|
-
'devframe:rpc:server-state:get': (key: string) => Promise<any>;
|
|
1141
|
-
/**
|
|
1142
|
-
* Replace a shared-state value (from the client). Wired by
|
|
1143
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
1216
|
+
* Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
|
|
1217
|
+
* auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
|
|
1144
1218
|
*
|
|
1145
|
-
* @
|
|
1219
|
+
* @default true
|
|
1146
1220
|
*/
|
|
1147
|
-
|
|
1221
|
+
auth?: boolean | DevframeAuthHandler;
|
|
1148
1222
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
* `
|
|
1223
|
+
* Expose a route-based MCP server alongside the dev server, speaking the
|
|
1224
|
+
* MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
|
|
1225
|
+
* It surfaces the same `ctx.agent` tools + shared-state resources as the
|
|
1226
|
+
* stdio `mcp` command, but against the live, running server.
|
|
1151
1227
|
*
|
|
1152
|
-
*
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
* Client→server streaming subscription with optional replay cursor.
|
|
1157
|
-
* Wired by `RpcStreamingHost`; do not register manually.
|
|
1228
|
+
* - `false` / omitted (default) — no MCP route is mounted.
|
|
1229
|
+
* - `true` — mount at the default `__mcp` route with the loopback-only
|
|
1230
|
+
* origin gate.
|
|
1231
|
+
* - {@link McpRouteOptions} — customise the route path / allowed origins.
|
|
1158
1232
|
*
|
|
1159
|
-
*
|
|
1233
|
+
* The `--mcp` / `--no-mcp` CLI flags override this per run.
|
|
1160
1234
|
*/
|
|
1161
|
-
|
|
1162
|
-
afterSeq?: number;
|
|
1163
|
-
}) => Promise<void>;
|
|
1235
|
+
mcp?: boolean | McpRouteOptions;
|
|
1164
1236
|
/**
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1237
|
+
* Author's SPA dist — served as the devframe's UI. A local directory, or
|
|
1238
|
+
* a {@link StaticAssetsSource} remote declaration (`{ package, version }`)
|
|
1239
|
+
* served through devframe's caching CDN back-proxy so the assets need not
|
|
1240
|
+
* ship inside the node package.
|
|
1169
1241
|
*/
|
|
1170
|
-
|
|
1242
|
+
distDir?: StaticAssetsSource;
|
|
1171
1243
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
* `
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1244
|
+
* How the browser reaches the RPC WebSocket. Defaults to sharing the HTTP
|
|
1245
|
+
* port on the `__ws` route. See {@link DevframeWsOptions} for the
|
|
1246
|
+
* different-port and remote-origin variants. Pass `false` to serve no
|
|
1247
|
+
* WebSocket at all — clients connect over SSE instead (`backend: 'sse'`).
|
|
1176
1248
|
*/
|
|
1177
|
-
|
|
1249
|
+
ws?: DevframeWsOptions | false;
|
|
1178
1250
|
/**
|
|
1179
|
-
*
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
* @internal
|
|
1251
|
+
* How the browser reaches the SSE RPC endpoint — enabled by default
|
|
1252
|
+
* alongside the WebSocket as the more portable transport. Pass `false`
|
|
1253
|
+
* to disable, or a {@link DevframeSseOptions} to change its route.
|
|
1183
1254
|
*/
|
|
1184
|
-
|
|
1255
|
+
sse?: boolean | DevframeSseOptions;
|
|
1185
1256
|
/**
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
1190
|
-
|
|
1191
|
-
'devframe:streaming:upload-end': (channel: string, id: string, error?: {
|
|
1192
|
-
name: string;
|
|
1193
|
-
message: string;
|
|
1194
|
-
}) => Promise<void>;
|
|
1195
|
-
}
|
|
1196
|
-
/**
|
|
1197
|
-
* To be extended
|
|
1198
|
-
*/
|
|
1199
|
-
interface DevframeRpcSharedStates {
|
|
1200
|
-
/**
|
|
1201
|
-
* Wire-service advertisements — package name → `{ package, version,
|
|
1202
|
-
* scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
|
|
1203
|
-
* Written by the node services host at the `ready()` barrier; clients read
|
|
1204
|
-
* it through `client.services` (or subscribe to it directly for
|
|
1205
|
-
* reactivity). Read-only from the browser.
|
|
1257
|
+
* Capability-side CAC hook. Called with the CAC instance after the
|
|
1258
|
+
* adapter registers its built-in commands (`build` / `mcp`)
|
|
1259
|
+
* but before `createCac`'s own `configureCli` caller. Use this to
|
|
1260
|
+
* contribute tool-specific flags and subcommands from the definition
|
|
1261
|
+
* itself.
|
|
1206
1262
|
*/
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1209
|
-
//#endregion
|
|
1210
|
-
//#region src/types/rpc.d.ts
|
|
1211
|
-
interface DevframeNodeRpcSession {
|
|
1212
|
-
meta: DevframeNodeRpcSessionMeta;
|
|
1213
|
-
rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
1214
|
-
}
|
|
1215
|
-
interface RpcBroadcastOptions<METHOD, Args extends any[]> {
|
|
1216
|
-
method: METHOD;
|
|
1217
|
-
args: Args;
|
|
1218
|
-
optional?: boolean;
|
|
1219
|
-
event?: boolean;
|
|
1220
|
-
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
1221
|
-
}
|
|
1222
|
-
type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
|
|
1263
|
+
configure?: (cli: CAC) => void;
|
|
1223
1264
|
/**
|
|
1224
|
-
*
|
|
1265
|
+
* Typed CLI flags for the default `dev` command, backed by any
|
|
1266
|
+
* [Standard Schema](https://standardschema.dev/) validator (valibot,
|
|
1267
|
+
* zod, arktype, or devframe's built-in `s`). The adapter registers
|
|
1268
|
+
* matching `--kebab-key` options on CAC, validates the parsed values,
|
|
1269
|
+
* and forwards the typed bag to `setup(ctx, { flags })`.
|
|
1225
1270
|
*
|
|
1226
|
-
*
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
/**
|
|
1230
|
-
* Broadcast a message to all connected clients
|
|
1231
|
-
*/
|
|
1232
|
-
broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
|
|
1233
|
-
/**
|
|
1234
|
-
* Get the current RPC client
|
|
1271
|
+
* Use {@link defineCliFlags} to preserve the literal schema-map
|
|
1272
|
+
* shape, and {@link InferCliFlags} to recover the typed output at the
|
|
1273
|
+
* call site:
|
|
1235
1274
|
*
|
|
1236
|
-
*
|
|
1275
|
+
* ```ts
|
|
1276
|
+
* const appFlags = defineCliFlags({
|
|
1277
|
+
* depth: v.pipe(v.number(), v.integer()),
|
|
1278
|
+
* config: v.optional(v.string()),
|
|
1279
|
+
* })
|
|
1280
|
+
*
|
|
1281
|
+
* defineDevframe({
|
|
1282
|
+
* cli: { flags: appFlags },
|
|
1283
|
+
* setup(ctx, info) {
|
|
1284
|
+
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
1285
|
+
* },
|
|
1286
|
+
* })
|
|
1287
|
+
* ```
|
|
1237
1288
|
*/
|
|
1238
|
-
|
|
1289
|
+
flags?: CliFlagsSchema;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Default dock attributes for the iframe entry a hub synthesizes when it
|
|
1293
|
+
* mounts this devframe. Framework-neutral metadata only — the hub layer
|
|
1294
|
+
* (`ctx.install`) merges these beneath its per-mount `dock` overrides,
|
|
1295
|
+
* which in turn sit beneath the locked, derived `id` / `type` / `url`.
|
|
1296
|
+
*
|
|
1297
|
+
* Every field is optional. `title` / `icon` default to the definition's
|
|
1298
|
+
* `name` / `icon` when omitted here; the rest are unset by default.
|
|
1299
|
+
* Standalone adapters (`cli` / `build`) ignore this entirely.
|
|
1300
|
+
*/
|
|
1301
|
+
interface DevframeDockDefaults {
|
|
1302
|
+
/** Dock entry title. Defaults to the definition's `name`. */
|
|
1303
|
+
title?: string;
|
|
1304
|
+
/** Dock entry icon. Defaults to the definition's `icon`. */
|
|
1305
|
+
icon?: string | {
|
|
1306
|
+
light: string;
|
|
1307
|
+
dark: string;
|
|
1308
|
+
};
|
|
1239
1309
|
/**
|
|
1240
|
-
*
|
|
1310
|
+
* Sort weight within the dock; higher sorts earlier.
|
|
1311
|
+
* @default 0
|
|
1241
1312
|
*/
|
|
1242
|
-
|
|
1313
|
+
defaultOrder?: number;
|
|
1243
1314
|
/**
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
* @see RpcStreamingHost
|
|
1315
|
+
* Category the entry groups under in the dock.
|
|
1316
|
+
* @default 'default'
|
|
1248
1317
|
*/
|
|
1249
|
-
|
|
1250
|
-
};
|
|
1251
|
-
interface RpcSharedStateGetOptions<T> {
|
|
1252
|
-
sharedState?: SharedState<T>;
|
|
1253
|
-
initialValue?: T;
|
|
1254
|
-
}
|
|
1255
|
-
interface RpcSharedStateHost {
|
|
1256
|
-
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
1257
|
-
keys: () => string[];
|
|
1318
|
+
category?: string;
|
|
1258
1319
|
/**
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
1262
|
-
* as dynamic resources.
|
|
1320
|
+
* Conditional-visibility expression (same syntax as command `when`
|
|
1321
|
+
* clauses). Set to `'false'` to hide the entry unconditionally.
|
|
1263
1322
|
*/
|
|
1264
|
-
|
|
1323
|
+
when?: string;
|
|
1265
1324
|
/**
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1325
|
+
* Render-only visibility expression, same syntax as {@link when}. Hides the
|
|
1326
|
+
* entry's own dock-bar button when it evaluates to `false` while leaving it
|
|
1327
|
+
* registered and reachable (activation, RPC lookups, etc.) — unlike `when`,
|
|
1328
|
+
* which is the general relevance switch for the entry as a whole.
|
|
1270
1329
|
*/
|
|
1271
|
-
|
|
1330
|
+
visibility?: string;
|
|
1331
|
+
/** Badge text rendered on the dock icon (e.g. an unread count). */
|
|
1332
|
+
badge?: string;
|
|
1333
|
+
/** Id of the dock group this entry collapses under, if any. */
|
|
1334
|
+
groupId?: string;
|
|
1272
1335
|
}
|
|
1273
1336
|
/**
|
|
1274
|
-
*
|
|
1337
|
+
* Runtime information threaded into `setup(ctx, info)`. Adapters
|
|
1338
|
+
* populate the fields that make sense for their deployment. In
|
|
1339
|
+
* particular, `createCac` fills `flags` with the parsed CAC bag.
|
|
1275
1340
|
*/
|
|
1276
|
-
interface
|
|
1341
|
+
interface DevframeSetupInfo {
|
|
1342
|
+
/** Parsed CLI flags, populated by the CLI adapter. */
|
|
1343
|
+
flags?: Record<string, unknown>;
|
|
1344
|
+
}
|
|
1345
|
+
interface DevframeDefinition {
|
|
1346
|
+
id: string;
|
|
1347
|
+
name: string;
|
|
1348
|
+
/** Semver of the tool, surfaced in hub UIs and diagnostics. */
|
|
1349
|
+
version: string;
|
|
1350
|
+
/** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
|
|
1351
|
+
packageName: string;
|
|
1277
1352
|
/**
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
* the
|
|
1353
|
+
* `import.meta.url` of the module that defines this devframe. **Always
|
|
1354
|
+
* provide it** (`importMetaUrl: import.meta.url`): it is the resolution base
|
|
1355
|
+
* for the tool's own dependency graph, which lets the host resolve
|
|
1356
|
+
* everything against the plugin's own installed packages rather than the
|
|
1357
|
+
* consuming app's.
|
|
1281
1358
|
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1359
|
+
* - **Remote assets** — becomes the default `resolveFrom` for any remote
|
|
1360
|
+
* {@link StaticAssetsSource} the devframe hosts (its `cli.distDir`, and
|
|
1361
|
+
* every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
|
|
1362
|
+
* locally installed copy of the assets package is served with zero
|
|
1363
|
+
* network. A per-asset `resolveFrom` still wins, and an explicit
|
|
1364
|
+
* `resolveFrom: null` still opts out.
|
|
1365
|
+
* - **Service dependencies** — becomes the base the host resolves declared
|
|
1366
|
+
* {@link DevframeServiceInput | services} from, so a plugin can ship a
|
|
1367
|
+
* service package as its own dependency instead of asking users to install
|
|
1368
|
+
* it.
|
|
1369
|
+
*
|
|
1370
|
+
* Optional for backward compatibility; omitting it falls back to
|
|
1371
|
+
* runtime-directory resolution and disables the zero-network installed-copy
|
|
1372
|
+
* fast paths above.
|
|
1284
1373
|
*/
|
|
1285
|
-
|
|
1374
|
+
importMetaUrl?: string;
|
|
1375
|
+
/** Project homepage or documentation URL. */
|
|
1376
|
+
homepage: string;
|
|
1377
|
+
/** One-line summary of what the tool does. */
|
|
1378
|
+
description: string;
|
|
1379
|
+
icon?: string | {
|
|
1380
|
+
light: string;
|
|
1381
|
+
dark: string;
|
|
1382
|
+
};
|
|
1286
1383
|
/**
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1384
|
+
* Default dock attributes applied when a hub mounts this devframe as an
|
|
1385
|
+
* iframe dock entry. Consulted only by the hub install path (`ctx.install`),
|
|
1386
|
+
* which merge these beneath the per-mount `dock` overrides; standalone
|
|
1387
|
+
* adapters (`cli` / `build`) ignore it.
|
|
1291
1388
|
*
|
|
1292
|
-
*
|
|
1293
|
-
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
1294
|
-
* post-mortem replay.
|
|
1389
|
+
* @see {@link DevframeDockDefaults}
|
|
1295
1390
|
*/
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
|
|
1300
|
-
* channel owns a wire namespace; calling `start()` produces individual
|
|
1301
|
-
* streams keyed by id.
|
|
1302
|
-
*
|
|
1303
|
-
* @see {@link https://devfra.me/guide/streaming Streaming guide}
|
|
1304
|
-
*/
|
|
1305
|
-
interface RpcStreamingChannel<T = unknown> {
|
|
1306
|
-
/** Channel name as registered with `ctx.rpc.streaming.create()`. */
|
|
1307
|
-
readonly name: string;
|
|
1391
|
+
dock?: DevframeDockDefaults;
|
|
1308
1392
|
/**
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
* `
|
|
1312
|
-
* disconnects or cancels.
|
|
1393
|
+
* Mount path override. Defaults depend on the adapter:
|
|
1394
|
+
* `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
|
|
1395
|
+
* (`vite` / `embedded`).
|
|
1313
1396
|
*/
|
|
1314
|
-
|
|
1315
|
-
id?: string;
|
|
1316
|
-
}) => StreamSink<T>;
|
|
1397
|
+
basePath?: string;
|
|
1317
1398
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1399
|
+
* How a hub reacts when another devframe sharing this one's `id` is
|
|
1400
|
+
* mounted onto the same hub. Consulted only by hub adapters
|
|
1401
|
+
* (`ctx.install`); standalone adapters (`cli` / `build`)
|
|
1402
|
+
* ignore it.
|
|
1320
1403
|
*
|
|
1321
|
-
*
|
|
1322
|
-
* before passing it here.
|
|
1404
|
+
* @default 'warn'
|
|
1323
1405
|
*/
|
|
1324
|
-
|
|
1325
|
-
id?: string;
|
|
1326
|
-
}) => Promise<StreamSink<T>>;
|
|
1327
|
-
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
1328
|
-
get: (id: string) => StreamSink<T> | undefined;
|
|
1329
|
-
/** All active outbound stream ids on this channel. */
|
|
1330
|
-
ids: () => string[];
|
|
1406
|
+
duplicationStrategy?: DevframeDuplicationStrategy;
|
|
1331
1407
|
/**
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* the client writes chunks. Typical pattern is to call this from an
|
|
1335
|
-
* action handler, kick off background processing, and return the id
|
|
1336
|
-
* so the caller can start uploading:
|
|
1337
|
-
*
|
|
1338
|
-
* ```ts
|
|
1339
|
-
* handler: async () => {
|
|
1340
|
-
* const reader = channel.openInbound()
|
|
1341
|
-
* ;(async () => {
|
|
1342
|
-
* for await (const chunk of reader) processChunk(chunk)
|
|
1343
|
-
* })()
|
|
1344
|
-
* return { uploadId: reader.id }
|
|
1345
|
-
* }
|
|
1346
|
-
* ```
|
|
1408
|
+
* Declares which runtimes meaningfully support this devframe. Adapters
|
|
1409
|
+
* act on a `false` before doing any work:
|
|
1347
1410
|
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1411
|
+
* - `build: false` — `createCac` skips registering the `build` subcommand,
|
|
1412
|
+
* and `createBuild` refuses (throws `DF0042`) unless `{ force: true }`.
|
|
1413
|
+
* Useful for a devframe whose value is inherently live (e.g. it manages
|
|
1414
|
+
* real files on disk), so a static export would only ever produce a
|
|
1415
|
+
* broken, write-less shell of the tool.
|
|
1416
|
+
* - `dev: false` — `createDevServer` refuses (throws `DF0058`) unless
|
|
1417
|
+
* `{ force: true }`. Useful for a devframe that only makes sense as a
|
|
1418
|
+
* static export (e.g. a report generator with nothing to serve live).
|
|
1350
1419
|
*/
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1420
|
+
capabilities?: {
|
|
1421
|
+
dev?: boolean;
|
|
1422
|
+
build?: boolean;
|
|
1423
|
+
};
|
|
1424
|
+
/**
|
|
1425
|
+
* Wire services this devframe consumes (see `DevframeServiceDefinition`).
|
|
1426
|
+
* Each entry is either a declarative descriptor
|
|
1427
|
+
* (`{ package, version?, required?, options? }` — the host imports the
|
|
1428
|
+
* package's default-export factory, resolving it against **this plugin's
|
|
1429
|
+
* own dependencies**) or a ready `DevframeServiceDefinition` (the factory
|
|
1430
|
+
* was already called). The adapter queues these before `setup(ctx)` runs
|
|
1431
|
+
* and constructs each service once at the `ctx.services.ready()` barrier,
|
|
1432
|
+
* merging option sets across every declarer. Missing services are skipped
|
|
1433
|
+
* unless marked `required` — clients feature-detect via
|
|
1434
|
+
* `client.services.has(pkg)` and degrade.
|
|
1435
|
+
*/
|
|
1436
|
+
services?: DevframeServiceInput[];
|
|
1437
|
+
/** RPC-level configuration for this devframe (see {@link DevframeRpcOptions}). */
|
|
1438
|
+
rpc?: DevframeRpcOptions;
|
|
1439
|
+
/** Server-side setup — the primary entrypoint. Runs in every runtime. */
|
|
1440
|
+
setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
|
|
1441
|
+
cli?: DevframeCliOptions;
|
|
1354
1442
|
}
|
|
1443
|
+
interface DevframeRpcOptions {
|
|
1444
|
+
/**
|
|
1445
|
+
* Opt an RPC function into the static-build snapshot **without owning its
|
|
1446
|
+
* definition** — the mechanism a devframe uses to bake a wire service's
|
|
1447
|
+
* RPC (e.g. `@devframes/service-git`'s `status`/`log`/`show`) into its
|
|
1448
|
+
* `build` export, since the service itself defines no `dump`/`snapshot`.
|
|
1449
|
+
*
|
|
1450
|
+
* Each entry is either a bare method id (bakes the no-argument call, like
|
|
1451
|
+
* `snapshot: true`) or `{ method, inputs }` where `inputs` is the list of
|
|
1452
|
+
* argument-tuples to bake — or an async provider given the node context
|
|
1453
|
+
* (so it can enumerate at build time, e.g. read commit hashes via the
|
|
1454
|
+
* service's node API). `createBuild` resolves these after setup and
|
|
1455
|
+
* executes the target's own handler per tuple; the first tuple's result
|
|
1456
|
+
* becomes the fallback so any call variant resolves to a baked value.
|
|
1457
|
+
*/
|
|
1458
|
+
snapshot?: DevframeSnapshotRpcEntry[];
|
|
1459
|
+
}
|
|
1460
|
+
/** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
|
|
1461
|
+
type DevframeSnapshotRpcInputs = readonly (readonly unknown[])[] | ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>);
|
|
1355
1462
|
/**
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
* and wire namespace.
|
|
1463
|
+
* One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
|
|
1464
|
+
* the no-argument call) or a method plus the argument-tuples to bake.
|
|
1359
1465
|
*/
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
* name is already taken.
|
|
1365
|
-
*/
|
|
1366
|
-
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
1367
|
-
/**
|
|
1368
|
-
* Adapters call this when a session disconnects so the host can drop
|
|
1369
|
-
* subscribers and abort orphaned streams. Most users do not need this;
|
|
1370
|
-
* it's wired by the RPC server binding automatically.
|
|
1371
|
-
*
|
|
1372
|
-
* @internal
|
|
1373
|
-
*/
|
|
1374
|
-
_onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
|
|
1375
|
-
}
|
|
1466
|
+
type DevframeSnapshotRpcEntry = string | {
|
|
1467
|
+
method: string;
|
|
1468
|
+
inputs: DevframeSnapshotRpcInputs;
|
|
1469
|
+
};
|
|
1376
1470
|
//#endregion
|
|
1377
|
-
//#region src/
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1471
|
+
//#region src/utils/shared-state.d.ts
|
|
1472
|
+
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
1473
|
+
type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
|
|
1474
|
+
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
1475
|
+
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
1476
|
+
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
1477
|
+
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
|
|
1478
|
+
/**
|
|
1479
|
+
* Serializable patch describing a single mutation to a `SharedState`.
|
|
1480
|
+
* Structurally compatible with JSON-Patch and is safe to send over RPC.
|
|
1481
|
+
*/
|
|
1482
|
+
interface SharedStatePatch {
|
|
1483
|
+
op: 'add' | 'remove' | 'replace';
|
|
1484
|
+
path: readonly (string | number)[];
|
|
1485
|
+
value?: unknown;
|
|
1381
1486
|
}
|
|
1382
1487
|
/**
|
|
1383
|
-
*
|
|
1384
|
-
* agent + the view-host (HTTP file-serving). Host adapters can wrap this
|
|
1385
|
-
* to add their own surfaces; for example, `@vitejs/devtools-kit`'s
|
|
1386
|
-
* `createKitContext` adds `docks`, `terminals`, `messages`, and
|
|
1387
|
-
* `commands` when mounted into Vite DevTools. JSON rendering is an opt-in
|
|
1388
|
-
* integration (`@devframes/json-render`) layered on top, not part of this
|
|
1389
|
-
* core surface.
|
|
1488
|
+
* State host that is immutable by default with explicit mutate.
|
|
1390
1489
|
*/
|
|
1391
|
-
interface
|
|
1392
|
-
readonly workspaceRoot: string;
|
|
1393
|
-
readonly cwd: string;
|
|
1490
|
+
interface SharedState<T> {
|
|
1394
1491
|
/**
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
* - `'dev'` — long-running, interactive session. Connections come and
|
|
1398
|
-
* go; broadcasts and shared-state mutations are debounced
|
|
1399
|
-
* to keep the UI responsive.
|
|
1400
|
-
* - `'build'` — one-shot batch run. The context is set up, the devtool
|
|
1401
|
-
* collects what it needs, and a snapshot is written. No
|
|
1402
|
-
* live UI, no WS server.
|
|
1403
|
-
*
|
|
1404
|
-
* Names are inherited from Vite's serve/build dichotomy but the meaning
|
|
1405
|
-
* is general: the same distinction applies to any tool that runs in
|
|
1406
|
-
* either an interactive or a static-output mode.
|
|
1492
|
+
* Get the current state. Immutable.
|
|
1407
1493
|
*/
|
|
1408
|
-
|
|
1494
|
+
value: () => Immutable<T>;
|
|
1409
1495
|
/**
|
|
1410
|
-
*
|
|
1411
|
-
* `getStorageDir`.
|
|
1496
|
+
* Subscribe to state changes.
|
|
1412
1497
|
*/
|
|
1413
|
-
|
|
1414
|
-
rpc: RpcFunctionsHost;
|
|
1415
|
-
views: DevframeViewHost;
|
|
1498
|
+
on: EventEmitter<SharedStateEvents<T>>['on'];
|
|
1416
1499
|
/**
|
|
1417
|
-
*
|
|
1418
|
-
* register their own coded errors/warnings into the shared lookup.
|
|
1500
|
+
* Mutate the state.
|
|
1419
1501
|
*/
|
|
1420
|
-
|
|
1502
|
+
mutate: (fn: (state: T) => void, syncId?: string) => void;
|
|
1421
1503
|
/**
|
|
1422
|
-
*
|
|
1504
|
+
* Apply patches to the state.
|
|
1423
1505
|
*/
|
|
1424
|
-
|
|
1506
|
+
patch: (patches: SharedStatePatch[], syncId?: string) => void;
|
|
1425
1507
|
/**
|
|
1426
|
-
*
|
|
1427
|
-
* integration exposes a capability (e.g. a data-source registry) and
|
|
1428
|
-
* others consume it without a hard package dependency. Ids follow the RPC
|
|
1429
|
-
* namespacing rule (`<plugin-id>:<service>`); types come from augmenting
|
|
1430
|
-
* the `DevframeServicesRegistry` interface. `whenAvailable` subscriptions
|
|
1431
|
-
* absorb setup-order differences between provider and consumer.
|
|
1508
|
+
* Sync IDs that have been applied to the state.
|
|
1432
1509
|
*/
|
|
1433
|
-
|
|
1510
|
+
syncIds: Set<string>;
|
|
1511
|
+
}
|
|
1512
|
+
interface SharedStateEvents<T> {
|
|
1513
|
+
updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
|
|
1514
|
+
}
|
|
1515
|
+
interface SharedStateOptions<T> {
|
|
1434
1516
|
/**
|
|
1435
|
-
*
|
|
1436
|
-
* config a host publishes once through the connection handshake and every
|
|
1437
|
-
* client reads read-only. A plain, **non-reactive** object: mutate it
|
|
1438
|
-
* during `setup(ctx)` (a plugin sets its keys, a hub aggregates across
|
|
1439
|
-
* every installed devframe), never during the session — it's serialized
|
|
1440
|
-
* once, after setup, and changing it afterwards reaches no client.
|
|
1441
|
-
*
|
|
1442
|
-
* ```ts
|
|
1443
|
-
* ctx.staticConfig.dock = {
|
|
1444
|
-
* ...ctx.staticConfig.dock,
|
|
1445
|
-
* categoryOrder: { ...ctx.staticConfig.dock?.categoryOrder, ...myOrder },
|
|
1446
|
-
* }
|
|
1447
|
-
* ```
|
|
1517
|
+
* Initial state.
|
|
1448
1518
|
*/
|
|
1449
|
-
|
|
1519
|
+
initialValue: T;
|
|
1450
1520
|
/**
|
|
1451
|
-
*
|
|
1452
|
-
* `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
|
|
1453
|
-
* key, and streaming channel with `my-plugin:`, and exposes a typed
|
|
1454
|
-
* top-level `settings` store. This is the preferred way to consume the
|
|
1455
|
-
* context from a single tool's setup code.
|
|
1521
|
+
* Enable patches.
|
|
1456
1522
|
*
|
|
1457
|
-
*
|
|
1523
|
+
* @default false
|
|
1458
1524
|
*/
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1525
|
+
enablePatches?: boolean;
|
|
1526
|
+
}
|
|
1527
|
+
declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
|
|
1528
|
+
//#endregion
|
|
1529
|
+
//#region src/utils/streaming-channel.d.ts
|
|
1530
|
+
/**
|
|
1531
|
+
* Serialized error shape sent over the wire when a stream ends with a failure.
|
|
1532
|
+
* Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
|
|
1533
|
+
*/
|
|
1534
|
+
interface StreamErrorPayload {
|
|
1535
|
+
name: string;
|
|
1536
|
+
message: string;
|
|
1463
1537
|
}
|
|
1464
1538
|
/**
|
|
1465
|
-
*
|
|
1466
|
-
* object form is the proxy-flexible default: `path` is resolved relative to
|
|
1467
|
-
* where `__connection.json` was loaded, and the connection is made to the
|
|
1468
|
-
* page's own origin (only the `http`→`ws` / `https`→`wss` protocol swap is
|
|
1469
|
-
* applied). This survives reverse proxies that change the host/port, because
|
|
1470
|
-
* the client never trusts a server-baked hostname — it reuses its own.
|
|
1539
|
+
* Single buffered chunk in the server-side ring buffer.
|
|
1471
1540
|
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1541
|
+
* Sequence numbers start at 1 and increment per write. Subscribers track
|
|
1542
|
+
* `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
|
|
1543
|
+
* replay any chunks the client missed during a brief disconnect.
|
|
1474
1544
|
*/
|
|
1475
|
-
interface
|
|
1545
|
+
interface BufferedChunk<T> {
|
|
1546
|
+
seq: number;
|
|
1547
|
+
chunk: T;
|
|
1548
|
+
}
|
|
1549
|
+
interface StreamSinkEvents<T> {
|
|
1550
|
+
/** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
|
|
1551
|
+
chunk: (seq: number, chunk: T) => void;
|
|
1552
|
+
/** Terminal — fired exactly once per sink lifetime. */
|
|
1553
|
+
end: (error?: StreamErrorPayload) => void;
|
|
1554
|
+
}
|
|
1555
|
+
interface CreateStreamSinkOptions {
|
|
1556
|
+
id?: string;
|
|
1476
1557
|
/**
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* resolve against the page origin.
|
|
1558
|
+
* Size of the per-stream ring buffer kept for replay-on-resubscribe.
|
|
1559
|
+
* `0` (default) disables replay.
|
|
1480
1560
|
*/
|
|
1481
|
-
|
|
1482
|
-
/** Override the port. Combined with the page hostname unless `host` is set. */
|
|
1483
|
-
port?: number;
|
|
1484
|
-
/** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
|
|
1485
|
-
host?: string;
|
|
1561
|
+
replayWindow?: number;
|
|
1486
1562
|
}
|
|
1487
1563
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
* `host`) only when the SSE endpoint genuinely lives on a different origin
|
|
1492
|
-
* than the page, e.g. a side-car server on its own port.
|
|
1564
|
+
* Server-side producer handle. Two equivalent surfaces share one piece of
|
|
1565
|
+
* state: the imperative `write/error/close` triple, and a `WritableStream<T>`
|
|
1566
|
+
* for `pipeTo`-style consumption.
|
|
1493
1567
|
*/
|
|
1494
|
-
interface
|
|
1495
|
-
/**
|
|
1496
|
-
|
|
1497
|
-
* are resolved against `__connection.json`'s location; absolute paths
|
|
1498
|
-
* (`/__sse`) resolve against the page origin.
|
|
1499
|
-
*/
|
|
1500
|
-
path?: string;
|
|
1501
|
-
/** Override the port. Combined with the page hostname unless `host` is set. */
|
|
1502
|
-
port?: number;
|
|
1503
|
-
/** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
|
|
1504
|
-
host?: string;
|
|
1505
|
-
}
|
|
1506
|
-
interface ConnectionMeta {
|
|
1507
|
-
/**
|
|
1508
|
-
* The server's primary live-RPC transport (`websocket` / `sse`), `static`
|
|
1509
|
-
* for a pre-computed RPC dump with no live server, or `none` for a server
|
|
1510
|
-
* exposing no RPC transport at all (e.g. an MCP-only deployment).
|
|
1511
|
-
*/
|
|
1512
|
-
backend: 'websocket' | 'sse' | 'static' | 'none';
|
|
1513
|
-
/**
|
|
1514
|
-
* WebSocket endpoint, resolved by the client into a `ws(s)://` URL:
|
|
1515
|
-
*
|
|
1516
|
-
* - {@link ConnectionMetaWebsocket} — the proxy-flexible default; a
|
|
1517
|
-
* same-origin path relative to `__connection.json`.
|
|
1518
|
-
* - `number` — a port on the page's hostname (`ws(s)://<host>:<port>`).
|
|
1519
|
-
* - `string` — a full `ws://`/`wss://` URL used verbatim, an `http(s)://`
|
|
1520
|
-
* URL with its protocol swapped, or a path resolved same-origin.
|
|
1521
|
-
*/
|
|
1522
|
-
websocket?: number | string | ConnectionMetaWebsocket;
|
|
1523
|
-
/**
|
|
1524
|
-
* SSE endpoint (`GET` opens the event stream, `POST` carries RPC calls),
|
|
1525
|
-
* resolved by the client into an `http(s)://` URL with the same
|
|
1526
|
-
* proxy-safe rules as {@link ConnectionMeta.websocket}:
|
|
1527
|
-
*
|
|
1528
|
-
* - {@link ConnectionMetaSse} — the proxy-flexible default; a
|
|
1529
|
-
* same-origin path relative to `__connection.json`.
|
|
1530
|
-
* - `string` — a full `http(s)://` URL used verbatim, or a path
|
|
1531
|
-
* resolved same-origin.
|
|
1532
|
-
*/
|
|
1533
|
-
sse?: string | ConnectionMetaSse;
|
|
1534
|
-
/**
|
|
1535
|
-
* Present when the dev server exposes a route-based MCP endpoint
|
|
1536
|
-
* (`cli.mcp`). Advertises the MCP Streamable-HTTP route so in-browser
|
|
1537
|
-
* tooling (e.g. an MCP inspector) can discover it without guessing the
|
|
1538
|
-
* path. `path` is relative to `__connection.json`'s location, like the
|
|
1539
|
-
* WebSocket `path`. `port` is set when the endpoint lives on a side-car
|
|
1540
|
-
* server on its own port (bridge mode — `devframeViteBridge`,
|
|
1541
|
-
* `@devframes/next`): the client combines the page hostname with `port`
|
|
1542
|
-
* and resolves `path` against that origin, mirroring
|
|
1543
|
-
* {@link ConnectionMetaWebsocket.port}.
|
|
1544
|
-
*/
|
|
1545
|
-
mcp?: {
|
|
1546
|
-
path: string;
|
|
1547
|
-
port?: number;
|
|
1548
|
-
};
|
|
1568
|
+
interface StreamSink<T> {
|
|
1569
|
+
/** Stable id used by clients to subscribe. */
|
|
1570
|
+
readonly id: string;
|
|
1549
1571
|
/**
|
|
1550
|
-
*
|
|
1551
|
-
*
|
|
1552
|
-
*
|
|
1553
|
-
* the rest). Populated by the server / build adapter; absent on
|
|
1554
|
-
* legacy clients, in which case all outgoing messages fall back to
|
|
1555
|
-
* structured-clone.
|
|
1572
|
+
* Aborts when the consumer cancels (server-side) or when the transport
|
|
1573
|
+
* loses every subscriber. Producers should poll `signal.aborted` and exit
|
|
1574
|
+
* cleanly.
|
|
1556
1575
|
*/
|
|
1557
|
-
|
|
1576
|
+
readonly signal: AbortSignal;
|
|
1577
|
+
/** `true` after `close()` / `error()`. Further writes throw. */
|
|
1578
|
+
readonly closed: boolean;
|
|
1579
|
+
/** Last allocated sequence number. `0` until the first write. */
|
|
1580
|
+
readonly lastSeq: number;
|
|
1581
|
+
write: (chunk: T) => void;
|
|
1582
|
+
error: (reason: unknown) => void;
|
|
1583
|
+
close: () => void;
|
|
1584
|
+
/** External-cancel path. Aborts the signal so handlers can short-circuit. */
|
|
1585
|
+
abort: (reason?: unknown) => void;
|
|
1586
|
+
/** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
|
|
1587
|
+
readonly writable: WritableStream<T>;
|
|
1558
1588
|
/**
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
* mounting several devframes at `/__foo/`, `/__bar/`, …) inherits a dialable
|
|
1564
|
-
* endpoint rather than resolving the path against its own mount.
|
|
1589
|
+
* Internal — RPC layer subscribes to receive chunk/end notifications.
|
|
1590
|
+
* Not part of the public contract; do not call directly.
|
|
1591
|
+
*
|
|
1592
|
+
* @internal
|
|
1565
1593
|
*/
|
|
1566
|
-
|
|
1594
|
+
readonly events: EventEmitter<StreamSinkEvents<T>>;
|
|
1567
1595
|
/**
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* **hub** serving a **per-frame** connection meta populates this — it
|
|
1571
|
-
* authenticates once at the top level and bakes the resulting token into
|
|
1572
|
-
* the meta each plugin iframe fetches, so a cross-origin frame (which
|
|
1573
|
-
* cannot read the hub's `localStorage`) is still pre-authorized. The
|
|
1574
|
-
* standalone `__connection.json` never carries a token.
|
|
1596
|
+
* Internal — replay buffer. RPC layer reads on (re)subscribe to feed
|
|
1597
|
+
* missed chunks before going live.
|
|
1575
1598
|
*
|
|
1576
|
-
*
|
|
1577
|
-
* > A token in a fetchable JSON is only as protected as the URL serving
|
|
1578
|
-
* > it. Emit it exclusively from hub-controlled, per-frame meta — never
|
|
1579
|
-
* > from a publicly reachable static `__connection.json`.
|
|
1599
|
+
* @internal
|
|
1580
1600
|
*/
|
|
1581
|
-
|
|
1601
|
+
readonly buffer: ReadonlyArray<BufferedChunk<T>>;
|
|
1602
|
+
}
|
|
1603
|
+
interface CreateStreamReaderOptions {
|
|
1604
|
+
id?: string;
|
|
1582
1605
|
/**
|
|
1583
|
-
*
|
|
1584
|
-
*
|
|
1585
|
-
* connection metadata, which the viewer obtains through the host page.
|
|
1586
|
-
*
|
|
1587
|
-
* Treat this as a bearer credential. Keep connection metadata containing the
|
|
1588
|
-
* token same-origin until the requesting origin has been verified.
|
|
1606
|
+
* Maximum number of buffered chunks held client-side while the consumer
|
|
1607
|
+
* isn't draining. On overflow, the oldest chunk is dropped.
|
|
1589
1608
|
*/
|
|
1590
|
-
|
|
1609
|
+
highWaterMark?: number;
|
|
1591
1610
|
/**
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
* client only ever reads `rpc.connectionMeta.configs`, never writes to it.
|
|
1596
|
-
*
|
|
1597
|
-
* Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
|
|
1598
|
-
* and a hub's `devframe:user-settings` shared-state key — both are
|
|
1599
|
-
* mutable, user-editable, and synced bidirectionally over RPC for the
|
|
1600
|
-
* life of the session. `configs` is the opposite: one-way, immutable,
|
|
1601
|
-
* decided by whoever assembled the server.
|
|
1602
|
-
*
|
|
1603
|
-
* Each key is owned by one package, contributed via declaration merging:
|
|
1604
|
-
*
|
|
1605
|
-
* ```ts
|
|
1606
|
-
* declare module 'devframe/types' {
|
|
1607
|
-
* interface DevframeConnectionConfigsRegistry {
|
|
1608
|
-
* 'my-key': { some: 'shape' }
|
|
1609
|
-
* }
|
|
1610
|
-
* }
|
|
1611
|
-
* ```
|
|
1611
|
+
* Called when the chunk queue overflows the high-water mark. The RPC
|
|
1612
|
+
* layer wires this to a coded warning; the primitive itself is
|
|
1613
|
+
* RPC-agnostic.
|
|
1612
1614
|
*/
|
|
1613
|
-
|
|
1615
|
+
onOverflow?: (dropped: number) => void;
|
|
1616
|
+
/** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
|
|
1617
|
+
onCancel?: () => void;
|
|
1614
1618
|
}
|
|
1615
1619
|
/**
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
* {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
|
|
1620
|
-
* augments it with `dock`; `@devframes/hub-ui` augments it with `ui`.
|
|
1620
|
+
* Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
|
|
1621
|
+
* and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one — they
|
|
1622
|
+
* share a single internal queue, so concurrent draining will race.
|
|
1621
1623
|
*/
|
|
1622
|
-
interface
|
|
1623
|
-
|
|
1624
|
-
|
|
1624
|
+
interface StreamReader<T> extends AsyncIterable<T> {
|
|
1625
|
+
readonly id: string;
|
|
1626
|
+
readonly cancelled: boolean;
|
|
1627
|
+
readonly done: boolean;
|
|
1628
|
+
/** Highest `seq` observed. Used for replay on reconnect. */
|
|
1629
|
+
readonly lastSeenSeq: number;
|
|
1630
|
+
/** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
|
|
1631
|
+
readonly readable: ReadableStream<T>;
|
|
1632
|
+
cancel: () => void;
|
|
1633
|
+
/** @internal */
|
|
1634
|
+
_push: (seq: number, chunk: T) => void;
|
|
1635
|
+
/** @internal */
|
|
1636
|
+
_end: (error?: StreamErrorPayload) => void;
|
|
1637
|
+
}
|
|
1625
1638
|
/**
|
|
1626
|
-
*
|
|
1627
|
-
* `
|
|
1628
|
-
*
|
|
1629
|
-
* the handshake RPC functions, the resolver gate, the connect-time trust
|
|
1630
|
-
* hook, and the startup banner.
|
|
1631
|
-
*
|
|
1632
|
-
* `initDevframe` / `initHub` accept one of these directly via their `auth` option —
|
|
1633
|
-
* see {@link https://devfra.me | devframe}'s server docs — or a host can
|
|
1634
|
-
* wire the four pieces itself against a lower-level transport.
|
|
1639
|
+
* Build a server-side stream sink. RPC-agnostic — the RPC host wires
|
|
1640
|
+
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
|
|
1641
|
+
* for late or reconnecting subscribers.
|
|
1635
1642
|
*/
|
|
1636
|
-
|
|
1643
|
+
declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
|
|
1644
|
+
/**
|
|
1645
|
+
* Build a client-side stream reader. RPC-agnostic — the RPC host calls
|
|
1646
|
+
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
|
|
1647
|
+
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
|
|
1648
|
+
*/
|
|
1649
|
+
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
1650
|
+
//#endregion
|
|
1651
|
+
//#region src/types/rpc.d.ts
|
|
1652
|
+
interface DevframeNodeRpcSession {
|
|
1653
|
+
meta: DevframeNodeRpcSessionMeta;
|
|
1654
|
+
rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
1655
|
+
}
|
|
1656
|
+
interface RpcBroadcastOptions<METHOD, Args extends any[]> {
|
|
1657
|
+
method: METHOD;
|
|
1658
|
+
args: Args;
|
|
1659
|
+
optional?: boolean;
|
|
1660
|
+
event?: boolean;
|
|
1661
|
+
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
1662
|
+
}
|
|
1663
|
+
type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
|
|
1637
1664
|
/**
|
|
1638
|
-
*
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
1665
|
+
* Invoke a locally registered server RPC function directly.
|
|
1666
|
+
*
|
|
1667
|
+
* This bypasses transport and is useful for server-side cross-function calls.
|
|
1641
1668
|
*/
|
|
1642
|
-
|
|
1669
|
+
invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
|
|
1643
1670
|
/**
|
|
1644
|
-
*
|
|
1645
|
-
* current trust state. Defaults to allowing any `anonymous:`-prefixed
|
|
1646
|
-
* method (see `isAnonymousRpcMethod`) plus anything once the session is
|
|
1647
|
-
* trusted.
|
|
1671
|
+
* Broadcast a message to all connected clients
|
|
1648
1672
|
*/
|
|
1649
|
-
|
|
1673
|
+
broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
|
|
1650
1674
|
/**
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1654
|
-
* trusted immediately — before the client's own handshake call.
|
|
1675
|
+
* Get the current RPC client
|
|
1676
|
+
*
|
|
1677
|
+
* Available in RPC functions to get the current RPC client
|
|
1655
1678
|
*/
|
|
1656
|
-
|
|
1679
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
1657
1680
|
/**
|
|
1658
|
-
*
|
|
1659
|
-
* headless — call this yourself once the server is listening. Safe to
|
|
1660
|
-
* call repeatedly; it only prints once per code.
|
|
1681
|
+
* The shared state host
|
|
1661
1682
|
*/
|
|
1662
|
-
|
|
1683
|
+
sharedState: RpcSharedStateHost;
|
|
1663
1684
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
* one gated by a pre-shared bearer token instead of an interactive code)
|
|
1669
|
-
* can omit it and the URL opens as-is.
|
|
1685
|
+
* The streaming channel host. Provides per-channel `start()` /
|
|
1686
|
+
* `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
|
|
1687
|
+
*
|
|
1688
|
+
* @see RpcStreamingHost
|
|
1670
1689
|
*/
|
|
1671
|
-
|
|
1690
|
+
streaming: RpcStreamingHost;
|
|
1691
|
+
};
|
|
1692
|
+
interface RpcSharedStateGetOptions<T> {
|
|
1693
|
+
sharedState?: SharedState<T>;
|
|
1694
|
+
initialValue?: T;
|
|
1672
1695
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
* Classification of how a devframe is being deployed. Hosted adapters
|
|
1677
|
-
* (`vite`, `embedded`) share their origin with a host app and must
|
|
1678
|
-
* namespace their mount path under `/__<id>/`. Standalone adapters
|
|
1679
|
-
* (`cli`, `build`) own the origin and default to `/`.
|
|
1680
|
-
*/
|
|
1681
|
-
type DevframeDeploymentKind = 'standalone' | 'hosted';
|
|
1682
|
-
/**
|
|
1683
|
-
* How a hub deduplicates devframes that share an `id` when more than one
|
|
1684
|
-
* is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
|
|
1685
|
-
*
|
|
1686
|
-
* - `'warn'` (default) — keep the first registration, drop later
|
|
1687
|
-
* duplicates, and emit a warning diagnostic (`DF8105`).
|
|
1688
|
-
* - `'silent'` — drop later duplicates without warning.
|
|
1689
|
-
* - `'throw'` — throw when a duplicate is mounted.
|
|
1690
|
-
* - `'duplicate'` — let every instance coexist under a disambiguated
|
|
1691
|
-
* dock id.
|
|
1692
|
-
*/
|
|
1693
|
-
type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
|
|
1694
|
-
/**
|
|
1695
|
-
* Controls where the browser opens the RPC WebSocket — advertised in
|
|
1696
|
-
* `__connection.json` and used to bind the dev server. The three shapes map
|
|
1697
|
-
* to the three connection scenarios; precedence is `url` > `port` > `route`:
|
|
1698
|
-
*
|
|
1699
|
-
* 1. **Same server, different route** (default) — leave `port`/`url` unset.
|
|
1700
|
-
* The socket shares the HTTP server's port and binds to `route`
|
|
1701
|
-
* (`__ws`). The client connects to its own origin, so the link
|
|
1702
|
-
* survives a reverse proxy that rewrites the host/port/subpath.
|
|
1703
|
-
*
|
|
1704
|
-
* 2. **Different port** — set `port`. The socket binds on its own port on the
|
|
1705
|
-
* same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
|
|
1706
|
-
*
|
|
1707
|
-
* 3. **Remote, different origin** — set `url` to a full `ws://`/`wss://`
|
|
1708
|
-
* endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
|
|
1709
|
-
*/
|
|
1710
|
-
interface DevframeWsOptions {
|
|
1696
|
+
interface RpcSharedStateHost {
|
|
1697
|
+
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
1698
|
+
keys: () => string[];
|
|
1711
1699
|
/**
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1700
|
+
* Subscribe to new shared-state keys becoming available. Fires when
|
|
1701
|
+
* `get(key, ...)` creates a fresh entry (not on subsequent gets).
|
|
1702
|
+
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
1703
|
+
* as dynamic resources.
|
|
1714
1704
|
*/
|
|
1715
|
-
|
|
1705
|
+
onKeyAdded: (fn: (key: string) => void) => () => void;
|
|
1716
1706
|
/**
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1707
|
+
* Unregister a shared state and drop its broadcast listeners. Returns
|
|
1708
|
+
* `true` when a state was removed, `false` when the key was unknown.
|
|
1709
|
+
* Used by short-lived states (e.g. a disposed JSON-render view) to avoid
|
|
1710
|
+
* leaking listeners and lingering entries for the context lifetime.
|
|
1720
1711
|
*/
|
|
1721
|
-
|
|
1712
|
+
delete: (key: string) => boolean;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Options for `RpcStreamingHost.create()`.
|
|
1716
|
+
*/
|
|
1717
|
+
interface RpcStreamingChannelOptions {
|
|
1722
1718
|
/**
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1725
|
-
*
|
|
1726
|
-
*
|
|
1719
|
+
* Size of the per-stream ring buffer kept on the server for
|
|
1720
|
+
* replay-on-resubscribe. `0` (default) disables replay; on reconnect
|
|
1721
|
+
* the consumer only sees chunks that arrive after subscribing.
|
|
1722
|
+
*
|
|
1723
|
+
* The buffer is per stream id, not per channel — each `channel.start()`
|
|
1724
|
+
* gets its own.
|
|
1727
1725
|
*/
|
|
1728
|
-
|
|
1726
|
+
replayWindow?: number;
|
|
1729
1727
|
/**
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1728
|
+
* Milliseconds a closed stream is retained on the server after its
|
|
1729
|
+
* last subscriber leaves (or if no subscriber ever arrived). During
|
|
1730
|
+
* this window, late subscribers can still join and replay the buffer
|
|
1731
|
+
* + receive the `end` frame.
|
|
1732
|
+
*
|
|
1733
|
+
* Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
|
|
1734
|
+
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
1735
|
+
* post-mortem replay.
|
|
1732
1736
|
*/
|
|
1733
|
-
|
|
1737
|
+
closedStreamRetention?: number;
|
|
1734
1738
|
}
|
|
1735
1739
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
* disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
|
|
1740
|
+
* Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
|
|
1741
|
+
* channel owns a wire namespace; calling `start()` produces individual
|
|
1742
|
+
* streams keyed by id.
|
|
1743
|
+
*
|
|
1744
|
+
* @see {@link https://devfra.me/guide/streaming Streaming guide}
|
|
1742
1745
|
*/
|
|
1743
|
-
interface
|
|
1746
|
+
interface RpcStreamingChannel<T = unknown> {
|
|
1747
|
+
/** Channel name as registered with `ctx.rpc.streaming.create()`. */
|
|
1748
|
+
readonly name: string;
|
|
1744
1749
|
/**
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1750
|
+
* Start a new stream. Returns a server-side sink with both an imperative
|
|
1751
|
+
* (`write` / `close` / `error`) surface and a `WritableStream<T>` for
|
|
1752
|
+
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
1753
|
+
* disconnects or cancels.
|
|
1747
1754
|
*/
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
* Configuration for the route-based MCP server mounted alongside the dev
|
|
1752
|
-
* server (opt-in via {@link DevframeCliOptions.mcp}). The endpoint speaks
|
|
1753
|
-
* the MCP Streamable-HTTP transport over the same origin as the SPA,
|
|
1754
|
-
* exposing the definition's `ctx.agent` tools + shared-state resources to
|
|
1755
|
-
* external MCP clients connected to the *running* server.
|
|
1756
|
-
*/
|
|
1757
|
-
interface McpRouteOptions {
|
|
1755
|
+
start: (opts?: {
|
|
1756
|
+
id?: string;
|
|
1757
|
+
}) => StreamSink<T>;
|
|
1758
1758
|
/**
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1759
|
+
* Convenience: start a stream and pipe a `ReadableStream<T>` into it.
|
|
1760
|
+
* The pipe uses `sink.signal` so cancellation propagates upstream.
|
|
1761
|
+
*
|
|
1762
|
+
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
1763
|
+
* before passing it here.
|
|
1761
1764
|
*/
|
|
1762
|
-
|
|
1765
|
+
pipeFrom: (readable: ReadableStream<T>, opts?: {
|
|
1766
|
+
id?: string;
|
|
1767
|
+
}) => Promise<StreamSink<T>>;
|
|
1768
|
+
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
1769
|
+
get: (id: string) => StreamSink<T> | undefined;
|
|
1770
|
+
/** All active outbound stream ids on this channel. */
|
|
1771
|
+
ids: () => string[];
|
|
1763
1772
|
/**
|
|
1764
|
-
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1773
|
+
* Open an inbound stream — the server side of a client-to-server
|
|
1774
|
+
* upload. Allocates an id, returns a `StreamReader<T>` that fills as
|
|
1775
|
+
* the client writes chunks. Typical pattern is to call this from an
|
|
1776
|
+
* action handler, kick off background processing, and return the id
|
|
1777
|
+
* so the caller can start uploading:
|
|
1769
1778
|
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1779
|
+
* ```ts
|
|
1780
|
+
* handler: async () => {
|
|
1781
|
+
* const reader = channel.openInbound()
|
|
1782
|
+
* ;(async () => {
|
|
1783
|
+
* for await (const chunk of reader) processChunk(chunk)
|
|
1784
|
+
* })()
|
|
1785
|
+
* return { uploadId: reader.id }
|
|
1786
|
+
* }
|
|
1787
|
+
* ```
|
|
1788
|
+
*
|
|
1789
|
+
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
1790
|
+
* the uploading client, which aborts its sink.
|
|
1774
1791
|
*/
|
|
1775
|
-
|
|
1792
|
+
openInbound: (opts?: {
|
|
1793
|
+
id?: string;
|
|
1794
|
+
}) => StreamReader<T>;
|
|
1776
1795
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
portRange?: [number, number];
|
|
1784
|
-
/** Prefer a random open port. */
|
|
1785
|
-
random?: boolean;
|
|
1786
|
-
/** Default host to bind to; `--host` overrides. */
|
|
1787
|
-
host?: string;
|
|
1796
|
+
/**
|
|
1797
|
+
* Server-side streaming host. Lives on `ctx.rpc.streaming` alongside
|
|
1798
|
+
* `ctx.rpc.sharedState`. Each named channel owns its own stream registry
|
|
1799
|
+
* and wire namespace.
|
|
1800
|
+
*/
|
|
1801
|
+
interface RpcStreamingHost {
|
|
1788
1802
|
/**
|
|
1789
|
-
*
|
|
1790
|
-
* `
|
|
1791
|
-
*
|
|
1803
|
+
* Register a streaming channel. Names follow the `<plugin-id>:<channel>`
|
|
1804
|
+
* convention (e.g. `'my-devtool:chat-stream'`). Throws `DF0032` if the
|
|
1805
|
+
* name is already taken.
|
|
1792
1806
|
*/
|
|
1793
|
-
|
|
1807
|
+
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
1794
1808
|
/**
|
|
1795
|
-
*
|
|
1796
|
-
*
|
|
1797
|
-
*
|
|
1798
|
-
* served `build`) auto-wire devframe's interactive OTP auth
|
|
1799
|
-
* (`createInteractiveAuth`): an untrusted client can only reach
|
|
1800
|
-
* `anonymous:` methods until it exchanges the printed one-time code.
|
|
1801
|
-
* The adapter prints the code + magic-link banner once the server is
|
|
1802
|
-
* listening.
|
|
1803
|
-
* - `false` — no gate, for trusted single-user localhost tools where an
|
|
1804
|
-
* auth round-trip only gets in the way (the built-in plugins set this).
|
|
1805
|
-
* The `--no-auth` CLI flag maps here for one-off runs.
|
|
1806
|
-
* - A {@link DevframeAuthHandler} — a custom handler (e.g. a tuned
|
|
1807
|
-
* `createInteractiveAuth`, or an entirely different scheme) passed
|
|
1808
|
-
* straight through to the RPC transport binding.
|
|
1809
|
-
*
|
|
1810
|
-
* Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
|
|
1811
|
-
* auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
|
|
1809
|
+
* Adapters call this when a session disconnects so the host can drop
|
|
1810
|
+
* subscribers and abort orphaned streams. Most users do not need this;
|
|
1811
|
+
* it's wired by the RPC server binding automatically.
|
|
1812
1812
|
*
|
|
1813
|
-
* @
|
|
1813
|
+
* @internal
|
|
1814
1814
|
*/
|
|
1815
|
-
|
|
1815
|
+
_onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
|
|
1816
|
+
}
|
|
1817
|
+
//#endregion
|
|
1818
|
+
//#region src/types/context.d.ts
|
|
1819
|
+
interface DevframeCapabilities {
|
|
1820
|
+
rpc?: boolean;
|
|
1821
|
+
views?: boolean;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Framework- and build-tool-agnostic node context — RPC + diagnostics +
|
|
1825
|
+
* agent + the view-host (HTTP file-serving). Host adapters can wrap this
|
|
1826
|
+
* to add their own surfaces; for example, `@vitejs/devtools-kit`'s
|
|
1827
|
+
* `createKitContext` adds `docks`, `terminals`, `messages`, and
|
|
1828
|
+
* `commands` when mounted into Vite DevTools. JSON rendering is an opt-in
|
|
1829
|
+
* integration (`@devframes/json-render`) layered on top, not part of this
|
|
1830
|
+
* core surface.
|
|
1831
|
+
*/
|
|
1832
|
+
interface DevframeNodeContext {
|
|
1833
|
+
readonly workspaceRoot: string;
|
|
1834
|
+
readonly cwd: string;
|
|
1816
1835
|
/**
|
|
1817
|
-
*
|
|
1818
|
-
* MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
|
|
1819
|
-
* It surfaces the same `ctx.agent` tools + shared-state resources as the
|
|
1820
|
-
* stdio `mcp` command, but against the live, running server.
|
|
1836
|
+
* Lifecycle distinction surfaced to plugin authors:
|
|
1821
1837
|
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1824
|
-
*
|
|
1825
|
-
* -
|
|
1838
|
+
* - `'dev'` — long-running, interactive session. Connections come and
|
|
1839
|
+
* go; broadcasts and shared-state mutations are debounced
|
|
1840
|
+
* to keep the UI responsive.
|
|
1841
|
+
* - `'build'` — one-shot batch run. The context is set up, the devtool
|
|
1842
|
+
* collects what it needs, and a snapshot is written. No
|
|
1843
|
+
* live UI, no WS server.
|
|
1826
1844
|
*
|
|
1827
|
-
*
|
|
1845
|
+
* Names are inherited from Vite's serve/build dichotomy but the meaning
|
|
1846
|
+
* is general: the same distinction applies to any tool that runs in
|
|
1847
|
+
* either an interactive or a static-output mode.
|
|
1828
1848
|
*/
|
|
1829
|
-
|
|
1849
|
+
readonly mode: 'dev' | 'build';
|
|
1830
1850
|
/**
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* served through devframe's caching CDN back-proxy so the assets need not
|
|
1834
|
-
* ship inside the node package.
|
|
1851
|
+
* Host runtime abstraction — exposes `mountStatic` / `resolveOrigin` /
|
|
1852
|
+
* `getStorageDir`.
|
|
1835
1853
|
*/
|
|
1836
|
-
|
|
1854
|
+
host: DevframeHost;
|
|
1855
|
+
rpc: RpcFunctionsHost;
|
|
1856
|
+
views: DevframeViewHost;
|
|
1837
1857
|
/**
|
|
1838
|
-
*
|
|
1839
|
-
*
|
|
1840
|
-
* different-port and remote-origin variants. Pass `false` to serve no
|
|
1841
|
-
* WebSocket at all — clients connect over SSE instead (`backend: 'sse'`).
|
|
1858
|
+
* Structured diagnostics host — wraps `nostics` and lets integrations
|
|
1859
|
+
* register their own coded errors/warnings into the shared lookup.
|
|
1842
1860
|
*/
|
|
1843
|
-
|
|
1861
|
+
diagnostics: DevframeDiagnosticsHost;
|
|
1844
1862
|
/**
|
|
1845
|
-
*
|
|
1846
|
-
* alongside the WebSocket as the more portable transport. Pass `false`
|
|
1847
|
-
* to disable, or a {@link DevframeSseOptions} to change its route.
|
|
1863
|
+
* Agent host — aggregates the agent-exposed surface of this devtool.
|
|
1848
1864
|
*/
|
|
1849
|
-
|
|
1865
|
+
agent: DevframeAgentHost;
|
|
1850
1866
|
/**
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1853
|
-
*
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1867
|
+
* Cross-plugin services — a typed, namespaced registry through which one
|
|
1868
|
+
* integration exposes a capability (e.g. a data-source registry) and
|
|
1869
|
+
* others consume it without a hard package dependency. Ids follow the RPC
|
|
1870
|
+
* namespacing rule (`<plugin-id>:<service>`); types come from augmenting
|
|
1871
|
+
* the `DevframeServicesRegistry` interface. `whenAvailable` subscriptions
|
|
1872
|
+
* absorb setup-order differences between provider and consumer.
|
|
1856
1873
|
*/
|
|
1857
|
-
|
|
1874
|
+
services: DevframeServicesHost;
|
|
1858
1875
|
/**
|
|
1859
|
-
*
|
|
1860
|
-
*
|
|
1861
|
-
*
|
|
1862
|
-
*
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1865
|
-
* Use {@link defineCliFlags} to preserve the literal schema-map
|
|
1866
|
-
* shape, and {@link InferCliFlags} to recover the typed output at the
|
|
1867
|
-
* call site:
|
|
1876
|
+
* This context's own {@link ConnectionMeta.configs} — static, boot-time
|
|
1877
|
+
* config a host publishes once through the connection handshake and every
|
|
1878
|
+
* client reads read-only. A plain, **non-reactive** object: mutate it
|
|
1879
|
+
* during `setup(ctx)` (a plugin sets its keys, a hub aggregates across
|
|
1880
|
+
* every installed devframe), never during the session — it's serialized
|
|
1881
|
+
* once, after setup, and changing it afterwards reaches no client.
|
|
1868
1882
|
*
|
|
1869
1883
|
* ```ts
|
|
1870
|
-
*
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
* }
|
|
1874
|
-
*
|
|
1875
|
-
* defineDevframe({
|
|
1876
|
-
* cli: { flags: appFlags },
|
|
1877
|
-
* setup(ctx, info) {
|
|
1878
|
-
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
1879
|
-
* },
|
|
1880
|
-
* })
|
|
1884
|
+
* ctx.staticConfig.dock = {
|
|
1885
|
+
* ...ctx.staticConfig.dock,
|
|
1886
|
+
* categoryOrder: { ...ctx.staticConfig.dock?.categoryOrder, ...myOrder },
|
|
1887
|
+
* }
|
|
1881
1888
|
* ```
|
|
1882
1889
|
*/
|
|
1883
|
-
|
|
1890
|
+
staticConfig: Partial<DevframeConnectionConfigsRegistry>;
|
|
1891
|
+
/**
|
|
1892
|
+
* Create a namespace-scoped view of this context. The returned
|
|
1893
|
+
* `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
|
|
1894
|
+
* key, and streaming channel with `my-plugin:`, and exposes a typed
|
|
1895
|
+
* top-level `settings` store. This is the preferred way to consume the
|
|
1896
|
+
* context from a single tool's setup code.
|
|
1897
|
+
*
|
|
1898
|
+
* Pass `null` or `''` to un-scope and get the base context.
|
|
1899
|
+
*/
|
|
1900
|
+
scope: {
|
|
1901
|
+
<NS extends string>(namespace: NS): DevframeScopedNodeContext<NS, SettingsForNamespace<NS>>;
|
|
1902
|
+
(namespace?: null | ''): DevframeNodeContext;
|
|
1903
|
+
};
|
|
1884
1904
|
}
|
|
1885
1905
|
/**
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1906
|
+
* Describes where the browser client should open its RPC WebSocket. The
|
|
1907
|
+
* object form is the proxy-flexible default: `path` is resolved relative to
|
|
1908
|
+
* where `__connection.json` was loaded, and the connection is made to the
|
|
1909
|
+
* page's own origin (only the `http`→`ws` / `https`→`wss` protocol swap is
|
|
1910
|
+
* applied). This survives reverse proxies that change the host/port, because
|
|
1911
|
+
* the client never trusts a server-baked hostname — it reuses its own.
|
|
1890
1912
|
*
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
* Standalone adapters (`cli` / `build`) ignore this entirely.
|
|
1913
|
+
* Set `port` (and/or `host`) only when the WS endpoint genuinely lives on a
|
|
1914
|
+
* different origin than the page, e.g. a side-car server on its own port.
|
|
1894
1915
|
*/
|
|
1895
|
-
interface
|
|
1896
|
-
/** Dock entry title. Defaults to the definition's `name`. */
|
|
1897
|
-
title?: string;
|
|
1898
|
-
/** Dock entry icon. Defaults to the definition's `icon`. */
|
|
1899
|
-
icon?: string | {
|
|
1900
|
-
light: string;
|
|
1901
|
-
dark: string;
|
|
1902
|
-
};
|
|
1916
|
+
interface ConnectionMetaWebsocket {
|
|
1903
1917
|
/**
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1918
|
+
* Path to the WS endpoint. Relative paths (the default, e.g. `__ws`) are
|
|
1919
|
+
* resolved against `__connection.json`'s location; absolute paths (`/__ws`)
|
|
1920
|
+
* resolve against the page origin.
|
|
1906
1921
|
*/
|
|
1907
|
-
|
|
1922
|
+
path?: string;
|
|
1923
|
+
/** Override the port. Combined with the page hostname unless `host` is set. */
|
|
1924
|
+
port?: number;
|
|
1925
|
+
/** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
|
|
1926
|
+
host?: string;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Object form of {@link ConnectionMeta.sse} — the same proxy-safe shape and
|
|
1930
|
+
* resolution rules as {@link ConnectionMetaWebsocket}, producing an
|
|
1931
|
+
* `http(s)://` endpoint instead of a `ws(s)://` one. Set `port` (and/or
|
|
1932
|
+
* `host`) only when the SSE endpoint genuinely lives on a different origin
|
|
1933
|
+
* than the page, e.g. a side-car server on its own port.
|
|
1934
|
+
*/
|
|
1935
|
+
interface ConnectionMetaSse {
|
|
1908
1936
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1937
|
+
* Path to the SSE endpoint. Relative paths (the default, e.g. `__sse`)
|
|
1938
|
+
* are resolved against `__connection.json`'s location; absolute paths
|
|
1939
|
+
* (`/__sse`) resolve against the page origin.
|
|
1911
1940
|
*/
|
|
1912
|
-
|
|
1941
|
+
path?: string;
|
|
1942
|
+
/** Override the port. Combined with the page hostname unless `host` is set. */
|
|
1943
|
+
port?: number;
|
|
1944
|
+
/** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
|
|
1945
|
+
host?: string;
|
|
1946
|
+
}
|
|
1947
|
+
interface ConnectionMeta {
|
|
1913
1948
|
/**
|
|
1914
|
-
*
|
|
1915
|
-
*
|
|
1949
|
+
* The server's primary live-RPC transport (`websocket` / `sse`), `static`
|
|
1950
|
+
* for a pre-computed RPC dump with no live server, or `none` for a server
|
|
1951
|
+
* exposing no RPC transport at all (e.g. an MCP-only deployment).
|
|
1916
1952
|
*/
|
|
1917
|
-
|
|
1953
|
+
backend: 'websocket' | 'sse' | 'static' | 'none';
|
|
1918
1954
|
/**
|
|
1919
|
-
*
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1955
|
+
* WebSocket endpoint, resolved by the client into a `ws(s)://` URL:
|
|
1956
|
+
*
|
|
1957
|
+
* - {@link ConnectionMetaWebsocket} — the proxy-flexible default; a
|
|
1958
|
+
* same-origin path relative to `__connection.json`.
|
|
1959
|
+
* - `number` — a port on the page's hostname (`ws(s)://<host>:<port>`).
|
|
1960
|
+
* - `string` — a full `ws://`/`wss://` URL used verbatim, an `http(s)://`
|
|
1961
|
+
* URL with its protocol swapped, or a path resolved same-origin.
|
|
1923
1962
|
*/
|
|
1924
|
-
|
|
1925
|
-
/** Badge text rendered on the dock icon (e.g. an unread count). */
|
|
1926
|
-
badge?: string;
|
|
1927
|
-
/** Id of the dock group this entry collapses under, if any. */
|
|
1928
|
-
groupId?: string;
|
|
1929
|
-
}
|
|
1930
|
-
/**
|
|
1931
|
-
* Runtime information threaded into `setup(ctx, info)`. Adapters
|
|
1932
|
-
* populate the fields that make sense for their deployment. In
|
|
1933
|
-
* particular, `createCac` fills `flags` with the parsed CAC bag.
|
|
1934
|
-
*/
|
|
1935
|
-
interface DevframeSetupInfo {
|
|
1936
|
-
/** Parsed CLI flags, populated by the CLI adapter. */
|
|
1937
|
-
flags?: Record<string, unknown>;
|
|
1938
|
-
}
|
|
1939
|
-
interface DevframeDefinition {
|
|
1940
|
-
id: string;
|
|
1941
|
-
name: string;
|
|
1942
|
-
/** Semver of the tool, surfaced in hub UIs and diagnostics. */
|
|
1943
|
-
version: string;
|
|
1944
|
-
/** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
|
|
1945
|
-
packageName: string;
|
|
1963
|
+
websocket?: number | string | ConnectionMetaWebsocket;
|
|
1946
1964
|
/**
|
|
1947
|
-
* `
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
1950
|
-
* everything against the plugin's own installed packages rather than the
|
|
1951
|
-
* consuming app's.
|
|
1952
|
-
*
|
|
1953
|
-
* - **Remote assets** — becomes the default `resolveFrom` for any remote
|
|
1954
|
-
* {@link StaticAssetsSource} the devframe hosts (its `cli.distDir`, and
|
|
1955
|
-
* every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
|
|
1956
|
-
* locally installed copy of the assets package is served with zero
|
|
1957
|
-
* network. A per-asset `resolveFrom` still wins, and an explicit
|
|
1958
|
-
* `resolveFrom: null` still opts out.
|
|
1959
|
-
* - **Service dependencies** — becomes the base the host resolves declared
|
|
1960
|
-
* {@link DevframeServiceInput | services} from, so a plugin can ship a
|
|
1961
|
-
* service package as its own dependency instead of asking users to install
|
|
1962
|
-
* it.
|
|
1965
|
+
* SSE endpoint (`GET` opens the event stream, `POST` carries RPC calls),
|
|
1966
|
+
* resolved by the client into an `http(s)://` URL with the same
|
|
1967
|
+
* proxy-safe rules as {@link ConnectionMeta.websocket}:
|
|
1963
1968
|
*
|
|
1964
|
-
*
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
1969
|
+
* - {@link ConnectionMetaSse} — the proxy-flexible default; a
|
|
1970
|
+
* same-origin path relative to `__connection.json`.
|
|
1971
|
+
* - `string` — a full `http(s)://` URL used verbatim, or a path
|
|
1972
|
+
* resolved same-origin.
|
|
1967
1973
|
*/
|
|
1968
|
-
|
|
1969
|
-
/**
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1974
|
+
sse?: string | ConnectionMetaSse;
|
|
1975
|
+
/**
|
|
1976
|
+
* Present when the dev server exposes a route-based MCP endpoint
|
|
1977
|
+
* (`cli.mcp`). Advertises the MCP Streamable-HTTP route so in-browser
|
|
1978
|
+
* tooling (e.g. an MCP inspector) can discover it without guessing the
|
|
1979
|
+
* path. `path` is relative to `__connection.json`'s location, like the
|
|
1980
|
+
* WebSocket `path`. `port` is set when the endpoint lives on a side-car
|
|
1981
|
+
* server on its own port (bridge mode — `devframeViteBridge`,
|
|
1982
|
+
* `@devframes/next`): the client combines the page hostname with `port`
|
|
1983
|
+
* and resolves `path` against that origin, mirroring
|
|
1984
|
+
* {@link ConnectionMetaWebsocket.port}.
|
|
1985
|
+
*/
|
|
1986
|
+
mcp?: {
|
|
1987
|
+
path: string;
|
|
1988
|
+
port?: number;
|
|
1976
1989
|
};
|
|
1977
1990
|
/**
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1991
|
+
* Names of RPC functions that have declared `jsonSerializable: true`.
|
|
1992
|
+
* Used by the WS / static client to dispatch the per-call wire
|
|
1993
|
+
* serializer (strict JSON for these methods, structured-clone for
|
|
1994
|
+
* the rest). Populated by the server / build adapter; absent on
|
|
1995
|
+
* legacy clients, in which case all outgoing messages fall back to
|
|
1996
|
+
* structured-clone.
|
|
1984
1997
|
*/
|
|
1985
|
-
|
|
1998
|
+
jsonSerializableMethods?: string[];
|
|
1986
1999
|
/**
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
2000
|
+
* Absolute URL of the `__connection.json` this meta was loaded from.
|
|
2001
|
+
* Annotated by the client (not served) when it publishes the meta on a
|
|
2002
|
+
* shared window for same-origin inheritance: a relative `websocket.path`
|
|
2003
|
+
* resolves against this, so a child SPA mounted at another base (e.g. a hub
|
|
2004
|
+
* mounting several devframes at `/__foo/`, `/__bar/`, …) inherits a dialable
|
|
2005
|
+
* endpoint rather than resolving the path against its own mount.
|
|
1990
2006
|
*/
|
|
1991
|
-
|
|
2007
|
+
baseUrl?: string;
|
|
1992
2008
|
/**
|
|
1993
|
-
*
|
|
1994
|
-
*
|
|
1995
|
-
*
|
|
1996
|
-
*
|
|
2009
|
+
* A pre-issued bearer token embedded in the meta so a client trusts the
|
|
2010
|
+
* server on connect without any prompt or `localStorage` lookup. Only a
|
|
2011
|
+
* **hub** serving a **per-frame** connection meta populates this — it
|
|
2012
|
+
* authenticates once at the top level and bakes the resulting token into
|
|
2013
|
+
* the meta each plugin iframe fetches, so a cross-origin frame (which
|
|
2014
|
+
* cannot read the hub's `localStorage`) is still pre-authorized. The
|
|
2015
|
+
* standalone `__connection.json` never carries a token.
|
|
1997
2016
|
*
|
|
1998
|
-
*
|
|
2017
|
+
* > [!WARNING]
|
|
2018
|
+
* > A token in a fetchable JSON is only as protected as the URL serving
|
|
2019
|
+
* > it. Emit it exclusively from hub-controlled, per-frame meta — never
|
|
2020
|
+
* > from a publicly reachable static `__connection.json`.
|
|
1999
2021
|
*/
|
|
2000
|
-
|
|
2022
|
+
authToken?: string;
|
|
2001
2023
|
/**
|
|
2002
|
-
*
|
|
2003
|
-
*
|
|
2024
|
+
* Session-scoped token that lets a trusted external viewer register its
|
|
2025
|
+
* browser origin before opening the WebSocket. The host includes it in the
|
|
2026
|
+
* connection metadata, which the viewer obtains through the host page.
|
|
2004
2027
|
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2007
|
-
* Useful for a devframe whose value is inherently live (e.g. it manages
|
|
2008
|
-
* real files on disk), so a static export would only ever produce a
|
|
2009
|
-
* broken, write-less shell of the tool.
|
|
2010
|
-
* - `dev: false` — `createDevServer` refuses (throws `DF0058`) unless
|
|
2011
|
-
* `{ force: true }`. Useful for a devframe that only makes sense as a
|
|
2012
|
-
* static export (e.g. a report generator with nothing to serve live).
|
|
2028
|
+
* Treat this as a bearer credential. Keep connection metadata containing the
|
|
2029
|
+
* token same-origin until the requesting origin has been verified.
|
|
2013
2030
|
*/
|
|
2014
|
-
|
|
2015
|
-
dev?: boolean;
|
|
2016
|
-
build?: boolean;
|
|
2017
|
-
};
|
|
2031
|
+
viewerOriginToken?: string;
|
|
2018
2032
|
/**
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
2025
|
-
* and
|
|
2026
|
-
*
|
|
2027
|
-
*
|
|
2028
|
-
*
|
|
2033
|
+
* Static, host-declared configuration — baked in once at connect time and
|
|
2034
|
+
* fixed for the life of the server (e.g. a hub's UI rebrand, or its
|
|
2035
|
+
* aggregated dock-bar layout preferences). Read-only from the browser: a
|
|
2036
|
+
* client only ever reads `rpc.connectionMeta.configs`, never writes to it.
|
|
2037
|
+
*
|
|
2038
|
+
* Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
|
|
2039
|
+
* and a hub's `devframe:user-settings` shared-state key — both are
|
|
2040
|
+
* mutable, user-editable, and synced bidirectionally over RPC for the
|
|
2041
|
+
* life of the session. `configs` is the opposite: one-way, immutable,
|
|
2042
|
+
* decided by whoever assembled the server.
|
|
2043
|
+
*
|
|
2044
|
+
* Each key is owned by one package, contributed via declaration merging:
|
|
2045
|
+
*
|
|
2046
|
+
* ```ts
|
|
2047
|
+
* declare module 'devframe/types' {
|
|
2048
|
+
* interface DevframeConnectionConfigsRegistry {
|
|
2049
|
+
* 'my-key': { some: 'shape' }
|
|
2050
|
+
* }
|
|
2051
|
+
* }
|
|
2052
|
+
* ```
|
|
2029
2053
|
*/
|
|
2030
|
-
|
|
2031
|
-
/** Server-side setup — the primary entrypoint. Runs in every runtime. */
|
|
2032
|
-
setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
|
|
2033
|
-
cli?: DevframeCliOptions;
|
|
2054
|
+
configs?: Partial<DevframeConnectionConfigsRegistry>;
|
|
2034
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Augmentation point for {@link ConnectionMeta.configs}. Empty by default —
|
|
2058
|
+
* a package that wants to publish static, boot-time config through the
|
|
2059
|
+
* connection handshake augments this interface with its own key (see
|
|
2060
|
+
* {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
|
|
2061
|
+
* augments it with `dock`; `@devframes/hub-ui` augments it with `ui`.
|
|
2062
|
+
*/
|
|
2063
|
+
interface DevframeConnectionConfigsRegistry {}
|
|
2035
2064
|
//#endregion
|
|
2036
|
-
export {
|
|
2065
|
+
export { DevframeServicesHost as $, SharedStateEvents as A, AgentResource as At, DevframeSetupInfo as B, EventUnsubscribe as Bt, createStreamSink as C, DevframeAuthHandler as Ct, ImmutableObject as D, parseCliFlags as Dt, ImmutableMap as E, defineCliFlags as Et, DevframeDefinition as F, AgentToolProvider as Ft, McpRouteOptions as G, DevframeSnapshotRpcInputs as H, DevframeDeploymentKind as I, AgentToolProviderHandle as It, DevframeServiceId as J, DevframeServiceDefinition as K, DevframeDockDefaults as L, DevframeAgentHost as Lt, SharedStatePatch as M, AgentResourceInput as Mt, createSharedState as N, AgentTool as Nt, ImmutableSet as O, AgentHandle as Ot, DevframeCliOptions as P, AgentToolInput as Pt, DevframeServiceScopeOf as Q, DevframeDuplicationStrategy as R, DevframeAgentHostEvents as Rt, createStreamReader as S, DevframeDiagnosticsLogger as St, ImmutableArray as T, InferCliFlags as Tt, DevframeSseOptions as U, DevframeSnapshotRpcEntry as V, EventsMap as Vt, DevframeWsOptions as W, DevframeServiceMeta as X, DevframeServiceInput as Y, DevframeServiceOf as Z, CreateStreamSinkOptions as _, DevframeRpcServerFunctions as _t, DevframeConnectionConfigsRegistry as a, DevframeScopedStreamingHost as at, StreamSink as b, DevframeStorageScope as bt, RpcBroadcastOptions as c, DevframeSettingsStore as ct, RpcSharedStateHost as d, ScopedRpcFn as dt, DevframeServicesRegistry as et, RpcStreamingChannel as f, ScopedServerFunctions as ft, CreateStreamReaderOptions as g, DevframeRpcClientFunctions as gt, BufferedChunk as h, DevframeViewHost as ht, DevframeCapabilities as i, DevframeScopedNodeRpc as it, SharedStateOptions as j, AgentResourceContent as jt, SharedState as k, AgentManifest as kt, RpcFunctionsHost as l, ScopedBroadcastOptions as lt, RpcStreamingHost as m, SettingsForNamespace as mt, ConnectionMetaSse as n, DevframeServicesState as nt, DevframeNodeContext as o, DevframeSettings as ot, RpcStreamingChannelOptions as p, ScopedSharedStates as pt, DevframeServiceDescriptor as q, ConnectionMetaWebsocket as r, DevframeScopedNodeContext as rt, DevframeNodeRpcSession as s, DevframeSettingsRegistry as st, ConnectionMeta as t, DevframeServicesScopeRegistry as tt, RpcSharedStateGetOptions as u, ScopedClientFunctions as ut, StreamErrorPayload as v, DevframeRpcSharedStates as vt, Immutable as w, CliFlagsSchema as wt, StreamSinkEvents as x, DevframeDiagnosticsHost as xt, StreamReader as y, DevframeHost as yt, DevframeRpcOptions as z, EventEmitter as zt };
|