@pylonsync/realtime 0.14.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 ADDED
@@ -0,0 +1,30 @@
1
+ # @pylonsync/realtime
2
+
3
+ The Pylon shard client with no framework: the wire protocol, the entity
4
+ replication decoder, and the pieces a game's render loop needs.
5
+
6
+ - `connectShardGame(shardId, options)`: a connection that applies each
7
+ replication frame and places entities for a render frame
8
+ (`game.frame(now)`), with interpolation, a server clock estimate,
9
+ prediction for the local player, the input round trip, and reconnects
10
+ with a full frame.
11
+ - `connectShard(shardId, options)`: the connection alone.
12
+ - `EntityTable`: applies replication frames.
13
+ - `EntityInterpolator`, `ShardClock`, `Predictor`: the parts on their own.
14
+
15
+ ```ts
16
+ import { connectShardGame } from "@pylonsync/realtime";
17
+
18
+ const game = connectShardGame<Input>("zone-1", { subscriberId, ticket, tickRate: 20 });
19
+
20
+ function frame(now: number) {
21
+ game.frame(now);
22
+ for (const id of game.left) removeMesh(id);
23
+ for (const id of game.entered) addMesh(id);
24
+ for (const e of game.entities.values()) moveMesh(e.id, e.x, e.y, e.z);
25
+ requestAnimationFrame(frame);
26
+ }
27
+ requestAnimationFrame(frame);
28
+ ```
29
+
30
+ See the shards guide: https://docs.pylonsync.com/concepts/shards
@@ -0,0 +1,74 @@
1
+ /**
2
+ * An estimate of the shard's current tick, from the ticks in the frames it
3
+ * sends and when they arrive.
4
+ *
5
+ * Each frame carries the tick it was built on. The frame that arrived
6
+ * soonest after it was built (lowest delay) anchors the estimate, so
7
+ * network jitter only ever makes a frame look late. The tick length is the
8
+ * shard's tick rate when the client knows it, or else the median slope of
9
+ * arrival time against tick.
10
+ *
11
+ * The estimate is continuous: when a new frame moves it, the change is
12
+ * spread over time instead of jumping (from `slew` of real time for small
13
+ * changes up to all of it). A step forward larger than `snapMs` applies at
14
+ * once. A step back larger than that applies only as far as the newest
15
+ * tick received, so it never goes back past a tick a frame carried.
16
+ *
17
+ * After three frames in a row arrive more than `snapMs` late and spaced
18
+ * like ticks (a shard that stalled), the older frames are dropped and the
19
+ * clock follows the new schedule. Frames that arrive together (a network
20
+ * burst) do not count.
21
+ */
22
+ export interface ShardClockOptions {
23
+ /** The shard's tick rate in ticks per second, when the client knows it. */
24
+ tickRate?: number;
25
+ /** Frames the estimate uses. Default 64. */
26
+ window?: number;
27
+ /** The least fraction of real time the estimate speeds up or slows
28
+ * down by to absorb a correction. Default 0.05. */
29
+ slew?: number;
30
+ /** A step forward larger than this (ms) applies at once, and a
31
+ * correction this large or larger runs at full rate. Default 250. */
32
+ snapMs?: number;
33
+ }
34
+ export declare class ShardClock {
35
+ private readonly fixedTickMs;
36
+ private readonly window;
37
+ private readonly slew;
38
+ private readonly snapMs;
39
+ private samples;
40
+ private measuredTickMs;
41
+ /** The newest tick seen, and the estimated arrival time of a frame for
42
+ * it with the lowest delay seen. */
43
+ private anchorTick;
44
+ private anchorAt;
45
+ /** Ticks the displayed estimate is ahead of the raw one; decays to 0. */
46
+ private offset;
47
+ private slewedTo;
48
+ /** Frames in a row that arrived more than `snapMs` after the anchor
49
+ * predicted. */
50
+ private late;
51
+ constructor(options?: ShardClockOptions);
52
+ /** True once a frame has arrived. */
53
+ get ready(): boolean;
54
+ /** The newest tick a frame carried, or -1. */
55
+ get latestTick(): number;
56
+ /** Milliseconds per tick: the configured rate, or the measured one. */
57
+ get tickMs(): number;
58
+ /** Forget everything (a new shard). */
59
+ reset(): void;
60
+ /** Record a frame for `tick` that arrived at `at` (ms, monotonic). */
61
+ observe(tick: number, at: number): void;
62
+ /**
63
+ * The estimated tick the shard is on at `now` (ms, same clock as
64
+ * `observe`), with a fraction. Before the first frame, -1.
65
+ */
66
+ serverTick(now: number): number;
67
+ private raw;
68
+ /**
69
+ * The median slope of arrival time against tick over every pair of
70
+ * samples (Theil-Sen): frames that arrive together in a burst are a
71
+ * minority of pairs and do not pull it.
72
+ */
73
+ private fitTickMs;
74
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * A shard connection with no framework: one WebSocket, reconnected with
3
+ * backoff, that decodes frames, applies replication frames to an
4
+ * `EntityTable`, and sends inputs. `useShard` in `@pylonsync/react` and
5
+ * `connectShardGame` build on it.
6
+ */
7
+ import { ShardClock } from "./clock";
8
+ import { EntityTable, type ReplicationSummary } from "./replication";
9
+ import { type ShardInputRejection, type ShardPayloadDecoder } from "./wire";
10
+ export interface ShardConnectOptions {
11
+ /** Subscriber ID (usually the logged-in user ID). Required for multiplayer. */
12
+ subscriberId: string;
13
+ /**
14
+ * Auth token. Sent as a `bearer.<token>` WebSocket subprotocol, which
15
+ * keeps it out of URLs (proxy logs, devtools, and error telemetry record
16
+ * URLs, not subprotocols).
17
+ */
18
+ token?: string;
19
+ /**
20
+ * Shard ticket from a server function (`ctx.shards.ticket(...)`). Sent as
21
+ * a `ticket.<ticket>` WebSocket subprotocol. The shard checks it names
22
+ * this shard and `subscriberId`, and passes its claims to the game's
23
+ * authorization hooks.
24
+ *
25
+ * Tickets expire. Pass a function to get a new one for each connection
26
+ * attempt, so a reconnect after the expiry still gets in.
27
+ */
28
+ ticket?: string | (() => string | Promise<string>);
29
+ /** Host (and port) of the Pylon server. Defaults to `window.location.host`. */
30
+ baseUrl?: string;
31
+ /**
32
+ * Connect to the dedicated shard port instead of `/shard` on the main
33
+ * port (the dedicated port is the HTTP port + 3, e.g. 4324).
34
+ */
35
+ wsPort?: number;
36
+ /** Explicit WebSocket URL. Overrides baseUrl/wsPort. */
37
+ wsUrl?: string;
38
+ /** Reconnect on unexpected close (default: true). */
39
+ autoReconnect?: boolean;
40
+ /** First reconnect delay in ms (default 500; doubles to at most 10 000). */
41
+ reconnectBackoffMs?: number;
42
+ /**
43
+ * Decoder for a payload codec the client does not know: bincode (`2`) or
44
+ * a game's own codec (`3`). JSON and MessagePack are built in.
45
+ */
46
+ decode?: ShardPayloadDecoder;
47
+ /** The shard's tick rate, when known; otherwise the clock measures it. */
48
+ tickRate?: number;
49
+ /** Monotonic time in ms. Default `performance.now()`. */
50
+ now?: () => number;
51
+ }
52
+ export interface ShardClient<TSnapshot = unknown, TInput = unknown> {
53
+ /** `ack` is the highest `send()` sequence number the shard has processed. */
54
+ onSnapshot: (fn: (snapshot: TSnapshot, tick: number, ack: number) => void) => void;
55
+ /** Called when the shard refuses an input (see `ShardInputRejection.code`). */
56
+ onInputRejected: (fn: (rejection: ShardInputRejection) => void) => void;
57
+ /**
58
+ * For a shard that replicates entities: called after each frame is
59
+ * applied to `entities`, with what it changed.
60
+ */
61
+ onReplication: (fn: (entities: EntityTable, summary: ReplicationSummary, tick: number, ack: number) => void) => void;
62
+ /**
63
+ * The entities a replicating shard has sent this client. Read it each
64
+ * frame (a render loop); it changes in place as frames arrive.
65
+ */
66
+ readonly entities: EntityTable;
67
+ /** The shard's current tick, estimated from frame arrivals. */
68
+ readonly clock: ShardClock;
69
+ /** The tick of the last frame, or -1. */
70
+ readonly tick: number;
71
+ /** The ack of the last frame. */
72
+ readonly ack: number;
73
+ /**
74
+ * Smoothed time from sending an input to the first frame that
75
+ * acknowledges it (ms), or null before one has. It includes the wait for
76
+ * the shard's next tick.
77
+ */
78
+ readonly rttMs: number | null;
79
+ onError: (fn: (err: Error) => void) => void;
80
+ onOpen: (fn: () => void) => void;
81
+ onClose: (fn: () => void) => void;
82
+ /**
83
+ * Send an input. Returns its sequence number, which later frames
84
+ * acknowledge, or 0 when the connection is not open (the input is not
85
+ * sent, and `onError` hears why).
86
+ */
87
+ send: (input: TInput) => number;
88
+ close: () => void;
89
+ readonly connected: boolean;
90
+ }
91
+ /**
92
+ * Connect to a shard without React. Returns a client you can wire into any
93
+ * framework or render loop.
94
+ */
95
+ export declare function connectShard<TSnapshot = unknown, TInput = unknown>(shardId: string, options: ShardConnectOptions): ShardClient<TSnapshot, TInput>;
package/dist/game.d.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * A shard client for a game's render loop: the connection, the clock,
3
+ * interpolated entities, and prediction, with no framework and no
4
+ * re-render per frame.
5
+ *
6
+ * ```ts
7
+ * const game = connectShardGame("zone-1", { subscriberId, ticket, tickRate: 20 });
8
+ * const me = game.predict<Vec3>((p, input) => move(p, input));
9
+ *
10
+ * function frame(now: number) {
11
+ * game.frame(now); // places game.entities at the render tick
12
+ * for (const e of game.entities.values()) draw(e.id, e.x, e.y, e.z);
13
+ * requestAnimationFrame(frame);
14
+ * }
15
+ * requestAnimationFrame(frame);
16
+ *
17
+ * game.onReplication((table, _summary, _tick, ack) => {
18
+ * const mine = table.get(myEntityId);
19
+ * if (mine) local = me.reconcile({ x: mine.x, y: mine.y, z: mine.z }, ack);
20
+ * });
21
+ * onKey((input) => {
22
+ * // 0: not sent (the connection is down), so do not predict it.
23
+ * if (game.send(input)) local = move(local, input);
24
+ * });
25
+ * ```
26
+ */
27
+ import type { ShardClock } from "./clock";
28
+ import { type ShardClient, type ShardConnectOptions } from "./connection";
29
+ import { type InterpolatedEntity, type InterpolationOptions } from "./interpolation";
30
+ import { Predictor, type PredictorOptions } from "./prediction";
31
+ import type { EntityTable, ReplicationSummary } from "./replication";
32
+ import type { ShardInputRejection } from "./wire";
33
+ export interface ShardGameOptions extends ShardConnectOptions {
34
+ /**
35
+ * How far behind the shard's estimated current tick entities are drawn,
36
+ * in ms. It must cover the time between frames plus network jitter.
37
+ * Default 100.
38
+ */
39
+ interpolationDelayMs?: number;
40
+ interpolation?: InterpolationOptions;
41
+ }
42
+ export interface ShardGame<TInput = unknown> {
43
+ /** The underlying connection. */
44
+ readonly connection: ShardClient<unknown, TInput>;
45
+ readonly clock: ShardClock;
46
+ /** Entities placed at the render tick by the last `frame` call. */
47
+ readonly entities: ReadonlyMap<number, InterpolatedEntity>;
48
+ /** Ids the last `frame` added to and removed from `entities`. */
49
+ readonly entered: readonly number[];
50
+ readonly left: readonly number[];
51
+ /** The newest state the shard sent, not interpolated. */
52
+ readonly latest: EntityTable;
53
+ readonly tick: number;
54
+ readonly ack: number;
55
+ readonly rttMs: number | null;
56
+ readonly connected: boolean;
57
+ /**
58
+ * Place `entities` for a render frame at `now` (ms, default
59
+ * `performance.now()`). Returns the render tick, or -1 before the first
60
+ * frame.
61
+ */
62
+ frame(now?: number): number;
63
+ /** Send an input; every predictor made by `predict` records it. Returns
64
+ * its sequence number, or 0 when it was not sent. */
65
+ send(input: TInput): number;
66
+ /**
67
+ * A predictor that records every input `send` sends, forgets inputs the
68
+ * shard refuses, and resets when the connection reopens. Call
69
+ * `reconcile` with the local entity's server state after each frame.
70
+ */
71
+ predict<S>(step: (state: S, input: TInput) => S, options?: PredictorOptions): Predictor<S, TInput>;
72
+ onReplication(fn: (entities: EntityTable, summary: ReplicationSummary, tick: number, ack: number) => void): void;
73
+ onInputRejected(fn: (rejection: ShardInputRejection) => void): void;
74
+ onOpen(fn: () => void): void;
75
+ onClose(fn: () => void): void;
76
+ onError(fn: (err: Error) => void): void;
77
+ close(): void;
78
+ }
79
+ /** Connect to a replicating shard for a render loop. */
80
+ export declare function connectShardGame<TInput = unknown>(shardId: string, options: ShardGameOptions): ShardGame<TInput>;
@@ -0,0 +1,7 @@
1
+ export * from "./wire";
2
+ export * from "./replication";
3
+ export * from "./clock";
4
+ export * from "./interpolation";
5
+ export * from "./prediction";
6
+ export * from "./connection";
7
+ export * from "./game";
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Entity interpolation for render loops.
3
+ *
4
+ * Record every replication frame with the tick it was built on; each render
5
+ * frame, call `update` with a tick a little behind the server's (see
6
+ * `ShardClock`). Each entity is then drawn between the two samples around
7
+ * that tick, so motion is smooth even though frames arrive at the tick rate
8
+ * and with jitter. Spawns, despawns, and component changes appear at the
9
+ * tick they happened, in step with the positions.
10
+ */
11
+ import type { EntityTable, ReplicationSummary } from "./replication";
12
+ export interface InterpolationOptions {
13
+ /**
14
+ * A move longer than this (world units) between two samples is a
15
+ * teleport: the entity jumps instead of sliding. Default: no limit.
16
+ */
17
+ snapDistance?: number;
18
+ /**
19
+ * Treat an entity that sent no update as not moving, so its next move
20
+ * starts from the tick before it arrives. Default true. Set false for a
21
+ * shard with a byte budget (`max_bytes_per_tick`), where updates can
22
+ * wait for later ticks.
23
+ */
24
+ holdWhenQuiet?: boolean;
25
+ /** Samples kept per entity. Default 32. */
26
+ maxSamples?: number;
27
+ }
28
+ /** An entity's state at one tick. */
29
+ export interface EntitySample {
30
+ readonly tick: number;
31
+ readonly x: number;
32
+ readonly y: number;
33
+ readonly z: number;
34
+ /** Component id to bytes. Shared between samples; do not change it. */
35
+ readonly components: ReadonlyMap<number, Uint8Array>;
36
+ }
37
+ /** One entity as drawn at the render tick. The object is reused. */
38
+ export interface InterpolatedEntity {
39
+ readonly id: number;
40
+ x: number;
41
+ y: number;
42
+ z: number;
43
+ /** Components as of the sample at or before the render tick. */
44
+ components: ReadonlyMap<number, Uint8Array>;
45
+ /**
46
+ * The samples around the render tick and the fraction between them, to
47
+ * interpolate game data kept in components (a heading, a health bar).
48
+ * `from` and `to` are the same sample when there is no later one.
49
+ */
50
+ from: EntitySample;
51
+ to: EntitySample;
52
+ t: number;
53
+ }
54
+ export declare class EntityInterpolator {
55
+ /** Entities that exist at the last `update`'s render tick. */
56
+ readonly entities: Map<number, InterpolatedEntity>;
57
+ /**
58
+ * Ids the last `update` added to `entities`. An id in both `left` and
59
+ * `entered` names a new entity that reused the id: rebuild what you
60
+ * drew for it.
61
+ */
62
+ readonly entered: number[];
63
+ /** Ids the last `update` removed from `entities`. */
64
+ readonly left: number[];
65
+ private readonly lives;
66
+ /** The life each entry in `entities` shows. */
67
+ private readonly shown;
68
+ private readonly snapDistance;
69
+ private readonly holdWhenQuiet;
70
+ private readonly maxSamples;
71
+ private lastTick;
72
+ /** The newest tick the last `update` drew; a render tick never goes
73
+ * below it. */
74
+ private floor;
75
+ /** Lives that ended, oldest first, so `record` can drop them when
76
+ * `update` does not run (a hidden tab keeps receiving frames). */
77
+ private ended;
78
+ constructor(options?: InterpolationOptions);
79
+ /** The newest tick recorded, or -1. */
80
+ get latestTick(): number;
81
+ /** Forget everything. The next `update` removes every entity. */
82
+ clear(): void;
83
+ /**
84
+ * Record the table after a replication frame for `tick` applied, with the
85
+ * summary `EntityTable.apply` returned.
86
+ */
87
+ record(table: EntityTable, summary: ReplicationSummary, tick: number): void;
88
+ /**
89
+ * Place every entity at `renderTick` (fractional). A render tick below
90
+ * one already drawn is raised to it, so nothing drawn goes back in time.
91
+ */
92
+ update(renderTick: number): void;
93
+ private end;
94
+ /**
95
+ * Forget lives that ended long before `tick`. `update` removes them
96
+ * when it runs; this bounds them when it does not.
97
+ */
98
+ private dropEnded;
99
+ private openLife;
100
+ private spawn;
101
+ private push;
102
+ private place;
103
+ private remove;
104
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Client-side prediction for the local player.
3
+ *
4
+ * Apply each input locally as it is sent, and keep it until the shard
5
+ * acknowledges it. When a frame arrives with the server's state and its
6
+ * `ack` (the highest input sequence number the shard has processed), start
7
+ * from the server's state and apply the inputs it has not processed yet.
8
+ * The result is where the local player is after every input, corrected by
9
+ * whatever the server decided.
10
+ *
11
+ * `step` must be deterministic and must match what the shard does with an
12
+ * input closely enough that the replayed state agrees with the server's.
13
+ * It must not change `state` in place; return a new one.
14
+ */
15
+ export interface PredictorOptions {
16
+ /**
17
+ * Inputs kept while waiting for an ack. Past this the oldest are
18
+ * dropped (the prediction then misses them). Default 256.
19
+ */
20
+ maxPending?: number;
21
+ }
22
+ export declare class Predictor<S, I> {
23
+ private readonly step;
24
+ private pending;
25
+ private readonly maxPending;
26
+ constructor(step: (state: S, input: I) => S, options?: PredictorOptions);
27
+ /** Inputs sent and not yet acknowledged. */
28
+ get size(): number;
29
+ /** Record an input sent with sequence number `seq`. A `seq` of 0 (not
30
+ * sent) is ignored. */
31
+ push(seq: number, input: I): void;
32
+ /** The shard refused input `seq`: it will never apply. */
33
+ reject(seq: number | null): void;
34
+ /**
35
+ * The predicted state: `server` (the shard's state as of a frame) with
36
+ * every input after `ack` applied. Inputs up to `ack` are dropped.
37
+ */
38
+ reconcile(server: S, ack: number): S;
39
+ /**
40
+ * Drop every pending input. Call it when the connection reopens: inputs
41
+ * sent on the old connection either applied before it closed (and are
42
+ * in the server's state) or never will.
43
+ */
44
+ reset(): void;
45
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Entity replication frames (see `pylon_replication::frame` in Rust).
3
+ *
4
+ * A shard that replicates entities sends each subscriber, per tick, the
5
+ * entities that left its view (despawn), came into view (spawn, full
6
+ * state), or changed (update: only the axes that moved, as a quantized
7
+ * difference, and the components that changed or were removed).
8
+ * {@link EntityTable} applies them; after each frame it holds what the
9
+ * server says this subscriber sees.
10
+ *
11
+ * ```text
12
+ * u8 version (1)
13
+ * u8 flags: bit 0 FULL: clear the table first
14
+ * f32 LE precision
15
+ * varint despawn count, ids (first absolute, then differences; ascending)
16
+ * varint spawn count, per entity: id, x, y, z (zigzag, quantized), components
17
+ * varint update count, per entity: id, u8 mask (1 x, 2 y, 4 z, 8 components),
18
+ * changed axes (zigzag differences), components if bit 8
19
+ * components: varint count, per component: u8 id, varint (length + 1),
20
+ * bytes; length field 0 means removed
21
+ * ```
22
+ *
23
+ * JavaScript numbers hold integers exactly up to 2^53. Entity ids and
24
+ * quantized positions must stay below that; a frame that exceeds it is
25
+ * refused rather than rounded.
26
+ */
27
+ export declare const REPLICATION_VERSION = 1;
28
+ export declare class ReplicationError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ /** One entity as the client sees it. */
32
+ export interface ReplicatedEntity {
33
+ readonly id: number;
34
+ /** Quantized position; `x/y/z` are these times the frame precision. */
35
+ qx: number;
36
+ qy: number;
37
+ qz: number;
38
+ x: number;
39
+ y: number;
40
+ z: number;
41
+ /** Component id to bytes, in the game's own encoding. */
42
+ components: Map<number, Uint8Array>;
43
+ }
44
+ /** What one frame did. */
45
+ export interface ReplicationSummary {
46
+ full: boolean;
47
+ spawned: number[];
48
+ updated: number[];
49
+ despawned: number[];
50
+ }
51
+ /**
52
+ * The entities one subscriber has been told about. Apply every
53
+ * replication frame in order; a frame that fails to apply means the
54
+ * connection is out of sync and should be reopened (the server then sends
55
+ * a full frame).
56
+ */
57
+ export declare class EntityTable {
58
+ readonly entities: Map<number, ReplicatedEntity>;
59
+ /** World units per quantization step, from the last frame. */
60
+ precision: number;
61
+ get size(): number;
62
+ get(id: number): ReplicatedEntity | undefined;
63
+ clear(): void;
64
+ apply(frame: Uint8Array): ReplicationSummary;
65
+ }
package/dist/wire.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The shard wire protocol, version 2 (see `pylon_realtime::wire` in Rust).
3
+ *
4
+ * A client asks for it with `?v=2` on the shard WebSocket URL. Each server
5
+ * message is a binary frame with an 18-byte header:
6
+ *
7
+ * 0 1 frame kind: 1 snapshot, 2 input rejected, 3 entity replication
8
+ * 1 1 codec: 0 JSON, 1 MessagePack, 2 bincode, 3 custom, 4 replication
9
+ * 2 8 tick (u64 big-endian)
10
+ * 10 8 ack: highest client_seq the shard processed for this subscriber (0 = none)
11
+ * 18 .. payload in the codec
12
+ *
13
+ * Inputs go up as `{ input, client_seq }`: JSON in a text frame, or the
14
+ * shard's codec in a binary frame.
15
+ */
16
+ export declare const SHARD_PROTOCOL_VERSION = 2;
17
+ export declare const SHARD_HEADER_LEN = 18;
18
+ export declare const ShardFrameKind: {
19
+ readonly Snapshot: 1;
20
+ readonly InputRejected: 2;
21
+ /** An entity replication frame: apply it to an `EntityTable`. */
22
+ readonly Replication: 3;
23
+ };
24
+ export declare const ShardCodec: {
25
+ readonly Json: 0;
26
+ readonly MessagePack: 1;
27
+ readonly Bincode: 2;
28
+ readonly Custom: 3;
29
+ /** The replication frame format (frame kind 3). */
30
+ readonly Replication: 4;
31
+ };
32
+ export interface ShardFrame {
33
+ kind: number;
34
+ codec: number;
35
+ tick: number;
36
+ ack: number;
37
+ payload: Uint8Array;
38
+ }
39
+ /** Why an input did not take effect. */
40
+ export interface ShardInputRejection {
41
+ clientSeq: number | null;
42
+ /** `unauthorized`, `rate_limited`, `queue_full`, `invalid`, `stopped`, or `apply_failed`. */
43
+ code: string;
44
+ message: string;
45
+ }
46
+ /**
47
+ * Decode a payload in a codec the JS client does not know (bincode, or a
48
+ * game's own codec `3`).
49
+ */
50
+ export type ShardPayloadDecoder = (payload: Uint8Array, codec: number) => unknown;
51
+ /** Split a version 2 frame into its header fields and payload. */
52
+ export declare function parseShardFrame(data: ArrayBuffer): ShardFrame;
53
+ /** Decode a payload. JSON and MessagePack are built in. */
54
+ export declare function decodeShardPayload(codec: number, payload: Uint8Array, custom?: ShardPayloadDecoder): unknown;
55
+ /** The payload of an input-rejected frame, with camelCase fields. */
56
+ export declare function decodeShardRejection(codec: number, payload: Uint8Array, custom?: ShardPayloadDecoder): ShardInputRejection;
57
+ /**
58
+ * Encode an input envelope. MessagePack shards get a binary frame; every
59
+ * other codec (and a connection that has not seen a frame yet) gets JSON
60
+ * text, which the server always accepts.
61
+ */
62
+ export declare function encodeShardInput(codec: number | null, input: unknown, clientSeq: number): string | Uint8Array;
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@pylonsync/realtime",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.14.0",
7
+ "description": "Pylon realtime shard client pieces with no framework: the wire protocol, codecs, and the entity replication decoder and table.",
8
+ "type": "module",
9
+ "main": "./src/index.ts",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.build.json",
13
+ "check": "tsc -p tsconfig.json --noEmit",
14
+ "test": "bun test",
15
+ "prepack": "bun run build"
16
+ },
17
+ "dependencies": {
18
+ "@msgpack/msgpack": "^3.1.3"
19
+ },
20
+ "files": [
21
+ "src",
22
+ "dist",
23
+ "README.md"
24
+ ]
25
+ }