@anakin824/prdg-chat-ui 0.3.1 → 0.3.2

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.js CHANGED
@@ -227,74 +227,146 @@ function userInboxSubject(natsTenantId, userId) {
227
227
  function entityConversationSubject(natsTenantId, conversationId) {
228
228
  return `chat.${natsTenantId}.conversation.${conversationId}`;
229
229
  }
230
+ function parseMessage(raw) {
231
+ if (typeof raw.id !== "string" || typeof raw.conversation_id !== "string") {
232
+ return null;
233
+ }
234
+ return {
235
+ id: raw.id,
236
+ conversation_id: raw.conversation_id,
237
+ sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
238
+ body: typeof raw.body === "string" ? raw.body : null,
239
+ created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
240
+ edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
241
+ deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
242
+ attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
243
+ mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
244
+ };
245
+ }
246
+ function getMessagePageSummary(queryClient, conversationId) {
247
+ const page = queryClient.getQueryData(chatKeys.messages(conversationId));
248
+ const lastItem = page && page.items.length > 0 ? page.items[page.items.length - 1] : null;
249
+ return {
250
+ count: page?.items.length ?? 0,
251
+ firstId: page?.items[0]?.id ?? null,
252
+ lastId: lastItem?.id ?? null
253
+ };
254
+ }
230
255
  function pushMessageToCache(queryClient, msg) {
231
- let inserted = false;
232
- queryClient.setQueryData(
233
- chatKeys.messages(msg.conversation_id),
234
- (prev) => {
235
- if (!prev) return void 0;
236
- if (prev.items.some((m) => m.id === msg.id)) return prev;
237
- inserted = true;
238
- return { ...prev, items: [msg, ...prev.items] };
256
+ let result = "missing-cache";
257
+ queryClient.setQueryData(chatKeys.messages(msg.conversation_id), (prev) => {
258
+ if (!prev) return void 0;
259
+ if (prev.items.some((m) => m.id === msg.id)) {
260
+ result = "duplicate";
261
+ return prev;
239
262
  }
240
- );
241
- return inserted;
263
+ result = "updated";
264
+ return { ...prev, items: [msg, ...prev.items] };
265
+ });
266
+ return result;
242
267
  }
243
268
  function markDeletedInCache(queryClient, conversationId, messageId, deletedAt) {
269
+ let result = "missing-cache";
244
270
  queryClient.setQueryData(
245
271
  chatKeys.messages(conversationId),
246
272
  (prev) => {
247
273
  if (!prev) return void 0;
248
274
  const idx = prev.items.findIndex((m) => m.id === messageId);
249
- if (idx === -1) return prev;
275
+ if (idx === -1) {
276
+ result = "missing-message";
277
+ return prev;
278
+ }
250
279
  const updated = [...prev.items];
251
280
  updated[idx] = { ...updated[idx], deleted_at: deletedAt };
281
+ result = "updated";
252
282
  return { ...prev, items: updated };
253
283
  }
254
284
  );
285
+ return result;
255
286
  }
256
287
  function applyEditInCache(queryClient, updated) {
288
+ let result = "missing-cache";
257
289
  queryClient.setQueryData(
258
290
  chatKeys.messages(updated.conversation_id),
259
291
  (prev) => {
260
292
  if (!prev) return void 0;
261
293
  const idx = prev.items.findIndex((m) => m.id === updated.id);
262
- if (idx === -1) return prev;
294
+ if (idx === -1) {
295
+ result = "missing-message";
296
+ return prev;
297
+ }
263
298
  const items = [...prev.items];
264
299
  items[idx] = { ...items[idx], ...updated };
300
+ result = "updated";
265
301
  return { ...prev, items };
266
302
  }
267
303
  );
304
+ return result;
268
305
  }
269
- function invalidateQueriesFromNatsPayload(payload, queryClient) {
270
- if (!payload || typeof payload !== "object") return;
306
+ function invalidateQueriesFromNatsPayload(payload, queryClient, debug = false) {
307
+ if (!payload || typeof payload !== "object") {
308
+ chatDebugWarn(debug, "NATS cache update ignored: payload was not an object", payload);
309
+ return;
310
+ }
271
311
  const p = payload;
272
312
  const eventType = p.type;
273
- if (typeof eventType !== "string") return;
313
+ if (typeof eventType !== "string") {
314
+ chatDebugWarn(debug, "NATS cache update ignored: payload.type missing", p);
315
+ return;
316
+ }
274
317
  const ed = p.event_data;
275
- if (!ed || typeof ed !== "object") return;
318
+ if (!ed || typeof ed !== "object") {
319
+ chatDebugWarn(debug, "NATS cache update ignored: payload.event_data missing", {
320
+ eventType,
321
+ payload: p
322
+ });
323
+ return;
324
+ }
276
325
  const eventData = ed;
326
+ const conversationId = typeof eventData.conversation_id === "string" ? eventData.conversation_id : null;
327
+ chatDebugLog(debug, "NATS cache update received", {
328
+ eventType,
329
+ conversationId,
330
+ hasMessage: !!eventData.message && typeof eventData.message === "object"
331
+ });
277
332
  if (eventType === "chat.message.new") {
278
333
  const convId = eventData.conversation_id;
279
- if (typeof convId !== "string" || !convId.trim()) return;
334
+ if (typeof convId !== "string" || !convId.trim()) {
335
+ chatDebugWarn(debug, "NATS message.new ignored: missing conversation_id", eventData);
336
+ return;
337
+ }
338
+ const before = getMessagePageSummary(queryClient, convId);
280
339
  const msgRaw = eventData.message;
281
340
  if (msgRaw && typeof msgRaw === "object") {
282
341
  const raw = msgRaw;
283
- if (typeof raw.id === "string" && typeof raw.conversation_id === "string") {
284
- const msg = {
285
- id: raw.id,
286
- conversation_id: raw.conversation_id,
287
- sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
288
- body: typeof raw.body === "string" ? raw.body : null,
289
- created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
290
- edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
291
- deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
292
- attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
293
- mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
294
- };
295
- pushMessageToCache(queryClient, msg);
342
+ const msg = parseMessage(raw);
343
+ if (msg) {
344
+ const mutation = pushMessageToCache(queryClient, msg);
345
+ const after = getMessagePageSummary(queryClient, convId);
346
+ chatDebugLog(debug, "NATS message.new cache mutation", {
347
+ conversationId: convId,
348
+ messageId: msg.id,
349
+ mutation,
350
+ before,
351
+ after
352
+ });
353
+ } else {
354
+ chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing required ids", {
355
+ conversationId: convId,
356
+ message: raw
357
+ });
296
358
  }
359
+ } else {
360
+ chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing or not an object", {
361
+ conversationId: convId,
362
+ eventData
363
+ });
297
364
  }
365
+ chatDebugLog(debug, "NATS message.new invalidating queries", {
366
+ conversationId: convId,
367
+ messageQueryKey: chatKeys.messages(convId),
368
+ conversationsQueryKey: chatKeys.conversations()
369
+ });
298
370
  void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
299
371
  void queryClient.invalidateQueries({ queryKey: chatKeys.conversations() });
300
372
  return;
@@ -303,21 +375,20 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
303
375
  const msgRaw = eventData.message;
304
376
  if (msgRaw && typeof msgRaw === "object") {
305
377
  const raw = msgRaw;
306
- if (typeof raw.id === "string" && typeof raw.conversation_id === "string") {
307
- const updated = {
308
- id: raw.id,
309
- conversation_id: raw.conversation_id,
310
- sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
311
- body: typeof raw.body === "string" ? raw.body : null,
312
- created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
313
- edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
314
- deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
315
- attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
316
- mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
317
- };
318
- applyEditInCache(queryClient, updated);
378
+ const updated = parseMessage(raw);
379
+ if (updated) {
380
+ const mutation = applyEditInCache(queryClient, updated);
381
+ chatDebugLog(debug, "NATS message.edited cache mutation", {
382
+ conversationId: updated.conversation_id,
383
+ messageId: updated.id,
384
+ mutation
385
+ });
319
386
  void queryClient.invalidateQueries({ queryKey: chatKeys.messages(updated.conversation_id) });
387
+ } else {
388
+ chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing required ids", raw);
320
389
  }
390
+ } else {
391
+ chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing or not an object", eventData);
321
392
  }
322
393
  return;
323
394
  }
@@ -329,16 +400,27 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
329
400
  const convId = raw.conversation_id;
330
401
  const deletedAt = typeof raw.deleted_at === "string" ? raw.deleted_at : (/* @__PURE__ */ new Date()).toISOString();
331
402
  if (typeof msgId === "string" && typeof convId === "string") {
332
- markDeletedInCache(queryClient, convId, msgId, deletedAt);
403
+ const mutation = markDeletedInCache(queryClient, convId, msgId, deletedAt);
404
+ chatDebugLog(debug, "NATS message.deleted cache mutation", {
405
+ conversationId: convId,
406
+ messageId: msgId,
407
+ mutation
408
+ });
333
409
  void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
410
+ } else {
411
+ chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing required ids", raw);
334
412
  }
413
+ } else {
414
+ chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing or not an object", eventData);
335
415
  }
416
+ return;
336
417
  }
418
+ chatDebugLog(debug, "NATS payload ignored: unsupported event type", { eventType, eventData });
337
419
  }
338
420
 
339
421
  // src/chat/provider/ChatNatsBridge.tsx
340
422
  function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
341
- const { natsTenantId, userId, queryClient } = useChat();
423
+ const { natsTenantId, userId, queryClient, debug } = useChat();
342
424
  const { data: convData } = useConversations();
343
425
  const entityConversationIds = react.useMemo(() => {
344
426
  const items = convData?.items ?? [];
@@ -351,6 +433,12 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
351
433
  const connRef = react.useRef(null);
352
434
  react.useEffect(() => {
353
435
  if (servers.length === 0) return;
436
+ chatDebugLog(debug, "ChatNatsBridge: starting subscriptions", {
437
+ userId,
438
+ natsTenantId,
439
+ servers,
440
+ entityConversationIds
441
+ });
354
442
  let cancelled = false;
355
443
  void (async () => {
356
444
  try {
@@ -369,10 +457,21 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
369
457
  }
370
458
  connRef.current = conn;
371
459
  onConnectedChangeRef.current(true);
460
+ chatDebugLog(debug, "ChatNatsBridge: NATS connected", {
461
+ userId,
462
+ natsTenantId,
463
+ servers
464
+ });
372
465
  const handleMsg = (data, subject) => {
466
+ const payloadText = new TextDecoder().decode(data);
373
467
  if (process.env.NODE_ENV === "development") {
374
468
  console.debug("[NATS] raw message received on subject:", subject, "bytes:", data.length);
375
469
  }
470
+ chatDebugLog(debug, "ChatNatsBridge: NATS payload received", {
471
+ subject,
472
+ bytes: data.length,
473
+ payloadText
474
+ });
376
475
  let payload;
377
476
  try {
378
477
  payload = jc.decode(data);
@@ -385,7 +484,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
385
484
  if (process.env.NODE_ENV === "development") {
386
485
  console.debug("[NATS] decoded payload:", payload);
387
486
  }
388
- invalidateQueriesFromNatsPayload(payload, queryClient);
487
+ invalidateQueriesFromNatsPayload(payload, queryClient, debug);
389
488
  if (process.env.NODE_ENV === "development") {
390
489
  if (payload && typeof payload === "object" && payload.type === "chat.message.new") {
391
490
  const p = payload;
@@ -423,6 +522,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
423
522
  }
424
523
  };
425
524
  const inbox = userInboxSubject(natsTenantId, userId);
525
+ chatDebugLog(debug, "ChatNatsBridge: subscribing inbox", { inbox });
426
526
  const subInbox = conn.subscribe(inbox);
427
527
  void (async () => {
428
528
  for await (const m of subInbox) {
@@ -432,6 +532,10 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
432
532
  })();
433
533
  for (const convId of entityConversationIds) {
434
534
  const subj = entityConversationSubject(natsTenantId, convId);
535
+ chatDebugLog(debug, "ChatNatsBridge: subscribing entity conversation", {
536
+ conversationId: convId,
537
+ subject: subj
538
+ });
435
539
  const sub = conn.subscribe(subj);
436
540
  void (async () => {
437
541
  for await (const m of sub) {
@@ -455,23 +559,26 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
455
559
  }
456
560
  await conn.closed();
457
561
  connRef.current = null;
562
+ chatDebugWarn(debug, "ChatNatsBridge: NATS connection closed");
458
563
  if (!cancelled) onConnectedChangeRef.current(false);
459
564
  } catch (err) {
460
565
  connRef.current = null;
461
566
  if (process.env.NODE_ENV === "development") {
462
567
  console.error("[NATS] WebSocket connect failed \u2014 check ws:// URL, NATS websocket block, and CORS/mixed content:", err);
463
568
  }
569
+ chatDebugWarn(debug, "ChatNatsBridge: NATS connection failed", err);
464
570
  if (!cancelled) onConnectedChangeRef.current(false);
465
571
  }
466
572
  })();
467
573
  return () => {
468
574
  cancelled = true;
575
+ chatDebugLog(debug, "ChatNatsBridge: cleaning up subscriptions");
469
576
  onConnectedChangeRef.current(false);
470
577
  const c = connRef.current;
471
578
  connRef.current = null;
472
579
  void c?.drain();
473
580
  };
474
- }, [servers, natsToken, natsTenantId, userId, entityKey, queryClient, entityConversationIds]);
581
+ }, [servers, natsToken, natsTenantId, userId, entityKey, queryClient, entityConversationIds, debug]);
475
582
  return null;
476
583
  }
477
584
 
@@ -847,16 +954,28 @@ function appendOlderMessages(current, older) {
847
954
  // src/chat/hooks/useMessages.ts
848
955
  var INITIAL_MESSAGE_PAGE_SIZE = 30;
849
956
  function useMessages(conversationId) {
850
- const { api, wsConnected, config, queryClient } = useChat();
957
+ const { api, wsConnected, config, queryClient, debug } = useChat();
851
958
  const interval = config.pollIntervalMs ?? 3e4;
852
959
  const [isFetchingOlder, setIsFetchingOlder] = react.useState(false);
853
960
  const fetchingRef = react.useRef(false);
854
961
  const query = reactQuery.useQuery({
855
962
  queryKey: chatKeys.messages(conversationId),
856
963
  queryFn: async () => {
964
+ chatDebugLog(debug, "useMessages: queryFn start", {
965
+ conversationId,
966
+ wsConnected
967
+ });
857
968
  const res = await api.listMessages(conversationId, void 0, INITIAL_MESSAGE_PAGE_SIZE);
858
969
  const prev = queryClient.getQueryData(chatKeys.messages(conversationId));
859
- return mergeMessagePages(res, prev);
970
+ const merged = mergeMessagePages(res, prev);
971
+ chatDebugLog(debug, "useMessages: queryFn success", {
972
+ conversationId,
973
+ serverCount: res.items.length,
974
+ previousCachedCount: prev?.items.length ?? 0,
975
+ mergedCount: merged.items.length,
976
+ firstMessageId: merged.items[0]?.id ?? null
977
+ });
978
+ return merged;
860
979
  },
861
980
  enabled: !!conversationId,
862
981
  refetchInterval: wsConnected ? false : interval
@@ -880,6 +999,29 @@ function useMessages(conversationId) {
880
999
  }, [conversationId, queryClient, api]);
881
1000
  const cached = queryClient.getQueryData(chatKeys.messages(conversationId));
882
1001
  const hasMoreMessages = !!cached?.next_cursor;
1002
+ react.useEffect(() => {
1003
+ if (!conversationId) return;
1004
+ chatDebugLog(debug, "useMessages: hook snapshot", {
1005
+ conversationId,
1006
+ wsConnected,
1007
+ status: query.status,
1008
+ fetchStatus: query.fetchStatus,
1009
+ dataCount: query.data?.items.length ?? 0,
1010
+ cachedCount: cached?.items.length ?? 0,
1011
+ firstDataMessageId: query.data?.items[0]?.id ?? null,
1012
+ firstCachedMessageId: cached?.items[0]?.id ?? null
1013
+ });
1014
+ }, [
1015
+ cached?.items.length,
1016
+ cached?.items[0]?.id,
1017
+ conversationId,
1018
+ debug,
1019
+ query.data?.items.length,
1020
+ query.data?.items[0]?.id,
1021
+ query.fetchStatus,
1022
+ query.status,
1023
+ wsConnected
1024
+ ]);
883
1025
  return {
884
1026
  ...query,
885
1027
  fetchOlderMessages,