Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/devframe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"structured-clone-es": "catalog:deps",
"tinyglobby": "catalog:deps",
"tsdown": "catalog:build",
"typescript": "catalog:tooling",
"ua-parser-modern": "catalog:inlined",
"valibot": "catalog:deps",
"whenexpr": "catalog:deps",
Expand Down
2 changes: 1 addition & 1 deletion packages/devframe/src/node/auth/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RpcFunctionDefinitionAny } from 'devframe/rpc'
import type { DevframeRpcConnection } from 'devframe/rpc/transports/ws-server'
import type { DevframeNodeRpcSession } from 'devframe/types'
import type { DevframeRpcConnection } from '../../rpc/transports/session'

/**
* A ready-made pre-auth RPC handler, as produced by
Expand Down
40 changes: 37 additions & 3 deletions packages/devframe/src/rpc/transports/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
import type { Peer } from 'crossws'
/**
* Structural view of the crossws `Peer` backing a WebSocket RPC connection:
* the transport-independent slice of its API (identity, send, pub/sub,
* close, backpressure), typed locally so the `devframe/types` declaration
* graph never imports `crossws`, whose own declarations require the DOM,
* Bun, and Cloudflare type libs a plain Node consumer doesn't load. Every
* member mirrors its crossws counterpart; for the full API (the upgrade
* `request`, raw `websocket`, connected `peers`), import `Peer` from
* `crossws` and cast, which opts your compilation into crossws's lib
* requirements.
*/
export interface DevframeWsPeer {
/** Unique random uuid v4 identifier for the peer. */
readonly id: string
/** IP address of the peer. */
readonly remoteAddress: string | undefined
/** All topics this peer has been subscribed to. */
readonly topics: Set<string>
/** Bytes queued for transmission but not yet flushed to the client. */
readonly bufferedAmount: number
/** Wait until the send buffer drains to `threshold` bytes (default `0`). */
waitForDrain: (opts?: { threshold?: number, pollInterval?: number }) => Promise<void>
/** Send a message to the peer. */
send: (data: unknown, options?: { compress?: boolean }) => number | void | undefined
/** Send a message to subscribers of a topic. */
publish: (topic: string, data: unknown, options?: { compress?: boolean }) => void
/** Subscribe to a topic. */
subscribe: (topic: string) => void
/** Unsubscribe from a topic. */
unsubscribe: (topic: string) => void
/** Close the connection. */
close: (code?: number, reason?: string) => void
/** Abruptly close the connection. */
terminate: () => void
}

/**
* Which wire transport produced an RPC connection. Every transport speaks
Expand Down Expand Up @@ -44,13 +78,13 @@ export interface DevframeRpcConnection {
* The crossws peer backing a `websocket` connection: the WS-specific
* escape hatch (pub/sub, raw socket access). Absent on other transports.
*/
peer?: Peer
peer?: DevframeWsPeer
}

export interface DevframeNodeRpcSessionMeta {
id: number
/** The crossws peer backing this session's socket (WS transport only). */
peer?: Peer
peer?: DevframeWsPeer
clientAuthToken?: string
isTrusted?: boolean
subscribedStates: Set<string>
Expand Down
1 change: 1 addition & 0 deletions packages/devframe/src/rpc/transports/ws-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type {
DevframeRpcConnection,
DevframeRpcConnectionRequest,
DevframeRpcTransportKind,
DevframeWsPeer,
} from './session'

export interface WsRpcTransportOptions {
Expand Down
5 changes: 3 additions & 2 deletions packages/devframe/src/types/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { BirpcReturn } from 'birpc'
import type { RpcFunctionsCollectorBase } from 'devframe/rpc'
import type { DevframeNodeRpcSessionMeta } from 'devframe/rpc/transports/ws-server'
import type { SharedState } from 'devframe/utils/shared-state'
import type { StreamReader, StreamSink } from 'devframe/utils/streaming-channel'
import type { DevframeNodeRpcSessionMeta } from '../rpc/transports/session'
import type { DevframeNodeContext } from './context'
import type { DevframeRpcClientFunctions, DevframeRpcServerFunctions } from './rpc-augments'

Expand All @@ -11,7 +11,8 @@ export type {
DevframeRpcConnection,
DevframeRpcConnectionRequest,
DevframeRpcTransportKind,
} from 'devframe/rpc/transports/ws-server'
DevframeWsPeer,
} from '../rpc/transports/session'

export interface DevframeNodeRpcSession {
meta: DevframeNodeRpcSessionMeta
Expand Down
42 changes: 42 additions & 0 deletions packages/devframe/test/types-lib-neutral.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
import { describe, expect, it } from 'vitest'

const pkgRoot = fileURLToPath(new URL('../', import.meta.url))

/**
* `devframe/types` is imported by config-surface packages (e.g.
* `@vitejs/devtools/config`), so its declaration graph must typecheck in a
* plain Node compilation: ES lib + `@types/node`, no DOM/Bun/Cloudflare libs,
* with `skipLibCheck: false` (how vitejs/vite's CI checks it). A `crossws`
* type import anywhere in the graph breaks that, since crossws's
* declarations require all three.
*/
describe('devframe/types lib-neutrality', () => {
it('typechecks with lib ES2022 + @types/node only, skipLibCheck: false', () => {
const options: ts.CompilerOptions = {
noEmit: true,
strict: true,
skipLibCheck: false,
lib: ['lib.es2022.d.ts'],
types: ['node'],
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
}
const host = ts.createCompilerHost(options)
host.getCurrentDirectory = () => pkgRoot
const program = ts.createProgram(
[resolve(pkgRoot, 'dist/types/index.d.mts')],
options,
host,
)
const diagnostics = ts.getPreEmitDiagnostics(program)
expect(ts.formatDiagnostics(diagnostics, {
getCanonicalFileName: f => f,
getCurrentDirectory: () => pkgRoot,
getNewLine: () => '\n',
})).toBe('')
})
})
Loading
Loading