@archie/react-sdk 0.3.0 → 0.3.2
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/README.md +40 -6
- package/dist/index.d.ts +98 -4
- package/dist/index.js +282 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -263,6 +263,9 @@ const { data, error, isLoading, isRefetching, refetch } = useQuery<T>(
|
|
|
263
263
|
- **Stale-while-revalidate**: Shows cached data immediately, refetches in background
|
|
264
264
|
- **Deduplication**: Multiple components with the same query+variables share a single network request
|
|
265
265
|
- **Auto-refetch on auth change**: When the user's token changes, all active queries refetch automatically
|
|
266
|
+
- **Auto-refetch on invalidation**: A mounted query re-runs as soon as its key is invalidated (e.g. by a `useMutation` with `invalidateQueries`); concurrent consumers of the same key still share one request
|
|
267
|
+
- **Latest-response-wins**: A response that was already in flight when its key got invalidated is discarded instead of repainting the view with pre-mutation data
|
|
268
|
+
- **Errors never blank the view**: A failed revalidation sets `error` but keeps the data already on screen
|
|
266
269
|
- **Variable tracking**: Deep comparison via `JSON.stringify` — refetches when variables change
|
|
267
270
|
- **SSR safe**: No `window` access when `refetchOnWindowFocus` is disabled
|
|
268
271
|
|
|
@@ -305,12 +308,12 @@ const { mutate, mutateAsync, data, error, isLoading, reset } = useMutation<T>(
|
|
|
305
308
|
|
|
306
309
|
**Options:**
|
|
307
310
|
|
|
308
|
-
| Option | Type | Description
|
|
309
|
-
| ------------------- | ------------------------------ |
|
|
310
|
-
| `onSuccess` | `(data: T) => void` | Called after successful mutation
|
|
311
|
-
| `onError` | `(error: ArchieError) => void` | Called on error
|
|
312
|
-
| `onSettled` | `() => void` | Called after success or error
|
|
313
|
-
| `invalidateQueries` | `string[]` | Query substrings to invalidate
|
|
311
|
+
| Option | Type | Description |
|
|
312
|
+
| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
313
|
+
| `onSuccess` | `(data: T) => void` | Called after successful mutation |
|
|
314
|
+
| `onError` | `(error: ArchieError) => void` | Called on error |
|
|
315
|
+
| `onSettled` | `() => void` | Called after success or error |
|
|
316
|
+
| `invalidateQueries` | `string[]` | Query substrings to invalidate after success — every mounted `useQuery` whose key matches refetches |
|
|
314
317
|
|
|
315
318
|
**Return:**
|
|
316
319
|
|
|
@@ -339,6 +342,12 @@ const handleCreate = () => {
|
|
|
339
342
|
};
|
|
340
343
|
```
|
|
341
344
|
|
|
345
|
+
> **Matching is by substring**, so `invalidateQueries: ['id']` would refetch every
|
|
346
|
+
> mounted query whose text contains `id` — each one a real network request. Prefer
|
|
347
|
+
> patterns distinctive enough to name the entity you mutated. Passing several
|
|
348
|
+
> patterns is cheap: the whole array is applied as one batch, so a query matched by
|
|
349
|
+
> more than one of them still refetches only once.
|
|
350
|
+
|
|
342
351
|
---
|
|
343
352
|
|
|
344
353
|
### useSubscription
|
|
@@ -583,6 +592,31 @@ const {
|
|
|
583
592
|
| `markAllAsRead` | `() => Promise<void>` | Mark all loaded notifications read; reconciles `unreadCount` with the server (normally 0) |
|
|
584
593
|
| `refetch` | `() => Promise<void>` | Reload the notification history |
|
|
585
594
|
|
|
595
|
+
#### Infinite scroll
|
|
596
|
+
|
|
597
|
+
`useNotifications` pages backward through history without dropping or duplicating live items:
|
|
598
|
+
|
|
599
|
+
```tsx
|
|
600
|
+
const { notifications, fetchMore, hasMore, isFetchingMore, error } = useNotifications(user.id, {
|
|
601
|
+
limit: 20, // page size for fetchMore()
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// e.g. an IntersectionObserver on a sentinel at the bottom of the list.
|
|
605
|
+
// Gate on `!error` so a failed page doesn't drive a tight auto-retry loop
|
|
606
|
+
// (the hook also suppresses fetchMore internally after an error until refetch).
|
|
607
|
+
useEffect(() => {
|
|
608
|
+
if (hasMore && !isFetchingMore && !error) void fetchMore();
|
|
609
|
+
}, [hasMore, isFetchingMore, error, fetchMore]);
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
- `fetchMore()` — loads the next older page and appends it (deduped, newest-first). No-op when
|
|
613
|
+
`!hasMore`, a fetch is already in flight, the hook is disabled, or a previous `fetchMore`
|
|
614
|
+
errored (call `refetch()` — or change `receiverId`/`unreadOnly` — to retry).
|
|
615
|
+
- `hasMore` — more older records exist for the active filter.
|
|
616
|
+
- `isFetchingMore` — a `fetchMore()` call is in flight (drive a bottom "loading older" indicator).
|
|
617
|
+
|
|
618
|
+
Changing `receiverId` or `unreadOnly` resets pagination and re-seeds from the first page.
|
|
619
|
+
|
|
586
620
|
**Example — notification bell with unread badge:**
|
|
587
621
|
|
|
588
622
|
```tsx
|
package/dist/index.d.ts
CHANGED
|
@@ -3,16 +3,22 @@ import { ArchieClient, Session, User, AuthSignInParams, ArchieError, AuthSignUpP
|
|
|
3
3
|
export { ArchieClient, ArchieClientOptions, ArchieError, AuthConfirmParams, AuthError, AuthEvent, AuthEventCallback, AuthRecoverParams, AuthResetPasswordParams, AuthSignInParams, AuthSignUpParams, BrowserLocalStorage, ConnectionState, ConnectionStateCallback, CsvUploadOptions, FileUploadOptions, FileUploadResult, GraphQLError, GraphQLRawRequest, GraphQLRequestOptions, GraphQLResponse, IAuthModule, IFileModule, IGraphQLModule, IHttpClient, IRealtimeModule, IRestModule, Logger, MemoryStorage, NetworkError, RealtimeChannel, RealtimeEvent, RestRequestOptions, Session, StorageAdapter, Subscription, SubscriptionCallbacks, TokenAccessor, User, createClient } from '@archie/js-sdk';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* In-memory query cache with in-flight request deduplication
|
|
6
|
+
* In-memory query cache with in-flight request deduplication, stale-while-revalidate
|
|
7
|
+
* and per-key invalidation subscriptions.
|
|
7
8
|
* @internal
|
|
8
9
|
*/
|
|
9
10
|
interface CacheEntry<T = unknown> {
|
|
10
11
|
data: T;
|
|
11
12
|
timestamp: number;
|
|
12
13
|
}
|
|
14
|
+
/** Called when the entry behind a key is dropped and should be re-fetched. */
|
|
15
|
+
type CacheListener = () => void;
|
|
13
16
|
declare class QueryCache {
|
|
14
17
|
private cache;
|
|
15
18
|
private inflight;
|
|
19
|
+
private subscribers;
|
|
20
|
+
private generations;
|
|
21
|
+
private pending;
|
|
16
22
|
private staleTime;
|
|
17
23
|
static key(query: string, variables?: Record<string, unknown>): string;
|
|
18
24
|
get<T>(key: string): CacheEntry<T> | undefined;
|
|
@@ -24,8 +30,80 @@ declare class QueryCache {
|
|
|
24
30
|
*/
|
|
25
31
|
dedup<T>(key: string, fetcher: () => Promise<T>): Promise<T>;
|
|
26
32
|
getInflight<T>(key: string): Promise<T> | undefined;
|
|
27
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to invalidations of a single cache key.
|
|
35
|
+
*
|
|
36
|
+
* The listener fires when {@link invalidate} matches this key or {@link clear} runs —
|
|
37
|
+
* never on {@link set}, so a listener that re-fetches (and writes the result back)
|
|
38
|
+
* cannot re-trigger itself.
|
|
39
|
+
*
|
|
40
|
+
* @returns an unsubscribe function; call it on unmount.
|
|
41
|
+
*/
|
|
42
|
+
subscribe(key: string, listener: CacheListener): () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Current generation of a key. It increases every time the key is invalidated,
|
|
45
|
+
* so a caller can capture it before an `await` and check {@link isCurrent}
|
|
46
|
+
* afterwards to find out whether its response was superseded meanwhile.
|
|
47
|
+
*
|
|
48
|
+
* Reading is free of side effects: a generation is only ever recorded for a key
|
|
49
|
+
* something can still compare against — see {@link pruneGeneration}.
|
|
50
|
+
*/
|
|
51
|
+
generation(key: string): number;
|
|
52
|
+
/** True while `generation` is still the live generation of `key`. */
|
|
53
|
+
isCurrent(key: string, generation: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Drop every cached entry matching `patterns`, then tell the subscribers on
|
|
56
|
+
* those keys to re-fetch. A key matches when it *contains* any of the patterns.
|
|
57
|
+
*
|
|
58
|
+
* Pass the whole batch in one call (`['user', 'users']`) rather than looping:
|
|
59
|
+
* the affected keys are resolved once, so a key matched by several patterns is
|
|
60
|
+
* invalidated and notified exactly once instead of racing itself.
|
|
61
|
+
*
|
|
62
|
+
* In-flight requests for matching keys are unregistered and the generation of
|
|
63
|
+
* each key is bumped: those requests were issued before the write landed, so
|
|
64
|
+
* neither joining them nor applying their result may hand back stale data.
|
|
65
|
+
*
|
|
66
|
+
* Matching is substring-based, which is deliberately loose — a pattern like
|
|
67
|
+
* `'id'` will match unrelated queries and cost them a refetch. Prefer patterns
|
|
68
|
+
* distinctive enough to name the entity you actually mutated.
|
|
69
|
+
*/
|
|
70
|
+
invalidate(patterns: string | string[]): void;
|
|
71
|
+
/**
|
|
72
|
+
* Wipe everything and notify **every** subscriber, so all mounted queries
|
|
73
|
+
* revalidate. That is intentional — a full clear means no key can be trusted —
|
|
74
|
+
* but it does mean calling this on sign-out fires a refetch for every mounted
|
|
75
|
+
* query, which is rarely what you want with a dead token.
|
|
76
|
+
*/
|
|
28
77
|
clear(): void;
|
|
78
|
+
private bumpGeneration;
|
|
79
|
+
/**
|
|
80
|
+
* Whether anything can still compare a generation for this key: a mounted
|
|
81
|
+
* consumer that may fetch it, or a request already on its way back.
|
|
82
|
+
*/
|
|
83
|
+
private hasHolder;
|
|
84
|
+
/**
|
|
85
|
+
* Forget a key's generation once nothing references it.
|
|
86
|
+
*
|
|
87
|
+
* Pruning is deliberately **not** done when a request settles. A pruned entry
|
|
88
|
+
* reads back as generation 0, which is exactly what a caller that captured 0
|
|
89
|
+
* holds — so a response that was superseded while in flight would be accepted
|
|
90
|
+
* again, and would write pre-mutation data into the cache. The prune therefore
|
|
91
|
+
* happens at the last unsubscribe, and only while nothing is pending.
|
|
92
|
+
*
|
|
93
|
+
* That leaves one bounded remainder, and it is worth naming precisely: if a key
|
|
94
|
+
* has no subscriber left but a request still on its way back, {@link invalidate}
|
|
95
|
+
* or {@link clear} bumps its entry rather than dropping it, and the settle path
|
|
96
|
+
* does not reclaim it. It survives — one entry, holding one key — until some
|
|
97
|
+
* *later* {@link invalidate} matches that key and finds nothing holding it. The
|
|
98
|
+
* two ways in are a consumer that unmounts mid-request and is then invalidated,
|
|
99
|
+
* and a `clear()` (a sign-out, say) fired while requests are in flight, which
|
|
100
|
+
* leaves one entry per key that was still loading. Since patterns match by
|
|
101
|
+
* substring, the next mutation touching that entity reclaims every such remnant
|
|
102
|
+
* at once. Bytes, deliberately traded for the guarantee above.
|
|
103
|
+
*/
|
|
104
|
+
private pruneGeneration;
|
|
105
|
+
/** Notify one key's listeners. Iterates a snapshot so listeners may unsubscribe. */
|
|
106
|
+
private notify;
|
|
29
107
|
}
|
|
30
108
|
|
|
31
109
|
interface ArchieContextValue {
|
|
@@ -253,6 +331,11 @@ declare function useRestMutation<T = unknown>(path: string, method?: 'POST' | 'P
|
|
|
253
331
|
interface UseNotificationsOptions {
|
|
254
332
|
enabled?: boolean;
|
|
255
333
|
unreadOnly?: boolean;
|
|
334
|
+
/**
|
|
335
|
+
* Page size. Applies to `fetchMore()` (older pages); when omitted, `fetchMore` uses 20.
|
|
336
|
+
* It also caps the initial load (`first: limit`) — so omitting it lets the backend choose the
|
|
337
|
+
* initial page size, meaning the first page and subsequent `fetchMore` pages may differ in size.
|
|
338
|
+
*/
|
|
256
339
|
limit?: number;
|
|
257
340
|
onNotification?: (notification: Notification) => void;
|
|
258
341
|
}
|
|
@@ -266,14 +349,25 @@ interface UseNotificationsReturn {
|
|
|
266
349
|
markAsRead: (id: string) => Promise<void>;
|
|
267
350
|
markAllAsRead: () => Promise<void>;
|
|
268
351
|
refetch: () => Promise<void>;
|
|
352
|
+
/**
|
|
353
|
+
* Load the next older page of history. No-op if `!hasMore`, a fetch is already in flight,
|
|
354
|
+
* disabled, or a previous `fetchMore` errored (call `refetch()` or change the filter to retry).
|
|
355
|
+
*/
|
|
356
|
+
fetchMore: () => Promise<void>;
|
|
357
|
+
/** True while more older history exists for the active filter (the last page did not exhaust it). */
|
|
358
|
+
hasMore: boolean;
|
|
359
|
+
/** True while a fetchMore() call is in flight (drives a "loading older" indicator). */
|
|
360
|
+
isFetchingMore: boolean;
|
|
269
361
|
}
|
|
270
362
|
/**
|
|
271
363
|
* Subscribe to a user's in-app notifications: loads the existing list,
|
|
272
|
-
* then streams new ones live
|
|
364
|
+
* then streams new ones live, and pages backward through history via fetchMore().
|
|
365
|
+
* Returns idiomatic React state.
|
|
273
366
|
*
|
|
274
367
|
* @example
|
|
275
368
|
* ```tsx
|
|
276
|
-
* const { notifications, unreadCount,
|
|
369
|
+
* const { notifications, unreadCount, fetchMore, hasMore, isFetchingMore } =
|
|
370
|
+
* useNotifications(user.id, { limit: 20 });
|
|
277
371
|
* ```
|
|
278
372
|
*/
|
|
279
373
|
declare function useNotifications(receiverId: string, options?: UseNotificationsOptions): UseNotificationsReturn;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ export { ArchieError, AuthError, BrowserLocalStorage, GraphQLError, MemoryStorag
|
|
|
7
7
|
var QueryCache = class {
|
|
8
8
|
cache = /* @__PURE__ */ new Map();
|
|
9
9
|
inflight = /* @__PURE__ */ new Map();
|
|
10
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
11
|
+
generations = /* @__PURE__ */ new Map();
|
|
12
|
+
pending = /* @__PURE__ */ new Map();
|
|
10
13
|
staleTime = 0;
|
|
11
14
|
static key(query, variables) {
|
|
12
15
|
return `${query}::${variables ? JSON.stringify(variables) : ""}`;
|
|
@@ -31,24 +34,172 @@ var QueryCache = class {
|
|
|
31
34
|
const existing = this.inflight.get(key);
|
|
32
35
|
if (existing) return existing;
|
|
33
36
|
const promise = fetcher().finally(() => {
|
|
34
|
-
this.
|
|
37
|
+
const remaining = (this.pending.get(key) ?? 1) - 1;
|
|
38
|
+
if (remaining > 0) {
|
|
39
|
+
this.pending.set(key, remaining);
|
|
40
|
+
} else {
|
|
41
|
+
this.pending.delete(key);
|
|
42
|
+
}
|
|
43
|
+
if (this.inflight.get(key) === promise) {
|
|
44
|
+
this.inflight.delete(key);
|
|
45
|
+
}
|
|
35
46
|
});
|
|
47
|
+
this.pending.set(key, (this.pending.get(key) ?? 0) + 1);
|
|
36
48
|
this.inflight.set(key, promise);
|
|
37
49
|
return promise;
|
|
38
50
|
}
|
|
39
51
|
getInflight(key) {
|
|
40
52
|
return this.inflight.get(key);
|
|
41
53
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to invalidations of a single cache key.
|
|
56
|
+
*
|
|
57
|
+
* The listener fires when {@link invalidate} matches this key or {@link clear} runs —
|
|
58
|
+
* never on {@link set}, so a listener that re-fetches (and writes the result back)
|
|
59
|
+
* cannot re-trigger itself.
|
|
60
|
+
*
|
|
61
|
+
* @returns an unsubscribe function; call it on unmount.
|
|
62
|
+
*/
|
|
63
|
+
subscribe(key, listener) {
|
|
64
|
+
let listeners = this.subscribers.get(key);
|
|
65
|
+
if (!listeners) {
|
|
66
|
+
listeners = /* @__PURE__ */ new Set();
|
|
67
|
+
this.subscribers.set(key, listeners);
|
|
68
|
+
}
|
|
69
|
+
listeners.add(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
const current = this.subscribers.get(key);
|
|
72
|
+
if (!current) return;
|
|
73
|
+
current.delete(listener);
|
|
74
|
+
if (current.size === 0) {
|
|
75
|
+
this.subscribers.delete(key);
|
|
76
|
+
this.pruneGeneration(key);
|
|
46
77
|
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Current generation of a key. It increases every time the key is invalidated,
|
|
82
|
+
* so a caller can capture it before an `await` and check {@link isCurrent}
|
|
83
|
+
* afterwards to find out whether its response was superseded meanwhile.
|
|
84
|
+
*
|
|
85
|
+
* Reading is free of side effects: a generation is only ever recorded for a key
|
|
86
|
+
* something can still compare against — see {@link pruneGeneration}.
|
|
87
|
+
*/
|
|
88
|
+
generation(key) {
|
|
89
|
+
return this.generations.get(key) ?? 0;
|
|
90
|
+
}
|
|
91
|
+
/** True while `generation` is still the live generation of `key`. */
|
|
92
|
+
isCurrent(key, generation) {
|
|
93
|
+
return (this.generations.get(key) ?? 0) === generation;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Drop every cached entry matching `patterns`, then tell the subscribers on
|
|
97
|
+
* those keys to re-fetch. A key matches when it *contains* any of the patterns.
|
|
98
|
+
*
|
|
99
|
+
* Pass the whole batch in one call (`['user', 'users']`) rather than looping:
|
|
100
|
+
* the affected keys are resolved once, so a key matched by several patterns is
|
|
101
|
+
* invalidated and notified exactly once instead of racing itself.
|
|
102
|
+
*
|
|
103
|
+
* In-flight requests for matching keys are unregistered and the generation of
|
|
104
|
+
* each key is bumped: those requests were issued before the write landed, so
|
|
105
|
+
* neither joining them nor applying their result may hand back stale data.
|
|
106
|
+
*
|
|
107
|
+
* Matching is substring-based, which is deliberately loose — a pattern like
|
|
108
|
+
* `'id'` will match unrelated queries and cost them a refetch. Prefer patterns
|
|
109
|
+
* distinctive enough to name the entity you actually mutated.
|
|
110
|
+
*/
|
|
111
|
+
invalidate(patterns) {
|
|
112
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
113
|
+
if (list.length === 0) return;
|
|
114
|
+
const matches = (key) => list.some((pattern) => key.includes(pattern));
|
|
115
|
+
const affected = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const key of this.cache.keys()) if (matches(key)) affected.add(key);
|
|
117
|
+
for (const key of this.inflight.keys()) if (matches(key)) affected.add(key);
|
|
118
|
+
for (const key of this.subscribers.keys()) if (matches(key)) affected.add(key);
|
|
119
|
+
for (const key of this.generations.keys()) if (matches(key)) affected.add(key);
|
|
120
|
+
for (const key of affected) {
|
|
121
|
+
this.cache.delete(key);
|
|
122
|
+
this.inflight.delete(key);
|
|
123
|
+
if (this.hasHolder(key)) {
|
|
124
|
+
this.bumpGeneration(key);
|
|
125
|
+
} else {
|
|
126
|
+
this.generations.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const key of affected) {
|
|
130
|
+
this.notify(key);
|
|
47
131
|
}
|
|
48
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Wipe everything and notify **every** subscriber, so all mounted queries
|
|
135
|
+
* revalidate. That is intentional — a full clear means no key can be trusted —
|
|
136
|
+
* but it does mean calling this on sign-out fires a refetch for every mounted
|
|
137
|
+
* query, which is rarely what you want with a dead token.
|
|
138
|
+
*/
|
|
49
139
|
clear() {
|
|
140
|
+
const known = /* @__PURE__ */ new Set([
|
|
141
|
+
...this.generations.keys(),
|
|
142
|
+
...this.pending.keys(),
|
|
143
|
+
...this.subscribers.keys()
|
|
144
|
+
]);
|
|
145
|
+
for (const key of known) {
|
|
146
|
+
if (this.hasHolder(key)) {
|
|
147
|
+
this.bumpGeneration(key);
|
|
148
|
+
} else {
|
|
149
|
+
this.generations.delete(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
50
152
|
this.cache.clear();
|
|
51
153
|
this.inflight.clear();
|
|
154
|
+
for (const key of [...this.subscribers.keys()]) {
|
|
155
|
+
this.notify(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
bumpGeneration(key) {
|
|
159
|
+
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Whether anything can still compare a generation for this key: a mounted
|
|
163
|
+
* consumer that may fetch it, or a request already on its way back.
|
|
164
|
+
*/
|
|
165
|
+
hasHolder(key) {
|
|
166
|
+
return this.subscribers.has(key) || (this.pending.get(key) ?? 0) > 0;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Forget a key's generation once nothing references it.
|
|
170
|
+
*
|
|
171
|
+
* Pruning is deliberately **not** done when a request settles. A pruned entry
|
|
172
|
+
* reads back as generation 0, which is exactly what a caller that captured 0
|
|
173
|
+
* holds — so a response that was superseded while in flight would be accepted
|
|
174
|
+
* again, and would write pre-mutation data into the cache. The prune therefore
|
|
175
|
+
* happens at the last unsubscribe, and only while nothing is pending.
|
|
176
|
+
*
|
|
177
|
+
* That leaves one bounded remainder, and it is worth naming precisely: if a key
|
|
178
|
+
* has no subscriber left but a request still on its way back, {@link invalidate}
|
|
179
|
+
* or {@link clear} bumps its entry rather than dropping it, and the settle path
|
|
180
|
+
* does not reclaim it. It survives — one entry, holding one key — until some
|
|
181
|
+
* *later* {@link invalidate} matches that key and finds nothing holding it. The
|
|
182
|
+
* two ways in are a consumer that unmounts mid-request and is then invalidated,
|
|
183
|
+
* and a `clear()` (a sign-out, say) fired while requests are in flight, which
|
|
184
|
+
* leaves one entry per key that was still loading. Since patterns match by
|
|
185
|
+
* substring, the next mutation touching that entity reclaims every such remnant
|
|
186
|
+
* at once. Bytes, deliberately traded for the guarantee above.
|
|
187
|
+
*/
|
|
188
|
+
pruneGeneration(key) {
|
|
189
|
+
if (this.hasHolder(key)) return;
|
|
190
|
+
this.generations.delete(key);
|
|
191
|
+
}
|
|
192
|
+
/** Notify one key's listeners. Iterates a snapshot so listeners may unsubscribe. */
|
|
193
|
+
notify(key) {
|
|
194
|
+
const listeners = this.subscribers.get(key);
|
|
195
|
+
if (!listeners) return;
|
|
196
|
+
for (const listener of [...listeners]) {
|
|
197
|
+
if (!listeners.has(listener)) continue;
|
|
198
|
+
try {
|
|
199
|
+
listener();
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
52
203
|
}
|
|
53
204
|
};
|
|
54
205
|
|
|
@@ -198,6 +349,7 @@ function useQuery(gql, variables, options) {
|
|
|
198
349
|
const cacheKey = QueryCache.key(gql, variables);
|
|
199
350
|
const optionsRef = useRef(options);
|
|
200
351
|
optionsRef.current = options;
|
|
352
|
+
const activeFetches = useRef(0);
|
|
201
353
|
const fetchData = useCallback(
|
|
202
354
|
async (isRefetch = false) => {
|
|
203
355
|
if (!enabled) return;
|
|
@@ -206,11 +358,14 @@ function useQuery(gql, variables, options) {
|
|
|
206
358
|
} else {
|
|
207
359
|
setIsLoading(true);
|
|
208
360
|
}
|
|
361
|
+
activeFetches.current += 1;
|
|
362
|
+
const generation = cache.generation(cacheKey);
|
|
209
363
|
try {
|
|
210
364
|
const result = await cache.dedup(cacheKey, () => client.graphql.query(gql, variables));
|
|
365
|
+
if (!cache.isCurrent(cacheKey, generation)) return;
|
|
211
366
|
if (result.error) {
|
|
212
367
|
setError(result.error);
|
|
213
|
-
setData(null);
|
|
368
|
+
if (!isRefetch) setData(null);
|
|
214
369
|
optionsRef.current?.onError?.(result.error);
|
|
215
370
|
} else {
|
|
216
371
|
setData(result.data);
|
|
@@ -219,11 +374,15 @@ function useQuery(gql, variables, options) {
|
|
|
219
374
|
optionsRef.current?.onSuccess?.(result.data);
|
|
220
375
|
}
|
|
221
376
|
} catch (err) {
|
|
377
|
+
if (!cache.isCurrent(cacheKey, generation)) return;
|
|
222
378
|
setError(err);
|
|
223
|
-
setData(null);
|
|
379
|
+
if (!isRefetch) setData(null);
|
|
224
380
|
} finally {
|
|
225
|
-
|
|
226
|
-
|
|
381
|
+
activeFetches.current -= 1;
|
|
382
|
+
if (activeFetches.current === 0) {
|
|
383
|
+
setIsLoading(false);
|
|
384
|
+
setIsRefetching(false);
|
|
385
|
+
}
|
|
227
386
|
}
|
|
228
387
|
},
|
|
229
388
|
[client, gql, variablesKey, enabled, cacheKey, cache]
|
|
@@ -242,6 +401,14 @@ function useQuery(gql, variables, options) {
|
|
|
242
401
|
fetchData(false);
|
|
243
402
|
}
|
|
244
403
|
}, [cacheKey, enabled]);
|
|
404
|
+
const fetchDataRef = useRef(fetchData);
|
|
405
|
+
fetchDataRef.current = fetchData;
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (!enabled) return;
|
|
408
|
+
return cache.subscribe(cacheKey, () => {
|
|
409
|
+
fetchDataRef.current(true);
|
|
410
|
+
});
|
|
411
|
+
}, [cacheKey, enabled, cache]);
|
|
245
412
|
const prevSessionRef = useRef(session?.accessToken);
|
|
246
413
|
useEffect(() => {
|
|
247
414
|
if (enabled && prevSessionRef.current !== session?.accessToken && prevSessionRef.current !== void 0) {
|
|
@@ -289,10 +456,9 @@ function useMutation(gql, options) {
|
|
|
289
456
|
setData(result.data);
|
|
290
457
|
setError(null);
|
|
291
458
|
optionsRef.current?.onSuccess?.(result.data);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
459
|
+
const invalidateQueries = optionsRef.current?.invalidateQueries;
|
|
460
|
+
if (invalidateQueries?.length) {
|
|
461
|
+
cache.invalidate(invalidateQueries);
|
|
296
462
|
}
|
|
297
463
|
optionsRef.current?.onSettled?.();
|
|
298
464
|
return result.data;
|
|
@@ -465,6 +631,11 @@ function useRestMutation(path, method = "POST") {
|
|
|
465
631
|
}, []);
|
|
466
632
|
return { execute, data, error, isLoading, reset };
|
|
467
633
|
}
|
|
634
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
635
|
+
function idsAt(items, ts) {
|
|
636
|
+
if (ts == null) return [];
|
|
637
|
+
return items.filter((n) => n.createdAt === ts).map((n) => n.id);
|
|
638
|
+
}
|
|
468
639
|
function useNotifications(receiverId, options) {
|
|
469
640
|
const { client } = useArchieContext();
|
|
470
641
|
const [notifications, setNotifications] = useState([]);
|
|
@@ -475,20 +646,59 @@ function useNotifications(receiverId, options) {
|
|
|
475
646
|
const [connectionState, setConnectionState] = useState(
|
|
476
647
|
() => client.realtime.currentState
|
|
477
648
|
);
|
|
649
|
+
const [hasMore, setHasMore] = useState(false);
|
|
650
|
+
const [isFetchingMore, setIsFetchingMore] = useState(false);
|
|
478
651
|
const enabled = options?.enabled !== false;
|
|
479
652
|
const unreadOnly = options?.unreadOnly ?? false;
|
|
480
653
|
const limit = options?.limit;
|
|
654
|
+
const pageSize = limit ?? DEFAULT_PAGE_SIZE;
|
|
481
655
|
const optionsRef = useRef(options);
|
|
482
656
|
optionsRef.current = options;
|
|
483
657
|
const notificationsRef = useRef([]);
|
|
484
658
|
notificationsRef.current = notifications;
|
|
485
659
|
const seenIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
660
|
+
const oldestCreatedAtRef = useRef(null);
|
|
661
|
+
const boundaryIdsRef = useRef([]);
|
|
662
|
+
const isFetchingMoreRef = useRef(false);
|
|
663
|
+
const paginationErroredRef = useRef(false);
|
|
664
|
+
const hasMoreRef = useRef(false);
|
|
665
|
+
hasMoreRef.current = hasMore;
|
|
666
|
+
const loadTokenRef = useRef(0);
|
|
667
|
+
const invalidateInFlightPagination = useCallback(() => {
|
|
668
|
+
loadTokenRef.current += 1;
|
|
669
|
+
isFetchingMoreRef.current = false;
|
|
670
|
+
paginationErroredRef.current = false;
|
|
671
|
+
setIsFetchingMore(false);
|
|
672
|
+
}, []);
|
|
673
|
+
const seedFromList = useCallback((items, totalCount) => {
|
|
674
|
+
const oldest = items.length > 0 ? items[items.length - 1].createdAt : null;
|
|
675
|
+
oldestCreatedAtRef.current = oldest;
|
|
676
|
+
boundaryIdsRef.current = idsAt(items, oldest);
|
|
677
|
+
setHasMore(items.length < totalCount);
|
|
678
|
+
}, []);
|
|
679
|
+
const mergeAndSeed = useCallback(
|
|
680
|
+
(items, totalCount) => {
|
|
681
|
+
const serverIds = new Set(items.map((n) => n.id));
|
|
682
|
+
const newestSnapshot = items.length > 0 ? items[0].createdAt : null;
|
|
683
|
+
const liveOnly = notificationsRef.current.filter(
|
|
684
|
+
(n) => !serverIds.has(n.id) && (newestSnapshot === null || n.createdAt > newestSnapshot)
|
|
685
|
+
);
|
|
686
|
+
setNotifications([...liveOnly, ...items]);
|
|
687
|
+
seenIdsRef.current = /* @__PURE__ */ new Set([...liveOnly.map((n) => n.id), ...items.map((n) => n.id)]);
|
|
688
|
+
seedFromList(items, totalCount);
|
|
689
|
+
},
|
|
690
|
+
[seedFromList]
|
|
691
|
+
);
|
|
486
692
|
useEffect(() => {
|
|
487
693
|
setNotifications([]);
|
|
488
694
|
setLatest(null);
|
|
489
695
|
setUnreadCount(0);
|
|
490
696
|
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
491
|
-
|
|
697
|
+
oldestCreatedAtRef.current = null;
|
|
698
|
+
boundaryIdsRef.current = [];
|
|
699
|
+
setHasMore(false);
|
|
700
|
+
invalidateInFlightPagination();
|
|
701
|
+
}, [receiverId, unreadOnly, invalidateInFlightPagination]);
|
|
492
702
|
const load = useCallback(async () => {
|
|
493
703
|
const [listResult, unread] = await Promise.all([
|
|
494
704
|
client.notifications.list(receiverId, { unreadOnly, first: limit }),
|
|
@@ -502,11 +712,11 @@ function useNotifications(receiverId, options) {
|
|
|
502
712
|
return;
|
|
503
713
|
}
|
|
504
714
|
let active = true;
|
|
715
|
+
invalidateInFlightPagination();
|
|
505
716
|
setIsLoading(true);
|
|
506
717
|
load().then(({ listResult, unread }) => {
|
|
507
718
|
if (!active) return;
|
|
508
|
-
|
|
509
|
-
seenIdsRef.current = new Set(listResult.items.map((n) => n.id));
|
|
719
|
+
mergeAndSeed(listResult.items, listResult.totalCount);
|
|
510
720
|
setUnreadCount(unread);
|
|
511
721
|
setError(null);
|
|
512
722
|
}).catch((err) => {
|
|
@@ -517,7 +727,7 @@ function useNotifications(receiverId, options) {
|
|
|
517
727
|
return () => {
|
|
518
728
|
active = false;
|
|
519
729
|
};
|
|
520
|
-
}, [enabled, load]);
|
|
730
|
+
}, [enabled, load, mergeAndSeed, invalidateInFlightPagination]);
|
|
521
731
|
useEffect(() => {
|
|
522
732
|
if (!enabled) return;
|
|
523
733
|
const unsubConnection = client.realtime.onConnectionStateChange(setConnectionState);
|
|
@@ -566,11 +776,11 @@ function useNotifications(receiverId, options) {
|
|
|
566
776
|
}
|
|
567
777
|
}, [client, receiverId]);
|
|
568
778
|
const refetch = useCallback(async () => {
|
|
779
|
+
invalidateInFlightPagination();
|
|
569
780
|
setIsLoading(true);
|
|
570
781
|
try {
|
|
571
782
|
const { listResult, unread } = await load();
|
|
572
|
-
|
|
573
|
-
seenIdsRef.current = new Set(listResult.items.map((n) => n.id));
|
|
783
|
+
mergeAndSeed(listResult.items, listResult.totalCount);
|
|
574
784
|
setUnreadCount(unread);
|
|
575
785
|
setError(null);
|
|
576
786
|
} catch (err) {
|
|
@@ -578,7 +788,55 @@ function useNotifications(receiverId, options) {
|
|
|
578
788
|
} finally {
|
|
579
789
|
setIsLoading(false);
|
|
580
790
|
}
|
|
581
|
-
}, [load]);
|
|
791
|
+
}, [load, mergeAndSeed, invalidateInFlightPagination]);
|
|
792
|
+
const fetchMore = useCallback(async () => {
|
|
793
|
+
if (!enabled || !hasMoreRef.current || isFetchingMoreRef.current || paginationErroredRef.current) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const cursor = oldestCreatedAtRef.current;
|
|
797
|
+
if (cursor == null) return;
|
|
798
|
+
const token = loadTokenRef.current;
|
|
799
|
+
isFetchingMoreRef.current = true;
|
|
800
|
+
setIsFetchingMore(true);
|
|
801
|
+
try {
|
|
802
|
+
const page = await client.notifications.list(receiverId, {
|
|
803
|
+
unreadOnly,
|
|
804
|
+
first: pageSize,
|
|
805
|
+
orderBy: { createdAt: "DESC" },
|
|
806
|
+
createdBefore: cursor,
|
|
807
|
+
// Tie-safe keyset: don't skip rows sharing the cursor timestamp that we haven't loaded yet.
|
|
808
|
+
excludeIds: boundaryIdsRef.current,
|
|
809
|
+
// Page-fill drives hasMore below, so skip the (potentially O(N)) COUNT on deep pages.
|
|
810
|
+
includeTotal: false
|
|
811
|
+
});
|
|
812
|
+
if (loadTokenRef.current !== token) return;
|
|
813
|
+
if (page.items.length === 0) {
|
|
814
|
+
setHasMore(false);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const fresh = page.items.filter((n) => !seenIdsRef.current.has(n.id));
|
|
818
|
+
fresh.forEach((n) => seenIdsRef.current.add(n.id));
|
|
819
|
+
if (fresh.length > 0) {
|
|
820
|
+
setNotifications((prev) => [...prev, ...fresh]);
|
|
821
|
+
}
|
|
822
|
+
const newOldest = page.items[page.items.length - 1].createdAt;
|
|
823
|
+
const idsAtNewOldest = idsAt(page.items, newOldest);
|
|
824
|
+
boundaryIdsRef.current = newOldest === cursor ? Array.from(/* @__PURE__ */ new Set([...boundaryIdsRef.current, ...idsAtNewOldest])) : idsAtNewOldest;
|
|
825
|
+
oldestCreatedAtRef.current = newOldest;
|
|
826
|
+
setHasMore(page.items.length === pageSize);
|
|
827
|
+
setError(null);
|
|
828
|
+
} catch (err) {
|
|
829
|
+
if (loadTokenRef.current === token) {
|
|
830
|
+
paginationErroredRef.current = true;
|
|
831
|
+
setError(err);
|
|
832
|
+
}
|
|
833
|
+
} finally {
|
|
834
|
+
if (loadTokenRef.current === token) {
|
|
835
|
+
isFetchingMoreRef.current = false;
|
|
836
|
+
setIsFetchingMore(false);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}, [client, receiverId, unreadOnly, pageSize, enabled]);
|
|
582
840
|
return {
|
|
583
841
|
notifications,
|
|
584
842
|
latest,
|
|
@@ -588,7 +846,10 @@ function useNotifications(receiverId, options) {
|
|
|
588
846
|
connectionState,
|
|
589
847
|
markAsRead,
|
|
590
848
|
markAllAsRead,
|
|
591
|
-
refetch
|
|
849
|
+
refetch,
|
|
850
|
+
fetchMore,
|
|
851
|
+
hasMore,
|
|
852
|
+
isFetchingMore
|
|
592
853
|
};
|
|
593
854
|
}
|
|
594
855
|
function AuthGuard({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/query-cache.ts","../src/provider.tsx","../src/hooks/useArchieClient.ts","../src/hooks/useAuth.ts","../src/hooks/useQuery.ts","../src/hooks/useMutation.ts","../src/hooks/useSubscription.ts","../src/hooks/useFileUpload.ts","../src/hooks/useRest.ts","../src/hooks/useNotifications.ts","../src/components/AuthGuard.tsx"],"names":["React","useMemo","useState","useCallback","useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-memory query cache with in-flight request deduplication and stale-while-revalidate.\n * @internal\n */\n\ninterface CacheEntry<T = unknown> {\n data: T;\n timestamp: number;\n}\n\nexport class QueryCache {\n private cache = new Map<string, CacheEntry>();\n private inflight = new Map<string, Promise<unknown>>();\n private staleTime = 0;\n\n static key(query: string, variables?: Record<string, unknown>): string {\n return `${query}::${variables ? JSON.stringify(variables) : ''}`;\n }\n\n get<T>(key: string): CacheEntry<T> | undefined {\n return this.cache.get(key) as CacheEntry<T> | undefined;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, timestamp: Date.now() });\n }\n\n isFresh(key: string): boolean {\n const entry = this.cache.get(key);\n if (!entry) return false;\n if (this.staleTime === 0) return false;\n return Date.now() - entry.timestamp < this.staleTime;\n }\n\n /**\n * Deduplicate in-flight requests: if an identical fetch is already pending,\n * return the same promise. Otherwise, run `fetcher` and share the promise.\n */\n async dedup<T>(key: string, fetcher: () => Promise<T>): Promise<T> {\n const existing = this.inflight.get(key);\n if (existing) return existing as Promise<T>;\n\n const promise = fetcher().finally(() => {\n this.inflight.delete(key);\n });\n\n this.inflight.set(key, promise);\n return promise;\n }\n\n getInflight<T>(key: string): Promise<T> | undefined {\n return this.inflight.get(key) as Promise<T> | undefined;\n }\n\n invalidate(querySubstring: string): void {\n for (const key of this.cache.keys()) {\n if (key.includes(querySubstring)) {\n this.cache.delete(key);\n }\n }\n }\n\n clear(): void {\n this.cache.clear();\n this.inflight.clear();\n }\n}\n","import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';\nimport type { ArchieClient, Session, AuthEvent } from '@archie/js-sdk';\nimport { QueryCache } from './lib/query-cache';\n\n// ─── Context ──────────────────────────────────────────────────\n\ninterface ArchieContextValue {\n client: ArchieClient;\n session: Session | null;\n isLoading: boolean;\n cache: QueryCache;\n}\n\nexport const ArchieContext = createContext<ArchieContextValue | null>(null);\n\n// ─── Provider ─────────────────────────────────────────────────\n\nexport interface ArchieProviderProps {\n client: ArchieClient;\n children: React.ReactNode;\n /** @internal Inject a custom cache (useful for testing). */\n cache?: QueryCache;\n}\n\n/**\n * ArchieProvider — wraps your React app to provide the Archie client and session state.\n *\n * @example\n * ```tsx\n * import { createClient } from '@archie/js-sdk'\n * import { ArchieProvider } from '@archie/react-sdk'\n *\n * const archie = createClient({ projectId: '...' })\n *\n * function App() {\n * return (\n * <ArchieProvider client={archie}>\n * <MyApp />\n * </ArchieProvider>\n * )\n * }\n * ```\n */\nexport function ArchieProvider({ client, children, cache: injectedCache }: ArchieProviderProps) {\n const [session, setSession] = useState<Session | null>(client.auth.getSession());\n const [isLoading, setIsLoading] = useState(true);\n const defaultCache = useMemo(() => new QueryCache(), [client]);\n const cache = injectedCache ?? defaultCache;\n\n useEffect(() => {\n client.auth.waitForInit().then(() => {\n setSession(client.auth.getSession());\n setIsLoading(false);\n });\n\n const unsubscribe = client.auth.onAuthStateChange(\n (_event: AuthEvent, newSession: Session | null) => {\n setSession(newSession);\n },\n );\n\n return unsubscribe;\n }, [client]);\n\n const value = useMemo<ArchieContextValue>(\n () => ({ client, session, isLoading, cache }),\n [client, session, isLoading, cache],\n );\n\n return React.createElement(ArchieContext.Provider, { value }, children);\n}\n\n/**\n * Internal hook to access the Archie context.\n * @throws if used outside ArchieProvider\n * @internal\n */\nexport function useArchieContext(): ArchieContextValue {\n const context = useContext(ArchieContext);\n if (!context) {\n throw new Error(\n 'useArchieClient must be used within an <ArchieProvider>. ' +\n 'Wrap your app with <ArchieProvider client={archieClient}>.',\n );\n }\n return context;\n}\n","import { useArchieContext } from '../provider';\nimport type { ArchieClient } from '@archie/js-sdk';\n\n/**\n * Returns the ArchieClient instance from context.\n * Must be used within an ArchieProvider.\n *\n * @throws Error if used outside ArchieProvider\n */\nexport function useArchieClient(): ArchieClient {\n const { client } = useArchieContext();\n return client;\n}\n","import { useCallback, useMemo } from 'react';\nimport { useArchieContext } from '../provider';\nimport type {\n User,\n Session,\n AuthSignUpParams,\n AuthSignInParams,\n AuthConfirmParams,\n AuthRecoverParams,\n AuthResetPasswordParams,\n ArchieError,\n} from '@archie/js-sdk';\n\ninterface UseAuthReturn {\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n signIn: (\n params: AuthSignInParams,\n ) => Promise<{ session: Session | null; error: ArchieError | null }>;\n signUp: (\n params: AuthSignUpParams,\n ) => Promise<{ data: { userId: string; message: string } | null; error: ArchieError | null }>;\n signOut: () => Promise<void>;\n confirmSignUp: (\n params: AuthConfirmParams,\n ) => Promise<{ session: Session | null; error: ArchieError | null }>;\n recoverPassword: (\n params: AuthRecoverParams,\n ) => Promise<{ data: { message: string } | null; error: ArchieError | null }>;\n resetPassword: (\n params: AuthResetPasswordParams,\n ) => Promise<{ data: { message: string } | null; error: ArchieError | null }>;\n refreshSession: () => Promise<{ session: Session | null; error: ArchieError | null }>;\n}\n\n/**\n * Hook for authentication state and operations.\n * All methods return `{ data/session, error }` (never throws).\n */\nexport function useAuth(): UseAuthReturn {\n const { client, session, isLoading } = useArchieContext();\n\n const signIn = useCallback(\n async (params: AuthSignInParams) => {\n try {\n const result = await client.auth.signIn(params);\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const signUp = useCallback(\n async (params: AuthSignUpParams) => {\n try {\n const result = await client.auth.signUp(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const signOut = useCallback(async () => {\n await client.auth.signOut();\n }, [client]);\n\n const confirmSignUp = useCallback(\n async (params: AuthConfirmParams) => {\n try {\n const result = await client.auth.confirmSignUp(params);\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const recoverPassword = useCallback(\n async (params: AuthRecoverParams) => {\n try {\n const result = await client.auth.recoverPassword(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const resetPassword = useCallback(\n async (params: AuthResetPasswordParams) => {\n try {\n const result = await client.auth.resetPassword(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const refreshSession = useCallback(async () => {\n try {\n const result = await client.auth.refreshSession();\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n }, [client]);\n\n return useMemo(\n () => ({\n user: session?.user ?? null,\n session,\n isLoading,\n isAuthenticated: session !== null,\n signIn,\n signUp,\n signOut,\n confirmSignUp,\n recoverPassword,\n resetPassword,\n refreshSession,\n }),\n [\n session,\n isLoading,\n signIn,\n signUp,\n signOut,\n confirmSignUp,\n recoverPassword,\n resetPassword,\n refreshSession,\n ],\n );\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport { QueryCache } from '../lib/query-cache';\nimport type { ArchieError } from '@archie/js-sdk';\n\ninterface UseQueryOptions<T> {\n enabled?: boolean;\n refetchInterval?: number;\n refetchOnWindowFocus?: boolean;\n onSuccess?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n}\n\ninterface UseQueryReturn<T> {\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n isRefetching: boolean;\n refetch: () => Promise<void>;\n}\n\n/**\n * Declarative GraphQL query hook with caching and stale-while-revalidate.\n *\n * @example\n * ```tsx\n * const { data, isLoading } = useQuery<{ users: User[] }>(\n * 'query { users { id email name } }',\n * { limit: 10 }\n * )\n * ```\n */\nexport function useQuery<T = unknown>(\n gql: string,\n variables?: Record<string, unknown>,\n options?: UseQueryOptions<T>,\n): UseQueryReturn<T> {\n const { client, session, cache } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [isRefetching, setIsRefetching] = useState(false);\n\n const enabled = options?.enabled !== false;\n\n const variablesKey = variables ? JSON.stringify(variables) : '';\n const cacheKey = QueryCache.key(gql, variables);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const fetchData = useCallback(\n async (isRefetch = false) => {\n if (!enabled) return;\n\n if (isRefetch) {\n setIsRefetching(true);\n } else {\n setIsLoading(true);\n }\n\n try {\n const result = await cache.dedup(cacheKey, () => client.graphql.query<T>(gql, variables));\n if (result.error) {\n setError(result.error);\n setData(null);\n optionsRef.current?.onError?.(result.error);\n } else {\n setData(result.data);\n setError(null);\n cache.set(cacheKey, result.data);\n optionsRef.current?.onSuccess?.(result.data as T);\n }\n } catch (err) {\n setError(err as ArchieError);\n setData(null);\n } finally {\n setIsLoading(false);\n setIsRefetching(false);\n }\n },\n [client, gql, variablesKey, enabled, cacheKey, cache],\n );\n\n // Initial fetch & refetch on variable change\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n\n const cached = cache.get<T>(cacheKey);\n if (cached) {\n setData(cached.data);\n setIsLoading(false);\n fetchData(true);\n } else {\n fetchData(false);\n }\n }, [cacheKey, enabled]);\n\n // Refetch on auth state change (new token)\n const prevSessionRef = useRef(session?.accessToken);\n useEffect(() => {\n if (\n enabled &&\n prevSessionRef.current !== session?.accessToken &&\n prevSessionRef.current !== undefined\n ) {\n fetchData(true);\n }\n prevSessionRef.current = session?.accessToken;\n }, [session?.accessToken, enabled]);\n\n // Polling (refetchInterval)\n useEffect(() => {\n if (!enabled || !options?.refetchInterval) return;\n\n const interval = setInterval(() => {\n fetchData(true);\n }, options.refetchInterval);\n\n return () => clearInterval(interval);\n }, [enabled, options?.refetchInterval]);\n\n // Refetch on window focus\n useEffect(() => {\n if (!enabled || !options?.refetchOnWindowFocus) return;\n if (typeof window === 'undefined') return;\n\n const handleFocus = () => fetchData(true);\n window.addEventListener('focus', handleFocus);\n return () => window.removeEventListener('focus', handleFocus);\n }, [enabled, options?.refetchOnWindowFocus]);\n\n const refetch = useCallback(() => fetchData(true), [fetchData]);\n\n return { data, error, isLoading, isRefetching, refetch };\n}\n","import { useState, useCallback, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError } from '@archie/js-sdk';\n\ninterface UseMutationOptions<T> {\n onSuccess?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n onSettled?: () => void;\n /** Query substrings to invalidate (forcing refetch) after a successful mutation. */\n invalidateQueries?: string[];\n}\n\ninterface UseMutationReturn<T> {\n mutate: (variables?: Record<string, unknown>) => void;\n mutateAsync: (variables?: Record<string, unknown>) => Promise<T | null>;\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n reset: () => void;\n}\n\n/**\n * Hook for GraphQL mutations.\n *\n * @example\n * ```tsx\n * const { mutate, isLoading } = useMutation<{ createUser: User }>(\n * 'mutation($data: CreateUserInput!) { createUser(data: $data) { id email } }',\n * { invalidateQueries: ['users'] }\n * )\n *\n * mutate({ data: { email: 'new@user.com', name: 'John' } })\n * ```\n */\nexport function useMutation<T = unknown>(\n gql: string,\n options?: UseMutationOptions<T>,\n): UseMutationReturn<T> {\n const { client, cache } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const execute = useCallback(\n async (variables?: Record<string, unknown>): Promise<T | null> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const result = await client.graphql.mutate<T>(gql, variables);\n\n if (result.error) {\n setError(result.error);\n setData(null);\n optionsRef.current?.onError?.(result.error);\n optionsRef.current?.onSettled?.();\n return null;\n }\n\n setData(result.data);\n setError(null);\n optionsRef.current?.onSuccess?.(result.data as T);\n\n if (optionsRef.current?.invalidateQueries) {\n for (const queryKey of optionsRef.current.invalidateQueries) {\n cache.invalidate(queryKey);\n }\n }\n\n optionsRef.current?.onSettled?.();\n return result.data;\n } catch (err) {\n const archieErr = err as ArchieError;\n setError(archieErr);\n setData(null);\n optionsRef.current?.onError?.(archieErr);\n optionsRef.current?.onSettled?.();\n return null;\n } finally {\n setIsLoading(false);\n }\n },\n [client, gql, cache],\n );\n\n const mutate = useCallback(\n (variables?: Record<string, unknown>) => {\n execute(variables);\n },\n [execute],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsLoading(false);\n }, []);\n\n return { mutate, mutateAsync: execute, data, error, isLoading, reset };\n}\n","import { useState, useEffect, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, ConnectionState } from '@archie/js-sdk';\n\ninterface UseSubscriptionOptions<T> {\n enabled?: boolean;\n onData?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n}\n\ninterface UseSubscriptionReturn<T> {\n data: T | null;\n error: ArchieError | null;\n connectionState: ConnectionState;\n}\n\n/**\n * Hook for real-time GraphQL subscriptions.\n * Subscribes on mount, unsubscribes on unmount.\n *\n * @example\n * ```tsx\n * const { data } = useSubscription<{ orderUpdated: Order }>(\n * 'subscription { orderUpdated { id status total } }'\n * )\n * ```\n */\nexport function useSubscription<T = unknown>(\n gql: string,\n variables?: Record<string, unknown>,\n options?: UseSubscriptionOptions<T>,\n): UseSubscriptionReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>('DISCONNECTED');\n\n const enabled = options?.enabled !== false;\n const variablesKey = variables ? JSON.stringify(variables) : '';\n\n // Track latest callbacks to avoid stale closures\n const callbacksRef = useRef(options);\n callbacksRef.current = options;\n\n useEffect(() => {\n if (!enabled) return;\n\n const unsub = client.realtime.onConnectionStateChange(setConnectionState);\n\n const subscription = client.realtime.subscribe<T>(gql, variables, {\n onData: (newData: T) => {\n setData(newData);\n setError(null);\n callbacksRef.current?.onData?.(newData);\n },\n onError: (err) => {\n setError(err);\n callbacksRef.current?.onError?.(err);\n },\n onComplete: () => {\n // Subscription completed server-side\n },\n });\n\n return () => {\n subscription.unsubscribe();\n unsub();\n };\n }, [client, gql, variablesKey, enabled]);\n\n return { data, error, connectionState };\n}\n","import { useState, useCallback } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, FileUploadOptions, FileUploadResult } from '@archie/js-sdk';\n\ninterface UseFileUploadReturn {\n upload: (file: File | Blob, options?: FileUploadOptions) => Promise<void>;\n progress: number;\n isUploading: boolean;\n data: FileUploadResult | null;\n error: ArchieError | null;\n reset: () => void;\n}\n\n/**\n * Hook for file uploads.\n *\n * @example\n * ```tsx\n * const { upload, isUploading, data } = useFileUpload()\n *\n * <input type=\"file\" onChange={(e) => upload(e.target.files[0])} />\n * {isUploading && <Spinner />}\n * ```\n */\nexport function useFileUpload(): UseFileUploadReturn {\n const { client } = useArchieContext();\n const [progress, setProgress] = useState(0);\n const [isUploading, setIsUploading] = useState(false);\n const [data, setData] = useState<FileUploadResult | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n\n const upload = useCallback(\n async (file: File | Blob, options?: FileUploadOptions) => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n setData(null);\n\n try {\n const result = await client.files.upload(file, options);\n\n setData(result);\n setProgress(100);\n } catch (err) {\n setError(err as ArchieError);\n } finally {\n setIsUploading(false);\n }\n },\n [client],\n );\n\n const reset = useCallback(() => {\n setProgress(0);\n setIsUploading(false);\n setData(null);\n setError(null);\n }, []);\n\n return { upload, progress, isUploading, data, error, reset };\n}\n","import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, RestRequestOptions, IRestModule } from '@archie/js-sdk';\n\n// ─── useRest (base) ──────────────────────────────────────────\n\n/**\n * Returns the REST module from the Archie client.\n * Provides direct access to `get`, `post`, `put`, `patch`, `delete`.\n *\n * @example\n * ```tsx\n * const rest = useRest()\n * const data = await rest.get('/api/products')\n * await rest.post('/api/products', { name: 'Widget' })\n * ```\n */\nexport function useRest(): IRestModule {\n const { client } = useArchieContext();\n return useMemo(() => client.rest, [client]);\n}\n\n// ─── useRestQuery ─────────────────────────────────────────────\n\ninterface UseRestQueryReturn<T> {\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n refetch: () => Promise<void>;\n}\n\n/**\n * Declarative hook for GET requests to custom REST APIs.\n */\nexport function useRestQuery<T = unknown>(\n path: string,\n options?: RestRequestOptions & { enabled?: boolean },\n): UseRestQueryReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n const enabled = options?.enabled !== false;\n\n const fetchData = useCallback(async () => {\n if (!enabled) return;\n setIsLoading(true);\n\n try {\n const result = await client.rest.get<T>(path, options);\n setData(result);\n setError(null);\n } catch (err) {\n setError(err as ArchieError);\n setData(null);\n } finally {\n setIsLoading(false);\n }\n }, [client, path, enabled]);\n\n useEffect(() => {\n if (enabled) {\n fetchData();\n } else {\n setIsLoading(false);\n }\n }, [fetchData, enabled]);\n\n return { data, error, isLoading, refetch: fetchData };\n}\n\n// ─── useRestMutation ──────────────────────────────────────────\n\ninterface UseRestMutationReturn<T> {\n execute: (body?: unknown, overrideOptions?: RestRequestOptions) => Promise<T | null>;\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n reset: () => void;\n}\n\n/**\n * Imperative hook for POST/PUT/PATCH/DELETE requests to custom REST APIs.\n */\nexport function useRestMutation<T = unknown>(\n path: string,\n method: 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'POST',\n): UseRestMutationReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const execute = useCallback(\n async (body?: unknown, overrideOptions?: RestRequestOptions): Promise<T | null> => {\n setIsLoading(true);\n setError(null);\n\n try {\n let result: T;\n switch (method) {\n case 'POST':\n result = await client.rest.post<T>(path, body, overrideOptions);\n break;\n case 'PUT':\n result = await client.rest.put<T>(path, body, overrideOptions);\n break;\n case 'PATCH':\n result = await client.rest.patch<T>(path, body, overrideOptions);\n break;\n case 'DELETE':\n result = await client.rest.delete<T>(path, overrideOptions);\n break;\n }\n\n setData(result);\n setError(null);\n return result;\n } catch (err) {\n const archieErr = err as ArchieError;\n setError(archieErr);\n setData(null);\n return null;\n } finally {\n setIsLoading(false);\n }\n },\n [client, path, method],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsLoading(false);\n }, []);\n\n return { execute, data, error, isLoading, reset };\n}\n","import { useState, useEffect, useRef, useCallback } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, ConnectionState, Notification } from '@archie/js-sdk';\n\nexport interface UseNotificationsOptions {\n enabled?: boolean;\n unreadOnly?: boolean;\n limit?: number;\n onNotification?: (notification: Notification) => void;\n}\n\nexport interface UseNotificationsReturn {\n notifications: Notification[];\n latest: Notification | null;\n unreadCount: number;\n isLoading: boolean;\n error: ArchieError | null;\n connectionState: ConnectionState;\n markAsRead: (id: string) => Promise<void>;\n markAllAsRead: () => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n/**\n * Subscribe to a user's in-app notifications: loads the existing list,\n * then streams new ones live. Returns idiomatic React state.\n *\n * @example\n * ```tsx\n * const { notifications, unreadCount, markAsRead } = useNotifications(user.id);\n * ```\n */\nexport function useNotifications(\n receiverId: string,\n options?: UseNotificationsOptions,\n): UseNotificationsReturn {\n const { client } = useArchieContext();\n const [notifications, setNotifications] = useState<Notification[]>([]);\n const [latest, setLatest] = useState<Notification | null>(null);\n const [unreadCount, setUnreadCount] = useState(0);\n const [isLoading, setIsLoading] = useState(options?.enabled !== false);\n const [error, setError] = useState<ArchieError | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>(\n () => client.realtime.currentState,\n );\n\n const enabled = options?.enabled !== false;\n const unreadOnly = options?.unreadOnly ?? false;\n const limit = options?.limit;\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const notificationsRef = useRef<Notification[]>([]);\n notificationsRef.current = notifications;\n\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Reset local state when the receiver changes (never show the prior user's data)\n useEffect(() => {\n setNotifications([]);\n setLatest(null);\n setUnreadCount(0);\n seenIdsRef.current = new Set();\n }, [receiverId]);\n\n const load = useCallback(async () => {\n const [listResult, unread] = await Promise.all([\n client.notifications.list(receiverId, { unreadOnly, first: limit }),\n client.notifications.getUnreadCount(receiverId),\n ]);\n return { listResult, unread };\n }, [client, receiverId, unreadOnly, limit]);\n\n // Data load — re-runs on list params; cancellable so stale results never win\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n let active = true;\n setIsLoading(true);\n load()\n .then(({ listResult, unread }) => {\n if (!active) return;\n setNotifications(listResult.items);\n seenIdsRef.current = new Set(listResult.items.map((n) => n.id));\n setUnreadCount(unread);\n setError(null);\n })\n .catch((err) => {\n if (active) setError(err as ArchieError);\n })\n .finally(() => {\n if (active) setIsLoading(false);\n });\n return () => {\n active = false;\n };\n }, [enabled, load]);\n\n // Live subscription — only re-runs on client/receiverId/enabled, NOT on list params\n useEffect(() => {\n if (!enabled) return;\n const unsubConnection = client.realtime.onConnectionStateChange(setConnectionState);\n setConnectionState(client.realtime.currentState);\n\n const channel = client.notifications.subscribe(receiverId, {\n onError: (err) => setError(err),\n });\n channel.on('*', (notification: Notification) => {\n if (seenIdsRef.current.has(notification.id)) return; // ignore duplicates entirely\n seenIdsRef.current.add(notification.id);\n setNotifications((prev) => [notification, ...prev]);\n setLatest(notification);\n if (!notification.isRead) setUnreadCount((c) => c + 1);\n optionsRef.current?.onNotification?.(notification);\n });\n\n return () => {\n channel.unsubscribe();\n unsubConnection();\n };\n }, [client, receiverId, enabled]);\n\n const markAsRead = useCallback(\n async (id: string) => {\n const wasUnread = notificationsRef.current.find((n) => n.id === id)?.isRead === false;\n try {\n await client.notifications.markAsRead(id);\n setNotifications((prev) =>\n prev.map((n) => (n.id === id && !n.isRead ? { ...n, isRead: true } : n)),\n );\n if (wasUnread) setUnreadCount((c) => Math.max(0, c - 1));\n } catch (err) {\n setError(err as ArchieError);\n throw err;\n }\n },\n [client],\n );\n\n const markAllAsRead = useCallback(async () => {\n try {\n await client.notifications.markAllAsRead(receiverId);\n setNotifications((prev) => prev.map((n) => (n.isRead ? n : { ...n, isRead: true })));\n // Reconcile against the authoritative server count rather than assuming 0\n const count = await client.notifications.getUnreadCount(receiverId);\n setUnreadCount(count);\n } catch (err) {\n setError(err as ArchieError);\n throw err;\n }\n }, [client, receiverId]);\n\n const refetch = useCallback(async () => {\n setIsLoading(true);\n try {\n const { listResult, unread } = await load();\n setNotifications(listResult.items);\n seenIdsRef.current = new Set(listResult.items.map((n) => n.id));\n setUnreadCount(unread);\n setError(null);\n } catch (err) {\n setError(err as ArchieError);\n } finally {\n setIsLoading(false);\n }\n }, [load]);\n\n return {\n notifications,\n latest,\n unreadCount,\n isLoading,\n error,\n connectionState,\n markAsRead,\n markAllAsRead,\n refetch,\n };\n}\n","import React from 'react';\nimport { useAuth } from '../hooks/useAuth';\n\nexport interface AuthGuardProps {\n /** Content to show when authenticated (and authorized if roles specified). */\n children: React.ReactNode;\n /** Content to show when NOT authenticated. */\n fallback?: React.ReactNode;\n /** Content to show while session is loading. */\n loadingComponent?: React.ReactNode;\n /** If specified, user must have at least one of these roles. */\n requiredRoles?: string[];\n}\n\n/**\n * AuthGuard component — conditionally renders children based on auth state.\n *\n * @example\n * ```tsx\n * <AuthGuard fallback={<LoginPage />} requiredRoles={['admin']}>\n * <AdminPanel />\n * </AuthGuard>\n * ```\n */\nexport function AuthGuard({\n children,\n fallback = null,\n loadingComponent = null,\n requiredRoles,\n}: AuthGuardProps) {\n const { user, isLoading, isAuthenticated } = useAuth();\n\n if (isLoading) {\n return React.createElement(React.Fragment, null, loadingComponent);\n }\n\n if (!isAuthenticated || !user) {\n return React.createElement(React.Fragment, null, fallback);\n }\n\n // Check roles if required\n if (requiredRoles && requiredRoles.length > 0) {\n const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role));\n if (!hasRequiredRole) {\n return React.createElement(React.Fragment, null, fallback);\n }\n }\n\n return React.createElement(React.Fragment, null, children);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/query-cache.ts","../src/provider.tsx","../src/hooks/useArchieClient.ts","../src/hooks/useAuth.ts","../src/hooks/useQuery.ts","../src/hooks/useMutation.ts","../src/hooks/useSubscription.ts","../src/hooks/useFileUpload.ts","../src/hooks/useRest.ts","../src/hooks/useNotifications.ts","../src/components/AuthGuard.tsx"],"names":["React","useMemo","useState","useCallback","useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-memory query cache with in-flight request deduplication, stale-while-revalidate\n * and per-key invalidation subscriptions.\n * @internal\n */\n\ninterface CacheEntry<T = unknown> {\n data: T;\n timestamp: number;\n}\n\n/** Called when the entry behind a key is dropped and should be re-fetched. */\nexport type CacheListener = () => void;\n\nexport class QueryCache {\n private cache = new Map<string, CacheEntry>();\n private inflight = new Map<string, Promise<unknown>>();\n private subscribers = new Map<string, Set<CacheListener>>();\n private generations = new Map<string, number>();\n private pending = new Map<string, number>();\n private staleTime = 0;\n\n static key(query: string, variables?: Record<string, unknown>): string {\n return `${query}::${variables ? JSON.stringify(variables) : ''}`;\n }\n\n get<T>(key: string): CacheEntry<T> | undefined {\n return this.cache.get(key) as CacheEntry<T> | undefined;\n }\n\n set<T>(key: string, data: T): void {\n this.cache.set(key, { data, timestamp: Date.now() });\n }\n\n isFresh(key: string): boolean {\n const entry = this.cache.get(key);\n if (!entry) return false;\n if (this.staleTime === 0) return false;\n return Date.now() - entry.timestamp < this.staleTime;\n }\n\n /**\n * Deduplicate in-flight requests: if an identical fetch is already pending,\n * return the same promise. Otherwise, run `fetcher` and share the promise.\n */\n async dedup<T>(key: string, fetcher: () => Promise<T>): Promise<T> {\n const existing = this.inflight.get(key);\n if (existing) return existing as Promise<T>;\n\n const promise: Promise<T> = fetcher().finally(() => {\n const remaining = (this.pending.get(key) ?? 1) - 1;\n if (remaining > 0) {\n this.pending.set(key, remaining);\n } else {\n this.pending.delete(key);\n }\n\n // Identity check: an invalidation may have dropped this promise and a newer\n // request may already own the slot — a blind delete would evict the newer one\n // and break deduplication for everyone waiting on it.\n if (this.inflight.get(key) === promise) {\n this.inflight.delete(key);\n }\n });\n\n // Counted separately from the in-flight slot: an invalidation unregisters the\n // slot, but the request it dropped is still on its way back and its result\n // still has to be recognised as stale when it lands.\n this.pending.set(key, (this.pending.get(key) ?? 0) + 1);\n this.inflight.set(key, promise);\n return promise;\n }\n\n getInflight<T>(key: string): Promise<T> | undefined {\n return this.inflight.get(key) as Promise<T> | undefined;\n }\n\n /**\n * Subscribe to invalidations of a single cache key.\n *\n * The listener fires when {@link invalidate} matches this key or {@link clear} runs —\n * never on {@link set}, so a listener that re-fetches (and writes the result back)\n * cannot re-trigger itself.\n *\n * @returns an unsubscribe function; call it on unmount.\n */\n subscribe(key: string, listener: CacheListener): () => void {\n let listeners = this.subscribers.get(key);\n if (!listeners) {\n listeners = new Set();\n this.subscribers.set(key, listeners);\n }\n listeners.add(listener);\n\n return () => {\n const current = this.subscribers.get(key);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) {\n this.subscribers.delete(key);\n this.pruneGeneration(key);\n }\n };\n }\n\n /**\n * Current generation of a key. It increases every time the key is invalidated,\n * so a caller can capture it before an `await` and check {@link isCurrent}\n * afterwards to find out whether its response was superseded meanwhile.\n *\n * Reading is free of side effects: a generation is only ever recorded for a key\n * something can still compare against — see {@link pruneGeneration}.\n */\n generation(key: string): number {\n return this.generations.get(key) ?? 0;\n }\n\n /** True while `generation` is still the live generation of `key`. */\n isCurrent(key: string, generation: number): boolean {\n return (this.generations.get(key) ?? 0) === generation;\n }\n\n /**\n * Drop every cached entry matching `patterns`, then tell the subscribers on\n * those keys to re-fetch. A key matches when it *contains* any of the patterns.\n *\n * Pass the whole batch in one call (`['user', 'users']`) rather than looping:\n * the affected keys are resolved once, so a key matched by several patterns is\n * invalidated and notified exactly once instead of racing itself.\n *\n * In-flight requests for matching keys are unregistered and the generation of\n * each key is bumped: those requests were issued before the write landed, so\n * neither joining them nor applying their result may hand back stale data.\n *\n * Matching is substring-based, which is deliberately loose — a pattern like\n * `'id'` will match unrelated queries and cost them a refetch. Prefer patterns\n * distinctive enough to name the entity you actually mutated.\n */\n invalidate(patterns: string | string[]): void {\n const list = Array.isArray(patterns) ? patterns : [patterns];\n if (list.length === 0) return;\n\n const matches = (key: string): boolean => list.some((pattern) => key.includes(pattern));\n\n // Resolve the affected keys once, across the maps, so every key is handled a\n // single time no matter how many patterns hit it. Sweeping `generations` costs\n // nothing asymptotically — reading a generation does not record one, so the map\n // only holds live keys plus the odd orphan left by a consumer that unmounted\n // mid-request, and this sweep is exactly where those orphans are dropped.\n const affected = new Set<string>();\n for (const key of this.cache.keys()) if (matches(key)) affected.add(key);\n for (const key of this.inflight.keys()) if (matches(key)) affected.add(key);\n for (const key of this.subscribers.keys()) if (matches(key)) affected.add(key);\n for (const key of this.generations.keys()) if (matches(key)) affected.add(key);\n\n for (const key of affected) {\n this.cache.delete(key);\n this.inflight.delete(key);\n\n // A generation is only worth recording while something can still compare it.\n // For a merely-cached key the entry itself was the whole state, and deleting\n // it is enough — this is also where a key left behind by a consumer that\n // unmounted mid-request is reclaimed.\n if (this.hasHolder(key)) {\n this.bumpGeneration(key);\n } else {\n this.generations.delete(key);\n }\n }\n\n for (const key of affected) {\n this.notify(key);\n }\n }\n\n /**\n * Wipe everything and notify **every** subscriber, so all mounted queries\n * revalidate. That is intentional — a full clear means no key can be trusted —\n * but it does mean calling this on sign-out fires a refetch for every mounted\n * query, which is rarely what you want with a dead token.\n */\n clear(): void {\n // Same rule as invalidate(), and for the same reason: a generation may never\n // move backwards while something can still compare it. Rebuilding the map from\n // scratch would put every pending key back on 1 — precisely what a caller that\n // has lived through one mutation is holding — and its pre-clear response would\n // read as current.\n //\n // Keeping the entry for anything with a holder *narrows* the window where a\n // response that has already settled, but not yet been applied, could still be\n // accepted; it does not close it. A caller whose consumer is already gone and\n // whose pending count has drained takes the delete branch, and a capture of 0\n // then matches the `?? 0` reading. Unreachable through the hooks — useQuery\n // compares immediately after its await, so getting there means chaining onto\n // the internal promise by hand — but the limit is real, and only per-request\n // capture tracking (see pruneGeneration) would remove it.\n const known = new Set([\n ...this.generations.keys(),\n ...this.pending.keys(),\n ...this.subscribers.keys(),\n ]);\n for (const key of known) {\n if (this.hasHolder(key)) {\n this.bumpGeneration(key);\n } else {\n this.generations.delete(key);\n }\n }\n\n this.cache.clear();\n this.inflight.clear();\n\n // Snapshot: a listener may (un)subscribe while being notified.\n for (const key of [...this.subscribers.keys()]) {\n this.notify(key);\n }\n }\n\n private bumpGeneration(key: string): void {\n this.generations.set(key, (this.generations.get(key) ?? 0) + 1);\n }\n\n /**\n * Whether anything can still compare a generation for this key: a mounted\n * consumer that may fetch it, or a request already on its way back.\n */\n private hasHolder(key: string): boolean {\n return this.subscribers.has(key) || (this.pending.get(key) ?? 0) > 0;\n }\n\n /**\n * Forget a key's generation once nothing references it.\n *\n * Pruning is deliberately **not** done when a request settles. A pruned entry\n * reads back as generation 0, which is exactly what a caller that captured 0\n * holds — so a response that was superseded while in flight would be accepted\n * again, and would write pre-mutation data into the cache. The prune therefore\n * happens at the last unsubscribe, and only while nothing is pending.\n *\n * That leaves one bounded remainder, and it is worth naming precisely: if a key\n * has no subscriber left but a request still on its way back, {@link invalidate}\n * or {@link clear} bumps its entry rather than dropping it, and the settle path\n * does not reclaim it. It survives — one entry, holding one key — until some\n * *later* {@link invalidate} matches that key and finds nothing holding it. The\n * two ways in are a consumer that unmounts mid-request and is then invalidated,\n * and a `clear()` (a sign-out, say) fired while requests are in flight, which\n * leaves one entry per key that was still loading. Since patterns match by\n * substring, the next mutation touching that entity reclaims every such remnant\n * at once. Bytes, deliberately traded for the guarantee above.\n */\n private pruneGeneration(key: string): void {\n if (this.hasHolder(key)) return;\n\n this.generations.delete(key);\n }\n\n /** Notify one key's listeners. Iterates a snapshot so listeners may unsubscribe. */\n private notify(key: string): void {\n const listeners = this.subscribers.get(key);\n if (!listeners) return;\n\n for (const listener of [...listeners]) {\n // An earlier listener in this same batch may have unmounted the component\n // this one belongs to; firing it would refetch for a dead subscriber.\n if (!listeners.has(listener)) continue;\n\n try {\n listener();\n } catch {\n // A broken subscriber must not stop the others from revalidating.\n }\n }\n }\n}\n","import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';\nimport type { ArchieClient, Session, AuthEvent } from '@archie/js-sdk';\nimport { QueryCache } from './lib/query-cache';\n\n// ─── Context ──────────────────────────────────────────────────\n\ninterface ArchieContextValue {\n client: ArchieClient;\n session: Session | null;\n isLoading: boolean;\n cache: QueryCache;\n}\n\nexport const ArchieContext = createContext<ArchieContextValue | null>(null);\n\n// ─── Provider ─────────────────────────────────────────────────\n\nexport interface ArchieProviderProps {\n client: ArchieClient;\n children: React.ReactNode;\n /** @internal Inject a custom cache (useful for testing). */\n cache?: QueryCache;\n}\n\n/**\n * ArchieProvider — wraps your React app to provide the Archie client and session state.\n *\n * @example\n * ```tsx\n * import { createClient } from '@archie/js-sdk'\n * import { ArchieProvider } from '@archie/react-sdk'\n *\n * const archie = createClient({ projectId: '...' })\n *\n * function App() {\n * return (\n * <ArchieProvider client={archie}>\n * <MyApp />\n * </ArchieProvider>\n * )\n * }\n * ```\n */\nexport function ArchieProvider({ client, children, cache: injectedCache }: ArchieProviderProps) {\n const [session, setSession] = useState<Session | null>(client.auth.getSession());\n const [isLoading, setIsLoading] = useState(true);\n const defaultCache = useMemo(() => new QueryCache(), [client]);\n const cache = injectedCache ?? defaultCache;\n\n useEffect(() => {\n client.auth.waitForInit().then(() => {\n setSession(client.auth.getSession());\n setIsLoading(false);\n });\n\n const unsubscribe = client.auth.onAuthStateChange(\n (_event: AuthEvent, newSession: Session | null) => {\n setSession(newSession);\n },\n );\n\n return unsubscribe;\n }, [client]);\n\n const value = useMemo<ArchieContextValue>(\n () => ({ client, session, isLoading, cache }),\n [client, session, isLoading, cache],\n );\n\n return React.createElement(ArchieContext.Provider, { value }, children);\n}\n\n/**\n * Internal hook to access the Archie context.\n * @throws if used outside ArchieProvider\n * @internal\n */\nexport function useArchieContext(): ArchieContextValue {\n const context = useContext(ArchieContext);\n if (!context) {\n throw new Error(\n 'useArchieClient must be used within an <ArchieProvider>. ' +\n 'Wrap your app with <ArchieProvider client={archieClient}>.',\n );\n }\n return context;\n}\n","import { useArchieContext } from '../provider';\nimport type { ArchieClient } from '@archie/js-sdk';\n\n/**\n * Returns the ArchieClient instance from context.\n * Must be used within an ArchieProvider.\n *\n * @throws Error if used outside ArchieProvider\n */\nexport function useArchieClient(): ArchieClient {\n const { client } = useArchieContext();\n return client;\n}\n","import { useCallback, useMemo } from 'react';\nimport { useArchieContext } from '../provider';\nimport type {\n User,\n Session,\n AuthSignUpParams,\n AuthSignInParams,\n AuthConfirmParams,\n AuthRecoverParams,\n AuthResetPasswordParams,\n ArchieError,\n} from '@archie/js-sdk';\n\ninterface UseAuthReturn {\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n signIn: (\n params: AuthSignInParams,\n ) => Promise<{ session: Session | null; error: ArchieError | null }>;\n signUp: (\n params: AuthSignUpParams,\n ) => Promise<{ data: { userId: string; message: string } | null; error: ArchieError | null }>;\n signOut: () => Promise<void>;\n confirmSignUp: (\n params: AuthConfirmParams,\n ) => Promise<{ session: Session | null; error: ArchieError | null }>;\n recoverPassword: (\n params: AuthRecoverParams,\n ) => Promise<{ data: { message: string } | null; error: ArchieError | null }>;\n resetPassword: (\n params: AuthResetPasswordParams,\n ) => Promise<{ data: { message: string } | null; error: ArchieError | null }>;\n refreshSession: () => Promise<{ session: Session | null; error: ArchieError | null }>;\n}\n\n/**\n * Hook for authentication state and operations.\n * All methods return `{ data/session, error }` (never throws).\n */\nexport function useAuth(): UseAuthReturn {\n const { client, session, isLoading } = useArchieContext();\n\n const signIn = useCallback(\n async (params: AuthSignInParams) => {\n try {\n const result = await client.auth.signIn(params);\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const signUp = useCallback(\n async (params: AuthSignUpParams) => {\n try {\n const result = await client.auth.signUp(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const signOut = useCallback(async () => {\n await client.auth.signOut();\n }, [client]);\n\n const confirmSignUp = useCallback(\n async (params: AuthConfirmParams) => {\n try {\n const result = await client.auth.confirmSignUp(params);\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const recoverPassword = useCallback(\n async (params: AuthRecoverParams) => {\n try {\n const result = await client.auth.recoverPassword(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const resetPassword = useCallback(\n async (params: AuthResetPasswordParams) => {\n try {\n const result = await client.auth.resetPassword(params);\n return { data: result, error: null };\n } catch (err) {\n return { data: null, error: err as ArchieError };\n }\n },\n [client],\n );\n\n const refreshSession = useCallback(async () => {\n try {\n const result = await client.auth.refreshSession();\n return { session: result.session, error: null };\n } catch (err) {\n return { session: null, error: err as ArchieError };\n }\n }, [client]);\n\n return useMemo(\n () => ({\n user: session?.user ?? null,\n session,\n isLoading,\n isAuthenticated: session !== null,\n signIn,\n signUp,\n signOut,\n confirmSignUp,\n recoverPassword,\n resetPassword,\n refreshSession,\n }),\n [\n session,\n isLoading,\n signIn,\n signUp,\n signOut,\n confirmSignUp,\n recoverPassword,\n resetPassword,\n refreshSession,\n ],\n );\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport { QueryCache } from '../lib/query-cache';\nimport type { ArchieError } from '@archie/js-sdk';\n\ninterface UseQueryOptions<T> {\n enabled?: boolean;\n refetchInterval?: number;\n refetchOnWindowFocus?: boolean;\n onSuccess?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n}\n\ninterface UseQueryReturn<T> {\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n isRefetching: boolean;\n refetch: () => Promise<void>;\n}\n\n/**\n * Declarative GraphQL query hook with caching and stale-while-revalidate.\n *\n * @example\n * ```tsx\n * const { data, isLoading } = useQuery<{ users: User[] }>(\n * 'query { users { id email name } }',\n * { limit: 10 }\n * )\n * ```\n */\nexport function useQuery<T = unknown>(\n gql: string,\n variables?: Record<string, unknown>,\n options?: UseQueryOptions<T>,\n): UseQueryReturn<T> {\n const { client, session, cache } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [isRefetching, setIsRefetching] = useState(false);\n\n const enabled = options?.enabled !== false;\n\n const variablesKey = variables ? JSON.stringify(variables) : '';\n const cacheKey = QueryCache.key(gql, variables);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n /** How many fetches this hook currently has in flight. */\n const activeFetches = useRef(0);\n\n const fetchData = useCallback(\n async (isRefetch = false) => {\n if (!enabled) return;\n\n if (isRefetch) {\n setIsRefetching(true);\n } else {\n setIsLoading(true);\n }\n activeFetches.current += 1;\n\n // Captured before the await: if this key is invalidated while the request is\n // in flight, the response predates the write that invalidated it. Applying it\n // would repaint the UI — and poison the cache — with pre-mutation data, which\n // is the very bug this hook is supposed to fix.\n const generation = cache.generation(cacheKey);\n\n try {\n const result = await cache.dedup(cacheKey, () => client.graphql.query<T>(gql, variables));\n if (!cache.isCurrent(cacheKey, generation)) return;\n\n if (result.error) {\n setError(result.error);\n // On a revalidation, keep whatever is on screen: a transient failure\n // after a successful mutation must not blank the view.\n if (!isRefetch) setData(null);\n optionsRef.current?.onError?.(result.error);\n } else {\n setData(result.data);\n setError(null);\n cache.set(cacheKey, result.data);\n optionsRef.current?.onSuccess?.(result.data as T);\n }\n } catch (err) {\n if (!cache.isCurrent(cacheKey, generation)) return;\n setError(err as ArchieError);\n if (!isRefetch) setData(null);\n } finally {\n // The flags are this hook's own state, not the key's, so what releases them\n // has to be hook-local too. Counting this hook's own fetches gets both ends\n // right: a superseded response whose replacement is already running leaves\n // the spinner alone (the replacement owns it), while one with no\n // replacement — the consumer was gated off before the invalidation, so the\n // notification reached nobody — still releases it instead of stranding it.\n activeFetches.current -= 1;\n if (activeFetches.current === 0) {\n setIsLoading(false);\n setIsRefetching(false);\n }\n }\n },\n [client, gql, variablesKey, enabled, cacheKey, cache],\n );\n\n // Initial fetch & refetch on variable change\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n\n const cached = cache.get<T>(cacheKey);\n if (cached) {\n setData(cached.data);\n setIsLoading(false);\n fetchData(true);\n } else {\n fetchData(false);\n }\n }, [cacheKey, enabled]);\n\n // Revalidate when this key is invalidated (e.g. by useMutation's invalidateQueries).\n // `fetchData` is read through a ref so option changes never re-subscribe, and the\n // refetch itself goes through `cache.dedup`, so N mounted consumers of the same key\n // collapse into a single request instead of a refetch storm.\n const fetchDataRef = useRef(fetchData);\n fetchDataRef.current = fetchData;\n\n useEffect(() => {\n if (!enabled) return;\n return cache.subscribe(cacheKey, () => {\n fetchDataRef.current(true);\n });\n }, [cacheKey, enabled, cache]);\n\n // Refetch on auth state change (new token)\n const prevSessionRef = useRef(session?.accessToken);\n useEffect(() => {\n if (\n enabled &&\n prevSessionRef.current !== session?.accessToken &&\n prevSessionRef.current !== undefined\n ) {\n fetchData(true);\n }\n prevSessionRef.current = session?.accessToken;\n }, [session?.accessToken, enabled]);\n\n // Polling (refetchInterval)\n useEffect(() => {\n if (!enabled || !options?.refetchInterval) return;\n\n const interval = setInterval(() => {\n fetchData(true);\n }, options.refetchInterval);\n\n return () => clearInterval(interval);\n }, [enabled, options?.refetchInterval]);\n\n // Refetch on window focus\n useEffect(() => {\n if (!enabled || !options?.refetchOnWindowFocus) return;\n if (typeof window === 'undefined') return;\n\n const handleFocus = () => fetchData(true);\n window.addEventListener('focus', handleFocus);\n return () => window.removeEventListener('focus', handleFocus);\n }, [enabled, options?.refetchOnWindowFocus]);\n\n const refetch = useCallback(() => fetchData(true), [fetchData]);\n\n return { data, error, isLoading, isRefetching, refetch };\n}\n","import { useState, useCallback, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError } from '@archie/js-sdk';\n\ninterface UseMutationOptions<T> {\n onSuccess?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n onSettled?: () => void;\n /** Query substrings to invalidate (forcing refetch) after a successful mutation. */\n invalidateQueries?: string[];\n}\n\ninterface UseMutationReturn<T> {\n mutate: (variables?: Record<string, unknown>) => void;\n mutateAsync: (variables?: Record<string, unknown>) => Promise<T | null>;\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n reset: () => void;\n}\n\n/**\n * Hook for GraphQL mutations.\n *\n * @example\n * ```tsx\n * const { mutate, isLoading } = useMutation<{ createUser: User }>(\n * 'mutation($data: CreateUserInput!) { createUser(data: $data) { id email } }',\n * { invalidateQueries: ['users'] }\n * )\n *\n * mutate({ data: { email: 'new@user.com', name: 'John' } })\n * ```\n */\nexport function useMutation<T = unknown>(\n gql: string,\n options?: UseMutationOptions<T>,\n): UseMutationReturn<T> {\n const { client, cache } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const execute = useCallback(\n async (variables?: Record<string, unknown>): Promise<T | null> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const result = await client.graphql.mutate<T>(gql, variables);\n\n if (result.error) {\n setError(result.error);\n setData(null);\n optionsRef.current?.onError?.(result.error);\n optionsRef.current?.onSettled?.();\n return null;\n }\n\n setData(result.data);\n setError(null);\n optionsRef.current?.onSuccess?.(result.data as T);\n\n const invalidateQueries = optionsRef.current?.invalidateQueries;\n if (invalidateQueries?.length) {\n // One batched call, not one per entry: overlapping substrings such as\n // ['user', 'users'] hit the same key, and invalidating them separately\n // would kill the first refetch's in-flight request and issue a second.\n cache.invalidate(invalidateQueries);\n }\n\n optionsRef.current?.onSettled?.();\n return result.data;\n } catch (err) {\n const archieErr = err as ArchieError;\n setError(archieErr);\n setData(null);\n optionsRef.current?.onError?.(archieErr);\n optionsRef.current?.onSettled?.();\n return null;\n } finally {\n setIsLoading(false);\n }\n },\n [client, gql, cache],\n );\n\n const mutate = useCallback(\n (variables?: Record<string, unknown>) => {\n execute(variables);\n },\n [execute],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsLoading(false);\n }, []);\n\n return { mutate, mutateAsync: execute, data, error, isLoading, reset };\n}\n","import { useState, useEffect, useRef } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, ConnectionState } from '@archie/js-sdk';\n\ninterface UseSubscriptionOptions<T> {\n enabled?: boolean;\n onData?: (data: T) => void;\n onError?: (error: ArchieError) => void;\n}\n\ninterface UseSubscriptionReturn<T> {\n data: T | null;\n error: ArchieError | null;\n connectionState: ConnectionState;\n}\n\n/**\n * Hook for real-time GraphQL subscriptions.\n * Subscribes on mount, unsubscribes on unmount.\n *\n * @example\n * ```tsx\n * const { data } = useSubscription<{ orderUpdated: Order }>(\n * 'subscription { orderUpdated { id status total } }'\n * )\n * ```\n */\nexport function useSubscription<T = unknown>(\n gql: string,\n variables?: Record<string, unknown>,\n options?: UseSubscriptionOptions<T>,\n): UseSubscriptionReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>('DISCONNECTED');\n\n const enabled = options?.enabled !== false;\n const variablesKey = variables ? JSON.stringify(variables) : '';\n\n // Track latest callbacks to avoid stale closures\n const callbacksRef = useRef(options);\n callbacksRef.current = options;\n\n useEffect(() => {\n if (!enabled) return;\n\n const unsub = client.realtime.onConnectionStateChange(setConnectionState);\n\n const subscription = client.realtime.subscribe<T>(gql, variables, {\n onData: (newData: T) => {\n setData(newData);\n setError(null);\n callbacksRef.current?.onData?.(newData);\n },\n onError: (err) => {\n setError(err);\n callbacksRef.current?.onError?.(err);\n },\n onComplete: () => {\n // Subscription completed server-side\n },\n });\n\n return () => {\n subscription.unsubscribe();\n unsub();\n };\n }, [client, gql, variablesKey, enabled]);\n\n return { data, error, connectionState };\n}\n","import { useState, useCallback } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, FileUploadOptions, FileUploadResult } from '@archie/js-sdk';\n\ninterface UseFileUploadReturn {\n upload: (file: File | Blob, options?: FileUploadOptions) => Promise<void>;\n progress: number;\n isUploading: boolean;\n data: FileUploadResult | null;\n error: ArchieError | null;\n reset: () => void;\n}\n\n/**\n * Hook for file uploads.\n *\n * @example\n * ```tsx\n * const { upload, isUploading, data } = useFileUpload()\n *\n * <input type=\"file\" onChange={(e) => upload(e.target.files[0])} />\n * {isUploading && <Spinner />}\n * ```\n */\nexport function useFileUpload(): UseFileUploadReturn {\n const { client } = useArchieContext();\n const [progress, setProgress] = useState(0);\n const [isUploading, setIsUploading] = useState(false);\n const [data, setData] = useState<FileUploadResult | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n\n const upload = useCallback(\n async (file: File | Blob, options?: FileUploadOptions) => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n setData(null);\n\n try {\n const result = await client.files.upload(file, options);\n\n setData(result);\n setProgress(100);\n } catch (err) {\n setError(err as ArchieError);\n } finally {\n setIsUploading(false);\n }\n },\n [client],\n );\n\n const reset = useCallback(() => {\n setProgress(0);\n setIsUploading(false);\n setData(null);\n setError(null);\n }, []);\n\n return { upload, progress, isUploading, data, error, reset };\n}\n","import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, RestRequestOptions, IRestModule } from '@archie/js-sdk';\n\n// ─── useRest (base) ──────────────────────────────────────────\n\n/**\n * Returns the REST module from the Archie client.\n * Provides direct access to `get`, `post`, `put`, `patch`, `delete`.\n *\n * @example\n * ```tsx\n * const rest = useRest()\n * const data = await rest.get('/api/products')\n * await rest.post('/api/products', { name: 'Widget' })\n * ```\n */\nexport function useRest(): IRestModule {\n const { client } = useArchieContext();\n return useMemo(() => client.rest, [client]);\n}\n\n// ─── useRestQuery ─────────────────────────────────────────────\n\ninterface UseRestQueryReturn<T> {\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n refetch: () => Promise<void>;\n}\n\n/**\n * Declarative hook for GET requests to custom REST APIs.\n */\nexport function useRestQuery<T = unknown>(\n path: string,\n options?: RestRequestOptions & { enabled?: boolean },\n): UseRestQueryReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n const enabled = options?.enabled !== false;\n\n const fetchData = useCallback(async () => {\n if (!enabled) return;\n setIsLoading(true);\n\n try {\n const result = await client.rest.get<T>(path, options);\n setData(result);\n setError(null);\n } catch (err) {\n setError(err as ArchieError);\n setData(null);\n } finally {\n setIsLoading(false);\n }\n }, [client, path, enabled]);\n\n useEffect(() => {\n if (enabled) {\n fetchData();\n } else {\n setIsLoading(false);\n }\n }, [fetchData, enabled]);\n\n return { data, error, isLoading, refetch: fetchData };\n}\n\n// ─── useRestMutation ──────────────────────────────────────────\n\ninterface UseRestMutationReturn<T> {\n execute: (body?: unknown, overrideOptions?: RestRequestOptions) => Promise<T | null>;\n data: T | null;\n error: ArchieError | null;\n isLoading: boolean;\n reset: () => void;\n}\n\n/**\n * Imperative hook for POST/PUT/PATCH/DELETE requests to custom REST APIs.\n */\nexport function useRestMutation<T = unknown>(\n path: string,\n method: 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'POST',\n): UseRestMutationReturn<T> {\n const { client } = useArchieContext();\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<ArchieError | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const execute = useCallback(\n async (body?: unknown, overrideOptions?: RestRequestOptions): Promise<T | null> => {\n setIsLoading(true);\n setError(null);\n\n try {\n let result: T;\n switch (method) {\n case 'POST':\n result = await client.rest.post<T>(path, body, overrideOptions);\n break;\n case 'PUT':\n result = await client.rest.put<T>(path, body, overrideOptions);\n break;\n case 'PATCH':\n result = await client.rest.patch<T>(path, body, overrideOptions);\n break;\n case 'DELETE':\n result = await client.rest.delete<T>(path, overrideOptions);\n break;\n }\n\n setData(result);\n setError(null);\n return result;\n } catch (err) {\n const archieErr = err as ArchieError;\n setError(archieErr);\n setData(null);\n return null;\n } finally {\n setIsLoading(false);\n }\n },\n [client, path, method],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsLoading(false);\n }, []);\n\n return { execute, data, error, isLoading, reset };\n}\n","import { useState, useEffect, useRef, useCallback } from 'react';\nimport { useArchieContext } from '../provider';\nimport type { ArchieError, ConnectionState, Notification } from '@archie/js-sdk';\n\nexport interface UseNotificationsOptions {\n enabled?: boolean;\n unreadOnly?: boolean;\n /**\n * Page size. Applies to `fetchMore()` (older pages); when omitted, `fetchMore` uses 20.\n * It also caps the initial load (`first: limit`) — so omitting it lets the backend choose the\n * initial page size, meaning the first page and subsequent `fetchMore` pages may differ in size.\n */\n limit?: number;\n onNotification?: (notification: Notification) => void;\n}\n\nexport interface UseNotificationsReturn {\n notifications: Notification[];\n latest: Notification | null;\n unreadCount: number;\n isLoading: boolean;\n error: ArchieError | null;\n connectionState: ConnectionState;\n markAsRead: (id: string) => Promise<void>;\n markAllAsRead: () => Promise<void>;\n refetch: () => Promise<void>;\n /**\n * Load the next older page of history. No-op if `!hasMore`, a fetch is already in flight,\n * disabled, or a previous `fetchMore` errored (call `refetch()` or change the filter to retry).\n */\n fetchMore: () => Promise<void>;\n /** True while more older history exists for the active filter (the last page did not exhaust it). */\n hasMore: boolean;\n /** True while a fetchMore() call is in flight (drives a \"loading older\" indicator). */\n isFetchingMore: boolean;\n}\n\n/** Default page size for fetchMore() when no `limit` is provided. Initial load is unaffected. */\nconst DEFAULT_PAGE_SIZE = 20;\n\n/** Ids of the items whose `createdAt` equals `ts` — the tie-group at a keyset boundary. */\nfunction idsAt(items: Notification[], ts: string | null): string[] {\n if (ts == null) return [];\n return items.filter((n) => n.createdAt === ts).map((n) => n.id);\n}\n\n/**\n * Subscribe to a user's in-app notifications: loads the existing list,\n * then streams new ones live, and pages backward through history via fetchMore().\n * Returns idiomatic React state.\n *\n * @example\n * ```tsx\n * const { notifications, unreadCount, fetchMore, hasMore, isFetchingMore } =\n * useNotifications(user.id, { limit: 20 });\n * ```\n */\nexport function useNotifications(\n receiverId: string,\n options?: UseNotificationsOptions,\n): UseNotificationsReturn {\n const { client } = useArchieContext();\n const [notifications, setNotifications] = useState<Notification[]>([]);\n const [latest, setLatest] = useState<Notification | null>(null);\n const [unreadCount, setUnreadCount] = useState(0);\n const [isLoading, setIsLoading] = useState(options?.enabled !== false);\n const [error, setError] = useState<ArchieError | null>(null);\n const [connectionState, setConnectionState] = useState<ConnectionState>(\n () => client.realtime.currentState,\n );\n const [hasMore, setHasMore] = useState(false);\n const [isFetchingMore, setIsFetchingMore] = useState(false);\n\n const enabled = options?.enabled !== false;\n const unreadOnly = options?.unreadOnly ?? false;\n const limit = options?.limit;\n const pageSize = limit ?? DEFAULT_PAGE_SIZE;\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const notificationsRef = useRef<Notification[]>([]);\n notificationsRef.current = notifications;\n\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Pagination bookkeeping (in refs so callbacks read fresh values without re-creating).\n const oldestCreatedAtRef = useRef<string | null>(null); // keyset cursor: oldest loaded createdAt\n const boundaryIdsRef = useRef<string[]>([]); // ids already loaded at the cursor timestamp (tie-safe keyset)\n const isFetchingMoreRef = useRef(false); // synchronous guard against overlapping fetchMore calls\n const paginationErroredRef = useRef(false); // set on a fetchMore error; suppresses further fetchMore until a reseed\n const hasMoreRef = useRef(false); // mirror of hasMore for the fetchMore closure\n hasMoreRef.current = hasMore;\n const loadTokenRef = useRef(0); // bumped on every (re)seed; drops stale in-flight fetchMore results\n\n // Invalidate any in-flight fetchMore and clear its transient flags. Called on every (re)seed\n // (receiver/filter change, load effect, refetch) so a stale page never lands and the error gate resets.\n const invalidateInFlightPagination = useCallback(() => {\n loadTokenRef.current += 1;\n isFetchingMoreRef.current = false;\n paginationErroredRef.current = false;\n setIsFetchingMore(false);\n }, []);\n\n // Seed pagination bookkeeping from a fresh first-page list result.\n const seedFromList = useCallback((items: Notification[], totalCount: number) => {\n const oldest = items.length > 0 ? items[items.length - 1].createdAt : null;\n oldestCreatedAtRef.current = oldest;\n boundaryIdsRef.current = idsAt(items, oldest);\n // Initial page has no cursor, so totalCount is the grand total for the filter.\n setHasMore(items.length < totalCount);\n }, []);\n\n // Apply a fresh first-page result: merge any live events that arrived during the load window\n // (present in state but not in the server snapshot) at the head so they aren't dropped, refresh\n // the seen-set, and seed pagination bookkeeping.\n const mergeAndSeed = useCallback(\n (items: Notification[], totalCount: number) => {\n const serverIds = new Set(items.map((n) => n.id));\n // Keep only items strictly NEWER than the snapshot's newest row (and not already in it):\n // those are live events pushed during the load window. Restricting to \"newer than newest\"\n // (not merely \"absent from the snapshot\") is what makes this safe on refetch()/limit change —\n // otherwise the already-loaded OLDER paginated rows would be re-hoisted above the first page.\n // ISO `createdAt` strings sort chronologically, so string `>` is the correct comparison.\n const newestSnapshot = items.length > 0 ? items[0].createdAt : null;\n const liveOnly = notificationsRef.current.filter(\n (n) => !serverIds.has(n.id) && (newestSnapshot === null || n.createdAt > newestSnapshot),\n );\n setNotifications([...liveOnly, ...items]);\n seenIdsRef.current = new Set([...liveOnly.map((n) => n.id), ...items.map((n) => n.id)]);\n seedFromList(items, totalCount);\n },\n [seedFromList],\n );\n\n // Reset local state when the receiver or filter changes (never show the prior query's data)\n useEffect(() => {\n setNotifications([]);\n setLatest(null);\n setUnreadCount(0);\n seenIdsRef.current = new Set();\n oldestCreatedAtRef.current = null;\n boundaryIdsRef.current = [];\n setHasMore(false);\n invalidateInFlightPagination();\n }, [receiverId, unreadOnly, invalidateInFlightPagination]);\n\n const load = useCallback(async () => {\n const [listResult, unread] = await Promise.all([\n client.notifications.list(receiverId, { unreadOnly, first: limit }),\n client.notifications.getUnreadCount(receiverId),\n ]);\n return { listResult, unread };\n }, [client, receiverId, unreadOnly, limit]);\n\n // Data load — re-runs on list params; cancellable so stale results never win\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n let active = true;\n // A (re)seed invalidates any in-flight fetchMore.\n invalidateInFlightPagination();\n setIsLoading(true);\n load()\n .then(({ listResult, unread }) => {\n if (!active) return;\n mergeAndSeed(listResult.items, listResult.totalCount);\n setUnreadCount(unread);\n setError(null);\n })\n .catch((err) => {\n if (active) setError(err as ArchieError);\n })\n .finally(() => {\n if (active) setIsLoading(false);\n });\n return () => {\n active = false;\n };\n }, [enabled, load, mergeAndSeed, invalidateInFlightPagination]);\n\n // Live subscription — only re-runs on client/receiverId/enabled, NOT on list params\n useEffect(() => {\n if (!enabled) return;\n const unsubConnection = client.realtime.onConnectionStateChange(setConnectionState);\n setConnectionState(client.realtime.currentState);\n\n const channel = client.notifications.subscribe(receiverId, {\n onError: (err) => setError(err),\n });\n channel.on('*', (notification: Notification) => {\n if (seenIdsRef.current.has(notification.id)) return; // ignore duplicates entirely\n seenIdsRef.current.add(notification.id);\n setNotifications((prev) => [notification, ...prev]);\n setLatest(notification);\n if (!notification.isRead) setUnreadCount((c) => c + 1);\n optionsRef.current?.onNotification?.(notification);\n });\n\n return () => {\n channel.unsubscribe();\n unsubConnection();\n };\n }, [client, receiverId, enabled]);\n\n const markAsRead = useCallback(\n async (id: string) => {\n const wasUnread = notificationsRef.current.find((n) => n.id === id)?.isRead === false;\n try {\n await client.notifications.markAsRead(id);\n setNotifications((prev) =>\n prev.map((n) => (n.id === id && !n.isRead ? { ...n, isRead: true } : n)),\n );\n if (wasUnread) setUnreadCount((c) => Math.max(0, c - 1));\n } catch (err) {\n setError(err as ArchieError);\n throw err;\n }\n },\n [client],\n );\n\n const markAllAsRead = useCallback(async () => {\n try {\n await client.notifications.markAllAsRead(receiverId);\n setNotifications((prev) => prev.map((n) => (n.isRead ? n : { ...n, isRead: true })));\n // Reconcile against the authoritative server count rather than assuming 0\n const count = await client.notifications.getUnreadCount(receiverId);\n setUnreadCount(count);\n } catch (err) {\n setError(err as ArchieError);\n throw err;\n }\n }, [client, receiverId]);\n\n const refetch = useCallback(async () => {\n invalidateInFlightPagination();\n setIsLoading(true);\n try {\n const { listResult, unread } = await load();\n mergeAndSeed(listResult.items, listResult.totalCount);\n setUnreadCount(unread);\n setError(null);\n } catch (err) {\n setError(err as ArchieError);\n } finally {\n setIsLoading(false);\n }\n }, [load, mergeAndSeed, invalidateInFlightPagination]);\n\n const fetchMore = useCallback(async () => {\n if (\n !enabled ||\n !hasMoreRef.current ||\n isFetchingMoreRef.current ||\n paginationErroredRef.current // a prior page errored — wait for refetch()/filter change\n ) {\n return;\n }\n const cursor = oldestCreatedAtRef.current;\n if (cursor == null) return;\n\n const token = loadTokenRef.current;\n isFetchingMoreRef.current = true;\n setIsFetchingMore(true);\n try {\n const page = await client.notifications.list(receiverId, {\n unreadOnly,\n first: pageSize,\n orderBy: { createdAt: 'DESC' },\n createdBefore: cursor,\n // Tie-safe keyset: don't skip rows sharing the cursor timestamp that we haven't loaded yet.\n excludeIds: boundaryIdsRef.current,\n // Page-fill drives hasMore below, so skip the (potentially O(N)) COUNT on deep pages.\n includeTotal: false,\n });\n if (loadTokenRef.current !== token) return; // reseeded mid-flight — drop this page\n if (page.items.length === 0) {\n // Server has no older rows — end of history. Also guards against a no-progress fetch loop.\n setHasMore(false);\n return;\n }\n const fresh = page.items.filter((n) => !seenIdsRef.current.has(n.id));\n fresh.forEach((n) => seenIdsRef.current.add(n.id));\n if (fresh.length > 0) {\n setNotifications((prev) => [...prev, ...fresh]);\n }\n // Advance the keyset cursor to the oldest row in this page, and track the ids at that exact\n // timestamp so the next page's excludeIds skips only what we've loaded — never a tie sibling.\n const newOldest = page.items[page.items.length - 1].createdAt;\n const idsAtNewOldest = idsAt(page.items, newOldest);\n boundaryIdsRef.current =\n newOldest === cursor\n ? Array.from(new Set([...boundaryIdsRef.current, ...idsAtNewOldest]))\n : idsAtNewOldest;\n oldestCreatedAtRef.current = newOldest;\n // Page-fill termination: a full page means more may remain; a short page is the tail.\n setHasMore(page.items.length === pageSize);\n setError(null);\n } catch (err) {\n if (loadTokenRef.current === token) {\n // Gate further fetchMore so a consumer's auto-load effect can't hammer the backend\n // in a tight retry loop; a reseed (refetch/receiver/filter change) clears the gate.\n paginationErroredRef.current = true;\n setError(err as ArchieError);\n }\n } finally {\n if (loadTokenRef.current === token) {\n isFetchingMoreRef.current = false;\n setIsFetchingMore(false);\n }\n }\n }, [client, receiverId, unreadOnly, pageSize, enabled]);\n\n return {\n notifications,\n latest,\n unreadCount,\n isLoading,\n error,\n connectionState,\n markAsRead,\n markAllAsRead,\n refetch,\n fetchMore,\n hasMore,\n isFetchingMore,\n };\n}\n","import React from 'react';\nimport { useAuth } from '../hooks/useAuth';\n\nexport interface AuthGuardProps {\n /** Content to show when authenticated (and authorized if roles specified). */\n children: React.ReactNode;\n /** Content to show when NOT authenticated. */\n fallback?: React.ReactNode;\n /** Content to show while session is loading. */\n loadingComponent?: React.ReactNode;\n /** If specified, user must have at least one of these roles. */\n requiredRoles?: string[];\n}\n\n/**\n * AuthGuard component — conditionally renders children based on auth state.\n *\n * @example\n * ```tsx\n * <AuthGuard fallback={<LoginPage />} requiredRoles={['admin']}>\n * <AdminPanel />\n * </AuthGuard>\n * ```\n */\nexport function AuthGuard({\n children,\n fallback = null,\n loadingComponent = null,\n requiredRoles,\n}: AuthGuardProps) {\n const { user, isLoading, isAuthenticated } = useAuth();\n\n if (isLoading) {\n return React.createElement(React.Fragment, null, loadingComponent);\n }\n\n if (!isAuthenticated || !user) {\n return React.createElement(React.Fragment, null, fallback);\n }\n\n // Check roles if required\n if (requiredRoles && requiredRoles.length > 0) {\n const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role));\n if (!hasRequiredRole) {\n return React.createElement(React.Fragment, null, fallback);\n }\n }\n\n return React.createElement(React.Fragment, null, children);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archie/react-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Archie BAAS - React hooks, provider & components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"react": ">=18.0.0",
|
|
23
|
-
"@archie/js-sdk": "0.3.
|
|
23
|
+
"@archie/js-sdk": "0.3.2"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"react": "^19.0.0",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@testing-library/react": "^16.0.0",
|
|
33
33
|
"@testing-library/jest-dom": "^6.0.0",
|
|
34
34
|
"jsdom": "^25.0.0",
|
|
35
|
-
"@archie/js-sdk": "0.3.
|
|
35
|
+
"@archie/js-sdk": "0.3.2"
|
|
36
36
|
},
|
|
37
37
|
"sideEffects": false,
|
|
38
38
|
"keywords": [
|