@rxferrix/client 0.0.0-stage → 0.2.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.
Files changed (97) hide show
  1. package/dist/api.d.ts +60 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +22 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/auth.d.ts +256 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +541 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/bytes.d.ts +21 -0
  10. package/dist/bytes.d.ts.map +1 -0
  11. package/dist/bytes.js +51 -0
  12. package/dist/bytes.js.map +1 -0
  13. package/dist/canonical.d.ts +13 -0
  14. package/dist/canonical.d.ts.map +1 -0
  15. package/dist/canonical.js +66 -0
  16. package/dist/canonical.js.map +1 -0
  17. package/dist/client.d.ts +170 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +638 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/diff.d.ts +20 -0
  22. package/dist/diff.d.ts.map +1 -0
  23. package/dist/diff.js +100 -0
  24. package/dist/diff.js.map +1 -0
  25. package/dist/errors.d.ts +24 -0
  26. package/dist/errors.d.ts.map +1 -0
  27. package/dist/errors.js +34 -0
  28. package/dist/errors.js.map +1 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +12 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/local_query_set.d.ts +56 -0
  34. package/dist/local_query_set.d.ts.map +1 -0
  35. package/dist/local_query_set.js +108 -0
  36. package/dist/local_query_set.js.map +1 -0
  37. package/dist/logging.d.ts +15 -0
  38. package/dist/logging.d.ts.map +1 -0
  39. package/dist/logging.js +17 -0
  40. package/dist/logging.js.map +1 -0
  41. package/dist/optimistic_updates.d.ts +48 -0
  42. package/dist/optimistic_updates.d.ts.map +1 -0
  43. package/dist/optimistic_updates.js +113 -0
  44. package/dist/optimistic_updates.js.map +1 -0
  45. package/dist/paginated_query.d.ts +118 -0
  46. package/dist/paginated_query.d.ts.map +1 -0
  47. package/dist/paginated_query.js +199 -0
  48. package/dist/paginated_query.js.map +1 -0
  49. package/dist/protocol.d.ts +136 -0
  50. package/dist/protocol.d.ts.map +1 -0
  51. package/dist/protocol.js +11 -0
  52. package/dist/protocol.js.map +1 -0
  53. package/dist/query_state.d.ts +16 -0
  54. package/dist/query_state.d.ts.map +1 -0
  55. package/dist/query_state.js +2 -0
  56. package/dist/query_state.js.map +1 -0
  57. package/dist/remote_query_set.d.ts +37 -0
  58. package/dist/remote_query_set.d.ts.map +1 -0
  59. package/dist/remote_query_set.js +87 -0
  60. package/dist/remote_query_set.js.map +1 -0
  61. package/dist/request_manager.d.ts +57 -0
  62. package/dist/request_manager.d.ts.map +1 -0
  63. package/dist/request_manager.js +163 -0
  64. package/dist/request_manager.js.map +1 -0
  65. package/dist/storage.d.ts +45 -0
  66. package/dist/storage.d.ts.map +1 -0
  67. package/dist/storage.js +120 -0
  68. package/dist/storage.js.map +1 -0
  69. package/dist/testing.d.ts +68 -0
  70. package/dist/testing.d.ts.map +1 -0
  71. package/dist/testing.js +97 -0
  72. package/dist/testing.js.map +1 -0
  73. package/dist/web_socket_manager.d.ts +88 -0
  74. package/dist/web_socket_manager.d.ts.map +1 -0
  75. package/dist/web_socket_manager.js +291 -0
  76. package/dist/web_socket_manager.js.map +1 -0
  77. package/package.json +50 -4
  78. package/src/api.ts +86 -0
  79. package/src/auth.ts +646 -0
  80. package/src/bytes.ts +57 -0
  81. package/src/canonical.ts +61 -0
  82. package/src/client.ts +740 -0
  83. package/src/diff.ts +87 -0
  84. package/src/errors.ts +36 -0
  85. package/src/index.ts +60 -0
  86. package/src/local_query_set.ts +130 -0
  87. package/src/logging.ts +26 -0
  88. package/src/optimistic_updates.ts +137 -0
  89. package/src/paginated_query.ts +299 -0
  90. package/src/protocol.ts +152 -0
  91. package/src/query_state.ts +12 -0
  92. package/src/remote_query_set.ts +105 -0
  93. package/src/request_manager.ts +199 -0
  94. package/src/storage.ts +171 -0
  95. package/src/testing.ts +120 -0
  96. package/src/web_socket_manager.ts +322 -0
  97. package/README.md +0 -3
@@ -0,0 +1,97 @@
1
+ export class FakeSocket {
2
+ url;
3
+ static CONNECTING = 0;
4
+ static OPEN = 1;
5
+ static CLOSING = 2;
6
+ static CLOSED = 3;
7
+ readyState = FakeSocket.CONNECTING;
8
+ sent = [];
9
+ closedByClient = false;
10
+ /** The server-side StateVersion of this socket, advanced by `transition`. */
11
+ version = { querySet: 0, identity: 0, ts: 0 };
12
+ onopen = null;
13
+ onmessage = null;
14
+ onclose = null;
15
+ onerror = null;
16
+ constructor(url) {
17
+ this.url = url;
18
+ }
19
+ send(data) {
20
+ if (this.readyState !== FakeSocket.OPEN)
21
+ throw new Error("send on a socket that is not open");
22
+ this.sent.push(JSON.parse(data));
23
+ }
24
+ close() {
25
+ if (this.readyState === FakeSocket.CLOSED)
26
+ return;
27
+ this.closedByClient = true;
28
+ this.readyState = FakeSocket.CLOSED;
29
+ }
30
+ }
31
+ export class FakeServer {
32
+ sockets = [];
33
+ WebSocket;
34
+ constructor() {
35
+ const sockets = this.sockets;
36
+ class BoundFakeSocket extends FakeSocket {
37
+ constructor(url) {
38
+ super(url);
39
+ sockets.push(this);
40
+ }
41
+ }
42
+ this.WebSocket = BoundFakeSocket;
43
+ }
44
+ /** The most recently created socket. */
45
+ get socket() {
46
+ const s = this.sockets[this.sockets.length - 1];
47
+ if (!s)
48
+ throw new Error("no socket created yet");
49
+ return s;
50
+ }
51
+ /** Everything the client sent on the current socket. */
52
+ get sent() {
53
+ return this.socket.sent;
54
+ }
55
+ /** Messages of one type sent on the current socket. */
56
+ sentOfType(type) {
57
+ return this.sent.filter((m) => m.type === type);
58
+ }
59
+ /** Returns and forgets what the client sent on the current socket so far. */
60
+ drain() {
61
+ return this.socket.sent.splice(0);
62
+ }
63
+ open() {
64
+ const s = this.socket;
65
+ s.readyState = FakeSocket.OPEN;
66
+ s.onopen?.({});
67
+ }
68
+ push(message) {
69
+ const s = this.socket;
70
+ if (s.readyState !== FakeSocket.OPEN)
71
+ throw new Error("push on a socket that is not open");
72
+ s.onmessage?.({ data: JSON.stringify(message) });
73
+ }
74
+ /**
75
+ * Pushes a Transition from the socket's current version. `end` overrides fields of the end version; unspecified
76
+ * fields keep their current value.
77
+ */
78
+ transition(end, modifications = []) {
79
+ const s = this.socket;
80
+ const startVersion = { ...s.version };
81
+ const endVersion = { ...s.version, ...end };
82
+ s.version = endVersion;
83
+ this.push({ type: "Transition", startVersion, endVersion, modifications });
84
+ }
85
+ /** The server closes the current socket. */
86
+ close(code = 1011, reason = "server closed") {
87
+ const s = this.socket;
88
+ s.readyState = FakeSocket.CLOSED;
89
+ s.onclose?.({ code, reason });
90
+ }
91
+ }
92
+ /** Lets queued microtasks (query-set flush, promise continuations) run. */
93
+ export async function flushMicrotasks() {
94
+ for (let i = 0; i < 5; i++)
95
+ await Promise.resolve();
96
+ }
97
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { type ClientMessage, type ServerMessage } from "./protocol.js";
2
+ import type { Logger } from "./logging.js";
3
+ /** Silence threshold: the server pings every 15s, so 30s without any frame means the socket is dead. */
4
+ export declare const SERVER_INACTIVITY_TIMEOUT_MS = 30000;
5
+ export type SocketState = "disconnected" | "connecting" | "ready" | "stopped";
6
+ export interface WebSocketManagerCallbacks {
7
+ /**
8
+ * The socket is open, `Connect` has been sent, and the manager is not paused. The client must now (re)build the
9
+ * server state for this socket: Authenticate, ModifyQuerySet, pending mutations.
10
+ */
11
+ onOpen(): void;
12
+ /** `resume()` was called while the socket was already initialized: the client may flush anything it held back. */
13
+ onResume(): void;
14
+ /** A parsed server message (Ping is answered internally and also forwarded). */
15
+ onMessage(message: ServerMessage): void;
16
+ /** The current socket is gone (closed by either side). Called once per socket that reached `connecting`. */
17
+ onClose(reason: string, wasOpen: boolean): void;
18
+ /** Any observable state change (for connection-state subscribers). */
19
+ onStateChange(): void;
20
+ }
21
+ export interface WebSocketManagerOptions {
22
+ url: string;
23
+ sessionId: string;
24
+ webSocketConstructor: typeof WebSocket;
25
+ logger: Logger;
26
+ }
27
+ /**
28
+ * Owns the WebSocket: connecting, the `Connect` handshake, the server-silence watchdog, Pong replies, and
29
+ * reconnecting with exponential backoff and jitter.
30
+ *
31
+ * Outgoing traffic is only possible while the socket is `ready` and not paused. While a reconnect is underway (the
32
+ * socket is `connecting` or `disconnected`) nothing is sent: the client keeps its state locally and rebuilds the
33
+ * server's view of it in `onOpen` on the next socket. `pause()` additionally holds back `onOpen` (or lets the client
34
+ * hold back its own sends) while the client fetches an auth token, so that queries are not run with stale identity.
35
+ */
36
+ export declare class WebSocketManager {
37
+ private readonly url;
38
+ private readonly sessionId;
39
+ private readonly WS;
40
+ private readonly logger;
41
+ private readonly callbacks;
42
+ private ws;
43
+ private _state;
44
+ /** True once `Connect` was sent and `onOpen` delivered for the current socket. */
45
+ private initialized;
46
+ private paused;
47
+ private _connectionCount;
48
+ private _hasEverConnected;
49
+ private lastCloseReason;
50
+ private retries;
51
+ private reconnectTimer;
52
+ private watchdogTimer;
53
+ private readonly removeEnvListeners;
54
+ constructor(options: WebSocketManagerOptions, callbacks: WebSocketManagerCallbacks);
55
+ /** Opens the first socket. */
56
+ start(): void;
57
+ get state(): SocketState;
58
+ get connectionCount(): number;
59
+ get hasEverConnected(): boolean;
60
+ get connectionRetries(): number;
61
+ get isPaused(): boolean;
62
+ /** True when a message handed to `send` would go out right now. */
63
+ canSend(): boolean;
64
+ /** Sends if possible; returns whether the message was sent. */
65
+ send(message: ClientMessage): boolean;
66
+ /** Called by the client once the server has proven healthy (a Transition was applied): resets the backoff. */
67
+ markHealthy(): void;
68
+ /** Holds back `onOpen` and client sends until `resume()`. Pings are still answered. */
69
+ pause(): void;
70
+ resume(): void;
71
+ /** Drops the current socket and reconnects after a backoff delay. */
72
+ reconnect(reason: string): void;
73
+ /** Permanently closes the socket. */
74
+ stop(): void;
75
+ private rawSend;
76
+ private connect;
77
+ /** Detaches and closes the current socket (if any), reporting `onClose` once. */
78
+ private closeCurrent;
79
+ private scheduleReconnect;
80
+ /** Exponential backoff with "equal jitter": half fixed, half random. */
81
+ private nextBackoff;
82
+ private clearReconnectTimer;
83
+ private resetWatchdog;
84
+ private clearWatchdog;
85
+ /** Skip the backoff wait when the browser comes back online or the tab becomes visible. */
86
+ private installEnvListeners;
87
+ }
88
+ //# sourceMappingURL=web_socket_manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web_socket_manager.d.ts","sourceRoot":"","sources":["../src/web_socket_manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,wGAAwG;AACxG,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAQnD,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,MAAM,IAAI,IAAI,CAAC;IACf,kHAAkH;IAClH,QAAQ,IAAI,IAAI,CAAC;IACjB,gFAAgF;IAChF,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACxC,4GAA4G;IAC5G,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAChD,sEAAsE;IACtE,aAAa,IAAI,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,OAAO,SAAS,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IAEtD,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAA+B;IAC7C,kFAAkF;IAClF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAa;gBAEpC,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,yBAAyB;IASlF,8BAA8B;IAC9B,KAAK,IAAI,IAAI;IAIb,IAAI,KAAK,IAAI,WAAW,CAEvB;IACD,IAAI,eAAe,IAAI,MAAM,CAE5B;IACD,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IACD,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IACD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,mEAAmE;IACnE,OAAO,IAAI,OAAO;IAIlB,+DAA+D;IAC/D,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO;IAQrC,8GAA8G;IAC9G,WAAW,IAAI,IAAI;IAInB,uFAAuF;IACvF,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;IAcd,qEAAqE;IACrE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,qCAAqC;IACrC,IAAI,IAAI,IAAI;IAWZ,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,OAAO;IAmEf,iFAAiF;IACjF,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,iBAAiB;IAYzB,wEAAwE;IACxE,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;IAOrB,2FAA2F;IAC3F,OAAO,CAAC,mBAAmB;CAoB5B"}
@@ -0,0 +1,291 @@
1
+ import { CLIENT_VERSION } from "./protocol.js";
2
+ /** Silence threshold: the server pings every 15s, so 30s without any frame means the socket is dead. */
3
+ export const SERVER_INACTIVITY_TIMEOUT_MS = 30_000;
4
+ const INITIAL_BACKOFF_MS = 250;
5
+ const MAX_BACKOFF_MS = 16_000;
6
+ /** The close code a server sends when it restarts or hands over to a new process (RFC 6455 "Service Restart"). */
7
+ const SERVICE_RESTART = 1012;
8
+ /** Spread of the immediate reconnects after a restart, so every client doesn't arrive in the same millisecond. */
9
+ const RESTART_JITTER_MS = 250;
10
+ /**
11
+ * Owns the WebSocket: connecting, the `Connect` handshake, the server-silence watchdog, Pong replies, and
12
+ * reconnecting with exponential backoff and jitter.
13
+ *
14
+ * Outgoing traffic is only possible while the socket is `ready` and not paused. While a reconnect is underway (the
15
+ * socket is `connecting` or `disconnected`) nothing is sent: the client keeps its state locally and rebuilds the
16
+ * server's view of it in `onOpen` on the next socket. `pause()` additionally holds back `onOpen` (or lets the client
17
+ * hold back its own sends) while the client fetches an auth token, so that queries are not run with stale identity.
18
+ */
19
+ export class WebSocketManager {
20
+ url;
21
+ sessionId;
22
+ WS;
23
+ logger;
24
+ callbacks;
25
+ ws = null;
26
+ _state = "disconnected";
27
+ /** True once `Connect` was sent and `onOpen` delivered for the current socket. */
28
+ initialized = false;
29
+ paused = false;
30
+ _connectionCount = -1;
31
+ _hasEverConnected = false;
32
+ lastCloseReason = null;
33
+ retries = 0;
34
+ reconnectTimer = null;
35
+ watchdogTimer = null;
36
+ removeEnvListeners;
37
+ constructor(options, callbacks) {
38
+ this.url = options.url;
39
+ this.sessionId = options.sessionId;
40
+ this.WS = options.webSocketConstructor;
41
+ this.logger = options.logger;
42
+ this.callbacks = callbacks;
43
+ this.removeEnvListeners = this.installEnvListeners();
44
+ }
45
+ /** Opens the first socket. */
46
+ start() {
47
+ if (this.ws === null && this.reconnectTimer === null)
48
+ this.connect();
49
+ }
50
+ get state() {
51
+ return this._state;
52
+ }
53
+ get connectionCount() {
54
+ return Math.max(this._connectionCount, 0);
55
+ }
56
+ get hasEverConnected() {
57
+ return this._hasEverConnected;
58
+ }
59
+ get connectionRetries() {
60
+ return this.retries;
61
+ }
62
+ get isPaused() {
63
+ return this.paused;
64
+ }
65
+ /** True when a message handed to `send` would go out right now. */
66
+ canSend() {
67
+ return this._state === "ready" && this.initialized && !this.paused;
68
+ }
69
+ /** Sends if possible; returns whether the message was sent. */
70
+ send(message) {
71
+ if (!this.canSend()) {
72
+ this.logger.debug("not sent (socket not ready):", message.type);
73
+ return false;
74
+ }
75
+ return this.rawSend(message);
76
+ }
77
+ /** Called by the client once the server has proven healthy (a Transition was applied): resets the backoff. */
78
+ markHealthy() {
79
+ this.retries = 0;
80
+ }
81
+ /** Holds back `onOpen` and client sends until `resume()`. Pings are still answered. */
82
+ pause() {
83
+ if (this._state === "stopped")
84
+ return;
85
+ this.paused = true;
86
+ this.callbacks.onStateChange();
87
+ }
88
+ resume() {
89
+ if (!this.paused)
90
+ return;
91
+ this.paused = false;
92
+ if (this._state === "ready") {
93
+ if (!this.initialized) {
94
+ this.initialized = true;
95
+ this.callbacks.onOpen();
96
+ }
97
+ else {
98
+ this.callbacks.onResume();
99
+ }
100
+ }
101
+ this.callbacks.onStateChange();
102
+ }
103
+ /** Drops the current socket and reconnects after a backoff delay. */
104
+ reconnect(reason) {
105
+ if (this._state === "stopped")
106
+ return;
107
+ this.logger.debug(`reconnecting: ${reason}`);
108
+ this.closeCurrent(reason);
109
+ this.scheduleReconnect(reason);
110
+ }
111
+ /** Permanently closes the socket. */
112
+ stop() {
113
+ if (this._state === "stopped")
114
+ return;
115
+ this.closeCurrent("ClientClosed");
116
+ this.clearReconnectTimer();
117
+ this._state = "stopped";
118
+ this.removeEnvListeners();
119
+ this.callbacks.onStateChange();
120
+ }
121
+ // -------------------------------------------------------------------------------------------------------------
122
+ rawSend(message) {
123
+ const ws = this.ws;
124
+ if (!ws)
125
+ return false;
126
+ try {
127
+ this.logger.debug("->", message);
128
+ ws.send(JSON.stringify(message));
129
+ return true;
130
+ }
131
+ catch (e) {
132
+ this.logger.warn("WebSocket send failed", e);
133
+ this.reconnect("SendFailed");
134
+ return false;
135
+ }
136
+ }
137
+ connect() {
138
+ if (this._state === "stopped")
139
+ return;
140
+ this.clearReconnectTimer();
141
+ let ws;
142
+ try {
143
+ ws = new this.WS(this.url);
144
+ }
145
+ catch (e) {
146
+ this.logger.warn("failed to construct WebSocket", e);
147
+ this._state = "disconnected";
148
+ this.scheduleReconnect("ConstructorFailed");
149
+ return;
150
+ }
151
+ this.ws = ws;
152
+ this._state = "connecting";
153
+ this.initialized = false;
154
+ this._connectionCount += 1;
155
+ this.callbacks.onStateChange();
156
+ ws.onopen = () => {
157
+ if (this.ws !== ws)
158
+ return;
159
+ this._state = "ready";
160
+ this._hasEverConnected = true;
161
+ this.resetWatchdog();
162
+ this.rawSend({
163
+ type: "Connect",
164
+ sessionId: this.sessionId,
165
+ connectionCount: this._connectionCount,
166
+ lastCloseReason: this.lastCloseReason,
167
+ clientVersion: CLIENT_VERSION,
168
+ features: ["diffs"],
169
+ });
170
+ if (!this.paused) {
171
+ this.initialized = true;
172
+ this.callbacks.onOpen();
173
+ }
174
+ this.callbacks.onStateChange();
175
+ };
176
+ ws.onmessage = (event) => {
177
+ if (this.ws !== ws)
178
+ return;
179
+ this.resetWatchdog();
180
+ let message;
181
+ try {
182
+ message = JSON.parse(typeof event.data === "string" ? event.data : String(event.data));
183
+ }
184
+ catch (e) {
185
+ this.logger.warn("received malformed frame", e);
186
+ this.reconnect("MalformedMessage");
187
+ return;
188
+ }
189
+ this.logger.debug("<-", message);
190
+ if (message.type === "Ping")
191
+ this.rawSend({ type: "Pong" });
192
+ this.callbacks.onMessage(message);
193
+ };
194
+ ws.onerror = () => {
195
+ // An error is always followed by a close event; reconnection is handled there.
196
+ if (this.ws === ws)
197
+ this.logger.debug("WebSocket error");
198
+ };
199
+ ws.onclose = (event) => {
200
+ if (this.ws !== ws)
201
+ return;
202
+ const reason = event?.reason ? `${event.code}: ${event.reason}` : `closed (${event?.code ?? "?"})`;
203
+ this.closeCurrent(reason);
204
+ // 1012 "service restart": the server is restarting or handing over to a new process, and the new one is
205
+ // already accepting (or about to): reconnect right away (with a little jitter) instead of backing off.
206
+ if (event?.code === SERVICE_RESTART)
207
+ this.retries = 0;
208
+ this.scheduleReconnect(reason, event?.code === SERVICE_RESTART ? Math.random() * RESTART_JITTER_MS : undefined);
209
+ };
210
+ }
211
+ /** Detaches and closes the current socket (if any), reporting `onClose` once. */
212
+ closeCurrent(reason) {
213
+ this.clearWatchdog();
214
+ const ws = this.ws;
215
+ if (!ws)
216
+ return;
217
+ const wasOpen = this._state === "ready";
218
+ this.ws = null;
219
+ this.initialized = false;
220
+ this._state = "disconnected";
221
+ this.lastCloseReason = reason;
222
+ ws.onopen = null;
223
+ ws.onmessage = null;
224
+ ws.onerror = null;
225
+ ws.onclose = null;
226
+ try {
227
+ ws.close();
228
+ }
229
+ catch {
230
+ // ignore
231
+ }
232
+ this.callbacks.onClose(reason, wasOpen);
233
+ this.callbacks.onStateChange();
234
+ }
235
+ scheduleReconnect(reason, delayMs) {
236
+ if (this._state === "stopped" || this.reconnectTimer !== null)
237
+ return;
238
+ const delay = delayMs ?? this.nextBackoff();
239
+ this.retries += 1;
240
+ this.logger.info(`connection lost (${reason}); reconnecting in ${Math.round(delay)}ms`);
241
+ this.reconnectTimer = setTimeout(() => {
242
+ this.reconnectTimer = null;
243
+ this.connect();
244
+ }, delay);
245
+ this.callbacks.onStateChange();
246
+ }
247
+ /** Exponential backoff with "equal jitter": half fixed, half random. */
248
+ nextBackoff() {
249
+ const cap = Math.min(MAX_BACKOFF_MS, INITIAL_BACKOFF_MS * 2 ** Math.min(this.retries, 16));
250
+ return cap / 2 + Math.random() * (cap / 2);
251
+ }
252
+ clearReconnectTimer() {
253
+ if (this.reconnectTimer !== null) {
254
+ clearTimeout(this.reconnectTimer);
255
+ this.reconnectTimer = null;
256
+ }
257
+ }
258
+ resetWatchdog() {
259
+ this.clearWatchdog();
260
+ this.watchdogTimer = setTimeout(() => {
261
+ this.watchdogTimer = null;
262
+ this.reconnect("ServerInactivity");
263
+ }, SERVER_INACTIVITY_TIMEOUT_MS);
264
+ }
265
+ clearWatchdog() {
266
+ if (this.watchdogTimer !== null) {
267
+ clearTimeout(this.watchdogTimer);
268
+ this.watchdogTimer = null;
269
+ }
270
+ }
271
+ /** Skip the backoff wait when the browser comes back online or the tab becomes visible. */
272
+ installEnvListeners() {
273
+ const g = globalThis;
274
+ if (typeof g.addEventListener !== "function" || typeof g.removeEventListener !== "function")
275
+ return () => { };
276
+ const kick = () => {
277
+ if (g.document?.visibilityState === "hidden")
278
+ return;
279
+ if (this._state === "disconnected" && this.reconnectTimer !== null) {
280
+ this.connect();
281
+ }
282
+ };
283
+ g.addEventListener("online", kick);
284
+ g.addEventListener("visibilitychange", kick);
285
+ return () => {
286
+ g.removeEventListener?.("online", kick);
287
+ g.removeEventListener?.("visibilitychange", kick);
288
+ };
289
+ }
290
+ }
291
+ //# sourceMappingURL=web_socket_manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web_socket_manager.js","sourceRoot":"","sources":["../src/web_socket_manager.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@rxferrix/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Framework-agnostic reactive sync client for RxFerrix",
5
+ "license": "MIT",
6
+ "homepage": "https://genesysflow.github.io/rxferrix/",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/genesysflow/rxferrix.git",
10
+ "directory": "packages/client"
11
+ },
12
+ "keywords": [
13
+ "rxferrix",
14
+ "reactive",
15
+ "realtime",
16
+ "websocket"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./testing": {
29
+ "types": "./dist/testing.d.ts",
30
+ "import": "./dist/testing.js",
31
+ "default": "./dist/testing.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "scripts": {
40
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "prepublishOnly": "bun run build"
44
+ },
45
+ "devDependencies": {
46
+ "typescript": "^6.0.3",
47
+ "vitest": "^5.0.3"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }
package/src/api.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Function references: typed handles to server functions.
3
+ *
4
+ * Codegen emits an `api` object whose leaves are created with {@link makeFunctionReference}:
5
+ *
6
+ * ```ts
7
+ * export const api = {
8
+ * todos: {
9
+ * list: makeFunctionReference<"query", { done?: boolean | null }, Doc<"todos">[]>("query", "todos:list"),
10
+ * },
11
+ * };
12
+ * ```
13
+ */
14
+
15
+ /** The three kinds of public server functions. */
16
+ export type FunctionKind = "query" | "mutation" | "action";
17
+
18
+ /**
19
+ * A typed reference to a server function.
20
+ *
21
+ * Only `_kind` and `_path` exist at runtime. `__args` and `__output` are phantom fields that carry the argument and
22
+ * return types for inference; they are never present on the object.
23
+ */
24
+ export interface FunctionReference<
25
+ Kind extends FunctionKind = FunctionKind,
26
+ Args = any,
27
+ Output = any,
28
+ > {
29
+ readonly _kind: Kind;
30
+ readonly _path: string;
31
+ /** Phantom: never present at runtime. */
32
+ readonly __args: Args;
33
+ /** Phantom: never present at runtime. */
34
+ readonly __output: Output;
35
+ }
36
+
37
+ /** Any reference to a query. */
38
+ export type AnyQueryReference = FunctionReference<"query", any, any>;
39
+ /** Any reference to a mutation. */
40
+ export type AnyMutationReference = FunctionReference<"mutation", any, any>;
41
+ /** Any reference to an action. */
42
+ export type AnyActionReference = FunctionReference<"action", any, any>;
43
+
44
+ /** Creates a function reference. Used by generated code; rarely needed by hand. */
45
+ export function makeFunctionReference<Kind extends FunctionKind, Args = Record<string, never>, Output = unknown>(
46
+ kind: Kind,
47
+ path: string,
48
+ ): FunctionReference<Kind, Args, Output> {
49
+ return Object.freeze({ _kind: kind, _path: path }) as unknown as FunctionReference<Kind, Args, Output>;
50
+ }
51
+
52
+ /** The argument object type of a function reference. */
53
+ export type ArgsOf<F> = F extends FunctionReference<any, infer A, any> ? A : never;
54
+
55
+ /** The return type of a function reference. */
56
+ export type OutputOf<F> = F extends FunctionReference<any, any, infer O> ? O : never;
57
+
58
+ /** True when every key of `Args` is optional (so the whole object may be omitted). */
59
+ export type HasNoRequiredArgs<Args> = {} extends Args ? true : false;
60
+
61
+ /**
62
+ * Rest-parameter tuple for a function's args: optional when the args object has no required keys.
63
+ *
64
+ * `fn(ref, ...args: OptionalArgs<typeof ref>)`
65
+ */
66
+ export type OptionalArgs<F> = HasNoRequiredArgs<ArgsOf<F>> extends true
67
+ ? [args?: ArgsOf<F>]
68
+ : [args: ArgsOf<F>];
69
+
70
+ /** Like {@link OptionalArgs} but also accepts `"skip"` in place of the args. */
71
+ export type OptionalArgsOrSkip<F> = HasNoRequiredArgs<ArgsOf<F>> extends true
72
+ ? [args?: ArgsOf<F> | "skip"]
73
+ : [args: ArgsOf<F> | "skip"];
74
+
75
+ /** Args followed by an optional options object; the args are optional when they have no required keys. */
76
+ export type ArgsAndOptions<F, Options> = HasNoRequiredArgs<ArgsOf<F>> extends true
77
+ ? [args?: ArgsOf<F>, options?: Options]
78
+ : [args: ArgsOf<F>, options?: Options];
79
+
80
+ /** A document ID for the given table. At runtime it is a plain string. */
81
+ export type Id<TableName extends string> = string & { __tableName: TableName };
82
+
83
+ /** Returns the `udfPath` (e.g. `"todos:list"`) of a function reference. */
84
+ export function getFunctionName(ref: FunctionReference): string {
85
+ return ref._path;
86
+ }