@go-avro/avro-js 0.0.44 → 0.0.45
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.
|
@@ -517,6 +517,11 @@ declare module '../client/QueryClient' {
|
|
|
517
517
|
}>>;
|
|
518
518
|
}
|
|
519
519
|
}
|
|
520
|
+
export type ListQueryMatch = 'match' | 'nomatch' | 'unknown';
|
|
521
|
+
export declare function matchesEventsListQuery(queryKey: readonly unknown[], item: any): ListQueryMatch;
|
|
522
|
+
export declare function matchesBillsListQuery(queryKey: readonly unknown[], item: any): ListQueryMatch;
|
|
523
|
+
export declare function matchesJobsListQuery(queryKey: readonly unknown[], item: any): ListQueryMatch;
|
|
524
|
+
export declare function matchesRoutesListQuery(queryKey: readonly unknown[], _item: any): ListQueryMatch;
|
|
520
525
|
export declare class AvroQueryClient {
|
|
521
526
|
protected config: Required<AvroQueryClientConfig>;
|
|
522
527
|
readonly socket: Socket;
|
|
@@ -618,23 +623,13 @@ export declare class AvroQueryClient {
|
|
|
618
623
|
getAuthState(): AuthState;
|
|
619
624
|
getAuthStateAsync(): Promise<AuthState>;
|
|
620
625
|
getQueryClient(): QueryClient;
|
|
621
|
-
/**
|
|
622
|
-
* Fetch an entity from the API, optionally construct it, and surgically
|
|
623
|
-
* update all matching React-Query caches (individual + list + infinite).
|
|
624
|
-
*
|
|
625
|
-
* Shared by socket handlers and mutation `onSuccess` callbacks so the
|
|
626
|
-
* sender gets an immediate cache sync and everyone else gets the socket
|
|
627
|
-
* update — both use the identical code path.
|
|
628
|
-
*
|
|
629
|
-
* @returns The fetched (and optionally constructed) item, or `undefined`
|
|
630
|
-
* for deletes / entities without a fetchPath.
|
|
631
|
-
*/
|
|
632
626
|
_syncEntity(queryClient: QueryClient, params: {
|
|
633
627
|
action: 'create' | 'update' | 'delete';
|
|
634
628
|
entityKey: string;
|
|
635
629
|
id: string;
|
|
636
630
|
fetchPath?: string;
|
|
637
631
|
construct?: (raw: any) => any;
|
|
632
|
+
matchesQuery?: (queryKey: readonly unknown[], item: any) => ListQueryMatch;
|
|
638
633
|
}): Promise<any>;
|
|
639
634
|
useLogout(): ReturnType<typeof useMutation<void, StandardError, CancelToken | undefined>>;
|
|
640
635
|
fetchJobs(body?: {
|
|
@@ -7,6 +7,91 @@ import { StandardError } from '../types/error';
|
|
|
7
7
|
function isBulkEvent(c) {
|
|
8
8
|
return 'invalidateKeys' in c;
|
|
9
9
|
}
|
|
10
|
+
export function matchesEventsListQuery(queryKey, item) {
|
|
11
|
+
if (queryKey.length < 11)
|
|
12
|
+
return 'match';
|
|
13
|
+
if (!item)
|
|
14
|
+
return 'match';
|
|
15
|
+
const knownIds = queryKey[3];
|
|
16
|
+
const unknownIds = queryKey[4];
|
|
17
|
+
const query = queryKey[5];
|
|
18
|
+
const includeUnbilled = queryKey[6];
|
|
19
|
+
const includeBilled = queryKey[7];
|
|
20
|
+
const includePaid = queryKey[8];
|
|
21
|
+
const filterJobId = queryKey[9];
|
|
22
|
+
if (knownIds && knownIds.length > 0 && !knownIds.includes(item.id))
|
|
23
|
+
return 'nomatch';
|
|
24
|
+
if (unknownIds && unknownIds.length > 0 && unknownIds.includes(item.id))
|
|
25
|
+
return 'nomatch';
|
|
26
|
+
if (filterJobId && item.job_id !== filterJobId)
|
|
27
|
+
return 'nomatch';
|
|
28
|
+
const status = item.status;
|
|
29
|
+
if (status !== undefined) {
|
|
30
|
+
const isPaid = status === 'PAID' || status === 'PREPAID' || status === 'EXTERNALLY_PAID';
|
|
31
|
+
const isBilled = status === 'BILLED' || status === 'EXTERNALLY_BILLED';
|
|
32
|
+
const isUnbilled = !isPaid && !isBilled;
|
|
33
|
+
if (includePaid === false && isPaid)
|
|
34
|
+
return 'nomatch';
|
|
35
|
+
if (includeBilled === false && isBilled)
|
|
36
|
+
return 'nomatch';
|
|
37
|
+
if (includeUnbilled === false && isUnbilled)
|
|
38
|
+
return 'nomatch';
|
|
39
|
+
}
|
|
40
|
+
if (query && query.length > 0)
|
|
41
|
+
return 'unknown';
|
|
42
|
+
return 'match';
|
|
43
|
+
}
|
|
44
|
+
export function matchesBillsListQuery(queryKey, item) {
|
|
45
|
+
if (queryKey.length < 6)
|
|
46
|
+
return 'match';
|
|
47
|
+
if (!item)
|
|
48
|
+
return 'match';
|
|
49
|
+
const query = queryKey[2];
|
|
50
|
+
const knownIds = queryKey[3];
|
|
51
|
+
const unknownIds = queryKey[4];
|
|
52
|
+
const paidFilter = queryKey[5];
|
|
53
|
+
if (knownIds && knownIds.length > 0 && !knownIds.includes(item.id))
|
|
54
|
+
return 'nomatch';
|
|
55
|
+
if (unknownIds && unknownIds.length > 0 && unknownIds.includes(item.id))
|
|
56
|
+
return 'nomatch';
|
|
57
|
+
const status = item.status;
|
|
58
|
+
if (paidFilter !== undefined && status !== undefined) {
|
|
59
|
+
const isPaid = status === 'PAID' || status === 'MANUALLY_PAID';
|
|
60
|
+
if (paidFilter === true && !isPaid)
|
|
61
|
+
return 'nomatch';
|
|
62
|
+
if (paidFilter === false && isPaid)
|
|
63
|
+
return 'nomatch';
|
|
64
|
+
}
|
|
65
|
+
if (query && query.length > 0)
|
|
66
|
+
return 'unknown';
|
|
67
|
+
return 'match';
|
|
68
|
+
}
|
|
69
|
+
export function matchesJobsListQuery(queryKey, item) {
|
|
70
|
+
if (queryKey[0] !== 'infinite')
|
|
71
|
+
return 'match';
|
|
72
|
+
if (queryKey.length < 6)
|
|
73
|
+
return 'match';
|
|
74
|
+
if (!item)
|
|
75
|
+
return 'match';
|
|
76
|
+
const query = queryKey[4];
|
|
77
|
+
const routeId = queryKey[5];
|
|
78
|
+
if (routeId && routeId.length > 0) {
|
|
79
|
+
const routes = item.routes ?? [];
|
|
80
|
+
if (!routes.some((r) => r?.route_id === routeId))
|
|
81
|
+
return 'nomatch';
|
|
82
|
+
}
|
|
83
|
+
if (query && query.length > 0)
|
|
84
|
+
return 'unknown';
|
|
85
|
+
return 'match';
|
|
86
|
+
}
|
|
87
|
+
export function matchesRoutesListQuery(queryKey, _item) {
|
|
88
|
+
if (queryKey.length < 5)
|
|
89
|
+
return 'match';
|
|
90
|
+
const query = queryKey[3];
|
|
91
|
+
if (query && query.length > 0)
|
|
92
|
+
return 'unknown';
|
|
93
|
+
return 'match';
|
|
94
|
+
}
|
|
10
95
|
/**
|
|
11
96
|
* Maps socket event names to cache-update strategies.
|
|
12
97
|
*
|
|
@@ -38,12 +123,14 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
38
123
|
action: 'create',
|
|
39
124
|
fetchPath: (id) => `/job/${id}`,
|
|
40
125
|
construct: (d) => new Job(d),
|
|
126
|
+
matchesQuery: matchesJobsListQuery,
|
|
41
127
|
},
|
|
42
128
|
update_job: {
|
|
43
129
|
entityKey: 'jobs',
|
|
44
130
|
action: 'update',
|
|
45
131
|
fetchPath: (id) => `/job/${id}`,
|
|
46
132
|
construct: (d) => new Job(d),
|
|
133
|
+
matchesQuery: matchesJobsListQuery,
|
|
47
134
|
},
|
|
48
135
|
delete_job: { entityKey: 'jobs', action: 'delete', fetchPath: null },
|
|
49
136
|
update_jobs: { invalidateKeys: [['jobs'], ['infinite', 'jobs']] },
|
|
@@ -54,12 +141,14 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
54
141
|
action: 'create',
|
|
55
142
|
fetchPath: (id) => `/route/${id}`,
|
|
56
143
|
construct: (d) => new Route(d),
|
|
144
|
+
matchesQuery: matchesRoutesListQuery,
|
|
57
145
|
},
|
|
58
146
|
update_route: {
|
|
59
147
|
entityKey: 'routes',
|
|
60
148
|
action: 'update',
|
|
61
149
|
fetchPath: (id) => `/route/${id}`,
|
|
62
150
|
construct: (d) => new Route(d),
|
|
151
|
+
matchesQuery: matchesRoutesListQuery,
|
|
63
152
|
},
|
|
64
153
|
delete_route: { entityKey: 'routes', action: 'delete', fetchPath: null },
|
|
65
154
|
// ── Events (also refetch parent job — overdueness, last_event, etc.) ──
|
|
@@ -68,12 +157,14 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
68
157
|
action: 'create',
|
|
69
158
|
fetchPath: (id) => `/event/${id}`,
|
|
70
159
|
construct: (d) => new _Event(d),
|
|
160
|
+
matchesQuery: matchesEventsListQuery,
|
|
71
161
|
relatedRefetch: [
|
|
72
162
|
{
|
|
73
163
|
entityKey: 'jobs',
|
|
74
164
|
idField: 'job_id',
|
|
75
165
|
fetchPath: (id) => `/job/${id}`,
|
|
76
166
|
construct: (d) => new Job(d),
|
|
167
|
+
matchesQuery: matchesJobsListQuery,
|
|
77
168
|
},
|
|
78
169
|
],
|
|
79
170
|
},
|
|
@@ -82,12 +173,14 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
82
173
|
action: 'update',
|
|
83
174
|
fetchPath: (id) => `/event/${id}`,
|
|
84
175
|
construct: (d) => new _Event(d),
|
|
176
|
+
matchesQuery: matchesEventsListQuery,
|
|
85
177
|
relatedRefetch: [
|
|
86
178
|
{
|
|
87
179
|
entityKey: 'jobs',
|
|
88
180
|
idField: 'job_id',
|
|
89
181
|
fetchPath: (id) => `/job/${id}`,
|
|
90
182
|
construct: (d) => new Job(d),
|
|
183
|
+
matchesQuery: matchesJobsListQuery,
|
|
91
184
|
},
|
|
92
185
|
],
|
|
93
186
|
},
|
|
@@ -101,6 +194,7 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
101
194
|
idField: 'job_id',
|
|
102
195
|
fetchPath: (id) => `/job/${id}`,
|
|
103
196
|
construct: (d) => new Job(d),
|
|
197
|
+
matchesQuery: matchesJobsListQuery,
|
|
104
198
|
},
|
|
105
199
|
],
|
|
106
200
|
},
|
|
@@ -115,6 +209,7 @@ const SOCKET_EVENT_CONFIG = {
|
|
|
115
209
|
entityKey: 'bills',
|
|
116
210
|
action: 'create',
|
|
117
211
|
fetchPath: (id) => `/bill/${id}`,
|
|
212
|
+
matchesQuery: matchesBillsListQuery,
|
|
118
213
|
},
|
|
119
214
|
delete_bill: { entityKey: 'bills', action: 'delete', fetchPath: null },
|
|
120
215
|
update_bills: { invalidateKeys: [['bills']] },
|
|
@@ -348,10 +443,10 @@ export class AvroQueryClient {
|
|
|
348
443
|
id,
|
|
349
444
|
fetchPath: fetchPath ? fetchPath(id) : undefined,
|
|
350
445
|
construct: config.construct,
|
|
446
|
+
matchesQuery: config.matchesQuery,
|
|
351
447
|
});
|
|
352
448
|
// Invalidate any additional keys (e.g. companies → /company/list)
|
|
353
449
|
alsoInvalidate?.forEach((k) => queryClient.invalidateQueries({ queryKey: k }));
|
|
354
|
-
// Refetch & patch related entities (e.g. event → parent job)
|
|
355
450
|
if (relatedRefetch) {
|
|
356
451
|
for (const related of relatedRefetch) {
|
|
357
452
|
const relatedId = data?.[related.idField] ?? fetchedItem?.[related.idField];
|
|
@@ -363,6 +458,7 @@ export class AvroQueryClient {
|
|
|
363
458
|
id: relatedId,
|
|
364
459
|
fetchPath: related.fetchPath(relatedId),
|
|
365
460
|
construct: related.construct,
|
|
461
|
+
matchesQuery: related.matchesQuery,
|
|
366
462
|
});
|
|
367
463
|
}
|
|
368
464
|
}
|
|
@@ -615,22 +711,10 @@ export class AvroQueryClient {
|
|
|
615
711
|
getQueryClient() {
|
|
616
712
|
return useQueryClient();
|
|
617
713
|
}
|
|
618
|
-
/**
|
|
619
|
-
* Fetch an entity from the API, optionally construct it, and surgically
|
|
620
|
-
* update all matching React-Query caches (individual + list + infinite).
|
|
621
|
-
*
|
|
622
|
-
* Shared by socket handlers and mutation `onSuccess` callbacks so the
|
|
623
|
-
* sender gets an immediate cache sync and everyone else gets the socket
|
|
624
|
-
* update — both use the identical code path.
|
|
625
|
-
*
|
|
626
|
-
* @returns The fetched (and optionally constructed) item, or `undefined`
|
|
627
|
-
* for deletes / entities without a fetchPath.
|
|
628
|
-
*/
|
|
629
714
|
async _syncEntity(queryClient, params) {
|
|
630
|
-
const { action, entityKey, id, fetchPath, construct } = params;
|
|
715
|
+
const { action, entityKey, id, fetchPath, construct, matchesQuery } = params;
|
|
631
716
|
const predicate = (q) => matchesEntityKey(q, entityKey);
|
|
632
717
|
const invalidate = () => queryClient.invalidateQueries({ predicate });
|
|
633
|
-
// ─── DELETE ─────────────────────────────────────────
|
|
634
718
|
if (action === 'delete') {
|
|
635
719
|
queryClient.removeQueries({ queryKey: [entityKey, id], exact: true });
|
|
636
720
|
queryClient.setQueriesData({ predicate }, (old) => {
|
|
@@ -649,7 +733,6 @@ export class AvroQueryClient {
|
|
|
649
733
|
});
|
|
650
734
|
return undefined;
|
|
651
735
|
}
|
|
652
|
-
// ─── CREATE / UPDATE ────────────────────────────────
|
|
653
736
|
if (!fetchPath) {
|
|
654
737
|
invalidate();
|
|
655
738
|
return undefined;
|
|
@@ -662,42 +745,60 @@ export class AvroQueryClient {
|
|
|
662
745
|
});
|
|
663
746
|
const item = construct ? construct(raw) : raw;
|
|
664
747
|
queryClient.setQueryData([entityKey, id], item);
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
748
|
+
const matchingQueries = queryClient.getQueryCache().findAll({ predicate });
|
|
749
|
+
for (const query of matchingQueries) {
|
|
750
|
+
const queryKey = query.queryKey;
|
|
751
|
+
if (queryKey.length === 2 && queryKey[0] === entityKey)
|
|
752
|
+
continue;
|
|
753
|
+
const verdict = matchesQuery
|
|
754
|
+
? matchesQuery(queryKey, item)
|
|
755
|
+
: 'match';
|
|
756
|
+
if (verdict === 'unknown') {
|
|
757
|
+
queryClient.invalidateQueries({ queryKey, exact: true });
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (action === 'create') {
|
|
761
|
+
if (verdict === 'nomatch')
|
|
762
|
+
continue;
|
|
763
|
+
queryClient.setQueryData(queryKey, (old) => {
|
|
764
|
+
if (!old)
|
|
671
765
|
return old;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
766
|
+
if (old.pages && Array.isArray(old.pages)) {
|
|
767
|
+
if (old.pages.some((p) => p.some((x) => x?.id === id)))
|
|
768
|
+
return old;
|
|
769
|
+
return {
|
|
770
|
+
...old,
|
|
771
|
+
pages: [[item, ...(old.pages[0] || [])], ...old.pages.slice(1)],
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
if (Array.isArray(old)) {
|
|
775
|
+
if (old.some((x) => x?.id === id))
|
|
776
|
+
return old;
|
|
777
|
+
return [...old, item];
|
|
778
|
+
}
|
|
779
|
+
return old;
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
queryClient.setQueryData(queryKey, (old) => {
|
|
784
|
+
if (!old)
|
|
679
785
|
return old;
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
786
|
+
if (old.pages && Array.isArray(old.pages)) {
|
|
787
|
+
return {
|
|
788
|
+
...old,
|
|
789
|
+
pages: old.pages.map((page) => verdict === 'match'
|
|
790
|
+
? page.map((x) => (x?.id === id ? item : x))
|
|
791
|
+
: page.filter((x) => x?.id !== id)),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
if (Array.isArray(old)) {
|
|
795
|
+
return verdict === 'match'
|
|
796
|
+
? old.map((x) => (x?.id === id ? item : x))
|
|
797
|
+
: old.filter((x) => x?.id !== id);
|
|
798
|
+
}
|
|
689
799
|
return old;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
...old,
|
|
693
|
-
pages: old.pages.map((page) => page.map((x) => (x?.id === id ? item : x))),
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (Array.isArray(old)) {
|
|
697
|
-
return old.map((x) => (x?.id === id ? item : x));
|
|
698
|
-
}
|
|
699
|
-
return old;
|
|
700
|
-
});
|
|
800
|
+
});
|
|
801
|
+
}
|
|
701
802
|
}
|
|
702
803
|
return item;
|
|
703
804
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { useInfiniteQuery, useQuery, useMutation } from '@tanstack/react-query';
|
|
2
|
-
import { AvroQueryClient } from '../../client/QueryClient';
|
|
2
|
+
import { AvroQueryClient, matchesEventsListQuery } from '../../client/QueryClient';
|
|
3
3
|
import { _Event, Job, LineItemStatus } from '../../types/api';
|
|
4
|
-
/** Predicate that matches all 'jobs' queries (list and individual, but NOT 'infinite'). */
|
|
5
4
|
const isJobsQuery = (q) => q.queryKey[0] === 'jobs';
|
|
6
|
-
/** Predicate that matches all 'events' queries (list and individual). */
|
|
7
5
|
const isEventsQuery = (q) => q.queryKey[0] === 'events';
|
|
6
|
+
const isEventsQueryFor = (item) => (q) => q.queryKey[0] === 'events' && matchesEventsListQuery(q.queryKey, item) === 'match';
|
|
8
7
|
AvroQueryClient.prototype.useGetEvents = function (body) {
|
|
9
8
|
const queryClient = this.getQueryClient();
|
|
10
9
|
const result = useInfiniteQuery({
|
|
@@ -102,8 +101,7 @@ AvroQueryClient.prototype.useCreateEvent = function () {
|
|
|
102
101
|
return old;
|
|
103
102
|
});
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
queryClient.setQueriesData({ predicate: isEventsQuery, type: 'active' }, (oldData) => {
|
|
104
|
+
queryClient.setQueriesData({ predicate: isEventsQueryFor(optimisticEvent), type: 'active' }, (oldData) => {
|
|
107
105
|
if (!oldData)
|
|
108
106
|
return oldData;
|
|
109
107
|
if (oldData.pages) {
|