From 404d12ee2129aab23df181a710329867f14cc0e6 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Sun, 28 Apr 2024 01:03:43 +0100 Subject: [PATCH] dev/streamlit app typing (#846) * Move the `ConnectionManager` impl for stlite to the Streamlit submodule to avoid the circular dependency between it in `@stlite/kernel` and `@streamlit/lib` * Set up `@stlite/sharing` to run `tsc` at building * Change `moduleResolution` to `Node` which is necessary to run type checking at the build without explicit \`tsc\` * Update the streamlit submodule --- packages/kernel/src/index.ts | 2 +- .../lib/ConnectionManager.ts | 221 ------------------ packages/sharing-editor/tsconfig.json | 2 +- packages/sharing/tsconfig.json | 2 +- streamlit | 2 +- 5 files changed, 4 insertions(+), 225 deletions(-) delete mode 100644 packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts diff --git a/packages/kernel/src/index.ts b/packages/kernel/src/index.ts index 6472649aa..1a39529ac 100644 --- a/packages/kernel/src/index.ts +++ b/packages/kernel/src/index.ts @@ -1,4 +1,4 @@ export * from "./kernel"; -export * from "./streamlit-replacements/lib/ConnectionManager"; export * from "./react-helpers"; export * from "./types"; +export * from "./consts"; diff --git a/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts b/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts deleted file mode 100644 index 75e75947d..000000000 --- a/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts +++ /dev/null @@ -1,221 +0,0 @@ -// Mimic https://github.com/streamlit/streamlit/blob/1.27.0/frontend/app/src/connection/ConnectionManager.ts -// and WebsocketConnection. - -import type { ReactNode } from "react" - -import { IHostConfigResponse } from "@streamlit/lib/src/hostComm/types" -import type { BaseUriParts } from "@streamlit/lib/src/util/UriUtil" -import { SessionInfo } from "@streamlit/lib/src/SessionInfo" -import type { StreamlitEndpoints } from "@streamlit/lib/src/StreamlitEndpoints" -import { BackMsg, ForwardMsg } from "@streamlit/lib/src/proto" -import { ensureError } from "@streamlit/lib/src/util/ErrorHandling" - -import { DUMMY_BASE_HOSTNAME, DUMMY_BASE_PORT } from "../../consts" -import { ConnectionState } from "@streamlit/app/src/connection/ConnectionState" - -import type { StliteKernel } from "../../kernel" - -interface MessageQueue { - [index: number]: any -} - -interface Props { - /** - * Kernel object to connect to. - */ - kernel: StliteKernel - - /** The app's SessionInfo instance */ - sessionInfo: SessionInfo - - /** The app's StreamlitEndpoints instance */ - endpoints: StreamlitEndpoints - - /** - * Function to be called when we receive a message from the server. - */ - onMessage: (message: ForwardMsg) => void - - /** - * Function to be called when the connection errors out. - */ - onConnectionError: (errNode: ReactNode) => void - - /** - * Called when our ConnectionState is changed. - */ - connectionStateChanged: (connectionState: ConnectionState) => void - - /** - * Function to get the auth token set by the host of this app (if in a - * relevant deployment scenario). - */ - claimHostAuthToken: () => Promise - - /** - * Function to clear the withHostCommunication hoc's auth token. This should - * be called after the promise returned by claimHostAuthToken successfully - * resolves. - */ - resetHostAuthToken: () => void - - /** - * Function to set the host config for this app (if in a relevant deployment - * scenario). - */ - onHostConfigResp: (resp: IHostConfigResponse) => void -} - -/** - * Manages our connection to the Server. - */ -export class ConnectionManager { - private readonly props: Props - - private connectionState: ConnectionState = ConnectionState.INITIAL - - constructor(props: Props) { - this.props = props - - this.props.kernel.onWebSocketMessage((payload) => { - if (typeof payload === "string") { - console.error("Unexpected payload type.") - return - } - this.handleMessage(payload) - }) - - this.props.kernel.loaded.then(() => { - console.log("The kernel has been loaded. Start connecting.") - this.props.onHostConfigResp(this.props.kernel.hostConfigResponse) - this.connect() - }) - } - - /** - * Indicates whether we're connected to the server. - */ - public isConnected(): boolean { - return this.connectionState === ConnectionState.CONNECTED - } - - /** - * Return the BaseUriParts for the server we're connected to, - * if we are connected to a server. - */ - public getBaseUriParts(): BaseUriParts | undefined { - if (this.connectionState === ConnectionState.CONNECTED) { - // The existence of this property became necessary for multi-page apps: https://github.com/streamlit/streamlit/pull/4698/files#diff-e56cb91573ddb6a97ecd071925fe26504bb5a65f921dc64c63e534162950e1ebR967-R975 - // so here a dummy BaseUriParts object is returned. - // The host and port are set as dummy values that are invalid as a URL - // in order to avoid unexpected accesses to external resources, - // while the basePath is representing the actual info. - return { - host: DUMMY_BASE_HOSTNAME, - port: DUMMY_BASE_PORT, - // When a new session starts, a page name for multi-page apps (a relative path to the app root url) is calculated based on this `basePath` - // then a `rerunScript` BackMsg is sent to the server with `pageName` (https://github.com/streamlit/streamlit/blob/ace58bfa3582d4f8e7f281b4dbd266ddd8a32b54/frontend/src/App.tsx#L1064) - // and `window.history.pushState` is called (https://github.com/streamlit/streamlit/blob/ace58bfa3582d4f8e7f281b4dbd266ddd8a32b54/frontend/src/App.tsx#L665). - basePath: this.props.kernel.basePath, - } - } - return undefined - } - - public sendMessage(obj: BackMsg): void { - if (this.isConnected()) { - this.props.kernel.sendWebSocketMessage( - BackMsg.encode(BackMsg.create(obj)).finish() - ) - } else { - // Don't need to make a big deal out of this. Just print to console. - console.error(`Cannot send message when server is disconnected: ${obj}`) - } - } - - /** - * To guarantee packet transmission order, this is the index of the last - * dispatched incoming message. - */ - private lastDispatchedMessageIndex = -1 - - /** - * And this is the index of the next message we receive. - */ - private nextMessageIndex = 0 - - /** - * This dictionary stores received messages that we haven't sent out yet - * (because we're still decoding previous messages) - */ - private readonly messageQueue: MessageQueue = {} - - /** - * No-op in stlite. - */ - public incrementMessageCacheRunCount(): void { - // no-op. - // Because caching is disabled in stlite. See https://github.com/whitphx/stlite/issues/495 - } - - /** - * No-op in stlite. - */ - disconnect(): void { - // no-op. - } - - private async handleMessage(data: ArrayBuffer): Promise { - // Assign this message an index. - const messageIndex = this.nextMessageIndex - this.nextMessageIndex += 1 - - const encodedMsg = new Uint8Array(data) - const msg = ForwardMsg.decode(encodedMsg) - - // stlite doesn't handle caches. - if (msg.type === "refHash") { - throw new Error(`Unexpected cache reference message.`) - } - - this.messageQueue[messageIndex] = msg - - // Dispatch any pending messages in the queue. This may *not* result - // in our just-decoded message being dispatched: if there are other - // messages that were received earlier than this one but are being - // downloaded, our message won't be sent until they're done. - while (this.lastDispatchedMessageIndex + 1 in this.messageQueue) { - const dispatchMessageIndex = this.lastDispatchedMessageIndex + 1 - this.props.onMessage(this.messageQueue[dispatchMessageIndex]) - delete this.messageQueue[dispatchMessageIndex] - this.lastDispatchedMessageIndex = dispatchMessageIndex - } - } - - private async connect(): Promise { - const WEBSOCKET_STREAM_PATH = "_stcore/stream" // The original is defined in streamlit/frontend/src/lib/WebsocketConnection.tsx - - try { - await this.props.kernel.connectWebSocket("/" + WEBSOCKET_STREAM_PATH) - this.setConnectionState(ConnectionState.CONNECTED) - } catch (e) { - const err = ensureError(e) - console.error(err.message) - this.setConnectionState(ConnectionState.DISCONNECTED_FOREVER, err.message) - } - } - - private setConnectionState = ( - connectionState: ConnectionState, - errMsg?: string - ): void => { - if (this.connectionState !== connectionState) { - this.connectionState = connectionState - this.props.connectionStateChanged(connectionState) - } - - if (errMsg || connectionState === ConnectionState.DISCONNECTED_FOREVER) { - this.props.onConnectionError(errMsg || "unknown") - } - } -} diff --git a/packages/sharing-editor/tsconfig.json b/packages/sharing-editor/tsconfig.json index 92e178534..a7b325bd1 100644 --- a/packages/sharing-editor/tsconfig.json +++ b/packages/sharing-editor/tsconfig.json @@ -10,7 +10,7 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "Bundler", + "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/packages/sharing/tsconfig.json b/packages/sharing/tsconfig.json index a7b325bd1..b543db9c6 100644 --- a/packages/sharing/tsconfig.json +++ b/packages/sharing/tsconfig.json @@ -16,5 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"] + "include": ["src", "../../streamlit/frontend/lib/src/**/*.d.ts"] } diff --git a/streamlit b/streamlit index 513d98182..d1ab0188c 160000 --- a/streamlit +++ b/streamlit @@ -1 +1 @@ -Subproject commit 513d98182b3505ab7534fca236fe7ab9fb93839e +Subproject commit d1ab0188cfcf85c269f3ea204257a2427cc2625e