@roko-app/instant-react-common 0.0.0-stage → 1.0.67-roko.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.
Files changed (77) hide show
  1. package/.tshy/build.json +8 -0
  2. package/.tshy/commonjs.json +16 -0
  3. package/.tshy/esm.json +15 -0
  4. package/LICENSE +202 -0
  5. package/README.md +59 -2
  6. package/dist/commonjs/InstantReactAbstractDatabase.d.ts +283 -0
  7. package/dist/commonjs/InstantReactAbstractDatabase.d.ts.map +1 -0
  8. package/dist/commonjs/InstantReactAbstractDatabase.js +390 -0
  9. package/dist/commonjs/InstantReactAbstractDatabase.js.map +1 -0
  10. package/dist/commonjs/InstantReactRoom.d.ts +183 -0
  11. package/dist/commonjs/InstantReactRoom.d.ts.map +1 -0
  12. package/dist/commonjs/InstantReactRoom.js +290 -0
  13. package/dist/commonjs/InstantReactRoom.js.map +1 -0
  14. package/dist/commonjs/index.d.ts +5 -0
  15. package/dist/commonjs/index.d.ts.map +1 -0
  16. package/dist/commonjs/index.js +11 -0
  17. package/dist/commonjs/index.js.map +1 -0
  18. package/dist/commonjs/package.json +3 -0
  19. package/dist/commonjs/useInfiniteQuerySubscription.d.ts +28 -0
  20. package/dist/commonjs/useInfiniteQuerySubscription.d.ts.map +1 -0
  21. package/dist/commonjs/useInfiniteQuerySubscription.js +57 -0
  22. package/dist/commonjs/useInfiniteQuerySubscription.js.map +1 -0
  23. package/dist/commonjs/useQuery.d.ts +7 -0
  24. package/dist/commonjs/useQuery.d.ts.map +1 -0
  25. package/dist/commonjs/useQuery.js +79 -0
  26. package/dist/commonjs/useQuery.js.map +1 -0
  27. package/dist/commonjs/useTimeout.d.ts +5 -0
  28. package/dist/commonjs/useTimeout.d.ts.map +1 -0
  29. package/dist/commonjs/useTimeout.js +21 -0
  30. package/dist/commonjs/useTimeout.js.map +1 -0
  31. package/dist/commonjs/version.d.ts +3 -0
  32. package/dist/commonjs/version.d.ts.map +1 -0
  33. package/dist/commonjs/version.js +5 -0
  34. package/dist/commonjs/version.js.map +1 -0
  35. package/dist/esm/InstantReactAbstractDatabase.d.ts +283 -0
  36. package/dist/esm/InstantReactAbstractDatabase.d.ts.map +1 -0
  37. package/dist/esm/InstantReactAbstractDatabase.js +387 -0
  38. package/dist/esm/InstantReactAbstractDatabase.js.map +1 -0
  39. package/dist/esm/InstantReactRoom.d.ts +183 -0
  40. package/dist/esm/InstantReactRoom.d.ts.map +1 -0
  41. package/dist/esm/InstantReactRoom.js +281 -0
  42. package/dist/esm/InstantReactRoom.js.map +1 -0
  43. package/dist/esm/index.d.ts +5 -0
  44. package/dist/esm/index.d.ts.map +1 -0
  45. package/dist/esm/index.js +4 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/package.json +3 -0
  48. package/dist/esm/useInfiniteQuerySubscription.d.ts +28 -0
  49. package/dist/esm/useInfiniteQuerySubscription.d.ts.map +1 -0
  50. package/dist/esm/useInfiniteQuerySubscription.js +54 -0
  51. package/dist/esm/useInfiniteQuerySubscription.js.map +1 -0
  52. package/dist/esm/useQuery.d.ts +7 -0
  53. package/dist/esm/useQuery.d.ts.map +1 -0
  54. package/dist/esm/useQuery.js +76 -0
  55. package/dist/esm/useQuery.js.map +1 -0
  56. package/dist/esm/useTimeout.d.ts +5 -0
  57. package/dist/esm/useTimeout.d.ts.map +1 -0
  58. package/dist/esm/useTimeout.js +18 -0
  59. package/dist/esm/useTimeout.js.map +1 -0
  60. package/dist/esm/version.d.ts +3 -0
  61. package/dist/esm/version.d.ts.map +1 -0
  62. package/dist/esm/version.js +3 -0
  63. package/dist/esm/version.js.map +1 -0
  64. package/dist/standalone/index.js +8718 -0
  65. package/dist/standalone/index.umd.cjs +36 -0
  66. package/package.json +43 -3
  67. package/src/InstantReactAbstractDatabase.tsx +510 -0
  68. package/src/InstantReactRoom.ts +451 -0
  69. package/src/index.ts +9 -0
  70. package/src/useInfiniteQuerySubscription.ts +128 -0
  71. package/src/useQuery.ts +124 -0
  72. package/src/useTimeout.ts +22 -0
  73. package/src/version.ts +3 -0
  74. package/tsconfig.cjs.dev.json +15 -0
  75. package/tsconfig.dev.json +14 -0
  76. package/tsconfig.json +12 -0
  77. package/vite.config.ts +35 -0
@@ -0,0 +1,510 @@
1
+ 'use client';
2
+ import {
3
+ // types
4
+ Auth,
5
+ Storage,
6
+ txInit,
7
+ type AuthState,
8
+ type User,
9
+ type ConnectionStatus,
10
+ type AppStatusState,
11
+ type TransactionChunk,
12
+ type RoomSchemaShape,
13
+ type InstaQLOptions,
14
+ type InstantConfig,
15
+ type PageInfoResponse,
16
+ InstantCoreDatabase,
17
+ init as core_init,
18
+ InstaQLLifecycleState,
19
+ InstaQLResponse,
20
+ RoomsOf,
21
+ InstantSchemaDef,
22
+ IInstantDatabase,
23
+ InstantError,
24
+ ValidQuery,
25
+ Streams,
26
+ } from '@instantdb/core';
27
+ import {
28
+ ReactNode,
29
+ useCallback,
30
+ useEffect,
31
+ useRef,
32
+ useState,
33
+ useSyncExternalStore,
34
+ } from 'react';
35
+ import { useQueryInternal } from './useQuery.ts';
36
+ import {
37
+ type InfiniteQueryResult,
38
+ useInfiniteQuerySubscription,
39
+ } from './useInfiniteQuerySubscription.ts';
40
+ import { InstantReactRoom, rooms } from './InstantReactRoom.ts';
41
+
42
+ const defaultAuthState = {
43
+ isLoading: true,
44
+ user: undefined,
45
+ error: undefined,
46
+ };
47
+
48
+ const defaultAppStatusState: AppStatusState = {
49
+ isLoading: true,
50
+ isReadOnly: undefined,
51
+ };
52
+
53
+ export default abstract class InstantReactAbstractDatabase<
54
+ // need to pull this schema out to another generic for query params, not sure why
55
+ Schema extends InstantSchemaDef<any, any, any>,
56
+ UseDates extends boolean = false,
57
+ Config extends InstantConfig<Schema, boolean> = InstantConfig<
58
+ Schema,
59
+ UseDates
60
+ >,
61
+ Rooms extends RoomSchemaShape = RoomsOf<Schema>,
62
+ > implements IInstantDatabase<Schema>
63
+ {
64
+ public tx = txInit<Schema>();
65
+
66
+ public auth: Auth;
67
+ public storage: Storage;
68
+ public streams: Streams;
69
+ public core: InstantCoreDatabase<Schema, UseDates>;
70
+
71
+ /** @deprecated use `core` instead */
72
+ public _core: InstantCoreDatabase<Schema, UseDates>;
73
+
74
+ static Store?: any;
75
+ static NetworkListener?: any;
76
+ static EventSourceImpl?: any;
77
+
78
+ constructor(
79
+ config: Omit<InstantConfig<Schema, UseDates>, 'useDateObjects'> & {
80
+ useDateObjects?: UseDates;
81
+ },
82
+ versions?: { [key: string]: string },
83
+ ) {
84
+ this.core = core_init<Schema, UseDates>(
85
+ config,
86
+ // @ts-expect-error because TS can't resolve subclass statics
87
+ config.Store || this.constructor.Store,
88
+ // @ts-expect-error because TS can't resolve subclass statics
89
+ this.constructor.NetworkListener,
90
+ versions,
91
+ // @ts-expect-error because TS can't resolve subclass statics
92
+ this.constructor.EventSourceImpl,
93
+ );
94
+ this._core = this.core;
95
+ this.auth = this.core.auth;
96
+ this.storage = this.core.storage;
97
+ this.streams = this.core.streams;
98
+ }
99
+
100
+ /**
101
+ * Returns a unique ID for a given `name`. It's stored in local storage,
102
+ * so you will get the same ID across sessions.
103
+ *
104
+ * This is useful for generating IDs that could identify a local device or user.
105
+ *
106
+ * @example
107
+ * const deviceId = await db.getLocalId('device');
108
+ */
109
+ getLocalId = (name: string): Promise<string> => {
110
+ return this.core.getLocalId(name);
111
+ };
112
+
113
+ /**
114
+ * A hook that returns a unique ID for a given `name`. localIds are
115
+ * stored in local storage, so you will get the same ID across sessions.
116
+ *
117
+ * Initially returns `null`, and then loads the localId.
118
+ *
119
+ * @example
120
+ * const deviceId = db.useLocalId('device');
121
+ * if (!deviceId) return null; // loading
122
+ * console.log('Device ID:', deviceId)
123
+ */
124
+ useLocalId = (name: string): string | null => {
125
+ const [localId, setLocalId] = useState<string | null>(null);
126
+
127
+ useEffect(() => {
128
+ let mounted = true;
129
+ const f = async () => {
130
+ const id = await this.getLocalId(name);
131
+ if (!mounted) return;
132
+ setLocalId(id);
133
+ };
134
+ f();
135
+ return;
136
+ }, [name]);
137
+
138
+ return localId;
139
+ };
140
+
141
+ /**
142
+ * Obtain a handle to a room, which allows you to listen to topics and presence data
143
+ *
144
+ * If you don't provide a `type` or `id`, Instant will default to `_defaultRoomType` and `_defaultRoomId`
145
+ * as the room type and id, respectively.
146
+ *
147
+ * @see https://instantdb.com/docs/presence-and-topics
148
+ *
149
+ * @example
150
+ * const room = db.room('chat', roomId);
151
+ * const { peers } = db.rooms.usePresence(room);
152
+ */
153
+ room<RoomType extends string & keyof Rooms>(
154
+ type: RoomType = '_defaultRoomType' as RoomType,
155
+ id: string = '_defaultRoomId',
156
+ ) {
157
+ return new InstantReactRoom<Schema, Rooms, RoomType>(this.core, type, id);
158
+ }
159
+
160
+ /**
161
+ * Hooks for working with rooms
162
+ *
163
+ * @see https://instantdb.com/docs/presence-and-topics
164
+ *
165
+ * @example
166
+ * const room = db.room('chat', roomId);
167
+ * const { peers } = db.rooms.usePresence(room);
168
+ * const publish = db.rooms.usePublishTopic(room, 'emoji');
169
+ * // ...
170
+ */
171
+ rooms = rooms;
172
+
173
+ /**
174
+ * Use this to write data! You can create, update, delete, and link objects
175
+ *
176
+ * @see https://instantdb.com/docs/instaml
177
+ *
178
+ * @example
179
+ * // Create a new object in the `goals` namespace
180
+ * const goalId = id();
181
+ * db.transact(db.tx.goals[goalId].update({title: "Get fit"}))
182
+ *
183
+ * // Update the title
184
+ * db.transact(db.tx.goals[goalId].update({title: "Get super fit"}))
185
+ *
186
+ * // Delete it
187
+ * db.transact(db.tx.goals[goalId].delete())
188
+ *
189
+ * // Or create an association:
190
+ * todoId = id();
191
+ * db.transact([
192
+ * db.tx.todos[todoId].update({ title: 'Go on a run' }),
193
+ * db.tx.goals[goalId].link({todos: todoId}),
194
+ * ])
195
+ */
196
+ transact = (
197
+ chunks: TransactionChunk<any, any> | TransactionChunk<any, any>[],
198
+ ) => {
199
+ return this.core.transact(chunks);
200
+ };
201
+
202
+ /**
203
+ * Use this to query your data!
204
+ *
205
+ * @see https://instantdb.com/docs/instaql
206
+ *
207
+ * @example
208
+ * // listen to all goals
209
+ * const { isLoading, error, data } = db.useQuery({ goals: {} });
210
+ *
211
+ * // goals where the title is "Get Fit"
212
+ * const { isLoading, error, data } = db.useQuery({
213
+ * goals: { $: { where: { title: 'Get Fit' } } },
214
+ * });
215
+ *
216
+ * // all goals, _alongside_ their todos
217
+ * const { isLoading, error, data } = db.useQuery({
218
+ * goals: { todos: {} },
219
+ * });
220
+ *
221
+ * // skip if `user` is not logged in
222
+ * const { isLoading, error, data } = db.useQuery(
223
+ * auth.user ? { goals: {} } : null,
224
+ * );
225
+ */
226
+ useQuery = <Q extends ValidQuery<Q, Schema>>(
227
+ query: null | Q,
228
+ opts?: InstaQLOptions,
229
+ ): InstaQLLifecycleState<Schema, Q, UseDates> => {
230
+ return useQueryInternal<Q, Schema, UseDates>(this.core, query, opts).state;
231
+ };
232
+
233
+ /**
234
+ * Listen for the logged in state. This is useful
235
+ * for deciding when to show a login screen.
236
+ *
237
+ * Check out the docs for an example `Login` component too!
238
+ *
239
+ * @see https://instantdb.com/docs/auth
240
+ * @example
241
+ * function App() {
242
+ * const { isLoading, user, error } = db.useAuth()
243
+ * if (isLoading) {
244
+ * return <div>Loading...</div>
245
+ * }
246
+ * if (error) {
247
+ * return <div>Uh oh! {error.message}</div>
248
+ * }
249
+ * if (user) {
250
+ * return <Main user={user} />
251
+ * }
252
+ * return <Login />
253
+ * }
254
+ *
255
+ */
256
+ useAuth = (): AuthState => {
257
+ return this._useAuth();
258
+ };
259
+
260
+ protected _useAuth(): AuthState {
261
+ // We use a ref to store the result of the query.
262
+ // This is becuase `useSyncExternalStore` uses `Object.is`
263
+ // to compare the previous and next state.
264
+ // If we don't use a ref, the state will always be considered different, so
265
+ // the component will always re-render.
266
+ const resultCacheRef = useRef<AuthState>(
267
+ this.core._reactor._currentUserCached,
268
+ );
269
+
270
+ // Similar to `resultCacheRef`, `useSyncExternalStore` will unsubscribe
271
+ // if `subscribe` changes, so we use `useCallback` to memoize the function.
272
+ const subscribe = useCallback((cb: Function) => {
273
+ const unsubscribe = this.core.subscribeAuth((auth) => {
274
+ resultCacheRef.current = { isLoading: false, ...auth };
275
+ cb();
276
+ });
277
+
278
+ return unsubscribe;
279
+ }, []);
280
+
281
+ const state = useSyncExternalStore<AuthState>(
282
+ subscribe,
283
+ () => resultCacheRef.current,
284
+ () => defaultAuthState,
285
+ );
286
+ return state;
287
+ }
288
+
289
+ /**
290
+ * Subscribe to the currently logged in user.
291
+ * If the user is not logged in, this hook with throw an Error.
292
+ * You will want to protect any calls of this hook with a
293
+ * <db.SignedIn> component, or your own logic based on db.useAuth()
294
+ *
295
+ * @see https://instantdb.com/docs/auth
296
+ * @throws Error indicating user not signed in
297
+ * @example
298
+ * function UserDisplay() {
299
+ * const user = db.useUser()
300
+ * return <div>Logged in as: {user.email}</div>
301
+ * }
302
+ *
303
+ * <db.SignedIn>
304
+ * <UserDisplay />
305
+ * </db.SignedIn>
306
+ *
307
+ */
308
+ useUser = (): User => {
309
+ const { user } = this.useAuth();
310
+ if (!user) {
311
+ throw new InstantError(
312
+ 'useUser must be used within an auth-protected route',
313
+ );
314
+ }
315
+ return user;
316
+ };
317
+
318
+ /**
319
+ * One time query for the logged in state. This is useful
320
+ * for scenarios where you want to know the current auth
321
+ * state without subscribing to changes.
322
+ *
323
+ * @see https://instantdb.com/docs/auth
324
+ * @example
325
+ * const user = await db.getAuth();
326
+ * console.log('logged in as', user.email)
327
+ */
328
+ getAuth(): Promise<User | null> {
329
+ return this.core.getAuth();
330
+ }
331
+
332
+ /**
333
+ * Listen for connection status changes to Instant. Use this for things like
334
+ * showing connection state to users
335
+ *
336
+ * @see https://www.instantdb.com/docs/patterns#connection-status
337
+ * @example
338
+ * function App() {
339
+ * const status = db.useConnectionStatus()
340
+ * const connectionState =
341
+ * status === 'connecting' || status === 'opened'
342
+ * ? 'authenticating'
343
+ * : status === 'authenticated'
344
+ * ? 'connected'
345
+ * : status === 'closed'
346
+ * ? 'closed'
347
+ * : status === 'errored'
348
+ * ? 'errored'
349
+ * : 'unexpected state';
350
+ *
351
+ * return <div>Connection state: {connectionState}</div>
352
+ * }
353
+ */
354
+ useConnectionStatus = (): ConnectionStatus => {
355
+ const statusRef = useRef<ConnectionStatus>(
356
+ this.core._reactor.status as ConnectionStatus,
357
+ );
358
+
359
+ const subscribe = useCallback((cb: Function) => {
360
+ const unsubscribe = this.core.subscribeConnectionStatus((newStatus) => {
361
+ if (newStatus !== statusRef.current) {
362
+ statusRef.current = newStatus;
363
+ cb();
364
+ }
365
+ });
366
+
367
+ return unsubscribe;
368
+ }, []);
369
+
370
+ const status = useSyncExternalStore<ConnectionStatus>(
371
+ subscribe,
372
+ () => statusRef.current,
373
+ // For SSR, always return 'connecting' as the initial state
374
+ () => 'connecting',
375
+ );
376
+
377
+ return status;
378
+ };
379
+
380
+ /**
381
+ * Observe the app's maintenance-mode state. While read-only, reads and
382
+ * live queries keep working but writes are rejected.
383
+ *
384
+ * @example
385
+ * function App() {
386
+ * const { isLoading, isReadOnly } = db.useAppStatus()
387
+ * if (isLoading) return null
388
+ * if (isReadOnly) return <MaintenanceBanner />
389
+ * // ...
390
+ * }
391
+ */
392
+ useAppStatus = (): AppStatusState => {
393
+ const stateRef = useRef<AppStatusState>(this.core._reactor._appStatusState);
394
+
395
+ const subscribe = useCallback((cb: () => void) => {
396
+ return this.core.subscribeAppStatus((state) => {
397
+ if (
398
+ state.isLoading !== stateRef.current.isLoading ||
399
+ state.isReadOnly !== stateRef.current.isReadOnly
400
+ ) {
401
+ stateRef.current = state;
402
+ cb();
403
+ }
404
+ });
405
+ }, []);
406
+
407
+ const state = useSyncExternalStore<AppStatusState>(
408
+ subscribe,
409
+ () => stateRef.current,
410
+ () => defaultAppStatusState,
411
+ );
412
+
413
+ return state;
414
+ };
415
+
416
+ /**
417
+ * Use this for one-off queries.
418
+ * Returns local data if available, otherwise fetches from the server.
419
+ * Because we want to avoid stale data, this method will throw an error
420
+ * if the user is offline or there is no active connection to the server.
421
+ *
422
+ * @see https://instantdb.com/docs/instaql
423
+ *
424
+ * @example
425
+ *
426
+ * const resp = await db.queryOnce({ goals: {} });
427
+ * console.log(resp.data.goals)
428
+ */
429
+ queryOnce = <Q extends ValidQuery<Q, Schema>>(
430
+ query: Q,
431
+ opts?: InstaQLOptions,
432
+ ): Promise<{
433
+ data: InstaQLResponse<Schema, Q, UseDates>;
434
+ pageInfo: PageInfoResponse<Q>;
435
+ }> => {
436
+ return this.core.queryOnce(query, opts);
437
+ };
438
+
439
+ /**
440
+ * Subscribe to a query and incrementally load more items
441
+ *
442
+ * Only one top level namespace in the query is allowed.
443
+ *
444
+ * Changing the query or options while the subscription is active will
445
+ * reset the subscription and start over with new data.
446
+ * @example
447
+ * const {
448
+ * data,
449
+ * loadNextPage,
450
+ * canLoadNextPage,
451
+ * } = db.useInfiniteQuery({
452
+ * posts: {
453
+ * $: {
454
+ * limit: 20, // Load 20 posts at a time
455
+ * order: {
456
+ * createdAt: 'desc',
457
+ * },
458
+ * },
459
+ * },
460
+ * });
461
+ */
462
+ useInfiniteQuery = <Q extends ValidQuery<Q, Schema>>(
463
+ query: Q,
464
+ opts?: InstaQLOptions,
465
+ ): InfiniteQueryResult<Schema, Q, UseDates> => {
466
+ const result = useInfiniteQuerySubscription<Schema, Q, UseDates>({
467
+ core: this.core,
468
+ query: query,
469
+ opts,
470
+ });
471
+ return result;
472
+ };
473
+
474
+ /**
475
+ * Only render children if the user is signed in.
476
+ * @see https://instantdb.com/docs/auth
477
+ *
478
+ * @example
479
+ * <db.SignedIn>
480
+ * <MyComponent />
481
+ * </db.SignedIn>
482
+ *
483
+ */
484
+ SignedIn: React.FC<{
485
+ children: ReactNode;
486
+ }> = ({ children }) => {
487
+ const auth = this.useAuth();
488
+ if (auth.isLoading || auth.error || !auth.user) return null;
489
+
490
+ return <>{children}</>;
491
+ };
492
+
493
+ /**
494
+ * Only render children if the user is signed out.
495
+ * @see https://instantdb.com/docs/auth
496
+ *
497
+ * @example
498
+ * <db.SignedOut>
499
+ * <MyComponent />
500
+ * </db.SignedOut>
501
+ *
502
+ */
503
+ SignedOut: React.FC<{
504
+ children: ReactNode;
505
+ }> = ({ children }) => {
506
+ const auth = this.useAuth();
507
+ if (auth.isLoading || auth.error || auth.user) return null;
508
+ return <>{children}</>;
509
+ };
510
+ }