@instantdb/solidjs 0.0.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 (48) 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/.turbo/turbo-build.log +69 -0
  5. package/dist/commonjs/InstantSolidDatabase.d.ts +156 -0
  6. package/dist/commonjs/InstantSolidDatabase.d.ts.map +1 -0
  7. package/dist/commonjs/InstantSolidDatabase.js +265 -0
  8. package/dist/commonjs/InstantSolidDatabase.js.map +1 -0
  9. package/dist/commonjs/InstantSolidRoom.d.ts +97 -0
  10. package/dist/commonjs/InstantSolidRoom.d.ts.map +1 -0
  11. package/dist/commonjs/InstantSolidRoom.js +200 -0
  12. package/dist/commonjs/InstantSolidRoom.js.map +1 -0
  13. package/dist/commonjs/index.d.ts +6 -0
  14. package/dist/commonjs/index.d.ts.map +1 -0
  15. package/dist/commonjs/index.js +24 -0
  16. package/dist/commonjs/index.js.map +1 -0
  17. package/dist/commonjs/package.json +3 -0
  18. package/dist/commonjs/version.d.ts +3 -0
  19. package/dist/commonjs/version.d.ts.map +1 -0
  20. package/dist/commonjs/version.js +5 -0
  21. package/dist/commonjs/version.js.map +1 -0
  22. package/dist/esm/InstantSolidDatabase.d.ts +156 -0
  23. package/dist/esm/InstantSolidDatabase.d.ts.map +1 -0
  24. package/dist/esm/InstantSolidDatabase.js +257 -0
  25. package/dist/esm/InstantSolidDatabase.js.map +1 -0
  26. package/dist/esm/InstantSolidRoom.d.ts +97 -0
  27. package/dist/esm/InstantSolidRoom.d.ts.map +1 -0
  28. package/dist/esm/InstantSolidRoom.js +191 -0
  29. package/dist/esm/InstantSolidRoom.js.map +1 -0
  30. package/dist/esm/index.d.ts +6 -0
  31. package/dist/esm/index.d.ts.map +1 -0
  32. package/dist/esm/index.js +18 -0
  33. package/dist/esm/index.js.map +1 -0
  34. package/dist/esm/package.json +3 -0
  35. package/dist/esm/version.d.ts +3 -0
  36. package/dist/esm/version.d.ts.map +1 -0
  37. package/dist/esm/version.js +3 -0
  38. package/dist/esm/version.js.map +1 -0
  39. package/dist/standalone/index.js +7010 -0
  40. package/dist/standalone/index.umd.cjs +7 -0
  41. package/package.json +63 -0
  42. package/src/InstantSolidDatabase.ts +363 -0
  43. package/src/InstantSolidRoom.ts +319 -0
  44. package/src/index.ts +191 -0
  45. package/src/version.ts +2 -0
  46. package/tsconfig.dev.json +14 -0
  47. package/tsconfig.json +45 -0
  48. package/vite.config.ts +20 -0
@@ -0,0 +1,363 @@
1
+ import {
2
+ // types
3
+ type AuthState,
4
+ type User,
5
+ type ConnectionStatus,
6
+ type TransactionChunk,
7
+ type RoomSchemaShape,
8
+ type InstaQLOptions,
9
+ type InstantConfig,
10
+ type PageInfoResponse,
11
+ type InstaQLLifecycleState,
12
+ type InstaQLResponse,
13
+ type ValidQuery,
14
+ // classes
15
+ Auth,
16
+ Storage,
17
+ txInit,
18
+ InstantCoreDatabase,
19
+ init as core_init,
20
+ coerceQuery,
21
+ InstantSchemaDef,
22
+ RoomsOf,
23
+ InstantError,
24
+ IInstantDatabase,
25
+ } from '@instantdb/core';
26
+
27
+ import { createSignal, createEffect, onCleanup, createMemo } from 'solid-js';
28
+ import type { Accessor } from 'solid-js';
29
+
30
+ import { InstantSolidRoom, rooms } from './InstantSolidRoom.js';
31
+ import version from './version.js';
32
+
33
+ const defaultState = {
34
+ isLoading: true,
35
+ data: undefined,
36
+ pageInfo: undefined,
37
+ error: undefined,
38
+ } as const;
39
+
40
+ const defaultAuthState: AuthState = {
41
+ isLoading: true,
42
+ user: undefined,
43
+ error: undefined,
44
+ };
45
+
46
+ function stateForResult(result: any) {
47
+ return {
48
+ isLoading: !Boolean(result),
49
+ data: undefined,
50
+ pageInfo: undefined,
51
+ error: undefined,
52
+ ...(result ? result : {}),
53
+ };
54
+ }
55
+
56
+ export class InstantSolidDatabase<
57
+ Schema extends InstantSchemaDef<any, any, any>,
58
+ UseDates extends boolean = false,
59
+ Rooms extends RoomSchemaShape = RoomsOf<Schema>,
60
+ > implements IInstantDatabase<Schema>
61
+ {
62
+ public tx = txInit<Schema>();
63
+
64
+ public auth: Auth;
65
+ public storage: Storage;
66
+ public core: InstantCoreDatabase<Schema, UseDates>;
67
+
68
+ constructor(core: InstantCoreDatabase<Schema, UseDates>) {
69
+ this.core = core;
70
+ this.auth = this.core.auth;
71
+ this.storage = this.core.storage;
72
+ }
73
+
74
+ /**
75
+ * Returns a unique ID for a given `name`. It's stored in local storage,
76
+ * so you will get the same ID across sessions.
77
+ *
78
+ * @example
79
+ * const deviceId = await db.getLocalId('device');
80
+ */
81
+ getLocalId = (name: string): Promise<string> => {
82
+ return this.core.getLocalId(name);
83
+ };
84
+
85
+ /**
86
+ * Use this to write data! You can create, update, delete, and link objects
87
+ *
88
+ * @see https://instantdb.com/docs/instaml
89
+ *
90
+ * @example
91
+ * const goalId = id();
92
+ * db.transact(db.tx.goals[goalId].update({title: "Get fit"}))
93
+ */
94
+ transact = (
95
+ chunks: TransactionChunk<any, any> | TransactionChunk<any, any>[],
96
+ ) => {
97
+ return this.core.transact(chunks);
98
+ };
99
+
100
+ /**
101
+ * One time query for the logged in state.
102
+ *
103
+ * @see https://instantdb.com/docs/auth
104
+ * @example
105
+ * const user = await db.getAuth();
106
+ * console.log('logged in as', user.email)
107
+ */
108
+ getAuth(): Promise<User | null> {
109
+ return this.core.getAuth();
110
+ }
111
+
112
+ /**
113
+ * Use this for one-off queries.
114
+ * Returns local data if available, otherwise fetches from the server.
115
+ *
116
+ * @see https://instantdb.com/docs/instaql
117
+ *
118
+ * @example
119
+ * const resp = await db.queryOnce({ goals: {} });
120
+ * console.log(resp.data.goals)
121
+ */
122
+ queryOnce = <Q extends ValidQuery<Q, Schema>>(
123
+ query: Q,
124
+ opts?: InstaQLOptions,
125
+ ): Promise<{
126
+ data: InstaQLResponse<Schema, Q, UseDates>;
127
+ pageInfo: PageInfoResponse<Q>;
128
+ }> => {
129
+ return this.core.queryOnce(query, opts);
130
+ };
131
+
132
+ // -----------
133
+ // Solid reactive hooks
134
+
135
+ /**
136
+ * Use this to query your data!
137
+ *
138
+ * @see https://instantdb.com/docs/instaql
139
+ *
140
+ * @example
141
+ * const state = db.useQuery({ goals: {} });
142
+ * // state().isLoading, state().error, state().data
143
+ */
144
+ useQuery = <Q extends ValidQuery<Q, Schema>>(
145
+ query: null | Q,
146
+ opts?: InstaQLOptions,
147
+ ): Accessor<InstaQLLifecycleState<Schema, Q, UseDates>> => {
148
+ const [state, setState] = createSignal<
149
+ InstaQLLifecycleState<Schema, Q, UseDates>
150
+ >(defaultState as InstaQLLifecycleState<Schema, Q, UseDates>);
151
+
152
+ createEffect(() => {
153
+ if (!query) {
154
+ setState(
155
+ () => defaultState as InstaQLLifecycleState<Schema, Q, UseDates>,
156
+ );
157
+ return;
158
+ }
159
+
160
+ let q = query;
161
+ if (opts && 'ruleParams' in opts) {
162
+ q = { $$ruleParams: (opts as any)['ruleParams'], ...q };
163
+ }
164
+
165
+ const coerced = coerceQuery(q);
166
+ const prev = this.core._reactor.getPreviousResult(coerced);
167
+ if (prev) {
168
+ setState(
169
+ () =>
170
+ stateForResult(prev) as InstaQLLifecycleState<Schema, Q, UseDates>,
171
+ );
172
+ }
173
+
174
+ const unsub = this.core.subscribeQuery<Q, UseDates>(coerced, (result) => {
175
+ setState(
176
+ () =>
177
+ Object.assign(
178
+ {
179
+ isLoading: false,
180
+ data: undefined,
181
+ pageInfo: undefined,
182
+ error: undefined,
183
+ },
184
+ result,
185
+ ) as InstaQLLifecycleState<Schema, Q, UseDates>,
186
+ );
187
+ });
188
+
189
+ onCleanup(unsub);
190
+ });
191
+
192
+ return state;
193
+ };
194
+
195
+ /**
196
+ * Listen for the logged in state. This is useful
197
+ * for deciding when to show a login screen.
198
+ *
199
+ * @see https://instantdb.com/docs/auth
200
+ * @example
201
+ * function App() {
202
+ * const auth = db.useAuth();
203
+ * // auth().isLoading, auth().user, auth().error
204
+ * }
205
+ */
206
+ useAuth = (): Accessor<AuthState> => {
207
+ const [state, setState] = createSignal<AuthState>(
208
+ this.core._reactor._currentUserCached ?? defaultAuthState,
209
+ );
210
+
211
+ createEffect(() => {
212
+ const unsub = this.core.subscribeAuth((auth) => {
213
+ setState({ isLoading: false, ...auth });
214
+ });
215
+
216
+ onCleanup(unsub);
217
+ });
218
+
219
+ return state;
220
+ };
221
+
222
+ /**
223
+ * Subscribe to the currently logged in user.
224
+ * If the user is not logged in, this will throw an Error.
225
+ *
226
+ * @see https://instantdb.com/docs/auth
227
+ * @example
228
+ * function UserDisplay() {
229
+ * const user = db.useUser();
230
+ * return <div>Logged in as: {user().email}</div>
231
+ * }
232
+ */
233
+ useUser = (): Accessor<User> => {
234
+ const auth = this.useAuth();
235
+ return createMemo(() => {
236
+ const { user } = auth();
237
+ if (!user) {
238
+ throw new InstantError(
239
+ 'useUser must be used within an auth-protected route',
240
+ );
241
+ }
242
+ return user;
243
+ });
244
+ };
245
+
246
+ /**
247
+ * Listen for connection status changes to Instant.
248
+ *
249
+ * @see https://www.instantdb.com/docs/patterns#connection-status
250
+ * @example
251
+ * function App() {
252
+ * const status = db.useConnectionStatus();
253
+ * return <div>Connection state: {status()}</div>
254
+ * }
255
+ */
256
+ useConnectionStatus = (): Accessor<ConnectionStatus> => {
257
+ const [status, setStatus] = createSignal<ConnectionStatus>(
258
+ this.core._reactor.status as ConnectionStatus,
259
+ );
260
+
261
+ createEffect(() => {
262
+ const unsub = this.core.subscribeConnectionStatus((newStatus) => {
263
+ setStatus(() => newStatus);
264
+ });
265
+
266
+ onCleanup(unsub);
267
+ });
268
+
269
+ return status;
270
+ };
271
+
272
+ /**
273
+ * A hook that returns a unique ID for a given `name`. localIds are
274
+ * stored in local storage, so you will get the same ID across sessions.
275
+ *
276
+ * Initially returns `null`, and then loads the localId.
277
+ *
278
+ * @example
279
+ * const deviceId = db.useLocalId('device');
280
+ * // deviceId() is null initially, then the ID string
281
+ */
282
+ useLocalId = (name: string): Accessor<string | null> => {
283
+ const [localId, setLocalId] = createSignal<string | null>(null);
284
+
285
+ createEffect(() => {
286
+ let mounted = true;
287
+ this.getLocalId(name).then((id) => {
288
+ if (mounted) {
289
+ setLocalId(() => id);
290
+ }
291
+ });
292
+ onCleanup(() => {
293
+ mounted = false;
294
+ });
295
+ });
296
+
297
+ return localId;
298
+ };
299
+
300
+ /**
301
+ * Obtain a handle to a room, which allows you to listen to topics and presence data
302
+ *
303
+ * @see https://instantdb.com/docs/presence-and-topics
304
+ *
305
+ * @example
306
+ * const room = db.room('chat', roomId);
307
+ * const presence = db.rooms.usePresence(room);
308
+ */
309
+ room<RoomType extends keyof Rooms>(
310
+ type: RoomType = '_defaultRoomType' as RoomType,
311
+ id: string = '_defaultRoomId',
312
+ ) {
313
+ return new InstantSolidRoom<Schema, Rooms, RoomType>(this.core, type, id);
314
+ }
315
+
316
+ /**
317
+ * Hooks for working with rooms
318
+ *
319
+ * @see https://instantdb.com/docs/presence-and-topics
320
+ *
321
+ * @example
322
+ * const room = db.room('chat', roomId);
323
+ * const presence = db.rooms.usePresence(room);
324
+ * const publish = db.rooms.usePublishTopic(room, 'emoji');
325
+ */
326
+ rooms = rooms;
327
+ }
328
+
329
+ // -----------
330
+ // init
331
+
332
+ /**
333
+ * The first step: init your application!
334
+ *
335
+ * Visit https://instantdb.com/dash to get your `appId` :)
336
+ *
337
+ * @example
338
+ * import { init } from "@instantdb/solidjs"
339
+ *
340
+ * const db = init({ appId: "my-app-id" })
341
+ *
342
+ * // You can also provide a schema for type safety and editor autocomplete!
343
+ *
344
+ * import { init } from "@instantdb/solidjs"
345
+ * import schema from "../instant.schema.ts";
346
+ *
347
+ * const db = init({ appId: "my-app-id", schema })
348
+ */
349
+ export function init<
350
+ Schema extends InstantSchemaDef<any, any, any>,
351
+ UseDates extends boolean = false,
352
+ >(
353
+ config: Omit<InstantConfig<Schema, UseDates>, 'useDateObjects'> & {
354
+ useDateObjects?: UseDates;
355
+ },
356
+ ): InstantSolidDatabase<Schema, UseDates> {
357
+ const coreDb = core_init<Schema, UseDates>(config, undefined, undefined, {
358
+ '@instantdb/solidjs': version,
359
+ });
360
+ return new InstantSolidDatabase<Schema, UseDates>(coreDb);
361
+ }
362
+
363
+ export const init_experimental = init;
@@ -0,0 +1,319 @@
1
+ import {
2
+ type PresenceOpts,
3
+ type PresenceResponse,
4
+ type RoomSchemaShape,
5
+ InstantCoreDatabase,
6
+ InstantSchemaDef,
7
+ } from '@instantdb/core';
8
+
9
+ import { createSignal, createEffect, onCleanup, createMemo } from 'solid-js';
10
+ import type { Accessor } from 'solid-js';
11
+
12
+ // ------
13
+ // Types
14
+
15
+ export type PresenceHandle<
16
+ PresenceShape,
17
+ Keys extends keyof PresenceShape,
18
+ > = PresenceResponse<PresenceShape, Keys> & {
19
+ publishPresence: (data: Partial<PresenceShape>) => void;
20
+ };
21
+
22
+ export type TypingIndicatorOpts = {
23
+ timeout?: number | null;
24
+ stopOnEnter?: boolean;
25
+ // Perf opt - `active` will always be an empty array
26
+ writeOnly?: boolean;
27
+ };
28
+
29
+ export type TypingIndicatorHandle<PresenceShape> = {
30
+ active: Accessor<PresenceShape[]>;
31
+ setActive(active: boolean): void;
32
+ inputProps: {
33
+ onKeyDown: (e: KeyboardEvent) => void;
34
+ onBlur: () => void;
35
+ };
36
+ };
37
+
38
+ export const defaultActivityStopTimeout = 1_000;
39
+
40
+ // ------
41
+ // Topics
42
+
43
+ /**
44
+ * Listen for broadcasted events given a room and topic.
45
+ *
46
+ * @see https://instantdb.com/docs/presence-and-topics
47
+ * @example
48
+ * function App({ roomId }) {
49
+ * const room = db.room('chats', roomId);
50
+ * db.rooms.useTopicEffect(room, 'emoji', (message, peer) => {
51
+ * console.log(peer.name, 'sent', message);
52
+ * });
53
+ * // ...
54
+ * }
55
+ */
56
+ export function useTopicEffect<
57
+ RoomSchema extends RoomSchemaShape,
58
+ RoomType extends keyof RoomSchema,
59
+ TopicType extends keyof RoomSchema[RoomType]['topics'],
60
+ >(
61
+ room: InstantSolidRoom<any, RoomSchema, RoomType>,
62
+ topic: TopicType,
63
+ onEvent: (
64
+ event: RoomSchema[RoomType]['topics'][TopicType],
65
+ peer: RoomSchema[RoomType]['presence'],
66
+ ) => any,
67
+ ): void {
68
+ createEffect(() => {
69
+ const unsub = room.core._reactor.subscribeTopic(
70
+ room.type,
71
+ room.id,
72
+ topic,
73
+ (event: any, peer: any) => {
74
+ onEvent(event, peer);
75
+ },
76
+ );
77
+
78
+ onCleanup(unsub);
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Broadcast an event to a room.
84
+ *
85
+ * @see https://instantdb.com/docs/presence-and-topics
86
+ * @example
87
+ * function App({ roomId }) {
88
+ * const room = db.room('chat', roomId);
89
+ * const publishTopic = db.rooms.usePublishTopic(room, "emoji");
90
+ *
91
+ * return (
92
+ * <button onClick={() => publishTopic({ emoji: "🔥" })}>Send emoji</button>
93
+ * );
94
+ * }
95
+ *
96
+ */
97
+ export function usePublishTopic<
98
+ RoomSchema extends RoomSchemaShape,
99
+ RoomType extends keyof RoomSchema,
100
+ TopicType extends keyof RoomSchema[RoomType]['topics'],
101
+ >(
102
+ room: InstantSolidRoom<any, RoomSchema, RoomType>,
103
+ topic: TopicType,
104
+ ): (data: RoomSchema[RoomType]['topics'][TopicType]) => void {
105
+ createEffect(() => {
106
+ const unsub = room.core._reactor.joinRoom(room.type as string, room.id);
107
+ onCleanup(unsub);
108
+ });
109
+
110
+ return (data: RoomSchema[RoomType]['topics'][TopicType]) => {
111
+ room.core._reactor.publishTopic({
112
+ roomType: room.type,
113
+ roomId: room.id,
114
+ topic,
115
+ data,
116
+ });
117
+ };
118
+ }
119
+
120
+ // ---------
121
+ // Presence
122
+
123
+ /**
124
+ * Listen for peer's presence data in a room, and publish the current user's presence.
125
+ *
126
+ * @see https://instantdb.com/docs/presence-and-topics
127
+ * @example
128
+ * function App({ roomId }) {
129
+ * const presence = db.rooms.usePresence(room, { keys: ["name", "avatar"] });
130
+ * // presence().peers, presence().isLoading, presence().publishPresence
131
+ * }
132
+ */
133
+ export function usePresence<
134
+ RoomSchema extends RoomSchemaShape,
135
+ RoomType extends keyof RoomSchema,
136
+ Keys extends keyof RoomSchema[RoomType]['presence'],
137
+ >(
138
+ room: InstantSolidRoom<any, RoomSchema, RoomType>,
139
+ opts: PresenceOpts<RoomSchema[RoomType]['presence'], Keys> = {},
140
+ ): Accessor<PresenceHandle<RoomSchema[RoomType]['presence'], Keys>> {
141
+ const [state, setState] = createSignal<
142
+ PresenceResponse<RoomSchema[RoomType]['presence'], Keys>
143
+ >(
144
+ (room.core._reactor.getPresence(room.type, room.id, opts) ?? {
145
+ peers: {},
146
+ isLoading: true,
147
+ }) as PresenceResponse<RoomSchema[RoomType]['presence'], Keys>,
148
+ );
149
+
150
+ createEffect(() => {
151
+ const unsub = room.core._reactor.subscribePresence(
152
+ room.type,
153
+ room.id,
154
+ opts,
155
+ (data: any) => {
156
+ setState(data);
157
+ },
158
+ );
159
+
160
+ onCleanup(unsub);
161
+ });
162
+
163
+ const publishPresence = (data: Partial<RoomSchema[RoomType]['presence']>) => {
164
+ room.core._reactor.publishPresence(room.type, room.id, data);
165
+ };
166
+
167
+ return createMemo(() => ({
168
+ ...state(),
169
+ publishPresence,
170
+ }));
171
+ }
172
+
173
+ /**
174
+ * Publishes presence data to a room
175
+ *
176
+ * @see https://instantdb.com/docs/presence-and-topics
177
+ * @example
178
+ * function App({ roomId, nickname }) {
179
+ * const room = db.room('chat', roomId);
180
+ * db.rooms.useSyncPresence(room, { nickname });
181
+ * }
182
+ */
183
+ export function useSyncPresence<
184
+ RoomSchema extends RoomSchemaShape,
185
+ RoomType extends keyof RoomSchema,
186
+ >(
187
+ room: InstantSolidRoom<any, RoomSchema, RoomType>,
188
+ data: Partial<RoomSchema[RoomType]['presence']>,
189
+ deps?: any[],
190
+ ): void {
191
+ createEffect(() => {
192
+ const unsub = room.core._reactor.joinRoom(
193
+ room.type as string,
194
+ room.id,
195
+ data,
196
+ );
197
+ onCleanup(unsub);
198
+ });
199
+
200
+ createEffect(() => {
201
+ // Track deps if provided, otherwise track serialized data
202
+ if (deps) {
203
+ deps.forEach((d) => d);
204
+ } else {
205
+ JSON.stringify(data);
206
+ }
207
+ room.core._reactor.publishPresence(room.type, room.id, data);
208
+ });
209
+ }
210
+
211
+ // -----------------
212
+ // Typing Indicator
213
+
214
+ /**
215
+ * Manage typing indicator state
216
+ *
217
+ * @see https://instantdb.com/docs/presence-and-topics
218
+ * @example
219
+ * function App({ roomId }) {
220
+ * const room = db.room('chat', roomId);
221
+ * const typing = db.rooms.useTypingIndicator(room, "chat-input");
222
+ * // typing.active(), typing.setActive(bool), typing.inputProps
223
+ * }
224
+ */
225
+ export function useTypingIndicator<
226
+ RoomSchema extends RoomSchemaShape,
227
+ RoomType extends keyof RoomSchema,
228
+ >(
229
+ room: InstantSolidRoom<any, RoomSchema, RoomType>,
230
+ inputName: string,
231
+ opts: TypingIndicatorOpts = {},
232
+ ): TypingIndicatorHandle<RoomSchema[RoomType]['presence']> {
233
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
234
+
235
+ const presence = rooms.usePresence(room, {
236
+ keys: [inputName] as (keyof RoomSchema[RoomType]['presence'])[],
237
+ });
238
+
239
+ const active = createMemo(() => {
240
+ if (opts?.writeOnly) return [];
241
+ // Access presence to track it
242
+ presence();
243
+ const presenceSnapshot = room.core._reactor.getPresence(room.type, room.id);
244
+ return Object.values(presenceSnapshot?.peers ?? {}).filter(
245
+ (p: any) => p[inputName] === true,
246
+ );
247
+ });
248
+
249
+ const setActive = (isActive: boolean) => {
250
+ room.core._reactor.publishPresence(room.type, room.id, {
251
+ [inputName]: isActive,
252
+ } as unknown as Partial<RoomSchema[RoomType]['presence']>);
253
+
254
+ if (timeoutId) {
255
+ clearTimeout(timeoutId);
256
+ timeoutId = null;
257
+ }
258
+
259
+ if (!isActive) return;
260
+
261
+ if (opts?.timeout === null || opts?.timeout === 0) return;
262
+
263
+ timeoutId = setTimeout(() => {
264
+ room.core._reactor.publishPresence(room.type, room.id, {
265
+ [inputName]: null,
266
+ } as Partial<RoomSchema[RoomType]['presence']>);
267
+ }, opts?.timeout ?? defaultActivityStopTimeout);
268
+ };
269
+
270
+ const onKeyDown = (e: KeyboardEvent) => {
271
+ const isEnter = opts?.stopOnEnter && e.key === 'Enter';
272
+ const isActive = !isEnter;
273
+ setActive(isActive);
274
+ };
275
+
276
+ const onBlur = () => {
277
+ setActive(false);
278
+ };
279
+
280
+ return {
281
+ active,
282
+ setActive,
283
+ inputProps: { onKeyDown, onBlur },
284
+ };
285
+ }
286
+
287
+ // --------------
288
+ // Hooks namespace
289
+
290
+ export const rooms = {
291
+ useTopicEffect,
292
+ usePublishTopic,
293
+ usePresence,
294
+ useSyncPresence,
295
+ useTypingIndicator,
296
+ };
297
+
298
+ // ------------
299
+ // Class
300
+
301
+ export class InstantSolidRoom<
302
+ Schema extends InstantSchemaDef<any, any, any>,
303
+ RoomSchema extends RoomSchemaShape,
304
+ RoomType extends keyof RoomSchema,
305
+ > {
306
+ core: InstantCoreDatabase<Schema, boolean>;
307
+ type: RoomType;
308
+ id: string;
309
+
310
+ constructor(
311
+ core: InstantCoreDatabase<Schema, boolean>,
312
+ type: RoomType,
313
+ id: string,
314
+ ) {
315
+ this.core = core;
316
+ this.type = type;
317
+ this.id = id;
318
+ }
319
+ }