@flighthq/socket 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.1047.858b9b6

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/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/socket`. The `@fligh
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/socket@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/socket)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/socket@0.5.1-edge.1047.858b9b6](https://github.com/flighthq/flight/tree/858b9b601de814f6b6940cbb1ffff0916b903268/packages/socket)
17
+ - [License](https://github.com/flighthq/flight/blob/858b9b601de814f6b6940cbb1ffff0916b903268/LICENSE.md)
@@ -1,4 +1,5 @@
1
1
  export * from './enableSocketGuards';
2
2
  export * from './explainSocketSendFailure';
3
3
  export * from './socket';
4
+ export { initializeWebSocketBackend } from './socket';
4
5
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC"}
package/dist/contract.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './enableSocketGuards';
2
2
  export * from './explainSocketSendFailure';
3
3
  export * from './socket';
4
+ export { initializeWebSocketBackend } from './socket';
4
5
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC"}
@@ -17,7 +17,7 @@ export function enableSocketGuards() {
17
17
  function warnOnSocketMisuse(notice) {
18
18
  const url = notice.socket.url;
19
19
  const message = notice.reason === 'no-connection'
20
- ? 'createSocket: active backend returned no connection — call setSocketBackend(...) with a backend that supports this transport'
20
+ ? 'createSocket: the host carries no socket provider for this transport, or its provider returned no connection — pass a host whose net.socket supports it'
21
21
  : `${notice.operation}: socket is already disposed — call createSocket(...) to create a new socket`;
22
22
  logOnce(`socket:${notice.operation}:${notice.reason}`, LogLevel.Warn, { message, operation: notice.operation, reason: notice.reason, url }, 'socket');
23
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"enableSocketGuards.js","sourceRoot":"","sources":["../src/enableSocketGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,MAAM,UAAU,sBAAsB;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,cAAc,CAAC,IAAI,CAAC,CAAC;IACrB,QAAQ,GAAG,KAAK,CAAC;AACnB,CAAC;AAED,qGAAqG;AACrG,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB;IAChC,cAAc,CAAC,kBAAkB,CAAC,CAAC;IACnC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAmC;IAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IAC9B,MAAM,OAAO,GACX,MAAM,CAAC,MAAM,KAAK,eAAe;QAC/B,CAAC,CAAC,8HAA8H;QAChI,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,8EAA8E,CAAC;IACxG,OAAO,CACL,UAAU,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,EAC7C,QAAQ,CAAC,IAAI,EACb,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,EACpE,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,IAAI,QAAQ,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"enableSocketGuards.js","sourceRoot":"","sources":["../src/enableSocketGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,MAAM,UAAU,sBAAsB;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,cAAc,CAAC,IAAI,CAAC,CAAC;IACrB,QAAQ,GAAG,KAAK,CAAC;AACnB,CAAC;AAED,qGAAqG;AACrG,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB;IAChC,cAAc,CAAC,kBAAkB,CAAC,CAAC;IACnC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAmC;IAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;IAC9B,MAAM,OAAO,GACX,MAAM,CAAC,MAAM,KAAK,eAAe;QAC/B,CAAC,CAAC,yJAAyJ;QAC3J,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,8EAA8E,CAAC;IACxG,OAAO,CACL,UAAU,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,EAC7C,QAAQ,CAAC,IAAI,EACb,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,EACpE,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,IAAI,QAAQ,GAAG,KAAK,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { areSocketGuardsEnabled, attachSocket, closeSocket, createSocket, detachSocket, disableSocketGuards, disposeSocket, enableSocketGuards, enableSocketSignals, explainSocketSendFailure, getSocketReadyState, sendSocketMessage, } from './contract';
1
+ export { areSocketGuardsEnabled, attachSocket, closeSocket, createSocket, detachSocket, disableSocketGuards, disposeSocket, enableSocketGuards, enableSocketSignals, explainSocketSendFailure, getSocketReadyState, openTcpSocket, sendSocketMessage, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { areSocketGuardsEnabled, attachSocket, closeSocket, createSocket, detachSocket, disableSocketGuards, disposeSocket, enableSocketGuards, enableSocketSignals, explainSocketSendFailure, getSocketReadyState, sendSocketMessage, } from './contract';
1
+ export { areSocketGuardsEnabled, attachSocket, closeSocket, createSocket, detachSocket, disableSocketGuards, disposeSocket, enableSocketGuards, enableSocketSignals, explainSocketSendFailure, getSocketReadyState, openTcpSocket, sendSocketMessage, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
package/dist/socket.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import type { Socket, SocketBackend, SocketGuard, SocketOptions, SocketReadyState, SocketSignals } from '@flighthq/types/contract';
1
+ import type { Entity, EntityConstruction, HasNetSocket, Socket, SocketBackend, SocketGuard, SocketOptions, SocketReadyState, SocketSignals, TcpSocketConnection, TcpSocketOptions } from '@flighthq/types/contract';
2
2
  export declare function attachSocket(socket: Socket): void;
3
3
  export declare function closeSocket(socket: Socket, code?: number, reason?: string): void;
4
- export declare function createSocket(options: Readonly<SocketOptions>): Socket;
5
- export declare function createWebSocketBackend(): SocketBackend;
4
+ export declare function createSocket(host: HasNetSocket, options: Readonly<SocketOptions>): Socket;
5
+ export declare function createWebSocketBackend(): SocketBackend & Entity;
6
6
  export declare function detachSocket(socket: Socket): void;
7
7
  export declare function disposeSocket(socket: Socket): void;
8
8
  export declare function enableSocketSignals(socket: Socket): SocketSignals;
9
- export declare function getSocketBackend(): SocketBackend;
10
9
  export declare function getSocketReadyState(socket: Readonly<Socket>): SocketReadyState;
10
+ export declare function initializeWebSocketBackend(out: EntityConstruction<SocketBackend & Entity>): void;
11
+ export declare function openTcpSocket(host: HasNetSocket, options: Readonly<TcpSocketOptions>): TcpSocketConnection | null;
11
12
  export declare function sendSocketMessage(socket: Readonly<Socket>, data: string | ArrayBuffer): boolean;
12
- export declare function setSocketBackend(backend: SocketBackend | null): void;
13
13
  export declare function setSocketGuard(guard: SocketGuard | null): void;
14
14
  //# sourceMappingURL=socket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EAIb,WAAW,EAEX,aAAa,EACb,gBAAgB,EAEhB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGjD;AAKD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAShF;AAOD,wBAAgB,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,CAYrE;AAMD,wBAAgB,sBAAsB,IAAI,aAAa,CA0BtD;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjD;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CASlD;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAYjE;AAGD,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD;AAGD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAE9E;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAQ/F;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAEpE;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAE9D"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,MAAM,EACN,aAAa,EAIb,WAAW,EAEX,aAAa,EACb,gBAAgB,EAEhB,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGjD;AAKD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAShF;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,CAkBzF;AAED,wBAAgB,sBAAsB,IAAI,aAAa,GAAG,MAAM,CAI/D;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjD;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CASlD;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAcjE;AAGD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAE9E;AAMD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,kBAAkB,CAAC,aAAa,GAAG,MAAM,CAAC,GAAG,IAAI,CAwBhG;AAKD,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAEjH;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAQ/F;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAE9D"}
package/dist/socket.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { allocateEntity, finishEntity } from '@flighthq/entity/contract';
1
2
  import { createSignal, emitSignal } from '@flighthq/signals/contract';
2
3
  // Resumes delivery of backend events to the socket's signals (idempotent). Pair with detachSocket.
3
4
  // createSocket leaves a new socket attached, so this is only needed to resume after detachSocket. A
@@ -21,12 +22,13 @@ export function closeSocket(socket, code, reason) {
21
22
  runtime.readyState = 'closing';
22
23
  runtime.connection?.closeSocketConnection(code, reason);
23
24
  }
24
- // Allocates a Socket entity plus its runtime and opens the connection through the active backend,
25
+ // Allocates a Socket entity plus its runtime and opens the connection through the host's socket
26
+ // provider,
25
27
  // wiring the backend's open/message/close/error into the socket's (still inert) signal group. The
26
28
  // socket starts in 'connecting' and is left attached (delivering). Enable signals with
27
29
  // enableSocketSignals to observe events. A backend that does not support the transport yields a null
28
30
  // connection and the socket stays in 'connecting' until closed.
29
- export function createSocket(options) {
31
+ export function createSocket(host, options) {
30
32
  const runtime = {
31
33
  connection: null,
32
34
  signals: null,
@@ -34,42 +36,22 @@ export function createSocket(options) {
34
36
  delivering: true,
35
37
  disposed: false,
36
38
  };
37
- const socket = { url: options.url, runtime };
38
- runtime.connection = getSocketBackend().openSocket(options, makeSocketEventSink(runtime));
39
+ const socket = allocateEntity();
40
+ socket.url = options.url;
41
+ socket.runtime = runtime;
42
+ // The provider comes from the host the caller passed. A host that carries no socket transport
43
+ // yields no connection rather than reaching a process-global fallback: absence is an answer here,
44
+ // and the guard reports it.
45
+ const backend = host.net.socket;
46
+ runtime.connection = backend === undefined ? null : backend.openSocket(options, makeSocketEventSink(runtime));
39
47
  if (runtime.connection === null)
40
48
  _guard?.({ operation: 'createSocket', reason: 'no-connection', socket });
41
49
  return socket;
42
50
  }
43
- // Builds the default web backend over the DOM WebSocket. Created lazily by getSocketBackend — no
44
- // WebSocket is constructed at import time, so importing the package has no side effect. Returns a
45
- // null connection when WebSocket is unavailable (non-browser host) rather than throwing; raw TCP/UDP
46
- // is likewise unsupported here and only reachable through a native backend.
47
51
  export function createWebSocketBackend() {
48
- return {
49
- openSocket(options, events) {
50
- if (typeof WebSocket === 'undefined')
51
- return null;
52
- const ws = options.protocols !== undefined
53
- ? new WebSocket(options.url, options.protocols)
54
- : new WebSocket(options.url);
55
- ws.binaryType = options.binaryType ?? 'arraybuffer';
56
- ws.onopen = () => events.handleSocketOpen();
57
- ws.onmessage = (event) => events.handleSocketMessage(toSocketMessage(event.data));
58
- ws.onclose = (event) => events.handleSocketClose({ code: event.code, reason: event.reason, wasClean: event.wasClean });
59
- ws.onerror = () => events.handleSocketError();
60
- return {
61
- sendSocketFrame(data) {
62
- if (ws.readyState !== WebSocket.OPEN)
63
- return false;
64
- ws.send(data);
65
- return true;
66
- },
67
- closeSocketConnection(code, reason) {
68
- ws.close(code, reason);
69
- },
70
- };
71
- },
72
- };
52
+ const out = allocateEntity();
53
+ initializeWebSocketBackend(out);
54
+ return finishEntity(out);
73
55
  }
74
56
  // Stops delivery of backend events to the socket's signals. The live connection is untouched — use
75
57
  // closeSocket to close it. Safe to call repeatedly; resume with attachSocket.
@@ -99,25 +81,56 @@ export function enableSocketSignals(socket) {
99
81
  if (runtime.disposed)
100
82
  _guard?.({ operation: 'enableSocketSignals', reason: 'disposed', socket });
101
83
  if (runtime.signals === null) {
102
- runtime.signals = {
103
- onSocketOpen: createSignal(),
104
- onSocketMessage: createSignal(),
105
- onSocketClose: createSignal(),
106
- onSocketError: createSignal(),
107
- };
84
+ runtime.signals = (() => {
85
+ const out = allocateEntity();
86
+ out.onSocketOpen = createSignal();
87
+ out.onSocketMessage = createSignal();
88
+ out.onSocketClose = createSignal();
89
+ out.onSocketError = createSignal();
90
+ return finishEntity(out);
91
+ })();
108
92
  }
109
93
  return runtime.signals;
110
94
  }
111
- // The active socket backend, or a lazily-created web default. There is always a backend.
112
- export function getSocketBackend() {
113
- if (_backend === null)
114
- _backend = createWebSocketBackend();
115
- return _backend;
116
- }
117
95
  // The socket's current connection phase, tracked on the runtime from backend events and closeSocket.
118
96
  export function getSocketReadyState(socket) {
119
97
  return socket.runtime.readyState;
120
98
  }
99
+ // Builds the web backend over the DOM WebSocket. Nothing constructs one at import time, so importing
100
+ // the package has no side effect; a host composes this value into its own net group. Returns a
101
+ // null connection when WebSocket is unavailable (non-browser host) rather than throwing; raw TCP/UDP
102
+ // is likewise unsupported here and only reachable through a native backend.
103
+ export function initializeWebSocketBackend(out) {
104
+ out.openSocket = (options, events) => {
105
+ if (typeof WebSocket === 'undefined')
106
+ return null;
107
+ const ws = options.protocols !== undefined
108
+ ? new WebSocket(options.url, options.protocols)
109
+ : new WebSocket(options.url);
110
+ ws.binaryType = options.binaryType ?? 'arraybuffer';
111
+ ws.onopen = () => events.handleSocketOpen();
112
+ ws.onmessage = (event) => events.handleSocketMessage(toSocketMessage(event.data));
113
+ ws.onclose = (event) => events.handleSocketClose({ code: event.code, reason: event.reason, wasClean: event.wasClean });
114
+ ws.onerror = () => events.handleSocketError();
115
+ return {
116
+ sendSocketFrame(data) {
117
+ if (ws.readyState !== WebSocket.OPEN)
118
+ return false;
119
+ ws.send(data);
120
+ return true;
121
+ },
122
+ closeSocketConnection(code, reason) {
123
+ ws.close(code, reason);
124
+ },
125
+ };
126
+ };
127
+ }
128
+ // Opens a raw TCP byte stream through the socket provider selected by the caller's host. Browser and
129
+ // other framed-only providers omit openTcpSocket, so unsupported raw TCP returns null without trying
130
+ // openSocket or interpreting the endpoint as a WebSocket URL.
131
+ export function openTcpSocket(host, options) {
132
+ return host.net.socket?.openTcpSocket?.(options) ?? null;
133
+ }
121
134
  // Sends a text or binary frame over the live connection without mutating or copying either argument.
122
135
  // Returns false — a sentinel, not a throw — when the socket is disposed, not open, has no connection,
123
136
  // or the backend rejects the send. explainSocketSendFailure diagnoses the deterministic preflight paths.
@@ -131,16 +144,11 @@ export function sendSocketMessage(socket, data) {
131
144
  return false;
132
145
  return runtime.connection.sendSocketFrame(data);
133
146
  }
134
- // Installs a native host socket backend (adding TCP/UDP); pass null to fall back to the web default.
135
- export function setSocketBackend(backend) {
136
- _backend = backend;
137
- }
138
147
  // Installs the diagnostics hook used by the separately imported enableSocketGuards module. Null is
139
148
  // the production default and removes all guard-path work except the branch.
140
149
  export function setSocketGuard(guard) {
141
150
  _guard = guard;
142
151
  }
143
- let _backend = null;
144
152
  let _guard = null;
145
153
  // Builds the backend→entity sink bound to one socket's runtime: it updates readyState and emits the
146
154
  // opt-in signals. Every handler is a no-op once the runtime stops delivering (detach/dispose), so a
@@ -1 +1 @@
1
- {"version":3,"file":"socket.js","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAetE,mGAAmG;AACnG,oGAAoG;AACpG,wDAAwD;AACxD,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ;QAAE,OAAO;IACpC,MAAM,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,mGAAmG;AACnG,sGAAsG;AACtG,iGAAiG;AACjG,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAa,EAAE,MAAe;IACxE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO;IAChF,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,UAAU,EAAE,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED,kGAAkG;AAClG,kGAAkG;AAClG,uFAAuF;AACvF,qGAAqG;AACrG,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,OAAgC;IAC3D,MAAM,OAAO,GAAkB;QAC7B,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,YAAY;QACxB,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,MAAM,MAAM,GAAW,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;IACrD,OAAO,CAAC,UAAU,GAAG,gBAAgB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;QAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1G,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iGAAiG;AACjG,kGAAkG;AAClG,qGAAqG;AACrG,4EAA4E;AAC5E,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,UAAU,CAAC,OAAO,EAAE,MAAM;YACxB,IAAI,OAAO,SAAS,KAAK,WAAW;gBAAE,OAAO,IAAI,CAAC;YAClD,MAAM,EAAE,GACN,OAAO,CAAC,SAAS,KAAK,SAAS;gBAC7B,CAAC,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,SAAqB,CAAC;gBAC3D,CAAC,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,aAAa,CAAC;YACpD,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC5C,EAAE,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAChG,EAAE,CAAC,OAAO,GAAG,CAAC,KAAiB,EAAE,EAAE,CACjC,MAAM,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjG,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC9C,OAAO;gBACL,eAAe,CAAC,IAAI;oBAClB,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;wBAAE,OAAO,KAAK,CAAC;oBACnD,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACd,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,qBAAqB,CAAC,IAAI,EAAE,MAAM;oBAChC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBACzB,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,CAAC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC;AACpC,CAAC;AAED,mGAAmG;AACnG,sGAAsG;AACtG,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ;QAAE,OAAO;IACpC,WAAW,CAAC,MAAM,CAAC,CAAC;IACpB,YAAY,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC;IAC9B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,oGAAoG;AACpG,gGAAgG;AAChG,sGAAsG;AACtG,qGAAqG;AACrG,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,IAAI,OAAO,CAAC,QAAQ;QAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IACjG,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC7B,OAAO,CAAC,OAAO,GAAG;YAChB,YAAY,EAAE,YAAY,EAAc;YACxC,eAAe,EAAE,YAAY,EAA8C;YAC3E,aAAa,EAAE,YAAY,EAA6C;YACxE,aAAa,EAAE,YAAY,EAAc;SAC1C,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC;AACzB,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,gBAAgB;IAC9B,IAAI,QAAQ,KAAK,IAAI;QAAE,QAAQ,GAAG,sBAAsB,EAAE,CAAC;IAC3D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,mBAAmB,CAAC,MAAwB;IAC1D,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;AACnC,CAAC;AAED,qGAAqG;AACrG,sGAAsG;AACtG,yGAAyG;AACzG,MAAM,UAAU,iBAAiB,CAAC,MAAwB,EAAE,IAA0B;IACpF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC/E,OAAO,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC5D,QAAQ,GAAG,OAAO,CAAC;AACrB,CAAC;AAED,mGAAmG;AACnG,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,MAAM,GAAG,KAAK,CAAC;AACjB,CAAC;AAED,IAAI,QAAQ,GAAyB,IAAI,CAAC;AAC1C,IAAI,MAAM,GAAuB,IAAI,CAAC;AAEtC,oGAAoG;AACpG,oGAAoG;AACpG,mDAAmD;AACnD,SAAS,mBAAmB,CAAC,OAAsB;IACjD,OAAO;QACL,gBAAgB;YACd,IAAI,CAAC,OAAO,CAAC,UAAU;gBAAE,OAAO;YAChC,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC;YAC5B,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;gBAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACzE,CAAC;QACD,mBAAmB,CAAC,OAAO;YACzB,IAAI,CAAC,OAAO,CAAC,UAAU;gBAAE,OAAO;YAChC,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;gBAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACrF,CAAC;QACD,iBAAiB,CAAC,IAAI;YACpB,IAAI,CAAC,OAAO,CAAC,UAAU;gBAAE,OAAO;YAChC,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC;YAC9B,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;gBAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAChF,CAAC;QACD,iBAAiB;YACf,IAAI,CAAC,OAAO,CAAC,UAAU;gBAAE,OAAO;YAChC,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;gBAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC1E,CAAC;KACF,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,qEAAqE;AACrE,SAAS,eAAe,CAAC,IAAa;IACpC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC7D,OAAO,EAAE,IAAI,EAAE,IAAmB,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/socket",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.1047.858b9b6",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,9 +38,10 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/log": "0.5.0-next.1944.cd6b6e7",
42
- "@flighthq/signals": "0.5.0-next.1944.cd6b6e7",
43
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/entity": "0.5.1-edge.1047.858b9b6",
42
+ "@flighthq/log": "0.5.1-edge.1047.858b9b6",
43
+ "@flighthq/signals": "0.5.1-edge.1047.858b9b6",
44
+ "@flighthq/types": "0.5.1-edge.1047.858b9b6"
44
45
  },
45
46
  "devDependencies": {
46
47
  "typescript": "^5.3.0"
@@ -1,15 +1,8 @@
1
1
  import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
2
- import type { LogEntry, SocketBackend } from '@flighthq/types/contract';
2
+ import type { HasNetSocket, LogEntry, SocketBackend } from '@flighthq/types/contract';
3
3
 
4
4
  import { areSocketGuardsEnabled, disableSocketGuards, enableSocketGuards } from './enableSocketGuards';
5
- import {
6
- closeSocket,
7
- createSocket,
8
- disposeSocket,
9
- enableSocketSignals,
10
- sendSocketMessage,
11
- setSocketBackend,
12
- } from './socket';
5
+ import { closeSocket, createSocket, disposeSocket, enableSocketSignals, sendSocketMessage } from './socket';
13
6
 
14
7
  function captureLog(run: () => void): readonly LogEntry[] {
15
8
  const sink = createMemoryLogSink(8);
@@ -33,9 +26,12 @@ function backend(hasConnection: boolean): SocketBackend {
33
26
 
34
27
  afterEach(() => {
35
28
  disableSocketGuards();
36
- setSocketBackend(null);
37
29
  });
38
30
 
31
+ function hostOf(backend: SocketBackend): HasNetSocket {
32
+ return { net: { socket: backend } } as HasNetSocket;
33
+ }
34
+
39
35
  describe('areSocketGuardsEnabled', () => {
40
36
  it('reports whether the global guard hook is installed', () => {
41
37
  enableSocketGuards();
@@ -49,18 +45,18 @@ describe('disableSocketGuards', () => {
49
45
  it('restores silent core behavior', () => {
50
46
  enableSocketGuards();
51
47
  disableSocketGuards();
52
- setSocketBackend(backend(false));
53
- expect(captureLog(() => createSocket({ url: 'tcp://silent' }))).toEqual([]);
48
+ const host = hostOf(backend(false));
49
+ expect(captureLog(() => createSocket(host, { url: 'tcp://silent' }))).toEqual([]);
54
50
  });
55
51
  });
56
52
 
57
53
  describe('enableSocketGuards', () => {
58
54
  it('warns once when socket creation produces no connection', () => {
59
55
  enableSocketGuards();
60
- setSocketBackend(backend(false));
56
+ const host = hostOf(backend(false));
61
57
  const entries = captureLog(() => {
62
- createSocket({ url: 'tcp://unsupported' });
63
- createSocket({ url: 'tcp://unsupported-again' });
58
+ createSocket(host, { url: 'tcp://unsupported' });
59
+ createSocket(host, { url: 'tcp://unsupported-again' });
64
60
  });
65
61
  expect(entries).toHaveLength(1);
66
62
  expect(entries[0]).toMatchObject({ channel: 'socket' });
@@ -69,13 +65,13 @@ describe('enableSocketGuards', () => {
69
65
  reason: 'no-connection',
70
66
  url: 'tcp://unsupported',
71
67
  });
72
- expect(String((entries[0].data as Record<string, unknown>).message)).toContain('setSocketBackend');
68
+ expect(String((entries[0].data as Record<string, unknown>).message)).toContain('net.socket');
73
69
  });
74
70
 
75
71
  it('warns once per command issued after terminal disposal', () => {
76
72
  enableSocketGuards();
77
- setSocketBackend(backend(true));
78
- const socket = createSocket({ url: 'ws://disposed' });
73
+ const host = hostOf(backend(true));
74
+ const socket = createSocket(host, { url: 'ws://disposed' });
79
75
  disposeSocket(socket);
80
76
  const entries = captureLog(() => {
81
77
  closeSocket(socket);
@@ -97,8 +93,8 @@ describe('enableSocketGuards', () => {
97
93
 
98
94
  it('stays silent for ordinary pre-open sends and closes', () => {
99
95
  enableSocketGuards();
100
- setSocketBackend(backend(true));
101
- const socket = createSocket({ url: 'ws://connecting' });
96
+ const host = hostOf(backend(true));
97
+ const socket = createSocket(host, { url: 'ws://connecting' });
102
98
  const entries = captureLog(() => {
103
99
  sendSocketMessage(socket, 'early');
104
100
  closeSocket(socket);
@@ -1,9 +1,12 @@
1
- import type { SocketBackend, SocketEventSink } from '@flighthq/types/contract';
1
+ import type { HasNetSocket, SocketBackend, SocketEventSink } from '@flighthq/types/contract';
2
2
 
3
3
  import { explainSocketSendFailure } from './explainSocketSendFailure';
4
- import { createSocket, disposeSocket, sendSocketMessage, setSocketBackend } from './socket';
4
+ import { createSocket, disposeSocket, sendSocketMessage } from './socket';
5
5
 
6
- function installBackend(hasConnection: boolean, sendResult = true): () => SocketEventSink {
6
+ function installBackend(
7
+ hasConnection: boolean,
8
+ sendResult = true,
9
+ ): { host: HasNetSocket; sink: () => SocketEventSink } {
7
10
  let sink!: SocketEventSink;
8
11
  const backend: SocketBackend = {
9
12
  openSocket(_options, events) {
@@ -15,16 +18,17 @@ function installBackend(hasConnection: boolean, sendResult = true): () => Socket
15
18
  };
16
19
  },
17
20
  };
18
- setSocketBackend(backend);
19
- return () => sink;
21
+ return { host: hostOf(backend), sink: () => sink };
20
22
  }
21
23
 
22
- afterEach(() => setSocketBackend(null));
24
+ function hostOf(backend: SocketBackend): HasNetSocket {
25
+ return { net: { socket: backend } } as HasNetSocket;
26
+ }
23
27
 
24
28
  describe('explainSocketSendFailure', () => {
25
29
  it('identifies an unsupported backend with no connection', () => {
26
- installBackend(false);
27
- const socket = createSocket({ url: 'tcp://host' });
30
+ const { host } = installBackend(false);
31
+ const socket = createSocket(host, { url: 'tcp://host' });
28
32
  expect(explainSocketSendFailure(socket)).toEqual({
29
33
  reason: 'no-connection',
30
34
  readyState: 'connecting',
@@ -33,8 +37,8 @@ describe('explainSocketSendFailure', () => {
33
37
  });
34
38
 
35
39
  it('reports the current non-open phase when a connection exists', () => {
36
- installBackend(true);
37
- const socket = createSocket({ url: 'ws://host' });
40
+ const { host } = installBackend(true);
41
+ const socket = createSocket(host, { url: 'ws://host' });
38
42
  expect(explainSocketSendFailure(socket)).toEqual({
39
43
  reason: 'not-open',
40
44
  readyState: 'connecting',
@@ -43,16 +47,16 @@ describe('explainSocketSendFailure', () => {
43
47
  });
44
48
 
45
49
  it('returns null once the socket can reach its backend, even when that backend returns false', () => {
46
- const getSink = installBackend(true, false);
47
- const socket = createSocket({ url: 'ws://host' });
50
+ const { host, sink: getSink } = installBackend(true, false);
51
+ const socket = createSocket(host, { url: 'ws://host' });
48
52
  getSink().handleSocketOpen();
49
53
  expect(sendSocketMessage(socket, 'frame')).toBe(false);
50
54
  expect(explainSocketSendFailure(socket)).toBeNull();
51
55
  });
52
56
 
53
57
  it('distinguishes terminal disposal from other closed or disconnected states', () => {
54
- installBackend(true);
55
- const socket = createSocket({ url: 'ws://host' });
58
+ const { host } = installBackend(true);
59
+ const socket = createSocket(host, { url: 'ws://host' });
56
60
  disposeSocket(socket);
57
61
  expect(explainSocketSendFailure(socket)).toEqual({
58
62
  reason: 'disposed',
@@ -1,12 +1,16 @@
1
1
  import { connectSignal } from '@flighthq/signals/contract';
2
2
  import type {
3
+ HasNetSocket,
3
4
  SocketBackend,
4
5
  SocketCloseInfo,
5
6
  SocketConnection,
6
7
  SocketEventSink,
7
8
  SocketMessage,
9
+ TcpSocketConnection,
10
+ TcpSocketOptions,
8
11
  } from '@flighthq/types/contract';
9
12
 
13
+ import { openTcpSocket } from './index';
10
14
  import {
11
15
  attachSocket,
12
16
  closeSocket,
@@ -15,10 +19,9 @@ import {
15
19
  detachSocket,
16
20
  disposeSocket,
17
21
  enableSocketSignals,
18
- getSocketBackend,
19
22
  getSocketReadyState,
23
+ initializeWebSocketBackend,
20
24
  sendSocketMessage,
21
- setSocketBackend,
22
25
  setSocketGuard,
23
26
  } from './socket';
24
27
 
@@ -65,15 +68,26 @@ function fakeBackend(): FakeSocket {
65
68
  }
66
69
 
67
70
  afterEach(() => {
68
- setSocketBackend(null);
69
71
  setSocketGuard(null);
70
72
  });
71
73
 
74
+ function hostOf(backend: SocketBackend): HasNetSocket {
75
+ return { net: { socket: backend } } as HasNetSocket;
76
+ }
77
+
78
+ function tcpConnection(): TcpSocketConnection {
79
+ return {
80
+ readable: new ReadableStream<Uint8Array>(),
81
+ writable: new WritableStream<Uint8Array>(),
82
+ closeTcpSocketConnection(): void {},
83
+ };
84
+ }
85
+
72
86
  describe('attachSocket', () => {
73
87
  it('resumes delivery after a detach', () => {
74
88
  const fake = fakeBackend();
75
- setSocketBackend(fake.backend);
76
- const socket = createSocket({ url: 'ws://x' });
89
+ const host = hostOf(fake.backend);
90
+ const socket = createSocket(host, { url: 'ws://x' });
77
91
  const signals = enableSocketSignals(socket);
78
92
  let opens = 0;
79
93
  connectSignal(signals.onSocketOpen, () => opens++);
@@ -85,8 +99,8 @@ describe('attachSocket', () => {
85
99
 
86
100
  it('cannot resume delivery after terminal disposal', () => {
87
101
  const fake = fakeBackend();
88
- setSocketBackend(fake.backend);
89
- const socket = createSocket({ url: 'ws://x' });
102
+ const host = hostOf(fake.backend);
103
+ const socket = createSocket(host, { url: 'ws://x' });
90
104
  const signals = enableSocketSignals(socket);
91
105
  let opens = 0;
92
106
  connectSignal(signals.onSocketOpen, () => opens++);
@@ -101,8 +115,8 @@ describe('attachSocket', () => {
101
115
  describe('closeSocket', () => {
102
116
  it('transitions to closing and forwards code/reason to the connection', () => {
103
117
  const fake = fakeBackend();
104
- setSocketBackend(fake.backend);
105
- const socket = createSocket({ url: 'ws://x' });
118
+ const host = hostOf(fake.backend);
119
+ const socket = createSocket(host, { url: 'ws://x' });
106
120
  fake.sink.handleSocketOpen();
107
121
  closeSocket(socket, 1000, 'bye');
108
122
  expect(getSocketReadyState(socket)).toBe('closing');
@@ -111,8 +125,8 @@ describe('closeSocket', () => {
111
125
 
112
126
  it('reaches closed once the backend close event arrives', () => {
113
127
  const fake = fakeBackend();
114
- setSocketBackend(fake.backend);
115
- const socket = createSocket({ url: 'ws://x' });
128
+ const host = hostOf(fake.backend);
129
+ const socket = createSocket(host, { url: 'ws://x' });
116
130
  fake.sink.handleSocketOpen();
117
131
  closeSocket(socket);
118
132
  fake.sink.handleSocketClose({ code: 1000, reason: '', wasClean: true });
@@ -121,8 +135,8 @@ describe('closeSocket', () => {
121
135
 
122
136
  it('is a no-op when already closed', () => {
123
137
  const fake = fakeBackend();
124
- setSocketBackend(fake.backend);
125
- const socket = createSocket({ url: 'ws://x' });
138
+ const host = hostOf(fake.backend);
139
+ const socket = createSocket(host, { url: 'ws://x' });
126
140
  fake.sink.handleSocketOpen();
127
141
  closeSocket(socket);
128
142
  fake.sink.handleSocketClose({ code: 1000, reason: '', wasClean: true });
@@ -132,8 +146,8 @@ describe('closeSocket', () => {
132
146
 
133
147
  it('stays closed and does not close the connection again after disposal', () => {
134
148
  const fake = fakeBackend();
135
- setSocketBackend(fake.backend);
136
- const socket = createSocket({ url: 'ws://x' });
149
+ const host = hostOf(fake.backend);
150
+ const socket = createSocket(host, { url: 'ws://x' });
137
151
  disposeSocket(socket);
138
152
  closeSocket(socket);
139
153
  expect(getSocketReadyState(socket)).toBe('closed');
@@ -144,8 +158,8 @@ describe('closeSocket', () => {
144
158
  describe('createSocket', () => {
145
159
  it('opens through the backend in the connecting state and records the url', () => {
146
160
  const fake = fakeBackend();
147
- setSocketBackend(fake.backend);
148
- const socket = createSocket({ url: 'ws://host/path' });
161
+ const host = hostOf(fake.backend);
162
+ const socket = createSocket(host, { url: 'ws://host/path' });
149
163
  expect(socket.url).toBe('ws://host/path');
150
164
  expect(getSocketReadyState(socket)).toBe('connecting');
151
165
  expect(fake.lastOptions?.url).toBe('ws://host/path');
@@ -153,8 +167,8 @@ describe('createSocket', () => {
153
167
 
154
168
  it('passes protocols and binaryType through to the backend', () => {
155
169
  const fake = fakeBackend();
156
- setSocketBackend(fake.backend);
157
- createSocket({ url: 'ws://x', protocols: ['a', 'b'], binaryType: 'arraybuffer' });
170
+ const host = hostOf(fake.backend);
171
+ createSocket(host, { url: 'ws://x', protocols: ['a', 'b'], binaryType: 'arraybuffer' });
158
172
  expect(fake.lastOptions?.protocols).toEqual(['a', 'b']);
159
173
  expect(fake.lastOptions?.binaryType).toBe('arraybuffer');
160
174
  });
@@ -162,16 +176,16 @@ describe('createSocket', () => {
162
176
  it('tolerates a null connection from an unsupported transport', () => {
163
177
  const fake = fakeBackend();
164
178
  fake.openReturnsNull = true;
165
- setSocketBackend(fake.backend);
166
- const socket = createSocket({ url: 'tcp://x' });
179
+ const host = hostOf(fake.backend);
180
+ const socket = createSocket(host, { url: 'tcp://x' });
167
181
  expect(getSocketReadyState(socket)).toBe('connecting');
168
182
  expect(sendSocketMessage(socket, 'x')).toBe(false);
169
183
  });
170
184
 
171
185
  it('emits a text message with binary false and a binary message with binary true', () => {
172
186
  const fake = fakeBackend();
173
- setSocketBackend(fake.backend);
174
- const socket = createSocket({ url: 'ws://x' });
187
+ const host = hostOf(fake.backend);
188
+ const socket = createSocket(host, { url: 'ws://x' });
175
189
  const signals = enableSocketSignals(socket);
176
190
  const received: SocketMessage[] = [];
177
191
  connectSignal(signals.onSocketMessage, (m) => received.push(m));
@@ -187,8 +201,8 @@ describe('createSocket', () => {
187
201
 
188
202
  it('emits close info with code, reason, and wasClean', () => {
189
203
  const fake = fakeBackend();
190
- setSocketBackend(fake.backend);
191
- const socket = createSocket({ url: 'ws://x' });
204
+ const host = hostOf(fake.backend);
205
+ const socket = createSocket(host, { url: 'ws://x' });
192
206
  const signals = enableSocketSignals(socket);
193
207
  const infos: SocketCloseInfo[] = [];
194
208
  connectSignal(signals.onSocketClose, (i) => infos.push(i));
@@ -198,8 +212,8 @@ describe('createSocket', () => {
198
212
 
199
213
  it('emits onSocketError', () => {
200
214
  const fake = fakeBackend();
201
- setSocketBackend(fake.backend);
202
- const socket = createSocket({ url: 'ws://x' });
215
+ const host = hostOf(fake.backend);
216
+ const socket = createSocket(host, { url: 'ws://x' });
203
217
  const signals = enableSocketSignals(socket);
204
218
  let errors = 0;
205
219
  connectSignal(signals.onSocketError, () => errors++);
@@ -302,8 +316,8 @@ describe('createWebSocketBackend', () => {
302
316
  describe('detachSocket', () => {
303
317
  it('stops backend events from reaching the signals', () => {
304
318
  const fake = fakeBackend();
305
- setSocketBackend(fake.backend);
306
- const socket = createSocket({ url: 'ws://x' });
319
+ const host = hostOf(fake.backend);
320
+ const socket = createSocket(host, { url: 'ws://x' });
307
321
  const signals = enableSocketSignals(socket);
308
322
  let opens = 0;
309
323
  connectSignal(signals.onSocketOpen, () => opens++);
@@ -316,8 +330,8 @@ describe('detachSocket', () => {
316
330
  describe('disposeSocket', () => {
317
331
  it('closes an open connection and detaches so later events fire no signal', () => {
318
332
  const fake = fakeBackend();
319
- setSocketBackend(fake.backend);
320
- const socket = createSocket({ url: 'ws://x' });
333
+ const host = hostOf(fake.backend);
334
+ const socket = createSocket(host, { url: 'ws://x' });
321
335
  const signals = enableSocketSignals(socket);
322
336
  let messages = 0;
323
337
  connectSignal(signals.onSocketMessage, () => messages++);
@@ -332,8 +346,8 @@ describe('disposeSocket', () => {
332
346
 
333
347
  it('is safe to call on a fresh socket', () => {
334
348
  const fake = fakeBackend();
335
- setSocketBackend(fake.backend);
336
- const socket = createSocket({ url: 'ws://x' });
349
+ const host = hostOf(fake.backend);
350
+ const socket = createSocket(host, { url: 'ws://x' });
337
351
  expect(() => disposeSocket(socket)).not.toThrow();
338
352
  });
339
353
  });
@@ -341,22 +355,22 @@ describe('disposeSocket', () => {
341
355
  describe('enableSocketSignals', () => {
342
356
  it('returns the same group on repeated calls', () => {
343
357
  const fake = fakeBackend();
344
- setSocketBackend(fake.backend);
345
- const socket = createSocket({ url: 'ws://x' });
358
+ const host = hostOf(fake.backend);
359
+ const socket = createSocket(host, { url: 'ws://x' });
346
360
  expect(enableSocketSignals(socket)).toBe(enableSocketSignals(socket));
347
361
  });
348
362
 
349
363
  it('leaves a bare socket without signals', () => {
350
364
  const fake = fakeBackend();
351
- setSocketBackend(fake.backend);
352
- const socket = createSocket({ url: 'ws://x' });
365
+ const host = hostOf(fake.backend);
366
+ const socket = createSocket(host, { url: 'ws://x' });
353
367
  expect(socket.runtime.signals).toBeNull();
354
368
  });
355
369
 
356
370
  it('returns an inert stable group without reviving a disposed socket', () => {
357
371
  const fake = fakeBackend();
358
- setSocketBackend(fake.backend);
359
- const socket = createSocket({ url: 'ws://x' });
372
+ const host = hostOf(fake.backend);
373
+ const socket = createSocket(host, { url: 'ws://x' });
360
374
  disposeSocket(socket);
361
375
  const first = enableSocketSignals(socket);
362
376
  expect(enableSocketSignals(socket)).toBe(first);
@@ -365,23 +379,11 @@ describe('enableSocketSignals', () => {
365
379
  });
366
380
  });
367
381
 
368
- describe('getSocketBackend', () => {
369
- it('lazily returns a web backend by default', () => {
370
- expect(typeof getSocketBackend().openSocket).toBe('function');
371
- });
372
-
373
- it('returns the installed backend', () => {
374
- const fake = fakeBackend();
375
- setSocketBackend(fake.backend);
376
- expect(getSocketBackend()).toBe(fake.backend);
377
- });
378
- });
379
-
380
382
  describe('getSocketReadyState', () => {
381
383
  it('reflects connecting → open → closing → closed transitions', () => {
382
384
  const fake = fakeBackend();
383
- setSocketBackend(fake.backend);
384
- const socket = createSocket({ url: 'ws://x' });
385
+ const host = hostOf(fake.backend);
386
+ const socket = createSocket(host, { url: 'ws://x' });
385
387
  expect(getSocketReadyState(socket)).toBe('connecting');
386
388
  fake.sink.handleSocketOpen();
387
389
  expect(getSocketReadyState(socket)).toBe('open');
@@ -392,11 +394,54 @@ describe('getSocketReadyState', () => {
392
394
  });
393
395
  });
394
396
 
397
+ describe('initializeWebSocketBackend', () => {
398
+ it('is the construction initializer of createWebSocketBackend', () => {
399
+ expect(typeof initializeWebSocketBackend).toBe('function');
400
+ });
401
+ });
402
+
403
+ describe('openTcpSocket', () => {
404
+ it('forwards the endpoint unchanged and returns the backend connection by identity', () => {
405
+ const expected = tcpConnection();
406
+ const options: TcpSocketOptions = { host: 'db.internal', port: 5432 };
407
+ const openTcpSocketBackend = vi.fn(() => expected);
408
+ const backend: SocketBackend = {
409
+ openSocket: vi.fn(() => null),
410
+ openTcpSocket: openTcpSocketBackend,
411
+ };
412
+
413
+ expect(openTcpSocket(hostOf(backend), options)).toBe(expected);
414
+ expect(openTcpSocketBackend).toHaveBeenCalledWith(options);
415
+ });
416
+
417
+ it('returns null when the host carries no socket backend', () => {
418
+ const host = { net: {} } as HasNetSocket;
419
+ expect(openTcpSocket(host, { host: 'localhost', port: 9000 })).toBeNull();
420
+ });
421
+
422
+ it('returns null for the web backend without attempting framed openSocket', () => {
423
+ const backend = createWebSocketBackend();
424
+ const openSocket = vi.spyOn(backend, 'openSocket');
425
+
426
+ expect(openTcpSocket(hostOf(backend), { host: 'localhost', port: 9000 })).toBeNull();
427
+ expect(openSocket).not.toHaveBeenCalled();
428
+ });
429
+
430
+ it('preserves an explicit null from a backend that cannot open the endpoint', () => {
431
+ const backend: SocketBackend = {
432
+ openSocket: vi.fn(() => null),
433
+ openTcpSocket: vi.fn(() => null),
434
+ };
435
+
436
+ expect(openTcpSocket(hostOf(backend), { host: 'unreachable.internal', port: 22 })).toBeNull();
437
+ });
438
+ });
439
+
395
440
  describe('sendSocketMessage', () => {
396
441
  it('sends through the connection and returns true when open', () => {
397
442
  const fake = fakeBackend();
398
- setSocketBackend(fake.backend);
399
- const socket = createSocket({ url: 'ws://x' });
443
+ const host = hostOf(fake.backend);
444
+ const socket = createSocket(host, { url: 'ws://x' });
400
445
  fake.sink.handleSocketOpen();
401
446
  expect(sendSocketMessage(socket, 'ping')).toBe(true);
402
447
  expect(fake.sent).toEqual(['ping']);
@@ -404,16 +449,16 @@ describe('sendSocketMessage', () => {
404
449
 
405
450
  it('returns false without throwing when not open', () => {
406
451
  const fake = fakeBackend();
407
- setSocketBackend(fake.backend);
408
- const socket = createSocket({ url: 'ws://x' });
452
+ const host = hostOf(fake.backend);
453
+ const socket = createSocket(host, { url: 'ws://x' });
409
454
  expect(sendSocketMessage(socket, 'ping')).toBe(false);
410
455
  expect(fake.sent).toEqual([]);
411
456
  });
412
457
 
413
458
  it('returns false after disposal without reaching the released connection', () => {
414
459
  const fake = fakeBackend();
415
- setSocketBackend(fake.backend);
416
- const socket = createSocket({ url: 'ws://x' });
460
+ const host = hostOf(fake.backend);
461
+ const socket = createSocket(host, { url: 'ws://x' });
417
462
  fake.sink.handleSocketOpen();
418
463
  disposeSocket(socket);
419
464
  expect(sendSocketMessage(socket, 'ping')).toBe(false);
@@ -423,8 +468,8 @@ describe('sendSocketMessage', () => {
423
468
  it('returns false for an open state whose backend supplied no connection', () => {
424
469
  const fake = fakeBackend();
425
470
  fake.openReturnsNull = true;
426
- setSocketBackend(fake.backend);
427
- const socket = createSocket({ url: 'tcp://x' });
471
+ const host = hostOf(fake.backend);
472
+ const socket = createSocket(host, { url: 'tcp://x' });
428
473
  fake.sink.handleSocketOpen();
429
474
  expect(getSocketReadyState(socket)).toBe('open');
430
475
  expect(sendSocketMessage(socket, 'ping')).toBe(false);
@@ -433,8 +478,8 @@ describe('sendSocketMessage', () => {
433
478
  it('forwards binary data by identity and propagates false without mutating the readonly socket', () => {
434
479
  const fake = fakeBackend();
435
480
  fake.sendReturns = false;
436
- setSocketBackend(fake.backend);
437
- const socket = createSocket({ url: 'ws://x' });
481
+ const host = hostOf(fake.backend);
482
+ const socket = createSocket(host, { url: 'ws://x' });
438
483
  fake.sink.handleSocketOpen();
439
484
  Object.freeze(socket.runtime);
440
485
  Object.freeze(socket);
@@ -444,46 +489,6 @@ describe('sendSocketMessage', () => {
444
489
  });
445
490
  });
446
491
 
447
- describe('setSocketBackend', () => {
448
- it('restores the lazy web default when passed null', () => {
449
- const fake = fakeBackend();
450
- setSocketBackend(fake.backend);
451
- expect(getSocketBackend()).toBe(fake.backend);
452
- setSocketBackend(null);
453
- const web = getSocketBackend();
454
- expect(web).not.toBe(fake.backend);
455
- expect(typeof web.openSocket).toBe('function');
456
- });
457
- });
458
-
459
- describe('setSocketGuard', () => {
460
- it('installs and removes the core notice hook', () => {
461
- const notices: string[] = [];
462
- setSocketGuard((notice) => notices.push(`${notice.operation}:${notice.reason}`));
463
- const unsupported = fakeBackend();
464
- unsupported.openReturnsNull = true;
465
- setSocketBackend(unsupported.backend);
466
- createSocket({ url: 'tcp://x' });
467
-
468
- const supported = fakeBackend();
469
- setSocketBackend(supported.backend);
470
- const socket = createSocket({ url: 'ws://x' });
471
- disposeSocket(socket);
472
- closeSocket(socket);
473
- sendSocketMessage(socket, 'x');
474
- enableSocketSignals(socket);
475
- setSocketGuard(null);
476
- closeSocket(socket);
477
-
478
- expect(notices).toEqual([
479
- 'createSocket:no-connection',
480
- 'closeSocket:disposed',
481
- 'sendSocketMessage:disposed',
482
- 'enableSocketSignals:disposed',
483
- ]);
484
- });
485
- });
486
-
487
492
  function noopSink(): SocketEventSink {
488
493
  return {
489
494
  handleSocketOpen() {},
@@ -540,3 +545,29 @@ function installFakeWebSocket(): () => void {
540
545
  (globalThis as { WebSocket?: unknown }).WebSocket = original;
541
546
  };
542
547
  }
548
+ describe('setSocketGuard', () => {
549
+ it('installs and removes the core notice hook', () => {
550
+ const notices: string[] = [];
551
+ setSocketGuard((notice) => notices.push(`${notice.operation}:${notice.reason}`));
552
+ const unsupported = fakeBackend();
553
+ unsupported.openReturnsNull = true;
554
+ const host = hostOf(unsupported.backend);
555
+ createSocket(host, { url: 'tcp://x' });
556
+
557
+ const supported = fakeBackend();
558
+ const socket = createSocket(hostOf(supported.backend), { url: 'ws://x' });
559
+ disposeSocket(socket);
560
+ closeSocket(socket);
561
+ sendSocketMessage(socket, 'x');
562
+ enableSocketSignals(socket);
563
+ setSocketGuard(null);
564
+ closeSocket(socket);
565
+
566
+ expect(notices).toEqual([
567
+ 'createSocket:no-connection',
568
+ 'closeSocket:disposed',
569
+ 'sendSocketMessage:disposed',
570
+ 'enableSocketSignals:disposed',
571
+ ]);
572
+ });
573
+ });
@@ -0,0 +1,57 @@
1
+ import type { HasNetSocket, SocketBackend, SocketConnection, SocketEventSink } from '@flighthq/types/contract';
2
+
3
+ import { createSocket, getSocketReadyState } from './socket';
4
+
5
+ function recordingBackend(label: string, opened: string[]): SocketBackend {
6
+ return {
7
+ openSocket(_options, events: SocketEventSink): SocketConnection | null {
8
+ opened.push(label);
9
+ return {
10
+ closeSocketConnection(): void {},
11
+ sendSocketFrame(): boolean {
12
+ events.handleSocketOpen();
13
+ return true;
14
+ },
15
+ };
16
+ },
17
+ };
18
+ }
19
+
20
+ function hostWith(backend: SocketBackend | undefined): HasNetSocket {
21
+ return { net: backend === undefined ? {} : { socket: backend } } as HasNetSocket;
22
+ }
23
+
24
+ describe('createSocket', () => {
25
+ it('opens through the provider carried by the host it is given', () => {
26
+ const opened: string[] = [];
27
+ createSocket(hostWith(recordingBackend('a', opened)), { url: 'wss://example.test' });
28
+ expect(opened).toEqual(['a']);
29
+ });
30
+
31
+ // ★ HOST ISOLATION. Two hosts, two providers, two answers. Under the ambient resolver one
32
+ // process-wide backend answered for every caller, so the second host was unreachable no matter what
33
+ // it carried — this is the property the migration exists to create.
34
+ it('keeps two hosts independent', () => {
35
+ const opened: string[] = [];
36
+ createSocket(hostWith(recordingBackend('first', opened)), { url: 'wss://one.test' });
37
+ createSocket(hostWith(recordingBackend('second', opened)), { url: 'wss://two.test' });
38
+ expect(opened).toEqual(['first', 'second']);
39
+ });
40
+
41
+ // ★ NO AMBIENT FALLBACK. A host that carries no socket provider must yield no connection rather than
42
+ // quietly reaching a process-global web backend. The socket stays 'connecting', which is the
43
+ // documented shape for a backend that cannot open the transport.
44
+ it('yields no connection when the host carries no socket provider', () => {
45
+ const socket = createSocket(hostWith(undefined), { url: 'wss://absent.test' });
46
+ expect(socket.runtime.connection).toBeNull();
47
+ expect(getSocketReadyState(socket)).toBe('connecting');
48
+ });
49
+
50
+ it('does not consult one host provider when another host is passed', () => {
51
+ const opened: string[] = [];
52
+ const unused = recordingBackend('unused', opened);
53
+ createSocket(hostWith(recordingBackend('used', opened)), { url: 'wss://x.test' });
54
+ expect(opened).not.toContain('unused');
55
+ expect(unused).toBeDefined();
56
+ });
57
+ });