@instantdb/react 0.22.155 → 0.22.156-branch-suspense-debug.22925159420.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instantdb/react",
3
- "version": "0.22.155",
3
+ "version": "0.22.156-branch-suspense-debug.22925159420.1",
4
4
  "description": "Instant DB for React",
5
5
  "homepage": "https://github.com/instantdb/instant/tree/main/client/packages/react",
6
6
  "repository": {
@@ -66,9 +66,9 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "eventsource": "^4.0.0",
69
- "@instantdb/core": "0.22.155",
70
- "@instantdb/react-common": "0.22.155",
71
- "@instantdb/version": "0.22.155"
69
+ "@instantdb/react-common": "0.22.156-branch-suspense-debug.22925159420.1",
70
+ "@instantdb/version": "0.22.156-branch-suspense-debug.22925159420.1",
71
+ "@instantdb/core": "0.22.156-branch-suspense-debug.22925159420.1"
72
72
  },
73
73
  "scripts": {
74
74
  "test": "vitest",
@@ -131,7 +131,7 @@ export function createHydrationStreamProvider<TShape>() {
131
131
  if (!stream.length) {
132
132
  return null;
133
133
  }
134
- // console.log(`pushing ${stream.length} entries`)
134
+
135
135
  const serializedCacheArgs = stream
136
136
  .map((entry) => transformer.serialize(entry))
137
137
  .map((entry) => JSON.stringify(entry))
@@ -16,18 +16,26 @@ import {
16
16
  createHydrationStreamProvider,
17
17
  isServer,
18
18
  } from './HydrationStreamProvider.tsx';
19
- import { createContext, useContext, useRef, useState } from 'react';
20
- import { InstantReactAbstractDatabase } from '@instantdb/react-common';
19
+ import {
20
+ createContext,
21
+ useCallback,
22
+ useContext,
23
+ useMemo,
24
+ useRef,
25
+ useState,
26
+ useEffect,
27
+ } from 'react';
28
+ import {
29
+ InstantReactAbstractDatabase,
30
+ useQueryInternal,
31
+ } from '@instantdb/react-common';
21
32
 
22
33
  type InstantSuspenseProviderProps<
23
34
  Schema extends InstantSchemaDef<any, any, any>,
24
35
  > = {
25
36
  nonce?: string;
26
37
  children: React.ReactNode;
27
- db?: InstantReactWebDatabase<Schema, any>;
28
- config?: Omit<InstantConfig<any, any>, 'schema'> & {
29
- schema: string;
30
- };
38
+ db: InstantReactWebDatabase<Schema, any>;
31
39
  user?: User | null;
32
40
  };
33
41
 
@@ -71,95 +79,188 @@ type SuspenseQueryOpts = {
71
79
  ruleParams: RuleParams;
72
80
  };
73
81
 
74
- export const InstantSuspenseProvider = (
75
- props: InstantSuspenseProviderProps<any>,
76
- ) => {
77
- const clientRef = useRef<FrameworkClient | null>(null);
82
+ function makeUseSuspenseQueryServer(client: FrameworkClient) {
83
+ return function useSuspenseQueryServer(query: any, opts: SuspenseQueryOpts) {
84
+ let entry = client.getExistingResultForQuery(query, opts);
78
85
 
79
- if (!props.db) {
80
- throw new Error(
81
- 'Must provide either a db or config to InstantSuspenseProvider',
82
- );
83
- }
84
-
85
- const db = useRef<InstantReactAbstractDatabase<any, any>>(props.db);
86
+ if (!entry) {
87
+ entry = client.query(query, opts);
88
+ }
86
89
 
87
- const [trackedKeys] = useState(() => new Set<string>());
90
+ if (entry.status === 'pending') {
91
+ throw entry.promise;
92
+ }
88
93
 
89
- if (!clientRef.current) {
90
- if (props.user && !props.user.refresh_token) {
91
- throw new Error(
92
- 'User must have a refresh_token field. Recieved: ' +
93
- JSON.stringify(props.user, null, 2),
94
- );
94
+ if (entry.status === 'error') {
95
+ throw entry.error;
95
96
  }
96
- clientRef.current = new FrameworkClient({
97
- token: props.user?.refresh_token,
98
- db: db.current.core,
99
- });
100
- }
101
97
 
102
- if (isServer) {
103
- clientRef.current.subscribe((result) => {
104
- const { queryHash } = result;
105
- trackedKeys.add(queryHash);
106
- });
107
- }
98
+ if (entry.status === 'success') {
99
+ switch (entry.type) {
100
+ case 'session': {
101
+ return entry.data;
102
+ }
103
+ case 'http': {
104
+ const data = entry.data;
105
+ const result = client.completeIsomorphic(
106
+ query,
107
+ data.triples,
108
+ data.attrs,
109
+ data.pageInfo,
110
+ );
108
111
 
109
- const useSuspenseQuery = (query: any, opts: SuspenseQueryOpts) => {
110
- const nonSuspenseResult = db.current.useQuery(query, {
111
- ...opts,
112
- });
112
+ return result;
113
+ }
114
+ }
115
+ }
116
+ };
117
+ }
113
118
 
114
- if (nonSuspenseResult.data) {
115
- return {
116
- data: nonSuspenseResult.data,
117
- pageInfo: nonSuspenseResult.pageInfo,
118
- };
119
+ function makeUseSuspenseQueryClient(
120
+ db: InstantReactAbstractDatabase<any, any>,
121
+ client: FrameworkClient,
122
+ ) {
123
+ function getEntry(query: any, opts: SuspenseQueryOpts, allowFetch: boolean) {
124
+ const entry = client.getExistingResultForQuery(query, opts);
125
+
126
+ if (entry?.status === 'pending') {
127
+ throw entry.promise;
119
128
  }
120
129
 
121
- // should never happen (typeguard)
122
- if (!clientRef.current) {
123
- throw new Error('Client ref not set up');
130
+ if (entry?.status === 'error') {
131
+ throw entry.error;
124
132
  }
125
133
 
126
- let entry = clientRef.current.getExistingResultForQuery(query, {
127
- ruleParams: opts?.ruleParams,
128
- });
134
+ if (entry?.status === 'success') {
135
+ switch (entry.type) {
136
+ case 'session': {
137
+ return entry.data;
138
+ }
139
+ case 'http': {
140
+ const data = entry.data;
141
+ const result = client.completeIsomorphic(
142
+ query,
143
+ data.triples,
144
+ data.attrs,
145
+ data.pageInfo,
146
+ );
129
147
 
130
- if (!entry) {
131
- entry = clientRef.current!.query(query, opts);
148
+ return result;
149
+ }
150
+ }
132
151
  }
133
152
 
134
- if (entry.status === 'pending') {
135
- throw entry.promise;
153
+ if (allowFetch) {
154
+ const promise = client.queryClient(query, opts);
155
+ throw promise;
136
156
  }
157
+ }
137
158
 
138
- if (entry.status === 'error') {
139
- throw entry.error;
159
+ return function useSuspenseQueryClient(query: any, opts: SuspenseQueryOpts) {
160
+ const useQueryResult = useQueryInternal(
161
+ db.core,
162
+ query,
163
+ opts,
164
+ // Returns the server result for useSyncExternalStore
165
+ () => {
166
+ try {
167
+ const res = getEntry(query, opts, false);
168
+ return res;
169
+ } catch (throwable) {
170
+ return { error: throwable };
171
+ }
172
+ },
173
+ );
174
+
175
+ const hasData = !!useQueryResult.state.data;
176
+ const queryHash = useQueryResult.queryHash;
177
+
178
+ useEffect(() => {
179
+ if (hasData) {
180
+ // We have a newer result, so remove the cached SSR or suspended
181
+ // result from the framework client cache
182
+ client.removeCachedQueryResult(queryHash);
183
+ }
184
+ }, [hasData, queryHash]);
185
+
186
+ if (useQueryResult.state.data) {
187
+ return {
188
+ data: useQueryResult.state.data,
189
+ pageInfo: useQueryResult.state.pageInfo,
190
+ };
140
191
  }
141
192
 
142
- if (entry.status === 'success' && entry.type === 'session') {
143
- return entry.data;
193
+ if (useQueryResult.state.error) {
194
+ throw useQueryResult.state.error;
144
195
  }
145
196
 
146
- if (entry.status === 'success') {
147
- const data = entry.data;
148
- const result = clientRef.current.completeIsomorphic(
149
- query,
150
- data.triples,
151
- data.attrs,
152
- data.pageInfo,
153
- );
197
+ return getEntry(query, opts, true);
198
+ };
199
+ }
154
200
 
155
- return result;
201
+ function createFrameworkClient(
202
+ db: InstantReactAbstractDatabase<any, any>,
203
+ user: User | null | undefined,
204
+ ) {
205
+ if (isServer) {
206
+ if (user && !user.refresh_token) {
207
+ throw new Error(
208
+ 'User must have a refresh_token field. Recieved: ' +
209
+ JSON.stringify(user, null, 2),
210
+ );
156
211
  }
157
- };
212
+ return new FrameworkClient({
213
+ token: user?.refresh_token,
214
+ db: db.core,
215
+ });
216
+ }
217
+
218
+ // On the client, make sure we only have a single framework
219
+ // in case our suspense provider gets unmounted
220
+ const existing = db.core._reactor._frameworkClient;
221
+ if (existing) {
222
+ return existing;
223
+ }
224
+ const client = new FrameworkClient({ db: db.core });
225
+ db.core._reactor.setFrameworkClient(client);
226
+ return client;
227
+ }
228
+
229
+ export const InstantSuspenseProvider = (
230
+ props: InstantSuspenseProviderProps<any>,
231
+ ) => {
232
+ if (!props.db) {
233
+ throw new Error('Must provide db to InstantSuspenseProvider');
234
+ }
235
+
236
+ const db = props.db;
237
+
238
+ const [trackedKeys] = useState(() => new Set<string>());
239
+
240
+ const clientRef = useRef<FrameworkClient>(
241
+ createFrameworkClient(props.db, props.user),
242
+ );
243
+
244
+ if (isServer) {
245
+ clientRef.current.subscribe((result) => {
246
+ const { queryHash } = result;
247
+ trackedKeys.add(queryHash);
248
+ });
249
+ }
250
+
251
+ const useSuspenseQuery = useCallback(
252
+ isServer
253
+ ? makeUseSuspenseQueryServer(clientRef.current)
254
+ : makeUseSuspenseQueryClient(db, clientRef.current),
255
+ [],
256
+ );
257
+
258
+ const contextValue = useMemo(() => {
259
+ return { useSuspenseQuery, ssrUser: props.user };
260
+ }, [useSuspenseQuery, props.user]);
158
261
 
159
262
  return (
160
- <SuspsenseQueryContext.Provider
161
- value={{ useSuspenseQuery, ssrUser: props.user }}
162
- >
263
+ <SuspsenseQueryContext.Provider value={contextValue}>
163
264
  <stream.Provider
164
265
  nonce={props.nonce}
165
266
  onFlush={() => {