@go-avro/avro-js 0.0.37 → 0.0.39

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 +505 -391
  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 { Job, 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}`, construct: (d) => new Job(d) },
29
- update_job: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, construct: (d) => new Job(d) },
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}`, construct: (d) => new Job(d) }] },
39
- update_event: { entityKey: 'events', action: 'update', fetchPath: (id) => `/event/${id}`, relatedRefetch: [{ entityKey: 'jobs', idField: 'job_id', fetchPath: (id) => `/job/${id}`, construct: (d) => new Job(d) }] },
40
- delete_event: { entityKey: 'events', action: 'delete', fetchPath: null, relatedRefetch: [{ entityKey: 'jobs', idField: 'job_id', fetchPath: (id) => `/job/${id}`, construct: (d) => new Job(d) }] },
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', construct: (d) => new Job(d) },
80
- update_task: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, idField: 'job_id', construct: (d) => new Job(d) },
81
- delete_task: { entityKey: 'jobs', action: 'update', fetchPath: (id) => `/job/${id}`, idField: 'job_id', construct: (d) => new Job(d) },
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,54 @@ 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 => {
120
- this.companyId = id;
121
- });
122
232
  if (!this.socket.connected && isAuth === AuthState.AUTHENTICATED) {
123
- this.config.authManager.accessToken().then(token => {
124
- console.log('Initializing socket connection with token:', token);
233
+ // Resolve both companyId and access token before connecting so
234
+ // the 'connect' handler in setupSocketInvalidation can immediately
235
+ // join the company room.
236
+ Promise.all([
237
+ this.getCompanyId(),
238
+ this.config.authManager.accessToken(),
239
+ ])
240
+ .then(([id, token]) => {
241
+ this.companyId = id;
242
+ console.log("Initializing socket connection with token:", token);
125
243
  this.socket.auth = { token: token };
126
244
  this.socket.connect();
127
- }).catch(err => {
128
- console.error('Not logged in:', err);
245
+ })
246
+ .catch((err) => {
247
+ console.error("Not logged in:", err);
248
+ });
249
+ }
250
+ else {
251
+ // Not authenticated – still cache the companyId for later.
252
+ this.getCompanyId().then((id) => {
253
+ this.companyId = id;
129
254
  });
130
255
  }
131
256
  });
132
- this.socket.on('connect', () => {
257
+ this.socket.on("connect", () => {
133
258
  this.setAuthState(AuthState.AUTHENTICATED);
134
259
  console.log(`Socket connected with ID: ${this.socket?.id}`);
135
260
  });
136
- this.socket.on('disconnect', (reason) => {
261
+ this.socket.on("disconnect", (reason) => {
137
262
  console.log(`Socket disconnected: ${reason}`);
138
263
  });
139
- this.socket.on('connect_error', (err) => {
264
+ this.socket.on("connect_error", (err) => {
140
265
  console.error(`Socket connection error: ${err.message}`);
141
266
  });
142
267
  this.config.authManager.onTokenRefreshed((newAccessToken) => {
143
268
  if (this.socket && newAccessToken) {
144
269
  this.setAuthState(AuthState.AUTHENTICATED);
145
- console.log('Access token refreshed, updating socket auth...');
270
+ console.log("Access token refreshed, updating socket auth...");
146
271
  this.socket.auth = { token: newAccessToken };
147
272
  this.socket.disconnect().connect();
148
273
  }
@@ -157,7 +282,7 @@ export class AvroQueryClient {
157
282
  }
158
283
  emit(eventName, data) {
159
284
  if (!this.socket?.connected) {
160
- console.error('Socket is not connected. Cannot emit event.');
285
+ console.error("Socket is not connected. Cannot emit event.");
161
286
  return;
162
287
  }
163
288
  this.socket.emit(eventName, data);
@@ -191,7 +316,9 @@ export class AvroQueryClient {
191
316
  const client = this; // stable reference for async closures
192
317
  /** Full-invalidate every key that matches the entity (including 'infinite' prefix). */
193
318
  const invalidateEntity = (entityKey) => {
194
- queryClient.invalidateQueries({ predicate: (q) => matchesEntityKey(q, entityKey) });
319
+ queryClient.invalidateQueries({
320
+ predicate: (q) => matchesEntityKey(q, entityKey),
321
+ });
195
322
  };
196
323
  for (const [event, config] of Object.entries(SOCKET_EVENT_CONFIG)) {
197
324
  if (isBulkEvent(config)) {
@@ -206,160 +333,39 @@ export class AvroQueryClient {
206
333
  }
207
334
  else {
208
335
  // ── Targeted: surgical cache update ──────────────────
209
- const { entityKey, action, fetchPath, idField, alsoInvalidate, relatedRefetch } = config;
336
+ const { entityKey, action, fetchPath, idField, alsoInvalidate, relatedRefetch, } = config;
210
337
  const handler = async (data) => {
211
- const id = data?.[idField ?? 'id'];
338
+ const id = data?.[idField ?? "id"];
212
339
  // No id → old backend or malformed payload → full invalidation
213
- if (!id || typeof id !== 'string') {
340
+ if (!id || typeof id !== "string") {
214
341
  invalidateEntity(entityKey);
215
342
  alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
216
343
  return;
217
344
  }
218
- const entityPredicate = (q) => matchesEntityKey(q, entityKey);
219
345
  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 = config.construct ? config.construct(item) : item;
231
- queryClient.setQueryData([entityKey, id], fetchedItem);
232
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
233
- if (!old)
234
- return old;
235
- if (old.pages && Array.isArray(old.pages)) {
236
- // Skip if already present (avoids duplicates
237
- // when the creating user also gets the event)
238
- if (old.pages.some((p) => p.some((x) => x?.id === id)))
239
- return old;
240
- return {
241
- ...old,
242
- pages: [
243
- [fetchedItem, ...(old.pages[0] || [])],
244
- ...old.pages.slice(1),
245
- ],
246
- };
247
- }
248
- if (Array.isArray(old)) {
249
- if (old.some((x) => x?.id === id))
250
- return old;
251
- return [...old, fetchedItem];
252
- }
253
- return old;
254
- });
255
- }
256
- catch {
257
- invalidateEntity(entityKey);
258
- }
259
- }
260
- else {
261
- // No individual GET endpoint → invalidate lists
262
- invalidateEntity(entityKey);
263
- }
264
- break;
265
- }
266
- // ─── UPDATE ─────────────────────────────────
267
- case 'update': {
268
- if (fetchPath) {
269
- try {
270
- // Fetch fresh copy (also updates the [entity, id] cache)
271
- const item = await queryClient.fetchQuery({
272
- queryKey: [entityKey, id],
273
- queryFn: () => client.get({ path: fetchPath(id) }),
274
- staleTime: 0,
275
- });
276
- fetchedItem = config.construct ? config.construct(item) : item;
277
- queryClient.setQueryData([entityKey, id], fetchedItem);
278
- // Patch it into every active list / infinite-query cache
279
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
280
- if (!old)
281
- return old;
282
- if (old.pages && Array.isArray(old.pages)) {
283
- return {
284
- ...old,
285
- pages: old.pages.map((page) => page.map((x) => (x?.id === id ? fetchedItem : x))),
286
- };
287
- }
288
- if (Array.isArray(old)) {
289
- return old.map((x) => (x?.id === id ? fetchedItem : x));
290
- }
291
- return old;
292
- });
293
- }
294
- catch {
295
- invalidateEntity(entityKey);
296
- }
297
- }
298
- else {
299
- // No individual GET → invalidate everything for this entity
300
- invalidateEntity(entityKey);
301
- }
302
- break;
303
- }
304
- // ─── DELETE ─────────────────────────────────
305
- case 'delete': {
306
- // Remove the individual-item cache entry
307
- queryClient.removeQueries({ queryKey: [entityKey, id], exact: true });
308
- // Remove from every active list / infinite-query cache
309
- queryClient.setQueriesData({ predicate: entityPredicate, type: 'active' }, (old) => {
310
- if (!old)
311
- return old;
312
- if (old.pages && Array.isArray(old.pages)) {
313
- return {
314
- ...old,
315
- pages: old.pages.map((page) => page.filter((x) => x?.id !== id)),
316
- };
317
- }
318
- if (Array.isArray(old)) {
319
- return old.filter((x) => x?.id !== id);
320
- }
321
- return old;
322
- });
323
- break;
324
- }
325
- }
346
+ // ─── CREATE / UPDATE / DELETE via shared _syncEntity ───
347
+ fetchedItem = await client._syncEntity(queryClient, {
348
+ action,
349
+ entityKey,
350
+ id,
351
+ fetchPath: fetchPath ? fetchPath(id) : undefined,
352
+ construct: config.construct,
353
+ });
326
354
  // Invalidate any additional keys (e.g. companies → /company/list)
327
355
  alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
328
356
  // Refetch & patch related entities (e.g. event → parent job)
329
357
  if (relatedRefetch) {
330
358
  for (const related of relatedRefetch) {
331
359
  const relatedId = data?.[related.idField] ?? fetchedItem?.[related.idField];
332
- if (!relatedId || typeof relatedId !== 'string')
360
+ if (!relatedId || typeof relatedId !== "string")
333
361
  continue;
334
- try {
335
- let relatedItem = await queryClient.fetchQuery({
336
- queryKey: [related.entityKey, relatedId],
337
- queryFn: () => client.get({ path: related.fetchPath(relatedId) }),
338
- staleTime: 0,
339
- });
340
- if (related.construct)
341
- relatedItem = related.construct(relatedItem);
342
- queryClient.setQueryData([related.entityKey, relatedId], relatedItem);
343
- const relatedPredicate = (q) => matchesEntityKey(q, related.entityKey);
344
- queryClient.setQueriesData({ predicate: relatedPredicate, type: 'active' }, (old) => {
345
- if (!old)
346
- return old;
347
- if (old.pages && Array.isArray(old.pages)) {
348
- return {
349
- ...old,
350
- pages: old.pages.map((page) => page.map((x) => (x?.id === relatedId ? relatedItem : x))),
351
- };
352
- }
353
- if (Array.isArray(old)) {
354
- return old.map((x) => (x?.id === relatedId ? relatedItem : x));
355
- }
356
- return old;
357
- });
358
- }
359
- catch {
360
- // Fetch failed → invalidate related entity lists
361
- invalidateEntity(related.entityKey);
362
- }
362
+ await client._syncEntity(queryClient, {
363
+ action: "update",
364
+ entityKey: related.entityKey,
365
+ id: relatedId,
366
+ fetchPath: related.fetchPath(relatedId),
367
+ construct: related.construct,
368
+ });
363
369
  }
364
370
  }
365
371
  };
@@ -370,11 +376,11 @@ export class AvroQueryClient {
370
376
  // Auto join/leave company room
371
377
  const joinCompanyRoom = () => {
372
378
  if (this.companyId) {
373
- this.socket.emit('join_company', { company_id: this.companyId });
379
+ this.socket.emit("join_company", { company_id: this.companyId });
374
380
  }
375
381
  };
376
- this.socket.on('connect', joinCompanyRoom);
377
- handlers.push({ event: 'connect', handler: joinCompanyRoom });
382
+ this.socket.on("connect", joinCompanyRoom);
383
+ handlers.push({ event: "connect", handler: joinCompanyRoom });
378
384
  // If already connected, join immediately
379
385
  if (this.socket.connected && this.companyId) {
380
386
  joinCompanyRoom();
@@ -385,7 +391,7 @@ export class AvroQueryClient {
385
391
  }
386
392
  // Leave company room on teardown
387
393
  if (this.socket.connected && this.companyId) {
388
- this.socket.emit('leave_company', { company_id: this.companyId });
394
+ this.socket.emit("leave_company", { company_id: this.companyId });
389
395
  }
390
396
  this._queryClient = null;
391
397
  };
@@ -397,17 +403,17 @@ export class AvroQueryClient {
397
403
  this._socketInvalidationCleanup?.();
398
404
  this._socketInvalidationCleanup = null;
399
405
  }
400
- get({ path, cancelToken, headers, progressUpdateCallback }) {
401
- return this._xhr('GET', path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
406
+ get({ path, cancelToken, headers, progressUpdateCallback, }) {
407
+ return this._xhr("GET", path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
402
408
  }
403
- post({ path, data, cancelToken, headers, progressUpdateCallback }) {
404
- return this._xhr('POST', path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
409
+ post({ path, data, cancelToken, headers, progressUpdateCallback, }) {
410
+ return this._xhr("POST", path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
405
411
  }
406
- put({ path, data, cancelToken, headers, progressUpdateCallback }) {
407
- return this._xhr('PUT', path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
412
+ put({ path, data, cancelToken, headers, progressUpdateCallback, }) {
413
+ return this._xhr("PUT", path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
408
414
  }
409
- delete({ path, cancelToken, headers, progressUpdateCallback }) {
410
- return this._xhr('DELETE', path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
415
+ delete({ path, cancelToken, headers, progressUpdateCallback, }) {
416
+ return this._xhr("DELETE", path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
411
417
  }
412
418
  loginSuccess(tokens) {
413
419
  this.setAuthState(AuthState.AUTHENTICATED);
@@ -420,20 +426,23 @@ export class AvroQueryClient {
420
426
  useLogin() {
421
427
  const queryClient = this.getQueryClient();
422
428
  return useMutation({
423
- mutationFn: async ({ username, password, code, cancelToken }) => {
429
+ mutationFn: async ({ username, password, code, cancelToken, }) => {
424
430
  const resp = await this.post({
425
- path: '/login',
431
+ path: "/login",
426
432
  data: JSON.stringify({ username, password, code }),
427
433
  cancelToken,
428
- headers: { 'Content-Type': 'application/json' }
434
+ headers: { "Content-Type": "application/json" },
429
435
  });
430
- if (!resp || !('access_token' in resp)) {
436
+ if (!resp || !("access_token" in resp)) {
431
437
  if (resp.msg === "TOTP required") {
432
438
  return LoginResponse.NEEDS_TOTP;
433
439
  }
434
- throw new StandardError(401, 'Invalid login response');
440
+ throw new StandardError(401, "Invalid login response");
435
441
  }
436
- await this.loginSuccess({ access_token: resp.access_token, refresh_token: resp.refresh_token });
442
+ await this.loginSuccess({
443
+ access_token: resp.access_token,
444
+ refresh_token: resp.refresh_token,
445
+ });
437
446
  return LoginResponse.SUCCESS;
438
447
  },
439
448
  onSettled: () => {
@@ -441,19 +450,19 @@ export class AvroQueryClient {
441
450
  },
442
451
  onError: (err) => {
443
452
  this.config.authManager.clearCache();
444
- throw new StandardError(401, err.message || 'Login failed');
445
- }
453
+ throw new StandardError(401, err.message || "Login failed");
454
+ },
446
455
  });
447
456
  }
448
457
  useRequestCode() {
449
458
  const queryClient = this.getQueryClient();
450
459
  return useMutation({
451
- mutationFn: async ({ username, email, cancelToken }) => {
460
+ mutationFn: async ({ username, email, cancelToken, }) => {
452
461
  const resp = await this.post({
453
- path: '/code',
462
+ path: "/code",
454
463
  data: JSON.stringify({ username, email }),
455
464
  cancelToken,
456
- headers: { 'Content-Type': 'application/json' }
465
+ headers: { "Content-Type": "application/json" },
457
466
  });
458
467
  return resp;
459
468
  },
@@ -461,46 +470,49 @@ export class AvroQueryClient {
461
470
  queryClient.invalidateQueries();
462
471
  },
463
472
  onError: (err) => {
464
- throw new StandardError(err.status, err.message || 'Request code failed');
465
- }
473
+ throw new StandardError(err.status, err.message || "Request code failed");
474
+ },
466
475
  });
467
476
  }
468
477
  useUpdatePassword() {
469
478
  const queryClient = this.getQueryClient();
470
479
  return useMutation({
471
- mutationFn: async ({ username, email, code, newPassword, cancelToken }) => {
480
+ mutationFn: async ({ username, email, code, newPassword, cancelToken, }) => {
472
481
  await this.post({
473
482
  path: `/user/${username ?? email}/password`,
474
483
  data: JSON.stringify({ code, password: newPassword }),
475
484
  cancelToken,
476
- headers: { 'Content-Type': 'application/json' }
485
+ headers: { "Content-Type": "application/json" },
477
486
  });
478
487
  },
479
488
  onSettled: () => {
480
489
  queryClient.invalidateQueries();
481
490
  },
482
491
  onError: (err) => {
483
- throw new StandardError(err.status, err.message || 'Update password failed');
484
- }
492
+ throw new StandardError(err.status, err.message || "Update password failed");
493
+ },
485
494
  });
486
495
  }
487
496
  useGoogleLogin() {
488
497
  const queryClient = this.getQueryClient();
489
498
  return useMutation({
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)) {
499
+ mutationFn: async ({ token, cancelToken, }) => {
500
+ const resp = await this._xhr("POST", `/google/authorize?token=${token}`, {}, cancelToken, { "Content-Type": "application/json" });
501
+ if (!resp || !("access_token" in resp)) {
493
502
  if (resp.msg === "TOTP required") {
494
503
  return LoginResponse.NEEDS_TOTP;
495
504
  }
496
- throw new StandardError(401, 'Invalid Google login response');
505
+ throw new StandardError(401, "Invalid Google login response");
497
506
  }
498
507
  this.setAuthState(AuthState.AUTHENTICATED);
499
508
  this.socket.auth = { token: resp.access_token };
500
509
  if (!this.socket.connected) {
501
510
  this.socket.connect();
502
511
  }
503
- await this.config.authManager.setTokens({ access_token: resp.access_token, refresh_token: resp.refresh_token });
512
+ await this.config.authManager.setTokens({
513
+ access_token: resp.access_token,
514
+ refresh_token: resp.refresh_token,
515
+ });
504
516
  return LoginResponse.SUCCESS;
505
517
  },
506
518
  onSettled: () => {
@@ -508,27 +520,30 @@ export class AvroQueryClient {
508
520
  },
509
521
  onError: (err) => {
510
522
  this.config.authManager.clearCache();
511
- throw new StandardError(err.status, err.message || 'Google Login failed');
512
- }
523
+ throw new StandardError(err.status, err.message || "Google Login failed");
524
+ },
513
525
  });
514
526
  }
515
527
  useAppleLogin() {
516
528
  const queryClient = this.getQueryClient();
517
529
  return useMutation({
518
- mutationFn: async ({ token, cancelToken }) => {
519
- const resp = await this._xhr('POST', `/apple/authorize?token=${encodeURIComponent(token)}`, {}, cancelToken, { 'Content-Type': 'application/json' });
520
- if (!resp || !('access_token' in resp)) {
530
+ mutationFn: async ({ token, cancelToken, }) => {
531
+ const resp = await this._xhr("POST", `/apple/authorize?token=${encodeURIComponent(token)}`, {}, cancelToken, { "Content-Type": "application/json" });
532
+ if (!resp || !("access_token" in resp)) {
521
533
  if (resp.msg === "TOTP required") {
522
534
  return LoginResponse.NEEDS_TOTP;
523
535
  }
524
- throw new StandardError(401, 'Invalid Apple login response');
536
+ throw new StandardError(401, "Invalid Apple login response");
525
537
  }
526
538
  this.setAuthState(AuthState.AUTHENTICATED);
527
539
  this.socket.auth = { token: resp.access_token };
528
540
  if (!this.socket.connected) {
529
541
  this.socket.connect();
530
542
  }
531
- await this.config.authManager.setTokens({ access_token: resp.access_token, refresh_token: resp.refresh_token });
543
+ await this.config.authManager.setTokens({
544
+ access_token: resp.access_token,
545
+ refresh_token: resp.refresh_token,
546
+ });
532
547
  return LoginResponse.SUCCESS;
533
548
  },
534
549
  onSettled: () => {
@@ -536,8 +551,8 @@ export class AvroQueryClient {
536
551
  },
537
552
  onError: (err) => {
538
553
  this.config.authManager.clearCache();
539
- throw new StandardError(err.status, err.message || 'Apple Login failed');
540
- }
554
+ throw new StandardError(err.status, err.message || "Apple Login failed");
555
+ },
541
556
  });
542
557
  }
543
558
  setTokens(tokens) {
@@ -550,7 +565,15 @@ export class AvroQueryClient {
550
565
  return this.config.authManager.getCache(key);
551
566
  }
552
567
  setCompanyId(companyId) {
568
+ const previousId = this.companyId;
553
569
  this.companyId = companyId;
570
+ // If the socket is already connected, leave the old room and join the new one.
571
+ if (this.socket.connected) {
572
+ if (previousId && previousId !== companyId) {
573
+ this.socket.emit("leave_company", { company_id: previousId });
574
+ }
575
+ this.socket.emit("join_company", { company_id: companyId });
576
+ }
554
577
  return this.config.authManager.setCompanyId(companyId);
555
578
  }
556
579
  getCompanyId() {
@@ -576,10 +599,10 @@ export class AvroQueryClient {
576
599
  return () => this.offAuthStateChange(cb);
577
600
  }
578
601
  offAuthStateChange(cb) {
579
- this.authStateListeners = this.authStateListeners.filter(c => c !== cb);
602
+ this.authStateListeners = this.authStateListeners.filter((c) => c !== cb);
580
603
  }
581
604
  setAuthState(state) {
582
- this.authStateListeners.forEach(cb => cb(state));
605
+ this.authStateListeners.forEach((cb) => cb(state));
583
606
  this._authState = state;
584
607
  }
585
608
  getAuthState() {
@@ -591,13 +614,104 @@ export class AvroQueryClient {
591
614
  getQueryClient() {
592
615
  return useQueryClient();
593
616
  }
617
+ /**
618
+ * Fetch an entity from the API, optionally construct it, and surgically
619
+ * update all matching React-Query caches (individual + list + infinite).
620
+ *
621
+ * Shared by socket handlers and mutation `onSuccess` callbacks so the
622
+ * sender gets an immediate cache sync and everyone else gets the socket
623
+ * update — both use the identical code path.
624
+ *
625
+ * @returns The fetched (and optionally constructed) item, or `undefined`
626
+ * for deletes / entities without a fetchPath.
627
+ */
628
+ async _syncEntity(queryClient, params) {
629
+ const { action, entityKey, id, fetchPath, construct } = params;
630
+ const predicate = (q) => matchesEntityKey(q, entityKey);
631
+ const invalidate = () => queryClient.invalidateQueries({ predicate });
632
+ // ─── DELETE ─────────────────────────────────────────
633
+ if (action === "delete") {
634
+ queryClient.removeQueries({ queryKey: [entityKey, id], exact: true });
635
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
636
+ if (!old)
637
+ return old;
638
+ if (old.pages && Array.isArray(old.pages)) {
639
+ return {
640
+ ...old,
641
+ pages: old.pages.map((p) => p.filter((x) => x?.id !== id)),
642
+ };
643
+ }
644
+ if (Array.isArray(old)) {
645
+ return old.filter((x) => x?.id !== id);
646
+ }
647
+ return old;
648
+ });
649
+ return undefined;
650
+ }
651
+ // ─── CREATE / UPDATE ────────────────────────────────
652
+ if (!fetchPath) {
653
+ invalidate();
654
+ return undefined;
655
+ }
656
+ try {
657
+ const raw = await queryClient.fetchQuery({
658
+ queryKey: [entityKey, id],
659
+ queryFn: () => this.get({ path: fetchPath }),
660
+ staleTime: 0,
661
+ });
662
+ const item = construct ? construct(raw) : raw;
663
+ queryClient.setQueryData([entityKey, id], item);
664
+ if (action === "create") {
665
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
666
+ if (!old)
667
+ return old;
668
+ if (old.pages && Array.isArray(old.pages)) {
669
+ if (old.pages.some((p) => p.some((x) => x?.id === id)))
670
+ return old;
671
+ return {
672
+ ...old,
673
+ pages: [[item, ...(old.pages[0] || [])], ...old.pages.slice(1)],
674
+ };
675
+ }
676
+ if (Array.isArray(old)) {
677
+ if (old.some((x) => x?.id === id))
678
+ return old;
679
+ return [...old, item];
680
+ }
681
+ return old;
682
+ });
683
+ }
684
+ else {
685
+ // UPDATE — replace in every active list / infinite-query cache
686
+ queryClient.setQueriesData({ predicate, type: "active" }, (old) => {
687
+ if (!old)
688
+ return old;
689
+ if (old.pages && Array.isArray(old.pages)) {
690
+ return {
691
+ ...old,
692
+ pages: old.pages.map((page) => page.map((x) => (x?.id === id ? item : x))),
693
+ };
694
+ }
695
+ if (Array.isArray(old)) {
696
+ return old.map((x) => (x?.id === id ? item : x));
697
+ }
698
+ return old;
699
+ });
700
+ }
701
+ return item;
702
+ }
703
+ catch {
704
+ invalidate();
705
+ return undefined;
706
+ }
707
+ }
594
708
  useLogout() {
595
709
  const queryClient = this.getQueryClient();
596
710
  return useMutation({
597
711
  mutationFn: async (cancelToken) => {
598
712
  await this.post({
599
- path: '/logout',
600
- cancelToken
713
+ path: "/logout",
714
+ cancelToken,
601
715
  });
602
716
  await this.config.authManager.clearCache();
603
717
  if (this.socket && this.socket.connected) {
@@ -611,276 +725,276 @@ export class AvroQueryClient {
611
725
  },
612
726
  onError: (err) => {
613
727
  this.clearCache();
614
- console.error('Logout failed:', err);
615
- throw new StandardError(500, 'Logout failed');
616
- }
728
+ console.error("Logout failed:", err);
729
+ throw new StandardError(500, "Logout failed");
730
+ },
617
731
  });
618
732
  }
619
733
  fetchJobs(body = {}, companyId, cancelToken, headers = {}) {
620
734
  const companyIdToUse = companyId ?? this.companyId;
621
- if (!companyIdToUse || companyIdToUse.trim() === '') {
622
- throw new StandardError(400, 'Company ID is required');
735
+ if (!companyIdToUse || companyIdToUse.trim() === "") {
736
+ throw new StandardError(400, "Company ID is required");
623
737
  }
624
- return this._fetch('POST', `/company/${companyIdToUse}/jobs`, JSON.stringify(body), cancelToken, {
738
+ return this._fetch("POST", `/company/${companyIdToUse}/jobs`, JSON.stringify(body), cancelToken, {
625
739
  ...headers,
626
- 'Content-Type': 'application/json',
740
+ "Content-Type": "application/json",
627
741
  })
628
- .then(response => {
742
+ .then((response) => {
629
743
  if (!response || !Array.isArray(response)) {
630
- throw new StandardError(400, 'Invalid jobs response');
744
+ throw new StandardError(400, "Invalid jobs response");
631
745
  }
632
746
  return response;
633
747
  })
634
- .catch(err => {
635
- console.error('Failed to fetch jobs:', err);
748
+ .catch((err) => {
749
+ console.error("Failed to fetch jobs:", err);
636
750
  throw new StandardError(500, `Failed to fetch jobs: ${err.message ?? err}`);
637
751
  });
638
752
  }
639
753
  fetchChats(body = {}, cancelToken, headers = {}) {
640
- if (!this.companyId || this.companyId.trim() === '') {
641
- throw new StandardError(400, 'Company ID is required');
754
+ if (!this.companyId || this.companyId.trim() === "") {
755
+ throw new StandardError(400, "Company ID is required");
642
756
  }
643
- return this._fetch('POST', `/company/${this.companyId}/chats`, JSON.stringify(body), cancelToken, {
757
+ return this._fetch("POST", `/company/${this.companyId}/chats`, JSON.stringify(body), cancelToken, {
644
758
  ...headers,
645
- 'Content-Type': 'application/json',
759
+ "Content-Type": "application/json",
646
760
  })
647
- .then(response => {
761
+ .then((response) => {
648
762
  if (!response || !Array.isArray(response)) {
649
- throw new StandardError(400, 'Invalid chats response');
763
+ throw new StandardError(400, "Invalid chats response");
650
764
  }
651
765
  return response;
652
766
  })
653
- .catch(err => {
654
- console.error('Failed to fetch chats:', err);
655
- throw new StandardError(500, 'Failed to fetch chats');
767
+ .catch((err) => {
768
+ console.error("Failed to fetch chats:", err);
769
+ throw new StandardError(500, "Failed to fetch chats");
656
770
  });
657
771
  }
658
772
  fetchMessages(chatId, body = {}, cancelToken, headers = {}) {
659
- if (!chatId || chatId.trim() === '') {
660
- throw new StandardError(400, 'Chat ID is required');
773
+ if (!chatId || chatId.trim() === "") {
774
+ throw new StandardError(400, "Chat ID is required");
661
775
  }
662
- return this._fetch('POST', `/chat/${chatId}/messages`, JSON.stringify(body), cancelToken, {
776
+ return this._fetch("POST", `/chat/${chatId}/messages`, JSON.stringify(body), cancelToken, {
663
777
  ...headers,
664
- 'Content-Type': 'application/json',
778
+ "Content-Type": "application/json",
665
779
  })
666
- .then(response => {
780
+ .then((response) => {
667
781
  if (!response || !Array.isArray(response)) {
668
- throw new StandardError(400, 'Invalid messages response');
782
+ throw new StandardError(400, "Invalid messages response");
669
783
  }
670
784
  return response;
671
785
  })
672
- .catch(err => {
673
- console.error('Failed to fetch messages:', err);
674
- throw new StandardError(500, 'Failed to fetch messages');
786
+ .catch((err) => {
787
+ console.error("Failed to fetch messages:", err);
788
+ throw new StandardError(500, "Failed to fetch messages");
675
789
  });
676
790
  }
677
791
  async fetchPrepayments(body = {}, cancelToken, headers = {}) {
678
- if (!this.companyId || this.companyId.trim() === '') {
679
- throw new StandardError(400, 'Company ID is required');
792
+ if (!this.companyId || this.companyId.trim() === "") {
793
+ throw new StandardError(400, "Company ID is required");
680
794
  }
681
- return this._fetch('POST', `/company/${this.companyId}/prepayments`, JSON.stringify(body), cancelToken, {
795
+ return this._fetch("POST", `/company/${this.companyId}/prepayments`, JSON.stringify(body), cancelToken, {
682
796
  ...headers,
683
- 'Content-Type': 'application/json',
797
+ "Content-Type": "application/json",
684
798
  })
685
- .then(response => {
799
+ .then((response) => {
686
800
  if (!response || !Array.isArray(response)) {
687
- throw new StandardError(400, 'Invalid prepayments response');
801
+ throw new StandardError(400, "Invalid prepayments response");
688
802
  }
689
803
  return response;
690
804
  })
691
- .catch(err => {
692
- console.error('Failed to fetch prepayments:', err);
693
- throw new StandardError(500, 'Failed to fetch prepayments');
805
+ .catch((err) => {
806
+ console.error("Failed to fetch prepayments:", err);
807
+ throw new StandardError(500, "Failed to fetch prepayments");
694
808
  });
695
809
  }
696
810
  async fetchWaivers(body = {}, cancelToken, headers = {}) {
697
- if (!this.companyId || this.companyId.trim() === '') {
698
- throw new StandardError(400, 'Company ID is required');
811
+ if (!this.companyId || this.companyId.trim() === "") {
812
+ throw new StandardError(400, "Company ID is required");
699
813
  }
700
- return this._fetch('POST', `/company/${this.companyId}/waivers`, JSON.stringify(body), cancelToken, {
814
+ return this._fetch("POST", `/company/${this.companyId}/waivers`, JSON.stringify(body), cancelToken, {
701
815
  ...headers,
702
- 'Content-Type': 'application/json',
816
+ "Content-Type": "application/json",
703
817
  })
704
- .then(response => {
818
+ .then((response) => {
705
819
  if (!response || !Array.isArray(response)) {
706
- throw new StandardError(400, 'Invalid waivers response');
820
+ throw new StandardError(400, "Invalid waivers response");
707
821
  }
708
822
  return response;
709
823
  })
710
- .catch(err => {
711
- console.error('Failed to fetch waivers:', err);
712
- throw new StandardError(500, 'Failed to fetch waivers');
824
+ .catch((err) => {
825
+ console.error("Failed to fetch waivers:", err);
826
+ throw new StandardError(500, "Failed to fetch waivers");
713
827
  });
714
828
  }
715
829
  async fetchEvents(body = {}, cancelToken, headers = {}) {
716
- if (!this.companyId || this.companyId.trim() === '') {
717
- throw new StandardError(400, 'Company ID is required');
830
+ if (!this.companyId || this.companyId.trim() === "") {
831
+ throw new StandardError(400, "Company ID is required");
718
832
  }
719
- return this._fetch('POST', `/company/${this.companyId}/events`, JSON.stringify(body), cancelToken, {
833
+ return this._fetch("POST", `/company/${this.companyId}/events`, JSON.stringify(body), cancelToken, {
720
834
  ...headers,
721
- 'Content-Type': 'application/json',
835
+ "Content-Type": "application/json",
722
836
  })
723
- .then(response => {
837
+ .then((response) => {
724
838
  if (!response || !Array.isArray(response)) {
725
- throw new StandardError(400, 'Invalid events response');
839
+ throw new StandardError(400, "Invalid events response");
726
840
  }
727
841
  return response;
728
842
  })
729
- .catch(err => {
730
- console.error('Failed to fetch events:', err);
731
- throw new StandardError(500, 'Failed to fetch events');
843
+ .catch((err) => {
844
+ console.error("Failed to fetch events:", err);
845
+ throw new StandardError(500, "Failed to fetch events");
732
846
  });
733
847
  }
734
848
  fetchMonths(body = {}, cancelToken, headers = {}) {
735
- if (!this.companyId || this.companyId.trim() === '') {
736
- throw new StandardError(400, 'Company ID is required');
849
+ if (!this.companyId || this.companyId.trim() === "") {
850
+ throw new StandardError(400, "Company ID is required");
737
851
  }
738
- return this._fetch('POST', `/company/${this.companyId}/months`, JSON.stringify(body), cancelToken, {
852
+ return this._fetch("POST", `/company/${this.companyId}/months`, JSON.stringify(body), cancelToken, {
739
853
  ...headers,
740
- 'Content-Type': 'application/json',
854
+ "Content-Type": "application/json",
741
855
  })
742
- .then(response => {
856
+ .then((response) => {
743
857
  if (!response || !Array.isArray(response)) {
744
- throw new StandardError(400, 'Invalid months response');
858
+ throw new StandardError(400, "Invalid months response");
745
859
  }
746
860
  return response;
747
861
  })
748
- .catch(err => {
749
- console.error('Failed to fetch months:', err);
750
- throw new StandardError(500, 'Failed to fetch months');
862
+ .catch((err) => {
863
+ console.error("Failed to fetch months:", err);
864
+ throw new StandardError(500, "Failed to fetch months");
751
865
  });
752
866
  }
753
867
  fetchBills(body = {}, cancelToken, headers = {}) {
754
- if (!this.companyId || this.companyId.trim() === '') {
755
- throw new StandardError(400, 'Company ID is required');
868
+ if (!this.companyId || this.companyId.trim() === "") {
869
+ throw new StandardError(400, "Company ID is required");
756
870
  }
757
- return this._fetch('POST', `/company/${this.companyId}/bills`, JSON.stringify(body), cancelToken, {
871
+ return this._fetch("POST", `/company/${this.companyId}/bills`, JSON.stringify(body), cancelToken, {
758
872
  ...headers,
759
- 'Content-Type': 'application/json',
873
+ "Content-Type": "application/json",
760
874
  })
761
- .then(response => {
875
+ .then((response) => {
762
876
  if (!response || !Array.isArray(response)) {
763
- throw new StandardError(400, 'Invalid bills response');
877
+ throw new StandardError(400, "Invalid bills response");
764
878
  }
765
879
  return response;
766
880
  })
767
- .catch(err => {
768
- console.error('Failed to fetch bills:', err);
769
- throw new StandardError(500, 'Failed to fetch bills');
881
+ .catch((err) => {
882
+ console.error("Failed to fetch bills:", err);
883
+ throw new StandardError(500, "Failed to fetch bills");
770
884
  });
771
885
  }
772
886
  fetchRoutes(body = {}, cancelToken, headers = {}) {
773
- if (!this.companyId || this.companyId.trim() === '') {
774
- throw new StandardError(400, 'Company ID is required');
887
+ if (!this.companyId || this.companyId.trim() === "") {
888
+ throw new StandardError(400, "Company ID is required");
775
889
  }
776
- return this._fetch('POST', `/company/${this.companyId}/routes`, JSON.stringify(body), cancelToken, {
890
+ return this._fetch("POST", `/company/${this.companyId}/routes`, JSON.stringify(body), cancelToken, {
777
891
  ...headers,
778
- 'Content-Type': 'application/json',
892
+ "Content-Type": "application/json",
779
893
  })
780
- .then(response => {
894
+ .then((response) => {
781
895
  if (!response || !Array.isArray(response)) {
782
- throw new StandardError(400, 'Invalid routes response');
896
+ throw new StandardError(400, "Invalid routes response");
783
897
  }
784
898
  return response;
785
899
  })
786
- .catch(err => {
787
- console.error('Failed to fetch routes:', err);
788
- throw new StandardError(500, 'Failed to fetch routes');
900
+ .catch((err) => {
901
+ console.error("Failed to fetch routes:", err);
902
+ throw new StandardError(500, "Failed to fetch routes");
789
903
  });
790
904
  }
791
905
  fetchTeams(body = {}, cancelToken, headers = {}) {
792
- if (!this.companyId || this.companyId.trim() === '') {
793
- throw new StandardError(400, 'Company ID is required');
906
+ if (!this.companyId || this.companyId.trim() === "") {
907
+ throw new StandardError(400, "Company ID is required");
794
908
  }
795
- return this._fetch('POST', `/company/${this.companyId}/teams`, JSON.stringify(body), cancelToken, {
909
+ return this._fetch("POST", `/company/${this.companyId}/teams`, JSON.stringify(body), cancelToken, {
796
910
  ...headers,
797
- 'Content-Type': 'application/json',
911
+ "Content-Type": "application/json",
798
912
  })
799
- .then(response => {
913
+ .then((response) => {
800
914
  if (!response || !Array.isArray(response)) {
801
- throw new StandardError(400, 'Invalid teams response');
915
+ throw new StandardError(400, "Invalid teams response");
802
916
  }
803
917
  return response;
804
918
  })
805
- .catch(err => {
806
- console.error('Failed to fetch teams:', err);
807
- throw new StandardError(500, 'Failed to fetch teams');
919
+ .catch((err) => {
920
+ console.error("Failed to fetch teams:", err);
921
+ throw new StandardError(500, "Failed to fetch teams");
808
922
  });
809
923
  }
810
924
  fetchLabels(body = {}, cancelToken, headers = {}) {
811
- if (!this.companyId || this.companyId.trim() === '') {
812
- throw new StandardError(400, 'Company ID is required');
925
+ if (!this.companyId || this.companyId.trim() === "") {
926
+ throw new StandardError(400, "Company ID is required");
813
927
  }
814
- return this._fetch('POST', `/company/${this.companyId}/labels`, JSON.stringify(body), cancelToken, {
928
+ return this._fetch("POST", `/company/${this.companyId}/labels`, JSON.stringify(body), cancelToken, {
815
929
  ...headers,
816
- 'Content-Type': 'application/json',
930
+ "Content-Type": "application/json",
817
931
  })
818
- .then(response => {
932
+ .then((response) => {
819
933
  if (!response || !Array.isArray(response)) {
820
- throw new StandardError(400, 'Invalid labels response');
934
+ throw new StandardError(400, "Invalid labels response");
821
935
  }
822
936
  return response;
823
937
  })
824
- .catch(err => {
825
- console.error('Failed to fetch labels:', err);
826
- throw new StandardError(500, 'Failed to fetch labels');
938
+ .catch((err) => {
939
+ console.error("Failed to fetch labels:", err);
940
+ throw new StandardError(500, "Failed to fetch labels");
827
941
  });
828
942
  }
829
943
  fetchGroups(body = {}, cancelToken, headers = {}) {
830
- if (!this.companyId || this.companyId.trim() === '') {
831
- throw new StandardError(400, 'Company ID is required');
944
+ if (!this.companyId || this.companyId.trim() === "") {
945
+ throw new StandardError(400, "Company ID is required");
832
946
  }
833
- return this._fetch('POST', `/company/${this.companyId}/groups`, JSON.stringify(body), cancelToken, {
947
+ return this._fetch("POST", `/company/${this.companyId}/groups`, JSON.stringify(body), cancelToken, {
834
948
  ...headers,
835
- 'Content-Type': 'application/json',
949
+ "Content-Type": "application/json",
836
950
  })
837
- .then(response => {
951
+ .then((response) => {
838
952
  if (!response || !Array.isArray(response)) {
839
- throw new StandardError(400, 'Invalid groups response');
953
+ throw new StandardError(400, "Invalid groups response");
840
954
  }
841
955
  return response;
842
956
  })
843
- .catch(err => {
844
- console.error('Failed to fetch groups:', err);
845
- throw new StandardError(500, 'Failed to fetch groups');
957
+ .catch((err) => {
958
+ console.error("Failed to fetch groups:", err);
959
+ throw new StandardError(500, "Failed to fetch groups");
846
960
  });
847
961
  }
848
962
  fetchSkills(body = {}, cancelToken, headers = {}) {
849
- if (!this.companyId || this.companyId.trim() === '') {
850
- throw new StandardError(400, 'Company ID is required');
963
+ if (!this.companyId || this.companyId.trim() === "") {
964
+ throw new StandardError(400, "Company ID is required");
851
965
  }
852
- return this._fetch('POST', `/company/${this.companyId}/skills`, JSON.stringify(body), cancelToken, {
966
+ return this._fetch("POST", `/company/${this.companyId}/skills`, JSON.stringify(body), cancelToken, {
853
967
  ...headers,
854
- 'Content-Type': 'application/json',
968
+ "Content-Type": "application/json",
855
969
  })
856
- .then(response => {
970
+ .then((response) => {
857
971
  if (!response || !Array.isArray(response)) {
858
- throw new StandardError(400, 'Invalid skills response');
972
+ throw new StandardError(400, "Invalid skills response");
859
973
  }
860
974
  return response;
861
975
  })
862
- .catch(err => {
863
- console.error('Failed to fetch skills:', err);
864
- throw new StandardError(500, 'Failed to fetch skills');
976
+ .catch((err) => {
977
+ console.error("Failed to fetch skills:", err);
978
+ throw new StandardError(500, "Failed to fetch skills");
865
979
  });
866
980
  }
867
981
  fetchSessions(body = {}, cancelToken, headers = {}) {
868
- if (!this.companyId || this.companyId.trim() === '') {
869
- throw new StandardError(400, 'Company ID is required');
982
+ if (!this.companyId || this.companyId.trim() === "") {
983
+ throw new StandardError(400, "Company ID is required");
870
984
  }
871
- return this._fetch('POST', `/company/${this.companyId}/sessions`, JSON.stringify(body), cancelToken, {
985
+ return this._fetch("POST", `/company/${this.companyId}/sessions`, JSON.stringify(body), cancelToken, {
872
986
  ...headers,
873
- 'Content-Type': 'application/json',
987
+ "Content-Type": "application/json",
874
988
  })
875
- .then(response => {
989
+ .then((response) => {
876
990
  if (!response || !Array.isArray(response)) {
877
- throw new StandardError(400, 'Invalid sessions response');
991
+ throw new StandardError(400, "Invalid sessions response");
878
992
  }
879
993
  return response;
880
994
  })
881
- .catch(err => {
882
- console.error('Failed to fetch sessions:', err);
883
- throw new StandardError(500, 'Failed to fetch sessions');
995
+ .catch((err) => {
996
+ console.error("Failed to fetch sessions:", err);
997
+ throw new StandardError(500, "Failed to fetch sessions");
884
998
  });
885
999
  }
886
1000
  /* ── Email delivery tracking ──────────────────────────────────────── */
@@ -945,7 +1059,7 @@ export class AvroQueryClient {
945
1059
  return this.post({
946
1060
  path: `/email/${emailId}`,
947
1061
  data: formData,
948
- progressUpdateCallback
1062
+ progressUpdateCallback,
949
1063
  });
950
1064
  }
951
1065
  catch (error) {
@@ -957,7 +1071,7 @@ export class AvroQueryClient {
957
1071
  return this.post({
958
1072
  path: `/bill/${billId}/email`,
959
1073
  data: JSON.stringify(body),
960
- headers: { 'Content-Type': 'application/json' }
1074
+ headers: { "Content-Type": "application/json" },
961
1075
  });
962
1076
  }
963
1077
  catch (error) {