Skip to content

Commit fd0e514

Browse files
committed
fix(types): keep crossws out of the devframe/types declaration graph
devframe/types (and node/auth/handler behind it) imported the crossws Peer type, so any consumer of the types entry - e.g. @vitejs/devtools/config - had to load the DOM, Bun, and Cloudflare type libs crossws's declarations require, breaking downstream skipLibCheck: false compilations (vitejs/vite CI). The WS peer escape hatch is now typed by a local structural DevframeWsPeer mirroring the crossws Peer surface, and a regression test typechecks the shipped dist/types declarations with lib ES2022 + @types/node only.
1 parent 1a3ef61 commit fd0e514

10 files changed

Lines changed: 140 additions & 32 deletions

File tree

‎packages/devframe/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
"structured-clone-es": "catalog:deps",
119119
"tinyglobby": "catalog:deps",
120120
"tsdown": "catalog:build",
121+
"typescript": "catalog:tooling",
121122
"ua-parser-modern": "catalog:inlined",
122123
"valibot": "catalog:deps",
123124
"whenexpr": "catalog:deps",

‎packages/devframe/src/node/auth/handler.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { RpcFunctionDefinitionAny } from 'devframe/rpc'
2-
import type { DevframeRpcConnection } from 'devframe/rpc/transports/ws-server'
32
import type { DevframeNodeRpcSession } from 'devframe/types'
3+
import type { DevframeRpcConnection } from '../../rpc/transports/session'
44

55
/**
66
* A ready-made pre-auth RPC handler, as produced by

‎packages/devframe/src/rpc/transports/session.ts‎

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,38 @@
1-
import type { Peer } from 'crossws'
1+
/**
2+
* Structural view of the crossws `Peer` backing a WebSocket RPC connection:
3+
* the transport-independent slice of its API (identity, send, pub/sub,
4+
* close, backpressure), typed locally so the `devframe/types` declaration
5+
* graph never imports `crossws`, whose own declarations require the DOM,
6+
* Bun, and Cloudflare type libs a plain Node consumer doesn't load. Every
7+
* member mirrors its crossws counterpart; for the full API (the upgrade
8+
* `request`, raw `websocket`, connected `peers`), import `Peer` from
9+
* `crossws` and cast, which opts your compilation into crossws's lib
10+
* requirements.
11+
*/
12+
export interface DevframeWsPeer {
13+
/** Unique random uuid v4 identifier for the peer. */
14+
readonly id: string
15+
/** IP address of the peer. */
16+
readonly remoteAddress: string | undefined
17+
/** All topics this peer has been subscribed to. */
18+
readonly topics: Set<string>
19+
/** Bytes queued for transmission but not yet flushed to the client. */
20+
readonly bufferedAmount: number
21+
/** Wait until the send buffer drains to `threshold` bytes (default `0`). */
22+
waitForDrain: (opts?: { threshold?: number, pollInterval?: number }) => Promise<void>
23+
/** Send a message to the peer. */
24+
send: (data: unknown, options?: { compress?: boolean }) => number | void | undefined
25+
/** Send a message to subscribers of a topic. */
26+
publish: (topic: string, data: unknown, options?: { compress?: boolean }) => void
27+
/** Subscribe to a topic. */
28+
subscribe: (topic: string) => void
29+
/** Unsubscribe from a topic. */
30+
unsubscribe: (topic: string) => void
31+
/** Close the connection. */
32+
close: (code?: number, reason?: string) => void
33+
/** Abruptly close the connection. */
34+
terminate: () => void
35+
}
236

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

5084
export interface DevframeNodeRpcSessionMeta {
5185
id: number
5286
/** The crossws peer backing this session's socket (WS transport only). */
53-
peer?: Peer
87+
peer?: DevframeWsPeer
5488
clientAuthToken?: string
5589
isTrusted?: boolean
5690
subscribedStates: Set<string>

‎packages/devframe/src/rpc/transports/ws-server.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type {
2222
DevframeRpcConnection,
2323
DevframeRpcConnectionRequest,
2424
DevframeRpcTransportKind,
25+
DevframeWsPeer,
2526
} from './session'
2627

2728
export interface WsRpcTransportOptions {

‎packages/devframe/src/types/rpc.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { BirpcReturn } from 'birpc'
22
import type { RpcFunctionsCollectorBase } from 'devframe/rpc'
3-
import type { DevframeNodeRpcSessionMeta } from 'devframe/rpc/transports/ws-server'
43
import type { SharedState } from 'devframe/utils/shared-state'
54
import type { StreamReader, StreamSink } from 'devframe/utils/streaming-channel'
5+
import type { DevframeNodeRpcSessionMeta } from '../rpc/transports/session'
66
import type { DevframeNodeContext } from './context'
77
import type { DevframeRpcClientFunctions, DevframeRpcServerFunctions } from './rpc-augments'
88

@@ -11,7 +11,8 @@ export type {
1111
DevframeRpcConnection,
1212
DevframeRpcConnectionRequest,
1313
DevframeRpcTransportKind,
14-
} from 'devframe/rpc/transports/ws-server'
14+
DevframeWsPeer,
15+
} from '../rpc/transports/session'
1516

1617
export interface DevframeNodeRpcSession {
1718
meta: DevframeNodeRpcSessionMeta
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { resolve } from 'node:path'
2+
import { fileURLToPath } from 'node:url'
3+
import ts from 'typescript'
4+
import { describe, expect, it } from 'vitest'
5+
6+
const pkgRoot = fileURLToPath(new URL('../', import.meta.url))
7+
8+
/**
9+
* `devframe/types` is imported by config-surface packages (e.g.
10+
* `@vitejs/devtools/config`), so its declaration graph must typecheck in a
11+
* plain Node compilation: ES lib + `@types/node`, no DOM/Bun/Cloudflare libs,
12+
* with `skipLibCheck: false` (how vitejs/vite's CI checks it). A `crossws`
13+
* type import anywhere in the graph breaks that, since crossws's
14+
* declarations require all three.
15+
*/
16+
describe('devframe/types lib-neutrality', () => {
17+
it('typechecks with lib ES2022 + @types/node only, skipLibCheck: false', () => {
18+
const options: ts.CompilerOptions = {
19+
noEmit: true,
20+
strict: true,
21+
skipLibCheck: false,
22+
lib: ['lib.es2022.d.ts'],
23+
types: ['node'],
24+
target: ts.ScriptTarget.ES2022,
25+
module: ts.ModuleKind.ESNext,
26+
moduleResolution: ts.ModuleResolutionKind.Bundler,
27+
}
28+
const host = ts.createCompilerHost(options)
29+
host.getCurrentDirectory = () => pkgRoot
30+
const program = ts.createProgram(
31+
[resolve(pkgRoot, 'dist/types/index.d.mts')],
32+
options,
33+
host,
34+
)
35+
const diagnostics = ts.getPreEmitDiagnostics(program)
36+
expect(ts.formatDiagnostics(diagnostics, {
37+
getCanonicalFileName: f => f,
38+
getCurrentDirectory: () => pkgRoot,
39+
getNewLine: () => '\n',
40+
})).toBe('')
41+
})
42+
})

0 commit comments

Comments
 (0)