@alpamayo-solutions/colca-client 0.0.0 → 0.1.9
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 +143 -3
- package/dist/door.d.ts +155 -0
- package/dist/door.d.ts.map +1 -0
- package/dist/door.js +212 -0
- package/dist/door.js.map +1 -0
- package/dist/generated/contracts.d.ts +626 -0
- package/dist/generated/contracts.d.ts.map +1 -0
- package/dist/generated/contracts.js +39 -0
- package/dist/generated/contracts.js.map +1 -0
- package/dist/ids.d.ts +36 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +64 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/live.d.ts +148 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +500 -0
- package/dist/live.js.map +1 -0
- package/dist/stream.d.ts +54 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +85 -0
- package/dist/stream.js.map +1 -0
- package/dist/topics.d.ts +45 -0
- package/dist/topics.d.ts.map +1 -0
- package/dist/topics.js +70 -0
- package/dist/topics.js.map +1 -0
- package/package.json +66 -3
- package/LICENSE.md +0 -105
package/dist/live.js
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live values over MQTT, for a page or a service that wants them pushed.
|
|
3
|
+
*
|
|
4
|
+
* Colca retains the current value of every data and entity path, so a
|
|
5
|
+
* subscription brings the present state first and the changes after it. There
|
|
6
|
+
* is no snapshot to fetch and nothing to merge. What this module adds is what a
|
|
7
|
+
* long-lived client needs around that:
|
|
8
|
+
*
|
|
9
|
+
* - one connection, however many parts of a page subscribe, and an unsubscribe
|
|
10
|
+
* that ends only its own subscription;
|
|
11
|
+
* - the last value of every topic, so a second subscriber to a topic gets it at
|
|
12
|
+
* once instead of waiting for the next change;
|
|
13
|
+
* - a fresh token before the old one runs out — the node ends a session when
|
|
14
|
+
* its token expires — and every subscription sent again on the new connection;
|
|
15
|
+
* - growing, jittered waits between attempts after a drop, each with a fresh
|
|
16
|
+
* token;
|
|
17
|
+
* - commands that wait for their acknowledgement.
|
|
18
|
+
*
|
|
19
|
+
* These are values, not a log: a change that happens while the connection is
|
|
20
|
+
* being replaced is superseded by the retained value that follows. Anything that
|
|
21
|
+
* must see every record reads a stream through the door instead.
|
|
22
|
+
*
|
|
23
|
+
* mqtt.js is loaded on first use and is an optional dependency of this package.
|
|
24
|
+
*/
|
|
25
|
+
import { newUlid } from "./ids.js";
|
|
26
|
+
import { parseTopic, topicMatches } from "./topics.js";
|
|
27
|
+
/** Nobody answered a command before it expired. */
|
|
28
|
+
export class CommandTimeout extends Error {
|
|
29
|
+
topic;
|
|
30
|
+
correlationId;
|
|
31
|
+
constructor(topic, correlationId) {
|
|
32
|
+
super(`nobody acknowledged ${topic} (${correlationId}) before it expired`);
|
|
33
|
+
this.topic = topic;
|
|
34
|
+
this.correlationId = correlationId;
|
|
35
|
+
this.name = "CommandTimeout";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** A token lifetime shorter than this does not make the client reconnect any faster. */
|
|
39
|
+
const MIN_RENEW_MS = 5_000;
|
|
40
|
+
export class Live {
|
|
41
|
+
#options;
|
|
42
|
+
#filters = new Map();
|
|
43
|
+
#qos = new Map();
|
|
44
|
+
#values = new Map();
|
|
45
|
+
/** Per filter, the topics whose retained value its listeners already have on this connection. */
|
|
46
|
+
#retainedSent = new Map();
|
|
47
|
+
/** Filters the node has confirmed on this connection, and who waits for that. */
|
|
48
|
+
#granted = new Set();
|
|
49
|
+
#grantWaiters = new Map();
|
|
50
|
+
#ackFilters = new Set();
|
|
51
|
+
#pending = new Map();
|
|
52
|
+
#stateListeners = new Set();
|
|
53
|
+
#state = "connecting";
|
|
54
|
+
#client;
|
|
55
|
+
#generation = 0;
|
|
56
|
+
#attempt = 0;
|
|
57
|
+
#renewTimer;
|
|
58
|
+
#retryTimer;
|
|
59
|
+
constructor(options) {
|
|
60
|
+
this.#options = options;
|
|
61
|
+
void this.#open();
|
|
62
|
+
}
|
|
63
|
+
get state() {
|
|
64
|
+
return this.#state;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Values under `filter` (MQTT wildcards allowed), the current ones first. Returns
|
|
68
|
+
* the function that ends this subscription and no other.
|
|
69
|
+
*/
|
|
70
|
+
subscribe(filter, listener, options = {}) {
|
|
71
|
+
if (this.#state === "closed")
|
|
72
|
+
throw new Error("this Live client is closed");
|
|
73
|
+
const qos = options.qos ?? 0;
|
|
74
|
+
// A wrapper per call, so the same function subscribed twice is two subscriptions.
|
|
75
|
+
const own = (value) => {
|
|
76
|
+
listener(value);
|
|
77
|
+
};
|
|
78
|
+
let listeners = this.#filters.get(filter);
|
|
79
|
+
if (listeners === undefined) {
|
|
80
|
+
listeners = new Set([own]);
|
|
81
|
+
this.#filters.set(filter, listeners);
|
|
82
|
+
this.#qos.set(filter, qos);
|
|
83
|
+
// Offline, the filter goes out with the others once the connection is up.
|
|
84
|
+
if (this.#state === "online")
|
|
85
|
+
this.#subscribe([filter]);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
listeners.add(own);
|
|
89
|
+
if (qos > (this.#qos.get(filter) ?? 0)) {
|
|
90
|
+
// Subscribing again replaces the node's subscription with the stronger one.
|
|
91
|
+
this.#qos.set(filter, qos);
|
|
92
|
+
if (this.#state === "online")
|
|
93
|
+
this.#subscribe([filter]);
|
|
94
|
+
}
|
|
95
|
+
// The node sends retained values once per subscription, and this filter's
|
|
96
|
+
// went to whoever subscribed first. The newcomer gets them from here.
|
|
97
|
+
const current = this.values(filter);
|
|
98
|
+
queueMicrotask(() => {
|
|
99
|
+
if (!this.#filters.get(filter)?.has(own))
|
|
100
|
+
return;
|
|
101
|
+
for (const value of current)
|
|
102
|
+
this.#deliver(own, value);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return () => {
|
|
106
|
+
const set = this.#filters.get(filter);
|
|
107
|
+
if (!set?.delete(own) || set.size > 0)
|
|
108
|
+
return;
|
|
109
|
+
this.#filters.delete(filter);
|
|
110
|
+
this.#qos.delete(filter);
|
|
111
|
+
this.#retainedSent.delete(filter);
|
|
112
|
+
this.#granted.delete(filter);
|
|
113
|
+
if (this.#state === "online")
|
|
114
|
+
this.#client?.unsubscribe([filter]);
|
|
115
|
+
this.#forget(filter);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** The last value seen on a topic, if anyone is subscribed to it. */
|
|
119
|
+
latest(topic) {
|
|
120
|
+
return this.#values.get(topic);
|
|
121
|
+
}
|
|
122
|
+
/** Every last value under a filter. */
|
|
123
|
+
values(filter) {
|
|
124
|
+
return [...this.#values.values()].filter((value) => topicMatches(filter, value.topic));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* One record, as JSON, under this caller's identity. Nothing is queued: offline
|
|
128
|
+
* it fails at once, because a command sent minutes later is a different command.
|
|
129
|
+
* A person's session may only send commands; the node refuses anything else.
|
|
130
|
+
*/
|
|
131
|
+
publish(topic, payload, options = {}) {
|
|
132
|
+
const client = this.#client;
|
|
133
|
+
if (this.#state !== "online" || client === undefined) {
|
|
134
|
+
return Promise.reject(new Error(`not connected: ${topic} was not sent`));
|
|
135
|
+
}
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
client.publish(topic, JSON.stringify(payload), { qos: options.qos ?? 1, retain: false }, (error) => {
|
|
138
|
+
if (error)
|
|
139
|
+
reject(error);
|
|
140
|
+
else
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Send a command and wait for its acknowledgement.
|
|
147
|
+
*
|
|
148
|
+
* `body` is the command's own fields — `params` for a `_CmdParam`, say. The
|
|
149
|
+
* correlation id and the expiry are added here, and the answer is matched by
|
|
150
|
+
* the id, wherever in the tree the executor publishes it. The promise settles
|
|
151
|
+
* with the `_Ack`, whatever its `result_code`; it rejects when nothing answers
|
|
152
|
+
* in `timeoutMs`, when the node refuses the publish, or when the client closes.
|
|
153
|
+
*/
|
|
154
|
+
command(topic, body = {}, options = {}) {
|
|
155
|
+
const parts = parseTopic(topic);
|
|
156
|
+
if (!parts?.contract.startsWith("_Cmd")) {
|
|
157
|
+
return Promise.reject(new Error(`a command goes to a _Cmd… topic, got ${topic}`));
|
|
158
|
+
}
|
|
159
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
160
|
+
const ackFilter = options.ackFilter ?? `${parts.root}/${parts.version}/_Ack/#`;
|
|
161
|
+
const correlationId = typeof body.correlation_id === "string" ? body.correlation_id : newUlid();
|
|
162
|
+
this.#listenForAcks(ackFilter);
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
if (this.#pending.delete(correlationId))
|
|
166
|
+
reject(new CommandTimeout(topic, correlationId));
|
|
167
|
+
}, timeoutMs);
|
|
168
|
+
this.#pending.set(correlationId, { resolve, reject, timer });
|
|
169
|
+
// Only once the node confirmed the subscription to the answers: an executor
|
|
170
|
+
// that answers at once must not answer into nothing.
|
|
171
|
+
this.#whenGranted(ackFilter)
|
|
172
|
+
.then(() => this.publish(topic, { ...body, correlation_id: correlationId, expires_at: Date.now() + timeoutMs }))
|
|
173
|
+
.catch((error) => {
|
|
174
|
+
const pending = this.#pending.get(correlationId);
|
|
175
|
+
if (!pending)
|
|
176
|
+
return;
|
|
177
|
+
this.#pending.delete(correlationId);
|
|
178
|
+
clearTimeout(pending.timer);
|
|
179
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** Called on every change of the connection state; returns the function that stops it. */
|
|
184
|
+
onState(listener) {
|
|
185
|
+
this.#stateListeners.add(listener);
|
|
186
|
+
return () => this.#stateListeners.delete(listener);
|
|
187
|
+
}
|
|
188
|
+
close() {
|
|
189
|
+
if (this.#state === "closed")
|
|
190
|
+
return;
|
|
191
|
+
this.#generation += 1;
|
|
192
|
+
clearTimeout(this.#renewTimer);
|
|
193
|
+
clearTimeout(this.#retryTimer);
|
|
194
|
+
this.#client?.end();
|
|
195
|
+
this.#client = undefined;
|
|
196
|
+
this.#filters.clear();
|
|
197
|
+
this.#qos.clear();
|
|
198
|
+
this.#values.clear();
|
|
199
|
+
this.#granted.clear();
|
|
200
|
+
this.#grantWaiters.clear();
|
|
201
|
+
this.#ackFilters.clear();
|
|
202
|
+
for (const [id, pending] of this.#pending) {
|
|
203
|
+
clearTimeout(pending.timer);
|
|
204
|
+
pending.reject(new Error(`this Live client closed before ${id} was acknowledged`));
|
|
205
|
+
}
|
|
206
|
+
this.#pending.clear();
|
|
207
|
+
this.#setState("closed");
|
|
208
|
+
}
|
|
209
|
+
/** Connect, with `token` when the renewal already fetched one. */
|
|
210
|
+
async #open(token) {
|
|
211
|
+
const generation = ++this.#generation;
|
|
212
|
+
const current = () => generation === this.#generation && this.#state !== "closed";
|
|
213
|
+
// A planned renewal keeps saying online; the page has nothing to show for it.
|
|
214
|
+
if (this.#state !== "online")
|
|
215
|
+
this.#setState("connecting");
|
|
216
|
+
let client;
|
|
217
|
+
let expiresAt;
|
|
218
|
+
try {
|
|
219
|
+
token ??= await this.#options.token();
|
|
220
|
+
const claims = readClaims(token);
|
|
221
|
+
const username = this.#options.username ?? claims?.sub;
|
|
222
|
+
if (!username) {
|
|
223
|
+
throw new Error("the token carries no sub: pass `username` for a token that is not a JWT");
|
|
224
|
+
}
|
|
225
|
+
expiresAt = claims?.exp === undefined ? undefined : claims.exp * 1000;
|
|
226
|
+
const connect = this.#options.connect ?? connectWithMqttJs;
|
|
227
|
+
client = await connect(this.#options.url, {
|
|
228
|
+
...this.#options.mqttOptions,
|
|
229
|
+
clientId: `${this.#options.clientIdPrefix ?? "colca-client"}-${crypto.randomUUID()}`,
|
|
230
|
+
username,
|
|
231
|
+
password: token,
|
|
232
|
+
protocolVersion: 5,
|
|
233
|
+
clean: true,
|
|
234
|
+
keepalive: 30,
|
|
235
|
+
reconnectPeriod: 0,
|
|
236
|
+
connectTimeout: 10_000,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (!current())
|
|
241
|
+
return;
|
|
242
|
+
this.#report(error);
|
|
243
|
+
if (error instanceof MissingMqtt) {
|
|
244
|
+
this.close();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.#setState("offline");
|
|
248
|
+
this.#retry();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!current()) {
|
|
252
|
+
client.end(true);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.#client = client;
|
|
256
|
+
client.on("connect", () => {
|
|
257
|
+
if (!current())
|
|
258
|
+
return;
|
|
259
|
+
this.#attempt = 0;
|
|
260
|
+
this.#setState("online");
|
|
261
|
+
this.#retainedSent.clear();
|
|
262
|
+
this.#granted.clear();
|
|
263
|
+
this.#subscribe([...this.#filters.keys()]);
|
|
264
|
+
this.#scheduleRenewal(expiresAt);
|
|
265
|
+
});
|
|
266
|
+
client.on("message", (topic, payload, packet) => {
|
|
267
|
+
if (current())
|
|
268
|
+
this.#receive(topic, payload, packet.retain);
|
|
269
|
+
});
|
|
270
|
+
client.on("error", (error) => {
|
|
271
|
+
if (current())
|
|
272
|
+
this.#report(error);
|
|
273
|
+
});
|
|
274
|
+
client.on("close", () => {
|
|
275
|
+
if (!current())
|
|
276
|
+
return;
|
|
277
|
+
this.#setState("offline");
|
|
278
|
+
this.#retry();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** End the connection in hand and open the next one. */
|
|
282
|
+
#replace(token) {
|
|
283
|
+
clearTimeout(this.#renewTimer);
|
|
284
|
+
this.#renewTimer = undefined;
|
|
285
|
+
this.#generation += 1;
|
|
286
|
+
this.#client?.end(true);
|
|
287
|
+
this.#client = undefined;
|
|
288
|
+
void this.#open(token);
|
|
289
|
+
}
|
|
290
|
+
#retry() {
|
|
291
|
+
if (this.#retryTimer !== undefined)
|
|
292
|
+
return;
|
|
293
|
+
const ceiling = Math.min(this.#options.maxRetryMs ?? 30_000, (this.#options.retryMs ?? 1_000) * 2 ** this.#attempt);
|
|
294
|
+
this.#attempt += 1;
|
|
295
|
+
// Full jitter: a hundred pages that lost the same node do not come back in step.
|
|
296
|
+
this.#retryTimer = setTimeout(() => {
|
|
297
|
+
this.#retryTimer = undefined;
|
|
298
|
+
this.#replace();
|
|
299
|
+
}, Math.random() * ceiling);
|
|
300
|
+
}
|
|
301
|
+
#scheduleRenewal(expiresAt) {
|
|
302
|
+
clearTimeout(this.#renewTimer);
|
|
303
|
+
// A token without an expiry, such as a personal access token, is not renewed on a clock.
|
|
304
|
+
if (expiresAt === undefined)
|
|
305
|
+
return;
|
|
306
|
+
const due = expiresAt - (this.#options.renewBeforeMs ?? 30_000) - Date.now();
|
|
307
|
+
this.#renewTimer = setTimeout(() => {
|
|
308
|
+
this.#renewTimer = undefined;
|
|
309
|
+
void this.#renew(expiresAt);
|
|
310
|
+
}, Math.max(MIN_RENEW_MS, due));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Replace the connection only with a token that outlives it. An identity proxy
|
|
314
|
+
* can keep handing out the token it holds until its own refresh is due, and
|
|
315
|
+
* reconnecting with that one gains nothing and costs a gap in the values.
|
|
316
|
+
*/
|
|
317
|
+
async #renew(expiresAt) {
|
|
318
|
+
const generation = this.#generation;
|
|
319
|
+
let token;
|
|
320
|
+
try {
|
|
321
|
+
token = await this.#options.token();
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (generation === this.#generation)
|
|
325
|
+
this.#report(error);
|
|
326
|
+
}
|
|
327
|
+
if (generation !== this.#generation || this.#state === "closed")
|
|
328
|
+
return;
|
|
329
|
+
const exp = token === undefined ? undefined : readClaims(token)?.exp;
|
|
330
|
+
if (token !== undefined && (exp === undefined || exp * 1000 > expiresAt)) {
|
|
331
|
+
this.#replace(token);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// Ask again shortly; once the token has run out, the node ends the session and
|
|
335
|
+
// reconnecting takes over.
|
|
336
|
+
if (Date.now() + MIN_RENEW_MS >= expiresAt)
|
|
337
|
+
return;
|
|
338
|
+
this.#renewTimer = setTimeout(() => {
|
|
339
|
+
this.#renewTimer = undefined;
|
|
340
|
+
void this.#renew(expiresAt);
|
|
341
|
+
}, MIN_RENEW_MS);
|
|
342
|
+
}
|
|
343
|
+
#subscribe(filters) {
|
|
344
|
+
const generation = this.#generation;
|
|
345
|
+
for (const qos of [0, 1]) {
|
|
346
|
+
const group = filters.filter((filter) => (this.#qos.get(filter) ?? 0) === qos);
|
|
347
|
+
if (group.length === 0)
|
|
348
|
+
continue;
|
|
349
|
+
this.#client?.subscribe(group, { qos }, (error, granted) => {
|
|
350
|
+
if (generation !== this.#generation)
|
|
351
|
+
return;
|
|
352
|
+
if (error) {
|
|
353
|
+
this.#report(error);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
for (const grant of granted ?? []) {
|
|
357
|
+
// 128 and up is a refusal: MQTT 3's failure code, or an MQTT 5 reason code.
|
|
358
|
+
if (grant.qos >= 128) {
|
|
359
|
+
this.#report(new Error(`the node refused the subscription to ${grant.topic}`));
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
this.#granted.add(grant.topic);
|
|
363
|
+
for (const wake of this.#grantWaiters.get(grant.topic) ?? [])
|
|
364
|
+
wake();
|
|
365
|
+
this.#grantWaiters.delete(grant.topic);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
#whenGranted(filter) {
|
|
371
|
+
if (this.#granted.has(filter))
|
|
372
|
+
return Promise.resolve();
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
this.#grantWaiters.set(filter, [...(this.#grantWaiters.get(filter) ?? []), resolve]);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/** One subscription per acknowledgement filter, kept for as long as the client lives. */
|
|
378
|
+
#listenForAcks(filter) {
|
|
379
|
+
if (this.#ackFilters.has(filter))
|
|
380
|
+
return;
|
|
381
|
+
this.#ackFilters.add(filter);
|
|
382
|
+
this.subscribe(filter, (value) => {
|
|
383
|
+
const ack = value.payload;
|
|
384
|
+
if (typeof ack !== "object" || ack === null)
|
|
385
|
+
return;
|
|
386
|
+
const id = ack.correlation_id;
|
|
387
|
+
const pending = typeof id === "string" ? this.#pending.get(id) : undefined;
|
|
388
|
+
// Somebody else's command, or one that already timed out.
|
|
389
|
+
if (pending === undefined || typeof id !== "string")
|
|
390
|
+
return;
|
|
391
|
+
this.#pending.delete(id);
|
|
392
|
+
clearTimeout(pending.timer);
|
|
393
|
+
pending.resolve(ack);
|
|
394
|
+
}, { qos: 1 });
|
|
395
|
+
}
|
|
396
|
+
#receive(topic, payload, retained) {
|
|
397
|
+
let decoded;
|
|
398
|
+
if (payload.length > 0) {
|
|
399
|
+
try {
|
|
400
|
+
decoded = JSON.parse(new TextDecoder().decode(payload));
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
this.#report(new Error(`unreadable payload on ${topic}`));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const value = { topic, payload: decoded, retained, receivedAt: Date.now() };
|
|
408
|
+
if (decoded === undefined)
|
|
409
|
+
this.#values.delete(topic);
|
|
410
|
+
else
|
|
411
|
+
this.#values.set(topic, value);
|
|
412
|
+
for (const [filter, listeners] of this.#filters) {
|
|
413
|
+
if (!topicMatches(filter, topic))
|
|
414
|
+
continue;
|
|
415
|
+
if (retained && !this.#firstRetained(filter, topic))
|
|
416
|
+
continue;
|
|
417
|
+
for (const listener of listeners)
|
|
418
|
+
this.#deliver(listener, value);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The node answers every subscription with the retained values under it, so a
|
|
423
|
+
* topic covered by two filters arrives retained twice when the second filter is
|
|
424
|
+
* subscribed. Each filter's listeners get it once per connection.
|
|
425
|
+
*/
|
|
426
|
+
#firstRetained(filter, topic) {
|
|
427
|
+
let sent = this.#retainedSent.get(filter);
|
|
428
|
+
if (sent === undefined) {
|
|
429
|
+
sent = new Set();
|
|
430
|
+
this.#retainedSent.set(filter, sent);
|
|
431
|
+
}
|
|
432
|
+
if (sent.has(topic))
|
|
433
|
+
return false;
|
|
434
|
+
sent.add(topic);
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
#deliver(listener, value) {
|
|
438
|
+
try {
|
|
439
|
+
listener(value);
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
this.#report(error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Drop the values no remaining filter covers, so a later subscriber is not handed stale ones. */
|
|
446
|
+
#forget(filter) {
|
|
447
|
+
for (const topic of this.#values.keys()) {
|
|
448
|
+
if (!topicMatches(filter, topic))
|
|
449
|
+
continue;
|
|
450
|
+
if (![...this.#filters.keys()].some((other) => topicMatches(other, topic)))
|
|
451
|
+
this.#values.delete(topic);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
#setState(state) {
|
|
455
|
+
if (state === this.#state)
|
|
456
|
+
return;
|
|
457
|
+
this.#state = state;
|
|
458
|
+
for (const listener of this.#stateListeners)
|
|
459
|
+
listener(state);
|
|
460
|
+
}
|
|
461
|
+
#report(error) {
|
|
462
|
+
this.#options.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
class MissingMqtt extends Error {
|
|
466
|
+
}
|
|
467
|
+
async function connectWithMqttJs(url, options) {
|
|
468
|
+
let mqtt;
|
|
469
|
+
try {
|
|
470
|
+
mqtt = await import("mqtt");
|
|
471
|
+
}
|
|
472
|
+
catch (cause) {
|
|
473
|
+
throw new MissingMqtt("colca-client/live needs the mqtt package: npm install mqtt", { cause });
|
|
474
|
+
}
|
|
475
|
+
// A bundler hands a browser mqtt.js's ESM build, which exports only a default;
|
|
476
|
+
// Node gets the CommonJS build, where connect is also a named export.
|
|
477
|
+
const connect = mqtt.connect ?? mqtt.default?.connect;
|
|
478
|
+
if (connect === undefined)
|
|
479
|
+
throw new MissingMqtt("the mqtt package in use has no connect()");
|
|
480
|
+
return connect(url, options);
|
|
481
|
+
}
|
|
482
|
+
/** `sub` and `exp` from a JWT, without checking it — the node does that. */
|
|
483
|
+
function readClaims(token) {
|
|
484
|
+
const parts = token.split(".");
|
|
485
|
+
if (parts.length !== 3)
|
|
486
|
+
return undefined;
|
|
487
|
+
try {
|
|
488
|
+
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
489
|
+
const bytes = Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
|
|
490
|
+
const claims = JSON.parse(new TextDecoder().decode(bytes));
|
|
491
|
+
return {
|
|
492
|
+
sub: typeof claims.sub === "string" ? claims.sub : undefined,
|
|
493
|
+
exp: typeof claims.exp === "number" ? claims.exp : undefined,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
//# sourceMappingURL=live.js.map
|
package/dist/live.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A named cursor over one stream.
|
|
3
|
+
*
|
|
4
|
+
* The cursor lives at the node: `fetch` reads from its stored position and
|
|
5
|
+
* only `ack` moves it. Reopening the same name resumes where it was acked; a
|
|
6
|
+
* new name starts at the first record still retained.
|
|
7
|
+
*
|
|
8
|
+
* `drain()` acks a page once the consumer has asked for the record after its
|
|
9
|
+
* last one. A consumer that throws mid-page sees that page again, so handlers
|
|
10
|
+
* must be able to run twice on the same record.
|
|
11
|
+
*/
|
|
12
|
+
import type { Door, DoorRecord, Gap, Page } from "./door.js";
|
|
13
|
+
export interface StreamOptions {
|
|
14
|
+
/** Records per page, 1…1000. */
|
|
15
|
+
max?: number;
|
|
16
|
+
/** Narrows to a subtree of the UNS path. */
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/** Only for the `metrics` stream. */
|
|
19
|
+
signalIds?: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* Called when a page reports pruned records. Nothing else announces it — a
|
|
22
|
+
* consumer that cares about completeness should say so here.
|
|
23
|
+
*/
|
|
24
|
+
onGap?: (gap: Gap) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface FollowOptions {
|
|
27
|
+
/** How long to wait after an empty page. */
|
|
28
|
+
pollMs?: number;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
export declare class Stream {
|
|
32
|
+
#private;
|
|
33
|
+
private readonly door;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly cursor: string;
|
|
36
|
+
private readonly options;
|
|
37
|
+
constructor(door: Door, name: string, cursor: string, options?: StreamOptions);
|
|
38
|
+
/** One page from the cursor's position. Never moves the cursor. */
|
|
39
|
+
page(signal?: AbortSignal): Promise<Page>;
|
|
40
|
+
/** Ack `upto` — a record or its offset — as the last processed position. */
|
|
41
|
+
ack(upto: DoorRecord | number): Promise<boolean>;
|
|
42
|
+
/** Delete this cursor at the node. Fine when it never existed. */
|
|
43
|
+
retire(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* The newest records on the stream, without touching the cursor — what a
|
|
46
|
+
* view wants when it opens. A consumer and a view can share a cursor name.
|
|
47
|
+
*/
|
|
48
|
+
tail(max?: number): Promise<DoorRecord[]>;
|
|
49
|
+
/** Every record from the cursor to the head, acking page by page. Ends at the first empty page. */
|
|
50
|
+
drain(signal?: AbortSignal): AsyncGenerator<DoorRecord>;
|
|
51
|
+
/** `drain()` without an end: after an empty page, wait and drain again. */
|
|
52
|
+
follow({ pollMs, signal }?: FollowOptions): AsyncGenerator<DoorRecord>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAgB,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qBAAa,MAAM;;IAEf,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAHP,IAAI,EAAE,IAAI,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACN,OAAO,GAAE,aAAkB;IAG9C,mEAAmE;IAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,4EAA4E;IACtE,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD,kEAAkE;IAC5D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;OAGG;IACG,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAS/C,mGAAmG;IAC5F,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC;IAc9D,2EAA2E;IACpE,MAAM,CAAC,EAAE,MAAa,EAAE,MAAM,EAAE,GAAE,aAAkB,GAAG,cAAc,CAAC,UAAU,CAAC;CAUzF"}
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A named cursor over one stream.
|
|
3
|
+
*
|
|
4
|
+
* The cursor lives at the node: `fetch` reads from its stored position and
|
|
5
|
+
* only `ack` moves it. Reopening the same name resumes where it was acked; a
|
|
6
|
+
* new name starts at the first record still retained.
|
|
7
|
+
*
|
|
8
|
+
* `drain()` acks a page once the consumer has asked for the record after its
|
|
9
|
+
* last one. A consumer that throws mid-page sees that page again, so handlers
|
|
10
|
+
* must be able to run twice on the same record.
|
|
11
|
+
*/
|
|
12
|
+
export class Stream {
|
|
13
|
+
door;
|
|
14
|
+
name;
|
|
15
|
+
cursor;
|
|
16
|
+
options;
|
|
17
|
+
constructor(door, name, cursor, options = {}) {
|
|
18
|
+
this.door = door;
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.cursor = cursor;
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
/** One page from the cursor's position. Never moves the cursor. */
|
|
24
|
+
async page(signal) {
|
|
25
|
+
return this.door.fetchPage(this.name, this.cursor, this.#fetchOptions(signal));
|
|
26
|
+
}
|
|
27
|
+
/** Ack `upto` — a record or its offset — as the last processed position. */
|
|
28
|
+
async ack(upto) {
|
|
29
|
+
return this.door.ack(this.name, this.cursor, typeof upto === "number" ? upto : upto.offset);
|
|
30
|
+
}
|
|
31
|
+
/** Delete this cursor at the node. Fine when it never existed. */
|
|
32
|
+
async retire() {
|
|
33
|
+
await this.door.deleteCursor(this.name, this.cursor);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The newest records on the stream, without touching the cursor — what a
|
|
37
|
+
* view wants when it opens. A consumer and a view can share a cursor name.
|
|
38
|
+
*/
|
|
39
|
+
async tail(max) {
|
|
40
|
+
const page = await this.door.fetchPage(this.name, this.cursor, {
|
|
41
|
+
...this.#fetchOptions(),
|
|
42
|
+
max: max ?? this.options.max,
|
|
43
|
+
tail: true,
|
|
44
|
+
});
|
|
45
|
+
return page.records;
|
|
46
|
+
}
|
|
47
|
+
/** Every record from the cursor to the head, acking page by page. Ends at the first empty page. */
|
|
48
|
+
async *drain(signal) {
|
|
49
|
+
for (;;) {
|
|
50
|
+
const page = await this.page(signal);
|
|
51
|
+
if (page.gap)
|
|
52
|
+
this.options.onGap?.(page.gap);
|
|
53
|
+
yield* page.records;
|
|
54
|
+
// The gap's bound when the hole swallowed the whole page, so the same
|
|
55
|
+
// gap is not reported forever.
|
|
56
|
+
const offset = page.records.at(-1)?.offset ?? page.gap?.toOffset;
|
|
57
|
+
if (offset === undefined)
|
|
58
|
+
return;
|
|
59
|
+
await this.ack(offset);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** `drain()` without an end: after an empty page, wait and drain again. */
|
|
63
|
+
async *follow({ pollMs = 1000, signal } = {}) {
|
|
64
|
+
while (!signal?.aborted) {
|
|
65
|
+
yield* this.drain(signal);
|
|
66
|
+
await sleep(pollMs, signal);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
#fetchOptions(signal) {
|
|
70
|
+
return { max: this.options.max, prefix: this.options.prefix, signalIds: this.options.signalIds, signal };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Resolves early when the signal aborts, so `follow()` stops within a poll. */
|
|
74
|
+
function sleep(ms, signal) {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const timer = setTimeout(done, ms);
|
|
77
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
78
|
+
function done() {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
signal?.removeEventListener("abort", done);
|
|
81
|
+
resolve();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=stream.js.map
|