@kispi/chat 0.1.2 → 0.2.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
@@ -75,6 +75,11 @@ type RestOptions = {
75
75
  key: string;
76
76
  /** Returns the current user JWT, or undefined before the first connect. */
77
77
  token: () => Promise<string | undefined>;
78
+ /**
79
+ * Fetches a fresh token after a 401, or undefined when there is none to
80
+ * fetch. Optional so a Rest built without it behaves as before.
81
+ */
82
+ refreshToken?: () => Promise<string | undefined>;
78
83
  fetch: typeof globalThis.fetch;
79
84
  };
80
85
  declare class Rest {
@@ -86,6 +91,7 @@ declare class Rest {
86
91
  put<T>(path: string, body?: unknown): Promise<T>;
87
92
  delete<T>(path: string): Promise<T>;
88
93
  private call;
94
+ private callWith;
89
95
  }
90
96
 
91
97
  /** A message as the server renders it. */
@@ -197,7 +203,12 @@ type RoomEvents = {
197
203
  'room.updated': any;
198
204
  'room.deleted': any;
199
205
  custom: any;
200
- /** The room's message list changed, for any reason. */
206
+ /**
207
+ * The room's message list changed, for any reason.
208
+ *
209
+ * **A new array every time**, and one that is never mutated after it is
210
+ * emitted, so it can go straight into state that compares by reference.
211
+ */
201
212
  messages: Message[];
202
213
  };
203
214
  declare class Room extends Emitter<RoomEvents> {
@@ -245,8 +256,27 @@ declare class Room extends Emitter<RoomEvents> {
245
256
  id?: string;
246
257
  key?: string;
247
258
  });
248
- /** Everything this client knows about the room, in seq order. */
259
+ /** Everything this client knows about the room, in seq order. A new array whenever it changes. */
249
260
  get messages(): Message[];
261
+ /**
262
+ * Reads the page just before the oldest message held and **prepends it to
263
+ * `messages`**.
264
+ *
265
+ * The scroll-back call. Unlike `history()`, the rows join the room's own
266
+ * list, so ordering, de-duplication, deletes, reactions and reconnects
267
+ * apply to them like any other row, and the `messages` event fires once.
268
+ * A `reset` (or `reload()`) drops them with everything else; a read that
269
+ * lands after one is discarded rather than stitched onto the new list.
270
+ *
271
+ * `hasMore` is false once the top of the room -- or of its retention --
272
+ * is reached. Concurrent calls share one request.
273
+ */
274
+ loadOlder(options?: {
275
+ limit?: number;
276
+ }): Promise<{
277
+ messages: Message[];
278
+ hasMore: boolean;
279
+ }>;
250
280
  /**
251
281
  * Reloads recent history, discarding what is held.
252
282
  *
@@ -277,7 +307,8 @@ declare class Room extends Emitter<RoomEvents> {
277
307
  *
278
308
  * The list this room keeps is the live one; this is for a consumer
279
309
  * scrolling back, which owns its own window and does not want the
280
- * bottom of the room rearranged under it.
310
+ * bottom of the room rearranged under it. **Scrolling the room's own
311
+ * list back is `loadOlder()`**, which keeps one list instead of two.
281
312
  *
282
313
  * `view` defaults to the server's, which is every message including
283
314
  * thread replies. **`view: 'main'` is a display filter, not a sync
@@ -379,6 +410,16 @@ declare class Room extends Emitter<RoomEvents> {
379
410
  * mistake.
380
411
  */
381
412
  private requireId;
413
+ /**
414
+ * The room's id, waiting for a subscribe in flight to learn it.
415
+ *
416
+ * `roomByKey(k).subscribe()`를 await하지 않고 곧바로 `send`하는 것은 자연스러운
417
+ * 코드이고, 그때 id는 구독 ack가 와야 생긴다. 거절(`closed`)하면 소비자는
418
+ * "보내도 되는 때"를 알릴 신호를 따로 찾아야 한다 — 이미 날아가고 있는
419
+ * 구독을 기다리면 그 신호가 필요 없다. 구독이 실패하면 그 실패가 그대로
420
+ * 나간다. 구독한 적이 없으면 기다릴 것이 없으니 예전처럼 거절한다.
421
+ */
422
+ private resolveId;
382
423
  /** Publishes and resolves when the server acks. */
383
424
  send(input: SendInput, options?: SendOptions): Promise<PublishAck>;
384
425
  /** Routes a frame the client decided belongs to this room. */
@@ -430,9 +471,13 @@ type ChatClientOptions = {
430
471
  * has no way to decide who anybody is on its own, and the modes that
431
472
  * pretended otherwise were removed.
432
473
  *
433
- * Called again on every connect, so an expired token is replaced
434
- * rather than reused. **Never sign these in the browser** -- signing
435
- * needs the `sk_`, and a `sk_` in a browser is the whole app.
474
+ * Called again on every connect, and again when a REST call is
475
+ * answered 401 (the call is then retried once), so an expired token is
476
+ * replaced rather than reused. A throw here is retried with backoff
477
+ * like a dropped socket; only `close()` stops that.
478
+ *
479
+ * **Never sign these in the browser** -- signing needs the `sk_`, and
480
+ * a `sk_` in a browser is the whole app.
436
481
  */
437
482
  token: () => string | Promise<string>;
438
483
  /** external mode. **The server refuses this today** -- the verification half is unbuilt. */
@@ -639,6 +684,16 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
639
684
  */
640
685
  private nextDelay;
641
686
  private authData;
687
+ /**
688
+ * REST가 401을 받았을 때 토큰을 새로 받는다.
689
+ *
690
+ * 소켓은 접속할 때 한 번 인증하고 그 뒤로는 토큰을 다시 보지 않지만, REST는
691
+ * 요청마다 본다. 그래서 한 시간짜리 토큰이면 한 시간 뒤 라이브는 멀쩡한데
692
+ * 히스토리·구멍 메우기만 401이 된다. 동시에 실패한 요청들이 `token()`을
693
+ * 각자 부르지 않도록 진행 중인 것 하나를 나눠 쓴다.
694
+ */
695
+ private refreshToken;
696
+ private refreshing;
642
697
  private failPending;
643
698
  private clearHeartbeat;
644
699
  private clearTimers;
@@ -659,6 +714,8 @@ declare class ChatError extends Error {
659
714
  readonly retryAfterMs?: number;
660
715
  /** The consumer's own code, when a before_publish hook denied this. */
661
716
  readonly appCode?: string;
717
+ /** The HTTP status, when this came from a REST call. */
718
+ status?: number;
662
719
  constructor(code: string, message: string, extra?: {
663
720
  retryAfterMs?: number;
664
721
  appCode?: string;
@@ -724,7 +781,20 @@ declare class Timeline {
724
781
  * rows into the new list.
725
782
  */
726
783
  private epoch;
784
+ /**
785
+ * `items` has been handed out -- by the getter or by `onChange`.
786
+ *
787
+ * 한 번 내준 배열은 다시 건드리지 않는다. 같은 배열을 제자리에서 고쳐
788
+ * 다시 내주면 Svelte `$state.raw`, React `useState`, Vue `shallowRef`처럼
789
+ * 참조가 바뀌어야 다시 그리는 쪽은 변화를 보지 못하고 화면이 멈춘다. 그래서
790
+ * 내준 뒤의 첫 변경은 복사본에서 한다(copy-on-write). 행 객체도 같은 규칙이라
791
+ * 바뀐 행만 새 객체다.
792
+ */
793
+ private shared;
794
+ /** The `loadOlder` in flight, so a scroll handler firing twice asks once. */
795
+ private older;
727
796
  constructor(options: TimelineOptions);
797
+ /** A new array whenever the list changes; never mutated once returned. */
728
798
  get messages(): Message[];
729
799
  /** The highest seq this timeline holds, hole or no hole. */
730
800
  get highestSeq(): number;
@@ -807,6 +877,24 @@ declare class Timeline {
807
877
  thread(rootId: string, count: number, lastSeq?: number): void;
808
878
  /** Applies a `message.deleted`, remembering it if the row is not here. */
809
879
  remove(id: string): void;
880
+ /**
881
+ * Prepends the page just below the oldest message held.
882
+ *
883
+ * 스크롤을 올려 과거를 읽는 소비자가 `history()` 결과를 라이브 목록과 따로
884
+ * 들고 합치고, 중복을 걸러 내고, reset과 재접속을 따로 처리해야 했다. 같은
885
+ * 목록에 끼워 넣으면 그 일이 전부 이미 있는 규칙(seq 자리에 넣기, 같은 seq는
886
+ * 한 행, reset이면 epoch로 버리기)으로 끝난다.
887
+ *
888
+ * `hasMore`는 더 올라갈 것이 있는지다. 서버의 seq는 1부터 구멍 없이 붙으므로
889
+ * 맨 위가 1이거나 페이지가 덜 찼으면 끝이다(보존 기간이 지운 앞부분도 덜 찬
890
+ * 페이지로 드러난다). 한 번에 하나만 돈다 — 스크롤 핸들러가 두 번 불러도
891
+ * 요청은 하나다(진행 중인 호출과 같은 결과를 받으므로 뒤 호출의 `limit`은 쓰이지 않는다).
892
+ */
893
+ loadOlder(limit?: number): Promise<{
894
+ messages: Message[];
895
+ hasMore: boolean;
896
+ }>;
897
+ private readOlder;
810
898
  /**
811
899
  * Fetches everything between what we have and `upTo`.
812
900
  *
@@ -827,6 +915,8 @@ declare class Timeline {
827
915
  /** Inserts at the seq position, replacing an existing row with that seq. */
828
916
  private insert;
829
917
  private applyDelete;
918
+ /** Makes `items` safe to mutate: a copy, if the current one was handed out. */
919
+ private own;
830
920
  private changed;
831
921
  }
832
922
  declare function createTimeline(options: TimelineOptions): Timeline;
package/dist/index.d.ts CHANGED
@@ -75,6 +75,11 @@ type RestOptions = {
75
75
  key: string;
76
76
  /** Returns the current user JWT, or undefined before the first connect. */
77
77
  token: () => Promise<string | undefined>;
78
+ /**
79
+ * Fetches a fresh token after a 401, or undefined when there is none to
80
+ * fetch. Optional so a Rest built without it behaves as before.
81
+ */
82
+ refreshToken?: () => Promise<string | undefined>;
78
83
  fetch: typeof globalThis.fetch;
79
84
  };
80
85
  declare class Rest {
@@ -86,6 +91,7 @@ declare class Rest {
86
91
  put<T>(path: string, body?: unknown): Promise<T>;
87
92
  delete<T>(path: string): Promise<T>;
88
93
  private call;
94
+ private callWith;
89
95
  }
90
96
 
91
97
  /** A message as the server renders it. */
@@ -197,7 +203,12 @@ type RoomEvents = {
197
203
  'room.updated': any;
198
204
  'room.deleted': any;
199
205
  custom: any;
200
- /** The room's message list changed, for any reason. */
206
+ /**
207
+ * The room's message list changed, for any reason.
208
+ *
209
+ * **A new array every time**, and one that is never mutated after it is
210
+ * emitted, so it can go straight into state that compares by reference.
211
+ */
201
212
  messages: Message[];
202
213
  };
203
214
  declare class Room extends Emitter<RoomEvents> {
@@ -245,8 +256,27 @@ declare class Room extends Emitter<RoomEvents> {
245
256
  id?: string;
246
257
  key?: string;
247
258
  });
248
- /** Everything this client knows about the room, in seq order. */
259
+ /** Everything this client knows about the room, in seq order. A new array whenever it changes. */
249
260
  get messages(): Message[];
261
+ /**
262
+ * Reads the page just before the oldest message held and **prepends it to
263
+ * `messages`**.
264
+ *
265
+ * The scroll-back call. Unlike `history()`, the rows join the room's own
266
+ * list, so ordering, de-duplication, deletes, reactions and reconnects
267
+ * apply to them like any other row, and the `messages` event fires once.
268
+ * A `reset` (or `reload()`) drops them with everything else; a read that
269
+ * lands after one is discarded rather than stitched onto the new list.
270
+ *
271
+ * `hasMore` is false once the top of the room -- or of its retention --
272
+ * is reached. Concurrent calls share one request.
273
+ */
274
+ loadOlder(options?: {
275
+ limit?: number;
276
+ }): Promise<{
277
+ messages: Message[];
278
+ hasMore: boolean;
279
+ }>;
250
280
  /**
251
281
  * Reloads recent history, discarding what is held.
252
282
  *
@@ -277,7 +307,8 @@ declare class Room extends Emitter<RoomEvents> {
277
307
  *
278
308
  * The list this room keeps is the live one; this is for a consumer
279
309
  * scrolling back, which owns its own window and does not want the
280
- * bottom of the room rearranged under it.
310
+ * bottom of the room rearranged under it. **Scrolling the room's own
311
+ * list back is `loadOlder()`**, which keeps one list instead of two.
281
312
  *
282
313
  * `view` defaults to the server's, which is every message including
283
314
  * thread replies. **`view: 'main'` is a display filter, not a sync
@@ -379,6 +410,16 @@ declare class Room extends Emitter<RoomEvents> {
379
410
  * mistake.
380
411
  */
381
412
  private requireId;
413
+ /**
414
+ * The room's id, waiting for a subscribe in flight to learn it.
415
+ *
416
+ * `roomByKey(k).subscribe()`를 await하지 않고 곧바로 `send`하는 것은 자연스러운
417
+ * 코드이고, 그때 id는 구독 ack가 와야 생긴다. 거절(`closed`)하면 소비자는
418
+ * "보내도 되는 때"를 알릴 신호를 따로 찾아야 한다 — 이미 날아가고 있는
419
+ * 구독을 기다리면 그 신호가 필요 없다. 구독이 실패하면 그 실패가 그대로
420
+ * 나간다. 구독한 적이 없으면 기다릴 것이 없으니 예전처럼 거절한다.
421
+ */
422
+ private resolveId;
382
423
  /** Publishes and resolves when the server acks. */
383
424
  send(input: SendInput, options?: SendOptions): Promise<PublishAck>;
384
425
  /** Routes a frame the client decided belongs to this room. */
@@ -430,9 +471,13 @@ type ChatClientOptions = {
430
471
  * has no way to decide who anybody is on its own, and the modes that
431
472
  * pretended otherwise were removed.
432
473
  *
433
- * Called again on every connect, so an expired token is replaced
434
- * rather than reused. **Never sign these in the browser** -- signing
435
- * needs the `sk_`, and a `sk_` in a browser is the whole app.
474
+ * Called again on every connect, and again when a REST call is
475
+ * answered 401 (the call is then retried once), so an expired token is
476
+ * replaced rather than reused. A throw here is retried with backoff
477
+ * like a dropped socket; only `close()` stops that.
478
+ *
479
+ * **Never sign these in the browser** -- signing needs the `sk_`, and
480
+ * a `sk_` in a browser is the whole app.
436
481
  */
437
482
  token: () => string | Promise<string>;
438
483
  /** external mode. **The server refuses this today** -- the verification half is unbuilt. */
@@ -639,6 +684,16 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
639
684
  */
640
685
  private nextDelay;
641
686
  private authData;
687
+ /**
688
+ * REST가 401을 받았을 때 토큰을 새로 받는다.
689
+ *
690
+ * 소켓은 접속할 때 한 번 인증하고 그 뒤로는 토큰을 다시 보지 않지만, REST는
691
+ * 요청마다 본다. 그래서 한 시간짜리 토큰이면 한 시간 뒤 라이브는 멀쩡한데
692
+ * 히스토리·구멍 메우기만 401이 된다. 동시에 실패한 요청들이 `token()`을
693
+ * 각자 부르지 않도록 진행 중인 것 하나를 나눠 쓴다.
694
+ */
695
+ private refreshToken;
696
+ private refreshing;
642
697
  private failPending;
643
698
  private clearHeartbeat;
644
699
  private clearTimers;
@@ -659,6 +714,8 @@ declare class ChatError extends Error {
659
714
  readonly retryAfterMs?: number;
660
715
  /** The consumer's own code, when a before_publish hook denied this. */
661
716
  readonly appCode?: string;
717
+ /** The HTTP status, when this came from a REST call. */
718
+ status?: number;
662
719
  constructor(code: string, message: string, extra?: {
663
720
  retryAfterMs?: number;
664
721
  appCode?: string;
@@ -724,7 +781,20 @@ declare class Timeline {
724
781
  * rows into the new list.
725
782
  */
726
783
  private epoch;
784
+ /**
785
+ * `items` has been handed out -- by the getter or by `onChange`.
786
+ *
787
+ * 한 번 내준 배열은 다시 건드리지 않는다. 같은 배열을 제자리에서 고쳐
788
+ * 다시 내주면 Svelte `$state.raw`, React `useState`, Vue `shallowRef`처럼
789
+ * 참조가 바뀌어야 다시 그리는 쪽은 변화를 보지 못하고 화면이 멈춘다. 그래서
790
+ * 내준 뒤의 첫 변경은 복사본에서 한다(copy-on-write). 행 객체도 같은 규칙이라
791
+ * 바뀐 행만 새 객체다.
792
+ */
793
+ private shared;
794
+ /** The `loadOlder` in flight, so a scroll handler firing twice asks once. */
795
+ private older;
727
796
  constructor(options: TimelineOptions);
797
+ /** A new array whenever the list changes; never mutated once returned. */
728
798
  get messages(): Message[];
729
799
  /** The highest seq this timeline holds, hole or no hole. */
730
800
  get highestSeq(): number;
@@ -807,6 +877,24 @@ declare class Timeline {
807
877
  thread(rootId: string, count: number, lastSeq?: number): void;
808
878
  /** Applies a `message.deleted`, remembering it if the row is not here. */
809
879
  remove(id: string): void;
880
+ /**
881
+ * Prepends the page just below the oldest message held.
882
+ *
883
+ * 스크롤을 올려 과거를 읽는 소비자가 `history()` 결과를 라이브 목록과 따로
884
+ * 들고 합치고, 중복을 걸러 내고, reset과 재접속을 따로 처리해야 했다. 같은
885
+ * 목록에 끼워 넣으면 그 일이 전부 이미 있는 규칙(seq 자리에 넣기, 같은 seq는
886
+ * 한 행, reset이면 epoch로 버리기)으로 끝난다.
887
+ *
888
+ * `hasMore`는 더 올라갈 것이 있는지다. 서버의 seq는 1부터 구멍 없이 붙으므로
889
+ * 맨 위가 1이거나 페이지가 덜 찼으면 끝이다(보존 기간이 지운 앞부분도 덜 찬
890
+ * 페이지로 드러난다). 한 번에 하나만 돈다 — 스크롤 핸들러가 두 번 불러도
891
+ * 요청은 하나다(진행 중인 호출과 같은 결과를 받으므로 뒤 호출의 `limit`은 쓰이지 않는다).
892
+ */
893
+ loadOlder(limit?: number): Promise<{
894
+ messages: Message[];
895
+ hasMore: boolean;
896
+ }>;
897
+ private readOlder;
810
898
  /**
811
899
  * Fetches everything between what we have and `upTo`.
812
900
  *
@@ -827,6 +915,8 @@ declare class Timeline {
827
915
  /** Inserts at the seq position, replacing an existing row with that seq. */
828
916
  private insert;
829
917
  private applyDelete;
918
+ /** Makes `items` safe to mutate: a copy, if the current one was handed out. */
919
+ private own;
830
920
  private changed;
831
921
  }
832
922
  declare function createTimeline(options: TimelineOptions): Timeline;