transport-io 0.0.1 → 0.1.0

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
@@ -1,4 +1,9 @@
1
- # transport-io
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/transport-io/transport-io/main/assets/brand/transport-io-lockup-bone.svg">
4
+ <img alt="transport-io" src="https://raw.githubusercontent.com/transport-io/transport-io/main/assets/brand/transport-io-lockup-ink.svg" width="340">
5
+ </picture>
6
+ </p>
2
7
 
3
8
  Real-time apps over WebTransport. Socket.IO's shape, on a transport with multiple streams
4
9
  and datagrams, without Socket.IO's mistakes.
@@ -7,13 +12,15 @@ Framing, length prefixes, buffer accumulation, stream lifecycle and backpressure
7
12
  hidden. Reliability is not: an event declares `stream` or `datagram` in the contract, and
8
13
  "this message may be dropped" is a property of your data that lives in the type system.
9
14
 
10
- **Full documentation, and the limitations you should read before installing, are in the
11
- [repository README](https://github.com/v0id-user/transport-io#readme).** The short version:
15
+ **Read [KNOWN-ISSUES.md](https://github.com/transport-io/transport-io/blob/main/KNOWN-ISSUES.md)
16
+ before you start.** It is what this library refuses to do and will not change, plus the one
17
+ measured defect. Full documentation is in the
18
+ [repository README](https://github.com/transport-io/transport-io#readme). The short version:
12
19
 
13
- - **WebTransport only.** No WebSocket fallback, deliberately a fallback would silently
20
+ - **WebTransport only.** No WebSocket fallback, deliberately - a fallback would silently
14
21
  make the datagram lane reliable and ordered, which is a lie about your data.
15
22
  - **Chrome and Firefox.** Safari cannot talk to a quiche-backed server and is unsupported.
16
- - **The server needs a separate native install**, and its Linux prebuild needs glibc 2.38
23
+ - **The server needs a separate native install**, and its Linux prebuild needs glibc 2.38 -
17
24
  no default Node `-slim` image has it, and Alpine has no prebuild at all.
18
25
  - **Each `call()` leaks ~5.95 KB of server memory**, upstream in the QUIC binding, not in
19
26
  this library. `emit` and datagrams are flat.
@@ -23,6 +30,6 @@ hidden. Reliability is not: an event declares `stream` or `datagram` in the cont
23
30
  npm install transport-io
24
31
  ```
25
32
 
26
- A git install does not work the repository root is a private monorepo package.
33
+ A git install does not work - the repository root is a private monorepo package.
27
34
 
28
35
  MIT © #V0ID
package/dist/adapter.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Rules core obeys (D40): every method is async even in memory, frames cross as bytes and
6
6
  * never as live objects, `PeerId` is a stable cross-process string, no node assumes it
7
7
  * knows a room's full membership, a frame for a room with no local members is dropped
8
- * silently rather than erroring, and any method may reject core degrades rather than
8
+ * silently rather than erroring, and any method may reject - core degrades rather than
9
9
  * crashing.
10
10
  */
11
11
  export type PeerId = string;
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Client surface.
3
3
  *
4
4
  * Constructible without I/O: nothing here touches `window` or `WebTransport` at module
5
- * scope, so importing this on a server which Next.js will do is safe. Feature
5
+ * scope, so importing this on a server - which Next.js will do - is safe. Feature
6
6
  * detection happens inside connect().
7
7
  */
8
8
  import { type AnyMap, type CallableOf, type Contract } from './contract.ts';
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Client surface.
3
3
  *
4
4
  * Constructible without I/O: nothing here touches `window` or `WebTransport` at module
5
- * scope, so importing this on a server which Next.js will do is safe. Feature
5
+ * scope, so importing this on a server - which Next.js will do - is safe. Feature
6
6
  * detection happens inside connect().
7
7
  */
8
8
  import { buildEventTable } from './contract.js';
package/dist/codec.js CHANGED
@@ -1,4 +1,4 @@
1
- /** JSON over UTF-8. PROTOCOL.md §5.3 codec 0x01, the only one this version speaks. */
1
+ /** JSON over UTF-8. PROTOCOL.md §5.3 - codec 0x01, the only one this version speaks. */
2
2
  import { TransportError } from './errors.js';
3
3
  const encoder = new TextEncoder();
4
4
  const decoder = new TextDecoder('utf-8', { fatal: true });
@@ -12,7 +12,7 @@ export type Schema = StandardSchemaV1;
12
12
  * property checking against a *union* admits any property present on any member, so
13
13
  * `{ lane: 'datagram', payload, returns }` compiled happily, `CallableOf` admitted it, and
14
14
  * `call()` served it over a bidirectional stream. A contract that says "may be dropped"
15
- * produced a guaranteed ordered message with the type system agreeing a direct violation
15
+ * produced a guaranteed ordered message with the type system agreeing - a direct violation
16
16
  * of D1, the first decision this project made.
17
17
  */
18
18
  export type EventDef = {
@@ -30,7 +30,7 @@ export type Contract = Readonly<Record<string, EventDef>>;
30
30
  export type Infer<S extends StandardTypedV1> = StandardTypedV1.InferOutput<S>;
31
31
  /**
32
32
  * The plain payload/returns map every public signature is written against, so that no
33
- * method hover ever has to print a validator's internal types. See D57 declaring
33
+ * method hover ever has to print a validator's internal types. See D57 - declaring
34
34
  * `interface AppMap extends MapOf<typeof contract> {}` is what keeps hover at 126
35
35
  * characters instead of 303.
36
36
  */
@@ -68,7 +68,7 @@ export interface EventTable {
68
68
  wire(): readonly [string, number, Lane][];
69
69
  }
70
70
  /**
71
- * PROTOCOL.md §5.4 the first four bytes of SHA-256 of the event name, big-endian.
71
+ * PROTOCOL.md §5.4 - the first four bytes of SHA-256 of the event name, big-endian.
72
72
  *
73
73
  * Async because `crypto.subtle` is, and because writing SHA-256 by hand would mean typing
74
74
  * its round constants from memory, which is the one thing D58 forbids. Both `connect()`
package/dist/contract.js CHANGED
@@ -14,7 +14,7 @@ export function type$() {
14
14
  };
15
15
  }
16
16
  /**
17
- * PROTOCOL.md §5.4 the first four bytes of SHA-256 of the event name, big-endian.
17
+ * PROTOCOL.md §5.4 - the first four bytes of SHA-256 of the event name, big-endian.
18
18
  *
19
19
  * Async because `crypto.subtle` is, and because writing SHA-256 by hand would mean typing
20
20
  * its round constants from memory, which is the one thing D58 forbids. Both `connect()`
package/dist/datagram.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Datagram header. PROTOCOL.md §7.1 codec, event id, origin, sequence, then payload. */
1
+ /** Datagram header. PROTOCOL.md §7.1 - codec, event id, origin, sequence, then payload. */
2
2
  import { TransportError } from './errors.js';
3
3
  import { Codec, DATAGRAM_CONSERVATIVE_FLOOR, DATAGRAM_HEADER_BYTES, EVENT_ID_NOT_APPLICABLE, } from './protocol.js';
4
4
  /**
package/dist/errors.js CHANGED
@@ -2,7 +2,7 @@ export class TransportError extends Error {
2
2
  code;
3
3
  remedy;
4
4
  constructor(code, message, remedy) {
5
- super(`${code}: ${message} ${remedy}`);
5
+ super(`${code}: ${message} - ${remedy}`);
6
6
  this.name = 'TransportError';
7
7
  this.code = code;
8
8
  this.remedy = remedy;
package/dist/framer.d.ts CHANGED
@@ -5,12 +5,12 @@ export interface Frame {
5
5
  readonly eventId: number;
6
6
  readonly payload: Uint8Array;
7
7
  }
8
- /** §5.1 the cap is per frame type: a call is the documented home for a large payload. */
8
+ /** §5.1 - the cap is per frame type: a call is the documented home for a large payload. */
9
9
  export declare function maxPayloadFor(type: FrameType): number;
10
10
  export declare function encodeFrame(frame: Frame): Uint8Array;
11
11
  /**
12
- * Incremental decoder. Feed it whatever the transport hands you a fragment, several
13
- * frames, or a frame split across many reads and it yields whole frames only.
12
+ * Incremental decoder. Feed it whatever the transport hands you - a fragment, several
13
+ * frames, or a frame split across many reads - and it yields whole frames only.
14
14
  */
15
15
  export declare class FrameDecoder {
16
16
  #private;
package/dist/framer.js CHANGED
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * QUIC streams are byte streams and do not preserve write boundaries. Measured on the
5
5
  * reference transport, 51 writes arrived as 217 reads, and the large write fragmented
6
- * while the small ones happened to survive which is the worst case, because naive
6
+ * while the small ones happened to survive - which is the worst case, because naive
7
7
  * boundary-trusting code passes in development and fails under load. The length prefix
8
8
  * is the only thing that recovers frame boundaries, and nobody using this library should
9
9
  * ever have to think about it.
10
10
  */
11
11
  import { TransportError } from './errors.js';
12
12
  import { Codec, FrameType, isFrameType, LENGTH_PREFIX_BYTES, MAX_CALL_PAYLOAD_BYTES, MAX_EMIT_PAYLOAD_BYTES, MIN_LENGTH, STREAM_HEADER_BYTES, } from './protocol.js';
13
- /** §5.1 the cap is per frame type: a call is the documented home for a large payload. */
13
+ /** §5.1 - the cap is per frame type: a call is the documented home for a large payload. */
14
14
  export function maxPayloadFor(type) {
15
15
  return type === FrameType.CALL_REQUEST || type === FrameType.CALL_RESPONSE
16
16
  ? MAX_CALL_PAYLOAD_BYTES
@@ -36,8 +36,8 @@ export function encodeFrame(frame) {
36
36
  return out;
37
37
  }
38
38
  /**
39
- * Incremental decoder. Feed it whatever the transport hands you a fragment, several
40
- * frames, or a frame split across many reads and it yields whole frames only.
39
+ * Incremental decoder. Feed it whatever the transport hands you - a fragment, several
40
+ * frames, or a frame split across many reads - and it yields whole frames only.
41
41
  */
42
42
  export class FrameDecoder {
43
43
  #buf = new Uint8Array(0);
@@ -63,8 +63,8 @@ export class FrameDecoder {
63
63
  }
64
64
  const payloadLength = length - STREAM_HEADER_BYTES;
65
65
  // §5.3 gives calls 16 MiB and everything else 1 MiB. This enforced the call cap for
66
- // every frame type, so a peer could declare 16 MiB on the emit lane sixteen times
67
- // its documented cap and the decoder would buffer toward it.
66
+ // every frame type, so a peer could declare 16 MiB on the emit lane - sixteen times
67
+ // its documented cap - and the decoder would buffer toward it.
68
68
  //
69
69
  // The type byte is at offset 4, so it is readable as soon as five bytes are, which is
70
70
  // before any payload has to be held. Until then the universal cap applies, which
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Handshake. PROTOCOL.md §4 frame 0 of the emit stream.
2
+ * Handshake. PROTOCOL.md §4 - frame 0 of the emit stream.
3
3
  *
4
4
  * Being frame 0 of a stream that already exists is what removes the early-traffic race:
5
5
  * in-order delivery within a stream means nothing can arrive before it. There is no
package/dist/handshake.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Handshake. PROTOCOL.md §4 frame 0 of the emit stream.
2
+ * Handshake. PROTOCOL.md §4 - frame 0 of the emit stream.
3
3
  *
4
4
  * Being frame 0 of a stream that already exists is what removes the early-traffic race:
5
5
  * in-order delivery within a stream means nothing can arrive before it. There is no
package/dist/hub.d.ts CHANGED
@@ -26,7 +26,7 @@ export declare class Hub {
26
26
  * Teardown runs to completion whatever the bus does.
27
27
  *
28
28
  * `broadcast` already wrapped its adapter call; this did not, so a rejection on the
29
- * first room threw straight out of the loop rooms 2..N kept their `Member` record,
29
+ * first room threw straight out of the loop - rooms 2..N kept their `Member` record,
30
30
  * each holding a live Session, and `#peerRooms.delete(id)` never ran. Nothing retries,
31
31
  * because `conn.closed` resolves exactly once. A later `to(room).emit()` then fanned
32
32
  * frames into a session that was already gone.
package/dist/hub.js CHANGED
@@ -13,7 +13,7 @@ export class Hub {
13
13
  this.#adapter = adapter;
14
14
  this.#table = table;
15
15
  // A node receiving its own publish back is normal, so core dedupes by origin node
16
- // rather than relying on the adapter to suppress it and dedupes against the
16
+ // rather than relying on the adapter to suppress it - and dedupes against the
17
17
  // *adapter's* id, which is the one stamped into the envelope. It used to compare
18
18
  // against the Server's separate `nodeId`, so any deployment where those differed
19
19
  // delivered every local broadcast twice, in silence.
@@ -30,7 +30,7 @@ export class Hub {
30
30
  // the emit path's swallowing catch, so nothing surfaced, and the teardown that would
31
31
  // have removed it had already run.
32
32
  if (session.disposed) {
33
- throw new TransportError('WT_SESSION_CLOSED', `peer ${id} disconnected before it could join '${room}'`, 'Check the peer is still connected after any await, or ignore this it is routine.');
33
+ throw new TransportError('WT_SESSION_CLOSED', `peer ${id} disconnected before it could join '${room}'`, 'Check the peer is still connected after any await, or ignore this - it is routine.');
34
34
  }
35
35
  let members = this.#rooms.get(room);
36
36
  if (members === undefined) {
@@ -40,7 +40,7 @@ export class Hub {
40
40
  // The bus first, local state second. Mutating before the await left a rejected join
41
41
  // half-applied: the hub fanned broadcasts to a peer the bus had no record of, and the
42
42
  // client was never notified it had joined. For a room whose join is gated on
43
- // authorization, that is traffic reaching someone who was refused permanently,
43
+ // authorization, that is traffic reaching someone who was refused - permanently,
44
44
  // because nothing rolls it back and nothing retries.
45
45
  await this.#adapter.join(room, id);
46
46
  members.set(id, { id, session });
@@ -69,7 +69,7 @@ export class Hub {
69
69
  * Teardown runs to completion whatever the bus does.
70
70
  *
71
71
  * `broadcast` already wrapped its adapter call; this did not, so a rejection on the
72
- * first room threw straight out of the loop rooms 2..N kept their `Member` record,
72
+ * first room threw straight out of the loop - rooms 2..N kept their `Member` record,
73
73
  * each holding a live Session, and `#peerRooms.delete(id)` never ran. Nothing retries,
74
74
  * because `conn.closed` resolves exactly once. A later `to(room).emit()` then fanned
75
75
  * frames into a session that was already gone.
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export type { SessionStats } from './session.ts';
12
12
  * `isolatedDeclarations` forbids inferring it from an import and a version that drifts from
13
13
  * its manifest is a lie in the one place a user checks first.
14
14
  *
15
- * `changeset version` moves the manifest, so this moves in the same commit which the test
15
+ * `changeset version` moves the manifest, so this moves in the same commit - which the test
16
16
  * enforces rather than trusting anyone to remember.
17
17
  */
18
18
  export declare const VERSION: string;
package/dist/index.js CHANGED
@@ -11,8 +11,8 @@ export { createServer, Server, } from './server.js';
11
11
  * `isolatedDeclarations` forbids inferring it from an import and a version that drifts from
12
12
  * its manifest is a lie in the one place a user checks first.
13
13
  *
14
- * `changeset version` moves the manifest, so this moves in the same commit which the test
14
+ * `changeset version` moves the manifest, so this moves in the same commit - which the test
15
15
  * enforces rather than trusting anyone to remember.
16
16
  */
17
- export const VERSION = '0.0.1';
17
+ export const VERSION = '0.1.0';
18
18
  //# sourceMappingURL=index.js.map
package/dist/origin.d.ts CHANGED
@@ -3,7 +3,7 @@ export declare class OriginAllocator {
3
3
  /**
4
4
  * `counterSpace` exists so the exhaustion branch can be reached in a test. Exhausting the
5
5
  * real space means 4,194,304 allocations, which is why §7.3's "MUST refuse new sessions"
6
- * went unproven and an unreachable branch in an allocator is exactly the kind of code
6
+ * went unproven - and an unreachable branch in an allocator is exactly the kind of code
7
7
  * that is wrong the first time it runs. Production never passes it.
8
8
  */
9
9
  constructor(hostOrdinal?: number, quarantineMs?: number, counterSpace?: number);
package/dist/origin.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * where a collision is close to undebuggable, since two peers would share a sequence space
6
6
  * and each would silently discard the other's datagrams as stale.
7
7
  *
8
- * Quarantined, not retired: never reusing turns the counter into a clock 2^22 values at
9
- * 100 sessions per second exhausts in under twelve hours so a busy host would stop
8
+ * Quarantined, not retired: never reusing turns the counter into a clock - 2^22 values at
9
+ * 100 sessions per second exhausts in under twelve hours - so a busy host would stop
10
10
  * accepting sessions and need a restart.
11
11
  */
12
12
  import { TransportError } from './errors.js';
@@ -23,7 +23,7 @@ export class OriginAllocator {
23
23
  /**
24
24
  * `counterSpace` exists so the exhaustion branch can be reached in a test. Exhausting the
25
25
  * real space means 4,194,304 allocations, which is why §7.3's "MUST refuse new sessions"
26
- * went unproven and an unreachable branch in an allocator is exactly the kind of code
26
+ * went unproven - and an unreachable branch in an allocator is exactly the kind of code
27
27
  * that is wrong the first time it runs. Production never passes it.
28
28
  */
29
29
  constructor(hostOrdinal = 0, quarantineMs = ORIGIN_QUARANTINE_MS, counterSpace = COUNTER_SPACE) {
@@ -5,7 +5,7 @@
5
5
  * these values, so a document and an implementation cannot drift. Change one and the
6
6
  * build fails until you change the other.
7
7
  */
8
- /** PROTOCOL.md §4.2 Stage 0 requires exact equality and refuses otherwise. */
8
+ /** PROTOCOL.md §4.2 - Stage 0 requires exact equality and refuses otherwise. */
9
9
  export declare const PROTOCOL_VERSION = 0;
10
10
  /** PROTOCOL.md §5.2 */
11
11
  export declare const FrameType: {
@@ -24,9 +24,9 @@ export declare const Codec: {
24
24
  readonly JSON: 1;
25
25
  };
26
26
  export type Codec = (typeof Codec)[keyof typeof Codec];
27
- /** PROTOCOL.md §5.4 used by frames whose meaning comes from the stream, not the table. */
27
+ /** PROTOCOL.md §5.4 - used by frames whose meaning comes from the stream, not the table. */
28
28
  export declare const EVENT_ID_NOT_APPLICABLE = 0;
29
- /** PROTOCOL.md §5.1 stream frame field budget. */
29
+ /** PROTOCOL.md §5.1 - stream frame field budget. */
30
30
  export declare const LENGTH_PREFIX_BYTES = 4;
31
31
  export declare const STREAM_HEADER_BYTES = 8;
32
32
  export declare const STREAM_FRAME_OVERHEAD_BYTES: number;
@@ -34,31 +34,31 @@ export declare const STREAM_FRAME_OVERHEAD_BYTES: number;
34
34
  export declare const MIN_LENGTH: number;
35
35
  export declare const MAX_EMIT_PAYLOAD_BYTES = 1048576;
36
36
  export declare const MAX_CALL_PAYLOAD_BYTES = 16777216;
37
- /** PROTOCOL.md §7.2 datagram field budget. */
37
+ /** PROTOCOL.md §7.2 - datagram field budget. */
38
38
  export declare const DATAGRAM_HEADER_BYTES = 13;
39
- /** PROTOCOL.md §7.4 used only when the transport reports nothing usable. */
39
+ /** PROTOCOL.md §7.4 - used only when the transport reports nothing usable. */
40
40
  export declare const DATAGRAM_CONSERVATIVE_FLOOR = 1024;
41
41
  export declare const DATAGRAM_CONSERVATIVE_PAYLOAD_MAX: number;
42
- /** PROTOCOL.md §9 per-peer bounds. Numbers, not adjectives. */
42
+ /** PROTOCOL.md §9 - per-peer bounds. Numbers, not adjectives. */
43
43
  export declare const DATAGRAM_QUEUE_MAX = 64;
44
44
  export declare const EMIT_QUEUE_MAX = 256;
45
- /** PROTOCOL.md §10.1 code 9 a rejected open resets that stream, never the session. */
45
+ /** PROTOCOL.md §10.1 code 9 - a rejected open resets that stream, never the session. */
46
46
  export declare const MAX_CONCURRENT_CALL_STREAMS = 256;
47
47
  /** Checked at dequeue, not enqueue: overflow handles a burst, TTL handles a stall. */
48
48
  export declare const DATAGRAM_TTL_MS = 150;
49
- /** PROTOCOL.md §7.3 a receiver discards (origin, event) sequence state after this idle. */
49
+ /** PROTOCOL.md §7.3 - a receiver discards (origin, event) sequence state after this idle. */
50
50
  export declare const SEQUENCE_STATE_RETENTION_MS = 60000;
51
- /** PROTOCOL.md §7.3 a released Origin waits this long before returning to the pool. */
51
+ /** PROTOCOL.md §7.3 - a released Origin waits this long before returning to the pool. */
52
52
  export declare const ORIGIN_QUARANTINE_MS = 120000;
53
- /** PROTOCOL.md §7.3 a departed host's ordinal waits this long before reallocation. */
53
+ /** PROTOCOL.md §7.3 - a departed host's ordinal waits this long before reallocation. */
54
54
  export declare const HOST_ORDINAL_QUARANTINE_MS = 300000;
55
- /** PROTOCOL.md §7.3 stated ceiling on concurrent session hosts. */
55
+ /** PROTOCOL.md §7.3 - stated ceiling on concurrent session hosts. */
56
56
  export declare const MAX_SESSION_HOSTS = 1024;
57
57
  /** PROTOCOL.md §4.1 */
58
58
  export declare const HANDSHAKE_DEADLINE_MS = 5000;
59
59
  /** PROTOCOL.md §10.2 */
60
60
  export declare const CLOSE_REASON_MAX_BYTES = 1024;
61
- /** PROTOCOL.md §10.1 one byte, because the browser API clamps stream codes to an octet. */
61
+ /** PROTOCOL.md §10.1 - one byte, because the browser API clamps stream codes to an octet. */
62
62
  export declare const ResetCode: {
63
63
  readonly WT_NO_ERROR: 0;
64
64
  readonly WT_ABORTED: 1;
package/dist/protocol.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * these values, so a document and an implementation cannot drift. Change one and the
6
6
  * build fails until you change the other.
7
7
  */
8
- /** PROTOCOL.md §4.2 Stage 0 requires exact equality and refuses otherwise. */
8
+ /** PROTOCOL.md §4.2 - Stage 0 requires exact equality and refuses otherwise. */
9
9
  export const PROTOCOL_VERSION = 0;
10
10
  /** PROTOCOL.md §5.2 */
11
11
  export const FrameType = {
@@ -23,9 +23,9 @@ export function isFrameType(v) {
23
23
  }
24
24
  /** PROTOCOL.md §5.3. `0x00` is permanently reserved so a zero-filled buffer cannot parse. */
25
25
  export const Codec = { JSON: 0x01 };
26
- /** PROTOCOL.md §5.4 used by frames whose meaning comes from the stream, not the table. */
26
+ /** PROTOCOL.md §5.4 - used by frames whose meaning comes from the stream, not the table. */
27
27
  export const EVENT_ID_NOT_APPLICABLE = 0x00000000;
28
- /** PROTOCOL.md §5.1 stream frame field budget. */
28
+ /** PROTOCOL.md §5.1 - stream frame field budget. */
29
29
  export const LENGTH_PREFIX_BYTES = 4;
30
30
  export const STREAM_HEADER_BYTES = 8;
31
31
  export const STREAM_FRAME_OVERHEAD_BYTES = LENGTH_PREFIX_BYTES + STREAM_HEADER_BYTES;
@@ -33,31 +33,31 @@ export const STREAM_FRAME_OVERHEAD_BYTES = LENGTH_PREFIX_BYTES + STREAM_HEADER_B
33
33
  export const MIN_LENGTH = STREAM_HEADER_BYTES + 1;
34
34
  export const MAX_EMIT_PAYLOAD_BYTES = 1_048_576;
35
35
  export const MAX_CALL_PAYLOAD_BYTES = 16_777_216;
36
- /** PROTOCOL.md §7.2 datagram field budget. */
36
+ /** PROTOCOL.md §7.2 - datagram field budget. */
37
37
  export const DATAGRAM_HEADER_BYTES = 13;
38
- /** PROTOCOL.md §7.4 used only when the transport reports nothing usable. */
38
+ /** PROTOCOL.md §7.4 - used only when the transport reports nothing usable. */
39
39
  export const DATAGRAM_CONSERVATIVE_FLOOR = 1024;
40
40
  export const DATAGRAM_CONSERVATIVE_PAYLOAD_MAX = DATAGRAM_CONSERVATIVE_FLOOR - DATAGRAM_HEADER_BYTES;
41
- /** PROTOCOL.md §9 per-peer bounds. Numbers, not adjectives. */
41
+ /** PROTOCOL.md §9 - per-peer bounds. Numbers, not adjectives. */
42
42
  export const DATAGRAM_QUEUE_MAX = 64;
43
43
  export const EMIT_QUEUE_MAX = 256;
44
- /** PROTOCOL.md §10.1 code 9 a rejected open resets that stream, never the session. */
44
+ /** PROTOCOL.md §10.1 code 9 - a rejected open resets that stream, never the session. */
45
45
  export const MAX_CONCURRENT_CALL_STREAMS = 256;
46
46
  /** Checked at dequeue, not enqueue: overflow handles a burst, TTL handles a stall. */
47
47
  export const DATAGRAM_TTL_MS = 150;
48
- /** PROTOCOL.md §7.3 a receiver discards (origin, event) sequence state after this idle. */
48
+ /** PROTOCOL.md §7.3 - a receiver discards (origin, event) sequence state after this idle. */
49
49
  export const SEQUENCE_STATE_RETENTION_MS = 60_000;
50
- /** PROTOCOL.md §7.3 a released Origin waits this long before returning to the pool. */
50
+ /** PROTOCOL.md §7.3 - a released Origin waits this long before returning to the pool. */
51
51
  export const ORIGIN_QUARANTINE_MS = 120_000;
52
- /** PROTOCOL.md §7.3 a departed host's ordinal waits this long before reallocation. */
52
+ /** PROTOCOL.md §7.3 - a departed host's ordinal waits this long before reallocation. */
53
53
  export const HOST_ORDINAL_QUARANTINE_MS = 300_000;
54
- /** PROTOCOL.md §7.3 stated ceiling on concurrent session hosts. */
54
+ /** PROTOCOL.md §7.3 - stated ceiling on concurrent session hosts. */
55
55
  export const MAX_SESSION_HOSTS = 1024;
56
56
  /** PROTOCOL.md §4.1 */
57
57
  export const HANDSHAKE_DEADLINE_MS = 5000;
58
58
  /** PROTOCOL.md §10.2 */
59
59
  export const CLOSE_REASON_MAX_BYTES = 1024;
60
- /** PROTOCOL.md §10.1 one byte, because the browser API clamps stream codes to an octet. */
60
+ /** PROTOCOL.md §10.1 - one byte, because the browser API clamps stream codes to an octet. */
61
61
  export const ResetCode = {
62
62
  WT_NO_ERROR: 0,
63
63
  WT_ABORTED: 1,
package/dist/queue.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface QueueStats {
7
7
  * Datagram lane: bounded ring, drop OLDEST on overflow, TTL checked at DEQUEUE.
8
8
  *
9
9
  * The two axes are different problems. Drop-oldest handles a burst. It does nothing for a
10
- * peer that stalls two seconds and resumes the ring never overflows, so a backlog of
10
+ * peer that stalls two seconds and resumes - the ring never overflows, so a backlog of
11
11
  * stale positions is delivered and the application renders history, which is worse than
12
12
  * delivering nothing. TTL at dequeue is what fixes that, and the two causes are counted
13
13
  * separately so an operator can tell a slow consumer from a slow network.
@@ -35,7 +35,7 @@ export declare class EmitQueue<T> {
35
35
  push(item: T): void;
36
36
  /**
37
37
  * The head, left in place. An item leaves this queue only when its write has actually
38
- * completed `shift()` on hand-off would make depth a measure of nothing, which is
38
+ * completed - `shift()` on hand-off would make depth a measure of nothing, which is
39
39
  * precisely how the bound came to be unreachable.
40
40
  */
41
41
  peek(): T | undefined;
package/dist/queue.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Backpressure. PROTOCOL.md §9 three lanes, three answers, because they make different
2
+ * Backpressure. PROTOCOL.md §9 - three lanes, three answers, because they make different
3
3
  * promises.
4
4
  *
5
5
  * The transport swallows its own "blocked" indication along with "too big", so a refused
@@ -11,7 +11,7 @@ import { DATAGRAM_QUEUE_MAX, DATAGRAM_TTL_MS, EMIT_QUEUE_MAX } from './protocol.
11
11
  * Datagram lane: bounded ring, drop OLDEST on overflow, TTL checked at DEQUEUE.
12
12
  *
13
13
  * The two axes are different problems. Drop-oldest handles a burst. It does nothing for a
14
- * peer that stalls two seconds and resumes the ring never overflows, so a backlog of
14
+ * peer that stalls two seconds and resumes - the ring never overflows, so a backlog of
15
15
  * stale positions is delivered and the application renders history, which is worse than
16
16
  * delivering nothing. TTL at dequeue is what fixes that, and the two causes are counted
17
17
  * separately so an operator can tell a slow consumer from a slow network.
@@ -78,7 +78,7 @@ export class EmitQueue {
78
78
  }
79
79
  /**
80
80
  * The head, left in place. An item leaves this queue only when its write has actually
81
- * completed `shift()` on hand-off would make depth a measure of nothing, which is
81
+ * completed - `shift()` on hand-off would make depth a measure of nothing, which is
82
82
  * precisely how the bound came to be unreachable.
83
83
  */
84
84
  peek() {
package/dist/server.js CHANGED
@@ -42,7 +42,7 @@ export class Server {
42
42
  // Sweeping current peers rather than the ones present at registration: a session
43
43
  // accepted in between picked the handler up from `#callHandlers`, so it has to be
44
44
  // revoked too. Deleting from the map alone left every already-connected peer still
45
- // being answered revoking a privileged responder did nothing for anyone connected.
45
+ // being answered - revoking a privileged responder did nothing for anyone connected.
46
46
  for (const { session } of this.#peers.values())
47
47
  session.unhandle(event);
48
48
  };
@@ -95,7 +95,7 @@ export class Server {
95
95
  })
96
96
  // Teardown is the last thing that runs for this peer; there is no caller left to
97
97
  // hand a rejection to. Without this it was an unhandled rejection, which ends the
98
- // process under Node's default the opposite of what ADR/0005 and D40 promise.
98
+ // process under Node's default - the opposite of what ADR/0005 and D40 promise.
99
99
  .catch(() => undefined);
100
100
  await session.start();
101
101
  for (const cb of this.#onPeer)
package/dist/session.d.ts CHANGED
@@ -50,7 +50,7 @@ export declare class Session {
50
50
  * identifiers, no pending map, and a stalled call blocks nothing else.
51
51
  *
52
52
  * There is no default timeout. A dead peer is detected by the QUIC idle timeout, which
53
- * closes the session and rejects every pending call the case a timeout is usually
53
+ * closes the session and rejects every pending call - the case a timeout is usually
54
54
  * reached for is already handled. Pass `AbortSignal.timeout(ms)` for a slow but live
55
55
  * responder.
56
56
  */
@@ -69,8 +69,8 @@ export declare class Session {
69
69
  * The only door out of a session, and every internal path now uses it.
70
70
  *
71
71
  * Guarding this method alone was not enough: four call sites reached `#conn.close()`
72
- * directly the handshake deadline, the peer-too-slow bound, an emit write failure and a
73
- * protocol error on the read loop so the guard covered the one path that already had
72
+ * directly - the handshake deadline, the peer-too-slow bound, an emit write failure and a
73
+ * protocol error on the read loop - so the guard covered the one path that already had
74
74
  * the fewest duplicates. A soak still produced 619,422 `close sent twice` complaints from
75
75
  * quiche after the first fix, which is what a partial guard looks like from the outside.
76
76
  */
@@ -78,13 +78,13 @@ export declare class Session {
78
78
  /**
79
79
  * Idempotent, and wired to `conn.closed` in `start()` so it cannot be forgotten.
80
80
  *
81
- * It was forgotten. `clearInterval` appeared in exactly one place `close()` and
81
+ * It was forgotten. `clearInterval` appeared in exactly one place - `close()` - and
82
82
  * neither teardown path called it: the server's `conn.closed` continuation freed the
83
83
  * origin and removed the peer, and the client's patched a snapshot. Whichever side did
84
84
  * not *initiate* the close kept a live interval whose callback closes over `this`,
85
85
  * retaining the Session, its Connection, the frame decoder, both queues, the sequence
86
86
  * gate and every handler set. At 100 sessions a second that is 360,000 unreclaimable
87
- * Sessions an hour, and `unref()` does nothing about it it stops a timer holding the
87
+ * Sessions an hour, and `unref()` does nothing about it - it stops a timer holding the
88
88
  * event loop open, not holding memory.
89
89
  */
90
90
  dispose(): void;
package/dist/session.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * One side of a session. Both the server's view of a peer and the client's view of the
3
- * server are this class the handshake is symmetric, and so is everything after it.
3
+ * server are this class - the handshake is symmetric, and so is everything after it.
4
4
  */
5
5
  import { decodePayload, encodePayload, validate } from './codec.js';
6
6
  import { decodeDatagram, encodeDatagram, SequenceGate } from './datagram.js';
@@ -26,7 +26,7 @@ function closeCodeFor(e) {
26
26
  }
27
27
  /**
28
28
  * §10.2 caps the reason at 1024 **bytes**. Slicing to 1024 characters overshoots by up to
29
- * threefold on non-ASCII and event names, which appear in mismatch messages, are the
29
+ * threefold on non-ASCII - and event names, which appear in mismatch messages, are the
30
30
  * user's own domain language. Truncated on a code-point boundary so the result is never
31
31
  * a broken surrogate pair.
32
32
  */
@@ -95,7 +95,7 @@ export class Session {
95
95
  this.#handshakeReject = rej;
96
96
  });
97
97
  // `ready` is rejected from the emit-stream read loop, which can reach a refusal before
98
- // start() has got as far as awaiting it the peer's handshake is frame 0 and may be
98
+ // start() has got as far as awaiting it - the peer's handshake is frame 0 and may be
99
99
  // decoded during our own `openEmitStream()`. An unobserved rejection terminates a Node
100
100
  // server by default, so it is observed here. start() still surfaces it to its caller.
101
101
  void this.ready.catch(() => undefined);
@@ -114,7 +114,7 @@ export class Session {
114
114
  * Armed before the stream is opened, and raced against every await that follows.
115
115
  *
116
116
  * It used to be armed *after* `openEmitStream()` and after our own handshake write, so
117
- * if either never settled precisely the stalled-peer case this deadline exists for
117
+ * if either never settled - precisely the stalled-peer case this deadline exists for -
118
118
  * no timer was ever armed and `connect()` hung for ever. Racing `ready` instead would
119
119
  * not work: a peer whose handshake arrives before we have opened our own stream
120
120
  * resolves `ready` early, and the race would fire on success.
@@ -229,7 +229,7 @@ export class Session {
229
229
  * identifiers, no pending map, and a stalled call blocks nothing else.
230
230
  *
231
231
  * There is no default timeout. A dead peer is detected by the QUIC idle timeout, which
232
- * closes the session and rejects every pending call the case a timeout is usually
232
+ * closes the session and rejects every pending call - the case a timeout is usually
233
233
  * reached for is already handled. Pass `AbortSignal.timeout(ms)` for a slow but live
234
234
  * responder.
235
235
  */
@@ -241,7 +241,7 @@ export class Session {
241
241
  // An already-aborted signal must not open a stream just to tear it down.
242
242
  // §11: a peer that has detected its counterpart is gone MUST NOT reuse a stream from
243
243
  // that session. Opening a new one on a dead session is the same mistake wearing a
244
- // different hat the transport may even accept it, and the call then hangs.
244
+ // different hat - the transport may even accept it, and the call then hangs.
245
245
  if (this.#disposed) {
246
246
  throw new TransportError('WT_SESSION_CLOSED', 'the session is closed, so no call stream can be opened on it', 'Reconnect. A reconnect is a new session and does not restore room membership (D4).');
247
247
  }
@@ -249,7 +249,7 @@ export class Session {
249
249
  throw abortToTransportError(opts.signal.reason);
250
250
  // An event that declares no `returns` has no response to wait for. The type system
251
251
  // already excludes it from `CallableOf`; this is the same refusal for a caller that
252
- // reached the wire without the types and it names the actual problem instead of
252
+ // reached the wire without the types - and it names the actual problem instead of
253
253
  // travelling to the responder to come back as "no handler registered", which is a
254
254
  // different fault with a different remedy.
255
255
  if (entry.lane === 'stream' && entry.def.returns === undefined) {
@@ -268,7 +268,7 @@ export class Session {
268
268
  catch (e) {
269
269
  // D18 removes the default call timeout on the grounds that `AbortSignal.timeout(ms)`
270
270
  // is the documented substitute, so aborting is the most-documented failure this
271
- // library has and it rejected with a raw DOMException carrying no code and no
271
+ // library has - and it rejected with a raw DOMException carrying no code and no
272
272
  // remedy, which the error helper printed in API.md reports as 'unknown'.
273
273
  // Read through a call so narrowing from the pre-check above does not apply: the
274
274
  // signal can abort at any point during the call, which is the whole reason it exists.
@@ -285,7 +285,7 @@ export class Session {
285
285
  /**
286
286
  * The cap is a receiver-side refusal or it is nothing. `call()` declining to open a
287
287
  * 257th stream protects the peer from us; it does nothing about a peer that opens 10,000
288
- * a Go implementation written from PROTOCOL.md, or a browser calling
288
+ * - a Go implementation written from PROTOCOL.md, or a browser calling
289
289
  * `createBidirectionalStream()` directly. That is the case §10.1 code 9 exists for.
290
290
  *
291
291
  * Refused before the request is read, deliberately: the cost this bound exists to bound
@@ -336,15 +336,15 @@ export class Session {
336
336
  * The only door out of a session, and every internal path now uses it.
337
337
  *
338
338
  * Guarding this method alone was not enough: four call sites reached `#conn.close()`
339
- * directly the handshake deadline, the peer-too-slow bound, an emit write failure and a
340
- * protocol error on the read loop so the guard covered the one path that already had
339
+ * directly - the handshake deadline, the peer-too-slow bound, an emit write failure and a
340
+ * protocol error on the read loop - so the guard covered the one path that already had
341
341
  * the fewest duplicates. A soak still produced 619,422 `close sent twice` complaints from
342
342
  * quiche after the first fix, which is what a partial guard looks like from the outside.
343
343
  */
344
344
  close(code, reason) {
345
345
  // Idempotent in both halves. `dispose()` already was; `conn.close()` was not, so a
346
- // second close a client disconnecting while the server is tearing the same session
347
- // down, which is ordinary reached the transport twice. quiche logs
346
+ // second close - a client disconnecting while the server is tearing the same session
347
+ // down, which is ordinary - reached the transport twice. quiche logs
348
348
  // "WebTransportHttp3 close sent twice" and refuses it, which is a protocol-level
349
349
  // complaint we were generating and then ignoring.
350
350
  if (this.#disposed)
@@ -355,13 +355,13 @@ export class Session {
355
355
  /**
356
356
  * Idempotent, and wired to `conn.closed` in `start()` so it cannot be forgotten.
357
357
  *
358
- * It was forgotten. `clearInterval` appeared in exactly one place `close()` and
358
+ * It was forgotten. `clearInterval` appeared in exactly one place - `close()` - and
359
359
  * neither teardown path called it: the server's `conn.closed` continuation freed the
360
360
  * origin and removed the peer, and the client's patched a snapshot. Whichever side did
361
361
  * not *initiate* the close kept a live interval whose callback closes over `this`,
362
362
  * retaining the Session, its Connection, the frame decoder, both queues, the sequence
363
363
  * gate and every handler set. At 100 sessions a second that is 360,000 unreclaimable
364
- * Sessions an hour, and `unref()` does nothing about it it stops a timer holding the
364
+ * Sessions an hour, and `unref()` does nothing about it - it stops a timer holding the
365
365
  * event loop open, not holding memory.
366
366
  */
367
367
  dispose() {
@@ -449,7 +449,7 @@ export class Session {
449
449
  return;
450
450
  }
451
451
  // The request is fully read at this point, so nothing is watching the stream any
452
- // more which is why an abort never reached the handler. The initiator's abort
452
+ // more - which is why an abort never reached the handler. The initiator's abort
453
453
  // resets its send side AND cancels its read side, and that STOP_SENDING surfaces here
454
454
  // as a rejection on our writer. Watch it, or `ctx.signal` is decoration.
455
455
  void writer.closed.catch(() => controller.abort());
@@ -534,7 +534,7 @@ export class Session {
534
534
  * same turn as the push and appended each frame to an unbounded promise chain, so depth
535
535
  * returned to zero after every push and `EmitQueue`'s bound could never be reached from
536
536
  * a Session. The backlog did not go away, it went somewhere that could not disconnect
537
- * anyone and whose `.catch(() => undefined)` discarded every write failure on the lane
537
+ * anyone - and whose `.catch(() => undefined)` discarded every write failure on the lane
538
538
  * that advertises reliable ordered delivery.
539
539
  *
540
540
  * Nothing flushes before the handshake, so frame 0 keeps its position by construction
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The browser Connection, over the platform's own `WebTransport`.
3
3
  *
4
- * Nothing here is imported at module scope `WebTransport` is read inside connect()
4
+ * Nothing here is imported at module scope - `WebTransport` is read inside connect() -
5
5
  * so importing this on a server, which Next.js will do, is safe. There is no native
6
6
  * addon and no binding, which is why this file is not `*.node.ts`.
7
7
  */
@@ -158,7 +158,7 @@ export async function connectHttp3(opts) {
158
158
  // The binding loads its native transport through a dynamic import and throws
159
159
  // `Lib quiche loading attempt did not end` if a client is constructed before it
160
160
  // settles. A process that also runs a server never sees this, because the server
161
- // awaits the same promise on the way up which is exactly why it went unnoticed
161
+ // awaits the same promise on the way up - which is exactly why it went unnoticed
162
162
  // until a client ran on its own.
163
163
  await quicheLoaded;
164
164
  const wt = new WebTransport(opts.url, {
@@ -168,7 +168,7 @@ export async function connectHttp3(opts) {
168
168
  requireUnreliable: true,
169
169
  });
170
170
  // `closed` rejects independently of `ready`. If the handshake fails, nothing has
171
- // attached to it yet and Node sees an unhandled rejection which terminates a server
171
+ // attached to it yet and Node sees an unhandled rejection - which terminates a server
172
172
  // by default. Claim it before awaiting `ready`; FailsConnection re-reads the same
173
173
  // settled promise, so nothing is lost.
174
174
  const closedGuard = wt.closed.catch(() => undefined);
@@ -68,7 +68,7 @@ function toReadable(recv) {
68
68
  }
69
69
  },
70
70
  cancel(reason) {
71
- // STOP_SENDING with an explicit code no message-string parsing needed here.
71
+ // STOP_SENDING with an explicit code - no message-string parsing needed here.
72
72
  void recv.stop(codeOf(reason)).catch(() => undefined);
73
73
  },
74
74
  });
@@ -11,7 +11,7 @@ export interface UnderTest {
11
11
  *
12
12
  * `false` is a real capability gap, not a preference. moq surfaces STOP_SENDING only on
13
13
  * the next write, and a long-running handler never makes one, so the handler is not
14
- * told to stop. The caller still rejects either way the work just keeps running.
14
+ * told to stop. The caller still rejects either way - the work just keeps running.
15
15
  */
16
16
  readonly propagatesAbortToHandler: boolean;
17
17
  readonly name: string;
@@ -30,7 +30,7 @@ export interface UnderTest {
30
30
  /**
31
31
  * One transport per process, deliberately.
32
32
  *
33
- * Running both bindings' servers concurrently in a single process hangs verified: each
33
+ * Running both bindings' servers concurrently in a single process hangs - verified: each
34
34
  * works alone and both can bind, but sessions on both at once deadlock. That is a
35
35
  * property of running two native QUIC stacks side by side, not of transport-io, and no
36
36
  * deployment would do it. Splitting by process is also better isolation.
@@ -23,7 +23,7 @@ const contract = defineContract({
23
23
  slow: { lane: 'stream', payload: type$(), returns: type$() },
24
24
  });
25
25
  // Random high ports. A fixed port makes this suite fail for a reason that has nothing to
26
- // do with the code an orphan from a previous killed run still holding the socket, which
26
+ // do with the code - an orphan from a previous killed run still holding the socket, which
27
27
  // cost an hour to diagnose once already.
28
28
  const dir = mkdtempSync(join(tmpdir(), 'parity-'));
29
29
  execFileSync('openssl', [
@@ -59,7 +59,7 @@ const settle = async (ms = 400) => {
59
59
  /**
60
60
  * One transport per process, deliberately.
61
61
  *
62
- * Running both bindings' servers concurrently in a single process hangs verified: each
62
+ * Running both bindings' servers concurrently in a single process hangs - verified: each
63
63
  * works alone and both can bind, but sessions on both at once deadlock. That is a
64
64
  * property of running two native QUIC stacks side by side, not of transport-io, and no
65
65
  * deployment would do it. Splitting by process is also better isolation.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The transport seam. Internal not exported from the package (D21).
2
+ * The transport seam. Internal - not exported from the package (D21).
3
3
  *
4
4
  * It exists so the reference transport's defects stay in one place: it silently swallows
5
5
  * oversized and blocked datagrams, its error type omits the specification's
@@ -25,7 +25,7 @@ export interface Connection {
25
25
  /** A property of the path, not a constant. Queried at send time, never cached. */
26
26
  maxDatagramSize(): number;
27
27
  /**
28
- * `undefined` where the runtime does not implement the attribute which includes the
28
+ * `undefined` where the runtime does not implement the attribute - which includes the
29
29
  * dominant browser. Treated as "unknown, allowed"; only an explicit 'reliable-only' is
30
30
  * refused. See D10.
31
31
  */
@@ -3,7 +3,7 @@
3
3
  * reordering, duplicating and delayed, on command.
4
4
  *
5
5
  * Test-only. The plain loopback delivers datagrams reliably and in order, which is
6
- * convenient and a lie every guarantee the datagram lane deliberately does not make
6
+ * convenient and a lie - every guarantee the datagram lane deliberately does not make
7
7
  * goes unexercised against it. This exists so loss, reordering and duplication are forced
8
8
  * deliberately rather than waited for.
9
9
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transport-io",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Real-time apps over WebTransport. Two lanes, one contract, no fallback.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  "author": "#V0ID <hey@v0id.me>",
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "git+https://github.com/v0id-user/transport-io.git",
52
+ "url": "git+https://github.com/transport-io/transport-io.git",
53
53
  "directory": "packages/core"
54
54
  }
55
55
  }