@headless-lms/core 0.4.0 → 0.6.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/assets/events.d.ts +15 -9
- package/dist/assets/file-urls.d.ts +14 -7
- package/dist/assets/file-urls.js +15 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.js +3 -3
- package/dist/assets/ports.d.ts +34 -15
- package/dist/assets/service.d.ts +30 -14
- package/dist/assets/service.js +233 -54
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +68 -3
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +16 -16
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +34 -4
- package/dist/automations/community-mail.js +32 -4
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/index.d.ts +2 -1
- package/dist/automations/index.js +2 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- package/dist/automations/notifications/debounce.js +1 -1
- package/dist/automations/notifications/debounce.js.map +1 -1
- package/dist/automations/notifications/defaults.js +33 -3
- package/dist/automations/notifications/defaults.js.map +1 -1
- package/dist/automations/notifications/processor.js +1 -1
- package/dist/automations/notifications/processor.js.map +1 -1
- package/dist/automations/notifications/rules/content.js +60 -3
- package/dist/automations/notifications/rules/content.js.map +1 -1
- package/dist/automations/notifications/rules/discussion.js +29 -29
- package/dist/automations/notifications/rules/discussion.js.map +1 -1
- package/dist/automations/notifications/rules/entitlements.js +36 -9
- package/dist/automations/notifications/rules/entitlements.js.map +1 -1
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.js +17 -10
- package/dist/automations/notifications/rules/organizations.js.map +1 -1
- package/dist/automations/notifications/rules/recipients.js +0 -1
- package/dist/automations/notifications/rules/recipients.js.map +1 -1
- package/dist/automations/notifications/service.d.ts +8 -6
- package/dist/automations/notifications/service.js +12 -12
- package/dist/automations/notifications/service.js.map +1 -1
- package/dist/automations/notifications/types.js +94 -7
- package/dist/automations/notifications/types.js.map +1 -1
- package/dist/automations/ports.d.ts +30 -31
- package/dist/automations/service.d.ts +20 -17
- package/dist/automations/service.js +90 -61
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +465 -88
- package/dist/content/events.js +34 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +6 -4
- package/dist/content/index.js +3 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +5 -4
- package/dist/content/notification-categories.js +15 -6
- package/dist/content/notification-categories.js.map +1 -1
- package/dist/content/ports.d.ts +113 -68
- package/dist/content/service.d.ts +103 -67
- package/dist/content/service.js +739 -152
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +6 -3
- package/dist/content/titles.js +37 -24
- package/dist/content/titles.js.map +1 -1
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +56 -37
- package/dist/discussion/chats.js +392 -160
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/events.d.ts +85 -47
- package/dist/discussion/events.js +41 -3
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +8 -5
- package/dist/discussion/index.js +5 -3
- package/dist/discussion/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.js.map +1 -0
- package/dist/discussion/mentions.js +2 -2
- package/dist/discussion/mentions.js.map +1 -1
- package/dist/discussion/ports.d.ts +182 -100
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +45 -39
- package/dist/discussion/posts.js +266 -152
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +37 -31
- package/dist/discussion/service.js +250 -186
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +8 -8
- package/dist/discussion/subscriptions.js +28 -12
- package/dist/discussion/subscriptions.js.map +1 -1
- package/dist/discussion/titles.d.ts +4 -2
- package/dist/discussion/titles.js +27 -13
- package/dist/discussion/titles.js.map +1 -1
- package/dist/entitlements/events.d.ts +58 -58
- package/dist/entitlements/index.d.ts +4 -4
- package/dist/entitlements/index.js +2 -2
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +65 -39
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +40 -28
- package/dist/entitlements/service.js +369 -162
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +3 -3
- package/dist/identity/devices.js +6 -4
- package/dist/identity/devices.js.map +1 -1
- package/dist/identity/index.d.ts +2 -2
- package/dist/identity/index.js +2 -2
- package/dist/identity/notification-settings.d.ts +1 -1
- package/dist/identity/notification-settings.js +2 -1
- package/dist/identity/notification-settings.js.map +1 -1
- package/dist/identity/ports.d.ts +7 -7
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +16 -12
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +3 -3
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +31 -14
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +47 -0
- package/dist/organizations/effective-permissions.js +118 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +326 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +14 -10
- package/dist/organizations/index.js +7 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/model.d.ts +2 -2
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +6 -4
- package/dist/organizations/notification-categories.js +25 -10
- package/dist/organizations/notification-categories.js.map +1 -1
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +102 -35
- package/dist/organizations/service.js +1254 -327
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/events.d.ts +14 -14
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +21 -6
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +29 -5
- package/dist/reporting/member/ports.d.ts +49 -29
- package/dist/reporting/member/service.d.ts +57 -29
- package/dist/reporting/member/service.js +345 -114
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +23 -3
- package/dist/shared/errors.d.ts +2 -1
- package/dist/shared/errors.js +3 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +4 -0
- package/dist/shared/id.js +4 -0
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/ports.d.ts +25 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +22 -3
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +13 -16
- package/dist/types/index.d.ts +9 -9
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +4 -4
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/content.d.ts +599 -13
- package/dist/types/schemas/content.js +219 -8
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +280 -57
- package/dist/types/schemas/discussion.js +77 -3
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +21 -8
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/index.d.ts +5 -5
- package/dist/types/schemas/index.js +5 -5
- package/dist/types/schemas/notifications.d.ts +11 -10
- package/dist/types/schemas/notifications.js +3 -2
- package/dist/types/schemas/notifications.js.map +1 -1
- package/dist/types/schemas/organizations.d.ts +99 -60
- package/dist/types/schemas/organizations.js +80 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +3 -4
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -1
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.js","names":[],"sources":["../../src/discussion/live.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { DomainEvent, EventBus, JsonValue, Logger, PubSub } from '../shared/ports.js';\nimport { describeError, noopLogger } from '../shared/logger.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { ChatLiveEvent } from '../types/index.js';\nimport { discussionEvents } from './events.js';\nimport type {\n ChatCreated,\n ChatMuted,\n ChatRead,\n ChatUpdated,\n MessageDeleted,\n MessageSent,\n MessageUpdated,\n} from './events.js';\nimport type { ChatLiveReads } from './ports.js';\n\nexport const CHAT_CHANNEL = 'discussion.chats';\nexport const TYPING = 'typing';\n\nconst HEARTBEAT_MS = 25_000;\nconst PUBLISH_TIMEOUT_MS = 2000;\nconst FLUSH_MS = 1000;\nconst CLOSE_TIMEOUT_MS = 2000;\nconst MAX_CHATS = 16;\nconst MAX_PENDING = 5000;\nconst STREAM_QUEUE = 256;\n\nconst SENT = discussionEvents.messageSent.type;\nconst UPDATED = discussionEvents.messageUpdated.type;\nconst DELETED = discussionEvents.messageDeleted.type;\nconst REACTIONS = discussionEvents.messageReactionsChanged.type;\nconst CREATED = discussionEvents.chatCreated.type;\nconst CHAT_UPDATED = discussionEvents.chatUpdated.type;\nconst ADDED = discussionEvents.chatParticipantAdded.type;\nconst READ = discussionEvents.chatRead.type;\nconst MUTED = discussionEvents.chatMuted.type;\n\nconst chatChangeSchema = z.object({\n event: z.string(),\n orgId: z.string(),\n communityId: z.string(),\n chatId: z.string(),\n messageId: z.string().optional(),\n orgUserId: z.string().optional(),\n members: z.array(z.string()).optional(),\n reaction: z.string().nullable().optional(),\n until: z.string().optional(),\n});\n\n/** One chat change on the `discussion.chats` channel. It never carries a body;\n * every instance reads current state itself. */\nexport type ChatChange = z.infer<typeof chatChangeSchema>;\n\n/** Publishes a change, logging and dropping it when the publish fails or has\n * not finished within 2 s. Never throws. */\nexport async function publishChange(\n pubsub: PubSub,\n change: ChatChange,\n logger: Logger,\n timeoutMs = PUBLISH_TIMEOUT_MS,\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n pubsub.publish(CHAT_CHANNEL, change as JsonValue),\n new Promise((_, reject) => {\n timer = setTimeout(() => reject(new Error('publish timed out')), timeoutMs);\n }),\n ]);\n } catch (error) {\n logger.warn('live publish failed', {\n orgId: change.orgId,\n chatId: change.chatId,\n event: change.event,\n error: describeError(error),\n });\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction actorOf(event: DomainEvent): string[] {\n const actor = event.metadata?.actorId;\n return typeof actor === 'string' ? [actor] : [];\n}\n\nfunction union(...lists: string[][]): string[] {\n return [...new Set(lists.flat())].sort();\n}\n\nexport type ChatLiveForwarderParams = {\n pubsub: PubSub;\n logger?: Logger;\n timeoutMs?: number;\n};\n\n/** Publishes every chat change committed through the outbox to every instance. */\nexport class ChatLiveForwarder {\n private readonly pubsub: PubSub;\n private readonly logger: Logger;\n private readonly timeoutMs: number;\n\n constructor(params: ChatLiveForwarderParams) {\n this.pubsub = params.pubsub;\n this.logger = params.logger ?? noopLogger;\n this.timeoutMs = params.timeoutMs ?? PUBLISH_TIMEOUT_MS;\n }\n\n attach(bus: EventBus): void {\n const forward = (change: ChatChange) =>\n publishChange(this.pubsub, change, this.logger, this.timeoutMs);\n const chat = (event: ChatCreated | ChatUpdated) =>\n forward({\n event: event.type,\n orgId: event.orgId,\n communityId: event.data.communityId,\n chatId: event.data.id,\n members: union(event.data.participantIds, actorOf(event)),\n });\n const message = (event: MessageSent | MessageUpdated | MessageDeleted) =>\n forward({\n event: event.type,\n orgId: event.orgId,\n communityId: event.data.communityId,\n chatId: event.data.chatId,\n messageId: event.data.id,\n });\n const member = (event: ChatRead | ChatMuted) =>\n forward({\n event: event.type,\n orgId: event.orgId,\n communityId: event.data.communityId,\n chatId: event.data.chatId,\n orgUserId: event.data.orgUserId,\n });\n bus.subscribe(discussionEvents.chatCreated, chat);\n bus.subscribe(discussionEvents.chatUpdated, chat);\n bus.subscribe(discussionEvents.chatParticipantAdded, (event) =>\n forward({\n event: event.type,\n orgId: event.orgId,\n communityId: event.data.communityId,\n chatId: event.data.chatId,\n orgUserId: event.data.orgUserId,\n members: union([event.data.orgUserId], actorOf(event)),\n }),\n );\n bus.subscribe(discussionEvents.messageSent, message);\n bus.subscribe(discussionEvents.messageUpdated, message);\n bus.subscribe(discussionEvents.messageDeleted, message);\n bus.subscribe(discussionEvents.messageReactionsChanged, (event) =>\n forward({\n event: event.type,\n orgId: event.orgId,\n communityId: event.data.communityId,\n chatId: event.data.chatId,\n messageId: event.data.messageId,\n orgUserId: event.data.orgUserId,\n reaction: event.data.reaction,\n }),\n );\n bus.subscribe(discussionEvents.chatRead, member);\n bus.subscribe(discussionEvents.chatMuted, member);\n }\n}\n\nexport interface ChatHubOptions {\n flushMs?: number;\n closeTimeoutMs?: number;\n}\n\nexport type ChatHubParams = ChatHubOptions & {\n pubsub: PubSub;\n reads: ChatLiveReads;\n logger?: Logger;\n};\n\nexport interface LiveRegistration {\n communityId: string;\n actor: Actor;\n}\n\nclass LiveStream implements AsyncIterableIterator<ChatLiveEvent> {\n readonly done: Promise<void>;\n private queue: ChatLiveEvent[] = [];\n private waiter: ((result: IteratorResult<ChatLiveEvent>) => void) | null = null;\n private ending = false;\n private finished = false;\n private resolveDone!: () => void;\n\n constructor(\n readonly orgId: string,\n readonly communityId: string,\n readonly orgUserId: string,\n private readonly onEnd: (stream: LiveStream) => void,\n private readonly onOverflow: (stream: LiveStream) => void,\n ) {\n this.done = new Promise((resolve) => {\n this.resolveDone = resolve;\n });\n }\n\n get ended(): boolean {\n return this.ending;\n }\n\n push(event: ChatLiveEvent): void {\n if (this.ending) {\n return;\n }\n const waiter = this.waiter;\n if (waiter) {\n this.waiter = null;\n waiter({ value: event, done: false });\n return;\n }\n if (this.queue.length >= STREAM_QUEUE) {\n this.queue = [{ type: 'resync' }];\n this.onOverflow(this);\n return;\n }\n this.queue.push(event);\n }\n\n end(last?: ChatLiveEvent): void {\n if (this.ending) {\n return;\n }\n if (last) {\n this.push(last);\n }\n this.ending = true;\n this.onEnd(this);\n const waiter = this.waiter;\n if (waiter && this.queue.length === 0) {\n this.waiter = null;\n this.finish();\n waiter({ value: undefined, done: true });\n }\n }\n\n next(): Promise<IteratorResult<ChatLiveEvent>> {\n const event = this.queue.shift();\n if (event) {\n return Promise.resolve({ value: event, done: false });\n }\n if (this.ending) {\n this.finish();\n return Promise.resolve({ value: undefined, done: true });\n }\n return new Promise((resolve) => {\n this.waiter = resolve;\n });\n }\n\n return(): Promise<IteratorResult<ChatLiveEvent>> {\n this.queue = [];\n this.end();\n const waiter = this.waiter;\n if (waiter) {\n this.waiter = null;\n waiter({ value: undefined, done: true });\n }\n this.finish();\n return Promise.resolve({ value: undefined, done: true });\n }\n\n [Symbol.asyncIterator](): AsyncIterableIterator<ChatLiveEvent> {\n return this;\n }\n\n private finish(): void {\n if (this.finished) {\n return;\n }\n this.finished = true;\n this.resolveDone();\n }\n}\n\ninterface ChatQueue {\n pending: ChatChange[];\n running: boolean;\n queued: boolean;\n}\n\ninterface DirtyGroup {\n orgId: string;\n communityId: string;\n members: Map<string, Set<string>>;\n}\n\nfunction coalesceKey(change: ChatChange): string | null {\n switch (change.event) {\n case SENT:\n case UPDATED:\n case DELETED:\n return `message:${change.messageId}`;\n case REACTIONS:\n return `reaction:${change.messageId}:${change.orgUserId}`;\n case CREATED:\n case CHAT_UPDATED:\n case ADDED:\n return 'chat';\n case READ:\n case MUTED:\n return `member:${change.orgUserId}`;\n default:\n return null;\n }\n}\n\nfunction merge(existing: ChatChange, next: ChatChange): ChatChange {\n switch (existing.event) {\n case SENT:\n case UPDATED:\n case DELETED: {\n const events = [existing.event, next.event];\n const event = events.includes(SENT) ? SENT : events.includes(DELETED) ? DELETED : UPDATED;\n return { ...next, event };\n }\n case CREATED:\n case CHAT_UPDATED:\n case ADDED:\n return { ...existing, members: union(existing.members ?? [], next.members ?? []) };\n case REACTIONS:\n return next;\n default:\n return existing;\n }\n}\n\nconst key = (orgId: string, id: string) => `${orgId}\\u0000${id}`;\n\n/** This process's open chat streams, and the live delivery of every chat\n * change to them. */\nexport class ChatHub {\n private readonly pubsub: PubSub;\n private readonly reads: ChatLiveReads;\n private readonly logger: Logger;\n private readonly flushMs: number;\n private readonly closeTimeoutMs: number;\n private readonly streams = new Set<LiveStream>();\n private readonly byMember = new Map<string, Set<LiveStream>>();\n private readonly byCommunity = new Map<string, Set<LiveStream>>();\n private readonly queues = new Map<string, ChatQueue>();\n private ready: string[] = [];\n private running = 0;\n private pending = 0;\n private dirty = new Map<string, DirtyGroup>();\n private flushing = false;\n private flushAgain = false;\n private closing = false;\n private unsubscribe: (() => void) | null = null;\n private flushTimer: NodeJS.Timeout | null = null;\n\n constructor(params: ChatHubParams) {\n this.pubsub = params.pubsub;\n this.reads = params.reads;\n this.logger = params.logger ?? noopLogger;\n this.flushMs = params.flushMs ?? FLUSH_MS;\n this.closeTimeoutMs = params.closeTimeoutMs ?? CLOSE_TIMEOUT_MS;\n }\n\n async start(): Promise<void> {\n if (this.unsubscribe || this.closing) {\n return;\n }\n this.unsubscribe = this.pubsub.subscribe(\n CHAT_CHANNEL,\n (message) => this.receive(message),\n () => this.reset(),\n );\n this.flushTimer = setInterval(() => {\n if (!this.flushing) {\n void this.flush();\n }\n }, this.flushMs);\n this.flushTimer.unref();\n this.logger.info('chat hub started');\n }\n\n /** Ends every stream with `closed { reason: 'shutdown' }` and waits, up to\n * 2 s in all, for each to be read to its end. */\n async close(): Promise<void> {\n if (this.closing) {\n return;\n }\n this.closing = true;\n this.unsubscribe?.();\n this.unsubscribe = null;\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n }\n this.queues.clear();\n this.ready = [];\n this.pending = 0;\n const streams = [...this.streams];\n for (const stream of streams) {\n stream.end({ type: 'closed', reason: 'shutdown' });\n }\n let timer: NodeJS.Timeout | undefined;\n await Promise.race([\n Promise.all(streams.map((stream) => stream.done)),\n new Promise((resolve) => {\n timer = setTimeout(resolve, this.closeTimeoutMs);\n }),\n ]);\n clearTimeout(timer);\n this.logger.info('chat hub closed', { streams: streams.length });\n }\n\n /** Registers one stream. It starts with `ready`, then the member's unread. */\n open(registration: LiveRegistration): AsyncIterable<ChatLiveEvent> {\n const { actor, communityId } = registration;\n const stream = new LiveStream(\n actor.orgId,\n communityId,\n actor.id,\n (ended) => this.unregister(ended),\n (full) => this.markDirty(full.orgId, full.communityId, full.orgUserId),\n );\n if (this.closing) {\n stream.end({ type: 'closed', reason: 'shutdown' });\n return stream;\n }\n this.streams.add(stream);\n this.index(this.byMember, key(stream.orgId, stream.orgUserId), stream);\n this.index(this.byCommunity, key(stream.orgId, stream.communityId), stream);\n stream.push({ type: 'ready', heartbeatMs: HEARTBEAT_MS });\n this.markDirty(stream.orgId, stream.communityId, stream.orgUserId);\n void this.flush();\n this.logger.debug('chat stream opened', {\n orgId: stream.orgId,\n communityId,\n streams: this.streams.size,\n });\n return stream;\n }\n\n private index(map: Map<string, Set<LiveStream>>, at: string, stream: LiveStream): void {\n const set = map.get(at) ?? new Set<LiveStream>();\n set.add(stream);\n map.set(at, set);\n }\n\n private unindex(map: Map<string, Set<LiveStream>>, at: string, stream: LiveStream): void {\n const set = map.get(at);\n set?.delete(stream);\n if (set?.size === 0) {\n map.delete(at);\n }\n }\n\n private unregister(stream: LiveStream): void {\n if (!this.streams.delete(stream)) {\n return;\n }\n this.unindex(this.byMember, key(stream.orgId, stream.orgUserId), stream);\n this.unindex(this.byCommunity, key(stream.orgId, stream.communityId), stream);\n this.logger.debug('chat stream closed', {\n orgId: stream.orgId,\n communityId: stream.communityId,\n streams: this.streams.size,\n });\n }\n\n private streamsOf(orgId: string, communityId: string, orgUserId: string): LiveStream[] {\n return [...(this.byMember.get(key(orgId, orgUserId)) ?? [])].filter(\n (stream) => stream.communityId === communityId,\n );\n }\n\n private communityStreams(orgId: string, communityId: string): LiveStream[] {\n return [...(this.byCommunity.get(key(orgId, communityId)) ?? [])];\n }\n\n private receive(message: JsonValue): void {\n const parsed = chatChangeSchema.safeParse(message);\n if (!parsed.success) {\n this.logger.warn('chat change unreadable');\n return;\n }\n const change = parsed.data;\n if (this.closing || !this.byCommunity.has(key(change.orgId, change.communityId))) {\n return;\n }\n const at = key(change.orgId, change.chatId);\n const queue = this.queues.get(at) ?? { pending: [], running: false, queued: false };\n this.queues.set(at, queue);\n const coalesce = coalesceKey(change);\n const index =\n coalesce === null ? -1 : queue.pending.findIndex((other) => coalesceKey(other) === coalesce);\n if (index >= 0) {\n queue.pending[index] = merge(queue.pending[index]!, change);\n } else {\n if (this.pending >= MAX_PENDING) {\n this.overflow();\n return;\n }\n queue.pending.push(change);\n this.pending += 1;\n }\n if (!queue.running && !queue.queued) {\n queue.queued = true;\n this.ready.push(at);\n }\n this.pump();\n }\n\n private pump(): void {\n while (this.running < MAX_CHATS && this.ready.length > 0) {\n const at = this.ready.shift()!;\n const queue = this.queues.get(at);\n if (!queue) {\n continue;\n }\n queue.queued = false;\n const change = queue.pending.shift();\n if (!change) {\n this.queues.delete(at);\n continue;\n }\n this.pending -= 1;\n queue.running = true;\n this.running += 1;\n void this.handle(change).finally(() => {\n queue.running = false;\n this.running -= 1;\n if (this.queues.get(at) === queue) {\n if (queue.pending.length > 0) {\n queue.queued = true;\n this.ready.push(at);\n } else {\n this.queues.delete(at);\n }\n }\n this.pump();\n });\n }\n }\n\n private overflow(): void {\n this.logger.warn('chat hub queue full; dropping pending changes', { pending: this.pending });\n for (const [at, queue] of this.queues) {\n queue.pending = [];\n queue.queued = false;\n if (!queue.running) {\n this.queues.delete(at);\n }\n }\n this.ready = [];\n this.pending = 0;\n this.resync([...this.streams]);\n }\n\n private reset(): void {\n this.logger.info('chat hub reset', { streams: this.streams.size });\n this.resync([...this.streams]);\n }\n\n private resync(streams: LiveStream[]): void {\n for (const stream of streams) {\n stream.push({ type: 'resync' });\n this.markDirty(stream.orgId, stream.communityId, stream.orgUserId);\n }\n }\n\n private async handle(change: ChatChange): Promise<void> {\n try {\n switch (change.event) {\n case SENT:\n case UPDATED:\n case DELETED:\n await this.onMessage(change);\n return;\n case REACTIONS:\n await this.onReactions(change);\n return;\n case CREATED:\n case CHAT_UPDATED:\n case ADDED:\n await this.onChat(change);\n return;\n case READ:\n case MUTED:\n if (change.orgUserId) {\n this.markDirty(change.orgId, change.communityId, change.orgUserId, change.chatId);\n }\n return;\n case TYPING:\n await this.onTyping(change);\n return;\n default:\n this.logger.debug('chat change ignored', { event: change.event });\n }\n } catch (error) {\n this.logger.error('chat change read failed', {\n orgId: change.orgId,\n chatId: change.chatId,\n event: change.event,\n error: describeError(error),\n });\n this.resync(this.communityStreams(change.orgId, change.communityId));\n }\n }\n\n private async participantStreams(\n change: ChatChange,\n ): Promise<{ members: string[]; streams: LiveStream[] }> {\n const active = await this.reads.activeParticipants(change.orgId, change.chatId);\n const members: string[] = [];\n const streams: LiveStream[] = [];\n for (const orgUserId of active) {\n const local = this.streamsOf(change.orgId, change.communityId, orgUserId);\n if (local.length > 0) {\n members.push(orgUserId);\n streams.push(...local);\n }\n }\n return { members, streams };\n }\n\n private async onMessage(change: ChatChange): Promise<void> {\n const { members, streams } = await this.participantStreams(change);\n if (streams.length === 0 || !change.messageId) {\n return;\n }\n const [view] = await this.reads.messageViews(change.orgId, [change.messageId]);\n if (!view || view.chatId !== change.chatId) {\n return;\n }\n const event: ChatLiveEvent = view.deletedAt\n ? {\n type: 'message.deleted',\n chatId: change.chatId,\n messageId: view.id,\n deletedAt: view.deletedAt,\n }\n : {\n type: change.event === SENT ? 'message.created' : 'message.updated',\n chatId: change.chatId,\n message: view,\n };\n for (const stream of streams) {\n stream.push(event);\n }\n if (change.event !== UPDATED || view.deletedAt) {\n for (const orgUserId of members) {\n this.markDirty(change.orgId, change.communityId, orgUserId, change.chatId);\n }\n }\n }\n\n private async onReactions(change: ChatChange): Promise<void> {\n const { streams } = await this.participantStreams(change);\n if (streams.length === 0 || !change.messageId || !change.orgUserId) {\n return;\n }\n const reactions = await this.reads.messageReactions(change.orgId, change.messageId);\n for (const stream of streams) {\n stream.push({\n type: 'message.reactions',\n chatId: change.chatId,\n messageId: change.messageId,\n reactions,\n orgUserId: change.orgUserId,\n reaction: change.reaction ?? null,\n });\n }\n }\n\n private async onChat(change: ChatChange): Promise<void> {\n const live = await this.reads.liveChat(change.orgId, change.chatId);\n const active = new Set(live?.participantIds ?? []);\n if (live) {\n for (const orgUserId of active) {\n const streams = this.streamsOf(change.orgId, change.communityId, orgUserId);\n if (streams.length === 0) {\n continue;\n }\n for (const stream of streams) {\n stream.push({\n type: 'chat.updated',\n chatId: change.chatId,\n chat: live.chat,\n participants: live.participants,\n });\n }\n this.markDirty(change.orgId, change.communityId, orgUserId, change.chatId);\n }\n }\n for (const orgUserId of change.members ?? []) {\n if (active.has(orgUserId)) {\n continue;\n }\n const streams = this.streamsOf(change.orgId, change.communityId, orgUserId);\n if (streams.length === 0) {\n continue;\n }\n for (const stream of streams) {\n stream.push({ type: 'chat.removed', chatId: change.chatId });\n }\n this.markDirty(change.orgId, change.communityId, orgUserId);\n }\n }\n\n private async onTyping(change: ChatChange): Promise<void> {\n if (!change.orgUserId || !change.until) {\n return;\n }\n const { streams } = await this.participantStreams(change);\n const until = new Date(change.until);\n for (const stream of streams) {\n if (stream.orgUserId === change.orgUserId) {\n continue;\n }\n stream.push({\n type: 'typing',\n chatId: change.chatId,\n orgUserId: change.orgUserId,\n until,\n });\n }\n }\n\n private markDirty(orgId: string, communityId: string, orgUserId: string, chatId?: string): void {\n const at = key(orgId, communityId);\n const group = this.dirty.get(at) ?? { orgId, communityId, members: new Map() };\n this.dirty.set(at, group);\n const chats = group.members.get(orgUserId) ?? new Set<string>();\n group.members.set(orgUserId, chats);\n if (chatId) {\n chats.add(chatId);\n }\n }\n\n /** Pushes the `chat.state` of every dirty member and chat and the `unread`\n * of every dirty member, read now. One flush runs at a time. */\n private async flush(): Promise<void> {\n if (this.flushing) {\n this.flushAgain = true;\n return;\n }\n this.flushing = true;\n try {\n do {\n this.flushAgain = false;\n const groups = [...this.dirty.values()];\n this.dirty = new Map();\n await Promise.all(groups.map((group) => this.flushGroup(group)));\n } while (this.flushAgain && this.dirty.size > 0 && !this.closing);\n } finally {\n this.flushing = false;\n }\n }\n\n private async flushGroup(group: DirtyGroup): Promise<void> {\n const { orgId, communityId } = group;\n const members = [...group.members.keys()].filter(\n (orgUserId) => this.streamsOf(orgId, communityId, orgUserId).length > 0,\n );\n if (members.length === 0) {\n return;\n }\n let states: Awaited<ReturnType<ChatLiveReads['memberStates']>>;\n try {\n states = await this.reads.memberStates(orgId, communityId, members);\n } catch (error) {\n this.logger.error('chat member state read failed', {\n orgId,\n communityId,\n error: describeError(error),\n });\n for (const orgUserId of members) {\n const chats = group.members.get(orgUserId) ?? new Set<string>();\n this.markDirty(orgId, communityId, orgUserId);\n for (const chatId of chats) {\n this.markDirty(orgId, communityId, orgUserId, chatId);\n }\n }\n return;\n }\n for (const state of states) {\n const chats = group.members.get(state.orgUserId);\n const streams = this.streamsOf(orgId, communityId, state.orgUserId);\n if (!chats || streams.length === 0) {\n continue;\n }\n const events: ChatLiveEvent[] = state.chats\n .filter((chat) => chats.has(chat.chatId))\n .map((chat) => ({\n type: 'chat.state',\n chatId: chat.chatId,\n unread: chat.unread,\n lastReadMessageId: chat.lastReadMessageId,\n subscription: chat.subscription,\n }));\n events.push({ type: 'unread', ...state.unread });\n for (const stream of streams) {\n for (const event of events) {\n stream.push(event);\n }\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authorName } from "./authors.js";
|
|
2
2
|
//#region src/discussion/mentions.ts
|
|
3
3
|
const TOKEN = /<@([A-Za-z0-9_-]+)>/g;
|
|
4
4
|
const UNNAMED = "A member";
|
|
@@ -25,7 +25,7 @@ async function mentionViews(mentions, profiles, orgId, itemType, itemIds) {
|
|
|
25
25
|
const record = records[orgUserId];
|
|
26
26
|
return record ? [{
|
|
27
27
|
orgUserId,
|
|
28
|
-
name:
|
|
28
|
+
name: authorName(record) || UNNAMED
|
|
29
29
|
}] : [];
|
|
30
30
|
})]));
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mentions.js","names":[],"sources":["../../src/discussion/mentions.ts"],"sourcesContent":["import type { MentionItemType, MentionView } from '../types/index.js';\nimport {
|
|
1
|
+
{"version":3,"file":"mentions.js","names":[],"sources":["../../src/discussion/mentions.ts"],"sourcesContent":["import type { MentionItemType, MentionView } from '../types/index.js';\nimport { authorName } from './authors.js';\nimport type { AuthorProfileReader, MentionsRepository } from './ports.js';\n\nconst TOKEN = /<@([A-Za-z0-9_-]+)>/g;\nconst UNNAMED = 'A member';\n\nexport function mentionTokens(...texts: string[]): string[] {\n const found = new Set<string>();\n for (const text of texts) {\n for (const match of text.matchAll(TOKEN)) {\n found.add(match[1]!);\n }\n }\n return [...found];\n}\n\nexport async function nameMentions(\n text: string,\n nameOf: (orgUserId: string) => Promise<string>,\n): Promise<string> {\n const names = new Map<string, string>();\n for (const orgUserId of mentionTokens(text)) {\n names.set(orgUserId, await nameOf(orgUserId));\n }\n return text.replace(TOKEN, (_token, orgUserId: string) => names.get(orgUserId) ?? UNNAMED);\n}\n\nexport function keepAllowed(tokens: string[], allowed: string[]): string[] {\n const kept = new Set(allowed);\n return tokens.filter((token) => kept.has(token));\n}\n\nexport async function mentionViews(\n mentions: Pick<MentionsRepository, 'forItems'>,\n profiles: AuthorProfileReader,\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n): Promise<Record<string, MentionView[]>> {\n if (itemIds.length === 0) {\n return {};\n }\n const byItem = await mentions.forItems(orgId, itemType, [...new Set(itemIds)]);\n const people = [...new Set(Object.values(byItem).flat())];\n const records = people.length > 0 ? await profiles.authorsOf(orgId, people) : {};\n return Object.fromEntries(\n Object.entries(byItem).map(([itemId, orgUserIds]) => [\n itemId,\n orgUserIds.flatMap((orgUserId) => {\n const record = records[orgUserId];\n return record ? [{ orgUserId, name: authorName(record) || UNNAMED }] : [];\n }),\n ]),\n );\n}\n"],"mappings":";;AAIA,MAAM,QAAQ;AACd,MAAM,UAAU;AAEhB,SAAgB,cAAc,GAAG,OAA2B;CAC1D,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,QAAQ,OACjB,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GACrC,MAAM,IAAI,MAAM,EAAG;CAGvB,OAAO,CAAC,GAAG,KAAK;AAClB;AAEA,eAAsB,aACpB,MACA,QACiB;CACjB,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,aAAa,cAAc,IAAI,GACxC,MAAM,IAAI,WAAW,MAAM,OAAO,SAAS,CAAC;CAE9C,OAAO,KAAK,QAAQ,QAAQ,QAAQ,cAAsB,MAAM,IAAI,SAAS,KAAK,OAAO;AAC3F;AAEA,SAAgB,YAAY,QAAkB,SAA6B;CACzE,MAAM,OAAO,IAAI,IAAI,OAAO;CAC5B,OAAO,OAAO,QAAQ,UAAU,KAAK,IAAI,KAAK,CAAC;AACjD;AAEA,eAAsB,aACpB,UACA,UACA,OACA,UACA,SACwC;CACxC,IAAI,QAAQ,WAAW,GACrB,OAAO,CAAC;CAEV,MAAM,SAAS,MAAM,SAAS,SAAS,OAAO,UAAU,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC;CAC7E,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;CACxD,MAAM,UAAU,OAAO,SAAS,IAAI,MAAM,SAAS,UAAU,OAAO,MAAM,IAAI,CAAC;CAC/E,OAAO,OAAO,YACZ,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,gBAAgB,CACnD,QACA,WAAW,SAAS,cAAc;EAChC,MAAM,SAAS,QAAQ;EACvB,OAAO,SAAS,CAAC;GAAE;GAAW,MAAM,WAAW,MAAM,KAAK;EAAQ,CAAC,IAAI,CAAC;CAC1E,CAAC,CACH,CAAC,CACH;AACF"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { JsonValue, Page } from "../types/shared.js";
|
|
2
|
-
import { Activity, SpaceDetail } from "../types/schemas/content.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Comment, CommentReport } from "../types/discussion.js";
|
|
2
|
+
import { Activity, DmSettings, SpaceDetail } from "../types/schemas/content.js";
|
|
3
|
+
import { Chat, ChatLastMessage, ChatLiveEvent, ChatUnreadSummary, ChatUser, CommentListItem, CommentView, CommentsConfig, DigestRun, FlaggedComment, FlaggedCommentView, ListCommentsQuery, LiveMessage, MentionItemType, Message, MessageFlag, MessageFlagResolution, MessageView, Post, PostFlag, PostFlagResolution, PostStatus, PostView, ReactionCounts, ReactionEmoji, Subscription, SubscriptionItemType, SubscriptionView } from "../types/schemas/discussion.js";
|
|
4
|
+
import { Comment, CommentAuthor, CommentReport } from "../types/discussion.js";
|
|
6
5
|
import { SubjectRef } from "../types/notifications.js";
|
|
7
6
|
import "../types/index.js";
|
|
7
|
+
import { Actor, EffectivePermissions } from "../shared/actor.js";
|
|
8
8
|
import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
|
|
9
|
-
import { Actor } from "../shared/actor.js";
|
|
10
9
|
import { CommunityDigest } from "../content/ports.js";
|
|
11
10
|
import "../content/index.js";
|
|
12
|
-
import "../organizations/index.js";
|
|
13
11
|
import "./model.js";
|
|
14
12
|
import "./types.js";
|
|
15
13
|
//#region src/discussion/ports.d.ts
|
|
@@ -29,7 +27,6 @@ interface CommentWithContext {
|
|
|
29
27
|
activityTitle: string;
|
|
30
28
|
}
|
|
31
29
|
type PostCommentInput = {
|
|
32
|
-
actor: Actor;
|
|
33
30
|
parentId: string | null;
|
|
34
31
|
body: string;
|
|
35
32
|
} & ({
|
|
@@ -40,14 +37,13 @@ type PostCommentInput = {
|
|
|
40
37
|
activityId?: undefined;
|
|
41
38
|
});
|
|
42
39
|
/** A profile row as the repository loads it, keyed by `org_users.id`. `email`
|
|
43
|
-
* is stripped before comments are served and kept only for the
|
|
40
|
+
* is stripped before comments are served and kept only for the moderation list. */
|
|
44
41
|
interface AuthorRecord {
|
|
45
42
|
id: string;
|
|
46
43
|
firstName: string | null;
|
|
47
44
|
lastName: string | null;
|
|
48
45
|
email: string;
|
|
49
46
|
image: string | null;
|
|
50
|
-
role: Role;
|
|
51
47
|
displayName: string | null;
|
|
52
48
|
headline: string | null;
|
|
53
49
|
imageAssetOrgId: string | null;
|
|
@@ -58,43 +54,46 @@ interface ItemTitle {
|
|
|
58
54
|
excerpt?: string;
|
|
59
55
|
}
|
|
60
56
|
interface ItemTitles {
|
|
61
|
-
titlesFor(
|
|
57
|
+
titlesFor(refs: SubjectRef[], actor: Actor): Promise<Record<string, ItemTitle>>;
|
|
62
58
|
}
|
|
63
59
|
interface AuthorProfileReader {
|
|
64
60
|
authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;
|
|
65
61
|
}
|
|
62
|
+
/** Where a comment must be for a moderation call: in one community, on a
|
|
63
|
+
* lesson, or anywhere the actor can reach. */
|
|
64
|
+
type CommentWithin = {
|
|
65
|
+
communityId: string;
|
|
66
|
+
} | 'lesson' | 'any';
|
|
66
67
|
interface DiscussionService {
|
|
67
68
|
/** Post a root comment or a reply. Lands pending where review is required and
|
|
68
|
-
* the poster
|
|
69
|
-
postComment(
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
* the poster does not skip review. */
|
|
70
|
+
postComment(input: PostCommentInput, actor: Actor): Promise<CommentView>;
|
|
71
|
+
/** Reply to a lesson comment as a moderator of its course; a reply to a reply
|
|
72
|
+
* attaches to its root. */
|
|
73
|
+
replyToComment(commentId: string, body: string, actor: Actor): Promise<CommentView>;
|
|
74
|
+
/** Author-only. Throws ForbiddenError for anyone else, moderators included. */
|
|
75
|
+
edit(commentId: string, body: string, actor: Actor): Promise<CommentView>;
|
|
76
|
+
/** The author, or a moderator of where the comment is. */
|
|
77
|
+
remove(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;
|
|
78
|
+
/** A moderator of where the comment is. Returns the comment to published. */
|
|
79
|
+
restore(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;
|
|
80
|
+
/** A moderator of where the comment is. Publishes a pending comment. */
|
|
81
|
+
approve(commentId: string, actor: Actor): Promise<Comment>;
|
|
82
|
+
/** Make a comment published, whichever state it is in. A moderator of where
|
|
83
|
+
* the comment is. */
|
|
84
|
+
publish(commentId: string, within: CommentWithin, actor: Actor): Promise<Comment>;
|
|
80
85
|
/** The activity's comments as this reader may see them. */
|
|
81
|
-
activityComments(
|
|
86
|
+
activityComments(activityId: string, actor: Actor): Promise<ActivityComments>;
|
|
82
87
|
/** One page of top-level comments, oldest first, each with its replies. */
|
|
83
|
-
postComments(
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* No rule is applied beyond the filters — this is what exists, not what
|
|
90
|
-
* someone is allowed to read.
|
|
91
|
-
*/
|
|
92
|
-
listComments(orgId: string, query: ListCommentsQuery): Promise<Page<CommentListItem>>;
|
|
93
|
-
setReaction(orgId: string, commentId: string, actor: Actor, emoji: ReactionEmoji | null): Promise<CommentReactions>;
|
|
94
|
-
resolveReports(orgId: string, commentId: string, actor: Actor, communityId?: string): Promise<void>;
|
|
88
|
+
postComments(postId: string, query: PageQuery, actor: Actor): Promise<Page<CommentView>>;
|
|
89
|
+
/** The lesson comments of the courses the actor moderates, filtered. Rows
|
|
90
|
+
* carry the author's email and a removed comment's body. */
|
|
91
|
+
listComments(query: ListCommentsQuery, actor: Actor): Promise<Page<CommentListItem>>;
|
|
92
|
+
setReaction(commentId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<CommentReactions>;
|
|
93
|
+
resolveReports(commentId: string, within: CommentWithin, actor: Actor): Promise<void>;
|
|
95
94
|
/** Accepted even on locked comments — a locked activity can still hold
|
|
96
95
|
* something a moderator needs to see. */
|
|
97
|
-
reportComment(
|
|
96
|
+
reportComment(commentId: string, reason: string, actor: Actor): Promise<CommentReport>;
|
|
98
97
|
/** Plain read of one comment — no rules applied. */
|
|
99
98
|
getComment(orgId: string, commentId: string): Promise<Comment | null>;
|
|
100
99
|
commenterIds(orgId: string, target: {
|
|
@@ -107,7 +106,24 @@ interface ContentAccessReader {
|
|
|
107
106
|
hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;
|
|
108
107
|
}
|
|
109
108
|
interface ActivityGetter {
|
|
110
|
-
getActivity(
|
|
109
|
+
getActivity(activityId: string, actor: Actor): Promise<Activity | null>;
|
|
110
|
+
}
|
|
111
|
+
interface SpaceReader {
|
|
112
|
+
getSpace(spaceId: string, actor: Actor): Promise<SpaceDetail | null>;
|
|
113
|
+
getDmSettings(communityId: string, actor: Actor): Promise<DmSettings | null>;
|
|
114
|
+
}
|
|
115
|
+
interface OpenableSpace {
|
|
116
|
+
id: string;
|
|
117
|
+
communityId: string;
|
|
118
|
+
accessRule: SpaceDetail['accessRule'];
|
|
119
|
+
}
|
|
120
|
+
interface CommunityAccess {
|
|
121
|
+
isCommunityMember(communityId: string, actor: Actor): Promise<boolean>;
|
|
122
|
+
canOpenSpace(spaceId: string, actor: Actor): Promise<boolean>;
|
|
123
|
+
canOpen(space: OpenableSpace, actor: Actor): Promise<boolean>;
|
|
124
|
+
openSpaceIds(communityId: string, actor: Actor): Promise<string[]>;
|
|
125
|
+
communityMembersAmong(orgId: string, communityId: string, orgUserIds: string[]): Promise<string[]>;
|
|
126
|
+
spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]>;
|
|
111
127
|
}
|
|
112
128
|
interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {
|
|
113
129
|
insertComment(orgId: string, comment: Comment): Promise<Comment>;
|
|
@@ -133,7 +149,8 @@ interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {
|
|
|
133
149
|
resolvedBy: string;
|
|
134
150
|
resolution: 'dismissed' | 'removed';
|
|
135
151
|
}): Promise<void>;
|
|
136
|
-
|
|
152
|
+
/** `courseIds` limits the rows to those courses; undefined means every course. */
|
|
153
|
+
listComments(orgId: string, query: ListCommentsQuery, courseIds?: string[]): Promise<Page<CommentWithContext>>;
|
|
137
154
|
}
|
|
138
155
|
/** Writes that emit events run through this scope so the row and the outbox
|
|
139
156
|
* entry commit in one transaction. */
|
|
@@ -143,9 +160,10 @@ interface DiscussionWriteScope {
|
|
|
143
160
|
outbox: OutboxAppender;
|
|
144
161
|
}
|
|
145
162
|
type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;
|
|
146
|
-
interface
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
interface AuthorPermissionReader {
|
|
164
|
+
permissionsOfMany(orgId: string, orgUserIds: string[]): Promise<Map<string, EffectivePermissions>>;
|
|
165
|
+
actorFor(orgId: string, orgUserId: string): Promise<Actor | null>;
|
|
166
|
+
systemActor(orgId: string): Actor;
|
|
149
167
|
}
|
|
150
168
|
type PostMode = 'draft' | 'submit' | 'schedule';
|
|
151
169
|
interface CreatePostInput {
|
|
@@ -188,7 +206,7 @@ declare const MODERATION_QUEUE_LIMIT = 100;
|
|
|
188
206
|
interface SpaceModerationQueue {
|
|
189
207
|
pending: PostView[];
|
|
190
208
|
flags: PostFlag[];
|
|
191
|
-
commentFlags:
|
|
209
|
+
commentFlags: FlaggedCommentView[];
|
|
192
210
|
}
|
|
193
211
|
interface PostCommentReader {
|
|
194
212
|
listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;
|
|
@@ -202,40 +220,41 @@ interface PublishedPost {
|
|
|
202
220
|
commentCount: number;
|
|
203
221
|
}
|
|
204
222
|
interface PostService {
|
|
205
|
-
visibleTitles(
|
|
223
|
+
visibleTitles(postIds: string[], actor: Actor): Promise<Record<string, string>>;
|
|
206
224
|
publishedBetween(orgId: string, communityId: string, from: Date, to: Date): Promise<PublishedPost[]>;
|
|
207
|
-
create(
|
|
208
|
-
update(
|
|
209
|
-
submit(
|
|
210
|
-
schedule(
|
|
211
|
-
unschedule(
|
|
225
|
+
create(input: CreatePostInput, actor: Actor): Promise<PostView>;
|
|
226
|
+
update(postId: string, patch: UpdatePostInput, actor: Actor): Promise<PostView>;
|
|
227
|
+
submit(postId: string, actor: Actor): Promise<PostView>;
|
|
228
|
+
schedule(postId: string, scheduledAt: Date, actor: Actor): Promise<PostView>;
|
|
229
|
+
unschedule(postId: string, actor: Actor): Promise<PostView>;
|
|
212
230
|
publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;
|
|
213
|
-
approve(
|
|
214
|
-
remove(
|
|
215
|
-
restore(
|
|
216
|
-
pin(
|
|
217
|
-
unpin(
|
|
218
|
-
closeComments(
|
|
219
|
-
openComments(
|
|
220
|
-
setReactions(
|
|
221
|
-
setReaction(
|
|
222
|
-
bookmark(
|
|
223
|
-
unbookmark(
|
|
224
|
-
flag(
|
|
225
|
-
resolveFlags(
|
|
226
|
-
spaceFeed(
|
|
227
|
-
memberFeed(
|
|
228
|
-
listOwn(
|
|
229
|
-
listBookmarked(
|
|
231
|
+
approve(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
|
|
232
|
+
remove(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
|
|
233
|
+
restore(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
|
|
234
|
+
pin(postId: string, actor: Actor): Promise<PostView>;
|
|
235
|
+
unpin(postId: string, actor: Actor): Promise<PostView>;
|
|
236
|
+
closeComments(postId: string, actor: Actor): Promise<PostView>;
|
|
237
|
+
openComments(postId: string, actor: Actor): Promise<PostView>;
|
|
238
|
+
setReactions(postId: string, enabled: boolean, actor: Actor): Promise<PostView>;
|
|
239
|
+
setReaction(postId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<PostReactions>;
|
|
240
|
+
bookmark(postId: string, actor: Actor): Promise<void>;
|
|
241
|
+
unbookmark(postId: string, actor: Actor): Promise<void>;
|
|
242
|
+
flag(postId: string, reason: string, actor: Actor): Promise<PostFlag>;
|
|
243
|
+
resolveFlags(postId: string, resolution: Extract<PostFlagResolution, 'dismissed'>, communityId: string | null, actor: Actor): Promise<void>;
|
|
244
|
+
spaceFeed(communityId: string, spaceId: string, query: FeedQuery, actor: Actor): Promise<Page<PostView>>;
|
|
245
|
+
memberFeed(communityId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>, actor: Actor): Promise<Page<PostView>>;
|
|
246
|
+
listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
247
|
+
listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
230
248
|
/** Signed URL of the post's cover, for whoever can read the post. */
|
|
231
|
-
coverFile(
|
|
232
|
-
|
|
233
|
-
|
|
249
|
+
coverFile(postId: string, actor: Actor): Promise<string>;
|
|
250
|
+
assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;
|
|
251
|
+
getPost(postId: string, actor: Actor): Promise<PostView>;
|
|
252
|
+
visible(postId: string, actor: Actor): Promise<{
|
|
234
253
|
post: Post;
|
|
235
254
|
space: SpaceDetail;
|
|
236
255
|
}>;
|
|
237
256
|
find(orgId: string, postId: string): Promise<Post | null>;
|
|
238
|
-
moderationQueue(
|
|
257
|
+
moderationQueue(communityId: string, spaceId: string, actor: Actor): Promise<SpaceModerationQueue>;
|
|
239
258
|
}
|
|
240
259
|
interface PostGuard {
|
|
241
260
|
status: PostStatus;
|
|
@@ -302,16 +321,54 @@ interface SendMessageInput {
|
|
|
302
321
|
body: string;
|
|
303
322
|
replyToId: string | null;
|
|
304
323
|
assetIds: string[];
|
|
324
|
+
clientId?: string;
|
|
305
325
|
}
|
|
306
326
|
interface ChatListItem {
|
|
307
327
|
chat: Chat;
|
|
308
328
|
participantIds: string[];
|
|
329
|
+
participants: CommentAuthor[];
|
|
309
330
|
lastReadMessageId: string | null;
|
|
310
331
|
subscription: SubscriptionView | null;
|
|
311
332
|
unread: number;
|
|
312
333
|
lastMessage: ChatLastMessage | null;
|
|
313
334
|
}
|
|
314
|
-
type ChatInboxRow = Omit<ChatListItem, 'subscription'>;
|
|
335
|
+
type ChatInboxRow = Omit<ChatListItem, 'subscription' | 'participants'>;
|
|
336
|
+
/** `requests`: true for only the pending requests to the member, false for
|
|
337
|
+
* everything else, omitted for every chat. */
|
|
338
|
+
interface ChatListQuery extends PageQuery {
|
|
339
|
+
requests?: boolean;
|
|
340
|
+
}
|
|
341
|
+
interface ReactionChange {
|
|
342
|
+
inserted: boolean;
|
|
343
|
+
changed: boolean;
|
|
344
|
+
}
|
|
345
|
+
interface ChatMemberState {
|
|
346
|
+
chatId: string;
|
|
347
|
+
unread: number;
|
|
348
|
+
lastReadMessageId: string | null;
|
|
349
|
+
subscription: SubscriptionView | null;
|
|
350
|
+
}
|
|
351
|
+
/** A member's state in each chat they are in in one community, with their
|
|
352
|
+
* unread total there. */
|
|
353
|
+
interface ChatMemberStates {
|
|
354
|
+
orgUserId: string;
|
|
355
|
+
unread: ChatUnreadSummary;
|
|
356
|
+
chats: ChatMemberState[];
|
|
357
|
+
}
|
|
358
|
+
interface LiveChat {
|
|
359
|
+
chat: Chat;
|
|
360
|
+
participantIds: string[];
|
|
361
|
+
participants: CommentAuthor[];
|
|
362
|
+
}
|
|
363
|
+
/** What the chat hub reads for each change, as current state. */
|
|
364
|
+
interface ChatLiveReads {
|
|
365
|
+
activeParticipants(orgId: string, chatId: string): Promise<string[]>;
|
|
366
|
+
messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;
|
|
367
|
+
messageReactions(orgId: string, messageId: string): Promise<ReactionCounts>;
|
|
368
|
+
/** The chat with its active participants and their profiles. */
|
|
369
|
+
liveChat(orgId: string, chatId: string): Promise<LiveChat | null>;
|
|
370
|
+
memberStates(orgId: string, communityId: string, orgUserIds: string[]): Promise<ChatMemberStates[]>;
|
|
371
|
+
}
|
|
315
372
|
interface ChatUnread {
|
|
316
373
|
count: number;
|
|
317
374
|
latest: {
|
|
@@ -337,24 +394,33 @@ interface MessageQueueEntry {
|
|
|
337
394
|
interface MessageQueue {
|
|
338
395
|
entries: MessageQueueEntry[];
|
|
339
396
|
}
|
|
340
|
-
interface ChatService {
|
|
341
|
-
startDirect(
|
|
342
|
-
startGroup(
|
|
343
|
-
addParticipant(
|
|
344
|
-
leave(
|
|
345
|
-
accept(
|
|
346
|
-
send(
|
|
347
|
-
editMessage(
|
|
348
|
-
deleteMessage(
|
|
349
|
-
setReaction(
|
|
350
|
-
markRead(
|
|
351
|
-
block(
|
|
352
|
-
unblock(
|
|
353
|
-
flagMessage(
|
|
354
|
-
resolveMessageFlags(
|
|
355
|
-
lock(
|
|
356
|
-
unlock(
|
|
357
|
-
listChats(
|
|
397
|
+
interface ChatService extends ChatLiveReads {
|
|
398
|
+
startDirect(input: StartDirectInput, actor: Actor): Promise<Chat>;
|
|
399
|
+
startGroup(input: StartGroupInput, actor: Actor): Promise<Chat>;
|
|
400
|
+
addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat>;
|
|
401
|
+
leave(chatId: string, actor: Actor): Promise<Chat>;
|
|
402
|
+
accept(chatId: string, actor: Actor): Promise<Chat>;
|
|
403
|
+
send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView>;
|
|
404
|
+
editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView>;
|
|
405
|
+
deleteMessage(messageId: string, actor: Actor): Promise<MessageView>;
|
|
406
|
+
setReaction(messageId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<MessageReactions>;
|
|
407
|
+
markRead(chatId: string, messageId: string, actor: Actor): Promise<void>;
|
|
408
|
+
block(communityId: string, orgUserId: string, actor: Actor): Promise<void>;
|
|
409
|
+
unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void>;
|
|
410
|
+
flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag>;
|
|
411
|
+
resolveMessageFlags(messageId: string, resolution: Extract<MessageFlagResolution, 'dismissed'>, communityId: string | null, actor: Actor): Promise<void>;
|
|
412
|
+
lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;
|
|
413
|
+
unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;
|
|
414
|
+
listChats(communityId: string, query: ChatListQuery, actor: Actor): Promise<Page<ChatListItem>>;
|
|
415
|
+
/** One live stream of the member's chat events in the community. */
|
|
416
|
+
watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>>;
|
|
417
|
+
chat(chatId: string, actor: Actor): Promise<ChatListItem>;
|
|
418
|
+
directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem>;
|
|
419
|
+
unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary>;
|
|
420
|
+
typing(chatId: string, actor: Actor): Promise<void>;
|
|
421
|
+
/** The messages as every participant sees them: a deleted message without
|
|
422
|
+
* its body, attachments or mentions, and no viewer reaction. */
|
|
423
|
+
messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;
|
|
358
424
|
chatSummary(orgId: string, chatId: string): Promise<{
|
|
359
425
|
chat: Chat;
|
|
360
426
|
participants: ChatUser[];
|
|
@@ -363,12 +429,13 @@ interface ChatService {
|
|
|
363
429
|
unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
364
430
|
findMessage(orgId: string, messageId: string): Promise<Message | null>;
|
|
365
431
|
isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;
|
|
366
|
-
titlesFor(
|
|
367
|
-
messages(
|
|
432
|
+
titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>>;
|
|
433
|
+
messages(chatId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>, actor: Actor): Promise<Page<MessageView>>;
|
|
368
434
|
/** Signed URL of a message attachment: for the chat's participants while the
|
|
369
|
-
* message stands (
|
|
370
|
-
|
|
371
|
-
|
|
435
|
+
* message stands (moderators also after deletion), and for moderators while
|
|
436
|
+
* it is flagged. */
|
|
437
|
+
attachmentFile(chatId: string, messageId: string, assetId: string, actor: Actor): Promise<string>;
|
|
438
|
+
messageQueue(communityId: string, actor: Actor): Promise<MessageQueue>;
|
|
372
439
|
}
|
|
373
440
|
interface ChatRepository {
|
|
374
441
|
lockDirectPair(orgId: string, communityId: string, orgUserId: string, otherOrgUserId: string): Promise<void>;
|
|
@@ -376,12 +443,22 @@ interface ChatRepository {
|
|
|
376
443
|
insertChat(orgId: string, chat: Chat): Promise<Chat>;
|
|
377
444
|
updateChat(orgId: string, id: string, patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>): Promise<Chat | null>;
|
|
378
445
|
findChat(orgId: string, id: string): Promise<Chat | null>;
|
|
379
|
-
listForUser(orgId: string, communityId: string, orgUserId: string, query:
|
|
446
|
+
listForUser(orgId: string, communityId: string, orgUserId: string, query: ChatListQuery): Promise<Page<ChatInboxRow>>;
|
|
447
|
+
/** The member's inbox row for one chat they are in, or null. */
|
|
448
|
+
inboxRow(orgId: string, chatId: string, orgUserId: string): Promise<ChatInboxRow | null>;
|
|
380
449
|
participants(orgId: string, chatId: string): Promise<ChatUser[]>;
|
|
450
|
+
activeParticipants(orgId: string, chatId: string): Promise<string[]>;
|
|
381
451
|
join(orgId: string, user: ChatUser): Promise<void>;
|
|
382
452
|
markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;
|
|
383
|
-
|
|
453
|
+
/** Moves the read mark to the message only when it is newer (by created
|
|
454
|
+
* time, then id) than the current one. True when it moved. */
|
|
455
|
+
setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<boolean>;
|
|
384
456
|
unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
457
|
+
/** Unread messages across the member's chats in the community that are
|
|
458
|
+
* neither muted at `at` nor pending requests to them, plus those requests. */
|
|
459
|
+
unreadSummary(orgId: string, communityId: string, orgUserId: string, at: Date): Promise<ChatUnreadSummary>;
|
|
460
|
+
memberStates(orgId: string, communityId: string, orgUserIds: string[], at: Date): Promise<ChatMemberStates[]>;
|
|
461
|
+
findByClientId(orgId: string, chatId: string, orgUserId: string, clientId: string): Promise<Message | null>;
|
|
385
462
|
insertMessage(orgId: string, message: Message): Promise<Message>;
|
|
386
463
|
updateMessage(orgId: string, id: string, patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>): Promise<Message | null>;
|
|
387
464
|
findMessage(orgId: string, id: string): Promise<Message | null>;
|
|
@@ -389,7 +466,7 @@ interface ChatRepository {
|
|
|
389
466
|
attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;
|
|
390
467
|
attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;
|
|
391
468
|
blockedWith(orgId: string, communityId: string, orgUserId: string, otherIds: string[]): Promise<string[]>;
|
|
392
|
-
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
469
|
+
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<ReactionChange>;
|
|
393
470
|
reactionsOf(orgId: string, messageIds: string[], viewerId: string): Promise<Record<string, MessageReactions>>;
|
|
394
471
|
block(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
395
472
|
unblock(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
@@ -428,10 +505,15 @@ type SubscriptionInput = {
|
|
|
428
505
|
type MuteInput = Exclude<SubscriptionInput, {
|
|
429
506
|
state: 'following';
|
|
430
507
|
}>;
|
|
508
|
+
interface SubscriptionWriteScope {
|
|
509
|
+
subscriptions: SubscriptionsRepository;
|
|
510
|
+
outbox: OutboxAppender;
|
|
511
|
+
}
|
|
512
|
+
type SubscriptionUnitOfWork = UnitOfWork<SubscriptionWriteScope>;
|
|
431
513
|
interface SubscriptionService {
|
|
432
|
-
setPost(
|
|
433
|
-
setSpace(
|
|
434
|
-
setChat(
|
|
514
|
+
setPost(postId: string, input: SubscriptionInput, actor: Actor): Promise<void>;
|
|
515
|
+
setSpace(spaceId: string, input: MuteInput, actor: Actor): Promise<void>;
|
|
516
|
+
setChat(chatId: string, input: MuteInput, actor: Actor): Promise<void>;
|
|
435
517
|
viewerSubscriptions(orgId: string, orgUserId: string, itemType: SubscriptionItemType, itemIds: string[], at: Date): Promise<Record<string, SubscriptionView>>;
|
|
436
518
|
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
437
519
|
mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;
|
|
@@ -452,5 +534,5 @@ interface MentionsRepository {
|
|
|
452
534
|
removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;
|
|
453
535
|
}
|
|
454
536
|
//#endregion
|
|
455
|
-
export { ActivityComments, ActivityGetter, type Actor, AuthorProfileReader, AuthorRecord,
|
|
537
|
+
export { ActivityComments, ActivityGetter, type Actor, AuthorPermissionReader, AuthorProfileReader, AuthorRecord, BookmarkQuery, ChatInboxRow, ChatListItem, ChatListQuery, ChatLiveReads, ChatMemberState, ChatMemberStates, ChatRepository, ChatService, ChatSummary, ChatUnitOfWork, ChatUnread, ChatWriteScope, CommentReactions, CommentWithContext, CommentWithin, CommunityAccess, ContentAccessReader, CreatePostInput, DigestRunRepository, DigestScheduleReader, DigestService, DigestUnitOfWork, DigestWriteScope, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, DiscussionWriteScope, FeedQuery, ItemTitle, ItemTitles, LiveChat, MODERATION_QUEUE_LIMIT, MentionsRepository, MessageQueue, MessageQueueEntry, MessageReactions, MuteInput, OpenableSpace, PageQuery, PostCommentInput, PostCommentReader, PostGuard, PostMode, PostPatch, PostReactions, PostRepository, PostService, PostThreadsQuery, PostUnitOfWork, PostWriteScope, PublishedPost, ReactionChange, SendMessageInput, SpaceModerationQueue, SpaceReader, StartDirectInput, StartGroupInput, SubscriptionInput, SubscriptionService, SubscriptionUnitOfWork, SubscriptionWriteScope, SubscriptionsRepository, UpdatePostInput };
|
|
456
538
|
//# sourceMappingURL=ports.d.ts.map
|