@noverachat/sdk-react 0.3.0 → 0.4.0

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/dist/index.d.cts CHANGED
@@ -1,65 +1,8 @@
1
- import { SenderMini, FileInline, ReactionEntry, MessageOut, WsChatReceive, ClientOptions, NoveraChat, RoomMemberOut, FileMeta, MemberPreview, InviteToken, JoinRequestOut, PushTrigger, SendFileOptions, DeleteScope, Room, UnreadSummary } from '@noverachat/sdk-web';
1
+ import { ClientOptions, NoveraChat, RoomMemberOut, FileMeta, MemberPreview, WsChatReceive, InviteToken, JoinRequestOut, PushTrigger, ChatMessage, SendFileOptions, DeleteScope, Room, UnreadSummary } from '@noverachat/sdk-web';
2
2
  export * from '@noverachat/sdk-web';
3
+ export { ChatMessage, ChatMessageStatus, chatMessageFromHistory, chatMessageFromLive } from '@noverachat/sdk-web';
3
4
  import { ReactNode } from 'react';
4
5
 
5
- /** Delivery status of an outgoing (optimistic) message. */
6
- type ChatMessageStatus = "sending" | "sent" | "failed";
7
- /**
8
- * A single message as the UI wants it — one uniform shape over the SDK's
9
- * three sources: `MessageOut` (history/REST), `WsChatReceive` (live/WS), and
10
- * an optimistic bubble we just sent.
11
- *
12
- * Treat instances as immutable — `RoomStore` evolves them by replacement,
13
- * never mutation.
14
- */
15
- interface ChatMessage {
16
- /** The server message id, or the `tempId` while still `sending`. */
17
- id: string;
18
- /** Author user id. `null` means the current user (an optimistic bubble). */
19
- senderId: string | null;
20
- content: string | null;
21
- /** Creation time in unix milliseconds. */
22
- createdAtMs: number | null;
23
- status: ChatMessageStatus;
24
- /** `TEXT | FILE | EMOTICON | ADMIN | SYSTEM` — UIs use this to render
25
- * system/admin lines (centered notices) differently from chat bubbles. */
26
- messageType: string;
27
- /** Sender identity (nickname / profile image) when the server included it.
28
- * Null for optimistic bubbles and live frames (which carry only
29
- * `senderId`) — UIs typically resolve the profile from their member list. */
30
- sender: SenderMini | null;
31
- /** App-defined subtype (e.g. a message-priority convention). */
32
- customType: string | null;
33
- /** Snowflake id (string) of the attached file, when this is a file message. */
34
- fileId: string | null;
35
- /** Inline metadata of the attached file — enough to render a placeholder
36
- * without a round-trip. May be null even when `fileId` is set; fetch via
37
- * `chat.getFileMeta` then. */
38
- file: FileInline | null;
39
- /** Multi-file bundle (`file_group`). Null for single-file / text messages.
40
- * Check `files?.length` first and fall back to the `file` singleton. */
41
- files: FileInline[] | null;
42
- /** Id of the message this one replies to, if any. */
43
- replyToId: string | null;
44
- /** Emoji reactions grouped by key. Kept live by `RoomStore` from the
45
- * reaction WS events. */
46
- reactions: ReactionEntry[];
47
- /** Sender-attached custom metadata (`data._customMeta`). */
48
- customMeta: Record<string, unknown> | null;
49
- /** How many times the message was edited. `> 0` → show an "edited" mark. */
50
- editedCount: number;
51
- /** Upload progress in [0, 1] while an optimistic FILE message's bytes are
52
- * still going up to S3. Null once committed (or for non-file messages). */
53
- uploadProgress: number | null;
54
- /** The optimistic temp id, kept for ack reconciliation. Null once confirmed. */
55
- tempId: string | null;
56
- isDeleted: boolean;
57
- }
58
- /** Build a `ChatMessage` from server history (REST). */
59
- declare function chatMessageFromHistory(m: MessageOut): ChatMessage;
60
- /** Build a `ChatMessage` from a live inbound frame (WS). */
61
- declare function chatMessageFromLive(f: WsChatReceive): ChatMessage;
62
-
63
6
  interface NoveraChatProviderProps {
64
7
  /** Options for the `NoveraChat` this provider creates. Read **once**, when
65
8
  * the provider first mounts — to connect with different options, give the
@@ -380,19 +323,20 @@ interface RoomSnapshot {
380
323
  readWatermarks: Readonly<Record<string, string>>;
381
324
  }
382
325
  /**
383
- * Turns a `Room`'s events into an externally-subscribable message list and
384
- * drives optimistic sends. Framework-agnostic on purpose so it can also be
385
- * used outside hooks (tests, non-React glue).
326
+ * React-side glue over the core `MessageCollection` the message state
327
+ * (event folding, optimistic sends, cache hydrate→replace) is owned by the
328
+ * core; this store bridges the collection's change events into
329
+ * `useSyncExternalStore`-shaped immutable snapshots. Framework-agnostic on
330
+ * purpose so it can also be used outside hooks (tests, non-React glue).
386
331
  *
387
- * Lifecycle: construct → `attach()` (subscribe WS events; idempotent)
388
- * `dispose()` (flush read watermark + unsubscribe). `useMessages` does all
389
- * three for you.
332
+ * Lifecycle: construct → `attach()` (idempotent) `dispose()` (flush read
333
+ * watermark + detach page listeners). `useMessages` does all three for you.
390
334
  *
391
335
  * Holds `(chat, roomId)` rather than a `Room` instance and re-resolves the
392
336
  * live facade on every use — `chat.disconnect()` drops its Room facades, so
393
337
  * a remounted provider (React StrictMode simulates this) would otherwise
394
- * leave the store subscribed to an orphaned Room that no longer receives
395
- * dispatches.
338
+ * leave the store bound to an orphaned Room that no longer receives
339
+ * dispatches. The backing collection is recreated when the facade changes.
396
340
  *
397
341
  * ```ts
398
342
  * const store = new RoomStore(chat, "room_123");
@@ -406,12 +350,8 @@ declare class RoomStore {
406
350
  private readonly chat;
407
351
  readonly roomId: string;
408
352
  private readonly listeners;
409
- private readonly unsubs;
410
- private messages;
411
- private readWatermarks;
412
- private hasMore;
413
- private oldestCursor;
414
- private loadingMore;
353
+ private col;
354
+ private colUnsub;
415
355
  private historyRequested;
416
356
  private snapshot;
417
357
  private attached;
@@ -420,26 +360,32 @@ declare class RoomStore {
420
360
  /** The live `Room` facade — resolved from the client on every access
421
361
  * (get-or-create in a map, cheap) so we never act on an orphan. */
422
362
  private get room();
423
- /** Subscribe to WS events + page lifecycle. Safe to call repeatedly
424
- * (no-op while attached) — matches React 18/19 StrictMode's
363
+ /** The backing collection, bound to the CURRENT facade. Recreated when
364
+ * the facade was dropped (post-`chat.disconnect()`)the old one would
365
+ * never receive dispatches again. */
366
+ private collection;
367
+ /** Subscribe page lifecycle + ensure the collection is live. Safe to call
368
+ * repeatedly (no-op while attached) — matches React 18/19 StrictMode's
425
369
  * mount → cleanup → mount effect cycle. */
426
370
  attach(): void;
427
- /** Unsubscribe everything and flush the pending read watermark. The store
428
- * keeps its message state, so a later `attach()` resumes cleanly. */
371
+ /** Detach page listeners and flush the pending read watermark. The
372
+ * collection (and its state) survives, so a later `attach()` resumes
373
+ * cleanly — StrictMode's mount → cleanup → mount cycle keeps messages. */
429
374
  dispose(): void;
430
375
  /** `useSyncExternalStore`-compatible subscribe. Returns an unsubscribe fn. */
431
376
  readonly subscribe: (fn: () => void) => (() => void);
432
377
  readonly getSnapshot: () => RoomSnapshot;
433
- private emit;
378
+ private rebuild;
434
379
  /** Whether `userId` has read `messageId` according to the latest watermark. */
435
380
  isReadBy(userId: string, messageId: string): boolean;
436
381
  /** How many of the given users have read `messageId`. Pass the room's
437
382
  * member ids (minus the sender) to get a KakaoTalk-style unread count:
438
383
  * `members.length - readCount(...)`. */
439
384
  readCount(messageId: string, userIds: Iterable<string>): number;
440
- /** Load the most recent page of history and prepend it. No-op after the
441
- * first call (StrictMode double-mount safe) — use `loadMore` for
442
- * scrollback. */
385
+ /** Load the most recent page of history. No-op after the first call
386
+ * (StrictMode double-mount safe) — use `loadMore` for scrollback.
387
+ * 캐시(`ClientOptions.cacheStore`)가 켜져 있으면 스냅샷 즉시 렌더 →
388
+ * API 도착 시 통째 교체가 컬렉션 안에서 일어난다. */
443
389
  loadHistory(limit?: number): Promise<void>;
444
390
  /** Scrollback: load the page of messages older than what's on screen and
445
391
  * prepend it. No-op while a previous call is in flight or when `hasMore`
@@ -474,17 +420,7 @@ declare class RoomStore {
474
420
  search(q: string, limit?: number): Promise<ChatMessage[]>;
475
421
  /** Mark `messageId` read (debounced inside the SDK). */
476
422
  markRead(messageId: string): void;
477
- private onIncoming;
478
- private onUpdated;
479
- private onDeleted;
480
- private onCleared;
481
- private onReactionAdded;
482
- private onReactionRemoved;
483
- private onSyncTick;
484
423
  private onVisibilityChange;
485
- private bindAck;
486
- private mutateById;
487
- private mutateByTempId;
488
424
  }
489
425
 
490
426
  interface UseMessagesOptions {
@@ -672,4 +608,4 @@ interface UseUnreadResult {
672
608
  */
673
609
  declare function useUnread(opts?: UseUnreadOptions): UseUnreadResult;
674
610
 
675
- export { type ChatMessage, type ChatMessageStatus, type MemberListSnapshot, MemberListStore, NoveraChatProvider, type NoveraChatProviderProps, type RoomFileItem, type RoomFilesSnapshot, RoomFilesStore, type RoomListItem, type RoomListSnapshot, RoomListStore, type RoomListStoreOptions, type RoomSettingsSnapshot, RoomSettingsStore, type RoomSnapshot, RoomStore, type UseMemberListResult, type UseMessagesOptions, type UseMessagesResult, type UseRoomFilesOptions, type UseRoomFilesResult, type UseRoomListResult, type UseRoomSettingsResult, type UseTypingOptions, type UseTypingResult, type UseUnreadOptions, type UseUnreadResult, chatMessageFromHistory, chatMessageFromLive, useMemberList, useMessages, useNoveraChat, useRoom, useRoomFiles, useRoomList, useRoomSettings, useTyping, useUnread };
611
+ export { type MemberListSnapshot, MemberListStore, NoveraChatProvider, type NoveraChatProviderProps, type RoomFileItem, type RoomFilesSnapshot, RoomFilesStore, type RoomListItem, type RoomListSnapshot, RoomListStore, type RoomListStoreOptions, type RoomSettingsSnapshot, RoomSettingsStore, type RoomSnapshot, RoomStore, type UseMemberListResult, type UseMessagesOptions, type UseMessagesResult, type UseRoomFilesOptions, type UseRoomFilesResult, type UseRoomListResult, type UseRoomSettingsResult, type UseTypingOptions, type UseTypingResult, type UseUnreadOptions, type UseUnreadResult, useMemberList, useMessages, useNoveraChat, useRoom, useRoomFiles, useRoomList, useRoomSettings, useTyping, useUnread };
package/dist/index.d.ts CHANGED
@@ -1,65 +1,8 @@
1
- import { SenderMini, FileInline, ReactionEntry, MessageOut, WsChatReceive, ClientOptions, NoveraChat, RoomMemberOut, FileMeta, MemberPreview, InviteToken, JoinRequestOut, PushTrigger, SendFileOptions, DeleteScope, Room, UnreadSummary } from '@noverachat/sdk-web';
1
+ import { ClientOptions, NoveraChat, RoomMemberOut, FileMeta, MemberPreview, WsChatReceive, InviteToken, JoinRequestOut, PushTrigger, ChatMessage, SendFileOptions, DeleteScope, Room, UnreadSummary } from '@noverachat/sdk-web';
2
2
  export * from '@noverachat/sdk-web';
3
+ export { ChatMessage, ChatMessageStatus, chatMessageFromHistory, chatMessageFromLive } from '@noverachat/sdk-web';
3
4
  import { ReactNode } from 'react';
4
5
 
5
- /** Delivery status of an outgoing (optimistic) message. */
6
- type ChatMessageStatus = "sending" | "sent" | "failed";
7
- /**
8
- * A single message as the UI wants it — one uniform shape over the SDK's
9
- * three sources: `MessageOut` (history/REST), `WsChatReceive` (live/WS), and
10
- * an optimistic bubble we just sent.
11
- *
12
- * Treat instances as immutable — `RoomStore` evolves them by replacement,
13
- * never mutation.
14
- */
15
- interface ChatMessage {
16
- /** The server message id, or the `tempId` while still `sending`. */
17
- id: string;
18
- /** Author user id. `null` means the current user (an optimistic bubble). */
19
- senderId: string | null;
20
- content: string | null;
21
- /** Creation time in unix milliseconds. */
22
- createdAtMs: number | null;
23
- status: ChatMessageStatus;
24
- /** `TEXT | FILE | EMOTICON | ADMIN | SYSTEM` — UIs use this to render
25
- * system/admin lines (centered notices) differently from chat bubbles. */
26
- messageType: string;
27
- /** Sender identity (nickname / profile image) when the server included it.
28
- * Null for optimistic bubbles and live frames (which carry only
29
- * `senderId`) — UIs typically resolve the profile from their member list. */
30
- sender: SenderMini | null;
31
- /** App-defined subtype (e.g. a message-priority convention). */
32
- customType: string | null;
33
- /** Snowflake id (string) of the attached file, when this is a file message. */
34
- fileId: string | null;
35
- /** Inline metadata of the attached file — enough to render a placeholder
36
- * without a round-trip. May be null even when `fileId` is set; fetch via
37
- * `chat.getFileMeta` then. */
38
- file: FileInline | null;
39
- /** Multi-file bundle (`file_group`). Null for single-file / text messages.
40
- * Check `files?.length` first and fall back to the `file` singleton. */
41
- files: FileInline[] | null;
42
- /** Id of the message this one replies to, if any. */
43
- replyToId: string | null;
44
- /** Emoji reactions grouped by key. Kept live by `RoomStore` from the
45
- * reaction WS events. */
46
- reactions: ReactionEntry[];
47
- /** Sender-attached custom metadata (`data._customMeta`). */
48
- customMeta: Record<string, unknown> | null;
49
- /** How many times the message was edited. `> 0` → show an "edited" mark. */
50
- editedCount: number;
51
- /** Upload progress in [0, 1] while an optimistic FILE message's bytes are
52
- * still going up to S3. Null once committed (or for non-file messages). */
53
- uploadProgress: number | null;
54
- /** The optimistic temp id, kept for ack reconciliation. Null once confirmed. */
55
- tempId: string | null;
56
- isDeleted: boolean;
57
- }
58
- /** Build a `ChatMessage` from server history (REST). */
59
- declare function chatMessageFromHistory(m: MessageOut): ChatMessage;
60
- /** Build a `ChatMessage` from a live inbound frame (WS). */
61
- declare function chatMessageFromLive(f: WsChatReceive): ChatMessage;
62
-
63
6
  interface NoveraChatProviderProps {
64
7
  /** Options for the `NoveraChat` this provider creates. Read **once**, when
65
8
  * the provider first mounts — to connect with different options, give the
@@ -380,19 +323,20 @@ interface RoomSnapshot {
380
323
  readWatermarks: Readonly<Record<string, string>>;
381
324
  }
382
325
  /**
383
- * Turns a `Room`'s events into an externally-subscribable message list and
384
- * drives optimistic sends. Framework-agnostic on purpose so it can also be
385
- * used outside hooks (tests, non-React glue).
326
+ * React-side glue over the core `MessageCollection` the message state
327
+ * (event folding, optimistic sends, cache hydrate→replace) is owned by the
328
+ * core; this store bridges the collection's change events into
329
+ * `useSyncExternalStore`-shaped immutable snapshots. Framework-agnostic on
330
+ * purpose so it can also be used outside hooks (tests, non-React glue).
386
331
  *
387
- * Lifecycle: construct → `attach()` (subscribe WS events; idempotent)
388
- * `dispose()` (flush read watermark + unsubscribe). `useMessages` does all
389
- * three for you.
332
+ * Lifecycle: construct → `attach()` (idempotent) `dispose()` (flush read
333
+ * watermark + detach page listeners). `useMessages` does all three for you.
390
334
  *
391
335
  * Holds `(chat, roomId)` rather than a `Room` instance and re-resolves the
392
336
  * live facade on every use — `chat.disconnect()` drops its Room facades, so
393
337
  * a remounted provider (React StrictMode simulates this) would otherwise
394
- * leave the store subscribed to an orphaned Room that no longer receives
395
- * dispatches.
338
+ * leave the store bound to an orphaned Room that no longer receives
339
+ * dispatches. The backing collection is recreated when the facade changes.
396
340
  *
397
341
  * ```ts
398
342
  * const store = new RoomStore(chat, "room_123");
@@ -406,12 +350,8 @@ declare class RoomStore {
406
350
  private readonly chat;
407
351
  readonly roomId: string;
408
352
  private readonly listeners;
409
- private readonly unsubs;
410
- private messages;
411
- private readWatermarks;
412
- private hasMore;
413
- private oldestCursor;
414
- private loadingMore;
353
+ private col;
354
+ private colUnsub;
415
355
  private historyRequested;
416
356
  private snapshot;
417
357
  private attached;
@@ -420,26 +360,32 @@ declare class RoomStore {
420
360
  /** The live `Room` facade — resolved from the client on every access
421
361
  * (get-or-create in a map, cheap) so we never act on an orphan. */
422
362
  private get room();
423
- /** Subscribe to WS events + page lifecycle. Safe to call repeatedly
424
- * (no-op while attached) — matches React 18/19 StrictMode's
363
+ /** The backing collection, bound to the CURRENT facade. Recreated when
364
+ * the facade was dropped (post-`chat.disconnect()`)the old one would
365
+ * never receive dispatches again. */
366
+ private collection;
367
+ /** Subscribe page lifecycle + ensure the collection is live. Safe to call
368
+ * repeatedly (no-op while attached) — matches React 18/19 StrictMode's
425
369
  * mount → cleanup → mount effect cycle. */
426
370
  attach(): void;
427
- /** Unsubscribe everything and flush the pending read watermark. The store
428
- * keeps its message state, so a later `attach()` resumes cleanly. */
371
+ /** Detach page listeners and flush the pending read watermark. The
372
+ * collection (and its state) survives, so a later `attach()` resumes
373
+ * cleanly — StrictMode's mount → cleanup → mount cycle keeps messages. */
429
374
  dispose(): void;
430
375
  /** `useSyncExternalStore`-compatible subscribe. Returns an unsubscribe fn. */
431
376
  readonly subscribe: (fn: () => void) => (() => void);
432
377
  readonly getSnapshot: () => RoomSnapshot;
433
- private emit;
378
+ private rebuild;
434
379
  /** Whether `userId` has read `messageId` according to the latest watermark. */
435
380
  isReadBy(userId: string, messageId: string): boolean;
436
381
  /** How many of the given users have read `messageId`. Pass the room's
437
382
  * member ids (minus the sender) to get a KakaoTalk-style unread count:
438
383
  * `members.length - readCount(...)`. */
439
384
  readCount(messageId: string, userIds: Iterable<string>): number;
440
- /** Load the most recent page of history and prepend it. No-op after the
441
- * first call (StrictMode double-mount safe) — use `loadMore` for
442
- * scrollback. */
385
+ /** Load the most recent page of history. No-op after the first call
386
+ * (StrictMode double-mount safe) — use `loadMore` for scrollback.
387
+ * 캐시(`ClientOptions.cacheStore`)가 켜져 있으면 스냅샷 즉시 렌더 →
388
+ * API 도착 시 통째 교체가 컬렉션 안에서 일어난다. */
443
389
  loadHistory(limit?: number): Promise<void>;
444
390
  /** Scrollback: load the page of messages older than what's on screen and
445
391
  * prepend it. No-op while a previous call is in flight or when `hasMore`
@@ -474,17 +420,7 @@ declare class RoomStore {
474
420
  search(q: string, limit?: number): Promise<ChatMessage[]>;
475
421
  /** Mark `messageId` read (debounced inside the SDK). */
476
422
  markRead(messageId: string): void;
477
- private onIncoming;
478
- private onUpdated;
479
- private onDeleted;
480
- private onCleared;
481
- private onReactionAdded;
482
- private onReactionRemoved;
483
- private onSyncTick;
484
423
  private onVisibilityChange;
485
- private bindAck;
486
- private mutateById;
487
- private mutateByTempId;
488
424
  }
489
425
 
490
426
  interface UseMessagesOptions {
@@ -672,4 +608,4 @@ interface UseUnreadResult {
672
608
  */
673
609
  declare function useUnread(opts?: UseUnreadOptions): UseUnreadResult;
674
610
 
675
- export { type ChatMessage, type ChatMessageStatus, type MemberListSnapshot, MemberListStore, NoveraChatProvider, type NoveraChatProviderProps, type RoomFileItem, type RoomFilesSnapshot, RoomFilesStore, type RoomListItem, type RoomListSnapshot, RoomListStore, type RoomListStoreOptions, type RoomSettingsSnapshot, RoomSettingsStore, type RoomSnapshot, RoomStore, type UseMemberListResult, type UseMessagesOptions, type UseMessagesResult, type UseRoomFilesOptions, type UseRoomFilesResult, type UseRoomListResult, type UseRoomSettingsResult, type UseTypingOptions, type UseTypingResult, type UseUnreadOptions, type UseUnreadResult, chatMessageFromHistory, chatMessageFromLive, useMemberList, useMessages, useNoveraChat, useRoom, useRoomFiles, useRoomList, useRoomSettings, useTyping, useUnread };
611
+ export { type MemberListSnapshot, MemberListStore, NoveraChatProvider, type NoveraChatProviderProps, type RoomFileItem, type RoomFilesSnapshot, RoomFilesStore, type RoomListItem, type RoomListSnapshot, RoomListStore, type RoomListStoreOptions, type RoomSettingsSnapshot, RoomSettingsStore, type RoomSnapshot, RoomStore, type UseMemberListResult, type UseMessagesOptions, type UseMessagesResult, type UseRoomFilesOptions, type UseRoomFilesResult, type UseRoomListResult, type UseRoomSettingsResult, type UseTypingOptions, type UseTypingResult, type UseUnreadOptions, type UseUnreadResult, useMemberList, useMessages, useNoveraChat, useRoom, useRoomFiles, useRoomList, useRoomSettings, useTyping, useUnread };