@nanolink/mirrors 1.1.33 → 1.1.35
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 +21 -21
- package/README.md +269 -208
- package/dist/MirrorSync.d.ts +17 -0
- package/dist/MirrorSync.js +38 -0
- package/dist/MirrorSync.js.map +1 -1
- package/dist/definitions/mirrors.d.ts +1 -0
- package/dist/definitions/mirrors.js +1 -0
- package/dist/definitions/mirrors.js.map +1 -1
- package/dist/definitions/opsubscriptions/contained.js +12 -12
- package/dist/definitions/opsubscriptions/stateLinks.js +16 -16
- package/dist/definitions/opsubscriptions/trips.js +25 -25
- package/dist/definitions/requiredMirrors/roles.js +17 -17
- package/dist/definitions/requiredMirrors/settings.js +27 -27
- package/dist/definitions/requiredMirrors/systemSettings.js +25 -25
- package/dist/definitions/subscriptions/activecounter.js +13 -13
- package/dist/definitions/subscriptions/activecycles.js +20 -20
- package/dist/definitions/subscriptions/activesteps.js +24 -24
- package/dist/definitions/subscriptions/alarm.d.ts +1 -0
- package/dist/definitions/subscriptions/alarm.js +19 -0
- package/dist/definitions/subscriptions/alarm.js.map +1 -0
- package/dist/definitions/subscriptions/batterypercent.js +13 -13
- package/dist/definitions/subscriptions/calculatedodometer.js +13 -13
- package/dist/definitions/subscriptions/connected.js +12 -12
- package/dist/definitions/subscriptions/cycles.js +75 -75
- package/dist/definitions/subscriptions/gps.js +31 -31
- package/dist/definitions/subscriptions/groups.js +18 -18
- package/dist/definitions/subscriptions/humiditydecimal.js +13 -13
- package/dist/definitions/subscriptions/index.d.ts +1 -0
- package/dist/definitions/subscriptions/index.js +1 -0
- package/dist/definitions/subscriptions/index.js.map +1 -1
- package/dist/definitions/subscriptions/internalvoltage.js +14 -14
- package/dist/definitions/subscriptions/jobs.js +62 -62
- package/dist/definitions/subscriptions/linkconnected.js +12 -12
- package/dist/definitions/subscriptions/lostTransmitters.js +35 -35
- package/dist/definitions/subscriptions/meshDiagnostics.js +29 -29
- package/dist/definitions/subscriptions/meshdisconnectslasthour.js +13 -13
- package/dist/definitions/subscriptions/meshscanner.js +13 -13
- package/dist/definitions/subscriptions/messages.js +26 -26
- package/dist/definitions/subscriptions/referenceGeoLinks.js +18 -18
- package/dist/definitions/subscriptions/referenceLinks.js +18 -18
- package/dist/definitions/subscriptions/references.js +250 -250
- package/dist/definitions/subscriptions/reports.js +14 -14
- package/dist/definitions/subscriptions/servicePlans.js +42 -42
- package/dist/definitions/subscriptions/taskTemplates.js +32 -32
- package/dist/definitions/subscriptions/tasks.js +16 -16
- package/dist/definitions/subscriptions/temperature.js +13 -13
- package/dist/definitions/subscriptions/temperaturedecimal.js +13 -13
- package/dist/definitions/subscriptions/trackerLinks.js +17 -17
- package/dist/definitions/subscriptions/trackers.js +20 -20
- package/dist/definitions/subscriptions/tripignition.js +14 -14
- package/dist/definitions/subscriptions/trips.js +36 -36
- package/dist/definitions/subscriptions/trips2.js +27 -27
- package/dist/definitions/subscriptions/unplug.js +14 -14
- package/dist/definitions/subscriptions/voltage.js +14 -14
- package/dist/definitions/subscriptions/workignition.js +14 -14
- package/dist/definitions/subscriptions/workseconds.js +13 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist-compat/index.js +51 -0
- package/dist-compat/index.js.map +1 -1
- package/package.json +91 -91
package/LICENSE
CHANGED
|
@@ -1,21 +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.
|
|
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
CHANGED
|
@@ -1,208 +1,269 @@
|
|
|
1
|
-
# @nanolink/mirrors
|
|
2
|
-
|
|
3
|
-
GraphQL subscription client + in‑memory mirror synchronization utilities. Optimized for incremental change streams that send START / UPDATED / DELETED / DONE / VERSION_ERROR frames.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
* Lightweight `SubscriptionClient` around `graphql-ws` with explicit connect, controlled reconnect, and small event surface.
|
|
7
|
-
* Dual version support in `MirrorSync` (`version` numeric + optional `opVersion` string) for hybrid sequence + causality ordering (either dimension can drive resync logic when present).
|
|
8
|
-
* Stale delete & update guards: ignores events older in either version dimension to prevent resurrecting removed or outdated entities.
|
|
9
|
-
* Efficient updates: UPDATED replaces item wholesale only when newer; no deep merge overhead.
|
|
10
|
-
* Full sync cycle handling via START/DONE gates; `loaded` promise resolves after first DONE and re-arms on VERSION_ERROR.
|
|
11
|
-
* Automatic resubscribe after reconnect using last known versions (no duplicate inserts).
|
|
12
|
-
* Read‑only delegated map interface for consumers (prevents accidental mutation of internal state).
|
|
13
|
-
* `Connection` helper manages multiple mirrors, re‑emitting namespaced events (`mirror:start`, `mirror:updated`, ...).
|
|
14
|
-
* Proxy-aware WebSocket resolution: if proxy env vars are present (ALL_PROXY / HTTPS_PROXY / HTTP_PROXY / GLOBAL_AGENT_HTTP_PROXY) the client prefers the Node `ws` implementation; otherwise uses existing global WebSocket (browser / Node >=18) or falls back to `ws`.
|
|
15
|
-
* Minimal dependencies; event system via `eventemitter3`.
|
|
16
|
-
|
|
17
|
-
## Install
|
|
18
|
-
```bash
|
|
19
|
-
npm install @nanolink/mirrors
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Optional (proxy via global-agent for generic HTTP(S) requests—WebSocket selection is still handled automatically as described):
|
|
23
|
-
```js
|
|
24
|
-
// enableProxy.js
|
|
25
|
-
import 'global-agent/bootstrap';
|
|
26
|
-
process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy:3128';
|
|
27
|
-
```
|
|
28
|
-
Run with:
|
|
29
|
-
```bash
|
|
30
|
-
node -r global-agent/bootstrap app.js
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Usage
|
|
34
|
-
###
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
1
|
+
# @nanolink/mirrors
|
|
2
|
+
|
|
3
|
+
GraphQL subscription client + in‑memory mirror synchronization utilities. Optimized for incremental change streams that send START / UPDATED / DELETED / DONE / VERSION_ERROR frames.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
* Lightweight `SubscriptionClient` around `graphql-ws` with explicit connect, controlled reconnect, and small event surface.
|
|
7
|
+
* Dual version support in `MirrorSync` (`version` numeric + optional `opVersion` string) for hybrid sequence + causality ordering (either dimension can drive resync logic when present).
|
|
8
|
+
* Stale delete & update guards: ignores events older in either version dimension to prevent resurrecting removed or outdated entities.
|
|
9
|
+
* Efficient updates: UPDATED replaces item wholesale only when newer; no deep merge overhead.
|
|
10
|
+
* Full sync cycle handling via START/DONE gates; `loaded` promise resolves after first DONE and re-arms on VERSION_ERROR.
|
|
11
|
+
* Automatic resubscribe after reconnect using last known versions (no duplicate inserts).
|
|
12
|
+
* Read‑only delegated map interface for consumers (prevents accidental mutation of internal state).
|
|
13
|
+
* `Connection` helper manages multiple mirrors, re‑emitting namespaced events (`mirror:start`, `mirror:updated`, ...).
|
|
14
|
+
* Proxy-aware WebSocket resolution: if proxy env vars are present (ALL_PROXY / HTTPS_PROXY / HTTP_PROXY / GLOBAL_AGENT_HTTP_PROXY) the client prefers the Node `ws` implementation; otherwise uses existing global WebSocket (browser / Node >=18) or falls back to `ws`.
|
|
15
|
+
* Minimal dependencies; event system via `eventemitter3`.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
```bash
|
|
19
|
+
npm install @nanolink/mirrors
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Optional (proxy via global-agent for generic HTTP(S) requests—WebSocket selection is still handled automatically as described):
|
|
23
|
+
```js
|
|
24
|
+
// enableProxy.js
|
|
25
|
+
import 'global-agent/bootstrap';
|
|
26
|
+
process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy:3128';
|
|
27
|
+
```
|
|
28
|
+
Run with:
|
|
29
|
+
```bash
|
|
30
|
+
node -r global-agent/bootstrap app.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
### What is a mirror?
|
|
35
|
+
A mirror is an in-memory, read-only view of a server-side collection. The server sends GraphQL subscription frames with `START`, `UPDATED`, `DELETED`, `DONE`, and `VERSION_ERROR` messages; `MirrorSync` applies those messages to a local `Map` keyed by `id`.
|
|
36
|
+
|
|
37
|
+
Each mirror tracks the latest cursor it has seen. Older subscriptions use numeric `version` / `deleteVersion`; newer subscriptions may use string `opVersion` / `deleteOpVersion`. `MirrorSync` supports both cursor styles and resubscribes from the latest known cursor after reconnect.
|
|
38
|
+
|
|
39
|
+
`Connection` manages multiple named mirrors. It creates `MirrorSync` instances, stores them in `Connection.Mirrors`, and re-emits mirror events as `mirror:<event>` with the mirror name included.
|
|
40
|
+
|
|
41
|
+
### Quick connection with a predefined mirror
|
|
42
|
+
```ts
|
|
43
|
+
import { Connection } from '@nanolink/mirrors';
|
|
44
|
+
|
|
45
|
+
const conn = new Connection();
|
|
46
|
+
|
|
47
|
+
conn.InitConnection('wss://api.example.com/ws', async () => ({
|
|
48
|
+
authToken: 'TOKEN',
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
conn.on('connected', () => console.log('socket up'));
|
|
52
|
+
conn.on('mirror:updated', e => console.log('updated', e.mirrorName, e.doc.id));
|
|
53
|
+
|
|
54
|
+
conn.connect();
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const trackers = await conn.getPredefinedMirror('trackers');
|
|
58
|
+
|
|
59
|
+
console.log('Initial size', trackers.size);
|
|
60
|
+
for (const tracker of trackers.values()) {
|
|
61
|
+
console.log(tracker);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Custom mirror subscription
|
|
69
|
+
```ts
|
|
70
|
+
const users = await conn.getMirror('users', `
|
|
71
|
+
subscription Users($version: Long, $opVersion: String) {
|
|
72
|
+
users(version: $version, opVersion: $opVersion) {
|
|
73
|
+
type
|
|
74
|
+
total
|
|
75
|
+
deleteId
|
|
76
|
+
deleteVersion
|
|
77
|
+
deleteOpVersion
|
|
78
|
+
data { id version opVersion name }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`, {});
|
|
82
|
+
|
|
83
|
+
console.log('Initial size', users.size);
|
|
84
|
+
for (const user of users.values()) {
|
|
85
|
+
console.log(user);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Accessing the MirrorSync instance
|
|
90
|
+
`getMirror()` and `getPredefinedMirror()` wait for the initial `DONE` frame and return a `ReadonlyMapView`. If you need mirror-specific events or stats, use `registerMirror()` directly.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const mirror = conn.registerMirror('users', usersSubscription, {});
|
|
94
|
+
|
|
95
|
+
mirror.on('updated', e => console.log('user updated', e.doc.id));
|
|
96
|
+
mirror.on('deleted', e => console.log('user deleted', e.orgDoc.id));
|
|
97
|
+
|
|
98
|
+
const users = await mirror.load();
|
|
99
|
+
console.log(mirror.stats(), users.size);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Waiting for a specific mirror change
|
|
103
|
+
Use `waitForUpdated()` or `waitForDeleted()` when a command or mutation must wait until the local mirror has observed the resulting server-side change. Create the wait promise before sending the command, then await it after the command completes.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const updated = mirror.waitForUpdated(referenceId);
|
|
107
|
+
|
|
108
|
+
await saveReferenceMutation(referenceId);
|
|
109
|
+
const { doc, orgDoc } = await updated;
|
|
110
|
+
|
|
111
|
+
console.log('Mirror caught up', doc.id, orgDoc?.version, doc.version);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Both methods accept either an id or a predicate, and default to a 120 second timeout.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
await mirror.waitForUpdated(
|
|
118
|
+
event => event.doc.externalIds?.some((id: any) => id.key === 'SAP_ID' && id.value === sapId),
|
|
119
|
+
{ timeoutMs: 30_000 }
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await mirror.waitForDeleted(referenceId);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Numeric‑only subscriptions (most common)
|
|
126
|
+
Most Nanolink GraphQL subscription fields expose only a numeric `version` and omit `opVersion`. `MirrorSync` handles this seamlessly: pass just `$version` in the query and the server responses won’t include `opVersion` / `deleteOpVersion` fields.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// Numeric-only example
|
|
130
|
+
const products = await conn.getMirror('products', `
|
|
131
|
+
subscription Products($version: Long) {
|
|
132
|
+
products(version: $version) {
|
|
133
|
+
type
|
|
134
|
+
total
|
|
135
|
+
deleteId
|
|
136
|
+
deleteVersion
|
|
137
|
+
data { id version title }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`, {});
|
|
141
|
+
|
|
142
|
+
await products.loaded; // after first DONE
|
|
143
|
+
console.log('Products count', products.size);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
When `opVersion` fields are absent they are simply ignored; ordering & stale protections rely on numeric `version` only.
|
|
147
|
+
|
|
148
|
+
### Direct low-level client
|
|
149
|
+
```ts
|
|
150
|
+
import { SubscriptionClient } from '@nanolink/mirrors';
|
|
151
|
+
|
|
152
|
+
const sc = new SubscriptionClient();
|
|
153
|
+
|
|
154
|
+
sc.InitClient({
|
|
155
|
+
url: 'wss://api.example.com/ws',
|
|
156
|
+
connectionParams: async () => ({ authToken: 'TOKEN' }),
|
|
157
|
+
maxReconnectAttempts: 10,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
sc.connect();
|
|
161
|
+
|
|
162
|
+
sc.on('connected', () => {
|
|
163
|
+
const dispose = sc.clientsubscribe({
|
|
164
|
+
query: 'subscription Ping { ping }'
|
|
165
|
+
}, {
|
|
166
|
+
next: (msg) => console.log(msg),
|
|
167
|
+
error: (e) => console.error('err', e),
|
|
168
|
+
complete: () => console.log('done')
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Events
|
|
174
|
+
`SubscriptionClient` emits:
|
|
175
|
+
* connecting
|
|
176
|
+
* connected (first successful connect)
|
|
177
|
+
* reconnected (subsequent successful connect after a disconnect)
|
|
178
|
+
* disconnected ({ code, reason, wasClean })
|
|
179
|
+
* retry ({ attempt }) before a reconnect attempt delay
|
|
180
|
+
* error (network/protocol)
|
|
181
|
+
|
|
182
|
+
`Connection` re‑emits mirror events as `mirror:<event>` with payload `{ mirrorName, ... }`:
|
|
183
|
+
* start
|
|
184
|
+
* updated (only when a newer item actually replaced stored data)
|
|
185
|
+
* deleted
|
|
186
|
+
* done (end of full sync batch)
|
|
187
|
+
* versionError (triggered resync)
|
|
188
|
+
* resubscribe (automatic after reconnect)
|
|
189
|
+
* error
|
|
190
|
+
* removed (mirror explicitly removed)
|
|
191
|
+
* cleared (mirror internal state cleared)
|
|
192
|
+
|
|
193
|
+
## API Surface
|
|
194
|
+
* `SubscriptionClient` – low level websocket subscription wrapper.
|
|
195
|
+
* `MirrorSync` – single mirror controller (dual version tracking).
|
|
196
|
+
* `Connection` – manages multiple named mirrors and re-emits namespaced mirror events.
|
|
197
|
+
* `ReadonlyMapView` – read-only view returned by `getMirror()`, `getPredefinedMirror()`, and `MirrorSync.load()`.
|
|
198
|
+
* `waitForUpdated()` / `waitForDeleted()` – wait until the mirror observes a specific future update or delete event.
|
|
199
|
+
|
|
200
|
+
Note about mirror helpers
|
|
201
|
+
-------------------------
|
|
202
|
+
`getMirror()` is available for custom ad-hoc mirrors but is seldom used in most integrations. The more commonly used helper is `getPredefinedMirror()` which returns mirrors for known server-side definitions (IDs and field payload shapes) and avoids having to supply the raw GraphQL subscription yourself. Check `src/definitions` for available predefined mirror names and subscription fragments.
|
|
203
|
+
|
|
204
|
+
## Notes
|
|
205
|
+
* Always call `connect()` explicitly; no implicit lazy connect.
|
|
206
|
+
* Call `InitConnection()` on `Connection`, or `InitClient()` on `SubscriptionClient`, before connecting.
|
|
207
|
+
* VERSION_ERROR triggers automatic full resync (re-arms `loaded`).
|
|
208
|
+
* First top-level field in GraphQL subscription payload is treated as the sync envelope.
|
|
209
|
+
* Full sync updates are buffered until `DONE`, then swapped into the live mirror.
|
|
210
|
+
* Provide `webSocketImpl` manually if bundling for environments without a global WebSocket and you do NOT want `ws` as fallback.
|
|
211
|
+
* When proxy env vars are set in Node, `SubscriptionClient` prefers `ws` (allowing external agent configuration); browsers ignore these env vars.
|
|
212
|
+
|
|
213
|
+
## Build & Publish
|
|
214
|
+
TypeScript sources compile to `dist/`.
|
|
215
|
+
|
|
216
|
+
Scripts:
|
|
217
|
+
```bash
|
|
218
|
+
npm run build # compile
|
|
219
|
+
npm run publish:dry # preview publish contents
|
|
220
|
+
npm run release # build + publish (public)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Developer notes (recent refactor)
|
|
224
|
+
---------------------------------
|
|
225
|
+
This repository recently split GraphQL subscription template literals into per-property modules to make maintenance easier:
|
|
226
|
+
|
|
227
|
+
- Subscription templates: `src/definitions/subscriptions/*.ts` — one file per subscription property.
|
|
228
|
+
- Shared fragments: `src/definitions/fragments.ts` — common fragment string constants used by the subscription files.
|
|
229
|
+
- Compatibility surface: `src/definitions/mirrors.ts` now re-exports the assembled `Subscriptions`, `RequiredMirrors`, `TempSubscriptions`, and the fragment constants to preserve the original API.
|
|
230
|
+
|
|
231
|
+
Packaging and what is published
|
|
232
|
+
--------------------------------
|
|
233
|
+
- The npm package only ships the compiled build output. `package.json` lists `dist` and `dist-compat` in the `files` field, so the raw TypeScript source files under `src/` (including `src/definitions/subscriptions/*.ts`) are not included in the published package by default.
|
|
234
|
+
- The TypeScript compiler emits JavaScript (to `dist`) and declaration files (`.d.ts`) when you run the build; those compiled artifacts are what go into the package.
|
|
235
|
+
|
|
236
|
+
How to verify locally (PowerShell)
|
|
237
|
+
----------------------------------
|
|
238
|
+
1) Build the project:
|
|
239
|
+
|
|
240
|
+
```powershell
|
|
241
|
+
npm run build
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
2) Inspect the compiled `dist` tree to see the compiled outputs for the subscription modules:
|
|
245
|
+
|
|
246
|
+
```powershell
|
|
247
|
+
Get-ChildItem -Recurse .\dist | Select-Object FullName
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
3) See exactly what would be published (dry-run):
|
|
251
|
+
|
|
252
|
+
```powershell
|
|
253
|
+
npm pack --dry-run
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
If you want `.ts` sources included in the published package, add `src` to the `files` array in `package.json` or add a copy step that places sources in `dist`/`dist-compat` before publishing; then verify with `npm pack --dry-run`.
|
|
257
|
+
|
|
258
|
+
## CI / GitHub Actions
|
|
259
|
+
|
|
260
|
+
This repository includes a workflow that publishes the package to npm when changes are pushed to `main` and when a GitHub Release is published. The workflow expects a repository secret named `NPM_TOKEN` containing a valid npm automation token.
|
|
261
|
+
|
|
262
|
+
To create and add the secret:
|
|
263
|
+
1. Generate an npm token on https://www.npmjs.com/ under your account settings (Access Tokens -> Automation).
|
|
264
|
+
2. In the GitHub repository, go to Settings → Secrets → Actions and add a new secret named `NPM_TOKEN` with the token value.
|
|
265
|
+
|
|
266
|
+
The workflow builds both `dist` and `dist-compat` and then runs `npm publish --access public`. If you need to restrict publishing (for example, to skip on regular pushes), adjust the workflow triggers in `.github/workflows/publish.yml`.
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
MIT
|
package/dist/MirrorSync.d.ts
CHANGED
|
@@ -61,6 +61,19 @@ export interface MirrorItem {
|
|
|
61
61
|
[k: string]: any;
|
|
62
62
|
deleted?: boolean;
|
|
63
63
|
}
|
|
64
|
+
export interface MirrorUpdatedPayload {
|
|
65
|
+
doc: MirrorItem;
|
|
66
|
+
orgDoc: MirrorItem | undefined;
|
|
67
|
+
full: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface MirrorDeletedPayload {
|
|
70
|
+
orgDoc: MirrorItem;
|
|
71
|
+
}
|
|
72
|
+
export interface MirrorWaitOptions {
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
}
|
|
75
|
+
export type MirrorUpdatedMatcher = string | ((payload: MirrorUpdatedPayload) => boolean);
|
|
76
|
+
export type MirrorDeletedMatcher = string | ((payload: MirrorDeletedPayload) => boolean);
|
|
64
77
|
/**
|
|
65
78
|
* MirrorSync maintains an in-memory mirror of server state based on CacheSyncResult messages.
|
|
66
79
|
* Generic TBase is the item shape (must include id & version).
|
|
@@ -98,6 +111,10 @@ export declare class MirrorSync extends EventEmitter {
|
|
|
98
111
|
private resubscribe;
|
|
99
112
|
private start;
|
|
100
113
|
load(): Promise<ReadonlyMapView<string, any>>;
|
|
114
|
+
waitForUpdated(matcher: MirrorUpdatedMatcher, options?: MirrorWaitOptions): Promise<MirrorUpdatedPayload>;
|
|
115
|
+
waitForDeleted(matcher: MirrorDeletedMatcher, options?: MirrorWaitOptions): Promise<MirrorDeletedPayload>;
|
|
116
|
+
private waitForMirrorEvent;
|
|
117
|
+
private matchesMirrorEvent;
|
|
101
118
|
private resetLoaded;
|
|
102
119
|
private handlePayload;
|
|
103
120
|
private handleVersionError;
|
package/dist/MirrorSync.js
CHANGED
|
@@ -123,6 +123,44 @@ class MirrorSync extends eventemitter3_1.default {
|
|
|
123
123
|
this.start();
|
|
124
124
|
return this.loaded;
|
|
125
125
|
}
|
|
126
|
+
waitForUpdated(matcher, options) {
|
|
127
|
+
return this.waitForMirrorEvent('updated', matcher, options);
|
|
128
|
+
}
|
|
129
|
+
waitForDeleted(matcher, options) {
|
|
130
|
+
return this.waitForMirrorEvent('deleted', matcher, options);
|
|
131
|
+
}
|
|
132
|
+
waitForMirrorEvent(event, matcher, options) {
|
|
133
|
+
var _a;
|
|
134
|
+
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : 120000;
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
let timeout;
|
|
137
|
+
const cleanup = () => {
|
|
138
|
+
this.off(event, handler);
|
|
139
|
+
if (timeout)
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
};
|
|
142
|
+
const handler = (payload) => {
|
|
143
|
+
if (!this.matchesMirrorEvent(event, matcher, payload))
|
|
144
|
+
return;
|
|
145
|
+
cleanup();
|
|
146
|
+
resolve(payload);
|
|
147
|
+
};
|
|
148
|
+
this.on(event, handler);
|
|
149
|
+
if (timeoutMs > 0) {
|
|
150
|
+
timeout = setTimeout(() => {
|
|
151
|
+
cleanup();
|
|
152
|
+
reject(new Error(`Timeout waiting for mirror ${event} event`));
|
|
153
|
+
}, timeoutMs);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
matchesMirrorEvent(event, matcher, payload) {
|
|
158
|
+
if (typeof matcher === 'function')
|
|
159
|
+
return matcher(payload);
|
|
160
|
+
return event === 'updated'
|
|
161
|
+
? payload.doc.id === matcher
|
|
162
|
+
: payload.orgDoc.id === matcher;
|
|
163
|
+
}
|
|
126
164
|
resetLoaded() {
|
|
127
165
|
this.loaded = new Promise(resolve => {
|
|
128
166
|
this.resolveLoaded = resolve;
|