devframe 0.7.2 → 0.7.4
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/adapters/build.d.mts +1 -1
- package/dist/adapters/build.mjs +1 -1
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +1 -1
- package/dist/adapters/cli.d.mts +1 -1
- package/dist/adapters/cli.mjs +1 -1
- package/dist/adapters/dev.d.mts +2 -2
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/{build-server-Bo0G5XcS.mjs → build-server-vGNtqlOd.mjs} +7 -3
- package/dist/{cac-BFCgKymz.mjs → cac-CZk1y4rm.mjs} +2 -2
- package/dist/client/index.d.mts +18 -2
- package/dist/client/index.mjs +27 -4
- package/dist/{context-CKJheEtf.mjs → context-ClRdkpJ7.mjs} +1 -1
- package/dist/{context-B1sekkgB.d.mts → context-DxFsOWkc.d.mts} +1 -1
- package/dist/{dev-Bs4oRVnI.mjs → dev-CicFHIt8.mjs} +18 -5
- package/dist/{devframe-CnTVuGw9.d.mts → devframe-WHtcqYlZ.d.mts} +705 -554
- package/dist/helpers/vite.d.mts +1 -1
- package/dist/helpers/vite.mjs +2 -2
- package/dist/{host-h3-Dsf-Tgwx.mjs → host-h3-DBzbpjXk.mjs} +51 -4
- package/dist/{http-BirzlyE9.mjs → http-DHckZCYZ.mjs} +1 -1
- package/dist/{index-Dpsr-8dD.d.mts → index-Cj0M3G3F.d.mts} +3 -47
- package/dist/index.d.mts +3 -3
- package/dist/node/auth.d.mts +2 -1
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +1 -1
- package/dist/node/index.d.mts +23 -6
- package/dist/node/index.mjs +4 -4
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +1 -1
- package/dist/recipes/open-helpers.d.mts +1 -1
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/{server-4QH37sb-.d.mts → server-BRxA2OAh.d.mts} +3 -3
- package/dist/{server-CRFYfRqK.mjs → server-DvrOR1LG.mjs} +2 -2
- package/dist/{storage-yrUZaiib.mjs → storage-E9KrCsLr.mjs} +4 -0
- package/dist/types/index.d.mts +3 -3
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/{ws-server-CceOy6He.d.mts → ws-server-9-wn7MNQ.d.mts} +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { _ as RpcFunctionDefinition } from "./types-CrzNxXKq.mjs";
|
|
1
|
+
import { _ as RpcFunctionDefinition, v as RpcFunctionDefinitionAny } from "./types-CrzNxXKq.mjs";
|
|
2
2
|
import { S as RpcFunctionsCollectorBase } from "./index-DgsLFhZg.mjs";
|
|
3
|
-
import { t as DevframeNodeRpcSessionMeta } from "./ws-server-
|
|
3
|
+
import { t as DevframeNodeRpcSessionMeta } from "./ws-server-9-wn7MNQ.mjs";
|
|
4
4
|
import { CAC } from "cac";
|
|
5
5
|
import { GenericSchema, InferOutput } from "valibot";
|
|
6
|
+
import { Peer } from "crossws";
|
|
6
7
|
import { BirpcReturn } from "birpc";
|
|
7
8
|
import { Diagnostic, DiagnosticDefinition, defineDiagnostics } from "nostics";
|
|
8
9
|
//#region src/adapters/flags.d.ts
|
|
@@ -284,134 +285,185 @@ interface DevframeAgentHost {
|
|
|
284
285
|
getResource: (id: string) => AgentResource | undefined;
|
|
285
286
|
}
|
|
286
287
|
//#endregion
|
|
287
|
-
//#region src/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
*/
|
|
295
|
-
type DevframeDiagnosticsDefinition = ReturnType<typeof defineDiagnostics<any, any>>;
|
|
296
|
-
/**
|
|
297
|
-
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
|
|
298
|
-
* any registered code name to its `nostics` handle (a callable that builds
|
|
299
|
-
* a diagnostic and routes it through registered reporters). Typed loosely
|
|
300
|
-
* because it spans heterogeneous definitions registered by different
|
|
301
|
-
* integrations.
|
|
302
|
-
*/
|
|
303
|
-
type DevframeDiagnosticsLogger = Record<string, any>;
|
|
288
|
+
//#region src/utils/shared-state.d.ts
|
|
289
|
+
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
290
|
+
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>;
|
|
291
|
+
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
292
|
+
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
293
|
+
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
294
|
+
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
|
|
304
295
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* plugins typically omit `reporters`.
|
|
296
|
+
* Serializable patch describing a single mutation to a `SharedState`.
|
|
297
|
+
* Structurally compatible with JSON-Patch and is safe to send over RPC.
|
|
308
298
|
*/
|
|
309
|
-
interface
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
299
|
+
interface SharedStatePatch {
|
|
300
|
+
op: 'add' | 'remove' | 'replace';
|
|
301
|
+
path: readonly (string | number)[];
|
|
302
|
+
value?: unknown;
|
|
313
303
|
}
|
|
314
304
|
/**
|
|
315
|
-
*
|
|
316
|
-
* integrations register their own coded errors/warnings into a shared
|
|
317
|
-
* registry without taking a direct dependency on `nostics`.
|
|
318
|
-
*
|
|
319
|
-
* Typical usage from a plugin's `setup(ctx)`:
|
|
320
|
-
*
|
|
321
|
-
* ```ts
|
|
322
|
-
* const myDiagnostics = ctx.diagnostics.defineDiagnostics({
|
|
323
|
-
* docsBase: 'https://example.com/errors',
|
|
324
|
-
* codes: {
|
|
325
|
-
* MYP0001: { why: 'Something went wrong' },
|
|
326
|
-
* },
|
|
327
|
-
* })
|
|
328
|
-
* ctx.diagnostics.register(myDiagnostics)
|
|
329
|
-
*
|
|
330
|
-
* // Through the shared lookup (loose typing):
|
|
331
|
-
* throw ctx.diagnostics.logger.MYP0001()
|
|
332
|
-
*
|
|
333
|
-
* // Or directly on the typed handle returned from `defineDiagnostics`:
|
|
334
|
-
* throw myDiagnostics.MYP0001()
|
|
335
|
-
* ```
|
|
305
|
+
* State host that is immutable by default with explicit mutate.
|
|
336
306
|
*/
|
|
337
|
-
interface
|
|
307
|
+
interface SharedState<T> {
|
|
338
308
|
/**
|
|
339
|
-
*
|
|
340
|
-
* Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
|
|
341
|
-
* diagnostic and routes it through registered reporters; prefix with
|
|
342
|
-
* `throw` to raise. Loosely typed — for autocompletion, keep a reference
|
|
343
|
-
* to the typed result of `defineDiagnostics()` instead.
|
|
309
|
+
* Get the current state. Immutable.
|
|
344
310
|
*/
|
|
345
|
-
|
|
311
|
+
value: () => Immutable<T>;
|
|
346
312
|
/**
|
|
347
|
-
*
|
|
348
|
-
* registration, codes from the new definition are reachable via
|
|
349
|
-
* `host.logger.CODE`. Plugins that want shared output formatting should
|
|
350
|
-
* build their diagnostics via `host.defineDiagnostics()` first — that
|
|
351
|
-
* factory pre-wires the host's ANSI console reporter.
|
|
313
|
+
* Subscribe to state changes.
|
|
352
314
|
*/
|
|
353
|
-
|
|
315
|
+
on: EventEmitter<SharedStateEvents<T>>['on'];
|
|
354
316
|
/**
|
|
355
|
-
*
|
|
356
|
-
* pre-wired. Mirrors `nostics`'s `defineDiagnostics` so integrations don't
|
|
357
|
-
* need to take a direct dependency on `nostics`.
|
|
317
|
+
* Mutate the state.
|
|
358
318
|
*/
|
|
359
|
-
|
|
319
|
+
mutate: (fn: (state: T) => void, syncId?: string) => void;
|
|
320
|
+
/**
|
|
321
|
+
* Apply patches to the state.
|
|
322
|
+
*/
|
|
323
|
+
patch: (patches: SharedStatePatch[], syncId?: string) => void;
|
|
324
|
+
/**
|
|
325
|
+
* Sync IDs that have been applied to the state.
|
|
326
|
+
*/
|
|
327
|
+
syncIds: Set<string>;
|
|
360
328
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
329
|
+
interface SharedStateEvents<T> {
|
|
330
|
+
updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
|
|
331
|
+
}
|
|
332
|
+
interface SharedStateOptions<T> {
|
|
364
333
|
/**
|
|
365
|
-
*
|
|
366
|
-
* `DevframeViewHost.hostStatic`. Implementations map this to whatever
|
|
367
|
-
* the underlying runtime expects (Vite middleware, h3 handler, no-op
|
|
368
|
-
* for build snapshots).
|
|
334
|
+
* Initial state.
|
|
369
335
|
*/
|
|
370
|
-
|
|
336
|
+
initialValue: T;
|
|
371
337
|
/**
|
|
372
|
-
*
|
|
373
|
-
* base, so a devframe SPA mounted there can discover the RPC/WS endpoint
|
|
374
|
-
* via `connectDevframe()`'s relative `./__connection.json` fetch.
|
|
375
|
-
*
|
|
376
|
-
* Called by `mountDevframe` for each mounted devframe (alongside
|
|
377
|
-
* `mountStatic`). Without it, an embedded SPA can only discover the
|
|
378
|
-
* endpoint by inheriting it from a same-origin parent window — which fails
|
|
379
|
-
* for cross-origin or sandboxed iframes. Implementations serve the same
|
|
380
|
-
* meta they expose at the hub's own base.
|
|
338
|
+
* Enable patches.
|
|
381
339
|
*
|
|
382
|
-
*
|
|
383
|
-
* `distDir` yet omits this hook triggers a `DF8106` diagnostic, since the
|
|
384
|
-
* SPA's `./__connection.json` fetch would otherwise fall through and break
|
|
385
|
-
* silently. A static-snapshot host that bakes the meta into its served files
|
|
386
|
-
* can implement it as a no-op to acknowledge this intentionally.
|
|
340
|
+
* @default false
|
|
387
341
|
*/
|
|
388
|
-
|
|
342
|
+
enablePatches?: boolean;
|
|
343
|
+
}
|
|
344
|
+
declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/utils/streaming-channel.d.ts
|
|
347
|
+
/**
|
|
348
|
+
* Serialized error shape sent over the wire when a stream ends with a failure.
|
|
349
|
+
* Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
|
|
350
|
+
*/
|
|
351
|
+
interface StreamErrorPayload {
|
|
352
|
+
name: string;
|
|
353
|
+
message: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Single buffered chunk in the server-side ring buffer.
|
|
357
|
+
*
|
|
358
|
+
* Sequence numbers start at 1 and increment per write. Subscribers track
|
|
359
|
+
* `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
|
|
360
|
+
* replay any chunks the client missed during a brief disconnect.
|
|
361
|
+
*/
|
|
362
|
+
interface BufferedChunk<T> {
|
|
363
|
+
seq: number;
|
|
364
|
+
chunk: T;
|
|
365
|
+
}
|
|
366
|
+
interface StreamSinkEvents<T> {
|
|
367
|
+
/** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
|
|
368
|
+
chunk: (seq: number, chunk: T) => void;
|
|
369
|
+
/** Terminal — fired exactly once per sink lifetime. */
|
|
370
|
+
end: (error?: StreamErrorPayload) => void;
|
|
371
|
+
}
|
|
372
|
+
interface CreateStreamSinkOptions {
|
|
373
|
+
id?: string;
|
|
389
374
|
/**
|
|
390
|
-
*
|
|
391
|
-
* `
|
|
392
|
-
* iframe URLs with a full `origin`. Called only when a dock needs an
|
|
393
|
-
* absolute URL; hosts that never serve remote docks can return any
|
|
394
|
-
* reasonable value.
|
|
375
|
+
* Size of the per-stream ring buffer kept for replay-on-resubscribe.
|
|
376
|
+
* `0` (default) disables replay.
|
|
395
377
|
*/
|
|
396
|
-
|
|
378
|
+
replayWindow?: number;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Server-side producer handle. Two equivalent surfaces share one piece of
|
|
382
|
+
* state: the imperative `write/error/close` triple, and a `WritableStream<T>`
|
|
383
|
+
* for `pipeTo`-style consumption.
|
|
384
|
+
*/
|
|
385
|
+
interface StreamSink<T> {
|
|
386
|
+
/** Stable id used by clients to subscribe. */
|
|
387
|
+
readonly id: string;
|
|
397
388
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
|
|
389
|
+
* Aborts when the consumer cancels (server-side) or when the transport
|
|
390
|
+
* loses every subscriber. Producers should poll `signal.aborted` and exit
|
|
391
|
+
* cleanly.
|
|
392
|
+
*/
|
|
393
|
+
readonly signal: AbortSignal;
|
|
394
|
+
/** `true` after `close()` / `error()`. Further writes throw. */
|
|
395
|
+
readonly closed: boolean;
|
|
396
|
+
/** Last allocated sequence number. `0` until the first write. */
|
|
397
|
+
readonly lastSeq: number;
|
|
398
|
+
write: (chunk: T) => void;
|
|
399
|
+
error: (reason: unknown) => void;
|
|
400
|
+
close: () => void;
|
|
401
|
+
/** External-cancel path. Aborts the signal so handlers can short-circuit. */
|
|
402
|
+
abort: (reason?: unknown) => void;
|
|
403
|
+
/** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
|
|
404
|
+
readonly writable: WritableStream<T>;
|
|
405
|
+
/**
|
|
406
|
+
* Internal — RPC layer subscribes to receive chunk/end notifications.
|
|
407
|
+
* Not part of the public contract; do not call directly.
|
|
402
408
|
*
|
|
403
|
-
*
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
*
|
|
409
|
+
* @internal
|
|
410
|
+
*/
|
|
411
|
+
readonly events: EventEmitter<StreamSinkEvents<T>>;
|
|
412
|
+
/**
|
|
413
|
+
* Internal — replay buffer. RPC layer reads on (re)subscribe to feed
|
|
414
|
+
* missed chunks before going live.
|
|
408
415
|
*
|
|
409
|
-
*
|
|
410
|
-
* pass to a downstream `createStorage(...)` call that creates it
|
|
411
|
-
* lazily.
|
|
416
|
+
* @internal
|
|
412
417
|
*/
|
|
413
|
-
|
|
418
|
+
readonly buffer: ReadonlyArray<BufferedChunk<T>>;
|
|
419
|
+
}
|
|
420
|
+
interface CreateStreamReaderOptions {
|
|
421
|
+
id?: string;
|
|
422
|
+
/**
|
|
423
|
+
* Maximum number of buffered chunks held client-side while the consumer
|
|
424
|
+
* isn't draining. On overflow, the oldest chunk is dropped.
|
|
425
|
+
*/
|
|
426
|
+
highWaterMark?: number;
|
|
427
|
+
/**
|
|
428
|
+
* Called when the chunk queue overflows the high-water mark. The RPC
|
|
429
|
+
* layer wires this to a coded warning; the primitive itself is
|
|
430
|
+
* RPC-agnostic.
|
|
431
|
+
*/
|
|
432
|
+
onOverflow?: (dropped: number) => void;
|
|
433
|
+
/** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
|
|
434
|
+
onCancel?: () => void;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
|
|
438
|
+
* and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one — they
|
|
439
|
+
* share a single internal queue, so concurrent draining will race.
|
|
440
|
+
*/
|
|
441
|
+
interface StreamReader<T> extends AsyncIterable<T> {
|
|
442
|
+
readonly id: string;
|
|
443
|
+
readonly cancelled: boolean;
|
|
444
|
+
readonly done: boolean;
|
|
445
|
+
/** Highest `seq` observed. Used for replay on reconnect. */
|
|
446
|
+
readonly lastSeenSeq: number;
|
|
447
|
+
/** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
|
|
448
|
+
readonly readable: ReadableStream<T>;
|
|
449
|
+
cancel: () => void;
|
|
450
|
+
/** @internal */
|
|
451
|
+
_push: (seq: number, chunk: T) => void;
|
|
452
|
+
/** @internal */
|
|
453
|
+
_end: (error?: StreamErrorPayload) => void;
|
|
414
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Build a server-side stream sink. RPC-agnostic — the RPC host wires
|
|
457
|
+
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
|
|
458
|
+
* for late or reconnecting subscribers.
|
|
459
|
+
*/
|
|
460
|
+
declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
|
|
461
|
+
/**
|
|
462
|
+
* Build a client-side stream reader. RPC-agnostic — the RPC host calls
|
|
463
|
+
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
|
|
464
|
+
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
|
|
465
|
+
*/
|
|
466
|
+
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
415
467
|
//#endregion
|
|
416
468
|
//#region src/types/rpc-augments.d.ts
|
|
417
469
|
/**
|
|
@@ -588,543 +640,555 @@ interface DevframeRpcServerFunctions {
|
|
|
588
640
|
*/
|
|
589
641
|
interface DevframeRpcSharedStates {}
|
|
590
642
|
//#endregion
|
|
591
|
-
//#region src/types/
|
|
592
|
-
interface
|
|
643
|
+
//#region src/types/rpc.d.ts
|
|
644
|
+
interface DevframeNodeRpcSession {
|
|
645
|
+
meta: DevframeNodeRpcSessionMeta;
|
|
646
|
+
rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
647
|
+
}
|
|
648
|
+
interface RpcBroadcastOptions<METHOD, Args extends any[]> {
|
|
649
|
+
method: METHOD;
|
|
650
|
+
args: Args;
|
|
651
|
+
optional?: boolean;
|
|
652
|
+
event?: boolean;
|
|
653
|
+
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
654
|
+
}
|
|
655
|
+
type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
|
|
593
656
|
/**
|
|
594
|
-
*
|
|
657
|
+
* Invoke a locally registered server RPC function directly.
|
|
658
|
+
*
|
|
659
|
+
* This bypasses transport and is useful for server-side cross-function calls.
|
|
595
660
|
*/
|
|
596
|
-
|
|
597
|
-
baseUrl: string;
|
|
598
|
-
distDir: string;
|
|
599
|
-
}[];
|
|
661
|
+
invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
|
|
600
662
|
/**
|
|
601
|
-
*
|
|
602
|
-
* - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
|
|
603
|
-
* - In `build` mode, it will copy the static files to the dist directory
|
|
663
|
+
* Broadcast a message to all connected clients
|
|
604
664
|
*/
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
//#endregion
|
|
608
|
-
//#region src/types/scope.d.ts
|
|
609
|
-
type AnyRpcFn = (...args: any[]) => any;
|
|
610
|
-
/**
|
|
611
|
-
* Augmentable registry mapping a scope namespace to the shape of its
|
|
612
|
-
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
|
|
613
|
-
* gets a fully-typed `settings.global` / `settings.project`:
|
|
614
|
-
*
|
|
615
|
-
* ```ts
|
|
616
|
-
* declare module 'devframe' {
|
|
617
|
-
* interface DevframeSettingsRegistry {
|
|
618
|
-
* 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
|
|
619
|
-
* }
|
|
620
|
-
* }
|
|
621
|
-
* ```
|
|
622
|
-
*/
|
|
623
|
-
interface DevframeSettingsRegistry {}
|
|
624
|
-
/**
|
|
625
|
-
* Resolve the settings shape for a namespace from
|
|
626
|
-
* {@link DevframeSettingsRegistry}, falling back to an open record when
|
|
627
|
-
* the namespace hasn't been augmented.
|
|
628
|
-
*/
|
|
629
|
-
type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
|
|
630
|
-
/**
|
|
631
|
-
* A persisted key-value settings store for one scope (`global` or
|
|
632
|
-
* `project`). Backed by a file on the node side and by the shared-state
|
|
633
|
-
* sync protocol on the client, so a `set` on either side propagates to
|
|
634
|
-
* every connected peer and survives restarts.
|
|
635
|
-
*
|
|
636
|
-
* Every method is async because the underlying shared state is resolved
|
|
637
|
-
* lazily on first access.
|
|
638
|
-
*/
|
|
639
|
-
interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
|
|
640
|
-
/** Read a single setting. Resolves to `undefined` when unset. */
|
|
641
|
-
get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
|
|
642
|
-
/** Write a single setting. */
|
|
643
|
-
set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
|
|
644
|
-
/** Remove a single setting. */
|
|
645
|
-
delete: <K extends keyof T>(key: K) => Promise<void>;
|
|
646
|
-
/** Read the whole settings object (immutable snapshot). */
|
|
647
|
-
all: () => Promise<Readonly<T>>;
|
|
665
|
+
broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
|
|
648
666
|
/**
|
|
649
|
-
*
|
|
650
|
-
*
|
|
667
|
+
* Get the current RPC client
|
|
668
|
+
*
|
|
669
|
+
* Available in RPC functions to get the current RPC client
|
|
651
670
|
*/
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
* namespace, so a scoped `call('get-cwd')` is typed without the
|
|
669
|
-
* `my-plugin:` prefix.
|
|
670
|
-
*/
|
|
671
|
-
type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
|
|
672
|
-
/**
|
|
673
|
-
* Map the client-side RPC registry down to the bare names owned by a
|
|
674
|
-
* namespace, used by scoped `broadcast`.
|
|
675
|
-
*/
|
|
676
|
-
type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
|
|
677
|
-
/**
|
|
678
|
-
* Map the shared-state registry down to the bare keys owned by a
|
|
679
|
-
* namespace, so scoped `sharedState('messages')` is typed without the
|
|
680
|
-
* `my-plugin:` prefix.
|
|
681
|
-
*/
|
|
682
|
-
type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
|
|
683
|
-
/**
|
|
684
|
-
* Broadcast options for a scoped client method (bare name).
|
|
685
|
-
*/
|
|
686
|
-
interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
|
|
687
|
-
method: METHOD;
|
|
688
|
-
args: Args;
|
|
689
|
-
optional?: boolean;
|
|
690
|
-
event?: boolean;
|
|
671
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
672
|
+
/**
|
|
673
|
+
* The shared state host
|
|
674
|
+
*/
|
|
675
|
+
sharedState: RpcSharedStateHost;
|
|
676
|
+
/**
|
|
677
|
+
* The streaming channel host. Provides per-channel `start()` /
|
|
678
|
+
* `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
|
|
679
|
+
*
|
|
680
|
+
* @see RpcStreamingHost
|
|
681
|
+
*/
|
|
682
|
+
streaming: RpcStreamingHost;
|
|
683
|
+
};
|
|
684
|
+
interface RpcSharedStateGetOptions<T> {
|
|
685
|
+
sharedState?: SharedState<T>;
|
|
686
|
+
initialValue?: T;
|
|
691
687
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
*/
|
|
696
|
-
interface DevframeScopedStreamingHost {
|
|
688
|
+
interface RpcSharedStateHost {
|
|
689
|
+
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
690
|
+
keys: () => string[];
|
|
697
691
|
/**
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
692
|
+
* Subscribe to new shared-state keys becoming available. Fires when
|
|
693
|
+
* `get(key, ...)` creates a fresh entry (not on subsequent gets).
|
|
694
|
+
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
695
|
+
* as dynamic resources.
|
|
701
696
|
*/
|
|
702
|
-
|
|
697
|
+
onKeyAdded: (fn: (key: string) => void) => () => void;
|
|
703
698
|
}
|
|
704
699
|
/**
|
|
705
|
-
*
|
|
706
|
-
* IDs and keys you pass are auto-namespaced with `my-plugin:`.
|
|
700
|
+
* Options for `RpcStreamingHost.create()`.
|
|
707
701
|
*/
|
|
708
|
-
interface
|
|
709
|
-
/** The namespace this surface is scoped to. */
|
|
710
|
-
readonly namespace: NS;
|
|
702
|
+
interface RpcStreamingChannelOptions {
|
|
711
703
|
/**
|
|
712
|
-
*
|
|
713
|
-
* (
|
|
714
|
-
*
|
|
704
|
+
* Size of the per-stream ring buffer kept on the server for
|
|
705
|
+
* replay-on-resubscribe. `0` (default) disables replay; on reconnect
|
|
706
|
+
* the consumer only sees chunks that arrive after subscribing.
|
|
707
|
+
*
|
|
708
|
+
* The buffer is per stream id, not per channel — each `channel.start()`
|
|
709
|
+
* gets its own.
|
|
715
710
|
*/
|
|
716
|
-
|
|
711
|
+
replayWindow?: number;
|
|
717
712
|
/**
|
|
718
|
-
*
|
|
719
|
-
*
|
|
713
|
+
* Milliseconds a closed stream is retained on the server after its
|
|
714
|
+
* last subscriber leaves (or if no subscriber ever arrived). During
|
|
715
|
+
* this window, late subscribers can still join and replay the buffer
|
|
716
|
+
* + receive the `end` frame.
|
|
717
|
+
*
|
|
718
|
+
* Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
|
|
719
|
+
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
720
|
+
* post-mortem replay.
|
|
720
721
|
*/
|
|
721
|
-
|
|
722
|
+
closedStreamRetention?: number;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
|
|
726
|
+
* channel owns a wire namespace; calling `start()` produces individual
|
|
727
|
+
* streams keyed by id.
|
|
728
|
+
*
|
|
729
|
+
* @see {@link https://devfra.me/guide/streaming Streaming guide}
|
|
730
|
+
*/
|
|
731
|
+
interface RpcStreamingChannel<T = unknown> {
|
|
732
|
+
/** Channel name as registered with `ctx.rpc.streaming.create()`. */
|
|
733
|
+
readonly name: string;
|
|
722
734
|
/**
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
735
|
+
* Start a new stream. Returns a server-side sink with both an imperative
|
|
736
|
+
* (`write` / `close` / `error`) surface and a `WritableStream<T>` for
|
|
737
|
+
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
738
|
+
* disconnects or cancels.
|
|
726
739
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
(method: string, ...args: any[]): Promise<any>;
|
|
731
|
-
};
|
|
740
|
+
start: (opts?: {
|
|
741
|
+
id?: string;
|
|
742
|
+
}) => StreamSink<T>;
|
|
732
743
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
744
|
+
* Convenience: start a stream and pipe a `ReadableStream<T>` into it.
|
|
745
|
+
* The pipe uses `sink.signal` so cancellation propagates upstream.
|
|
746
|
+
*
|
|
747
|
+
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
748
|
+
* before passing it here.
|
|
735
749
|
*/
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
750
|
+
pipeFrom: (readable: ReadableStream<T>, opts?: {
|
|
751
|
+
id?: string;
|
|
752
|
+
}) => Promise<StreamSink<T>>;
|
|
753
|
+
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
754
|
+
get: (id: string) => StreamSink<T> | undefined;
|
|
755
|
+
/** All active outbound stream ids on this channel. */
|
|
756
|
+
ids: () => string[];
|
|
740
757
|
/**
|
|
741
|
-
*
|
|
742
|
-
*
|
|
758
|
+
* Open an inbound stream — the server side of a client-to-server
|
|
759
|
+
* upload. Allocates an id, returns a `StreamReader<T>` that fills as
|
|
760
|
+
* the client writes chunks. Typical pattern is to call this from an
|
|
761
|
+
* action handler, kick off background processing, and return the id
|
|
762
|
+
* so the caller can start uploading:
|
|
763
|
+
*
|
|
764
|
+
* ```ts
|
|
765
|
+
* handler: async () => {
|
|
766
|
+
* const reader = channel.openInbound()
|
|
767
|
+
* ;(async () => {
|
|
768
|
+
* for await (const chunk of reader) processChunk(chunk)
|
|
769
|
+
* })()
|
|
770
|
+
* return { uploadId: reader.id }
|
|
771
|
+
* }
|
|
772
|
+
* ```
|
|
773
|
+
*
|
|
774
|
+
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
775
|
+
* the uploading client, which aborts its sink.
|
|
743
776
|
*/
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
};
|
|
748
|
-
/** Streaming host scoped to this namespace. */
|
|
749
|
-
streaming: DevframeScopedStreamingHost;
|
|
750
|
-
/** Pass-through to the current RPC session, when called inside a handler. */
|
|
751
|
-
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
777
|
+
openInbound: (opts?: {
|
|
778
|
+
id?: string;
|
|
779
|
+
}) => StreamReader<T>;
|
|
752
780
|
}
|
|
753
781
|
/**
|
|
754
|
-
*
|
|
755
|
-
* `ctx.
|
|
756
|
-
*
|
|
757
|
-
* {@link DevframeScopedNodeRpc}, and adds a top-level `settings` store.
|
|
782
|
+
* Server-side streaming host. Lives on `ctx.rpc.streaming` alongside
|
|
783
|
+
* `ctx.rpc.sharedState`. Each named channel owns its own stream registry
|
|
784
|
+
* and wire namespace.
|
|
758
785
|
*/
|
|
759
|
-
interface
|
|
760
|
-
/** The namespace this context is scoped to. */
|
|
761
|
-
readonly namespace: NS;
|
|
762
|
-
/** The underlying unscoped context. */
|
|
763
|
-
readonly base: DevframeNodeContext;
|
|
764
|
-
readonly workspaceRoot: string;
|
|
765
|
-
readonly cwd: string;
|
|
766
|
-
readonly mode: 'dev' | 'build';
|
|
767
|
-
host: DevframeHost;
|
|
768
|
-
rpc: DevframeScopedNodeRpc<NS>;
|
|
769
|
-
/** Persisted settings for this namespace (`global` + `project`). */
|
|
770
|
-
settings: DevframeSettings<Settings>;
|
|
771
|
-
views: DevframeViewHost;
|
|
772
|
-
diagnostics: DevframeDiagnosticsHost;
|
|
773
|
-
agent: DevframeAgentHost;
|
|
786
|
+
interface RpcStreamingHost {
|
|
774
787
|
/**
|
|
775
|
-
*
|
|
776
|
-
*
|
|
788
|
+
* Register a streaming channel. Names follow the `<plugin-id>:<channel>`
|
|
789
|
+
* convention (e.g. `'my-devtool:chat-stream'`). Throws `DF0032` if the
|
|
790
|
+
* name is already taken.
|
|
777
791
|
*/
|
|
778
|
-
|
|
792
|
+
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
793
|
+
/**
|
|
794
|
+
* Adapters call this when a session disconnects so the host can drop
|
|
795
|
+
* subscribers and abort orphaned streams. Most users do not need this;
|
|
796
|
+
* it's wired by `startHttpAndWs` automatically.
|
|
797
|
+
*
|
|
798
|
+
* @internal
|
|
799
|
+
*/
|
|
800
|
+
_onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
|
|
779
801
|
}
|
|
780
802
|
//#endregion
|
|
781
|
-
//#region src/types/
|
|
782
|
-
type Thenable<T> = T | Promise<T>;
|
|
783
|
-
type EntriesToObject<T extends readonly [string, any][]> = { [K in T[number] as K[0]]: K[1]; };
|
|
784
|
-
type PartialWithoutId<T extends {
|
|
785
|
-
id: string;
|
|
786
|
-
}> = Partial<Omit<T, 'id'>> & {
|
|
787
|
-
id: string;
|
|
788
|
-
};
|
|
789
|
-
//#endregion
|
|
790
|
-
//#region src/utils/shared-state.d.ts
|
|
791
|
-
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
792
|
-
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>;
|
|
793
|
-
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
794
|
-
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
795
|
-
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
796
|
-
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
|
|
803
|
+
//#region src/types/diagnostics.d.ts
|
|
797
804
|
/**
|
|
798
|
-
*
|
|
799
|
-
*
|
|
805
|
+
* A diagnostics definition object built with `defineDiagnostics`. Typed as
|
|
806
|
+
* `unknown` because each integration's definition has a distinct narrow shape
|
|
807
|
+
* (e.g. specific code keys like `DF0001` / `MYP0001`), and TypeScript's mapped
|
|
808
|
+
* types don't allow assigning a narrow-keyed result to a generically-keyed
|
|
809
|
+
* one. The host stores them in a heterogeneous registry.
|
|
800
810
|
*/
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
811
|
+
type DevframeDiagnosticsDefinition = ReturnType<typeof defineDiagnostics<any, any>>;
|
|
812
|
+
/**
|
|
813
|
+
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
|
|
814
|
+
* any registered code name to its `nostics` handle (a callable that builds
|
|
815
|
+
* a diagnostic and routes it through registered reporters). Typed loosely
|
|
816
|
+
* because it spans heterogeneous definitions registered by different
|
|
817
|
+
* integrations.
|
|
818
|
+
*/
|
|
819
|
+
type DevframeDiagnosticsLogger = Record<string, any>;
|
|
820
|
+
/**
|
|
821
|
+
* Options accepted by the host's `defineDiagnostics()` factory — mirrors
|
|
822
|
+
* `nostics`'s shape but the host pre-wires its ANSI console reporter, so
|
|
823
|
+
* plugins typically omit `reporters`.
|
|
824
|
+
*/
|
|
825
|
+
interface DevframeDefineDiagnosticsOptions<Codes extends Record<string, DiagnosticDefinition>> {
|
|
826
|
+
docsBase?: string | ((code: keyof Codes) => string | undefined);
|
|
827
|
+
codes: Codes;
|
|
828
|
+
reporters?: ReadonlyArray<(d: Diagnostic, o?: any) => void>;
|
|
805
829
|
}
|
|
806
830
|
/**
|
|
807
|
-
*
|
|
831
|
+
* Host for structured diagnostics — a thin layer over `nostics` that lets
|
|
832
|
+
* integrations register their own coded errors/warnings into a shared
|
|
833
|
+
* registry without taking a direct dependency on `nostics`.
|
|
834
|
+
*
|
|
835
|
+
* Typical usage from a plugin's `setup(ctx)`:
|
|
836
|
+
*
|
|
837
|
+
* ```ts
|
|
838
|
+
* const myDiagnostics = ctx.diagnostics.defineDiagnostics({
|
|
839
|
+
* docsBase: 'https://example.com/errors',
|
|
840
|
+
* codes: {
|
|
841
|
+
* MYP0001: { why: 'Something went wrong' },
|
|
842
|
+
* },
|
|
843
|
+
* })
|
|
844
|
+
* ctx.diagnostics.register(myDiagnostics)
|
|
845
|
+
*
|
|
846
|
+
* // Through the shared lookup (loose typing):
|
|
847
|
+
* throw ctx.diagnostics.logger.MYP0001()
|
|
848
|
+
*
|
|
849
|
+
* // Or directly on the typed handle returned from `defineDiagnostics`:
|
|
850
|
+
* throw myDiagnostics.MYP0001()
|
|
851
|
+
* ```
|
|
808
852
|
*/
|
|
809
|
-
interface
|
|
853
|
+
interface DevframeDiagnosticsHost {
|
|
810
854
|
/**
|
|
811
|
-
*
|
|
855
|
+
* Proxy-backed lookup of every registered diagnostic handle by code name.
|
|
856
|
+
* Resolves to a `nostics` `DiagnosticHandle` — a callable that builds a
|
|
857
|
+
* diagnostic and routes it through registered reporters; prefix with
|
|
858
|
+
* `throw` to raise. Loosely typed — for autocompletion, keep a reference
|
|
859
|
+
* to the typed result of `defineDiagnostics()` instead.
|
|
812
860
|
*/
|
|
813
|
-
|
|
861
|
+
readonly logger: DevframeDiagnosticsLogger;
|
|
814
862
|
/**
|
|
815
|
-
*
|
|
863
|
+
* Register additional diagnostic definitions with this host. After
|
|
864
|
+
* registration, codes from the new definition are reachable via
|
|
865
|
+
* `host.logger.CODE`. Plugins that want shared output formatting should
|
|
866
|
+
* build their diagnostics via `host.defineDiagnostics()` first — that
|
|
867
|
+
* factory pre-wires the host's ANSI console reporter.
|
|
816
868
|
*/
|
|
817
|
-
|
|
869
|
+
register: (definitions: Record<string, unknown>) => void;
|
|
818
870
|
/**
|
|
819
|
-
*
|
|
871
|
+
* Build a typed diagnostics object with the host's ANSI console reporter
|
|
872
|
+
* pre-wired. Mirrors `nostics`'s `defineDiagnostics` so integrations don't
|
|
873
|
+
* need to take a direct dependency on `nostics`.
|
|
820
874
|
*/
|
|
821
|
-
|
|
875
|
+
defineDiagnostics: <const Codes extends Record<string, DiagnosticDefinition>>(options: DevframeDefineDiagnosticsOptions<Codes>) => ReturnType<typeof defineDiagnostics<Codes, any>>;
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/types/host.d.ts
|
|
879
|
+
interface DevframeHost {
|
|
822
880
|
/**
|
|
823
|
-
*
|
|
881
|
+
* Serve a static directory at the given URL base. Called by
|
|
882
|
+
* `DevframeViewHost.hostStatic`. Implementations map this to whatever
|
|
883
|
+
* the underlying runtime expects (Vite middleware, h3 handler, no-op
|
|
884
|
+
* for build snapshots).
|
|
824
885
|
*/
|
|
825
|
-
|
|
886
|
+
mountStatic: (base: string, distDir: string) => void | Promise<void>;
|
|
826
887
|
/**
|
|
827
|
-
*
|
|
888
|
+
* Serve the host's connection meta (`__connection.json`) at the given URL
|
|
889
|
+
* base, so a devframe SPA mounted there can discover the RPC/WS endpoint
|
|
890
|
+
* via `connectDevframe()`'s relative `./__connection.json` fetch.
|
|
891
|
+
*
|
|
892
|
+
* Called by `mountDevframe` for each mounted devframe (alongside
|
|
893
|
+
* `mountStatic`). Without it, an embedded SPA can only discover the
|
|
894
|
+
* endpoint by inheriting it from a same-origin parent window — which fails
|
|
895
|
+
* for cross-origin or sandboxed iframes. Implementations serve the same
|
|
896
|
+
* meta they expose at the hub's own base.
|
|
897
|
+
*
|
|
898
|
+
* Optional in the type, but a host that mounts a devframe with a servable
|
|
899
|
+
* `distDir` yet omits this hook triggers a `DF8106` diagnostic, since the
|
|
900
|
+
* SPA's `./__connection.json` fetch would otherwise fall through and break
|
|
901
|
+
* silently. A static-snapshot host that bakes the meta into its served files
|
|
902
|
+
* can implement it as a no-op to acknowledge this intentionally.
|
|
828
903
|
*/
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
interface SharedStateEvents<T> {
|
|
832
|
-
updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
|
|
833
|
-
}
|
|
834
|
-
interface SharedStateOptions<T> {
|
|
904
|
+
mountConnectionMeta?: (base: string) => void | Promise<void>;
|
|
835
905
|
/**
|
|
836
|
-
*
|
|
906
|
+
* Return the public origin the host is reachable at, e.g.
|
|
907
|
+
* `http://localhost:5173`. Used by the dock host to enrich remote
|
|
908
|
+
* iframe URLs with a full `origin`. Called only when a dock needs an
|
|
909
|
+
* absolute URL; hosts that never serve remote docks can return any
|
|
910
|
+
* reasonable value.
|
|
837
911
|
*/
|
|
838
|
-
|
|
912
|
+
resolveOrigin: () => string;
|
|
839
913
|
/**
|
|
840
|
-
*
|
|
914
|
+
* Resolve a directory the host owns for persisted devframe state.
|
|
915
|
+
* Each host picks its own app-name namespace so storage doesn't
|
|
916
|
+
* collide between, say, the Vite host (`.vite/devframe`) and a
|
|
917
|
+
* standalone CLI host (`.<appName>/devframe`).
|
|
841
918
|
*
|
|
842
|
-
*
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Single buffered chunk in the server-side ring buffer.
|
|
859
|
-
*
|
|
860
|
-
* Sequence numbers start at 1 and increment per write. Subscribers track
|
|
861
|
-
* `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
|
|
862
|
-
* replay any chunks the client missed during a brief disconnect.
|
|
863
|
-
*/
|
|
864
|
-
interface BufferedChunk<T> {
|
|
865
|
-
seq: number;
|
|
866
|
-
chunk: T;
|
|
867
|
-
}
|
|
868
|
-
interface StreamSinkEvents<T> {
|
|
869
|
-
/** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
|
|
870
|
-
chunk: (seq: number, chunk: T) => void;
|
|
871
|
-
/** Terminal — fired exactly once per sink lifetime. */
|
|
872
|
-
end: (error?: StreamErrorPayload) => void;
|
|
873
|
-
}
|
|
874
|
-
interface CreateStreamSinkOptions {
|
|
875
|
-
id?: string;
|
|
876
|
-
/**
|
|
877
|
-
* Size of the per-stream ring buffer kept for replay-on-resubscribe.
|
|
878
|
-
* `0` (default) disables replay.
|
|
919
|
+
* - `workspace` — state shared with the whole team through version
|
|
920
|
+
* control (saved queries, shared presets). Conventionally
|
|
921
|
+
* `${workspaceRoot}/.devframe/`; hosts must place it somewhere
|
|
922
|
+
* committable.
|
|
923
|
+
* - `project` — per-checkout private state (caches, personal
|
|
924
|
+
* settings). Typically under
|
|
925
|
+
* `${cwd}/node_modules/.<appName>/devframe/`, which version
|
|
926
|
+
* control ignores.
|
|
927
|
+
* - `global` — per-user state (auth tokens, machine-wide
|
|
928
|
+
* preferences). Typically under
|
|
929
|
+
* `${homedir()}/.<appName>/devframe/`.
|
|
930
|
+
*
|
|
931
|
+
* Implementations should ensure the directory exists or be safe to
|
|
932
|
+
* pass to a downstream `createStorage(...)` call that creates it
|
|
933
|
+
* lazily.
|
|
879
934
|
*/
|
|
880
|
-
|
|
935
|
+
getStorageDir: (scope: DevframeStorageScope) => string;
|
|
881
936
|
}
|
|
882
937
|
/**
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
938
|
+
* Storage placement classes for {@link DevframeHost.getStorageDir}:
|
|
939
|
+
* `workspace` is committable and team-shared, `project` is per-checkout
|
|
940
|
+
* private, `global` is per-user.
|
|
886
941
|
*/
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
* Aborts when the consumer cancels (server-side) or when the transport
|
|
892
|
-
* loses every subscriber. Producers should poll `signal.aborted` and exit
|
|
893
|
-
* cleanly.
|
|
894
|
-
*/
|
|
895
|
-
readonly signal: AbortSignal;
|
|
896
|
-
/** `true` after `close()` / `error()`. Further writes throw. */
|
|
897
|
-
readonly closed: boolean;
|
|
898
|
-
/** Last allocated sequence number. `0` until the first write. */
|
|
899
|
-
readonly lastSeq: number;
|
|
900
|
-
write: (chunk: T) => void;
|
|
901
|
-
error: (reason: unknown) => void;
|
|
902
|
-
close: () => void;
|
|
903
|
-
/** External-cancel path. Aborts the signal so handlers can short-circuit. */
|
|
904
|
-
abort: (reason?: unknown) => void;
|
|
905
|
-
/** `WritableStream<T>` adapter — same in-memory state as the imperative API. */
|
|
906
|
-
readonly writable: WritableStream<T>;
|
|
942
|
+
type DevframeStorageScope = 'workspace' | 'project' | 'global';
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/types/views.d.ts
|
|
945
|
+
interface DevframeViewHost {
|
|
907
946
|
/**
|
|
908
|
-
* Internal — RPC layer subscribes to receive chunk/end notifications.
|
|
909
|
-
* Not part of the public contract; do not call directly.
|
|
910
|
-
*
|
|
911
947
|
* @internal
|
|
912
948
|
*/
|
|
913
|
-
|
|
949
|
+
buildStaticDirs: {
|
|
950
|
+
baseUrl: string;
|
|
951
|
+
distDir: string;
|
|
952
|
+
}[];
|
|
914
953
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
* @internal
|
|
954
|
+
* Helper to host static files
|
|
955
|
+
* - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
|
|
956
|
+
* - In `build` mode, it will copy the static files to the dist directory
|
|
919
957
|
*/
|
|
920
|
-
|
|
958
|
+
hostStatic: (baseUrl: string, distDir: string) => void;
|
|
921
959
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/types/scope.d.ts
|
|
962
|
+
type AnyRpcFn = (...args: any[]) => any;
|
|
963
|
+
/**
|
|
964
|
+
* Augmentable registry mapping a scope namespace to the shape of its
|
|
965
|
+
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
|
|
966
|
+
* gets a fully-typed `settings.global` / `settings.project`:
|
|
967
|
+
*
|
|
968
|
+
* ```ts
|
|
969
|
+
* declare module 'devframe' {
|
|
970
|
+
* interface DevframeSettingsRegistry {
|
|
971
|
+
* 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
|
|
972
|
+
* }
|
|
973
|
+
* }
|
|
974
|
+
* ```
|
|
975
|
+
*/
|
|
976
|
+
interface DevframeSettingsRegistry {}
|
|
977
|
+
/**
|
|
978
|
+
* Resolve the settings shape for a namespace from
|
|
979
|
+
* {@link DevframeSettingsRegistry}, falling back to an open record when
|
|
980
|
+
* the namespace hasn't been augmented.
|
|
981
|
+
*/
|
|
982
|
+
type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
|
|
983
|
+
/**
|
|
984
|
+
* A persisted key-value settings store for one scope (`global` or
|
|
985
|
+
* `project`). Backed by a file on the node side and by the shared-state
|
|
986
|
+
* sync protocol on the client, so a `set` on either side propagates to
|
|
987
|
+
* every connected peer and survives restarts.
|
|
988
|
+
*
|
|
989
|
+
* Every method is async because the underlying shared state is resolved
|
|
990
|
+
* lazily on first access.
|
|
991
|
+
*/
|
|
992
|
+
interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
|
|
993
|
+
/** Read a single setting. Resolves to `undefined` when unset. */
|
|
994
|
+
get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
|
|
995
|
+
/** Write a single setting. */
|
|
996
|
+
set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
|
|
997
|
+
/** Remove a single setting. */
|
|
998
|
+
delete: <K extends keyof T>(key: K) => Promise<void>;
|
|
999
|
+
/** Read the whole settings object (immutable snapshot). */
|
|
1000
|
+
all: () => Promise<Readonly<T>>;
|
|
929
1001
|
/**
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
* RPC-agnostic.
|
|
1002
|
+
* Subscribe to settings changes. Resolves to an unsubscribe function.
|
|
1003
|
+
* Fires on every local or remote mutation with the full snapshot.
|
|
933
1004
|
*/
|
|
934
|
-
|
|
935
|
-
/** Called when the consumer cancels — the RPC layer sends `:cancel` upstream. */
|
|
936
|
-
onCancel?: () => void;
|
|
1005
|
+
onChange: (fn: (value: Readonly<T>) => void) => Promise<() => void>;
|
|
937
1006
|
}
|
|
938
1007
|
/**
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
1008
|
+
* The two settings scopes available on a scoped context.
|
|
1009
|
+
*
|
|
1010
|
+
* - `project` — per-workspace state, persisted under the host's
|
|
1011
|
+
* `workspace` storage dir. Project-local settings.
|
|
1012
|
+
* - `global` — per-user state, persisted under the host's `global`
|
|
1013
|
+
* storage dir. Machine-wide preferences.
|
|
942
1014
|
*/
|
|
943
|
-
interface
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
readonly done: boolean;
|
|
947
|
-
/** Highest `seq` observed. Used for replay on reconnect. */
|
|
948
|
-
readonly lastSeenSeq: number;
|
|
949
|
-
/** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
|
|
950
|
-
readonly readable: ReadableStream<T>;
|
|
951
|
-
cancel: () => void;
|
|
952
|
-
/** @internal */
|
|
953
|
-
_push: (seq: number, chunk: T) => void;
|
|
954
|
-
/** @internal */
|
|
955
|
-
_end: (error?: StreamErrorPayload) => void;
|
|
1015
|
+
interface DevframeSettings<T extends Record<string, any> = Record<string, any>> {
|
|
1016
|
+
global: DevframeSettingsStore<T>;
|
|
1017
|
+
project: DevframeSettingsStore<T>;
|
|
956
1018
|
}
|
|
957
1019
|
/**
|
|
958
|
-
*
|
|
959
|
-
* `
|
|
960
|
-
*
|
|
1020
|
+
* Map the server-side RPC registry down to the bare names owned by a
|
|
1021
|
+
* namespace, so a scoped `call('get-cwd')` is typed without the
|
|
1022
|
+
* `my-plugin:` prefix.
|
|
1023
|
+
*/
|
|
1024
|
+
type ScopedServerFunctions<NS extends string> = { [K in keyof DevframeRpcServerFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcServerFunctions[K]; };
|
|
1025
|
+
/**
|
|
1026
|
+
* Map the client-side RPC registry down to the bare names owned by a
|
|
1027
|
+
* namespace, used by scoped `broadcast`.
|
|
1028
|
+
*/
|
|
1029
|
+
type ScopedClientFunctions<NS extends string> = { [K in keyof DevframeRpcClientFunctions as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcClientFunctions[K]; };
|
|
1030
|
+
/**
|
|
1031
|
+
* Map the shared-state registry down to the bare keys owned by a
|
|
1032
|
+
* namespace, so scoped `sharedState('messages')` is typed without the
|
|
1033
|
+
* `my-plugin:` prefix.
|
|
961
1034
|
*/
|
|
962
|
-
|
|
1035
|
+
type ScopedSharedStates<NS extends string> = { [K in keyof DevframeRpcSharedStates as K extends `${NS}:${infer R}` ? R : never]: DevframeRpcSharedStates[K]; };
|
|
963
1036
|
/**
|
|
964
|
-
*
|
|
965
|
-
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
|
|
966
|
-
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
|
|
1037
|
+
* Broadcast options for a scoped client method (bare name).
|
|
967
1038
|
*/
|
|
968
|
-
|
|
969
|
-
//#endregion
|
|
970
|
-
//#region src/types/rpc.d.ts
|
|
971
|
-
interface DevframeNodeRpcSession {
|
|
972
|
-
meta: DevframeNodeRpcSessionMeta;
|
|
973
|
-
rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
974
|
-
}
|
|
975
|
-
interface RpcBroadcastOptions<METHOD, Args extends any[]> {
|
|
1039
|
+
interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
|
|
976
1040
|
method: METHOD;
|
|
977
1041
|
args: Args;
|
|
978
1042
|
optional?: boolean;
|
|
979
1043
|
event?: boolean;
|
|
980
|
-
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
981
1044
|
}
|
|
982
|
-
|
|
1045
|
+
/**
|
|
1046
|
+
* Node-side streaming host scoped to a namespace. Channel names are
|
|
1047
|
+
* auto-prefixed with `<namespace>:`.
|
|
1048
|
+
*/
|
|
1049
|
+
interface DevframeScopedStreamingHost {
|
|
983
1050
|
/**
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
1051
|
+
* Register a streaming channel. The bare `name` is auto-prefixed with
|
|
1052
|
+
* the scope namespace (`<namespace>:<name>`); pass an already-qualified
|
|
1053
|
+
* name (containing `:`) to opt out.
|
|
987
1054
|
*/
|
|
988
|
-
|
|
1055
|
+
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* The scoped node RPC surface exposed on `ctx.scope('my-plugin').rpc`.
|
|
1059
|
+
* IDs and keys you pass are auto-namespaced with `my-plugin:`.
|
|
1060
|
+
*/
|
|
1061
|
+
interface DevframeScopedNodeRpc<NS extends string = string> {
|
|
1062
|
+
/** The namespace this surface is scoped to. */
|
|
1063
|
+
readonly namespace: NS;
|
|
989
1064
|
/**
|
|
990
|
-
*
|
|
1065
|
+
* Register a server RPC function. The definition's `name` must be bare
|
|
1066
|
+
* (no `:`); it is stored as `<namespace>:<name>`. Throws `DF0034` if an
|
|
1067
|
+
* already-namespaced name is passed.
|
|
991
1068
|
*/
|
|
992
|
-
|
|
1069
|
+
register: (fn: RpcFunctionDefinition<string, any, any, any, any, any, DevframeNodeContext>, force?: boolean) => void;
|
|
993
1070
|
/**
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
* Available in RPC functions to get the current RPC client
|
|
1071
|
+
* Update a previously registered server RPC function. Same naming rules
|
|
1072
|
+
* as {@link DevframeScopedNodeRpc.register}.
|
|
997
1073
|
*/
|
|
998
|
-
|
|
1074
|
+
update: (fn: RpcFunctionDefinition<string, any, any, any, any, any, DevframeNodeContext>, force?: boolean) => void;
|
|
999
1075
|
/**
|
|
1000
|
-
*
|
|
1076
|
+
* Invoke a locally registered server RPC function. Bare names are
|
|
1077
|
+
* resolved within this namespace; pass a fully-qualified name
|
|
1078
|
+
* (containing `:`) to call another scope's function.
|
|
1001
1079
|
*/
|
|
1002
|
-
|
|
1080
|
+
call: {
|
|
1081
|
+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>>>;
|
|
1082
|
+
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>>>;
|
|
1083
|
+
(method: string, ...args: any[]): Promise<any>;
|
|
1084
|
+
};
|
|
1003
1085
|
/**
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
* @see RpcStreamingHost
|
|
1086
|
+
* Broadcast a client RPC event to every connected client. Bare method
|
|
1087
|
+
* names are resolved within this namespace.
|
|
1008
1088
|
*/
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
initialValue?: T;
|
|
1014
|
-
}
|
|
1015
|
-
interface RpcSharedStateHost {
|
|
1016
|
-
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
1017
|
-
keys: () => string[];
|
|
1089
|
+
broadcast: {
|
|
1090
|
+
<T extends keyof ScopedClientFunctions<NS> & string>(options: ScopedBroadcastOptions<T, Parameters<Extract<ScopedClientFunctions<NS>[T], AnyRpcFn>>>): Promise<void>;
|
|
1091
|
+
(options: ScopedBroadcastOptions<string, any[]>): Promise<void>;
|
|
1092
|
+
};
|
|
1018
1093
|
/**
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
1022
|
-
* as dynamic resources.
|
|
1094
|
+
* Resolve a namespaced shared state. Bare keys are resolved within this
|
|
1095
|
+
* namespace; pass a fully-qualified key (containing `:`) to opt out.
|
|
1023
1096
|
*/
|
|
1024
|
-
|
|
1097
|
+
sharedState: {
|
|
1098
|
+
<T extends keyof ScopedSharedStates<NS> & string>(key: T, options?: RpcSharedStateGetOptions<ScopedSharedStates<NS>[T]>): Promise<SharedState<ScopedSharedStates<NS>[T]>>;
|
|
1099
|
+
<T extends Record<string, any> = Record<string, any>>(key: string, options?: RpcSharedStateGetOptions<T>): Promise<SharedState<T>>;
|
|
1100
|
+
};
|
|
1101
|
+
/** Streaming host scoped to this namespace. */
|
|
1102
|
+
streaming: DevframeScopedStreamingHost;
|
|
1103
|
+
/** Pass-through to the current RPC session, when called inside a handler. */
|
|
1104
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
1025
1105
|
}
|
|
1026
1106
|
/**
|
|
1027
|
-
*
|
|
1107
|
+
* A namespace-scoped view of {@link DevframeNodeContext}. Returned by
|
|
1108
|
+
* `ctx.scope('my-plugin')`. Re-exposes the unscoped surfaces (`views`,
|
|
1109
|
+
* `diagnostics`, `agent`, …), replaces `rpc` with the auto-namespaced
|
|
1110
|
+
* {@link DevframeScopedNodeRpc}, and adds a top-level `settings` store.
|
|
1028
1111
|
*/
|
|
1029
|
-
interface
|
|
1030
|
-
/**
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1112
|
+
interface DevframeScopedNodeContext<NS extends string = string, Settings extends Record<string, any> = Record<string, any>> {
|
|
1113
|
+
/** The namespace this context is scoped to. */
|
|
1114
|
+
readonly namespace: NS;
|
|
1115
|
+
/** The underlying unscoped context. */
|
|
1116
|
+
readonly base: DevframeNodeContext;
|
|
1117
|
+
readonly workspaceRoot: string;
|
|
1118
|
+
readonly cwd: string;
|
|
1119
|
+
readonly mode: 'dev' | 'build';
|
|
1120
|
+
host: DevframeHost;
|
|
1121
|
+
rpc: DevframeScopedNodeRpc<NS>;
|
|
1122
|
+
/** Persisted settings for this namespace (`global` + `project`). */
|
|
1123
|
+
settings: DevframeSettings<Settings>;
|
|
1124
|
+
views: DevframeViewHost;
|
|
1125
|
+
diagnostics: DevframeDiagnosticsHost;
|
|
1126
|
+
agent: DevframeAgentHost;
|
|
1039
1127
|
/**
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
* this window, late subscribers can still join and replay the buffer
|
|
1043
|
-
* + receive the `end` frame.
|
|
1044
|
-
*
|
|
1045
|
-
* Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
|
|
1046
|
-
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
1047
|
-
* post-mortem replay.
|
|
1128
|
+
* Return a new scoped context, replacing the current scope. Pass `null`
|
|
1129
|
+
* or `''` to un-scope and get the base context.
|
|
1048
1130
|
*/
|
|
1049
|
-
|
|
1131
|
+
scope: DevframeNodeContext['scope'];
|
|
1050
1132
|
}
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/types/services.d.ts
|
|
1051
1135
|
/**
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1136
|
+
* Cross-plugin services — a typed, namespaced registry on the shared node
|
|
1137
|
+
* context through which one integration exposes a capability and others
|
|
1138
|
+
* consume it without a hard package dependency.
|
|
1055
1139
|
*
|
|
1056
|
-
*
|
|
1140
|
+
* A provider ships a *types-only* augmentation of {@link DevframeServicesRegistry}
|
|
1141
|
+
* (so consumers get full typing from `import type`), then provides the
|
|
1142
|
+
* implementation at setup time:
|
|
1143
|
+
*
|
|
1144
|
+
* ```ts
|
|
1145
|
+
* // provider (e.g. the data-inspector plugin)
|
|
1146
|
+
* declare module 'devframe' {
|
|
1147
|
+
* interface DevframeServicesRegistry {
|
|
1148
|
+
* 'devframes:plugin:data-inspector:sources': DataSourcesService
|
|
1149
|
+
* }
|
|
1150
|
+
* }
|
|
1151
|
+
* ctx.services.provide('devframes:plugin:data-inspector:sources', host)
|
|
1152
|
+
*
|
|
1153
|
+
* // consumer (another plugin) — no runtime dependency on the provider
|
|
1154
|
+
* ctx.services.whenAvailable('devframes:plugin:data-inspector:sources', (sources) => {
|
|
1155
|
+
* sources.register({ id: 'my-plugin:state', title: 'My state', data: () => state })
|
|
1156
|
+
* })
|
|
1157
|
+
* ```
|
|
1158
|
+
*
|
|
1159
|
+
* Service ids follow the same namespacing rule as RPC functions: prefix with
|
|
1160
|
+
* the providing plugin's id.
|
|
1057
1161
|
*/
|
|
1058
|
-
interface RpcStreamingChannel<T = unknown> {
|
|
1059
|
-
/** Channel name as registered with `ctx.rpc.streaming.create()`. */
|
|
1060
|
-
readonly name: string;
|
|
1061
|
-
/**
|
|
1062
|
-
* Start a new stream. Returns a server-side sink with both an imperative
|
|
1063
|
-
* (`write` / `close` / `error`) surface and a `WritableStream<T>` for
|
|
1064
|
-
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
1065
|
-
* disconnects or cancels.
|
|
1066
|
-
*/
|
|
1067
|
-
start: (opts?: {
|
|
1068
|
-
id?: string;
|
|
1069
|
-
}) => StreamSink<T>;
|
|
1070
|
-
/**
|
|
1071
|
-
* Convenience: start a stream and pipe a `ReadableStream<T>` into it.
|
|
1072
|
-
* The pipe uses `sink.signal` so cancellation propagates upstream.
|
|
1073
|
-
*
|
|
1074
|
-
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
1075
|
-
* before passing it here.
|
|
1076
|
-
*/
|
|
1077
|
-
pipeFrom: (readable: ReadableStream<T>, opts?: {
|
|
1078
|
-
id?: string;
|
|
1079
|
-
}) => Promise<StreamSink<T>>;
|
|
1080
|
-
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
1081
|
-
get: (id: string) => StreamSink<T> | undefined;
|
|
1082
|
-
/** All active outbound stream ids on this channel. */
|
|
1083
|
-
ids: () => string[];
|
|
1084
|
-
/**
|
|
1085
|
-
* Open an inbound stream — the server side of a client-to-server
|
|
1086
|
-
* upload. Allocates an id, returns a `StreamReader<T>` that fills as
|
|
1087
|
-
* the client writes chunks. Typical pattern is to call this from an
|
|
1088
|
-
* action handler, kick off background processing, and return the id
|
|
1089
|
-
* so the caller can start uploading:
|
|
1090
|
-
*
|
|
1091
|
-
* ```ts
|
|
1092
|
-
* handler: async () => {
|
|
1093
|
-
* const reader = channel.openInbound()
|
|
1094
|
-
* ;(async () => {
|
|
1095
|
-
* for await (const chunk of reader) processChunk(chunk)
|
|
1096
|
-
* })()
|
|
1097
|
-
* return { uploadId: reader.id }
|
|
1098
|
-
* }
|
|
1099
|
-
* ```
|
|
1100
|
-
*
|
|
1101
|
-
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
1102
|
-
* the uploading client, which aborts its sink.
|
|
1103
|
-
*/
|
|
1104
|
-
openInbound: (opts?: {
|
|
1105
|
-
id?: string;
|
|
1106
|
-
}) => StreamReader<T>;
|
|
1107
|
-
}
|
|
1108
1162
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
* and wire namespace.
|
|
1163
|
+
* Augmentation point mapping service ids to their implementation types.
|
|
1164
|
+
* Providers extend this via `declare module 'devframe'`.
|
|
1112
1165
|
*/
|
|
1113
|
-
interface
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1166
|
+
interface DevframeServicesRegistry {}
|
|
1167
|
+
/**
|
|
1168
|
+
* A known (augmented) service id, or any namespaced string for services
|
|
1169
|
+
* without a published type augmentation.
|
|
1170
|
+
*/
|
|
1171
|
+
type DevframeServiceId = keyof DevframeServicesRegistry | (string & {});
|
|
1172
|
+
/** Resolved service type for an id: augmented type, or `unknown`. */
|
|
1173
|
+
type DevframeServiceOf<ID> = ID extends keyof DevframeServicesRegistry ? DevframeServicesRegistry[ID] : unknown;
|
|
1174
|
+
interface DevframeServicesHost {
|
|
1175
|
+
/**
|
|
1176
|
+
* Publish a service under a namespaced id. Throws `DF0037` when the id is
|
|
1177
|
+
* already provided (revoke first to replace). Returns a revoke function.
|
|
1178
|
+
*/
|
|
1179
|
+
provide: <ID extends DevframeServiceId>(id: ID, service: DevframeServiceOf<ID>) => () => void;
|
|
1180
|
+
/** The service currently provided under `id`, or `undefined`. */
|
|
1181
|
+
get: <ID extends DevframeServiceId>(id: ID) => DevframeServiceOf<ID> | undefined;
|
|
1182
|
+
has: (id: DevframeServiceId) => boolean;
|
|
1183
|
+
/**
|
|
1184
|
+
* Run `callback` with the service as soon as it is available — immediately
|
|
1185
|
+
* when already provided, otherwise on `provide`. Survives provider/consumer
|
|
1186
|
+
* setup-order differences. The callback also re-fires if the service is
|
|
1187
|
+
* revoked and provided again. Returns an unsubscribe function.
|
|
1188
|
+
*/
|
|
1189
|
+
whenAvailable: <ID extends DevframeServiceId>(id: ID, callback: (service: DevframeServiceOf<ID>) => void) => () => void;
|
|
1190
|
+
/** Ids of every currently-provided service. */
|
|
1191
|
+
keys: () => string[];
|
|
1128
1192
|
}
|
|
1129
1193
|
//#endregion
|
|
1130
1194
|
//#region src/types/context.d.ts
|
|
@@ -1175,6 +1239,15 @@ interface DevframeNodeContext {
|
|
|
1175
1239
|
* @experimental
|
|
1176
1240
|
*/
|
|
1177
1241
|
agent: DevframeAgentHost;
|
|
1242
|
+
/**
|
|
1243
|
+
* Cross-plugin services — a typed, namespaced registry through which one
|
|
1244
|
+
* integration exposes a capability (e.g. a data-source registry) and
|
|
1245
|
+
* others consume it without a hard package dependency. Ids follow the RPC
|
|
1246
|
+
* namespacing rule (`<plugin-id>:<service>`); types come from augmenting
|
|
1247
|
+
* the `DevframeServicesRegistry` interface. `whenAvailable` subscriptions
|
|
1248
|
+
* absorb setup-order differences between provider and consumer.
|
|
1249
|
+
*/
|
|
1250
|
+
services: DevframeServicesHost;
|
|
1178
1251
|
/**
|
|
1179
1252
|
* Create a namespace-scoped view of this context. The returned
|
|
1180
1253
|
* `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
|
|
@@ -1252,6 +1325,71 @@ interface ConnectionMeta {
|
|
|
1252
1325
|
* endpoint rather than resolving the path against its own mount.
|
|
1253
1326
|
*/
|
|
1254
1327
|
baseUrl?: string;
|
|
1328
|
+
/**
|
|
1329
|
+
* A pre-issued bearer token embedded in the meta so a client trusts the
|
|
1330
|
+
* server on connect without any prompt or `localStorage` lookup. Only a
|
|
1331
|
+
* **hub** serving a **per-frame** connection meta populates this — it
|
|
1332
|
+
* authenticates once at the top level and bakes the resulting token into
|
|
1333
|
+
* the meta each plugin iframe fetches, so a cross-origin frame (which
|
|
1334
|
+
* cannot read the hub's `localStorage`) is still pre-authorized. The
|
|
1335
|
+
* standalone `__connection.json` never carries a token.
|
|
1336
|
+
*
|
|
1337
|
+
* > [!WARNING]
|
|
1338
|
+
* > A token in a fetchable JSON is only as protected as the URL serving
|
|
1339
|
+
* > it. Emit it exclusively from hub-controlled, per-frame meta — never
|
|
1340
|
+
* > from a publicly reachable static `__connection.json`.
|
|
1341
|
+
*/
|
|
1342
|
+
authToken?: string;
|
|
1343
|
+
}
|
|
1344
|
+
//#endregion
|
|
1345
|
+
//#region src/types/utils.d.ts
|
|
1346
|
+
type Thenable<T> = T | Promise<T>;
|
|
1347
|
+
type EntriesToObject<T extends readonly [string, any][]> = { [K in T[number] as K[0]]: K[1]; };
|
|
1348
|
+
type PartialWithoutId<T extends {
|
|
1349
|
+
id: string;
|
|
1350
|
+
}> = Partial<Omit<T, 'id'>> & {
|
|
1351
|
+
id: string;
|
|
1352
|
+
};
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region src/node/auth/handler.d.ts
|
|
1355
|
+
/**
|
|
1356
|
+
* A ready-made pre-auth RPC handler, as produced by
|
|
1357
|
+
* `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
|
|
1358
|
+
* everything a host adapter needs to wire an authenticated server:
|
|
1359
|
+
* the handshake RPC functions, the resolver gate, the connect-time trust
|
|
1360
|
+
* hook, and the startup banner.
|
|
1361
|
+
*
|
|
1362
|
+
* `startHttpAndWs` accepts one of these directly via its `auth` option —
|
|
1363
|
+
* see {@link https://devfra.me | devframe}'s server docs — or a host can
|
|
1364
|
+
* wire the four pieces itself against a lower-level transport.
|
|
1365
|
+
*/
|
|
1366
|
+
interface DevframeAuthHandler {
|
|
1367
|
+
/**
|
|
1368
|
+
* `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
|
|
1369
|
+
* handshake) and `devframe:auth:revoke` (self-revoke) — register these on
|
|
1370
|
+
* the RPC host (e.g. `rpcHost.register(fn)` for each).
|
|
1371
|
+
*/
|
|
1372
|
+
rpcFunctions: RpcFunctionDefinitionAny[];
|
|
1373
|
+
/**
|
|
1374
|
+
* Resolver gate: whether `methodName` is callable given `session`'s
|
|
1375
|
+
* current trust state. Defaults to allowing any `anonymous:`-prefixed
|
|
1376
|
+
* method (see `isAnonymousRpcMethod`) plus anything once the session is
|
|
1377
|
+
* trusted.
|
|
1378
|
+
*/
|
|
1379
|
+
authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
|
|
1380
|
+
/**
|
|
1381
|
+
* Connect-time trust: reads a bearer token off the peer's upgrade request
|
|
1382
|
+
* (an `Authorization: Bearer <token>` header, or a static/pre-shared
|
|
1383
|
+
* token from `clientAuthTokens`) and, when valid, marks the session
|
|
1384
|
+
* trusted immediately — before the client's own handshake call.
|
|
1385
|
+
*/
|
|
1386
|
+
onConnect: (peer: Peer, session: DevframeNodeRpcSession) => void;
|
|
1387
|
+
/**
|
|
1388
|
+
* Print the current one-time code and its magic-link URL. Devframe stays
|
|
1389
|
+
* headless — call this yourself once the server is listening. Safe to
|
|
1390
|
+
* call repeatedly; it only prints once per code.
|
|
1391
|
+
*/
|
|
1392
|
+
printBanner: () => void;
|
|
1255
1393
|
}
|
|
1256
1394
|
//#endregion
|
|
1257
1395
|
//#region src/types/devframe.d.ts
|
|
@@ -1356,14 +1494,27 @@ interface DevframeCliOptions {
|
|
|
1356
1494
|
*/
|
|
1357
1495
|
open?: boolean | string;
|
|
1358
1496
|
/**
|
|
1359
|
-
*
|
|
1360
|
-
*
|
|
1497
|
+
* Authentication for the standalone dev server.
|
|
1498
|
+
*
|
|
1499
|
+
* - `undefined` / `true` — the standalone adapters (`cli` / `spa` /
|
|
1500
|
+
* served `build`) auto-wire devframe's interactive OTP auth
|
|
1501
|
+
* (`createInteractiveAuth`): an untrusted client can only reach
|
|
1502
|
+
* `anonymous:` methods until it exchanges the printed one-time code.
|
|
1503
|
+
* The adapter prints the code + magic-link banner once the server is
|
|
1504
|
+
* listening.
|
|
1505
|
+
* - `false` — no gate, for trusted single-user localhost tools where an
|
|
1506
|
+
* auth round-trip only gets in the way (the built-in plugins set this).
|
|
1507
|
+
* The `--no-auth` CLI flag maps here for one-off runs.
|
|
1508
|
+
* - A {@link DevframeAuthHandler} — a custom handler (e.g. a tuned
|
|
1509
|
+
* `createInteractiveAuth`, or an entirely different scheme) passed
|
|
1510
|
+
* straight through to `startHttpAndWs`.
|
|
1511
|
+
*
|
|
1512
|
+
* Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
|
|
1513
|
+
* auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
|
|
1361
1514
|
*
|
|
1362
|
-
*
|
|
1363
|
-
* ships its own auth layer; `@vitejs/devtools` honors the equivalent
|
|
1364
|
-
* `devtools.clientAuth` today.
|
|
1515
|
+
* @default true
|
|
1365
1516
|
*/
|
|
1366
|
-
auth?: boolean;
|
|
1517
|
+
auth?: boolean | DevframeAuthHandler;
|
|
1367
1518
|
/**
|
|
1368
1519
|
* Expose a route-based MCP server alongside the dev server, speaking the
|
|
1369
1520
|
* MCP Streamable-HTTP transport at `/__mcp` (relative to the base path).
|
|
@@ -1538,4 +1689,4 @@ interface DevframeDefinition {
|
|
|
1538
1689
|
}
|
|
1539
1690
|
declare function defineDevframe(d: DevframeDefinition): DevframeDefinition;
|
|
1540
1691
|
//#endregion
|
|
1541
|
-
export {
|
|
1692
|
+
export { DevframeRpcSharedStates as $, DevframeSettingsStore as A, EventsMap as At, DevframeDiagnosticsDefinition as B, DevframeServicesHost as C, AgentResourceInput as Ct, DevframeScopedStreamingHost as D, DevframeAgentHostEvents as Dt, DevframeScopedNodeRpc as E, DevframeAgentHost as Et, SettingsForNamespace as F, RpcFunctionsHost as G, DevframeDiagnosticsLogger as H, DevframeViewHost as I, RpcStreamingChannel as J, RpcSharedStateGetOptions as K, DevframeHost as L, ScopedClientFunctions as M, InferCliFlags as Mt, ScopedServerFunctions as N, defineCliFlags as Nt, DevframeSettings as O, EventEmitter as Ot, ScopedSharedStates as P, parseCliFlags as Pt, DevframeRpcServerFunctions as Q, DevframeStorageScope as R, DevframeServiceOf as S, AgentResourceContent as St, DevframeScopedNodeContext as T, AgentToolInput as Tt, DevframeNodeRpcSession as U, DevframeDiagnosticsHost as V, RpcBroadcastOptions as W, RpcStreamingHost as X, RpcStreamingChannelOptions as Y, DevframeRpcClientFunctions as Z, ConnectionMeta as _, SharedStatePatch as _t, DevframeDockDefaults as a, StreamSink as at, DevframeNodeContext as b, AgentManifest as bt, DevframeSetupInfo as c, createStreamSink as ct, McpRouteOptions as d, ImmutableMap as dt, BufferedChunk as et, defineDevframe as f, ImmutableObject as ft, Thenable as g, SharedStateOptions as gt, PartialWithoutId as h, SharedStateEvents as ht, DevframeDeploymentKind as i, StreamReader as it, ScopedBroadcastOptions as j, CliFlagsSchema as jt, DevframeSettingsRegistry as k, EventUnsubscribe as kt, DevframeSpaOptions as l, Immutable as lt, EntriesToObject as m, SharedState as mt, DevframeCliOptions as n, CreateStreamSinkOptions as nt, DevframeDuplicationStrategy as o, StreamSinkEvents as ot, DevframeAuthHandler as p, ImmutableSet as pt, RpcSharedStateHost as q, DevframeDefinition as r, StreamErrorPayload as rt, DevframeRuntime as s, createStreamReader as st, DevframeBrowserContext as t, CreateStreamReaderOptions as tt, DevframeWsOptions as u, ImmutableArray as ut, ConnectionMetaWebsocket as v, createSharedState as vt, DevframeServicesRegistry as w, AgentTool as wt, DevframeServiceId as x, AgentResource as xt, DevframeCapabilities as y, AgentHandle as yt, DevframeDefineDiagnosticsOptions as z };
|