devframe 0.9.0 → 0.9.2
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-BPmX9fqK.d.mts → _shared-BAGBgTUO.d.mts} +1 -1
- package/dist/{_shared-CbZ_yhbz.mjs → _shared-BM3PdYli.mjs} +1 -1
- package/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/build.mjs +12 -9
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +4 -3
- package/dist/adapters/dev.d.mts +3 -3
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/embedded.mjs +2 -0
- package/dist/adapters/initiate.d.mts +2 -2
- 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 +48 -2
- package/dist/client/index.mjs +133 -23
- package/dist/constants.d.mts +74 -2
- package/dist/constants.mjs +9 -2
- package/dist/{context-xdynGSLP.mjs → context-af2w_F0_.mjs} +2 -2
- package/dist/{context-B3YUu_95.d.mts → context-al99DDJ8.d.mts} +1 -1
- package/dist/{context-CEe4PBz1.mjs → context-riPPHGiu.mjs} +339 -26
- package/dist/{dev--rAPzbvO.mjs → dev-BKm_HisC.mjs} +15 -11
- package/dist/{devframe-DJMg0WLe.d.mts → devframe-mbfgpQQC.d.mts} +774 -587
- package/dist/{diagnostics-DOQPnQmX.mjs → diagnostics-DI1HGj2I.mjs} +20 -0
- package/dist/{dist-eNI9qM_I.mjs → dist-CZXfGEkd.mjs} +1 -1
- package/dist/events-DKfSmoTj.mjs +65 -0
- package/dist/hash-DdPnc4k3.mjs +360 -0
- package/dist/{host-agent-xLCSe65D.mjs → host-agent-DVDLqjvj.mjs} +14 -13
- package/dist/{http-DYpK0cUi.mjs → http-POepPnaQ.mjs} +10 -6
- package/dist/{index-CB-fSUyn.d.mts → index-Dipgo9ji.d.mts} +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/{instance-registry-WvQkt42E.mjs → instance-registry-Dy28drtm.mjs} +4 -4
- package/dist/{instance-shell-CMFyszWJ.mjs → instance-shell-GqTY93gC.mjs} +5 -5
- package/dist/{instance-shell-CljiKq1c.d.mts → instance-shell-mU5j5xWn.d.mts} +2 -2
- package/dist/internal/index.d.mts +40 -4
- package/dist/internal/index.mjs +7 -7
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +10 -1
- package/dist/node/index.mjs +2 -2
- package/dist/recipes/common-rpc-functions.d.mts +11 -1
- package/dist/recipes/common-rpc-functions.mjs +10 -0
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +2 -2
- package/dist/{remote-assets-nTTxY2MU.mjs → remote-assets-CqxiyltC.mjs} +18 -7
- package/dist/{revoke-DZbJ7Cl0.mjs → revoke-3Q6aDKWk.mjs} +2 -1
- package/dist/{rolldown-runtime-JspESFgx.mjs → rolldown-runtime-B4iAMlE-.mjs} +1 -1
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.mjs +1 -1
- package/dist/rpc/transports/ws-bun.d.mts +1 -1
- package/dist/rpc/transports/ws-deno.d.mts +26 -0
- package/dist/rpc/transports/ws-deno.mjs +30 -0
- package/dist/{rpc-core-DMTe-hLo.mjs → rpc-core-DRYnbxdD.mjs} +2 -2
- package/dist/{rpc-core-Cc4lucI0.d.mts → rpc-core-dV69u4lY.d.mts} +2 -2
- package/dist/{shared-state-fRRbRUtD.mjs → shared-state-DoXutg_U.mjs} +1 -1
- package/dist/{state-Cc1IeSmQ.mjs → state-CK9LjrnT.mjs} +1 -1
- package/dist/{static-BPZ1_OPA.mjs → static-DV_qUSRS.mjs} +1 -1
- package/dist/{storage-CYye7P7L.mjs → storage-BNqSAOxA.mjs} +3 -3
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/get-port.mjs +1 -1
- package/dist/utils/hash.mjs +1 -1
- package/dist/utils/launch-editor.mjs +12 -11
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/remote-assets.d.mts +7 -1
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.mjs +6 -5
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/validation-YrZH6atx.mjs +311 -0
- package/package.json +4 -3
- package/skills/devframe/SKILL.md +1 -1
- package/dist/hash-KtDZYXDN.mjs +0 -266
- package/dist/validation-CpXFB6Dz.mjs +0 -266
- package/dist/{host-h3-Crkzm42q.mjs → host-h3-fRbF9yor.mjs} +1 -1
|
@@ -493,734 +493,885 @@ declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamS
|
|
|
493
493
|
*/
|
|
494
494
|
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
495
495
|
//#endregion
|
|
496
|
-
//#region src/types/
|
|
496
|
+
//#region src/types/diagnostics.d.ts
|
|
497
497
|
/**
|
|
498
|
-
*
|
|
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.
|
|
499
503
|
*/
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Server→client notification that this connection's auth token has been
|
|
503
|
-
* revoked. The client drops to untrusted on receipt. Broadcast by
|
|
504
|
-
* `revokeActiveConnectionsForToken`.
|
|
505
|
-
*
|
|
506
|
-
* @internal
|
|
507
|
-
*/
|
|
508
|
-
'devframe:auth:revoked': () => Promise<void>;
|
|
509
|
-
/**
|
|
510
|
-
* Streaming chunk pushed from server to subscribed clients. Wired by
|
|
511
|
-
* `RpcStreamingHost`; do not register manually.
|
|
512
|
-
*
|
|
513
|
-
* @internal
|
|
514
|
-
*/
|
|
515
|
-
'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
516
|
-
/**
|
|
517
|
-
* Streaming terminator pushed from server to subscribed clients. Wired by
|
|
518
|
-
* `RpcStreamingHost`; do not register manually.
|
|
519
|
-
*
|
|
520
|
-
* @internal
|
|
521
|
-
*/
|
|
522
|
-
'devframe:streaming:end': (channel: string, id: string, error?: {
|
|
523
|
-
name: string;
|
|
524
|
-
message: string;
|
|
525
|
-
}) => Promise<void>;
|
|
526
|
-
/**
|
|
527
|
-
* Server→client cancel for an in-flight upload. Wired by
|
|
528
|
-
* `RpcStreamingHost`; do not register manually.
|
|
529
|
-
*
|
|
530
|
-
* @internal
|
|
531
|
-
*/
|
|
532
|
-
'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
|
|
533
|
-
/**
|
|
534
|
-
* Full shared-state snapshot pushed from server to subscribed clients.
|
|
535
|
-
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
536
|
-
*
|
|
537
|
-
* @internal
|
|
538
|
-
*/
|
|
539
|
-
'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
|
|
540
|
-
/**
|
|
541
|
-
* Incremental shared-state patch pushed from server to subscribed clients.
|
|
542
|
-
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
543
|
-
*
|
|
544
|
-
* @internal
|
|
545
|
-
*/
|
|
546
|
-
'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
547
|
-
}
|
|
504
|
+
type DevframeDiagnosticsLogger = Record<string, any>;
|
|
548
505
|
/**
|
|
549
|
-
*
|
|
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
|
+
* ```
|
|
550
527
|
*/
|
|
551
|
-
interface
|
|
528
|
+
interface DevframeDiagnosticsHost {
|
|
552
529
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
559
|
-
* reachable before the connection is trusted — this is the *only* rule the
|
|
560
|
-
* pre-trust gate applies, no per-method allowlist.
|
|
561
|
-
*
|
|
562
|
-
* @internal
|
|
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.
|
|
563
535
|
*/
|
|
564
|
-
|
|
565
|
-
authToken: string;
|
|
566
|
-
ua: string;
|
|
567
|
-
origin: string;
|
|
568
|
-
}) => Promise<{
|
|
569
|
-
isTrusted: boolean;
|
|
570
|
-
}>;
|
|
536
|
+
readonly logger: DevframeDiagnosticsLogger;
|
|
571
537
|
/**
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
* reachable before the connection is trusted.
|
|
578
|
-
*
|
|
579
|
-
* @internal
|
|
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.
|
|
580
543
|
*/
|
|
581
|
-
|
|
582
|
-
code: string;
|
|
583
|
-
ua: string;
|
|
584
|
-
origin: string;
|
|
585
|
-
}) => Promise<{
|
|
586
|
-
authToken: string | null;
|
|
587
|
-
}>;
|
|
544
|
+
register: (definitions: Record<string, unknown>) => void;
|
|
588
545
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
* @internal
|
|
546
|
+
* Build a typed diagnostics object with the host's ANSI console reporter
|
|
547
|
+
* pre-wired. The same `devframe/utils/nostics` `defineDiagnostics` every
|
|
548
|
+
* built-in plugin's module-level `diagnostics.ts` uses, so integrations
|
|
549
|
+
* don't need to take a direct dependency on `nostics`.
|
|
595
550
|
*/
|
|
596
|
-
|
|
551
|
+
defineDiagnostics: typeof defineDiagnostics;
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/types/host.d.ts
|
|
555
|
+
interface DevframeHost {
|
|
597
556
|
/**
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
557
|
+
* Serve static assets at the given URL base — a local directory, or a
|
|
558
|
+
* resolved {@link RemoteAssetsStore} back-proxy. Called by
|
|
559
|
+
* `DevframeViewHost.hostStatic` (which normalizes `RemoteAssets`
|
|
560
|
+
* declarations into stores first). Implementations map this to whatever
|
|
561
|
+
* the underlying runtime expects (Vite middleware, h3 handler, no-op
|
|
562
|
+
* for build snapshots) — the shared engine in
|
|
563
|
+
* `devframe/utils/serve-static` accepts either shape.
|
|
602
564
|
*/
|
|
603
|
-
|
|
565
|
+
mountStatic: (base: string, source: string | RemoteAssetsStore) => void | Promise<void>;
|
|
604
566
|
/**
|
|
605
|
-
*
|
|
606
|
-
*
|
|
567
|
+
* Serve the host's connection meta (`__connection.json`) at the given URL
|
|
568
|
+
* base, so a devframe SPA mounted there can discover the RPC/WS endpoint
|
|
569
|
+
* via `connectDevframe()`'s relative `./__connection.json` fetch.
|
|
607
570
|
*
|
|
608
|
-
*
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
*
|
|
613
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
571
|
+
* Called by `ctx.install` for each mounted devframe (alongside
|
|
572
|
+
* `mountStatic`). Without it, an embedded SPA can only discover the
|
|
573
|
+
* endpoint by inheriting it from a same-origin parent window — which fails
|
|
574
|
+
* for cross-origin or sandboxed iframes. Implementations serve the same
|
|
575
|
+
* meta they expose at the hub's own base.
|
|
614
576
|
*
|
|
615
|
-
*
|
|
577
|
+
* Optional in the type, but a host that mounts a devframe with a servable
|
|
578
|
+
* `distDir` yet omits this hook triggers a `DF8106` diagnostic, since the
|
|
579
|
+
* SPA's `./__connection.json` fetch would otherwise fall through and break
|
|
580
|
+
* silently. A static-snapshot host that bakes the meta into its served files
|
|
581
|
+
* can implement it as a no-op to acknowledge this intentionally.
|
|
616
582
|
*/
|
|
617
|
-
|
|
583
|
+
mountConnectionMeta?: (base: string) => void | Promise<void>;
|
|
618
584
|
/**
|
|
619
|
-
*
|
|
620
|
-
* `
|
|
621
|
-
*
|
|
622
|
-
*
|
|
585
|
+
* Return the public origin the host is reachable at, e.g.
|
|
586
|
+
* `http://localhost:5173`. Used by the dock host to enrich remote
|
|
587
|
+
* iframe URLs with a full `origin`. Called only when a dock needs an
|
|
588
|
+
* absolute URL; hosts that never serve remote docks can return any
|
|
589
|
+
* reasonable value.
|
|
623
590
|
*/
|
|
624
|
-
|
|
591
|
+
resolveOrigin: () => string;
|
|
625
592
|
/**
|
|
626
|
-
*
|
|
627
|
-
*
|
|
593
|
+
* Resolve a directory the host owns for persisted devframe state.
|
|
594
|
+
* Each host picks its own app-name namespace so storage doesn't
|
|
595
|
+
* collide between, say, the Vite host (`.vite/devframe`) and a
|
|
596
|
+
* standalone CLI host (`.<appName>/devframe`).
|
|
628
597
|
*
|
|
629
|
-
*
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
*
|
|
636
|
-
*
|
|
598
|
+
* - `workspace` — state shared with the whole team through version
|
|
599
|
+
* control (saved queries, shared presets). Conventionally
|
|
600
|
+
* `${workspaceRoot}/.devframe/`; hosts must place it somewhere
|
|
601
|
+
* committable.
|
|
602
|
+
* - `project` — per-checkout private state (caches, personal
|
|
603
|
+
* settings). Typically under
|
|
604
|
+
* `${cwd}/node_modules/.<appName>/devframe/`, which version
|
|
605
|
+
* control ignores.
|
|
606
|
+
* - `global` — per-user state (auth tokens, machine-wide
|
|
607
|
+
* preferences). Typically under
|
|
608
|
+
* `${homedir()}/.<appName>/devframe/`.
|
|
637
609
|
*
|
|
638
|
-
*
|
|
610
|
+
* Implementations should ensure the directory exists or be safe to
|
|
611
|
+
* pass to a downstream `createStorage(...)` call that creates it
|
|
612
|
+
* lazily.
|
|
639
613
|
*/
|
|
640
|
-
|
|
614
|
+
getStorageDir: (scope: DevframeStorageScope) => string;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Storage placement classes for {@link DevframeHost.getStorageDir}:
|
|
618
|
+
* `workspace` is committable and team-shared, `project` is per-checkout
|
|
619
|
+
* private, `global` is per-user.
|
|
620
|
+
*/
|
|
621
|
+
type DevframeStorageScope = 'workspace' | 'project' | 'global';
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/types/views.d.ts
|
|
624
|
+
interface DevframeViewHost {
|
|
641
625
|
/**
|
|
642
|
-
* Client→server streaming cancellation request. Wired by
|
|
643
|
-
* `RpcStreamingHost`; do not register manually.
|
|
644
|
-
*
|
|
645
626
|
* @internal
|
|
646
627
|
*/
|
|
647
|
-
|
|
628
|
+
buildStaticDirs: {
|
|
629
|
+
baseUrl: string;
|
|
630
|
+
source: StaticAssetsSource;
|
|
631
|
+
}[];
|
|
648
632
|
/**
|
|
649
|
-
*
|
|
650
|
-
* register
|
|
633
|
+
* Helper to host static files
|
|
634
|
+
* - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
|
|
635
|
+
* - In `build` mode, it will copy the static files to the dist directory
|
|
651
636
|
*
|
|
652
|
-
* @
|
|
653
|
-
|
|
654
|
-
'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
655
|
-
/**
|
|
656
|
-
* Client→server upload terminator. Wired by `RpcStreamingHost`; do not
|
|
657
|
-
* register manually.
|
|
637
|
+
* Accepts a local dist directory, or a {@link StaticAssetsSource} remote
|
|
638
|
+
* declaration served through devframe's caching CDN back-proxy.
|
|
658
639
|
*
|
|
659
|
-
*
|
|
640
|
+
* `defaultResolveFrom` overrides, for this call only, the context's own
|
|
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.
|
|
660
646
|
*/
|
|
661
|
-
|
|
662
|
-
name: string;
|
|
663
|
-
message: string;
|
|
664
|
-
}) => Promise<void>;
|
|
647
|
+
hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
|
|
665
648
|
}
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/types/scope.d.ts
|
|
651
|
+
type AnyRpcFn = (...args: any[]) => any;
|
|
666
652
|
/**
|
|
667
|
-
*
|
|
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.
|
|
668
658
|
*/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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>>;
|
|
698
|
+
/**
|
|
699
|
+
* Subscribe to settings changes. Resolves to an unsubscribe function.
|
|
700
|
+
* Fires on every local or remote mutation with the full snapshot.
|
|
701
|
+
*/
|
|
702
|
+
onChange: (fn: (value: Readonly<T>) => void) => Promise<() => void>;
|
|
675
703
|
}
|
|
676
|
-
|
|
704
|
+
/**
|
|
705
|
+
* The two settings scopes available on a scoped context.
|
|
706
|
+
*
|
|
707
|
+
* - `project` — per-workspace state, persisted under the host's
|
|
708
|
+
* `workspace` storage dir. Project-local settings.
|
|
709
|
+
* - `global` — per-user state, persisted under the host's `global`
|
|
710
|
+
* storage dir. Machine-wide preferences.
|
|
711
|
+
*/
|
|
712
|
+
interface DevframeSettings<T extends Record<string, any> = Record<string, any>> {
|
|
713
|
+
global: DevframeSettingsStore<T>;
|
|
714
|
+
project: DevframeSettingsStore<T>;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Map the server-side RPC registry down to the bare names owned by a
|
|
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).
|
|
735
|
+
*/
|
|
736
|
+
interface ScopedBroadcastOptions<METHOD, Args extends any[]> {
|
|
677
737
|
method: METHOD;
|
|
678
738
|
args: Args;
|
|
679
739
|
optional?: boolean;
|
|
680
740
|
event?: boolean;
|
|
681
|
-
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
682
741
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
*/
|
|
689
|
-
invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
|
|
742
|
+
/**
|
|
743
|
+
* Node-side streaming host scoped to a namespace. Channel names are
|
|
744
|
+
* auto-prefixed with `<namespace>:`.
|
|
745
|
+
*/
|
|
746
|
+
interface DevframeScopedStreamingHost {
|
|
690
747
|
/**
|
|
691
|
-
*
|
|
748
|
+
* Register a streaming channel. The bare `name` is auto-prefixed with
|
|
749
|
+
* the scope namespace (`<namespace>:<name>`); pass an already-qualified
|
|
750
|
+
* name (containing `:`) to opt out.
|
|
692
751
|
*/
|
|
693
|
-
|
|
752
|
+
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* The scoped node RPC surface exposed on `ctx.scope('my-plugin').rpc`.
|
|
756
|
+
* IDs and keys you pass are auto-namespaced with `my-plugin:`.
|
|
757
|
+
*/
|
|
758
|
+
interface DevframeScopedNodeRpc<NS extends string = string> {
|
|
759
|
+
/** The namespace this surface is scoped to. */
|
|
760
|
+
readonly namespace: NS;
|
|
694
761
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
762
|
+
* Register a server RPC function. The definition's `name` must be bare
|
|
763
|
+
* (no `:`); it is stored as `<namespace>:<name>`. Throws `DF0034` if an
|
|
764
|
+
* already-namespaced name is passed.
|
|
698
765
|
*/
|
|
699
|
-
|
|
766
|
+
register: (fn: RpcFunctionDefinition<string, any, any, any, any, any, DevframeNodeContext>, force?: boolean) => void;
|
|
700
767
|
/**
|
|
701
|
-
*
|
|
768
|
+
* Update a previously registered server RPC function. Same naming rules
|
|
769
|
+
* as {@link DevframeScopedNodeRpc.register}.
|
|
702
770
|
*/
|
|
703
|
-
|
|
771
|
+
update: (fn: RpcFunctionDefinition<string, any, any, any, any, any, DevframeNodeContext>, force?: boolean) => void;
|
|
704
772
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* @see RpcStreamingHost
|
|
773
|
+
* Invoke a locally registered server RPC function. Bare names are
|
|
774
|
+
* resolved within this namespace; pass a fully-qualified name
|
|
775
|
+
* (containing `:`) to call another scope's function.
|
|
709
776
|
*/
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
interface RpcSharedStateHost {
|
|
717
|
-
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
718
|
-
keys: () => string[];
|
|
777
|
+
call: {
|
|
778
|
+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): Promise<Awaited<ReturnType<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>>>;
|
|
779
|
+
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>>>;
|
|
780
|
+
(method: string, ...args: any[]): Promise<any>;
|
|
781
|
+
};
|
|
719
782
|
/**
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
723
|
-
* as dynamic resources.
|
|
783
|
+
* Broadcast a client RPC event to every connected client. Bare method
|
|
784
|
+
* names are resolved within this namespace.
|
|
724
785
|
*/
|
|
725
|
-
|
|
786
|
+
broadcast: {
|
|
787
|
+
<T extends keyof ScopedClientFunctions<NS> & string>(options: ScopedBroadcastOptions<T, Parameters<ScopedRpcFn<DevframeRpcClientFunctions, NS, T>>>): Promise<void>;
|
|
788
|
+
(options: ScopedBroadcastOptions<string, any[]>): Promise<void>;
|
|
789
|
+
};
|
|
726
790
|
/**
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* Used by short-lived states (e.g. a disposed JSON-render view) to avoid
|
|
730
|
-
* leaking listeners and lingering entries for the context lifetime.
|
|
791
|
+
* Resolve a namespaced shared state. Bare keys are resolved within this
|
|
792
|
+
* namespace; pass a fully-qualified key (containing `:`) to opt out.
|
|
731
793
|
*/
|
|
732
|
-
|
|
794
|
+
sharedState: {
|
|
795
|
+
<T extends keyof ScopedSharedStates<NS> & string>(key: T, options?: RpcSharedStateGetOptions<ScopedSharedStates<NS>[T]>): Promise<SharedState<ScopedSharedStates<NS>[T]>>;
|
|
796
|
+
<T extends Record<string, any> = Record<string, any>>(key: string, options?: RpcSharedStateGetOptions<T>): Promise<SharedState<T>>;
|
|
797
|
+
};
|
|
798
|
+
/** Streaming host scoped to this namespace. */
|
|
799
|
+
streaming: DevframeScopedStreamingHost;
|
|
800
|
+
/** Pass-through to the current RPC session, when called inside a handler. */
|
|
801
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
733
802
|
}
|
|
734
803
|
/**
|
|
735
|
-
*
|
|
804
|
+
* A namespace-scoped view of {@link DevframeNodeContext}. Returned by
|
|
805
|
+
* `ctx.scope('my-plugin')`. Re-exposes the unscoped surfaces (`views`,
|
|
806
|
+
* `diagnostics`, `agent`, …), replaces `rpc` with the auto-namespaced
|
|
807
|
+
* {@link DevframeScopedNodeRpc}, and adds a top-level `settings` store.
|
|
736
808
|
*/
|
|
737
|
-
interface
|
|
738
|
-
/**
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
809
|
+
interface DevframeScopedNodeContext<NS extends string = string, Settings extends Record<string, any> = Record<string, any>> {
|
|
810
|
+
/** The namespace this context is scoped to. */
|
|
811
|
+
readonly namespace: NS;
|
|
812
|
+
/** The underlying unscoped context. */
|
|
813
|
+
readonly base: DevframeNodeContext;
|
|
814
|
+
readonly workspaceRoot: string;
|
|
815
|
+
readonly cwd: string;
|
|
816
|
+
readonly mode: 'dev' | 'build';
|
|
817
|
+
host: DevframeHost;
|
|
818
|
+
rpc: DevframeScopedNodeRpc<NS>;
|
|
819
|
+
/** Persisted settings for this namespace (`global` + `project`). */
|
|
820
|
+
settings: DevframeSettings<Settings>;
|
|
821
|
+
views: DevframeViewHost;
|
|
822
|
+
diagnostics: DevframeDiagnosticsHost;
|
|
823
|
+
agent: DevframeAgentHost;
|
|
747
824
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
* this window, late subscribers can still join and replay the buffer
|
|
751
|
-
* + receive the `end` frame.
|
|
752
|
-
*
|
|
753
|
-
* Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
|
|
754
|
-
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
755
|
-
* post-mortem replay.
|
|
825
|
+
* Return a new scoped context, replacing the current scope. Pass `null`
|
|
826
|
+
* or `''` to un-scope and get the base context.
|
|
756
827
|
*/
|
|
757
|
-
|
|
828
|
+
scope: DevframeNodeContext['scope'];
|
|
758
829
|
}
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/types/services.d.ts
|
|
759
832
|
/**
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
|
|
833
|
+
* Augmentation point mapping service ids to their implementation types.
|
|
834
|
+
* Providers extend this via `declare module 'devframe'`.
|
|
835
|
+
*/
|
|
836
|
+
interface DevframeServicesRegistry {}
|
|
837
|
+
/**
|
|
838
|
+
* A known (augmented) service id, or any namespaced string for services
|
|
839
|
+
* without a published type augmentation.
|
|
840
|
+
*/
|
|
841
|
+
type DevframeServiceId = keyof DevframeServicesRegistry | (string & {});
|
|
842
|
+
/** Resolved service type for an id: augmented type, or `unknown`. */
|
|
843
|
+
type DevframeServiceOf<ID> = ID extends keyof DevframeServicesRegistry ? DevframeServicesRegistry[ID] : unknown;
|
|
844
|
+
/**
|
|
845
|
+
* Augmentation point mapping a service's npm package name to the RPC scope
|
|
846
|
+
* namespace it registers its functions under, so a client's
|
|
847
|
+
* `services.get('@devframes/service-x')` returns a scoped RPC surface typed
|
|
848
|
+
* against that namespace. Service packages contribute their entry via
|
|
849
|
+
* declaration merging:
|
|
763
850
|
*
|
|
764
|
-
*
|
|
851
|
+
* ```ts
|
|
852
|
+
* declare module 'devframe' {
|
|
853
|
+
* interface DevframeServicesScopeRegistry {
|
|
854
|
+
* '@devframes/service-open': 'devframes:service:open'
|
|
855
|
+
* }
|
|
856
|
+
* }
|
|
857
|
+
* ```
|
|
765
858
|
*/
|
|
766
|
-
interface
|
|
767
|
-
|
|
768
|
-
|
|
859
|
+
interface DevframeServicesScopeRegistry {}
|
|
860
|
+
/** Resolved RPC scope namespace for a service package name, or `string`. */
|
|
861
|
+
type DevframeServiceScopeOf<PKG> = PKG extends keyof DevframeServicesScopeRegistry ? DevframeServicesScopeRegistry[PKG] & string : string;
|
|
862
|
+
/**
|
|
863
|
+
* A **wire service** — a shared server-side capability (e.g. open-in-editor,
|
|
864
|
+
* syntax highlighting) packaged so any devframe host can install it once and
|
|
865
|
+
* every plugin/client can consume it without re-implementing or re-bundling
|
|
866
|
+
* it. Contrast with plain {@link DevframeServicesHost.provide}, which shares
|
|
867
|
+
* an in-process object between plugins on the node side only: a
|
|
868
|
+
* `DevframeServiceDefinition` additionally registers RPC functions under its
|
|
869
|
+
* {@link DevframeServiceDefinition.scope} and is **advertised to clients**
|
|
870
|
+
* through the reactive `devframe:services` shared state, so browser UIs can
|
|
871
|
+
* feature-detect it (`ctx.services.has(pkg)`) and degrade gracefully.
|
|
872
|
+
*
|
|
873
|
+
* Ship one per npm package (`@devframes/service-<slug>` for first-party),
|
|
874
|
+
* with the package's default export being the `create<X>Service` factory —
|
|
875
|
+
* never a pre-built instance.
|
|
876
|
+
*/
|
|
877
|
+
interface DevframeServiceDefinition<API = unknown, Options = any> {
|
|
769
878
|
/**
|
|
770
|
-
*
|
|
771
|
-
* (`
|
|
772
|
-
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
773
|
-
* disconnects or cancels.
|
|
879
|
+
* The npm package name this service ships in — also its registry key
|
|
880
|
+
* (`ctx.services.has('@devframes/service-x')` on both node and client).
|
|
774
881
|
*/
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
882
|
+
package: string;
|
|
883
|
+
/** Semver of the service — advertised to clients, checked against descriptor ranges. */
|
|
884
|
+
version: string;
|
|
778
885
|
/**
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
783
|
-
* before passing it here.
|
|
886
|
+
* RPC namespace the service's functions register under, following the
|
|
887
|
+
* plugin id grammar (e.g. `devframes:service:open`). `setup` receives a
|
|
888
|
+
* context pre-scoped to it, so functions register with bare names.
|
|
784
889
|
*/
|
|
785
|
-
|
|
786
|
-
id?: string;
|
|
787
|
-
}) => Promise<StreamSink<T>>;
|
|
788
|
-
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
789
|
-
get: (id: string) => StreamSink<T> | undefined;
|
|
790
|
-
/** All active outbound stream ids on this channel. */
|
|
791
|
-
ids: () => string[];
|
|
890
|
+
scope: string;
|
|
792
891
|
/**
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
* the client writes chunks. Typical pattern is to call this from an
|
|
796
|
-
* action handler, kick off background processing, and return the id
|
|
797
|
-
* so the caller can start uploading:
|
|
798
|
-
*
|
|
799
|
-
* ```ts
|
|
800
|
-
* handler: async () => {
|
|
801
|
-
* const reader = channel.openInbound()
|
|
802
|
-
* ;(async () => {
|
|
803
|
-
* for await (const chunk of reader) processChunk(chunk)
|
|
804
|
-
* })()
|
|
805
|
-
* return { uploadId: reader.id }
|
|
806
|
-
* }
|
|
807
|
-
* ```
|
|
808
|
-
*
|
|
809
|
-
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
810
|
-
* the uploading client, which aborts its sink.
|
|
892
|
+
* Extra advertised metadata (feature flags, defaults, …). Must be
|
|
893
|
+
* JSON-serializable — it is mirrored to every client.
|
|
811
894
|
*/
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
895
|
+
meta?: Record<string, unknown>;
|
|
896
|
+
/**
|
|
897
|
+
* This instance's own option set (usually baked in by the factory that
|
|
898
|
+
* created the definition). Joins the merge alongside every declarative
|
|
899
|
+
* descriptor's `options`.
|
|
900
|
+
*/
|
|
901
|
+
options?: Options;
|
|
902
|
+
/**
|
|
903
|
+
* Merge the option sets contributed by multiple installers (in declaration
|
|
904
|
+
* order) into the one bag passed to `setup`. Defaults to a shallow merge
|
|
905
|
+
* where later sets win.
|
|
906
|
+
*/
|
|
907
|
+
mergeOptions?: (sets: Options[]) => Options;
|
|
908
|
+
/**
|
|
909
|
+
* Construct the service: register its RPC functions on the pre-scoped
|
|
910
|
+
* context and return its **node API** — the in-process surface other
|
|
911
|
+
* plugins get from `ctx.services.get(package)` (no RPC hop server-side).
|
|
912
|
+
* `info.options` carries every installer's option sets merged at the
|
|
913
|
+
* `ready()` barrier (via {@link mergeOptions} when present, otherwise a
|
|
914
|
+
* shallow merge in declaration order — later sets win).
|
|
915
|
+
*/
|
|
916
|
+
setup: (ctx: DevframeScopedNodeContext, info: {
|
|
917
|
+
options?: Options;
|
|
918
|
+
}) => API | Promise<API>;
|
|
815
919
|
}
|
|
816
920
|
/**
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
921
|
+
* Declarative reference to a service package — the form a
|
|
922
|
+
* {@link DevframeServiceInput} takes when the installer doesn't hold the
|
|
923
|
+
* factory itself (e.g. `DevframeDefinition.services`). The host imports the
|
|
924
|
+
* package's default-export factory and installs the resulting definition at
|
|
925
|
+
* the `ready()` barrier.
|
|
820
926
|
*/
|
|
821
|
-
interface
|
|
927
|
+
interface DevframeServiceDescriptor<Options = any> {
|
|
928
|
+
/** npm package name of the service (its default export is the factory). */
|
|
929
|
+
package: string;
|
|
822
930
|
/**
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
931
|
+
* Accepted semver range for the installed service. An unsatisfied range
|
|
932
|
+
* warns (`DF0069`) — or throws (`DF0068`) when {@link required} — while the
|
|
933
|
+
* service still installs; the advertised meta carries the real version.
|
|
826
934
|
*/
|
|
827
|
-
|
|
935
|
+
version?: string;
|
|
828
936
|
/**
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
937
|
+
* Fail hard when the service can't be imported or its version range isn't
|
|
938
|
+
* satisfied. By default a missing service is skipped silently — clients see
|
|
939
|
+
* `has() === false` and degrade.
|
|
832
940
|
*
|
|
833
|
-
* @
|
|
941
|
+
* @default false
|
|
834
942
|
*/
|
|
835
|
-
|
|
943
|
+
required?: boolean;
|
|
944
|
+
/** Option set this installer contributes to the merge. */
|
|
945
|
+
options?: Options;
|
|
836
946
|
}
|
|
837
|
-
//#endregion
|
|
838
|
-
//#region src/types/diagnostics.d.ts
|
|
839
947
|
/**
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
* integrations.
|
|
948
|
+
* What can be passed to `ctx.services.install()` (and listed in
|
|
949
|
+
* `DevframeDefinition.services`): a declarative {@link DevframeServiceDescriptor}
|
|
950
|
+
* (the host imports the factory) or a ready {@link DevframeServiceDefinition}
|
|
951
|
+
* (the installer already called the factory — its `options` join the merge).
|
|
845
952
|
*/
|
|
846
|
-
type
|
|
953
|
+
type DevframeServiceInput<API = unknown, Options = any> = DevframeServiceDescriptor<Options> | DevframeServiceDefinition<API, Options>;
|
|
847
954
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* registry without taking a direct dependency on `nostics`.
|
|
851
|
-
*
|
|
852
|
-
* Typical usage from a plugin's `setup(ctx)`:
|
|
853
|
-
*
|
|
854
|
-
* ```ts
|
|
855
|
-
* const myDiagnostics = ctx.diagnostics.defineDiagnostics({
|
|
856
|
-
* docsBase: 'https://example.com/errors',
|
|
857
|
-
* codes: {
|
|
858
|
-
* MYP0001: { why: 'Something went wrong' },
|
|
859
|
-
* },
|
|
860
|
-
* })
|
|
861
|
-
* ctx.diagnostics.register(myDiagnostics)
|
|
862
|
-
*
|
|
863
|
-
* // Through the shared lookup (loose typing):
|
|
864
|
-
* throw ctx.diagnostics.logger.MYP0001()
|
|
865
|
-
*
|
|
866
|
-
* // Or directly on the typed handle returned from `defineDiagnostics`:
|
|
867
|
-
* throw myDiagnostics.MYP0001()
|
|
868
|
-
* ```
|
|
955
|
+
* One service's advertisement entry, mirrored to clients through the
|
|
956
|
+
* `devframe:services` shared state.
|
|
869
957
|
*/
|
|
870
|
-
interface
|
|
958
|
+
interface DevframeServiceMeta {
|
|
959
|
+
/** npm package name — the registry key. */
|
|
960
|
+
package: string;
|
|
961
|
+
/** Installed version of the service. */
|
|
962
|
+
version: string;
|
|
963
|
+
/** RPC namespace its functions live under. */
|
|
964
|
+
scope: string;
|
|
965
|
+
/** Extra service-declared metadata. */
|
|
966
|
+
meta?: Record<string, unknown>;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Shape of the `devframe:services` shared state: package name → advertisement.
|
|
970
|
+
*/
|
|
971
|
+
type DevframeServicesState = Record<string, DevframeServiceMeta>;
|
|
972
|
+
interface DevframeServicesHost {
|
|
871
973
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
* diagnostic and routes it through registered reporters; prefix with
|
|
875
|
-
* `throw` to raise. Loosely typed — for autocompletion, keep a reference
|
|
876
|
-
* to the typed result of `defineDiagnostics()` instead.
|
|
974
|
+
* Publish a service under a namespaced id. Throws `DF0037` when the id is
|
|
975
|
+
* already provided (revoke first to replace). Returns a revoke function.
|
|
877
976
|
*/
|
|
878
|
-
|
|
977
|
+
provide: <ID extends DevframeServiceId>(id: ID, service: DevframeServiceOf<ID>) => () => void;
|
|
978
|
+
/** The service currently provided under `id`, or `undefined`. */
|
|
979
|
+
get: <ID extends DevframeServiceId>(id: ID) => DevframeServiceOf<ID> | undefined;
|
|
980
|
+
has: (id: DevframeServiceId) => boolean;
|
|
879
981
|
/**
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
* factory pre-wires the host's ANSI console reporter.
|
|
982
|
+
* Run `callback` with the service as soon as it is available — immediately
|
|
983
|
+
* when already provided, otherwise on `provide`. Survives provider/consumer
|
|
984
|
+
* setup-order differences. The callback also re-fires if the service is
|
|
985
|
+
* revoked and provided again. Returns an unsubscribe function.
|
|
885
986
|
*/
|
|
886
|
-
|
|
987
|
+
whenAvailable: <ID extends DevframeServiceId>(id: ID, callback: (service: DevframeServiceOf<ID>) => void) => () => void;
|
|
988
|
+
/** Ids of every currently-provided service. */
|
|
989
|
+
keys: () => string[];
|
|
887
990
|
/**
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
|
|
893
|
-
|
|
991
|
+
* Install a **wire service** (see {@link DevframeServiceDefinition}). The
|
|
992
|
+
* common path is declarative — list services on `DevframeDefinition.services`
|
|
993
|
+
* (or `initHub({ services })`) and the adapter installs them for you before
|
|
994
|
+
* `setup` runs. Call `install()` directly only for the dynamic escape hatch:
|
|
995
|
+
* a service configured at runtime from data unknown until then.
|
|
996
|
+
*
|
|
997
|
+
* Before the pre-setup ready fires, installs are queued and their option
|
|
998
|
+
* sets deep-merged, constructing each service **once**. After it, an install
|
|
999
|
+
* constructs immediately; installing an already-installed package returns
|
|
1000
|
+
* the existing API (a warning — `DF0066` — when the late install carried
|
|
1001
|
+
* options, since they're ignored). The returned promise resolves with the
|
|
1002
|
+
* node API (or `undefined` when an optional descriptor's package can't be
|
|
1003
|
+
* imported).
|
|
1004
|
+
*
|
|
1005
|
+
* `resolveFrom` is where a descriptor's package resolves **from**: a path
|
|
1006
|
+
* or file URL (e.g. the declaring devframe's `importMetaUrl`), or an npm
|
|
1007
|
+
* package name, so its declared services resolve against the declarer's own
|
|
1008
|
+
* dependencies. Falls back to the context's `workspaceRoot` then `cwd`.
|
|
1009
|
+
*/
|
|
1010
|
+
install: <API = unknown, Options = any>(input: DevframeServiceInput<API, Options>, options?: {
|
|
1011
|
+
resolveFrom?: string | null;
|
|
1012
|
+
}) => Promise<API | undefined>;
|
|
1013
|
+
/**
|
|
1014
|
+
* Construct every queued service — importing descriptor packages, merging
|
|
1015
|
+
* option sets, `provide()`-ing each node API under its package name, and
|
|
1016
|
+
* advertising it to clients via the `devframe:services` shared state.
|
|
1017
|
+
* Idempotent. **Internal**: the adapters call it once, before running any
|
|
1018
|
+
* `setup`, so services are ready for `setup` to consume; application code
|
|
1019
|
+
* uses declarative `services` (or `install()` for the dynamic case) and
|
|
1020
|
+
* never calls this. Rejects when a `required` service fails to import or
|
|
1021
|
+
* misses its version range.
|
|
1022
|
+
*/
|
|
1023
|
+
ready: () => Promise<void>;
|
|
894
1024
|
}
|
|
895
1025
|
//#endregion
|
|
896
|
-
//#region src/types/
|
|
897
|
-
|
|
1026
|
+
//#region src/types/rpc-augments.d.ts
|
|
1027
|
+
/**
|
|
1028
|
+
* To be extended
|
|
1029
|
+
*/
|
|
1030
|
+
interface DevframeRpcClientFunctions {
|
|
898
1031
|
/**
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
* `
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
* for build snapshots) — the shared engine in
|
|
905
|
-
* `devframe/utils/serve-static` accepts either shape.
|
|
1032
|
+
* Server→client notification that this connection's auth token has been
|
|
1033
|
+
* revoked. The client drops to untrusted on receipt. Broadcast by
|
|
1034
|
+
* `revokeActiveConnectionsForToken`.
|
|
1035
|
+
*
|
|
1036
|
+
* @internal
|
|
906
1037
|
*/
|
|
907
|
-
|
|
1038
|
+
'devframe:auth:revoked': () => Promise<void>;
|
|
908
1039
|
/**
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
* via `connectDevframe()`'s relative `./__connection.json` fetch.
|
|
1040
|
+
* Streaming chunk pushed from server to subscribed clients. Wired by
|
|
1041
|
+
* `RpcStreamingHost`; do not register manually.
|
|
912
1042
|
*
|
|
913
|
-
*
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
*
|
|
1043
|
+
* @internal
|
|
1044
|
+
*/
|
|
1045
|
+
'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
1046
|
+
/**
|
|
1047
|
+
* Streaming terminator pushed from server to subscribed clients. Wired by
|
|
1048
|
+
* `RpcStreamingHost`; do not register manually.
|
|
918
1049
|
*
|
|
919
|
-
*
|
|
920
|
-
* `distDir` yet omits this hook triggers a `DF8106` diagnostic, since the
|
|
921
|
-
* SPA's `./__connection.json` fetch would otherwise fall through and break
|
|
922
|
-
* silently. A static-snapshot host that bakes the meta into its served files
|
|
923
|
-
* can implement it as a no-op to acknowledge this intentionally.
|
|
1050
|
+
* @internal
|
|
924
1051
|
*/
|
|
925
|
-
|
|
1052
|
+
'devframe:streaming:end': (channel: string, id: string, error?: {
|
|
1053
|
+
name: string;
|
|
1054
|
+
message: string;
|
|
1055
|
+
}) => Promise<void>;
|
|
926
1056
|
/**
|
|
927
|
-
*
|
|
928
|
-
* `
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
* reasonable value.
|
|
1057
|
+
* Server→client cancel for an in-flight upload. Wired by
|
|
1058
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1059
|
+
*
|
|
1060
|
+
* @internal
|
|
932
1061
|
*/
|
|
933
|
-
|
|
1062
|
+
'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
|
|
934
1063
|
/**
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
* collide between, say, the Vite host (`.vite/devframe`) and a
|
|
938
|
-
* standalone CLI host (`.<appName>/devframe`).
|
|
1064
|
+
* Full shared-state snapshot pushed from server to subscribed clients.
|
|
1065
|
+
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
939
1066
|
*
|
|
940
|
-
*
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* `${cwd}/node_modules/.<appName>/devframe/`, which version
|
|
947
|
-
* control ignores.
|
|
948
|
-
* - `global` — per-user state (auth tokens, machine-wide
|
|
949
|
-
* preferences). Typically under
|
|
950
|
-
* `${homedir()}/.<appName>/devframe/`.
|
|
1067
|
+
* @internal
|
|
1068
|
+
*/
|
|
1069
|
+
'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
|
|
1070
|
+
/**
|
|
1071
|
+
* Incremental shared-state patch pushed from server to subscribed clients.
|
|
1072
|
+
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
951
1073
|
*
|
|
952
|
-
*
|
|
953
|
-
* pass to a downstream `createStorage(...)` call that creates it
|
|
954
|
-
* lazily.
|
|
1074
|
+
* @internal
|
|
955
1075
|
*/
|
|
956
|
-
|
|
1076
|
+
'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
957
1077
|
}
|
|
958
1078
|
/**
|
|
959
|
-
*
|
|
960
|
-
* `workspace` is committable and team-shared, `project` is per-checkout
|
|
961
|
-
* private, `global` is per-user.
|
|
1079
|
+
* To be extended
|
|
962
1080
|
*/
|
|
963
|
-
|
|
964
|
-
//#endregion
|
|
965
|
-
//#region src/types/views.d.ts
|
|
966
|
-
interface DevframeViewHost {
|
|
1081
|
+
interface DevframeRpcServerFunctions {
|
|
967
1082
|
/**
|
|
1083
|
+
* Authenticate a connection with a previously-issued bearer token; resolves
|
|
1084
|
+
* whether the connection is now trusted. The interactive handler is provided
|
|
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
|
+
*
|
|
968
1092
|
* @internal
|
|
969
1093
|
*/
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1094
|
+
'anonymous:devframe:auth': (params: {
|
|
1095
|
+
authToken: string;
|
|
1096
|
+
ua: string;
|
|
1097
|
+
origin: string;
|
|
1098
|
+
}) => Promise<{
|
|
1099
|
+
isTrusted: boolean;
|
|
1100
|
+
}>;
|
|
974
1101
|
/**
|
|
975
|
-
*
|
|
976
|
-
* -
|
|
977
|
-
*
|
|
1102
|
+
* Exchange a one-time authentication code (shown by the dev server) for a fresh,
|
|
1103
|
+
* node-issued bearer token, returning the token on success or `null`. The
|
|
1104
|
+
* handler is provided by the host adapter on top of `exchangeTempAuthCode`.
|
|
978
1105
|
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
1106
|
+
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
1107
|
+
* reachable before the connection is trusted.
|
|
1108
|
+
*
|
|
1109
|
+
* @internal
|
|
981
1110
|
*/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
* `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
|
|
990
|
-
* guard keeps TypeScript from rejecting a generic index into the
|
|
991
|
-
* key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
|
|
992
|
-
* the registry, while preserving each name's argument/return typing.
|
|
993
|
-
*/
|
|
994
|
-
type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
|
|
995
|
-
/**
|
|
996
|
-
* Augmentable registry mapping a scope namespace to the shape of its
|
|
997
|
-
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
|
|
998
|
-
* gets a fully-typed `settings.global` / `settings.project`:
|
|
999
|
-
*
|
|
1000
|
-
* ```ts
|
|
1001
|
-
* declare module 'devframe' {
|
|
1002
|
-
* interface DevframeSettingsRegistry {
|
|
1003
|
-
* 'my-plugin': { theme: 'light' | 'dark', recentFiles: string[] }
|
|
1004
|
-
* }
|
|
1005
|
-
* }
|
|
1006
|
-
* ```
|
|
1007
|
-
*/
|
|
1008
|
-
interface DevframeSettingsRegistry {}
|
|
1009
|
-
/**
|
|
1010
|
-
* Resolve the settings shape for a namespace from
|
|
1011
|
-
* {@link DevframeSettingsRegistry}, falling back to an open record when
|
|
1012
|
-
* the namespace hasn't been augmented.
|
|
1013
|
-
*/
|
|
1014
|
-
type SettingsForNamespace<NS extends string> = NS extends keyof DevframeSettingsRegistry ? DevframeSettingsRegistry[NS] extends Record<string, any> ? DevframeSettingsRegistry[NS] : Record<string, any> : Record<string, any>;
|
|
1015
|
-
/**
|
|
1016
|
-
* A persisted key-value settings store for one scope (`global` or
|
|
1017
|
-
* `project`). Backed by a file on the node side and by the shared-state
|
|
1018
|
-
* sync protocol on the client, so a `set` on either side propagates to
|
|
1019
|
-
* every connected peer and survives restarts.
|
|
1020
|
-
*
|
|
1021
|
-
* Every method is async because the underlying shared state is resolved
|
|
1022
|
-
* lazily on first access.
|
|
1023
|
-
*/
|
|
1024
|
-
interface DevframeSettingsStore<T extends Record<string, any> = Record<string, any>> {
|
|
1025
|
-
/** Read a single setting. Resolves to `undefined` when unset. */
|
|
1026
|
-
get: <K extends keyof T>(key: K) => Promise<T[K] | undefined>;
|
|
1027
|
-
/** Write a single setting. */
|
|
1028
|
-
set: <K extends keyof T>(key: K, value: T[K]) => Promise<void>;
|
|
1029
|
-
/** Remove a single setting. */
|
|
1030
|
-
delete: <K extends keyof T>(key: K) => Promise<void>;
|
|
1031
|
-
/** Read the whole settings object (immutable snapshot). */
|
|
1032
|
-
all: () => Promise<Readonly<T>>;
|
|
1111
|
+
'anonymous:devframe:auth:exchange': (params: {
|
|
1112
|
+
code: string;
|
|
1113
|
+
ua: string;
|
|
1114
|
+
origin: string;
|
|
1115
|
+
}) => Promise<{
|
|
1116
|
+
authToken: string | null;
|
|
1117
|
+
}>;
|
|
1033
1118
|
/**
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1119
|
+
* Self-revoke: the caller asks the server to revoke its own bearer token
|
|
1120
|
+
* (if any) and drop to untrusted. Requires an already-trusted caller, so
|
|
1121
|
+
* unlike the two handshake methods above it does **not** carry the
|
|
1122
|
+
* `anonymous:` prefix. Registered by `recipes/interactive-auth`.
|
|
1123
|
+
*
|
|
1124
|
+
* @internal
|
|
1036
1125
|
*/
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1126
|
+
'devframe:auth:revoke': () => Promise<void>;
|
|
1127
|
+
/**
|
|
1128
|
+
* Subscribe a client to a shared-state key. Wired by
|
|
1129
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1130
|
+
*
|
|
1131
|
+
* @internal
|
|
1132
|
+
*/
|
|
1133
|
+
'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
|
|
1134
|
+
/**
|
|
1135
|
+
* Read the current value for a shared-state key. Wired by
|
|
1136
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1137
|
+
*
|
|
1138
|
+
* @internal
|
|
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.
|
|
1144
|
+
*
|
|
1145
|
+
* @internal
|
|
1146
|
+
*/
|
|
1147
|
+
'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
|
|
1148
|
+
/**
|
|
1149
|
+
* Apply a patch to a shared-state value (from the client). Wired by
|
|
1150
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1151
|
+
*
|
|
1152
|
+
* @internal
|
|
1153
|
+
*/
|
|
1154
|
+
'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
1155
|
+
/**
|
|
1156
|
+
* Client→server streaming subscription with optional replay cursor.
|
|
1157
|
+
* Wired by `RpcStreamingHost`; do not register manually.
|
|
1158
|
+
*
|
|
1159
|
+
* @internal
|
|
1160
|
+
*/
|
|
1161
|
+
'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
|
|
1162
|
+
afterSeq?: number;
|
|
1163
|
+
}) => Promise<void>;
|
|
1164
|
+
/**
|
|
1165
|
+
* Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
|
|
1166
|
+
* do not register manually.
|
|
1167
|
+
*
|
|
1168
|
+
* @internal
|
|
1169
|
+
*/
|
|
1170
|
+
'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
|
|
1171
|
+
/**
|
|
1172
|
+
* Client→server streaming cancellation request. Wired by
|
|
1173
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1174
|
+
*
|
|
1175
|
+
* @internal
|
|
1176
|
+
*/
|
|
1177
|
+
'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
|
|
1178
|
+
/**
|
|
1179
|
+
* Client→server upload chunk. Wired by `RpcStreamingHost`; do not
|
|
1180
|
+
* register manually.
|
|
1181
|
+
*
|
|
1182
|
+
* @internal
|
|
1183
|
+
*/
|
|
1184
|
+
'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Client→server upload terminator. Wired by `RpcStreamingHost`; do not
|
|
1187
|
+
* register manually.
|
|
1188
|
+
*
|
|
1189
|
+
* @internal
|
|
1190
|
+
*/
|
|
1191
|
+
'devframe:streaming:upload-end': (channel: string, id: string, error?: {
|
|
1192
|
+
name: string;
|
|
1193
|
+
message: string;
|
|
1194
|
+
}) => Promise<void>;
|
|
1195
|
+
}
|
|
1068
1196
|
/**
|
|
1069
|
-
*
|
|
1197
|
+
* To be extended
|
|
1070
1198
|
*/
|
|
1071
|
-
interface
|
|
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.
|
|
1206
|
+
*/
|
|
1207
|
+
'devframe:services': DevframeServicesState;
|
|
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[]> {
|
|
1072
1216
|
method: METHOD;
|
|
1073
1217
|
args: Args;
|
|
1074
1218
|
optional?: boolean;
|
|
1075
1219
|
event?: boolean;
|
|
1220
|
+
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
1076
1221
|
}
|
|
1077
|
-
|
|
1078
|
-
* Node-side streaming host scoped to a namespace. Channel names are
|
|
1079
|
-
* auto-prefixed with `<namespace>:`.
|
|
1080
|
-
*/
|
|
1081
|
-
interface DevframeScopedStreamingHost {
|
|
1222
|
+
type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
|
|
1082
1223
|
/**
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1224
|
+
* Invoke a locally registered server RPC function directly.
|
|
1225
|
+
*
|
|
1226
|
+
* This bypasses transport and is useful for server-side cross-function calls.
|
|
1086
1227
|
*/
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* The scoped node RPC surface exposed on `ctx.scope('my-plugin').rpc`.
|
|
1091
|
-
* IDs and keys you pass are auto-namespaced with `my-plugin:`.
|
|
1092
|
-
*/
|
|
1093
|
-
interface DevframeScopedNodeRpc<NS extends string = string> {
|
|
1094
|
-
/** The namespace this surface is scoped to. */
|
|
1095
|
-
readonly namespace: NS;
|
|
1228
|
+
invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
|
|
1096
1229
|
/**
|
|
1097
|
-
*
|
|
1098
|
-
* (no `:`); it is stored as `<namespace>:<name>`. Throws `DF0034` if an
|
|
1099
|
-
* already-namespaced name is passed.
|
|
1230
|
+
* Broadcast a message to all connected clients
|
|
1100
1231
|
*/
|
|
1101
|
-
|
|
1232
|
+
broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
|
|
1102
1233
|
/**
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1234
|
+
* Get the current RPC client
|
|
1235
|
+
*
|
|
1236
|
+
* Available in RPC functions to get the current RPC client
|
|
1105
1237
|
*/
|
|
1106
|
-
|
|
1238
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
1107
1239
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
* resolved within this namespace; pass a fully-qualified name
|
|
1110
|
-
* (containing `:`) to call another scope's function.
|
|
1240
|
+
* The shared state host
|
|
1111
1241
|
*/
|
|
1112
|
-
|
|
1113
|
-
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): Promise<Awaited<ReturnType<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>>>;
|
|
1114
|
-
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>>>;
|
|
1115
|
-
(method: string, ...args: any[]): Promise<any>;
|
|
1116
|
-
};
|
|
1242
|
+
sharedState: RpcSharedStateHost;
|
|
1117
1243
|
/**
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1244
|
+
* The streaming channel host. Provides per-channel `start()` /
|
|
1245
|
+
* `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
|
|
1246
|
+
*
|
|
1247
|
+
* @see RpcStreamingHost
|
|
1120
1248
|
*/
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1249
|
+
streaming: RpcStreamingHost;
|
|
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[];
|
|
1125
1258
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1259
|
+
* Subscribe to new shared-state keys becoming available. Fires when
|
|
1260
|
+
* `get(key, ...)` creates a fresh entry (not on subsequent gets).
|
|
1261
|
+
* Useful for protocol adapters (e.g. MCP) that surface shared state
|
|
1262
|
+
* as dynamic resources.
|
|
1128
1263
|
*/
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1264
|
+
onKeyAdded: (fn: (key: string) => void) => () => void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Unregister a shared state and drop its broadcast listeners. Returns
|
|
1267
|
+
* `true` when a state was removed, `false` when the key was unknown.
|
|
1268
|
+
* Used by short-lived states (e.g. a disposed JSON-render view) to avoid
|
|
1269
|
+
* leaking listeners and lingering entries for the context lifetime.
|
|
1270
|
+
*/
|
|
1271
|
+
delete: (key: string) => boolean;
|
|
1137
1272
|
}
|
|
1138
1273
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
* `ctx.scope('my-plugin')`. Re-exposes the unscoped surfaces (`views`,
|
|
1141
|
-
* `diagnostics`, `agent`, …), replaces `rpc` with the auto-namespaced
|
|
1142
|
-
* {@link DevframeScopedNodeRpc}, and adds a top-level `settings` store.
|
|
1274
|
+
* Options for `RpcStreamingHost.create()`.
|
|
1143
1275
|
*/
|
|
1144
|
-
interface
|
|
1145
|
-
/** The namespace this context is scoped to. */
|
|
1146
|
-
readonly namespace: NS;
|
|
1147
|
-
/** The underlying unscoped context. */
|
|
1148
|
-
readonly base: DevframeNodeContext;
|
|
1149
|
-
readonly workspaceRoot: string;
|
|
1150
|
-
readonly cwd: string;
|
|
1151
|
-
readonly mode: 'dev' | 'build';
|
|
1152
|
-
host: DevframeHost;
|
|
1153
|
-
rpc: DevframeScopedNodeRpc<NS>;
|
|
1154
|
-
/** Persisted settings for this namespace (`global` + `project`). */
|
|
1155
|
-
settings: DevframeSettings<Settings>;
|
|
1156
|
-
views: DevframeViewHost;
|
|
1157
|
-
diagnostics: DevframeDiagnosticsHost;
|
|
1158
|
-
agent: DevframeAgentHost;
|
|
1276
|
+
interface RpcStreamingChannelOptions {
|
|
1159
1277
|
/**
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1278
|
+
* Size of the per-stream ring buffer kept on the server for
|
|
1279
|
+
* replay-on-resubscribe. `0` (default) disables replay; on reconnect
|
|
1280
|
+
* the consumer only sees chunks that arrive after subscribing.
|
|
1281
|
+
*
|
|
1282
|
+
* The buffer is per stream id, not per channel — each `channel.start()`
|
|
1283
|
+
* gets its own.
|
|
1162
1284
|
*/
|
|
1163
|
-
|
|
1285
|
+
replayWindow?: number;
|
|
1286
|
+
/**
|
|
1287
|
+
* Milliseconds a closed stream is retained on the server after its
|
|
1288
|
+
* last subscriber leaves (or if no subscriber ever arrived). During
|
|
1289
|
+
* this window, late subscribers can still join and replay the buffer
|
|
1290
|
+
* + receive the `end` frame.
|
|
1291
|
+
*
|
|
1292
|
+
* Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
|
|
1293
|
+
* (immediate free). Set to `0` to opt out, or higher for longer
|
|
1294
|
+
* post-mortem replay.
|
|
1295
|
+
*/
|
|
1296
|
+
closedStreamRetention?: number;
|
|
1164
1297
|
}
|
|
1165
|
-
//#endregion
|
|
1166
|
-
//#region src/types/services.d.ts
|
|
1167
1298
|
/**
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1172
|
-
* A provider ships a *types-only* augmentation of {@link DevframeServicesRegistry}
|
|
1173
|
-
* (so consumers get full typing from `import type`), then provides the
|
|
1174
|
-
* implementation at setup time:
|
|
1175
|
-
*
|
|
1176
|
-
* ```ts
|
|
1177
|
-
* // provider (e.g. the data-inspector plugin)
|
|
1178
|
-
* declare module 'devframe' {
|
|
1179
|
-
* interface DevframeServicesRegistry {
|
|
1180
|
-
* 'devframes:plugin:data-inspector:sources': DataSourcesService
|
|
1181
|
-
* }
|
|
1182
|
-
* }
|
|
1183
|
-
* ctx.services.provide('devframes:plugin:data-inspector:sources', host)
|
|
1184
|
-
*
|
|
1185
|
-
* // consumer (another plugin) — no runtime dependency on the provider
|
|
1186
|
-
* ctx.services.whenAvailable('devframes:plugin:data-inspector:sources', (sources) => {
|
|
1187
|
-
* sources.register({ id: 'my-plugin:state', title: 'My state', data: () => state })
|
|
1188
|
-
* })
|
|
1189
|
-
* ```
|
|
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.
|
|
1190
1302
|
*
|
|
1191
|
-
*
|
|
1192
|
-
* the providing plugin's id.
|
|
1193
|
-
*/
|
|
1194
|
-
/**
|
|
1195
|
-
* Augmentation point mapping service ids to their implementation types.
|
|
1196
|
-
* Providers extend this via `declare module 'devframe'`.
|
|
1303
|
+
* @see {@link https://devfra.me/guide/streaming Streaming guide}
|
|
1197
1304
|
*/
|
|
1198
|
-
interface
|
|
1305
|
+
interface RpcStreamingChannel<T = unknown> {
|
|
1306
|
+
/** Channel name as registered with `ctx.rpc.streaming.create()`. */
|
|
1307
|
+
readonly name: string;
|
|
1308
|
+
/**
|
|
1309
|
+
* Start a new stream. Returns a server-side sink with both an imperative
|
|
1310
|
+
* (`write` / `close` / `error`) surface and a `WritableStream<T>` for
|
|
1311
|
+
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
1312
|
+
* disconnects or cancels.
|
|
1313
|
+
*/
|
|
1314
|
+
start: (opts?: {
|
|
1315
|
+
id?: string;
|
|
1316
|
+
}) => StreamSink<T>;
|
|
1317
|
+
/**
|
|
1318
|
+
* Convenience: start a stream and pipe a `ReadableStream<T>` into it.
|
|
1319
|
+
* The pipe uses `sink.signal` so cancellation propagates upstream.
|
|
1320
|
+
*
|
|
1321
|
+
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
1322
|
+
* before passing it here.
|
|
1323
|
+
*/
|
|
1324
|
+
pipeFrom: (readable: ReadableStream<T>, opts?: {
|
|
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[];
|
|
1331
|
+
/**
|
|
1332
|
+
* Open an inbound stream — the server side of a client-to-server
|
|
1333
|
+
* upload. Allocates an id, returns a `StreamReader<T>` that fills as
|
|
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
|
+
* ```
|
|
1347
|
+
*
|
|
1348
|
+
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
1349
|
+
* the uploading client, which aborts its sink.
|
|
1350
|
+
*/
|
|
1351
|
+
openInbound: (opts?: {
|
|
1352
|
+
id?: string;
|
|
1353
|
+
}) => StreamReader<T>;
|
|
1354
|
+
}
|
|
1199
1355
|
/**
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1356
|
+
* Server-side streaming host. Lives on `ctx.rpc.streaming` alongside
|
|
1357
|
+
* `ctx.rpc.sharedState`. Each named channel owns its own stream registry
|
|
1358
|
+
* and wire namespace.
|
|
1202
1359
|
*/
|
|
1203
|
-
|
|
1204
|
-
/** Resolved service type for an id: augmented type, or `unknown`. */
|
|
1205
|
-
type DevframeServiceOf<ID> = ID extends keyof DevframeServicesRegistry ? DevframeServicesRegistry[ID] : unknown;
|
|
1206
|
-
interface DevframeServicesHost {
|
|
1360
|
+
interface RpcStreamingHost {
|
|
1207
1361
|
/**
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1362
|
+
* Register a streaming channel. Names follow the `<plugin-id>:<channel>`
|
|
1363
|
+
* convention (e.g. `'my-devtool:chat-stream'`). Throws `DF0032` if the
|
|
1364
|
+
* name is already taken.
|
|
1210
1365
|
*/
|
|
1211
|
-
|
|
1212
|
-
/** The service currently provided under `id`, or `undefined`. */
|
|
1213
|
-
get: <ID extends DevframeServiceId>(id: ID) => DevframeServiceOf<ID> | undefined;
|
|
1214
|
-
has: (id: DevframeServiceId) => boolean;
|
|
1366
|
+
create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
|
|
1215
1367
|
/**
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
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
|
|
1220
1373
|
*/
|
|
1221
|
-
|
|
1222
|
-
/** Ids of every currently-provided service. */
|
|
1223
|
-
keys: () => string[];
|
|
1374
|
+
_onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
|
|
1224
1375
|
}
|
|
1225
1376
|
//#endregion
|
|
1226
1377
|
//#region src/types/context.d.ts
|
|
@@ -1792,6 +1943,29 @@ interface DevframeDefinition {
|
|
|
1792
1943
|
version: string;
|
|
1793
1944
|
/** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
|
|
1794
1945
|
packageName: string;
|
|
1946
|
+
/**
|
|
1947
|
+
* `import.meta.url` of the module that defines this devframe. **Always
|
|
1948
|
+
* provide it** (`importMetaUrl: import.meta.url`): it is the resolution base
|
|
1949
|
+
* for the tool's own dependency graph, which lets the host resolve
|
|
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.
|
|
1963
|
+
*
|
|
1964
|
+
* Optional for backward compatibility; omitting it falls back to
|
|
1965
|
+
* runtime-directory resolution and disables the zero-network installed-copy
|
|
1966
|
+
* fast paths above.
|
|
1967
|
+
*/
|
|
1968
|
+
importMetaUrl?: string;
|
|
1795
1969
|
/** Project homepage or documentation URL. */
|
|
1796
1970
|
homepage: string;
|
|
1797
1971
|
/** One-line summary of what the tool does. */
|
|
@@ -1841,9 +2015,22 @@ interface DevframeDefinition {
|
|
|
1841
2015
|
dev?: boolean;
|
|
1842
2016
|
build?: boolean;
|
|
1843
2017
|
};
|
|
2018
|
+
/**
|
|
2019
|
+
* Wire services this devframe consumes (see `DevframeServiceDefinition`).
|
|
2020
|
+
* Each entry is either a declarative descriptor
|
|
2021
|
+
* (`{ package, version?, required?, options? }` — the host imports the
|
|
2022
|
+
* package's default-export factory, resolving it against **this plugin's
|
|
2023
|
+
* own dependencies**) or a ready `DevframeServiceDefinition` (the factory
|
|
2024
|
+
* was already called). The adapter queues these before `setup(ctx)` runs
|
|
2025
|
+
* and constructs each service once at the `ctx.services.ready()` barrier,
|
|
2026
|
+
* merging option sets across every declarer. Missing services are skipped
|
|
2027
|
+
* unless marked `required` — clients feature-detect via
|
|
2028
|
+
* `client.services.has(pkg)` and degrade.
|
|
2029
|
+
*/
|
|
2030
|
+
services?: DevframeServiceInput[];
|
|
1844
2031
|
/** Server-side setup — the primary entrypoint. Runs in every runtime. */
|
|
1845
2032
|
setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
|
|
1846
2033
|
cli?: DevframeCliOptions;
|
|
1847
2034
|
}
|
|
1848
2035
|
//#endregion
|
|
1849
|
-
export {
|
|
2036
|
+
export { DevframeStorageScope as $, DevframeServiceId as A, DevframeAgentHost as At, DevframeScopedNodeRpc as B, RpcStreamingChannelOptions as C, AgentResource as Ct, DevframeRpcSharedStates as D, AgentToolInput as Dt, DevframeRpcServerFunctions as E, AgentTool as Et, DevframeServicesHost as F, CliFlagsSchema as Ft, ScopedBroadcastOptions as G, DevframeSettings as H, DevframeServicesRegistry as I, InferCliFlags as It, ScopedServerFunctions as J, ScopedClientFunctions as K, DevframeServicesScopeRegistry as L, defineCliFlags as Lt, DevframeServiceMeta as M, EventEmitter as Mt, DevframeServiceOf as N, EventUnsubscribe as Nt, DevframeServiceDefinition as O, AgentToolProvider as Ot, DevframeServiceScopeOf as P, EventsMap as Pt, DevframeHost as Q, DevframeServicesState as R, parseCliFlags as Rt, RpcStreamingChannel as S, AgentManifest as St, DevframeRpcClientFunctions as T, AgentResourceInput as Tt, DevframeSettingsRegistry as U, DevframeScopedStreamingHost as V, DevframeSettingsStore as W, SettingsForNamespace as X, ScopedSharedStates as Y, DevframeViewHost as Z, DevframeNodeRpcSession as _, SharedStateEvents as _t, DevframeDuplicationStrategy as a, StreamErrorPayload as at, RpcSharedStateGetOptions as b, createSharedState as bt, DevframeWsOptions as c, StreamSinkEvents as ct, ConnectionMeta as d, Immutable as dt, DevframeDiagnosticsHost as et, ConnectionMetaSse as f, ImmutableArray as ft, DevframeNodeContext as g, SharedState as gt, DevframeConnectionConfigsRegistry as h, ImmutableSet as ht, DevframeDockDefaults as i, CreateStreamSinkOptions as it, DevframeServiceInput as j, DevframeAgentHostEvents as jt, DevframeServiceDescriptor as k, AgentToolProviderHandle as kt, McpRouteOptions as l, createStreamReader as lt, DevframeCapabilities as m, ImmutableObject as mt, DevframeDefinition as n, BufferedChunk as nt, DevframeSetupInfo as o, StreamReader as ot, ConnectionMetaWebsocket as p, ImmutableMap as pt, ScopedRpcFn as q, DevframeDeploymentKind as r, CreateStreamReaderOptions as rt, DevframeSseOptions as s, StreamSink as st, DevframeCliOptions as t, DevframeDiagnosticsLogger as tt, DevframeAuthHandler as u, createStreamSink as ut, RpcBroadcastOptions as v, SharedStateOptions as vt, RpcStreamingHost as w, AgentResourceContent as wt, RpcSharedStateHost as x, AgentHandle as xt, RpcFunctionsHost as y, SharedStatePatch as yt, DevframeScopedNodeContext as z };
|