@sixfathoms/lplex 0.2.1 → 0.3.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 CHANGED
@@ -203,6 +203,18 @@ var Client = class {
203
203
  }
204
204
  return parseSSE(resp.body);
205
205
  }
206
+ /** Fetch the last-seen decoded values for each (device, PGN) pair. */
207
+ async decodedValues(filter, signal) {
208
+ let url = `${this.#baseURL}/values/decoded`;
209
+ const qs = filterToQueryString(filter);
210
+ if (qs) url += `?${qs}`;
211
+ const resp = await this.#fetch(url, { signal });
212
+ if (!resp.ok) {
213
+ const body = await resp.text();
214
+ throw new HttpError("GET", url, resp.status, body);
215
+ }
216
+ return resp.json();
217
+ }
206
218
  /** Transmit a CAN frame through the server. */
207
219
  async send(params, signal) {
208
220
  const url = `${this.#baseURL}/send`;
@@ -217,6 +229,44 @@ var Client = class {
217
229
  throw new HttpError("POST", url, resp.status, body);
218
230
  }
219
231
  }
232
+ /**
233
+ * Send an ISO Request (PGN 59904) and wait for the response frame.
234
+ * Returns the response frame, or throws HttpError with status 504 on timeout.
235
+ */
236
+ async query(params, signal) {
237
+ const url = `${this.#baseURL}/query`;
238
+ const resp = await this.#fetch(url, {
239
+ method: "POST",
240
+ headers: { "Content-Type": "application/json" },
241
+ body: JSON.stringify(params),
242
+ signal
243
+ });
244
+ if (!resp.ok) {
245
+ const body = await resp.text();
246
+ throw new HttpError("POST", url, resp.status, body);
247
+ }
248
+ return resp.json();
249
+ }
250
+ /** Check server health. */
251
+ async health(signal) {
252
+ const url = `${this.#baseURL}/healthz`;
253
+ const resp = await this.#fetch(url, { signal });
254
+ if (!resp.ok) {
255
+ const body = await resp.text();
256
+ throw new HttpError("GET", url, resp.status, body);
257
+ }
258
+ return resp.json();
259
+ }
260
+ /** Fetch boat-side replication status (only available when replication is configured). */
261
+ async replicationStatus(signal) {
262
+ const url = `${this.#baseURL}/replication/status`;
263
+ const resp = await this.#fetch(url, { signal });
264
+ if (!resp.ok) {
265
+ const body = await resp.text();
266
+ throw new HttpError("GET", url, resp.status, body);
267
+ }
268
+ return resp.json();
269
+ }
220
270
  /** Create or reconnect a buffered session on the server. */
221
271
  async createSession(config, signal) {
222
272
  const url = `${this.#baseURL}/clients/${config.clientId}`;
@@ -241,23 +291,28 @@ var Client = class {
241
291
  }
242
292
  };
243
293
  function filterIsEmpty(f) {
244
- return !f.pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length;
294
+ return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length;
245
295
  }
246
296
  function filterToQueryString(f) {
247
297
  if (!f || filterIsEmpty(f)) return "";
248
298
  const params = new URLSearchParams();
249
299
  for (const p of f.pgn ?? []) params.append("pgn", p.toString());
300
+ for (const p of f.exclude_pgn ?? [])
301
+ params.append("exclude_pgn", p.toString());
250
302
  for (const m of f.manufacturer ?? []) params.append("manufacturer", m);
251
303
  for (const i of f.instance ?? []) params.append("instance", i.toString());
252
304
  for (const n of f.name ?? []) params.append("name", n);
305
+ for (const n of f.exclude_name ?? []) params.append("exclude_name", n);
253
306
  return params.toString();
254
307
  }
255
308
  function filterToJSON(f) {
256
309
  const m = {};
257
310
  if (f.pgn?.length) m.pgn = f.pgn;
311
+ if (f.exclude_pgn?.length) m.exclude_pgn = f.exclude_pgn;
258
312
  if (f.manufacturer?.length) m.manufacturer = f.manufacturer;
259
313
  if (f.instance?.length) m.instance = f.instance;
260
314
  if (f.name?.length) m.name = f.name;
315
+ if (f.exclude_name?.length) m.exclude_name = f.exclude_name;
261
316
  return m;
262
317
  }
263
318
 
package/dist/index.d.cts CHANGED
@@ -42,9 +42,11 @@ type Event = {
42
42
  */
43
43
  interface Filter {
44
44
  pgn?: number[];
45
+ exclude_pgn?: number[];
45
46
  manufacturer?: string[];
46
47
  instance?: number[];
47
48
  name?: string[];
49
+ exclude_name?: string[];
48
50
  }
49
51
  /** Configuration for creating a buffered session. */
50
52
  interface SessionConfig {
@@ -82,6 +84,43 @@ interface DeviceValues {
82
84
  model_id?: string;
83
85
  values: PGNValue[];
84
86
  }
87
+ /** A single PGN's decoded value for a device. */
88
+ interface DecodedPGNValue {
89
+ pgn: number;
90
+ ts: string;
91
+ data: string;
92
+ seq: number;
93
+ decoded: Record<string, unknown>;
94
+ }
95
+ /** Decoded values grouped by device. */
96
+ interface DecodedDeviceValues {
97
+ name: string;
98
+ src: number;
99
+ manufacturer?: string;
100
+ model_id?: string;
101
+ values: DecodedPGNValue[];
102
+ }
103
+ /** Parameters for an ISO Request query (POST /query). */
104
+ interface QueryParams {
105
+ pgn: number;
106
+ dst: number;
107
+ timeout?: string;
108
+ }
109
+ /** Health check response from GET /healthz. */
110
+ interface HealthStatus {
111
+ status: string;
112
+ }
113
+ /** Boat-side replication status from GET /replication/status. */
114
+ interface ReplicationStatus {
115
+ connected: boolean;
116
+ instance_id: string;
117
+ local_head_seq: number;
118
+ cloud_cursor: number;
119
+ holes: SeqRange[];
120
+ live_lag: number;
121
+ backfill_remaining_seqs: number;
122
+ last_ack: string;
123
+ }
85
124
  /** Summary of a cloud instance, returned by GET /instances. */
86
125
  interface InstanceSummary {
87
126
  id: string;
@@ -149,8 +188,19 @@ declare class Client {
149
188
  * No session, no replay, no ACK.
150
189
  */
151
190
  subscribe(filter?: Filter, signal?: AbortSignal): Promise<AsyncIterable<Event>>;
191
+ /** Fetch the last-seen decoded values for each (device, PGN) pair. */
192
+ decodedValues(filter?: Filter, signal?: AbortSignal): Promise<DecodedDeviceValues[]>;
152
193
  /** Transmit a CAN frame through the server. */
153
194
  send(params: SendParams, signal?: AbortSignal): Promise<void>;
195
+ /**
196
+ * Send an ISO Request (PGN 59904) and wait for the response frame.
197
+ * Returns the response frame, or throws HttpError with status 504 on timeout.
198
+ */
199
+ query(params: QueryParams, signal?: AbortSignal): Promise<Frame>;
200
+ /** Check server health. */
201
+ health(signal?: AbortSignal): Promise<HealthStatus>;
202
+ /** Fetch boat-side replication status (only available when replication is configured). */
203
+ replicationStatus(signal?: AbortSignal): Promise<ReplicationStatus>;
154
204
  /** Create or reconnect a buffered session on the server. */
155
205
  createSession(config: SessionConfig, signal?: AbortSignal): Promise<Session>;
156
206
  }
@@ -191,4 +241,4 @@ declare class HttpError extends LplexError {
191
241
  constructor(method: string, path: string, status: number, body: string);
192
242
  }
193
243
 
194
- export { Client, type ClientOptions, CloudClient, type CloudClientOptions, type Device, type DeviceValues, type Event, type Filter, type Frame, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type ReplicationEvent, type ReplicationEventType, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo };
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 };
package/dist/index.d.ts CHANGED
@@ -42,9 +42,11 @@ type Event = {
42
42
  */
43
43
  interface Filter {
44
44
  pgn?: number[];
45
+ exclude_pgn?: number[];
45
46
  manufacturer?: string[];
46
47
  instance?: number[];
47
48
  name?: string[];
49
+ exclude_name?: string[];
48
50
  }
49
51
  /** Configuration for creating a buffered session. */
50
52
  interface SessionConfig {
@@ -82,6 +84,43 @@ interface DeviceValues {
82
84
  model_id?: string;
83
85
  values: PGNValue[];
84
86
  }
87
+ /** A single PGN's decoded value for a device. */
88
+ interface DecodedPGNValue {
89
+ pgn: number;
90
+ ts: string;
91
+ data: string;
92
+ seq: number;
93
+ decoded: Record<string, unknown>;
94
+ }
95
+ /** Decoded values grouped by device. */
96
+ interface DecodedDeviceValues {
97
+ name: string;
98
+ src: number;
99
+ manufacturer?: string;
100
+ model_id?: string;
101
+ values: DecodedPGNValue[];
102
+ }
103
+ /** Parameters for an ISO Request query (POST /query). */
104
+ interface QueryParams {
105
+ pgn: number;
106
+ dst: number;
107
+ timeout?: string;
108
+ }
109
+ /** Health check response from GET /healthz. */
110
+ interface HealthStatus {
111
+ status: string;
112
+ }
113
+ /** Boat-side replication status from GET /replication/status. */
114
+ interface ReplicationStatus {
115
+ connected: boolean;
116
+ instance_id: string;
117
+ local_head_seq: number;
118
+ cloud_cursor: number;
119
+ holes: SeqRange[];
120
+ live_lag: number;
121
+ backfill_remaining_seqs: number;
122
+ last_ack: string;
123
+ }
85
124
  /** Summary of a cloud instance, returned by GET /instances. */
86
125
  interface InstanceSummary {
87
126
  id: string;
@@ -149,8 +188,19 @@ declare class Client {
149
188
  * No session, no replay, no ACK.
150
189
  */
151
190
  subscribe(filter?: Filter, signal?: AbortSignal): Promise<AsyncIterable<Event>>;
191
+ /** Fetch the last-seen decoded values for each (device, PGN) pair. */
192
+ decodedValues(filter?: Filter, signal?: AbortSignal): Promise<DecodedDeviceValues[]>;
152
193
  /** Transmit a CAN frame through the server. */
153
194
  send(params: SendParams, signal?: AbortSignal): Promise<void>;
195
+ /**
196
+ * Send an ISO Request (PGN 59904) and wait for the response frame.
197
+ * Returns the response frame, or throws HttpError with status 504 on timeout.
198
+ */
199
+ query(params: QueryParams, signal?: AbortSignal): Promise<Frame>;
200
+ /** Check server health. */
201
+ health(signal?: AbortSignal): Promise<HealthStatus>;
202
+ /** Fetch boat-side replication status (only available when replication is configured). */
203
+ replicationStatus(signal?: AbortSignal): Promise<ReplicationStatus>;
154
204
  /** Create or reconnect a buffered session on the server. */
155
205
  createSession(config: SessionConfig, signal?: AbortSignal): Promise<Session>;
156
206
  }
@@ -191,4 +241,4 @@ declare class HttpError extends LplexError {
191
241
  constructor(method: string, path: string, status: number, body: string);
192
242
  }
193
243
 
194
- export { Client, type ClientOptions, CloudClient, type CloudClientOptions, type Device, type DeviceValues, type Event, type Filter, type Frame, HttpError, type InstanceStatus, type InstanceSummary, LplexError, type PGNValue, type ReplicationEvent, type ReplicationEventType, type SendParams, type SeqRange, Session, type SessionConfig, type SessionInfo };
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 };
package/dist/index.js CHANGED
@@ -173,6 +173,18 @@ var Client = class {
173
173
  }
174
174
  return parseSSE(resp.body);
175
175
  }
176
+ /** Fetch the last-seen decoded values for each (device, PGN) pair. */
177
+ async decodedValues(filter, signal) {
178
+ let url = `${this.#baseURL}/values/decoded`;
179
+ const qs = filterToQueryString(filter);
180
+ if (qs) url += `?${qs}`;
181
+ const resp = await this.#fetch(url, { signal });
182
+ if (!resp.ok) {
183
+ const body = await resp.text();
184
+ throw new HttpError("GET", url, resp.status, body);
185
+ }
186
+ return resp.json();
187
+ }
176
188
  /** Transmit a CAN frame through the server. */
177
189
  async send(params, signal) {
178
190
  const url = `${this.#baseURL}/send`;
@@ -187,6 +199,44 @@ var Client = class {
187
199
  throw new HttpError("POST", url, resp.status, body);
188
200
  }
189
201
  }
202
+ /**
203
+ * Send an ISO Request (PGN 59904) and wait for the response frame.
204
+ * Returns the response frame, or throws HttpError with status 504 on timeout.
205
+ */
206
+ async query(params, signal) {
207
+ const url = `${this.#baseURL}/query`;
208
+ const resp = await this.#fetch(url, {
209
+ method: "POST",
210
+ headers: { "Content-Type": "application/json" },
211
+ body: JSON.stringify(params),
212
+ signal
213
+ });
214
+ if (!resp.ok) {
215
+ const body = await resp.text();
216
+ throw new HttpError("POST", url, resp.status, body);
217
+ }
218
+ return resp.json();
219
+ }
220
+ /** Check server health. */
221
+ async health(signal) {
222
+ const url = `${this.#baseURL}/healthz`;
223
+ const resp = await this.#fetch(url, { signal });
224
+ if (!resp.ok) {
225
+ const body = await resp.text();
226
+ throw new HttpError("GET", url, resp.status, body);
227
+ }
228
+ return resp.json();
229
+ }
230
+ /** Fetch boat-side replication status (only available when replication is configured). */
231
+ async replicationStatus(signal) {
232
+ const url = `${this.#baseURL}/replication/status`;
233
+ const resp = await this.#fetch(url, { signal });
234
+ if (!resp.ok) {
235
+ const body = await resp.text();
236
+ throw new HttpError("GET", url, resp.status, body);
237
+ }
238
+ return resp.json();
239
+ }
190
240
  /** Create or reconnect a buffered session on the server. */
191
241
  async createSession(config, signal) {
192
242
  const url = `${this.#baseURL}/clients/${config.clientId}`;
@@ -211,23 +261,28 @@ var Client = class {
211
261
  }
212
262
  };
213
263
  function filterIsEmpty(f) {
214
- return !f.pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length;
264
+ return !f.pgn?.length && !f.exclude_pgn?.length && !f.manufacturer?.length && !f.instance?.length && !f.name?.length && !f.exclude_name?.length;
215
265
  }
216
266
  function filterToQueryString(f) {
217
267
  if (!f || filterIsEmpty(f)) return "";
218
268
  const params = new URLSearchParams();
219
269
  for (const p of f.pgn ?? []) params.append("pgn", p.toString());
270
+ for (const p of f.exclude_pgn ?? [])
271
+ params.append("exclude_pgn", p.toString());
220
272
  for (const m of f.manufacturer ?? []) params.append("manufacturer", m);
221
273
  for (const i of f.instance ?? []) params.append("instance", i.toString());
222
274
  for (const n of f.name ?? []) params.append("name", n);
275
+ for (const n of f.exclude_name ?? []) params.append("exclude_name", n);
223
276
  return params.toString();
224
277
  }
225
278
  function filterToJSON(f) {
226
279
  const m = {};
227
280
  if (f.pgn?.length) m.pgn = f.pgn;
281
+ if (f.exclude_pgn?.length) m.exclude_pgn = f.exclude_pgn;
228
282
  if (f.manufacturer?.length) m.manufacturer = f.manufacturer;
229
283
  if (f.instance?.length) m.instance = f.instance;
230
284
  if (f.name?.length) m.name = f.name;
285
+ if (f.exclude_name?.length) m.exclude_name = f.exclude_name;
231
286
  return m;
232
287
  }
233
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixfathoms/lplex",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript client for lplex CAN bus HTTP bridge",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",