@liveblocks/node 3.14.0-pre2 → 3.14.0-pre3
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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-node/dist/index.cjs","../src/index.ts","../src/version.ts","../src/client.ts","../src/lib/itertools.ts","../src/lib/ndjson.ts","../src/Session.ts","../src/utils.ts","../src/webhooks.ts"],"names":["url","LiveObject"],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-node/dist/index.cjs","sourcesContent":[null,"import { detectDupes } from \"@liveblocks/core\";\n\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nexport type {\n AiCopilot,\n CreateAiCopilotOptions,\n CreateFileKnowledgeSourceOptions,\n CreateRoomOptions,\n CreateWebKnowledgeSourceOptions,\n GetAiCopilotsOptions,\n GetInboxNotificationsOptions,\n GetKnowledgeSourcesOptions,\n GetRoomsOptions,\n GetWebKnowledgeSourceLinksOptions,\n InboxNotificationsQueryCriteria,\n KnowledgeSource,\n LiveblocksOptions,\n MassMutateStorageCallback,\n MassMutateStorageOptions,\n MutateStorageCallback,\n MutateStorageOptions,\n Page,\n PaginationOptions,\n RoomAccesses,\n RoomData,\n RoomPermission,\n RoomsQueryCriteria,\n RoomUser,\n ThreadParticipants,\n UpdateAiCopilotOptions,\n UpdateRoomOptions,\n UpsertRoomOptions,\n WebKnowledgeSourceLink,\n} from \"./client\";\nexport { Liveblocks, LiveblocksError } from \"./client\";\nexport type {\n CommentCreatedEvent,\n CommentDeletedEvent,\n CommentEditedEvent,\n CommentReactionAdded,\n CommentReactionRemoved,\n CustomNotificationEvent,\n NotificationEvent,\n RoomCreatedEvent,\n RoomDeletedEvent,\n StorageUpdatedEvent,\n TextMentionNotificationEvent,\n ThreadCreatedEvent,\n ThreadDeletedEvent,\n ThreadMarkedAsResolvedEvent,\n ThreadMarkedAsUnresolvedEvent,\n ThreadMetadataUpdatedEvent,\n ThreadNotificationEvent,\n UserEnteredEvent,\n UserLeftEvent,\n WebhookEvent,\n WebhookRequest,\n YDocUpdatedEvent,\n} from \"./webhooks\";\nexport {\n isCustomNotificationEvent,\n isTextMentionNotificationEvent,\n isThreadNotificationEvent,\n WebhookHandler,\n} from \"./webhooks\";\nexport type {\n CommentBody,\n CommentBodyBlockElement,\n CommentBodyElement,\n CommentBodyInlineElement,\n CommentBodyLink,\n CommentBodyLinkElementArgs,\n CommentBodyMention,\n CommentBodyMentionElementArgs,\n CommentBodyParagraph,\n CommentBodyParagraphElementArgs,\n CommentBodyText,\n CommentBodyTextElementArgs,\n CommentData,\n CommentUserReaction,\n IUserInfo,\n Json,\n JsonArray,\n JsonObject,\n JsonScalar,\n LiveStructure,\n Lson,\n LsonObject,\n PlainLsonObject,\n ResolveUsersArgs,\n StringifyCommentBodyElements,\n StringifyCommentBodyOptions,\n ThreadData,\n User,\n} from \"@liveblocks/core\";\nexport {\n getMentionsFromCommentBody,\n isNotificationChannelEnabled,\n LiveList,\n LiveMap,\n LiveObject,\n stringifyCommentBody,\n} from \"@liveblocks/core\";\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/node\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n","/**\n * NOTE: only types should be imported from @liveblocks/core.\n * This is because this package is made to be used in Node.js, and\n * @liveblocks/core has browser-specific code.\n */\nimport type {\n Awaitable,\n BaseMetadata,\n BaseUserMeta,\n ClientMsg,\n ClientWireOp,\n CommentBody,\n CommentData,\n CommentDataPlain,\n CommentUserReaction,\n CommentUserReactionPlain,\n DAD,\n DCM,\n DE,\n DS,\n DTM,\n DU,\n GroupData,\n GroupDataPlain,\n GroupScopes,\n IdTuple,\n InboxNotificationData,\n InboxNotificationDataPlain,\n Json,\n JsonObject,\n KDAD,\n LsonObject,\n NotificationSettings,\n NotificationSettingsPlain,\n Op,\n OptionalTupleUnless,\n PartialNotificationSettings,\n PartialUnless,\n Patchable,\n PlainLsonObject,\n QueryMetadata,\n QueryParams,\n RoomSubscriptionSettings,\n SerializedCrdt,\n StorageUpdate,\n SubscriptionData,\n SubscriptionDataPlain,\n ThreadData,\n ThreadDataPlain,\n ToImmutable,\n URLSafeString,\n UserRoomSubscriptionSettings,\n UserSubscriptionData,\n UserSubscriptionDataPlain,\n} from \"@liveblocks/core\";\nimport {\n checkBounds,\n ClientMsgCode,\n convertToCommentData,\n convertToCommentUserReaction,\n convertToGroupData,\n convertToInboxNotificationData,\n convertToSubscriptionData,\n convertToThreadData,\n convertToUserSubscriptionData,\n createManagedPool,\n createNotificationSettings,\n isPlainObject,\n LiveObject,\n makeAbortController,\n objectToQuery,\n tryParseJson,\n url,\n urljoin,\n} from \"@liveblocks/core\";\n\nimport { asyncConsume, runConcurrently } from \"./lib/itertools\";\nimport { LineStream, NdJsonStream } from \"./lib/ndjson\";\nimport { Session } from \"./Session\";\nimport {\n assertNonEmpty,\n assertSecretKey,\n fetchPolyfill,\n getBaseUrl,\n normalizeStatusCode,\n} from \"./utils\";\n\n// Recursively convert ReadonlyMap<K, V> to { [key: K]: V }\ntype SerializeMaps<T> =\n T extends ReadonlyMap<infer K, infer V>\n ? K extends string\n ? { readonly [P in K]: SerializeMaps<V> }\n : { readonly [key: string]: SerializeMaps<V> }\n : T extends object\n ? { readonly [P in keyof T]: SerializeMaps<T[P]> }\n : T;\n\ntype ToSimplifiedJson<S extends LsonObject> = LsonObject extends S\n ? JsonObject\n : // ToImmutable converts LiveMap instances to ReadonlyMap versions, but\n // the \"simplified JSON\" format actually requires (because of serialization)\n // and converts the maps to plain objects.\n SerializeMaps<ToImmutable<S>>;\n\nexport type LiveblocksOptions = {\n /**\n * The Liveblocks secret key. Must start with \"sk_\".\n * Get it from https://liveblocks.io/dashboard/apikeys\n */\n secret: string;\n\n /** Point the client to an alternative Liveblocks server. */\n baseUrl?: string;\n};\n\ntype DateToString<T> = {\n [P in keyof T]: Date extends T[P] ? string : T[P];\n};\n\nexport type CreateSessionOptions<U extends BaseUserMeta = DU> =\n //\n PartialUnless<U[\"info\"], { userInfo: U[\"info\"] }> & {\n tenantId?: string;\n };\n\nexport type IdentifyUserOptions<U extends BaseUserMeta = DU> =\n //\n PartialUnless<U[\"info\"], { userInfo: U[\"info\"] }>;\n\nexport type AuthResponse = {\n status: number;\n body: string;\n error?: Error;\n};\n\ntype Identity = {\n userId: string;\n groupIds: string[];\n tenantId?: string;\n};\n\nexport type ThreadParticipants = {\n participantIds: string[];\n};\n\nexport type CreateThreadOptions<\n TM extends BaseMetadata,\n CM extends BaseMetadata,\n> = {\n roomId: string;\n data: {\n comment: {\n userId: string;\n createdAt?: Date;\n body: CommentBody;\n } & PartialUnless<CM, { metadata: CM }>; // Comment metadata (data.comment.metadata)\n } & PartialUnless<TM, { metadata: TM }>; // Thread metadata (data.metadata)\n};\n\nexport type CreateCommentOptions<CM extends BaseMetadata> = {\n roomId: string;\n threadId: string;\n data: {\n userId: string;\n createdAt?: Date;\n body: CommentBody;\n } & PartialUnless<CM, { metadata: CM }>;\n};\n\nexport type RoomPermission =\n | []\n | [\"room:write\"]\n | [\"room:read\", \"room:presence:write\"]\n | [\"room:read\", \"room:presence:write\", \"comments:write\"];\nexport type RoomAccesses = Record<\n string,\n | [\"room:write\"]\n | [\"room:read\", \"room:presence:write\"]\n | [\"room:read\", \"room:presence:write\", \"comments:write\"]\n>;\nexport type RoomMetadata = Record<string, string | string[]>;\ntype QueryRoomMetadata = Record<string, string>;\n\nexport type RoomData = {\n type: \"room\";\n id: string;\n createdAt: Date;\n lastConnectionAt?: Date;\n defaultAccesses: RoomPermission;\n usersAccesses: RoomAccesses;\n groupsAccesses: RoomAccesses;\n metadata: RoomMetadata;\n};\n\ntype RoomDataPlain = DateToString<RoomData>;\n\ntype AiCopilotProviderSettings = {\n maxTokens?: number;\n temperature?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n maxRetries?: number;\n};\n\ntype OpenAiModel =\n | \"o1\"\n | \"o1-mini\"\n | \"o3\"\n | \"o3-mini\"\n | \"o4-mini\"\n | \"gpt-4.1\"\n | \"gpt-4.1-mini\"\n | \"gpt-4.1-nano\"\n | \"gpt-4o\"\n | \"gpt-4o-mini\"\n | \"gpt-4-turbo\"\n | \"gpt-4\"\n | \"gpt-5\"\n | \"gpt-5-mini\"\n | \"gpt-5-nano\"\n | \"gpt-5-chat-latest\"\n | \"gpt-5.1\"\n | \"gpt-5.1-mini\"\n | \"gpt-5.1-chat-latest\";\n\ntype OpenAiProviderOptions = {\n openai: {\n reasoningEffort?: \"low\" | \"medium\" | \"high\";\n webSearch?: {\n allowedDomains?: string[];\n };\n };\n};\n\ntype AnthropicModel =\n | \"claude-sonnet-4-5-20250929\"\n | \"claude-haiku-4-5-20251001\"\n | \"claude-opus-4-1-20250805\"\n | \"claude-4-opus-20250514\"\n | \"claude-4-sonnet-20250514\"\n | \"claude-3-7-sonnet-20250219\"\n | \"claude-3-5-sonnet-latest\"\n | \"claude-3-5-haiku-latest\"\n | \"claude-3-opus-latest\";\ntype AnthropicProviderOptions = {\n anthropic: {\n thinking?:\n | {\n type: \"enabled\";\n budgetTokens: number;\n }\n | {\n type: \"disabled\";\n };\n webSearch?: {\n allowedDomains?: string[];\n };\n };\n};\n\ntype GoogleModel =\n | \"gemini-2.5-flash\"\n | \"gemini-2.5-pro\"\n | \"gemini-2.0-flash-001\"\n | \"gemini-1.5-flash\"\n | \"gemini-1.5-pro\";\ntype GoogleProviderOptions = {\n google: {\n thinkingConfig?: {\n thinkingBudget?: number;\n };\n };\n};\n\nexport type AiCopilot = {\n type: \"copilot\";\n id: string;\n name: string;\n description?: string;\n\n systemPrompt: string;\n knowledgePrompt?: string;\n alwaysUseKnowledge: boolean;\n\n createdAt: Date;\n updatedAt: Date;\n lastUsedAt?: Date;\n\n settings?: AiCopilotProviderSettings;\n} & (\n | {\n provider: \"openai\";\n providerModel: OpenAiModel;\n providerOptions?: OpenAiProviderOptions;\n }\n | {\n provider: \"anthropic\";\n providerModel: AnthropicModel;\n providerOptions?: AnthropicProviderOptions;\n }\n | {\n provider: \"google\";\n providerModel: GoogleModel;\n providerOptions?: GoogleProviderOptions;\n }\n | {\n provider: \"openai-compatible\";\n providerModel: string;\n compatibleProviderName: string;\n providerBaseUrl: string;\n }\n);\n\ntype AiCopilotPlain = DateToString<AiCopilot>;\nexport type RoomUser<U extends BaseUserMeta = DU> = {\n type: \"user\";\n id: string | null;\n connectionId: number;\n info: U[\"info\"];\n};\n\ntype RequestStorageMutationResponse = {\n actor: number;\n nodes: IdTuple<SerializedCrdt>[];\n};\n\nexport type MutateStorageCallback = (context: {\n root: LiveObject<S>;\n}) => Awaitable<void>;\nexport type MutateStorageOptions = RequestOptions;\n\nexport type MassMutateStorageCallback = (context: {\n room: RoomData;\n root: LiveObject<S>;\n}) => Awaitable<void>;\n\n// prettier-ignore\nexport type MassMutateStorageOptions =\n & MutateStorageOptions\n & { concurrency?: number };\n\n// NOTE: We should _never_ rely on using the default types (DS, DU, DE, ...)\n// inside the Liveblocks implementation. We should only rely on the type\n// \"params\" (S, U, E, ...) instead, where the concrete type is bound to the\n// class. In this case, we're not doing that at the class level, but globally.\n// The idea is that we \"start small\" and could always add them in at the class\n// level later.\ntype E = DE;\ntype TM = DTM;\ntype CM = DCM;\ntype S = DS;\ntype U = DU;\n\nexport type RoomsQueryCriteria = {\n tenantId?: string;\n userId?: string;\n groupIds?: string[];\n /**\n * The query to filter rooms by. It is based on our query language.\n * @example\n * ```\n * {\n * query: 'metadata[\"status\"]:\"open\" AND roomId^\"liveblocks:\"'\n * }\n * ```\n * @example\n * ```\n * {\n * query: {\n * metadata: {\n * status: \"open\",\n * },\n * roomId: {\n * startsWith: \"liveblocks:\"\n * }\n * }\n * }\n * ```\n */\n query?:\n | string\n | {\n metadata?: QueryRoomMetadata;\n roomId?: {\n startsWith: string;\n };\n };\n};\n\nexport type InboxNotificationsQueryCriteria = {\n userId: string;\n tenantId?: string;\n /**\n * The query to filter inbox notifications by. It is based on our query language.\n *\n * @example\n * ```\n * {\n * query: \"unread:true\"\n * }\n * ```\n *\n * @example\n * ```\n * {\n * query: {\n * unread: true\n * }\n * }\n * ```\n *\n */\n query?: string | { unread: boolean };\n};\n\nexport type PaginationOptions = {\n limit?: number;\n startingAfter?: string;\n};\n\nexport type Page<T> = {\n nextCursor: string | null;\n data: T[];\n};\n\n// prettier-ignore\nexport type GetRoomsOptions =\n & RoomsQueryCriteria\n & PaginationOptions\n\n// prettier-ignore\nexport type GetInboxNotificationsOptions =\n & InboxNotificationsQueryCriteria\n & PaginationOptions;\n\nexport type CreateRoomOptions = {\n defaultAccesses: RoomPermission;\n groupsAccesses?: RoomAccesses;\n usersAccesses?: RoomAccesses;\n metadata?: RoomMetadata;\n tenantId?: string;\n\n /**\n * @private Preferred storage engine version to use when creating the\n * room. Only takes effect if the room doesn't exist yet. Version\n * 2 supports streaming and will become the default in the future.\n */\n engine?: 1 | 2;\n};\n\nexport type UpdateRoomOptions = {\n defaultAccesses?: RoomPermission | null;\n groupsAccesses?: Record<\n string,\n [\"room:write\"] | [\"room:read\", \"room:presence:write\"] | null\n >;\n usersAccesses?: Record<\n string,\n [\"room:write\"] | [\"room:read\", \"room:presence:write\"] | null\n >;\n metadata?: Record<string, string | string[] | null>;\n};\n\nexport type UpsertRoomOptions = {\n update: UpdateRoomOptions;\n create?: CreateRoomOptions;\n};\n\nexport type GetAiCopilotsOptions = PaginationOptions;\n\nexport type CreateAiCopilotOptions = {\n name: string;\n description?: string;\n\n systemPrompt: string;\n knowledgePrompt?: string;\n alwaysUseKnowledge?: boolean;\n\n settings?: AiCopilotProviderSettings;\n\n providerApiKey: string;\n} & (\n | {\n provider: \"openai\";\n providerModel: OpenAiModel;\n\n providerOptions?: OpenAiProviderOptions;\n }\n | {\n provider: \"anthropic\";\n providerModel: AnthropicModel;\n providerOptions?: AnthropicProviderOptions;\n }\n | {\n provider: \"google\";\n providerModel: GoogleModel;\n providerOptions?: GoogleProviderOptions;\n }\n | {\n provider: \"openai-compatible\";\n providerModel: string;\n compatibleProviderName: string;\n providerBaseUrl: string;\n }\n);\n\nexport type UpdateAiCopilotOptions = {\n name?: string;\n description?: string | null;\n\n systemPrompt?: string;\n knowledgePrompt?: string | null;\n alwaysUseKnowledge?: boolean;\n\n settings?: AiCopilotProviderSettings | null;\n\n providerApiKey?: string;\n} & (\n | {\n provider?: \"openai\";\n /**\n * The provider model to use.\n */\n providerModel?: OpenAiModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs.\n */\n providerOptions?: OpenAiProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"anthropic\";\n /**\n * The provider model to use.\n */\n providerModel?: AnthropicModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs..\n */\n providerOptions?: AnthropicProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"google\";\n /**\n * The provider model to use.\n */\n providerModel?: GoogleModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs.\n */\n providerOptions?: GoogleProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"openai-compatible\";\n /**\n * The provider model to use.\n */\n providerModel?: string;\n compatibleProviderName?: string;\n providerBaseUrl?: string;\n providerOptions?: never;\n }\n);\nexport type CreateWebKnowledgeSourceOptions = {\n copilotId: string;\n url: string;\n type: \"individual_link\" | \"crawl\" | \"sitemap\";\n};\n\nexport type CreateFileKnowledgeSourceOptions = {\n copilotId: string;\n file: File;\n};\n\nexport type GetKnowledgeSourcesOptions = {\n copilotId: string;\n} & PaginationOptions;\n\nexport type GetWebKnowledgeSourceLinksOptions = {\n copilotId: string;\n knowledgeSourceId: string;\n} & PaginationOptions;\n\ntype KnowledgeSourcePlain = DateToString<KnowledgeSource>;\n\nexport type KnowledgeSource = (\n | {\n type: \"ai-knowledge-web-source\";\n link: {\n url: string;\n type: \"individual_link\" | \"crawl\" | \"sitemap\";\n };\n }\n | {\n type: \"ai-knowledge-file-source\";\n file: {\n name: string;\n mimeType: string;\n };\n }\n) & {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n lastIndexedAt: Date;\n} & (\n | { status: \"ingesting\" | \"ready\" }\n | { status: \"error\"; errorMessage: string }\n );\n\ntype WebKnowledgeSourceLinkPlain = DateToString<WebKnowledgeSourceLink>;\n\nexport type WebKnowledgeSourceLink = {\n id: string;\n url: string;\n status: \"ingesting\" | \"ready\" | \"error\";\n createdAt: Date;\n lastIndexedAt: Date;\n};\n\nexport type RequestOptions = {\n signal?: AbortSignal;\n};\n\n/**\n * Converts ISO-formatted date strings to Date instances on RoomDataPlain\n * values.\n */\nfunction inflateRoomData(room: RoomDataPlain): RoomData {\n const createdAt = new Date(room.createdAt);\n const lastConnectionAt = room.lastConnectionAt\n ? new Date(room.lastConnectionAt)\n : undefined;\n\n return {\n ...room,\n createdAt,\n lastConnectionAt,\n };\n}\n\nfunction inflateAiCopilot(copilot: AiCopilotPlain): AiCopilot {\n return {\n ...copilot,\n createdAt: new Date(copilot.createdAt),\n updatedAt: new Date(copilot.updatedAt),\n lastUsedAt: copilot.lastUsedAt ? new Date(copilot.lastUsedAt) : undefined,\n };\n}\n\nfunction inflateKnowledgeSource(source: KnowledgeSourcePlain): KnowledgeSource {\n return {\n ...source,\n createdAt: new Date(source.createdAt),\n updatedAt: new Date(source.updatedAt),\n lastIndexedAt: new Date(source.lastIndexedAt),\n };\n}\n\nfunction inflateWebKnowledgeSourceLink(\n link: WebKnowledgeSourceLinkPlain\n): WebKnowledgeSourceLink {\n return {\n ...link,\n createdAt: new Date(link.createdAt),\n lastIndexedAt: new Date(link.lastIndexedAt),\n };\n}\n\n/**\n * Interact with the Liveblocks API from your Node.js backend.\n */\nexport class Liveblocks {\n readonly #secret: string;\n readonly #baseUrl: URL;\n\n /**\n * Interact with the Liveblocks API from your Node.js backend.\n */\n constructor(options: LiveblocksOptions) {\n const options_ = options as Record<string, unknown>;\n const secret = options_.secret;\n assertSecretKey(secret, \"secret\");\n this.#secret = secret;\n this.#baseUrl = new URL(getBaseUrl(options.baseUrl));\n }\n\n async #post(\n path: URLSafeString,\n json: Json,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": \"application/json\",\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(json),\n signal: options?.signal,\n });\n return res;\n }\n\n async #putBinary(\n path: URLSafeString,\n body: Uint8Array,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": \"application/octet-stream\",\n };\n const fetch = await fetchPolyfill();\n return await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n signal: options?.signal,\n });\n }\n\n async #delete(\n path: URLSafeString,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"DELETE\",\n headers,\n signal: options?.signal,\n });\n return res;\n }\n\n async #get(\n path: URLSafeString,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"GET\",\n headers,\n signal: options?.signal,\n });\n return res;\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Authentication\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Prepares a new session to authorize a user to access Liveblocks.\n *\n * IMPORTANT:\n * Always make sure that you trust the user making the request to your\n * backend before calling .prepareSession()!\n *\n * @param userId Tell Liveblocks the user ID of the user to authorize. Must\n * uniquely identify the user account in your system. The uniqueness of this\n * value will determine how many MAUs will be counted/billed.\n *\n * @param tenantId (optional) The tenant ID to authorize the user for.\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n *\n */\n prepareSession(\n userId: string,\n ...rest: OptionalTupleUnless<\n CreateSessionOptions<U>,\n [options: CreateSessionOptions<U>]\n >\n ): Session {\n const options = rest[0];\n return new Session(\n this.#post.bind(this),\n userId,\n options?.userInfo,\n options?.tenantId\n );\n }\n\n /**\n * Call this to authenticate the user as an actor you want to allow to use\n * Liveblocks.\n *\n * You should use this method only if you want to manage your permissions\n * through the Liveblocks Permissions API. This method is more complicated to\n * set up, but allows for finer-grained specification of permissions.\n *\n * Calling `.identifyUser()` only lets you securely identify a user (and what\n * groups they belong to). What permissions this user will end up having is\n * determined by whatever permissions you assign the user/group in your\n * Liveblocks account, through the Permissions API:\n * https://liveblocks.io/docs/rooms/permissions\n *\n * IMPORTANT:\n * Always verify that you trust the user making the request before calling\n * .identifyUser()!\n *\n * @param identity Tell Liveblocks the user ID of the user to authenticate.\n * Must uniquely identify the user account in your system. The uniqueness of\n * this value will determine how many MAUs will be counted/billed.\n *\n * If you also want to assign which groups this user belongs to, use the\n * object form and specify the `groupIds` property. Those `groupIds` should\n * match the groupIds you assigned permissions to via the Liveblocks\n * Permissions API, see\n * https://liveblocks.io/docs/rooms/permissions#permissions-levels-groups-accesses-example\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n */\n // These fields define the security identity of the user. Whatever you pass in here will define which\n public async identifyUser(\n identity:\n | string // Shorthand for userId\n | Identity,\n ...rest: OptionalTupleUnless<\n IdentifyUserOptions<U>,\n [options: IdentifyUserOptions<U>]\n >\n ): Promise<AuthResponse> {\n const options = rest[0];\n\n const path = url`/v2/identify-user`;\n\n const { userId, groupIds, tenantId } =\n typeof identity === \"string\"\n ? { userId: identity, groupIds: undefined, tenantId: undefined }\n : identity;\n\n assertNonEmpty(userId, \"userId\");\n\n const body = {\n userId,\n groupIds,\n tenantId,\n userInfo: options?.userInfo,\n };\n\n try {\n const resp = await this.#post(path, body);\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: `Call to ${urljoin(\n this.#baseUrl,\n path\n )} failed. See \"error\" for more information.`,\n error: er as Error | undefined,\n };\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Room\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns a list of your rooms. The rooms are returned sorted by creation date, from newest to oldest. You can filter rooms by metadata, users accesses and groups accesses.\n * @param params.limit (optional) A limit on the number of rooms to be returned. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param params.userId (optional) A filter on users accesses.\n * @param params.metadata (optional) A filter on metadata. Multiple metadata keys can be used to filter rooms.\n * @param params.groupIds (optional) A filter on groups accesses. Multiple groups can be used.\n * @param params.tenantId (optional) A filter on tenant ID.\n * @param params.query (optional) A query to filter rooms by. It is based on our query language. You can filter by metadata and room ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of rooms.\n */\n public async getRooms(\n params: GetRoomsOptions = {},\n options?: RequestOptions\n ): Promise<Page<RoomData>> {\n const path = url`/v2/rooms`;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const queryParams = {\n limit: params.limit,\n startingAfter: params.startingAfter,\n userId: params.userId,\n tenantId: params.tenantId,\n groupIds: params.groupIds ? params.groupIds.join(\",\") : undefined,\n query,\n };\n\n const res = await this.#get(path, queryParams, options);\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<RoomDataPlain>;\n const rooms: RoomData[] = page.data.map(inflateRoomData);\n return {\n ...page,\n data: rooms,\n };\n }\n\n /**\n * Iterates over all rooms that match the given criteria.\n *\n * The difference with .getRooms() is that pagination will happen\n * automatically under the hood, using the given `pageSize`.\n *\n * @param criteria.userId (optional) A filter on users accesses.\n * @param criteria.groupIds (optional) A filter on groups accesses. Multiple groups can be used.\n * @param criteria.query.roomId (optional) A filter by room ID.\n * @param criteria.query.metadata (optional) A filter by metadata.\n *\n * @param options.pageSize (optional) The page size to use for each request.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n async *iterRooms(\n criteria: RoomsQueryCriteria,\n options?: RequestOptions & { pageSize?: number }\n ): AsyncGenerator<RoomData> {\n // TODO Dry up this async iterable implementation for pagination\n const { signal } = options ?? {};\n const pageSize = checkBounds(\"pageSize\", options?.pageSize ?? 40, 20);\n\n let cursor: string | undefined = undefined;\n while (true) {\n const { nextCursor, data } = await this.getRooms(\n { ...criteria, startingAfter: cursor, limit: pageSize },\n { signal }\n );\n for (const item of data) {\n yield item;\n }\n if (!nextCursor) {\n break;\n }\n cursor = nextCursor;\n }\n }\n\n /**\n * Creates a new room with the given id.\n * @param roomId The id of the room to create.\n * @param params.defaultAccesses The default accesses for the room.\n * @param params.groupsAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param params.tenantId (optional) The tenant ID to create the room for.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created room.\n */\n public async createRoom(\n roomId: string,\n params: CreateRoomOptions,\n options?: RequestOptions & { idempotent?: boolean }\n ): Promise<RoomData> {\n const {\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n metadata,\n tenantId,\n engine,\n } = params;\n\n const res = await this.#post(\n options?.idempotent ? url`/v2/rooms?idempotent` : url`/v2/rooms`,\n {\n id: roomId,\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n tenantId,\n metadata,\n engine,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Returns a room with the given id, or creates one with the given creation\n * options if it doesn't exist yet.\n *\n * @param roomId The id of the room.\n * @param params.defaultAccesses The default accesses for the room if the room will be created.\n * @param params.groupsAccesses (optional) The group accesses for the room if the room will be created. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room if the room will be created. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room if the room will be created. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param params.tenantId (optional) The tenant ID to create the room for.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room.\n */\n public async getOrCreateRoom(\n roomId: string,\n params: CreateRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n return await this.createRoom(roomId, params, {\n ...options,\n idempotent: true,\n });\n }\n\n /**\n * Updates or creates a new room with the given properties.\n *\n * @param roomId The id of the room to update or create.\n * @param update The fields to update. These values will be updated when the room exists, or set when the room does not exist and gets created. Must specify at least one key.\n * @param create (optional) The fields to only use when the room does not exist and will be created. When the room already exists, these values are ignored.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room.\n */\n public async upsertRoom(\n roomId: string,\n params: UpsertRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/upsert`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Returns a room with the given id.\n * @param roomId The id of the room to return.\n * @returns The room with the given id.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<RoomData> {\n const res = await this.#get(url`/v2/rooms/${roomId}`, undefined, options);\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Updates specific properties of a room. It’s not necessary to provide the entire room’s information.\n * Setting a property to `null` means to delete this property.\n * @param roomId The id of the room to update.\n * @param params.defaultAccesses (optional) The default accesses for the room.\n * @param params.groupsAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated room.\n */\n public async updateRoom(\n roomId: string,\n params: UpdateRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n const { defaultAccesses, groupsAccesses, usersAccesses, metadata } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}`,\n {\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n metadata,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Deletes a room with the given id. A deleted room is no longer accessible from the API or the dashboard and it cannot be restored.\n * @param roomId The id of the room to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/rooms/${roomId}`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Prepares a room for connectivity, making the eventual connection faster. Use this when you know you'll be loading a room but are not yet connected to it.\n * @param roomId The id of the room to prewarm.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async prewarmRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/prewarm`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns a list of users currently present in the requested room. For better performance, we recommand to call this endpoint every 10 seconds maximum. Duplicates can happen if a user is in the requested room with multiple browser tabs opened.\n * @param roomId The id of the room to get the users from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of users currently present in the requested room.\n */\n public async getActiveUsers(\n roomId: string,\n options?: RequestOptions\n ): Promise<{ data: RoomUser<U>[] }> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/active_users`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Promise<{ data: RoomUser<U>[] }>;\n }\n\n /**\n * Boadcasts an event to a room without having to connect to it via the client from @liveblocks/client. The connectionId passed to event listeners is -1 when using this API.\n * @param roomId The id of the room to broadcast the event to.\n * @param message The message to broadcast. It can be any JSON serializable value.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async broadcastEvent(\n roomId: string,\n message: E,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/broadcast_event`,\n message,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Storage\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns the contents of the room’s Storage tree.\n * The default outputted format is called “plain LSON”, which includes information on the Live data structures in the tree.\n * These nodes show up in the output as objects with two properties:\n *\n * ```json\n * {\n * \"liveblocksType\": \"LiveObject\",\n * \"data\": ...\n * }\n * ```\n *\n * If you’re not interested in this information, you can use the `format` parameter to get a more compact output.\n *\n * @param roomId The id of the room to get the storage from.\n * @param format (optional) Set to return `plan-lson` representation by default. If set to `json`, the output will be formatted as a simplified JSON representation of the Storage tree.\n * @param options.signal (optional) An abort signal to cancel the request.\n * In that format, each LiveObject and LiveMap will be formatted as a simple JSON object, and each LiveList will be formatted as a simple JSON array. This is a lossy format because information about the original data structures is not retained, but it may be easier to work with.\n */\n public getStorageDocument(\n roomId: string,\n format: \"plain-lson\",\n options?: RequestOptions\n ): Promise<PlainLsonObject>;\n\n public getStorageDocument(roomId: string): Promise<PlainLsonObject>; // Default to 'plain-lson' when no format is provided\n\n public getStorageDocument(\n roomId: string,\n format: \"json\",\n options?: RequestOptions\n ): Promise<ToSimplifiedJson<S>>;\n\n public async getStorageDocument(\n roomId: string,\n format: \"plain-lson\" | \"json\" = \"plain-lson\",\n options?: RequestOptions\n ): Promise<PlainLsonObject | ToSimplifiedJson<S>> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/storage`,\n { format },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as PlainLsonObject | ToSimplifiedJson<S>;\n }\n\n async #requestStorageMutation(\n roomId: string,\n options?: RequestOptions\n ): Promise<RequestStorageMutationResponse> {\n const resp = await this.#post(\n url`/v2/rooms/${roomId}/request-storage-mutation`,\n {},\n options\n );\n if (!resp.ok) {\n throw await LiveblocksError.from(resp);\n }\n\n if (resp.headers.get(\"content-type\") !== \"application/x-ndjson\") {\n throw new Error(\"Unexpected response content type\");\n }\n if (resp.body === null) {\n throw new Error(\"Unexpected null body in response\");\n }\n\n const stream = resp.body\n .pipeThrough(new TextDecoderStream()) // stream-decode all bytes to utf8 chunks\n .pipeThrough(new LineStream()) // stream those strings by lines\n .pipeThrough(new NdJsonStream()); // parse each line as JSON\n\n // Read the first element from the NDJson stream and interpret it as the response data\n const iter = stream[Symbol.asyncIterator]();\n const first = (await iter.next()).value;\n if (!isPlainObject(first) || typeof first.actor !== \"number\") {\n throw new Error(\"Failed to obtain a unique session\");\n }\n\n // The rest of the stream are all the Storage nodes\n const nodes = (await asyncConsume(iter)) as IdTuple<SerializedCrdt>[];\n return { actor: first.actor, nodes };\n }\n\n /**\n * Initializes a room’s Storage. The room must already exist and have an empty Storage.\n * Calling this endpoint will disconnect all users from the room if there are any.\n *\n * @param roomId The id of the room to initialize the storage from.\n * @param document The document to initialize the storage with.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The initialized storage document. It is of the same format as the one passed in.\n */\n public async initializeStorageDocument(\n roomId: string,\n document: PlainLsonObject,\n options?: RequestOptions\n ): Promise<PlainLsonObject> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/storage`,\n document,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as Promise<PlainLsonObject>;\n }\n\n /**\n * Deletes all of the room’s Storage data and disconnect all users from the room if there are any. Note that this does not delete the Yjs document in the room if one exists.\n * @param roomId The id of the room to delete the storage from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteStorageDocument(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/storage`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Yjs\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns a JSON representation of the room’s Yjs document.\n * @param roomId The id of the room to get the Yjs document from.\n * @param params.format (optional) If true, YText will return formatting.\n * @param params.key (optional) If provided, returns only a single key’s value, e.g. doc.get(key).toJSON().\n * @param params.type (optional) Used with key to override the inferred type, i.e. \"ymap\" will return doc.get(key, Y.Map).\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A JSON representation of the room’s Yjs document.\n */\n public async getYjsDocument(\n roomId: string,\n params: { format?: boolean; key?: string; type?: string } = {},\n options?: RequestOptions\n ): Promise<JsonObject> {\n const { format, key, type } = params;\n\n const path = url`v2/rooms/${roomId}/ydoc`;\n\n const res = await this.#get(\n path,\n { formatting: format ? \"true\" : undefined, key, type },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Promise<JsonObject>;\n }\n\n /**\n * Send a Yjs binary update to the room’s Yjs document. You can use this endpoint to initialize Yjs data for the room or to update the room’s Yjs document.\n * @param roomId The id of the room to send the Yjs binary update to.\n * @param update The Yjs update to send. Typically the result of calling `Yjs.encodeStateAsUpdate(doc)`. Read the [Yjs documentation](https://docs.yjs.dev/api/document-updates) to learn how to create a binary update.\n * @param params.guid (optional) If provided, the binary update will be applied to the Yjs subdocument with the given guid. If not provided, the binary update will be applied to the root Yjs document.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async sendYjsBinaryUpdate(\n roomId: string,\n update: Uint8Array,\n params: { guid?: string } = {},\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#putBinary(\n url`/v2/rooms/${roomId}/ydoc`,\n update,\n { guid: params.guid },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns the room’s Yjs document encoded as a single binary update. This can be used by Y.applyUpdate(responseBody) to get a copy of the document in your backend.\n * See [Yjs documentation](https://docs.yjs.dev/api/document-updates) for more information on working with updates.\n * @param roomId The id of the room to get the Yjs document from.\n * @param params.guid (optional) If provided, returns the binary update of the Yjs subdocument with the given guid. If not provided, returns the binary update of the root Yjs document.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room’s Yjs document encoded as a single binary update.\n */\n public async getYjsDocumentAsBinaryUpdate(\n roomId: string,\n params: { guid?: string } = {},\n options?: RequestOptions\n ): Promise<ArrayBuffer> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/ydoc-binary`,\n { guid: params.guid },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return res.arrayBuffer();\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Comments\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Gets all the threads in a room.\n *\n * @param params.roomId The room ID to get the threads from.\n * @param params.query The query to filter threads by. It is based on our query language and can filter by metadata.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of threads.\n */\n public async getThreads(\n params: {\n roomId: string;\n /**\n * The query to filter threads by. It is based on our query language.\n *\n * @example\n * ```\n * {\n * query: \"metadata['organization']^'liveblocks:' AND metadata['status']:'open' AND metadata['pinned']:false AND metadata['priority']:3 AND resolved:true\"\n * }\n * ```\n * @example\n * ```\n * {\n * query: {\n * metadata: {\n * status: \"open\",\n * pinned: false,\n * priority: 3,\n * organization: {\n * startsWith: \"liveblocks:\"\n * }\n * },\n * resolved: true\n * }\n * }\n * ```\n */\n query?:\n | string\n | {\n metadata?: Partial<QueryMetadata<TM>>;\n resolved?: boolean;\n };\n },\n options?: RequestOptions\n ): Promise<{ data: ThreadData<TM, CM>[] }> {\n const { roomId } = params;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads`,\n { query },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const { data } = (await res.json()) as { data: ThreadDataPlain<TM, CM>[] };\n return {\n data: data.map((thread) => convertToThreadData(thread)),\n };\n }\n\n /**\n * Gets a thread.\n *\n * @param params.roomId The room ID to get the thread from.\n * @param params.threadId The thread ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A thread.\n */\n public async getThread(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * @deprecated Prefer using `getMentionsFromCommentBody` to extract mentions\n * from comments and threads, or `Liveblocks.getThreadSubscriptions` to get\n * the list of users who are subscribed to a thread.\n *\n * Gets a thread's participants.\n *\n * Participants are users who have commented on the thread\n * or users that have been mentioned in a comment.\n *\n * @param params.roomId The room ID to get the thread participants from.\n * @param params.threadId The thread ID to get the participants from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns An object containing an array of participant IDs.\n */\n public async getThreadParticipants(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<ThreadParticipants> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/participants`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as Promise<ThreadParticipants>;\n }\n\n /**\n * Gets a thread's subscriptions.\n *\n * @param params.roomId The room ID to get the thread subscriptions from.\n * @param params.threadId The thread ID to get the subscriptions from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns An array of subscriptions.\n */\n public async getThreadSubscriptions(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<{ data: UserSubscriptionData[] }> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/subscriptions`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const { data } = (await res.json()) as {\n data: UserSubscriptionDataPlain[];\n };\n\n return {\n data: data.map(convertToUserSubscriptionData),\n };\n }\n\n /**\n * Gets a thread's comment.\n *\n * @param params.roomId The room ID to get the comment from.\n * @param params.threadId The thread ID to get the comment from.\n * @param params.commentId The comment ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A comment.\n */\n public async getComment(\n params: { roomId: string; threadId: string; commentId: string },\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, commentId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Creates a comment.\n *\n * @param params.roomId The room ID to create the comment in.\n * @param params.threadId The thread ID to create the comment in.\n * @param params.data.userId The user ID of the user who is set to create the comment.\n * @param params.data.createdAt (optional) The date the comment is set to be created.\n * @param params.data.body The body of the comment.\n * @param params.data.metadata (optional) The metadata for the comment.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created comment.\n */\n public async createComment(\n params: CreateCommentOptions<CM>,\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments`,\n {\n ...data,\n createdAt: data.createdAt?.toISOString(),\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Edits a comment.\n * @param params.roomId The room ID to edit the comment in.\n * @param params.threadId The thread ID to edit the comment in.\n * @param params.commentId The comment ID to edit.\n * @param params.data.body The body of the comment.\n * @param params.data.metadata (optional) The metadata for the comment. Value must be a string, boolean or number. Use null to delete a key.\n * @param params.data.editedAt (optional) The date the comment was edited.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The edited comment.\n */\n public async editComment(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: {\n body: CommentBody;\n metadata?: Patchable<CM>;\n editedAt?: Date;\n };\n },\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, commentId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n {\n body: data.body,\n editedAt: data.editedAt?.toISOString(),\n metadata: data.metadata,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Deletes a comment. Deletes a comment. If there are no remaining comments in the thread, the thread is also deleted.\n * @param params.roomId The room ID to delete the comment in.\n * @param params.threadId The thread ID to delete the comment in.\n * @param params.commentId The comment ID to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteComment(\n params: { roomId: string; threadId: string; commentId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId, commentId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Creates a new thread. The thread will be created with the specified comment as its first comment.\n * If the thread already exists, a `LiveblocksError` will be thrown with status code 409.\n * @param params.roomId The room ID to create the thread in.\n * @param params.thread.metadata (optional) The metadata for the thread. Supports upto a maximum of 10 entries. Value must be a string, boolean or number\n * @param params.thread.comment.userId The user ID of the user who created the comment.\n * @param params.thread.comment.createdAt (optional) The date the comment was created.\n * @param params.thread.comment.body The body of the comment.\n * @param params.thread.comment.metadata (optional) The metadata for the comment.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created thread. The thread will be created with the specified comment as its first comment.\n */\n public async createThread(\n params: CreateThreadOptions<TM, CM>,\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads`,\n {\n ...data,\n comment: {\n ...data.comment,\n createdAt: data.comment.createdAt?.toISOString(),\n },\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Deletes a thread and all of its comments.\n * @param params.roomId The room ID to delete the thread in.\n * @param params.threadId The thread ID to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteThread(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/threads/${threadId}`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Mark a thread as resolved.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to mark as resolved.\n * @param params.data.userId The user ID of the user who marked the thread as resolved.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread marked as resolved.\n */\n public async markThreadAsResolved(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/mark-as-resolved`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Mark a thread as unresolved.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to mark as unresolved.\n * @param params.data.userId The user ID of the user who marked the thread as unresolved.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread marked as unresolved.\n */\n public async markThreadAsUnresolved(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/mark-as-unresolved`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Subscribes a user to a thread.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to subscribe to.\n * @param params.data.userId The user ID of the user to subscribe to the thread.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread subscription.\n */\n public async subscribeToThread(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<SubscriptionData> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/subscribe`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToSubscriptionData(\n (await res.json()) as SubscriptionDataPlain\n );\n }\n\n /**\n * Unsubscribes a user from a thread.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to unsubscribe from.\n * @param params.data.userId The user ID of the user to unsubscribe from the thread.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async unsubscribeFromThread(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/unsubscribe`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Updates the metadata of the specified thread in a room.\n * @param params.roomId The room ID to update the thread in.\n * @param params.threadId The thread ID to update.\n * @param params.data.metadata The metadata for the thread. Value must be a string, boolean or number\n * @param params.data.userId The user ID of the user who updated the thread.\n * @param params.data.updatedAt (optional) The date the thread is set to be updated.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated thread metadata.\n */\n public async editThreadMetadata(\n params: {\n roomId: string;\n threadId: string;\n data: { metadata: Patchable<TM>; userId: string; updatedAt?: Date };\n },\n options?: RequestOptions\n ): Promise<TM> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/metadata`,\n {\n ...data,\n updatedAt: data.updatedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as TM;\n }\n\n /**\n * Updates the metadata of the specified comment in a room.\n * @param params.roomId The room ID to update the comment in.\n * @param params.threadId The thread ID to update the comment in.\n * @param params.commentId The comment ID to update.\n * @param params.data.metadata The metadata for the comment. Value must be a string, boolean or number. Use null to delete a key.\n * @param params.data.userId The user ID of the user who updated the comment.\n * @param params.data.updatedAt (optional) The date the comment metadata is set to be updated.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated comment metadata.\n */\n public async editCommentMetadata(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: { metadata: Patchable<CM>; userId: string; updatedAt?: Date };\n },\n options?: RequestOptions\n ): Promise<CM> {\n const { roomId, threadId, commentId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}/metadata`,\n {\n ...data,\n updatedAt: data.updatedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as CM;\n }\n\n /**\n * Adds a new comment reaction to a comment.\n * @param params.roomId The room ID to add the comment reaction in.\n * @param params.threadId The thread ID to add the comment reaction in.\n * @param params.commentId The comment ID to add the reaction in.\n * @param params.data.emoji The (emoji) reaction to add.\n * @param params.data.userId The user ID of the user associated with the reaction.\n * @param params.data.createdAt (optional) The date the reaction is set to be created.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created comment reaction.\n */\n public async addCommentReaction(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: { emoji: string; userId: string; createdAt?: Date };\n },\n options?: RequestOptions\n ): Promise<CommentUserReaction> {\n const { roomId, threadId, commentId, data } = params;\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}/add-reaction`,\n {\n ...data,\n createdAt: data.createdAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const reaction = (await res.json()) as CommentUserReactionPlain;\n return convertToCommentUserReaction(reaction);\n }\n\n /**\n * Removes a reaction from a comment.\n * @param params.roomId The room ID to remove the comment reaction from.\n * @param params.threadId The thread ID to remove the comment reaction from.\n * @param params.commentId The comment ID to remove the reaction from.\n * @param params.data.emoji The (emoji) reaction to remove.\n * @param params.data.userId The user ID of the user associated with the reaction.\n * @param params.data.removedAt (optional) The date the reaction is set to be removed.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async removeCommentReaction(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: {\n emoji: string;\n userId: string;\n removedAt?: Date;\n };\n },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${params.commentId}/remove-reaction`,\n {\n ...data,\n removedAt: data.removedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns the inbox notifications for a user.\n * @param params.userId The user ID to get the inbox notifications from.\n * @param params.inboxNotificationId The ID of the inbox notification to get.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getInboxNotification(\n params: {\n userId: string;\n inboxNotificationId: string;\n },\n options?: RequestOptions\n ): Promise<InboxNotificationData> {\n const { userId, inboxNotificationId } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/inbox-notifications/${inboxNotificationId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToInboxNotificationData(\n (await res.json()) as InboxNotificationDataPlain\n );\n }\n\n /**\n * Returns the inbox notifications for a user.\n * @param params.userId The user ID to get the inbox notifications from.\n * @param params.query The query to filter inbox notifications by. It is based on our query language and can filter by unread.\n * @param params.tenantId (optional) The tenant ID to get the inbox notifications for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getInboxNotifications(\n params: GetInboxNotificationsOptions,\n options?: RequestOptions\n ): Promise<Page<InboxNotificationData>> {\n const { userId, tenantId, limit, startingAfter } = params;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const res = await this.#get(\n url`/v2/users/${userId}/inbox-notifications`,\n {\n query,\n limit,\n startingAfter,\n tenantId,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<InboxNotificationDataPlain>;\n return {\n ...page,\n data: page.data.map(convertToInboxNotificationData),\n };\n }\n\n /**\n * Iterates over all inbox notifications for a user.\n *\n * The difference with .getInboxNotifications() is that pagination will\n * happen automatically under the hood, using the given `pageSize`.\n *\n * @param criteria.userId The user ID to get the inbox notifications from.\n * @param criteria.query The query to filter inbox notifications by. It is based on our query language and can filter by unread.\n * @param criteria.tenantId (optional) The tenant ID to get the inbox notifications for.\n * @param options.pageSize (optional) The page size to use for each request.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n async *iterInboxNotifications(\n criteria: InboxNotificationsQueryCriteria,\n options?: RequestOptions & { pageSize?: number }\n ): AsyncGenerator<InboxNotificationData> {\n // TODO Dry up this async iterable implementation for pagination\n const { signal } = options ?? {};\n const pageSize = checkBounds(\"pageSize\", options?.pageSize ?? 50, 10);\n\n let cursor: string | undefined = undefined;\n while (true) {\n const { nextCursor, data } = await this.getInboxNotifications(\n { ...criteria, startingAfter: cursor, limit: pageSize },\n { signal }\n );\n for (const item of data) {\n yield item;\n }\n if (!nextCursor) {\n break;\n }\n cursor = nextCursor;\n }\n }\n\n /**\n * Returns all room subscription settings for a user.\n * @param params.userId The user ID to get the room subscription settings from.\n * @param params.tenantId (optional) The tenant ID to get the room subscription settings for.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param params.limit (optional) The number of items to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getUserRoomSubscriptionSettings(\n params: { userId: string; tenantId?: string } & PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<UserRoomSubscriptionSettings>> {\n const { userId, tenantId, startingAfter, limit } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/room-subscription-settings`,\n {\n tenantId,\n startingAfter,\n limit,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Page<UserRoomSubscriptionSettings>;\n }\n\n /**\n * Gets the user's room subscription settings.\n * @param params.userId The user ID to get the room subscription settings from.\n * @param params.roomId The room ID to get the room subscription settings from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n },\n options?: RequestOptions\n ): Promise<RoomSubscriptionSettings> {\n const { userId, roomId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as RoomSubscriptionSettings;\n }\n\n /**\n * Updates the user's room subscription settings.\n * @param params.userId The user ID to update the room subscription settings for.\n * @param params.roomId The room ID to update the room subscription settings for.\n * @param params.data The new room subscription settings for the user.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n data: Partial<RoomSubscriptionSettings>;\n },\n options?: RequestOptions\n ): Promise<RoomSubscriptionSettings> {\n const { userId, roomId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n data,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as RoomSubscriptionSettings;\n }\n\n /**\n * Delete the user's room subscription settings.\n * @param params.userId The user ID to delete the room subscription settings from.\n * @param params.roomId The room ID to delete the room subscription settings from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, roomId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Update a room ID.\n * @param params.roomId The current ID of the room.\n * @param params.newRoomId The new room ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateRoomId(\n params: {\n currentRoomId: string;\n newRoomId: string;\n },\n options?: RequestOptions\n ): Promise<RoomData> {\n const { currentRoomId, newRoomId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${currentRoomId}/update-room-id`,\n { newRoomId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Triggers an inbox notification for a user.\n * @param params.userId The user ID to trigger the inbox notification for.\n * @param params.kind The kind of inbox notification to trigger.\n * @param params.subjectId The subject ID of the triggered inbox notification.\n * @param params.activityData The activity data of the triggered inbox notification.\n * @param params.roomId (optional) The room ID to trigger the inbox notification for.\n * @param params.tenantId (optional) The tenant ID to trigger the inbox notification for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async triggerInboxNotification<K extends KDAD>(\n params: {\n userId: string;\n kind: K;\n roomId?: string;\n tenantId?: string;\n subjectId: string;\n activityData: DAD[K];\n },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#post(\n url`/v2/inbox-notifications/trigger`,\n params,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes an inbox notification for a user.\n * @param params.userId The user ID for which to delete the inbox notification.\n * @param params.inboxNotificationId The ID of the inbox notification to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteInboxNotification(\n params: {\n userId: string;\n inboxNotificationId: string;\n },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, inboxNotificationId } = params;\n\n const res = await this.#delete(\n url`/v2/users/${userId}/inbox-notifications/${inboxNotificationId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes all inbox notifications for a user.\n * @param params.userId The user ID for which to delete all the inbox notifications.\n * @param params.tenantId (optional) The tenant ID to delete the inbox notifications for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteAllInboxNotifications(\n params: { userId: string; tenantId?: string },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, tenantId } = params;\n\n const res = await this.#delete(\n url`/v2/users/${userId}/inbox-notifications`,\n { tenantId },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Get notification settings for a user for a project.\n * @param params.userId The user ID to get the notifications settings for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getNotificationSettings(\n params: { userId: string },\n options?: RequestOptions\n ): Promise<NotificationSettings> {\n const { userId } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/notification-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const plainSettings = (await res.json()) as NotificationSettingsPlain;\n const settings = createNotificationSettings(plainSettings);\n\n return settings;\n }\n\n /**\n * Update the user's notification settings.\n * @param params.userId The user ID to update the notification settings for.\n * @param params.data The new notification settings for the user.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateNotificationSettings(\n params: { userId: string; data: PartialNotificationSettings },\n options?: RequestOptions\n ): Promise<NotificationSettings> {\n const { userId, data } = params;\n\n const res = await this.#post(\n url`/v2/users/${userId}/notification-settings`,\n data,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const plainSettings = (await res.json()) as NotificationSettingsPlain;\n const settings = createNotificationSettings(plainSettings);\n\n return settings;\n }\n\n /**\n * Delete the user's notification settings\n * @param params.userId The user ID to update the notification settings for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteNotificationSettings(\n params: { userId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { userId } = params;\n const res = await this.#delete(\n url`/v2/users/${userId}/notification-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Create a group\n * @param params.groupId The ID of the group to create.\n * @param params.memberIds The IDs of the members to add to the group.\n * @param params.tenantId (optional) The tenant ID to create the group for.\n * @param params.scopes (optional) The scopes to grant to the group. The default is `{ mention: true }`.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async createGroup(\n params: {\n groupId: string;\n memberIds?: string[];\n tenantId?: string;\n scopes?: GroupScopes;\n },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups`,\n {\n ...params,\n\n // The REST API uses `id` since a group is a resource,\n // but we use `groupId` here for consistency with the other methods.\n id: params.groupId,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Get a group\n * @param params.groupId The ID of the group to get.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getGroup(\n params: { groupId: string },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#get(\n url`/v2/groups/${params.groupId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Add members to a group\n * @param params.groupId The ID of the group to add members to.\n * @param params.memberIds The IDs of the members to add to the group.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async addGroupMembers(\n params: { groupId: string; memberIds: string[] },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups/${params.groupId}/add-members`,\n { memberIds: params.memberIds },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Remove members from a group\n * @param params.groupId The ID of the group to remove members from.\n * @param params.memberIds The IDs of the members to remove from the group.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async removeGroupMembers(\n params: { groupId: string; memberIds: string[] },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups/${params.groupId}/remove-members`,\n { memberIds: params.memberIds },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Delete a group\n * @param params.groupId The ID of the group to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteGroup(\n params: { groupId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/groups/${params.groupId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Get all groups\n * @param params.limit (optional) The number of groups to return.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getGroups(\n params?: PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<GroupData>> {\n const res = await this.#get(\n url`/v2/groups`,\n { startingAfter: params?.startingAfter, limit: params?.limit },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<GroupDataPlain>;\n return {\n ...page,\n data: page.data.map(convertToGroupData),\n };\n }\n\n /**\n * Returns all groups a user is a member of.\n * @param params.userId The user ID to get the groups for.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param params.limit (optional) The number of items to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getUserGroups(\n params: { userId: string } & PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<GroupData>> {\n const { userId, startingAfter, limit } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/groups`,\n { startingAfter, limit },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<GroupDataPlain>;\n\n return {\n ...page,\n data: page.data.map(convertToGroupData),\n };\n }\n\n /**\n * Retrieves the current Storage contents for the given room ID and calls the\n * provided callback function, in which you can mutate the Storage contents\n * at will.\n *\n * If you need to run the same mutation across multiple rooms, prefer using\n * `.massMutateStorage()` instead of looping over room IDs yourself.\n */\n public async mutateStorage(\n roomId: string,\n callback: MutateStorageCallback,\n options?: MutateStorageOptions\n ): Promise<void> {\n return this.#_mutateOneRoom(roomId, undefined, callback, options);\n }\n\n /**\n * Retrieves the Storage contents for each room that matches the given\n * criteria and calls the provided callback function, in which you can mutate\n * the Storage contents at will.\n *\n * You can use the `criteria` parameter to select which rooms to process by\n * their metadata. If you pass `{}` (empty object), all rooms will be\n * selected and processed.\n *\n * This method will execute mutations in parallel, using the specified\n * `concurrency` value. If you which to run the mutations serially, set\n * `concurrency` to 1.\n */\n public async massMutateStorage(\n criteria: RoomsQueryCriteria,\n callback: MassMutateStorageCallback,\n massOptions?: MassMutateStorageOptions\n ): Promise<void> {\n const concurrency = checkBounds(\n \"concurrency\",\n massOptions?.concurrency ?? 8,\n 1,\n 20\n );\n\n // Try to select a reasonable page size based on the concurrency level, but\n // at least never less than 20.\n const pageSize = Math.max(20, concurrency * 4);\n const { signal } = massOptions ?? {};\n const rooms = this.iterRooms(criteria, { pageSize, signal });\n\n const options = { signal };\n await runConcurrently(\n rooms,\n (roomData) =>\n this.#_mutateOneRoom(roomData.id, roomData, callback, options),\n concurrency\n );\n }\n\n async #_mutateOneRoom<RD extends RoomData | undefined>(\n roomId: string,\n room: RD,\n callback: (context: { room: RD; root: LiveObject<S> }) => Awaitable<void>,\n options?: MutateStorageOptions\n ): Promise<void> {\n // Hard-coded for now, see https://github.com/liveblocks/liveblocks/pull/2293#issuecomment-2740067249\n const debounceInterval = 200;\n\n // The plan:\n // 1. Create a new pool\n // 2. Download the storage contents\n // 3. Construct the Live tree\n // 4. Run the callback\n // 5. Capture all the changes to the pool\n // 6. Send the resulting ops to the server at a throttled interval\n\n const { signal, abort } = makeAbortController(options?.signal);\n\n // Set up a \"debouncer\": we'll flush the buffered ops to the server if\n // there hasn't been an update to the buffered ops for a while. This\n // behavior is slightly different from the browser client, which will emit\n // ops as soon as they are available (= throttling)\n let opsBuffer: ClientWireOp[] = [];\n let outstandingFlush$: Promise<void> | undefined = undefined;\n let lastFlush = performance.now();\n\n const flushIfNeeded = (force: boolean) => {\n if (opsBuffer.length === 0)\n // Nothing to do\n return;\n\n if (outstandingFlush$) {\n // There already is an outstanding flush, wait for it to complete\n return;\n }\n\n const now = performance.now();\n if (!(force || now - lastFlush > debounceInterval)) {\n // We're still within the debounce window, do nothing right now\n return;\n }\n\n // All good, flush right now\n lastFlush = now;\n const ops = opsBuffer;\n opsBuffer = [];\n\n outstandingFlush$ = this.#sendMessage(\n roomId,\n [{ type: ClientMsgCode.UPDATE_STORAGE, ops }],\n { signal }\n )\n .catch((err) => {\n // For now, if any error happens during one of the flushes, abort the entire thing\n // TODO Think about more error handling control options here later (auto-retry, etc)\n abort(err);\n })\n .finally(() => {\n outstandingFlush$ = undefined;\n });\n };\n\n // Download the storage contents\n try {\n const resp = await this.#requestStorageMutation(roomId, { signal });\n const { actor, nodes } = resp;\n\n // Create a new pool\n const pool = createManagedPool(roomId, {\n getCurrentConnectionId: () => actor,\n onDispatch: (\n ops: ClientWireOp[],\n _reverse: Op[],\n _storageUpdates: Map<string, StorageUpdate>\n ) => {\n if (ops.length === 0) return;\n\n // Capture all the changes to the pool\n for (const op of ops) {\n opsBuffer.push(op);\n }\n flushIfNeeded(/* force */ false);\n },\n });\n\n // Construct the Live tree\n const root = LiveObject._fromItems<S>(nodes, pool);\n\n // Run the callback\n const callback$ = callback({ room, root });\n\n // If the callback synchronously makes changes, we'll want to flush those as soon as possible, then flush on an interval for the remainder of the async callback.\n flushIfNeeded(/* force */ true);\n\n await callback$;\n } catch (e) {\n abort();\n throw e;\n } finally {\n // Await any outstanding flushes, and then flush one last time\n await outstandingFlush$; // eslint-disable-line @typescript-eslint/await-thenable\n flushIfNeeded(/* force */ true);\n await outstandingFlush$; // eslint-disable-line @typescript-eslint/await-thenable\n }\n }\n\n async #sendMessage(\n roomId: string,\n messages: ClientMsg<JsonObject, Json>[],\n options?: RequestOptions\n ) {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/send-message`,\n { messages },\n { signal: options?.signal }\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n // TODO: If res.ok, it will be a 200 response containing all returned Ops.\n // These may include fix ops, which should get applied back to the managed\n // pool.\n // TODO Implement the handling of fix-ops:\n // const data = (await res.json()) as {\n // messages: readonly (\n // | ServerMsg<JsonObject, BaseUserMeta, Json>\n // | readonly ServerMsg<JsonObject, BaseUserMeta, Json>[]\n // )[];\n // };\n // return data;\n }\n\n /**\n * Returns a paginated list of AI copilots. The copilots are returned sorted by creation date, from newest to oldest.\n * @param params.limit (optional) A limit on the number of copilots to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of AI copilots.\n */\n public async getAiCopilots(\n params: PaginationOptions = {},\n options?: RequestOptions\n ): Promise<Page<AiCopilot>> {\n const res = await this.#get(\n url`/v2/ai/copilots`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<AiCopilotPlain>;\n return {\n ...page,\n data: page.data.map(inflateAiCopilot),\n };\n }\n\n /**\n * Creates an AI copilot.\n * @param params The parameters to create the copilot with.\n * @returns The created copilot.\n */\n public async createAiCopilot(\n params: CreateAiCopilotOptions,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#post(url`/v2/ai/copilots`, params, options);\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Returns an AI copilot with the given id.\n * @param copilotId The id of the copilot to return.\n * @returns The copilot with the given id.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getAiCopilot(\n copilotId: string,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#get(\n url`/v2/ai/copilots/${copilotId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Updates an AI copilot with the given id.\n * @param copilotId The id of the copilot to update.\n * @param params The parameters to update the copilot with.\n * @returns The updated copilot.\n */\n public async updateAiCopilot(\n copilotId: string,\n params: UpdateAiCopilotOptions,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#post(\n url`/v2/ai/copilots/${copilotId}`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Deletes an AI copilot with the given id. A deleted copilot is no longer accessible from the API or the dashboard and it cannot be restored.\n * @param copilotId The id of the copilot to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteAiCopilot(\n copilotId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${copilotId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Creates a web knowledge source.\n * @param params.url The URL of the web knowledge source.\n * @param params.type The type of the web knowledge source: \"individual_link\", \"crawl\" or \"sitemap\".\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The id of the created web knowledge source.\n */\n public async createWebKnowledgeSource(\n params: CreateWebKnowledgeSourceOptions,\n options?: RequestOptions\n ): Promise<{ id: string }> {\n const res = await this.#post(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string };\n return data;\n }\n\n /**\n * Creates a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.name The name of the file knowledge source.\n * @param params.file The file to create the knowledge source from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The id of the created file knowledge source.\n */\n public async createFileKnowledgeSource(\n params: CreateFileKnowledgeSourceOptions,\n options?: RequestOptions\n ): Promise<{ id: string }> {\n const fetch = await fetchPolyfill();\n const res = await fetch(\n urljoin(\n this.#baseUrl,\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.file.name}`\n ),\n {\n method: \"PUT\",\n body: params.file,\n headers: {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": params.file.type,\n \"Content-Length\": String(params.file.size),\n },\n signal: options?.signal,\n }\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string };\n return data;\n }\n\n /**\n * Deletes a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteFileKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes a web knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteWebKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns a paginated list of knowledge sources.\n * @param params.copilotId The id of the copilot.\n * @param params.limit (optional) A limit on the number of knowledge sources to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of knowledge sources.\n */\n public async getKnowledgeSources(\n params: GetKnowledgeSourcesOptions,\n options?: RequestOptions\n ): Promise<Page<KnowledgeSource>> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<KnowledgeSourcePlain>;\n return {\n ...page,\n data: page.data.map(inflateKnowledgeSource),\n };\n }\n\n /**\n * Returns a knowledge source with the given id.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The knowledge source.\n */\n public async getKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<KnowledgeSource> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as KnowledgeSourcePlain;\n return inflateKnowledgeSource(data);\n }\n\n /**\n * Returns the content of a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The content of the file knowledge source.\n */\n public async getFileKnowledgeSourceMarkdown(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<string> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string; content: string };\n return data.content;\n }\n\n /**\n * Returns a paginated list of web knowledge source links.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source.\n * @param params.limit (optional) A limit on the number of links to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of web knowledge source links.\n */\n public async getWebKnowledgeSourceLinks(\n params: GetWebKnowledgeSourceLinksOptions,\n options?: RequestOptions\n ): Promise<Page<WebKnowledgeSourceLink>> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}/links`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<WebKnowledgeSourceLinkPlain>;\n return {\n ...page,\n data: page.data.map(inflateWebKnowledgeSourceLink),\n };\n }\n}\n\nexport class LiveblocksError extends Error {\n readonly status: number;\n readonly details?: string;\n\n private constructor(message: string, status: number, details?: string) {\n super(message);\n this.name = \"LiveblocksError\";\n this.status = status;\n this.details = details;\n }\n\n public toString(): string {\n let msg = `${this.name}: ${this.message} (status ${this.status})`;\n if (this.details) {\n msg += `\\n${this.details}`;\n }\n return msg;\n }\n\n static async from(res: Response): Promise<LiveblocksError> {\n // Retain the stack trace of the original error location, not the async return point\n const origErrLocation = new Error();\n Error.captureStackTrace(origErrLocation, LiveblocksError.from); // eslint-disable-line\n\n const FALLBACK = \"An error happened without an error message\";\n let text: string;\n try {\n text = await res.text();\n } catch {\n text = FALLBACK;\n }\n const obj = (tryParseJson(text) ?? { message: text }) as JsonObject;\n\n const message = (obj.message || FALLBACK) as string;\n const details =\n [\n obj.suggestion ? `Suggestion: ${String(obj.suggestion)}` : undefined,\n obj.docs ? `See also: ${String(obj.docs)}` : undefined,\n ]\n .filter(Boolean)\n .join(\"\\n\") || undefined;\n\n const err = new LiveblocksError(message, res.status, details);\n err.stack = origErrLocation.stack;\n return err;\n }\n}\n","export async function asyncConsume<T>(\n iterable: AsyncIterable<T>\n): Promise<T[]> {\n const result: T[] = [];\n for await (const item of iterable) {\n result.push(item);\n }\n return result;\n}\n\n/**\n * Iterates an async iterable, invoking a side-effect on every element. Will\n * run at most `concurrency` callbacks simultaneously.\n */\nexport async function runConcurrently<T>(\n iterable: AsyncIterable<T>,\n fn: (item: T) => Promise<void>,\n concurrency: number\n): Promise<void> {\n const queue = new Set<Promise<void>>();\n\n for await (const item of iterable) {\n // If we've reached max concurrency, wait for one task to complete\n if (queue.size >= concurrency) {\n await Promise.race(queue);\n }\n\n // Process the next item in the queue\n const promise = (async () => {\n try {\n await fn(item);\n } finally {\n // @ts-expect-error var used before it was assigned, but it's fine\n queue.delete(promise);\n }\n })();\n\n queue.add(promise);\n }\n\n // Wait for any remaining tasks\n if (queue.size > 0) {\n await Promise.all(queue);\n }\n}\n","import type { Json } from \"@liveblocks/core\";\n\nexport class LineStream extends TransformStream<string, string> {\n constructor() {\n let buffer: string = \"\";\n\n super({\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>\n ) {\n // Append the chunk to the buffer\n buffer += chunk;\n if (buffer.includes(\"\\n\")) {\n // Split the buffer into lines\n const lines = buffer.split(\"\\n\");\n\n // Emit all lines... except the last one!\n for (let i = 0; i < lines.length - 1; i++) {\n // Skip empty lines\n if (lines[i]!.length > 0) {\n controller.enqueue(lines[i]);\n }\n }\n\n // Update the buffer with the last line (might be incomplete)\n buffer = lines[lines.length - 1]!;\n }\n },\n\n flush(controller: TransformStreamDefaultController<string>) {\n // Emit the remaining buffer as a line\n if (buffer.length > 0) {\n controller.enqueue(buffer);\n }\n },\n });\n }\n}\n\nexport class NdJsonStream<J extends Json> extends TransformStream<string, J> {\n constructor() {\n super({\n transform(\n line: string,\n controller: TransformStreamDefaultController<Json>\n ) {\n // Here, we _want_ JSON.parse() to throw if its input is invalid\n const json = JSON.parse(line) as J;\n controller.enqueue(json);\n },\n });\n }\n}\n","import type {\n IUserInfo,\n Json,\n JsonObject,\n URLSafeString,\n} from \"@liveblocks/core\";\nimport { url } from \"@liveblocks/core\";\n\nimport type { AuthResponse } from \"./client\";\nimport { assertNonEmpty, normalizeStatusCode } from \"./utils\";\n\n// As defined in the source of truth in ApiScope in\n// https://github.com/liveblocks/liveblocks-cloudflare/blob/main/src/security.ts\nconst ALL_PERMISSIONS = Object.freeze([\n \"room:write\",\n \"room:read\",\n \"room:presence:write\",\n \"comments:write\",\n \"comments:read\",\n] as const);\n\nexport type Permission = (typeof ALL_PERMISSIONS)[number];\n\nfunction isPermission(value: string): value is Permission {\n return (ALL_PERMISSIONS as readonly unknown[]).includes(value);\n}\n\nconst MAX_PERMS_PER_SET = 10;\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * read permissions to the storage and comments data for this room. (Note that\n * the user will still have permissions to update their own presence.)\n */\nconst READ_ACCESS = Object.freeze([\n \"room:read\",\n \"room:presence:write\",\n \"comments:read\",\n] as const);\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * permissions to read and write to the room's storage and comments.\n */\nconst FULL_ACCESS = Object.freeze([\"room:write\", \"comments:write\"] as const);\n\nconst roomPatternRegex = /^([*]|[^*]{1,128}[*]?)$/;\n\ntype PostFn = (path: URLSafeString, json: Json) => Promise<Response>;\n\n/**\n * Class to help you construct the exact permission set to grant a user, used\n * when making `.authorizeUser()` calls.\n *\n * Usage:\n *\n * const session = liveblocks.prepareSession();\n * session.allow(roomId, permissions) // or...\n *\n * For the `permissions` argument, you can pass a list of specific permissions,\n * or use one of our presets:\n *\n * session.allow('my-room', session.FULL_ACCESS) // Read + write access to room storage and comments\n * session.allow('my-room', session.READ_ACCESS) // Read-only access to room storage and comments\n *\n * Rooms can be specified with a prefix match, if the name ends in an asterisk.\n * In that case, access is granted to *all* rooms that start with that prefix:\n *\n * // Read + write access to *all* rooms that start with \"abc:\"\n * session.allow('abc:*', session.FULL_ACCESS)\n *\n * You can define at most 10 room IDs (or patterns) in a single token,\n * otherwise the token would become too large and unwieldy.\n *\n * All permissions granted are additive. You cannot \"remove\" permissions once\n * you grant them. For example:\n *\n * session\n * .allow('abc:*', session.FULL_ACCESS)\n * .allow('abc:123', session.READ_ACCESS)\n *\n * Here, room `abc:123` would have full access. The second .allow() call only\n * _adds_ read permissions, but that has no effect since full access\n * permissions were already added to the set.\n */\nexport class Session {\n public readonly FULL_ACCESS = FULL_ACCESS;\n public readonly READ_ACCESS = READ_ACCESS;\n\n #postFn: PostFn;\n #userId: string;\n #userInfo?: IUserInfo;\n #tenantId?: string;\n #sealed = false;\n readonly #permissions: Map<string, Set<Permission>> = new Map();\n\n /** @internal */\n constructor(\n postFn: PostFn,\n userId: string,\n userInfo?: IUserInfo,\n tenantId?: string\n ) {\n assertNonEmpty(userId, \"userId\"); // TODO: Check if this is a legal userId value too\n\n this.#postFn = postFn;\n this.#userId = userId;\n this.#userInfo = userInfo;\n this.#tenantId = tenantId;\n }\n\n #getOrCreate(roomId: string): Set<Permission> {\n if (this.#sealed) {\n throw new Error(\"You can no longer change these permissions.\");\n }\n\n let perms = this.#permissions.get(roomId);\n if (perms) {\n return perms;\n } else {\n if (this.#permissions.size >= MAX_PERMS_PER_SET) {\n throw new Error(\n \"You cannot add permissions for more than 10 rooms in a single token\"\n );\n }\n\n perms = new Set<Permission>();\n this.#permissions.set(roomId, perms);\n return perms;\n }\n }\n\n public allow(roomIdOrPattern: string, newPerms: readonly Permission[]): this {\n if (typeof roomIdOrPattern !== \"string\") {\n throw new Error(\"Room name or pattern must be a string\");\n }\n if (!roomPatternRegex.test(roomIdOrPattern)) {\n throw new Error(\"Invalid room name or pattern\");\n }\n\n if (newPerms.length === 0) {\n throw new Error(\"Permission list cannot be empty\");\n }\n\n const existingPerms = this.#getOrCreate(roomIdOrPattern);\n for (const perm of newPerms) {\n if (!isPermission(perm as string)) {\n throw new Error(`Not a valid permission: ${perm}`);\n }\n existingPerms.add(perm);\n }\n return this; // To allow chaining multiple allow calls\n }\n\n /** @internal - For unit tests only */\n public hasPermissions(): boolean {\n return this.#permissions.size > 0;\n }\n\n /** @internal - For unit tests only */\n public seal(): void {\n if (this.#sealed) {\n throw new Error(\n \"You cannot reuse Session instances. Please create a new session every time.\"\n );\n }\n this.#sealed = true;\n }\n\n /** @internal - For unit tests only */\n public serializePermissions(): JsonObject {\n return Object.fromEntries(\n Array.from(this.#permissions.entries()).map(([pat, perms]) => [\n pat,\n Array.from(perms),\n ])\n );\n }\n\n /**\n * Call this to authorize the session to access Liveblocks. Note that this\n * will return a Liveblocks \"access token\". Anyone that obtains such access\n * token will have access to the allowed resources.\n */\n public async authorize(): Promise<AuthResponse> {\n this.seal();\n if (!this.hasPermissions()) {\n console.warn(\n \"Access tokens without any permission will not be supported soon, you should use wildcards when the client requests a token for resources outside a room. See https://liveblocks.io/docs/errors/liveblocks-client/access-tokens-not-enough-permissions\"\n );\n }\n\n try {\n const resp = await this.#postFn(url`/v2/authorize-user`, {\n // Required\n userId: this.#userId,\n permissions: this.serializePermissions(),\n\n // Optional metadata\n userInfo: this.#userInfo,\n tenantId: this.#tenantId,\n });\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: 'Call to /v2/authorize-user failed. See \"error\" for more information.',\n error: er as Error | undefined,\n };\n }\n }\n}\n","const DEFAULT_BASE_URL = \"https://api.liveblocks.io\";\n\n// Valid alphabet for secret/public keys\nconst VALID_KEY_CHARS_REGEX = /^[\\w-]+$/;\n\nexport function getBaseUrl(baseUrl?: string | undefined): string {\n if (\n typeof baseUrl === \"string\" &&\n baseUrl.startsWith(\"http\") // Must be http or https URL\n ) {\n return baseUrl;\n } else {\n return DEFAULT_BASE_URL;\n }\n}\n\nexport async function fetchPolyfill(): Promise<typeof fetch> {\n return typeof globalThis.fetch !== \"undefined\"\n ? globalThis.fetch\n : ((await import(\"node-fetch\")).default as unknown as typeof fetch);\n}\n\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nexport function startsWith<P extends string>(\n value: unknown,\n prefix: P\n): value is `${P}${string}` {\n return isString(value) && value.startsWith(prefix);\n}\n\nfunction isNonEmpty(value: unknown): value is string {\n return isString(value) && value.length > 0;\n}\n\nexport function assertNonEmpty(\n value: unknown,\n field: string\n): asserts value is string {\n if (!isNonEmpty(value)) {\n throw new Error(\n `Invalid value for field '${field}'. Please provide a non-empty string. For more information: https://liveblocks.io/docs/api-reference/liveblocks-node#authorize`\n );\n }\n}\n\nexport function assertSecretKey(\n value: unknown,\n field: string\n): asserts value is string {\n if (!startsWith(value, \"sk_\")) {\n throw new Error(\n `Invalid value for field '${field}'. Secret keys must start with 'sk_'. Please provide the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`\n );\n }\n\n if (!VALID_KEY_CHARS_REGEX.test(value)) {\n throw new Error(\n `Invalid chars found in field '${field}'. Please check that you correctly copied the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`\n );\n }\n}\n\nexport function normalizeStatusCode(statusCode: number): number {\n if (statusCode >= 200 && statusCode < 300) {\n return 200; /* OK */\n } else if (statusCode >= 500) {\n return 503; /* Service Unavailable */\n } else {\n return statusCode; // 429, 404, 403, 409, 422, 400\n }\n}\n","import type { NotificationChannel } from \"@liveblocks/core\";\nimport * as base64 from \"@stablelib/base64\";\nimport * as sha256 from \"fast-sha256\";\nimport type { IncomingHttpHeaders } from \"http\";\n\nimport { isString } from \"./utils\";\n\nexport class WebhookHandler {\n #secretBuffer: Buffer;\n static #secretPrefix = \"whsec_\";\n\n constructor(\n /**\n * The signing secret provided on the dashboard's webhooks page\n * @example \"whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm\"\n */\n secret: string\n ) {\n if (!secret) throw new Error(\"Secret is required\");\n if (typeof secret !== \"string\") throw new Error(\"Secret must be a string\");\n\n if (secret.startsWith(WebhookHandler.#secretPrefix) === false)\n throw new Error(\"Invalid secret, must start with whsec_\");\n\n const secretKey = secret.slice(WebhookHandler.#secretPrefix.length);\n this.#secretBuffer = Buffer.from(secretKey, \"base64\");\n }\n\n /**\n * Verifies a webhook request and returns the event\n */\n public verifyRequest(request: WebhookRequest): WebhookEvent {\n const { headers, rawBody } = request;\n\n const { webhookId, timestamp, rawSignatures } =\n this.#verifyHeaders(headers);\n\n if (typeof rawBody !== \"string\") {\n throw new Error(\n `Invalid rawBody field, must be a string, got \"${typeof rawBody}\" instead. It is likely that you need to JSON.stringify the body before passing it.`\n );\n }\n\n this.#verifyTimestamp(timestamp);\n\n const signature = this.#sign(`${webhookId}.${timestamp}.${rawBody}`);\n\n const expectedSignatures = rawSignatures\n .split(\" \")\n .map((rawSignature) => {\n const [, parsedSignature] = rawSignature.split(\",\");\n return parsedSignature;\n })\n .filter(isNotUndefined);\n\n if (expectedSignatures.includes(signature) === false)\n throw new Error(\n `Invalid signature, expected one of ${expectedSignatures.join(\n \", \"\n )}, got ${signature}`\n );\n\n const event: WebhookEvent = JSON.parse(rawBody) as WebhookEvent;\n\n this.#verifyWebhookEventType(event);\n\n return event;\n }\n\n /**\n * Verifies the headers and returns the webhookId, timestamp and rawSignatures\n */\n #verifyHeaders(headers: IncomingHttpHeaders | Headers) {\n const usingNativeHeaders =\n typeof Headers !== \"undefined\" && headers instanceof Headers;\n const normalizedHeaders = usingNativeHeaders\n ? Object.fromEntries(headers)\n : (headers as IncomingHttpHeaders);\n\n const sanitizedHeaders: IncomingHttpHeaders = {};\n Object.keys(normalizedHeaders).forEach((key) => {\n sanitizedHeaders[key.toLowerCase()] = normalizedHeaders[key];\n });\n\n const webhookId = sanitizedHeaders[\"webhook-id\"];\n if (typeof webhookId !== \"string\")\n throw new Error(\"Invalid webhook-id header\");\n\n const timestamp = sanitizedHeaders[\"webhook-timestamp\"];\n if (typeof timestamp !== \"string\")\n throw new Error(\"Invalid webhook-timestamp header\");\n\n const rawSignatures = sanitizedHeaders[\"webhook-signature\"];\n if (typeof rawSignatures !== \"string\")\n throw new Error(\"Invalid webhook-signature header\");\n\n return { webhookId, timestamp, rawSignatures };\n }\n\n /**\n * Signs the content with the secret\n * @param content\n * @returns `string`\n */\n #sign(content: string): string {\n const encoder = new TextEncoder();\n const toSign = encoder.encode(content);\n return base64.encode(sha256.hmac(this.#secretBuffer, toSign));\n }\n\n /**\n * Verifies that the timestamp is not too old or in the future\n */\n #verifyTimestamp(timestampHeader: string) {\n const now = Math.floor(Date.now() / 1000);\n const timestamp = parseInt(timestampHeader, 10);\n\n if (isNaN(timestamp)) {\n throw new Error(\"Invalid timestamp\");\n }\n\n // Check if timestamp is too old\n if (timestamp < now - WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp too old\");\n }\n\n // Check if timestamp is in the future\n if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp in the future\");\n }\n }\n\n /**\n * Ensures that the event is a known event type\n * or throws and prompts the user to upgrade to a higher version of @liveblocks/node\n */\n #verifyWebhookEventType(event: WebhookEvent): asserts event is WebhookEvent {\n if (\n event &&\n event.type &&\n [\n \"storageUpdated\",\n \"userEntered\",\n \"userLeft\",\n \"roomCreated\",\n \"roomDeleted\",\n \"commentCreated\",\n \"commentEdited\",\n \"commentDeleted\",\n \"commentReactionAdded\",\n \"commentReactionRemoved\",\n \"commentMetadataUpdated\",\n \"threadMetadataUpdated\",\n \"threadCreated\",\n \"threadDeleted\",\n \"ydocUpdated\",\n \"notification\",\n \"threadMarkedAsResolved\",\n \"threadMarkedAsUnresolved\",\n ].includes(event.type)\n ) {\n if (event.type === \"notification\") {\n const notification = event;\n if (\n notification.data.kind === \"thread\" ||\n notification.data.kind === \"textMention\" ||\n isCustomKind(notification.data.kind)\n ) {\n return;\n } else {\n // Using JSON.stringify because `notification.data.kind`\n // is considered as `never` now because of the type guard.\n throw new Error(\n `Unknown notification kind: ${JSON.stringify(notification.data.kind)}`\n );\n }\n }\n\n return;\n }\n\n throw new Error(\n \"Unknown event type, please upgrade to a higher version of @liveblocks/node\"\n );\n }\n}\n\nconst WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; // 5 minutes\n\nconst isNotUndefined = <T>(value: T | undefined): value is T =>\n value !== undefined;\n\ntype WebhookRequest = {\n /**\n * Headers of the request, can be a regular object or a Headers object\n * @example\n * {\n * \"webhook-id\": \"123\",\n * \"webhook-timestamp\": \"1614588800000\",\n * \"webhook-signature\": \"v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=\"\n * }\n *\n * new Headers({\n * \"webhook-id\": \"123\",\n * \"webhook-timestamp\": \"1614588800000\",\n * \"webhook-signature\": \"v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=\"\n * }}\n */\n headers: IncomingHttpHeaders | Headers;\n /**\n * Raw body of the request, do not parse it\n * @example '{\"type\":\"storageUpdated\",\"data\":{\"roomId\":\"my-room-id\",\"appId\":\"my-app-id\",\"updatedAt\":\"2021-03-01T12:00:00.000Z\"}}'\n */\n rawBody: string;\n};\n\n/**\n * When receiving an event we cannot define the `kind`\n * as member of the augmentation\n */\ntype CustomKind = `$${string}`;\n\nconst isCustomKind = (value: unknown): value is CustomKind => {\n return isString(value) && value.startsWith(\"$\");\n};\n\ntype WebhookEvent =\n | StorageUpdatedEvent\n | UserEnteredEvent\n | UserLeftEvent\n | RoomCreatedEvent\n | RoomDeletedEvent\n | CommentCreatedEvent\n | CommentEditedEvent\n | CommentDeletedEvent\n | CommentReactionAdded\n | CommentReactionRemoved\n | CommentMetadataUpdatedEvent\n | ThreadMetadataUpdatedEvent\n | NotificationEvent\n | ThreadCreatedEvent\n | ThreadDeletedEvent\n | ThreadMarkedAsResolvedEvent\n | ThreadMarkedAsUnresolvedEvent\n | YDocUpdatedEvent;\n\ntype StorageUpdatedEvent = {\n type: \"storageUpdated\";\n data: {\n roomId: string;\n projectId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n };\n};\n\ntype UserEnteredEvent = {\n type: \"userEntered\";\n data: {\n projectId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user entered the room.\n */\n enteredAt: string;\n numActiveUsers: number;\n };\n};\n\ntype UserLeftEvent = {\n type: \"userLeft\";\n data: {\n projectId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user left the room.\n */\n leftAt: string;\n numActiveUsers: number;\n };\n};\n\ntype RoomCreatedEvent = {\n type: \"roomCreated\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n };\n};\n\ntype RoomDeletedEvent = {\n type: \"roomDeleted\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype CommentCreatedEvent = {\n type: \"commentCreated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n createdBy: string;\n };\n};\n\ntype CommentEditedEvent = {\n type: \"commentEdited\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n editedAt: string;\n };\n};\n\ntype CommentDeletedEvent = {\n type: \"commentDeleted\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype CommentReactionAdded = {\n type: \"commentReactionAdded\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n emoji: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n addedAt: string;\n addedBy: string;\n };\n};\n\ntype CommentReactionRemoved = {\n type: \"commentReactionRemoved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n emoji: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n removedAt: string;\n removedBy: string;\n };\n};\n\ntype YDocUpdatedEvent = {\n type: \"ydocUpdated\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n };\n};\n\ntype ThreadMetadataUpdatedEvent = {\n type: \"threadMetadataUpdated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype CommentMetadataUpdatedEvent = {\n type: \"commentMetadataUpdated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadCreatedEvent = {\n type: \"threadCreated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n createdBy: string;\n };\n};\n\ntype ThreadDeletedEvent = {\n type: \"threadDeleted\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype ThreadMarkedAsResolvedEvent = {\n type: \"threadMarkedAsResolved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadMarkedAsUnresolvedEvent = {\n type: \"threadMarkedAsUnresolved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: \"thread\";\n projectId: string;\n roomId: string;\n userId: string;\n threadId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype TextMentionNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: \"textMention\";\n projectId: string;\n roomId: string;\n userId: string;\n mentionId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype CustomNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: CustomKind;\n projectId: string;\n roomId: string | null;\n userId: string;\n subjectId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype NotificationEvent =\n | ThreadNotificationEvent\n | TextMentionNotificationEvent\n | CustomNotificationEvent;\n\nexport type {\n CommentCreatedEvent,\n CommentDeletedEvent,\n CommentEditedEvent,\n CommentMetadataUpdatedEvent,\n CommentReactionAdded,\n CommentReactionRemoved,\n CustomNotificationEvent,\n NotificationEvent,\n RoomCreatedEvent,\n RoomDeletedEvent,\n StorageUpdatedEvent,\n TextMentionNotificationEvent,\n ThreadCreatedEvent,\n ThreadDeletedEvent,\n ThreadMarkedAsResolvedEvent,\n ThreadMarkedAsUnresolvedEvent,\n ThreadMetadataUpdatedEvent,\n ThreadNotificationEvent,\n UserEnteredEvent,\n UserLeftEvent,\n WebhookEvent,\n WebhookRequest,\n YDocUpdatedEvent,\n};\n\n/**\n * Type guard to check if a webhook event is a `ThreadNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `ThreadNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isThreadNotificationEvent(\n event: WebhookEvent\n): event is ThreadNotificationEvent {\n return event.type === \"notification\" && event.data.kind === \"thread\";\n}\n\n/**\n * Type guard to check if a webhook event is a `TextMentionNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `TextMentionNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isTextMentionNotificationEvent(\n event: WebhookEvent\n): event is TextMentionNotificationEvent {\n return event.type === \"notification\" && event.data.kind === \"textMention\";\n}\n\n/**\n * Type guard to check if a webhook event is a `CustomNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `CustomNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isCustomNotificationEvent(\n event: WebhookEvent\n): event is CustomNotificationEvent {\n return event.type === \"notification\" && isCustomKind(event.data.kind);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-node/dist/index.cjs","../src/index.ts","../src/version.ts","../src/client.ts","../src/lib/itertools.ts","../src/lib/ndjson.ts","../src/Session.ts","../src/utils.ts","../src/webhooks.ts"],"names":["url","LiveObject"],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-node/dist/index.cjs","sourcesContent":[null,"import { detectDupes } from \"@liveblocks/core\";\n\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nexport type {\n AiCopilot,\n CreateAiCopilotOptions,\n CreateFileKnowledgeSourceOptions,\n CreateRoomOptions,\n CreateWebKnowledgeSourceOptions,\n GetAiCopilotsOptions,\n GetInboxNotificationsOptions,\n GetKnowledgeSourcesOptions,\n GetRoomsOptions,\n GetWebKnowledgeSourceLinksOptions,\n InboxNotificationsQueryCriteria,\n KnowledgeSource,\n LiveblocksOptions,\n MassMutateStorageCallback,\n MassMutateStorageOptions,\n MutateStorageCallback,\n MutateStorageOptions,\n Page,\n PaginationOptions,\n RoomAccesses,\n RoomData,\n RoomPermission,\n RoomsQueryCriteria,\n RoomUser,\n ThreadParticipants,\n UpdateAiCopilotOptions,\n UpdateRoomOptions,\n UpsertRoomOptions,\n WebKnowledgeSourceLink,\n} from \"./client\";\nexport { Liveblocks, LiveblocksError } from \"./client\";\nexport type {\n CommentCreatedEvent,\n CommentDeletedEvent,\n CommentEditedEvent,\n CommentReactionAdded,\n CommentReactionRemoved,\n CustomNotificationEvent,\n NotificationEvent,\n RoomCreatedEvent,\n RoomDeletedEvent,\n StorageUpdatedEvent,\n TextMentionNotificationEvent,\n ThreadCreatedEvent,\n ThreadDeletedEvent,\n ThreadMarkedAsResolvedEvent,\n ThreadMarkedAsUnresolvedEvent,\n ThreadMetadataUpdatedEvent,\n ThreadNotificationEvent,\n UserEnteredEvent,\n UserLeftEvent,\n WebhookEvent,\n WebhookRequest,\n YDocUpdatedEvent,\n} from \"./webhooks\";\nexport {\n isCustomNotificationEvent,\n isTextMentionNotificationEvent,\n isThreadNotificationEvent,\n WebhookHandler,\n} from \"./webhooks\";\nexport type {\n CommentBody,\n CommentBodyBlockElement,\n CommentBodyElement,\n CommentBodyInlineElement,\n CommentBodyLink,\n CommentBodyLinkElementArgs,\n CommentBodyMention,\n CommentBodyMentionElementArgs,\n CommentBodyParagraph,\n CommentBodyParagraphElementArgs,\n CommentBodyText,\n CommentBodyTextElementArgs,\n CommentData,\n CommentUserReaction,\n IUserInfo,\n Json,\n JsonArray,\n JsonObject,\n JsonScalar,\n LiveStructure,\n Lson,\n LsonObject,\n PlainLsonObject,\n ResolveUsersArgs,\n StringifyCommentBodyElements,\n StringifyCommentBodyOptions,\n ThreadData,\n User,\n} from \"@liveblocks/core\";\nexport {\n getMentionsFromCommentBody,\n isNotificationChannelEnabled,\n LiveList,\n LiveMap,\n LiveObject,\n stringifyCommentBody,\n} from \"@liveblocks/core\";\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/node\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n","/**\n * NOTE: only types should be imported from @liveblocks/core.\n * This is because this package is made to be used in Node.js, and\n * @liveblocks/core has browser-specific code.\n */\nimport type {\n Awaitable,\n BaseMetadata,\n BaseUserMeta,\n ClientMsg,\n ClientWireOp,\n CommentBody,\n CommentData,\n CommentDataPlain,\n CommentUserReaction,\n CommentUserReactionPlain,\n DAD,\n DCM,\n DE,\n DS,\n DTM,\n DU,\n GroupData,\n GroupDataPlain,\n GroupScopes,\n InboxNotificationData,\n InboxNotificationDataPlain,\n Json,\n JsonObject,\n KDAD,\n LsonObject,\n NotificationSettings,\n NotificationSettingsPlain,\n Op,\n OptionalTupleUnless,\n PartialNotificationSettings,\n PartialUnless,\n Patchable,\n PlainLsonObject,\n QueryMetadata,\n QueryParams,\n RoomSubscriptionSettings,\n StorageNode,\n StorageUpdate,\n SubscriptionData,\n SubscriptionDataPlain,\n ThreadData,\n ThreadDataPlain,\n ToImmutable,\n URLSafeString,\n UserRoomSubscriptionSettings,\n UserSubscriptionData,\n UserSubscriptionDataPlain,\n} from \"@liveblocks/core\";\nimport {\n checkBounds,\n ClientMsgCode,\n convertToCommentData,\n convertToCommentUserReaction,\n convertToGroupData,\n convertToInboxNotificationData,\n convertToSubscriptionData,\n convertToThreadData,\n convertToUserSubscriptionData,\n createManagedPool,\n createNotificationSettings,\n isPlainObject,\n LiveObject,\n makeAbortController,\n objectToQuery,\n tryParseJson,\n url,\n urljoin,\n} from \"@liveblocks/core\";\n\nimport { asyncConsume, runConcurrently } from \"./lib/itertools\";\nimport { LineStream, NdJsonStream } from \"./lib/ndjson\";\nimport { Session } from \"./Session\";\nimport {\n assertNonEmpty,\n assertSecretKey,\n fetchPolyfill,\n getBaseUrl,\n normalizeStatusCode,\n} from \"./utils\";\n\n// Recursively convert ReadonlyMap<K, V> to { [key: K]: V }\ntype SerializeMaps<T> =\n T extends ReadonlyMap<infer K, infer V>\n ? K extends string\n ? { readonly [P in K]: SerializeMaps<V> }\n : { readonly [key: string]: SerializeMaps<V> }\n : T extends object\n ? { readonly [P in keyof T]: SerializeMaps<T[P]> }\n : T;\n\ntype ToSimplifiedJson<S extends LsonObject> = LsonObject extends S\n ? JsonObject\n : // ToImmutable converts LiveMap instances to ReadonlyMap versions, but\n // the \"simplified JSON\" format actually requires (because of serialization)\n // and converts the maps to plain objects.\n SerializeMaps<ToImmutable<S>>;\n\nexport type LiveblocksOptions = {\n /**\n * The Liveblocks secret key. Must start with \"sk_\".\n * Get it from https://liveblocks.io/dashboard/apikeys\n */\n secret: string;\n\n /** Point the client to an alternative Liveblocks server. */\n baseUrl?: string;\n};\n\ntype DateToString<T> = {\n [P in keyof T]: Date extends T[P] ? string : T[P];\n};\n\nexport type CreateSessionOptions<U extends BaseUserMeta = DU> =\n //\n PartialUnless<U[\"info\"], { userInfo: U[\"info\"] }> & {\n tenantId?: string;\n };\n\nexport type IdentifyUserOptions<U extends BaseUserMeta = DU> =\n //\n PartialUnless<U[\"info\"], { userInfo: U[\"info\"] }>;\n\nexport type AuthResponse = {\n status: number;\n body: string;\n error?: Error;\n};\n\ntype Identity = {\n userId: string;\n groupIds: string[];\n tenantId?: string;\n};\n\nexport type ThreadParticipants = {\n participantIds: string[];\n};\n\nexport type CreateThreadOptions<\n TM extends BaseMetadata,\n CM extends BaseMetadata,\n> = {\n roomId: string;\n data: {\n comment: {\n userId: string;\n createdAt?: Date;\n body: CommentBody;\n } & PartialUnless<CM, { metadata: CM }>; // Comment metadata (data.comment.metadata)\n } & PartialUnless<TM, { metadata: TM }>; // Thread metadata (data.metadata)\n};\n\nexport type CreateCommentOptions<CM extends BaseMetadata> = {\n roomId: string;\n threadId: string;\n data: {\n userId: string;\n createdAt?: Date;\n body: CommentBody;\n } & PartialUnless<CM, { metadata: CM }>;\n};\n\nexport type RoomPermission =\n | []\n | [\"room:write\"]\n | [\"room:read\", \"room:presence:write\"]\n | [\"room:read\", \"room:presence:write\", \"comments:write\"];\nexport type RoomAccesses = Record<\n string,\n | [\"room:write\"]\n | [\"room:read\", \"room:presence:write\"]\n | [\"room:read\", \"room:presence:write\", \"comments:write\"]\n>;\nexport type RoomMetadata = Record<string, string | string[]>;\ntype QueryRoomMetadata = Record<string, string>;\n\nexport type RoomData = {\n type: \"room\";\n id: string;\n createdAt: Date;\n lastConnectionAt?: Date;\n defaultAccesses: RoomPermission;\n usersAccesses: RoomAccesses;\n groupsAccesses: RoomAccesses;\n metadata: RoomMetadata;\n};\n\ntype RoomDataPlain = DateToString<RoomData>;\n\ntype AiCopilotProviderSettings = {\n maxTokens?: number;\n temperature?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n maxRetries?: number;\n};\n\ntype OpenAiModel =\n | \"o1\"\n | \"o1-mini\"\n | \"o3\"\n | \"o3-mini\"\n | \"o4-mini\"\n | \"gpt-4.1\"\n | \"gpt-4.1-mini\"\n | \"gpt-4.1-nano\"\n | \"gpt-4o\"\n | \"gpt-4o-mini\"\n | \"gpt-4-turbo\"\n | \"gpt-4\"\n | \"gpt-5\"\n | \"gpt-5-mini\"\n | \"gpt-5-nano\"\n | \"gpt-5-chat-latest\"\n | \"gpt-5.1\"\n | \"gpt-5.1-mini\"\n | \"gpt-5.1-chat-latest\";\n\ntype OpenAiProviderOptions = {\n openai: {\n reasoningEffort?: \"low\" | \"medium\" | \"high\";\n webSearch?: {\n allowedDomains?: string[];\n };\n };\n};\n\ntype AnthropicModel =\n | \"claude-sonnet-4-5-20250929\"\n | \"claude-haiku-4-5-20251001\"\n | \"claude-opus-4-1-20250805\"\n | \"claude-4-opus-20250514\"\n | \"claude-4-sonnet-20250514\"\n | \"claude-3-7-sonnet-20250219\"\n | \"claude-3-5-sonnet-latest\"\n | \"claude-3-5-haiku-latest\"\n | \"claude-3-opus-latest\";\ntype AnthropicProviderOptions = {\n anthropic: {\n thinking?:\n | {\n type: \"enabled\";\n budgetTokens: number;\n }\n | {\n type: \"disabled\";\n };\n webSearch?: {\n allowedDomains?: string[];\n };\n };\n};\n\ntype GoogleModel =\n | \"gemini-2.5-flash\"\n | \"gemini-2.5-pro\"\n | \"gemini-2.0-flash-001\"\n | \"gemini-1.5-flash\"\n | \"gemini-1.5-pro\";\ntype GoogleProviderOptions = {\n google: {\n thinkingConfig?: {\n thinkingBudget?: number;\n };\n };\n};\n\nexport type AiCopilot = {\n type: \"copilot\";\n id: string;\n name: string;\n description?: string;\n\n systemPrompt: string;\n knowledgePrompt?: string;\n alwaysUseKnowledge: boolean;\n\n createdAt: Date;\n updatedAt: Date;\n lastUsedAt?: Date;\n\n settings?: AiCopilotProviderSettings;\n} & (\n | {\n provider: \"openai\";\n providerModel: OpenAiModel;\n providerOptions?: OpenAiProviderOptions;\n }\n | {\n provider: \"anthropic\";\n providerModel: AnthropicModel;\n providerOptions?: AnthropicProviderOptions;\n }\n | {\n provider: \"google\";\n providerModel: GoogleModel;\n providerOptions?: GoogleProviderOptions;\n }\n | {\n provider: \"openai-compatible\";\n providerModel: string;\n compatibleProviderName: string;\n providerBaseUrl: string;\n }\n);\n\ntype AiCopilotPlain = DateToString<AiCopilot>;\nexport type RoomUser<U extends BaseUserMeta = DU> = {\n type: \"user\";\n id: string | null;\n connectionId: number;\n info: U[\"info\"];\n};\n\ntype RequestStorageMutationResponse = {\n actor: number;\n nodes: StorageNode[];\n};\n\nexport type MutateStorageCallback = (context: {\n root: LiveObject<S>;\n}) => Awaitable<void>;\nexport type MutateStorageOptions = RequestOptions;\n\nexport type MassMutateStorageCallback = (context: {\n room: RoomData;\n root: LiveObject<S>;\n}) => Awaitable<void>;\n\n// prettier-ignore\nexport type MassMutateStorageOptions =\n & MutateStorageOptions\n & { concurrency?: number };\n\n// NOTE: We should _never_ rely on using the default types (DS, DU, DE, ...)\n// inside the Liveblocks implementation. We should only rely on the type\n// \"params\" (S, U, E, ...) instead, where the concrete type is bound to the\n// class. In this case, we're not doing that at the class level, but globally.\n// The idea is that we \"start small\" and could always add them in at the class\n// level later.\ntype E = DE;\ntype TM = DTM;\ntype CM = DCM;\ntype S = DS;\ntype U = DU;\n\nexport type RoomsQueryCriteria = {\n tenantId?: string;\n userId?: string;\n groupIds?: string[];\n /**\n * The query to filter rooms by. It is based on our query language.\n * @example\n * ```\n * {\n * query: 'metadata[\"status\"]:\"open\" AND roomId^\"liveblocks:\"'\n * }\n * ```\n * @example\n * ```\n * {\n * query: {\n * metadata: {\n * status: \"open\",\n * },\n * roomId: {\n * startsWith: \"liveblocks:\"\n * }\n * }\n * }\n * ```\n */\n query?:\n | string\n | {\n metadata?: QueryRoomMetadata;\n roomId?: {\n startsWith: string;\n };\n };\n};\n\nexport type InboxNotificationsQueryCriteria = {\n userId: string;\n tenantId?: string;\n /**\n * The query to filter inbox notifications by. It is based on our query language.\n *\n * @example\n * ```\n * {\n * query: \"unread:true\"\n * }\n * ```\n *\n * @example\n * ```\n * {\n * query: {\n * unread: true\n * }\n * }\n * ```\n *\n */\n query?: string | { unread: boolean };\n};\n\nexport type PaginationOptions = {\n limit?: number;\n startingAfter?: string;\n};\n\nexport type Page<T> = {\n nextCursor: string | null;\n data: T[];\n};\n\n// prettier-ignore\nexport type GetRoomsOptions =\n & RoomsQueryCriteria\n & PaginationOptions\n\n// prettier-ignore\nexport type GetInboxNotificationsOptions =\n & InboxNotificationsQueryCriteria\n & PaginationOptions;\n\nexport type CreateRoomOptions = {\n defaultAccesses: RoomPermission;\n groupsAccesses?: RoomAccesses;\n usersAccesses?: RoomAccesses;\n metadata?: RoomMetadata;\n tenantId?: string;\n\n /**\n * @private Preferred storage engine version to use when creating the\n * room. Only takes effect if the room doesn't exist yet. Version\n * 2 supports streaming and will become the default in the future.\n */\n engine?: 1 | 2;\n};\n\nexport type UpdateRoomOptions = {\n defaultAccesses?: RoomPermission | null;\n groupsAccesses?: Record<\n string,\n [\"room:write\"] | [\"room:read\", \"room:presence:write\"] | null\n >;\n usersAccesses?: Record<\n string,\n [\"room:write\"] | [\"room:read\", \"room:presence:write\"] | null\n >;\n metadata?: Record<string, string | string[] | null>;\n};\n\nexport type UpsertRoomOptions = {\n update: UpdateRoomOptions;\n create?: CreateRoomOptions;\n};\n\nexport type GetAiCopilotsOptions = PaginationOptions;\n\nexport type CreateAiCopilotOptions = {\n name: string;\n description?: string;\n\n systemPrompt: string;\n knowledgePrompt?: string;\n alwaysUseKnowledge?: boolean;\n\n settings?: AiCopilotProviderSettings;\n\n providerApiKey: string;\n} & (\n | {\n provider: \"openai\";\n providerModel: OpenAiModel;\n\n providerOptions?: OpenAiProviderOptions;\n }\n | {\n provider: \"anthropic\";\n providerModel: AnthropicModel;\n providerOptions?: AnthropicProviderOptions;\n }\n | {\n provider: \"google\";\n providerModel: GoogleModel;\n providerOptions?: GoogleProviderOptions;\n }\n | {\n provider: \"openai-compatible\";\n providerModel: string;\n compatibleProviderName: string;\n providerBaseUrl: string;\n }\n);\n\nexport type UpdateAiCopilotOptions = {\n name?: string;\n description?: string | null;\n\n systemPrompt?: string;\n knowledgePrompt?: string | null;\n alwaysUseKnowledge?: boolean;\n\n settings?: AiCopilotProviderSettings | null;\n\n providerApiKey?: string;\n} & (\n | {\n provider?: \"openai\";\n /**\n * The provider model to use.\n */\n providerModel?: OpenAiModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs.\n */\n providerOptions?: OpenAiProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"anthropic\";\n /**\n * The provider model to use.\n */\n providerModel?: AnthropicModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs..\n */\n providerOptions?: AnthropicProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"google\";\n /**\n * The provider model to use.\n */\n providerModel?: GoogleModel;\n /**\n * The provider options to use. Replaces the entire existing provider options; no deep merge of the nested fields occurs.\n */\n providerOptions?: GoogleProviderOptions | null;\n compatibleProviderName?: never;\n providerBaseUrl?: never;\n }\n | {\n provider?: \"openai-compatible\";\n /**\n * The provider model to use.\n */\n providerModel?: string;\n compatibleProviderName?: string;\n providerBaseUrl?: string;\n providerOptions?: never;\n }\n);\nexport type CreateWebKnowledgeSourceOptions = {\n copilotId: string;\n url: string;\n type: \"individual_link\" | \"crawl\" | \"sitemap\";\n};\n\nexport type CreateFileKnowledgeSourceOptions = {\n copilotId: string;\n file: File;\n};\n\nexport type GetKnowledgeSourcesOptions = {\n copilotId: string;\n} & PaginationOptions;\n\nexport type GetWebKnowledgeSourceLinksOptions = {\n copilotId: string;\n knowledgeSourceId: string;\n} & PaginationOptions;\n\ntype KnowledgeSourcePlain = DateToString<KnowledgeSource>;\n\nexport type KnowledgeSource = (\n | {\n type: \"ai-knowledge-web-source\";\n link: {\n url: string;\n type: \"individual_link\" | \"crawl\" | \"sitemap\";\n };\n }\n | {\n type: \"ai-knowledge-file-source\";\n file: {\n name: string;\n mimeType: string;\n };\n }\n) & {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n lastIndexedAt: Date;\n} & (\n | { status: \"ingesting\" | \"ready\" }\n | { status: \"error\"; errorMessage: string }\n );\n\ntype WebKnowledgeSourceLinkPlain = DateToString<WebKnowledgeSourceLink>;\n\nexport type WebKnowledgeSourceLink = {\n id: string;\n url: string;\n status: \"ingesting\" | \"ready\" | \"error\";\n createdAt: Date;\n lastIndexedAt: Date;\n};\n\nexport type RequestOptions = {\n signal?: AbortSignal;\n};\n\n/**\n * Converts ISO-formatted date strings to Date instances on RoomDataPlain\n * values.\n */\nfunction inflateRoomData(room: RoomDataPlain): RoomData {\n const createdAt = new Date(room.createdAt);\n const lastConnectionAt = room.lastConnectionAt\n ? new Date(room.lastConnectionAt)\n : undefined;\n\n return {\n ...room,\n createdAt,\n lastConnectionAt,\n };\n}\n\nfunction inflateAiCopilot(copilot: AiCopilotPlain): AiCopilot {\n return {\n ...copilot,\n createdAt: new Date(copilot.createdAt),\n updatedAt: new Date(copilot.updatedAt),\n lastUsedAt: copilot.lastUsedAt ? new Date(copilot.lastUsedAt) : undefined,\n };\n}\n\nfunction inflateKnowledgeSource(source: KnowledgeSourcePlain): KnowledgeSource {\n return {\n ...source,\n createdAt: new Date(source.createdAt),\n updatedAt: new Date(source.updatedAt),\n lastIndexedAt: new Date(source.lastIndexedAt),\n };\n}\n\nfunction inflateWebKnowledgeSourceLink(\n link: WebKnowledgeSourceLinkPlain\n): WebKnowledgeSourceLink {\n return {\n ...link,\n createdAt: new Date(link.createdAt),\n lastIndexedAt: new Date(link.lastIndexedAt),\n };\n}\n\n/**\n * Interact with the Liveblocks API from your Node.js backend.\n */\nexport class Liveblocks {\n readonly #secret: string;\n readonly #baseUrl: URL;\n\n /**\n * Interact with the Liveblocks API from your Node.js backend.\n */\n constructor(options: LiveblocksOptions) {\n const options_ = options as Record<string, unknown>;\n const secret = options_.secret;\n assertSecretKey(secret, \"secret\");\n this.#secret = secret;\n this.#baseUrl = new URL(getBaseUrl(options.baseUrl));\n }\n\n async #post(\n path: URLSafeString,\n json: Json,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": \"application/json\",\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(json),\n signal: options?.signal,\n });\n return res;\n }\n\n async #putBinary(\n path: URLSafeString,\n body: Uint8Array,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": \"application/octet-stream\",\n };\n const fetch = await fetchPolyfill();\n return await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n signal: options?.signal,\n });\n }\n\n async #delete(\n path: URLSafeString,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"DELETE\",\n headers,\n signal: options?.signal,\n });\n return res;\n }\n\n async #get(\n path: URLSafeString,\n params?: QueryParams,\n options?: RequestOptions\n ): Promise<Response> {\n const url = urljoin(this.#baseUrl, path, params);\n const headers = {\n Authorization: `Bearer ${this.#secret}`,\n };\n const fetch = await fetchPolyfill();\n const res = await fetch(url, {\n method: \"GET\",\n headers,\n signal: options?.signal,\n });\n return res;\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Authentication\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Prepares a new session to authorize a user to access Liveblocks.\n *\n * IMPORTANT:\n * Always make sure that you trust the user making the request to your\n * backend before calling .prepareSession()!\n *\n * @param userId Tell Liveblocks the user ID of the user to authorize. Must\n * uniquely identify the user account in your system. The uniqueness of this\n * value will determine how many MAUs will be counted/billed.\n *\n * @param tenantId (optional) The tenant ID to authorize the user for.\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n *\n */\n prepareSession(\n userId: string,\n ...rest: OptionalTupleUnless<\n CreateSessionOptions<U>,\n [options: CreateSessionOptions<U>]\n >\n ): Session {\n const options = rest[0];\n return new Session(\n this.#post.bind(this),\n userId,\n options?.userInfo,\n options?.tenantId\n );\n }\n\n /**\n * Call this to authenticate the user as an actor you want to allow to use\n * Liveblocks.\n *\n * You should use this method only if you want to manage your permissions\n * through the Liveblocks Permissions API. This method is more complicated to\n * set up, but allows for finer-grained specification of permissions.\n *\n * Calling `.identifyUser()` only lets you securely identify a user (and what\n * groups they belong to). What permissions this user will end up having is\n * determined by whatever permissions you assign the user/group in your\n * Liveblocks account, through the Permissions API:\n * https://liveblocks.io/docs/rooms/permissions\n *\n * IMPORTANT:\n * Always verify that you trust the user making the request before calling\n * .identifyUser()!\n *\n * @param identity Tell Liveblocks the user ID of the user to authenticate.\n * Must uniquely identify the user account in your system. The uniqueness of\n * this value will determine how many MAUs will be counted/billed.\n *\n * If you also want to assign which groups this user belongs to, use the\n * object form and specify the `groupIds` property. Those `groupIds` should\n * match the groupIds you assigned permissions to via the Liveblocks\n * Permissions API, see\n * https://liveblocks.io/docs/rooms/permissions#permissions-levels-groups-accesses-example\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n */\n // These fields define the security identity of the user. Whatever you pass in here will define which\n public async identifyUser(\n identity:\n | string // Shorthand for userId\n | Identity,\n ...rest: OptionalTupleUnless<\n IdentifyUserOptions<U>,\n [options: IdentifyUserOptions<U>]\n >\n ): Promise<AuthResponse> {\n const options = rest[0];\n\n const path = url`/v2/identify-user`;\n\n const { userId, groupIds, tenantId } =\n typeof identity === \"string\"\n ? { userId: identity, groupIds: undefined, tenantId: undefined }\n : identity;\n\n assertNonEmpty(userId, \"userId\");\n\n const body = {\n userId,\n groupIds,\n tenantId,\n userInfo: options?.userInfo,\n };\n\n try {\n const resp = await this.#post(path, body);\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: `Call to ${urljoin(\n this.#baseUrl,\n path\n )} failed. See \"error\" for more information.`,\n error: er as Error | undefined,\n };\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Room\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns a list of your rooms. The rooms are returned sorted by creation date, from newest to oldest. You can filter rooms by metadata, users accesses and groups accesses.\n * @param params.limit (optional) A limit on the number of rooms to be returned. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param params.userId (optional) A filter on users accesses.\n * @param params.metadata (optional) A filter on metadata. Multiple metadata keys can be used to filter rooms.\n * @param params.groupIds (optional) A filter on groups accesses. Multiple groups can be used.\n * @param params.tenantId (optional) A filter on tenant ID.\n * @param params.query (optional) A query to filter rooms by. It is based on our query language. You can filter by metadata and room ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of rooms.\n */\n public async getRooms(\n params: GetRoomsOptions = {},\n options?: RequestOptions\n ): Promise<Page<RoomData>> {\n const path = url`/v2/rooms`;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const queryParams = {\n limit: params.limit,\n startingAfter: params.startingAfter,\n userId: params.userId,\n tenantId: params.tenantId,\n groupIds: params.groupIds ? params.groupIds.join(\",\") : undefined,\n query,\n };\n\n const res = await this.#get(path, queryParams, options);\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<RoomDataPlain>;\n const rooms: RoomData[] = page.data.map(inflateRoomData);\n return {\n ...page,\n data: rooms,\n };\n }\n\n /**\n * Iterates over all rooms that match the given criteria.\n *\n * The difference with .getRooms() is that pagination will happen\n * automatically under the hood, using the given `pageSize`.\n *\n * @param criteria.userId (optional) A filter on users accesses.\n * @param criteria.groupIds (optional) A filter on groups accesses. Multiple groups can be used.\n * @param criteria.query.roomId (optional) A filter by room ID.\n * @param criteria.query.metadata (optional) A filter by metadata.\n *\n * @param options.pageSize (optional) The page size to use for each request.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n async *iterRooms(\n criteria: RoomsQueryCriteria,\n options?: RequestOptions & { pageSize?: number }\n ): AsyncGenerator<RoomData> {\n // TODO Dry up this async iterable implementation for pagination\n const { signal } = options ?? {};\n const pageSize = checkBounds(\"pageSize\", options?.pageSize ?? 40, 20);\n\n let cursor: string | undefined = undefined;\n while (true) {\n const { nextCursor, data } = await this.getRooms(\n { ...criteria, startingAfter: cursor, limit: pageSize },\n { signal }\n );\n for (const item of data) {\n yield item;\n }\n if (!nextCursor) {\n break;\n }\n cursor = nextCursor;\n }\n }\n\n /**\n * Creates a new room with the given id.\n * @param roomId The id of the room to create.\n * @param params.defaultAccesses The default accesses for the room.\n * @param params.groupsAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param params.tenantId (optional) The tenant ID to create the room for.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created room.\n */\n public async createRoom(\n roomId: string,\n params: CreateRoomOptions,\n options?: RequestOptions & { idempotent?: boolean }\n ): Promise<RoomData> {\n const {\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n metadata,\n tenantId,\n engine,\n } = params;\n\n const res = await this.#post(\n options?.idempotent ? url`/v2/rooms?idempotent` : url`/v2/rooms`,\n {\n id: roomId,\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n tenantId,\n metadata,\n engine,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Returns a room with the given id, or creates one with the given creation\n * options if it doesn't exist yet.\n *\n * @param roomId The id of the room.\n * @param params.defaultAccesses The default accesses for the room if the room will be created.\n * @param params.groupsAccesses (optional) The group accesses for the room if the room will be created. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room if the room will be created. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room if the room will be created. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param params.tenantId (optional) The tenant ID to create the room for.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room.\n */\n public async getOrCreateRoom(\n roomId: string,\n params: CreateRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n return await this.createRoom(roomId, params, {\n ...options,\n idempotent: true,\n });\n }\n\n /**\n * Updates or creates a new room with the given properties.\n *\n * @param roomId The id of the room to update or create.\n * @param update The fields to update. These values will be updated when the room exists, or set when the room does not exist and gets created. Must specify at least one key.\n * @param create (optional) The fields to only use when the room does not exist and will be created. When the room already exists, these values are ignored.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room.\n */\n public async upsertRoom(\n roomId: string,\n params: UpsertRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/upsert`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Returns a room with the given id.\n * @param roomId The id of the room to return.\n * @returns The room with the given id.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<RoomData> {\n const res = await this.#get(url`/v2/rooms/${roomId}`, undefined, options);\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Updates specific properties of a room. It’s not necessary to provide the entire room’s information.\n * Setting a property to `null` means to delete this property.\n * @param roomId The id of the room to update.\n * @param params.defaultAccesses (optional) The default accesses for the room.\n * @param params.groupsAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.usersAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.\n * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated room.\n */\n public async updateRoom(\n roomId: string,\n params: UpdateRoomOptions,\n options?: RequestOptions\n ): Promise<RoomData> {\n const { defaultAccesses, groupsAccesses, usersAccesses, metadata } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}`,\n {\n defaultAccesses,\n groupsAccesses,\n usersAccesses,\n metadata,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Deletes a room with the given id. A deleted room is no longer accessible from the API or the dashboard and it cannot be restored.\n * @param roomId The id of the room to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/rooms/${roomId}`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Prepares a room for connectivity, making the eventual connection faster. Use this when you know you'll be loading a room but are not yet connected to it.\n * @param roomId The id of the room to prewarm.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async prewarmRoom(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/prewarm`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns a list of users currently present in the requested room. For better performance, we recommand to call this endpoint every 10 seconds maximum. Duplicates can happen if a user is in the requested room with multiple browser tabs opened.\n * @param roomId The id of the room to get the users from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of users currently present in the requested room.\n */\n public async getActiveUsers(\n roomId: string,\n options?: RequestOptions\n ): Promise<{ data: RoomUser<U>[] }> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/active_users`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Promise<{ data: RoomUser<U>[] }>;\n }\n\n /**\n * Boadcasts an event to a room without having to connect to it via the client from @liveblocks/client. The connectionId passed to event listeners is -1 when using this API.\n * @param roomId The id of the room to broadcast the event to.\n * @param message The message to broadcast. It can be any JSON serializable value.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async broadcastEvent(\n roomId: string,\n message: E,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/broadcast_event`,\n message,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Storage\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns the contents of the room’s Storage tree.\n * The default outputted format is called “plain LSON”, which includes information on the Live data structures in the tree.\n * These nodes show up in the output as objects with two properties:\n *\n * ```json\n * {\n * \"liveblocksType\": \"LiveObject\",\n * \"data\": ...\n * }\n * ```\n *\n * If you’re not interested in this information, you can use the `format` parameter to get a more compact output.\n *\n * @param roomId The id of the room to get the storage from.\n * @param format (optional) Set to return `plan-lson` representation by default. If set to `json`, the output will be formatted as a simplified JSON representation of the Storage tree.\n * @param options.signal (optional) An abort signal to cancel the request.\n * In that format, each LiveObject and LiveMap will be formatted as a simple JSON object, and each LiveList will be formatted as a simple JSON array. This is a lossy format because information about the original data structures is not retained, but it may be easier to work with.\n */\n public getStorageDocument(\n roomId: string,\n format: \"plain-lson\",\n options?: RequestOptions\n ): Promise<PlainLsonObject>;\n\n public getStorageDocument(roomId: string): Promise<PlainLsonObject>; // Default to 'plain-lson' when no format is provided\n\n public getStorageDocument(\n roomId: string,\n format: \"json\",\n options?: RequestOptions\n ): Promise<ToSimplifiedJson<S>>;\n\n public async getStorageDocument(\n roomId: string,\n format: \"plain-lson\" | \"json\" = \"plain-lson\",\n options?: RequestOptions\n ): Promise<PlainLsonObject | ToSimplifiedJson<S>> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/storage`,\n { format },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as PlainLsonObject | ToSimplifiedJson<S>;\n }\n\n async #requestStorageMutation(\n roomId: string,\n options?: RequestOptions\n ): Promise<RequestStorageMutationResponse> {\n const resp = await this.#post(\n url`/v2/rooms/${roomId}/request-storage-mutation`,\n {},\n options\n );\n if (!resp.ok) {\n throw await LiveblocksError.from(resp);\n }\n\n if (resp.headers.get(\"content-type\") !== \"application/x-ndjson\") {\n throw new Error(\"Unexpected response content type\");\n }\n if (resp.body === null) {\n throw new Error(\"Unexpected null body in response\");\n }\n\n const stream = resp.body\n .pipeThrough(new TextDecoderStream()) // stream-decode all bytes to utf8 chunks\n .pipeThrough(new LineStream()) // stream those strings by lines\n .pipeThrough(new NdJsonStream()); // parse each line as JSON\n\n // Read the first element from the NDJson stream and interpret it as the response data\n const iter = stream[Symbol.asyncIterator]();\n const first = (await iter.next()).value;\n if (!isPlainObject(first) || typeof first.actor !== \"number\") {\n throw new Error(\"Failed to obtain a unique session\");\n }\n\n // The rest of the stream are all the Storage nodes\n const nodes = (await asyncConsume(iter)) as StorageNode[];\n return { actor: first.actor, nodes };\n }\n\n /**\n * Initializes a room’s Storage. The room must already exist and have an empty Storage.\n * Calling this endpoint will disconnect all users from the room if there are any.\n *\n * @param roomId The id of the room to initialize the storage from.\n * @param document The document to initialize the storage with.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The initialized storage document. It is of the same format as the one passed in.\n */\n public async initializeStorageDocument(\n roomId: string,\n document: PlainLsonObject,\n options?: RequestOptions\n ): Promise<PlainLsonObject> {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/storage`,\n document,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as Promise<PlainLsonObject>;\n }\n\n /**\n * Deletes all of the room’s Storage data and disconnect all users from the room if there are any. Note that this does not delete the Yjs document in the room if one exists.\n * @param roomId The id of the room to delete the storage from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteStorageDocument(\n roomId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/storage`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Yjs\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Returns a JSON representation of the room’s Yjs document.\n * @param roomId The id of the room to get the Yjs document from.\n * @param params.format (optional) If true, YText will return formatting.\n * @param params.key (optional) If provided, returns only a single key’s value, e.g. doc.get(key).toJSON().\n * @param params.type (optional) Used with key to override the inferred type, i.e. \"ymap\" will return doc.get(key, Y.Map).\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A JSON representation of the room’s Yjs document.\n */\n public async getYjsDocument(\n roomId: string,\n params: { format?: boolean; key?: string; type?: string } = {},\n options?: RequestOptions\n ): Promise<JsonObject> {\n const { format, key, type } = params;\n\n const path = url`v2/rooms/${roomId}/ydoc`;\n\n const res = await this.#get(\n path,\n { formatting: format ? \"true\" : undefined, key, type },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Promise<JsonObject>;\n }\n\n /**\n * Send a Yjs binary update to the room’s Yjs document. You can use this endpoint to initialize Yjs data for the room or to update the room’s Yjs document.\n * @param roomId The id of the room to send the Yjs binary update to.\n * @param update The Yjs update to send. Typically the result of calling `Yjs.encodeStateAsUpdate(doc)`. Read the [Yjs documentation](https://docs.yjs.dev/api/document-updates) to learn how to create a binary update.\n * @param params.guid (optional) If provided, the binary update will be applied to the Yjs subdocument with the given guid. If not provided, the binary update will be applied to the root Yjs document.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async sendYjsBinaryUpdate(\n roomId: string,\n update: Uint8Array,\n params: { guid?: string } = {},\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#putBinary(\n url`/v2/rooms/${roomId}/ydoc`,\n update,\n { guid: params.guid },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns the room’s Yjs document encoded as a single binary update. This can be used by Y.applyUpdate(responseBody) to get a copy of the document in your backend.\n * See [Yjs documentation](https://docs.yjs.dev/api/document-updates) for more information on working with updates.\n * @param roomId The id of the room to get the Yjs document from.\n * @param params.guid (optional) If provided, returns the binary update of the Yjs subdocument with the given guid. If not provided, returns the binary update of the root Yjs document.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The room’s Yjs document encoded as a single binary update.\n */\n public async getYjsDocumentAsBinaryUpdate(\n roomId: string,\n params: { guid?: string } = {},\n options?: RequestOptions\n ): Promise<ArrayBuffer> {\n const res = await this.#get(\n url`/v2/rooms/${roomId}/ydoc-binary`,\n { guid: params.guid },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return res.arrayBuffer();\n }\n\n /* -------------------------------------------------------------------------------------------------\n * Comments\n * -----------------------------------------------------------------------------------------------*/\n\n /**\n * Gets all the threads in a room.\n *\n * @param params.roomId The room ID to get the threads from.\n * @param params.query The query to filter threads by. It is based on our query language and can filter by metadata.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A list of threads.\n */\n public async getThreads(\n params: {\n roomId: string;\n /**\n * The query to filter threads by. It is based on our query language.\n *\n * @example\n * ```\n * {\n * query: \"metadata['organization']^'liveblocks:' AND metadata['status']:'open' AND metadata['pinned']:false AND metadata['priority']:3 AND resolved:true\"\n * }\n * ```\n * @example\n * ```\n * {\n * query: {\n * metadata: {\n * status: \"open\",\n * pinned: false,\n * priority: 3,\n * organization: {\n * startsWith: \"liveblocks:\"\n * }\n * },\n * resolved: true\n * }\n * }\n * ```\n */\n query?:\n | string\n | {\n metadata?: Partial<QueryMetadata<TM>>;\n resolved?: boolean;\n };\n },\n options?: RequestOptions\n ): Promise<{ data: ThreadData<TM, CM>[] }> {\n const { roomId } = params;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads`,\n { query },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const { data } = (await res.json()) as { data: ThreadDataPlain<TM, CM>[] };\n return {\n data: data.map((thread) => convertToThreadData(thread)),\n };\n }\n\n /**\n * Gets a thread.\n *\n * @param params.roomId The room ID to get the thread from.\n * @param params.threadId The thread ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A thread.\n */\n public async getThread(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * @deprecated Prefer using `getMentionsFromCommentBody` to extract mentions\n * from comments and threads, or `Liveblocks.getThreadSubscriptions` to get\n * the list of users who are subscribed to a thread.\n *\n * Gets a thread's participants.\n *\n * Participants are users who have commented on the thread\n * or users that have been mentioned in a comment.\n *\n * @param params.roomId The room ID to get the thread participants from.\n * @param params.threadId The thread ID to get the participants from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns An object containing an array of participant IDs.\n */\n public async getThreadParticipants(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<ThreadParticipants> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/participants`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return (await res.json()) as Promise<ThreadParticipants>;\n }\n\n /**\n * Gets a thread's subscriptions.\n *\n * @param params.roomId The room ID to get the thread subscriptions from.\n * @param params.threadId The thread ID to get the subscriptions from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns An array of subscriptions.\n */\n public async getThreadSubscriptions(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<{ data: UserSubscriptionData[] }> {\n const { roomId, threadId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/subscriptions`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const { data } = (await res.json()) as {\n data: UserSubscriptionDataPlain[];\n };\n\n return {\n data: data.map(convertToUserSubscriptionData),\n };\n }\n\n /**\n * Gets a thread's comment.\n *\n * @param params.roomId The room ID to get the comment from.\n * @param params.threadId The thread ID to get the comment from.\n * @param params.commentId The comment ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A comment.\n */\n public async getComment(\n params: { roomId: string; threadId: string; commentId: string },\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, commentId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Creates a comment.\n *\n * @param params.roomId The room ID to create the comment in.\n * @param params.threadId The thread ID to create the comment in.\n * @param params.data.userId The user ID of the user who is set to create the comment.\n * @param params.data.createdAt (optional) The date the comment is set to be created.\n * @param params.data.body The body of the comment.\n * @param params.data.metadata (optional) The metadata for the comment.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created comment.\n */\n public async createComment(\n params: CreateCommentOptions<CM>,\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments`,\n {\n ...data,\n createdAt: data.createdAt?.toISOString(),\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Edits a comment.\n * @param params.roomId The room ID to edit the comment in.\n * @param params.threadId The thread ID to edit the comment in.\n * @param params.commentId The comment ID to edit.\n * @param params.data.body The body of the comment.\n * @param params.data.metadata (optional) The metadata for the comment. Value must be a string, boolean or number. Use null to delete a key.\n * @param params.data.editedAt (optional) The date the comment was edited.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The edited comment.\n */\n public async editComment(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: {\n body: CommentBody;\n metadata?: Patchable<CM>;\n editedAt?: Date;\n };\n },\n options?: RequestOptions\n ): Promise<CommentData<CM>> {\n const { roomId, threadId, commentId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n {\n body: data.body,\n editedAt: data.editedAt?.toISOString(),\n metadata: data.metadata,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToCommentData<CM>((await res.json()) as CommentDataPlain<CM>);\n }\n\n /**\n * Deletes a comment. Deletes a comment. If there are no remaining comments in the thread, the thread is also deleted.\n * @param params.roomId The room ID to delete the comment in.\n * @param params.threadId The thread ID to delete the comment in.\n * @param params.commentId The comment ID to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteComment(\n params: { roomId: string; threadId: string; commentId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId, commentId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Creates a new thread. The thread will be created with the specified comment as its first comment.\n * If the thread already exists, a `LiveblocksError` will be thrown with status code 409.\n * @param params.roomId The room ID to create the thread in.\n * @param params.thread.metadata (optional) The metadata for the thread. Supports upto a maximum of 10 entries. Value must be a string, boolean or number\n * @param params.thread.comment.userId The user ID of the user who created the comment.\n * @param params.thread.comment.createdAt (optional) The date the comment was created.\n * @param params.thread.comment.body The body of the comment.\n * @param params.thread.comment.metadata (optional) The metadata for the comment.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created thread. The thread will be created with the specified comment as its first comment.\n */\n public async createThread(\n params: CreateThreadOptions<TM, CM>,\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads`,\n {\n ...data,\n comment: {\n ...data.comment,\n createdAt: data.comment.createdAt?.toISOString(),\n },\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Deletes a thread and all of its comments.\n * @param params.roomId The room ID to delete the thread in.\n * @param params.threadId The thread ID to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteThread(\n params: { roomId: string; threadId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/threads/${threadId}`,\n undefined,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Mark a thread as resolved.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to mark as resolved.\n * @param params.data.userId The user ID of the user who marked the thread as resolved.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread marked as resolved.\n */\n public async markThreadAsResolved(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/mark-as-resolved`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Mark a thread as unresolved.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to mark as unresolved.\n * @param params.data.userId The user ID of the user who marked the thread as unresolved.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread marked as unresolved.\n */\n public async markThreadAsUnresolved(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<ThreadData<TM, CM>> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/mark-as-unresolved`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToThreadData((await res.json()) as ThreadDataPlain<TM, CM>);\n }\n\n /**\n * Subscribes a user to a thread.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to subscribe to.\n * @param params.data.userId The user ID of the user to subscribe to the thread.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The thread subscription.\n */\n public async subscribeToThread(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<SubscriptionData> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/subscribe`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToSubscriptionData(\n (await res.json()) as SubscriptionDataPlain\n );\n }\n\n /**\n * Unsubscribes a user from a thread.\n * @param params.roomId The room ID of the thread.\n * @param params.threadId The thread ID to unsubscribe from.\n * @param params.data.userId The user ID of the user to unsubscribe from the thread.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async unsubscribeFromThread(\n params: { roomId: string; threadId: string; data: { userId: string } },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/unsubscribe`,\n { userId: params.data.userId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Updates the metadata of the specified thread in a room.\n * @param params.roomId The room ID to update the thread in.\n * @param params.threadId The thread ID to update.\n * @param params.data.metadata The metadata for the thread. Value must be a string, boolean or number\n * @param params.data.userId The user ID of the user who updated the thread.\n * @param params.data.updatedAt (optional) The date the thread is set to be updated.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated thread metadata.\n */\n public async editThreadMetadata(\n params: {\n roomId: string;\n threadId: string;\n data: { metadata: Patchable<TM>; userId: string; updatedAt?: Date };\n },\n options?: RequestOptions\n ): Promise<TM> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/metadata`,\n {\n ...data,\n updatedAt: data.updatedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as TM;\n }\n\n /**\n * Updates the metadata of the specified comment in a room.\n * @param params.roomId The room ID to update the comment in.\n * @param params.threadId The thread ID to update the comment in.\n * @param params.commentId The comment ID to update.\n * @param params.data.metadata The metadata for the comment. Value must be a string, boolean or number. Use null to delete a key.\n * @param params.data.userId The user ID of the user who updated the comment.\n * @param params.data.updatedAt (optional) The date the comment metadata is set to be updated.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The updated comment metadata.\n */\n public async editCommentMetadata(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: { metadata: Patchable<CM>; userId: string; updatedAt?: Date };\n },\n options?: RequestOptions\n ): Promise<CM> {\n const { roomId, threadId, commentId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}/metadata`,\n {\n ...data,\n updatedAt: data.updatedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as CM;\n }\n\n /**\n * Adds a new comment reaction to a comment.\n * @param params.roomId The room ID to add the comment reaction in.\n * @param params.threadId The thread ID to add the comment reaction in.\n * @param params.commentId The comment ID to add the reaction in.\n * @param params.data.emoji The (emoji) reaction to add.\n * @param params.data.userId The user ID of the user associated with the reaction.\n * @param params.data.createdAt (optional) The date the reaction is set to be created.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The created comment reaction.\n */\n public async addCommentReaction(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: { emoji: string; userId: string; createdAt?: Date };\n },\n options?: RequestOptions\n ): Promise<CommentUserReaction> {\n const { roomId, threadId, commentId, data } = params;\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}/add-reaction`,\n {\n ...data,\n createdAt: data.createdAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const reaction = (await res.json()) as CommentUserReactionPlain;\n return convertToCommentUserReaction(reaction);\n }\n\n /**\n * Removes a reaction from a comment.\n * @param params.roomId The room ID to remove the comment reaction from.\n * @param params.threadId The thread ID to remove the comment reaction from.\n * @param params.commentId The comment ID to remove the reaction from.\n * @param params.data.emoji The (emoji) reaction to remove.\n * @param params.data.userId The user ID of the user associated with the reaction.\n * @param params.data.removedAt (optional) The date the reaction is set to be removed.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async removeCommentReaction(\n params: {\n roomId: string;\n threadId: string;\n commentId: string;\n data: {\n emoji: string;\n userId: string;\n removedAt?: Date;\n };\n },\n options?: RequestOptions\n ): Promise<void> {\n const { roomId, threadId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/threads/${threadId}/comments/${params.commentId}/remove-reaction`,\n {\n ...data,\n removedAt: data.removedAt?.toISOString(),\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns the inbox notifications for a user.\n * @param params.userId The user ID to get the inbox notifications from.\n * @param params.inboxNotificationId The ID of the inbox notification to get.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getInboxNotification(\n params: {\n userId: string;\n inboxNotificationId: string;\n },\n options?: RequestOptions\n ): Promise<InboxNotificationData> {\n const { userId, inboxNotificationId } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/inbox-notifications/${inboxNotificationId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return convertToInboxNotificationData(\n (await res.json()) as InboxNotificationDataPlain\n );\n }\n\n /**\n * Returns the inbox notifications for a user.\n * @param params.userId The user ID to get the inbox notifications from.\n * @param params.query The query to filter inbox notifications by. It is based on our query language and can filter by unread.\n * @param params.tenantId (optional) The tenant ID to get the inbox notifications for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getInboxNotifications(\n params: GetInboxNotificationsOptions,\n options?: RequestOptions\n ): Promise<Page<InboxNotificationData>> {\n const { userId, tenantId, limit, startingAfter } = params;\n\n let query: string | undefined;\n\n if (typeof params.query === \"string\") {\n query = params.query;\n } else if (typeof params.query === \"object\") {\n query = objectToQuery(params.query);\n }\n\n const res = await this.#get(\n url`/v2/users/${userId}/inbox-notifications`,\n {\n query,\n limit,\n startingAfter,\n tenantId,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<InboxNotificationDataPlain>;\n return {\n ...page,\n data: page.data.map(convertToInboxNotificationData),\n };\n }\n\n /**\n * Iterates over all inbox notifications for a user.\n *\n * The difference with .getInboxNotifications() is that pagination will\n * happen automatically under the hood, using the given `pageSize`.\n *\n * @param criteria.userId The user ID to get the inbox notifications from.\n * @param criteria.query The query to filter inbox notifications by. It is based on our query language and can filter by unread.\n * @param criteria.tenantId (optional) The tenant ID to get the inbox notifications for.\n * @param options.pageSize (optional) The page size to use for each request.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n async *iterInboxNotifications(\n criteria: InboxNotificationsQueryCriteria,\n options?: RequestOptions & { pageSize?: number }\n ): AsyncGenerator<InboxNotificationData> {\n // TODO Dry up this async iterable implementation for pagination\n const { signal } = options ?? {};\n const pageSize = checkBounds(\"pageSize\", options?.pageSize ?? 50, 10);\n\n let cursor: string | undefined = undefined;\n while (true) {\n const { nextCursor, data } = await this.getInboxNotifications(\n { ...criteria, startingAfter: cursor, limit: pageSize },\n { signal }\n );\n for (const item of data) {\n yield item;\n }\n if (!nextCursor) {\n break;\n }\n cursor = nextCursor;\n }\n }\n\n /**\n * Returns all room subscription settings for a user.\n * @param params.userId The user ID to get the room subscription settings from.\n * @param params.tenantId (optional) The tenant ID to get the room subscription settings for.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param params.limit (optional) The number of items to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getUserRoomSubscriptionSettings(\n params: { userId: string; tenantId?: string } & PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<UserRoomSubscriptionSettings>> {\n const { userId, tenantId, startingAfter, limit } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/room-subscription-settings`,\n {\n tenantId,\n startingAfter,\n limit,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as Page<UserRoomSubscriptionSettings>;\n }\n\n /**\n * Gets the user's room subscription settings.\n * @param params.userId The user ID to get the room subscription settings from.\n * @param params.roomId The room ID to get the room subscription settings from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n },\n options?: RequestOptions\n ): Promise<RoomSubscriptionSettings> {\n const { userId, roomId } = params;\n\n const res = await this.#get(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as RoomSubscriptionSettings;\n }\n\n /**\n * Updates the user's room subscription settings.\n * @param params.userId The user ID to update the room subscription settings for.\n * @param params.roomId The room ID to update the room subscription settings for.\n * @param params.data The new room subscription settings for the user.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n data: Partial<RoomSubscriptionSettings>;\n },\n options?: RequestOptions\n ): Promise<RoomSubscriptionSettings> {\n const { userId, roomId, data } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n data,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n return (await res.json()) as RoomSubscriptionSettings;\n }\n\n /**\n * Delete the user's room subscription settings.\n * @param params.userId The user ID to delete the room subscription settings from.\n * @param params.roomId The room ID to delete the room subscription settings from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteRoomSubscriptionSettings(\n params: {\n userId: string;\n roomId: string;\n },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, roomId } = params;\n\n const res = await this.#delete(\n url`/v2/rooms/${roomId}/users/${userId}/subscription-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Update a room ID.\n * @param params.roomId The current ID of the room.\n * @param params.newRoomId The new room ID.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateRoomId(\n params: {\n currentRoomId: string;\n newRoomId: string;\n },\n options?: RequestOptions\n ): Promise<RoomData> {\n const { currentRoomId, newRoomId } = params;\n\n const res = await this.#post(\n url`/v2/rooms/${currentRoomId}/update-room-id`,\n { newRoomId },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as RoomDataPlain;\n return inflateRoomData(data);\n }\n\n /**\n * Triggers an inbox notification for a user.\n * @param params.userId The user ID to trigger the inbox notification for.\n * @param params.kind The kind of inbox notification to trigger.\n * @param params.subjectId The subject ID of the triggered inbox notification.\n * @param params.activityData The activity data of the triggered inbox notification.\n * @param params.roomId (optional) The room ID to trigger the inbox notification for.\n * @param params.tenantId (optional) The tenant ID to trigger the inbox notification for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async triggerInboxNotification<K extends KDAD>(\n params: {\n userId: string;\n kind: K;\n roomId?: string;\n tenantId?: string;\n subjectId: string;\n activityData: DAD[K];\n },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#post(\n url`/v2/inbox-notifications/trigger`,\n params,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes an inbox notification for a user.\n * @param params.userId The user ID for which to delete the inbox notification.\n * @param params.inboxNotificationId The ID of the inbox notification to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteInboxNotification(\n params: {\n userId: string;\n inboxNotificationId: string;\n },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, inboxNotificationId } = params;\n\n const res = await this.#delete(\n url`/v2/users/${userId}/inbox-notifications/${inboxNotificationId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes all inbox notifications for a user.\n * @param params.userId The user ID for which to delete all the inbox notifications.\n * @param params.tenantId (optional) The tenant ID to delete the inbox notifications for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteAllInboxNotifications(\n params: { userId: string; tenantId?: string },\n options?: RequestOptions\n ): Promise<void> {\n const { userId, tenantId } = params;\n\n const res = await this.#delete(\n url`/v2/users/${userId}/inbox-notifications`,\n { tenantId },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Get notification settings for a user for a project.\n * @param params.userId The user ID to get the notifications settings for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getNotificationSettings(\n params: { userId: string },\n options?: RequestOptions\n ): Promise<NotificationSettings> {\n const { userId } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/notification-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const plainSettings = (await res.json()) as NotificationSettingsPlain;\n const settings = createNotificationSettings(plainSettings);\n\n return settings;\n }\n\n /**\n * Update the user's notification settings.\n * @param params.userId The user ID to update the notification settings for.\n * @param params.data The new notification settings for the user.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async updateNotificationSettings(\n params: { userId: string; data: PartialNotificationSettings },\n options?: RequestOptions\n ): Promise<NotificationSettings> {\n const { userId, data } = params;\n\n const res = await this.#post(\n url`/v2/users/${userId}/notification-settings`,\n data,\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const plainSettings = (await res.json()) as NotificationSettingsPlain;\n const settings = createNotificationSettings(plainSettings);\n\n return settings;\n }\n\n /**\n * Delete the user's notification settings\n * @param params.userId The user ID to update the notification settings for.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteNotificationSettings(\n params: { userId: string },\n options?: RequestOptions\n ): Promise<void> {\n const { userId } = params;\n const res = await this.#delete(\n url`/v2/users/${userId}/notification-settings`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Create a group\n * @param params.groupId The ID of the group to create.\n * @param params.memberIds The IDs of the members to add to the group.\n * @param params.tenantId (optional) The tenant ID to create the group for.\n * @param params.scopes (optional) The scopes to grant to the group. The default is `{ mention: true }`.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async createGroup(\n params: {\n groupId: string;\n memberIds?: string[];\n tenantId?: string;\n scopes?: GroupScopes;\n },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups`,\n {\n ...params,\n\n // The REST API uses `id` since a group is a resource,\n // but we use `groupId` here for consistency with the other methods.\n id: params.groupId,\n },\n options\n );\n\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Get a group\n * @param params.groupId The ID of the group to get.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getGroup(\n params: { groupId: string },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#get(\n url`/v2/groups/${params.groupId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Add members to a group\n * @param params.groupId The ID of the group to add members to.\n * @param params.memberIds The IDs of the members to add to the group.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async addGroupMembers(\n params: { groupId: string; memberIds: string[] },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups/${params.groupId}/add-members`,\n { memberIds: params.memberIds },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Remove members from a group\n * @param params.groupId The ID of the group to remove members from.\n * @param params.memberIds The IDs of the members to remove from the group.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async removeGroupMembers(\n params: { groupId: string; memberIds: string[] },\n options?: RequestOptions\n ): Promise<GroupData> {\n const res = await this.#post(\n url`/v2/groups/${params.groupId}/remove-members`,\n { memberIds: params.memberIds },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const group = (await res.json()) as GroupDataPlain;\n return convertToGroupData(group);\n }\n\n /**\n * Delete a group\n * @param params.groupId The ID of the group to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteGroup(\n params: { groupId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/groups/${params.groupId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Get all groups\n * @param params.limit (optional) The number of groups to return.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getGroups(\n params?: PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<GroupData>> {\n const res = await this.#get(\n url`/v2/groups`,\n { startingAfter: params?.startingAfter, limit: params?.limit },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<GroupDataPlain>;\n return {\n ...page,\n data: page.data.map(convertToGroupData),\n };\n }\n\n /**\n * Returns all groups a user is a member of.\n * @param params.userId The user ID to get the groups for.\n * @param params.startingAfter (optional) The cursor to start the pagination from.\n * @param params.limit (optional) The number of items to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getUserGroups(\n params: { userId: string } & PaginationOptions,\n options?: RequestOptions\n ): Promise<Page<GroupData>> {\n const { userId, startingAfter, limit } = params;\n\n const res = await this.#get(\n url`/v2/users/${userId}/groups`,\n { startingAfter, limit },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const page = (await res.json()) as Page<GroupDataPlain>;\n\n return {\n ...page,\n data: page.data.map(convertToGroupData),\n };\n }\n\n /**\n * Retrieves the current Storage contents for the given room ID and calls the\n * provided callback function, in which you can mutate the Storage contents\n * at will.\n *\n * If you need to run the same mutation across multiple rooms, prefer using\n * `.massMutateStorage()` instead of looping over room IDs yourself.\n */\n public async mutateStorage(\n roomId: string,\n callback: MutateStorageCallback,\n options?: MutateStorageOptions\n ): Promise<void> {\n return this.#_mutateOneRoom(roomId, undefined, callback, options);\n }\n\n /**\n * Retrieves the Storage contents for each room that matches the given\n * criteria and calls the provided callback function, in which you can mutate\n * the Storage contents at will.\n *\n * You can use the `criteria` parameter to select which rooms to process by\n * their metadata. If you pass `{}` (empty object), all rooms will be\n * selected and processed.\n *\n * This method will execute mutations in parallel, using the specified\n * `concurrency` value. If you which to run the mutations serially, set\n * `concurrency` to 1.\n */\n public async massMutateStorage(\n criteria: RoomsQueryCriteria,\n callback: MassMutateStorageCallback,\n massOptions?: MassMutateStorageOptions\n ): Promise<void> {\n const concurrency = checkBounds(\n \"concurrency\",\n massOptions?.concurrency ?? 8,\n 1,\n 20\n );\n\n // Try to select a reasonable page size based on the concurrency level, but\n // at least never less than 20.\n const pageSize = Math.max(20, concurrency * 4);\n const { signal } = massOptions ?? {};\n const rooms = this.iterRooms(criteria, { pageSize, signal });\n\n const options = { signal };\n await runConcurrently(\n rooms,\n (roomData) =>\n this.#_mutateOneRoom(roomData.id, roomData, callback, options),\n concurrency\n );\n }\n\n async #_mutateOneRoom<RD extends RoomData | undefined>(\n roomId: string,\n room: RD,\n callback: (context: { room: RD; root: LiveObject<S> }) => Awaitable<void>,\n options?: MutateStorageOptions\n ): Promise<void> {\n // Hard-coded for now, see https://github.com/liveblocks/liveblocks/pull/2293#issuecomment-2740067249\n const debounceInterval = 200;\n\n // The plan:\n // 1. Create a new pool\n // 2. Download the storage contents\n // 3. Construct the Live tree\n // 4. Run the callback\n // 5. Capture all the changes to the pool\n // 6. Send the resulting ops to the server at a throttled interval\n\n const { signal, abort } = makeAbortController(options?.signal);\n\n // Set up a \"debouncer\": we'll flush the buffered ops to the server if\n // there hasn't been an update to the buffered ops for a while. This\n // behavior is slightly different from the browser client, which will emit\n // ops as soon as they are available (= throttling)\n let opsBuffer: ClientWireOp[] = [];\n let outstandingFlush$: Promise<void> | undefined = undefined;\n let lastFlush = performance.now();\n\n const flushIfNeeded = (force: boolean) => {\n if (opsBuffer.length === 0)\n // Nothing to do\n return;\n\n if (outstandingFlush$) {\n // There already is an outstanding flush, wait for it to complete\n return;\n }\n\n const now = performance.now();\n if (!(force || now - lastFlush > debounceInterval)) {\n // We're still within the debounce window, do nothing right now\n return;\n }\n\n // All good, flush right now\n lastFlush = now;\n const ops = opsBuffer;\n opsBuffer = [];\n\n outstandingFlush$ = this.#sendMessage(\n roomId,\n [{ type: ClientMsgCode.UPDATE_STORAGE, ops }],\n { signal }\n )\n .catch((err) => {\n // For now, if any error happens during one of the flushes, abort the entire thing\n // TODO Think about more error handling control options here later (auto-retry, etc)\n abort(err);\n })\n .finally(() => {\n outstandingFlush$ = undefined;\n });\n };\n\n // Download the storage contents\n try {\n const resp = await this.#requestStorageMutation(roomId, { signal });\n const { actor, nodes } = resp;\n\n // Create a new pool\n const pool = createManagedPool(roomId, {\n getCurrentConnectionId: () => actor,\n onDispatch: (\n ops: ClientWireOp[],\n _reverse: Op[],\n _storageUpdates: Map<string, StorageUpdate>\n ) => {\n if (ops.length === 0) return;\n\n // Capture all the changes to the pool\n for (const op of ops) {\n opsBuffer.push(op);\n }\n flushIfNeeded(/* force */ false);\n },\n });\n\n // Construct the Live tree\n const root = LiveObject._fromItems<S>(nodes, pool);\n\n // Run the callback\n const callback$ = callback({ room, root });\n\n // If the callback synchronously makes changes, we'll want to flush those as soon as possible, then flush on an interval for the remainder of the async callback.\n flushIfNeeded(/* force */ true);\n\n await callback$;\n } catch (e) {\n abort();\n throw e;\n } finally {\n // Await any outstanding flushes, and then flush one last time\n await outstandingFlush$; // eslint-disable-line @typescript-eslint/await-thenable\n flushIfNeeded(/* force */ true);\n await outstandingFlush$; // eslint-disable-line @typescript-eslint/await-thenable\n }\n }\n\n async #sendMessage(\n roomId: string,\n messages: ClientMsg<JsonObject, Json>[],\n options?: RequestOptions\n ) {\n const res = await this.#post(\n url`/v2/rooms/${roomId}/send-message`,\n { messages },\n { signal: options?.signal }\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n // TODO: If res.ok, it will be a 200 response containing all returned Ops.\n // These may include fix ops, which should get applied back to the managed\n // pool.\n // TODO Implement the handling of fix-ops:\n // const data = (await res.json()) as {\n // messages: readonly (\n // | ServerMsg<JsonObject, BaseUserMeta, Json>\n // | readonly ServerMsg<JsonObject, BaseUserMeta, Json>[]\n // )[];\n // };\n // return data;\n }\n\n /**\n * Returns a paginated list of AI copilots. The copilots are returned sorted by creation date, from newest to oldest.\n * @param params.limit (optional) A limit on the number of copilots to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of AI copilots.\n */\n public async getAiCopilots(\n params: PaginationOptions = {},\n options?: RequestOptions\n ): Promise<Page<AiCopilot>> {\n const res = await this.#get(\n url`/v2/ai/copilots`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<AiCopilotPlain>;\n return {\n ...page,\n data: page.data.map(inflateAiCopilot),\n };\n }\n\n /**\n * Creates an AI copilot.\n * @param params The parameters to create the copilot with.\n * @returns The created copilot.\n */\n public async createAiCopilot(\n params: CreateAiCopilotOptions,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#post(url`/v2/ai/copilots`, params, options);\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Returns an AI copilot with the given id.\n * @param copilotId The id of the copilot to return.\n * @returns The copilot with the given id.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async getAiCopilot(\n copilotId: string,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#get(\n url`/v2/ai/copilots/${copilotId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Updates an AI copilot with the given id.\n * @param copilotId The id of the copilot to update.\n * @param params The parameters to update the copilot with.\n * @returns The updated copilot.\n */\n public async updateAiCopilot(\n copilotId: string,\n params: UpdateAiCopilotOptions,\n options?: RequestOptions\n ): Promise<AiCopilot> {\n const res = await this.#post(\n url`/v2/ai/copilots/${copilotId}`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as AiCopilotPlain;\n return inflateAiCopilot(data);\n }\n\n /**\n * Deletes an AI copilot with the given id. A deleted copilot is no longer accessible from the API or the dashboard and it cannot be restored.\n * @param copilotId The id of the copilot to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteAiCopilot(\n copilotId: string,\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${copilotId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Creates a web knowledge source.\n * @param params.url The URL of the web knowledge source.\n * @param params.type The type of the web knowledge source: \"individual_link\", \"crawl\" or \"sitemap\".\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The id of the created web knowledge source.\n */\n public async createWebKnowledgeSource(\n params: CreateWebKnowledgeSourceOptions,\n options?: RequestOptions\n ): Promise<{ id: string }> {\n const res = await this.#post(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web`,\n params,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string };\n return data;\n }\n\n /**\n * Creates a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.name The name of the file knowledge source.\n * @param params.file The file to create the knowledge source from.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The id of the created file knowledge source.\n */\n public async createFileKnowledgeSource(\n params: CreateFileKnowledgeSourceOptions,\n options?: RequestOptions\n ): Promise<{ id: string }> {\n const fetch = await fetchPolyfill();\n const res = await fetch(\n urljoin(\n this.#baseUrl,\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.file.name}`\n ),\n {\n method: \"PUT\",\n body: params.file,\n headers: {\n Authorization: `Bearer ${this.#secret}`,\n \"Content-Type\": params.file.type,\n \"Content-Length\": String(params.file.size),\n },\n signal: options?.signal,\n }\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string };\n return data;\n }\n\n /**\n * Deletes a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteFileKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Deletes a web knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to delete.\n * @param options.signal (optional) An abort signal to cancel the request.\n */\n public async deleteWebKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<void> {\n const res = await this.#delete(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n }\n\n /**\n * Returns a paginated list of knowledge sources.\n * @param params.copilotId The id of the copilot.\n * @param params.limit (optional) A limit on the number of knowledge sources to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of knowledge sources.\n */\n public async getKnowledgeSources(\n params: GetKnowledgeSourcesOptions,\n options?: RequestOptions\n ): Promise<Page<KnowledgeSource>> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<KnowledgeSourcePlain>;\n return {\n ...page,\n data: page.data.map(inflateKnowledgeSource),\n };\n }\n\n /**\n * Returns a knowledge source with the given id.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source to return.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The knowledge source.\n */\n public async getKnowledgeSource(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<KnowledgeSource> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as KnowledgeSourcePlain;\n return inflateKnowledgeSource(data);\n }\n\n /**\n * Returns the content of a file knowledge source.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns The content of the file knowledge source.\n */\n public async getFileKnowledgeSourceMarkdown(\n params: { copilotId: string; knowledgeSourceId: string },\n options?: RequestOptions\n ): Promise<string> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`,\n undefined,\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const data = (await res.json()) as { id: string; content: string };\n return data.content;\n }\n\n /**\n * Returns a paginated list of web knowledge source links.\n * @param params.copilotId The id of the copilot.\n * @param params.knowledgeSourceId The id of the knowledge source.\n * @param params.limit (optional) A limit on the number of links to return. The limit can range between 1 and 100, and defaults to 20.\n * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.\n * @param options.signal (optional) An abort signal to cancel the request.\n * @returns A paginated list of web knowledge source links.\n */\n public async getWebKnowledgeSourceLinks(\n params: GetWebKnowledgeSourceLinksOptions,\n options?: RequestOptions\n ): Promise<Page<WebKnowledgeSourceLink>> {\n const res = await this.#get(\n url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}/links`,\n {\n limit: params.limit,\n startingAfter: params.startingAfter,\n },\n options\n );\n if (!res.ok) {\n throw await LiveblocksError.from(res);\n }\n const page = (await res.json()) as Page<WebKnowledgeSourceLinkPlain>;\n return {\n ...page,\n data: page.data.map(inflateWebKnowledgeSourceLink),\n };\n }\n}\n\nexport class LiveblocksError extends Error {\n readonly status: number;\n readonly details?: string;\n\n private constructor(message: string, status: number, details?: string) {\n super(message);\n this.name = \"LiveblocksError\";\n this.status = status;\n this.details = details;\n }\n\n public toString(): string {\n let msg = `${this.name}: ${this.message} (status ${this.status})`;\n if (this.details) {\n msg += `\\n${this.details}`;\n }\n return msg;\n }\n\n static async from(res: Response): Promise<LiveblocksError> {\n // Retain the stack trace of the original error location, not the async return point\n const origErrLocation = new Error();\n Error.captureStackTrace(origErrLocation, LiveblocksError.from); // eslint-disable-line\n\n const FALLBACK = \"An error happened without an error message\";\n let text: string;\n try {\n text = await res.text();\n } catch {\n text = FALLBACK;\n }\n const obj = (tryParseJson(text) ?? { message: text }) as JsonObject;\n\n const message = (obj.message || FALLBACK) as string;\n const details =\n [\n obj.suggestion ? `Suggestion: ${String(obj.suggestion)}` : undefined,\n obj.docs ? `See also: ${String(obj.docs)}` : undefined,\n ]\n .filter(Boolean)\n .join(\"\\n\") || undefined;\n\n const err = new LiveblocksError(message, res.status, details);\n err.stack = origErrLocation.stack;\n return err;\n }\n}\n","export async function asyncConsume<T>(\n iterable: AsyncIterable<T>\n): Promise<T[]> {\n const result: T[] = [];\n for await (const item of iterable) {\n result.push(item);\n }\n return result;\n}\n\n/**\n * Iterates an async iterable, invoking a side-effect on every element. Will\n * run at most `concurrency` callbacks simultaneously.\n */\nexport async function runConcurrently<T>(\n iterable: AsyncIterable<T>,\n fn: (item: T) => Promise<void>,\n concurrency: number\n): Promise<void> {\n const queue = new Set<Promise<void>>();\n\n for await (const item of iterable) {\n // If we've reached max concurrency, wait for one task to complete\n if (queue.size >= concurrency) {\n await Promise.race(queue);\n }\n\n // Process the next item in the queue\n const promise = (async () => {\n try {\n await fn(item);\n } finally {\n // @ts-expect-error var used before it was assigned, but it's fine\n queue.delete(promise);\n }\n })();\n\n queue.add(promise);\n }\n\n // Wait for any remaining tasks\n if (queue.size > 0) {\n await Promise.all(queue);\n }\n}\n","import type { Json } from \"@liveblocks/core\";\n\nexport class LineStream extends TransformStream<string, string> {\n constructor() {\n let buffer: string = \"\";\n\n super({\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>\n ) {\n // Append the chunk to the buffer\n buffer += chunk;\n if (buffer.includes(\"\\n\")) {\n // Split the buffer into lines\n const lines = buffer.split(\"\\n\");\n\n // Emit all lines... except the last one!\n for (let i = 0; i < lines.length - 1; i++) {\n // Skip empty lines\n if (lines[i]!.length > 0) {\n controller.enqueue(lines[i]);\n }\n }\n\n // Update the buffer with the last line (might be incomplete)\n buffer = lines[lines.length - 1]!;\n }\n },\n\n flush(controller: TransformStreamDefaultController<string>) {\n // Emit the remaining buffer as a line\n if (buffer.length > 0) {\n controller.enqueue(buffer);\n }\n },\n });\n }\n}\n\nexport class NdJsonStream<J extends Json> extends TransformStream<string, J> {\n constructor() {\n super({\n transform(\n line: string,\n controller: TransformStreamDefaultController<Json>\n ) {\n // Here, we _want_ JSON.parse() to throw if its input is invalid\n const json = JSON.parse(line) as J;\n controller.enqueue(json);\n },\n });\n }\n}\n","import type {\n IUserInfo,\n Json,\n JsonObject,\n URLSafeString,\n} from \"@liveblocks/core\";\nimport { url } from \"@liveblocks/core\";\n\nimport type { AuthResponse } from \"./client\";\nimport { assertNonEmpty, normalizeStatusCode } from \"./utils\";\n\n// As defined in the source of truth in ApiScope in\n// https://github.com/liveblocks/liveblocks-cloudflare/blob/main/src/security.ts\nconst ALL_PERMISSIONS = Object.freeze([\n \"room:write\",\n \"room:read\",\n \"room:presence:write\",\n \"comments:write\",\n \"comments:read\",\n] as const);\n\nexport type Permission = (typeof ALL_PERMISSIONS)[number];\n\nfunction isPermission(value: string): value is Permission {\n return (ALL_PERMISSIONS as readonly unknown[]).includes(value);\n}\n\nconst MAX_PERMS_PER_SET = 10;\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * read permissions to the storage and comments data for this room. (Note that\n * the user will still have permissions to update their own presence.)\n */\nconst READ_ACCESS = Object.freeze([\n \"room:read\",\n \"room:presence:write\",\n \"comments:read\",\n] as const);\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * permissions to read and write to the room's storage and comments.\n */\nconst FULL_ACCESS = Object.freeze([\"room:write\", \"comments:write\"] as const);\n\nconst roomPatternRegex = /^([*]|[^*]{1,128}[*]?)$/;\n\ntype PostFn = (path: URLSafeString, json: Json) => Promise<Response>;\n\n/**\n * Class to help you construct the exact permission set to grant a user, used\n * when making `.authorizeUser()` calls.\n *\n * Usage:\n *\n * const session = liveblocks.prepareSession();\n * session.allow(roomId, permissions) // or...\n *\n * For the `permissions` argument, you can pass a list of specific permissions,\n * or use one of our presets:\n *\n * session.allow('my-room', session.FULL_ACCESS) // Read + write access to room storage and comments\n * session.allow('my-room', session.READ_ACCESS) // Read-only access to room storage and comments\n *\n * Rooms can be specified with a prefix match, if the name ends in an asterisk.\n * In that case, access is granted to *all* rooms that start with that prefix:\n *\n * // Read + write access to *all* rooms that start with \"abc:\"\n * session.allow('abc:*', session.FULL_ACCESS)\n *\n * You can define at most 10 room IDs (or patterns) in a single token,\n * otherwise the token would become too large and unwieldy.\n *\n * All permissions granted are additive. You cannot \"remove\" permissions once\n * you grant them. For example:\n *\n * session\n * .allow('abc:*', session.FULL_ACCESS)\n * .allow('abc:123', session.READ_ACCESS)\n *\n * Here, room `abc:123` would have full access. The second .allow() call only\n * _adds_ read permissions, but that has no effect since full access\n * permissions were already added to the set.\n */\nexport class Session {\n public readonly FULL_ACCESS = FULL_ACCESS;\n public readonly READ_ACCESS = READ_ACCESS;\n\n #postFn: PostFn;\n #userId: string;\n #userInfo?: IUserInfo;\n #tenantId?: string;\n #sealed = false;\n readonly #permissions: Map<string, Set<Permission>> = new Map();\n\n /** @internal */\n constructor(\n postFn: PostFn,\n userId: string,\n userInfo?: IUserInfo,\n tenantId?: string\n ) {\n assertNonEmpty(userId, \"userId\"); // TODO: Check if this is a legal userId value too\n\n this.#postFn = postFn;\n this.#userId = userId;\n this.#userInfo = userInfo;\n this.#tenantId = tenantId;\n }\n\n #getOrCreate(roomId: string): Set<Permission> {\n if (this.#sealed) {\n throw new Error(\"You can no longer change these permissions.\");\n }\n\n let perms = this.#permissions.get(roomId);\n if (perms) {\n return perms;\n } else {\n if (this.#permissions.size >= MAX_PERMS_PER_SET) {\n throw new Error(\n \"You cannot add permissions for more than 10 rooms in a single token\"\n );\n }\n\n perms = new Set<Permission>();\n this.#permissions.set(roomId, perms);\n return perms;\n }\n }\n\n public allow(roomIdOrPattern: string, newPerms: readonly Permission[]): this {\n if (typeof roomIdOrPattern !== \"string\") {\n throw new Error(\"Room name or pattern must be a string\");\n }\n if (!roomPatternRegex.test(roomIdOrPattern)) {\n throw new Error(\"Invalid room name or pattern\");\n }\n\n if (newPerms.length === 0) {\n throw new Error(\"Permission list cannot be empty\");\n }\n\n const existingPerms = this.#getOrCreate(roomIdOrPattern);\n for (const perm of newPerms) {\n if (!isPermission(perm as string)) {\n throw new Error(`Not a valid permission: ${perm}`);\n }\n existingPerms.add(perm);\n }\n return this; // To allow chaining multiple allow calls\n }\n\n /** @internal - For unit tests only */\n public hasPermissions(): boolean {\n return this.#permissions.size > 0;\n }\n\n /** @internal - For unit tests only */\n public seal(): void {\n if (this.#sealed) {\n throw new Error(\n \"You cannot reuse Session instances. Please create a new session every time.\"\n );\n }\n this.#sealed = true;\n }\n\n /** @internal - For unit tests only */\n public serializePermissions(): JsonObject {\n return Object.fromEntries(\n Array.from(this.#permissions.entries()).map(([pat, perms]) => [\n pat,\n Array.from(perms),\n ])\n );\n }\n\n /**\n * Call this to authorize the session to access Liveblocks. Note that this\n * will return a Liveblocks \"access token\". Anyone that obtains such access\n * token will have access to the allowed resources.\n */\n public async authorize(): Promise<AuthResponse> {\n this.seal();\n if (!this.hasPermissions()) {\n console.warn(\n \"Access tokens without any permission will not be supported soon, you should use wildcards when the client requests a token for resources outside a room. See https://liveblocks.io/docs/errors/liveblocks-client/access-tokens-not-enough-permissions\"\n );\n }\n\n try {\n const resp = await this.#postFn(url`/v2/authorize-user`, {\n // Required\n userId: this.#userId,\n permissions: this.serializePermissions(),\n\n // Optional metadata\n userInfo: this.#userInfo,\n tenantId: this.#tenantId,\n });\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: 'Call to /v2/authorize-user failed. See \"error\" for more information.',\n error: er as Error | undefined,\n };\n }\n }\n}\n","const DEFAULT_BASE_URL = \"https://api.liveblocks.io\";\n\n// Valid alphabet for secret/public keys\nconst VALID_KEY_CHARS_REGEX = /^[\\w-]+$/;\n\nexport function getBaseUrl(baseUrl?: string | undefined): string {\n if (\n typeof baseUrl === \"string\" &&\n baseUrl.startsWith(\"http\") // Must be http or https URL\n ) {\n return baseUrl;\n } else {\n return DEFAULT_BASE_URL;\n }\n}\n\nexport async function fetchPolyfill(): Promise<typeof fetch> {\n return typeof globalThis.fetch !== \"undefined\"\n ? globalThis.fetch\n : ((await import(\"node-fetch\")).default as unknown as typeof fetch);\n}\n\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nexport function startsWith<P extends string>(\n value: unknown,\n prefix: P\n): value is `${P}${string}` {\n return isString(value) && value.startsWith(prefix);\n}\n\nfunction isNonEmpty(value: unknown): value is string {\n return isString(value) && value.length > 0;\n}\n\nexport function assertNonEmpty(\n value: unknown,\n field: string\n): asserts value is string {\n if (!isNonEmpty(value)) {\n throw new Error(\n `Invalid value for field '${field}'. Please provide a non-empty string. For more information: https://liveblocks.io/docs/api-reference/liveblocks-node#authorize`\n );\n }\n}\n\nexport function assertSecretKey(\n value: unknown,\n field: string\n): asserts value is string {\n if (!startsWith(value, \"sk_\")) {\n throw new Error(\n `Invalid value for field '${field}'. Secret keys must start with 'sk_'. Please provide the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`\n );\n }\n\n if (!VALID_KEY_CHARS_REGEX.test(value)) {\n throw new Error(\n `Invalid chars found in field '${field}'. Please check that you correctly copied the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`\n );\n }\n}\n\nexport function normalizeStatusCode(statusCode: number): number {\n if (statusCode >= 200 && statusCode < 300) {\n return 200; /* OK */\n } else if (statusCode >= 500) {\n return 503; /* Service Unavailable */\n } else {\n return statusCode; // 429, 404, 403, 409, 422, 400\n }\n}\n","import type { NotificationChannel } from \"@liveblocks/core\";\nimport * as base64 from \"@stablelib/base64\";\nimport * as sha256 from \"fast-sha256\";\nimport type { IncomingHttpHeaders } from \"http\";\n\nimport { isString } from \"./utils\";\n\nexport class WebhookHandler {\n #secretBuffer: Buffer;\n static #secretPrefix = \"whsec_\";\n\n constructor(\n /**\n * The signing secret provided on the dashboard's webhooks page\n * @example \"whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm\"\n */\n secret: string\n ) {\n if (!secret) throw new Error(\"Secret is required\");\n if (typeof secret !== \"string\") throw new Error(\"Secret must be a string\");\n\n if (secret.startsWith(WebhookHandler.#secretPrefix) === false)\n throw new Error(\"Invalid secret, must start with whsec_\");\n\n const secretKey = secret.slice(WebhookHandler.#secretPrefix.length);\n this.#secretBuffer = Buffer.from(secretKey, \"base64\");\n }\n\n /**\n * Verifies a webhook request and returns the event\n */\n public verifyRequest(request: WebhookRequest): WebhookEvent {\n const { headers, rawBody } = request;\n\n const { webhookId, timestamp, rawSignatures } =\n this.#verifyHeaders(headers);\n\n if (typeof rawBody !== \"string\") {\n throw new Error(\n `Invalid rawBody field, must be a string, got \"${typeof rawBody}\" instead. It is likely that you need to JSON.stringify the body before passing it.`\n );\n }\n\n this.#verifyTimestamp(timestamp);\n\n const signature = this.#sign(`${webhookId}.${timestamp}.${rawBody}`);\n\n const expectedSignatures = rawSignatures\n .split(\" \")\n .map((rawSignature) => {\n const [, parsedSignature] = rawSignature.split(\",\");\n return parsedSignature;\n })\n .filter(isNotUndefined);\n\n if (expectedSignatures.includes(signature) === false)\n throw new Error(\n `Invalid signature, expected one of ${expectedSignatures.join(\n \", \"\n )}, got ${signature}`\n );\n\n const event: WebhookEvent = JSON.parse(rawBody) as WebhookEvent;\n\n this.#verifyWebhookEventType(event);\n\n return event;\n }\n\n /**\n * Verifies the headers and returns the webhookId, timestamp and rawSignatures\n */\n #verifyHeaders(headers: IncomingHttpHeaders | Headers) {\n const usingNativeHeaders =\n typeof Headers !== \"undefined\" && headers instanceof Headers;\n const normalizedHeaders = usingNativeHeaders\n ? Object.fromEntries(headers)\n : (headers as IncomingHttpHeaders);\n\n const sanitizedHeaders: IncomingHttpHeaders = {};\n Object.keys(normalizedHeaders).forEach((key) => {\n sanitizedHeaders[key.toLowerCase()] = normalizedHeaders[key];\n });\n\n const webhookId = sanitizedHeaders[\"webhook-id\"];\n if (typeof webhookId !== \"string\")\n throw new Error(\"Invalid webhook-id header\");\n\n const timestamp = sanitizedHeaders[\"webhook-timestamp\"];\n if (typeof timestamp !== \"string\")\n throw new Error(\"Invalid webhook-timestamp header\");\n\n const rawSignatures = sanitizedHeaders[\"webhook-signature\"];\n if (typeof rawSignatures !== \"string\")\n throw new Error(\"Invalid webhook-signature header\");\n\n return { webhookId, timestamp, rawSignatures };\n }\n\n /**\n * Signs the content with the secret\n * @param content\n * @returns `string`\n */\n #sign(content: string): string {\n const encoder = new TextEncoder();\n const toSign = encoder.encode(content);\n return base64.encode(sha256.hmac(this.#secretBuffer, toSign));\n }\n\n /**\n * Verifies that the timestamp is not too old or in the future\n */\n #verifyTimestamp(timestampHeader: string) {\n const now = Math.floor(Date.now() / 1000);\n const timestamp = parseInt(timestampHeader, 10);\n\n if (isNaN(timestamp)) {\n throw new Error(\"Invalid timestamp\");\n }\n\n // Check if timestamp is too old\n if (timestamp < now - WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp too old\");\n }\n\n // Check if timestamp is in the future\n if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp in the future\");\n }\n }\n\n /**\n * Ensures that the event is a known event type\n * or throws and prompts the user to upgrade to a higher version of @liveblocks/node\n */\n #verifyWebhookEventType(event: WebhookEvent): asserts event is WebhookEvent {\n if (\n event &&\n event.type &&\n [\n \"storageUpdated\",\n \"userEntered\",\n \"userLeft\",\n \"roomCreated\",\n \"roomDeleted\",\n \"commentCreated\",\n \"commentEdited\",\n \"commentDeleted\",\n \"commentReactionAdded\",\n \"commentReactionRemoved\",\n \"commentMetadataUpdated\",\n \"threadMetadataUpdated\",\n \"threadCreated\",\n \"threadDeleted\",\n \"ydocUpdated\",\n \"notification\",\n \"threadMarkedAsResolved\",\n \"threadMarkedAsUnresolved\",\n ].includes(event.type)\n ) {\n if (event.type === \"notification\") {\n const notification = event;\n if (\n notification.data.kind === \"thread\" ||\n notification.data.kind === \"textMention\" ||\n isCustomKind(notification.data.kind)\n ) {\n return;\n } else {\n // Using JSON.stringify because `notification.data.kind`\n // is considered as `never` now because of the type guard.\n throw new Error(\n `Unknown notification kind: ${JSON.stringify(notification.data.kind)}`\n );\n }\n }\n\n return;\n }\n\n throw new Error(\n \"Unknown event type, please upgrade to a higher version of @liveblocks/node\"\n );\n }\n}\n\nconst WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; // 5 minutes\n\nconst isNotUndefined = <T>(value: T | undefined): value is T =>\n value !== undefined;\n\ntype WebhookRequest = {\n /**\n * Headers of the request, can be a regular object or a Headers object\n * @example\n * {\n * \"webhook-id\": \"123\",\n * \"webhook-timestamp\": \"1614588800000\",\n * \"webhook-signature\": \"v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=\"\n * }\n *\n * new Headers({\n * \"webhook-id\": \"123\",\n * \"webhook-timestamp\": \"1614588800000\",\n * \"webhook-signature\": \"v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=\"\n * }}\n */\n headers: IncomingHttpHeaders | Headers;\n /**\n * Raw body of the request, do not parse it\n * @example '{\"type\":\"storageUpdated\",\"data\":{\"roomId\":\"my-room-id\",\"appId\":\"my-app-id\",\"updatedAt\":\"2021-03-01T12:00:00.000Z\"}}'\n */\n rawBody: string;\n};\n\n/**\n * When receiving an event we cannot define the `kind`\n * as member of the augmentation\n */\ntype CustomKind = `$${string}`;\n\nconst isCustomKind = (value: unknown): value is CustomKind => {\n return isString(value) && value.startsWith(\"$\");\n};\n\ntype WebhookEvent =\n | StorageUpdatedEvent\n | UserEnteredEvent\n | UserLeftEvent\n | RoomCreatedEvent\n | RoomDeletedEvent\n | CommentCreatedEvent\n | CommentEditedEvent\n | CommentDeletedEvent\n | CommentReactionAdded\n | CommentReactionRemoved\n | CommentMetadataUpdatedEvent\n | ThreadMetadataUpdatedEvent\n | NotificationEvent\n | ThreadCreatedEvent\n | ThreadDeletedEvent\n | ThreadMarkedAsResolvedEvent\n | ThreadMarkedAsUnresolvedEvent\n | YDocUpdatedEvent;\n\ntype StorageUpdatedEvent = {\n type: \"storageUpdated\";\n data: {\n roomId: string;\n projectId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n };\n};\n\ntype UserEnteredEvent = {\n type: \"userEntered\";\n data: {\n projectId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user entered the room.\n */\n enteredAt: string;\n numActiveUsers: number;\n };\n};\n\ntype UserLeftEvent = {\n type: \"userLeft\";\n data: {\n projectId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user left the room.\n */\n leftAt: string;\n numActiveUsers: number;\n };\n};\n\ntype RoomCreatedEvent = {\n type: \"roomCreated\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n };\n};\n\ntype RoomDeletedEvent = {\n type: \"roomDeleted\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype CommentCreatedEvent = {\n type: \"commentCreated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n createdBy: string;\n };\n};\n\ntype CommentEditedEvent = {\n type: \"commentEdited\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n editedAt: string;\n };\n};\n\ntype CommentDeletedEvent = {\n type: \"commentDeleted\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype CommentReactionAdded = {\n type: \"commentReactionAdded\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n emoji: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n addedAt: string;\n addedBy: string;\n };\n};\n\ntype CommentReactionRemoved = {\n type: \"commentReactionRemoved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n emoji: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n removedAt: string;\n removedBy: string;\n };\n};\n\ntype YDocUpdatedEvent = {\n type: \"ydocUpdated\";\n data: {\n projectId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n };\n};\n\ntype ThreadMetadataUpdatedEvent = {\n type: \"threadMetadataUpdated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype CommentMetadataUpdatedEvent = {\n type: \"commentMetadataUpdated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n commentId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadCreatedEvent = {\n type: \"threadCreated\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n createdBy: string;\n };\n};\n\ntype ThreadDeletedEvent = {\n type: \"threadDeleted\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\ntype ThreadMarkedAsResolvedEvent = {\n type: \"threadMarkedAsResolved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadMarkedAsUnresolvedEvent = {\n type: \"threadMarkedAsUnresolved\";\n data: {\n projectId: string;\n roomId: string;\n threadId: string;\n updatedAt: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedBy: string;\n };\n};\n\ntype ThreadNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: \"thread\";\n projectId: string;\n roomId: string;\n userId: string;\n threadId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype TextMentionNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: \"textMention\";\n projectId: string;\n roomId: string;\n userId: string;\n mentionId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype CustomNotificationEvent = {\n type: \"notification\";\n data: {\n channel: NotificationChannel;\n kind: CustomKind;\n projectId: string;\n roomId: string | null;\n userId: string;\n subjectId: string;\n inboxNotificationId: string;\n /**\n * Date representing the time when the webhook event was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n /**\n * Date representing the time when the notification was created.\n *\n * ISO 8601 datestring\n * @example \"2021-03-01T18:00:00.000Z\"\n */\n triggeredAt: string;\n };\n};\n\ntype NotificationEvent =\n | ThreadNotificationEvent\n | TextMentionNotificationEvent\n | CustomNotificationEvent;\n\nexport type {\n CommentCreatedEvent,\n CommentDeletedEvent,\n CommentEditedEvent,\n CommentMetadataUpdatedEvent,\n CommentReactionAdded,\n CommentReactionRemoved,\n CustomNotificationEvent,\n NotificationEvent,\n RoomCreatedEvent,\n RoomDeletedEvent,\n StorageUpdatedEvent,\n TextMentionNotificationEvent,\n ThreadCreatedEvent,\n ThreadDeletedEvent,\n ThreadMarkedAsResolvedEvent,\n ThreadMarkedAsUnresolvedEvent,\n ThreadMetadataUpdatedEvent,\n ThreadNotificationEvent,\n UserEnteredEvent,\n UserLeftEvent,\n WebhookEvent,\n WebhookRequest,\n YDocUpdatedEvent,\n};\n\n/**\n * Type guard to check if a webhook event is a `ThreadNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `ThreadNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isThreadNotificationEvent(\n event: WebhookEvent\n): event is ThreadNotificationEvent {\n return event.type === \"notification\" && event.data.kind === \"thread\";\n}\n\n/**\n * Type guard to check if a webhook event is a `TextMentionNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `TextMentionNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isTextMentionNotificationEvent(\n event: WebhookEvent\n): event is TextMentionNotificationEvent {\n return event.type === \"notification\" && event.data.kind === \"textMention\";\n}\n\n/**\n * Type guard to check if a webhook event is a `CustomNotificationEvent`\n *\n * The check is made against the event type and event data kind.\n * You should use this guard to safely check the webhook event you received\n * when you're expecting a `CustomNotificationEvent`.\n *\n * @param event The webhook event received after calling `webhookHandler.verifyRequest()`.\n * @returns A boolean type predicate.\n */\nexport function isCustomNotificationEvent(\n event: WebhookEvent\n): event is CustomNotificationEvent {\n return event.type === \"notification\" && isCustomKind(event.data.kind);\n}\n"]}
|