fireboard-api-client 0.1.0 → 0.2.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/README.md CHANGED
@@ -22,25 +22,29 @@ const token = await login({
22
22
 
23
23
  const fireboard = new FireboardClient({ token });
24
24
  const devices = await fireboard.getDevices();
25
- const sessions = await fireboard.getSessions();
25
+
26
+ for (const device of devices) {
27
+ console.log(device.uuid, device.title, device.channels);
28
+ }
26
29
  ```
27
30
 
28
- If you already have a token, pass it directly to `new FireboardClient({ token })`. Reuse one client per account. The library supplies Fireboard's required User-Agent header and does not store login credentials.
31
+ If you already have a token, pass it directly to `new FireboardClient({ token })`. Reuse one client per account: device lists are cached for two minutes by default. The client supplies Fireboard’s required User-Agent header and does not store login credentials.
29
32
 
30
- API reference: [Fireboard Cloud API](https://docs.fireboard.io/app/app-api/).
33
+ API reference: [Fireboard API](https://docs.fireboard.io/app/app-api/).
31
34
 
32
35
  ## Methods
33
36
 
34
- All methods below are called on `FireboardClient`, except the standalone `login` function. Read methods return promises of the listed types.
37
+ Read and write methods are called on `FireboardClient`. `login` is a standalone function. Read methods return promises of the listed types.
35
38
 
36
39
  ### Read methods
37
40
 
38
41
  | Method | What it does | Returns |
39
42
  | ------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------- |
40
43
  | `getDevices()` | Lists the account’s devices with channels, temperatures, and available metadata | `RawDevice[]` |
44
+ | `getDevicesWithCache()` | Lists devices with cache freshness metadata | `DeviceListResult` |
41
45
  | `getDevice(deviceUuid)` | Retrieves detailed information for one device | `RawDevice` |
42
- | `getDeviceTemperatures(deviceUuid)` | Retrieves readings less than one minute old | `RawDeviceTemperatures` |
43
- | `getDriveLog(deviceUuid)` | Retrieves a Drive log less than one minute old | `RawDriveLog` |
46
+ | `getDeviceTemperatures(deviceUuid)` | Retrieves the latest available readings | `RawDeviceTemperatures` |
47
+ | `getDriveLog(deviceUuid)` | Retrieves a recent Drive log or an empty object | `RawDriveLogResponse` |
44
48
  | `getSessions()` | Lists current and historical account sessions | `RawSessionSummary[]` |
45
49
  | `getSessionDetail(sessionId)` | Retrieves a session’s metadata, devices, and available notes | `RawSessionDetail` |
46
50
  | `getSessionChart(sessionId, includeDrive?)` | Retrieves temperature time-series, optionally including Drive data | `RawChartChannel[]` |
@@ -63,35 +67,35 @@ Options are `loginUrl`, `timeoutMs`, `fetch`, and `userAgent`. Store the returne
63
67
 
64
68
  ### `getDevices()`
65
69
 
66
- Returns an array of devices. Reuse their UUIDs in later method calls.
70
+ Returns an array of devices, cached for two minutes by default. Pass a device’s `uuid`, not its numeric `id`, to device reads and Drive controls.
67
71
 
68
- ```typescript
69
- const devices = await fireboard.getDevices();
70
- ```
72
+ ### `getDevicesWithCache()`
73
+
74
+ Returns `{ data, fromCache, cacheAgeSeconds }`, where `data` is `RawDevice[]`. Shares the cache with `getDevices()`. Newly fetched responses have `fromCache: false` and age `0`; cached responses report their rounded age in seconds. This age describes the cached response, not the readings within it.
71
75
 
72
76
  ### `getDevice(deviceUuid)`
73
77
 
74
- Pass the device’s UUID, not its numeric ID. Returns the device and its available channel metadata.
78
+ Returns the device and its available channel metadata without using the device-list cache.
75
79
 
76
80
  ### `getDeviceTemperatures(deviceUuid)`
77
81
 
78
- Retrieves the latest channel temperatures for one device. Fireboard includes readings only when they are less than one minute old; older channels are omitted.
79
-
80
- ```typescript
81
- const readings = await fireboard.getDeviceTemperatures("your-device-uuid");
82
- ```
82
+ Retrieves the latest available channel temperatures. Fireboard documents a one-minute freshness cutoff, but live checks returned older data after power-off. Check reading timestamps before treating values as current.
83
83
 
84
84
  Fireboard does not document this response’s JSON shape. `RawDeviceTemperatures` preserves the returned JSON; narrow the result before accessing its fields.
85
85
 
86
86
  ### `getDriveLog(deviceUuid)`
87
87
 
88
- Returns a recent Drive log containing the observed `driveper`, `setpoint`, `modetype`, `tiedchannel`, `degreetype`, and `created` fields. Pass the device’s UUID.
88
+ Returns a recent Drive log with `driveper`, `setpoint`, `modetype`, `tiedchannel`, `degreetype`, and `created` fields, or `{}` when no recent log is available.
89
+
90
+ Check for an empty object before accessing log fields. Incomplete non-empty logs and other response shapes raise `INVALID_RESPONSE`.
89
91
 
90
92
  ```typescript
91
93
  const drive = await fireboard.getDriveLog("your-device-uuid");
92
- ```
93
94
 
94
- Fireboard only returns log data less than one minute old. The client expects a single object; other shapes raise `INVALID_RESPONSE`. Fireboard does not document the empty/stale-log response shape.
95
+ if (Object.keys(drive).length === 0) {
96
+ console.log("No recent Drive data");
97
+ }
98
+ ```
95
99
 
96
100
  ### `getSessions()`
97
101
 
@@ -105,15 +109,13 @@ Pass a non-negative integer session ID. Returns metadata, linked devices, and co
105
109
 
106
110
  ### `getSessionChart(sessionId, includeDrive = false)`
107
111
 
108
- Returns an array of channel series. Each includes `x` timestamps, `y` temperature values or Drive percentages, `device`, `label`, and `degreetype` (`1` for Celsius, `2` for Fahrenheit). Observed `channel_id` values are optional and numeric identifiers are normalized to strings.
112
+ Returns an array of channel series. Each includes `x` Unix timestamps in seconds, `y` temperature values or Drive percentages, `device`, `label`, and `degreetype`. `channel_id` is optional; numeric identifiers become strings.
109
113
 
110
114
  ```typescript
111
115
  const temperatures = await fireboard.getSessionChart(42);
112
116
  const temperaturesAndDrive = await fireboard.getSessionChart(42, true);
113
117
  ```
114
118
 
115
- Passing `true` includes Drive data.
116
-
117
119
  ### `setDriveSetpoint(deviceUuid, options)`
118
120
 
119
121
  Pass a finite numeric `setpoint`, a positive integer `channel`, or both. At least one is required, and all arguments are validated before any write is sent.
@@ -136,7 +138,7 @@ Pass a finite percentage between `0` and `100`. This puts Drive in manual mode a
136
138
  await fireboard.setDriveSpeed("your-device-uuid", 50);
137
139
  ```
138
140
 
139
- A value of `0` stops the fan. Use `setDriveOff` when you want to explicitly clear the setpoint as well.
141
+ A value of `0` stops the fan. Use `setDriveOff()` to clear the setpoint as well.
140
142
 
141
143
  ### `setDriveOff(deviceUuid)`
142
144
 
@@ -148,24 +150,31 @@ await fireboard.setDriveOff("your-device-uuid");
148
150
 
149
151
  ### Response validation
150
152
 
151
- Responses retain Fireboard’s field names and unknown fields, including nested fields. Zod validates known fields. Channel, alert, note, and Drive-log models include observed fields that Fireboard’s public reference does not fully specify.
153
+ Responses retain Fireboard’s field names and extra fields, including nested fields. Zod validates known fields; extra fields are preserved without individual validation or specific TypeScript types. Missing alerts and notes become empty arrays, and numeric chart `channel_id` values become strings.
154
+
155
+ Use each reading or chart series’ `degreetype` to interpret temperatures: `1` is Celsius and `2` is Fahrenheit. Device-level unit metadata may differ from the reading’s unit. Channels remain listed when probes are unplugged, but reading fields may be omitted.
156
+
157
+ The exported TypeScript types describe the client’s validated fields, including observed fields not covered by Fireboard’s public reference.
152
158
 
153
159
  ## Options
154
160
 
155
- `new FireboardClient({ token, baseUrl?, timeoutMs?, fetch?, userAgent? })`
161
+ `new FireboardClient({ token, baseUrl?, deviceCacheTtlMs?, timeoutMs?, fetch?, userAgent? })`
156
162
 
157
163
  - `baseUrl`: defaults to `https://fireboard.io/api/v1`.
164
+ - `deviceCacheTtlMs`: device-list cache TTL; defaults to 120,000 ms. Set `0` to disable caching and shared in-flight reads.
158
165
  - `timeoutMs`: defaults to 15,000 ms and covers fetching the response body.
159
166
  - `fetch`: injectable for testing or custom transports.
160
167
  - `userAgent`: optional application identification; defaults to `fireboard-api-client/<version>`.
161
168
 
162
169
  `login` accepts `loginUrl`, `timeoutMs`, `fetch`, and `userAgent` separately. URL overrides must use HTTP(S), without credentials, query strings, or fragments. Redirects are rejected to avoid forwarding credentials.
163
170
 
164
- ## Rate limits
171
+ ## Caching and rate limits
172
+
173
+ Only `getDevices()` and `getDevicesWithCache()` are cached. Each client has its own cache, and concurrent cache misses share a request. The TTL starts when a valid response arrives; failures are never cached. Returned data is copied so caller mutations do not affect the cache.
165
174
 
166
- The client does not cache responses, schedule requests, or retry failures. Consuming applications are responsible for caching and rate-limit mitigation.
175
+ Other reads and all writes are uncached. Writes do not invalidate cached device data. Nested temperature and Drive readings can remain stale until expiry; use `deviceCacheTtlMs: 0` for fresh device-list reads.
167
176
 
168
- Fireboard returns temperature and Drive-log readings only when they are less than one minute old.
177
+ The client does not schedule requests or retry failures. Consuming applications remain responsible for rate-limit mitigation.
169
178
 
170
179
  Fireboard limits each user to **17 API calls within any five-minute period**. After a rate-limit error, pause requests: continued calls can extend the block. Access generally resumes after approximately five minutes, once the request rate falls below the threshold.
171
180
 
@@ -209,11 +218,13 @@ All public exports are imported from `"fireboard-api-client"`; no internal file
209
218
  | `FireboardClientOptions` | Constructor options, including the required token |
210
219
  | `RequestOptions` | Shared transport options: `fetch`, `timeoutMs`, and `userAgent` |
211
220
  | `LoginOptions` | Login transport options and optional `loginUrl` |
221
+ | `DeviceListResult` | Device-list data and cache freshness metadata |
212
222
  | `DriveSetpointOptions` | A setpoint, a control channel, or both for `setDriveSetpoint` |
213
223
  | `FireboardErrorCode` | The union of error codes listed above |
214
224
  | `RawDevice` | One parsed device and its available metadata/channels |
215
225
  | `RawDeviceTemperatures` | JSON returned by the temperature read, without an assumed field schema |
216
226
  | `RawAlert` | An observed channel-alert model |
227
+ | `RawDriveLogResponse` | A populated `RawDriveLog` or an empty object |
217
228
  | `RawDriveLog` | An observed Drive-log model |
218
229
  | `RawSessionSummary` | A session-list entry |
219
230
  | `RawSessionDetail` | Detailed session metadata, devices, and notes |
@@ -221,11 +232,12 @@ All public exports are imported from `"fireboard-api-client"`; no internal file
221
232
 
222
233
  ### Default constants
223
234
 
224
- | Constant | Value |
225
- | -------------------- | ------------------------------------------- |
226
- | `DEFAULT_BASE_URL` | `https://fireboard.io/api/v1` |
227
- | `DEFAULT_LOGIN_URL` | `https://fireboard.io/api/rest-auth/login/` |
228
- | `DEFAULT_TIMEOUT_MS` | `15_000` |
235
+ | Constant | Value |
236
+ | ----------------------------- | ------------------------------------------- |
237
+ | `DEFAULT_BASE_URL` | `https://fireboard.io/api/v1` |
238
+ | `DEFAULT_LOGIN_URL` | `https://fireboard.io/api/rest-auth/login/` |
239
+ | `DEFAULT_DEVICE_CACHE_TTL_MS` | `120_000` |
240
+ | `DEFAULT_TIMEOUT_MS` | `15_000` |
229
241
 
230
242
  ### Zod validators
231
243
 
@@ -236,7 +248,7 @@ The client already validates responses. These exports are also available if you
236
248
  | `deviceSchema` | One device |
237
249
  | `devicesResponseSchema` | An array of devices |
238
250
  | `deviceTemperaturesResponseSchema` | JSON temperature data without an assumed response shape |
239
- | `driveLogResponseSchema` | The observed single-object Drive log |
251
+ | `driveLogResponseSchema` | A populated Drive log or an empty object |
240
252
  | `sessionsResponseSchema` | An array of session summaries |
241
253
  | `sessionDetailSchema` | One detailed session |
242
254
  | `chartChannelSchema` | One chart channel |
package/dist/client.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import type { z } from "zod";
2
- import type { DriveSetpointOptions, FireboardClientOptions } from "./types.js";
2
+ import type { DeviceListResult, DriveSetpointOptions, FireboardClientOptions } from "./types.js";
3
3
  export declare const DEFAULT_BASE_URL = "https://fireboard.io/api/v1";
4
4
  export declare class FireboardClient {
5
5
  #private;
6
6
  private readonly baseUrl;
7
7
  private readonly drive;
8
+ private readonly deviceCache;
8
9
  constructor(options: FireboardClientOptions);
9
10
  private get;
10
- /** Lists account devices; every call sends a separate request. */
11
+ /** Lists account devices using the per-client device cache. */
11
12
  getDevices(): Promise<{
12
13
  [x: string]: unknown;
13
14
  uuid: string;
@@ -57,14 +58,16 @@ export declare class FireboardClient {
57
58
  created: string;
58
59
  } | null | undefined;
59
60
  active?: boolean | undefined;
60
- last_battery_reading?: number | undefined;
61
+ last_battery_reading?: number | null | undefined;
61
62
  model_name?: string | undefined;
62
63
  created?: string | undefined;
63
64
  hardware_id?: string | undefined;
64
65
  latest_temps?: unknown[] | undefined;
65
- device_log?: Record<string, unknown> | undefined;
66
- last_templog?: string | undefined;
66
+ device_log?: Record<string, unknown> | null | undefined;
67
+ last_templog?: string | null | undefined;
67
68
  }[]>;
69
+ /** Lists devices with cache freshness metadata. */
70
+ getDevicesWithCache(): Promise<DeviceListResult>;
68
71
  /** Retrieves one device and its available metadata. */
69
72
  getDevice(uuid: string): Promise<{
70
73
  [x: string]: unknown;
@@ -115,13 +118,13 @@ export declare class FireboardClient {
115
118
  created: string;
116
119
  } | null | undefined;
117
120
  active?: boolean | undefined;
118
- last_battery_reading?: number | undefined;
121
+ last_battery_reading?: number | null | undefined;
119
122
  model_name?: string | undefined;
120
123
  created?: string | undefined;
121
124
  hardware_id?: string | undefined;
122
125
  latest_temps?: unknown[] | undefined;
123
- device_log?: Record<string, unknown> | undefined;
124
- last_templog?: string | undefined;
126
+ device_log?: Record<string, unknown> | null | undefined;
127
+ last_templog?: string | null | undefined;
125
128
  }>;
126
129
  /** Retrieves recent temperatures; the official reference does not define their JSON fields. */
127
130
  getDeviceTemperatures(uuid: string): Promise<z.core.util.JSONType>;
@@ -134,7 +137,7 @@ export declare class FireboardClient {
134
137
  tiedchannel: number;
135
138
  degreetype: number;
136
139
  created: string;
137
- }>;
140
+ } | Record<string, never>>;
138
141
  /** Lists account sessions; the API does not document their ordering. */
139
142
  getSessions(): Promise<{
140
143
  [x: string]: unknown;
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DeviceCache, DEFAULT_DEVICE_CACHE_TTL_MS } from "./device-cache.js";
1
2
  import { DriveControls } from "./drive.js";
2
3
  import { decode, headers, request, DEFAULT_TIMEOUT_MS } from "./request.js";
3
4
  import { chartResponseSchema, devicesResponseSchema, deviceSchema, deviceTemperaturesResponseSchema, driveLogResponseSchema, sessionDetailSchema, sessionsResponseSchema, } from "./schemas.js";
@@ -7,11 +8,13 @@ export class FireboardClient {
7
8
  #options;
8
9
  baseUrl;
9
10
  drive;
11
+ deviceCache;
10
12
  constructor(options) {
11
13
  if (typeof options?.token !== "string" || !options.token.trim()) {
12
14
  throw new TypeError("Fireboard token is required");
13
15
  }
14
16
  validateTiming(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, "timeoutMs");
17
+ this.deviceCache = new DeviceCache(validateTiming(options.deviceCacheTtlMs ?? DEFAULT_DEVICE_CACHE_TTL_MS, "deviceCacheTtlMs", true));
15
18
  this.baseUrl = validateEndpoint(options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
16
19
  this.#options = { ...options };
17
20
  this.drive = new DriveControls(this.baseUrl, this.#options);
@@ -20,9 +23,13 @@ export class FireboardClient {
20
23
  const response = await request(`${this.baseUrl}${path}`, { headers: headers(this.#options, this.#options.token) }, this.#options);
21
24
  return decode(response, schema);
22
25
  }
23
- /** Lists account devices; every call sends a separate request. */
24
- getDevices() {
25
- return this.get("/devices.json", devicesResponseSchema);
26
+ /** Lists account devices using the per-client device cache. */
27
+ async getDevices() {
28
+ return (await this.getDevicesWithCache()).data;
29
+ }
30
+ /** Lists devices with cache freshness metadata. */
31
+ getDevicesWithCache() {
32
+ return this.deviceCache.get(() => this.get("/devices.json", devicesResponseSchema));
26
33
  }
27
34
  /** Retrieves one device and its available metadata. */
28
35
  getDevice(uuid) {
@@ -0,0 +1,11 @@
1
+ import type { RawDevice } from "./schemas.js";
2
+ import type { DeviceListResult } from "./types.js";
3
+ export declare const DEFAULT_DEVICE_CACHE_TTL_MS = 120000;
4
+ export declare class DeviceCache {
5
+ private readonly ttlMs;
6
+ private cached;
7
+ private pending;
8
+ constructor(ttlMs: number);
9
+ get(load: () => Promise<RawDevice[]>): Promise<DeviceListResult>;
10
+ private fetch;
11
+ }
@@ -0,0 +1,40 @@
1
+ export const DEFAULT_DEVICE_CACHE_TTL_MS = 120_000;
2
+ export class DeviceCache {
3
+ ttlMs;
4
+ cached;
5
+ pending;
6
+ constructor(ttlMs) {
7
+ this.ttlMs = ttlMs;
8
+ }
9
+ async get(load) {
10
+ if (this.ttlMs === 0) {
11
+ return { data: await load(), fromCache: false, cacheAgeSeconds: 0 };
12
+ }
13
+ const now = Date.now();
14
+ if (this.cached && now - this.cached.fetchedAt < this.ttlMs) {
15
+ return {
16
+ data: structuredClone(this.cached.data),
17
+ fromCache: true,
18
+ cacheAgeSeconds: Math.round((now - this.cached.fetchedAt) / 1000),
19
+ };
20
+ }
21
+ if (!this.pending) {
22
+ this.pending = this.fetch(load);
23
+ }
24
+ const pending = this.pending;
25
+ try {
26
+ const result = await pending;
27
+ return { ...result, data: structuredClone(result.data) };
28
+ }
29
+ finally {
30
+ if (this.pending === pending) {
31
+ this.pending = undefined;
32
+ }
33
+ }
34
+ }
35
+ async fetch(load) {
36
+ const data = await load();
37
+ this.cached = { data, fetchedAt: Date.now() };
38
+ return { data, fromCache: false, cacheAgeSeconds: 0 };
39
+ }
40
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { FireboardClient, DEFAULT_BASE_URL } from "./client.js";
2
+ export { DEFAULT_DEVICE_CACHE_TTL_MS } from "./device-cache.js";
2
3
  export { login, DEFAULT_LOGIN_URL } from "./login.js";
3
4
  export { DEFAULT_TIMEOUT_MS } from "./request.js";
4
5
  export { FireboardError } from "./errors.js";
5
6
  export type { FireboardErrorCode } from "./errors.js";
6
- export type { FireboardClientOptions, RequestOptions, LoginOptions, DriveSetpointOptions, } from "./types.js";
7
+ export type { FireboardClientOptions, RequestOptions, LoginOptions, DriveSetpointOptions, DeviceListResult, } from "./types.js";
7
8
  export * from "./schemas.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { FireboardClient, DEFAULT_BASE_URL } from "./client.js";
2
+ export { DEFAULT_DEVICE_CACHE_TTL_MS } from "./device-cache.js";
2
3
  export { login, DEFAULT_LOGIN_URL } from "./login.js";
3
4
  export { DEFAULT_TIMEOUT_MS } from "./request.js";
4
5
  export { FireboardError } from "./errors.js";
package/dist/schemas.d.ts CHANGED
@@ -68,13 +68,13 @@ export declare const deviceSchema: z.ZodObject<{
68
68
  created: z.ZodString;
69
69
  }, z.core.$loose>>>;
70
70
  active: z.ZodOptional<z.ZodBoolean>;
71
- last_battery_reading: z.ZodOptional<z.ZodNumber>;
71
+ last_battery_reading: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
72
72
  model_name: z.ZodOptional<z.ZodString>;
73
73
  created: z.ZodOptional<z.ZodString>;
74
74
  hardware_id: z.ZodOptional<z.ZodString>;
75
75
  latest_temps: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
76
- device_log: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
77
- last_templog: z.ZodOptional<z.ZodString>;
76
+ device_log: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
77
+ last_templog: z.ZodOptional<z.ZodNullable<z.ZodString>>;
78
78
  }, z.core.$loose>;
79
79
  export declare const devicesResponseSchema: z.ZodArray<z.ZodObject<{
80
80
  uuid: z.ZodString;
@@ -120,23 +120,23 @@ export declare const devicesResponseSchema: z.ZodArray<z.ZodObject<{
120
120
  created: z.ZodString;
121
121
  }, z.core.$loose>>>;
122
122
  active: z.ZodOptional<z.ZodBoolean>;
123
- last_battery_reading: z.ZodOptional<z.ZodNumber>;
123
+ last_battery_reading: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
124
124
  model_name: z.ZodOptional<z.ZodString>;
125
125
  created: z.ZodOptional<z.ZodString>;
126
126
  hardware_id: z.ZodOptional<z.ZodString>;
127
127
  latest_temps: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
128
- device_log: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
129
- last_templog: z.ZodOptional<z.ZodString>;
128
+ device_log: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
129
+ last_templog: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
130
  }, z.core.$loose>>;
131
131
  export declare const deviceTemperaturesResponseSchema: z.ZodJSONSchema;
132
- export declare const driveLogResponseSchema: z.ZodObject<{
132
+ export declare const driveLogResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
133
133
  driveper: z.ZodNumber;
134
134
  setpoint: z.ZodNumber;
135
135
  modetype: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
136
136
  tiedchannel: z.ZodNumber;
137
137
  degreetype: z.ZodNumber;
138
138
  created: z.ZodString;
139
- }, z.core.$loose>;
139
+ }, z.core.$loose>, z.ZodObject<{}, z.core.$strict>]>;
140
140
  declare const sessionSummarySchema: z.ZodObject<{
141
141
  id: z.ZodNumber;
142
142
  title: z.ZodString;
@@ -207,5 +207,6 @@ export type RawSessionSummary = z.infer<typeof sessionSummarySchema>;
207
207
  export type RawSessionDetail = z.infer<typeof sessionDetailSchema>;
208
208
  export type RawChartChannel = z.infer<typeof chartChannelSchema>;
209
209
  export type RawDriveLog = z.infer<typeof driveLogSchema>;
210
+ export type RawDriveLogResponse = z.infer<typeof driveLogResponseSchema>;
210
211
  export type RawDeviceTemperatures = z.infer<typeof deviceTemperaturesResponseSchema>;
211
212
  export {};
package/dist/schemas.js CHANGED
@@ -96,13 +96,13 @@ export const deviceSchema = z.looseObject({
96
96
  channels: z.array(deviceChannelSchema),
97
97
  last_drivelog: driveLogSchema.nullable().optional(),
98
98
  active: z.boolean().optional(),
99
- last_battery_reading: z.number().optional(),
99
+ last_battery_reading: z.number().nullable().optional(),
100
100
  model_name: z.string().optional(),
101
101
  created: z.string().optional(),
102
102
  hardware_id: z.string().optional(),
103
103
  latest_temps: z.array(z.unknown()).optional(),
104
- device_log: z.record(z.string(), z.unknown()).optional(),
105
- last_templog: z.string().optional(),
104
+ device_log: z.record(z.string(), z.unknown()).nullable().optional(),
105
+ last_templog: z.string().nullable().optional(),
106
106
  });
107
107
  // GET /api/v1/devices.json
108
108
  // Returns all devices associated with the authenticated account.
@@ -132,8 +132,9 @@ export const deviceTemperaturesResponseSchema = z.json();
132
132
  // GET /api/v1/devices/{uuid}/drivelog.json
133
133
  // Official docs: returns the latest Drive log if it is less than one minute old.
134
134
  // A single-object response and the fields above are based on observed responses;
135
- // the official page does not define the body when no recent Drive log exists.
136
- export const driveLogResponseSchema = driveLogSchema;
135
+ // Observed with Drive disconnected or the device offline: HTTP 200 with {}.
136
+ // Keep an empty response unchanged, and reject incomplete non-empty logs.
137
+ export const driveLogResponseSchema = z.union([driveLogSchema, z.strictObject({})]);
137
138
  // Represents a session summary as returned in the sessions list.
138
139
  // Used as an element in GET /api/v1/sessions.json.
139
140
  //
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { RawDevice } from "./schemas.js";
1
2
  export interface RequestOptions {
2
3
  fetch?: typeof globalThis.fetch;
3
4
  timeoutMs?: number;
@@ -6,6 +7,7 @@ export interface RequestOptions {
6
7
  export interface FireboardClientOptions extends RequestOptions {
7
8
  token: string;
8
9
  baseUrl?: string;
10
+ deviceCacheTtlMs?: number;
9
11
  }
10
12
  export interface LoginOptions extends RequestOptions {
11
13
  loginUrl?: string;
@@ -17,3 +19,8 @@ export type DriveSetpointOptions = {
17
19
  setpoint?: number;
18
20
  channel: number;
19
21
  };
22
+ export interface DeviceListResult {
23
+ data: RawDevice[];
24
+ fromCache: boolean;
25
+ cacheAgeSeconds: number;
26
+ }
@@ -1,4 +1,4 @@
1
1
  export declare function validateEndpoint(value: string): string;
2
- export declare function validateTiming(value: number, name: string): number;
2
+ export declare function validateTiming(value: number, name: string, allowZero?: boolean): number;
3
3
  export declare function encodeDeviceId(uuid: string): string;
4
4
  export declare function validateSessionId(id: number): number;
@@ -9,8 +9,8 @@ export function validateEndpoint(value) {
9
9
  }
10
10
  return url.href;
11
11
  }
12
- export function validateTiming(value, name) {
13
- if (!Number.isFinite(value) || value < 1 || value > 2_147_483_647) {
12
+ export function validateTiming(value, name, allowZero = false) {
13
+ if (!Number.isFinite(value) || value < (allowZero ? 0 : 1) || value > 2_147_483_647) {
14
14
  throw new TypeError(`Invalid ${name}`);
15
15
  }
16
16
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fireboard-api-client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Unofficial TypeScript client for the Fireboard temperature monitoring API",
5
5
  "keywords": [
6
6
  "api-client",