@go-avro/avro-js 0.0.37 → 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.
- package/dist/auth/AuthManager.d.ts +2 -2
- package/dist/auth/AuthManager.js +37 -32
- package/dist/auth/storage.d.ts +1 -1
- package/dist/auth/storage.js +6 -6
- package/dist/client/AvroQueryClientProvider.js +1 -1
- package/dist/client/QueryClient.d.ts +35 -17
- package/dist/client/QueryClient.js +486 -389
- package/dist/client/core/fetch.js +16 -11
- package/dist/client/core/utils.js +5 -5
- package/dist/client/core/xhr.js +28 -23
- package/dist/client/hooks/analytics.js +14 -14
- package/dist/client/hooks/avro.js +2 -2
- package/dist/client/hooks/bills.js +66 -30
- package/dist/client/hooks/catalog_items.js +57 -22
- package/dist/client/hooks/chats.js +4 -4
- package/dist/client/hooks/companies.js +96 -39
- package/dist/client/hooks/email.js +1 -1
- package/dist/client/hooks/events.js +174 -63
- package/dist/client/hooks/groups.js +37 -22
- package/dist/client/hooks/jobs.js +69 -18
- package/dist/client/hooks/labels.js +36 -21
- package/dist/client/hooks/messages.js +9 -6
- package/dist/client/hooks/months.js +42 -22
- package/dist/client/hooks/plans.js +2 -2
- package/dist/client/hooks/prepayments.js +42 -22
- package/dist/client/hooks/proposal.js +21 -5
- package/dist/client/hooks/root.js +4 -4
- package/dist/client/hooks/routes.js +77 -32
- package/dist/client/hooks/sessions.js +66 -34
- package/dist/client/hooks/skills.js +33 -18
- package/dist/client/hooks/teams.js +36 -21
- package/dist/client/hooks/timecards.js +6 -0
- package/dist/client/hooks/users.js +61 -29
- package/dist/client/hooks/waivers.js +41 -19
- package/dist/index.d.ts +38 -38
- package/dist/index.js +37 -37
- package/dist/types/api/Bill.d.ts +1 -1
- package/dist/types/api/Bill.js +1 -1
- package/dist/types/api/Job.d.ts +1 -1
- package/dist/types/api/Job.js +14 -14
- package/dist/types/api/LineItem.d.ts +3 -3
- package/dist/types/api/LineItem.js +5 -2
- package/dist/types/api/PaymentType.d.ts +1 -1
- package/dist/types/api/Prepayment.d.ts +1 -1
- package/dist/types/api/Route.d.ts +3 -3
- package/dist/types/api/Route.js +4 -2
- package/dist/types/api/RouteJob.d.ts +1 -1
- package/dist/types/api/Task.d.ts +2 -2
- package/dist/types/api/Task.js +12 -7
- package/dist/types/api/Timecard.d.ts +1 -1
- package/dist/types/api/TimecardAction.d.ts +1 -1
- package/dist/types/api/UserCompanyAssociation.d.ts +2 -2
- package/dist/types/api/UserCompanyAssociation.js +1 -1
- package/dist/types/api/_Event.d.ts +1 -1
- package/dist/types/api/_Event.js +1 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/types/auth.d.ts +1 -1
- package/dist/types/client.d.ts +1 -1
- package/package.json +3 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import io from
|
|
2
|
-
import { useMutation, useQueryClient } from
|
|
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
|
|
5
|
-
import { AuthState } from
|
|
6
|
-
import { StandardError } from
|
|
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
|
|
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: {
|
|
22
|
-
|
|
23
|
-
|
|
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: [[
|
|
26
|
-
update_users: { invalidateKeys: [[
|
|
33
|
+
user_updated: { invalidateKeys: [["users"], ["user"]] },
|
|
34
|
+
update_users: { invalidateKeys: [["users"], ["user"]] },
|
|
27
35
|
// ── Jobs ──
|
|
28
|
-
create_job: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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: {
|
|
35
|
-
|
|
36
|
-
|
|
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: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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:
|
|
44
|
-
update_team: { entityKey:
|
|
45
|
-
delete_team: { entityKey:
|
|
46
|
-
update_teams: { invalidateKeys: [[
|
|
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: {
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
53
|
-
update_session: { entityKey:
|
|
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: {
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
60
|
-
update_group: { entityKey:
|
|
61
|
-
delete_group: { entityKey:
|
|
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:
|
|
64
|
-
update_label: { entityKey:
|
|
65
|
-
delete_label: { entityKey:
|
|
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:
|
|
68
|
-
update_skill: { entityKey:
|
|
69
|
-
delete_skill: { entityKey:
|
|
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: {
|
|
72
|
-
|
|
73
|
-
|
|
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:
|
|
76
|
-
update_months: { invalidateKeys: [[
|
|
77
|
-
delete_months: { invalidateKeys: [[
|
|
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: {
|
|
80
|
-
|
|
81
|
-
|
|
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: {
|
|
191
|
+
schedule_complete: {
|
|
192
|
+
invalidateKeys: [["routes"], ["jobs"], ["infinite", "jobs"]],
|
|
193
|
+
},
|
|
84
194
|
// ── Location ──
|
|
85
|
-
location_update: { invalidateKeys: [[
|
|
195
|
+
location_update: { invalidateKeys: [["teams"]] },
|
|
86
196
|
// ── Prepayments ──
|
|
87
|
-
update_prepayments: { invalidateKeys: [[
|
|
197
|
+
update_prepayments: { invalidateKeys: [["prepayments"]] },
|
|
88
198
|
// ── Chats ──
|
|
89
|
-
new_message: { invalidateKeys: [[
|
|
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] ===
|
|
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 ??
|
|
223
|
+
retryStrategy: config.retryStrategy ?? "fixed",
|
|
114
224
|
timeout: config.timeout ?? 0,
|
|
115
225
|
};
|
|
116
|
-
this.socket = io(config.baseUrl, {
|
|
117
|
-
|
|
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
|
|
124
|
-
|
|
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
|
-
})
|
|
128
|
-
|
|
242
|
+
})
|
|
243
|
+
.catch((err) => {
|
|
244
|
+
console.error("Not logged in:", err);
|
|
129
245
|
});
|
|
130
246
|
}
|
|
131
247
|
});
|
|
132
|
-
this.socket.on(
|
|
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(
|
|
252
|
+
this.socket.on("disconnect", (reason) => {
|
|
137
253
|
console.log(`Socket disconnected: ${reason}`);
|
|
138
254
|
});
|
|
139
|
-
this.socket.on(
|
|
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(
|
|
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(
|
|
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({
|
|
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,160 +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 ??
|
|
329
|
+
const id = data?.[idField ?? "id"];
|
|
212
330
|
// No id → old backend or malformed payload → full invalidation
|
|
213
|
-
if (!id || typeof id !==
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
}
|
|
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
|
+
});
|
|
326
345
|
// Invalidate any additional keys (e.g. companies → /company/list)
|
|
327
346
|
alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
|
|
328
347
|
// Refetch & patch related entities (e.g. event → parent job)
|
|
329
348
|
if (relatedRefetch) {
|
|
330
349
|
for (const related of relatedRefetch) {
|
|
331
350
|
const relatedId = data?.[related.idField] ?? fetchedItem?.[related.idField];
|
|
332
|
-
if (!relatedId || typeof relatedId !==
|
|
351
|
+
if (!relatedId || typeof relatedId !== "string")
|
|
333
352
|
continue;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
}
|
|
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
|
+
});
|
|
363
360
|
}
|
|
364
361
|
}
|
|
365
362
|
};
|
|
@@ -370,11 +367,11 @@ export class AvroQueryClient {
|
|
|
370
367
|
// Auto join/leave company room
|
|
371
368
|
const joinCompanyRoom = () => {
|
|
372
369
|
if (this.companyId) {
|
|
373
|
-
this.socket.emit(
|
|
370
|
+
this.socket.emit("join_company", { company_id: this.companyId });
|
|
374
371
|
}
|
|
375
372
|
};
|
|
376
|
-
this.socket.on(
|
|
377
|
-
handlers.push({ event:
|
|
373
|
+
this.socket.on("connect", joinCompanyRoom);
|
|
374
|
+
handlers.push({ event: "connect", handler: joinCompanyRoom });
|
|
378
375
|
// If already connected, join immediately
|
|
379
376
|
if (this.socket.connected && this.companyId) {
|
|
380
377
|
joinCompanyRoom();
|
|
@@ -385,7 +382,7 @@ export class AvroQueryClient {
|
|
|
385
382
|
}
|
|
386
383
|
// Leave company room on teardown
|
|
387
384
|
if (this.socket.connected && this.companyId) {
|
|
388
|
-
this.socket.emit(
|
|
385
|
+
this.socket.emit("leave_company", { company_id: this.companyId });
|
|
389
386
|
}
|
|
390
387
|
this._queryClient = null;
|
|
391
388
|
};
|
|
@@ -397,17 +394,17 @@ export class AvroQueryClient {
|
|
|
397
394
|
this._socketInvalidationCleanup?.();
|
|
398
395
|
this._socketInvalidationCleanup = null;
|
|
399
396
|
}
|
|
400
|
-
get({ path, cancelToken, headers, progressUpdateCallback }) {
|
|
401
|
-
return this._xhr(
|
|
397
|
+
get({ path, cancelToken, headers, progressUpdateCallback, }) {
|
|
398
|
+
return this._xhr("GET", path, null, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
|
|
402
399
|
}
|
|
403
|
-
post({ path, data, cancelToken, headers, progressUpdateCallback }) {
|
|
404
|
-
return this._xhr(
|
|
400
|
+
post({ path, data, cancelToken, headers, progressUpdateCallback, }) {
|
|
401
|
+
return this._xhr("POST", path, data, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
|
|
405
402
|
}
|
|
406
|
-
put({ path, data, cancelToken, headers, progressUpdateCallback }) {
|
|
407
|
-
return this._xhr(
|
|
403
|
+
put({ path, data, cancelToken, headers, progressUpdateCallback, }) {
|
|
404
|
+
return this._xhr("PUT", path, data, cancelToken, headers, true, this.config.maxRetries, progressUpdateCallback);
|
|
408
405
|
}
|
|
409
|
-
delete({ path, cancelToken, headers, progressUpdateCallback }) {
|
|
410
|
-
return this._xhr(
|
|
406
|
+
delete({ path, cancelToken, headers, progressUpdateCallback, }) {
|
|
407
|
+
return this._xhr("DELETE", path, null, cancelToken, headers, false, this.config.maxRetries, progressUpdateCallback);
|
|
411
408
|
}
|
|
412
409
|
loginSuccess(tokens) {
|
|
413
410
|
this.setAuthState(AuthState.AUTHENTICATED);
|
|
@@ -420,20 +417,23 @@ export class AvroQueryClient {
|
|
|
420
417
|
useLogin() {
|
|
421
418
|
const queryClient = this.getQueryClient();
|
|
422
419
|
return useMutation({
|
|
423
|
-
mutationFn: async ({ username, password, code, cancelToken }) => {
|
|
420
|
+
mutationFn: async ({ username, password, code, cancelToken, }) => {
|
|
424
421
|
const resp = await this.post({
|
|
425
|
-
path:
|
|
422
|
+
path: "/login",
|
|
426
423
|
data: JSON.stringify({ username, password, code }),
|
|
427
424
|
cancelToken,
|
|
428
|
-
headers: {
|
|
425
|
+
headers: { "Content-Type": "application/json" },
|
|
429
426
|
});
|
|
430
|
-
if (!resp || !(
|
|
427
|
+
if (!resp || !("access_token" in resp)) {
|
|
431
428
|
if (resp.msg === "TOTP required") {
|
|
432
429
|
return LoginResponse.NEEDS_TOTP;
|
|
433
430
|
}
|
|
434
|
-
throw new StandardError(401,
|
|
431
|
+
throw new StandardError(401, "Invalid login response");
|
|
435
432
|
}
|
|
436
|
-
await this.loginSuccess({
|
|
433
|
+
await this.loginSuccess({
|
|
434
|
+
access_token: resp.access_token,
|
|
435
|
+
refresh_token: resp.refresh_token,
|
|
436
|
+
});
|
|
437
437
|
return LoginResponse.SUCCESS;
|
|
438
438
|
},
|
|
439
439
|
onSettled: () => {
|
|
@@ -441,19 +441,19 @@ export class AvroQueryClient {
|
|
|
441
441
|
},
|
|
442
442
|
onError: (err) => {
|
|
443
443
|
this.config.authManager.clearCache();
|
|
444
|
-
throw new StandardError(401, err.message ||
|
|
445
|
-
}
|
|
444
|
+
throw new StandardError(401, err.message || "Login failed");
|
|
445
|
+
},
|
|
446
446
|
});
|
|
447
447
|
}
|
|
448
448
|
useRequestCode() {
|
|
449
449
|
const queryClient = this.getQueryClient();
|
|
450
450
|
return useMutation({
|
|
451
|
-
mutationFn: async ({ username, email, cancelToken }) => {
|
|
451
|
+
mutationFn: async ({ username, email, cancelToken, }) => {
|
|
452
452
|
const resp = await this.post({
|
|
453
|
-
path:
|
|
453
|
+
path: "/code",
|
|
454
454
|
data: JSON.stringify({ username, email }),
|
|
455
455
|
cancelToken,
|
|
456
|
-
headers: {
|
|
456
|
+
headers: { "Content-Type": "application/json" },
|
|
457
457
|
});
|
|
458
458
|
return resp;
|
|
459
459
|
},
|
|
@@ -461,46 +461,49 @@ export class AvroQueryClient {
|
|
|
461
461
|
queryClient.invalidateQueries();
|
|
462
462
|
},
|
|
463
463
|
onError: (err) => {
|
|
464
|
-
throw new StandardError(err.status, err.message ||
|
|
465
|
-
}
|
|
464
|
+
throw new StandardError(err.status, err.message || "Request code failed");
|
|
465
|
+
},
|
|
466
466
|
});
|
|
467
467
|
}
|
|
468
468
|
useUpdatePassword() {
|
|
469
469
|
const queryClient = this.getQueryClient();
|
|
470
470
|
return useMutation({
|
|
471
|
-
mutationFn: async ({ username, email, code, newPassword, cancelToken }) => {
|
|
471
|
+
mutationFn: async ({ username, email, code, newPassword, cancelToken, }) => {
|
|
472
472
|
await this.post({
|
|
473
473
|
path: `/user/${username ?? email}/password`,
|
|
474
474
|
data: JSON.stringify({ code, password: newPassword }),
|
|
475
475
|
cancelToken,
|
|
476
|
-
headers: {
|
|
476
|
+
headers: { "Content-Type": "application/json" },
|
|
477
477
|
});
|
|
478
478
|
},
|
|
479
479
|
onSettled: () => {
|
|
480
480
|
queryClient.invalidateQueries();
|
|
481
481
|
},
|
|
482
482
|
onError: (err) => {
|
|
483
|
-
throw new StandardError(err.status, err.message ||
|
|
484
|
-
}
|
|
483
|
+
throw new StandardError(err.status, err.message || "Update password failed");
|
|
484
|
+
},
|
|
485
485
|
});
|
|
486
486
|
}
|
|
487
487
|
useGoogleLogin() {
|
|
488
488
|
const queryClient = this.getQueryClient();
|
|
489
489
|
return useMutation({
|
|
490
|
-
mutationFn: async ({ token, cancelToken }) => {
|
|
491
|
-
const resp = await this._xhr(
|
|
492
|
-
if (!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)) {
|
|
493
493
|
if (resp.msg === "TOTP required") {
|
|
494
494
|
return LoginResponse.NEEDS_TOTP;
|
|
495
495
|
}
|
|
496
|
-
throw new StandardError(401,
|
|
496
|
+
throw new StandardError(401, "Invalid Google login response");
|
|
497
497
|
}
|
|
498
498
|
this.setAuthState(AuthState.AUTHENTICATED);
|
|
499
499
|
this.socket.auth = { token: resp.access_token };
|
|
500
500
|
if (!this.socket.connected) {
|
|
501
501
|
this.socket.connect();
|
|
502
502
|
}
|
|
503
|
-
await this.config.authManager.setTokens({
|
|
503
|
+
await this.config.authManager.setTokens({
|
|
504
|
+
access_token: resp.access_token,
|
|
505
|
+
refresh_token: resp.refresh_token,
|
|
506
|
+
});
|
|
504
507
|
return LoginResponse.SUCCESS;
|
|
505
508
|
},
|
|
506
509
|
onSettled: () => {
|
|
@@ -508,27 +511,30 @@ export class AvroQueryClient {
|
|
|
508
511
|
},
|
|
509
512
|
onError: (err) => {
|
|
510
513
|
this.config.authManager.clearCache();
|
|
511
|
-
throw new StandardError(err.status, err.message ||
|
|
512
|
-
}
|
|
514
|
+
throw new StandardError(err.status, err.message || "Google Login failed");
|
|
515
|
+
},
|
|
513
516
|
});
|
|
514
517
|
}
|
|
515
518
|
useAppleLogin() {
|
|
516
519
|
const queryClient = this.getQueryClient();
|
|
517
520
|
return useMutation({
|
|
518
|
-
mutationFn: async ({ token, cancelToken }) => {
|
|
519
|
-
const resp = await this._xhr(
|
|
520
|
-
if (!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)) {
|
|
521
524
|
if (resp.msg === "TOTP required") {
|
|
522
525
|
return LoginResponse.NEEDS_TOTP;
|
|
523
526
|
}
|
|
524
|
-
throw new StandardError(401,
|
|
527
|
+
throw new StandardError(401, "Invalid Apple login response");
|
|
525
528
|
}
|
|
526
529
|
this.setAuthState(AuthState.AUTHENTICATED);
|
|
527
530
|
this.socket.auth = { token: resp.access_token };
|
|
528
531
|
if (!this.socket.connected) {
|
|
529
532
|
this.socket.connect();
|
|
530
533
|
}
|
|
531
|
-
await this.config.authManager.setTokens({
|
|
534
|
+
await this.config.authManager.setTokens({
|
|
535
|
+
access_token: resp.access_token,
|
|
536
|
+
refresh_token: resp.refresh_token,
|
|
537
|
+
});
|
|
532
538
|
return LoginResponse.SUCCESS;
|
|
533
539
|
},
|
|
534
540
|
onSettled: () => {
|
|
@@ -536,8 +542,8 @@ export class AvroQueryClient {
|
|
|
536
542
|
},
|
|
537
543
|
onError: (err) => {
|
|
538
544
|
this.config.authManager.clearCache();
|
|
539
|
-
throw new StandardError(err.status, err.message ||
|
|
540
|
-
}
|
|
545
|
+
throw new StandardError(err.status, err.message || "Apple Login failed");
|
|
546
|
+
},
|
|
541
547
|
});
|
|
542
548
|
}
|
|
543
549
|
setTokens(tokens) {
|
|
@@ -576,10 +582,10 @@ export class AvroQueryClient {
|
|
|
576
582
|
return () => this.offAuthStateChange(cb);
|
|
577
583
|
}
|
|
578
584
|
offAuthStateChange(cb) {
|
|
579
|
-
this.authStateListeners = this.authStateListeners.filter(c => c !== cb);
|
|
585
|
+
this.authStateListeners = this.authStateListeners.filter((c) => c !== cb);
|
|
580
586
|
}
|
|
581
587
|
setAuthState(state) {
|
|
582
|
-
this.authStateListeners.forEach(cb => cb(state));
|
|
588
|
+
this.authStateListeners.forEach((cb) => cb(state));
|
|
583
589
|
this._authState = state;
|
|
584
590
|
}
|
|
585
591
|
getAuthState() {
|
|
@@ -591,13 +597,104 @@ export class AvroQueryClient {
|
|
|
591
597
|
getQueryClient() {
|
|
592
598
|
return useQueryClient();
|
|
593
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
|
+
}
|
|
594
691
|
useLogout() {
|
|
595
692
|
const queryClient = this.getQueryClient();
|
|
596
693
|
return useMutation({
|
|
597
694
|
mutationFn: async (cancelToken) => {
|
|
598
695
|
await this.post({
|
|
599
|
-
path:
|
|
600
|
-
cancelToken
|
|
696
|
+
path: "/logout",
|
|
697
|
+
cancelToken,
|
|
601
698
|
});
|
|
602
699
|
await this.config.authManager.clearCache();
|
|
603
700
|
if (this.socket && this.socket.connected) {
|
|
@@ -611,276 +708,276 @@ export class AvroQueryClient {
|
|
|
611
708
|
},
|
|
612
709
|
onError: (err) => {
|
|
613
710
|
this.clearCache();
|
|
614
|
-
console.error(
|
|
615
|
-
throw new StandardError(500,
|
|
616
|
-
}
|
|
711
|
+
console.error("Logout failed:", err);
|
|
712
|
+
throw new StandardError(500, "Logout failed");
|
|
713
|
+
},
|
|
617
714
|
});
|
|
618
715
|
}
|
|
619
716
|
fetchJobs(body = {}, companyId, cancelToken, headers = {}) {
|
|
620
717
|
const companyIdToUse = companyId ?? this.companyId;
|
|
621
|
-
if (!companyIdToUse || companyIdToUse.trim() ===
|
|
622
|
-
throw new StandardError(400,
|
|
718
|
+
if (!companyIdToUse || companyIdToUse.trim() === "") {
|
|
719
|
+
throw new StandardError(400, "Company ID is required");
|
|
623
720
|
}
|
|
624
|
-
return this._fetch(
|
|
721
|
+
return this._fetch("POST", `/company/${companyIdToUse}/jobs`, JSON.stringify(body), cancelToken, {
|
|
625
722
|
...headers,
|
|
626
|
-
|
|
723
|
+
"Content-Type": "application/json",
|
|
627
724
|
})
|
|
628
|
-
.then(response => {
|
|
725
|
+
.then((response) => {
|
|
629
726
|
if (!response || !Array.isArray(response)) {
|
|
630
|
-
throw new StandardError(400,
|
|
727
|
+
throw new StandardError(400, "Invalid jobs response");
|
|
631
728
|
}
|
|
632
729
|
return response;
|
|
633
730
|
})
|
|
634
|
-
.catch(err => {
|
|
635
|
-
console.error(
|
|
731
|
+
.catch((err) => {
|
|
732
|
+
console.error("Failed to fetch jobs:", err);
|
|
636
733
|
throw new StandardError(500, `Failed to fetch jobs: ${err.message ?? err}`);
|
|
637
734
|
});
|
|
638
735
|
}
|
|
639
736
|
fetchChats(body = {}, cancelToken, headers = {}) {
|
|
640
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
641
|
-
throw new StandardError(400,
|
|
737
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
738
|
+
throw new StandardError(400, "Company ID is required");
|
|
642
739
|
}
|
|
643
|
-
return this._fetch(
|
|
740
|
+
return this._fetch("POST", `/company/${this.companyId}/chats`, JSON.stringify(body), cancelToken, {
|
|
644
741
|
...headers,
|
|
645
|
-
|
|
742
|
+
"Content-Type": "application/json",
|
|
646
743
|
})
|
|
647
|
-
.then(response => {
|
|
744
|
+
.then((response) => {
|
|
648
745
|
if (!response || !Array.isArray(response)) {
|
|
649
|
-
throw new StandardError(400,
|
|
746
|
+
throw new StandardError(400, "Invalid chats response");
|
|
650
747
|
}
|
|
651
748
|
return response;
|
|
652
749
|
})
|
|
653
|
-
.catch(err => {
|
|
654
|
-
console.error(
|
|
655
|
-
throw new StandardError(500,
|
|
750
|
+
.catch((err) => {
|
|
751
|
+
console.error("Failed to fetch chats:", err);
|
|
752
|
+
throw new StandardError(500, "Failed to fetch chats");
|
|
656
753
|
});
|
|
657
754
|
}
|
|
658
755
|
fetchMessages(chatId, body = {}, cancelToken, headers = {}) {
|
|
659
|
-
if (!chatId || chatId.trim() ===
|
|
660
|
-
throw new StandardError(400,
|
|
756
|
+
if (!chatId || chatId.trim() === "") {
|
|
757
|
+
throw new StandardError(400, "Chat ID is required");
|
|
661
758
|
}
|
|
662
|
-
return this._fetch(
|
|
759
|
+
return this._fetch("POST", `/chat/${chatId}/messages`, JSON.stringify(body), cancelToken, {
|
|
663
760
|
...headers,
|
|
664
|
-
|
|
761
|
+
"Content-Type": "application/json",
|
|
665
762
|
})
|
|
666
|
-
.then(response => {
|
|
763
|
+
.then((response) => {
|
|
667
764
|
if (!response || !Array.isArray(response)) {
|
|
668
|
-
throw new StandardError(400,
|
|
765
|
+
throw new StandardError(400, "Invalid messages response");
|
|
669
766
|
}
|
|
670
767
|
return response;
|
|
671
768
|
})
|
|
672
|
-
.catch(err => {
|
|
673
|
-
console.error(
|
|
674
|
-
throw new StandardError(500,
|
|
769
|
+
.catch((err) => {
|
|
770
|
+
console.error("Failed to fetch messages:", err);
|
|
771
|
+
throw new StandardError(500, "Failed to fetch messages");
|
|
675
772
|
});
|
|
676
773
|
}
|
|
677
774
|
async fetchPrepayments(body = {}, cancelToken, headers = {}) {
|
|
678
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
679
|
-
throw new StandardError(400,
|
|
775
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
776
|
+
throw new StandardError(400, "Company ID is required");
|
|
680
777
|
}
|
|
681
|
-
return this._fetch(
|
|
778
|
+
return this._fetch("POST", `/company/${this.companyId}/prepayments`, JSON.stringify(body), cancelToken, {
|
|
682
779
|
...headers,
|
|
683
|
-
|
|
780
|
+
"Content-Type": "application/json",
|
|
684
781
|
})
|
|
685
|
-
.then(response => {
|
|
782
|
+
.then((response) => {
|
|
686
783
|
if (!response || !Array.isArray(response)) {
|
|
687
|
-
throw new StandardError(400,
|
|
784
|
+
throw new StandardError(400, "Invalid prepayments response");
|
|
688
785
|
}
|
|
689
786
|
return response;
|
|
690
787
|
})
|
|
691
|
-
.catch(err => {
|
|
692
|
-
console.error(
|
|
693
|
-
throw new StandardError(500,
|
|
788
|
+
.catch((err) => {
|
|
789
|
+
console.error("Failed to fetch prepayments:", err);
|
|
790
|
+
throw new StandardError(500, "Failed to fetch prepayments");
|
|
694
791
|
});
|
|
695
792
|
}
|
|
696
793
|
async fetchWaivers(body = {}, cancelToken, headers = {}) {
|
|
697
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
698
|
-
throw new StandardError(400,
|
|
794
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
795
|
+
throw new StandardError(400, "Company ID is required");
|
|
699
796
|
}
|
|
700
|
-
return this._fetch(
|
|
797
|
+
return this._fetch("POST", `/company/${this.companyId}/waivers`, JSON.stringify(body), cancelToken, {
|
|
701
798
|
...headers,
|
|
702
|
-
|
|
799
|
+
"Content-Type": "application/json",
|
|
703
800
|
})
|
|
704
|
-
.then(response => {
|
|
801
|
+
.then((response) => {
|
|
705
802
|
if (!response || !Array.isArray(response)) {
|
|
706
|
-
throw new StandardError(400,
|
|
803
|
+
throw new StandardError(400, "Invalid waivers response");
|
|
707
804
|
}
|
|
708
805
|
return response;
|
|
709
806
|
})
|
|
710
|
-
.catch(err => {
|
|
711
|
-
console.error(
|
|
712
|
-
throw new StandardError(500,
|
|
807
|
+
.catch((err) => {
|
|
808
|
+
console.error("Failed to fetch waivers:", err);
|
|
809
|
+
throw new StandardError(500, "Failed to fetch waivers");
|
|
713
810
|
});
|
|
714
811
|
}
|
|
715
812
|
async fetchEvents(body = {}, cancelToken, headers = {}) {
|
|
716
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
717
|
-
throw new StandardError(400,
|
|
813
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
814
|
+
throw new StandardError(400, "Company ID is required");
|
|
718
815
|
}
|
|
719
|
-
return this._fetch(
|
|
816
|
+
return this._fetch("POST", `/company/${this.companyId}/events`, JSON.stringify(body), cancelToken, {
|
|
720
817
|
...headers,
|
|
721
|
-
|
|
818
|
+
"Content-Type": "application/json",
|
|
722
819
|
})
|
|
723
|
-
.then(response => {
|
|
820
|
+
.then((response) => {
|
|
724
821
|
if (!response || !Array.isArray(response)) {
|
|
725
|
-
throw new StandardError(400,
|
|
822
|
+
throw new StandardError(400, "Invalid events response");
|
|
726
823
|
}
|
|
727
824
|
return response;
|
|
728
825
|
})
|
|
729
|
-
.catch(err => {
|
|
730
|
-
console.error(
|
|
731
|
-
throw new StandardError(500,
|
|
826
|
+
.catch((err) => {
|
|
827
|
+
console.error("Failed to fetch events:", err);
|
|
828
|
+
throw new StandardError(500, "Failed to fetch events");
|
|
732
829
|
});
|
|
733
830
|
}
|
|
734
831
|
fetchMonths(body = {}, cancelToken, headers = {}) {
|
|
735
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
736
|
-
throw new StandardError(400,
|
|
832
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
833
|
+
throw new StandardError(400, "Company ID is required");
|
|
737
834
|
}
|
|
738
|
-
return this._fetch(
|
|
835
|
+
return this._fetch("POST", `/company/${this.companyId}/months`, JSON.stringify(body), cancelToken, {
|
|
739
836
|
...headers,
|
|
740
|
-
|
|
837
|
+
"Content-Type": "application/json",
|
|
741
838
|
})
|
|
742
|
-
.then(response => {
|
|
839
|
+
.then((response) => {
|
|
743
840
|
if (!response || !Array.isArray(response)) {
|
|
744
|
-
throw new StandardError(400,
|
|
841
|
+
throw new StandardError(400, "Invalid months response");
|
|
745
842
|
}
|
|
746
843
|
return response;
|
|
747
844
|
})
|
|
748
|
-
.catch(err => {
|
|
749
|
-
console.error(
|
|
750
|
-
throw new StandardError(500,
|
|
845
|
+
.catch((err) => {
|
|
846
|
+
console.error("Failed to fetch months:", err);
|
|
847
|
+
throw new StandardError(500, "Failed to fetch months");
|
|
751
848
|
});
|
|
752
849
|
}
|
|
753
850
|
fetchBills(body = {}, cancelToken, headers = {}) {
|
|
754
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
755
|
-
throw new StandardError(400,
|
|
851
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
852
|
+
throw new StandardError(400, "Company ID is required");
|
|
756
853
|
}
|
|
757
|
-
return this._fetch(
|
|
854
|
+
return this._fetch("POST", `/company/${this.companyId}/bills`, JSON.stringify(body), cancelToken, {
|
|
758
855
|
...headers,
|
|
759
|
-
|
|
856
|
+
"Content-Type": "application/json",
|
|
760
857
|
})
|
|
761
|
-
.then(response => {
|
|
858
|
+
.then((response) => {
|
|
762
859
|
if (!response || !Array.isArray(response)) {
|
|
763
|
-
throw new StandardError(400,
|
|
860
|
+
throw new StandardError(400, "Invalid bills response");
|
|
764
861
|
}
|
|
765
862
|
return response;
|
|
766
863
|
})
|
|
767
|
-
.catch(err => {
|
|
768
|
-
console.error(
|
|
769
|
-
throw new StandardError(500,
|
|
864
|
+
.catch((err) => {
|
|
865
|
+
console.error("Failed to fetch bills:", err);
|
|
866
|
+
throw new StandardError(500, "Failed to fetch bills");
|
|
770
867
|
});
|
|
771
868
|
}
|
|
772
869
|
fetchRoutes(body = {}, cancelToken, headers = {}) {
|
|
773
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
774
|
-
throw new StandardError(400,
|
|
870
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
871
|
+
throw new StandardError(400, "Company ID is required");
|
|
775
872
|
}
|
|
776
|
-
return this._fetch(
|
|
873
|
+
return this._fetch("POST", `/company/${this.companyId}/routes`, JSON.stringify(body), cancelToken, {
|
|
777
874
|
...headers,
|
|
778
|
-
|
|
875
|
+
"Content-Type": "application/json",
|
|
779
876
|
})
|
|
780
|
-
.then(response => {
|
|
877
|
+
.then((response) => {
|
|
781
878
|
if (!response || !Array.isArray(response)) {
|
|
782
|
-
throw new StandardError(400,
|
|
879
|
+
throw new StandardError(400, "Invalid routes response");
|
|
783
880
|
}
|
|
784
881
|
return response;
|
|
785
882
|
})
|
|
786
|
-
.catch(err => {
|
|
787
|
-
console.error(
|
|
788
|
-
throw new StandardError(500,
|
|
883
|
+
.catch((err) => {
|
|
884
|
+
console.error("Failed to fetch routes:", err);
|
|
885
|
+
throw new StandardError(500, "Failed to fetch routes");
|
|
789
886
|
});
|
|
790
887
|
}
|
|
791
888
|
fetchTeams(body = {}, cancelToken, headers = {}) {
|
|
792
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
793
|
-
throw new StandardError(400,
|
|
889
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
890
|
+
throw new StandardError(400, "Company ID is required");
|
|
794
891
|
}
|
|
795
|
-
return this._fetch(
|
|
892
|
+
return this._fetch("POST", `/company/${this.companyId}/teams`, JSON.stringify(body), cancelToken, {
|
|
796
893
|
...headers,
|
|
797
|
-
|
|
894
|
+
"Content-Type": "application/json",
|
|
798
895
|
})
|
|
799
|
-
.then(response => {
|
|
896
|
+
.then((response) => {
|
|
800
897
|
if (!response || !Array.isArray(response)) {
|
|
801
|
-
throw new StandardError(400,
|
|
898
|
+
throw new StandardError(400, "Invalid teams response");
|
|
802
899
|
}
|
|
803
900
|
return response;
|
|
804
901
|
})
|
|
805
|
-
.catch(err => {
|
|
806
|
-
console.error(
|
|
807
|
-
throw new StandardError(500,
|
|
902
|
+
.catch((err) => {
|
|
903
|
+
console.error("Failed to fetch teams:", err);
|
|
904
|
+
throw new StandardError(500, "Failed to fetch teams");
|
|
808
905
|
});
|
|
809
906
|
}
|
|
810
907
|
fetchLabels(body = {}, cancelToken, headers = {}) {
|
|
811
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
812
|
-
throw new StandardError(400,
|
|
908
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
909
|
+
throw new StandardError(400, "Company ID is required");
|
|
813
910
|
}
|
|
814
|
-
return this._fetch(
|
|
911
|
+
return this._fetch("POST", `/company/${this.companyId}/labels`, JSON.stringify(body), cancelToken, {
|
|
815
912
|
...headers,
|
|
816
|
-
|
|
913
|
+
"Content-Type": "application/json",
|
|
817
914
|
})
|
|
818
|
-
.then(response => {
|
|
915
|
+
.then((response) => {
|
|
819
916
|
if (!response || !Array.isArray(response)) {
|
|
820
|
-
throw new StandardError(400,
|
|
917
|
+
throw new StandardError(400, "Invalid labels response");
|
|
821
918
|
}
|
|
822
919
|
return response;
|
|
823
920
|
})
|
|
824
|
-
.catch(err => {
|
|
825
|
-
console.error(
|
|
826
|
-
throw new StandardError(500,
|
|
921
|
+
.catch((err) => {
|
|
922
|
+
console.error("Failed to fetch labels:", err);
|
|
923
|
+
throw new StandardError(500, "Failed to fetch labels");
|
|
827
924
|
});
|
|
828
925
|
}
|
|
829
926
|
fetchGroups(body = {}, cancelToken, headers = {}) {
|
|
830
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
831
|
-
throw new StandardError(400,
|
|
927
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
928
|
+
throw new StandardError(400, "Company ID is required");
|
|
832
929
|
}
|
|
833
|
-
return this._fetch(
|
|
930
|
+
return this._fetch("POST", `/company/${this.companyId}/groups`, JSON.stringify(body), cancelToken, {
|
|
834
931
|
...headers,
|
|
835
|
-
|
|
932
|
+
"Content-Type": "application/json",
|
|
836
933
|
})
|
|
837
|
-
.then(response => {
|
|
934
|
+
.then((response) => {
|
|
838
935
|
if (!response || !Array.isArray(response)) {
|
|
839
|
-
throw new StandardError(400,
|
|
936
|
+
throw new StandardError(400, "Invalid groups response");
|
|
840
937
|
}
|
|
841
938
|
return response;
|
|
842
939
|
})
|
|
843
|
-
.catch(err => {
|
|
844
|
-
console.error(
|
|
845
|
-
throw new StandardError(500,
|
|
940
|
+
.catch((err) => {
|
|
941
|
+
console.error("Failed to fetch groups:", err);
|
|
942
|
+
throw new StandardError(500, "Failed to fetch groups");
|
|
846
943
|
});
|
|
847
944
|
}
|
|
848
945
|
fetchSkills(body = {}, cancelToken, headers = {}) {
|
|
849
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
850
|
-
throw new StandardError(400,
|
|
946
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
947
|
+
throw new StandardError(400, "Company ID is required");
|
|
851
948
|
}
|
|
852
|
-
return this._fetch(
|
|
949
|
+
return this._fetch("POST", `/company/${this.companyId}/skills`, JSON.stringify(body), cancelToken, {
|
|
853
950
|
...headers,
|
|
854
|
-
|
|
951
|
+
"Content-Type": "application/json",
|
|
855
952
|
})
|
|
856
|
-
.then(response => {
|
|
953
|
+
.then((response) => {
|
|
857
954
|
if (!response || !Array.isArray(response)) {
|
|
858
|
-
throw new StandardError(400,
|
|
955
|
+
throw new StandardError(400, "Invalid skills response");
|
|
859
956
|
}
|
|
860
957
|
return response;
|
|
861
958
|
})
|
|
862
|
-
.catch(err => {
|
|
863
|
-
console.error(
|
|
864
|
-
throw new StandardError(500,
|
|
959
|
+
.catch((err) => {
|
|
960
|
+
console.error("Failed to fetch skills:", err);
|
|
961
|
+
throw new StandardError(500, "Failed to fetch skills");
|
|
865
962
|
});
|
|
866
963
|
}
|
|
867
964
|
fetchSessions(body = {}, cancelToken, headers = {}) {
|
|
868
|
-
if (!this.companyId || this.companyId.trim() ===
|
|
869
|
-
throw new StandardError(400,
|
|
965
|
+
if (!this.companyId || this.companyId.trim() === "") {
|
|
966
|
+
throw new StandardError(400, "Company ID is required");
|
|
870
967
|
}
|
|
871
|
-
return this._fetch(
|
|
968
|
+
return this._fetch("POST", `/company/${this.companyId}/sessions`, JSON.stringify(body), cancelToken, {
|
|
872
969
|
...headers,
|
|
873
|
-
|
|
970
|
+
"Content-Type": "application/json",
|
|
874
971
|
})
|
|
875
|
-
.then(response => {
|
|
972
|
+
.then((response) => {
|
|
876
973
|
if (!response || !Array.isArray(response)) {
|
|
877
|
-
throw new StandardError(400,
|
|
974
|
+
throw new StandardError(400, "Invalid sessions response");
|
|
878
975
|
}
|
|
879
976
|
return response;
|
|
880
977
|
})
|
|
881
|
-
.catch(err => {
|
|
882
|
-
console.error(
|
|
883
|
-
throw new StandardError(500,
|
|
978
|
+
.catch((err) => {
|
|
979
|
+
console.error("Failed to fetch sessions:", err);
|
|
980
|
+
throw new StandardError(500, "Failed to fetch sessions");
|
|
884
981
|
});
|
|
885
982
|
}
|
|
886
983
|
/* ── Email delivery tracking ──────────────────────────────────────── */
|
|
@@ -945,7 +1042,7 @@ export class AvroQueryClient {
|
|
|
945
1042
|
return this.post({
|
|
946
1043
|
path: `/email/${emailId}`,
|
|
947
1044
|
data: formData,
|
|
948
|
-
progressUpdateCallback
|
|
1045
|
+
progressUpdateCallback,
|
|
949
1046
|
});
|
|
950
1047
|
}
|
|
951
1048
|
catch (error) {
|
|
@@ -957,7 +1054,7 @@ export class AvroQueryClient {
|
|
|
957
1054
|
return this.post({
|
|
958
1055
|
path: `/bill/${billId}/email`,
|
|
959
1056
|
data: JSON.stringify(body),
|
|
960
|
-
headers: {
|
|
1057
|
+
headers: { "Content-Type": "application/json" },
|
|
961
1058
|
});
|
|
962
1059
|
}
|
|
963
1060
|
catch (error) {
|