@ttt-productions/chat-core 0.11.3 → 0.11.4

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.
@@ -1,4 +1,4 @@
1
- import type { ChatMessageGroupKey, ChatMessageV1 } from "./types.js";
1
+ import type { ChatMessageGroupKey, ChatMessageV1 } from './types.js';
2
2
  /**
3
3
  * Determine if `msg` is a "continuation" of `prev` for the purpose of
4
4
  * visually grouping consecutive messages from the same sender in the
package/dist/grouping.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GROUP_GAP_SEC } from "./types.js";
1
+ import { GROUP_GAP_SEC } from './types.js';
2
2
  /**
3
3
  * Determine if `msg` is a "continuation" of `prev` for the purpose of
4
4
  * visually grouping consecutive messages from the same sender in the
@@ -1 +1 @@
1
- {"version":3,"file":"grouping.js","sourceRoot":"","sources":["../src/grouping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA+B,EAC/B,GAAkB,EAClB,QAA8B;IAE9B,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7C,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,aAAa,GAAG,IAAI,CAAC;AACrD,CAAC","sourcesContent":["import type { ChatMessageGroupKey, ChatMessageV1 } from \"./types.js\";\nimport { GROUP_GAP_SEC } from \"./types.js\";\n\n/**\n * Determine if `msg` is a \"continuation\" of `prev` for the purpose of\n * visually grouping consecutive messages from the same sender in the\n * message list.\n *\n * A message is a continuation when ALL of the following hold:\n * - `prev` exists.\n * - Neither `prev` nor `msg` is a system message.\n * - `prev` and `msg` have the same `senderId`.\n * - `msg.createdAt - prev.createdAt` is in the inclusive range\n * `[0, GROUP_GAP_SEC * 1000]` milliseconds. Negative gaps\n * (out-of-order messages) are treated as not-a-continuation.\n * - When the app supplies `groupKey`, both messages have the same key — a\n * continuation hides the sender line, so a per-message fact the app shows\n * there must break the group when it changes.\n *\n * Pure function. No side effects. Safe to call from server and client.\n */\nexport function isContinuation(\n prev: ChatMessageV1 | undefined,\n msg: ChatMessageV1,\n groupKey?: ChatMessageGroupKey,\n): boolean {\n if (!prev) return false;\n if (msg.isSystemMessage || prev.isSystemMessage) return false;\n if (msg.senderId !== prev.senderId) return false;\n if (groupKey && groupKey(msg) !== groupKey(prev)) return false;\n const gapMs = msg.createdAt - prev.createdAt;\n return gapMs >= 0 && gapMs <= GROUP_GAP_SEC * 1000;\n}\n"]}
1
+ {"version":3,"file":"grouping.js","sourceRoot":"","sources":["../src/grouping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA+B,EAC/B,GAAkB,EAClB,QAA8B;IAE9B,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7C,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,aAAa,GAAG,IAAI,CAAC;AACrD,CAAC","sourcesContent":["import type { ChatMessageGroupKey, ChatMessageV1 } from './types.js';\nimport { GROUP_GAP_SEC } from './types.js';\n\n/**\n * Determine if `msg` is a \"continuation\" of `prev` for the purpose of\n * visually grouping consecutive messages from the same sender in the\n * message list.\n *\n * A message is a continuation when ALL of the following hold:\n * - `prev` exists.\n * - Neither `prev` nor `msg` is a system message.\n * - `prev` and `msg` have the same `senderId`.\n * - `msg.createdAt - prev.createdAt` is in the inclusive range\n * `[0, GROUP_GAP_SEC * 1000]` milliseconds. Negative gaps\n * (out-of-order messages) are treated as not-a-continuation.\n * - When the app supplies `groupKey`, both messages have the same key — a\n * continuation hides the sender line, so a per-message fact the app shows\n * there must break the group when it changes.\n *\n * Pure function. No side effects. Safe to call from server and client.\n */\nexport function isContinuation(\n prev: ChatMessageV1 | undefined,\n msg: ChatMessageV1,\n groupKey?: ChatMessageGroupKey,\n): boolean {\n if (!prev) return false;\n if (msg.isSystemMessage || prev.isSystemMessage) return false;\n if (msg.senderId !== prev.senderId) return false;\n if (groupKey && groupKey(msg) !== groupKey(prev)) return false;\n const gapMs = msg.createdAt - prev.createdAt;\n return gapMs >= 0 && gapMs <= GROUP_GAP_SEC * 1000;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { GROUP_GAP_SEC } from "./types.js";
2
- export { isContinuation } from "./grouping.js";
3
- export type { ChatId, ChatThreadV1, ChatMessageV1, ChatMessageGroupKey, ModerationHandlers, ChatNameResolver, ChatNameResolution, ChatPrewarmSenders, } from "./types.js";
1
+ export { GROUP_GAP_SEC } from './types.js';
2
+ export { isContinuation } from './grouping.js';
3
+ export type { ChatId, ChatThreadV1, ChatMessageV1, ChatMessageGroupKey, ModerationHandlers, ChatNameResolver, ChatNameResolution, ChatPrewarmSenders, } from './types.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,MAAM,EACN,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACrB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACV,MAAM,EACN,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,6 @@
5
5
  // The chat React UI, hooks, Firestore-client adapter config, and render types
6
6
  // live in @ttt-productions/chat-react. The realtime wire contract and the pure
7
7
  // chat Zod schemas live in @ttt-productions/chat-schemas.
8
- export { GROUP_GAP_SEC } from "./types.js";
9
- export { isContinuation } from "./grouping.js";
8
+ export { GROUP_GAP_SEC } from './types.js';
9
+ export { isContinuation } from './grouping.js';
10
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,0DAA0D;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// Pure chat-core barrel. No React, no Firebase (client or admin) reachable from\n// here — safe for Cloud Functions, scripts, and future native/TV clients that\n// only need the contracts and grouping logic. Zero @ttt-productions/* deps.\n//\n// The chat React UI, hooks, Firestore-client adapter config, and render types\n// live in @ttt-productions/chat-react. The realtime wire contract and the pure\n// chat Zod schemas live in @ttt-productions/chat-schemas.\n\nexport { GROUP_GAP_SEC } from \"./types.js\";\nexport { isContinuation } from \"./grouping.js\";\nexport type {\n ChatId,\n ChatThreadV1,\n ChatMessageV1,\n ChatMessageGroupKey,\n ModerationHandlers,\n ChatNameResolver,\n ChatNameResolution,\n ChatPrewarmSenders,\n} from \"./types.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,0DAA0D;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// Pure chat-core barrel. No React, no Firebase (client or admin) reachable from\n// here — safe for Cloud Functions, scripts, and future native/TV clients that\n// only need the contracts and grouping logic. Zero @ttt-productions/* deps.\n//\n// The chat React UI, hooks, Firestore-client adapter config, and render types\n// live in @ttt-productions/chat-react. The realtime wire contract and the pure\n// chat Zod schemas live in @ttt-productions/chat-schemas.\n\nexport { GROUP_GAP_SEC } from './types.js';\nexport { isContinuation } from './grouping.js';\nexport type {\n ChatId,\n ChatThreadV1,\n ChatMessageV1,\n ChatMessageGroupKey,\n ModerationHandlers,\n ChatNameResolver,\n ChatNameResolution,\n ChatPrewarmSenders,\n} from './types.js';\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,mBAAmB;AACnB,+CAA+C;AAC/C,EAAE;AACF,yEAAyE;AACzE,gFAAgF;AAChF,8EAA8E;AAC9E,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,kCAAkC;AA0ClC,+CAA+C;AAC/C,kDAAkD;AAClD,+CAA+C;AAE/C,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC","sourcesContent":["// ============================================\n// THREAD & MESSAGE\n// ============================================\n//\n// Chat is TEXT-ONLY. A message never carries a file: files belong to the\n// CONVERSATION (the Conversation Files list owned by the consuming app), not to\n// a message in its timeline. There is no attachment contract in this package.\n//\n// A message also carries NO reply pointer: no chat surface has an authoring\n// affordance for replying to a specific message, so a `replyTo` contract here\n// would be unreachable machinery.\n\nexport type ChatId = string;\n\nexport type ChatThreadV1 = {\n participantUserIds?: string[];\n createdAt: number; // millis\n lastMessageAt: number; // millis\n status?: string; // opaque to chat-core\n meta?: Record<string, unknown>; // opaque\n};\n\nexport type ChatMessageV1 = {\n messageId: string;\n threadId: string;\n createdAt: number; // millis\n senderId: string;\n text?: string;\n type?: string; // optional for renderer registry\n isSystemMessage?: boolean;\n /**\n * Account ids a server-written message's text refers to, stored beside the text (never inside\n * it). The UI prewarms and resolves them like senders; an account's anonymization rewrites them.\n */\n referencedUids?: string[];\n /** Moderation tombstone flag on the stored message (backend-written); consumers\n * render a tombstone instead of the content when true. */\n hidden?: boolean;\n meta?: Record<string, unknown>;\n};\n\n// ============================================\n// MODERATION\n// ============================================\n\nexport type ModerationHandlers = {\n onReportMessage?: (messageId: string, reason?: string) => void | Promise<void>;\n onReportThread?: (threadId: string, reason?: string) => void | Promise<void>;\n onDeleteMessage?: (messageId: string) => void | Promise<void>; // admin only (gated)\n onDeleteThread?: (threadId: string) => void | Promise<void>; // admin only (gated)\n};\n\n// ============================================\n// MESSAGE GROUPING (internal, exported for tests)\n// ============================================\n\n/** Max seconds between messages to be grouped as continuation */\nexport const GROUP_GAP_SEC = 120;\n\n/**\n * The app's own per-message grouping fact: two consecutive messages from one sender group only\n * when their keys are equal. `undefined` is an ordinary key value like any other.\n */\nexport type ChatMessageGroupKey = (m: ChatMessageV1) => string | undefined;\n\n// ============================================\n// NAME RESOLUTION\n// ============================================\n\n/**\n * What the app knows about a sender's display name right now. `pending` — the read has\n * not answered; `unavailable` — the read answered and there is no person to name;\n * `failed` — the read failed, and `retry` asks again. Only `resolved` carries a name:\n * the chat UI never invents one for the other three, it hands them to the app's\n * renderer (chat-react's `renderUnresolvedName`).\n */\nexport type ChatNameResolution =\n | { status: 'resolved'; name: string }\n | { status: 'pending' }\n | { status: 'unavailable' }\n | { status: 'failed'; retry: () => void };\n\n/**\n * Resolves a senderId synchronously from the app's own cache. Called during render,\n * so it reads what the app already holds and never starts a fetch itself — the app\n * fetches through {@link ChatPrewarmSenders}.\n */\nexport type ChatNameResolver = (senderId: string) => ChatNameResolution;\n\n/**\n * Optional pre-warm callback. The chat UI calls this with the deduped list of\n * senderIds visible in the current message page so the consuming app can\n * batch-fetch names into its cache. Implementations should be idempotent —\n * the same id list will be passed across re-renders.\n */\nexport type ChatPrewarmSenders = (senderIds: string[]) => void;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,mBAAmB;AACnB,+CAA+C;AAC/C,EAAE;AACF,yEAAyE;AACzE,gFAAgF;AAChF,8EAA8E;AAC9E,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,kCAAkC;AA0ClC,+CAA+C;AAC/C,kDAAkD;AAClD,+CAA+C;AAE/C,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC","sourcesContent":["// ============================================\n// THREAD & MESSAGE\n// ============================================\n//\n// Chat is TEXT-ONLY. A message never carries a file: files belong to the\n// CONVERSATION (the Conversation Files list owned by the consuming app), not to\n// a message in its timeline. There is no attachment contract in this package.\n//\n// A message also carries NO reply pointer: no chat surface has an authoring\n// affordance for replying to a specific message, so a `replyTo` contract here\n// would be unreachable machinery.\n\nexport type ChatId = string;\n\nexport type ChatThreadV1 = {\n participantUserIds?: string[];\n createdAt: number; // millis\n lastMessageAt: number; // millis\n status?: string; // opaque to chat-core\n meta?: Record<string, unknown>; // opaque\n};\n\nexport type ChatMessageV1 = {\n messageId: string;\n threadId: string;\n createdAt: number; // millis\n senderId: string;\n text?: string;\n type?: string; // optional for renderer registry\n isSystemMessage?: boolean;\n /**\n * Account ids a server-written message's text refers to, stored beside the text (never inside\n * it). The UI prewarms and resolves them like senders; an account's anonymization rewrites them.\n */\n referencedUids?: string[];\n /** Moderation tombstone flag on the stored message (backend-written); consumers\n * render a tombstone instead of the content when true. */\n hidden?: boolean;\n meta?: Record<string, unknown>;\n};\n\n// ============================================\n// MODERATION\n// ============================================\n\nexport type ModerationHandlers = {\n onReportMessage?: (messageId: string, reason?: string) => void | Promise<void>;\n onReportThread?: (threadId: string, reason?: string) => void | Promise<void>;\n onDeleteMessage?: (messageId: string) => void | Promise<void>; // admin only (gated)\n onDeleteThread?: (threadId: string) => void | Promise<void>; // admin only (gated)\n};\n\n// ============================================\n// MESSAGE GROUPING (internal, exported for tests)\n// ============================================\n\n/** Max seconds between messages to be grouped as continuation */\nexport const GROUP_GAP_SEC = 120;\n\n/**\n * The app's own per-message grouping fact: two consecutive messages from one sender group only\n * when their keys are equal. `undefined` is an ordinary key value like any other.\n */\nexport type ChatMessageGroupKey = (m: ChatMessageV1) => string | undefined;\n\n// ============================================\n// NAME RESOLUTION\n// ============================================\n\n/**\n * What the app knows about a sender's display name right now. `pending` — the read has\n * not answered; `unavailable` — the read answered and there is no person to name;\n * `failed` — the read failed, and `retry` asks again. Only `resolved` carries a name:\n * the chat UI never invents one for the other three, it hands them to the app's\n * renderer (chat-react's `renderUnresolvedName`).\n */\nexport type ChatNameResolution =\n | { status: 'resolved'; name: string }\n | { status: 'pending' }\n | { status: 'unavailable' }\n | { status: 'failed'; retry: () => void };\n\n/**\n * Resolves a senderId synchronously from the app's own cache. Called during render,\n * so it reads what the app already holds and never starts a fetch itself — the app\n * fetches through {@link ChatPrewarmSenders}.\n */\nexport type ChatNameResolver = (senderId: string) => ChatNameResolution;\n\n/**\n * Optional pre-warm callback. The chat UI calls this with the deduped list of\n * senderIds visible in the current message page so the consuming app can\n * batch-fetch names into its cache. Implementations should be idempotent —\n * the same id list will be passed across re-renders.\n */\nexport type ChatPrewarmSenders = (senderIds: string[]) => void;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/chat-core",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Pure chat contracts and grouping helpers for TTT Productions apps (no React, no Firebase)",
5
5
  "repository": {
6
6
  "type": "git",