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