doover-js 0.5.2 → 0.5.4

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.
@@ -30,6 +30,14 @@ export declare class AgentsApi {
30
30
  private readonly rest;
31
31
  constructor(rest: RestClient);
32
32
  getMultiAgentMessages(channelName: string, params: MultiAgentMessagesParams): Promise<BatchMessagesResponse>;
33
+ /**
34
+ * Batch-fetch aggregates for many agents. Auto-chunks `agent_id` so each
35
+ * request's URL stays under CloudFront's hard 8,192-byte URL quota — each
36
+ * agent contributes ~30 chars (`agent_id=<snowflake>&`), so the chunk size
37
+ * below caps the query string at ~7.5KB even before the base URL. Chunks
38
+ * are fetched in parallel and merged into one `{ results, count }` so
39
+ * callers never have to think about the limit.
40
+ */
33
41
  getMultiAgentAggregates(channelName: string, params: MultiAgentAggregatesParams): Promise<{
34
42
  results: AgentAggregate[];
35
43
  count: number;
@@ -13,8 +13,37 @@ class AgentsApi {
13
13
  results: response.results.map((message) => "timestamp" in message ? message : (0, snowflake_1.addTimestampToMessage)(message)),
14
14
  };
15
15
  }
16
- getMultiAgentAggregates(channelName, params) {
17
- return this.rest.get(`/agents/channels/${channelName}/aggregates`, params);
16
+ /**
17
+ * Batch-fetch aggregates for many agents. Auto-chunks `agent_id` so each
18
+ * request's URL stays under CloudFront's hard 8,192-byte URL quota — each
19
+ * agent contributes ~30 chars (`agent_id=<snowflake>&`), so the chunk size
20
+ * below caps the query string at ~7.5KB even before the base URL. Chunks
21
+ * are fetched in parallel and merged into one `{ results, count }` so
22
+ * callers never have to think about the limit.
23
+ */
24
+ async getMultiAgentAggregates(channelName, params) {
25
+ const path = `/agents/channels/${channelName}/aggregates`;
26
+ const { agent_id, ...rest } = params;
27
+ if (agent_id.length <= MULTI_AGENT_CHUNK_SIZE) {
28
+ return this.rest.get(path, params);
29
+ }
30
+ const chunks = [];
31
+ for (let i = 0; i < agent_id.length; i += MULTI_AGENT_CHUNK_SIZE) {
32
+ chunks.push(agent_id.slice(i, i + MULTI_AGENT_CHUNK_SIZE));
33
+ }
34
+ const responses = await Promise.all(chunks.map((chunk) => this.rest.get(path, {
35
+ ...rest,
36
+ agent_id: chunk,
37
+ })));
38
+ return {
39
+ results: responses.flatMap((r) => r.results),
40
+ count: responses.reduce((acc, r) => acc + r.count, 0),
41
+ };
18
42
  }
19
43
  }
20
44
  exports.AgentsApi = AgentsApi;
45
+ // CloudFront fronts the channels-rest API with a hard 8,192-byte URL quota.
46
+ // Each agent adds ~30 chars (`agent_id=<19-digit snowflake>&`), so 250 IDs
47
+ // is ~7.5KB of query string — comfortably under the cap with room for the
48
+ // base URL and any `field_name` params.
49
+ const MULTI_AGENT_CHUNK_SIZE = 250;
@@ -16,7 +16,12 @@ export interface UseChannelAggregateResult<TData> extends Omit<UseQueryResult<Ag
16
16
  data: TData | undefined;
17
17
  /** Attachments (files/blobs) on the aggregate. */
18
18
  attachments: Aggregate["attachments"] | undefined;
19
- /** Server timestamp (epoch seconds) of the last aggregate update. */
19
+ /**
20
+ * Server timestamp (epoch **milliseconds**) of the last aggregate update —
21
+ * matches the `Date` / `dayjs()` constructor. Use `dayjs(last_updated)`, not
22
+ * `dayjs.unix(last_updated)` (the latter assumes seconds and lands ~58000
23
+ * years in the future).
24
+ */
20
25
  last_updated: number | null | undefined;
21
26
  }
22
27
  export interface UseChannelAggregateOptions {
@@ -61,6 +61,27 @@ function setupRest(responseFactory) {
61
61
  (0, chai_1.expect)(fetchMock.getCall(0).args[0]).to.equal("https://api.example.com/agents/channels/c1/messages?agent_id=a1&agent_id=a2&limit=5");
62
62
  (0, chai_1.expect)(fetchMock.getCall(1).args[0]).to.equal("https://api.example.com/agents/channels/c1/aggregates?agent_id=a1");
63
63
  });
64
+ (0, mocha_1.it)("chunks getMultiAgentAggregates over the per-request agent cap", async () => {
65
+ const { rest, fetchMock } = setupRest((url) => {
66
+ const ids = [...new URL(url).searchParams.getAll("agent_id")];
67
+ return (0, helpers_1.createJsonResponse)({
68
+ results: ids.map((id) => ({ agent_id: id, data: {}, attachments: [] })),
69
+ count: ids.length,
70
+ });
71
+ });
72
+ const api = new agents_api_1.AgentsApi(rest);
73
+ // 600 agents → 3 chunks of 250/250/100.
74
+ const agentIds = Array.from({ length: 600 }, (_, i) => `a${i}`);
75
+ const aggregates = await api.getMultiAgentAggregates("c1", {
76
+ agent_id: agentIds,
77
+ });
78
+ (0, chai_1.expect)(fetchMock.callCount).to.equal(3);
79
+ (0, chai_1.expect)(aggregates.results).to.have.length(600);
80
+ (0, chai_1.expect)(aggregates.count).to.equal(600);
81
+ // Every agent is represented exactly once across the merged chunks.
82
+ const returnedIds = aggregates.results.map((r) => r.agent_id).sort();
83
+ (0, chai_1.expect)(returnedIds).to.deep.equal([...agentIds].sort());
84
+ });
64
85
  (0, mocha_1.it)("covers channels methods", async () => {
65
86
  const { rest, fetchMock } = setupRest(() => (0, helpers_1.createJsonResponse)([]));
66
87
  const api = new channels_api_1.ChannelsApi(rest);
@@ -51,9 +51,10 @@ export interface Alarm {
51
51
  state: AlarmState;
52
52
  expiry_mins: number | null;
53
53
  entered_state_ts: number;
54
+ alarm_pending_ms: number | null;
54
55
  }
55
56
  export type AlarmOperator = "eq" | "ge" | "gt" | "le" | "lt";
56
- export type AlarmState = "NoData" | "OK" | "Alarm";
57
+ export type AlarmState = "NoData" | "OK" | "Alarm" | "AlarmPending";
57
58
  export interface ConnectionSubscription {
58
59
  channel: ChannelRef;
59
60
  subscribed_at: number;
@@ -103,6 +103,7 @@ export interface CreateAlarmRequest {
103
103
  operator: Alarm["operator"];
104
104
  value: unknown;
105
105
  expiry_mins?: number | null;
106
+ alarm_pending_ms?: number | null;
106
107
  }
107
108
  export interface PatchAlarmRequest extends Partial<CreateAlarmRequest> {
108
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doover-js",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "TypeScript client for Doover.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",