@sixfathoms/lplex 0.3.0 → 0.4.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/dist/index.cjs +117 -17
- package/dist/index.d.cts +93 -6
- package/dist/index.d.ts +93 -6
- package/dist/index.js +117 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -89,8 +89,16 @@ async function* parseSSE(body) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
function classify(obj) {
|
|
92
|
-
if ("type" in obj
|
|
93
|
-
|
|
92
|
+
if ("type" in obj) {
|
|
93
|
+
if (obj.type === "device") {
|
|
94
|
+
return { type: "device", device: obj };
|
|
95
|
+
}
|
|
96
|
+
if (obj.type === "device_removed") {
|
|
97
|
+
return {
|
|
98
|
+
type: "device_removed",
|
|
99
|
+
deviceRemoved: obj
|
|
100
|
+
};
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
if ("seq" in obj) {
|
|
96
104
|
return { type: "frame", frame: obj };
|
|
@@ -185,14 +193,27 @@ var Client = class {
|
|
|
185
193
|
/**
|
|
186
194
|
* Open an ephemeral SSE stream with optional filtering.
|
|
187
195
|
* No session, no replay, no ACK.
|
|
196
|
+
*
|
|
197
|
+
* Accepts either a Filter (for backwards compatibility) or
|
|
198
|
+
* SubscribeOptions for additional control (decode, signal).
|
|
188
199
|
*/
|
|
189
|
-
async subscribe(
|
|
200
|
+
async subscribe(filterOrOptions, signal) {
|
|
201
|
+
let filter;
|
|
202
|
+
let decode;
|
|
203
|
+
let sig = signal;
|
|
204
|
+
if (filterOrOptions && isSubscribeOptions(filterOrOptions)) {
|
|
205
|
+
filter = filterOrOptions.filter;
|
|
206
|
+
decode = filterOrOptions.decode;
|
|
207
|
+
sig = filterOrOptions.signal ?? sig;
|
|
208
|
+
} else {
|
|
209
|
+
filter = filterOrOptions;
|
|
210
|
+
}
|
|
190
211
|
let url = `${this.#baseURL}/events`;
|
|
191
|
-
const qs = filterToQueryString(filter);
|
|
212
|
+
const qs = filterToQueryString(filter, decode);
|
|
192
213
|
if (qs) url += `?${qs}`;
|
|
193
214
|
const resp = await this.#fetch(url, {
|
|
194
215
|
headers: { Accept: "text/event-stream" },
|
|
195
|
-
signal
|
|
216
|
+
signal: sig
|
|
196
217
|
});
|
|
197
218
|
if (!resp.ok) {
|
|
198
219
|
const body = await resp.text();
|
|
@@ -231,7 +252,7 @@ var Client = class {
|
|
|
231
252
|
}
|
|
232
253
|
/**
|
|
233
254
|
* Send an ISO Request (PGN 59904) and wait for the response frame.
|
|
234
|
-
* Returns the response frame, or throws HttpError
|
|
255
|
+
* Returns the response frame, or throws HttpError on timeout (408).
|
|
235
256
|
*/
|
|
236
257
|
async query(params, signal) {
|
|
237
258
|
const url = `${this.#baseURL}/query`;
|
|
@@ -247,7 +268,28 @@ var Client = class {
|
|
|
247
268
|
}
|
|
248
269
|
return resp.json();
|
|
249
270
|
}
|
|
250
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* Query historical frames (requires journaling on the server).
|
|
273
|
+
* Returns an array of frames matching the query parameters.
|
|
274
|
+
*/
|
|
275
|
+
async history(params, signal) {
|
|
276
|
+
const qs = new URLSearchParams();
|
|
277
|
+
qs.set("from", params.from);
|
|
278
|
+
if (params.to) qs.set("to", params.to);
|
|
279
|
+
for (const p of params.pgn ?? []) qs.append("pgn", p.toString());
|
|
280
|
+
for (const s of params.src ?? []) qs.append("src", s.toString());
|
|
281
|
+
if (params.limit !== void 0) qs.set("limit", params.limit.toString());
|
|
282
|
+
if (params.interval) qs.set("interval", params.interval);
|
|
283
|
+
if (params.decode) qs.set("decode", "true");
|
|
284
|
+
const url = `${this.#baseURL}/history?${qs.toString()}`;
|
|
285
|
+
const resp = await this.#fetch(url, { signal });
|
|
286
|
+
if (!resp.ok) {
|
|
287
|
+
const body = await resp.text();
|
|
288
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
289
|
+
}
|
|
290
|
+
return resp.json();
|
|
291
|
+
}
|
|
292
|
+
/** Check server health (GET /healthz). */
|
|
251
293
|
async health(signal) {
|
|
252
294
|
const url = `${this.#baseURL}/healthz`;
|
|
253
295
|
const resp = await this.#fetch(url, { signal });
|
|
@@ -257,6 +299,26 @@ var Client = class {
|
|
|
257
299
|
}
|
|
258
300
|
return resp.json();
|
|
259
301
|
}
|
|
302
|
+
/** Liveness probe (GET /livez). */
|
|
303
|
+
async liveness(signal) {
|
|
304
|
+
const url = `${this.#baseURL}/livez`;
|
|
305
|
+
const resp = await this.#fetch(url, { signal });
|
|
306
|
+
if (!resp.ok) {
|
|
307
|
+
const body = await resp.text();
|
|
308
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
309
|
+
}
|
|
310
|
+
return resp.json();
|
|
311
|
+
}
|
|
312
|
+
/** Readiness probe (GET /readyz). */
|
|
313
|
+
async readiness(signal) {
|
|
314
|
+
const url = `${this.#baseURL}/readyz`;
|
|
315
|
+
const resp = await this.#fetch(url, { signal });
|
|
316
|
+
if (!resp.ok) {
|
|
317
|
+
const body = await resp.text();
|
|
318
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
319
|
+
}
|
|
320
|
+
return resp.json();
|
|
321
|
+
}
|
|
260
322
|
/** Fetch boat-side replication status (only available when replication is configured). */
|
|
261
323
|
async replicationStatus(signal) {
|
|
262
324
|
const url = `${this.#baseURL}/replication/status`;
|
|
@@ -290,19 +352,26 @@ var Client = class {
|
|
|
290
352
|
return new Session(this.#baseURL, this.#fetch, info);
|
|
291
353
|
}
|
|
292
354
|
};
|
|
355
|
+
function isSubscribeOptions(obj) {
|
|
356
|
+
return "decode" in obj || "signal" in obj || "filter" in obj;
|
|
357
|
+
}
|
|
293
358
|
function filterIsEmpty(f) {
|
|
294
|
-
return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length;
|
|
359
|
+
return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length && !f.bus?.length;
|
|
295
360
|
}
|
|
296
|
-
function filterToQueryString(f) {
|
|
297
|
-
if (!f || filterIsEmpty(f)) return "";
|
|
361
|
+
function filterToQueryString(f, decode) {
|
|
362
|
+
if ((!f || filterIsEmpty(f)) && !decode) return "";
|
|
298
363
|
const params = new URLSearchParams();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
364
|
+
if (f) {
|
|
365
|
+
for (const p of f.pgn ?? []) params.append("pgn", p.toString());
|
|
366
|
+
for (const p of f.exclude_pgn ?? [])
|
|
367
|
+
params.append("exclude_pgn", p.toString());
|
|
368
|
+
for (const m of f.manufacturer ?? []) params.append("manufacturer", m);
|
|
369
|
+
for (const i of f.instance ?? []) params.append("instance", i.toString());
|
|
370
|
+
for (const n of f.name ?? []) params.append("name", n);
|
|
371
|
+
for (const n of f.exclude_name ?? []) params.append("exclude_name", n);
|
|
372
|
+
for (const b of f.bus ?? []) params.append("bus", b);
|
|
373
|
+
}
|
|
374
|
+
if (decode) params.set("decode", "true");
|
|
306
375
|
return params.toString();
|
|
307
376
|
}
|
|
308
377
|
function filterToJSON(f) {
|
|
@@ -313,6 +382,7 @@ function filterToJSON(f) {
|
|
|
313
382
|
if (f.instance?.length) m.instance = f.instance;
|
|
314
383
|
if (f.name?.length) m.name = f.name;
|
|
315
384
|
if (f.exclude_name?.length) m.exclude_name = f.exclude_name;
|
|
385
|
+
if (f.bus?.length) m.bus = f.bus;
|
|
316
386
|
return m;
|
|
317
387
|
}
|
|
318
388
|
|
|
@@ -368,6 +438,36 @@ var CloudClient = class {
|
|
|
368
438
|
}
|
|
369
439
|
return resp.json();
|
|
370
440
|
}
|
|
441
|
+
/** Check cloud server health (GET /healthz). */
|
|
442
|
+
async health(signal) {
|
|
443
|
+
const url = `${this.#baseURL}/healthz`;
|
|
444
|
+
const resp = await this.#fetch(url, { signal });
|
|
445
|
+
if (!resp.ok) {
|
|
446
|
+
const body = await resp.text();
|
|
447
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
448
|
+
}
|
|
449
|
+
return resp.json();
|
|
450
|
+
}
|
|
451
|
+
/** Liveness probe (GET /livez). */
|
|
452
|
+
async liveness(signal) {
|
|
453
|
+
const url = `${this.#baseURL}/livez`;
|
|
454
|
+
const resp = await this.#fetch(url, { signal });
|
|
455
|
+
if (!resp.ok) {
|
|
456
|
+
const body = await resp.text();
|
|
457
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
458
|
+
}
|
|
459
|
+
return resp.json();
|
|
460
|
+
}
|
|
461
|
+
/** Readiness probe (GET /readyz). */
|
|
462
|
+
async readiness(signal) {
|
|
463
|
+
const url = `${this.#baseURL}/readyz`;
|
|
464
|
+
const resp = await this.#fetch(url, { signal });
|
|
465
|
+
if (!resp.ok) {
|
|
466
|
+
const body = await resp.text();
|
|
467
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
468
|
+
}
|
|
469
|
+
return resp.json();
|
|
470
|
+
}
|
|
371
471
|
};
|
|
372
472
|
// Annotate the CommonJS export names for ESM import in node:
|
|
373
473
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
interface Frame {
|
|
3
3
|
seq: number;
|
|
4
4
|
ts: string;
|
|
5
|
+
bus?: string;
|
|
5
6
|
prio: number;
|
|
6
7
|
pgn: number;
|
|
7
8
|
src: number;
|
|
8
9
|
dst: number;
|
|
9
10
|
data: string;
|
|
11
|
+
decoded?: Record<string, unknown>;
|
|
10
12
|
}
|
|
11
13
|
/** An NMEA 2000 device discovered on the bus. */
|
|
12
14
|
interface Device {
|
|
15
|
+
bus?: string;
|
|
13
16
|
src: number;
|
|
14
17
|
name: string;
|
|
15
18
|
manufacturer: string;
|
|
@@ -28,6 +31,12 @@ interface Device {
|
|
|
28
31
|
packet_count: number;
|
|
29
32
|
byte_count: number;
|
|
30
33
|
}
|
|
34
|
+
/** A device-removed notification from the bus. */
|
|
35
|
+
interface DeviceRemoved {
|
|
36
|
+
type: "device_removed";
|
|
37
|
+
bus?: string;
|
|
38
|
+
src: number;
|
|
39
|
+
}
|
|
31
40
|
/** Discriminated union for SSE events. */
|
|
32
41
|
type Event = {
|
|
33
42
|
type: "frame";
|
|
@@ -35,6 +44,9 @@ type Event = {
|
|
|
35
44
|
} | {
|
|
36
45
|
type: "device";
|
|
37
46
|
device: Device;
|
|
47
|
+
} | {
|
|
48
|
+
type: "device_removed";
|
|
49
|
+
deviceRemoved: DeviceRemoved;
|
|
38
50
|
};
|
|
39
51
|
/**
|
|
40
52
|
* Filter for CAN frames.
|
|
@@ -47,6 +59,14 @@ interface Filter {
|
|
|
47
59
|
instance?: number[];
|
|
48
60
|
name?: string[];
|
|
49
61
|
exclude_name?: string[];
|
|
62
|
+
bus?: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Options for ephemeral SSE subscription. */
|
|
65
|
+
interface SubscribeOptions {
|
|
66
|
+
filter?: Filter;
|
|
67
|
+
/** When true, frames include decoded field values. */
|
|
68
|
+
decode?: boolean;
|
|
69
|
+
signal?: AbortSignal;
|
|
50
70
|
}
|
|
51
71
|
/** Configuration for creating a buffered session. */
|
|
52
72
|
interface SessionConfig {
|
|
@@ -68,6 +88,7 @@ interface SendParams {
|
|
|
68
88
|
dst: number;
|
|
69
89
|
prio: number;
|
|
70
90
|
data: string;
|
|
91
|
+
bus?: string;
|
|
71
92
|
}
|
|
72
93
|
/** A single PGN's last-known value for a device. */
|
|
73
94
|
interface PGNValue {
|
|
@@ -103,12 +124,56 @@ interface DecodedDeviceValues {
|
|
|
103
124
|
/** Parameters for an ISO Request query (POST /query). */
|
|
104
125
|
interface QueryParams {
|
|
105
126
|
pgn: number;
|
|
106
|
-
|
|
127
|
+
/** Destination address. Defaults to 0xFF (broadcast) on the server. */
|
|
128
|
+
dst?: number;
|
|
107
129
|
timeout?: string;
|
|
130
|
+
bus?: string;
|
|
131
|
+
}
|
|
132
|
+
/** Parameters for historical data query (GET /history). */
|
|
133
|
+
interface HistoryParams {
|
|
134
|
+
/** Start timestamp (RFC 3339). */
|
|
135
|
+
from: string;
|
|
136
|
+
/** End timestamp (RFC 3339). Defaults to now. */
|
|
137
|
+
to?: string;
|
|
138
|
+
/** Filter by PGN(s). */
|
|
139
|
+
pgn?: number[];
|
|
140
|
+
/** Filter by source address(es). */
|
|
141
|
+
src?: number[];
|
|
142
|
+
/** Max frames to return. Defaults to 10000. */
|
|
143
|
+
limit?: number;
|
|
144
|
+
/** Downsample interval (e.g. "1s", "PT1M"). */
|
|
145
|
+
interval?: string;
|
|
146
|
+
/** Include decoded values in response. */
|
|
147
|
+
decode?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/** Broker health details. */
|
|
150
|
+
interface BrokerHealth {
|
|
151
|
+
status: string;
|
|
152
|
+
frames_total: number;
|
|
153
|
+
head_seq: number;
|
|
154
|
+
last_frame_time: string;
|
|
155
|
+
device_count: number;
|
|
156
|
+
ring_entries: number;
|
|
157
|
+
ring_capacity: number;
|
|
108
158
|
}
|
|
109
|
-
/**
|
|
159
|
+
/** Replication component health (within health response). */
|
|
160
|
+
interface ReplicationHealth {
|
|
161
|
+
status: string;
|
|
162
|
+
connected: boolean;
|
|
163
|
+
live_lag: number;
|
|
164
|
+
backfill_remaining_seqs: number;
|
|
165
|
+
last_ack: string;
|
|
166
|
+
}
|
|
167
|
+
/** Health check response from GET /healthz or /readyz. */
|
|
110
168
|
interface HealthStatus {
|
|
111
169
|
status: string;
|
|
170
|
+
broker?: BrokerHealth;
|
|
171
|
+
replication?: ReplicationHealth;
|
|
172
|
+
components?: Record<string, unknown>;
|
|
173
|
+
/** Cloud-only: total known instances. */
|
|
174
|
+
instances_total?: number;
|
|
175
|
+
/** Cloud-only: currently connected instances. */
|
|
176
|
+
instances_connected?: number;
|
|
112
177
|
}
|
|
113
178
|
/** Boat-side replication status from GET /replication/status. */
|
|
114
179
|
interface ReplicationStatus {
|
|
@@ -120,6 +185,10 @@ interface ReplicationStatus {
|
|
|
120
185
|
live_lag: number;
|
|
121
186
|
backfill_remaining_seqs: number;
|
|
122
187
|
last_ack: string;
|
|
188
|
+
live_frames_sent: number;
|
|
189
|
+
backfill_blocks_sent: number;
|
|
190
|
+
backfill_bytes_sent: number;
|
|
191
|
+
reconnects: number;
|
|
123
192
|
}
|
|
124
193
|
/** Summary of a cloud instance, returned by GET /instances. */
|
|
125
194
|
interface InstanceSummary {
|
|
@@ -186,19 +255,31 @@ declare class Client {
|
|
|
186
255
|
/**
|
|
187
256
|
* Open an ephemeral SSE stream with optional filtering.
|
|
188
257
|
* No session, no replay, no ACK.
|
|
258
|
+
*
|
|
259
|
+
* Accepts either a Filter (for backwards compatibility) or
|
|
260
|
+
* SubscribeOptions for additional control (decode, signal).
|
|
189
261
|
*/
|
|
190
|
-
subscribe(
|
|
262
|
+
subscribe(filterOrOptions?: Filter | SubscribeOptions, signal?: AbortSignal): Promise<AsyncIterable<Event>>;
|
|
191
263
|
/** Fetch the last-seen decoded values for each (device, PGN) pair. */
|
|
192
264
|
decodedValues(filter?: Filter, signal?: AbortSignal): Promise<DecodedDeviceValues[]>;
|
|
193
265
|
/** Transmit a CAN frame through the server. */
|
|
194
266
|
send(params: SendParams, signal?: AbortSignal): Promise<void>;
|
|
195
267
|
/**
|
|
196
268
|
* Send an ISO Request (PGN 59904) and wait for the response frame.
|
|
197
|
-
* Returns the response frame, or throws HttpError
|
|
269
|
+
* Returns the response frame, or throws HttpError on timeout (408).
|
|
198
270
|
*/
|
|
199
271
|
query(params: QueryParams, signal?: AbortSignal): Promise<Frame>;
|
|
200
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* Query historical frames (requires journaling on the server).
|
|
274
|
+
* Returns an array of frames matching the query parameters.
|
|
275
|
+
*/
|
|
276
|
+
history(params: HistoryParams, signal?: AbortSignal): Promise<Frame[]>;
|
|
277
|
+
/** Check server health (GET /healthz). */
|
|
201
278
|
health(signal?: AbortSignal): Promise<HealthStatus>;
|
|
279
|
+
/** Liveness probe (GET /livez). */
|
|
280
|
+
liveness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
281
|
+
/** Readiness probe (GET /readyz). */
|
|
282
|
+
readiness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
202
283
|
/** Fetch boat-side replication status (only available when replication is configured). */
|
|
203
284
|
replicationStatus(signal?: AbortSignal): Promise<ReplicationStatus>;
|
|
204
285
|
/** Create or reconnect a buffered session on the server. */
|
|
@@ -230,6 +311,12 @@ declare class CloudClient {
|
|
|
230
311
|
status(instanceId: string, signal?: AbortSignal): Promise<InstanceStatus>;
|
|
231
312
|
/** Fetch recent replication diagnostic events for an instance. */
|
|
232
313
|
replicationEvents(instanceId: string, limit?: number, signal?: AbortSignal): Promise<ReplicationEvent[]>;
|
|
314
|
+
/** Check cloud server health (GET /healthz). */
|
|
315
|
+
health(signal?: AbortSignal): Promise<HealthStatus>;
|
|
316
|
+
/** Liveness probe (GET /livez). */
|
|
317
|
+
liveness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
318
|
+
/** Readiness probe (GET /readyz). */
|
|
319
|
+
readiness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
233
320
|
}
|
|
234
321
|
|
|
235
322
|
declare class LplexError extends Error {
|
|
@@ -241,4 +328,4 @@ declare class HttpError extends LplexError {
|
|
|
241
328
|
constructor(method: string, path: string, status: number, body: string);
|
|
242
329
|
}
|
|
243
330
|
|
|
244
|
-
export { Client, type ClientOptions, CloudClient, type CloudClientOptions, type DecodedDeviceValues, type DecodedPGNValue, type Device, type DeviceValues, type Event, type Filter, type Frame, type HealthStatus, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type QueryParams, type ReplicationEvent, type ReplicationEventType, type ReplicationStatus, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo };
|
|
331
|
+
export { type BrokerHealth, Client, type ClientOptions, CloudClient, type CloudClientOptions, type DecodedDeviceValues, type DecodedPGNValue, type Device, type DeviceRemoved, type DeviceValues, type Event, type Filter, type Frame, type HealthStatus, type HistoryParams, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type QueryParams, type ReplicationEvent, type ReplicationEventType, type ReplicationHealth, type ReplicationStatus, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo, type SubscribeOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
interface Frame {
|
|
3
3
|
seq: number;
|
|
4
4
|
ts: string;
|
|
5
|
+
bus?: string;
|
|
5
6
|
prio: number;
|
|
6
7
|
pgn: number;
|
|
7
8
|
src: number;
|
|
8
9
|
dst: number;
|
|
9
10
|
data: string;
|
|
11
|
+
decoded?: Record<string, unknown>;
|
|
10
12
|
}
|
|
11
13
|
/** An NMEA 2000 device discovered on the bus. */
|
|
12
14
|
interface Device {
|
|
15
|
+
bus?: string;
|
|
13
16
|
src: number;
|
|
14
17
|
name: string;
|
|
15
18
|
manufacturer: string;
|
|
@@ -28,6 +31,12 @@ interface Device {
|
|
|
28
31
|
packet_count: number;
|
|
29
32
|
byte_count: number;
|
|
30
33
|
}
|
|
34
|
+
/** A device-removed notification from the bus. */
|
|
35
|
+
interface DeviceRemoved {
|
|
36
|
+
type: "device_removed";
|
|
37
|
+
bus?: string;
|
|
38
|
+
src: number;
|
|
39
|
+
}
|
|
31
40
|
/** Discriminated union for SSE events. */
|
|
32
41
|
type Event = {
|
|
33
42
|
type: "frame";
|
|
@@ -35,6 +44,9 @@ type Event = {
|
|
|
35
44
|
} | {
|
|
36
45
|
type: "device";
|
|
37
46
|
device: Device;
|
|
47
|
+
} | {
|
|
48
|
+
type: "device_removed";
|
|
49
|
+
deviceRemoved: DeviceRemoved;
|
|
38
50
|
};
|
|
39
51
|
/**
|
|
40
52
|
* Filter for CAN frames.
|
|
@@ -47,6 +59,14 @@ interface Filter {
|
|
|
47
59
|
instance?: number[];
|
|
48
60
|
name?: string[];
|
|
49
61
|
exclude_name?: string[];
|
|
62
|
+
bus?: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Options for ephemeral SSE subscription. */
|
|
65
|
+
interface SubscribeOptions {
|
|
66
|
+
filter?: Filter;
|
|
67
|
+
/** When true, frames include decoded field values. */
|
|
68
|
+
decode?: boolean;
|
|
69
|
+
signal?: AbortSignal;
|
|
50
70
|
}
|
|
51
71
|
/** Configuration for creating a buffered session. */
|
|
52
72
|
interface SessionConfig {
|
|
@@ -68,6 +88,7 @@ interface SendParams {
|
|
|
68
88
|
dst: number;
|
|
69
89
|
prio: number;
|
|
70
90
|
data: string;
|
|
91
|
+
bus?: string;
|
|
71
92
|
}
|
|
72
93
|
/** A single PGN's last-known value for a device. */
|
|
73
94
|
interface PGNValue {
|
|
@@ -103,12 +124,56 @@ interface DecodedDeviceValues {
|
|
|
103
124
|
/** Parameters for an ISO Request query (POST /query). */
|
|
104
125
|
interface QueryParams {
|
|
105
126
|
pgn: number;
|
|
106
|
-
|
|
127
|
+
/** Destination address. Defaults to 0xFF (broadcast) on the server. */
|
|
128
|
+
dst?: number;
|
|
107
129
|
timeout?: string;
|
|
130
|
+
bus?: string;
|
|
131
|
+
}
|
|
132
|
+
/** Parameters for historical data query (GET /history). */
|
|
133
|
+
interface HistoryParams {
|
|
134
|
+
/** Start timestamp (RFC 3339). */
|
|
135
|
+
from: string;
|
|
136
|
+
/** End timestamp (RFC 3339). Defaults to now. */
|
|
137
|
+
to?: string;
|
|
138
|
+
/** Filter by PGN(s). */
|
|
139
|
+
pgn?: number[];
|
|
140
|
+
/** Filter by source address(es). */
|
|
141
|
+
src?: number[];
|
|
142
|
+
/** Max frames to return. Defaults to 10000. */
|
|
143
|
+
limit?: number;
|
|
144
|
+
/** Downsample interval (e.g. "1s", "PT1M"). */
|
|
145
|
+
interval?: string;
|
|
146
|
+
/** Include decoded values in response. */
|
|
147
|
+
decode?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/** Broker health details. */
|
|
150
|
+
interface BrokerHealth {
|
|
151
|
+
status: string;
|
|
152
|
+
frames_total: number;
|
|
153
|
+
head_seq: number;
|
|
154
|
+
last_frame_time: string;
|
|
155
|
+
device_count: number;
|
|
156
|
+
ring_entries: number;
|
|
157
|
+
ring_capacity: number;
|
|
108
158
|
}
|
|
109
|
-
/**
|
|
159
|
+
/** Replication component health (within health response). */
|
|
160
|
+
interface ReplicationHealth {
|
|
161
|
+
status: string;
|
|
162
|
+
connected: boolean;
|
|
163
|
+
live_lag: number;
|
|
164
|
+
backfill_remaining_seqs: number;
|
|
165
|
+
last_ack: string;
|
|
166
|
+
}
|
|
167
|
+
/** Health check response from GET /healthz or /readyz. */
|
|
110
168
|
interface HealthStatus {
|
|
111
169
|
status: string;
|
|
170
|
+
broker?: BrokerHealth;
|
|
171
|
+
replication?: ReplicationHealth;
|
|
172
|
+
components?: Record<string, unknown>;
|
|
173
|
+
/** Cloud-only: total known instances. */
|
|
174
|
+
instances_total?: number;
|
|
175
|
+
/** Cloud-only: currently connected instances. */
|
|
176
|
+
instances_connected?: number;
|
|
112
177
|
}
|
|
113
178
|
/** Boat-side replication status from GET /replication/status. */
|
|
114
179
|
interface ReplicationStatus {
|
|
@@ -120,6 +185,10 @@ interface ReplicationStatus {
|
|
|
120
185
|
live_lag: number;
|
|
121
186
|
backfill_remaining_seqs: number;
|
|
122
187
|
last_ack: string;
|
|
188
|
+
live_frames_sent: number;
|
|
189
|
+
backfill_blocks_sent: number;
|
|
190
|
+
backfill_bytes_sent: number;
|
|
191
|
+
reconnects: number;
|
|
123
192
|
}
|
|
124
193
|
/** Summary of a cloud instance, returned by GET /instances. */
|
|
125
194
|
interface InstanceSummary {
|
|
@@ -186,19 +255,31 @@ declare class Client {
|
|
|
186
255
|
/**
|
|
187
256
|
* Open an ephemeral SSE stream with optional filtering.
|
|
188
257
|
* No session, no replay, no ACK.
|
|
258
|
+
*
|
|
259
|
+
* Accepts either a Filter (for backwards compatibility) or
|
|
260
|
+
* SubscribeOptions for additional control (decode, signal).
|
|
189
261
|
*/
|
|
190
|
-
subscribe(
|
|
262
|
+
subscribe(filterOrOptions?: Filter | SubscribeOptions, signal?: AbortSignal): Promise<AsyncIterable<Event>>;
|
|
191
263
|
/** Fetch the last-seen decoded values for each (device, PGN) pair. */
|
|
192
264
|
decodedValues(filter?: Filter, signal?: AbortSignal): Promise<DecodedDeviceValues[]>;
|
|
193
265
|
/** Transmit a CAN frame through the server. */
|
|
194
266
|
send(params: SendParams, signal?: AbortSignal): Promise<void>;
|
|
195
267
|
/**
|
|
196
268
|
* Send an ISO Request (PGN 59904) and wait for the response frame.
|
|
197
|
-
* Returns the response frame, or throws HttpError
|
|
269
|
+
* Returns the response frame, or throws HttpError on timeout (408).
|
|
198
270
|
*/
|
|
199
271
|
query(params: QueryParams, signal?: AbortSignal): Promise<Frame>;
|
|
200
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* Query historical frames (requires journaling on the server).
|
|
274
|
+
* Returns an array of frames matching the query parameters.
|
|
275
|
+
*/
|
|
276
|
+
history(params: HistoryParams, signal?: AbortSignal): Promise<Frame[]>;
|
|
277
|
+
/** Check server health (GET /healthz). */
|
|
201
278
|
health(signal?: AbortSignal): Promise<HealthStatus>;
|
|
279
|
+
/** Liveness probe (GET /livez). */
|
|
280
|
+
liveness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
281
|
+
/** Readiness probe (GET /readyz). */
|
|
282
|
+
readiness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
202
283
|
/** Fetch boat-side replication status (only available when replication is configured). */
|
|
203
284
|
replicationStatus(signal?: AbortSignal): Promise<ReplicationStatus>;
|
|
204
285
|
/** Create or reconnect a buffered session on the server. */
|
|
@@ -230,6 +311,12 @@ declare class CloudClient {
|
|
|
230
311
|
status(instanceId: string, signal?: AbortSignal): Promise<InstanceStatus>;
|
|
231
312
|
/** Fetch recent replication diagnostic events for an instance. */
|
|
232
313
|
replicationEvents(instanceId: string, limit?: number, signal?: AbortSignal): Promise<ReplicationEvent[]>;
|
|
314
|
+
/** Check cloud server health (GET /healthz). */
|
|
315
|
+
health(signal?: AbortSignal): Promise<HealthStatus>;
|
|
316
|
+
/** Liveness probe (GET /livez). */
|
|
317
|
+
liveness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
318
|
+
/** Readiness probe (GET /readyz). */
|
|
319
|
+
readiness(signal?: AbortSignal): Promise<HealthStatus>;
|
|
233
320
|
}
|
|
234
321
|
|
|
235
322
|
declare class LplexError extends Error {
|
|
@@ -241,4 +328,4 @@ declare class HttpError extends LplexError {
|
|
|
241
328
|
constructor(method: string, path: string, status: number, body: string);
|
|
242
329
|
}
|
|
243
330
|
|
|
244
|
-
export { Client, type ClientOptions, CloudClient, type CloudClientOptions, type DecodedDeviceValues, type DecodedPGNValue, type Device, type DeviceValues, type Event, type Filter, type Frame, type HealthStatus, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type QueryParams, type ReplicationEvent, type ReplicationEventType, type ReplicationStatus, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo };
|
|
331
|
+
export { type BrokerHealth, Client, type ClientOptions, CloudClient, type CloudClientOptions, type DecodedDeviceValues, type DecodedPGNValue, type Device, type DeviceRemoved, type DeviceValues, type Event, type Filter, type Frame, type HealthStatus, type HistoryParams, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type QueryParams, type ReplicationEvent, type ReplicationEventType, type ReplicationHealth, type ReplicationStatus, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo, type SubscribeOptions };
|
package/dist/index.js
CHANGED
|
@@ -59,8 +59,16 @@ async function* parseSSE(body) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
function classify(obj) {
|
|
62
|
-
if ("type" in obj
|
|
63
|
-
|
|
62
|
+
if ("type" in obj) {
|
|
63
|
+
if (obj.type === "device") {
|
|
64
|
+
return { type: "device", device: obj };
|
|
65
|
+
}
|
|
66
|
+
if (obj.type === "device_removed") {
|
|
67
|
+
return {
|
|
68
|
+
type: "device_removed",
|
|
69
|
+
deviceRemoved: obj
|
|
70
|
+
};
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
if ("seq" in obj) {
|
|
66
74
|
return { type: "frame", frame: obj };
|
|
@@ -155,14 +163,27 @@ var Client = class {
|
|
|
155
163
|
/**
|
|
156
164
|
* Open an ephemeral SSE stream with optional filtering.
|
|
157
165
|
* No session, no replay, no ACK.
|
|
166
|
+
*
|
|
167
|
+
* Accepts either a Filter (for backwards compatibility) or
|
|
168
|
+
* SubscribeOptions for additional control (decode, signal).
|
|
158
169
|
*/
|
|
159
|
-
async subscribe(
|
|
170
|
+
async subscribe(filterOrOptions, signal) {
|
|
171
|
+
let filter;
|
|
172
|
+
let decode;
|
|
173
|
+
let sig = signal;
|
|
174
|
+
if (filterOrOptions && isSubscribeOptions(filterOrOptions)) {
|
|
175
|
+
filter = filterOrOptions.filter;
|
|
176
|
+
decode = filterOrOptions.decode;
|
|
177
|
+
sig = filterOrOptions.signal ?? sig;
|
|
178
|
+
} else {
|
|
179
|
+
filter = filterOrOptions;
|
|
180
|
+
}
|
|
160
181
|
let url = `${this.#baseURL}/events`;
|
|
161
|
-
const qs = filterToQueryString(filter);
|
|
182
|
+
const qs = filterToQueryString(filter, decode);
|
|
162
183
|
if (qs) url += `?${qs}`;
|
|
163
184
|
const resp = await this.#fetch(url, {
|
|
164
185
|
headers: { Accept: "text/event-stream" },
|
|
165
|
-
signal
|
|
186
|
+
signal: sig
|
|
166
187
|
});
|
|
167
188
|
if (!resp.ok) {
|
|
168
189
|
const body = await resp.text();
|
|
@@ -201,7 +222,7 @@ var Client = class {
|
|
|
201
222
|
}
|
|
202
223
|
/**
|
|
203
224
|
* Send an ISO Request (PGN 59904) and wait for the response frame.
|
|
204
|
-
* Returns the response frame, or throws HttpError
|
|
225
|
+
* Returns the response frame, or throws HttpError on timeout (408).
|
|
205
226
|
*/
|
|
206
227
|
async query(params, signal) {
|
|
207
228
|
const url = `${this.#baseURL}/query`;
|
|
@@ -217,7 +238,28 @@ var Client = class {
|
|
|
217
238
|
}
|
|
218
239
|
return resp.json();
|
|
219
240
|
}
|
|
220
|
-
/**
|
|
241
|
+
/**
|
|
242
|
+
* Query historical frames (requires journaling on the server).
|
|
243
|
+
* Returns an array of frames matching the query parameters.
|
|
244
|
+
*/
|
|
245
|
+
async history(params, signal) {
|
|
246
|
+
const qs = new URLSearchParams();
|
|
247
|
+
qs.set("from", params.from);
|
|
248
|
+
if (params.to) qs.set("to", params.to);
|
|
249
|
+
for (const p of params.pgn ?? []) qs.append("pgn", p.toString());
|
|
250
|
+
for (const s of params.src ?? []) qs.append("src", s.toString());
|
|
251
|
+
if (params.limit !== void 0) qs.set("limit", params.limit.toString());
|
|
252
|
+
if (params.interval) qs.set("interval", params.interval);
|
|
253
|
+
if (params.decode) qs.set("decode", "true");
|
|
254
|
+
const url = `${this.#baseURL}/history?${qs.toString()}`;
|
|
255
|
+
const resp = await this.#fetch(url, { signal });
|
|
256
|
+
if (!resp.ok) {
|
|
257
|
+
const body = await resp.text();
|
|
258
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
259
|
+
}
|
|
260
|
+
return resp.json();
|
|
261
|
+
}
|
|
262
|
+
/** Check server health (GET /healthz). */
|
|
221
263
|
async health(signal) {
|
|
222
264
|
const url = `${this.#baseURL}/healthz`;
|
|
223
265
|
const resp = await this.#fetch(url, { signal });
|
|
@@ -227,6 +269,26 @@ var Client = class {
|
|
|
227
269
|
}
|
|
228
270
|
return resp.json();
|
|
229
271
|
}
|
|
272
|
+
/** Liveness probe (GET /livez). */
|
|
273
|
+
async liveness(signal) {
|
|
274
|
+
const url = `${this.#baseURL}/livez`;
|
|
275
|
+
const resp = await this.#fetch(url, { signal });
|
|
276
|
+
if (!resp.ok) {
|
|
277
|
+
const body = await resp.text();
|
|
278
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
279
|
+
}
|
|
280
|
+
return resp.json();
|
|
281
|
+
}
|
|
282
|
+
/** Readiness probe (GET /readyz). */
|
|
283
|
+
async readiness(signal) {
|
|
284
|
+
const url = `${this.#baseURL}/readyz`;
|
|
285
|
+
const resp = await this.#fetch(url, { signal });
|
|
286
|
+
if (!resp.ok) {
|
|
287
|
+
const body = await resp.text();
|
|
288
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
289
|
+
}
|
|
290
|
+
return resp.json();
|
|
291
|
+
}
|
|
230
292
|
/** Fetch boat-side replication status (only available when replication is configured). */
|
|
231
293
|
async replicationStatus(signal) {
|
|
232
294
|
const url = `${this.#baseURL}/replication/status`;
|
|
@@ -260,19 +322,26 @@ var Client = class {
|
|
|
260
322
|
return new Session(this.#baseURL, this.#fetch, info);
|
|
261
323
|
}
|
|
262
324
|
};
|
|
325
|
+
function isSubscribeOptions(obj) {
|
|
326
|
+
return "decode" in obj || "signal" in obj || "filter" in obj;
|
|
327
|
+
}
|
|
263
328
|
function filterIsEmpty(f) {
|
|
264
|
-
return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length;
|
|
329
|
+
return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length && !f.bus?.length;
|
|
265
330
|
}
|
|
266
|
-
function filterToQueryString(f) {
|
|
267
|
-
if (!f || filterIsEmpty(f)) return "";
|
|
331
|
+
function filterToQueryString(f, decode) {
|
|
332
|
+
if ((!f || filterIsEmpty(f)) && !decode) return "";
|
|
268
333
|
const params = new URLSearchParams();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
334
|
+
if (f) {
|
|
335
|
+
for (const p of f.pgn ?? []) params.append("pgn", p.toString());
|
|
336
|
+
for (const p of f.exclude_pgn ?? [])
|
|
337
|
+
params.append("exclude_pgn", p.toString());
|
|
338
|
+
for (const m of f.manufacturer ?? []) params.append("manufacturer", m);
|
|
339
|
+
for (const i of f.instance ?? []) params.append("instance", i.toString());
|
|
340
|
+
for (const n of f.name ?? []) params.append("name", n);
|
|
341
|
+
for (const n of f.exclude_name ?? []) params.append("exclude_name", n);
|
|
342
|
+
for (const b of f.bus ?? []) params.append("bus", b);
|
|
343
|
+
}
|
|
344
|
+
if (decode) params.set("decode", "true");
|
|
276
345
|
return params.toString();
|
|
277
346
|
}
|
|
278
347
|
function filterToJSON(f) {
|
|
@@ -283,6 +352,7 @@ function filterToJSON(f) {
|
|
|
283
352
|
if (f.instance?.length) m.instance = f.instance;
|
|
284
353
|
if (f.name?.length) m.name = f.name;
|
|
285
354
|
if (f.exclude_name?.length) m.exclude_name = f.exclude_name;
|
|
355
|
+
if (f.bus?.length) m.bus = f.bus;
|
|
286
356
|
return m;
|
|
287
357
|
}
|
|
288
358
|
|
|
@@ -338,6 +408,36 @@ var CloudClient = class {
|
|
|
338
408
|
}
|
|
339
409
|
return resp.json();
|
|
340
410
|
}
|
|
411
|
+
/** Check cloud server health (GET /healthz). */
|
|
412
|
+
async health(signal) {
|
|
413
|
+
const url = `${this.#baseURL}/healthz`;
|
|
414
|
+
const resp = await this.#fetch(url, { signal });
|
|
415
|
+
if (!resp.ok) {
|
|
416
|
+
const body = await resp.text();
|
|
417
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
418
|
+
}
|
|
419
|
+
return resp.json();
|
|
420
|
+
}
|
|
421
|
+
/** Liveness probe (GET /livez). */
|
|
422
|
+
async liveness(signal) {
|
|
423
|
+
const url = `${this.#baseURL}/livez`;
|
|
424
|
+
const resp = await this.#fetch(url, { signal });
|
|
425
|
+
if (!resp.ok) {
|
|
426
|
+
const body = await resp.text();
|
|
427
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
428
|
+
}
|
|
429
|
+
return resp.json();
|
|
430
|
+
}
|
|
431
|
+
/** Readiness probe (GET /readyz). */
|
|
432
|
+
async readiness(signal) {
|
|
433
|
+
const url = `${this.#baseURL}/readyz`;
|
|
434
|
+
const resp = await this.#fetch(url, { signal });
|
|
435
|
+
if (!resp.ok) {
|
|
436
|
+
const body = await resp.text();
|
|
437
|
+
throw new HttpError("GET", url, resp.status, body);
|
|
438
|
+
}
|
|
439
|
+
return resp.json();
|
|
440
|
+
}
|
|
341
441
|
};
|
|
342
442
|
export {
|
|
343
443
|
Client,
|