@nanolink/mirrors 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @nanolink/mirrors
2
+
3
+ GraphQL subscription client + mirror synchronization helper utilities.
4
+
5
+ ## Features
6
+ - Lightweight `SubscriptionClient` around `graphql-ws` with manual reconnect & lifecycle events.
7
+ - `MirrorSync` keeps a local in‑memory mirror using START / UPDATED / DELETED / DONE / VERSION_ERROR messages.
8
+ - Deferred `loaded` promise resolves after first full sync DONE.
9
+ - Automatic resubscribe after reconnect (from last version) without duplicating items.
10
+ - Read‑only delegated map interface for safe consumer access.
11
+ - `Connection` helper to manage multiple named mirrors and re‑emit namespaced events (`mirror:start`, `mirror:updated`, ...).
12
+ - Optional global proxy support via `global-agent` (enable before creating the client).
13
+
14
+ ## Install
15
+ ```
16
+ npm install @nanolink/mirrors
17
+ ```
18
+
19
+ If you need a proxy:
20
+ ```js
21
+ // enableGlobalProxy.js
22
+ import 'global-agent/bootstrap';
23
+ process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy:3128';
24
+ ```
25
+ Run node with: `node -r global-agent/bootstrap yourApp.js`
26
+
27
+ ## Usage
28
+ ```ts
29
+ import { Connection } from '@nanolink/mirrors';
30
+
31
+ const conn = new Connection('https://api.example.com', 'TOKEN');
32
+ conn.connect();
33
+
34
+ conn.on('connected', () => console.log('socket up'));
35
+ conn.on('mirror:updated', e => console.log('item updated', e.mirrorName, e.item.id));
36
+
37
+ async function start() {
38
+ const users = await conn.getMirror('users', /* GraphQL subscription */ `
39
+ subscription Users($version: Long!) {
40
+ users(version: $version) { type total deleteId deleteVersion data { id version name } }
41
+ }
42
+ `, {});
43
+
44
+ // Waits until first DONE; then you can read:
45
+ console.log('Initial size', users.size);
46
+ for (const [id, user] of users) console.log(user);
47
+ }
48
+ start();
49
+ ```
50
+
51
+ ## Events
52
+ `SubscriptionClient` emits:
53
+ - connecting, connected, reconnected, disconnected, retry, error
54
+
55
+ `Connection` re‑emits mirror events as `mirror:<event>` with payload `{ mirrorName, ... }`:
56
+ - start, updated, deleted, done, versionError, resubscribe, error, removed, cleared
57
+
58
+ ## API Surface
59
+ - `SubscriptionClient` - low level client (call `connect()` then `subscribe()`)
60
+ - `MirrorSync` - single mirror controller (normally use via `Connection.getMirror()`)
61
+ - `Connection` - multi‑mirror manager extending `SubscriptionClient`
62
+ - `ReadonlyMapView` - interface returned by `getMirror()` / `MirrorSync.load()`
63
+
64
+ ## Notes
65
+ - Always call `connect()` explicitly; there is no implicit lazy connect.
66
+ - A VERSION_ERROR triggers an automatic full resync (resetting the loaded promise).
67
+ - The first (and only the first) field in the GraphQL payload is used as the sync message wrapper.
68
+
69
+ ## Build
70
+ TypeScript compiles to `dist/`. Published package exposes `dist/index.js` and types.
71
+
72
+ ## License
73
+ MIT
@@ -0,0 +1,23 @@
1
+ export interface ReadonlyMapView<K, V> extends Iterable<[K, V]> {
2
+ readonly size: number;
3
+ get(key: K): V | undefined;
4
+ has(key: K): boolean;
5
+ entries(): IterableIterator<[K, V]>;
6
+ keys(): IterableIterator<K>;
7
+ values(): IterableIterator<V>;
8
+ forEach(callbackfn: (value: V, key: K, map: ReadonlyMapView<K, V>) => void, thisArg?: any): void;
9
+ [Symbol.iterator](): IterableIterator<[K, V]>;
10
+ }
11
+ export declare class DelegatingMap<K, V> implements ReadonlyMapView<K, V> {
12
+ private readonly resolver;
13
+ constructor(resolver: () => Map<K, V>);
14
+ private t;
15
+ get size(): number;
16
+ get(key: K): V | undefined;
17
+ has(key: K): boolean;
18
+ entries(): IterableIterator<[K, V]>;
19
+ keys(): IterableIterator<K>;
20
+ values(): IterableIterator<V>;
21
+ forEach(callbackfn: (value: V, key: K, map: ReadonlyMapView<K, V>) => void, thisArg?: any): void;
22
+ [Symbol.iterator](): IterableIterator<[K, V]>;
23
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DelegatingMap = void 0;
4
+ class DelegatingMap {
5
+ constructor(resolver) {
6
+ this.resolver = resolver;
7
+ }
8
+ t() { return this.resolver(); }
9
+ get size() { return this.t().size; }
10
+ get(key) { return this.t().get(key); }
11
+ has(key) { return this.t().has(key); }
12
+ entries() { return this.t().entries(); }
13
+ keys() { return this.t().keys(); }
14
+ values() { return this.t().values(); }
15
+ forEach(callbackfn, thisArg) {
16
+ this.t().forEach(((v, k) => callbackfn(v, k, this)), thisArg);
17
+ }
18
+ [Symbol.iterator]() { return this.t()[Symbol.iterator](); }
19
+ }
20
+ exports.DelegatingMap = DelegatingMap;
21
+ //# sourceMappingURL=DelegateMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegateMap.js","sourceRoot":"","sources":["../src/DelegateMap.ts"],"names":[],"mappings":";;;AAYA,MAAa,aAAa;IACxB,YAA6B,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAI,CAAC;IACnD,CAAC,KAAgB,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAClD,IAAI,IAAI,KAAa,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,GAAG,CAAC,GAAM,IAAmB,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACxD,GAAG,CAAC,GAAM,IAAa,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClD,OAAO,KAA+B,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAClE,IAAI,KAA0B,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvD,MAAM,KAA0B,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,UAAkE,EAAE,OAAa;QACvF,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAI,EAAE,CAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAQ,EAAE,OAAO,CAAC,CAAC;IAC7E,CAAC;IACD,CAAC,MAAM,CAAC,QAAQ,CAAC,KAA+B,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;CACtF;AAbD,sCAaC"}
@@ -0,0 +1,100 @@
1
+ import { ReadonlyMapView } from './DelegateMap';
2
+ import { SubscriptionClient } from './SubscriptionClient';
3
+ import EventEmitter from 'eventemitter3';
4
+ export declare enum SyncType {
5
+ DELETED = "DELETED",
6
+ UPDATED = "UPDATED",
7
+ START = "START",
8
+ DONE = "DONE",
9
+ VERSION_ERROR = "VERSION_ERROR"
10
+ }
11
+ export interface CacheSyncResult {
12
+ type: SyncType;
13
+ total?: number | null;
14
+ deleteId?: string;
15
+ data?: any;
16
+ deleteVersion?: number;
17
+ deleteOpVersion?: string;
18
+ opVersion?: string;
19
+ }
20
+ export interface MirrorSyncOptions {
21
+ /** Subscription document (query string) */
22
+ subscription: string;
23
+ /** Optional logger */
24
+ /** Custom variables base (besides version) */
25
+ baseVariables?: Record<string, unknown>;
26
+ }
27
+ export interface MirrorStats {
28
+ version: number;
29
+ opVersion?: string | null;
30
+ size: number;
31
+ fullSyncInProgress: boolean;
32
+ totalHint?: number | null;
33
+ }
34
+ export interface MirrorSyncEvents {
35
+ start: (info: {
36
+ full: boolean;
37
+ }) => void;
38
+ updated: (item: MirrorItem, isFullPhase: boolean) => void;
39
+ deleted: (info: {
40
+ id: string;
41
+ version: number | undefined;
42
+ }) => void;
43
+ done: (info: {
44
+ full: boolean;
45
+ }) => void;
46
+ versionError: (info: {
47
+ currentVersion: number;
48
+ }) => void;
49
+ resubscribe: (info: {
50
+ fromVersion: number;
51
+ }) => void;
52
+ error?: (errors: any) => void;
53
+ }
54
+ export interface MirrorItem {
55
+ id: string;
56
+ version?: number;
57
+ opVersion?: string;
58
+ [k: string]: any;
59
+ }
60
+ /**
61
+ * MirrorSync maintains an in-memory mirror of server state based on CacheSyncResult messages.
62
+ * Generic TBase is the item shape (must include id & version).
63
+ */
64
+ export declare class MirrorSync extends EventEmitter {
65
+ private client;
66
+ private options;
67
+ private mirror;
68
+ private buffer;
69
+ private currentVersion;
70
+ private currentOpVersion;
71
+ private unsub;
72
+ private fullSyncPhase;
73
+ private log?;
74
+ private dmap;
75
+ /** Deferred promise resolved on first DONE after a (re)start full sync cycle. Reset on constructor, start(), and VERSION_ERROR re-init. */
76
+ loaded: Promise<ReadonlyMapView<string, any>>;
77
+ private resolveLoaded?;
78
+ constructor(client: SubscriptionClient, options: MirrorSyncOptions & {
79
+ log?: (msg: string, meta?: any) => void;
80
+ });
81
+ private l;
82
+ private attachClientEvents;
83
+ private needsFullSync;
84
+ private targetMap;
85
+ private advanceNumeric;
86
+ private advanceOp;
87
+ private subscribe;
88
+ private resubscribe;
89
+ private start;
90
+ load(): Promise<ReadonlyMapView<string, any>>;
91
+ private resetLoaded;
92
+ private handlePayload;
93
+ private normalizeItems;
94
+ private applyUpdated;
95
+ private applyDeleted;
96
+ get(id: string): any | undefined;
97
+ values(): any[];
98
+ stats(): MirrorStats;
99
+ stop(): void;
100
+ }
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MirrorSync = exports.SyncType = void 0;
7
+ const DelegateMap_1 = require("./DelegateMap");
8
+ const eventemitter3_1 = __importDefault(require("eventemitter3"));
9
+ // SyncType enum as described
10
+ var SyncType;
11
+ (function (SyncType) {
12
+ SyncType["DELETED"] = "DELETED";
13
+ SyncType["UPDATED"] = "UPDATED";
14
+ SyncType["START"] = "START";
15
+ SyncType["DONE"] = "DONE";
16
+ SyncType["VERSION_ERROR"] = "VERSION_ERROR";
17
+ })(SyncType || (exports.SyncType = SyncType = {}));
18
+ /**
19
+ * MirrorSync maintains an in-memory mirror of server state based on CacheSyncResult messages.
20
+ * Generic TBase is the item shape (must include id & version).
21
+ */
22
+ class MirrorSync extends eventemitter3_1.default {
23
+ constructor(client, options) {
24
+ super();
25
+ this.mirror = new Map();
26
+ this.buffer = null; // used during full/resubscription phase
27
+ this.currentVersion = -1; // numeric cursor (-1 => full sync needed)
28
+ this.currentOpVersion = null; // op cursor (null => full sync needed)
29
+ this.unsub = null;
30
+ this.fullSyncPhase = false; // only true during START..DONE when initiating from version -1
31
+ this.dmap = new DelegateMap_1.DelegatingMap(() => this.mirror);
32
+ this.client = client;
33
+ this.options = options;
34
+ this.log = options.log;
35
+ this.attachClientEvents();
36
+ this.resetLoaded();
37
+ }
38
+ l(msg, meta) { if (this.log)
39
+ this.log(msg, meta); }
40
+ attachClientEvents() {
41
+ this.client.on('reconnected', () => {
42
+ // On reconnect start a resubscription from currentVersion
43
+ this.emit('resubscribe', { fromVersion: this.currentVersion }); // keep existing event shape (no new events)
44
+ this.resubscribe();
45
+ });
46
+ // Handle first successful connection if start was invoked early
47
+ this.client.on('connected', () => {
48
+ if ((this.currentVersion === -1 || this.currentOpVersion === null) && !this.unsub) {
49
+ this.l('initial connected subscribe', { version: this.currentVersion, opVersion: this.currentOpVersion });
50
+ this.subscribe(this.currentVersion, this.currentOpVersion);
51
+ }
52
+ });
53
+ }
54
+ needsFullSync() { return this.currentVersion === -1 || this.currentOpVersion === null; }
55
+ targetMap() { return (this.fullSyncPhase && this.buffer) ? this.buffer : this.mirror; }
56
+ advanceNumeric(v) { if (typeof v === 'number' && v > this.currentVersion)
57
+ this.currentVersion = v; }
58
+ advanceOp(v) { if (v && (!this.currentOpVersion || v > this.currentOpVersion))
59
+ this.currentOpVersion = v; }
60
+ subscribe(version, opVersion) {
61
+ if (!this.client)
62
+ throw new Error('SubscriptionClient missing');
63
+ if (this.unsub) {
64
+ this.unsub();
65
+ this.unsub = null;
66
+ }
67
+ const variables = { version, opVersion, ...(this.options.baseVariables || {}) };
68
+ this.l('subscribe', { version, opVersion });
69
+ this.unsub = this.client.subscribe({ query: this.options.subscription, variables }, {
70
+ next: (envelope) => {
71
+ if (envelope.errors) {
72
+ this.l('graphql errors', envelope.errors);
73
+ this.emit('error', envelope.errors);
74
+ }
75
+ const payload = envelope.data;
76
+ if (payload)
77
+ this.handlePayload(payload);
78
+ },
79
+ error: (err) => this.l('subscription error', err),
80
+ complete: () => this.l('subscription complete'),
81
+ });
82
+ }
83
+ resubscribe() {
84
+ // Resubscribe starting from current cursors
85
+ this.subscribe(this.currentVersion, this.currentOpVersion);
86
+ }
87
+ start() {
88
+ if (!this.client.isConnected()) {
89
+ this.l('start deferred (not connected)');
90
+ return;
91
+ }
92
+ this.subscribe(this.currentVersion, this.currentOpVersion);
93
+ }
94
+ load() {
95
+ if (!this.unsub)
96
+ this.start();
97
+ return this.loaded;
98
+ }
99
+ resetLoaded() {
100
+ this.loaded = new Promise(resolve => {
101
+ this.resolveLoaded = resolve;
102
+ });
103
+ }
104
+ handlePayload(raw) {
105
+ if (!raw)
106
+ return;
107
+ // Always pick the first field in the payload object regardless of configured rootField
108
+ const firstKey = Object.keys(raw)[0];
109
+ if (!firstKey)
110
+ return;
111
+ const msg = raw[firstKey];
112
+ if (!msg)
113
+ return;
114
+ const { type } = msg;
115
+ switch (type) {
116
+ case SyncType.START: {
117
+ const isFull = this.needsFullSync();
118
+ this.fullSyncPhase = isFull;
119
+ // Only allocate a new buffer for a true full sync; for delta/resubscribe we append directly
120
+ this.buffer = isFull ? new Map() : null;
121
+ const payload = { full: isFull };
122
+ this.emit('start', payload);
123
+ break;
124
+ }
125
+ case SyncType.UPDATED:
126
+ this.applyUpdated(msg.data, this.fullSyncPhase);
127
+ break;
128
+ case SyncType.DELETED:
129
+ this.applyDeleted(msg.deleteId, msg.deleteVersion, msg.deleteOpVersion);
130
+ break;
131
+ case SyncType.DONE: {
132
+ const wasFull = this.fullSyncPhase;
133
+ if (wasFull && this.buffer) {
134
+ this.mirror = this.buffer;
135
+ this.buffer = null;
136
+ }
137
+ this.fullSyncPhase = false;
138
+ const d = { full: wasFull };
139
+ this.emit('done', d);
140
+ // Resolve deferred if pending
141
+ if (this.resolveLoaded) {
142
+ const r = this.resolveLoaded;
143
+ this.resolveLoaded = undefined;
144
+ r(this.dmap);
145
+ }
146
+ break;
147
+ }
148
+ case SyncType.VERSION_ERROR:
149
+ const ve = { currentVersion: this.currentVersion };
150
+ this.emit('versionError', ve);
151
+ // restart as full sync
152
+ this.currentVersion = -1;
153
+ this.currentOpVersion = null;
154
+ this.buffer = null;
155
+ this.resetLoaded();
156
+ this.subscribe(this.currentVersion, this.currentOpVersion);
157
+ break;
158
+ }
159
+ }
160
+ normalizeItems(data) {
161
+ if (!data)
162
+ return [];
163
+ return Array.isArray(data) ? data : [data];
164
+ }
165
+ applyUpdated(data, isFullPhase) {
166
+ for (const item of this.normalizeItems(data)) {
167
+ if (!item || typeof item.id !== 'string')
168
+ continue;
169
+ const hasNumeric = typeof item.version === 'number';
170
+ const hasOp = typeof item.opVersion === 'string';
171
+ if (!hasNumeric && !hasOp)
172
+ continue;
173
+ const existing = this.targetMap().get(item.id);
174
+ const newerNumeric = hasNumeric && (!existing?.version || item.version >= existing.version);
175
+ const newerOp = hasOp && (!existing?.opVersion || item.opVersion >= existing.opVersion);
176
+ if (!existing || newerNumeric || newerOp) {
177
+ this.targetMap().set(item.id, item);
178
+ this.advanceNumeric(item.version);
179
+ this.advanceOp(item.opVersion);
180
+ this.emit('updated', item, isFullPhase);
181
+ }
182
+ }
183
+ }
184
+ applyDeleted(id, deleteVersion, deleteOpVersion) {
185
+ if (!id)
186
+ return;
187
+ const numericStale = !this.fullSyncPhase && typeof deleteVersion === 'number' && this.currentVersion >= 0 && deleteVersion < this.currentVersion;
188
+ const opStale = !this.fullSyncPhase && deleteOpVersion && this.currentOpVersion && deleteOpVersion < this.currentOpVersion;
189
+ if (numericStale && opStale) {
190
+ this.l?.('skip stale delete', { id, deleteVersion, deleteOpVersion });
191
+ return;
192
+ }
193
+ this.targetMap().delete(id);
194
+ this.advanceNumeric(deleteVersion);
195
+ this.advanceOp(deleteOpVersion);
196
+ const del = { id, version: deleteVersion };
197
+ if (deleteOpVersion)
198
+ del.opVersion = deleteOpVersion;
199
+ this.emit('deleted', del);
200
+ }
201
+ get(id) { return this.mirror.get(id); }
202
+ values() { return Array.from(this.mirror.values()); }
203
+ stats() { return { version: this.currentVersion, opVersion: this.currentOpVersion, size: this.mirror.size, fullSyncInProgress: this.fullSyncPhase }; }
204
+ stop() {
205
+ if (this.unsub) {
206
+ this.unsub();
207
+ this.unsub = null;
208
+ }
209
+ this.currentVersion = -1;
210
+ this.currentOpVersion = null;
211
+ this.buffer = null;
212
+ if (this.resolveLoaded) {
213
+ const r = this.resolveLoaded;
214
+ this.resolveLoaded = undefined;
215
+ r(this.dmap);
216
+ }
217
+ this.resetLoaded();
218
+ }
219
+ }
220
+ exports.MirrorSync = MirrorSync;
221
+ //# sourceMappingURL=MirrorSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MirrorSync.js","sourceRoot":"","sources":["../src/MirrorSync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { ClientOptions } from 'graphql-ws';
2
+ import EventEmitter from 'eventemitter3';
3
+ /** Events emitted by SubscriptionClient
4
+ * connecting - before opening a socket (initial or reconnect attempt)
5
+ * connected - first successful connection
6
+ * reconnected - a subsequent successful connection after a disconnect
7
+ * disconnected - socket closed ( { code, reason, wasClean } )
8
+ * error - protocol or network error (Error)
9
+ * retry - about to attempt reconnect ( { attempt } )
10
+ */
11
+ export interface SubscriptionClientEvents {
12
+ connecting: () => void;
13
+ connected: () => void;
14
+ reconnected: () => void;
15
+ disconnected: (info: {
16
+ code: number;
17
+ reason: string;
18
+ wasClean: boolean;
19
+ }) => void;
20
+ error: (err: unknown) => void;
21
+ retry: (info: {
22
+ attempt: number;
23
+ }) => void;
24
+ }
25
+ export interface SubscriptionClientOptions {
26
+ url: string;
27
+ /** optional async or sync connection params */
28
+ connectionParams?: ClientOptions['connectionParams'];
29
+ /** number of reconnect attempts, default Infinity */
30
+ maxReconnectAttempts?: number;
31
+ /** initial delay ms before first retry (exponential backoff) */
32
+ retryDelayMs?: number;
33
+ /** max backoff cap */
34
+ maxRetryDelayMs?: number;
35
+ /** idle timeout for operations (optional) */
36
+ operationTimeoutMs?: number;
37
+ /** optional custom WebSocket implementation */
38
+ webSocketImpl?: any;
39
+ /** optional logger */
40
+ log?: (msg: string, meta?: any) => void;
41
+ }
42
+ export interface SubscribeOptions<TData = any, TVars = Record<string, unknown>> {
43
+ query: string;
44
+ variables?: TVars;
45
+ /** optional per-subscription timeout override */
46
+ timeoutMs?: number;
47
+ operationName?: string;
48
+ extensions?: Record<string, any>;
49
+ }
50
+ export interface Observer<T> {
51
+ next?: (data: T) => void;
52
+ error?: (err: unknown) => void;
53
+ complete?: () => void;
54
+ }
55
+ export declare class SubscriptionClient extends EventEmitter {
56
+ private options;
57
+ private client;
58
+ private connectAttempt;
59
+ private closedExplicitly;
60
+ private reconnecting;
61
+ private hasEverConnected;
62
+ constructor(opts: SubscriptionClientOptions);
63
+ private resolveWebSocketImpl;
64
+ /** Returns true if an underlying websocket client exists (connected or in-flight). */
65
+ isConnected(): boolean;
66
+ private log;
67
+ private createClientIfNeeded;
68
+ /** Establish the WebSocket connection if not already created. Idempotent. */
69
+ connect(): void;
70
+ private scheduleReconnect;
71
+ subscribe<TData = any, TVars extends Record<string, unknown> = Record<string, unknown>>(options: SubscribeOptions<TData, TVars>, observer: Observer<{
72
+ data?: TData;
73
+ errors?: any;
74
+ }>): () => void;
75
+ close(code?: number, reason?: string): Promise<void>;
76
+ }
77
+ export default SubscriptionClient;