@go-avro/avro-js 0.0.36 → 0.0.38

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.
Files changed (59) hide show
  1. package/dist/auth/AuthManager.d.ts +2 -2
  2. package/dist/auth/AuthManager.js +37 -32
  3. package/dist/auth/storage.d.ts +1 -1
  4. package/dist/auth/storage.js +6 -6
  5. package/dist/client/AvroQueryClientProvider.js +1 -1
  6. package/dist/client/QueryClient.d.ts +35 -17
  7. package/dist/client/QueryClient.js +486 -384
  8. package/dist/client/core/fetch.js +16 -11
  9. package/dist/client/core/utils.js +5 -5
  10. package/dist/client/core/xhr.js +28 -23
  11. package/dist/client/hooks/analytics.js +14 -14
  12. package/dist/client/hooks/avro.js +2 -2
  13. package/dist/client/hooks/bills.js +66 -30
  14. package/dist/client/hooks/catalog_items.js +57 -22
  15. package/dist/client/hooks/chats.js +4 -4
  16. package/dist/client/hooks/companies.js +96 -39
  17. package/dist/client/hooks/email.js +1 -1
  18. package/dist/client/hooks/events.js +174 -63
  19. package/dist/client/hooks/groups.js +37 -22
  20. package/dist/client/hooks/jobs.js +69 -18
  21. package/dist/client/hooks/labels.js +36 -21
  22. package/dist/client/hooks/messages.js +9 -6
  23. package/dist/client/hooks/months.js +42 -22
  24. package/dist/client/hooks/plans.js +2 -2
  25. package/dist/client/hooks/prepayments.js +42 -22
  26. package/dist/client/hooks/proposal.js +21 -5
  27. package/dist/client/hooks/root.js +4 -4
  28. package/dist/client/hooks/routes.js +77 -32
  29. package/dist/client/hooks/sessions.js +66 -34
  30. package/dist/client/hooks/skills.js +33 -18
  31. package/dist/client/hooks/teams.js +36 -21
  32. package/dist/client/hooks/timecards.js +6 -0
  33. package/dist/client/hooks/users.js +61 -29
  34. package/dist/client/hooks/waivers.js +41 -19
  35. package/dist/index.d.ts +38 -38
  36. package/dist/index.js +37 -37
  37. package/dist/types/api/Bill.d.ts +1 -1
  38. package/dist/types/api/Bill.js +1 -1
  39. package/dist/types/api/Job.d.ts +1 -1
  40. package/dist/types/api/Job.js +14 -14
  41. package/dist/types/api/LineItem.d.ts +3 -3
  42. package/dist/types/api/LineItem.js +5 -2
  43. package/dist/types/api/PaymentType.d.ts +1 -1
  44. package/dist/types/api/Prepayment.d.ts +1 -1
  45. package/dist/types/api/Route.d.ts +3 -3
  46. package/dist/types/api/Route.js +4 -2
  47. package/dist/types/api/RouteJob.d.ts +1 -1
  48. package/dist/types/api/Task.d.ts +2 -2
  49. package/dist/types/api/Task.js +12 -7
  50. package/dist/types/api/Timecard.d.ts +1 -1
  51. package/dist/types/api/TimecardAction.d.ts +1 -1
  52. package/dist/types/api/UserCompanyAssociation.d.ts +2 -2
  53. package/dist/types/api/UserCompanyAssociation.js +1 -1
  54. package/dist/types/api/_Event.d.ts +1 -1
  55. package/dist/types/api/_Event.js +1 -1
  56. package/dist/types/api.d.ts +1 -1
  57. package/dist/types/auth.d.ts +1 -1
  58. package/dist/types/client.d.ts +1 -1
  59. package/package.json +3 -2
@@ -1,11 +1,11 @@
1
- import io from 'socket.io-client';
2
- import { useMutation, useQueryClient } from '@tanstack/react-query';
1
+ import io from "socket.io-client";
2
+ import { useMutation, useQueryClient, } from "@tanstack/react-query";
3
3
  import { v4 as uuidv4 } from "uuid";
4
- import { LoginResponse } from '../types/api';
5
- import { AuthState } from '../types/auth';
6
- import { StandardError } from '../types/error';
4
+ import { Job, LoginResponse, } from "../types/api";
5
+ import { AuthState } from "../types/auth";
6
+ import { StandardError } from "../types/error";
7
7
  function isBulkEvent(c) {
8
- return 'invalidateKeys' in c;
8
+ return "invalidateKeys" in c;
9
9
  }
10
10
  /**
11
11
  * Maps socket event names to cache-update strategies.
@@ -18,75 +18,185 @@ function isBulkEvent(c) {
18
18
  */
19
19
  const SOCKET_EVENT_CONFIG = {
20
20
  // ── Company ──
21
- create_company: { entityKey: 'companies', action: 'create', fetchPath: (id) => `/company/${id}` },
22
- update_company: { entityKey: 'companies', action: 'update', fetchPath: (id) => `/company/${id}` },
23
- delete_company: { entityKey: 'companies', action: 'delete', fetchPath: null },
21
+ create_company: {
22
+ entityKey: "companies",
23
+ action: "create",
24
+ fetchPath: (id) => `/company/${id}`,
25
+ },
26
+ update_company: {
27
+ entityKey: "companies",
28
+ action: "update",
29
+ fetchPath: (id) => `/company/${id}`,
30
+ },
31
+ delete_company: { entityKey: "companies", action: "delete", fetchPath: null },
24
32
  // ── Users (no single-entity socket events) ──
25
- user_updated: { invalidateKeys: [['users'], ['user']] },
26
- update_users: { invalidateKeys: [['users'], ['user']] },
33
+ user_updated: { invalidateKeys: [["users"], ["user"]] },
34
+ update_users: { invalidateKeys: [["users"], ["user"]] },
27
35
  // ── Jobs ──
28
- create_job: { entityKey: 'jobs', action: 'create', fetchPath: (id) => `/job/${id}` },
29
- update_job: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}` },
30
- delete_job: { entityKey: 'jobs', action: 'delete', fetchPath: null },
31
- update_jobs: { invalidateKeys: [['jobs'], ['infinite', 'jobs']] },
32
- delete_jobs: { invalidateKeys: [['jobs'], ['infinite', 'jobs']] },
36
+ create_job: {
37
+ entityKey: "jobs",
38
+ action: "create",
39
+ fetchPath: (id) => `/job/${id}`,
40
+ construct: (d) => new Job(d),
41
+ },
42
+ update_job: {
43
+ entityKey: "jobs",
44
+ action: "update",
45
+ fetchPath: (id) => `/job/${id}`,
46
+ construct: (d) => new Job(d),
47
+ },
48
+ delete_job: { entityKey: "jobs", action: "delete", fetchPath: null },
49
+ update_jobs: { invalidateKeys: [["jobs"], ["infinite", "jobs"]] },
50
+ delete_jobs: { invalidateKeys: [["jobs"], ["infinite", "jobs"]] },
33
51
  // ── Routes ──
34
- create_route: { entityKey: 'routes', action: 'create', fetchPath: (id) => `/route/${id}` },
35
- update_route: { entityKey: 'routes', action: 'update', fetchPath: (id) => `/route/${id}` },
36
- delete_route: { entityKey: 'routes', action: 'delete', fetchPath: null },
52
+ create_route: {
53
+ entityKey: "routes",
54
+ action: "create",
55
+ fetchPath: (id) => `/route/${id}`,
56
+ },
57
+ update_route: {
58
+ entityKey: "routes",
59
+ action: "update",
60
+ fetchPath: (id) => `/route/${id}`,
61
+ },
62
+ delete_route: { entityKey: "routes", action: "delete", fetchPath: null },
37
63
  // ── Events (also refetch parent job — overdueness, last_event, etc.) ──
38
- create_event: { entityKey: 'events', action: 'create', fetchPath: (id) => `/event/${id}`, relatedRefetch: [{ entityKey: 'jobs', idField: 'job_id', fetchPath: (id) => `/job/${id}` }] },
39
- update_event: { entityKey: 'events', action: 'update', fetchPath: (id) => `/event/${id}`, relatedRefetch: [{ entityKey: 'jobs', idField: 'job_id', fetchPath: (id) => `/job/${id}` }] },
40
- delete_event: { entityKey: 'events', action: 'delete', fetchPath: null, relatedRefetch: [{ entityKey: 'jobs', idField: 'job_id', fetchPath: (id) => `/job/${id}` }] },
41
- update_events: { invalidateKeys: [['events']] },
64
+ create_event: {
65
+ entityKey: "events",
66
+ action: "create",
67
+ fetchPath: (id) => `/event/${id}`,
68
+ relatedRefetch: [
69
+ {
70
+ entityKey: "jobs",
71
+ idField: "job_id",
72
+ fetchPath: (id) => `/job/${id}`,
73
+ construct: (d) => new Job(d),
74
+ },
75
+ ],
76
+ },
77
+ update_event: {
78
+ entityKey: "events",
79
+ action: "update",
80
+ fetchPath: (id) => `/event/${id}`,
81
+ relatedRefetch: [
82
+ {
83
+ entityKey: "jobs",
84
+ idField: "job_id",
85
+ fetchPath: (id) => `/job/${id}`,
86
+ construct: (d) => new Job(d),
87
+ },
88
+ ],
89
+ },
90
+ delete_event: {
91
+ entityKey: "events",
92
+ action: "delete",
93
+ fetchPath: null,
94
+ relatedRefetch: [
95
+ {
96
+ entityKey: "jobs",
97
+ idField: "job_id",
98
+ fetchPath: (id) => `/job/${id}`,
99
+ construct: (d) => new Job(d),
100
+ },
101
+ ],
102
+ },
103
+ update_events: { invalidateKeys: [["events"]] },
42
104
  // ── Teams ──
43
- create_team: { entityKey: 'teams', action: 'create', fetchPath: null },
44
- update_team: { entityKey: 'teams', action: 'update', fetchPath: null },
45
- delete_team: { entityKey: 'teams', action: 'delete', fetchPath: null },
46
- update_teams: { invalidateKeys: [['teams']] },
105
+ create_team: { entityKey: "teams", action: "create", fetchPath: null },
106
+ update_team: { entityKey: "teams", action: "update", fetchPath: null },
107
+ delete_team: { entityKey: "teams", action: "delete", fetchPath: null },
108
+ update_teams: { invalidateKeys: [["teams"]] },
47
109
  // ── Bills ──
48
- create_bill: { entityKey: 'bills', action: 'create', fetchPath: (id) => `/bill/${id}` },
49
- delete_bill: { entityKey: 'bills', action: 'delete', fetchPath: null },
50
- update_bills: { invalidateKeys: [['bills']] },
110
+ create_bill: {
111
+ entityKey: "bills",
112
+ action: "create",
113
+ fetchPath: (id) => `/bill/${id}`,
114
+ },
115
+ delete_bill: { entityKey: "bills", action: "delete", fetchPath: null },
116
+ update_bills: { invalidateKeys: [["bills"]] },
51
117
  // ── Sessions ──
52
- create_session: { entityKey: 'sessions', action: 'create', fetchPath: null },
53
- update_session: { entityKey: 'sessions', action: 'update', fetchPath: null },
118
+ create_session: { entityKey: "sessions", action: "create", fetchPath: null },
119
+ update_session: { entityKey: "sessions", action: "update", fetchPath: null },
54
120
  // ── Catalog Items ──
55
- create_catalog_item: { entityKey: 'catalog_items', action: 'create', fetchPath: (id) => `/catalog_item/${id}` },
56
- update_catalog_item: { entityKey: 'catalog_items', action: 'update', fetchPath: (id) => `/catalog_item/${id}` },
57
- delete_catalog_item: { entityKey: 'catalog_items', action: 'delete', fetchPath: null },
121
+ create_catalog_item: {
122
+ entityKey: "catalog_items",
123
+ action: "create",
124
+ fetchPath: (id) => `/catalog_item/${id}`,
125
+ },
126
+ update_catalog_item: {
127
+ entityKey: "catalog_items",
128
+ action: "update",
129
+ fetchPath: (id) => `/catalog_item/${id}`,
130
+ },
131
+ delete_catalog_item: {
132
+ entityKey: "catalog_items",
133
+ action: "delete",
134
+ fetchPath: null,
135
+ },
58
136
  // ── Groups ──
59
- create_group: { entityKey: 'groups', action: 'create', fetchPath: null },
60
- update_group: { entityKey: 'groups', action: 'update', fetchPath: null },
61
- delete_group: { entityKey: 'groups', action: 'delete', fetchPath: null },
137
+ create_group: { entityKey: "groups", action: "create", fetchPath: null },
138
+ update_group: { entityKey: "groups", action: "update", fetchPath: null },
139
+ delete_group: { entityKey: "groups", action: "delete", fetchPath: null },
62
140
  // ── Labels ──
63
- create_label: { entityKey: 'labels', action: 'create', fetchPath: null },
64
- update_label: { entityKey: 'labels', action: 'update', fetchPath: null },
65
- delete_label: { entityKey: 'labels', action: 'delete', fetchPath: null },
141
+ create_label: { entityKey: "labels", action: "create", fetchPath: null },
142
+ update_label: { entityKey: "labels", action: "update", fetchPath: null },
143
+ delete_label: { entityKey: "labels", action: "delete", fetchPath: null },
66
144
  // ── Skills ──
67
- create_skill: { entityKey: 'skills', action: 'create', fetchPath: null },
68
- update_skill: { entityKey: 'skills', action: 'update', fetchPath: null },
69
- delete_skill: { entityKey: 'skills', action: 'delete', fetchPath: null },
145
+ create_skill: { entityKey: "skills", action: "create", fetchPath: null },
146
+ update_skill: { entityKey: "skills", action: "update", fetchPath: null },
147
+ delete_skill: { entityKey: "skills", action: "delete", fetchPath: null },
70
148
  // ── Proposals ──
71
- create_proposal: { entityKey: 'proposals', action: 'create', fetchPath: (id) => `/proposal/${id}` },
72
- update_proposal: { entityKey: 'proposals', action: 'update', fetchPath: (id) => `/proposal/${id}` },
73
- delete_proposal: { entityKey: 'proposals', action: 'delete', fetchPath: null },
149
+ create_proposal: {
150
+ entityKey: "proposals",
151
+ action: "create",
152
+ fetchPath: (id) => `/proposal/${id}`,
153
+ },
154
+ update_proposal: {
155
+ entityKey: "proposals",
156
+ action: "update",
157
+ fetchPath: (id) => `/proposal/${id}`,
158
+ },
159
+ delete_proposal: {
160
+ entityKey: "proposals",
161
+ action: "delete",
162
+ fetchPath: null,
163
+ },
74
164
  // ── Service Months ──
75
- create_month: { entityKey: 'months', action: 'create', fetchPath: null },
76
- update_months: { invalidateKeys: [['months']] },
77
- delete_months: { invalidateKeys: [['months']] },
165
+ create_month: { entityKey: "months", action: "create", fetchPath: null },
166
+ update_months: { invalidateKeys: [["months"]] },
167
+ delete_months: { invalidateKeys: [["months"]] },
78
168
  // ── Tasks (nested under jobs — target the parent job) ──
79
- create_task: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, idField: 'job_id' },
80
- update_task: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, idField: 'job_id' },
81
- delete_task: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, idField: 'job_id' },
169
+ create_task: {
170
+ entityKey: "jobs",
171
+ action: "update",
172
+ fetchPath: (id) => `/job/${id}`,
173
+ idField: "job_id",
174
+ construct: (d) => new Job(d),
175
+ },
176
+ update_task: {
177
+ entityKey: "jobs",
178
+ action: "update",
179
+ fetchPath: (id) => `/job/${id}`,
180
+ idField: "job_id",
181
+ construct: (d) => new Job(d),
182
+ },
183
+ delete_task: {
184
+ entityKey: "jobs",
185
+ action: "update",
186
+ fetchPath: (id) => `/job/${id}`,
187
+ idField: "job_id",
188
+ construct: (d) => new Job(d),
189
+ },
82
190
  // ── Scheduling ──
83
- schedule_complete: { invalidateKeys: [['routes'], ['jobs'], ['infinite', 'jobs']] },
191
+ schedule_complete: {
192
+ invalidateKeys: [["routes"], ["jobs"], ["infinite", "jobs"]],
193
+ },
84
194
  // ── Location ──
85
- location_update: { invalidateKeys: [['teams']] },
195
+ location_update: { invalidateKeys: [["teams"]] },
86
196
  // ── Prepayments ──
87
- update_prepayments: { invalidateKeys: [['prepayments']] },
197
+ update_prepayments: { invalidateKeys: [["prepayments"]] },
88
198
  // ── Chats ──
89
- new_message: { invalidateKeys: [['chats'], ['messages']] },
199
+ new_message: { invalidateKeys: [["chats"], ["messages"]] },
90
200
  };
91
201
  /**
92
202
  * Returns true when `query.queryKey` belongs to the given entity,
@@ -94,7 +204,7 @@ const SOCKET_EVENT_CONFIG = {
94
204
  */
95
205
  function matchesEntityKey(query, entityKey) {
96
206
  const k = query.queryKey;
97
- return k[0] === entityKey || (k[0] === 'infinite' && k[1] === entityKey);
207
+ return k[0] === entityKey || (k[0] === "infinite" && k[1] === entityKey);
98
208
  }
99
209
  export class AvroQueryClient {
100
210
  constructor(config) {
@@ -110,39 +220,45 @@ export class AvroQueryClient {
110
220
  baseUrl: config.baseUrl,
111
221
  authManager: config.authManager,
112
222
  maxRetries: config.maxRetries ?? 3,
113
- retryStrategy: config.retryStrategy ?? 'fixed',
223
+ retryStrategy: config.retryStrategy ?? "fixed",
114
224
  timeout: config.timeout ?? 0,
115
225
  };
116
- this.socket = io(config.baseUrl, { autoConnect: false, transports: ["websocket"], });
117
- config.authManager.isAuthenticated().then(isAuth => {
226
+ this.socket = io(config.baseUrl, {
227
+ autoConnect: false,
228
+ transports: ["websocket"],
229
+ });
230
+ config.authManager.isAuthenticated().then((isAuth) => {
118
231
  this.setAuthState(isAuth);
119
- this.getCompanyId().then(id => {
232
+ this.getCompanyId().then((id) => {
120
233
  this.companyId = id;
121
234
  });
122
235
  if (!this.socket.connected && isAuth === AuthState.AUTHENTICATED) {
123
- this.config.authManager.accessToken().then(token => {
124
- console.log('Initializing socket connection with token:', token);
236
+ this.config.authManager
237
+ .accessToken()
238
+ .then((token) => {
239
+ console.log("Initializing socket connection with token:", token);
125
240
  this.socket.auth = { token: token };
126
241
  this.socket.connect();
127
- }).catch(err => {
128
- console.error('Not logged in:', err);
242
+ })
243
+ .catch((err) => {
244
+ console.error("Not logged in:", err);
129
245
  });
130
246
  }
131
247
  });
132
- this.socket.on('connect', () => {
248
+ this.socket.on("connect", () => {
133
249
  this.setAuthState(AuthState.AUTHENTICATED);
134
250
  console.log(`Socket connected with ID: ${this.socket?.id}`);
135
251
  });
136
- this.socket.on('disconnect', (reason) => {
252
+ this.socket.on("disconnect", (reason) => {
137
253
  console.log(`Socket disconnected: ${reason}`);
138
254
  });
139
- this.socket.on('connect_error', (err) => {
255
+ this.socket.on("connect_error", (err) => {
140
256
  console.error(`Socket connection error: ${err.message}`);
141
257
  });
142
258
  this.config.authManager.onTokenRefreshed((newAccessToken) => {
143
259
  if (this.socket && newAccessToken) {
144
260
  this.setAuthState(AuthState.AUTHENTICATED);
145
- console.log('Access token refreshed, updating socket auth...');
261
+ console.log("Access token refreshed, updating socket auth...");
146
262
  this.socket.auth = { token: newAccessToken };
147
263
  this.socket.disconnect().connect();
148
264
  }
@@ -157,7 +273,7 @@ export class AvroQueryClient {
157
273
  }
158
274
  emit(eventName, data) {
159
275
  if (!this.socket?.connected) {
160
- console.error('Socket is not connected. Cannot emit event.');
276
+ console.error("Socket is not connected. Cannot emit event.");
161
277
  return;
162
278
  }
163
279
  this.socket.emit(eventName, data);
@@ -191,7 +307,9 @@ export class AvroQueryClient {
191
307
  const client = this; // stable reference for async closures
192
308
  /** Full-invalidate every key that matches the entity (including 'infinite' prefix). */
193
309
  const invalidateEntity = (entityKey) => {
194
- queryClient.invalidateQueries({ predicate: (q) => matchesEntityKey(q, entityKey) });
310
+ queryClient.invalidateQueries({
311
+ predicate: (q) => matchesEntityKey(q, entityKey),
312
+ });
195
313
  };
196
314
  for (const [event, config] of Object.entries(SOCKET_EVENT_CONFIG)) {
197
315
  if (isBulkEvent(config)) {
@@ -206,155 +324,39 @@ export class AvroQueryClient {
206
324
  }
207
325
  else {
208
326
  // ── Targeted: surgical cache update ──────────────────
209
- const { entityKey, action, fetchPath, idField, alsoInvalidate, relatedRefetch } = config;
327
+ const { entityKey, action, fetchPath, idField, alsoInvalidate, relatedRefetch, } = config;
210
328
  const handler = async (data) => {
211
- const id = data?.[idField ?? 'id'];
329
+ const id = data?.[idField ?? "id"];
212
330
  // No id → old backend or malformed payload → full invalidation
213
- if (!id || typeof id !== 'string') {
331
+ if (!id || typeof id !== "string") {
214
332
  invalidateEntity(entityKey);
215
333
  alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
216
334
  return;
217
335
  }
218
- const entityPredicate = (q) => matchesEntityKey(q, entityKey);
219
336
  let fetchedItem;
220
- switch (action) {
221
- // ─── CREATE ─────────────────────────────────
222
- case 'create': {
223
- if (fetchPath) {
224
- try {
225
- const item = await queryClient.fetchQuery({
226
- queryKey: [entityKey, id],
227
- queryFn: () => client.get({ path: fetchPath(id) }),
228
- staleTime: 0,
229
- });
230
- fetchedItem = item;
231
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
232
- if (!old)
233
- return old;
234
- if (old.pages && Array.isArray(old.pages)) {
235
- // Skip if already present (avoids duplicates
236
- // when the creating user also gets the event)
237
- if (old.pages.some((p) => p.some((x) => x?.id === id)))
238
- return old;
239
- return {
240
- ...old,
241
- pages: [
242
- [item, ...(old.pages[0] || [])],
243
- ...old.pages.slice(1),
244
- ],
245
- };
246
- }
247
- if (Array.isArray(old)) {
248
- if (old.some((x) => x?.id === id))
249
- return old;
250
- return [...old, item];
251
- }
252
- return old;
253
- });
254
- }
255
- catch {
256
- invalidateEntity(entityKey);
257
- }
258
- }
259
- else {
260
- // No individual GET endpoint → invalidate lists
261
- invalidateEntity(entityKey);
262
- }
263
- break;
264
- }
265
- // ─── UPDATE ─────────────────────────────────
266
- case 'update': {
267
- if (fetchPath) {
268
- try {
269
- // Fetch fresh copy (also updates the [entity, id] cache)
270
- const item = await queryClient.fetchQuery({
271
- queryKey: [entityKey, id],
272
- queryFn: () => client.get({ path: fetchPath(id) }),
273
- staleTime: 0,
274
- });
275
- fetchedItem = item;
276
- // Patch it into every active list / infinite-query cache
277
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
278
- if (!old)
279
- return old;
280
- if (old.pages && Array.isArray(old.pages)) {
281
- return {
282
- ...old,
283
- pages: old.pages.map((page) => page.map((x) => (x?.id === id ? item : x))),
284
- };
285
- }
286
- if (Array.isArray(old)) {
287
- return old.map((x) => (x?.id === id ? item : x));
288
- }
289
- return old;
290
- });
291
- }
292
- catch {
293
- invalidateEntity(entityKey);
294
- }
295
- }
296
- else {
297
- // No individual GET → invalidate everything for this entity
298
- invalidateEntity(entityKey);
299
- }
300
- break;
301
- }
302
- // ─── DELETE ─────────────────────────────────
303
- case 'delete': {
304
- // Remove the individual-item cache entry
305
- queryClient.removeQueries({ queryKey: [entityKey, id], exact: true });
306
- // Remove from every active list / infinite-query cache
307
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
308
- if (!old)
309
- return old;
310
- if (old.pages && Array.isArray(old.pages)) {
311
- return {
312
- ...old,
313
- pages: old.pages.map((page) => page.filter((x) => x?.id !== id)),
314
- };
315
- }
316
- if (Array.isArray(old)) {
317
- return old.filter((x) => x?.id !== id);
318
- }
319
- return old;
320
- });
321
- break;
322
- }
323
- }
337
+ // ─── CREATE / UPDATE / DELETE via shared _syncEntity ───
338
+ fetchedItem = await client._syncEntity(queryClient, {
339
+ action,
340
+ entityKey,
341
+ id,
342
+ fetchPath: fetchPath ? fetchPath(id) : undefined,
343
+ construct: config.construct,
344
+ });
324
345
  // Invalidate any additional keys (e.g. companies → /company/list)
325
346
  alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
326
347
  // Refetch & patch related entities (e.g. event → parent job)
327
348
  if (relatedRefetch) {
328
349
  for (const related of relatedRefetch) {
329
350
  const relatedId = data?.[related.idField] ?? fetchedItem?.[related.idField];
330
- if (!relatedId || typeof relatedId !== 'string')
351
+ if (!relatedId || typeof relatedId !== "string")
331
352
  continue;
332
- try {
333
- const item = await queryClient.fetchQuery({
334
- queryKey: [related.entityKey, relatedId],
335
- queryFn: () => client.get({ path: related.fetchPath(relatedId) }),
336
- staleTime: 0,
337
- });
338
- const relatedPredicate = (q) => matchesEntityKey(q, related.entityKey);
339
- queryClient.setQueriesData({ predicate: relatedPredicate, type: 'active' }, (old) => {
340
- if (!old)
341
- return old;
342
- if (old.pages && Array.isArray(old.pages)) {
343
- return {
344
- ...old,
345
- pages: old.pages.map((page) => page.map((x) => (x?.id === relatedId ? item : x))),
346
- };
347
- }
348
- if (Array.isArray(old)) {
349
- return old.map((x) => (x?.id === relatedId ? item : x));
350
- }
351
- return old;
352
- });
353
- }
354
- catch {
355
- // Fetch failed → invalidate related entity lists
356
- invalidateEntity(related.entityKey);
357
- }
353
+ await client._syncEntity(queryClient, {
354
+ action: "update",
355
+ entityKey: related.entityKey,
356
+ id: relatedId,
357
+ fetchPath: related.fetchPath(relatedId),
358
+ construct: related.construct,
359
+ });
358
360
  }
359
361
  }
360
362
  };
@@ -365,11 +367,11 @@ export class AvroQueryClient {
365
367
  // Auto join/leave company room
366
368
  const joinCompanyRoom = () => {
367
369
  if (this.companyId) {
368
- this.socket.emit('join_company', { company_id: this.companyId });
370
+ this.socket.emit("join_company", { company_id: this.companyId });
369
371
  }
370
372
  };
371
- this.socket.on('connect', joinCompanyRoom);
372
- handlers.push({ event: 'connect', handler: joinCompanyRoom });
373
+ this.socket.on("connect", joinCompanyRoom);
374
+ handlers.push({ event: "connect", handler: joinCompanyRoom });
373
375
  // If already connected, join immediately
374
376
  if (this.socket.connected && this.companyId) {
375
377
  joinCompanyRoom();
@@ -380,7 +382,7 @@ export class AvroQueryClient {
380
382
  }
381
383
  // Leave company room on teardown
382
384
  if (this.socket.connected && this.companyId) {
383
- this.socket.emit('leave_company', { company_id: this.companyId });
385
+ this.socket.emit("leave_company", { company_id: this.companyId });
384
386
  }
385
387
  this._queryClient = null;
386
388
  };
@@ -392,17 +394,17 @@ export class AvroQueryClient {
392
394
  this._socketInvalidationCleanup?.();
393
395
  this._socketInvalidationCleanup = null;
394
396
  }
395
- get({ path, cancelToken, headers, progressUpdateCallback }) {
396
- return this._xhr('GET', path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
397
+ get({ path, cancelToken, headers, progressUpdateCallback, }) {
398
+ return this._xhr("GET", path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
397
399
  }
398
- post({ path, data, cancelToken, headers, progressUpdateCallback }) {
399
- return this._xhr('POST', path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
400
+ post({ path, data, cancelToken, headers, progressUpdateCallback, }) {
401
+ return this._xhr("POST", path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
400
402
  }
401
- put({ path, data, cancelToken, headers, progressUpdateCallback }) {
402
- return this._xhr('PUT', path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
403
+ put({ path, data, cancelToken, headers, progressUpdateCallback, }) {
404
+ return this._xhr("PUT", path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
403
405
  }
404
- delete({ path, cancelToken, headers, progressUpdateCallback }) {
405
- return this._xhr('DELETE', path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
406
+ delete({ path, cancelToken, headers, progressUpdateCallback, }) {
407
+ return this._xhr("DELETE", path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
406
408
  }
407
409
  loginSuccess(tokens) {
408
410
  this.setAuthState(AuthState.AUTHENTICATED);
@@ -415,20 +417,23 @@ export class AvroQueryClient {
415
417
  useLogin() {
416
418
  const queryClient = this.getQueryClient();
417
419
  return useMutation({
418
- mutationFn: async ({ username, password, code, cancelToken }) => {
420
+ mutationFn: async ({ username, password, code, cancelToken, }) => {
419
421
  const resp = await this.post({
420
- path: '/login',
422
+ path: "/login",
421
423
  data: JSON.stringify({ username, password, code }),
422
424
  cancelToken,
423
- headers: { 'Content-Type': 'application/json' }
425
+ headers: { "Content-Type": "application/json" },
424
426
  });
425
- if (!resp || !('access_token' in resp)) {
427
+ if (!resp || !("access_token" in resp)) {
426
428
  if (resp.msg === "TOTP required") {
427
429
  return LoginResponse.NEEDS_TOTP;
428
430
  }
429
- throw new StandardError(401, 'Invalid login response');
431
+ throw new StandardError(401, "Invalid login response");
430
432
  }
431
- await this.loginSuccess({ access_token: resp.access_token, refresh_token: resp.refresh_token });
433
+ await this.loginSuccess({
434
+ access_token: resp.access_token,
435
+ refresh_token: resp.refresh_token,
436
+ });
432
437
  return LoginResponse.SUCCESS;
433
438
  },
434
439
  onSettled: () => {
@@ -436,19 +441,19 @@ export class AvroQueryClient {
436
441
  },
437
442
  onError: (err) => {
438
443
  this.config.authManager.clearCache();
439
- throw new StandardError(401, err.message || 'Login failed');
440
- }
444
+ throw new StandardError(401, err.message || "Login failed");
445
+ },
441
446
  });
442
447
  }
443
448
  useRequestCode() {
444
449
  const queryClient = this.getQueryClient();
445
450
  return useMutation({
446
- mutationFn: async ({ username, email, cancelToken }) => {
451
+ mutationFn: async ({ username, email, cancelToken, }) => {
447
452
  const resp = await this.post({
448
- path: '/code',
453
+ path: "/code",
449
454
  data: JSON.stringify({ username, email }),
450
455
  cancelToken,
451
- headers: { 'Content-Type': 'application/json' }
456
+ headers: { "Content-Type": "application/json" },
452
457
  });
453
458
  return resp;
454
459
  },
@@ -456,46 +461,49 @@ export class AvroQueryClient {
456
461
  queryClient.invalidateQueries();
457
462
  },
458
463
  onError: (err) => {
459
- throw new StandardError(err.status, err.message || 'Request code failed');
460
- }
464
+ throw new StandardError(err.status, err.message || "Request code failed");
465
+ },
461
466
  });
462
467
  }
463
468
  useUpdatePassword() {
464
469
  const queryClient = this.getQueryClient();
465
470
  return useMutation({
466
- mutationFn: async ({ username, email, code, newPassword, cancelToken }) => {
471
+ mutationFn: async ({ username, email, code, newPassword, cancelToken, }) => {
467
472
  await this.post({
468
473
  path: `/user/${username ?? email}/password`,
469
474
  data: JSON.stringify({ code, password: newPassword }),
470
475
  cancelToken,
471
- headers: { 'Content-Type': 'application/json' }
476
+ headers: { "Content-Type": "application/json" },
472
477
  });
473
478
  },
474
479
  onSettled: () => {
475
480
  queryClient.invalidateQueries();
476
481
  },
477
482
  onError: (err) => {
478
- throw new StandardError(err.status, err.message || 'Update password failed');
479
- }
483
+ throw new StandardError(err.status, err.message || "Update password failed");
484
+ },
480
485
  });
481
486
  }
482
487
  useGoogleLogin() {
483
488
  const queryClient = this.getQueryClient();
484
489
  return useMutation({
485
- mutationFn: async ({ token, cancelToken }) => {
486
- const resp = await this._xhr('POST', `/google/authorize?token=${token}`, {}, cancelToken, { 'Content-Type': 'application/json' });
487
- if (!resp || !('access_token' in resp)) {
490
+ mutationFn: async ({ token, cancelToken, }) => {
491
+ const resp = await this._xhr("POST", `/google/authorize?token=${token}`, {}, cancelToken, { "Content-Type": "application/json" });
492
+ if (!resp || !("access_token" in resp)) {
488
493
  if (resp.msg === "TOTP required") {
489
494
  return LoginResponse.NEEDS_TOTP;
490
495
  }
491
- throw new StandardError(401, 'Invalid Google login response');
496
+ throw new StandardError(401, "Invalid Google login response");
492
497
  }
493
498
  this.setAuthState(AuthState.AUTHENTICATED);
494
499
  this.socket.auth = { token: resp.access_token };
495
500
  if (!this.socket.connected) {
496
501
  this.socket.connect();
497
502
  }
498
- await this.config.authManager.setTokens({ access_token: resp.access_token, refresh_token: resp.refresh_token });
503
+ await this.config.authManager.setTokens({
504
+ access_token: resp.access_token,
505
+ refresh_token: resp.refresh_token,
506
+ });
499
507
  return LoginResponse.SUCCESS;
500
508
  },
501
509
  onSettled: () => {
@@ -503,27 +511,30 @@ export class AvroQueryClient {
503
511
  },
504
512
  onError: (err) => {
505
513
  this.config.authManager.clearCache();
506
- throw new StandardError(err.status, err.message || 'Google Login failed');
507
- }
514
+ throw new StandardError(err.status, err.message || "Google Login failed");
515
+ },
508
516
  });
509
517
  }
510
518
  useAppleLogin() {
511
519
  const queryClient = this.getQueryClient();
512
520
  return useMutation({
513
- mutationFn: async ({ token, cancelToken }) => {
514
- const resp = await this._xhr('POST', `/apple/authorize?token=${encodeURIComponent(token)}`, {}, cancelToken, { 'Content-Type': 'application/json' });
515
- if (!resp || !('access_token' in resp)) {
521
+ mutationFn: async ({ token, cancelToken, }) => {
522
+ const resp = await this._xhr("POST", `/apple/authorize?token=${encodeURIComponent(token)}`, {}, cancelToken, { "Content-Type": "application/json" });
523
+ if (!resp || !("access_token" in resp)) {
516
524
  if (resp.msg === "TOTP required") {
517
525
  return LoginResponse.NEEDS_TOTP;
518
526
  }
519
- throw new StandardError(401, 'Invalid Apple login response');
527
+ throw new StandardError(401, "Invalid Apple login response");
520
528
  }
521
529
  this.setAuthState(AuthState.AUTHENTICATED);
522
530
  this.socket.auth = { token: resp.access_token };
523
531
  if (!this.socket.connected) {
524
532
  this.socket.connect();
525
533
  }
526
- await this.config.authManager.setTokens({ access_token: resp.access_token, refresh_token: resp.refresh_token });
534
+ await this.config.authManager.setTokens({
535
+ access_token: resp.access_token,
536
+ refresh_token: resp.refresh_token,
537
+ });
527
538
  return LoginResponse.SUCCESS;
528
539
  },
529
540
  onSettled: () => {
@@ -531,8 +542,8 @@ export class AvroQueryClient {
531
542
  },
532
543
  onError: (err) => {
533
544
  this.config.authManager.clearCache();
534
- throw new StandardError(err.status, err.message || 'Apple Login failed');
535
- }
545
+ throw new StandardError(err.status, err.message || "Apple Login failed");
546
+ },
536
547
  });
537
548
  }
538
549
  setTokens(tokens) {
@@ -571,10 +582,10 @@ export class AvroQueryClient {
571
582
  return () => this.offAuthStateChange(cb);
572
583
  }
573
584
  offAuthStateChange(cb) {
574
- this.authStateListeners = this.authStateListeners.filter(c => c !== cb);
585
+ this.authStateListeners = this.authStateListeners.filter((c) => c !== cb);
575
586
  }
576
587
  setAuthState(state) {
577
- this.authStateListeners.forEach(cb => cb(state));
588
+ this.authStateListeners.forEach((cb) => cb(state));
578
589
  this._authState = state;
579
590
  }
580
591
  getAuthState() {
@@ -586,13 +597,104 @@ export class AvroQueryClient {
586
597
  getQueryClient() {
587
598
  return useQueryClient();
588
599
  }
600
+ /**
601
+ * Fetch an entity from the API, optionally construct it, and surgically
602
+ * update all matching React-Query caches (individual + list + infinite).
603
+ *
604
+ * Shared by socket handlers and mutation `onSuccess` callbacks so the
605
+ * sender gets an immediate cache sync and everyone else gets the socket
606
+ * update — both use the identical code path.
607
+ *
608
+ * @returns The fetched (and optionally constructed) item, or `undefined`
609
+ * for deletes / entities without a fetchPath.
610
+ */
611
+ async _syncEntity(queryClient, params) {
612
+ const { action, entityKey, id, fetchPath, construct } = params;
613
+ const predicate = (q) => matchesEntityKey(q, entityKey);
614
+ const invalidate = () => queryClient.invalidateQueries({ predicate });
615
+ // ─── DELETE ─────────────────────────────────────────
616
+ if (action === "delete") {
617
+ queryClient.removeQueries({ queryKey: [entityKey, id], exact: true });
618
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
619
+ if (!old)
620
+ return old;
621
+ if (old.pages && Array.isArray(old.pages)) {
622
+ return {
623
+ ...old,
624
+ pages: old.pages.map((p) => p.filter((x) => x?.id !== id)),
625
+ };
626
+ }
627
+ if (Array.isArray(old)) {
628
+ return old.filter((x) => x?.id !== id);
629
+ }
630
+ return old;
631
+ });
632
+ return undefined;
633
+ }
634
+ // ─── CREATE / UPDATE ────────────────────────────────
635
+ if (!fetchPath) {
636
+ invalidate();
637
+ return undefined;
638
+ }
639
+ try {
640
+ const raw = await queryClient.fetchQuery({
641
+ queryKey: [entityKey, id],
642
+ queryFn: () => this.get({ path: fetchPath }),
643
+ staleTime: 0,
644
+ });
645
+ const item = construct ? construct(raw) : raw;
646
+ queryClient.setQueryData([entityKey, id], item);
647
+ if (action === "create") {
648
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
649
+ if (!old)
650
+ return old;
651
+ if (old.pages && Array.isArray(old.pages)) {
652
+ if (old.pages.some((p) => p.some((x) => x?.id === id)))
653
+ return old;
654
+ return {
655
+ ...old,
656
+ pages: [[item, ...(old.pages[0] || [])], ...old.pages.slice(1)],
657
+ };
658
+ }
659
+ if (Array.isArray(old)) {
660
+ if (old.some((x) => x?.id === id))
661
+ return old;
662
+ return [...old, item];
663
+ }
664
+ return old;
665
+ });
666
+ }
667
+ else {
668
+ // UPDATE — replace in every active list / infinite-query cache
669
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
670
+ if (!old)
671
+ return old;
672
+ if (old.pages && Array.isArray(old.pages)) {
673
+ return {
674
+ ...old,
675
+ pages: old.pages.map((page) => page.map((x) => (x?.id === id ? item : x))),
676
+ };
677
+ }
678
+ if (Array.isArray(old)) {
679
+ return old.map((x) => (x?.id === id ? item : x));
680
+ }
681
+ return old;
682
+ });
683
+ }
684
+ return item;
685
+ }
686
+ catch {
687
+ invalidate();
688
+ return undefined;
689
+ }
690
+ }
589
691
  useLogout() {
590
692
  const queryClient = this.getQueryClient();
591
693
  return useMutation({
592
694
  mutationFn: async (cancelToken) => {
593
695
  await this.post({
594
- path: '/logout',
595
- cancelToken
696
+ path: "/logout",
697
+ cancelToken,
596
698
  });
597
699
  await this.config.authManager.clearCache();
598
700
  if (this.socket && this.socket.connected) {
@@ -606,276 +708,276 @@ export class AvroQueryClient {
606
708
  },
607
709
  onError: (err) => {
608
710
  this.clearCache();
609
- console.error('Logout failed:', err);
610
- throw new StandardError(500, 'Logout failed');
611
- }
711
+ console.error("Logout failed:", err);
712
+ throw new StandardError(500, "Logout failed");
713
+ },
612
714
  });
613
715
  }
614
716
  fetchJobs(body = {}, companyId, cancelToken, headers = {}) {
615
717
  const companyIdToUse = companyId ?? this.companyId;
616
- if (!companyIdToUse || companyIdToUse.trim() === '') {
617
- throw new StandardError(400, 'Company ID is required');
718
+ if (!companyIdToUse || companyIdToUse.trim() === "") {
719
+ throw new StandardError(400, "Company ID is required");
618
720
  }
619
- return this._fetch('POST', `/company/${companyIdToUse}/jobs`, JSON.stringify(body), cancelToken, {
721
+ return this._fetch("POST", `/company/${companyIdToUse}/jobs`, JSON.stringify(body), cancelToken, {
620
722
  ...headers,
621
- 'Content-Type': 'application/json',
723
+ "Content-Type": "application/json",
622
724
  })
623
- .then(response => {
725
+ .then((response) => {
624
726
  if (!response || !Array.isArray(response)) {
625
- throw new StandardError(400, 'Invalid jobs response');
727
+ throw new StandardError(400, "Invalid jobs response");
626
728
  }
627
729
  return response;
628
730
  })
629
- .catch(err => {
630
- console.error('Failed to fetch jobs:', err);
731
+ .catch((err) => {
732
+ console.error("Failed to fetch jobs:", err);
631
733
  throw new StandardError(500, `Failed to fetch jobs: ${err.message ?? err}`);
632
734
  });
633
735
  }
634
736
  fetchChats(body = {}, cancelToken, headers = {}) {
635
- if (!this.companyId || this.companyId.trim() === '') {
636
- throw new StandardError(400, 'Company ID is required');
737
+ if (!this.companyId || this.companyId.trim() === "") {
738
+ throw new StandardError(400, "Company ID is required");
637
739
  }
638
- return this._fetch('POST', `/company/${this.companyId}/chats`, JSON.stringify(body), cancelToken, {
740
+ return this._fetch("POST", `/company/${this.companyId}/chats`, JSON.stringify(body), cancelToken, {
639
741
  ...headers,
640
- 'Content-Type': 'application/json',
742
+ "Content-Type": "application/json",
641
743
  })
642
- .then(response => {
744
+ .then((response) => {
643
745
  if (!response || !Array.isArray(response)) {
644
- throw new StandardError(400, 'Invalid chats response');
746
+ throw new StandardError(400, "Invalid chats response");
645
747
  }
646
748
  return response;
647
749
  })
648
- .catch(err => {
649
- console.error('Failed to fetch chats:', err);
650
- throw new StandardError(500, 'Failed to fetch chats');
750
+ .catch((err) => {
751
+ console.error("Failed to fetch chats:", err);
752
+ throw new StandardError(500, "Failed to fetch chats");
651
753
  });
652
754
  }
653
755
  fetchMessages(chatId, body = {}, cancelToken, headers = {}) {
654
- if (!chatId || chatId.trim() === '') {
655
- throw new StandardError(400, 'Chat ID is required');
756
+ if (!chatId || chatId.trim() === "") {
757
+ throw new StandardError(400, "Chat ID is required");
656
758
  }
657
- return this._fetch('POST', `/chat/${chatId}/messages`, JSON.stringify(body), cancelToken, {
759
+ return this._fetch("POST", `/chat/${chatId}/messages`, JSON.stringify(body), cancelToken, {
658
760
  ...headers,
659
- 'Content-Type': 'application/json',
761
+ "Content-Type": "application/json",
660
762
  })
661
- .then(response => {
763
+ .then((response) => {
662
764
  if (!response || !Array.isArray(response)) {
663
- throw new StandardError(400, 'Invalid messages response');
765
+ throw new StandardError(400, "Invalid messages response");
664
766
  }
665
767
  return response;
666
768
  })
667
- .catch(err => {
668
- console.error('Failed to fetch messages:', err);
669
- throw new StandardError(500, 'Failed to fetch messages');
769
+ .catch((err) => {
770
+ console.error("Failed to fetch messages:", err);
771
+ throw new StandardError(500, "Failed to fetch messages");
670
772
  });
671
773
  }
672
774
  async fetchPrepayments(body = {}, cancelToken, headers = {}) {
673
- if (!this.companyId || this.companyId.trim() === '') {
674
- throw new StandardError(400, 'Company ID is required');
775
+ if (!this.companyId || this.companyId.trim() === "") {
776
+ throw new StandardError(400, "Company ID is required");
675
777
  }
676
- return this._fetch('POST', `/company/${this.companyId}/prepayments`, JSON.stringify(body), cancelToken, {
778
+ return this._fetch("POST", `/company/${this.companyId}/prepayments`, JSON.stringify(body), cancelToken, {
677
779
  ...headers,
678
- 'Content-Type': 'application/json',
780
+ "Content-Type": "application/json",
679
781
  })
680
- .then(response => {
782
+ .then((response) => {
681
783
  if (!response || !Array.isArray(response)) {
682
- throw new StandardError(400, 'Invalid prepayments response');
784
+ throw new StandardError(400, "Invalid prepayments response");
683
785
  }
684
786
  return response;
685
787
  })
686
- .catch(err => {
687
- console.error('Failed to fetch prepayments:', err);
688
- throw new StandardError(500, 'Failed to fetch prepayments');
788
+ .catch((err) => {
789
+ console.error("Failed to fetch prepayments:", err);
790
+ throw new StandardError(500, "Failed to fetch prepayments");
689
791
  });
690
792
  }
691
793
  async fetchWaivers(body = {}, cancelToken, headers = {}) {
692
- if (!this.companyId || this.companyId.trim() === '') {
693
- throw new StandardError(400, 'Company ID is required');
794
+ if (!this.companyId || this.companyId.trim() === "") {
795
+ throw new StandardError(400, "Company ID is required");
694
796
  }
695
- return this._fetch('POST', `/company/${this.companyId}/waivers`, JSON.stringify(body), cancelToken, {
797
+ return this._fetch("POST", `/company/${this.companyId}/waivers`, JSON.stringify(body), cancelToken, {
696
798
  ...headers,
697
- 'Content-Type': 'application/json',
799
+ "Content-Type": "application/json",
698
800
  })
699
- .then(response => {
801
+ .then((response) => {
700
802
  if (!response || !Array.isArray(response)) {
701
- throw new StandardError(400, 'Invalid waivers response');
803
+ throw new StandardError(400, "Invalid waivers response");
702
804
  }
703
805
  return response;
704
806
  })
705
- .catch(err => {
706
- console.error('Failed to fetch waivers:', err);
707
- throw new StandardError(500, 'Failed to fetch waivers');
807
+ .catch((err) => {
808
+ console.error("Failed to fetch waivers:", err);
809
+ throw new StandardError(500, "Failed to fetch waivers");
708
810
  });
709
811
  }
710
812
  async fetchEvents(body = {}, cancelToken, headers = {}) {
711
- if (!this.companyId || this.companyId.trim() === '') {
712
- throw new StandardError(400, 'Company ID is required');
813
+ if (!this.companyId || this.companyId.trim() === "") {
814
+ throw new StandardError(400, "Company ID is required");
713
815
  }
714
- return this._fetch('POST', `/company/${this.companyId}/events`, JSON.stringify(body), cancelToken, {
816
+ return this._fetch("POST", `/company/${this.companyId}/events`, JSON.stringify(body), cancelToken, {
715
817
  ...headers,
716
- 'Content-Type': 'application/json',
818
+ "Content-Type": "application/json",
717
819
  })
718
- .then(response => {
820
+ .then((response) => {
719
821
  if (!response || !Array.isArray(response)) {
720
- throw new StandardError(400, 'Invalid events response');
822
+ throw new StandardError(400, "Invalid events response");
721
823
  }
722
824
  return response;
723
825
  })
724
- .catch(err => {
725
- console.error('Failed to fetch events:', err);
726
- throw new StandardError(500, 'Failed to fetch events');
826
+ .catch((err) => {
827
+ console.error("Failed to fetch events:", err);
828
+ throw new StandardError(500, "Failed to fetch events");
727
829
  });
728
830
  }
729
831
  fetchMonths(body = {}, cancelToken, headers = {}) {
730
- if (!this.companyId || this.companyId.trim() === '') {
731
- throw new StandardError(400, 'Company ID is required');
832
+ if (!this.companyId || this.companyId.trim() === "") {
833
+ throw new StandardError(400, "Company ID is required");
732
834
  }
733
- return this._fetch('POST', `/company/${this.companyId}/months`, JSON.stringify(body), cancelToken, {
835
+ return this._fetch("POST", `/company/${this.companyId}/months`, JSON.stringify(body), cancelToken, {
734
836
  ...headers,
735
- 'Content-Type': 'application/json',
837
+ "Content-Type": "application/json",
736
838
  })
737
- .then(response => {
839
+ .then((response) => {
738
840
  if (!response || !Array.isArray(response)) {
739
- throw new StandardError(400, 'Invalid months response');
841
+ throw new StandardError(400, "Invalid months response");
740
842
  }
741
843
  return response;
742
844
  })
743
- .catch(err => {
744
- console.error('Failed to fetch months:', err);
745
- throw new StandardError(500, 'Failed to fetch months');
845
+ .catch((err) => {
846
+ console.error("Failed to fetch months:", err);
847
+ throw new StandardError(500, "Failed to fetch months");
746
848
  });
747
849
  }
748
850
  fetchBills(body = {}, cancelToken, headers = {}) {
749
- if (!this.companyId || this.companyId.trim() === '') {
750
- throw new StandardError(400, 'Company ID is required');
851
+ if (!this.companyId || this.companyId.trim() === "") {
852
+ throw new StandardError(400, "Company ID is required");
751
853
  }
752
- return this._fetch('POST', `/company/${this.companyId}/bills`, JSON.stringify(body), cancelToken, {
854
+ return this._fetch("POST", `/company/${this.companyId}/bills`, JSON.stringify(body), cancelToken, {
753
855
  ...headers,
754
- 'Content-Type': 'application/json',
856
+ "Content-Type": "application/json",
755
857
  })
756
- .then(response => {
858
+ .then((response) => {
757
859
  if (!response || !Array.isArray(response)) {
758
- throw new StandardError(400, 'Invalid bills response');
860
+ throw new StandardError(400, "Invalid bills response");
759
861
  }
760
862
  return response;
761
863
  })
762
- .catch(err => {
763
- console.error('Failed to fetch bills:', err);
764
- throw new StandardError(500, 'Failed to fetch bills');
864
+ .catch((err) => {
865
+ console.error("Failed to fetch bills:", err);
866
+ throw new StandardError(500, "Failed to fetch bills");
765
867
  });
766
868
  }
767
869
  fetchRoutes(body = {}, cancelToken, headers = {}) {
768
- if (!this.companyId || this.companyId.trim() === '') {
769
- throw new StandardError(400, 'Company ID is required');
870
+ if (!this.companyId || this.companyId.trim() === "") {
871
+ throw new StandardError(400, "Company ID is required");
770
872
  }
771
- return this._fetch('POST', `/company/${this.companyId}/routes`, JSON.stringify(body), cancelToken, {
873
+ return this._fetch("POST", `/company/${this.companyId}/routes`, JSON.stringify(body), cancelToken, {
772
874
  ...headers,
773
- 'Content-Type': 'application/json',
875
+ "Content-Type": "application/json",
774
876
  })
775
- .then(response => {
877
+ .then((response) => {
776
878
  if (!response || !Array.isArray(response)) {
777
- throw new StandardError(400, 'Invalid routes response');
879
+ throw new StandardError(400, "Invalid routes response");
778
880
  }
779
881
  return response;
780
882
  })
781
- .catch(err => {
782
- console.error('Failed to fetch routes:', err);
783
- throw new StandardError(500, 'Failed to fetch routes');
883
+ .catch((err) => {
884
+ console.error("Failed to fetch routes:", err);
885
+ throw new StandardError(500, "Failed to fetch routes");
784
886
  });
785
887
  }
786
888
  fetchTeams(body = {}, cancelToken, headers = {}) {
787
- if (!this.companyId || this.companyId.trim() === '') {
788
- throw new StandardError(400, 'Company ID is required');
889
+ if (!this.companyId || this.companyId.trim() === "") {
890
+ throw new StandardError(400, "Company ID is required");
789
891
  }
790
- return this._fetch('POST', `/company/${this.companyId}/teams`, JSON.stringify(body), cancelToken, {
892
+ return this._fetch("POST", `/company/${this.companyId}/teams`, JSON.stringify(body), cancelToken, {
791
893
  ...headers,
792
- 'Content-Type': 'application/json',
894
+ "Content-Type": "application/json",
793
895
  })
794
- .then(response => {
896
+ .then((response) => {
795
897
  if (!response || !Array.isArray(response)) {
796
- throw new StandardError(400, 'Invalid teams response');
898
+ throw new StandardError(400, "Invalid teams response");
797
899
  }
798
900
  return response;
799
901
  })
800
- .catch(err => {
801
- console.error('Failed to fetch teams:', err);
802
- throw new StandardError(500, 'Failed to fetch teams');
902
+ .catch((err) => {
903
+ console.error("Failed to fetch teams:", err);
904
+ throw new StandardError(500, "Failed to fetch teams");
803
905
  });
804
906
  }
805
907
  fetchLabels(body = {}, cancelToken, headers = {}) {
806
- if (!this.companyId || this.companyId.trim() === '') {
807
- throw new StandardError(400, 'Company ID is required');
908
+ if (!this.companyId || this.companyId.trim() === "") {
909
+ throw new StandardError(400, "Company ID is required");
808
910
  }
809
- return this._fetch('POST', `/company/${this.companyId}/labels`, JSON.stringify(body), cancelToken, {
911
+ return this._fetch("POST", `/company/${this.companyId}/labels`, JSON.stringify(body), cancelToken, {
810
912
  ...headers,
811
- 'Content-Type': 'application/json',
913
+ "Content-Type": "application/json",
812
914
  })
813
- .then(response => {
915
+ .then((response) => {
814
916
  if (!response || !Array.isArray(response)) {
815
- throw new StandardError(400, 'Invalid labels response');
917
+ throw new StandardError(400, "Invalid labels response");
816
918
  }
817
919
  return response;
818
920
  })
819
- .catch(err => {
820
- console.error('Failed to fetch labels:', err);
821
- throw new StandardError(500, 'Failed to fetch labels');
921
+ .catch((err) => {
922
+ console.error("Failed to fetch labels:", err);
923
+ throw new StandardError(500, "Failed to fetch labels");
822
924
  });
823
925
  }
824
926
  fetchGroups(body = {}, cancelToken, headers = {}) {
825
- if (!this.companyId || this.companyId.trim() === '') {
826
- throw new StandardError(400, 'Company ID is required');
927
+ if (!this.companyId || this.companyId.trim() === "") {
928
+ throw new StandardError(400, "Company ID is required");
827
929
  }
828
- return this._fetch('POST', `/company/${this.companyId}/groups`, JSON.stringify(body), cancelToken, {
930
+ return this._fetch("POST", `/company/${this.companyId}/groups`, JSON.stringify(body), cancelToken, {
829
931
  ...headers,
830
- 'Content-Type': 'application/json',
932
+ "Content-Type": "application/json",
831
933
  })
832
- .then(response => {
934
+ .then((response) => {
833
935
  if (!response || !Array.isArray(response)) {
834
- throw new StandardError(400, 'Invalid groups response');
936
+ throw new StandardError(400, "Invalid groups response");
835
937
  }
836
938
  return response;
837
939
  })
838
- .catch(err => {
839
- console.error('Failed to fetch groups:', err);
840
- throw new StandardError(500, 'Failed to fetch groups');
940
+ .catch((err) => {
941
+ console.error("Failed to fetch groups:", err);
942
+ throw new StandardError(500, "Failed to fetch groups");
841
943
  });
842
944
  }
843
945
  fetchSkills(body = {}, cancelToken, headers = {}) {
844
- if (!this.companyId || this.companyId.trim() === '') {
845
- throw new StandardError(400, 'Company ID is required');
946
+ if (!this.companyId || this.companyId.trim() === "") {
947
+ throw new StandardError(400, "Company ID is required");
846
948
  }
847
- return this._fetch('POST', `/company/${this.companyId}/skills`, JSON.stringify(body), cancelToken, {
949
+ return this._fetch("POST", `/company/${this.companyId}/skills`, JSON.stringify(body), cancelToken, {
848
950
  ...headers,
849
- 'Content-Type': 'application/json',
951
+ "Content-Type": "application/json",
850
952
  })
851
- .then(response => {
953
+ .then((response) => {
852
954
  if (!response || !Array.isArray(response)) {
853
- throw new StandardError(400, 'Invalid skills response');
955
+ throw new StandardError(400, "Invalid skills response");
854
956
  }
855
957
  return response;
856
958
  })
857
- .catch(err => {
858
- console.error('Failed to fetch skills:', err);
859
- throw new StandardError(500, 'Failed to fetch skills');
959
+ .catch((err) => {
960
+ console.error("Failed to fetch skills:", err);
961
+ throw new StandardError(500, "Failed to fetch skills");
860
962
  });
861
963
  }
862
964
  fetchSessions(body = {}, cancelToken, headers = {}) {
863
- if (!this.companyId || this.companyId.trim() === '') {
864
- throw new StandardError(400, 'Company ID is required');
965
+ if (!this.companyId || this.companyId.trim() === "") {
966
+ throw new StandardError(400, "Company ID is required");
865
967
  }
866
- return this._fetch('POST', `/company/${this.companyId}/sessions`, JSON.stringify(body), cancelToken, {
968
+ return this._fetch("POST", `/company/${this.companyId}/sessions`, JSON.stringify(body), cancelToken, {
867
969
  ...headers,
868
- 'Content-Type': 'application/json',
970
+ "Content-Type": "application/json",
869
971
  })
870
- .then(response => {
972
+ .then((response) => {
871
973
  if (!response || !Array.isArray(response)) {
872
- throw new StandardError(400, 'Invalid sessions response');
974
+ throw new StandardError(400, "Invalid sessions response");
873
975
  }
874
976
  return response;
875
977
  })
876
- .catch(err => {
877
- console.error('Failed to fetch sessions:', err);
878
- throw new StandardError(500, 'Failed to fetch sessions');
978
+ .catch((err) => {
979
+ console.error("Failed to fetch sessions:", err);
980
+ throw new StandardError(500, "Failed to fetch sessions");
879
981
  });
880
982
  }
881
983
  /* ── Email delivery tracking ──────────────────────────────────────── */
@@ -940,7 +1042,7 @@ export class AvroQueryClient {
940
1042
  return this.post({
941
1043
  path: `/email/${emailId}`,
942
1044
  data: formData,
943
- progressUpdateCallback
1045
+ progressUpdateCallback,
944
1046
  });
945
1047
  }
946
1048
  catch (error) {
@@ -952,7 +1054,7 @@ export class AvroQueryClient {
952
1054
  return this.post({
953
1055
  path: `/bill/${billId}/email`,
954
1056
  data: JSON.stringify(body),
955
- headers: { 'Content-Type': 'application/json' }
1057
+ headers: { "Content-Type": "application/json" },
956
1058
  });
957
1059
  }
958
1060
  catch (error) {