From fb8cf6a6f1436361f14aa1caf51c50089791d5c8 Mon Sep 17 00:00:00 2001 From: dvcolomban Date: Tue, 29 Sep 2026 00:01:45 +0900 Subject: [PATCH] feat(rpc): expose native shared state for custom channels --- alias.ts | 1 + docs/content/8.references/5.browser-api.md | 13 ++ knip.jsonc | 2 +- packages/devframe/package.json | 1 + .../devframe/src/client/rpc-shared-state.ts | 15 +- .../devframe/src/node/rpc-shared-state.ts | 23 ++- .../devframe/src/rpc/shared-state.test.ts | 144 ++++++++++++++ packages/devframe/src/rpc/shared-state.ts | 2 + packages/devframe/tsdown.config.ts | 11 +- .../tsnapi/devframe/client.snapshot.d.ts | 187 +++--------------- .../tsnapi/devframe/rpc.snapshot.js | 32 +-- .../devframe/rpc/shared-state.snapshot.d.ts | 10 + .../devframe/rpc/shared-state.snapshot.js | 10 + tsconfig.base.json | 3 + 14 files changed, 251 insertions(+), 203 deletions(-) create mode 100644 packages/devframe/src/rpc/shared-state.test.ts create mode 100644 packages/devframe/src/rpc/shared-state.ts create mode 100644 tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.js diff --git a/alias.ts b/alias.ts index 6940f8e2f..d31aea0d2 100644 --- a/alias.ts +++ b/alias.ts @@ -16,6 +16,7 @@ export const alias = { 'devframe/rpc/transports/ws-client': r('devframe/src/rpc/transports/ws-client.ts'), 'devframe/rpc/client': r('devframe/src/rpc/client.ts'), 'devframe/rpc/dump': r('devframe/src/rpc/dump/index.ts'), + 'devframe/rpc/shared-state': r('devframe/src/rpc/shared-state.ts'), 'devframe/rpc/server': r('devframe/src/rpc/server.ts'), 'devframe/rpc': r('devframe/src/rpc'), 'devframe/types': r('devframe/src/types/index.ts'), diff --git a/docs/content/8.references/5.browser-api.md b/docs/content/8.references/5.browser-api.md index adee7668a..894fdd09c 100644 --- a/docs/content/8.references/5.browser-api.md +++ b/docs/content/8.references/5.browser-api.md @@ -72,3 +72,16 @@ The `error.code` values of `InPageChannelError`: [Errors and fallbacks](/guide/i | `not-cloneable` | The port refused to clone a payload (`DataCloneError`) | Strip functions/DOM nodes/reactivity proxies, or declare `jsonSerializable: true` for the precise error above | | `invalid-args` | Incoming arguments failed their Standard-Schema validation | The message lists the schema issues | | `state-uninitialized` | The page script read a shared state before providing its `initialValue` | Initialize on first access | + +## Shared state over custom RPC channels + +`devframe/rpc/shared-state` exports the same `RpcSharedStateHost` implementations used by the RPC client and node context. Custom channel bindings can compose them with `RpcFunctionsCollectorBase`, `createRpcClient()` and `createRpcServer()`. + +| Factory | Required connection members | Result | +|---------|-----------------------------|--------| +| `createRpcSharedStateClientHost(rpc)` | `call`, `callEvent`, function registration through `client`, trust events, `isTrusted`, and `connectionMeta.backend` | Native `get`, `keys`, `onKeyAdded` and `delete`. | +| `createRpcSharedStateServerHost(rpc)` | Function registration and filtered broadcasts | Native state publication, including `get(key, { sharedState })`. | + +The channel binding owns peer authentication, serialization and disconnection. Each serving channel supplies birpc metadata with a `subscribedStates: Set` and retains the default RPC `this` binding. Subscription handlers use that calling connection's metadata; filtered broadcasts reach its subscribed keys. Close the birpc connection when its transport disconnects and delete mirrored keys when disposing their owner. + +A snapshot requested without an initial value rejects if its RPC call fails. Supplying an initial value retains the RPC client's existing immediate-state behavior. diff --git a/knip.jsonc b/knip.jsonc index db19dee20..7bfb0b31b 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -137,7 +137,7 @@ "src/node/index.ts", "src/node/{auth,hub-internals}/index.ts", "src/recipes/interactive-auth.ts", - "src/rpc/{index,client,server}.ts", + "src/rpc/{index,client,server,shared-state}.ts", "src/rpc/dump/index.ts", "src/rpc/transports/{sse-client,sse-server,ws-bun,ws-deno,ws-client,ws-server}.ts", "src/types/index.ts", diff --git a/packages/devframe/package.json b/packages/devframe/package.json index a4d0e9650..e1e942df3 100644 --- a/packages/devframe/package.json +++ b/packages/devframe/package.json @@ -38,6 +38,7 @@ "./rpc/client": "./dist/rpc/client.mjs", "./rpc/dump": "./dist/rpc/dump.mjs", "./rpc/server": "./dist/rpc/server.mjs", + "./rpc/shared-state": "./dist/rpc/shared-state.mjs", "./rpc/transports/sse-client": "./dist/rpc/transports/sse-client.mjs", "./rpc/transports/sse-server": "./dist/rpc/transports/sse-server.mjs", "./rpc/transports/ws-bun": "./dist/rpc/transports/ws-bun.mjs", diff --git a/packages/devframe/src/client/rpc-shared-state.ts b/packages/devframe/src/client/rpc-shared-state.ts index ae7c6b289..46cdb7762 100644 --- a/packages/devframe/src/client/rpc-shared-state.ts +++ b/packages/devframe/src/client/rpc-shared-state.ts @@ -1,10 +1,17 @@ -import type { RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types' +import type { RpcFunctionsCollector } from 'devframe/rpc' +import type { ConnectionMeta, DevframeRpcClientFunctions, RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types' import type { SharedState, SharedStatePatch } from 'devframe/utils/shared-state' import type { DevframeRpcClient } from './rpc' import { createSharedState } from 'devframe/utils/shared-state' import { DEVFRAME_EVENTS } from '../events' -export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcSharedStateHost { +/** Native shared-state synchronization over an authenticated RPC connection. */ +export function createRpcSharedStateClientHost( + rpc: Pick & { + client: Pick, 'register'> + connectionMeta: Pick + }, +): RpcSharedStateHost { const sharedState = new Map>() const stateDisposers = new Map void>() const initialValues = new Map() @@ -121,7 +128,7 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare } } - return new Promise>((resolve) => { + return new Promise>((resolve, reject) => { if (!rpc.isTrusted) { resolve(state) let initialized = false @@ -133,7 +140,7 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare }) } else { - initSharedState().then(resolve) + initSharedState().then(resolve, reject) } }) }, diff --git a/packages/devframe/src/node/rpc-shared-state.ts b/packages/devframe/src/node/rpc-shared-state.ts index 306b217c6..702e14ac5 100644 --- a/packages/devframe/src/node/rpc-shared-state.ts +++ b/packages/devframe/src/node/rpc-shared-state.ts @@ -1,4 +1,5 @@ -import type { RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types' +import type { RpcFunctionsCollector } from 'devframe/rpc' +import type { DevframeNodeRpcSessionMeta, DevframeRpcServerFunctions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/types' import type { SharedState, SharedStatePatch } from 'devframe/utils/shared-state' import { createSharedState } from 'devframe/utils/shared-state' import { createDebug } from 'obug' @@ -8,8 +9,13 @@ import { diagnostics } from './diagnostics' const debug = createDebug('devframe:rpc:state:changed') const debugSubscribe = createDebug('devframe:rpc:state:subscribe') -export function createRpcSharedStateServerHost( - rpc: RpcFunctionsHost, +/** + * Publish native shared state over registered RPC functions and broadcasts. + * Each channel supplies session metadata with a `subscribedStates` set and + * retains birpc's default RPC `this` binding for subscription handlers. + */ +export function createRpcSharedStateServerHost( + rpc: Pick, 'register'> & Pick, ): RpcSharedStateHost { const sharedState = new Map>() const stateDisposers = new Map void>() @@ -93,12 +99,13 @@ export function createRpcSharedStateServerHost( rpc.register({ name: 'devframe:rpc:server-state:subscribe', type: 'event', - handler(key: string) { - const session = rpc.getCurrentRpcSession() - if (!session) + handler(this: { $meta?: DevframeNodeRpcSessionMeta } | undefined, key: string) { + /** birpc binds this handler to the calling connection across transports. */ + const meta = this?.$meta + if (!meta) return - debugSubscribe('subscribe', { key, session: session.meta.id }) - session.meta.subscribedStates.add(key) + debugSubscribe('subscribe', { key, session: meta.id }) + meta.subscribedStates.add(key) }, }) diff --git a/packages/devframe/src/rpc/shared-state.test.ts b/packages/devframe/src/rpc/shared-state.test.ts new file mode 100644 index 000000000..7836804c9 --- /dev/null +++ b/packages/devframe/src/rpc/shared-state.test.ts @@ -0,0 +1,144 @@ +import type { ChannelOptions } from 'birpc' +import type { RpcClientEvents } from 'devframe/client' +import type { DevframeRpcClientFunctions, DevframeRpcServerFunctions, RpcFunctionsHost } from 'devframe/types' +import type { MessagePort } from 'node:worker_threads' +import { MessageChannel } from 'node:worker_threads' +import { createHostContext } from 'devframe/node' +import { RpcFunctionsCollectorBase } from 'devframe/rpc' +import { createRpcClient } from 'devframe/rpc/client' +import { createRpcServer } from 'devframe/rpc/server' +import { createRpcSharedStateClientHost, createRpcSharedStateServerHost } from 'devframe/rpc/shared-state' +import { createEventEmitter } from 'devframe/utils/events' +import { structuredCloneDeserialize, structuredCloneSerialize } from 'devframe/utils/structured-clone' +import { expect, it } from 'vitest' +import { createContextRpcServer } from '../node/rpc-core' + +/** JSON records also travel through transports that cannot clone native values. */ +function channelFor(port: MessagePort): ChannelOptions { + return { + post: message => port.postMessage(message), + on: (handler) => { + port.on('message', handler) + }, + off: (handler) => { + port.off('message', handler) + }, + serialize: value => JSON.stringify(structuredCloneSerialize(value)), + deserialize: value => structuredCloneDeserialize(JSON.parse(value)), + } +} + +async function createStateServer(mode: string) { + if (mode === 'node context') { + const context = await createHostContext({ + cwd: process.cwd(), + mode: 'dev', + host: { + mountStatic() {}, + resolveOrigin: () => 'http://localhost', + getStorageDir: () => process.cwd(), + }, + }) + const { rpcGroup } = createContextRpcServer({ context, auth: false }) + return { group: rpcGroup, sharedState: context.rpc.sharedState } + } + + const collector = new RpcFunctionsCollectorBase(undefined) + const group = createRpcServer(collector.functions) + const broadcast: RpcFunctionsHost['broadcast'] = async (options) => { + await Promise.all(group.clients + .filter(client => options.filter?.(client) !== false) + .map(client => client.$callRaw({ ...options, optional: true, event: true }))) + } + const sharedState = createRpcSharedStateServerHost({ register: collector.register.bind(collector), broadcast }) + return { group, sharedState } +} + +it.each(['custom channels', 'node context'])('shares state across %s with per-connection subscriptions', async (mode) => { + expect.assertions(13) + const { group, sharedState } = await createStateServer(mode) + const counter = await sharedState.get('counter', { initialValue: { count: 1 } }) + const channels = [new MessageChannel(), new MessageChannel()] + const peers = channels.map((channel, index) => { + const meta = { id: index, subscribedStates: new Set() } + const serverChannel = { ...channelFor(channel.port1), meta } + group.updateChannels(current => current.push(serverChannel)) + const client = new RpcFunctionsCollectorBase(undefined) + const rpc = createRpcClient(client.functions, { + channel: channelFor(channel.port2), + }) + const state = createRpcSharedStateClientHost({ + call: rpc.$call, + callEvent: rpc.$callEvent, + client, + isTrusted: true, + events: createEventEmitter(), + connectionMeta: { backend: 'none' }, + }) + return { meta, rpc, state, serverChannel } + }) + const [first, second] = peers + try { + const firstCounter = await first.state.get<{ count: number }>('counter') + expect(firstCounter.value()).toEqual({ count: 1 }) + expect(first.meta.subscribedStates.has('counter')).toBe(true) + expect(second.meta.subscribedStates.has('counter')).toBe(false) + + const secondCounter = await second.state.get<{ count: number }>('counter') + expect(second.meta.subscribedStates.has('counter')).toBe(true) + firstCounter.mutate((draft) => { + draft.count = 2 + }) + await expect.poll(() => counter.value().count).toBe(2) + await expect.poll(() => secondCounter.value().count).toBe(2) + + group.clients.find(client => client.$meta === first.meta)?.$close() + group.updateChannels(current => current.splice(current.indexOf(first.serverChannel), 1)) + first.rpc.$close() + await expect(first.rpc.$call('devframe:rpc:server-state:get', 'counter')).rejects.toThrow() + secondCounter.mutate((draft) => { + draft.count = 3 + }) + await expect.poll(() => counter.value().count).toBe(3) + expect(firstCounter.value().count).toBe(2) + expect(sharedState.keys()).toEqual(['counter']) + expect(first.state.delete('counter')).toBe(true) + expect(sharedState.delete('counter')).toBe(true) + expect(sharedState.keys()).toEqual([]) + } + finally { + for (const peer of peers) peer.rpc.$close() + for (const client of group.clients) client.$close() + group.updateChannels(current => current.splice(0)) + for (const channel of channels) { + channel.port1.close() + channel.port2.close() + } + } +}) + +it('rejects an initial snapshot when its RPC connection closes', async () => { + expect.assertions(1) + const client = new RpcFunctionsCollectorBase(undefined) + const channel = new MessageChannel() + const rpc = createRpcClient(client.functions, { + channel: channelFor(channel.port1), + }) + const state = createRpcSharedStateClientHost({ + call: rpc.$call, + callEvent: rpc.$callEvent, + client, + isTrusted: true, + events: createEventEmitter(), + connectionMeta: { backend: 'none' }, + }) + try { + const pending = state.get('counter') + rpc.$close() + await expect(pending).rejects.toThrow('closed') + } + finally { + channel.port1.close() + channel.port2.close() + } +}, 1000) diff --git a/packages/devframe/src/rpc/shared-state.ts b/packages/devframe/src/rpc/shared-state.ts new file mode 100644 index 000000000..f1b388be4 --- /dev/null +++ b/packages/devframe/src/rpc/shared-state.ts @@ -0,0 +1,2 @@ +export { createRpcSharedStateClientHost } from '../client/rpc-shared-state' +export { createRpcSharedStateServerHost } from '../node/rpc-shared-state' diff --git a/packages/devframe/tsdown.config.ts b/packages/devframe/tsdown.config.ts index 2cc3b188b..7fb7158d9 100644 --- a/packages/devframe/tsdown.config.ts +++ b/packages/devframe/tsdown.config.ts @@ -54,6 +54,10 @@ const nodeDeps = { const clientEntries = { 'client/index': 'src/client/index.ts', 'in-page-channel/index': 'src/in-page-channel/index.ts', + 'rpc/index': 'src/rpc/index.ts', + 'rpc/client': 'src/rpc/client.ts', + 'rpc/server': 'src/rpc/server.ts', + 'rpc/shared-state': 'src/rpc/shared-state.ts', 'utils/agent-tool-name': 'src/utils/agent-tool-name.ts', 'utils/colors': 'src/utils/colors.ts', 'utils/crypto-token': 'src/utils/crypto-token.ts', @@ -75,10 +79,7 @@ const serverEntries = { 'index': 'src/index.ts', 'constants': 'src/constants.ts', 'types/index': 'src/types/index.ts', - 'rpc/index': 'src/rpc/index.ts', - 'rpc/client': 'src/rpc/client.ts', 'rpc/dump': 'src/rpc/dump/index.ts', - 'rpc/server': 'src/rpc/server.ts', 'rpc/transports/sse-client': 'src/rpc/transports/sse-client.ts', 'rpc/transports/sse-server': 'src/rpc/transports/sse-server.ts', 'rpc/transports/ws-bun': 'src/rpc/transports/ws-bun.ts', @@ -140,6 +141,10 @@ export default defineConfig([ await checkClientDist({ entries: [ resolve(distDir, 'client/index.mjs'), + resolve(distDir, 'rpc/index.mjs'), + resolve(distDir, 'rpc/client.mjs'), + resolve(distDir, 'rpc/server.mjs'), + resolve(distDir, 'rpc/shared-state.mjs'), resolve(distDir, 'in-page-channel/index.mjs'), resolve(distDir, 'utils/agent-tool-name.mjs'), resolve(distDir, 'utils/colors.mjs'), diff --git a/tests/__snapshots__/tsnapi/devframe/client.snapshot.d.ts b/tests/__snapshots__/tsnapi/devframe/client.snapshot.d.ts index 0eebf21b1..782938424 100644 --- a/tests/__snapshots__/tsnapi/devframe/client.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/devframe/client.snapshot.d.ts @@ -2,137 +2,9 @@ * Generated by tsnapi — public API snapshot of `devframe/client` */ // #region Interfaces -export interface DevframeConnection { - connectionMeta: ConnectionMeta; - metaBaseUrl: string; - authToken?: string; -} -export interface DevframeRpcClient { - events: EventEmitter; - readonly isTrusted: boolean | null; - readonly status: DevframeConnectionStatus; - readonly connectionError: Error | null; - readonly transport: 'websocket' | 'sse' | 'static'; - readonly connection: DevframeConnection; - readonly connectionMeta: ConnectionMeta; - ensureTrusted: (_?: number) => Promise; - requestTrust: () => Promise; - requestTrustWithToken: (_: string) => Promise; - requestTrustWithCode: (_: string) => Promise; - requestAuthCode: (_?: { - reissue?: boolean; - }) => Promise; - call: DevframeRpcClientCall; - callEvent: DevframeRpcClientCallEvent; - callOptional: DevframeRpcClientCallOptional; - client: DevframeClientRpcHost; - sharedState: RpcSharedStateHost; - services: DevframeServicesClient; - streaming: RpcStreamingClientHost; - cacheManager: RpcCacheManager; - scope: { - (_: NS): DevframeScopedClientContext>; - (_?: null | ''): DevframeRpcClient; - }; - close?: () => void; -} -export interface DevframeRpcClientMode { - readonly transport?: 'websocket' | 'sse' | 'static'; - readonly isTrusted: boolean; - readonly status: DevframeConnectionStatus; - readonly connectionError: Error | null; - ensureTrusted: DevframeRpcClient['ensureTrusted']; - requestTrust: DevframeRpcClient['requestTrust']; - requestTrustWithToken: DevframeRpcClient['requestTrustWithToken']; - requestTrustWithCode: (_: string) => Promise; - requestAuthCode: DevframeRpcClient['requestAuthCode']; - call: DevframeRpcClient['call']; - callEvent: DevframeRpcClient['callEvent']; - callOptional: DevframeRpcClient['callOptional']; - close?: () => void; -} -export interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions { - authToken?: string; - otpParam?: string | false; - simpleAuth?: boolean; - transport?: 'auto' | 'websocket' | 'sse'; - wsOptions?: Partial; - sseOptions?: Partial; - rpcOptions?: Partial>; - cacheOptions?: boolean | Partial; - webmcp?: boolean; - callTimeout?: number; -} -export interface DevframeRpcContext { - readonly rpc: DevframeRpcClient; -} -export interface DevframeScopedClientContext = Record> { - readonly namespace: NS; - readonly base: DevframeRpcClient; - rpc: DevframeScopedClientRpc; - settings: DevframeSettings; - scope: DevframeRpcClient['scope']; -} -export interface DevframeScopedClientRpc { - readonly namespace: NS; - register: (_: RpcFunctionDefinition) => void; - call: { - & string>(_: T, ..._: Parameters>): Promise>>>; - (_: T, ..._: Parameters>): Promise>>>; - (_: string, ..._: any[]): Promise; - }; - callEvent: { - & string>(_: T, ..._: Parameters>): void; - (_: T, ..._: Parameters>): void; - (_: string, ..._: any[]): void; - }; - callOptional: { - & string>(_: T, ..._: Parameters>): Promise>> | undefined>; - (_: T, ..._: Parameters>): Promise>> | undefined>; - (_: string, ..._: any[]): Promise; - }; - sharedState: { - & string>(_: T, _?: RpcSharedStateGetOptions[T]>): Promise[T]>>; - = Record>(_: string, _?: RpcSharedStateGetOptions): Promise>; - }; - streaming: DevframeScopedClientStreamingHost; -} -export interface DevframeScopedClientStreamingHost { - subscribe: (_: string, _: string, _?: StreamingSubscribeOptions) => StreamReader; - upload: (_: string, _: string) => StreamSink; -} -export interface DevframeServiceClientHandle extends DevframeServiceMeta { - readonly scope: NS; - readonly rpc: DevframeScopedClientRpc; -} -export interface DevframeServicesClient { - has: (_: string) => boolean; - get: (_: PKG) => DevframeServiceClientHandle> | undefined; - keys: () => string[]; - state: () => Promise>; -} export interface RegisterWebMcpToolsOptions { modelContext?: WebMcpModelContext; } -export interface RpcClientEvents { - 'rpc:is-trusted:updated': (_: boolean) => void; - 'connection:status': (_: DevframeConnectionStatus, _: DevframeConnectionStatus) => void; - 'connection:error': (_: Error) => void; - 'rpc:error': (_: Error, _: string) => void; -} -export interface RpcStreamingClientHost { - subscribe: (_: string, _: string, _?: StreamingSubscribeOptions) => StreamReader; - upload: (_: string, _: string) => StreamSink; -} -export interface SetupDevframeConnectionOptions { - connection?: DevframeConnection; - connectionMeta?: ConnectionMeta; - baseURL?: string | string[]; - authToken?: string; -} -export interface StreamingSubscribeOptions { - highWaterMark?: number; -} export interface WebMcpModelContext { registerTool: (_: WebMcpToolDescriptor, _?: { signal?: AbortSignal; @@ -178,50 +50,51 @@ export interface WsUrlLocation { } // #endregion -// #region Types -export type DevframeClientRpcHost = RpcFunctionsCollector; -export type DevframeConnectionErrorKind = 'connection' | 'auth' | 'timeout'; -export type DevframeConnectionStatus = 'connecting' | 'connected' | 'unauthorized' | 'disconnected' | 'error'; -export type DevframeRpcClientCall = BirpcReturn['$call']; -export type DevframeRpcClientCallEvent = BirpcReturn['$callEvent']; -export type DevframeRpcClientCallOptional = BirpcReturn['$callOptional']; -// #endregion - -// #region Classes -export declare class DevframeConnectionError extends Error { - name: string; - readonly kind: DevframeConnectionErrorKind; - constructor(_: DevframeConnectionErrorKind, _: string, _?: { - cause?: unknown; - }); -} -// #endregion - // #region Functions export declare function authenticateWithUrlOtp(_: Pick, _?: { param?: string; }): Promise; export declare function consumeOtpFromUrl(_?: string): string | undefined; export declare function createClientSettings = Record>(_: DevframeRpcClient, _: string): DevframeSettings; -export declare function createRpcStreamingClientHost(_: DevframeRpcClient): RpcStreamingClientHost; -export declare function createScopedClientContext(_: DevframeRpcClient, _: NS): DevframeScopedClientContext; -export declare function getDevframeConnection(): DevframeConnection | undefined; -export declare function getDevframeRpcClient(_?: DevframeRpcClientOptions): Promise; -export declare function isCallableStatus(_: DevframeConnectionStatus): boolean; export declare function readOtpFromUrl(_?: string): string | undefined; -export declare function registerDevframeViewerOrigin(_: DevframeConnection, _?: any): Promise; export declare function registerWebMcpTools(_: RpcFunctionsCollector, _?: RegisterWebMcpToolsOptions): () => void; -export declare function resolveClientTransport(_: 'auto' | 'websocket' | 'sse', _: ConnectionMeta): 'websocket' | 'sse' | 'static'; export declare function resolveSseUrl(_: ConnectionMeta['sse'], _: string, _: WsUrlLocation): string; export declare function resolveWebMcpModelContext(): WebMcpModelContext | undefined; export declare function resolveWsUrl(_: ConnectionMeta['websocket'], _: string, _: WsUrlLocation): string; -export declare function setupDevframeConnection(_?: SetupDevframeConnectionOptions): Promise; // #endregion // #region Variables export declare const connectDevframe: typeof getDevframeRpcClient; // #endregion -// #region Referenced (internal) -type AnyRpcFn = (..._: any[]) => any; +// #region Other +export { createRpcStreamingClientHost } +export { createScopedClientContext } +export { DevframeClientRpcHost } +export { DevframeConnection } +export { DevframeConnectionError } +export { DevframeConnectionErrorKind } +export { DevframeConnectionStatus } +export { DevframeRpcClient } +export { DevframeRpcClientCall } +export { DevframeRpcClientCallEvent } +export { DevframeRpcClientCallOptional } +export { DevframeRpcClientMode } +export { DevframeRpcClientOptions } +export { DevframeRpcContext } +export { DevframeScopedClientContext } +export { DevframeScopedClientRpc } +export { DevframeScopedClientStreamingHost } +export { DevframeServiceClientHandle } +export { DevframeServicesClient } +export { getDevframeConnection } +export { getDevframeRpcClient } +export { isCallableStatus } +export { registerDevframeViewerOrigin } +export { resolveClientTransport } +export { RpcClientEvents } +export { RpcStreamingClientHost } +export { setupDevframeConnection } +export { SetupDevframeConnectionOptions } +export { StreamingSubscribeOptions } // #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js b/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js index 7c38cfa7a..63b15efbd 100644 --- a/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js +++ b/tests/__snapshots__/tsnapi/devframe/rpc.snapshot.js @@ -1,41 +1,13 @@ /** * Generated by tsnapi — public API snapshot of `devframe/rpc` */ -// #region Classes -export class RpcCacheManager { - cacheMap - options - keySerializer - constructor(_) {} - updateOptions(_) {} - cached(_, _) {} - has(_, _) {} - apply(_, _) {} - validate(_) {} - clear(_) {} -} -export class RpcFunctionsCollectorBase { - context - definitions - functions - _onChanged - constructor(_) {} - register(_, _) {} - update(_, _) {} - onChanged(_) {} - async getHandler(_) {} - getSchema(_) {} - has(_) {} - get(_) {} - list() {} -} -// #endregion - // #region Other export { createDefineWrapperWithContext } export { defineRpcFunction } export { getRpcHandler } export { getRpcResolvedSetupResult } +export { RpcCacheManager } +export { RpcFunctionsCollectorBase } export { strictJsonStringify } export { STRUCTURED_CLONE_PREFIX } export { validateDefinition } diff --git a/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.d.ts b/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.d.ts new file mode 100644 index 000000000..0acdf23f1 --- /dev/null +++ b/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.d.ts @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `devframe/rpc/shared-state` + */ +// #region Functions +export declare function createRpcSharedStateClientHost(_: Pick & { + client: Pick, 'register'>; + connectionMeta: Pick; +}): RpcSharedStateHost; +export declare function createRpcSharedStateServerHost(_: Pick, 'register'> & Pick): RpcSharedStateHost; +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.js b/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.js new file mode 100644 index 000000000..79281bc71 --- /dev/null +++ b/tests/__snapshots__/tsnapi/devframe/rpc/shared-state.snapshot.js @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `devframe/rpc/shared-state` + */ +// #region Functions +export function createRpcSharedStateServerHost(_) {} +// #endregion + +// #region Other +export { createRpcSharedStateClientHost } +// #endregion \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index 4839e92af..ab4446ad7 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,6 +31,9 @@ "devframe/rpc/dump": [ "./packages/devframe/src/rpc/dump/index.ts" ], + "devframe/rpc/shared-state": [ + "./packages/devframe/src/rpc/shared-state.ts" + ], "devframe/rpc/server": [ "./packages/devframe/src/rpc/server.ts" ],