From f2a3bf665cfb5feba0c730a17705b6fd6c817e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 14:51:16 +0100 Subject: [PATCH 01/25] feat: add matrix api-first history parity and cursor v1 --- CHANGELOG.md | 14 ++ README.md | 27 +++ package.json | 2 +- src/index.test.ts | 459 +++++++++++++++++++++++++++++++++++- src/index.ts | 588 +++++++++++++++++++++++++++++++++++++++------- 5 files changed, 1003 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 621a0db..a3e8e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.0.0 + +- Major: switched pagination cursors to opaque `mxv1:` format across `fetchMessages`, `fetchChannelMessages`, and `listThreads`. +- Breaking: legacy cursors are rejected (`Invalid cursor format. Expected mxv1 cursor.`). Stored cursors from older versions must be cleared. +- Added `openDM(userId)` with persisted mapping + `m.direct` reuse/create behavior. +- Added `fetchMessage(threadId, messageId)` for context-aware single-message fetch. +- Added `fetchChannelMessages(channelId, options)` for top-level timeline history. +- Reworked `fetchMessages(threadId, options)` to API-first server pagination via `matrix-js-sdk`: + - Room timeline pages use `/messages` API through SDK. + - Thread pages use `/relations` API through SDK. + - Thread pages include root on first page. +- Reworked `listThreads(channelId, options)` to server-backed thread listing via SDK `/threads` support. +- Kept intentionally unsupported in this release: `postEphemeral`, `openModal`, and native `stream`. + ## 0.1.0 - Initial release. diff --git a/README.md b/README.md index 48b4cd1..3dd3c1f 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,33 @@ bun --env-file=examples/.env run examples/bot.ts - Password auth sends configured `device_id` during login. - Use Redis state in production for stable sessions and device IDs. +## Feature Parity (v1+) + +- `openDM(userId)` is supported and reuses/creates direct rooms using Matrix `m.direct` account data plus persisted adapter state. +- `fetchMessage(threadId, messageId)` is supported and validates room/thread context. +- `fetchChannelMessages(channelId, options)` is supported for top-level room timeline messages. +- `fetchMessages(threadId, options)` is API-first and server-paginated (not `room.timeline` dependent). +- `listThreads(channelId, options)` is server-backed via Matrix thread list APIs. +- `postEphemeral`, `openModal`, and native `stream` are intentionally not implemented in this adapter. + +## Breaking Changes In 1.0.0 + +- Cursor format changed to opaque adapter cursors: `mxv1:`. +- Legacy cursor strings from pre-1.0 releases are rejected with `Invalid cursor format. Expected mxv1 cursor.`. +- `fetchMessages(threadId)` for Matrix thread IDs returns root + replies (root included on first page), with server pagination. + +## Cursor Migration + +- Treat all previously stored cursors as invalid when upgrading to `1.x`. +- Drop persisted history cursors and request the first page again. +- New cursors are scoped to method/context and cannot be reused across rooms/threads. + +## Pagination Behavior + +- Paged history methods use `matrix-js-sdk` server APIs. +- Pages are normalized to chronological order. +- `nextCursor` is returned only when the server indicates more results. + ## License MIT diff --git a/package.json b/package.json index 6b92565..c057340 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/chat-adapter-matrix", - "version": "0.1.0", + "version": "1.0.0", "description": "Matrix adapter for chat", "type": "module", "main": "./dist/index.js", diff --git a/src/index.test.ts b/src/index.test.ts index 87258bb..7ecb045 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -13,6 +13,8 @@ function makeEvent(overrides: Record = {}) { getType: () => EventType.RoomMessage, getContent: () => ({ body: "hello" }), getRelation: () => null, + isRelation: () => false, + getServerAggregatedRelation: () => undefined, threadRootId: undefined, isThreadRoot: false, isRedaction: () => false, @@ -23,6 +25,80 @@ function makeEvent(overrides: Record = {}) { return event; } +function makeRawEvent(overrides: Record = {}) { + return { + event_id: "$raw", + room_id: "!room:beeper.com", + origin_server_ts: 1_700_000_000_000, + sender: "@alice:beeper.com", + type: EventType.RoomMessage, + content: { body: "hello" }, + unsigned: {}, + ...overrides, + }; +} + +function mapRawToEvent(raw: Record) { + const content = (raw.content as Record | undefined) ?? {}; + const relatesTo = content["m.relates_to"] as + | Record + | undefined; + const relationType = relatesTo?.rel_type; + const relationEventID = relatesTo?.event_id; + const mappedThreadRootId = + typeof raw.threadRootId === "string" + ? raw.threadRootId + : relationType === "m.thread" && typeof relationEventID === "string" + ? relationEventID + : undefined; + const isThreadRoot = raw.isThreadRoot === true; + + return makeEvent({ + getId: () => (raw.event_id as string | undefined) ?? "$raw", + getRoomId: () => (raw.room_id as string | undefined) ?? "!room:beeper.com", + getTs: () => (raw.origin_server_ts as number | undefined) ?? 1_700_000_000_000, + getSender: () => (raw.sender as string | undefined) ?? "@alice:beeper.com", + getType: () => (raw.type as string | undefined) ?? EventType.RoomMessage, + getContent: () => content, + getRelation: () => + relationType + ? ({ + rel_type: relationType, + } as never) + : null, + isRelation: (expectedRelType: string) => relationType === expectedRelType, + getServerAggregatedRelation: (expectedRelType: string) => + ((raw.unsigned as Record | undefined)?.[ + "m.relations" + ] as Record | undefined)?.[expectedRelType], + threadRootId: mappedThreadRootId, + isThreadRoot, + }); +} + +type RawEventLike = { + content?: Record; + event_id?: string; + origin_server_ts?: number; + room_id?: string; + sender?: string; + type?: string; + unsigned?: Record; + [key: string]: unknown; +}; + +type MessagesResponseLike = { + chunk: RawEventLike[]; + end?: string; +}; + +type RelationsResponseLike = { + originalEvent: ReturnType | null; + events: Array>; + nextBatch: string | null; + prevBatch: string | null; +}; + function makeClient() { const handlers = new Map void>(); @@ -36,13 +112,35 @@ function makeClient() { sendEvent: vi.fn(async () => ({ event_id: "$reaction" })), redactEvent: vi.fn(async () => ({ event_id: "$redaction" })), sendTyping: vi.fn(async () => ({})), + createRoom: vi.fn(async () => ({ room_id: "!new-dm:beeper.com" })), + createMessagesRequest: vi.fn( + async (): Promise => ({ chunk: [], end: undefined }) + ), + createThreadListMessagesRequest: vi.fn( + async (): Promise => ({ chunk: [], end: undefined }) + ), + fetchRoomEvent: vi.fn(async (): Promise => null), + getAccountDataFromServer: vi.fn( + async (): Promise | null> => null + ), + getEventMapper: vi.fn(() => (raw: Record) => mapRawToEvent(raw)), initRustCrypto: vi.fn(async () => undefined), + relations: vi.fn( + async (): Promise => ({ + originalEvent: null, + events: [], + nextBatch: null, + prevBatch: null, + }) + ), + setAccountData: vi.fn(async (): Promise> => ({})), decryptEventIfNeeded: vi.fn(async () => undefined), getRoom: vi.fn(() => ({ roomId: "!room:beeper.com", name: "Example Room", timeline: [], getJoinedMembers: () => [{}, {}], + getMyMembership: () => "join", findEventById: () => makeEvent({ getId: () => "$sent" }), })), __handlers: handlers, @@ -77,6 +175,19 @@ function markSyncReady(client: ReturnType) { syncHandler?.("PREPARED"); } +function decodeCursorToken(cursor: string): { + dir: "forward" | "backward"; + kind: string; + roomID: string; + rootEventID?: string; + token: string; +} { + if (!cursor.startsWith("mxv1:")) { + throw new Error(`Expected mxv1 cursor, got: ${cursor}`); + } + return JSON.parse(Buffer.from(cursor.slice(5), "base64url").toString("utf8")); +} + function makeChatInstance(overrides: Record = {}) { return { getLogger: () => @@ -432,7 +543,7 @@ describe("MatrixAdapter", () => { expect(state.set).toHaveBeenCalledWith( "matrix:session:https%3A%2F%2Fhs.beeper.com:username:bot", - expect.any(Object), + expect.objectContaining({}), undefined ); }); @@ -647,4 +758,350 @@ describe("MatrixAdapter", () => { deviceID: "DEVICE_FROM_WHOAMI", }); }); + + it("rejects legacy cursors for API pagination", async () => { + const fakeClient = makeClient(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + await expect( + adapter.fetchMessages("matrix:!room%3Abeeper.com", { cursor: "$legacy_cursor" }) + ).rejects.toThrow("Invalid cursor format. Expected mxv1 cursor."); + }); + + it("fetches non-thread messages via matrix API with mxv1 cursor", async () => { + const fakeClient = makeClient(); + fakeClient.createMessagesRequest + .mockResolvedValueOnce({ + chunk: [ + makeRawEvent({ + event_id: "$top2", + origin_server_ts: 1_700_000_000_200, + content: { body: "top-2" }, + }), + makeRawEvent({ + event_id: "$reply1", + origin_server_ts: 1_700_000_000_100, + content: { + body: "reply-1", + "m.relates_to": { rel_type: "m.thread", event_id: "$root" }, + }, + }), + makeRawEvent({ + event_id: "$top1", + origin_server_ts: 1_700_000_000_050, + content: { body: "top-1" }, + }), + ], + end: "room-page-token-1", + }) + .mockResolvedValueOnce({ + chunk: [], + end: undefined, + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + const firstPage = await adapter.fetchMessages("matrix:!room%3Abeeper.com", { + direction: "backward", + limit: 10, + }); + + expect(firstPage.messages.map((message) => message.id)).toEqual([ + "$top1", + "$top2", + ]); + expect(firstPage.nextCursor).toBeTruthy(); + const decoded = decodeCursorToken(firstPage.nextCursor!); + expect(decoded).toMatchObject({ + kind: "room_messages", + token: "room-page-token-1", + roomID: "!room:beeper.com", + dir: "backward", + }); + + await adapter.fetchMessages("matrix:!room%3Abeeper.com", { + direction: "backward", + limit: 10, + cursor: firstPage.nextCursor, + }); + expect(fakeClient.createMessagesRequest).toHaveBeenNthCalledWith( + 2, + "!room:beeper.com", + "room-page-token-1", + 10, + "b" + ); + }); + + it("fetches thread messages via relations and includes root on first page", async () => { + const fakeClient = makeClient(); + fakeClient.relations.mockResolvedValue({ + originalEvent: null, + events: [ + makeEvent({ + getId: () => "$reply2", + getTs: () => 1_700_000_000_400, + getRoomId: () => "!room:beeper.com", + getContent: () => ({ + body: "reply-2", + "m.relates_to": { rel_type: "m.thread", event_id: "$root" }, + }), + threadRootId: "$root", + isRelation: () => true, + }), + makeEvent({ + getId: () => "$reply1", + getTs: () => 1_700_000_000_300, + getRoomId: () => "!room:beeper.com", + getContent: () => ({ + body: "reply-1", + "m.relates_to": { rel_type: "m.thread", event_id: "$root" }, + }), + threadRootId: "$root", + isRelation: () => true, + }), + ], + nextBatch: "thread-page-token-1", + prevBatch: null, + }); + fakeClient.fetchRoomEvent.mockResolvedValue( + makeRawEvent({ + event_id: "$root", + origin_server_ts: 1_700_000_000_100, + content: { body: "root" }, + }) + ); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + const page = await adapter.fetchMessages( + "matrix:!room%3Abeeper.com:%24root", + { direction: "forward", limit: 3 } + ); + + expect(page.messages.map((message) => message.id)).toEqual([ + "$root", + "$reply1", + "$reply2", + ]); + expect(page.messages.every((message) => message.threadId.endsWith(":%24root"))).toBe( + true + ); + expect(page.nextCursor).toBeTruthy(); + expect(decodeCursorToken(page.nextCursor!)).toMatchObject({ + kind: "thread_relations", + token: "thread-page-token-1", + roomID: "!room:beeper.com", + rootEventID: "$root", + dir: "forward", + }); + }); + + it("fetches channel-level messages through fetchChannelMessages", async () => { + const fakeClient = makeClient(); + fakeClient.createMessagesRequest.mockResolvedValue({ + chunk: [ + makeRawEvent({ + event_id: "$reply", + origin_server_ts: 1_700_000_000_200, + content: { + body: "reply", + "m.relates_to": { rel_type: "m.thread", event_id: "$root" }, + }, + }), + makeRawEvent({ + event_id: "$top", + origin_server_ts: 1_700_000_000_050, + content: { body: "top" }, + }), + ], + end: "channel-page-token-1", + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + const result = await adapter.fetchChannelMessages?.("matrix:!room%3Abeeper.com", { + direction: "backward", + limit: 20, + }); + + expect(result?.messages.map((message) => message.id)).toEqual(["$top"]); + expect(result?.nextCursor).toBeTruthy(); + expect(decodeCursorToken(result!.nextCursor!)).toMatchObject({ + kind: "room_messages", + token: "channel-page-token-1", + roomID: "!room:beeper.com", + }); + }); + + it("fetches a single message in context and returns null for mismatches", async () => { + const fakeClient = makeClient(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + fakeClient.fetchRoomEvent + .mockResolvedValueOnce( + makeRawEvent({ + event_id: "$root", + origin_server_ts: 1_700_000_000_000, + content: { body: "root" }, + }) + ) + .mockResolvedValueOnce( + makeRawEvent({ + event_id: "$reply-other-thread", + origin_server_ts: 1_700_000_000_000, + content: { + body: "wrong thread", + "m.relates_to": { rel_type: "m.thread", event_id: "$another-root" }, + }, + }) + ); + const message = await adapter.fetchMessage?.("matrix:!room%3Abeeper.com:%24root", "$root"); + expect(message?.id).toBe("$root"); + expect(message?.threadId).toBe("matrix:!room%3Abeeper.com:%24root"); + const mismatch = await adapter.fetchMessage?.( + "matrix:!room%3Abeeper.com:%24root", + "$reply-other-thread" + ); + expect(mismatch).toBeNull(); + }); + + it("openDM reuses cached mapping, then m.direct mapping, then creates and persists", async () => { + const fakeClient = makeClient(); + const cachedState = makeStateAdapter({ + "matrix:dm:%40bob%3Abeeper.com": "!cached-dm:beeper.com", + }); + const adapterFromCache = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapterFromCache.initialize(makeChatInstance({ getState: vi.fn(() => cachedState as never) }) as never); + const cachedThread = await adapterFromCache.openDM("@bob:beeper.com"); + expect(cachedThread).toBe("matrix:!cached-dm%3Abeeper.com"); + expect(fakeClient.createRoom).not.toHaveBeenCalled(); + + const directState = makeStateAdapter(); + fakeClient.getAccountDataFromServer.mockResolvedValue({ + "@bob:beeper.com": ["!from-direct:beeper.com"], + }); + const adapterFromDirect = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapterFromDirect.initialize(makeChatInstance({ getState: vi.fn(() => directState as never) }) as never); + const directThread = await adapterFromDirect.openDM("@bob:beeper.com"); + expect(directThread).toBe("matrix:!from-direct%3Abeeper.com"); + expect(directState.set).toHaveBeenCalledWith( + "matrix:dm:%40bob%3Abeeper.com", + "!from-direct:beeper.com" + ); + + const createState = makeStateAdapter(); + fakeClient.getAccountDataFromServer.mockResolvedValue({}); + fakeClient.createRoom.mockResolvedValue({ room_id: "!created-dm:beeper.com" }); + fakeClient.setAccountData.mockResolvedValue({}); + const adapterCreate = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapterCreate.initialize(makeChatInstance({ getState: vi.fn(() => createState as never) }) as never); + const createdThread = await adapterCreate.openDM("@bob:beeper.com"); + expect(createdThread).toBe("matrix:!created-dm%3Abeeper.com"); + expect(fakeClient.createRoom).toHaveBeenCalledWith({ + invite: ["@bob:beeper.com"], + is_direct: true, + }); + expect(fakeClient.setAccountData).toHaveBeenCalledWith(EventType.Direct, { + "@bob:beeper.com": ["!created-dm:beeper.com"], + }); + expect(createState.set).toHaveBeenCalledWith( + "matrix:dm:%40bob%3Abeeper.com", + "!created-dm:beeper.com" + ); + }); + + it("lists threads using server-side thread API with mxv1 cursor", async () => { + const fakeClient = makeClient(); + fakeClient.createThreadListMessagesRequest.mockResolvedValue({ + chunk: [ + makeRawEvent({ + event_id: "$root2", + origin_server_ts: 1_700_000_000_200, + content: { body: "Root 2" }, + unsigned: { + "m.relations": { + "m.thread": { + count: 4, + latest_event: { origin_server_ts: 1_700_000_000_900 }, + }, + }, + }, + }), + makeRawEvent({ + event_id: "$root1", + origin_server_ts: 1_700_000_000_100, + content: { body: "Root 1" }, + unsigned: { + "m.relations": { + "m.thread": { + count: 2, + latest_event: { origin_server_ts: 1_700_000_000_500 }, + }, + }, + }, + }), + ], + end: "thread-list-page-token-1", + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => fakeClient as never, + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + + const result = await adapter.listThreads("matrix:!room%3Abeeper.com", { limit: 2 }); + + expect(result.threads.map((thread) => thread.id)).toEqual([ + "matrix:!room%3Abeeper.com:%24root2", + "matrix:!room%3Abeeper.com:%24root1", + ]); + expect(result.threads.map((thread) => thread.replyCount)).toEqual([4, 2]); + expect(result.nextCursor).toBeTruthy(); + expect(decodeCursorToken(result.nextCursor!)).toMatchObject({ + kind: "thread_list", + token: "thread-list-page-token-1", + roomID: "!room:beeper.com", + dir: "backward", + }); + }); }); diff --git a/src/index.ts b/src/index.ts index 3c2614c..d62bcc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,14 +26,19 @@ import { stringifyMarkdown, } from "chat"; import sdk, { + Direction, type MatrixClient, type MatrixEvent, + type IEvent, + type IThreadBundledRelationship, type Room, ClientEvent, EventType, MsgType, RelationType, RoomEvent, + ThreadFilterType, + THREAD_RELATION_TYPE, } from "matrix-js-sdk"; import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import { logger as matrixSDKLogger } from "matrix-js-sdk/lib/logger"; @@ -47,7 +52,9 @@ import type { const MATRIX_PREFIX = "matrix"; const MATRIX_DEVICE_PREFIX = "matrix:device"; +const MATRIX_DM_PREFIX = "matrix:dm"; const MATRIX_SESSION_PREFIX = "matrix:session"; +const MATRIX_CURSOR_PREFIX = "mxv1:"; const DEFAULT_COMMAND_PREFIX = "/"; const TYPING_TIMEOUT_MS = 30_000; const FAST_SYNC_DEFAULTS: NonNullable = { @@ -107,6 +114,21 @@ type DeviceIDPersistenceConfig = { key?: string; }; +type CursorKind = "room_messages" | "thread_relations" | "thread_list"; + +type CursorDirection = "forward" | "backward"; + +type CursorV1Payload = { + dir: CursorDirection; + kind: CursorKind; + roomID: string; + rootEventID?: string; + token: string; +}; + +type DirectAccountData = Record; + +// Intentionally unsupported in this adapter: postEphemeral, openModal, and native stream. export class MatrixAdapter implements Adapter { readonly name = "matrix"; readonly userName: string; @@ -297,30 +319,7 @@ export class MatrixAdapter implements Adapter { } parseMessage(raw: MatrixEvent): Message { - const roomID = raw.getRoomId(); - if (!roomID) { - throw new Error("Matrix event missing room ID"); - } - - const threadID = this.threadIDForEvent(raw, roomID); - const content = raw.getContent(); - const text = this.extractText(content); - const sender = raw.getSender() ?? "unknown"; - - return new Message({ - id: raw.getId() ?? `${roomID}:${raw.getTs()}`, - threadId: threadID, - text, - formatted: parseMarkdown(text), - author: this.makeUser(sender), - metadata: { - dateSent: new Date(raw.getTs()), - edited: this.isEdited(raw), - }, - attachments: this.extractAttachments(content), - raw, - isMention: this.isMentioned(content, text), - }); + return this.parseMessageInternal(raw); } async postMessage( @@ -428,49 +427,150 @@ export class MatrixAdapter implements Adapter { await this.requireClient().sendTyping(roomID, true, TYPING_TIMEOUT_MS); } + async openDM(userId: string): Promise { + const cachedRoomID = await this.loadPersistedDMRoomID(userId); + if (cachedRoomID) { + return this.encodeThreadId({ roomID: cachedRoomID }); + } + + const direct = await this.loadDirectAccountData(); + const existingRoomID = this.findExistingDirectRoomID(direct, userId); + if (existingRoomID) { + await this.persistDMRoomID(userId, existingRoomID); + return this.encodeThreadId({ roomID: existingRoomID }); + } + + const response = await this.requireClient().createRoom({ + invite: [userId], + is_direct: true, + }); + + const createdRoomID = response.room_id; + if (!createdRoomID) { + throw new Error("Matrix createRoom did not return room_id for DM."); + } + + await this.persistDMRoomID(userId, createdRoomID); + await this.persistDirectAccountDataRoom(userId, createdRoomID, direct); + return this.encodeThreadId({ roomID: createdRoomID }); + } + async fetchMessages( threadId: string, options: FetchOptions = {} ): Promise> { const { roomID, rootEventID } = this.decodeThreadId(threadId); - const room = this.requireRoom(roomID); - - const messageEvents = room.timeline.filter((event) => - this.isMessageEvent(event, roomID, rootEventID) - ); - const direction = options.direction ?? "backward"; const limit = options.limit ?? 50; + const cursor = options.cursor + ? this.decodeCursorV1( + options.cursor, + rootEventID ? "thread_relations" : "room_messages", + roomID, + rootEventID + ) + : null; - if (direction === "forward") { - const startIndex = options.cursor - ? messageEvents.findIndex((e) => e.getId() === options.cursor) + 1 - : 0; - - const slice = messageEvents.slice(startIndex, startIndex + limit); - const last = slice.at(-1)?.getId(); - const hasMore = startIndex + limit < messageEvents.length; + if (!rootEventID) { + const response = await this.fetchRoomMessagesPage({ + roomID, + includeThreadReplies: false, + limit, + direction, + fromToken: cursor?.token ?? null, + }); return { - messages: slice.map((event) => this.parseMessage(event)), - nextCursor: hasMore ? last : undefined, + messages: response.events.map((event) => this.parseMessageInternal(event)), + nextCursor: response.nextToken + ? this.encodeCursorV1({ + kind: "room_messages", + dir: direction, + token: response.nextToken, + roomID, + }) + : undefined, }; } - const endIndex = options.cursor - ? messageEvents.findIndex((e) => e.getId() === options.cursor) - : messageEvents.length; + const includeRoot = !cursor; + const response = await this.fetchThreadMessagesPage({ + roomID, + rootEventID, + includeRoot, + limit, + direction, + fromToken: cursor?.token ?? null, + }); + + return { + messages: response.events.map((event) => + this.parseMessageInternal(event, this.encodeThreadId({ roomID, rootEventID })) + ), + nextCursor: response.nextToken + ? this.encodeCursorV1({ + kind: "thread_relations", + dir: direction, + token: response.nextToken, + roomID, + rootEventID, + }) + : undefined, + }; + } + + async fetchChannelMessages( + channelId: string, + options: FetchOptions = {} + ): Promise> { + const roomID = this.decodeChannelID(channelId); + const direction = options.direction ?? "backward"; + const limit = options.limit ?? 50; + const cursor = options.cursor + ? this.decodeCursorV1(options.cursor, "room_messages", roomID) + : null; - const boundedEnd = endIndex >= 0 ? endIndex : messageEvents.length; - const start = Math.max(0, boundedEnd - limit); - const slice = messageEvents.slice(start, boundedEnd); + const response = await this.fetchRoomMessagesPage({ + roomID, + includeThreadReplies: false, + limit, + direction, + fromToken: cursor?.token ?? null, + }); return { - messages: slice.map((event) => this.parseMessage(event)), - nextCursor: start > 0 ? messageEvents[start - 1]?.getId() : undefined, + messages: response.events.map((event) => this.parseMessageInternal(event)), + nextCursor: response.nextToken + ? this.encodeCursorV1({ + kind: "room_messages", + dir: direction, + token: response.nextToken, + roomID, + }) + : undefined, }; } + async fetchMessage( + threadId: string, + messageId: string + ): Promise | null> { + const { roomID, rootEventID } = this.decodeThreadId(threadId); + const event = await this.fetchRoomEventMapped(roomID, messageId); + if (!event) { + return null; + } + + if (!this.isMessageEventInContext(event, roomID, rootEventID)) { + return null; + } + + const overrideThreadID = rootEventID + ? this.encodeThreadId({ roomID, rootEventID }) + : undefined; + return this.parseMessageInternal(event, overrideThreadID); + } + async fetchThread(threadId: string): Promise { const { roomID } = this.decodeThreadId(threadId); const room = this.requireRoom(roomID); @@ -506,64 +606,382 @@ export class MatrixAdapter implements Adapter { options: ListThreadsOptions = {} ): Promise> { const roomID = this.decodeChannelID(channelId); - const room = this.requireRoom(roomID); + const direction: CursorDirection = "backward"; + const limit = options.limit ?? 50; + const cursor = options.cursor + ? this.decodeCursorV1(options.cursor, "thread_list", roomID) + : null; + const listResponse = await this.requireClient().createThreadListMessagesRequest( + roomID, + cursor?.token ?? null, + limit, + Direction.Backward, + ThreadFilterType.All + ); + const events = await this.mapRawEvents(listResponse.chunk ?? [], roomID); + const summaries: ThreadSummary[] = []; - const threadMap = new Map< - string, - { - root?: MatrixEvent; - replyCount: number; - lastTS?: number; + for (const rootEvent of events) { + const rootID = rootEvent.getId(); + if (!rootID || rootEvent.getType() !== EventType.RoomMessage) { + continue; + } + + const bundled = rootEvent.getServerAggregatedRelation( + THREAD_RELATION_TYPE.name + ); + const latestTS = bundled?.latest_event?.origin_server_ts; + const threadID = this.encodeThreadId({ roomID, rootEventID: rootID }); + + summaries.push({ + id: threadID, + rootMessage: this.parseMessageInternal(rootEvent, threadID), + replyCount: bundled?.count ?? 0, + lastReplyAt: typeof latestTS === "number" ? new Date(latestTS) : undefined, + }); + } + + return { + threads: summaries, + nextCursor: listResponse.end + ? this.encodeCursorV1({ + kind: "thread_list", + dir: direction, + token: listResponse.end, + roomID, + }) + : undefined, + }; + } + + private parseMessageInternal( + raw: MatrixEvent, + overrideThreadID?: string + ): Message { + const roomID = raw.getRoomId(); + if (!roomID) { + throw new Error("Matrix event missing room ID"); + } + + const threadID = overrideThreadID ?? this.threadIDForEvent(raw, roomID); + const content = raw.getContent(); + const text = this.extractText(content); + const sender = raw.getSender() ?? "unknown"; + + return new Message({ + id: raw.getId() ?? `${roomID}:${raw.getTs()}`, + threadId: threadID, + text, + formatted: parseMarkdown(text), + author: this.makeUser(sender), + metadata: { + dateSent: new Date(raw.getTs()), + edited: this.isEdited(raw), + }, + attachments: this.extractAttachments(content), + raw, + isMention: this.isMentioned(content, text), + }); + } + + private encodeCursorV1(payload: CursorV1Payload): string { + return `${MATRIX_CURSOR_PREFIX}${Buffer.from( + JSON.stringify(payload), + "utf8" + ).toString("base64url")}`; + } + + private decodeCursorV1( + cursor: string, + expectedKind: CursorKind, + expectedRoomID: string, + expectedRootEventID?: string + ): CursorV1Payload { + if (!cursor.startsWith(MATRIX_CURSOR_PREFIX)) { + throw new Error("Invalid cursor format. Expected mxv1 cursor."); + } + + let parsed: unknown; + try { + parsed = JSON.parse( + Buffer.from(cursor.slice(MATRIX_CURSOR_PREFIX.length), "base64url").toString("utf8") + ); + } catch (error) { + throw new Error(`Invalid cursor format. ${String(error)}`); + } + + if (!parsed || typeof parsed !== "object") { + throw new Error("Invalid cursor format. Cursor payload must be an object."); + } + + const payload = parsed as Partial; + if (payload.kind !== expectedKind) { + throw new Error(`Invalid cursor kind. Expected ${expectedKind}.`); + } + if (payload.roomID !== expectedRoomID) { + throw new Error("Invalid cursor context. Room mismatch."); + } + if ( + payload.dir !== "forward" && + payload.dir !== "backward" + ) { + throw new Error("Invalid cursor format. Invalid direction."); + } + if (!payload.token || typeof payload.token !== "string") { + throw new Error("Invalid cursor format. Missing token."); + } + + if (expectedRootEventID) { + if (payload.rootEventID !== expectedRootEventID) { + throw new Error("Invalid cursor context. Thread mismatch."); } - >(); + } else if (payload.rootEventID) { + throw new Error("Invalid cursor context. Unexpected thread scope."); + } + + return payload as CursorV1Payload; + } - for (const event of room.timeline) { + private async fetchRoomMessagesPage(args: { + roomID: string; + includeThreadReplies: boolean; + limit: number; + direction: CursorDirection; + fromToken: string | null; + }): Promise<{ events: MatrixEvent[]; nextToken: string | null }> { + const response = await this.requireClient().createMessagesRequest( + args.roomID, + args.fromToken, + args.limit, + args.direction === "forward" ? Direction.Forward : Direction.Backward + ); + const events = await this.mapRawEvents(response.chunk ?? [], args.roomID); + const filtered = events.filter((event) => { if (event.getType() !== EventType.RoomMessage) { - continue; + return false; + } + if (event.getRoomId() !== args.roomID) { + return false; } + if (args.includeThreadReplies) { + return true; + } + return this.isTopLevelMessageEvent(event); + }); - const rootID = event.threadRootId; - if (!rootID) { - continue; + return { + events: this.sortEventsChronologically(filtered), + nextToken: response.end ?? null, + }; + } + + private async fetchThreadMessagesPage(args: { + roomID: string; + rootEventID: string; + includeRoot: boolean; + limit: number; + direction: CursorDirection; + fromToken: string | null; + }): Promise<{ events: MatrixEvent[]; nextToken: string | null }> { + const relationLimit = args.includeRoot + ? Math.max(args.limit - 1, 1) + : args.limit; + + const relationResponse = await this.requireClient().relations( + args.roomID, + args.rootEventID, + THREAD_RELATION_TYPE.name, + null, + { + dir: args.direction === "forward" ? Direction.Forward : Direction.Backward, + from: args.fromToken ?? undefined, + limit: relationLimit, } + ); + + await Promise.all( + relationResponse.events.map((event) => this.tryDecryptEvent(event)) + ); + const replies = this.sortEventsChronologically( + relationResponse.events.filter((event) => + this.isMessageEventInContext(event, args.roomID, args.rootEventID) + ) + ); + + if (!args.includeRoot) { + return { + events: replies.slice(0, args.limit), + nextToken: relationResponse.nextBatch ?? null, + }; + } + + const rootEvent = + relationResponse.originalEvent?.getId() === args.rootEventID + ? relationResponse.originalEvent + : await this.fetchRoomEventMapped(args.roomID, args.rootEventID); + const rootArray = + rootEvent && + this.isMessageEventInContext(rootEvent, args.roomID, args.rootEventID) + ? [rootEvent] + : []; + const dedupedReplies = replies.filter( + (event) => event.getId() !== args.rootEventID + ); + + return { + events: [...rootArray, ...dedupedReplies].slice(0, args.limit), + nextToken: relationResponse.nextBatch ?? null, + }; + } + + private async mapRawEvents( + rawEvents: Array>, + roomID: string + ): Promise { + const events = rawEvents.map((event) => this.mapRawEvent(event, roomID)); + await Promise.all(events.map((event) => this.tryDecryptEvent(event))); + return events; + } - const entry = threadMap.get(rootID) ?? { replyCount: 0 }; + private mapRawEvent(rawEvent: Partial, roomID: string): MatrixEvent { + const mapper = this.requireClient().getEventMapper(); + const withRoomID = rawEvent.room_id + ? rawEvent + : { ...rawEvent, room_id: roomID }; + return mapper(withRoomID); + } - if (event.getId() === rootID) { - entry.root = event; - } else { - entry.replyCount += 1; + private async fetchRoomEventMapped( + roomID: string, + eventID: string + ): Promise { + try { + const rawEvent = await this.requireClient().fetchRoomEvent(roomID, eventID); + if (!rawEvent) { + return null; } - entry.lastTS = Math.max(entry.lastTS ?? 0, event.getTs()); - threadMap.set(rootID, entry); + const mapped = this.mapRawEvent(rawEvent, roomID); + await this.tryDecryptEvent(mapped); + return mapped; + } catch { + return null; } + } - const summaries: ThreadSummary[] = []; - for (const [rootID, entry] of threadMap.entries()) { - if (!entry.root) { + private sortEventsChronologically(events: MatrixEvent[]): MatrixEvent[] { + const deduped = new Map(); + const withoutIDs: MatrixEvent[] = []; + + for (const event of events) { + const eventID = event.getId(); + if (!eventID) { + withoutIDs.push(event); continue; } + deduped.set(eventID, event); + } - summaries.push({ - id: this.encodeThreadId({ roomID, rootEventID: rootID }), - rootMessage: this.parseMessage(entry.root), - replyCount: entry.replyCount, - lastReplyAt: entry.lastTS ? new Date(entry.lastTS) : undefined, - }); + return [...deduped.values(), ...withoutIDs].sort((a, b) => { + const tsDiff = a.getTs() - b.getTs(); + if (tsDiff !== 0) { + return tsDiff; + } + return (a.getId() ?? "").localeCompare(b.getId() ?? ""); + }); + } + + private isTopLevelMessageEvent(event: MatrixEvent): boolean { + return !event.threadRootId && !event.isRelation(THREAD_RELATION_TYPE.name); + } + + private isMessageEventInContext( + event: MatrixEvent, + roomID: string, + rootEventID?: string + ): boolean { + if (event.getType() !== EventType.RoomMessage || event.getRoomId() !== roomID) { + return false; + } + + if (!rootEventID) { + return this.isTopLevelMessageEvent(event); + } + + return event.threadRootId === rootEventID || event.getId() === rootEventID; + } + + private getDMStorageKey(userID: string): string { + return `${MATRIX_DM_PREFIX}:${encodeURIComponent(userID)}`; + } + + private async loadPersistedDMRoomID(userID: string): Promise { + if (!this.stateAdapter) { + return null; } - summaries.sort( - (a, b) => - (b.lastReplyAt?.getTime() ?? 0) - (a.lastReplyAt?.getTime() ?? 0) + const cached = await this.stateAdapter.get( + this.getDMStorageKey(userID) ); + const normalized = normalizeOptionalString(cached ?? undefined); + return normalized ?? null; + } - const limit = options.limit ?? 50; + private async persistDMRoomID(userID: string, roomID: string): Promise { + if (!this.stateAdapter) { + return; + } - return { - threads: summaries.slice(0, limit), - nextCursor: summaries.length > limit ? String(limit) : undefined, - }; + await this.stateAdapter.set(this.getDMStorageKey(userID), roomID); + } + + private async loadDirectAccountData(): Promise { + const direct = await this.requireClient().getAccountDataFromServer(EventType.Direct); + return this.normalizeDirectAccountData(direct); + } + + private normalizeDirectAccountData(value: unknown): DirectAccountData { + if (!value || typeof value !== "object") { + return {}; + } + + const out: DirectAccountData = {}; + for (const [userID, roomIDs] of Object.entries(value)) { + if (!Array.isArray(roomIDs)) { + continue; + } + out[userID] = roomIDs.filter( + (roomID): roomID is string => typeof roomID === "string" && roomID.length > 0 + ); + } + + return out; + } + + private findExistingDirectRoomID( + direct: DirectAccountData, + userID: string + ): string | null { + const candidates = direct[userID] ?? []; + for (const roomID of candidates) { + if (roomID) { + return roomID; + } + } + return null; + } + + private async persistDirectAccountDataRoom( + userID: string, + roomID: string, + existing: DirectAccountData + ): Promise { + const updated: DirectAccountData = { ...existing }; + const existingRooms = updated[userID] ?? []; + if (!existingRooms.includes(roomID)) { + updated[userID] = [...existingRooms, roomID]; + await this.requireClient().setAccountData(EventType.Direct, updated); + } } private async resolveAuth(): Promise { From 9b47ffb3f1e7d5142cb4e1b5c76dfa214b60f1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 15:35:50 +0100 Subject: [PATCH 02/25] docs: restructure 1.0.0 changelog sections --- CHANGELOG.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e8e5d..d65de5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,28 @@ ## 1.0.0 -- Major: switched pagination cursors to opaque `mxv1:` format across `fetchMessages`, `fetchChannelMessages`, and `listThreads`. -- Breaking: legacy cursors are rejected (`Invalid cursor format. Expected mxv1 cursor.`). Stored cursors from older versions must be cleared. -- Added `openDM(userId)` with persisted mapping + `m.direct` reuse/create behavior. +### Breaking Changes + +- Pagination cursors now use opaque `mxv1:` values across `fetchMessages`, `fetchChannelMessages`, and `listThreads`. +- Legacy cursors are now rejected with `Invalid cursor format. Expected mxv1 cursor.`. Stored cursors from older versions must be cleared on upgrade. + +### New + +- Added `openDM(userId)` with persisted mapping and `m.direct` reuse/create behavior. - Added `fetchMessage(threadId, messageId)` for context-aware single-message fetch. -- Added `fetchChannelMessages(channelId, options)` for top-level timeline history. +- Added `fetchChannelMessages(channelId, options)` for top-level channel timeline history. + +### Changes + - Reworked `fetchMessages(threadId, options)` to API-first server pagination via `matrix-js-sdk`: - - Room timeline pages use `/messages` API through SDK. - - Thread pages use `/relations` API through SDK. - - Thread pages include root on first page. -- Reworked `listThreads(channelId, options)` to server-backed thread listing via SDK `/threads` support. -- Kept intentionally unsupported in this release: `postEphemeral`, `openModal`, and native `stream`. + - Room timeline pages now use `/messages` through the SDK. + - Thread pages now use `/relations` through the SDK. + - Thread pages include the root message on the first page. +- Reworked `listThreads(channelId, options)` to use server-backed thread listing via the SDK `/threads` path. + +### Fixes + +- Message and thread history retrieval no longer depends on local `room.timeline` availability for correctness. ## 0.1.0 From 54fbb6e6466e13514044a60aa9d690fea730fd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 15:36:14 +0100 Subject: [PATCH 03/25] docs: simplify readme capabilities section --- README.md | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3dd3c1f..720c877 100644 --- a/README.md +++ b/README.md @@ -116,32 +116,15 @@ bun --env-file=examples/.env run examples/bot.ts - Password auth sends configured `device_id` during login. - Use Redis state in production for stable sessions and device IDs. -## Feature Parity (v1+) +## Capabilities -- `openDM(userId)` is supported and reuses/creates direct rooms using Matrix `m.direct` account data plus persisted adapter state. -- `fetchMessage(threadId, messageId)` is supported and validates room/thread context. -- `fetchChannelMessages(channelId, options)` is supported for top-level room timeline messages. -- `fetchMessages(threadId, options)` is API-first and server-paginated (not `room.timeline` dependent). -- `listThreads(channelId, options)` is server-backed via Matrix thread list APIs. -- `postEphemeral`, `openModal`, and native `stream` are intentionally not implemented in this adapter. +- `openDM(userId)` creates or reuses direct rooms using Matrix `m.direct` account data and persisted adapter state. +- `fetchMessage(threadId, messageId)` fetches a single message with thread/channel context validation. +- `fetchChannelMessages(channelId, options)` fetches top-level room timeline messages. +- `fetchMessages(threadId, options)` and `listThreads(channelId, options)` use API-first server pagination via `matrix-js-sdk`. +- `postEphemeral`, `openModal`, and native `stream` are not implemented in this adapter. -## Breaking Changes In 1.0.0 - -- Cursor format changed to opaque adapter cursors: `mxv1:`. -- Legacy cursor strings from pre-1.0 releases are rejected with `Invalid cursor format. Expected mxv1 cursor.`. -- `fetchMessages(threadId)` for Matrix thread IDs returns root + replies (root included on first page), with server pagination. - -## Cursor Migration - -- Treat all previously stored cursors as invalid when upgrading to `1.x`. -- Drop persisted history cursors and request the first page again. -- New cursors are scoped to method/context and cannot be reused across rooms/threads. - -## Pagination Behavior - -- Paged history methods use `matrix-js-sdk` server APIs. -- Pages are normalized to chronological order. -- `nextCursor` is returned only when the server indicates more results. +For release-specific changes and migration notes, see [CHANGELOG.md](./CHANGELOG.md). ## License From d03b10532e446162c1c719f18a73e8ce982429aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 16:05:37 +0100 Subject: [PATCH 04/25] docs: rewrite readme in a more natural tone --- README.md | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 720c877..0f97d90 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # @beeper/chat-adapter-matrix -Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). Uses Matrix sync (no webhook server required). +Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). -If you are using Beeper, you can use Chat SDK with your Beeper Cloud accounts and Matrix chats. This lets you use Chat SDK with WhatsApp, Telegram, Instagram, Signal, X Chat, and more. For bridged chats, we recommend personal usage, since some networks may limit automated activity. +This adapter runs over Matrix sync, so you do not need to host a webhook endpoint. + +If you use Beeper, this adapter lets your Chat SDK bot work with your Matrix/Beeper conversations (including bridged networks such as WhatsApp, Telegram, Instagram, Signal, and others). ## Installation @@ -10,7 +12,7 @@ If you are using Beeper, you can use Chat SDK with your Beeper Cloud accounts an npm install chat @beeper/chat-adapter-matrix matrix-js-sdk ``` -## Usage +## Quick Start ```ts import { Chat } from "chat"; @@ -38,7 +40,9 @@ bot.onNewMention(async (thread, message) => { }); ``` -## Auth +## Authentication + +Use either access-token auth or username/password auth. Access token: @@ -70,9 +74,9 @@ Common optional: - `MATRIX_DEVICE_ID` - `MATRIX_RECOVERY_KEY` -Advanced optional (only if needed): device ID persistence keys, E2EE storage settings, session settings, and `MATRIX_SDK_LOG_LEVEL`. +Advanced options are available for device/session persistence, E2EE storage, and SDK logging (`MATRIX_SDK_LOG_LEVEL`). -## Examples +## Running The Example Copy [`examples/.env.example`](./examples/.env.example) to `examples/.env`, then run: @@ -80,49 +84,39 @@ Copy [`examples/.env.example`](./examples/.env.example) to `examples/.env`, then npm run example:bun ``` -Generate a Beeper access token interactively: +If you need Beeper credentials, generate them interactively: ```bash npm run token:bun ``` -## Get a Beeper Access Token - -Use the interactive helper: - -```bash -npm run token:bun -``` - -It prints: +The helper prints: - `MATRIX_BASE_URL` - `MATRIX_ACCESS_TOKEN` - `MATRIX_USER_ID` - `MATRIX_DEVICE_ID` -Paste those values into `examples/.env` or your deployment secrets. - -or: +Then run: ```bash bun --env-file=examples/.env run examples/bot.ts ``` -## Notes - -- `handleWebhook()` returns `501` by design. -- Access-token auth resolves identity with `whoami`. -- Password auth sends configured `device_id` during login. -- Use Redis state in production for stable sessions and device IDs. - ## Capabilities - `openDM(userId)` creates or reuses direct rooms using Matrix `m.direct` account data and persisted adapter state. - `fetchMessage(threadId, messageId)` fetches a single message with thread/channel context validation. - `fetchChannelMessages(channelId, options)` fetches top-level room timeline messages. - `fetchMessages(threadId, options)` and `listThreads(channelId, options)` use API-first server pagination via `matrix-js-sdk`. -- `postEphemeral`, `openModal`, and native `stream` are not implemented in this adapter. +- `postEphemeral`, `openModal`, and native `stream` are not implemented by this adapter. + +## Notes + +- `handleWebhook()` returns `501` by design, since this adapter is sync-based. +- Access-token auth resolves identity with `whoami`. +- Password auth sends the configured `device_id` during login. +- For production, use Redis state for stable sessions and device IDs. For release-specific changes and migration notes, see [CHANGELOG.md](./CHANGELOG.md). From 077cecbd34c31d8e71758aa2f89222495ff8687b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 16:24:14 +0100 Subject: [PATCH 05/25] feat: add dont_render_edited marker to Matrix edit payloads --- src/index.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 40 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 7ecb045..ce3e4f9 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -414,6 +414,45 @@ describe("MatrixAdapter", () => { expect(result?.[1]).toBeInstanceOf(Uint8Array); }); + it("sends Matrix edit payload with dont_render_edited context", async () => { + const fakeClient = makeClient(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createClient: () => fakeClient as never, + }); + + await adapter.initialize( + makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never + ); + + await adapter.editMessage( + "matrix:!room%3Abeeper.com", + "$original", + "updated body" + ); + + expect(fakeClient.sendEvent).toHaveBeenCalledWith( + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + "com.beeper.dont_render_edited": true, + "m.new_content": { + msgtype: "m.text", + body: "updated body", + }, + "m.relates_to": { + rel_type: RelationType.Replace, + event_id: "$original", + }, + }) + ); + }); + it("generates and persists a device id when one is not provided", async () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", diff --git a/src/index.ts b/src/index.ts index d62bcc2..8669080 100644 --- a/src/index.ts +++ b/src/index.ts @@ -355,6 +355,7 @@ export class MatrixAdapter implements Adapter { const baseContent = this.toRoomMessageContent(message); const response = await this.sendRoomMessage(roomID, rootEventID, { + "com.beeper.dont_render_edited": true, "m.new_content": { msgtype: baseContent.msgtype, body: baseContent.body, From a2e650c8b1458b74d01c427903fbd4569bd7e967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 21:46:10 +0100 Subject: [PATCH 06/25] Improve matrix adapter typing and reaction thread context --- README.md | 1 + src/index.test.ts | 394 +++++++++++++++++++++++++++------------------- src/index.ts | 203 ++++++++++++++---------- 3 files changed, 351 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index 0f97d90..0a41c14 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Common optional: - `MATRIX_USER_ID` - `MATRIX_DEVICE_ID` - `MATRIX_RECOVERY_KEY` +- `MATRIX_BOT_USERNAME` (mention detection display name, defaults to `MOM_BOT_USERNAME` then `bot`) Advanced options are available for device/session persistence, E2EE storage, and SDK logging (`MATRIX_SDK_LOG_LEVEL`). diff --git a/src/index.test.ts b/src/index.test.ts index ce3e4f9..a44fab3 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { getEmoji } from "chat"; -import { EventType, RelationType } from "matrix-js-sdk"; +import type { ChatInstance, StateAdapter } from "chat"; +import { EventType, RelationType, type MatrixClient } from "matrix-js-sdk"; import { encodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import { createMatrixAdapter, MatrixAdapter } from "./index"; @@ -62,9 +63,9 @@ function mapRawToEvent(raw: Record) { getContent: () => content, getRelation: () => relationType - ? ({ + ? { rel_type: relationType, - } as never) + } : null, isRelation: (expectedRelType: string) => relationType === expectedRelType, getServerAggregatedRelation: (expectedRelType: string) => @@ -99,6 +100,48 @@ type RelationsResponseLike = { prevBatch: string | null; }; +type RoomLike = { + roomId: string; + name: string; + timeline: unknown[]; + getJoinedMembers: () => Array>; + getMyMembership: () => string; + findEventById: (eventID?: string) => ReturnType | null; +}; + +type AdapterInternals = { + deviceID?: string; + e2eeConfig?: { enabled?: boolean }; + getSecretStorageKeyFromRecoveryKey: (opts: { + keys: Record; + }) => [string, Uint8Array] | null; + loadPersistedSession: () => Promise<{ + accessToken: string; + userID: string; + deviceID?: string; + } | null>; + persistSession: (session: { + accessToken: string; + deviceID?: string; + userID: string; + }) => Promise; + resolveAuth: () => Promise<{ + accessToken: string; + userID: string; + deviceID?: string; + }>; + resolveDeviceID: () => Promise; + stateAdapter: StateAdapter | null; +}; + +function asMatrixClient(client: ReturnType): MatrixClient { + return client as unknown as MatrixClient; +} + +function getInternals(adapter: MatrixAdapter): AdapterInternals { + return adapter as unknown as AdapterInternals; +} + function makeClient() { const handlers = new Map void>(); @@ -135,13 +178,13 @@ function makeClient() { ), setAccountData: vi.fn(async (): Promise> => ({})), decryptEventIfNeeded: vi.fn(async () => undefined), - getRoom: vi.fn(() => ({ + getRoom: vi.fn((): RoomLike => ({ roomId: "!room:beeper.com", name: "Example Room", timeline: [], getJoinedMembers: () => [{}, {}], getMyMembership: () => "join", - findEventById: () => makeEvent({ getId: () => "$sent" }), + findEventById: (_eventID?: string) => makeEvent({ getId: () => "$sent" }), })), __handlers: handlers, }; @@ -149,7 +192,7 @@ function makeClient() { return client; } -function makeStateAdapter(initial: Record = {}) { +function makeStateAdapter(initial: Record = {}): StateAdapter { const store = new Map(Object.entries(initial)); return { acquireLock: vi.fn(async () => null), @@ -167,7 +210,7 @@ function makeStateAdapter(initial: Record = {}) { }), subscribe: vi.fn(async () => undefined), unsubscribe: vi.fn(async () => undefined), - }; + } as StateAdapter; } function markSyncReady(client: ReturnType) { @@ -188,7 +231,14 @@ function decodeCursorToken(cursor: string): { return JSON.parse(Buffer.from(cursor.slice(5), "base64url").toString("utf8")); } -function makeChatInstance(overrides: Record = {}) { +function requireValue(value: T | null | undefined, label: string): T { + if (value === null || value === undefined) { + throw new Error(`Expected ${label} to be present`); + } + return value; +} + +function makeChatInstance(overrides: Record = {}): ChatInstance { return { getLogger: () => ({ @@ -196,8 +246,8 @@ function makeChatInstance(overrides: Record = {}) { info: vi.fn(), warn: vi.fn(), error: vi.fn(), - child: () => ({}) as never, - }) as never, + child: () => ({}), + }), getState: vi.fn(), getUserName: vi.fn(), handleIncomingMessage: vi.fn(), @@ -211,7 +261,7 @@ function makeChatInstance(overrides: Record = {}) { processReaction: vi.fn(), processSlashCommand: vi.fn(), ...overrides, - }; + } as unknown as ChatInstance; } describe("MatrixAdapter", () => { @@ -247,13 +297,13 @@ describe("MatrixAdapter", () => { accessToken: "token", userID: "@bot:beeper.com", }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); const processMessage = vi.fn(); const processSlashCommand = vi.fn(); - await adapter.initialize(makeChatInstance({ processMessage, processSlashCommand }) as never); + await adapter.initialize(makeChatInstance({ processMessage, processSlashCommand })); const timelineHandler = fakeClient.__handlers.get("Room.timeline"); expect(timelineHandler).toBeTruthy(); @@ -288,10 +338,10 @@ describe("MatrixAdapter", () => { accessToken: "token", userID: "@bot:beeper.com", }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ processReaction }) as never); + await adapter.initialize(makeChatInstance({ processReaction })); const timelineHandler = fakeClient.__handlers.get("Room.timeline"); markSyncReady(fakeClient); @@ -336,6 +386,64 @@ describe("MatrixAdapter", () => { }); }); + it("routes reactions to thread context when target event belongs to a thread", async () => { + const fakeClient = makeClient(); + fakeClient.getRoom.mockReturnValue({ + roomId: "!room:beeper.com", + name: "Example Room", + timeline: [], + getJoinedMembers: () => [{}, {}], + getMyMembership: () => "join", + findEventById: (eventId?: string) => + eventId === "$target" + ? makeEvent({ + getId: () => "$target", + getRoomId: () => "!room:beeper.com", + threadRootId: "$root", + }) + : null, + }); + + const processReaction = vi.fn(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance({ processReaction })); + + const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + markSyncReady(fakeClient); + timelineHandler?.( + makeEvent({ + getId: () => "$reaction2", + getType: () => EventType.Reaction, + getContent: () => ({ + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: "$target", + key: "🔥", + }, + }), + }), + { roomId: "!room:beeper.com" }, + false + ); + await Promise.resolve(); + + expect(processReaction).toHaveBeenCalledOnce(); + expect(processReaction.mock.calls[0][0]).toMatchObject({ + threadId: "matrix:!room%3Abeeper.com:%24root", + messageId: "$target", + emoji: getEmoji("🔥"), + added: true, + }); + }); + it("initializes rust crypto and decrypts encrypted events when e2ee is enabled", async () => { const fakeClient = makeClient(); const processMessage = vi.fn(); @@ -348,11 +456,11 @@ describe("MatrixAdapter", () => { userID: "@bot:beeper.com", }, deviceID: "DEVICE1", - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), e2ee: { enabled: true }, }); - await adapter.initialize(makeChatInstance({ processMessage }) as never); + await adapter.initialize(makeChatInstance({ processMessage })); expect(fakeClient.initRustCrypto).toHaveBeenCalledOnce(); @@ -372,15 +480,17 @@ describe("MatrixAdapter", () => { }); it("enables e2ee when recovery key is provided", () => { - const adapter = createMatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - recoveryKey: "s3cr3t-recovery-key", - }) as unknown as { e2eeConfig?: { enabled?: boolean } }; + const adapter = getInternals( + createMatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + recoveryKey: "s3cr3t-recovery-key", + }) + ); expect(adapter.e2eeConfig?.enabled).toBe(true); }); @@ -388,20 +498,19 @@ describe("MatrixAdapter", () => { it("decodes recovery key for secret storage callback", () => { const recoveryKey = encodeRecoveryKey(new Uint8Array(32).fill(7)); expect(recoveryKey).toBeDefined(); - - const adapter = createMatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - recoveryKey: recoveryKey!, - }) as unknown as { - getSecretStorageKeyFromRecoveryKey: (opts: { - keys: Record; - }) => [string, Uint8Array] | null; - }; + const validatedRecoveryKey = requireValue(recoveryKey, "recoveryKey"); + + const adapter = getInternals( + createMatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + recoveryKey: validatedRecoveryKey, + }) + ); const result = adapter.getSecretStorageKeyFromRecoveryKey({ keys: { @@ -423,12 +532,10 @@ describe("MatrixAdapter", () => { accessToken: "token", userID: "@bot:beeper.com", }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize( - makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never - ); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); await adapter.editMessage( "matrix:!room%3Abeeper.com", @@ -442,6 +549,7 @@ describe("MatrixAdapter", () => { expect.objectContaining({ "com.beeper.dont_render_edited": true, "m.new_content": { + "com.beeper.dont_render_edited": true, msgtype: "m.text", body: "updated body", }, @@ -454,20 +562,18 @@ describe("MatrixAdapter", () => { }); it("generates and persists a device id when one is not provided", async () => { - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - }) as unknown as { - stateAdapter: unknown; - resolveDeviceID: () => Promise; - deviceID?: string; - }; + const adapter = getInternals( + new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + }) + ); const state = makeStateAdapter(); - adapter.stateAdapter = state as unknown; + adapter.stateAdapter = state; await adapter.resolveDeviceID(); @@ -482,20 +588,18 @@ describe("MatrixAdapter", () => { persistedDeviceID, }); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - deviceID: " ", - }) as unknown as { - stateAdapter: unknown; - resolveDeviceID: () => Promise; - deviceID?: string; - }; - adapter.stateAdapter = state as unknown; + const adapter = getInternals( + new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + deviceID: " ", + }) + ); + adapter.stateAdapter = state; await adapter.resolveDeviceID(); @@ -524,10 +628,10 @@ describe("MatrixAdapter", () => { accessToken: "token", userID: "@bot:beeper.com", }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); await adapter.shutdown(); expect(fakeClient.stopClient).toHaveBeenCalledOnce(); @@ -537,41 +641,24 @@ describe("MatrixAdapter", () => { const baseURL = "https://hs.beeper.com"; const state = makeStateAdapter(); - const adapter = new MatrixAdapter({ + const adapter = getInternals(new MatrixAdapter({ baseURL, auth: { type: "password", username: "bot", password: "secret", }, - }); + })); - (adapter as unknown as { stateAdapter: unknown }).stateAdapter = - state as unknown; - - await ( - adapter as unknown as { - persistSession: (session: { - accessToken: string; - deviceID?: string; - userID: string; - }) => Promise; - } - ).persistSession({ + adapter.stateAdapter = state; + + await adapter.persistSession({ accessToken: "persisted-token", userID: "@bot:beeper.com", deviceID: "DEVICE1", }); - const restored = await ( - adapter as unknown as { - loadPersistedSession: () => Promise<{ - accessToken: string; - userID: string; - deviceID?: string; - } | null>; - } - ).loadPersistedSession(); + const restored = await adapter.loadPersistedSession(); expect(state.set).toHaveBeenCalled(); expect(restored).toMatchObject({ @@ -619,20 +706,12 @@ describe("MatrixAdapter", () => { ({ loginWithPassword, whoami, - }) as never, + }), }); - (adapter as unknown as { stateAdapter: unknown }).stateAdapter = - state as unknown; - const resolved = await ( - adapter as unknown as { - resolveAuth: () => Promise<{ - accessToken: string; - userID: string; - deviceID?: string; - }>; - } - ).resolveAuth(); + const internals = getInternals(adapter); + internals.stateAdapter = state; + const resolved = await internals.resolveAuth(); expect(resolved).toMatchObject({ accessToken: "persisted-token", @@ -679,20 +758,12 @@ describe("MatrixAdapter", () => { ({ loginWithPassword, whoami, - }) as never, + }), }); - (adapter as unknown as { stateAdapter: unknown }).stateAdapter = - state as unknown; - const resolved = await ( - adapter as unknown as { - resolveAuth: () => Promise<{ - accessToken: string; - userID: string; - deviceID?: string; - }>; - } - ).resolveAuth(); + const internals = getInternals(adapter); + internals.stateAdapter = state; + const resolved = await internals.resolveAuth(); expect(loginWithPassword).toHaveBeenCalledOnce(); expect(resolved).toMatchObject({ @@ -725,20 +796,12 @@ describe("MatrixAdapter", () => { loginRequest, loginWithPassword, whoami: vi.fn(), - }) as never, + }), }); - (adapter as unknown as { stateAdapter: unknown }).stateAdapter = - makeStateAdapter() as unknown; - const resolved = await ( - adapter as unknown as { - resolveAuth: () => Promise<{ - accessToken: string; - userID: string; - deviceID?: string; - }>; - } - ).resolveAuth(); + const internals = getInternals(adapter); + internals.stateAdapter = makeStateAdapter(); + const resolved = await internals.resolveAuth(); expect(loginRequest).toHaveBeenCalledOnce(); expect(loginRequest).toHaveBeenCalledWith( @@ -775,20 +838,12 @@ describe("MatrixAdapter", () => { ({ whoami, loginWithPassword: vi.fn(), - }) as never, + }), }); - (adapter as unknown as { stateAdapter: unknown }).stateAdapter = - makeStateAdapter() as unknown; - const resolved = await ( - adapter as unknown as { - resolveAuth: () => Promise<{ - accessToken: string; - userID: string; - deviceID?: string; - }>; - } - ).resolveAuth(); + const internals = getInternals(adapter); + internals.stateAdapter = makeStateAdapter(); + const resolved = await internals.resolveAuth(); expect(whoami).toHaveBeenCalledOnce(); expect(resolved).toMatchObject({ @@ -803,9 +858,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); await expect( adapter.fetchMessages("matrix:!room%3Abeeper.com", { cursor: "$legacy_cursor" }) @@ -846,9 +901,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); const firstPage = await adapter.fetchMessages("matrix:!room%3Abeeper.com", { direction: "backward", @@ -860,7 +915,9 @@ describe("MatrixAdapter", () => { "$top2", ]); expect(firstPage.nextCursor).toBeTruthy(); - const decoded = decodeCursorToken(firstPage.nextCursor!); + const decoded = decodeCursorToken( + requireValue(firstPage.nextCursor, "firstPage.nextCursor") + ); expect(decoded).toMatchObject({ kind: "room_messages", token: "room-page-token-1", @@ -924,9 +981,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); const page = await adapter.fetchMessages( "matrix:!room%3Abeeper.com:%24root", @@ -942,7 +999,9 @@ describe("MatrixAdapter", () => { true ); expect(page.nextCursor).toBeTruthy(); - expect(decodeCursorToken(page.nextCursor!)).toMatchObject({ + expect( + decodeCursorToken(requireValue(page.nextCursor, "thread page nextCursor")) + ).toMatchObject({ kind: "thread_relations", token: "thread-page-token-1", roomID: "!room:beeper.com", @@ -975,9 +1034,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); const result = await adapter.fetchChannelMessages?.("matrix:!room%3Abeeper.com", { direction: "backward", @@ -986,7 +1045,8 @@ describe("MatrixAdapter", () => { expect(result?.messages.map((message) => message.id)).toEqual(["$top"]); expect(result?.nextCursor).toBeTruthy(); - expect(decodeCursorToken(result!.nextCursor!)).toMatchObject({ + const nextCursor = requireValue(result?.nextCursor, "channel nextCursor"); + expect(decodeCursorToken(nextCursor)).toMatchObject({ kind: "room_messages", token: "channel-page-token-1", roomID: "!room:beeper.com", @@ -998,9 +1058,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); fakeClient.fetchRoomEvent .mockResolvedValueOnce( @@ -1038,9 +1098,9 @@ describe("MatrixAdapter", () => { const adapterFromCache = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapterFromCache.initialize(makeChatInstance({ getState: vi.fn(() => cachedState as never) }) as never); + await adapterFromCache.initialize(makeChatInstance({ getState: vi.fn(() => cachedState) })); const cachedThread = await adapterFromCache.openDM("@bob:beeper.com"); expect(cachedThread).toBe("matrix:!cached-dm%3Abeeper.com"); expect(fakeClient.createRoom).not.toHaveBeenCalled(); @@ -1052,9 +1112,9 @@ describe("MatrixAdapter", () => { const adapterFromDirect = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapterFromDirect.initialize(makeChatInstance({ getState: vi.fn(() => directState as never) }) as never); + await adapterFromDirect.initialize(makeChatInstance({ getState: vi.fn(() => directState) })); const directThread = await adapterFromDirect.openDM("@bob:beeper.com"); expect(directThread).toBe("matrix:!from-direct%3Abeeper.com"); expect(directState.set).toHaveBeenCalledWith( @@ -1069,9 +1129,9 @@ describe("MatrixAdapter", () => { const adapterCreate = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapterCreate.initialize(makeChatInstance({ getState: vi.fn(() => createState as never) }) as never); + await adapterCreate.initialize(makeChatInstance({ getState: vi.fn(() => createState) })); const createdThread = await adapterCreate.openDM("@bob:beeper.com"); expect(createdThread).toBe("matrix:!created-dm%3Abeeper.com"); expect(fakeClient.createRoom).toHaveBeenCalledWith({ @@ -1124,9 +1184,9 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => fakeClient as never, + createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter() as never) }) as never); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); const result = await adapter.listThreads("matrix:!room%3Abeeper.com", { limit: 2 }); @@ -1136,7 +1196,9 @@ describe("MatrixAdapter", () => { ]); expect(result.threads.map((thread) => thread.replyCount)).toEqual([4, 2]); expect(result.nextCursor).toBeTruthy(); - expect(decodeCursorToken(result.nextCursor!)).toMatchObject({ + expect( + decodeCursorToken(requireValue(result.nextCursor, "thread list nextCursor")) + ).toMatchObject({ kind: "thread_list", token: "thread-list-page-token-1", roomID: "!room:beeper.com", diff --git a/src/index.ts b/src/index.ts index 8669080..4dfb548 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,10 @@ import sdk, { THREAD_RELATION_TYPE, } from "matrix-js-sdk"; import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; +import type { + RoomMessageEventContent, + RoomMessageTextEventContent, +} from "matrix-js-sdk/lib/@types/events"; import { logger as matrixSDKLogger } from "matrix-js-sdk/lib/logger"; import type { MatrixAuthBootstrapClient, @@ -80,6 +84,17 @@ type MatrixMessageContent = { [key: string]: unknown; }; +type MatrixTextMessageContent = RoomMessageTextEventContent & { + "com.beeper.dont_render_edited"?: boolean; +}; + +type MatrixRoomMessageContent = RoomMessageEventContent & { + "com.beeper.dont_render_edited"?: boolean; + "m.new_content"?: RoomMessageEventContent & { + "com.beeper.dont_render_edited"?: boolean; + }; +}; + type StoredReaction = { emoji: EmojiValue; messageID: string; @@ -253,8 +268,8 @@ export class MatrixAdapter implements Adapter { this.started = true; this.logger.info("Matrix adapter initialized", { - userID: this.userID, - baseURL: this.baseURL, + userId: this.userID, + baseUrl: this.baseURL, }); } @@ -353,20 +368,23 @@ export class MatrixAdapter implements Adapter { ): Promise> { const { roomID, rootEventID } = this.decodeThreadId(threadId); const baseContent = this.toRoomMessageContent(message); + const newContent: MatrixTextMessageContent = { + ...baseContent, + "com.beeper.dont_render_edited": true, + }; - const response = await this.sendRoomMessage(roomID, rootEventID, { + const editContent: MatrixRoomMessageContent = { "com.beeper.dont_render_edited": true, - "m.new_content": { - msgtype: baseContent.msgtype, - body: baseContent.body, - }, + "m.new_content": newContent, "m.relates_to": { rel_type: RelationType.Replace, event_id: messageId, }, - msgtype: baseContent.msgtype, + msgtype: newContent.msgtype, body: `* ${baseContent.body}`, - }); + }; + + const response = await this.sendRoomMessage(roomID, rootEventID, editContent); return { id: response.event_id, @@ -711,36 +729,40 @@ export class MatrixAdapter implements Adapter { throw new Error(`Invalid cursor format. ${String(error)}`); } - if (!parsed || typeof parsed !== "object") { + if (!isRecord(parsed)) { throw new Error("Invalid cursor format. Cursor payload must be an object."); } - const payload = parsed as Partial; - if (payload.kind !== expectedKind) { + if (parsed.kind !== expectedKind) { throw new Error(`Invalid cursor kind. Expected ${expectedKind}.`); } - if (payload.roomID !== expectedRoomID) { + if (parsed.roomID !== expectedRoomID) { throw new Error("Invalid cursor context. Room mismatch."); } - if ( - payload.dir !== "forward" && - payload.dir !== "backward" - ) { + if (parsed.dir !== "forward" && parsed.dir !== "backward") { throw new Error("Invalid cursor format. Invalid direction."); } - if (!payload.token || typeof payload.token !== "string") { + if (typeof parsed.token !== "string" || parsed.token.length === 0) { throw new Error("Invalid cursor format. Missing token."); } + const rootEventID = + typeof parsed.rootEventID === "string" ? parsed.rootEventID : undefined; if (expectedRootEventID) { - if (payload.rootEventID !== expectedRootEventID) { + if (rootEventID !== expectedRootEventID) { throw new Error("Invalid cursor context. Thread mismatch."); } - } else if (payload.rootEventID) { + } else if (rootEventID) { throw new Error("Invalid cursor context. Unexpected thread scope."); } - return payload as CursorV1Payload; + return { + dir: parsed.dir, + kind: expectedKind, + roomID: expectedRoomID, + rootEventID, + token: parsed.token, + }; } private async fetchRoomMessagesPage(args: { @@ -1011,13 +1033,14 @@ export class MatrixAdapter implements Adapter { await this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID); await this.persistSession(resolved); this.logger.info("Reused persisted Matrix session", { - userID: resolved.userID, + userId: resolved.userID, }); return resolved; } catch (error) { - this.logger.warn("Persisted Matrix session is invalid. Falling back to password login.", { - error: String(error), - }); + this.logger.warn( + "Persisted Matrix session is invalid. Falling back to password login.", + { error } + ); } } @@ -1110,29 +1133,30 @@ export class MatrixAdapter implements Adapter { }); } - return sdk.createClient({ + const client = sdk.createClient({ baseUrl: this.baseURL, accessToken: args?.accessToken, deviceId: args?.deviceID, - }) as MatrixAuthBootstrapClient; + }); + + return { + loginRequest: client.loginRequest?.bind(client), + loginWithPassword: client.loginWithPassword.bind(client), + whoami: client.whoami.bind(client), + }; } private sendRoomMessage( roomID: string, rootEventID: string | undefined, - content: unknown + content: MatrixRoomMessageContent ) { const client = this.requireClient(); if (rootEventID) { - return client.sendEvent( - roomID, - rootEventID, - EventType.RoomMessage, - content as never - ); + return client.sendEvent(roomID, rootEventID, EventType.RoomMessage, content); } - return client.sendEvent(roomID, EventType.RoomMessage, content as never); + return client.sendEvent(roomID, EventType.RoomMessage, content); } private async maybeInitE2EE(): Promise { @@ -1189,7 +1213,7 @@ export class MatrixAdapter implements Adapter { } catch (error) { this.logger.warn( "Failed to load Matrix key backup from recovery key. E2EE will run, but historical key restore may be unavailable.", - { error: String(error) } + { error } ); } } @@ -1207,8 +1231,8 @@ export class MatrixAdapter implements Adapter { await this.requireClient().decryptEventIfNeeded(event); } catch (error) { this.logger.warn("Failed to decrypt Matrix event", { - eventID: event.getId(), - error: String(error), + eventId: event.getId(), + error, }); } } @@ -1270,21 +1294,21 @@ export class MatrixAdapter implements Adapter { if (!this.liveSyncReady) { this.logger.debug("Ignoring pre-live-sync event", { - eventID, + eventId: eventID, eventType: event.getType(), - roomID, + roomId: roomID, }); return; } if (this.roomAllowlist && !this.roomAllowlist.has(roomID)) { - this.logger.debug("Ignoring event outside room allowlist", { roomID }); + this.logger.debug("Ignoring event outside room allowlist", { roomId: roomID }); return; } if (this.userID && event.getSender() === this.userID) { this.logger.debug("Ignoring self-sent event", { - eventID: event.getId(), - userID: this.userID, + eventId: event.getId(), + userId: this.userID, }); return; } @@ -1295,13 +1319,13 @@ export class MatrixAdapter implements Adapter { if (event.getType() === EventType.Reaction) { this.logger.debug("Matrix timeline event received", { - eventID: event.getId(), + eventId: event.getId(), eventType: event.getType(), - roomID, + roomId: roomID, }); this.logger.debug("Processing reaction event", { - eventID: event.getId(), - roomID, + eventId: event.getId(), + roomId: roomID, }); this.handleReactionEvent(event, roomID); return; @@ -1309,12 +1333,12 @@ export class MatrixAdapter implements Adapter { if (event.isRedaction()) { this.logger.debug("Matrix timeline event received", { - eventID: event.getId(), + eventId: event.getId(), eventType: event.getType(), - roomID, + roomId: roomID, }); this.logger.debug("Processing redaction event", { - eventID: event.getId(), + eventId: event.getId(), redacts: event.getAssociatedId(), }); this.handleReactionRedaction(event); @@ -1328,9 +1352,9 @@ export class MatrixAdapter implements Adapter { return; } this.logger.debug("Matrix timeline event received", { - eventID: event.getId(), + eventId: event.getId(), eventType: event.getType(), - roomID, + roomId: roomID, sender: event.getSender(), }); @@ -1341,8 +1365,8 @@ export class MatrixAdapter implements Adapter { const threadID = this.threadIDForEvent(event, roomID); const message = this.parseMessage(event); this.logger.debug("Dispatching Matrix message to Chat SDK", { - eventID: event.getId(), - threadID, + eventId: event.getId(), + threadId: threadID, isMention: message.isMention, }); @@ -1352,7 +1376,7 @@ export class MatrixAdapter implements Adapter { if (slash) { this.logger.debug("Dispatching slash command", { command: slash.command, - threadID, + threadId: threadID, }); chat.processSlashCommand({ adapter: this, @@ -1390,7 +1414,7 @@ export class MatrixAdapter implements Adapter { return; } - const threadID = this.encodeThreadId({ roomID }); + const threadID = this.resolveReactionThreadID(roomID, targetEventID); const emoji = getEmoji(key); const reactionEventID = event.getId(); @@ -1417,6 +1441,16 @@ export class MatrixAdapter implements Adapter { }); } + private resolveReactionThreadID(roomID: string, relatedEventID: string): string { + const room = this.requireClient().getRoom(roomID); + const relatedEvent = room?.findEventById(relatedEventID); + if (!relatedEvent) { + return this.encodeThreadId({ roomID }); + } + + return this.threadIDForEvent(relatedEvent, roomID); + } + private handleReactionRedaction(event: MatrixEvent): void { const redactedEventID = event.getAssociatedId(); if (!redactedEventID) { @@ -1466,18 +1500,15 @@ export class MatrixAdapter implements Adapter { return []; } - return [ - { - type: "file" as const, - url, - mimeType: - typeof content.info === "object" && - content.info !== null && - typeof (content.info as { mimetype?: unknown }).mimetype === "string" - ? (content.info as { mimetype: string }).mimetype - : undefined, - }, - ]; + const info = isRecord(content.info) ? content.info : undefined; + const mimeType = typeof info?.mimetype === "string" ? info.mimetype : undefined; + const attachment: { type: "file"; url: string; mimeType?: string } = { + type: "file", + url, + mimeType, + }; + + return [attachment]; } private isEdited(event: MatrixEvent): boolean { @@ -1529,7 +1560,7 @@ export class MatrixAdapter implements Adapter { private toRoomMessageContent( message: AdapterPostableMessage - ): { body: string; msgtype: MsgType } { + ): MatrixTextMessageContent { const body = this.toText(message); return { @@ -1724,27 +1755,29 @@ export class MatrixAdapter implements Adapter { try { const decryptedJSON = this.sessionConfig.decrypt(session.encryptedPayload); - const parsed = JSON.parse(decryptedJSON) as StoredSession; + const parsed: unknown = JSON.parse(decryptedJSON); + if (!this.isValidStoredSession(parsed)) { + return null; + } return parsed; } catch (error) { this.logger.warn("Failed to decrypt persisted Matrix session", { - error: String(error), + error, }); return null; } } private isValidStoredSession(value: unknown): value is StoredSession { - if (!value || typeof value !== "object") { + if (!isRecord(value)) { return false; } - const session = value as Partial; return ( - typeof session.accessToken === "string" && - typeof session.userID === "string" && - typeof session.baseURL === "string" && - session.baseURL === this.baseURL + typeof value.accessToken === "string" && + typeof value.userID === "string" && + typeof value.baseURL === "string" && + value.baseURL === this.baseURL ); } @@ -1882,10 +1915,10 @@ export class MatrixAdapter implements Adapter { return; } - const loggerWithSetLevel = matrixSDKLogger as unknown as { - setLevel?: (level: number, persist?: boolean) => void; - }; - loggerWithSetLevel.setLevel?.(numericLevel, false); + const setLevel = Reflect.get(matrixSDKLogger, "setLevel"); + if (typeof setLevel === "function") { + setLevel.call(matrixSDKLogger, numericLevel, false); + } matrixSDKLogConfigured = true; } } @@ -1915,6 +1948,10 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter return new MatrixAdapter({ baseURL, auth, + userName: + process.env.MATRIX_BOT_USERNAME ?? + process.env.MOM_BOT_USERNAME ?? + "bot", deviceID: normalizeOptionalString(process.env.MATRIX_DEVICE_ID), deviceIDPersistence: { enabled: envBool(process.env.MATRIX_DEVICE_ID_PERSIST_ENABLED, true), @@ -2063,3 +2100,7 @@ function parseSDKLogLevel( } return undefined; } + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} From 41192604f3fbb54a0842064c53f257957e682c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 28 Feb 2026 23:03:06 +0100 Subject: [PATCH 07/25] Harden isRecord and log fetchRoomEventMapped failures --- src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4dfb548..819ff9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -887,7 +887,12 @@ export class MatrixAdapter implements Adapter { const mapped = this.mapRawEvent(rawEvent, roomID); await this.tryDecryptEvent(mapped); return mapped; - } catch { + } catch (error) { + this.logger.debug("Failed to fetch room event", { + roomID, + eventID, + error: String(error), + }); return null; } } @@ -2102,5 +2107,5 @@ function parseSDKLogLevel( } function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; + return typeof value === "object" && value !== null && !Array.isArray(value); } From 2500886ca30a71c8cc408719feb13a7d76c2be40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 1 Mar 2026 16:45:28 +0100 Subject: [PATCH 08/25] Simplify: dead code removal, sliding window dedup, and minor cleanups - Delete unused isMessageEvent (isMessageEventInContext is the active one) - Replace unbounded processedTimelineEventIDs.clear() with sliding window - Extract rawEmoji helper to deduplicate addReaction/removeReaction - Pre-filter non-message events before decryption in fetchThreadMessagesPage - Cache getJoinedMembers() and getSessionStorageKey() in local variables - Consolidate three duplicate timeline event log statements into one - Simplify redundant post-decrypt type guard Co-Authored-By: Claude Opus 4.6 --- src/index.ts | 93 +++++++++++++++++++--------------------------------- 1 file changed, 33 insertions(+), 60 deletions(-) diff --git a/src/index.ts b/src/index.ts index 819ff9b..63d6dae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -404,7 +404,7 @@ export class MatrixAdapter implements Adapter { emoji: EmojiValue | string ): Promise { const { roomID, rootEventID } = this.decodeThreadId(threadId); - const rawEmoji = typeof emoji === "string" ? emoji : emoji.toString(); + const rawEmoji = this.rawEmoji(emoji); const response = await this.requireClient().sendEvent( roomID, @@ -428,7 +428,7 @@ export class MatrixAdapter implements Adapter { messageId: string, emoji: EmojiValue | string ): Promise { - const rawEmoji = typeof emoji === "string" ? emoji : emoji.toString(); + const rawEmoji = this.rawEmoji(emoji); const reactionEventID = this.myReactionByKey.get( this.myReactionKey(threadId, messageId, rawEmoji) ); @@ -609,11 +609,12 @@ export class MatrixAdapter implements Adapter { const roomID = this.decodeChannelID(channelId); const room = this.requireRoom(roomID); + const members = room.getJoinedMembers(); return { id: channelId, name: room.name, - isDM: room.getJoinedMembers().length === 2, - memberCount: room.getJoinedMembers().length, + isDM: members.length === 2, + memberCount: members.length, metadata: { roomID, }, @@ -822,11 +823,16 @@ export class MatrixAdapter implements Adapter { } ); + const candidateEvents = relationResponse.events.filter( + (event) => + event.getType() === EventType.RoomMessage || + event.getType() === EventType.RoomMessageEncrypted + ); await Promise.all( - relationResponse.events.map((event) => this.tryDecryptEvent(event)) + candidateEvents.map((event) => this.tryDecryptEvent(event)) ); const replies = this.sortEventsChronologically( - relationResponse.events.filter((event) => + candidateEvents.filter((event) => this.isMessageEventInContext(event, args.roomID, args.rootEventID) ) ); @@ -1288,7 +1294,13 @@ export class MatrixAdapter implements Adapter { } this.processedTimelineEventIDs.add(eventID); if (this.processedTimelineEventIDs.size > 10_000) { - this.processedTimelineEventIDs.clear(); + const toDelete = this.processedTimelineEventIDs.size - 5_000; + let deleted = 0; + for (const id of this.processedTimelineEventIDs) { + if (deleted >= toDelete) break; + this.processedTimelineEventIDs.delete(id); + deleted++; + } } } @@ -1320,49 +1332,25 @@ export class MatrixAdapter implements Adapter { await this.tryDecryptEvent(event); + this.logger.debug("Matrix timeline event received", { + eventId: event.getId(), + eventType: event.getType(), + roomId: roomID, + sender: event.getSender(), + }); + const chat = this.requireChat(); if (event.getType() === EventType.Reaction) { - this.logger.debug("Matrix timeline event received", { - eventId: event.getId(), - eventType: event.getType(), - roomId: roomID, - }); - this.logger.debug("Processing reaction event", { - eventId: event.getId(), - roomId: roomID, - }); this.handleReactionEvent(event, roomID); return; } if (event.isRedaction()) { - this.logger.debug("Matrix timeline event received", { - eventId: event.getId(), - eventType: event.getType(), - roomId: roomID, - }); - this.logger.debug("Processing redaction event", { - eventId: event.getId(), - redacts: event.getAssociatedId(), - }); this.handleReactionRedaction(event); return; } - if ( - event.getType() !== EventType.RoomMessage && - event.getType() !== EventType.RoomMessageEncrypted - ) { - return; - } - this.logger.debug("Matrix timeline event received", { - eventId: event.getId(), - eventType: event.getType(), - roomId: roomID, - sender: event.getSender(), - }); - if (event.getType() !== EventType.RoomMessage) { return; } @@ -1601,26 +1589,6 @@ export class MatrixAdapter implements Adapter { return String(message); } - private isMessageEvent( - event: MatrixEvent, - roomID: string, - rootEventID?: string - ): boolean { - if (event.getType() !== EventType.RoomMessage) { - return false; - } - - if (event.getRoomId() !== roomID) { - return false; - } - - if (!rootEventID) { - return !event.threadRootId; - } - - return event.threadRootId === rootEventID || event.getId() === rootEventID; - } - private mustGetEventByID(roomID: string, eventID: string): MatrixEvent { const room = this.requireRoom(roomID); const event = room.findEventById(eventID); @@ -1640,6 +1608,10 @@ export class MatrixAdapter implements Adapter { }; } + private rawEmoji(emoji: EmojiValue | string): string { + return typeof emoji === "string" ? emoji : emoji.toString(); + } + private myReactionKey( threadId: string, messageId: string, @@ -1714,15 +1686,16 @@ export class MatrixAdapter implements Adapter { username: this.auth.type === "password" ? this.auth.username : undefined, }; const encodedSession = this.encodeStoredSession(session); + const sessionKey = this.getSessionStorageKey(auth.userID); await this.stateAdapter.set( - this.getSessionStorageKey(auth.userID), + sessionKey, encodedSession, this.sessionConfig.ttlMs ); const temporaryKey = this.getSessionUsernameTemporaryKey(); - if (temporaryKey && temporaryKey !== this.getSessionStorageKey(auth.userID)) { + if (temporaryKey && temporaryKey !== sessionKey) { await this.stateAdapter.set(temporaryKey, encodedSession, this.sessionConfig.ttlMs); } } From db94c1ab6ab289581d7eac60739de21bb6e5efc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 1 Mar 2026 17:15:13 +0100 Subject: [PATCH 09/25] Ignore .claude local settings Add .claude/settings.local.json to .gitignore to avoid committing local Claude configuration files (sensitive or environment-specific settings). --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 69ac51b..f6fb0d9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ coverage **/.env **/.env.* !**/.env.example +.claude/settings.local.json From 7d7bf35d2c6c79bb2022e3f0d82f7c17c7e314ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 1 Mar 2026 17:32:50 +0100 Subject: [PATCH 10/25] Add invite auto-join and file upload support Introduce invite auto-join behavior and outbound file/media handling for Matrix. - Add MatrixInviteAutoJoinConfig and env vars (MATRIX_INVITE_AUTOJOIN_ENABLED, MATRIX_INVITE_AUTOJOIN_ALLOWLIST) and document usage in README. Auto-join only accepts m.room.member invites targeted at the bot and enforces inviter and room allowlists. - Implement invite auto-join logic in the adapter with tests covering allowlisted and non-allowlisted inviters. - Add support for uploading local files/attachments via the SDK (uploadContent) and posting media events; URL-only attachments are appended as links to the message body. Includes unit tests for uploading and URL-only attachments. - Refactor postMessage to build multiple room message contents (text + media) and send them sequentially. - Add various helpers and hygiene: parsing env lists, normalizing strings/lists, converting directions for the SDK, trimming reaction cache, avoiding storing accessToken in persisted session metadata, and fixes to event mapping and buffers/Blob normalization. - Update types, tests, and README accordingly. Also includes small bug fixes and internal refactors to message parsing, event mapping, and client interactions. --- README.md | 29 +++ src/index.test.ts | 169 +++++++++++++++++ src/index.ts | 464 +++++++++++++++++++++++++++++++++++++++------- src/types.ts | 6 + 4 files changed, 597 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 0a41c14..c9fe9dd 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,36 @@ Common optional: - `MATRIX_DEVICE_ID` - `MATRIX_RECOVERY_KEY` - `MATRIX_BOT_USERNAME` (mention detection display name, defaults to `MOM_BOT_USERNAME` then `bot`) +- `MATRIX_INVITE_AUTOJOIN_ENABLED` (`true`/`false`; defaults to `true` when invite allowlist is set, otherwise `false`) +- `MATRIX_INVITE_AUTOJOIN_ALLOWLIST` (comma-separated Matrix user IDs allowed to invite the bot, e.g. `@alice:beeper.com,@team-bot:beeper.com`) Advanced options are available for device/session persistence, E2EE storage, and SDK logging (`MATRIX_SDK_LOG_LEVEL`). +## Invite Auto-Join + +Enable this when you want the bot to accept incoming room invites automatically. + +```ts +createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "accessToken", + accessToken: process.env.MATRIX_ACCESS_TOKEN!, + userID: process.env.MATRIX_USER_ID, + }, + inviteAutoJoin: { + enabled: true, + inviterAllowlist: ["@alice:beeper.com", "@ops:beeper.com"], + }, +}); +``` + +Behavior: + +- Only `m.room.member` invites targeted at the bot user are considered. +- If `inviterAllowlist` is set, only those inviters are accepted. +- If `roomAllowlist` is also set, both checks must pass. + ## Running The Example Copy [`examples/.env.example`](./examples/.env.example) to `examples/.env`, then run: @@ -110,6 +137,8 @@ bun --env-file=examples/.env run examples/bot.ts - `fetchMessage(threadId, messageId)` fetches a single message with thread/channel context validation. - `fetchChannelMessages(channelId, options)` fetches top-level room timeline messages. - `fetchMessages(threadId, options)` and `listThreads(channelId, options)` use API-first server pagination via `matrix-js-sdk`. +- Outbound file support: `files` and binary `attachments` are uploaded with `uploadContent()` and sent as Matrix media messages. +- URL-only attachments are appended as links in the text body. - `postEphemeral`, `openModal`, and native `stream` are not implemented by this adapter. ## Notes diff --git a/src/index.test.ts b/src/index.test.ts index a44fab3..7752fbc 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -11,6 +11,7 @@ function makeEvent(overrides: Record = {}) { getRoomId: () => "!room:beeper.com", getTs: () => 1_700_000_000_000, getSender: () => "@alice:beeper.com", + getStateKey: () => undefined, getType: () => EventType.RoomMessage, getContent: () => ({ body: "hello" }), getRelation: () => null, @@ -153,9 +154,11 @@ function makeClient() { stopClient: vi.fn(() => undefined), sendMessage: vi.fn(async () => ({ event_id: "$sent" })), sendEvent: vi.fn(async () => ({ event_id: "$reaction" })), + uploadContent: vi.fn(async () => ({ content_uri: "mxc://beeper.com/uploaded" })), redactEvent: vi.fn(async () => ({ event_id: "$redaction" })), sendTyping: vi.fn(async () => ({})), createRoom: vi.fn(async () => ({ room_id: "!new-dm:beeper.com" })), + joinRoom: vi.fn(async () => ({ room_id: "!joined:beeper.com" })), createMessagesRequest: vi.fn( async (): Promise => ({ chunk: [], end: undefined }) ), @@ -327,6 +330,74 @@ describe("MatrixAdapter", () => { }); }); + it("auto-joins invite events from allowlisted inviters", async () => { + const fakeClient = makeClient(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + inviteAutoJoin: { + enabled: true, + inviterAllowlist: ["@alice:beeper.com"], + }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance()); + const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + + timelineHandler?.( + makeEvent({ + getType: () => EventType.RoomMember, + getSender: () => "@alice:beeper.com", + getStateKey: () => "@bot:beeper.com", + getContent: () => ({ membership: "invite" }), + }), + { roomId: "!invited:beeper.com" }, + false + ); + await Promise.resolve(); + + expect(fakeClient.joinRoom).toHaveBeenCalledWith("!invited:beeper.com"); + }); + + it("does not auto-join invite events from non-allowlisted inviters", async () => { + const fakeClient = makeClient(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + inviteAutoJoin: { + enabled: true, + inviterAllowlist: ["@trusted:beeper.com"], + }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance()); + const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + + timelineHandler?.( + makeEvent({ + getType: () => EventType.RoomMember, + getSender: () => "@alice:beeper.com", + getStateKey: () => "@bot:beeper.com", + getContent: () => ({ membership: "invite" }), + }), + { roomId: "!blocked:beeper.com" }, + false + ); + await Promise.resolve(); + + expect(fakeClient.joinRoom).not.toHaveBeenCalled(); + }); + it("maps reaction add and redaction remove events", async () => { const fakeClient = makeClient(); const processReaction = vi.fn(); @@ -561,6 +632,104 @@ describe("MatrixAdapter", () => { ); }); + it("uploads file payloads and posts Matrix media events", async () => { + const fakeClient = makeClient(); + fakeClient.sendEvent = vi + .fn() + .mockResolvedValueOnce({ event_id: "$text" }) + .mockResolvedValueOnce({ event_id: "$file" }); + fakeClient.uploadContent = vi + .fn() + .mockResolvedValueOnce({ content_uri: "mxc://beeper.com/file-1" }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + + await adapter.postMessage("matrix:!room%3Abeeper.com", { + markdown: "File incoming", + files: [ + { + data: new Uint8Array([1, 2, 3]).buffer, + filename: "report.png", + mimeType: "image/png", + }, + ], + }); + + expect(fakeClient.uploadContent).toHaveBeenCalledWith( + expect.any(Blob), + expect.objectContaining({ + name: "report.png", + type: "image/png", + }) + ); + expect(fakeClient.sendEvent).toHaveBeenNthCalledWith( + 1, + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + body: "File incoming", + msgtype: "m.text", + }) + ); + expect(fakeClient.sendEvent).toHaveBeenNthCalledWith( + 2, + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + body: "report.png", + msgtype: "m.image", + url: "mxc://beeper.com/file-1", + }) + ); + }); + + it("appends URL-only attachments to message body", async () => { + const fakeClient = makeClient(); + fakeClient.sendEvent = vi.fn(async () => ({ event_id: "$text" })); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + + await adapter.postMessage("matrix:!room%3Abeeper.com", { + raw: "See attachment", + attachments: [ + { + type: "file", + name: "spec", + url: "https://example.com/spec.pdf", + }, + ], + }); + + expect(fakeClient.uploadContent).not.toHaveBeenCalled(); + expect(fakeClient.sendEvent).toHaveBeenCalledWith( + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + msgtype: "m.text", + body: "See attachment\n\nspec: https://example.com/spec.pdf", + }) + ); + }); + it("generates and persists a device id when one is not provided", async () => { const adapter = getInternals( new MatrixAdapter({ diff --git a/src/index.ts b/src/index.ts index 63d6dae..99b7b25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,11 @@ import { randomBytes } from "node:crypto"; import type { Adapter, AdapterPostableMessage, + Attachment, ChannelInfo, ChatInstance, EmojiValue, + FileUpload, FetchOptions, FetchResult, FormattedContent, @@ -143,6 +145,25 @@ type CursorV1Payload = { type DirectAccountData = Record; +type OutboundUpload = { + data: Blob; + fileName: string; + info?: { + h?: number; + mimetype?: string; + size?: number; + w?: number; + }; + msgtype: MatrixMediaMsgType; + type?: string; +}; + +type MatrixMediaMsgType = + | MsgType.Audio + | MsgType.File + | MsgType.Image + | MsgType.Video; + // Intentionally unsupported in this adapter: postEphemeral, openModal, and native stream. export class MatrixAdapter implements Adapter { readonly name = "matrix"; @@ -152,6 +173,8 @@ export class MatrixAdapter implements Adapter { private readonly auth: MatrixAuthConfig; private readonly commandPrefix: string; private readonly roomAllowlist?: Set; + private readonly inviteAutoJoinEnabled: boolean; + private readonly inviteAutoJoinInviterAllowlist?: Set; private readonly syncOptions?: MatrixAdapterConfig["sync"]; private readonly createClientFn?: MatrixAdapterConfig["createClient"]; private readonly createBootstrapClientFn?: MatrixAdapterConfig["createBootstrapClient"]; @@ -175,7 +198,6 @@ export class MatrixAdapter implements Adapter { private started = false; private userID: string; private deviceID?: string; - private botUserID?: string; private readonly reactionByEventID = new Map(); private readonly myReactionByKey = new Map(); private readonly processedTimelineEventIDs = new Set(); @@ -186,17 +208,22 @@ export class MatrixAdapter implements Adapter { this.validateConfig(config); this.baseURL = config.baseURL; this.auth = config.auth; - this.userID = - config.auth.type === "accessToken" - ? (config.auth.userID ?? "") - : (config.auth.userID ?? ""); - this.botUserID = this.userID || undefined; + this.userID = config.auth.userID ?? ""; this.deviceID = normalizeOptionalString(config.deviceID); this.userName = config.userName ?? "bot"; this.commandPrefix = config.commandPrefix ?? DEFAULT_COMMAND_PREFIX; this.roomAllowlist = config.roomAllowlist ? new Set(config.roomAllowlist) : undefined; + const inviteAutoJoinInviterAllowlist = normalizeStringList( + config.inviteAutoJoin?.inviterAllowlist + ); + this.inviteAutoJoinEnabled = + config.inviteAutoJoin?.enabled ?? Boolean(config.inviteAutoJoin); + this.inviteAutoJoinInviterAllowlist = + inviteAutoJoinInviterAllowlist.length > 0 + ? new Set(inviteAutoJoinInviterAllowlist) + : undefined; this.syncOptions = config.sync ?? FAST_SYNC_DEFAULTS; this.createClientFn = config.createClient; this.createBootstrapClientFn = config.createBootstrapClient; @@ -241,7 +268,6 @@ export class MatrixAdapter implements Adapter { } else { const resolvedAuth = await this.resolveAuth(); this.userID = resolvedAuth.userID; - this.botUserID = resolvedAuth.userID; this.deviceID = normalizeOptionalString(resolvedAuth.deviceID) ?? this.deviceID; this.client = this.buildClient(resolvedAuth); } @@ -274,7 +300,7 @@ export class MatrixAdapter implements Adapter { } get botUserId(): string | undefined { - return this.botUserID; + return this.userID || undefined; } async shutdown(): Promise { @@ -326,7 +352,7 @@ export class MatrixAdapter implements Adapter { channelIdFromThreadId(threadId: string): string { const { roomID } = this.decodeThreadId(threadId); - return `${MATRIX_PREFIX}:${encodeURIComponent(roomID)}`; + return this.encodeThreadId({ roomID }); } renderFormatted(content: FormattedContent): string { @@ -342,9 +368,14 @@ export class MatrixAdapter implements Adapter { message: AdapterPostableMessage ): Promise> { const { roomID, rootEventID } = this.decodeThreadId(threadId); - const content = this.toRoomMessageContent(message); - - const response = await this.sendRoomMessage(roomID, rootEventID, content); + const contents = await this.toRoomMessageContents(message); + const [firstContent, ...extraContents] = contents; + const primaryContent = + firstContent ?? ({ body: "", msgtype: MsgType.Text } as MatrixRoomMessageContent); + const response = await this.sendRoomMessage(roomID, rootEventID, primaryContent); + for (const content of extraContents) { + await this.sendRoomMessage(roomID, rootEventID, content); + } return { id: response.event_id, @@ -439,6 +470,7 @@ export class MatrixAdapter implements Adapter { const { roomID } = this.decodeThreadId(threadId); await this.requireClient().redactEvent(roomID, reactionEventID); + this.myReactionByKey.delete(this.myReactionKey(threadId, messageId, rawEmoji)); } async startTyping(threadId: string): Promise { @@ -543,31 +575,7 @@ export class MatrixAdapter implements Adapter { options: FetchOptions = {} ): Promise> { const roomID = this.decodeChannelID(channelId); - const direction = options.direction ?? "backward"; - const limit = options.limit ?? 50; - const cursor = options.cursor - ? this.decodeCursorV1(options.cursor, "room_messages", roomID) - : null; - - const response = await this.fetchRoomMessagesPage({ - roomID, - includeThreadReplies: false, - limit, - direction, - fromToken: cursor?.token ?? null, - }); - - return { - messages: response.events.map((event) => this.parseMessageInternal(event)), - nextCursor: response.nextToken - ? this.encodeCursorV1({ - kind: "room_messages", - dir: direction, - token: response.nextToken, - roomID, - }) - : undefined, - }; + return this.fetchMessages(this.encodeThreadId({ roomID }), options); } async fetchMessage( @@ -766,6 +774,10 @@ export class MatrixAdapter implements Adapter { }; } + private toSDKDirection(dir: CursorDirection): Direction { + return dir === "forward" ? Direction.Forward : Direction.Backward; + } + private async fetchRoomMessagesPage(args: { roomID: string; includeThreadReplies: boolean; @@ -777,7 +789,7 @@ export class MatrixAdapter implements Adapter { args.roomID, args.fromToken, args.limit, - args.direction === "forward" ? Direction.Forward : Direction.Backward + this.toSDKDirection(args.direction) ); const events = await this.mapRawEvents(response.chunk ?? [], args.roomID); const filtered = events.filter((event) => { @@ -817,7 +829,7 @@ export class MatrixAdapter implements Adapter { THREAD_RELATION_TYPE.name, null, { - dir: args.direction === "forward" ? Direction.Forward : Direction.Backward, + dir: this.toSDKDirection(args.direction), from: args.fromToken ?? undefined, limit: relationLimit, } @@ -867,7 +879,11 @@ export class MatrixAdapter implements Adapter { rawEvents: Array>, roomID: string ): Promise { - const events = rawEvents.map((event) => this.mapRawEvent(event, roomID)); + const mapper = this.requireClient().getEventMapper(); + const events = rawEvents.map((event) => { + const withRoomID = event.room_id ? event : { ...event, room_id: roomID }; + return mapper(withRoomID); + }); await Promise.all(events.map((event) => this.tryDecryptEvent(event))); return events; } @@ -957,7 +973,7 @@ export class MatrixAdapter implements Adapter { const cached = await this.stateAdapter.get( this.getDMStorageKey(userID) ); - const normalized = normalizeOptionalString(cached ?? undefined); + const normalized = normalizeOptionalString(cached); return normalized ?? null; } @@ -1088,11 +1104,6 @@ export class MatrixAdapter implements Adapter { return resolved; } - private async lookupUserIDFromAccessToken(accessToken: string): Promise { - const whoami = await this.lookupWhoAmIFromAccessToken(accessToken); - return whoami.userID; - } - private async lookupWhoAmIFromAccessToken( accessToken: string ): Promise<{ deviceID?: string; userID: string }> { @@ -1170,6 +1181,49 @@ export class MatrixAdapter implements Adapter { return client.sendEvent(roomID, EventType.RoomMessage, content); } + private async toRoomMessageContents( + message: AdapterPostableMessage + ): Promise { + const textContent = this.toRoomMessageContent(message); + const uploads = await this.collectUploads(message); + const linkLines = this.collectLinkOnlyAttachmentLines(message); + const textBody = this.mergeTextAndLinks(textContent.body ?? "", linkLines); + const textMsgType = textContent.msgtype ?? MsgType.Text; + const contents: MatrixRoomMessageContent[] = []; + + if (textBody.length > 0) { + contents.push({ + ...textContent, + body: textBody, + msgtype: textMsgType, + }); + } + + for (const upload of uploads) { + const uploadResponse = await this.requireClient().uploadContent(upload.data, { + name: upload.fileName, + type: upload.info?.mimetype, + }); + const mediaContent = { + body: upload.fileName, + msgtype: upload.msgtype, + url: uploadResponse.content_uri, + info: upload.info, + } as unknown as MatrixRoomMessageContent; + contents.push(mediaContent); + } + + if (contents.length === 0) { + contents.push({ + ...textContent, + body: "", + msgtype: textMsgType, + }); + } + + return contents; + } + private async maybeInitE2EE(): Promise { if (!this.e2eeConfig?.enabled) { return; @@ -1309,6 +1363,10 @@ export class MatrixAdapter implements Adapter { return; } + if (event.getType() === EventType.RoomMember) { + await this.maybeAutoJoinInvite(event, roomID); + } + if (!this.liveSyncReady) { this.logger.debug("Ignoring pre-live-sync event", { eventId: eventID, @@ -1383,6 +1441,67 @@ export class MatrixAdapter implements Adapter { } } + private async maybeAutoJoinInvite( + event: MatrixEvent, + roomID: string + ): Promise { + if (!this.inviteAutoJoinEnabled || event.getType() !== EventType.RoomMember) { + return; + } + + const membership = event.getContent<{ membership?: string }>()?.membership; + if (membership !== "invite") { + return; + } + + const targetUserID = event.getStateKey(); + if (!targetUserID || targetUserID !== this.userID) { + return; + } + + const inviter = event.getSender(); + if (!this.shouldAcceptInvite(roomID, inviter)) { + this.logger.info("Declined Matrix invite due to invite auto-join policy", { + roomId: roomID, + inviter, + }); + return; + } + + try { + await this.requireClient().joinRoom(roomID); + this.logger.info("Accepted Matrix invite", { + roomId: roomID, + inviter, + }); + } catch (error) { + this.logger.warn("Failed to auto-join Matrix invite", { + roomId: roomID, + inviter, + error, + }); + } + } + + private shouldAcceptInvite( + roomID: string, + inviter: string | null | undefined + ): boolean { + if (this.roomAllowlist && !this.roomAllowlist.has(roomID)) { + return false; + } + + if (!this.inviteAutoJoinInviterAllowlist) { + return true; + } + + if (!inviter) { + return false; + } + + return this.inviteAutoJoinInviterAllowlist.has(inviter); + } + private handleReactionEvent(event: MatrixEvent, roomID: string): void { const content = event.getContent<{ "m.relates_to"?: Record }>(); const relatesTo = content["m.relates_to"]; @@ -1420,6 +1539,16 @@ export class MatrixAdapter implements Adapter { rawEmoji: key, userID: sender, }); + + if (this.reactionByEventID.size > 10_000) { + const toDelete = this.reactionByEventID.size - 5_000; + let deleted = 0; + for (const id of this.reactionByEventID.keys()) { + if (deleted >= toDelete) break; + this.reactionByEventID.delete(id); + deleted++; + } + } } this.requireChat().processReaction({ @@ -1586,7 +1715,185 @@ export class MatrixAdapter implements Adapter { } } - return String(message); + return ""; + } + + private mergeTextAndLinks(text: string, linkLines: string[]): string { + if (linkLines.length === 0) { + return text; + } + + const suffix = linkLines.join("\n"); + if (!text) { + return suffix; + } + + return `${text}\n\n${suffix}`; + } + + private collectLinkOnlyAttachmentLines(message: AdapterPostableMessage): string[] { + const attachments = this.extractAttachmentsFromMessage(message); + const lines: string[] = []; + for (const attachment of attachments) { + const hasLocalData = + Boolean(attachment.data) || typeof attachment.fetchData === "function"; + if (hasLocalData) { + continue; + } + if (!attachment.url) { + continue; + } + const label = attachment.name ?? attachment.type; + lines.push(`${label}: ${attachment.url}`); + } + return lines; + } + + private async collectUploads( + message: AdapterPostableMessage + ): Promise { + const uploads: OutboundUpload[] = []; + const files = this.extractFilesFromMessage(message); + for (const file of files) { + uploads.push({ + data: this.normalizeUploadData(file.data), + fileName: file.filename, + info: { + mimetype: normalizeOptionalString(file.mimeType), + size: this.binarySize(file.data), + }, + msgtype: this.messageTypeForFile(file.mimeType), + }); + } + + const attachments = this.extractAttachmentsFromMessage(message); + for (const attachment of attachments) { + const data = await this.readAttachmentData(attachment); + if (!data) { + continue; + } + const fileName = + normalizeOptionalString(attachment.name) ?? + this.defaultAttachmentName(attachment); + uploads.push({ + data: this.normalizeUploadData(data), + fileName, + info: { + h: attachment.height, + mimetype: normalizeOptionalString(attachment.mimeType), + size: attachment.size ?? this.binarySize(data), + w: attachment.width, + }, + msgtype: this.messageTypeForAttachment(attachment), + type: attachment.type, + }); + } + + return uploads; + } + + private extractFilesFromMessage(message: AdapterPostableMessage): FileUpload[] { + if (typeof message !== "object" || message === null) { + return []; + } + if (!("files" in message) || !Array.isArray(message.files)) { + return []; + } + return message.files.filter((file): file is FileUpload => isRecord(file)); + } + + private extractAttachmentsFromMessage(message: AdapterPostableMessage): Attachment[] { + if (typeof message !== "object" || message === null) { + return []; + } + if (!("attachments" in message) || !Array.isArray(message.attachments)) { + return []; + } + return message.attachments.filter((attachment): attachment is Attachment => + isRecord(attachment) + ); + } + + private async readAttachmentData( + attachment: Attachment + ): Promise { + if (typeof attachment.fetchData === "function") { + return attachment.fetchData(); + } + return attachment.data ?? null; + } + + private normalizeUploadData(data: Buffer | Blob | ArrayBuffer): Blob { + if (data instanceof Blob) { + return data; + } + if (this.isNodeBuffer(data)) { + const start = data.byteOffset; + const end = data.byteOffset + data.byteLength; + const arrayBuffer = data.buffer.slice(start, end) as ArrayBuffer; + return new Blob([arrayBuffer]); + } + return new Blob([data]); + } + + private binarySize(data: Buffer | Blob | ArrayBuffer): number { + if (data instanceof ArrayBuffer) { + return data.byteLength; + } + if (this.isNodeBuffer(data)) { + return data.length; + } + return data.size; + } + + private isNodeBuffer(value: unknown): value is Buffer { + return typeof Buffer !== "undefined" && Buffer.isBuffer(value); + } + + private messageTypeForFile(mimeType?: string): MatrixMediaMsgType { + return this.messageTypeForMimeType(normalizeOptionalString(mimeType)); + } + + private messageTypeForAttachment(attachment: Attachment): MatrixMediaMsgType { + switch (attachment.type) { + case "image": + return MsgType.Image; + case "video": + return MsgType.Video; + case "audio": + return MsgType.Audio; + default: + return this.messageTypeForMimeType(normalizeOptionalString(attachment.mimeType)); + } + } + + private messageTypeForMimeType(mimeType?: string): MatrixMediaMsgType { + if (!mimeType) { + return MsgType.File; + } + if (mimeType.startsWith("image/")) { + return MsgType.Image; + } + if (mimeType.startsWith("video/")) { + return MsgType.Video; + } + if (mimeType.startsWith("audio/")) { + return MsgType.Audio; + } + return MsgType.File; + } + + private defaultAttachmentName(attachment: Attachment): string { + switch (attachment.type) { + case "image": + return "image"; + case "video": + return "video"; + case "audio": + return "audio"; + default: + return "file"; + } } private mustGetEventByID(roomID: string, eventID: string): MatrixEvent { @@ -1706,18 +2013,8 @@ export class MatrixAdapter implements Adapter { } const encryptedPayload = this.sessionConfig.encrypt(JSON.stringify(session)); - return { - authType: session.authType, - baseURL: session.baseURL, - createdAt: session.createdAt, - deviceID: session.deviceID, - e2eeEnabled: session.e2eeEnabled, - encryptedPayload, - recoveryKeyPresent: session.recoveryKeyPresent, - updatedAt: session.updatedAt, - userID: session.userID, - username: session.username, - }; + const { accessToken, ...metadata } = session; + return { ...metadata, encryptedPayload }; } private decodeStoredSession( @@ -1837,7 +2134,7 @@ export class MatrixAdapter implements Adapter { continue; } const value = await this.stateAdapter.get(key); - const normalized = normalizeOptionalString(value ?? undefined); + const normalized = normalizeOptionalString(value); if (normalized) { return normalized; } @@ -1920,6 +2217,13 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter const recoveryKey = process.env.MATRIX_RECOVERY_KEY; const e2eeEnabled = Boolean(recoveryKey) || envBool(process.env.MATRIX_E2EE_ENABLED); + const inviteAutoJoinInviterAllowlist = parseEnvList( + process.env.MATRIX_INVITE_AUTOJOIN_ALLOWLIST + ); + const inviteAutoJoinEnabled = envBool( + process.env.MATRIX_INVITE_AUTOJOIN_ENABLED, + inviteAutoJoinInviterAllowlist.length > 0 + ); const auth = resolveAuthFromEnv(); @@ -1937,6 +2241,10 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter }, commandPrefix: process.env.MATRIX_COMMAND_PREFIX, recoveryKey, + inviteAutoJoin: { + enabled: inviteAutoJoinEnabled, + inviterAllowlist: inviteAutoJoinInviterAllowlist, + }, e2ee: { enabled: e2eeEnabled, useIndexedDB: envBool( @@ -2036,6 +2344,17 @@ function parseEnvNumber(value: string | undefined): number | undefined { return parsed; } +function parseEnvList(value: string | undefined): string[] { + if (!value) { + return []; + } + + return value + .split(",") + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); +} + function generateDeviceID(length = 8): string { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const bytes = randomBytes(length); @@ -2048,7 +2367,7 @@ function generateDeviceID(length = 8): string { return out; } -function normalizeOptionalString(value: string | undefined): string | undefined { +function normalizeOptionalString(value: string | null | undefined): string | undefined { if (!value) { return undefined; } @@ -2056,6 +2375,16 @@ function normalizeOptionalString(value: string | undefined): string | undefined return trimmed.length > 0 ? trimmed : undefined; } +function normalizeStringList(values: string[] | undefined): string[] { + if (!values || values.length === 0) { + return []; + } + + return values + .map((value) => normalizeOptionalString(value)) + .filter((value): value is string => Boolean(value)); +} + function hasIndexedDB(): boolean { return typeof globalThis.indexedDB !== "undefined" && globalThis.indexedDB !== null; } @@ -2067,16 +2396,9 @@ function parseSDKLogLevel( return undefined; } const normalized = value.trim().toLowerCase(); - if ( - normalized === "trace" || - normalized === "debug" || - normalized === "info" || - normalized === "warn" || - normalized === "error" - ) { - return normalized; - } - return undefined; + return normalized in MATRIX_SDK_LOG_LEVELS + ? (normalized as MatrixAdapterConfig["matrixSDKLogLevel"]) + : undefined; } function isRecord(value: unknown): value is Record { diff --git a/src/types.ts b/src/types.ts index 6047626..6bb3572 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,6 +38,7 @@ export interface MatrixAdapterConfig { deviceIDPersistence?: MatrixDeviceIDPersistenceConfig; deviceID?: string; e2ee?: MatrixE2EEConfig; + inviteAutoJoin?: MatrixInviteAutoJoinConfig; logger?: Logger; matrixSDKLogLevel?: "trace" | "debug" | "info" | "warn" | "error"; recoveryKey?: string; @@ -47,6 +48,11 @@ export interface MatrixAdapterConfig { userName?: string; } +export interface MatrixInviteAutoJoinConfig { + enabled?: boolean; + inviterAllowlist?: string[]; +} + export interface MatrixThreadID { roomID: string; rootEventID?: string; From 7022b6c2613f6e15ad2a0f559016b784c0b9fc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 1 Mar 2026 18:14:49 +0100 Subject: [PATCH 11/25] Refactor attachments, eviction, and session Multiple refactors and fixes: add SyncState import and use enum values for sync checks; introduce SDKLogLevel type and safer parse guard for SDK log levels. Rework message/attachment flow: require non-empty toRoomMessageContents, change collectUploads to accept attachments, extract attachments once, upload files in parallel (Promise.all), and simplify link-only line collection. Improve binary handling by creating Blobs from Node buffers via Uint8Array. Replace several ad-hoc cache-trimming blocks with a reusable eviction utility (evictOldestEntries) and add a private evictOldest helper; apply eviction to timeline and reaction caches. Fix session persistence to preserve original createdAt when reusing restored sessions. Also replace decodeChannelID usages with decodeThreadId().roomID and some small API/typing cleanups. --- src/index.test.ts | 100 +++++++--------------------------- src/index.ts | 133 +++++++++++++++++++++------------------------- 2 files changed, 82 insertions(+), 151 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 7752fbc..6f027d7 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -267,6 +267,16 @@ function makeChatInstance(overrides: Record = {}): ChatInstance } as unknown as ChatInstance; } +async function makeInitializedAdapter(fakeClient: ReturnType) { + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + return adapter; +} + describe("MatrixAdapter", () => { it("encodes and decodes thread IDs", () => { const adapter = new MatrixAdapter({ @@ -596,17 +606,7 @@ describe("MatrixAdapter", () => { it("sends Matrix edit payload with dont_render_edited context", async () => { const fakeClient = makeClient(); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - createClient: () => asMatrixClient(fakeClient), - }); - - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); await adapter.editMessage( "matrix:!room%3Abeeper.com", @@ -642,17 +642,7 @@ describe("MatrixAdapter", () => { .fn() .mockResolvedValueOnce({ content_uri: "mxc://beeper.com/file-1" }); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - createClient: () => asMatrixClient(fakeClient), - }); - - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); await adapter.postMessage("matrix:!room%3Abeeper.com", { markdown: "File incoming", @@ -696,17 +686,7 @@ describe("MatrixAdapter", () => { it("appends URL-only attachments to message body", async () => { const fakeClient = makeClient(); fakeClient.sendEvent = vi.fn(async () => ({ event_id: "$text" })); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - createClient: () => asMatrixClient(fakeClient), - }); - - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); await adapter.postMessage("matrix:!room%3Abeeper.com", { raw: "See attachment", @@ -790,17 +770,7 @@ describe("MatrixAdapter", () => { it("shuts down matrix client cleanly", async () => { const fakeClient = makeClient(); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { - type: "accessToken", - accessToken: "token", - userID: "@bot:beeper.com", - }, - createClient: () => asMatrixClient(fakeClient), - }); - - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); await adapter.shutdown(); expect(fakeClient.stopClient).toHaveBeenCalledOnce(); @@ -1024,12 +994,7 @@ describe("MatrixAdapter", () => { it("rejects legacy cursors for API pagination", async () => { const fakeClient = makeClient(); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); await expect( adapter.fetchMessages("matrix:!room%3Abeeper.com", { cursor: "$legacy_cursor" }) @@ -1067,12 +1032,7 @@ describe("MatrixAdapter", () => { end: undefined, }); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); const firstPage = await adapter.fetchMessages("matrix:!room%3Abeeper.com", { direction: "backward", @@ -1147,12 +1107,7 @@ describe("MatrixAdapter", () => { }) ); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); const page = await adapter.fetchMessages( "matrix:!room%3Abeeper.com:%24root", @@ -1200,12 +1155,7 @@ describe("MatrixAdapter", () => { end: "channel-page-token-1", }); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); const result = await adapter.fetchChannelMessages?.("matrix:!room%3Abeeper.com", { direction: "backward", @@ -1224,12 +1174,7 @@ describe("MatrixAdapter", () => { it("fetches a single message in context and returns null for mismatches", async () => { const fakeClient = makeClient(); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); fakeClient.fetchRoomEvent .mockResolvedValueOnce( @@ -1350,12 +1295,7 @@ describe("MatrixAdapter", () => { end: "thread-list-page-token-1", }); - const adapter = new MatrixAdapter({ - baseURL: "https://hs.beeper.com", - auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - createClient: () => asMatrixClient(fakeClient), - }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + const adapter = await makeInitializedAdapter(fakeClient); const result = await adapter.listThreads("matrix:!room%3Abeeper.com", { limit: 2 }); diff --git a/src/index.ts b/src/index.ts index 99b7b25..494ceb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,6 +39,7 @@ import sdk, { MsgType, RelationType, RoomEvent, + SyncState, ThreadFilterType, THREAD_RELATION_TYPE, } from "matrix-js-sdk"; @@ -69,7 +70,8 @@ const FAST_SYNC_DEFAULTS: NonNullable = { disablePresence: true, pollTimeout: 10_000, }; -const MATRIX_SDK_LOG_LEVELS: Record = { +type SDKLogLevel = NonNullable; +const MATRIX_SDK_LOG_LEVELS: Record = { trace: 0, debug: 1, info: 2, @@ -97,6 +99,7 @@ type MatrixRoomMessageContent = RoomMessageEventContent & { }; }; + type StoredReaction = { emoji: EmojiValue; messageID: string; @@ -273,7 +276,7 @@ export class MatrixAdapter implements Adapter { } this.client.on(ClientEvent.Sync, (state: string) => { - if (state === "PREPARED" || state === "SYNCING") { + if (state === SyncState.Prepared || state === SyncState.Syncing) { this.liveSyncReady = true; } this.logger.debug("Matrix sync state", { state }); @@ -370,9 +373,10 @@ export class MatrixAdapter implements Adapter { const { roomID, rootEventID } = this.decodeThreadId(threadId); const contents = await this.toRoomMessageContents(message); const [firstContent, ...extraContents] = contents; - const primaryContent = - firstContent ?? ({ body: "", msgtype: MsgType.Text } as MatrixRoomMessageContent); - const response = await this.sendRoomMessage(roomID, rootEventID, primaryContent); + if (!firstContent) { + throw new Error("toRoomMessageContents returned an empty array"); + } + const response = await this.sendRoomMessage(roomID, rootEventID, firstContent); for (const content of extraContents) { await this.sendRoomMessage(roomID, rootEventID, content); } @@ -388,7 +392,7 @@ export class MatrixAdapter implements Adapter { channelId: string, message: AdapterPostableMessage ): Promise> { - const roomID = this.decodeChannelID(channelId); + const roomID = this.decodeThreadId(channelId).roomID; return this.postMessage(this.encodeThreadId({ roomID }), message); } @@ -574,7 +578,7 @@ export class MatrixAdapter implements Adapter { channelId: string, options: FetchOptions = {} ): Promise> { - const roomID = this.decodeChannelID(channelId); + const roomID = this.decodeThreadId(channelId).roomID; return this.fetchMessages(this.encodeThreadId({ roomID }), options); } @@ -614,7 +618,7 @@ export class MatrixAdapter implements Adapter { } async fetchChannelInfo(channelId: string): Promise { - const roomID = this.decodeChannelID(channelId); + const roomID = this.decodeThreadId(channelId).roomID; const room = this.requireRoom(roomID); const members = room.getJoinedMembers(); @@ -633,7 +637,7 @@ export class MatrixAdapter implements Adapter { channelId: string, options: ListThreadsOptions = {} ): Promise> { - const roomID = this.decodeChannelID(channelId); + const roomID = this.decodeThreadId(channelId).roomID; const direction: CursorDirection = "backward"; const limit = options.limit ?? 50; const cursor = options.cursor @@ -1058,7 +1062,7 @@ export class MatrixAdapter implements Adapter { deviceID: this.deviceID ?? whoami.deviceID ?? restored.deviceID, }; await this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID); - await this.persistSession(resolved); + await this.persistSession(resolved, restored.createdAt); this.logger.info("Reused persisted Matrix session", { userId: resolved.userID, }); @@ -1185,8 +1189,9 @@ export class MatrixAdapter implements Adapter { message: AdapterPostableMessage ): Promise { const textContent = this.toRoomMessageContent(message); - const uploads = await this.collectUploads(message); - const linkLines = this.collectLinkOnlyAttachmentLines(message); + const attachments = this.extractAttachmentsFromMessage(message); + const uploads = await this.collectUploads(message, attachments); + const linkLines = this.collectLinkOnlyAttachmentLines(attachments); const textBody = this.mergeTextAndLinks(textContent.body ?? "", linkLines); const textMsgType = textContent.msgtype ?? MsgType.Text; const contents: MatrixRoomMessageContent[] = []; @@ -1199,19 +1204,21 @@ export class MatrixAdapter implements Adapter { }); } - for (const upload of uploads) { - const uploadResponse = await this.requireClient().uploadContent(upload.data, { - name: upload.fileName, - type: upload.info?.mimetype, - }); - const mediaContent = { - body: upload.fileName, - msgtype: upload.msgtype, - url: uploadResponse.content_uri, - info: upload.info, - } as unknown as MatrixRoomMessageContent; - contents.push(mediaContent); - } + const uploadedContents = await Promise.all( + uploads.map(async (upload) => { + const uploadResponse = await this.requireClient().uploadContent(upload.data, { + name: upload.fileName, + type: upload.info?.mimetype, + }); + return { + body: upload.fileName, + msgtype: upload.msgtype, + url: uploadResponse.content_uri, + info: upload.info, + } as unknown as MatrixRoomMessageContent; + }) + ); + contents.push(...uploadedContents); if (contents.length === 0) { contents.push({ @@ -1324,14 +1331,6 @@ export class MatrixAdapter implements Adapter { return room; } - private decodeChannelID(channelId: string): string { - const parts = channelId.split(":"); - if (parts.length !== 2 || parts[0] !== MATRIX_PREFIX) { - throw new Error(`Invalid Matrix channel ID: ${channelId}`); - } - return decodeURIComponent(parts[1]); - } - private async onTimelineEvent( event: MatrixEvent, room: Room | undefined, @@ -1347,15 +1346,7 @@ export class MatrixAdapter implements Adapter { return; } this.processedTimelineEventIDs.add(eventID); - if (this.processedTimelineEventIDs.size > 10_000) { - const toDelete = this.processedTimelineEventIDs.size - 5_000; - let deleted = 0; - for (const id of this.processedTimelineEventIDs) { - if (deleted >= toDelete) break; - this.processedTimelineEventIDs.delete(id); - deleted++; - } - } + evictOldestEntries(this.processedTimelineEventIDs); } const roomID = room?.roomId ?? event.getRoomId(); @@ -1540,15 +1531,7 @@ export class MatrixAdapter implements Adapter { userID: sender, }); - if (this.reactionByEventID.size > 10_000) { - const toDelete = this.reactionByEventID.size - 5_000; - let deleted = 0; - for (const id of this.reactionByEventID.keys()) { - if (deleted >= toDelete) break; - this.reactionByEventID.delete(id); - deleted++; - } - } + evictOldestEntries(this.reactionByEventID); } this.requireChat().processReaction({ @@ -1731,8 +1714,7 @@ export class MatrixAdapter implements Adapter { return `${text}\n\n${suffix}`; } - private collectLinkOnlyAttachmentLines(message: AdapterPostableMessage): string[] { - const attachments = this.extractAttachmentsFromMessage(message); + private collectLinkOnlyAttachmentLines(attachments: Attachment[]): string[] { const lines: string[] = []; for (const attachment of attachments) { const hasLocalData = @@ -1750,7 +1732,8 @@ export class MatrixAdapter implements Adapter { } private async collectUploads( - message: AdapterPostableMessage + message: AdapterPostableMessage, + attachments: Attachment[] ): Promise { const uploads: OutboundUpload[] = []; const files = this.extractFilesFromMessage(message); @@ -1766,7 +1749,6 @@ export class MatrixAdapter implements Adapter { }); } - const attachments = this.extractAttachmentsFromMessage(message); for (const attachment of attachments) { const data = await this.readAttachmentData(attachment); if (!data) { @@ -1809,9 +1791,7 @@ export class MatrixAdapter implements Adapter { if (!("attachments" in message) || !Array.isArray(message.attachments)) { return []; } - return message.attachments.filter((attachment): attachment is Attachment => - isRecord(attachment) - ); + return message.attachments.filter((a): a is Attachment => isRecord(a)); } private async readAttachmentData( @@ -1828,10 +1808,7 @@ export class MatrixAdapter implements Adapter { return data; } if (this.isNodeBuffer(data)) { - const start = data.byteOffset; - const end = data.byteOffset + data.byteLength; - const arrayBuffer = data.buffer.slice(start, end) as ArrayBuffer; - return new Blob([arrayBuffer]); + return new Blob([new Uint8Array(data)]); } return new Blob([data]); } @@ -1973,18 +1950,17 @@ export class MatrixAdapter implements Adapter { return null; } - private async persistSession(auth: ResolvedAuth): Promise { + private async persistSession(auth: ResolvedAuth, existingCreatedAt?: string): Promise { if (!this.sessionConfig.enabled || !this.stateAdapter) { return; } const now = new Date().toISOString(); - const existing = await this.loadPersistedSession(); const session: StoredSession = { accessToken: auth.accessToken, authType: this.auth.type, baseURL: this.baseURL, - createdAt: existing?.createdAt ?? now, + createdAt: existingCreatedAt ?? now, deviceID: auth.deviceID, e2eeEnabled: Boolean(this.e2eeConfig?.enabled), recoveryKeyPresent: Boolean(this.e2eeConfig?.storagePassword), @@ -2389,16 +2365,31 @@ function hasIndexedDB(): boolean { return typeof globalThis.indexedDB !== "undefined" && globalThis.indexedDB !== null; } -function parseSDKLogLevel( - value: string | undefined -): MatrixAdapterConfig["matrixSDKLogLevel"] | undefined { +function isSDKLogLevel(value: string): value is SDKLogLevel { + return value in MATRIX_SDK_LOG_LEVELS; +} + +function parseSDKLogLevel(value: string | undefined): SDKLogLevel | undefined { if (!value) { return undefined; } const normalized = value.trim().toLowerCase(); - return normalized in MATRIX_SDK_LOG_LEVELS - ? (normalized as MatrixAdapterConfig["matrixSDKLogLevel"]) - : undefined; + return isSDKLogLevel(normalized) ? normalized : undefined; +} + +function evictOldestEntries( + collection: { size: number; keys(): Iterable; delete(key: string): unknown }, + maxSize = 10_000, + targetSize = 5_000 +): void { + if (collection.size <= maxSize) return; + const toDelete = collection.size - targetSize; + let deleted = 0; + for (const key of collection.keys()) { + if (deleted >= toDelete) break; + collection.delete(key); + deleted++; + } } function isRecord(value: unknown): value is Record { From 5b14e4fcc402880d0ece19a5cc3f9082a2aeb3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 1 Mar 2026 21:51:26 +0100 Subject: [PATCH 12/25] Handle M_NOT_FOUND and optimize adapter ops Add tests for fetchMessage to return null on M_NOT_FOUND and to propagate transient server errors. Import MatrixError and update fetchRoomEvent handling to return null for M_NOT_FOUND/404 while rethrowing other errors. Filter server /sync chunk to message events before mapping to avoid processing non-message events. Run session/device persistence calls in parallel via Promise.all to avoid unnecessary sequential awaits. Replace usage of messageTypeForFile with messageTypeForMimeType and remove the now-unused helper; minor cleanup of direction usage in thread list cursor encoding. --- src/index.test.ts | 25 ++++++++++++++++++++++++ src/index.ts | 50 +++++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 6f027d7..83c4f1c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { getEmoji } from "chat"; import type { ChatInstance, StateAdapter } from "chat"; import { EventType, RelationType, type MatrixClient } from "matrix-js-sdk"; +import { MatrixError } from "matrix-js-sdk/lib/http-api/errors"; import { encodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import { createMatrixAdapter, MatrixAdapter } from "./index"; @@ -1204,6 +1205,30 @@ describe("MatrixAdapter", () => { expect(mismatch).toBeNull(); }); + it("fetchMessage returns null when server returns M_NOT_FOUND", async () => { + const fakeClient = makeClient(); + const adapter = await makeInitializedAdapter(fakeClient); + fakeClient.fetchRoomEvent.mockRejectedValueOnce( + new MatrixError({ errcode: "M_NOT_FOUND", error: "Event not found" }, 404) + ); + const result = await adapter.fetchMessage?.( + "matrix:!room%3Abeeper.com:%24root", + "$missing" + ); + expect(result).toBeNull(); + }); + + it("fetchMessage propagates transient server errors", async () => { + const fakeClient = makeClient(); + const adapter = await makeInitializedAdapter(fakeClient); + fakeClient.fetchRoomEvent.mockRejectedValueOnce( + new MatrixError({ errcode: "M_UNKNOWN", error: "Internal server error" }, 500) + ); + await expect( + adapter.fetchMessage?.("matrix:!room%3Abeeper.com:%24root", "$event") + ).rejects.toThrow("Internal server error"); + }); + it("openDM reuses cached mapping, then m.direct mapping, then creates and persists", async () => { const fakeClient = makeClient(); const cachedState = makeStateAdapter({ diff --git a/src/index.ts b/src/index.ts index 494ceb1..0c087eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,6 +43,7 @@ import sdk, { ThreadFilterType, THREAD_RELATION_TYPE, } from "matrix-js-sdk"; +import { MatrixError } from "matrix-js-sdk/lib/http-api/errors"; import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import type { RoomMessageEventContent, @@ -99,7 +100,6 @@ type MatrixRoomMessageContent = RoomMessageEventContent & { }; }; - type StoredReaction = { emoji: EmojiValue; messageID: string; @@ -638,7 +638,6 @@ export class MatrixAdapter implements Adapter { options: ListThreadsOptions = {} ): Promise> { const roomID = this.decodeThreadId(channelId).roomID; - const direction: CursorDirection = "backward"; const limit = options.limit ?? 50; const cursor = options.cursor ? this.decodeCursorV1(options.cursor, "thread_list", roomID) @@ -678,7 +677,7 @@ export class MatrixAdapter implements Adapter { nextCursor: listResponse.end ? this.encodeCursorV1({ kind: "thread_list", - dir: direction, + dir: "backward", token: listResponse.end, roomID, }) @@ -795,7 +794,12 @@ export class MatrixAdapter implements Adapter { args.limit, this.toSDKDirection(args.direction) ); - const events = await this.mapRawEvents(response.chunk ?? [], args.roomID); + const messageChunk = (response.chunk ?? []).filter( + (raw) => + raw.type === EventType.RoomMessage || + raw.type === EventType.RoomMessageEncrypted + ); + const events = await this.mapRawEvents(messageChunk, args.roomID); const filtered = events.filter((event) => { if (event.getType() !== EventType.RoomMessage) { return false; @@ -914,12 +918,14 @@ export class MatrixAdapter implements Adapter { await this.tryDecryptEvent(mapped); return mapped; } catch (error) { - this.logger.debug("Failed to fetch room event", { - roomID, - eventID, - error: String(error), - }); - return null; + const isNotFound = + error instanceof MatrixError && + (error.errcode === "M_NOT_FOUND" || error.httpStatus === 404); + if (isNotFound) { + this.logger.debug("Room event not found", { roomID, eventID }); + return null; + } + throw error; } } @@ -1047,8 +1053,10 @@ export class MatrixAdapter implements Adapter { userID, deviceID: whoami.deviceID ?? this.deviceID, }; - await this.persistDeviceIDForResolvedUser(userID); - await this.persistSession(resolved); + await Promise.all([ + this.persistDeviceIDForResolvedUser(userID), + this.persistSession(resolved), + ]); return resolved; } @@ -1061,8 +1069,10 @@ export class MatrixAdapter implements Adapter { userID: whoami.userID, deviceID: this.deviceID ?? whoami.deviceID ?? restored.deviceID, }; - await this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID); - await this.persistSession(resolved, restored.createdAt); + await Promise.all([ + this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID), + this.persistSession(resolved, restored.createdAt), + ]); this.logger.info("Reused persisted Matrix session", { userId: resolved.userID, }); @@ -1103,8 +1113,10 @@ export class MatrixAdapter implements Adapter { userID, deviceID: loginResponse.device_id ?? this.deviceID ?? undefined, }; - await this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID); - await this.persistSession(resolved); + await Promise.all([ + this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID), + this.persistSession(resolved), + ]); return resolved; } @@ -1745,7 +1757,7 @@ export class MatrixAdapter implements Adapter { mimetype: normalizeOptionalString(file.mimeType), size: this.binarySize(file.data), }, - msgtype: this.messageTypeForFile(file.mimeType), + msgtype: this.messageTypeForMimeType(normalizeOptionalString(file.mimeType)), }); } @@ -1827,10 +1839,6 @@ export class MatrixAdapter implements Adapter { return typeof Buffer !== "undefined" && Buffer.isBuffer(value); } - private messageTypeForFile(mimeType?: string): MatrixMediaMsgType { - return this.messageTypeForMimeType(normalizeOptionalString(mimeType)); - } - private messageTypeForAttachment(attachment: Attachment): MatrixMediaMsgType { switch (attachment.type) { case "image": From 5f5fccaae0a9719e499786edc7b202e73cf82ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 2 Mar 2026 01:46:06 +0100 Subject: [PATCH 13/25] Add e2e tests, bump deps and update changelog Add an end-to-end test suite and supporting files (e2e/.env.example, e2e/e2e.test.ts, e2e/helpers.ts, vitest.config.e2e.ts), update test and source code (src/index.ts, src/index.test.ts), and adjust test runner config (vitest.config.ts, package.json). Update CHANGELOG to 0.2.0 documenting new APIs (openDM, fetchMessage, fetchChannelMessages), server-backed pagination, SDK bump and fixes (clean shutdown listeners, guard decodeRecoveryKey). Also add a lockfile and dependency updates. --- CHANGELOG.md | 26 +- bun.lock | 661 +++++++++++++++++++++++++++++++++++++++++++ e2e/.env.example | 15 + e2e/e2e.test.ts | 300 ++++++++++++++++++++ e2e/helpers.ts | 154 ++++++++++ package.json | 9 +- src/index.test.ts | 1 + src/index.ts | 10 +- vitest.config.e2e.ts | 8 + vitest.config.ts | 1 + 10 files changed, 1161 insertions(+), 24 deletions(-) create mode 100644 bun.lock create mode 100644 e2e/.env.example create mode 100644 e2e/e2e.test.ts create mode 100644 e2e/helpers.ts create mode 100644 vitest.config.e2e.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d65de5b..3d53aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,19 @@ # Changelog -## 1.0.0 - -### Breaking Changes - -- Pagination cursors now use opaque `mxv1:` values across `fetchMessages`, `fetchChannelMessages`, and `listThreads`. -- Legacy cursors are now rejected with `Invalid cursor format. Expected mxv1 cursor.`. Stored cursors from older versions must be cleared on upgrade. +## 0.2.0 ### New -- Added `openDM(userId)` with persisted mapping and `m.direct` reuse/create behavior. -- Added `fetchMessage(threadId, messageId)` for context-aware single-message fetch. -- Added `fetchChannelMessages(channelId, options)` for top-level channel timeline history. +- `openDM(userId)` for DM reuse/create +- `fetchMessage(threadId, messageId)` for single-message fetch +- `fetchChannelMessages(channelId, options)` for top-level channel history ### Changes -- Reworked `fetchMessages(threadId, options)` to API-first server pagination via `matrix-js-sdk`: - - Room timeline pages now use `/messages` through the SDK. - - Thread pages now use `/relations` through the SDK. - - Thread pages include the root message on the first page. -- Reworked `listThreads(channelId, options)` to use server-backed thread listing via the SDK `/threads` path. - -### Fixes - -- Message and thread history retrieval no longer depends on local `room.timeline` availability for correctness. +- `fetchMessages` and `listThreads` use server-backed pagination +- Bump `chat` SDK to 4.15.0 +- Clean up event listeners on shutdown +- Guard `decodeRecoveryKey` against malformed keys ## 0.1.0 diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..e6aacee --- /dev/null +++ b/bun.lock @@ -0,0 +1,661 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "@beeper/chat-adapter-matrix", + "dependencies": { + "@chat-adapter/state-memory": "^4.15.0", + "@chat-adapter/state-redis": "^4.15.0", + "chat": "^4.15.0", + "matrix-js-sdk": "^41.0.0", + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@vitest/coverage-v8": "^2.1.8", + "tsup": "^8.3.5", + "typescript": "^5.7.2", + "vitest": "^2.1.8", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], + + "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.15.0", "", { "dependencies": { "chat": "4.15.0" } }, "sha512-I/p6dA8fl8peZH9utKOsO8raFttUSyIQwyhB/uP6cR9d07hZVcG/SF30t4RjKDgegnq8+euqPFj3lCssIaYhoA=="], + + "@chat-adapter/state-redis": ["@chat-adapter/state-redis@4.15.0", "", { "dependencies": { "chat": "4.15.0", "redis": "^4.7.0" } }, "sha512-156CGZCkMXzsnMC20nIpNkAGScbasOHxR9YcPMuLm2IwSC1L/Kyea7o9TEv2ldvk63q/e+AaU+98T7T5/2+mvg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@matrix-org/matrix-sdk-crypto-wasm": ["@matrix-org/matrix-sdk-crypto-wasm@17.1.0", "", {}, "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="], + + "@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="], + + "@redis/graph": ["@redis/graph@1.1.1", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="], + + "@redis/json": ["@redis/json@1.0.7", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="], + + "@redis/search": ["@redis/search@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="], + + "@redis/time-series": ["@redis/time-series@1.1.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/events": ["@types/events@3.0.3", "", {}, "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@vitest/coverage-v8": ["@vitest/coverage-v8@2.1.9", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.12", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, "peerDependencies": { "@vitest/browser": "2.1.9", "vitest": "2.1.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ=="], + + "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], + + "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="], + + "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="], + + "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="], + + "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="], + + "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="], + + "@workflow/serde": ["@workflow/serde@4.1.0-beta.2", "", {}, "sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww=="], + + "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "another-json": ["another-json@0.2.0", "", {}, "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], + + "brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], + + "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "chat": ["chat@4.15.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.5" } }, "sha512-fE1m3UEiKQoiYkhXWT0rpH1+ZhKqJGZYkS+1gNCZWNkLv0QWTZhzdzBM5qYWe5A/Y0wJ8yLVjaqvGO4zZFxZqg=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": "bin/esbuild" }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "matrix-events-sdk": ["matrix-events-sdk@0.0.1", "", {}, "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="], + + "matrix-js-sdk": ["matrix-js-sdk@41.0.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/matrix-sdk-crypto-wasm": "^17.1.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", "p-retry": "7", "sdp-transform": "^3.0.0", "unhomoglyph": "^1.0.6", "uuid": "13" } }, "sha512-58j4WuOwXvT6bmTgc48b3bITVG/rWv5+N//yA5mvr6Rc04EUSpwTPhgMOTjUVnJsrhLGzdkgal4Xtt2R+xNdwg=="], + + "matrix-widget-api": ["matrix-widget-api@1.17.0", "", { "dependencies": { "@types/events": "^3.0.0", "events": "^3.2.0" } }, "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "oidc-client-ts": ["oidc-client-ts@3.4.1", "", { "dependencies": { "jwt-decode": "^4.0.0" } }, "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw=="], + + "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], + + "sdp-transform": ["sdp-transform@3.0.0", "", { "bin": { "sdp-verify": "checker.js" } }, "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ=="], + + "semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "test-exclude": ["test-exclude@7.0.2", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^10.2.2" } }, "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], + + "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": "cli.js" }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unhomoglyph": ["unhomoglyph@1.0.6", "", {}, "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "uuid": ["uuid@13.0.0", "", { "bin": "dist-node/bin/uuid" }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": "bin/vite.js" }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": "vite-node.mjs" }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="], + + "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@vitest/runner/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "@vitest/snapshot/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "glob/minimatch": ["minimatch@9.0.6", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ=="], + + "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": "bin/esbuild" }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "vite-node/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/e2e/.env.example b/e2e/.env.example new file mode 100644 index 0000000..0235301 --- /dev/null +++ b/e2e/.env.example @@ -0,0 +1,15 @@ +# Both required +E2E_BASE_URL=https://matrix.example.com + +# Bot account (used by the adapter) +E2E_BOT_ACCESS_TOKEN=... +E2E_BOT_USER_ID=@bot:example.com +# E2E_BOT_RECOVERY_KEY=EsTc... + +# Sender account (second adapter instance) +E2E_SENDER_ACCESS_TOKEN=... +E2E_SENDER_USER_ID=@sender:example.com +# E2E_SENDER_RECOVERY_KEY=EsTc... + +# Optional: use pre-existing room instead of creating one +# E2E_ROOM_ID=!abc:example.com diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts new file mode 100644 index 0000000..1bbfa6e --- /dev/null +++ b/e2e/e2e.test.ts @@ -0,0 +1,300 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { Message } from "chat"; +import type { MatrixEvent } from "matrix-js-sdk"; +import { + createParticipant, + env, + type E2EParticipant, + getOrCreateRoom, + nonce, + sleep, + waitForEvent, +} from "./helpers"; + +const hasCredentials = Boolean( + process.env.E2E_BASE_URL && + process.env.E2E_BOT_ACCESS_TOKEN && + process.env.E2E_BOT_USER_ID && + process.env.E2E_SENDER_ACCESS_TOKEN && + process.env.E2E_SENDER_USER_ID +); + +describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { + let bot: E2EParticipant; + let sender: E2EParticipant; + let roomID: string; + + beforeAll(async () => { + [bot, sender] = await Promise.all([ + createParticipant({ + name: "e2e-bot", + accessToken: env.botAccessToken, + userID: env.botUserID, + recoveryKey: env.botRecoveryKey, + }), + createParticipant({ + name: "e2e-sender", + accessToken: env.senderAccessToken, + userID: env.senderUserID, + recoveryKey: env.senderRecoveryKey, + }), + ]); + + roomID = await getOrCreateRoom(bot.matrixClient, env.senderUserID); + + // Let sync settle so both clients are aware of the room + await sleep(2_000); + }); + + afterAll(async () => { + bot.onMessage(null); + bot.onReaction(null); + sender.onMessage(null); + sender.onReaction(null); + await Promise.all([bot.adapter.shutdown(), sender.adapter.shutdown()]); + }); + + it("bot receives text message from sender", async () => { + const tag = `e2e-text-${nonce()}`; + const threadId = sender.adapter.encodeThreadId({ roomID }); + + const botReceived = waitForEvent<{ threadID: string; message: Message }>( + (cb) => { + bot.onMessage((threadID, message) => { + if (message.text.includes(tag)) cb({ threadID, message }); + }); + return () => bot.onMessage(null); + } + ); + + await sender.adapter.postMessage(threadId, { text: `hello ${tag}` }); + + const { threadID, message } = await botReceived; + + expect(message.text).toContain(tag); + expect(message.author.id).toBe(env.senderUserID); + + const decoded = bot.adapter.decodeThreadId(threadID); + expect(decoded.roomID).toBe(roomID); + }); + + it("bot posts a message visible to sender", async () => { + const tag = `e2e-post-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + + const senderReceived = waitForEvent>( + (cb) => { + sender.onMessage((_threadID, message) => { + if (message.text.includes(tag)) cb(message); + }); + return () => sender.onMessage(null); + } + ); + + await bot.adapter.postMessage(threadId, { text: `bot says ${tag}` }); + + const message = await senderReceived; + expect(message.text).toContain(tag); + expect(message.author.id).toBe(env.botUserID); + }); + + it("thread round-trip: sender creates thread, bot replies in it", async () => { + const rootTag = `e2e-thread-root-${nonce()}`; + const replyTag = `e2e-thread-reply-${nonce()}`; + const threadId = sender.adapter.encodeThreadId({ roomID }); + + // Sender sends root message + const rootPosted = await sender.adapter.postMessage(threadId, { + text: `Thread root ${rootTag}`, + }); + const rootEventId = rootPosted.id; + + // Wait for bot to receive the root + const botReceivedRoot = waitForEvent<{ threadID: string }>( + (cb) => { + bot.onMessage((threadID, message) => { + if (message.text.includes(rootTag)) cb({ threadID }); + }); + return () => bot.onMessage(null); + } + ); + await botReceivedRoot; + + // Sender sends a threaded reply + const threadReplyTag = `e2e-thread-child-${nonce()}`; + const senderThreadId = sender.adapter.encodeThreadId({ + roomID, + rootEventID: rootEventId, + }); + await sender.adapter.postMessage(senderThreadId, { + text: `Thread reply ${threadReplyTag}`, + }); + + // Wait for bot to receive the threaded message + const botReceivedThread = waitForEvent<{ threadID: string }>( + (cb) => { + bot.onMessage((threadID, message) => { + if (message.text.includes(threadReplyTag)) cb({ threadID }); + }); + return () => bot.onMessage(null); + } + ); + + const { threadID: childThreadID } = await botReceivedThread; + const decoded = bot.adapter.decodeThreadId(childThreadID); + expect(decoded.roomID).toBe(roomID); + expect(decoded.rootEventID).toBe(rootEventId); + + // Bot replies in the same thread + const senderSeesReply = waitForEvent>( + (cb) => { + sender.onMessage((_threadID, message) => { + if (message.text.includes(replyTag)) cb(message); + }); + return () => sender.onMessage(null); + } + ); + + await bot.adapter.postMessage(childThreadID, { + text: `Bot thread reply ${replyTag}`, + }); + + const replyMessage = await senderSeesReply; + expect(replyMessage.text).toContain(replyTag); + }); + + it("reaction round-trip", async () => { + const tag = `e2e-react-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + + // Bot sends a message for both sides to react to + const posted = await bot.adapter.postMessage(threadId, { + text: `React target ${tag}`, + }); + const messageId = posted.id; + + await sleep(500); + + // Bot adds reaction — sender should see it + const senderSeesReaction = waitForEvent<{ rawEmoji: string; added: boolean }>( + (cb) => { + sender.onReaction((data) => { + if (data.messageId === messageId && data.added) { + cb({ rawEmoji: data.rawEmoji, added: data.added }); + } + }); + return () => sender.onReaction(null); + } + ); + + await bot.adapter.addReaction(threadId, messageId, "👍"); + + const senderReaction = await senderSeesReaction; + expect(senderReaction.rawEmoji).toBe("👍"); + expect(senderReaction.added).toBe(true); + + // Sender adds reaction — bot should receive via onReaction + const botSeesReaction = waitForEvent<{ rawEmoji: string; added: boolean }>( + (cb) => { + bot.onReaction((data) => { + if (data.messageId === messageId && data.added) { + cb({ rawEmoji: data.rawEmoji, added: data.added }); + } + }); + return () => bot.onReaction(null); + } + ); + + const senderThreadId = sender.adapter.encodeThreadId({ roomID }); + await sender.adapter.addReaction(senderThreadId, messageId, "🎉"); + + const botReaction = await botSeesReaction; + expect(botReaction.rawEmoji).toBe("🎉"); + expect(botReaction.added).toBe(true); + }); + + it("edit round-trip: bot sends and edits, sender sees edited content", async () => { + const tag = `e2e-edit-${nonce()}`; + const editedTag = `e2e-edited-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + + // Bot sends original + const posted = await bot.adapter.postMessage(threadId, { + text: `Original ${tag}`, + }); + const messageId = posted.id; + + // Wait for sender to see original + const senderSeesOriginal = waitForEvent( + (cb) => { + sender.onMessage((_threadID, message) => { + if (message.text.includes(tag)) cb(); + }); + return () => sender.onMessage(null); + } + ); + await senderSeesOriginal; + + // Bot edits the message + await bot.adapter.editMessage(threadId, messageId, { + text: `Edited ${editedTag}`, + }); + + // Verify edit via fetchMessages — the edited content should appear + await sleep(2_000); + + const fetched = await sender.adapter.fetchMessages( + sender.adapter.encodeThreadId({ roomID }), + { direction: "backward", limit: 10 } + ); + + const editedMsg = fetched.messages.find((m) => m.id === messageId); + expect(editedMsg).toBeDefined(); + // The original event text or the edited text should reflect the edit + // (depends on homeserver aggregation; at minimum the edit event was sent) + }); + + it("fetchMessages pagination", async () => { + const tag = `e2e-page-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + const count = 5; + + // Sender sends N messages + const senderThreadId = sender.adapter.encodeThreadId({ roomID }); + for (let i = 0; i < count; i++) { + await sender.adapter.postMessage(senderThreadId, { + text: `${tag} msg-${i}`, + }); + await sleep(200); + } + + // Let sync propagate + await sleep(2_000); + + // Fetch with a small page size + const page1 = await bot.adapter.fetchMessages(threadId, { + direction: "backward", + limit: 3, + }); + + expect(page1.messages.length).toBeGreaterThanOrEqual(1); + expect(page1.messages.length).toBeLessThanOrEqual(3); + + // If there's a next cursor, fetch more + if (page1.nextCursor) { + const page2 = await bot.adapter.fetchMessages(threadId, { + direction: "backward", + limit: 3, + cursor: page1.nextCursor, + }); + + expect(page2.messages.length).toBeGreaterThanOrEqual(1); + + // No overlap between pages + const page1Ids = new Set(page1.messages.map((m) => m.id)); + for (const m of page2.messages) { + expect(page1Ids.has(m.id)).toBe(false); + } + } + }); +}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts new file mode 100644 index 0000000..d77de6d --- /dev/null +++ b/e2e/helpers.ts @@ -0,0 +1,154 @@ +import { randomBytes } from "node:crypto"; +import { Chat, type Message } from "chat"; +import { createMemoryState } from "@chat-adapter/state-memory"; +import type { MatrixClient, MatrixEvent } from "matrix-js-sdk"; +import { MatrixAdapter } from "../src/index"; + +export const env = { + get baseURL(): string { + return process.env.E2E_BASE_URL!; + }, + get botAccessToken(): string { + return process.env.E2E_BOT_ACCESS_TOKEN!; + }, + get botUserID(): string { + return process.env.E2E_BOT_USER_ID!; + }, + get botRecoveryKey(): string | undefined { + return process.env.E2E_BOT_RECOVERY_KEY || undefined; + }, + get senderAccessToken(): string { + return process.env.E2E_SENDER_ACCESS_TOKEN!; + }, + get senderUserID(): string { + return process.env.E2E_SENDER_USER_ID!; + }, + get senderRecoveryKey(): string | undefined { + return process.env.E2E_SENDER_RECOVERY_KEY || undefined; + }, + get roomID(): string | undefined { + return process.env.E2E_ROOM_ID || undefined; + }, +}; + +export function generateDeviceID(): string { + return `E2E_${randomBytes(8).toString("hex").toUpperCase()}`; +} + +export interface E2EParticipant { + adapter: MatrixAdapter; + chat: Chat; + matrixClient: MatrixClient; + onMessage: (cb: ((threadID: string, message: Message) => void) | null) => void; + onReaction: (cb: ((data: { + threadId: string; + messageId: string; + emoji: unknown; + rawEmoji: string; + added: boolean; + user: unknown; + }) => void) | null) => void; +} + +export async function createParticipant(opts: { + name: string; + accessToken: string; + userID: string; + recoveryKey?: string; +}): Promise { + const adapter = new MatrixAdapter({ + baseURL: env.baseURL, + auth: { + type: "accessToken", + accessToken: opts.accessToken, + userID: opts.userID, + }, + deviceID: generateDeviceID(), + inviteAutoJoin: { enabled: true }, + e2ee: { enabled: true }, + recoveryKey: opts.recoveryKey, + }); + + let messageCallback: ((threadID: string, message: Message) => void) | null = null; + let reactionCallback: ((data: { + threadId: string; + messageId: string; + emoji: unknown; + rawEmoji: string; + added: boolean; + user: unknown; + }) => void) | null = null; + + const chat = new Chat({ + userName: opts.name, + state: createMemoryState(), + adapters: { matrix: adapter }, + }); + + chat.onNewMessage(async (_thread, message, { threadId }) => { + messageCallback?.(threadId, message); + }); + + chat.onSubscribedMessage(async (_thread, message, { threadId }) => { + messageCallback?.(threadId, message); + }); + + chat.onReaction(async (event) => { + reactionCallback?.(event as any); + }); + + await chat.initialize(); + + const matrixClient = (adapter as any).client as MatrixClient; + + return { + adapter, + chat, + matrixClient, + onMessage: (cb) => { messageCallback = cb; }, + onReaction: (cb) => { reactionCallback = cb; }, + }; +} + +export async function getOrCreateRoom( + botClient: MatrixClient, + senderUserID: string, +): Promise { + if (env.roomID) { + return env.roomID; + } + + const { room_id } = await botClient.createRoom({ + invite: [senderUserID], + }); + + // sender auto-joins via inviteAutoJoin; give sync time to propagate + await sleep(2_000); + + return room_id; +} + +export function waitForEvent( + subscribe: (callback: (value: T) => void) => (() => void) | void, + timeoutMs = 10_000 +): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + cleanup?.(); + reject(new Error(`waitForEvent timed out after ${timeoutMs}ms`)); + }, timeoutMs); + + const cleanup = subscribe((value) => { + clearTimeout(timer); + resolve(value); + }); + }); +} + +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function nonce(): string { + return Math.random().toString(36).slice(2, 10); +} diff --git a/package.json b/package.json index c057340..695e0be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/chat-adapter-matrix", - "version": "1.0.0", + "version": "0.2.0", "description": "Matrix adapter for chat", "type": "module", "main": "./dist/index.js", @@ -23,14 +23,15 @@ "token:bun": "bun run scripts/get-access-token.ts", "clean:repo": "rm -rf dist coverage", "test": "vitest run --coverage", + "test:e2e": "vitest run --config vitest.config.e2e.ts", "test:watch": "vitest", "typecheck": "tsc --noEmit", "clean": "rm -rf dist" }, "dependencies": { - "@chat-adapter/state-memory": "^4.13.4", - "@chat-adapter/state-redis": "^4.13.4", - "chat": "^4.13.4", + "@chat-adapter/state-memory": "^4.15.0", + "@chat-adapter/state-redis": "^4.15.0", + "chat": "^4.15.0", "matrix-js-sdk": "^41.0.0" }, "devDependencies": { diff --git a/src/index.test.ts b/src/index.test.ts index 83c4f1c..5d9cc97 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -153,6 +153,7 @@ function makeClient() { }, startClient: vi.fn(async () => undefined), stopClient: vi.fn(() => undefined), + removeAllListeners: vi.fn(() => undefined), sendMessage: vi.fn(async () => ({ event_id: "$sent" })), sendEvent: vi.fn(async () => ({ event_id: "$reaction" })), uploadContent: vi.fn(async () => ({ content_uri: "mxc://beeper.com/uploaded" })), diff --git a/src/index.ts b/src/index.ts index 0c087eb..40beb56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -313,6 +313,7 @@ export class MatrixAdapter implements Adapter { this.shuttingDown = true; try { + this.client.removeAllListeners(); this.client.stopClient(); this.reactionByEventID.clear(); this.myReactionByKey.clear(); @@ -2053,8 +2054,13 @@ export class MatrixAdapter implements Adapter { return null; } - const privateKey = decodeRecoveryKey(this.recoveryKey); - return [keyID, privateKey]; + try { + const privateKey = decodeRecoveryKey(this.recoveryKey); + return [keyID, privateKey]; + } catch { + this.logger.warn("Invalid recovery key format, unable to decode"); + return null; + } } private validateConfig(config: MatrixAdapterConfig): void { diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 0000000..51009fa --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["e2e/**/*.test.ts"], + testTimeout: 30_000, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts index 5b01228..d5a313d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ test: { globals: true, environment: "node", + include: ["src/**/*.test.ts"], coverage: { provider: "v8", reporter: ["text", "json-summary"], From 87c8a88add11367df7f90090396edcb040a02a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Fri, 6 Mar 2026 04:10:25 +0100 Subject: [PATCH 14/25] Improve Matrix E2E coverage and logging --- e2e/.env.example | 14 +- e2e/e2e.test.ts | 596 +++++++++++++++++----- e2e/helpers.ts | 324 ++++++++++-- eslint.config.mjs | 28 + package-lock.json | 1151 +++++++++++++++++++++++++++++++++++++++--- package.json | 12 +- src/index.test.ts | 655 ++++++++++++++++++++---- src/index.ts | 427 +++++++++++++--- vitest.config.e2e.ts | 3 +- 9 files changed, 2786 insertions(+), 424 deletions(-) create mode 100644 eslint.config.mjs diff --git a/e2e/.env.example b/e2e/.env.example index 0235301..e740b90 100644 --- a/e2e/.env.example +++ b/e2e/.env.example @@ -1,15 +1,13 @@ # Both required E2E_BASE_URL=https://matrix.example.com -# Bot account (used by the adapter) -E2E_BOT_ACCESS_TOKEN=... -E2E_BOT_USER_ID=@bot:example.com -# E2E_BOT_RECOVERY_KEY=EsTc... +# Bot account bootstrap for "new device" login +E2E_BOT_LOGIN_TOKEN=... +E2E_BOT_RECOVERY_KEY=EsTc... -# Sender account (second adapter instance) -E2E_SENDER_ACCESS_TOKEN=... -E2E_SENDER_USER_ID=@sender:example.com -# E2E_SENDER_RECOVERY_KEY=EsTc... +# Sender account bootstrap for "new device" login +E2E_SENDER_LOGIN_TOKEN=... +E2E_SENDER_RECOVERY_KEY=EsTc... # Optional: use pre-existing room instead of creating one # E2E_ROOM_ID=!abc:example.com diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index 1bbfa6e..de3d621 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -1,22 +1,29 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { Message } from "chat"; -import type { MatrixEvent } from "matrix-js-sdk"; +import { EventType, RoomEvent, RoomMemberEvent, type MatrixEvent } from "matrix-js-sdk"; import { createParticipant, + createParticipantFromSession, env, type E2EParticipant, getOrCreateRoom, nonce, + shutdownParticipant, sleep, waitForEvent, + waitForEncryptedRoom, + waitForFetchedMessage, + waitForJoinedMemberCount, + waitForMatchingMessage, + waitForRoom, } from "./helpers"; const hasCredentials = Boolean( process.env.E2E_BASE_URL && - process.env.E2E_BOT_ACCESS_TOKEN && - process.env.E2E_BOT_USER_ID && - process.env.E2E_SENDER_ACCESS_TOKEN && - process.env.E2E_SENDER_USER_ID + process.env.E2E_BOT_LOGIN_TOKEN && + process.env.E2E_BOT_RECOVERY_KEY && + process.env.E2E_SENDER_LOGIN_TOKEN && + process.env.E2E_SENDER_RECOVERY_KEY ); describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { @@ -28,74 +35,88 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { [bot, sender] = await Promise.all([ createParticipant({ name: "e2e-bot", - accessToken: env.botAccessToken, - userID: env.botUserID, + loginToken: env.botLoginToken, recoveryKey: env.botRecoveryKey, }), createParticipant({ name: "e2e-sender", - accessToken: env.senderAccessToken, - userID: env.senderUserID, + loginToken: env.senderLoginToken, recoveryKey: env.senderRecoveryKey, }), ]); - roomID = await getOrCreateRoom(bot.matrixClient, env.senderUserID); + roomID = await getOrCreateRoom(bot.matrixClient, sender.userID); + await Promise.all([ + waitForEncryptedRoom(bot.matrixClient, roomID, 30_000), + waitForEncryptedRoom(sender.matrixClient, roomID, 30_000), + waitForJoinedMemberCount(bot.matrixClient, roomID, 2, 30_000), + waitForJoinedMemberCount(sender.matrixClient, roomID, 2, 30_000), + ]); - // Let sync settle so both clients are aware of the room - await sleep(2_000); + const sharedThreadId = bot.adapter.encodeThreadId({ roomID }); + const botWarmupTag = `e2e-warmup-bot-${nonce()}`; + const botWarmup = await bot.adapter.postMessage(sharedThreadId, botWarmupTag); + await waitForFetchedMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + botWarmup.id, + (message) => message.text.includes(botWarmupTag), + 45_000 + ); + + const senderWarmupTag = `e2e-warmup-sender-${nonce()}`; + const senderWarmup = await sender.adapter.postMessage( + sender.adapter.encodeThreadId({ roomID }), + senderWarmupTag + ); + await waitForFetchedMessage( + bot.adapter, + sharedThreadId, + senderWarmup.id, + (message) => message.text.includes(senderWarmupTag), + 45_000 + ); + + await sleep(1_000); }); afterAll(async () => { - bot.onMessage(null); - bot.onReaction(null); - sender.onMessage(null); - sender.onReaction(null); - await Promise.all([bot.adapter.shutdown(), sender.adapter.shutdown()]); + const shutdowns = [bot ? shutdownParticipant(bot) : undefined, sender ? shutdownParticipant(sender) : undefined].filter( + (value): value is Promise => Boolean(value) + ); + await Promise.all(shutdowns); }); it("bot receives text message from sender", async () => { const tag = `e2e-text-${nonce()}`; const threadId = sender.adapter.encodeThreadId({ roomID }); - - const botReceived = waitForEvent<{ threadID: string; message: Message }>( - (cb) => { - bot.onMessage((threadID, message) => { - if (message.text.includes(tag)) cb({ threadID, message }); - }); - return () => bot.onMessage(null); - } + const posted = await sender.adapter.postMessage(threadId, `hello ${tag}`); + const message = await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + posted.id, + (candidate) => candidate.text.includes(tag) ); - await sender.adapter.postMessage(threadId, { text: `hello ${tag}` }); - - const { threadID, message } = await botReceived; - expect(message.text).toContain(tag); - expect(message.author.id).toBe(env.senderUserID); - - const decoded = bot.adapter.decodeThreadId(threadID); - expect(decoded.roomID).toBe(roomID); + expect(message.author.userId).toBe(sender.userID); + expect(message.raw.isEncrypted()).toBe(true); + expect(message.raw.getWireType()).toBe(EventType.RoomMessageEncrypted); + expect(message.raw.getRoomId()).toBe(roomID); }); it("bot posts a message visible to sender", async () => { const tag = `e2e-post-${nonce()}`; const threadId = bot.adapter.encodeThreadId({ roomID }); - - const senderReceived = waitForEvent>( - (cb) => { - sender.onMessage((_threadID, message) => { - if (message.text.includes(tag)) cb(message); - }); - return () => sender.onMessage(null); - } + const posted = await bot.adapter.postMessage(threadId, `bot says ${tag}`); + const message = await waitForFetchedMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + posted.id, + (candidate) => candidate.text.includes(tag) ); - - await bot.adapter.postMessage(threadId, { text: `bot says ${tag}` }); - - const message = await senderReceived; expect(message.text).toContain(tag); - expect(message.author.id).toBe(env.botUserID); + expect(message.author.userId).toBe(bot.userID); }); it("thread round-trip: sender creates thread, bot replies in it", async () => { @@ -104,21 +125,17 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { const threadId = sender.adapter.encodeThreadId({ roomID }); // Sender sends root message - const rootPosted = await sender.adapter.postMessage(threadId, { - text: `Thread root ${rootTag}`, - }); + const rootPosted = await sender.adapter.postMessage( + threadId, + `Thread root ${rootTag}` + ); const rootEventId = rootPosted.id; - - // Wait for bot to receive the root - const botReceivedRoot = waitForEvent<{ threadID: string }>( - (cb) => { - bot.onMessage((threadID, message) => { - if (message.text.includes(rootTag)) cb({ threadID }); - }); - return () => bot.onMessage(null); - } + await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + rootEventId, + (message) => message.text.includes(rootTag) ); - await botReceivedRoot; // Sender sends a threaded reply const threadReplyTag = `e2e-thread-child-${nonce()}`; @@ -126,40 +143,35 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { roomID, rootEventID: rootEventId, }); - await sender.adapter.postMessage(senderThreadId, { - text: `Thread reply ${threadReplyTag}`, - }); - - // Wait for bot to receive the threaded message - const botReceivedThread = waitForEvent<{ threadID: string }>( - (cb) => { - bot.onMessage((threadID, message) => { - if (message.text.includes(threadReplyTag)) cb({ threadID }); - }); - return () => bot.onMessage(null); - } + await sender.adapter.postMessage( + senderThreadId, + `Thread reply ${threadReplyTag}` ); - const { threadID: childThreadID } = await botReceivedThread; + const childThreadID = bot.adapter.encodeThreadId({ + roomID, + rootEventID: rootEventId, + }); + await waitForMatchingMessage( + bot.adapter, + childThreadID, + (message) => message.text.includes(threadReplyTag) + ); const decoded = bot.adapter.decodeThreadId(childThreadID); expect(decoded.roomID).toBe(roomID); expect(decoded.rootEventID).toBe(rootEventId); // Bot replies in the same thread - const senderSeesReply = waitForEvent>( - (cb) => { - sender.onMessage((_threadID, message) => { - if (message.text.includes(replyTag)) cb(message); - }); - return () => sender.onMessage(null); - } + const replyPosted = await bot.adapter.postMessage( + childThreadID, + `Bot thread reply ${replyTag}` + ); + const replyMessage = await waitForFetchedMessage( + sender.adapter, + senderThreadId, + replyPosted.id, + (message) => message.text.includes(replyTag) ); - - await bot.adapter.postMessage(childThreadID, { - text: `Bot thread reply ${replyTag}`, - }); - - const replyMessage = await senderSeesReply; expect(replyMessage.text).toContain(replyTag); }); @@ -168,9 +180,7 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { const threadId = bot.adapter.encodeThreadId({ roomID }); // Bot sends a message for both sides to react to - const posted = await bot.adapter.postMessage(threadId, { - text: `React target ${tag}`, - }); + const posted = await bot.adapter.postMessage(threadId, `React target ${tag}`); const messageId = posted.id; await sleep(500); @@ -213,45 +223,83 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect(botReaction.added).toBe(true); }); + it("reaction removal round-trip", async () => { + const tag = `e2e-unreact-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + const posted = await bot.adapter.postMessage( + threadId, + `Reaction remove target ${tag}` + ); + const messageId = posted.id; + + const senderSawAdd = waitForEvent<{ rawEmoji: string; added: boolean }>((cb) => { + sender.onReaction((data) => { + if (data.messageId === messageId && data.rawEmoji === "🔥" && data.added) { + cb({ rawEmoji: data.rawEmoji, added: data.added }); + } + }); + return () => sender.onReaction(null); + }); + + await bot.adapter.addReaction(threadId, messageId, "🔥"); + await senderSawAdd; + + const senderSawRemoval = waitForEvent<{ rawEmoji: string; added: boolean }>((cb) => { + sender.onReaction((data) => { + if (data.messageId === messageId && data.rawEmoji === "🔥" && !data.added) { + cb({ rawEmoji: data.rawEmoji, added: data.added }); + } + }); + return () => sender.onReaction(null); + }); + + await bot.adapter.removeReaction(threadId, messageId, "🔥"); + + const removal = await senderSawRemoval; + expect(removal.rawEmoji).toBe("🔥"); + expect(removal.added).toBe(false); + }); + it("edit round-trip: bot sends and edits, sender sees edited content", async () => { const tag = `e2e-edit-${nonce()}`; const editedTag = `e2e-edited-${nonce()}`; const threadId = bot.adapter.encodeThreadId({ roomID }); - // Bot sends original - const posted = await bot.adapter.postMessage(threadId, { - text: `Original ${tag}`, - }); + const posted = await bot.adapter.postMessage(threadId, `Original ${tag}`); const messageId = posted.id; - - // Wait for sender to see original - const senderSeesOriginal = waitForEvent( - (cb) => { - sender.onMessage((_threadID, message) => { - if (message.text.includes(tag)) cb(); - }); - return () => sender.onMessage(null); - } + await waitForFetchedMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + messageId, + (message) => message.text.includes(tag) ); - await senderSeesOriginal; // Bot edits the message - await bot.adapter.editMessage(threadId, messageId, { - text: `Edited ${editedTag}`, - }); + await bot.adapter.editMessage(threadId, messageId, `Edited ${editedTag}`); + const editedMessage = await waitForMatchingMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + (message) => + message.id === messageId && + message.text.includes(editedTag) && + !message.text.includes(tag), + 45_000 + ); - // Verify edit via fetchMessages — the edited content should appear - await sleep(2_000); + expect(editedMessage.text).toContain(editedTag); + expect(editedMessage.text).not.toContain(tag); - const fetched = await sender.adapter.fetchMessages( + const fetched = await waitForFetchedMessage( + sender.adapter, sender.adapter.encodeThreadId({ roomID }), - { direction: "backward", limit: 10 } + messageId, + (message) => + message.text.includes(editedTag) && + !message.text.includes(tag), + 45_000 ); - - const editedMsg = fetched.messages.find((m) => m.id === messageId); - expect(editedMsg).toBeDefined(); - // The original event text or the edited text should reflect the edit - // (depends on homeserver aggregation; at minimum the edit event was sent) + expect(fetched.text).toContain(editedTag); + expect(fetched.text).not.toContain(tag); }); it("fetchMessages pagination", async () => { @@ -262,9 +310,7 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { // Sender sends N messages const senderThreadId = sender.adapter.encodeThreadId({ roomID }); for (let i = 0; i < count; i++) { - await sender.adapter.postMessage(senderThreadId, { - text: `${tag} msg-${i}`, - }); + await sender.adapter.postMessage(senderThreadId, `${tag} msg-${i}`); await sleep(200); } @@ -297,4 +343,320 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { } } }); + + it("restarts on the same session and catches up paginated room history", async () => { + const offlineCount = 12; + const restartTag = `e2e-restart-${nonce()}`; + const roomThreadId = sender.adapter.encodeThreadId({ roomID }); + const botSession = bot.session; + const botState = bot.state; + + const baseline = await sender.adapter.postMessage( + roomThreadId, + `Restart baseline ${restartTag}` + ); + await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + baseline.id, + (message) => message.text.includes(restartTag) + ); + + await shutdownParticipant(bot); + + const offlinePosts = []; + for (let index = 0; index < offlineCount; index += 1) { + offlinePosts.push( + await sender.adapter.postMessage( + roomThreadId, + `Restart offline ${restartTag}-${index.toString().padStart(2, "0")}` + ) + ); + } + + bot = await createParticipantFromSession({ + name: "e2e-bot-restarted", + recoveryKey: env.botRecoveryKey, + session: botSession, + state: botState, + }); + + await Promise.all([ + waitForEncryptedRoom(bot.matrixClient, roomID, 45_000), + waitForJoinedMemberCount(bot.matrixClient, roomID, 2, 45_000), + ]); + + const latestOffline = offlinePosts[offlinePosts.length - 1]; + const caughtUpMessage = await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + latestOffline.id, + (message) => message.text.includes(restartTag), + 60_000 + ); + expect(caughtUpMessage.text).toContain(restartTag); + + const liveTag = `Restart live ${restartTag}`; + const botSawLiveMessage = waitForEvent>((cb) => { + bot.onMessage((incomingThreadId, message) => { + if ( + incomingThreadId === bot.adapter.encodeThreadId({ roomID }) && + message.text.includes(liveTag) + ) { + cb(message); + } + }); + return () => bot.onMessage(null); + }, 45_000); + + const livePosted = await sender.adapter.postMessage(roomThreadId, liveTag); + const liveMessage = await botSawLiveMessage; + expect(liveMessage.id).toBe(livePosted.id); + expect(liveMessage.text).toContain(liveTag); + + const fetchedIds = new Set(); + let cursor: string | undefined; + + for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { + const page = await bot.adapter.fetchMessages(bot.adapter.encodeThreadId({ roomID }), { + direction: "backward", + limit: 5, + cursor, + }); + for (const message of page.messages) { + fetchedIds.add(message.id); + } + cursor = page.nextCursor; + } + + expect(cursor).toBeTruthy(); + expect(fetchedIds.has(livePosted.id)).toBe(true); + expect(offlinePosts.every((post) => fetchedIds.has(post.id))).toBe(true); + }); + + it("fetches a single message and sees deletion via redaction", async () => { + const tag = `e2e-delete-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + const posted = await bot.adapter.postMessage(threadId, `Delete target ${tag}`); + + const initial = await sender.adapter.fetchMessage(threadId, posted.id); + expect(initial).toBeTruthy(); + const initialMessage = await waitForFetchedMessage( + sender.adapter, + threadId, + posted.id, + (message) => message.text.includes(tag) + ); + expect(initialMessage.text).toContain(tag); + + const senderRoom = await waitForRoom(sender.matrixClient, roomID); + const sawRedaction = waitForEvent((cb) => { + const handler = (event: MatrixEvent, room: unknown) => { + if (room !== senderRoom) { + return; + } + if (event.getAssociatedId() === posted.id) { + cb(event); + } + }; + + senderRoom.on(RoomEvent.Redaction, handler); + return () => senderRoom.off(RoomEvent.Redaction, handler); + }); + + await bot.adapter.deleteMessage(threadId, posted.id); + await sawRedaction; + + const deleted = await sender.adapter.fetchMessage(threadId, posted.id); + expect(deleted).toBeNull(); + }); + + it("creates a DM, reuses it, and posts through postChannelMessage", async () => { + const dmThreadId = await bot.adapter.openDM(sender.userID); + const dmThreadIdAgain = await bot.adapter.openDM(sender.userID); + expect(dmThreadIdAgain).toBe(dmThreadId); + + const decoded = bot.adapter.decodeThreadId(dmThreadId); + await waitForRoom(bot.matrixClient, decoded.roomID); + await waitForRoom(sender.matrixClient, decoded.roomID); + await waitForJoinedMemberCount(bot.matrixClient, decoded.roomID, 2, 30_000); + await waitForJoinedMemberCount(sender.matrixClient, decoded.roomID, 2, 30_000); + + const dmChannelId = bot.adapter.channelIdFromThreadId(dmThreadId); + const channelInfo = await bot.adapter.fetchChannelInfo(dmChannelId); + expect(channelInfo.id).toBe(dmChannelId); + expect(channelInfo.isDM).toBe(true); + expect(channelInfo.metadata?.roomID).toBe(decoded.roomID); + expect((channelInfo.memberCount ?? 0) >= 2).toBe(true); + + const tag = `e2e-dm-${nonce()}`; + const posted = await bot.adapter.postChannelMessage(dmChannelId, `DM hello ${tag}`); + const message = await waitForFetchedMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID: decoded.roomID }), + posted.id, + (candidate) => candidate.text.includes(tag) + ); + expect(message.author.userId).toBe(bot.userID); + expect(message.text).toContain(tag); + }); + + it("fetches channel info, channel messages, thread info, and thread lists", async () => { + const rootTag = `e2e-thread-list-root-${nonce()}`; + const replyTag = `e2e-thread-list-reply-${nonce()}`; + const channelId = bot.adapter.channelIdFromThreadId(bot.adapter.encodeThreadId({ roomID })); + + const roomInfo = await bot.adapter.fetchChannelInfo(channelId); + expect(roomInfo.id).toBe(channelId); + expect(roomInfo.isDM).toBe(false); + expect((roomInfo.memberCount ?? 0) >= 2).toBe(true); + expect(roomInfo.metadata?.roomID).toBe(roomID); + + const rootPosted = await sender.adapter.postMessage( + sender.adapter.encodeThreadId({ roomID }), + `Thread root ${rootTag}` + ); + + const threadId = sender.adapter.encodeThreadId({ + roomID, + rootEventID: rootPosted.id, + }); + await sender.adapter.postMessage(threadId, `Thread reply ${replyTag}`); + + await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + rootPosted.id, + (message) => message.text.includes(rootTag) + ); + await waitForMatchingMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID, rootEventID: rootPosted.id }), + (message) => message.text.includes(replyTag) + ); + + const channelMessages = await bot.adapter.fetchChannelMessages(channelId, { + direction: "backward", + limit: 20, + }); + expect(channelMessages.messages.some((message) => message.id === rootPosted.id)).toBe(true); + expect( + channelMessages.messages.some((message) => message.text.includes(replyTag)) + ).toBe(false); + + const threadInfo = await bot.adapter.fetchThread(threadId); + expect(threadInfo.id).toBe(threadId); + expect(threadInfo.channelId).toBe(channelId); + expect(threadInfo.isDM).toBe(false); + expect(threadInfo.metadata?.roomID).toBe(roomID); + + const threads = await bot.adapter.listThreads(channelId, { limit: 20 }); + const summary = threads.threads.find((thread) => thread.id === threadId); + expect(summary).toBeTruthy(); + expect(summary?.rootMessage.id).toBe(rootPosted.id); + expect(summary?.rootMessage.text).toContain(rootTag); + expect((summary?.replyCount ?? 0) >= 1).toBe(true); + }); + + it("uploads a file attachment and fetches it back", async () => { + const tag = `e2e-file-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + const expectedContents = `attachment payload ${tag}`; + const posted = await bot.adapter.postMessage(threadId, { + files: [ + { + filename: `${tag}.txt`, + mimeType: "text/plain", + data: Buffer.from(expectedContents, "utf8"), + }, + ], + }); + + const attachmentMessage = await waitForMatchingMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + (message) => + message.id === posted.id && + message.author.userId === bot.userID && + message.attachments.some((attachment) => attachment.name === `${tag}.txt`), + 45_000 + ); + expect(attachmentMessage.attachments).toHaveLength(1); + expect(attachmentMessage.attachments[0]?.url.startsWith("mxc://")).toBe(true); + expect(attachmentMessage.raw.isEncrypted()).toBe(true); + + const liveAttachment = attachmentMessage.attachments[0]; + expect(typeof liveAttachment?.fetchData).toBe("function"); + const liveAttachmentData = await liveAttachment?.fetchData?.(); + expect(liveAttachmentData?.toString("utf8")).toBe(expectedContents); + + const fetched = await sender.adapter.fetchMessage(threadId, attachmentMessage.id); + expect(fetched).toBeTruthy(); + expect(fetched?.attachments).toHaveLength(1); + expect(fetched?.attachments[0]?.url.startsWith("mxc://")).toBe(true); + expect(fetched?.raw.isEncrypted()).toBe(true); + + const fetchedAttachment = fetched?.attachments[0]; + expect(typeof fetchedAttachment?.fetchData).toBe("function"); + const fetchedAttachmentData = await fetchedAttachment?.fetchData?.(); + expect(fetchedAttachmentData?.toString("utf8")).toBe(expectedContents); + }); + + it("restores historical encrypted messages on a fresh device using recovery key", async () => { + await shutdownParticipant(sender); + + const tag = `e2e-recovery-${nonce()}`; + const threadId = bot.adapter.encodeThreadId({ roomID }); + const posted = await bot.adapter.postMessage(threadId, `Historical ${tag}`); + + const restoredSender = await createParticipant({ + name: "e2e-sender-restored", + loginToken: env.senderLoginToken, + recoveryKey: env.senderRecoveryKey, + }); + sender = restoredSender; + + await Promise.all([ + waitForEncryptedRoom(sender.matrixClient, roomID, 45_000), + waitForJoinedMemberCount(sender.matrixClient, roomID, 2, 45_000), + ]); + + const restoredMessage = await waitForFetchedMessage( + sender.adapter, + sender.adapter.encodeThreadId({ roomID }), + posted.id, + (message) => message.text.includes(tag), + 60_000 + ); + + expect(restoredMessage.text).toContain(tag); + expect(restoredMessage.author.userId).toBe(bot.userID); + expect(restoredMessage.raw.isEncrypted()).toBe(true); + }); + + it("emits typing notifications", async () => { + const threadId = bot.adapter.encodeThreadId({ roomID }); + const senderRoom = await waitForRoom(sender.matrixClient, roomID); + const botMember = senderRoom.getMember(bot.userID); + expect(botMember).toBeTruthy(); + + const senderSawTyping = waitForEvent((cb) => { + const member = senderRoom.getMember(bot.userID); + if (!member) { + throw new Error(`Missing room member ${bot.userID}`); + } + + const handler = (_event: MatrixEvent, updatedMember: typeof member) => { + if (updatedMember.userId === bot.userID && updatedMember.typing) { + cb(); + } + }; + + member.on(RoomMemberEvent.Typing, handler); + return () => member.off(RoomMemberEvent.Typing, handler); + }, 20_000); + + await bot.adapter.startTyping(threadId); + await senderSawTyping; + }); }); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index d77de6d..e9170a0 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -1,27 +1,22 @@ import { randomBytes } from "node:crypto"; -import { Chat, type Message } from "chat"; +import { Chat, type Message, type ReactionEvent, type StateAdapter } from "chat"; import { createMemoryState } from "@chat-adapter/state-memory"; -import type { MatrixClient, MatrixEvent } from "matrix-js-sdk"; +import { EventType } from "matrix-js-sdk"; +import type { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; import { MatrixAdapter } from "../src/index"; export const env = { get baseURL(): string { return process.env.E2E_BASE_URL!; }, - get botAccessToken(): string { - return process.env.E2E_BOT_ACCESS_TOKEN!; - }, - get botUserID(): string { - return process.env.E2E_BOT_USER_ID!; + get botLoginToken(): string { + return process.env.E2E_BOT_LOGIN_TOKEN!; }, get botRecoveryKey(): string | undefined { return process.env.E2E_BOT_RECOVERY_KEY || undefined; }, - get senderAccessToken(): string { - return process.env.E2E_SENDER_ACCESS_TOKEN!; - }, - get senderUserID(): string { - return process.env.E2E_SENDER_USER_ID!; + get senderLoginToken(): string { + return process.env.E2E_SENDER_LOGIN_TOKEN!; }, get senderRecoveryKey(): string | undefined { return process.env.E2E_SENDER_RECOVERY_KEY || undefined; @@ -31,85 +26,152 @@ export const env = { }, }; -export function generateDeviceID(): string { - return `E2E_${randomBytes(8).toString("hex").toUpperCase()}`; -} - export interface E2EParticipant { adapter: MatrixAdapter; chat: Chat; matrixClient: MatrixClient; + session: MatrixLoginResponse; + state: StateAdapter; + userID: string; onMessage: (cb: ((threadID: string, message: Message) => void) | null) => void; - onReaction: (cb: ((data: { - threadId: string; - messageId: string; - emoji: unknown; - rawEmoji: string; - added: boolean; - user: unknown; - }) => void) | null) => void; + onReaction: (cb: ((data: ReactionEvent) => void) | null) => void; } export async function createParticipant(opts: { + loginToken: string; name: string; - accessToken: string; - userID: string; recoveryKey?: string; }): Promise { + const login = await loginToMatrix(opts.loginToken, opts.name); + return createParticipantFromSession({ + name: opts.name, + recoveryKey: opts.recoveryKey, + session: login, + }); +} + +export async function createParticipantFromSession(opts: { + name: string; + recoveryKey?: string; + session: MatrixLoginResponse; + state?: StateAdapter; +}): Promise { + const state = opts.state ?? createMemoryState(); const adapter = new MatrixAdapter({ baseURL: env.baseURL, auth: { type: "accessToken", - accessToken: opts.accessToken, - userID: opts.userID, + accessToken: opts.session.accessToken, + userID: opts.session.userID, }, - deviceID: generateDeviceID(), + deviceID: opts.session.deviceID, inviteAutoJoin: { enabled: true }, - e2ee: { enabled: true }, + e2ee: { + enabled: true, + useIndexedDB: false, + }, recoveryKey: opts.recoveryKey, }); let messageCallback: ((threadID: string, message: Message) => void) | null = null; - let reactionCallback: ((data: { - threadId: string; - messageId: string; - emoji: unknown; - rawEmoji: string; - added: boolean; - user: unknown; - }) => void) | null = null; + let reactionCallback: ((data: ReactionEvent) => void) | null = null; const chat = new Chat({ userName: opts.name, - state: createMemoryState(), + state, adapters: { matrix: adapter }, }); - chat.onNewMessage(async (_thread, message, { threadId }) => { - messageCallback?.(threadId, message); + chat.onNewMessage(/[\s\S]*/u, async (thread, message, context) => { + messageCallback?.(context?.threadId ?? thread.id, message); }); - chat.onSubscribedMessage(async (_thread, message, { threadId }) => { - messageCallback?.(threadId, message); + chat.onSubscribedMessage(async (thread, message, context) => { + messageCallback?.(context?.threadId ?? thread.id, message); }); - chat.onReaction(async (event) => { - reactionCallback?.(event as any); + chat.onReaction(async (event: ReactionEvent) => { + reactionCallback?.(event); }); await chat.initialize(); - const matrixClient = (adapter as any).client as MatrixClient; + const matrixClient = getInitializedClient(adapter); return { adapter, chat, matrixClient, + session: opts.session, + state, + userID: opts.session.userID, onMessage: (cb) => { messageCallback = cb; }, onReaction: (cb) => { reactionCallback = cb; }, }; } +export async function shutdownParticipant(participant: E2EParticipant): Promise { + participant.onMessage(null); + participant.onReaction(null); + await participant.adapter.shutdown(); +} + +type MatrixLoginResponse = { + accessToken: string; + deviceID: string; + userID: string; +}; + +function generateDeviceID(): string { + return `E2E_${randomBytes(8).toString("hex").toUpperCase()}`; +} + +async function loginToMatrix( + loginToken: string, + participantName: string +): Promise { + const requestedDeviceID = generateDeviceID(); + const response = await fetch(`${env.baseURL}/_matrix/client/v3/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "org.matrix.login.jwt", + token: loginToken, + device_id: requestedDeviceID, + initial_device_display_name: `matrix-chat-adapter-${participantName}`, + }), + }); + + if (!response.ok) { + throw new Error( + `Matrix login failed with ${response.status}: ${await response.text()}` + ); + } + + const payload = (await response.json()) as { + access_token?: unknown; + device_id?: unknown; + user_id?: unknown; + }; + const accessToken = readStringProperty(payload.access_token, "access_token"); + const userID = readStringProperty(payload.user_id, "user_id"); + const deviceID = + typeof payload.device_id === "string" && payload.device_id.length > 0 + ? payload.device_id + : requestedDeviceID; + + return { accessToken, deviceID, userID }; +} + +function readStringProperty(value: unknown, key: string): string { + if (typeof value === "string" && value.length > 0) { + return value; + } + throw new Error(`Matrix login response is missing ${key}`); +} + export async function getOrCreateRoom( botClient: MatrixClient, senderUserID: string, @@ -119,7 +181,15 @@ export async function getOrCreateRoom( } const { room_id } = await botClient.createRoom({ + preset: "private_chat", invite: [senderUserID], + initial_state: [ + { + type: EventType.RoomEncryption, + state_key: "", + content: { algorithm: "m.megolm.v1.aes-sha2" }, + }, + ], }); // sender auto-joins via inviteAutoJoin; give sync time to propagate @@ -133,18 +203,164 @@ export function waitForEvent( timeoutMs = 10_000 ): Promise { return new Promise((resolve, reject) => { + let cleanup: (() => void) | void; + let settled = false; + let shouldCleanupAfterSubscribe = false; + + const settle = (finish: () => void) => { + if (settled) { + return; + } + + settled = true; + clearTimeout(timer); + if (cleanup) { + cleanup(); + } else { + shouldCleanupAfterSubscribe = true; + } + finish(); + }; + const timer = setTimeout(() => { - cleanup?.(); - reject(new Error(`waitForEvent timed out after ${timeoutMs}ms`)); + settle(() => reject(new Error(`waitForEvent timed out after ${timeoutMs}ms`))); }, timeoutMs); - const cleanup = subscribe((value) => { - clearTimeout(timer); - resolve(value); - }); + try { + cleanup = subscribe((value) => { + settle(() => resolve(value)); + }); + if (shouldCleanupAfterSubscribe) { + cleanup?.(); + } + } catch (error) { + settle(() => reject(error)); + } }); } +export async function waitForCondition( + condition: () => boolean, + timeoutMs = 10_000, + intervalMs = 250 +): Promise { + const startedAt = Date.now(); + + while (true) { + if (condition()) { + return; + } + + if (Date.now() - startedAt >= timeoutMs) { + throw new Error(`waitForCondition timed out after ${timeoutMs}ms`); + } + + await sleep(intervalMs); + } +} + +export async function waitForRoom( + client: MatrixClient, + roomID: string, + timeoutMs = 10_000 +): Promise { + await waitForCondition(() => Boolean(client.getRoom(roomID)), timeoutMs); + const room = client.getRoom(roomID); + if (!room) { + throw new Error(`Room ${roomID} was not found after waiting`); + } + return room; +} + +export async function waitForEncryptedRoom( + client: MatrixClient, + roomID: string, + timeoutMs = 20_000 +): Promise { + const room = await waitForRoom(client, roomID, timeoutMs); + await waitForCondition(() => client.isRoomEncrypted(roomID), timeoutMs); + return room; +} + +export async function waitForJoinedMemberCount( + client: MatrixClient, + roomID: string, + expectedCount: number, + timeoutMs = 20_000 +): Promise { + const room = await waitForRoom(client, roomID, timeoutMs); + await waitForCondition( + () => (client.getRoom(roomID)?.getJoinedMembers().length ?? 0) >= expectedCount, + timeoutMs + ); + return room; +} + +export async function waitForFetchedMessage( + adapter: MatrixAdapter, + threadId: string, + messageId: string, + predicate: (message: Message) => boolean = () => true, + timeoutMs = 30_000, + intervalMs = 1_000 +): Promise> { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const message = await adapter.fetchMessage(threadId, messageId); + if (message && isDecryptedMessage(message) && predicate(message)) { + return message; + } + await sleep(intervalMs); + } + + throw new Error(`waitForFetchedMessage timed out after ${timeoutMs}ms`); +} + +export async function waitForMatchingMessage( + adapter: MatrixAdapter, + threadId: string, + predicate: (message: Message) => boolean, + timeoutMs = 30_000, + intervalMs = 1_000, + limit = 20 +): Promise> { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const page = await adapter.fetchMessages(threadId, { + direction: "backward", + limit, + }); + const match = page.messages.find( + (message) => isDecryptedMessage(message) && predicate(message) + ); + if (match) { + return match; + } + await sleep(intervalMs); + } + + throw new Error(`waitForMatchingMessage timed out after ${timeoutMs}ms`); +} + +function getInitializedClient(adapter: MatrixAdapter): MatrixClient { + const candidate: unknown = Reflect.get(adapter, "client"); + if (!isMatrixClient(candidate)) { + throw new Error("Matrix client was not initialized"); + } + return candidate; +} + +function isMatrixClient(value: unknown): value is MatrixClient { + return ( + typeof value === "object" && + value !== null && + typeof Reflect.get(value, "startClient") === "function" && + typeof Reflect.get(value, "stopClient") === "function" + ); +} + export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -152,3 +368,7 @@ export function sleep(ms: number): Promise { export function nonce(): string { return Math.random().toString(36).slice(2, 10); } + +function isDecryptedMessage(message: Message): boolean { + return !message.text.startsWith("** Unable to decrypt:"); +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..0d075d8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,28 @@ +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: ["coverage/**", "dist/**", "node_modules/**"], + }, + { + files: ["**/*.ts"], + languageOptions: { parser: tseslint.parser }, + plugins: { "@typescript-eslint": tseslint.plugin }, + rules: { + "@typescript-eslint/no-explicit-any": "error", + "no-restricted-syntax": [ + "error", + { + selector: "TSAsExpression > TSAnyKeyword", + message: + "Do not assert to `any`. Prefer inference, `unknown`, or a narrower type.", + }, + { + selector: "TSTypeAssertion > TSAnyKeyword", + message: + "Do not assert to `any`. Prefer inference, `unknown`, or a narrower type.", + }, + ], + }, + } +); diff --git a/package-lock.json b/package-lock.json index 21e103f..586eefe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,27 @@ { "name": "@beeper/chat-adapter-matrix", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@beeper/chat-adapter-matrix", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { - "@chat-adapter/state-memory": "^4.13.4", - "@chat-adapter/state-redis": "^4.13.4", - "chat": "^4.13.4", + "@chat-adapter/state-memory": "^4.16.1", + "@chat-adapter/state-redis": "^4.16.1", + "chat": "^4.16.1", "matrix-js-sdk": "^41.0.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^22.10.2", "@vitest/coverage-v8": "^2.1.8", + "eslint": "^10.0.2", "tsup": "^8.3.5", "typescript": "^5.7.2", + "typescript-eslint": "^8.56.1", "vitest": "^2.1.8" } }, @@ -103,22 +106,22 @@ "license": "MIT" }, "node_modules/@chat-adapter/state-memory": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@chat-adapter/state-memory/-/state-memory-4.14.0.tgz", - "integrity": "sha512-6YWMok5/1tS81lXspXhObmXVN+Zaopdd4ZMMh3r2WVeCCLCvbRYz9X9tqA+QuI2XCWxUcAPuWavFATgn+PdkLw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@chat-adapter/state-memory/-/state-memory-4.16.1.tgz", + "integrity": "sha512-G/7MNxqqI19IRUUoaitSGWs0Tx+FAcvnB3rLfpWmV4eaUDzzhF+DEtmmR+DVxpz35RxBM3Y4Gi6Wqz5Ysfiytg==", "license": "MIT", "dependencies": { - "chat": "4.14.0" + "chat": "4.16.1" } }, "node_modules/@chat-adapter/state-redis": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@chat-adapter/state-redis/-/state-redis-4.14.0.tgz", - "integrity": "sha512-JammJeUWsh14Lk6egFI05aVx9FHvcAmvOwpNw6HRh56vff54kfCeBt+S07FnTkd2FSNUy7Jvo4bnVGHjF+cUWg==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@chat-adapter/state-redis/-/state-redis-4.16.1.tgz", + "integrity": "sha512-weXm3OweMb7/hejHdzyaSvBLjS31jbjrsQ+KtE+ikm9IixZvCATPw87+rm+mvTXwPUvUi6m72K0uDFK0Os3rRQ==", "license": "MIT", "dependencies": { - "chat": "4.14.0", - "redis": "^4.7.0" + "chat": "4.16.1", + "redis": "^5.11.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -563,6 +566,186 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.2", + "debug": "^4.3.1", + "minimatch": "^10.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -651,62 +834,71 @@ } }, "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.11.0.tgz", + "integrity": "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/client": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", + "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", "license": "MIT", "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" + "cluster-key-slot": "1.1.2" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", - "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", - "license": "MIT", + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@node-rs/xxhash": "^1.1.0" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + } } }, "node_modules/@redis/json": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", - "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.11.0.tgz", + "integrity": "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/search": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", - "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.11.0.tgz", + "integrity": "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.11.0.tgz", + "integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==", "license": "MIT", + "engines": { + "node": ">= 18" + }, "peerDependencies": { - "@redis/client": "^1.0.0" + "@redis/client": "^5.11.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -1068,6 +1260,13 @@ "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1081,6 +1280,13 @@ "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -1112,6 +1318,236 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/coverage-v8": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", @@ -1291,6 +1727,33 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/another-json": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", @@ -1452,9 +1915,9 @@ } }, "node_modules/chat": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/chat/-/chat-4.14.0.tgz", - "integrity": "sha512-gVod4FBptr4ewPEdY2zo1GnegI8/O0aC5F+IvFMIjimqmQjW0txhQbgI/DgmD48pwXJhxUGpvNrqzMrChx8GWw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/chat/-/chat-4.16.1.tgz", + "integrity": "sha512-y5om0n19gOIv54pAmPIIMlYRFxpphgY4+Eh5K0HdmyRsIq7jjEA6DiGSoIP8zy3/HhSYJPekqQz5QQt7neN6QA==", "license": "MIT", "dependencies": { "@workflow/serde": "4.1.0-beta.2", @@ -1462,6 +1925,7 @@ "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", + "remend": "^1.2.1", "unified": "^11.0.5" } }, @@ -1611,6 +2075,13 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1696,16 +2167,159 @@ "@esbuild/win32-x64": "0.27.3" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.1", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, "node_modules/estree-walker": { @@ -1718,6 +2332,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -1743,6 +2367,27 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1761,6 +2406,36 @@ } } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -1773,6 +2448,27 @@ "rollup": "^4.34.8" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1805,15 +2501,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -1836,6 +2523,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", @@ -1869,6 +2569,36 @@ "dev": true, "license": "MIT" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1879,6 +2609,19 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-network-error": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", @@ -1990,6 +2733,27 @@ "node": ">=10" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -1999,6 +2763,30 @@ "node": ">=18" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2029,6 +2817,22 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loglevel": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", @@ -2171,6 +2975,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", @@ -2983,6 +3799,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3005,6 +3828,56 @@ "node": ">=18" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-retry": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", @@ -3027,6 +3900,16 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3185,6 +4068,26 @@ } } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3200,20 +4103,19 @@ } }, "node_modules/redis": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", - "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.11.0.tgz", + "integrity": "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==", "license": "MIT", - "workspaces": [ - "./packages/*" - ], "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.6.1", - "@redis/graph": "1.1.1", - "@redis/json": "1.0.7", - "@redis/search": "1.2.0", - "@redis/time-series": "1.1.0" + "@redis/bloom": "5.11.0", + "@redis/client": "5.11.0", + "@redis/json": "5.11.0", + "@redis/search": "5.11.0", + "@redis/time-series": "5.11.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/remark-gfm": { @@ -3265,6 +4167,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remend": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/remend/-/remend-1.2.2.tgz", + "integrity": "sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w==", + "license": "Apache-2.0" + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -3678,6 +4586,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -3738,6 +4659,19 @@ } } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -3752,6 +4686,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/ufo": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", @@ -3846,6 +4804,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/uuid": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", @@ -4513,6 +5481,16 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4611,11 +5589,18 @@ "node": ">=8" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/zwitch": { "version": "2.0.4", diff --git a/package.json b/package.json index 695e0be..f253340 100644 --- a/package.json +++ b/package.json @@ -22,23 +22,27 @@ "example:bun:redis": "bun --env-file=examples/.env run examples/bot.redis.ts", "token:bun": "bun run scripts/get-access-token.ts", "clean:repo": "rm -rf dist coverage", + "lint:types": "eslint .", "test": "vitest run --coverage", "test:e2e": "vitest run --config vitest.config.e2e.ts", "test:watch": "vitest", - "typecheck": "tsc --noEmit", + "typecheck": "npm run lint:types && tsc --noEmit", "clean": "rm -rf dist" }, "dependencies": { - "@chat-adapter/state-memory": "^4.15.0", - "@chat-adapter/state-redis": "^4.15.0", - "chat": "^4.15.0", + "@chat-adapter/state-memory": "^4.16.1", + "@chat-adapter/state-redis": "^4.16.1", + "chat": "^4.16.1", "matrix-js-sdk": "^41.0.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^22.10.2", "@vitest/coverage-v8": "^2.1.8", + "eslint": "^10.0.2", "tsup": "^8.3.5", "typescript": "^5.7.2", + "typescript-eslint": "^8.56.1", "vitest": "^2.1.8" }, "repository": { diff --git a/src/index.test.ts b/src/index.test.ts index 5d9cc97..14e0c16 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,11 +1,58 @@ import { describe, expect, it, vi } from "vitest"; -import { getEmoji } from "chat"; -import type { ChatInstance, StateAdapter } from "chat"; -import { EventType, RelationType, type MatrixClient } from "matrix-js-sdk"; +import { Chat, getEmoji } from "chat"; +import type { ChatInstance, Logger, StateAdapter } from "chat"; +import { createMemoryState } from "@chat-adapter/state-memory"; +import { EventType, MsgType, RelationType, type MatrixClient } from "matrix-js-sdk"; import { MatrixError } from "matrix-js-sdk/lib/http-api/errors"; import { encodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import { createMatrixAdapter, MatrixAdapter } from "./index"; +type RawEventLike = { + content?: Record; + event_id?: string; + isThreadRoot?: boolean; + origin_server_ts?: number; + room_id?: string; + sender?: string; + threadRootId?: string; + type?: string; + unsigned?: Record; + [key: string]: unknown; +}; + +type MessagesResponseLike = { + chunk: RawEventLike[]; + end?: string; +}; + +type RelationsResponseLike = { + originalEvent: ReturnType | null; + events: Array>; + nextBatch: string | null; + prevBatch: string | null; +}; + +type RoomLike = { + roomId: string; + name: string; + timeline: unknown[]; + getJoinedMembers: () => Array>; + getMyMembership: () => string; + findEventById: (eventID?: string) => ReturnType | null; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function readString(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +function readNumber(value: unknown): number | undefined { + return typeof value === "number" ? value : undefined; +} + function makeEvent(overrides: Record = {}) { const event = { getId: () => "$event", @@ -41,13 +88,13 @@ function makeRawEvent(overrides: Record = {}) { }; } -function mapRawToEvent(raw: Record) { - const content = (raw.content as Record | undefined) ?? {}; - const relatesTo = content["m.relates_to"] as - | Record - | undefined; - const relationType = relatesTo?.rel_type; - const relationEventID = relatesTo?.event_id; +function mapRawToEvent(raw: RawEventLike) { + const content = raw.content ?? {}; + const relatesTo = isRecord(content["m.relates_to"]) + ? content["m.relates_to"] + : undefined; + const relationType = readString(relatesTo?.rel_type); + const relationEventID = readString(relatesTo?.event_id); const mappedThreadRootId = typeof raw.threadRootId === "string" ? raw.threadRootId @@ -55,13 +102,16 @@ function mapRawToEvent(raw: Record) { ? relationEventID : undefined; const isThreadRoot = raw.isThreadRoot === true; + const unsignedRelations = isRecord(raw.unsigned?.["m.relations"]) + ? raw.unsigned["m.relations"] + : undefined; return makeEvent({ - getId: () => (raw.event_id as string | undefined) ?? "$raw", - getRoomId: () => (raw.room_id as string | undefined) ?? "!room:beeper.com", - getTs: () => (raw.origin_server_ts as number | undefined) ?? 1_700_000_000_000, - getSender: () => (raw.sender as string | undefined) ?? "@alice:beeper.com", - getType: () => (raw.type as string | undefined) ?? EventType.RoomMessage, + getId: () => raw.event_id ?? "$raw", + getRoomId: () => raw.room_id ?? "!room:beeper.com", + getTs: () => raw.origin_server_ts ?? 1_700_000_000_000, + getSender: () => raw.sender ?? "@alice:beeper.com", + getType: () => raw.type ?? EventType.RoomMessage, getContent: () => content, getRelation: () => relationType @@ -71,46 +121,12 @@ function mapRawToEvent(raw: Record) { : null, isRelation: (expectedRelType: string) => relationType === expectedRelType, getServerAggregatedRelation: (expectedRelType: string) => - ((raw.unsigned as Record | undefined)?.[ - "m.relations" - ] as Record | undefined)?.[expectedRelType], + unsignedRelations?.[expectedRelType], threadRootId: mappedThreadRootId, isThreadRoot, }); } -type RawEventLike = { - content?: Record; - event_id?: string; - origin_server_ts?: number; - room_id?: string; - sender?: string; - type?: string; - unsigned?: Record; - [key: string]: unknown; -}; - -type MessagesResponseLike = { - chunk: RawEventLike[]; - end?: string; -}; - -type RelationsResponseLike = { - originalEvent: ReturnType | null; - events: Array>; - nextBatch: string | null; - prevBatch: string | null; -}; - -type RoomLike = { - roomId: string; - name: string; - timeline: unknown[]; - getJoinedMembers: () => Array>; - getMyMembership: () => string; - findEventById: (eventID?: string) => ReturnType | null; -}; - type AdapterInternals = { deviceID?: string; e2eeConfig?: { enabled?: boolean }; @@ -137,7 +153,10 @@ type AdapterInternals = { }; function asMatrixClient(client: ReturnType): MatrixClient { - return client as unknown as MatrixClient; + if (!isMatrixClient(client)) { + throw new Error("Fake client does not satisfy the MatrixClient contract used in tests"); + } + return client; } function getInternals(adapter: MatrixAdapter): AdapterInternals { @@ -171,8 +190,10 @@ function makeClient() { getAccountDataFromServer: vi.fn( async (): Promise | null> => null ), + getAccessToken: vi.fn(() => "token"), getEventMapper: vi.fn(() => (raw: Record) => mapRawToEvent(raw)), initRustCrypto: vi.fn(async () => undefined), + mxcUrlToHttp: vi.fn((url: string) => url), relations: vi.fn( async (): Promise => ({ originalEvent: null, @@ -183,13 +204,8 @@ function makeClient() { ), setAccountData: vi.fn(async (): Promise> => ({})), decryptEventIfNeeded: vi.fn(async () => undefined), - getRoom: vi.fn((): RoomLike => ({ - roomId: "!room:beeper.com", - name: "Example Room", - timeline: [], - getJoinedMembers: () => [{}, {}], - getMyMembership: () => "join", - findEventById: (_eventID?: string) => makeEvent({ getId: () => "$sent" }), + getRoom: vi.fn((roomID?: string): RoomLike | null => makeRoom({ + roomId: roomID ?? "!room:beeper.com", })), __handlers: handlers, }; @@ -197,25 +213,68 @@ function makeClient() { return client; } +function isMatrixClient(value: unknown): value is MatrixClient { + if (!isRecord(value)) { + return false; + } + + return [ + "createMessagesRequest", + "createRoom", + "getEventMapper", + "getRoom", + "relations", + "sendEvent", + "startClient", + "stopClient", + ].every((key) => typeof Reflect.get(value, key) === "function"); +} + +function makeRoom(overrides: Partial = {}): RoomLike { + return { + roomId: "!room:beeper.com", + name: "Example Room", + timeline: [], + getJoinedMembers: () => [{}, {}], + getMyMembership: () => "join", + findEventById: () => makeEvent({ getId: () => "$sent" }), + ...overrides, + }; +} + function makeStateAdapter(initial: Record = {}): StateAdapter { - const store = new Map(Object.entries(initial)); + const base = createMemoryState(); + const ready = (async () => { + await base.connect(); + for (const [key, value] of Object.entries(initial)) { + await base.set(key, value); + } + })(); + const afterReady = async (run: () => Promise): Promise => { + await ready; + return run(); + }; + const get: StateAdapter["get"] = (key) => afterReady(() => base.get(key)); + const set: StateAdapter["set"] = (key, value, ttlMs) => + afterReady(() => base.set(key, value, ttlMs)); + return { - acquireLock: vi.fn(async () => null), - connect: vi.fn(async () => undefined), - delete: vi.fn(async (key: string) => { - store.delete(key); - }), - disconnect: vi.fn(async () => undefined), - extendLock: vi.fn(async () => false), - get: vi.fn(async (key: string) => (store.has(key) ? store.get(key) : null)), - isSubscribed: vi.fn(async () => false), - releaseLock: vi.fn(async () => undefined), - set: vi.fn(async (key: string, value: unknown) => { - store.set(key, value); - }), - subscribe: vi.fn(async () => undefined), - unsubscribe: vi.fn(async () => undefined), - } as StateAdapter; + acquireLock: vi.fn((threadId, ttlMs) => + afterReady(() => base.acquireLock(threadId, ttlMs)) + ), + connect: vi.fn(() => afterReady(() => base.connect())), + delete: vi.fn((key) => afterReady(() => base.delete(key))), + disconnect: vi.fn(() => afterReady(() => base.disconnect())), + extendLock: vi.fn((lock, ttlMs) => + afterReady(() => base.extendLock(lock, ttlMs)) + ), + get, + isSubscribed: vi.fn((threadId) => afterReady(() => base.isSubscribed(threadId))), + releaseLock: vi.fn((lock) => afterReady(() => base.releaseLock(lock))), + set: vi.fn(set), + subscribe: vi.fn((threadId) => afterReady(() => base.subscribe(threadId))), + unsubscribe: vi.fn((threadId) => afterReady(() => base.unsubscribe(threadId))), + }; } function markSyncReady(client: ReturnType) { @@ -243,30 +302,46 @@ function requireValue(value: T | null | undefined, label: string): T { return value; } -function makeChatInstance(overrides: Record = {}): ChatInstance { +function makeChatInstance( + overrides: Partial & { state?: StateAdapter } = {} +): ChatInstance { + const { state = makeStateAdapter(), ...chatOverrides } = overrides; + const chat = new Chat({ + userName: "test-bot", + adapters: {}, + state, + }); + + Object.assign(chat, chatOverrides); + return chat; +} + +function makeTestLogger() { + const child = vi.fn<(prefix: string) => Logger>(); + const debug = vi.fn<(message: string, ...args: unknown[]) => void>(); + const info = vi.fn<(message: string, ...args: unknown[]) => void>(); + const warn = vi.fn<(message: string, ...args: unknown[]) => void>(); + const error = vi.fn<(message: string, ...args: unknown[]) => void>(); + + const logger: Logger = { + child(prefix) { + child(prefix); + return logger; + }, + debug, + info, + warn, + error, + }; + return { - getLogger: () => - ({ - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - child: () => ({}), - }), - getState: vi.fn(), - getUserName: vi.fn(), - handleIncomingMessage: vi.fn(), - processAction: vi.fn(), - processAppHomeOpened: vi.fn(), - processAssistantContextChanged: vi.fn(), - processAssistantThreadStarted: vi.fn(), - processMessage: vi.fn(), - processModalClose: vi.fn(), - processModalSubmit: vi.fn(), - processReaction: vi.fn(), - processSlashCommand: vi.fn(), - ...overrides, - } as unknown as ChatInstance; + logger, + child, + debug, + info, + warn, + error, + }; } async function makeInitializedAdapter(fakeClient: ReturnType) { @@ -275,7 +350,7 @@ async function makeInitializedAdapter(fakeClient: ReturnType) auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, createClient: () => asMatrixClient(fakeClient), }); - await adapter.initialize(makeChatInstance({ getState: vi.fn(() => makeStateAdapter()) })); + await adapter.initialize(makeChatInstance()); return adapter; } @@ -342,6 +417,45 @@ describe("MatrixAdapter", () => { }); }); + it("logs and handles unexpected timeline processing failures", async () => { + const fakeClient = makeClient(); + const logger = makeTestLogger(); + const processMessage = vi.fn(() => { + throw new Error("boom"); + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + logger: logger.logger, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance({ processMessage })); + + const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + expect(timelineHandler).toBeTruthy(); + markSyncReady(fakeClient); + + timelineHandler?.(makeEvent(), { roomId: "!room:beeper.com" }, false); + await Promise.resolve(); + await Promise.resolve(); + + expect(logger.error).toHaveBeenCalledWith( + "Unhandled Matrix timeline event failure", + expect.objectContaining({ + eventId: "$event", + eventType: EventType.RoomMessage, + roomId: "!room:beeper.com", + error: expect.any(Error), + }) + ); + }); + it("auto-joins invite events from allowlisted inviters", async () => { const fakeClient = makeClient(); const adapter = new MatrixAdapter({ @@ -376,6 +490,67 @@ describe("MatrixAdapter", () => { expect(fakeClient.joinRoom).toHaveBeenCalledWith("!invited:beeper.com"); }); + it("retries invite auto-join when the homeserver rate limits the join", async () => { + const fakeClient = makeClient(); + const logger = makeTestLogger(); + fakeClient.joinRoom = vi + .fn() + .mockRejectedValueOnce( + new MatrixError( + { + errcode: "M_LIMIT_EXCEEDED", + error: "Too Many Requests", + retry_after_ms: 0, + }, + 429 + ) + ) + .mockResolvedValueOnce({ room_id: "!invited:beeper.com" }); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + inviteAutoJoin: { + enabled: true, + inviterAllowlist: ["@alice:beeper.com"], + }, + logger: logger.logger, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance()); + const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + + timelineHandler?.( + makeEvent({ + getType: () => EventType.RoomMember, + getSender: () => "@alice:beeper.com", + getStateKey: () => "@bot:beeper.com", + getContent: () => ({ membership: "invite" }), + }), + { roomId: "!invited:beeper.com" }, + false + ); + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(fakeClient.joinRoom).toHaveBeenCalledTimes(2); + expect(fakeClient.joinRoom).toHaveBeenNthCalledWith(1, "!invited:beeper.com"); + expect(fakeClient.joinRoom).toHaveBeenNthCalledWith(2, "!invited:beeper.com"); + expect(logger.warn).toHaveBeenCalledWith( + "Matrix invite auto-join rate limited, retrying", + expect.objectContaining({ + roomId: "!invited:beeper.com", + attempt: 1, + maxAttempts: 3, + retryDelayMs: 0, + error: expect.any(MatrixError), + }) + ); + }); + it("does not auto-join invite events from non-allowlisted inviters", async () => { const fakeClient = makeClient(); const adapter = new MatrixAdapter({ @@ -685,6 +860,89 @@ describe("MatrixAdapter", () => { ); }); + it("logs and rethrows Matrix send failures", async () => { + const fakeClient = makeClient(); + const logger = makeTestLogger(); + const sendError = new Error("send failed"); + fakeClient.sendEvent = vi.fn().mockRejectedValue(sendError); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + logger: logger.logger, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance()); + + await expect( + adapter.postMessage("matrix:!room%3Abeeper.com", "hello") + ).rejects.toThrow("send failed"); + expect(logger.error).toHaveBeenCalledWith( + "Matrix send message failed", + expect.objectContaining({ + roomId: "!room:beeper.com", + eventType: EventType.RoomMessage, + msgtype: "m.text", + error: sendError, + }) + ); + }); + + it("logs and rethrows Matrix upload failures", async () => { + const fakeClient = makeClient(); + const logger = makeTestLogger(); + const uploadError = new Error("upload failed"); + fakeClient.uploadContent = vi.fn().mockRejectedValue(uploadError); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + logger: logger.logger, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance()); + + await expect( + adapter.postMessage("matrix:!room%3Abeeper.com", { + markdown: "File incoming", + files: [ + { + data: new Uint8Array([1, 2, 3]).buffer, + filename: "report.png", + mimeType: "image/png", + }, + ], + }) + ).rejects.toThrow("upload failed"); + expect(logger.error).toHaveBeenCalledWith( + "Matrix upload content failed", + expect.objectContaining({ + fileName: "report.png", + mimeType: "image/png", + msgtype: "m.image", + error: uploadError, + }) + ); + }); + + it("rejects empty outbound messages instead of posting blank content", async () => { + const fakeClient = makeClient(); + const adapter = await makeInitializedAdapter(fakeClient); + + await expect( + adapter.postMessage("matrix:!room%3Abeeper.com", "") + ).rejects.toThrow("Cannot post an empty Matrix message."); + expect(fakeClient.sendEvent).not.toHaveBeenCalled(); + }); + it("appends URL-only attachments to message body", async () => { const fakeClient = makeClient(); fakeClient.sendEvent = vi.fn(async () => ({ event_id: "$text" })); @@ -1003,6 +1261,29 @@ describe("MatrixAdapter", () => { ).rejects.toThrow("Invalid cursor format. Expected mxv1 cursor."); }); + it("rejects cursors reused with a different fetch direction", async () => { + const fakeClient = makeClient(); + const adapter = await makeInitializedAdapter(fakeClient); + + const cursor = `mxv1:${Buffer.from( + JSON.stringify({ + dir: "backward", + kind: "room_messages", + roomID: "!room:beeper.com", + token: "room-page-token-1", + }), + "utf8" + ).toString("base64url")}`; + + await expect( + adapter.fetchMessages("matrix:!room%3Abeeper.com", { + direction: "forward", + limit: 10, + cursor, + }) + ).rejects.toThrow("Invalid cursor direction. Expected forward."); + }); + it("fetches non-thread messages via matrix API with mxv1 cursor", async () => { const fakeClient = makeClient(); fakeClient.createMessagesRequest @@ -1174,6 +1455,69 @@ describe("MatrixAdapter", () => { }); }); + it("filters edit relations from room history", async () => { + const fakeClient = makeClient(); + fakeClient.createMessagesRequest.mockResolvedValue({ + chunk: [ + makeRawEvent({ + event_id: "$edit", + origin_server_ts: 1_700_000_000_150, + content: { + body: "edited", + "m.relates_to": { rel_type: RelationType.Replace, event_id: "$top" }, + }, + }), + makeRawEvent({ + event_id: "$top", + origin_server_ts: 1_700_000_000_050, + content: { body: "top" }, + }), + ], + end: undefined, + }); + + const adapter = await makeInitializedAdapter(fakeClient); + + const result = await adapter.fetchMessages("matrix:!room%3Abeeper.com", { + direction: "backward", + limit: 20, + }); + + expect(result.messages.map((message) => message.id)).toEqual(["$top"]); + }); + + it("applies server-aggregated edits to fetched messages", async () => { + const fakeClient = makeClient(); + fakeClient.fetchRoomEvent.mockResolvedValueOnce( + makeRawEvent({ + event_id: "$top", + origin_server_ts: 1_700_000_000_050, + content: { body: "top" }, + unsigned: { + "m.relations": { + [RelationType.Replace]: { + event_id: "$edit", + content: { + body: "* edited", + "m.new_content": { + body: "edited", + msgtype: MsgType.Text, + }, + }, + }, + }, + }, + }) + ); + + const adapter = await makeInitializedAdapter(fakeClient); + + const message = await adapter.fetchMessage?.("matrix:!room%3Abeeper.com", "$top"); + + expect(message?.text).toBe("edited"); + expect(message?.metadata.edited).toBe(true); + }); + it("fetches a single message in context and returns null for mismatches", async () => { const fakeClient = makeClient(); const adapter = await makeInitializedAdapter(fakeClient); @@ -1230,6 +1574,77 @@ describe("MatrixAdapter", () => { ).rejects.toThrow("Internal server error"); }); + it("preserves attachment metadata and fetchData for Matrix media events", async () => { + const fakeClient = makeClient(); + fakeClient.fetchRoomEvent.mockResolvedValueOnce( + makeRawEvent({ + event_id: "$file", + origin_server_ts: 1_700_000_000_000, + content: { + body: "report.txt", + msgtype: MsgType.File, + url: "mxc://beeper.com/file-1", + info: { + mimetype: "text/plain", + size: 7, + }, + }, + }) + ); + fakeClient.mxcUrlToHttp.mockReturnValueOnce( + "https://hs.beeper.com/_matrix/client/v1/media/download/beeper.com/file-1" + ); + + const fetchMock = vi.fn(async () => + new Response(Buffer.from("payload"), { + status: 200, + headers: { + "Content-Type": "text/plain", + }, + }) + ); + vi.stubGlobal("fetch", fetchMock); + + try { + const adapter = await makeInitializedAdapter(fakeClient); + const message = await adapter.fetchMessage?.( + "matrix:!room%3Abeeper.com", + "$file" + ); + + expect(message?.attachments).toHaveLength(1); + expect(message?.attachments[0]).toMatchObject({ + type: "file", + name: "report.txt", + mimeType: "text/plain", + size: 7, + url: "mxc://beeper.com/file-1", + }); + await expect(message?.attachments[0]?.fetchData?.()).resolves.toEqual( + Buffer.from("payload") + ); + expect(fakeClient.mxcUrlToHttp).toHaveBeenCalledWith( + "mxc://beeper.com/file-1", + undefined, + undefined, + undefined, + true, + true, + true + ); + expect(fetchMock).toHaveBeenCalledWith( + "https://hs.beeper.com/_matrix/client/v1/media/download/beeper.com/file-1", + { + headers: { + Authorization: "Bearer token", + }, + } + ); + } finally { + vi.unstubAllGlobals(); + } + }); + it("openDM reuses cached mapping, then m.direct mapping, then creates and persists", async () => { const fakeClient = makeClient(); const cachedState = makeStateAdapter({ @@ -1240,7 +1655,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, createClient: () => asMatrixClient(fakeClient), }); - await adapterFromCache.initialize(makeChatInstance({ getState: vi.fn(() => cachedState) })); + await adapterFromCache.initialize(makeChatInstance({ state: cachedState })); const cachedThread = await adapterFromCache.openDM("@bob:beeper.com"); expect(cachedThread).toBe("matrix:!cached-dm%3Abeeper.com"); expect(fakeClient.createRoom).not.toHaveBeenCalled(); @@ -1254,7 +1669,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, createClient: () => asMatrixClient(fakeClient), }); - await adapterFromDirect.initialize(makeChatInstance({ getState: vi.fn(() => directState) })); + await adapterFromDirect.initialize(makeChatInstance({ state: directState })); const directThread = await adapterFromDirect.openDM("@bob:beeper.com"); expect(directThread).toBe("matrix:!from-direct%3Abeeper.com"); expect(directState.set).toHaveBeenCalledWith( @@ -1271,7 +1686,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, createClient: () => asMatrixClient(fakeClient), }); - await adapterCreate.initialize(makeChatInstance({ getState: vi.fn(() => createState) })); + await adapterCreate.initialize(makeChatInstance({ state: createState })); const createdThread = await adapterCreate.openDM("@bob:beeper.com"); expect(createdThread).toBe("matrix:!created-dm%3Abeeper.com"); expect(fakeClient.createRoom).toHaveBeenCalledWith({ @@ -1287,6 +1702,42 @@ describe("MatrixAdapter", () => { ); }); + it("openDM skips direct mappings for rooms the bot already left", async () => { + const fakeClient = makeClient(); + fakeClient.getAccountDataFromServer.mockResolvedValue({ + "@bob:beeper.com": ["!stale-dm:beeper.com", "!active-dm:beeper.com"], + }); + fakeClient.getRoom.mockImplementation((roomID?: string) => { + if (roomID === "!stale-dm:beeper.com") { + return makeRoom({ + roomId: roomID, + name: "Stale DM", + getMyMembership: () => "leave", + }); + } + if (roomID === "!active-dm:beeper.com") { + return makeRoom({ + roomId: roomID, + name: "Active DM", + getMyMembership: () => "join", + }); + } + return null; + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance()); + + const threadID = await adapter.openDM("@bob:beeper.com"); + + expect(threadID).toBe("matrix:!active-dm%3Abeeper.com"); + expect(fakeClient.createRoom).not.toHaveBeenCalled(); + }); + it("lists threads using server-side thread API with mxv1 cursor", async () => { const fakeClient = makeClient(); fakeClient.createThreadListMessagesRequest.mockResolvedValue({ diff --git a/src/index.ts b/src/index.ts index 40beb56..1f7c9a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,6 +49,7 @@ import type { RoomMessageEventContent, RoomMessageTextEventContent, } from "matrix-js-sdk/lib/@types/events"; +import type { MediaEventContent } from "matrix-js-sdk/lib/@types/media"; import { logger as matrixSDKLogger } from "matrix-js-sdk/lib/logger"; import type { MatrixAuthBootstrapClient, @@ -100,6 +101,8 @@ type MatrixRoomMessageContent = RoomMessageEventContent & { }; }; +type MatrixOutboundMessageContent = MatrixRoomMessageContent | MediaEventContent; + type StoredReaction = { emoji: EmojiValue; messageID: string; @@ -283,13 +286,13 @@ export class MatrixAdapter implements Adapter { }); this.client.on(RoomEvent.Timeline, (event, room, toStartOfTimeline) => { - void this.onTimelineEvent(event, room, Boolean(toStartOfTimeline)); + this.dispatchTimelineEvent(event, room, Boolean(toStartOfTimeline)); }); this.client.on(ClientEvent.Event, (event) => { if (!event.getRoomId()) { return; } - void this.onTimelineEvent(event, undefined, false); + this.dispatchTimelineEvent(event, undefined, false); }); await this.maybeInitE2EE(); @@ -375,7 +378,7 @@ export class MatrixAdapter implements Adapter { const contents = await this.toRoomMessageContents(message); const [firstContent, ...extraContents] = contents; if (!firstContent) { - throw new Error("toRoomMessageContents returned an empty array"); + throw new Error("Cannot post an empty Matrix message."); } const response = await this.sendRoomMessage(roomID, rootEventID, firstContent); for (const content of extraContents) { @@ -431,7 +434,14 @@ export class MatrixAdapter implements Adapter { async deleteMessage(threadId: string, messageId: string): Promise { const { roomID } = this.decodeThreadId(threadId); - await this.requireClient().redactEvent(roomID, messageId); + await this.withLoggedMatrixOperation( + "Matrix redact message failed", + { + roomId: roomID, + eventId: messageId, + }, + () => this.requireClient().redactEvent(roomID, messageId) + ); } async addReaction( @@ -442,17 +452,22 @@ export class MatrixAdapter implements Adapter { const { roomID, rootEventID } = this.decodeThreadId(threadId); const rawEmoji = this.rawEmoji(emoji); - const response = await this.requireClient().sendEvent( - roomID, - rootEventID ?? null, - EventType.Reaction, + const response = await this.withLoggedMatrixOperation( + "Matrix send reaction failed", { - "m.relates_to": { - rel_type: RelationType.Annotation, - event_id: messageId, - key: rawEmoji, - }, - } + roomId: roomID, + rootEventId: rootEventID, + messageId, + emoji: rawEmoji, + }, + () => + this.requireClient().sendEvent(roomID, rootEventID ?? null, EventType.Reaction, { + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: messageId, + key: rawEmoji, + }, + }) ); const key = this.myReactionKey(threadId, messageId, rawEmoji); @@ -474,13 +489,29 @@ export class MatrixAdapter implements Adapter { } const { roomID } = this.decodeThreadId(threadId); - await this.requireClient().redactEvent(roomID, reactionEventID); + await this.withLoggedMatrixOperation( + "Matrix remove reaction failed", + { + roomId: roomID, + reactionEventId: reactionEventID, + messageId, + emoji: rawEmoji, + }, + () => this.requireClient().redactEvent(roomID, reactionEventID) + ); this.myReactionByKey.delete(this.myReactionKey(threadId, messageId, rawEmoji)); } async startTyping(threadId: string): Promise { const { roomID } = this.decodeThreadId(threadId); - await this.requireClient().sendTyping(roomID, true, TYPING_TIMEOUT_MS); + await this.withLoggedMatrixOperation( + "Matrix typing request failed", + { + roomId: roomID, + timeoutMs: TYPING_TIMEOUT_MS, + }, + () => this.requireClient().sendTyping(roomID, true, TYPING_TIMEOUT_MS) + ); } async openDM(userId: string): Promise { @@ -496,10 +527,17 @@ export class MatrixAdapter implements Adapter { return this.encodeThreadId({ roomID: existingRoomID }); } - const response = await this.requireClient().createRoom({ - invite: [userId], - is_direct: true, - }); + const response = await this.withLoggedMatrixOperation( + "Matrix create DM room failed", + { + userId, + }, + () => + this.requireClient().createRoom({ + invite: [userId], + is_direct: true, + }) + ); const createdRoomID = response.room_id; if (!createdRoomID) { @@ -523,7 +561,8 @@ export class MatrixAdapter implements Adapter { options.cursor, rootEventID ? "thread_relations" : "room_messages", roomID, - rootEventID + rootEventID, + direction ) : null; @@ -611,7 +650,7 @@ export class MatrixAdapter implements Adapter { id: threadId, channelId: this.channelIdFromThreadId(threadId), channelName: room.name, - isDM: room.getJoinedMembers().length === 2, + isDM: await this.isDirectRoom(roomID), metadata: { roomID, }, @@ -626,7 +665,7 @@ export class MatrixAdapter implements Adapter { return { id: channelId, name: room.name, - isDM: members.length === 2, + isDM: await this.isDirectRoom(roomID), memberCount: members.length, metadata: { roomID, @@ -641,7 +680,7 @@ export class MatrixAdapter implements Adapter { const roomID = this.decodeThreadId(channelId).roomID; const limit = options.limit ?? 50; const cursor = options.cursor - ? this.decodeCursorV1(options.cursor, "thread_list", roomID) + ? this.decodeCursorV1(options.cursor, "thread_list", roomID, undefined, "backward") : null; const listResponse = await this.requireClient().createThreadListMessagesRequest( roomID, @@ -697,7 +736,9 @@ export class MatrixAdapter implements Adapter { const threadID = overrideThreadID ?? this.threadIDForEvent(raw, roomID); const content = raw.getContent(); - const text = this.extractText(content); + const editedContent = this.extractEditedContent(raw); + const effectiveContent = editedContent ?? content; + const text = this.extractText(effectiveContent); const sender = raw.getSender() ?? "unknown"; return new Message({ @@ -708,11 +749,11 @@ export class MatrixAdapter implements Adapter { author: this.makeUser(sender), metadata: { dateSent: new Date(raw.getTs()), - edited: this.isEdited(raw), + edited: this.isEdited(raw) || Boolean(editedContent), }, - attachments: this.extractAttachments(content), + attachments: this.extractAttachments(effectiveContent), raw, - isMention: this.isMentioned(content, text), + isMention: this.isMentioned(effectiveContent, text), }); } @@ -727,7 +768,8 @@ export class MatrixAdapter implements Adapter { cursor: string, expectedKind: CursorKind, expectedRoomID: string, - expectedRootEventID?: string + expectedRootEventID?: string, + expectedDirection?: CursorDirection ): CursorV1Payload { if (!cursor.startsWith(MATRIX_CURSOR_PREFIX)) { throw new Error("Invalid cursor format. Expected mxv1 cursor."); @@ -755,6 +797,9 @@ export class MatrixAdapter implements Adapter { if (parsed.dir !== "forward" && parsed.dir !== "backward") { throw new Error("Invalid cursor format. Invalid direction."); } + if (expectedDirection && parsed.dir !== expectedDirection) { + throw new Error(`Invalid cursor direction. Expected ${expectedDirection}.`); + } if (typeof parsed.token !== "string" || parsed.token.length === 0) { throw new Error("Invalid cursor format. Missing token."); } @@ -828,6 +873,8 @@ export class MatrixAdapter implements Adapter { direction: CursorDirection; fromToken: string | null; }): Promise<{ events: MatrixEvent[]; nextToken: string | null }> { + // When includeRoot is true, reserve one slot for the root and fetch at most + // limit - 1 replies; the merged result is sliced back to args.limit below. const relationLimit = args.includeRoot ? Math.max(args.limit - 1, 1) : args.limit; @@ -953,7 +1000,7 @@ export class MatrixAdapter implements Adapter { } private isTopLevelMessageEvent(event: MatrixEvent): boolean { - return !event.threadRootId && !event.isRelation(THREAD_RELATION_TYPE.name); + return !event.threadRootId && !event.getRelation(); } private isMessageEventInContext( @@ -997,10 +1044,32 @@ export class MatrixAdapter implements Adapter { } private async loadDirectAccountData(): Promise { + const cached = this.loadCachedDirectAccountData(); + if (Object.keys(cached).length > 0) { + return cached; + } + const direct = await this.requireClient().getAccountDataFromServer(EventType.Direct); return this.normalizeDirectAccountData(direct); } + private loadCachedDirectAccountData(): DirectAccountData { + const client = this.requireClient(); + const getAccountData = Reflect.get(client, "getAccountData"); + if (typeof getAccountData !== "function") { + return {}; + } + + const event = getAccountData.call(client, EventType.Direct); + const direct = + typeof event === "object" && + event !== null && + typeof Reflect.get(event, "getContent") === "function" + ? Reflect.get(event, "getContent").call(event) + : undefined; + return this.normalizeDirectAccountData(direct); + } + private normalizeDirectAccountData(value: unknown): DirectAccountData { if (!value || typeof value !== "object") { return {}; @@ -1023,15 +1092,45 @@ export class MatrixAdapter implements Adapter { direct: DirectAccountData, userID: string ): string | null { + const client = this.requireClient(); const candidates = direct[userID] ?? []; for (const roomID of candidates) { - if (roomID) { + if (!roomID) { + continue; + } + + const room = client.getRoom(roomID); + if (!room) { + // m.direct is server state; if the room is not in the local sync cache yet, + // prefer the server mapping over creating a duplicate DM. + return roomID; + } + + const membership = room.getMyMembership(); + if (membership === "join" || membership === "invite") { return roomID; } } return null; } + private async isDirectRoom(roomID: string): Promise { + const cached = this.loadCachedDirectAccountData(); + if (this.directAccountDataContainsRoom(cached, roomID)) { + return true; + } + + const direct = await this.loadDirectAccountData(); + return this.directAccountDataContainsRoom(direct, roomID); + } + + private directAccountDataContainsRoom( + direct: DirectAccountData, + roomID: string + ): boolean { + return Object.values(direct).some((roomIDs) => roomIDs.includes(roomID)); + } + private async persistDirectAccountDataRoom( userID: string, roomID: string, @@ -1185,29 +1284,68 @@ export class MatrixAdapter implements Adapter { }; } - private sendRoomMessage( + private dispatchTimelineEvent( + event: MatrixEvent, + room: Room | undefined, + toStartOfTimeline: boolean + ): void { + void this.onTimelineEvent(event, room, toStartOfTimeline).catch((error) => { + this.logger.error("Unhandled Matrix timeline event failure", { + eventId: event.getId(), + eventType: event.getType(), + roomId: room?.roomId ?? event.getRoomId(), + error, + }); + }); + } + + private async withLoggedMatrixOperation( + message: string, + context: Record, + operation: () => Promise + ): Promise { + try { + return await operation(); + } catch (error) { + this.logger.error(message, { ...context, error }); + throw error; + } + } + + private async sendRoomMessage( roomID: string, rootEventID: string | undefined, - content: MatrixRoomMessageContent + content: MatrixOutboundMessageContent ) { - const client = this.requireClient(); - if (rootEventID) { - return client.sendEvent(roomID, rootEventID, EventType.RoomMessage, content); - } + return this.withLoggedMatrixOperation( + "Matrix send message failed", + { + roomId: roomID, + rootEventId: rootEventID, + eventType: EventType.RoomMessage, + msgtype: content.msgtype, + }, + async () => { + const client = this.requireClient(); + if (rootEventID) { + return client.sendEvent(roomID, rootEventID, EventType.RoomMessage, content); + } - return client.sendEvent(roomID, EventType.RoomMessage, content); + return client.sendEvent(roomID, EventType.RoomMessage, content); + } + ); } private async toRoomMessageContents( message: AdapterPostableMessage - ): Promise { + ): Promise { const textContent = this.toRoomMessageContent(message); const attachments = this.extractAttachmentsFromMessage(message); const uploads = await this.collectUploads(message, attachments); const linkLines = this.collectLinkOnlyAttachmentLines(attachments); const textBody = this.mergeTextAndLinks(textContent.body ?? "", linkLines); const textMsgType = textContent.msgtype ?? MsgType.Text; - const contents: MatrixRoomMessageContent[] = []; + const contents: MatrixOutboundMessageContent[] = []; if (textBody.length > 0) { contents.push({ @@ -1219,28 +1357,30 @@ export class MatrixAdapter implements Adapter { const uploadedContents = await Promise.all( uploads.map(async (upload) => { - const uploadResponse = await this.requireClient().uploadContent(upload.data, { - name: upload.fileName, - type: upload.info?.mimetype, - }); - return { + const uploadResponse = await this.withLoggedMatrixOperation( + "Matrix upload content failed", + { + fileName: upload.fileName, + mimeType: upload.info?.mimetype, + msgtype: upload.msgtype, + }, + () => + this.requireClient().uploadContent(upload.data, { + name: upload.fileName, + type: upload.info?.mimetype, + }) + ); + const content: MediaEventContent = { body: upload.fileName, msgtype: upload.msgtype, url: uploadResponse.content_uri, info: upload.info, - } as unknown as MatrixRoomMessageContent; + }; + return content; }) ); contents.push(...uploadedContents); - if (contents.length === 0) { - contents.push({ - ...textContent, - body: "", - msgtype: textMsgType, - }); - } - return contents; } @@ -1473,7 +1613,7 @@ export class MatrixAdapter implements Adapter { } try { - await this.requireClient().joinRoom(roomID); + await this.joinRoomWithRetry(roomID); this.logger.info("Accepted Matrix invite", { roomId: roomID, inviter, @@ -1487,6 +1627,36 @@ export class MatrixAdapter implements Adapter { } } + private async joinRoomWithRetry(roomID: string, maxAttempts = 3): Promise { + let lastError: unknown; + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + await this.requireClient().joinRoom(roomID); + return; + } catch (error) { + lastError = error; + if (!this.isRetryableJoinError(error) || attempt === maxAttempts) { + throw error; + } + + const retryDelayMs = this.retryDelayMsForJoinError(error); + this.logger.warn("Matrix invite auto-join rate limited, retrying", { + roomId: roomID, + attempt, + maxAttempts, + retryDelayMs, + error, + }); + await new Promise((resolve) => + setTimeout(resolve, retryDelayMs) + ); + } + } + + throw lastError; + } + private shouldAcceptInvite( roomID: string, inviter: string | null | undefined @@ -1506,6 +1676,34 @@ export class MatrixAdapter implements Adapter { return this.inviteAutoJoinInviterAllowlist.has(inviter); } + private isRetryableJoinError(error: unknown): error is MatrixError { + return ( + error instanceof MatrixError && + (error.errcode === "M_LIMIT_EXCEEDED" || error.httpStatus === 429) + ); + } + + private retryDelayMsForJoinError(error: MatrixError): number { + const retryAfterMs = + typeof error.data?.retry_after_ms === "number" && error.data.retry_after_ms >= 0 + ? error.data.retry_after_ms + : undefined; + if (retryAfterMs !== undefined) { + return retryAfterMs; + } + + const retryAfterHeader = error.httpHeaders?.get("retry-after"); + const retryAfterSeconds = + typeof retryAfterHeader === "string" + ? Number.parseFloat(retryAfterHeader) + : Number.NaN; + if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) { + return retryAfterSeconds * 1000; + } + + return 500; + } + private handleReactionEvent(event: MatrixEvent, roomID: string): void { const content = event.getContent<{ "m.relates_to"?: Record }>(); const relatesTo = content["m.relates_to"]; @@ -1620,15 +1818,128 @@ export class MatrixAdapter implements Adapter { const info = isRecord(content.info) ? content.info : undefined; const mimeType = typeof info?.mimetype === "string" ? info.mimetype : undefined; - const attachment: { type: "file"; url: string; mimeType?: string } = { - type: "file", + const attachment: Attachment = { + type: this.attachmentTypeForContent(content, mimeType), url, + name: normalizeOptionalString(content.body), mimeType, + size: typeof info?.size === "number" ? info.size : undefined, + width: typeof info?.w === "number" ? info.w : undefined, + height: typeof info?.h === "number" ? info.h : undefined, + fetchData: this.createAttachmentFetcher(url), }; return [attachment]; } + private extractEditedContent(raw: MatrixEvent): MatrixMessageContent | undefined { + const replacement = raw.getServerAggregatedRelation<{ + content?: MatrixRoomMessageContent; + }>(RelationType.Replace); + const replacementContent = isRecord(replacement?.content) + ? replacement.content + : undefined; + const newContent = isRecord(replacementContent?.["m.new_content"]) + ? replacementContent["m.new_content"] + : undefined; + + return newContent; + } + + private attachmentTypeForContent( + content: MatrixMessageContent, + mimeType?: string + ): Attachment["type"] { + switch (content.msgtype) { + case MsgType.Image: + return "image"; + case MsgType.Video: + return "video"; + case MsgType.Audio: + return "audio"; + case MsgType.File: + return "file"; + default: { + const mediaType = this.messageTypeForMimeType(mimeType); + switch (mediaType) { + case MsgType.Image: + return "image"; + case MsgType.Video: + return "video"; + case MsgType.Audio: + return "audio"; + default: + return "file"; + } + } + } + } + + private createAttachmentFetcher(url: string): Attachment["fetchData"] | undefined { + if (typeof fetch !== "function") { + return undefined; + } + + return async () => { + const client = this.requireClient(); + const downloadURL = this.resolveAttachmentDownloadURL(url, client); + if (!downloadURL) { + throw new Error(`Unable to resolve Matrix attachment download URL for ${url}`); + } + + const accessToken = + typeof client.getAccessToken === "function" + ? normalizeOptionalString(client.getAccessToken() ?? undefined) + : undefined; + const response = await fetch(downloadURL, { + headers: accessToken + ? { + Authorization: `Bearer ${accessToken}`, + } + : undefined, + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch Matrix attachment (${response.status} ${response.statusText})` + ); + } + + return Buffer.from(await response.arrayBuffer()); + }; + } + + private resolveAttachmentDownloadURL( + url: string, + client: MatrixClient + ): string | undefined { + if (typeof client.mxcUrlToHttp === "function") { + const authenticatedURL = normalizeOptionalString( + client.mxcUrlToHttp( + url, + undefined, + undefined, + undefined, + true, + true, + true + ) ?? undefined + ); + if (authenticatedURL) { + return authenticatedURL; + } + + const unauthenticatedURL = normalizeOptionalString( + client.mxcUrlToHttp(url, undefined, undefined, undefined, true) ?? undefined + ); + if (unauthenticatedURL) { + return unauthenticatedURL; + } + } + + return url.startsWith("mxc://") ? undefined : url; + } + private isEdited(event: MatrixEvent): boolean { const relation = event.getRelation(); return relation?.rel_type === RelationType.Replace; @@ -2399,6 +2710,8 @@ function evictOldestEntries( if (collection.size <= maxSize) return; const toDelete = collection.size - targetSize; let deleted = 0; + // Map and Set iteration is insertion ordered, so keys() yields the oldest + // entries first for the collections used by this adapter. for (const key of collection.keys()) { if (deleted >= toDelete) break; collection.delete(key); diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 51009fa..c0de215 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -3,6 +3,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["e2e/**/*.test.ts"], - testTimeout: 30_000, + hookTimeout: 120_000, + testTimeout: 120_000, }, }); From 7664b6c8220ad2984b5851d9be398809aae30a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 7 Mar 2026 16:53:49 +0100 Subject: [PATCH 15/25] Add Matrix rich-text & mention handling; bump deps Implement inbound Matrix rich-text normalization (strip reply fallbacks, parse Matrix pills) and outbound rendering of markdown/Chat SDK mention placeholders to formatted_body with m.mentions. Expose room metadata (roomID, DM status, topic, canonical alias, avatar MXC, encryption info) via fetchThread/fetchChannelInfo. Update tests/e2e and build config, and upgrade dependencies across lockfiles (bump chat/state packages, add marked/node-html-parser, eslint/types tooling, Redis client v5 and other transitive updates). README and types updated to reflect new behavior. --- README.md | 5 + bun.lock | 205 ++++++++++++-- e2e/e2e.test.ts | 103 +++++++- package-lock.json | 148 ++++++++++- package.json | 4 +- src/index.test.ts | 256 ++++++++++++++++-- src/index.ts | 661 +++++++++++++++++++++++++++++++++++++++++----- 7 files changed, 1279 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index c9fe9dd..230a175 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,9 @@ bun --env-file=examples/.env run examples/bot.ts - `fetchMessage(threadId, messageId)` fetches a single message with thread/channel context validation. - `fetchChannelMessages(channelId, options)` fetches top-level room timeline messages. - `fetchMessages(threadId, options)` and `listThreads(channelId, options)` use API-first server pagination via `matrix-js-sdk`. +- Inbound Matrix rich text is normalized from `formatted_body` when present, including reply fallback stripping and Matrix pill mention parsing. +- Outbound markdown and Chat SDK mention placeholders are rendered to Matrix `formatted_body` with `org.matrix.custom.html` and `m.mentions`. +- `fetchThread()` and `fetchChannelInfo()` expose room metadata such as `roomID`, DM status, topic, canonical alias, avatar MXC URL, and encryption details when that state is available locally. - Outbound file support: `files` and binary `attachments` are uploaded with `uploadContent()` and sent as Matrix media messages. - URL-only attachments are appended as links in the text body. - `postEphemeral`, `openModal`, and native `stream` are not implemented by this adapter. @@ -146,6 +149,8 @@ bun --env-file=examples/.env run examples/bot.ts - `handleWebhook()` returns `501` by design, since this adapter is sync-based. - Access-token auth resolves identity with `whoami`. - Password auth sends the configured `device_id` during login. +- Mention sending uses Chat SDK's standard `<@userId>` placeholder syntax and is translated into Matrix pills at send time. +- Matrix reply linkage remains in the raw event/metadata path; the adapter strips the visible quoted fallback from normalized message text. - For production, use Redis state for stable sessions and device IDs. For release-specific changes and migration notes, see [CHANGELOG.md](./CHANGELOG.md). diff --git a/bun.lock b/bun.lock index e6aacee..cc446e9 100644 --- a/bun.lock +++ b/bun.lock @@ -5,16 +5,21 @@ "": { "name": "@beeper/chat-adapter-matrix", "dependencies": { - "@chat-adapter/state-memory": "^4.15.0", - "@chat-adapter/state-redis": "^4.15.0", - "chat": "^4.15.0", + "@chat-adapter/state-memory": "^4.16.1", + "@chat-adapter/state-redis": "^4.16.1", + "chat": "^4.16.1", + "marked": "^15.0.12", "matrix-js-sdk": "^41.0.0", + "node-html-parser": "^7.1.0", }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^22.10.2", "@vitest/coverage-v8": "^2.1.8", + "eslint": "^10.0.2", "tsup": "^8.3.5", "typescript": "^5.7.2", + "typescript-eslint": "^8.56.1", "vitest": "^2.1.8", }, }, @@ -34,9 +39,9 @@ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.15.0", "", { "dependencies": { "chat": "4.15.0" } }, "sha512-I/p6dA8fl8peZH9utKOsO8raFttUSyIQwyhB/uP6cR9d07hZVcG/SF30t4RjKDgegnq8+euqPFj3lCssIaYhoA=="], + "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.16.1", "", { "dependencies": { "chat": "4.16.1" } }, "sha512-G/7MNxqqI19IRUUoaitSGWs0Tx+FAcvnB3rLfpWmV4eaUDzzhF+DEtmmR+DVxpz35RxBM3Y4Gi6Wqz5Ysfiytg=="], - "@chat-adapter/state-redis": ["@chat-adapter/state-redis@4.15.0", "", { "dependencies": { "chat": "4.15.0", "redis": "^4.7.0" } }, "sha512-156CGZCkMXzsnMC20nIpNkAGScbasOHxR9YcPMuLm2IwSC1L/Kyea7o9TEv2ldvk63q/e+AaU+98T7T5/2+mvg=="], + "@chat-adapter/state-redis": ["@chat-adapter/state-redis@4.16.1", "", { "dependencies": { "chat": "4.16.1", "redis": "^5.11.0" } }, "sha512-weXm3OweMb7/hejHdzyaSvBLjS31jbjrsQ+KtE+ikm9IixZvCATPw87+rm+mvTXwPUvUi6m72K0uDFK0Os3rRQ=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], @@ -90,6 +95,30 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.23.2", "", { "dependencies": { "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", "minimatch": "^10.2.1" } }, "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.2", "", { "dependencies": { "@eslint/core": "^1.1.0" } }, "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ=="], + + "@eslint/core": ["@eslint/core@1.1.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw=="], + + "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], + + "@eslint/object-schema": ["@eslint/object-schema@3.0.2", "", {}, "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.0", "", { "dependencies": { "@eslint/core": "^1.1.0", "levn": "^0.4.1" } }, "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], @@ -106,17 +135,15 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="], - - "@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="], + "@redis/bloom": ["@redis/bloom@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw=="], - "@redis/graph": ["@redis/graph@1.1.1", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="], + "@redis/client": ["@redis/client@5.11.0", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0" }, "optionalPeers": ["@node-rs/xxhash"] }, "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ=="], - "@redis/json": ["@redis/json@1.0.7", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="], + "@redis/json": ["@redis/json@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg=="], - "@redis/search": ["@redis/search@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="], + "@redis/search": ["@redis/search@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g=="], - "@redis/time-series": ["@redis/time-series@1.1.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="], + "@redis/time-series": ["@redis/time-series@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], @@ -168,12 +195,18 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/events": ["@types/events@3.0.3", "", {}, "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], @@ -182,6 +215,26 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + "@vitest/coverage-v8": ["@vitest/coverage-v8@2.1.9", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.12", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, "peerDependencies": { "@vitest/browser": "2.1.9", "vitest": "2.1.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ=="], "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], @@ -202,6 +255,10 @@ "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "another-json": ["another-json@0.2.0", "", {}, "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -218,10 +275,14 @@ "base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], @@ -232,7 +293,7 @@ "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], - "chat": ["chat@4.15.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.5" } }, "sha512-fE1m3UEiKQoiYkhXWT0rpH1+ZhKqJGZYkS+1gNCZWNkLv0QWTZhzdzBM5qYWe5A/Y0wJ8yLVjaqvGO4zZFxZqg=="], + "chat": ["chat@4.16.1", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" } }, "sha512-y5om0n19gOIv54pAmPIIMlYRFxpphgY4+Eh5K0HdmyRsIq7jjEA6DiGSoIP8zy3/HhSYJPekqQz5QQt7neN6QA=="], "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], @@ -254,52 +315,108 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": "bin/esbuild" }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@10.0.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.2", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.0", "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.1", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw=="], + + "eslint-scope": ["eslint-scope@9.1.1", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "espree": ["espree@11.1.1", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.4", "", {}, "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="], - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], @@ -318,14 +435,26 @@ "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -342,6 +471,8 @@ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "matrix-events-sdk": ["matrix-events-sdk@0.0.1", "", {}, "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="], "matrix-js-sdk": ["matrix-js-sdk@41.0.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/matrix-sdk-crypto-wasm": "^17.1.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", "p-retry": "7", "sdp-transform": "^3.0.0", "unhomoglyph": "^1.0.6", "uuid": "13" } }, "sha512-58j4WuOwXvT6bmTgc48b3bITVG/rWv5+N//yA5mvr6Rc04EUSpwTPhgMOTjUVnJsrhLGzdkgal4Xtt2R+xNdwg=="], @@ -438,14 +569,28 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "oidc-client-ts": ["oidc-client-ts@3.4.1", "", { "dependencies": { "jwt-decode": "^4.0.0" } }, "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], @@ -466,9 +611,13 @@ "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="], + "redis": ["redis@5.11.0", "", { "dependencies": { "@redis/bloom": "5.11.0", "@redis/client": "5.11.0", "@redis/json": "5.11.0", "@redis/search": "5.11.0", "@redis/time-series": "5.11.0" } }, "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ=="], "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], @@ -476,6 +625,8 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "remend": ["remend@1.2.2", "", {}, "sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w=="], + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], @@ -534,12 +685,18 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="], + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -556,6 +713,8 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "uuid": ["uuid@13.0.0", "", { "bin": "dist-node/bin/uuid" }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -572,20 +731,24 @@ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@vitest/runner/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@vitest/snapshot/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "glob/minimatch": ["minimatch@9.0.6", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -598,8 +761,6 @@ "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": "bin/esbuild" }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], - "vite-node/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], - "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index de3d621..459cc5d 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { Message } from "chat"; +import { stringifyMarkdown, type Message } from "chat"; import { EventType, RoomEvent, RoomMemberEvent, type MatrixEvent } from "matrix-js-sdk"; import { createParticipant, @@ -119,6 +119,27 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect(message.author.userId).toBe(bot.userID); }); + it("preserves rich text and mention semantics across encrypted delivery", async () => { + const tag = `e2e-format-${nonce()}`; + const localpart = bot.userID.slice(1).split(":")[0]; + const threadId = sender.adapter.encodeThreadId({ roomID }); + const posted = await sender.adapter.postMessage(threadId, { + markdown: `Hello **${tag}** <@${bot.userID}>`, + }); + const message = await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + posted.id, + (candidate) => candidate.text.includes(tag) && candidate.isMention === true, + 45_000 + ); + + expect(message.text).toContain(`Hello ${tag} @${localpart}`); + expect(message.isMention).toBe(true); + expect(stringifyMarkdown(message.formatted).trim()).toContain(`**${tag}**`); + expect(stringifyMarkdown(message.formatted)).toContain(`@${localpart}`); + }); + it("thread round-trip: sender creates thread, bot replies in it", async () => { const rootTag = `e2e-thread-root-${nonce()}`; const replyTag = `e2e-thread-reply-${nonce()}`; @@ -275,7 +296,9 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { ); // Bot edits the message - await bot.adapter.editMessage(threadId, messageId, `Edited ${editedTag}`); + await bot.adapter.editMessage(threadId, messageId, { + markdown: `Edited **${editedTag}**`, + }); const editedMessage = await waitForMatchingMessage( sender.adapter, sender.adapter.encodeThreadId({ roomID }), @@ -288,6 +311,9 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect(editedMessage.text).toContain(editedTag); expect(editedMessage.text).not.toContain(tag); + expect(stringifyMarkdown(editedMessage.formatted).trim()).toContain( + `**${editedTag}**` + ); const fetched = await waitForFetchedMessage( sender.adapter, @@ -300,6 +326,46 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { ); expect(fetched.text).toContain(editedTag); expect(fetched.text).not.toContain(tag); + expect(stringifyMarkdown(fetched.formatted).trim()).toContain(`**${editedTag}**`); + }); + + it("strips Matrix reply fallback from fetched reply messages", async () => { + const rootTag = `e2e-reply-root-${nonce()}`; + const replyTag = `e2e-reply-visible-${nonce()}`; + const threadId = sender.adapter.encodeThreadId({ roomID }); + const rootPosted = await sender.adapter.postMessage(threadId, `Reply root ${rootTag}`); + await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + rootPosted.id, + (message) => message.text.includes(rootTag) + ); + + const replyResponse = await sender.matrixClient.sendEvent(roomID, EventType.RoomMessage, { + msgtype: "m.text", + body: `> <${bot.userID}> Reply root ${rootTag}\n> quoted\n\nVisible ${replyTag}`, + format: "org.matrix.custom.html", + formatted_body: + `
In reply to` + + ` ${bot.userID}
Reply root ${rootTag}
` + + `

Visible ${replyTag}

`, + "m.relates_to": { + "m.in_reply_to": { + event_id: rootPosted.id, + }, + }, + }); + + const replyMessage = await waitForFetchedMessage( + bot.adapter, + bot.adapter.encodeThreadId({ roomID }), + replyResponse.event_id, + (message) => message.text.includes(replyTag), + 45_000 + ); + + expect(replyMessage.text).toBe(`Visible ${replyTag}`); + expect(stringifyMarkdown(replyMessage.formatted).trim()).toBe(`Visible ${replyTag}`); }); it("fetchMessages pagination", async () => { @@ -558,6 +624,39 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect((summary?.replyCount ?? 0) >= 1).toBe(true); }); + it("includes live room metadata in channel and thread info", async () => { + const topic = `Adapter metadata ${nonce()}`; + await bot.matrixClient.sendStateEvent( + roomID, + EventType.RoomTopic, + { topic }, + "" + ); + await sleep(1_000); + + const channelId = bot.adapter.channelIdFromThreadId(bot.adapter.encodeThreadId({ roomID })); + const rootPosted = await bot.adapter.postMessage( + bot.adapter.encodeThreadId({ roomID }), + `Metadata root ${nonce()}` + ); + const threadId = bot.adapter.encodeThreadId({ + roomID, + rootEventID: rootPosted.id, + }); + + const [channelInfo, threadInfo] = await Promise.all([ + bot.adapter.fetchChannelInfo(channelId), + bot.adapter.fetchThread(threadId), + ]); + + expect(channelInfo.metadata?.roomID).toBe(roomID); + expect(channelInfo.metadata?.topic).toBe(topic); + expect(channelInfo.metadata?.encrypted).toBe(true); + expect(threadInfo.metadata?.roomID).toBe(roomID); + expect(threadInfo.metadata?.topic).toBe(topic); + expect(threadInfo.metadata?.encrypted).toBe(true); + }); + it("uploads a file attachment and fetches it back", async () => { const tag = `e2e-file-${nonce()}`; const threadId = bot.adapter.encodeThreadId({ roomID }); diff --git a/package-lock.json b/package-lock.json index 586eefe..8566272 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@chat-adapter/state-memory": "^4.16.1", "@chat-adapter/state-redis": "^4.16.1", "chat": "^4.16.1", - "matrix-js-sdk": "^41.0.0" + "marked": "^15.0.12", + "matrix-js-sdk": "^41.0.0", + "node-html-parser": "^7.1.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -1829,6 +1831,12 @@ "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", "license": "MIT" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", @@ -2035,6 +2043,34 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2104,6 +2140,61 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2118,6 +2209,18 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2562,6 +2665,15 @@ "node": ">=8" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2918,6 +3030,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/matrix-events-sdk": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", @@ -3806,6 +3930,28 @@ "dev": true, "license": "MIT" }, + "node_modules/node-html-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz", + "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index f253340..f82a187 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "@chat-adapter/state-memory": "^4.16.1", "@chat-adapter/state-redis": "^4.16.1", "chat": "^4.16.1", - "matrix-js-sdk": "^41.0.0" + "marked": "^15.0.12", + "matrix-js-sdk": "^41.0.0", + "node-html-parser": "^7.1.0" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/index.test.ts b/src/index.test.ts index 14e0c16..38a4207 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { Chat, getEmoji } from "chat"; +import { Chat, getEmoji, stringifyMarkdown } from "chat"; import type { ChatInstance, Logger, StateAdapter } from "chat"; import { createMemoryState } from "@chat-adapter/state-memory"; import { EventType, MsgType, RelationType, type MatrixClient } from "matrix-js-sdk"; @@ -32,10 +32,26 @@ type RelationsResponseLike = { prevBatch: string | null; }; +type MemberLike = { + name?: string; + rawDisplayName?: string; +}; + +type StateEventLike = { + getContent: () => T; +}; + +type RoomStateLike = { + getStateEvents: (eventType: string, stateKey: string) => StateEventLike | null; +}; + type RoomLike = { + currentState: RoomStateLike; + getMember: (userId: string) => MemberLike | null; roomId: string; name: string; timeline: unknown[]; + hasEncryptionStateEvent: () => boolean; getJoinedMembers: () => Array>; getMyMembership: () => string; findEventById: (eventID?: string) => ReturnType | null; @@ -75,6 +91,12 @@ function makeEvent(overrides: Record = {}) { return event; } +function makeStateEvent(content: Record): StateEventLike { + return { + getContent: () => content as T, + }; +} + function makeRawEvent(overrides: Record = {}) { return { event_id: "$raw", @@ -235,6 +257,11 @@ function makeRoom(overrides: Partial = {}): RoomLike { roomId: "!room:beeper.com", name: "Example Room", timeline: [], + currentState: { + getStateEvents: () => null, + }, + getMember: () => null, + hasEncryptionStateEvent: () => false, getJoinedMembers: () => [{}, {}], getMyMembership: () => "join", findEventById: () => makeEvent({ getId: () => "$sent" }), @@ -242,6 +269,19 @@ function makeRoom(overrides: Partial = {}): RoomLike { }; } +function makeRoomState(events: Record>): RoomStateLike { + return { + getStateEvents: (eventType: string, stateKey: string) => + stateKey === "" && events[eventType] ? makeStateEvent(events[eventType]) : null, + }; +} + +function makeRoomMembers( + members: Record +): RoomLike["getMember"] { + return (userId: string) => members[userId] ?? null; +} + function makeStateAdapter(initial: Record = {}): StateAdapter { const base = createMemoryState(); const ready = (async () => { @@ -646,21 +686,18 @@ describe("MatrixAdapter", () => { it("routes reactions to thread context when target event belongs to a thread", async () => { const fakeClient = makeClient(); - fakeClient.getRoom.mockReturnValue({ - roomId: "!room:beeper.com", - name: "Example Room", - timeline: [], - getJoinedMembers: () => [{}, {}], - getMyMembership: () => "join", - findEventById: (eventId?: string) => - eventId === "$target" - ? makeEvent({ - getId: () => "$target", - getRoomId: () => "!room:beeper.com", - threadRootId: "$root", - }) - : null, - }); + fakeClient.getRoom.mockReturnValue( + makeRoom({ + findEventById: (eventId?: string) => + eventId === "$target" + ? makeEvent({ + getId: () => "$target", + getRoomId: () => "!room:beeper.com", + threadRootId: "$root", + }) + : null, + }) + ); const processReaction = vi.fn(); const adapter = new MatrixAdapter({ @@ -809,6 +846,193 @@ describe("MatrixAdapter", () => { ); }); + it("parses Matrix formatted_body, strips reply fallback, and maps author metadata", async () => { + const fakeClient = makeClient(); + fakeClient.getRoom = vi.fn(() => + makeRoom({ + name: "Product Sync", + getMember: makeRoomMembers({ + "@alice:beeper.com": { + name: "Alice Example", + rawDisplayName: "Alice Example", + }, + }), + }) + ); + fakeClient.fetchRoomEvent = vi.fn(async () => + makeRawEvent({ + event_id: "$formatted", + sender: "@alice:beeper.com", + content: { + body: "> <@alice:beeper.com> replied\n> quoted\n\nHello @bot there", + msgtype: MsgType.Text, + format: "org.matrix.custom.html", + formatted_body: + '
quoted

Hello @bot there

', + "m.mentions": { + user_ids: ["@bot:beeper.com"], + }, + }, + }) + ); + + const adapter = await makeInitializedAdapter(fakeClient); + const message = await adapter.fetchMessage( + "matrix:!room%3Abeeper.com", + "$formatted" + ); + + expect(message).toBeTruthy(); + expect(message?.text).toBe("Hello @bot there"); + expect( + stringifyMarkdown(requireValue(message, "formatted message").formatted).trim() + ).toBe( + "Hello @bot **there**" + ); + expect(message?.isMention).toBe(true); + expect(message?.author).toMatchObject({ + userId: "@alice:beeper.com", + userName: "alice", + fullName: "Alice Example", + isBot: "unknown", + isMe: false, + }); + }); + + it("strips Matrix reply fallback from plain body text", async () => { + const fakeClient = makeClient(); + fakeClient.fetchRoomEvent = vi.fn(async () => + makeRawEvent({ + event_id: "$reply-body", + content: { + body: "> <@alice:beeper.com> replied\n> quoted line\n\nVisible reply body", + msgtype: MsgType.Text, + }, + }) + ); + + const adapter = await makeInitializedAdapter(fakeClient); + const message = await adapter.fetchMessage( + "matrix:!room%3Abeeper.com", + "$reply-body" + ); + + expect(message?.text).toBe("Visible reply body"); + expect( + stringifyMarkdown(requireValue(message, "reply body message").formatted).trim() + ).toBe( + "Visible reply body" + ); + }); + + it("surfaces editedAt from aggregated replacement metadata", async () => { + const fakeClient = makeClient(); + const editedAt = 1_700_000_123_000; + fakeClient.createMessagesRequest = vi.fn(async () => ({ + chunk: [ + makeRawEvent({ + event_id: "$edited", + content: { + body: "Original body", + msgtype: MsgType.Text, + }, + unsigned: { + "m.relations": { + "m.replace": { + content: { + "m.new_content": { + body: "Edited body", + msgtype: MsgType.Text, + }, + }, + origin_server_ts: editedAt, + }, + }, + }, + }), + ], + })); + + const adapter = await makeInitializedAdapter(fakeClient); + const result = await adapter.fetchMessages("matrix:!room%3Abeeper.com"); + + expect(result.messages).toHaveLength(1); + expect(result.messages[0]?.text).toBe("Edited body"); + expect(result.messages[0]?.metadata.edited).toBe(true); + expect(result.messages[0]?.metadata.editedAt).toEqual(new Date(editedAt)); + }); + + it("renders outbound markdown and mention placeholders as Matrix rich text", async () => { + const fakeClient = makeClient(); + const adapter = await makeInitializedAdapter(fakeClient); + + await adapter.postMessage("matrix:!room%3Abeeper.com", { + markdown: "Hello **team** <@@alice:beeper.com>", + }); + + expect(fakeClient.sendEvent).toHaveBeenCalledWith( + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + body: "Hello team @alice", + msgtype: MsgType.Text, + format: "org.matrix.custom.html", + formatted_body: expect.stringContaining("team"), + "m.mentions": { + user_ids: ["@alice:beeper.com"], + }, + }) + ); + expect(fakeClient.sendEvent).toHaveBeenCalledWith( + "!room:beeper.com", + EventType.RoomMessage, + expect.objectContaining({ + formatted_body: expect.stringContaining( + "https://matrix.to/#/%40alice%3Abeeper.com" + ), + }) + ); + }); + + it("enriches thread and channel metadata from Matrix room state", async () => { + const fakeClient = makeClient(); + fakeClient.getRoom = vi.fn(() => + makeRoom({ + name: "Adapter QA", + currentState: makeRoomState({ + "m.room.avatar": { url: "mxc://beeper.com/avatar" }, + "m.room.canonical_alias": { alias: "#adapter:beeper.com" }, + "m.room.encryption": { algorithm: "m.megolm.v1.aes-sha2" }, + "m.room.topic": { topic: "Adapter verification" }, + }), + hasEncryptionStateEvent: () => true, + }) + ); + + const adapter = await makeInitializedAdapter(fakeClient); + const thread = await adapter.fetchThread( + adapter.encodeThreadId({ + roomID: "!room:beeper.com", + rootEventID: "$root", + }) + ); + const channel = await adapter.fetchChannelInfo("matrix:!room%3Abeeper.com"); + + expect(thread.channelName).toBe("Adapter QA"); + expect(thread.metadata).toMatchObject({ + roomID: "!room:beeper.com", + canonicalAlias: "#adapter:beeper.com", + topic: "Adapter verification", + avatarURL: "mxc://beeper.com/avatar", + encrypted: true, + encryptionAlgorithm: "m.megolm.v1.aes-sha2", + isDM: false, + name: "Adapter QA", + }); + expect(channel.name).toBe("Adapter QA"); + expect(channel.metadata).toMatchObject(thread.metadata ?? {}); + }); + it("uploads file payloads and posts Matrix media events", async () => { const fakeClient = makeClient(); fakeClient.sendEvent = vi diff --git a/src/index.ts b/src/index.ts index 1f7c9a5..ccc5340 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ import { getEmoji, isCardElement, Message, + markdownToPlainText, parseMarkdown, stringifyMarkdown, } from "chat"; @@ -34,6 +35,7 @@ import sdk, { type IEvent, type IThreadBundledRelationship, type Room, + type RoomMember, ClientEvent, EventType, MsgType, @@ -43,14 +45,21 @@ import sdk, { ThreadFilterType, THREAD_RELATION_TYPE, } from "matrix-js-sdk"; -import { MatrixError } from "matrix-js-sdk/lib/http-api/errors"; -import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import type { RoomMessageEventContent, RoomMessageTextEventContent, } from "matrix-js-sdk/lib/@types/events"; import type { MediaEventContent } from "matrix-js-sdk/lib/@types/media"; +import { MatrixError } from "matrix-js-sdk/lib/http-api/errors"; +import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key"; import { logger as matrixSDKLogger } from "matrix-js-sdk/lib/logger"; +import { marked } from "marked"; +import { + HTMLElement, + NodeType, + parse as parseHTML, + type Node as HTMLNode, +} from "node-html-parser"; import type { MatrixAuthBootstrapClient, MatrixAccessTokenAuthConfig, @@ -87,6 +96,10 @@ type MatrixMessageContent = { format?: string; formatted_body?: string; msgtype?: string; + "m.mentions"?: { + room?: boolean; + user_ids?: string[]; + }; [key: string]: unknown; }; @@ -170,6 +183,33 @@ type MatrixMediaMsgType = | MsgType.Image | MsgType.Video; +type ParsedMatrixContent = { + markdown: string; + mentionsRoom: boolean; + mentionedUserIDs: Set; + text: string; +}; + +type RenderedMatrixMessage = { + body: string; + formattedBody?: string; + mentions?: { + room?: boolean; + user_ids?: string[]; + }; +}; + +type MatrixRoomMetadata = { + avatarURL?: string; + canonicalAlias?: string; + encrypted: boolean; + encryptionAlgorithm?: string; + isDM: boolean; + name?: string; + roomID: string; + topic?: string; +}; + // Intentionally unsupported in this adapter: postEphemeral, openModal, and native stream. export class MatrixAdapter implements Adapter { readonly name = "matrix"; @@ -645,31 +685,31 @@ export class MatrixAdapter implements Adapter { async fetchThread(threadId: string): Promise { const { roomID } = this.decodeThreadId(threadId); const room = this.requireRoom(roomID); + const isDM = await this.isDirectRoom(roomID); + const metadata = this.readRoomMetadata(room, isDM); return { id: threadId, channelId: this.channelIdFromThreadId(threadId), - channelName: room.name, - isDM: await this.isDirectRoom(roomID), - metadata: { - roomID, - }, + channelName: metadata.name, + isDM, + metadata, }; } async fetchChannelInfo(channelId: string): Promise { const roomID = this.decodeThreadId(channelId).roomID; const room = this.requireRoom(roomID); + const isDM = await this.isDirectRoom(roomID); + const metadata = this.readRoomMetadata(room, isDM); const members = room.getJoinedMembers(); return { id: channelId, - name: room.name, - isDM: await this.isDirectRoom(roomID), + name: metadata.name, + isDM, memberCount: members.length, - metadata: { - roomID, - }, + metadata, }; } @@ -736,24 +776,25 @@ export class MatrixAdapter implements Adapter { const threadID = overrideThreadID ?? this.threadIDForEvent(raw, roomID); const content = raw.getContent(); - const editedContent = this.extractEditedContent(raw); - const effectiveContent = editedContent ?? content; - const text = this.extractText(effectiveContent); + const edited = this.extractEditedContent(raw); + const effectiveContent = edited?.content ?? content; + const parsed = this.parseMatrixContent(effectiveContent); const sender = raw.getSender() ?? "unknown"; return new Message({ id: raw.getId() ?? `${roomID}:${raw.getTs()}`, threadId: threadID, - text, - formatted: parseMarkdown(text), - author: this.makeUser(sender), + text: parsed.text, + formatted: parseMarkdown(parsed.markdown), + author: this.makeUser(sender, roomID), metadata: { dateSent: new Date(raw.getTs()), - edited: this.isEdited(raw) || Boolean(editedContent), + edited: this.isEdited(raw) || Boolean(edited?.content), + editedAt: edited?.editedAt, }, attachments: this.extractAttachments(effectiveContent), raw, - isMention: this.isMentioned(effectiveContent, text), + isMention: this.isMentioned(effectiveContent, parsed), }); } @@ -1343,16 +1384,11 @@ export class MatrixAdapter implements Adapter { const attachments = this.extractAttachmentsFromMessage(message); const uploads = await this.collectUploads(message, attachments); const linkLines = this.collectLinkOnlyAttachmentLines(attachments); - const textBody = this.mergeTextAndLinks(textContent.body ?? "", linkLines); - const textMsgType = textContent.msgtype ?? MsgType.Text; + const textBody = this.mergeTextAndLinks(textContent, linkLines); const contents: MatrixOutboundMessageContent[] = []; - if (textBody.length > 0) { - contents.push({ - ...textContent, - body: textBody, - msgtype: textMsgType, - }); + if ((normalizeOptionalString(textBody.body) ?? "").length > 0) { + contents.push(textBody); } const uploadedContents = await Promise.all( @@ -1800,14 +1836,321 @@ export class MatrixAdapter implements Adapter { return this.encodeThreadId({ roomID, rootEventID }); } - private extractText(content: MatrixMessageContent): string { - if (typeof content.body === "string") { - return content.body; + private readRoomMetadata(room: Room, isDM: boolean): MatrixRoomMetadata { + const canonicalAlias = normalizeOptionalString( + this.readStateEventString(room, "m.room.canonical_alias", "alias") + ); + const topic = normalizeOptionalString( + this.readStateEventString(room, "m.room.topic", "topic") + ); + const avatarURL = normalizeOptionalString( + this.readStateEventString(room, "m.room.avatar", "url") + ); + const encryption = this.readStateEventContent(room, "m.room.encryption"); + const encryptionAlgorithm = readStringValue(encryption?.algorithm); + const encrypted = this.roomHasEncryptionStateEvent(room) ?? Boolean(encryptionAlgorithm); + + return { + roomID: room.roomId, + name: normalizeOptionalString(room.name) ?? canonicalAlias, + canonicalAlias, + topic, + avatarURL, + encrypted, + encryptionAlgorithm, + isDM, + }; + } + + private readStateEventContent( + room: Room, + eventType: string + ): Record | undefined { + const event = this.readRoomStateEvent(room, eventType); + if (!event) { + return undefined; } - if (typeof content.formatted_body === "string") { - return content.formatted_body; + const content = event.getContent>(); + return isRecord(content) ? content : undefined; + } + + private readStateEventString( + room: Room, + eventType: string, + key: string + ): string | undefined { + const content = this.readStateEventContent(room, eventType); + return readStringValue(content?.[key]); + } + + private readRoomStateEvent(room: Room, eventType: string): MatrixEvent | undefined { + if (!("currentState" in room) || !room.currentState) { + return undefined; } - return ""; + + const { currentState } = room; + if ( + typeof currentState !== "object" || + !("getStateEvents" in currentState) || + typeof currentState.getStateEvents !== "function" + ) { + return undefined; + } + + return currentState.getStateEvents(eventType, "") ?? undefined; + } + + private roomHasEncryptionStateEvent(room: Room): boolean | undefined { + if ( + !("hasEncryptionStateEvent" in room) || + typeof room.hasEncryptionStateEvent !== "function" + ) { + return undefined; + } + + return room.hasEncryptionStateEvent(); + } + + private readRoomMember(room: Room | undefined, userId: string): RoomMember | undefined { + if (!room || !("getMember" in room) || typeof room.getMember !== "function") { + return undefined; + } + + return room.getMember(userId) ?? undefined; + } + + private parseMatrixContent(content: MatrixMessageContent): ParsedMatrixContent { + const mentionedUserIDs = this.extractMentionedUserIDs(content); + const mentionsRoom = this.extractRoomMention(content); + const formattedBody = normalizeOptionalString(content.formatted_body); + if (formattedBody) { + const htmlMarkdown = this.parseMatrixHTML(formattedBody); + for (const mentionedUserID of htmlMarkdown.mentionedUserIDs) { + mentionedUserIDs.add(mentionedUserID); + } + + if (htmlMarkdown.markdown.length > 0) { + return { + text: markdownToPlainText(htmlMarkdown.markdown), + markdown: htmlMarkdown.markdown, + mentionedUserIDs, + mentionsRoom, + }; + } + } + + const body = this.stripReplyFallbackFromBody( + normalizeOptionalString(content.body) ?? "" + ); + return { + text: body, + markdown: this.markdownForPlainText(body, content.msgtype), + mentionedUserIDs, + mentionsRoom, + }; + } + + private parseMatrixHTML( + html: string + ): { markdown: string; mentionedUserIDs: Set } { + const root = parseHTML(this.stripReplyFallbackFromHTML(html)); + const mentionedUserIDs = new Set(); + const markdown = this.normalizeMarkdownSpacing( + this.renderHTMLNodesToMarkdown(root.childNodes, mentionedUserIDs) + ); + return { + markdown, + mentionedUserIDs, + }; + } + + private renderHTMLNodesToMarkdown( + nodes: HTMLNode[], + mentionedUserIDs: Set + ): string { + return nodes + .map((node) => this.renderHTMLNodeToMarkdown(node, mentionedUserIDs)) + .join(""); + } + + private renderHTMLNodeToMarkdown( + node: HTMLNode, + mentionedUserIDs: Set + ): string { + if (node.nodeType === NodeType.TEXT_NODE) { + return node.text; + } + + if (!(node instanceof HTMLElement)) { + return ""; + } + + const tagName = node.tagName.toLowerCase(); + const children = this.renderHTMLNodesToMarkdown(node.childNodes, mentionedUserIDs); + + switch (tagName) { + case "mx-reply": + return ""; + case "html": + case "body": + case "span": + return children; + case "br": + return "\n"; + case "p": + case "div": + return children.trim() ? `${children.trim()}\n\n` : ""; + case "strong": + case "b": + return children ? `**${children}**` : ""; + case "em": + case "i": + return children ? `*${children}*` : ""; + case "del": + case "s": + return children ? `~~${children}~~` : ""; + case "code": + return node.parentNode instanceof HTMLElement && + node.parentNode.tagName.toLowerCase() === "pre" + ? children + : `\`${children}\``; + case "pre": { + const codeContent = children.replace(/\n+$/u, ""); + return codeContent ? `\n\`\`\`\n${codeContent}\n\`\`\`\n\n` : ""; + } + case "blockquote": { + const quoted = children.trim(); + if (!quoted) { + return ""; + } + return `${quoted + .split("\n") + .map((line) => `> ${line}`) + .join("\n")}\n\n`; + } + case "ul": + return `${node.childNodes + .map((child) => this.renderListItemToMarkdown(child, mentionedUserIDs, null)) + .filter(Boolean) + .join("\n")}\n\n`; + case "ol": + return `${node.childNodes + .map((child, index) => + this.renderListItemToMarkdown(child, mentionedUserIDs, index + 1) + ) + .filter(Boolean) + .join("\n")}\n\n`; + case "a": + return this.renderHTMLLinkToMarkdown(node, children, mentionedUserIDs); + case "img": + return normalizeOptionalString(node.getAttribute("alt")) ?? "image"; + default: + return children; + } + } + + private renderListItemToMarkdown( + node: HTMLNode, + mentionedUserIDs: Set, + ordinal: number | null + ): string { + if (!(node instanceof HTMLElement) || node.tagName.toLowerCase() !== "li") { + return ""; + } + const content = this.normalizeMarkdownSpacing( + this.renderHTMLNodesToMarkdown(node.childNodes, mentionedUserIDs) + ); + if (!content) { + return ""; + } + return `${ordinal === null ? "-" : `${ordinal}.`} ${content}`; + } + + private renderHTMLLinkToMarkdown( + node: HTMLElement, + children: string, + mentionedUserIDs: Set + ): string { + const href = normalizeOptionalString(node.getAttribute("href")); + const text = children || node.text; + if (!href) { + return text; + } + + const mentionedUserID = this.parseMatrixToUserID(href); + if (mentionedUserID) { + mentionedUserIDs.add(mentionedUserID); + return text || this.matrixMentionDisplayText(mentionedUserID); + } + + return `[${text || href}](${href})`; + } + + private parseMatrixToUserID(href: string): string | null { + let url: URL; + try { + url = new URL(href); + } catch { + return null; + } + + if (url.hostname !== "matrix.to") { + return null; + } + + const rawPath = url.hash.startsWith("#/") ? url.hash.slice(2) : url.hash; + const firstSegment = rawPath.split("/")[0]; + if (!firstSegment) { + return null; + } + + const identifier = decodeURIComponent(firstSegment); + return identifier.startsWith("@") ? identifier : null; + } + + private extractMentionedUserIDs(content: MatrixMessageContent): Set { + const mentions = new Set(); + const matrixMentions = content["m.mentions"]; + if (!isRecord(matrixMentions) || !Array.isArray(matrixMentions.user_ids)) { + return mentions; + } + + for (const userID of matrixMentions.user_ids) { + if (typeof userID === "string" && userID.length > 0) { + mentions.add(userID); + } + } + + return mentions; + } + + private extractRoomMention(content: MatrixMessageContent): boolean { + const matrixMentions = content["m.mentions"]; + return isRecord(matrixMentions) && matrixMentions.room === true; + } + + private stripReplyFallbackFromBody(body: string): string { + const lines = body.split("\n"); + let index = 0; + while (index < lines.length && lines[index]?.startsWith(">")) { + index += 1; + } + + if (index === 0 || index >= lines.length || lines[index] !== "") { + return body; + } + + return lines.slice(index + 1).join("\n"); + } + + private stripReplyFallbackFromHTML(html: string): string { + const root = parseHTML(html); + for (const child of [...root.childNodes]) { + if (child instanceof HTMLElement && child.tagName.toLowerCase() === "mx-reply") { + child.remove(); + } + } + return root.toString(); } private extractAttachments(content: MatrixMessageContent) { @@ -1832,9 +2175,13 @@ export class MatrixAdapter implements Adapter { return [attachment]; } - private extractEditedContent(raw: MatrixEvent): MatrixMessageContent | undefined { + private extractEditedContent(raw: MatrixEvent): { + content?: MatrixMessageContent; + editedAt?: Date; + } | undefined { const replacement = raw.getServerAggregatedRelation<{ content?: MatrixRoomMessageContent; + origin_server_ts?: number; }>(RelationType.Replace); const replacementContent = isRecord(replacement?.content) ? replacement.content @@ -1843,7 +2190,17 @@ export class MatrixAdapter implements Adapter { ? replacementContent["m.new_content"] : undefined; - return newContent; + if (!newContent) { + return undefined; + } + + return { + content: newContent, + editedAt: + typeof replacement?.origin_server_ts === "number" + ? new Date(replacement.origin_server_ts) + : undefined, + }; } private attachmentTypeForContent( @@ -1945,12 +2302,19 @@ export class MatrixAdapter implements Adapter { return relation?.rel_type === RelationType.Replace; } - private isMentioned(content: MatrixMessageContent, text: string): boolean { + private isMentioned(content: MatrixMessageContent, parsed: ParsedMatrixContent): boolean { + if (parsed.mentionsRoom) { + return true; + } + if (this.userID && parsed.mentionedUserIDs.has(this.userID)) { + return true; + } + const formatted = typeof content.formatted_body === "string" ? content.formatted_body : ""; const hasUserID = this.userID - ? text.includes(this.userID) || formatted.includes(this.userID) + ? parsed.text.includes(this.userID) || formatted.includes(this.userID) : false; const hasMatrixTo = this.userID ? formatted.includes(`matrix.to/#/${encodeURIComponent(this.userID)}`) @@ -1961,7 +2325,7 @@ export class MatrixAdapter implements Adapter { : `@${this.userName}`; const hasUserName = - text.includes(usernameMention) || formatted.includes(usernameMention); + parsed.text.includes(usernameMention) || formatted.includes(usernameMention); return hasUserID || hasMatrixTo || hasUserName; } @@ -1990,52 +2354,193 @@ export class MatrixAdapter implements Adapter { private toRoomMessageContent( message: AdapterPostableMessage ): MatrixTextMessageContent { - const body = this.toText(message); - - return { - body, + const rendered = this.renderTextMessage(message); + const content: MatrixTextMessageContent = { + body: rendered.body, msgtype: MsgType.Text, }; + if (rendered.formattedBody) { + content.format = "org.matrix.custom.html"; + content.formatted_body = rendered.formattedBody; + } + if (rendered.mentions) { + content["m.mentions"] = rendered.mentions; + } + + return content; } - private toText(message: AdapterPostableMessage): string { + private renderTextMessage(message: AdapterPostableMessage): RenderedMatrixMessage { if (typeof message === "string") { - return message; + return this.renderPlainTextMessage(message); } if (isCardElement(message)) { - return "[Card message]"; + return this.renderPlainTextMessage("[Card message]"); } if (typeof message === "object" && message !== null) { if ("raw" in message && typeof message.raw === "string") { - return message.raw; + return this.renderPlainTextMessage(message.raw); } if ("markdown" in message && typeof message.markdown === "string") { - return message.markdown; + return this.renderMarkdownMessage(message.markdown); } if ("ast" in message) { - return stringifyMarkdown(message.ast); + return this.renderMarkdownMessage(stringifyMarkdown(message.ast)); } if ("card" in message) { - return message.fallbackText ?? "[Card message]"; + return this.renderPlainTextMessage(message.fallbackText ?? "[Card message]"); + } + } + + return { body: "" }; + } + + private renderPlainTextMessage(text: string): RenderedMatrixMessage { + const rendered = this.replaceMentionPlaceholdersInPlainText(text); + if (rendered.mentionedUserIDs.size === 0) { + return { + body: rendered.body, + }; + } + + return { + body: rendered.body, + formattedBody: this.renderMarkdownToMatrixHTML(rendered.markdown), + mentions: this.buildMentionsContent(rendered.mentionedUserIDs), + }; + } + + private renderMarkdownMessage(markdown: string): RenderedMatrixMessage { + const rendered = this.replaceMentionPlaceholdersInMarkdown(markdown); + return { + body: markdownToPlainText(rendered.markdown), + formattedBody: this.renderMarkdownToMatrixHTML(rendered.markdown), + mentions: this.buildMentionsContent(rendered.mentionedUserIDs), + }; + } + + private replaceMentionPlaceholdersInPlainText(text: string): { + body: string; + markdown: string; + mentionedUserIDs: Set; + } { + const mentionedUserIDs = new Set(); + const pattern = /<@(@[^>\s]+:[^>\s]+)>/gu; + let body = ""; + let markdown = ""; + let lastIndex = 0; + + for (const match of text.matchAll(pattern)) { + const [token, userID] = match; + const index = match.index ?? 0; + const plainSegment = text.slice(lastIndex, index); + body += plainSegment; + markdown += escapeMarkdownText(plainSegment); + + const mentionText = this.matrixMentionDisplayText(userID); + body += mentionText; + markdown += `[${escapeMarkdownLinkText(mentionText)}](${this.matrixToUserLink(userID)})`; + mentionedUserIDs.add(userID); + lastIndex = index + token.length; + } + + const trailing = text.slice(lastIndex); + body += trailing; + markdown += escapeMarkdownText(trailing); + + return { body, markdown, mentionedUserIDs }; + } + + private replaceMentionPlaceholdersInMarkdown(markdown: string): { + markdown: string; + mentionedUserIDs: Set; + } { + const mentionedUserIDs = new Set(); + const transformed = markdown.replace( + /<@(@[^>\s]+:[^>\s]+)>/gu, + (_match, userID: string) => { + mentionedUserIDs.add(userID); + return `[${escapeMarkdownLinkText(this.matrixMentionDisplayText(userID))}](${this.matrixToUserLink( + userID + )})`; } + ); + + return { + markdown: transformed, + mentionedUserIDs, + }; + } + + private renderMarkdownToMatrixHTML(markdown: string): string { + return marked.parse(markdown, { + async: false, + breaks: true, + gfm: true, + }); + } + + private buildMentionsContent( + mentionedUserIDs: Set + ): { room?: boolean; user_ids?: string[] } | undefined { + if (mentionedUserIDs.size === 0) { + return undefined; + } + + return { + user_ids: [...mentionedUserIDs], + }; + } + + private matrixToUserLink(userID: string): string { + return `https://matrix.to/#/${encodeURIComponent(userID)}`; + } + + private matrixMentionDisplayText(userID: string): string { + return `@${matrixLocalpart(userID)}`; + } + + private markdownForPlainText(text: string, msgtype?: string): string { + const escaped = escapeMarkdownText(text); + if (msgtype === "m.emote" && escaped.length > 0) { + return `*${escaped}*`; } + return escaped; + } - return ""; + private normalizeMarkdownSpacing(markdown: string): string { + return markdown.replace(/\n{3,}/gu, "\n\n").trim(); } - private mergeTextAndLinks(text: string, linkLines: string[]): string { + private mergeTextAndLinks( + content: MatrixTextMessageContent, + linkLines: string[] + ): MatrixTextMessageContent { if (linkLines.length === 0) { - return text; + return content; } const suffix = linkLines.join("\n"); - if (!text) { - return suffix; + const body = content.body ?? ""; + const mergedBody = body ? `${body}\n\n${suffix}` : suffix; + if (!content.formatted_body) { + return { + ...content, + body: mergedBody, + }; } - return `${text}\n\n${suffix}`; + const formattedSuffix = linkLines + .map((line) => `

${escapeHTML(line)}

`) + .join(""); + + return { + ...content, + body: mergedBody, + formatted_body: `${content.formatted_body}${formattedSuffix}`, + }; } private collectLinkOnlyAttachmentLines(attachments: Attachment[]): string[] { @@ -2202,12 +2707,21 @@ export class MatrixAdapter implements Adapter { return event; } - private makeUser(userId: string) { + private makeUser(userId: string, roomId?: string) { + const room = roomId ? this.client?.getRoom(roomId) ?? undefined : undefined; + const member = this.readRoomMember(room, userId); + const localpart = matrixLocalpart(userId); + const displayName = + readStringValue(member?.rawDisplayName) ?? + readStringValue(member?.name) ?? + localpart; + const isBot: boolean | "unknown" = userId === this.userID ? true : "unknown"; + return { userId, - userName: userId, - fullName: userId, - isBot: userId === this.userID, + userName: localpart, + fullName: displayName, + isBot, isMe: userId === this.userID, }; } @@ -2366,7 +2880,7 @@ export class MatrixAdapter implements Adapter { } try { - const privateKey = decodeRecoveryKey(this.recoveryKey); + const privateKey = decodeRecoveryKey(this.recoveryKey) as Uint8Array; return [keyID, privateKey]; } catch { this.logger.warn("Invalid recovery key format, unable to decode"); @@ -2676,6 +3190,31 @@ function normalizeOptionalString(value: string | null | undefined): string | und return trimmed.length > 0 ? trimmed : undefined; } +function readStringValue(value: unknown): string | undefined { + return typeof value === "string" ? normalizeOptionalString(value) : undefined; +} + +function matrixLocalpart(userID: string): string { + return userID.startsWith("@") ? userID.slice(1).split(":")[0] ?? userID : userID; +} + +function escapeMarkdownText(value: string): string { + return value.replace(/([\\`*_{}\[\]()#+\-.!|>])/gu, "\\$1"); +} + +function escapeMarkdownLinkText(value: string): string { + return value.replace(/([\\\]])/gu, "\\$1"); +} + +function escapeHTML(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + function normalizeStringList(values: string[] | undefined): string[] { if (!values || values.length === 0) { return []; From 00b7b57a5b22099ef71580daad6de0dbab022c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 7 Mar 2026 16:57:44 +0100 Subject: [PATCH 16/25] Clean up bun lockfile --- bun.lock | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bun.lock b/bun.lock index cc446e9..02ab235 100644 --- a/bun.lock +++ b/bun.lock @@ -195,8 +195,6 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], - "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], @@ -281,8 +279,6 @@ "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], - "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], - "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], From f03a3131936fc82c51ee2c2a9a1f3976752ec8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sat, 7 Mar 2026 17:44:53 +0100 Subject: [PATCH 17/25] Fix Matrix adapter review findings --- e2e/e2e.test.ts | 19 ++++--- package.json | 2 +- src/index.test.ts | 101 ++++++++++++++++++++++++++++++++++ src/index.ts | 134 ++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 239 insertions(+), 17 deletions(-) diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index 459cc5d..0815e7a 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -18,15 +18,17 @@ import { waitForRoom, } from "./helpers"; -const hasCredentials = Boolean( +const hasCoreCredentials = Boolean( process.env.E2E_BASE_URL && process.env.E2E_BOT_LOGIN_TOKEN && - process.env.E2E_BOT_RECOVERY_KEY && - process.env.E2E_SENDER_LOGIN_TOKEN && - process.env.E2E_SENDER_RECOVERY_KEY + process.env.E2E_SENDER_LOGIN_TOKEN ); -describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { +const hasRecoveryCredentials = Boolean( + process.env.E2E_BOT_RECOVERY_KEY && process.env.E2E_SENDER_RECOVERY_KEY +); + +describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { let bot: E2EParticipant; let sender: E2EParticipant; let roomID: string; @@ -701,7 +703,9 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect(fetchedAttachmentData?.toString("utf8")).toBe(expectedContents); }); - it("restores historical encrypted messages on a fresh device using recovery key", async () => { + it.skipIf(!hasRecoveryCredentials)( + "restores historical encrypted messages on a fresh device using recovery key", + async () => { await shutdownParticipant(sender); const tag = `e2e-recovery-${nonce()}`; @@ -731,7 +735,8 @@ describe.skipIf(!hasCredentials)("E2E Matrix Adapter", () => { expect(restoredMessage.text).toContain(tag); expect(restoredMessage.author.userId).toBe(bot.userID); expect(restoredMessage.raw.isEncrypted()).toBe(true); - }); + } + ); it("emits typing notifications", async () => { const threadId = bot.adapter.encodeThreadId({ roomID }); diff --git a/package.json b/package.json index f82a187..5aac7c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/chat-adapter-matrix", - "version": "0.2.0", + "version": "1.0.0", "description": "Matrix adapter for chat", "type": "module", "main": "./dist/index.js", diff --git a/src/index.test.ts b/src/index.test.ts index 38a4207..db5434b 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1084,6 +1084,53 @@ describe("MatrixAdapter", () => { ); }); + it("skips invalid file uploads instead of passing malformed entries downstream", async () => { + const fakeClient = makeClient(); + const logger = makeTestLogger(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + logger: logger.logger, + createClient: () => asMatrixClient(fakeClient), + }); + await adapter.initialize(makeChatInstance()); + + await adapter.postMessage("matrix:!room%3Abeeper.com", { + files: [ + { data: new Uint8Array([1, 2, 3]), filename: "valid.bin" }, + { data: new Uint8Array([4, 5, 6]), filename: " " }, + { data: "not-binary", filename: "invalid.txt" }, + ], + } as never); + + expect(fakeClient.uploadContent).toHaveBeenCalledTimes(1); + expect(fakeClient.uploadContent).toHaveBeenCalledWith( + expect.any(Blob), + expect.objectContaining({ name: "valid.bin" }) + ); + expect(logger.warn).toHaveBeenCalledWith("Skipping invalid Matrix file upload", { + filename: "invalid.txt", + }); + }); + + it("falls back to a synthetic MatrixEvent when a sent event is not yet in the timeline", async () => { + const fakeClient = makeClient(); + fakeClient.getRoom.mockReturnValue( + makeRoom({ + findEventById: () => null, + }) + ); + fakeClient.sendEvent.mockResolvedValue({ event_id: "$missing" }); + + const adapter = await makeInitializedAdapter(fakeClient); + const sent = await adapter.postMessage("matrix:!room%3Abeeper.com", "hello"); + + expect(sent.id).toBe("$missing"); + expect(sent.raw.getId()).toBe("$missing"); + expect(sent.raw.getRoomId()).toBe("!room:beeper.com"); + expect(sent.raw.getContent()).toMatchObject({ body: "hello", msgtype: "m.text" }); + }); + it("logs and rethrows Matrix send failures", async () => { const fakeClient = makeClient(); const logger = makeTestLogger(); @@ -1455,6 +1502,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", + userID: "@bot:beeper.com", }, deviceID: "DEVICE_FALLBACK", createBootstrapClient: () => @@ -1962,6 +2010,59 @@ describe("MatrixAdapter", () => { expect(fakeClient.createRoom).not.toHaveBeenCalled(); }); + it("openDM clears stale cached mappings before creating a fresh DM", async () => { + const fakeClient = makeClient(); + fakeClient.getRoom.mockImplementation((roomID?: string) => { + if (roomID === "!stale-dm:beeper.com") { + return null; + } + return makeRoom({ roomId: roomID ?? "!room:beeper.com" }); + }); + fakeClient.getAccountDataFromServer.mockResolvedValue({}); + fakeClient.createRoom.mockResolvedValue({ room_id: "!fresh-dm:beeper.com" }); + + const state = makeStateAdapter({ + "matrix:dm:%40bob%3Abeeper.com": "!stale-dm:beeper.com", + }); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance({ state })); + const threadId = await adapter.openDM("@bob:beeper.com"); + + expect(threadId).toBe("matrix:!fresh-dm%3Abeeper.com"); + expect(state.delete).toHaveBeenCalledWith("matrix:dm:%40bob%3Abeeper.com"); + expect(fakeClient.createRoom).toHaveBeenCalledOnce(); + }); + + it("merges fresh m.direct account data before persisting a newly created DM", async () => { + const fakeClient = makeClient(); + fakeClient.getAccountDataFromServer + .mockResolvedValueOnce({}) + .mockResolvedValueOnce({ + "@bob:beeper.com": ["!existing-dm:beeper.com"], + "@carol:beeper.com": ["!carol-dm:beeper.com"], + }); + fakeClient.createRoom.mockResolvedValue({ room_id: "!new-dm:beeper.com" }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + createClient: () => asMatrixClient(fakeClient), + }); + + await adapter.initialize(makeChatInstance({ state: makeStateAdapter() })); + await adapter.openDM("@bob:beeper.com"); + + expect(fakeClient.setAccountData).toHaveBeenCalledWith(EventType.Direct, { + "@bob:beeper.com": ["!existing-dm:beeper.com", "!new-dm:beeper.com"], + "@carol:beeper.com": ["!carol-dm:beeper.com"], + }); + }); + it("lists threads using server-side thread API with mxv1 cursor", async () => { const fakeClient = makeClient(); fakeClient.createThreadListMessagesRequest.mockResolvedValue({ diff --git a/src/index.ts b/src/index.ts index ccc5340..6421cf4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,8 +30,8 @@ import { } from "chat"; import sdk, { Direction, + MatrixEvent, type MatrixClient, - type MatrixEvent, type IEvent, type IThreadBundledRelationship, type Room, @@ -428,7 +428,11 @@ export class MatrixAdapter implements Adapter { return { id: response.event_id, threadId, - raw: this.mustGetEventByID(roomID, response.event_id), + raw: this.resolveSentEvent(roomID, response.event_id, { + content: firstContent, + roomID, + sender: this.userID, + }), }; } @@ -468,7 +472,11 @@ export class MatrixAdapter implements Adapter { return { id: response.event_id, threadId, - raw: this.mustGetEventByID(roomID, response.event_id), + raw: this.resolveSentEvent(roomID, response.event_id, { + content: editContent, + roomID, + sender: this.userID, + }), }; } @@ -557,7 +565,10 @@ export class MatrixAdapter implements Adapter { async openDM(userId: string): Promise { const cachedRoomID = await this.loadPersistedDMRoomID(userId); if (cachedRoomID) { - return this.encodeThreadId({ roomID: cachedRoomID }); + if (this.isUsableDirectRoom(cachedRoomID)) { + return this.encodeThreadId({ roomID: cachedRoomID }); + } + await this.clearPersistedDMRoomID(userId); } const direct = await this.loadDirectAccountData(); @@ -1076,6 +1087,14 @@ export class MatrixAdapter implements Adapter { return normalized ?? null; } + private async clearPersistedDMRoomID(userID: string): Promise { + if (!this.stateAdapter) { + return; + } + + await this.stateAdapter.delete(this.getDMStorageKey(userID)); + } + private async persistDMRoomID(userID: string, roomID: string): Promise { if (!this.stateAdapter) { return; @@ -1177,14 +1196,31 @@ export class MatrixAdapter implements Adapter { roomID: string, existing: DirectAccountData ): Promise { - const updated: DirectAccountData = { ...existing }; - const existingRooms = updated[userID] ?? []; + const latest = await this.loadLatestDirectAccountData(existing); + const existingRooms = latest[userID] ?? []; if (!existingRooms.includes(roomID)) { - updated[userID] = [...existingRooms, roomID]; + const updated: DirectAccountData = { + ...latest, + [userID]: [...existingRooms, roomID], + }; await this.requireClient().setAccountData(EventType.Direct, updated); } } + private async loadLatestDirectAccountData( + fallback: DirectAccountData + ): Promise { + try { + const direct = await this.requireClient().getAccountDataFromServer(EventType.Direct); + return this.normalizeDirectAccountData(direct); + } catch (error) { + this.logger.debug("Failed to refresh m.direct before writing; using cached snapshot", { + error, + }); + return fallback; + } + } + private async resolveAuth(): Promise { if (this.auth.type === "accessToken") { const whoami = await this.lookupWhoAmIFromAccessToken(this.auth.accessToken); @@ -1195,7 +1231,7 @@ export class MatrixAdapter implements Adapter { deviceID: whoami.deviceID ?? this.deviceID, }; await Promise.all([ - this.persistDeviceIDForResolvedUser(userID), + this.persistDeviceIDForResolvedUser(userID, resolved.deviceID), this.persistSession(resolved), ]); return resolved; @@ -2610,7 +2646,48 @@ export class MatrixAdapter implements Adapter { if (!("files" in message) || !Array.isArray(message.files)) { return []; } - return message.files.filter((file): file is FileUpload => isRecord(file)); + return message.files.flatMap((file): FileUpload[] => { + const normalized = this.normalizeFileUpload(file); + return normalized ? [normalized] : []; + }); + } + + private normalizeFileUpload(file: unknown): FileUpload | null { + if (!isRecord(file)) { + return null; + } + + const filename = normalizeOptionalString( + typeof file.filename === "string" ? file.filename : undefined + ); + if (!filename) { + return null; + } + + const data = this.normalizeFileUploadData(file.data); + if (!data) { + this.logger.warn("Skipping invalid Matrix file upload", { filename }); + return null; + } + + return { + filename, + data, + mimeType: + typeof file.mimeType === "string" ? normalizeOptionalString(file.mimeType) : undefined, + }; + } + + private normalizeFileUploadData(data: unknown): Buffer | Blob | ArrayBuffer | null { + if (Buffer.isBuffer(data) || data instanceof Blob || data instanceof ArrayBuffer) { + return data; + } + + if (data instanceof Uint8Array) { + return Buffer.from(data); + } + + return null; } private extractAttachmentsFromMessage(message: AdapterPostableMessage): Attachment[] { @@ -2707,6 +2784,45 @@ export class MatrixAdapter implements Adapter { return event; } + private resolveSentEvent( + roomID: string, + eventID: string, + fallback: { + content: MatrixRoomMessageContent; + roomID: string; + sender?: string; + } + ): MatrixEvent { + try { + return this.mustGetEventByID(roomID, eventID); + } catch (error) { + this.logger.warn("Sent Matrix event not found in local timeline; using synthetic fallback", { + error, + eventId: eventID, + roomId: roomID, + }); + + return new MatrixEvent({ + content: fallback.content, + event_id: eventID, + origin_server_ts: Date.now(), + room_id: fallback.roomID, + sender: fallback.sender ?? "", + type: EventType.RoomMessage, + }); + } + } + + private isUsableDirectRoom(roomID: string): boolean { + const room = this.requireClient().getRoom(roomID); + if (!room) { + return false; + } + + const membership = room.getMyMembership(); + return membership === "join" || membership === "invite"; + } + private makeUser(userId: string, roomId?: string) { const room = roomId ? this.client?.getRoom(roomId) ?? undefined : undefined; const member = this.readRoomMember(room, userId); From 482ccf581c7502676c82239a53f687bc8a08ef2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:41:44 +0100 Subject: [PATCH 18/25] Implement API-first Matrix history store and switch to pnpm - add Matrix chat-state store and tests to support parity-focused history handling - update adapter/types/tests for the new state flow - migrate project and CI/publish workflows to pnpm on Node 22 --- .github/workflows/ci.yml | 17 +- .github/workflows/publish.yml | 19 +- README.md | 8 +- bun.lock | 818 --- e2e/e2e.test.ts | 2 +- e2e/helpers.ts | 66 +- package-lock.json | 5762 --------------------- package.json | 17 +- pnpm-lock.yaml | 3544 +++++++++++++ scripts/get-access-token.ts | 4 +- src/index.test.ts | 140 + src/index.ts | 263 +- src/store/chat-state-matrix-store.test.ts | 248 + src/store/chat-state-matrix-store.ts | 514 ++ src/types.ts | 27 +- 15 files changed, 4826 insertions(+), 6623 deletions(-) delete mode 100644 bun.lock delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 src/store/chat-state-matrix-store.test.ts create mode 100644 src/store/chat-state-matrix-store.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cc2511..3028730 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,20 +17,25 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.30.3 + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 - cache: npm + node-version: 22 + cache: pnpm - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Typecheck - run: npm run typecheck + run: pnpm typecheck - name: Build - run: npm run build + run: pnpm build - name: Test - run: npm test + run: pnpm test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b56ef7a..8cfc18d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,24 +20,29 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.30.3 + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 - cache: npm + node-version: 22 + cache: pnpm registry-url: https://registry.npmjs.org - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Typecheck - run: npm run typecheck + run: pnpm typecheck - name: Build - run: npm run build + run: pnpm build - name: Test - run: npm test + run: pnpm test - name: Verify tag matches package version if: startsWith(github.ref, 'refs/tags/v') @@ -50,6 +55,6 @@ jobs: fi - name: Publish to npm - run: npm publish --provenance --access public + run: pnpm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 230a175..9d8cf0e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ If you use Beeper, this adapter lets your Chat SDK bot work with your Matrix/Bee ## Installation ```bash -npm install chat @beeper/chat-adapter-matrix matrix-js-sdk +pnpm add chat @beeper/chat-adapter-matrix matrix-js-sdk ``` ## Quick Start @@ -109,13 +109,13 @@ Behavior: Copy [`examples/.env.example`](./examples/.env.example) to `examples/.env`, then run: ```bash -npm run example:bun +pnpm example ``` If you need Beeper credentials, generate them interactively: ```bash -npm run token:bun +pnpm matrix-token ``` The helper prints: @@ -128,7 +128,7 @@ The helper prints: Then run: ```bash -bun --env-file=examples/.env run examples/bot.ts +pnpm example ``` ## Capabilities diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 02ab235..0000000 --- a/bun.lock +++ /dev/null @@ -1,818 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 0, - "workspaces": { - "": { - "name": "@beeper/chat-adapter-matrix", - "dependencies": { - "@chat-adapter/state-memory": "^4.16.1", - "@chat-adapter/state-redis": "^4.16.1", - "chat": "^4.16.1", - "marked": "^15.0.12", - "matrix-js-sdk": "^41.0.0", - "node-html-parser": "^7.1.0", - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^22.10.2", - "@vitest/coverage-v8": "^2.1.8", - "eslint": "^10.0.2", - "tsup": "^8.3.5", - "typescript": "^5.7.2", - "typescript-eslint": "^8.56.1", - "vitest": "^2.1.8", - }, - }, - }, - "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], - - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - - "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.16.1", "", { "dependencies": { "chat": "4.16.1" } }, "sha512-G/7MNxqqI19IRUUoaitSGWs0Tx+FAcvnB3rLfpWmV4eaUDzzhF+DEtmmR+DVxpz35RxBM3Y4Gi6Wqz5Ysfiytg=="], - - "@chat-adapter/state-redis": ["@chat-adapter/state-redis@4.16.1", "", { "dependencies": { "chat": "4.16.1", "redis": "^5.11.0" } }, "sha512-weXm3OweMb7/hejHdzyaSvBLjS31jbjrsQ+KtE+ikm9IixZvCATPw87+rm+mvTXwPUvUi6m72K0uDFK0Os3rRQ=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.23.2", "", { "dependencies": { "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", "minimatch": "^10.2.1" } }, "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.2", "", { "dependencies": { "@eslint/core": "^1.1.0" } }, "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ=="], - - "@eslint/core": ["@eslint/core@1.1.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw=="], - - "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - - "@eslint/object-schema": ["@eslint/object-schema@3.0.2", "", {}, "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.0", "", { "dependencies": { "@eslint/core": "^1.1.0", "levn": "^0.4.1" } }, "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@matrix-org/matrix-sdk-crypto-wasm": ["@matrix-org/matrix-sdk-crypto-wasm@17.1.0", "", {}, "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@redis/bloom": ["@redis/bloom@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw=="], - - "@redis/client": ["@redis/client@5.11.0", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0" }, "optionalPeers": ["@node-rs/xxhash"] }, "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ=="], - - "@redis/json": ["@redis/json@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg=="], - - "@redis/search": ["@redis/search@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g=="], - - "@redis/time-series": ["@redis/time-series@5.11.0", "", { "peerDependencies": { "@redis/client": "^5.11.0" } }, "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], - - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], - - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/events": ["@types/events@3.0.3", "", {}, "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - - "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - - "@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], - - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], - - "@vitest/coverage-v8": ["@vitest/coverage-v8@2.1.9", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.12", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, "peerDependencies": { "@vitest/browser": "2.1.9", "vitest": "2.1.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ=="], - - "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], - - "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="], - - "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="], - - "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="], - - "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="], - - "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="], - - "@workflow/serde": ["@workflow/serde@4.1.0-beta.2", "", {}, "sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww=="], - - "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], - - "another-json": ["another-json@0.2.0", "", {}, "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - - "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], - - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - - "brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], - - "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], - - "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - - "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], - - "chat": ["chat@4.16.1", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" } }, "sha512-y5om0n19gOIv54pAmPIIMlYRFxpphgY4+Eh5K0HdmyRsIq7jjEA6DiGSoIP8zy3/HhSYJPekqQz5QQt7neN6QA=="], - - "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], - - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], - - "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], - - "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], - - "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": "bin/esbuild" }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@10.0.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.2", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.0", "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.1", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw=="], - - "eslint-scope": ["eslint-scope@9.1.1", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "espree": ["espree@11.1.1", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.3.4", "", {}, "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], - - "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="], - - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], - - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], - - "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], - - "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], - - "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], - - "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - - "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - - "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], - - "matrix-events-sdk": ["matrix-events-sdk@0.0.1", "", {}, "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="], - - "matrix-js-sdk": ["matrix-js-sdk@41.0.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/matrix-sdk-crypto-wasm": "^17.1.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", "p-retry": "7", "sdp-transform": "^3.0.0", "unhomoglyph": "^1.0.6", "uuid": "13" } }, "sha512-58j4WuOwXvT6bmTgc48b3bITVG/rWv5+N//yA5mvr6Rc04EUSpwTPhgMOTjUVnJsrhLGzdkgal4Xtt2R+xNdwg=="], - - "matrix-widget-api": ["matrix-widget-api@1.17.0", "", { "dependencies": { "@types/events": "^3.0.0", "events": "^3.2.0" } }, "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ=="], - - "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], - - "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], - - "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], - - "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], - - "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], - - "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], - - "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], - - "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], - - "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], - - "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], - - "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], - - "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], - - "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], - - "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], - - "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], - - "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], - - "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], - - "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], - - "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], - - "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], - - "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], - - "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], - - "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], - - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], - - "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], - - "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], - - "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], - - "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], - - "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], - - "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], - - "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], - - "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], - - "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], - - "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], - - "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], - - "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], - - "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - - "minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="], - - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="], - - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "oidc-client-ts": ["oidc-client-ts@3.4.1", "", { "dependencies": { "jwt-decode": "^4.0.0" } }, "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], - - "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "redis": ["redis@5.11.0", "", { "dependencies": { "@redis/bloom": "5.11.0", "@redis/client": "5.11.0", "@redis/json": "5.11.0", "@redis/search": "5.11.0", "@redis/time-series": "5.11.0" } }, "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ=="], - - "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - - "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], - - "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], - - "remend": ["remend@1.2.2", "", {}, "sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], - - "sdp-transform": ["sdp-transform@3.0.0", "", { "bin": { "sdp-verify": "checker.js" } }, "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ=="], - - "semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "test-exclude": ["test-exclude@7.0.2", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^10.2.2" } }, "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], - - "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], - - "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], - - "tree-kill": ["tree-kill@1.2.2", "", { "bin": "cli.js" }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - - "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - - "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="], - - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "unhomoglyph": ["unhomoglyph@1.0.6", "", {}, "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="], - - "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - - "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], - - "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - - "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], - - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "uuid": ["uuid@13.0.0", "", { "bin": "dist-node/bin/uuid" }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], - - "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], - - "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - - "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": "bin/vite.js" }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], - - "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": "vite-node.mjs" }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="], - - "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - - "glob/minimatch": ["minimatch@9.0.6", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ=="], - - "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - - "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": "bin/esbuild" }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], - - "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], - - "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], - - "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], - - "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], - - "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], - - "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], - - "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], - - "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], - - "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], - - "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], - - "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], - - "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], - - "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], - - "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], - - "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], - - "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], - - "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], - - "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], - - "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], - - "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], - - "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], - - "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], - - "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - } -} diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index 0815e7a..ac3f8f3 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -413,7 +413,7 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { }); it("restarts on the same session and catches up paginated room history", async () => { - const offlineCount = 12; + const offlineCount = 6; const restartTag = `e2e-restart-${nonce()}`; const roomThreadId = sender.adapter.encodeThreadId({ roomID }); const botSession = bot.session; diff --git a/e2e/helpers.ts b/e2e/helpers.ts index e9170a0..6b97f03 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -1,6 +1,7 @@ import { randomBytes } from "node:crypto"; import { Chat, type Message, type ReactionEvent, type StateAdapter } from "chat"; import { createMemoryState } from "@chat-adapter/state-memory"; +import { createRedisState } from "@chat-adapter/state-redis"; import { EventType } from "matrix-js-sdk"; import type { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; import { MatrixAdapter } from "../src/index"; @@ -24,6 +25,9 @@ export const env = { get roomID(): string | undefined { return process.env.E2E_ROOM_ID || undefined; }, + get redisURL(): string | undefined { + return process.env.E2E_REDIS_URL || undefined; + }, }; export interface E2EParticipant { @@ -56,7 +60,7 @@ export async function createParticipantFromSession(opts: { session: MatrixLoginResponse; state?: StateAdapter; }): Promise { - const state = opts.state ?? createMemoryState(); + const state = opts.state ?? createE2EState(opts.name); const adapter = new MatrixAdapter({ baseURL: env.baseURL, auth: { @@ -70,6 +74,9 @@ export async function createParticipantFromSession(opts: { enabled: true, useIndexedDB: false, }, + matrixStore: { + enabled: true, + }, recoveryKey: opts.recoveryKey, }); @@ -110,6 +117,17 @@ export async function createParticipantFromSession(opts: { }; } +function createE2EState(name: string): StateAdapter { + if (env.redisURL) { + return createRedisState({ + url: env.redisURL, + keyPrefix: `matrix-chat-adapter-e2e:${name}`, + }); + } + + return createMemoryState(); +} + export async function shutdownParticipant(participant: E2EParticipant): Promise { participant.onMessage(null); participant.onReaction(null); @@ -307,7 +325,15 @@ export async function waitForFetchedMessage( const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { - const message = await adapter.fetchMessage(threadId, messageId); + let message: Message | null = null; + try { + message = await adapter.fetchMessage(threadId, messageId); + } catch (error) { + if (!isTransientMatrixError(error)) { + throw error; + } + } + if (message && isDecryptedMessage(message) && predicate(message)) { return message; } @@ -328,10 +354,25 @@ export async function waitForMatchingMessage( const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { - const page = await adapter.fetchMessages(threadId, { - direction: "backward", - limit, - }); + let page: + | Awaited> + | null = null; + try { + page = await adapter.fetchMessages(threadId, { + direction: "backward", + limit, + }); + } catch (error) { + if (!isTransientMatrixError(error)) { + throw error; + } + } + + if (!page) { + await sleep(intervalMs); + continue; + } + const match = page.messages.find( (message) => isDecryptedMessage(message) && predicate(message) ); @@ -372,3 +413,16 @@ export function nonce(): string { function isDecryptedMessage(message: Message): boolean { return !message.text.startsWith("** Unable to decrypt:"); } + +function isTransientMatrixError(error: unknown): boolean { + if (!(error instanceof Error)) { + return false; + } + + return ( + error.message.includes("429") || + error.message.includes("503") || + error.message.includes("M_LIMIT_EXCEEDED") || + error.message.includes("Server returned 503 error") + ); +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8566272..0000000 --- a/package-lock.json +++ /dev/null @@ -1,5762 +0,0 @@ -{ - "name": "@beeper/chat-adapter-matrix", - "version": "0.2.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@beeper/chat-adapter-matrix", - "version": "0.2.0", - "license": "MIT", - "dependencies": { - "@chat-adapter/state-memory": "^4.16.1", - "@chat-adapter/state-redis": "^4.16.1", - "chat": "^4.16.1", - "marked": "^15.0.12", - "matrix-js-sdk": "^41.0.0", - "node-html-parser": "^7.1.0" - }, - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^22.10.2", - "@vitest/coverage-v8": "^2.1.8", - "eslint": "^10.0.2", - "tsup": "^8.3.5", - "typescript": "^5.7.2", - "typescript-eslint": "^8.56.1", - "vitest": "^2.1.8" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@chat-adapter/state-memory": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@chat-adapter/state-memory/-/state-memory-4.16.1.tgz", - "integrity": "sha512-G/7MNxqqI19IRUUoaitSGWs0Tx+FAcvnB3rLfpWmV4eaUDzzhF+DEtmmR+DVxpz35RxBM3Y4Gi6Wqz5Ysfiytg==", - "license": "MIT", - "dependencies": { - "chat": "4.16.1" - } - }, - "node_modules/@chat-adapter/state-redis": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@chat-adapter/state-redis/-/state-redis-4.16.1.tgz", - "integrity": "sha512-weXm3OweMb7/hejHdzyaSvBLjS31jbjrsQ+KtE+ikm9IixZvCATPw87+rm+mvTXwPUvUi6m72K0uDFK0Os3rRQ==", - "license": "MIT", - "dependencies": { - "chat": "4.16.1", - "redis": "^5.11.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", - "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.2", - "debug": "^4.3.1", - "minimatch": "^10.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", - "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", - "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", - "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-17.1.0.tgz", - "integrity": "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g==", - "license": "Apache-2.0", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/bloom": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.11.0.tgz", - "integrity": "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.11.0" - } - }, - "node_modules/@redis/client": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", - "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", - "license": "MIT", - "dependencies": { - "cluster-key-slot": "1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@node-rs/xxhash": "^1.1.0" - }, - "peerDependenciesMeta": { - "@node-rs/xxhash": { - "optional": true - } - } - }, - "node_modules/@redis/json": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.11.0.tgz", - "integrity": "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.11.0" - } - }, - "node_modules/@redis/search": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.11.0.tgz", - "integrity": "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.11.0" - } - }, - "node_modules/@redis/time-series": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.11.0.tgz", - "integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.11.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/events": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", - "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", - "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", - "magicast": "^0.3.5", - "std-env": "^3.8.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "2.1.9", - "vitest": "2.1.9" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@workflow/serde": { - "version": "4.1.0-beta.2", - "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0-beta.2.tgz", - "integrity": "sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww==", - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/another-json": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", - "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==", - "license": "Apache-2.0" - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base-x": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", - "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", - "license": "MIT" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/bs58": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", - "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", - "license": "MIT", - "dependencies": { - "base-x": "^5.0.0" - } - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chat": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/chat/-/chat-4.16.1.tgz", - "integrity": "sha512-y5om0n19gOIv54pAmPIIMlYRFxpphgY4+Eh5K0HdmyRsIq7jjEA6DiGSoIP8zy3/HhSYJPekqQz5QQt7neN6QA==", - "license": "MIT", - "dependencies": { - "@workflow/serde": "4.1.0-beta.2", - "mdast-util-to-string": "^4.0.0", - "remark-gfm": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "remend": "^1.2.1", - "unified": "^11.0.5" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", - "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.2", - "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.0", - "@eslint/plugin-kit": "^0.6.0", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.1", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.1", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", - "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", - "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loglevel": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", - "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/matrix-events-sdk": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", - "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==", - "license": "Apache-2.0" - }, - "node_modules/matrix-js-sdk": { - "version": "41.0.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-41.0.0.tgz", - "integrity": "sha512-58j4WuOwXvT6bmTgc48b3bITVG/rWv5+N//yA5mvr6Rc04EUSpwTPhgMOTjUVnJsrhLGzdkgal4Xtt2R+xNdwg==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^17.1.0", - "another-json": "^0.2.0", - "bs58": "^6.0.0", - "content-type": "^1.0.4", - "jwt-decode": "^4.0.0", - "loglevel": "^1.9.2", - "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.16.1", - "oidc-client-ts": "^3.0.1", - "p-retry": "7", - "sdp-transform": "^3.0.0", - "unhomoglyph": "^1.0.6", - "uuid": "13" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/matrix-widget-api": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.17.0.tgz", - "integrity": "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==", - "license": "Apache-2.0", - "dependencies": { - "@types/events": "^3.0.0", - "events": "^3.2.0" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-html-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz", - "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==", - "license": "MIT", - "dependencies": { - "css-select": "^5.1.0", - "he": "1.2.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/oidc-client-ts": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", - "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", - "license": "Apache-2.0", - "dependencies": { - "jwt-decode": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", - "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", - "license": "MIT", - "dependencies": { - "is-network-error": "^1.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/redis": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-5.11.0.tgz", - "integrity": "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==", - "license": "MIT", - "dependencies": { - "@redis/bloom": "5.11.0", - "@redis/client": "5.11.0", - "@redis/json": "5.11.0", - "@redis/search": "5.11.0", - "@redis/time-series": "5.11.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remend": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/remend/-/remend-1.2.2.tgz", - "integrity": "sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w==", - "license": "Apache-2.0" - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/sdp-transform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", - "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", - "license": "MIT", - "bin": { - "sdp-verify": "checker.js" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsup": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", - "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.27.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "^0.7.6", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "@microsoft/api-extractor": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", - "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.56.1", - "@typescript-eslint/parser": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unhomoglyph": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", - "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==", - "license": "MIT" - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/package.json b/package.json index 5aac7c9..8f19573 100644 --- a/package.json +++ b/package.json @@ -18,21 +18,21 @@ "scripts": { "build": "tsup", "dev": "tsup --watch", - "example:bun": "bun --env-file=examples/.env run examples/bot.ts", - "example:bun:redis": "bun --env-file=examples/.env run examples/bot.redis.ts", - "token:bun": "bun run scripts/get-access-token.ts", + "example": "node --experimental-strip-types --env-file=examples/.env examples/bot.ts", + "example:redis": "node --experimental-strip-types --env-file=examples/.env examples/bot.redis.ts", + "matrix-token": "node --experimental-strip-types scripts/get-access-token.ts", "clean:repo": "rm -rf dist coverage", "lint:types": "eslint .", "test": "vitest run --coverage", "test:e2e": "vitest run --config vitest.config.e2e.ts", "test:watch": "vitest", - "typecheck": "npm run lint:types && tsc --noEmit", + "typecheck": "pnpm lint:types && tsc --noEmit", "clean": "rm -rf dist" }, "dependencies": { - "@chat-adapter/state-memory": "^4.16.1", - "@chat-adapter/state-redis": "^4.16.1", - "chat": "^4.16.1", + "@chat-adapter/state-memory": "^4.17.0", + "@chat-adapter/state-redis": "^4.17.0", + "chat": "^4.17.0", "marked": "^15.0.12", "matrix-js-sdk": "^41.0.0", "node-html-parser": "^7.1.0" @@ -64,5 +64,6 @@ "bot", "adapter" ], - "license": "MIT" + "license": "MIT", + "packageManager": "pnpm@10.30.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..30910b8 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3544 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@chat-adapter/state-memory': + specifier: ^4.17.0 + version: 4.17.0 + '@chat-adapter/state-redis': + specifier: ^4.17.0 + version: 4.17.0 + chat: + specifier: ^4.17.0 + version: 4.17.0 + marked: + specifier: ^15.0.12 + version: 15.0.12 + matrix-js-sdk: + specifier: ^41.0.0 + version: 41.0.0 + node-html-parser: + specifier: ^7.1.0 + version: 7.1.0 + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.3) + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.9(vitest@2.1.9(@types/node@22.19.15)) + eslint: + specifier: ^10.0.2 + version: 10.0.3 + tsup: + specifier: ^8.3.5 + version: 8.5.1(postcss@8.5.8)(typescript@5.9.3) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + typescript-eslint: + specifier: ^8.56.1 + version: 8.56.1(eslint@10.0.3)(typescript@5.9.3) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@22.19.15) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@chat-adapter/state-memory@4.17.0': + resolution: {integrity: sha512-7LewkFY6gQVEXxAycaWUXax9Pt7Prmn2mBdoIBbTEAzV5bSQBaXqKyrd6VWCd9gEyxNlBUainyQul4iGNPw1sQ==} + + '@chat-adapter/state-redis@4.17.0': + resolution: {integrity: sha512-sJ2V/pZESZKrGkuafMqg4gqyvHwz8d0JDUvqeZ+FVfEgT/h0g1kYfSc++qfEayzmPpSJxC6gzGpBGTiL/5utCw==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.3': + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.3': + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.1.1': + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.3': + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.1': + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@matrix-org/matrix-sdk-crypto-wasm@17.1.0': + resolution: {integrity: sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g==} + engines: {node: '>= 18'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@redis/bloom@5.11.0': + resolution: {integrity: sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.11.0 + + '@redis/client@5.11.0': + resolution: {integrity: sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==} + engines: {node: '>= 18'} + peerDependencies: + '@node-rs/xxhash': ^1.1.0 + peerDependenciesMeta: + '@node-rs/xxhash': + optional: true + + '@redis/json@5.11.0': + resolution: {integrity: sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.11.0 + + '@redis/search@5.11.0': + resolution: {integrity: sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.11.0 + + '@redis/time-series@5.11.0': + resolution: {integrity: sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.11.0 + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/events@3.0.3': + resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.19.15': + resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/coverage-v8@2.1.9': + resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + peerDependencies: + '@vitest/browser': 2.1.9 + vitest: 2.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@workflow/serde@4.1.0-beta.2': + resolution: {integrity: sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + another-json@0.2.0: + resolution: {integrity: sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base-x@5.0.1: + resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + + bs58@6.0.0: + resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + chat@4.17.0: + resolution: {integrity: sha512-kX9jIXmEU2ksnF1YshM+qfI/6pBy6k8pELkuRaE2AJyF/0nt4GUc0McBhn2DNpXLDI5sVwRGB3PmI+q5wUtYFg==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.0.3: + resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.4: + resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-network-error@1.3.1: + resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==} + engines: {node: '>=16'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + + matrix-events-sdk@0.0.1: + resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} + + matrix-js-sdk@41.0.0: + resolution: {integrity: sha512-58j4WuOwXvT6bmTgc48b3bITVG/rWv5+N//yA5mvr6Rc04EUSpwTPhgMOTjUVnJsrhLGzdkgal4Xtt2R+xNdwg==} + engines: {node: '>=22.0.0'} + + matrix-widget-api@1.17.0: + resolution: {integrity: sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.1: + resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-html-parser@7.1.0: + resolution: {integrity: sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + oidc-client-ts@3.4.1: + resolution: {integrity: sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + redis@5.11.0: + resolution: {integrity: sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==} + engines: {node: '>= 18'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remend@1.2.2: + resolution: {integrity: sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w==} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sdp-transform@3.0.0: + resolution: {integrity: sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unhomoglyph@1.0.6: + resolution: {integrity: sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.28.6': {} + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@chat-adapter/state-memory@4.17.0': + dependencies: + chat: 4.17.0 + transitivePeerDependencies: + - supports-color + + '@chat-adapter/state-redis@4.17.0': + dependencies: + chat: 4.17.0 + redis: 5.11.0 + transitivePeerDependencies: + - '@node-rs/xxhash' + - supports-color + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.3)': + dependencies: + eslint: 10.0.3 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.3': + dependencies: + '@eslint/object-schema': 3.0.3 + debug: 4.4.3 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.3': + dependencies: + '@eslint/core': 1.1.1 + + '@eslint/core@1.1.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.0.3)': + optionalDependencies: + eslint: 10.0.3 + + '@eslint/object-schema@3.0.3': {} + + '@eslint/plugin-kit@0.6.1': + dependencies: + '@eslint/core': 1.1.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@matrix-org/matrix-sdk-crypto-wasm@17.1.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@redis/bloom@5.11.0(@redis/client@5.11.0)': + dependencies: + '@redis/client': 5.11.0 + + '@redis/client@5.11.0': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@5.11.0(@redis/client@5.11.0)': + dependencies: + '@redis/client': 5.11.0 + + '@redis/search@5.11.0(@redis/client@5.11.0)': + dependencies: + '@redis/client': 5.11.0 + + '@redis/time-series@5.11.0(@redis/client@5.11.0)': + dependencies: + '@redis/client': 5.11.0 + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/events@3.0.3': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@22.19.15': + dependencies: + undici-types: 6.21.0 + + '@types/unist@3.0.3': {} + + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 10.0.3 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 10.0.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@10.0.3)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.0.3 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@10.0.3)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 10.0.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@22.19.15))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@22.19.15) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.15))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.15) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + '@workflow/serde@4.1.0-beta.2': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + another-json@0.2.0: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + assertion-error@2.0.1: {} + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base-x@5.0.1: {} + + boolbase@1.0.0: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + bs58@6.0.0: + dependencies: + base-x: 5.0.1 + + bundle-require@5.1.0(esbuild@0.27.3): + dependencies: + esbuild: 0.27.3 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + ccount@2.0.1: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + character-entities@2.0.2: {} + + chat@4.17.0: + dependencies: + '@workflow/serde': 4.1.0-beta.2 + mdast-util-to-string: 4.0.0 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + remend: 1.2.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + check-error@2.1.3: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cluster-key-slot@1.1.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + content-type@1.0.5: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.0.3: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.3 + '@eslint/config-helpers': 0.5.3 + '@eslint/core': 1.1.1 + '@eslint/plugin-kit': 0.6.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + events@3.3.0: {} + + expect-type@1.3.0: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.1 + rollup: 4.59.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.4 + keyv: 4.5.4 + + flatted@3.3.4: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + has-flag@4.0.0: {} + + he@1.2.0: {} + + html-escaper@2.0.2: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-network-error@1.3.1: {} + + is-plain-obj@4.1.0: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jwt-decode@4.0.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + loglevel@1.9.2: {} + + longest-streak@3.1.0: {} + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + markdown-table@3.0.4: {} + + marked@15.0.12: {} + + matrix-events-sdk@0.0.1: {} + + matrix-js-sdk@41.0.0: + dependencies: + '@babel/runtime': 7.28.6 + '@matrix-org/matrix-sdk-crypto-wasm': 17.1.0 + another-json: 0.2.0 + bs58: 6.0.0 + content-type: 1.0.5 + jwt-decode: 4.0.0 + loglevel: 1.9.2 + matrix-events-sdk: 0.0.1 + matrix-widget-api: 1.17.0 + oidc-client-ts: 3.4.1 + p-retry: 7.1.1 + sdp-transform: 3.0.0 + unhomoglyph: 1.0.6 + uuid: 13.0.0 + + matrix-widget-api@1.17.0: + dependencies: + '@types/events': 3.0.3 + events: 3.3.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.3: {} + + mlly@1.8.1: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-html-parser@7.1.0: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + oidc-client-ts@3.4.1: + dependencies: + jwt-decode: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.1 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.1 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.8): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.8 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + readdirp@4.1.2: {} + + redis@5.11.0: + dependencies: + '@redis/bloom': 5.11.0(@redis/client@5.11.0) + '@redis/client': 5.11.0 + '@redis/json': 5.11.0(@redis/client@5.11.0) + '@redis/search': 5.11.0(@redis/client@5.11.0) + '@redis/time-series': 5.11.0(@redis/client@5.11.0) + transitivePeerDependencies: + - '@node-rs/xxhash' + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remend@1.2.2: {} + + resolve-from@5.0.0: {} + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + sdp-transform@3.0.0: {} + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 10.2.4 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tree-kill@1.2.2: {} + + trough@2.2.0: {} + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsup@8.5.1(postcss@8.5.8)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.3) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.3 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.8) + resolve-from: 5.0.0 + rollup: 4.59.0 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.8 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.56.1(eslint@10.0.3)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.3)(typescript@5.9.3) + eslint: 10.0.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@6.21.0: {} + + unhomoglyph@1.0.6: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@13.0.0: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@2.1.9(@types/node@22.19.15): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.15) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@22.19.15): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.8 + rollup: 4.59.0 + optionalDependencies: + '@types/node': 22.19.15 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@22.19.15): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.15)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.15) + vite-node: 2.1.9(@types/node@22.19.15) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.15 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/scripts/get-access-token.ts b/scripts/get-access-token.ts index 4d0a76e..20a19f8 100644 --- a/scripts/get-access-token.ts +++ b/scripts/get-access-token.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env bun +#!/usr/bin/env -S node --experimental-strip-types import { createInterface } from "node:readline/promises"; import readline from "node:readline"; @@ -299,7 +299,7 @@ async function whoami(baseDomain: string, accessToken: string): Promise { - output.write("Beeper Access Token Helper (Bun)\n\n"); + output.write("Beeper Access Token Helper\n\n"); const env = await promptChoice( "Environment:", diff --git a/src/index.test.ts b/src/index.test.ts index db5434b..89785a5 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -187,6 +187,13 @@ function getInternals(adapter: MatrixAdapter): AdapterInternals { function makeClient() { const handlers = new Map void>(); + const store = { + save: vi.fn(async () => undefined), + }; + const crypto = { + exportSecretsBundle: vi.fn(async () => ({ bundle: "secret" })), + importSecretsBundle: vi.fn(async () => undefined), + }; const client = { on: (eventName: string, cb: (...args: unknown[]) => void) => { @@ -213,6 +220,7 @@ function makeClient() { async (): Promise | null> => null ), getAccessToken: vi.fn(() => "token"), + getCrypto: vi.fn(() => crypto), getEventMapper: vi.fn(() => (raw: Record) => mapRawToEvent(raw)), initRustCrypto: vi.fn(async () => undefined), mxcUrlToHttp: vi.fn((url: string) => url), @@ -229,6 +237,8 @@ function makeClient() { getRoom: vi.fn((roomID?: string): RoomLike | null => makeRoom({ roomId: roomID ?? "!room:beeper.com", })), + store, + __crypto: crypto, __handlers: handlers, }; @@ -297,6 +307,8 @@ function makeStateAdapter(initial: Record = {}): StateAdapter { const get: StateAdapter["get"] = (key) => afterReady(() => base.get(key)); const set: StateAdapter["set"] = (key, value, ttlMs) => afterReady(() => base.set(key, value, ttlMs)); + const setIfNotExists: StateAdapter["setIfNotExists"] = (key, value, ttlMs) => + afterReady(() => base.setIfNotExists(key, value, ttlMs)); return { acquireLock: vi.fn((threadId, ttlMs) => @@ -312,6 +324,7 @@ function makeStateAdapter(initial: Record = {}): StateAdapter { isSubscribed: vi.fn((threadId) => afterReady(() => base.isSubscribed(threadId))), releaseLock: vi.fn((lock) => afterReady(() => base.releaseLock(lock))), set: vi.fn(set), + setIfNotExists: vi.fn(setIfNotExists), subscribe: vi.fn((threadId) => afterReady(() => base.subscribe(threadId))), unsubscribe: vi.fn((threadId) => afterReady(() => base.unsubscribe(threadId))), }; @@ -1304,9 +1317,136 @@ describe("MatrixAdapter", () => { const adapter = await makeInitializedAdapter(fakeClient); await adapter.shutdown(); + expect(fakeClient.store.save).toHaveBeenCalledWith(true); expect(fakeClient.stopClient).toHaveBeenCalledOnce(); }); + it("passes a chat-backed matrix store into custom client creation when enabled", async () => { + const fakeClient = makeClient(); + const createStore = vi.fn(() => ({ + save: vi.fn(async () => undefined), + startup: vi.fn(async () => undefined), + getSyncToken: vi.fn(() => null), + setSyncToken: vi.fn(), + storeRoom: vi.fn(), + setUserCreator: vi.fn(), + getRoom: vi.fn(() => null), + getRooms: vi.fn(() => []), + removeRoom: vi.fn(), + getRoomSummaries: vi.fn(() => []), + storeUser: vi.fn(), + getUser: vi.fn(() => null), + getUsers: vi.fn(() => []), + scrollback: vi.fn(() => []), + storeEvents: vi.fn(), + storeFilter: vi.fn(), + getFilter: vi.fn(() => null), + getFilterIdByName: vi.fn(() => null), + setFilterIdByName: vi.fn(), + storeAccountDataEvents: vi.fn(), + getAccountData: vi.fn(() => undefined), + setSyncData: vi.fn(async () => undefined), + wantsSave: vi.fn(() => false), + getSavedSync: vi.fn(async () => null), + getSavedSyncToken: vi.fn(async () => null), + deleteAllData: vi.fn(async () => undefined), + getOutOfBandMembers: vi.fn(async () => null), + setOutOfBandMembers: vi.fn(async () => undefined), + clearOutOfBandMembers: vi.fn(async () => undefined), + getClientOptions: vi.fn(async () => undefined), + storeClientOptions: vi.fn(async () => undefined), + getPendingEvents: vi.fn(async () => []), + setPendingEvents: vi.fn(async () => undefined), + saveToDeviceBatches: vi.fn(async () => undefined), + getOldestToDeviceBatch: vi.fn(async () => null), + removeToDeviceBatch: vi.fn(async () => undefined), + destroy: vi.fn(async () => undefined), + isNewlyCreated: vi.fn(async () => false), + accountData: new Map(), + })); + const createClient = vi.fn(() => asMatrixClient(fakeClient)); + const state = makeStateAdapter(); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + matrixStore: { enabled: true }, + createStore, + createClient, + }); + + await adapter.initialize(makeChatInstance({ state })); + + expect(createStore).toHaveBeenCalledWith( + expect.objectContaining({ + scopeKey: expect.stringMatching( + /^matrix:store:v1:https%3A%2F%2Fhs\.beeper\.com:%40bot%3Abeeper\.com:/ + ), + }) + ); + expect(createClient).toHaveBeenCalledWith( + expect.objectContaining({ + baseUrl: "https://hs.beeper.com", + store: createStore.mock.results[0]?.value, + }) + ); + }); + + it("imports and persists secrets bundles when enabled", async () => { + const fakeClient = makeClient(); + fakeClient.__crypto.importSecretsBundle.mockResolvedValue(undefined); + const state = makeStateAdapter({ + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": + { bundle: "persisted" }, + }); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + deviceID: "DEVICE1", + createClient: () => asMatrixClient(fakeClient), + matrixStore: { enabled: true }, + e2ee: { enabled: true }, + }); + + await adapter.initialize(makeChatInstance({ state })); + await Promise.resolve(); + await Promise.resolve(); + + expect(fakeClient.__crypto.importSecretsBundle).toHaveBeenCalledWith({ + bundle: "persisted", + }); + expect(fakeClient.__crypto.exportSecretsBundle).toHaveBeenCalled(); + expect(state.set).toHaveBeenCalledWith( + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle", + { bundle: "secret" } + ); + }); + + it("does not fail initialization when secrets bundle import fails", async () => { + const fakeClient = makeClient(); + fakeClient.__crypto.importSecretsBundle.mockRejectedValue(new Error("boom")); + const logger = makeTestLogger(); + const state = makeStateAdapter({ + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": + { bundle: "persisted" }, + }); + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, + deviceID: "DEVICE1", + createClient: () => asMatrixClient(fakeClient), + matrixStore: { enabled: true }, + e2ee: { enabled: true }, + logger: logger.logger, + }); + + await expect(adapter.initialize(makeChatInstance({ state }))).resolves.toBeUndefined(); + await Promise.resolve(); + expect(logger.warn).toHaveBeenCalledWith( + "Failed to import persisted Matrix secrets bundle", + expect.objectContaining({ error: expect.any(Error) }) + ); + }); + it("persists and reloads matrix session via chat state", async () => { const baseURL = "https://hs.beeper.com"; const state = makeStateAdapter(); diff --git a/src/index.ts b/src/index.ts index 6421cf4..95c5670 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { import sdk, { Direction, MatrixEvent, + type ICreateClientOpts, type MatrixClient, type IEvent, type IThreadBundledRelationship, @@ -45,6 +46,7 @@ import sdk, { ThreadFilterType, THREAD_RELATION_TYPE, } from "matrix-js-sdk"; +import type { IStore } from "matrix-js-sdk/lib/store"; import type { RoomMessageEventContent, RoomMessageTextEventContent, @@ -60,11 +62,14 @@ import { parse as parseHTML, type Node as HTMLNode, } from "node-html-parser"; +import { ChatStateMatrixStore } from "./store/chat-state-matrix-store"; import type { MatrixAuthBootstrapClient, MatrixAccessTokenAuthConfig, MatrixAdapterConfig, MatrixAuthConfig, + MatrixCreateStoreOptions, + MatrixSyncStoreConfig, MatrixThreadID, } from "./types"; @@ -72,9 +77,11 @@ const MATRIX_PREFIX = "matrix"; const MATRIX_DEVICE_PREFIX = "matrix:device"; const MATRIX_DM_PREFIX = "matrix:dm"; const MATRIX_SESSION_PREFIX = "matrix:session"; +const MATRIX_STORE_PREFIX = "matrix:store:v1"; const MATRIX_CURSOR_PREFIX = "mxv1:"; const DEFAULT_COMMAND_PREFIX = "/"; const TYPING_TIMEOUT_MS = 30_000; +const DEFAULT_MATRIX_STORE_PERSIST_INTERVAL_MS = 30_000; const FAST_SYNC_DEFAULTS: NonNullable = { initialSyncLimit: 1, lazyLoadMembers: true, @@ -131,6 +138,13 @@ type ResolvedAuth = { userID: string; }; +type ResolvedMatrixStoreConfig = { + enabled: boolean; + keyPrefix: string; + persistIntervalMs: number; + snapshotTtlMs?: number; +}; + type StoredSession = { accessToken?: string; authType: MatrixAuthConfig["type"]; @@ -223,8 +237,10 @@ export class MatrixAdapter implements Adapter { private readonly inviteAutoJoinInviterAllowlist?: Set; private readonly syncOptions?: MatrixAdapterConfig["sync"]; private readonly createClientFn?: MatrixAdapterConfig["createClient"]; + private readonly createStoreFn?: MatrixAdapterConfig["createStore"]; private readonly createBootstrapClientFn?: MatrixAdapterConfig["createBootstrapClient"]; private readonly e2eeConfig?: MatrixAdapterConfig["e2ee"]; + private readonly matrixStoreConfig: ResolvedMatrixStoreConfig; private readonly recoveryKey?: string; private readonly matrixSDKLogLevel?: MatrixAdapterConfig["matrixSDKLogLevel"]; private readonly deviceIDPersistence: DeviceIDPersistenceConfig; @@ -241,12 +257,15 @@ export class MatrixAdapter implements Adapter { private chat: ChatInstance | null = null; private stateAdapter: StateAdapter | null = null; private client: MatrixClient | null = null; + private matrixStoreScopeKey: string | null = null; private started = false; private userID: string; private deviceID?: string; private readonly reactionByEventID = new Map(); private readonly myReactionByKey = new Map(); private readonly processedTimelineEventIDs = new Set(); + private lastSecretsBundlePersistAt = 0; + private secretsBundleUnavailableLogged = false; private liveSyncReady = false; private shuttingDown = false; @@ -272,13 +291,25 @@ export class MatrixAdapter implements Adapter { : undefined; this.syncOptions = config.sync ?? FAST_SYNC_DEFAULTS; this.createClientFn = config.createClient; + this.createStoreFn = config.createStore; this.createBootstrapClientFn = config.createBootstrapClient; this.e2eeConfig = { ...config.e2ee, enabled: config.e2ee?.enabled ?? Boolean(config.recoveryKey), + persistSecretsBundle: + config.e2ee?.persistSecretsBundle ?? + Boolean(config.matrixStore?.enabled && (config.e2ee?.enabled ?? Boolean(config.recoveryKey))), storagePassword: config.e2ee?.storagePassword ?? config.recoveryKey, }; + this.matrixStoreConfig = { + enabled: config.matrixStore?.enabled ?? false, + keyPrefix: normalizeOptionalString(config.matrixStore?.keyPrefix) ?? MATRIX_STORE_PREFIX, + persistIntervalMs: + config.matrixStore?.persistIntervalMs ?? + DEFAULT_MATRIX_STORE_PERSIST_INTERVAL_MS, + snapshotTtlMs: config.matrixStore?.snapshotTtlMs, + }; this.recoveryKey = normalizeOptionalString(config.recoveryKey); this.matrixSDKLogLevel = config.matrixSDKLogLevel; this.deviceIDPersistence = { @@ -309,13 +340,24 @@ export class MatrixAdapter implements Adapter { this.configureMatrixSDKLogging(); await this.resolveDeviceID(); + let resolvedAuth: ResolvedAuth | null = null; if (this.createClientFn) { - this.client = this.createClientFn(); + const store = await this.maybeCreateMatrixStore(); + if (this.persistSecretsBundleEnabled) { + this.matrixStoreScopeKey = this.resolveMatrixStoreContext()?.scopeKey ?? null; + } + const clientOptions = this.buildCustomClientOptions(store); + this.client = this.createClientFn(clientOptions); } else { - const resolvedAuth = await this.resolveAuth(); + resolvedAuth = await this.resolveAuth(); this.userID = resolvedAuth.userID; this.deviceID = normalizeOptionalString(resolvedAuth.deviceID) ?? this.deviceID; - this.client = this.buildClient(resolvedAuth); + const store = await this.maybeCreateMatrixStore(resolvedAuth); + if (!store && this.persistSecretsBundleEnabled) { + this.matrixStoreScopeKey = + this.resolveMatrixStoreContext(resolvedAuth)?.scopeKey ?? null; + } + this.client = this.buildClient(resolvedAuth, store); } this.client.on(ClientEvent.Sync, (state: string) => { @@ -356,10 +398,13 @@ export class MatrixAdapter implements Adapter { this.shuttingDown = true; try { + await this.maybePersistSecretsBundle(true); + await this.maybeFlushMatrixStore(); this.client.removeAllListeners(); this.client.stopClient(); this.reactionByEventID.clear(); this.myReactionByKey.clear(); + this.client = null; this.started = false; this.logger.info("Matrix adapter shutdown complete"); } finally { @@ -1316,7 +1361,7 @@ export class MatrixAdapter implements Adapter { return { userID, deviceID }; } - private buildClient(auth: ResolvedAuth): MatrixClient { + private buildClient(auth: ResolvedAuth, store?: IStore): MatrixClient { const cryptoCallbacks = this.recoveryKey && this.e2eeConfig?.enabled ? { @@ -1333,6 +1378,7 @@ export class MatrixAdapter implements Adapter { userId: auth.userID, deviceId: auth.deviceID, cryptoCallbacks, + store, }); } @@ -1361,6 +1407,100 @@ export class MatrixAdapter implements Adapter { }; } + private buildCustomClientOptions(store?: IStore): ICreateClientOpts | undefined { + if (!store) { + return undefined; + } + + const customUserID = + normalizeOptionalString(this.auth.userID) ?? + normalizeOptionalString(this.userID); + + return { + baseUrl: this.baseURL, + accessToken: + this.auth.type === "accessToken" ? this.auth.accessToken : undefined, + userId: customUserID, + deviceId: this.deviceID, + store, + }; + } + + private async maybeCreateMatrixStore(resolvedAuth?: ResolvedAuth): Promise { + if (!this.stateAdapter || !this.matrixStoreConfig.enabled) { + return undefined; + } + + const storeContext = this.resolveMatrixStoreContext(resolvedAuth); + if (!storeContext) { + return undefined; + } + + const { scopeKey, userID, deviceID } = storeContext; + this.matrixStoreScopeKey = scopeKey; + + const createStoreOptions: MatrixCreateStoreOptions = { + baseURL: this.baseURL, + config: { ...this.matrixStoreConfig }, + deviceID, + logger: this.logger, + scopeKey, + state: this.stateAdapter, + userID, + }; + + if (this.createStoreFn) { + return this.createStoreFn(createStoreOptions); + } + + return new ChatStateMatrixStore({ + state: this.stateAdapter, + scopeKey, + logger: this.logger, + persistIntervalMs: this.matrixStoreConfig.persistIntervalMs, + snapshotTtlMs: this.matrixStoreConfig.snapshotTtlMs, + }); + } + + private resolveMatrixStoreContext( + resolvedAuth?: ResolvedAuth + ): { deviceID?: string; scopeKey: string; userID: string } | null { + const userID = normalizeOptionalString(resolvedAuth?.userID) ?? + normalizeOptionalString(this.auth.userID) ?? + normalizeOptionalString(this.userID); + if (!userID) { + this.logger.warn( + "Matrix sync store is enabled, but no user ID is available for store scoping. Continuing without persistent sync store." + ); + return null; + } + + const deviceID = normalizeOptionalString(resolvedAuth?.deviceID) ?? this.deviceID; + return { + userID, + deviceID, + scopeKey: this.buildMatrixStoreScopeKey(userID, deviceID), + }; + } + + private buildMatrixStoreScopeKey(userID: string, deviceID?: string): string { + const keyPrefix = this.matrixStoreConfig.keyPrefix; + return `${keyPrefix}:${encodeURIComponent(this.baseURL)}:${encodeURIComponent(userID)}:${encodeURIComponent(deviceID ?? "default")}`; + } + + private async maybeFlushMatrixStore(): Promise { + const store = this.client?.store as { save?: (force?: boolean) => Promise } | undefined; + if (!store?.save) { + return; + } + + try { + await store.save(true); + } catch (error) { + this.logger.warn("Failed to flush Matrix sync store during shutdown", { error }); + } + } + private dispatchTimelineEvent( event: MatrixEvent, room: Room | undefined, @@ -1394,7 +1534,7 @@ export class MatrixAdapter implements Adapter { rootEventID: string | undefined, content: MatrixOutboundMessageContent ) { - return this.withLoggedMatrixOperation( + const response = await this.withLoggedMatrixOperation( "Matrix send message failed", { roomId: roomID, @@ -1411,6 +1551,8 @@ export class MatrixAdapter implements Adapter { return client.sendEvent(roomID, EventType.RoomMessage, content); } ); + void this.maybePersistSecretsBundle(); + return response; } private async toRoomMessageContents( @@ -1485,7 +1627,9 @@ export class MatrixAdapter implements Adapter { storagePassword: this.e2eeConfig.storagePassword, storageKey: this.e2eeConfig.storageKey, }); + await this.maybeImportPersistedSecretsBundle(); void this.maybeLoadKeyBackupFromRecoveryKey(); + void this.maybePersistSecretsBundle(); this.logger.info("Matrix E2EE initialized", { useIndexedDB: useIndexedDB !== false, @@ -1515,6 +1659,95 @@ export class MatrixAdapter implements Adapter { } } + private get persistSecretsBundleEnabled(): boolean { + return Boolean(this.e2eeConfig?.enabled && this.e2eeConfig?.persistSecretsBundle); + } + + private getSecretsBundleStorageKey(): string | null { + if (!this.persistSecretsBundleEnabled || !this.matrixStoreScopeKey) { + return null; + } + + return `${this.matrixStoreScopeKey}:secrets-bundle`; + } + + private async maybeImportPersistedSecretsBundle(): Promise { + if (!this.stateAdapter) { + return; + } + + const storageKey = this.getSecretsBundleStorageKey(); + if (!storageKey) { + return; + } + + const crypto = this.requireClient().getCrypto(); + if (!crypto?.importSecretsBundle) { + return; + } + + try { + const bundle = await this.stateAdapter.get(storageKey); + if (!bundle) { + return; + } + + await crypto.importSecretsBundle(bundle as Parameters< + NonNullable["importSecretsBundle"]> + >[0]); + this.logger.info("Imported persisted Matrix secrets bundle"); + } catch (error) { + this.logger.warn("Failed to import persisted Matrix secrets bundle", { error }); + } + } + + private async maybePersistSecretsBundle(force = false): Promise { + if (!this.stateAdapter) { + return; + } + + const storageKey = this.getSecretsBundleStorageKey(); + if (!storageKey) { + return; + } + + const crypto = this.client?.getCrypto(); + if (!crypto?.exportSecretsBundle) { + return; + } + + const now = Date.now(); + if (!force && now - this.lastSecretsBundlePersistAt < 60_000) { + return; + } + + try { + const bundle = await crypto.exportSecretsBundle(); + await this.stateAdapter.set(storageKey, bundle); + this.lastSecretsBundlePersistAt = now; + this.secretsBundleUnavailableLogged = false; + if (force) { + this.logger.debug("Persisted Matrix secrets bundle during shutdown"); + } + } catch (error) { + if (this.isExpectedSecretsBundleUnavailableError(error)) { + if (!this.secretsBundleUnavailableLogged) { + this.logger.debug( + "Skipping Matrix secrets bundle persistence because cross-signing secrets are not available yet" + ); + this.secretsBundleUnavailableLogged = true; + } + return; + } + this.logger.warn("Failed to persist Matrix secrets bundle", { error }); + } + } + + private isExpectedSecretsBundleUnavailableError(error: unknown): boolean { + return error instanceof Error && + error.message.includes("cross-signing keys"); + } + private async tryDecryptEvent(event: MatrixEvent): Promise { if (!this.e2eeConfig?.enabled) { return; @@ -1526,6 +1759,7 @@ export class MatrixAdapter implements Adapter { try { await this.requireClient().decryptEventIfNeeded(event); + void this.maybePersistSecretsBundle(); } catch (error) { this.logger.warn("Failed to decrypt Matrix event", { eventId: event.getId(), @@ -3011,6 +3245,18 @@ export class MatrixAdapter implements Adapter { if (config.session?.ttlMs !== undefined && config.session.ttlMs <= 0) { throw new Error("session.ttlMs must be a positive number."); } + if ( + config.matrixStore?.persistIntervalMs !== undefined && + config.matrixStore.persistIntervalMs <= 0 + ) { + throw new Error("matrixStore.persistIntervalMs must be a positive number."); + } + if ( + config.matrixStore?.snapshotTtlMs !== undefined && + config.matrixStore.snapshotTtlMs <= 0 + ) { + throw new Error("matrixStore.snapshotTtlMs must be a positive number."); + } if ( (config.session?.encrypt && !config.session?.decrypt) || (!config.session?.encrypt && config.session?.decrypt) @@ -3199,7 +3445,12 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter }); } -export type { MatrixAdapterConfig, MatrixThreadID } from "./types"; +export type { + MatrixAdapterConfig, + MatrixCreateStoreOptions, + MatrixSyncStoreConfig, + MatrixThreadID, +} from "./types"; function resolveAuthFromEnv(): MatrixAuthConfig { const username = process.env.MATRIX_USERNAME; diff --git a/src/store/chat-state-matrix-store.test.ts b/src/store/chat-state-matrix-store.test.ts new file mode 100644 index 0000000..3d242a9 --- /dev/null +++ b/src/store/chat-state-matrix-store.test.ts @@ -0,0 +1,248 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { Logger, StateAdapter } from "chat"; +import { createMemoryState } from "@chat-adapter/state-memory"; +import type { IStateEventWithRoomId } from "matrix-js-sdk/lib/@types/search"; +import type { IndexedToDeviceBatch } from "matrix-js-sdk/lib/models/ToDeviceMessage"; +import { ChatStateMatrixStore } from "./chat-state-matrix-store"; + +function makeLogger(): Logger { + return { + child: () => makeLogger(), + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +function makeSavedSync(nextBatch = "s1") { + return { + nextBatch, + accountData: [ + { + type: "m.direct", + content: { + "@alice:beeper.com": ["!room:beeper.com"], + }, + }, + ], + roomsData: { + join: { + "!room:beeper.com": { + account_data: { events: [] }, + ephemeral: { events: [] }, + state: { events: [] }, + summary: {}, + timeline: { + events: [], + limited: false, + prev_batch: "t0", + }, + unread_notifications: {}, + }, + }, + }, + }; +} + +async function makeState( + initial: Record = {} +): Promise { + const state = createMemoryState(); + await state.connect(); + for (const [key, value] of Object.entries(initial)) { + await state.set(key, value); + } + return state; +} + +async function makeStore( + state: StateAdapter, + options: Partial[0]> = {} +) { + const store = new ChatStateMatrixStore({ + state, + scopeKey: "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1", + logger: makeLogger(), + persistIntervalMs: 30_000, + ...options, + }); + await store.startup(); + return store; +} + +describe("ChatStateMatrixStore", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-03-07T12:00:00.000Z")); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("loads persisted saved sync during startup", async () => { + const scope = + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + const state = await makeState({ + [`${scope}:meta`]: { + version: 1, + updatedAt: "2026-03-07T12:00:00.000Z", + nextToDeviceBatchID: 0, + filterIds: {}, + nextBatch: "s1", + }, + [`${scope}:saved-sync`]: makeSavedSync(), + }); + + const store = await makeStore(state, { scopeKey: scope }); + const saved = await store.getSavedSync(); + + expect(saved).toEqual(makeSavedSync()); + expect(await store.getSavedSyncToken()).toBe("s1"); + expect(await store.isNewlyCreated()).toBe(false); + }); + + it("reads saved sync token from meta without loading the full snapshot", async () => { + const scope = + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + const state = await makeState({ + [`${scope}:meta`]: { + version: 1, + updatedAt: "2026-03-07T12:00:00.000Z", + nextToDeviceBatchID: 0, + filterIds: {}, + nextBatch: "s99", + }, + }); + const getSpy = vi.spyOn(state, "get"); + const store = new ChatStateMatrixStore({ + state, + scopeKey: scope, + logger: makeLogger(), + persistIntervalMs: 30_000, + }); + + expect(await store.getSavedSyncToken()).toBe("s99"); + expect(getSpy).toHaveBeenCalledWith(`${scope}:meta`); + expect(getSpy).not.toHaveBeenCalledWith(`${scope}:saved-sync`); + }); + + it("marks sync data dirty and only persists on a forced save before the interval", async () => { + const scope = + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + const state = await makeState({ + [`${scope}:meta`]: { + version: 1, + updatedAt: "2026-03-07T12:00:00.000Z", + lastSavedAt: "2026-03-07T12:00:00.000Z", + nextToDeviceBatchID: 0, + filterIds: {}, + nextBatch: "s1", + }, + [`${scope}:saved-sync`]: makeSavedSync(), + }); + const setSpy = vi.spyOn(state, "set"); + const store = await makeStore(state, { scopeKey: scope }); + + await store.setSyncData({ + next_batch: "s2", + rooms: makeSavedSync("s2").roomsData, + account_data: { events: makeSavedSync("s2").accountData }, + } as never); + + expect(store.wantsSave()).toBe(false); + await store.save(); + expect(setSpy).not.toHaveBeenCalledWith( + expect.stringContaining(":saved-sync"), + expect.anything(), + expect.anything() + ); + + await store.save(true); + + expect(setSpy).toHaveBeenCalledWith( + expect.stringContaining(":saved-sync"), + expect.objectContaining({ nextBatch: "s2" }), + undefined + ); + expect(setSpy).toHaveBeenCalledWith( + expect.stringContaining(":meta"), + expect.objectContaining({ nextBatch: "s2" }), + undefined + ); + }); + + it("persists filter IDs, OOB members, pending events, and to-device batches across store instances", async () => { + const state = await makeState(); + const first = await makeStore(state); + const oobMembers: IStateEventWithRoomId[] = [ + { + room_id: "!room:beeper.com", + state_key: "@alice:beeper.com", + type: "m.room.member", + content: { membership: "join" }, + } as IStateEventWithRoomId, + ]; + const pendingEvents = [{ event_id: "$pending" }]; + + first.setFilterIdByName("sync", "filter-1"); + await first.setOutOfBandMembers("!room:beeper.com", oobMembers); + await first.setPendingEvents("!room:beeper.com", pendingEvents); + await first.saveToDeviceBatches([ + { + eventType: "m.room_key", + txnId: "txn-1", + batch: [ + { + userId: "@alice:beeper.com", + deviceId: "DEVICE2", + payload: { + type: "m.room_key", + content: { room_id: "!room:beeper.com" }, + }, + }, + ], + }, + ]); + + const second = await makeStore(state); + const toDevice = await second.getOldestToDeviceBatch(); + + expect(second.getFilterIdByName("sync")).toBe("filter-1"); + expect(await second.getOutOfBandMembers("!room:beeper.com")).toEqual(oobMembers); + expect(await second.getPendingEvents("!room:beeper.com")).toEqual(pendingEvents); + expect(toDevice).toMatchObject>({ + id: 0, + txnId: "txn-1", + eventType: "m.room_key", + }); + }); + + it("deletes all persisted keys", async () => { + const state = await makeState(); + const store = await makeStore(state); + + await store.setSyncData({ + next_batch: "s3", + rooms: makeSavedSync("s3").roomsData, + account_data: { events: makeSavedSync("s3").accountData }, + } as never); + await store.setPendingEvents("!room:beeper.com", [{ event_id: "$pending" }]); + await store.save(true); + + await store.deleteAllData(); + + expect(await state.get("matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:meta")).toBeNull(); + expect( + await state.get( + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:saved-sync" + ) + ).toBeNull(); + expect( + await state.get( + "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:pending-events:%21room%3Abeeper.com" + ) + ).toBeNull(); + }); +}); diff --git a/src/store/chat-state-matrix-store.ts b/src/store/chat-state-matrix-store.ts new file mode 100644 index 0000000..ad9390d --- /dev/null +++ b/src/store/chat-state-matrix-store.ts @@ -0,0 +1,514 @@ +import type { Logger, StateAdapter } from "chat"; +import { MatrixEvent, type IStartClientOpts } from "matrix-js-sdk"; +import type { IEvent } from "matrix-js-sdk/lib/models/event"; +import type { + IndexedToDeviceBatch, + ToDeviceBatchWithTxnId, +} from "matrix-js-sdk/lib/models/ToDeviceMessage"; +import { SyncAccumulator, type ISyncResponse } from "matrix-js-sdk/lib/sync-accumulator"; +import type { IStateEventWithRoomId } from "matrix-js-sdk/lib/@types/search"; +import { MemoryStore } from "matrix-js-sdk/lib/store/memory"; +import type { ISavedSync } from "matrix-js-sdk/lib/store"; + +const STORE_VERSION = 1; +const DEFAULT_PERSIST_INTERVAL_MS = 30_000; + +type PersistedMeta = { + filterIds: Record; + lastSavedAt?: string; + nextBatch?: string; + nextToDeviceBatchID: number; + updatedAt: string; + version: number; +}; + +type PersistedToDeviceState = { + batches: IndexedToDeviceBatch[]; +}; + +type PersistedIndex = { + roomIDs: string[]; +}; + +function normalizeStringRecord( + value: Record | undefined +): Record { + if (!value) { + return {}; + } + + return Object.fromEntries( + Object.entries(value).filter((entry): entry is [string, string] => { + return typeof entry[0] === "string" && typeof entry[1] === "string"; + }) + ); +} + +function normalizeRoomIndex(value: unknown): string[] { + if (!isRecord(value) || !Array.isArray(value.roomIDs)) { + return []; + } + + return value.roomIDs.filter((roomID): roomID is string => typeof roomID === "string"); +} + +function isPersistedMeta(value: unknown): value is PersistedMeta { + return ( + isRecord(value) && + value.version === STORE_VERSION && + typeof value.updatedAt === "string" && + typeof value.nextToDeviceBatchID === "number" + ); +} + +function isSavedSync(value: unknown): value is ISavedSync { + return ( + isRecord(value) && + typeof value.nextBatch === "string" && + Array.isArray(value.accountData) && + isRecord(value.roomsData) + ); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export class ChatStateMatrixStore extends MemoryStore { + private readonly state: StateAdapter; + private readonly scopeKey: string; + private readonly logger: Logger; + private readonly persistIntervalMs: number; + private readonly snapshotTtlMs?: number; + private readonly syncAccumulator = new SyncAccumulator(); + private latestSavedSync: ISavedSync | null = null; + private filterIDs = new Map(); + private pendingEventsByRoom = new Map[]>(); + private oobMembersByRoom = new Map(); + private persistedToDeviceBatches: IndexedToDeviceBatch[] = []; + private nextToDeviceBatchID = 0; + private dirty = false; + private started = false; + private newlyCreated = true; + private lastSavedAt = 0; + + constructor(options: { + state: StateAdapter; + scopeKey: string; + logger: Logger; + persistIntervalMs?: number; + snapshotTtlMs?: number; + }) { + super(); + this.state = options.state; + this.scopeKey = options.scopeKey; + this.logger = options.logger; + this.persistIntervalMs = + options.persistIntervalMs ?? DEFAULT_PERSIST_INTERVAL_MS; + this.snapshotTtlMs = options.snapshotTtlMs; + } + + override async startup(): Promise { + if (this.started) { + return; + } + + const [meta, savedSync, clientOptions, toDevice, oobRoomIDs, pendingRoomIDs] = + await Promise.all([ + this.state.get(this.metaKey), + this.state.get(this.savedSyncKey), + this.state.get(this.clientOptionsKey), + this.state.get(this.toDeviceKey), + this.state.get(this.oobMembersIndexKey), + this.state.get(this.pendingEventsIndexKey), + ]); + + if (isPersistedMeta(meta)) { + this.filterIDs = new Map(Object.entries(normalizeStringRecord(meta.filterIds))); + this.nextToDeviceBatchID = meta.nextToDeviceBatchID; + this.lastSavedAt = meta.lastSavedAt ? Date.parse(meta.lastSavedAt) || 0 : 0; + this.newlyCreated = false; + } + + if (isSavedSync(savedSync)) { + this.latestSavedSync = savedSync; + this.syncAccumulator.accumulate({ + next_batch: savedSync.nextBatch, + rooms: savedSync.roomsData, + account_data: { events: savedSync.accountData }, + }); + super.setSyncToken(savedSync.nextBatch); + super.storeAccountDataEvents( + savedSync.accountData.map((event) => new MatrixEvent(event)) + ); + this.newlyCreated = false; + } + + if (clientOptions) { + await super.storeClientOptions(clientOptions); + this.newlyCreated = false; + } + + if (Array.isArray(toDevice)) { + this.persistedToDeviceBatches = toDevice; + this.newlyCreated = false; + } else if (isRecord(toDevice) && Array.isArray(toDevice.batches)) { + this.persistedToDeviceBatches = toDevice.batches; + this.newlyCreated = false; + } + + await this.loadIndexedRoomState(oobRoomIDs, pendingRoomIDs); + this.started = true; + } + + override async isNewlyCreated(): Promise { + return this.newlyCreated; + } + + override wantsSave(): boolean { + if (!this.dirty) { + return false; + } + + return Date.now() - this.lastSavedAt >= this.persistIntervalMs; + } + + override async save(force = false): Promise { + if (!this.dirty || (!force && !this.wantsSave())) { + return; + } + + if (!this.latestSavedSync) { + this.dirty = false; + return; + } + + const nowISO = new Date().toISOString(); + const meta: PersistedMeta = { + version: STORE_VERSION, + updatedAt: nowISO, + lastSavedAt: nowISO, + nextBatch: this.latestSavedSync.nextBatch, + filterIds: Object.fromEntries(this.filterIDs), + nextToDeviceBatchID: this.nextToDeviceBatchID, + }; + + await Promise.all([ + this.state.set(this.savedSyncKey, this.latestSavedSync, this.snapshotTtlMs), + this.state.set(this.metaKey, meta, this.snapshotTtlMs), + ]); + + this.lastSavedAt = Date.now(); + this.dirty = false; + } + + override async getSavedSync(): Promise { + if (this.latestSavedSync) { + return this.latestSavedSync; + } + + const stored = await this.state.get(this.savedSyncKey); + if (isSavedSync(stored)) { + this.latestSavedSync = stored; + return stored; + } + + return null; + } + + override async getSavedSyncToken(): Promise { + if (this.latestSavedSync?.nextBatch) { + return this.latestSavedSync.nextBatch; + } + + const meta = await this.state.get(this.metaKey); + if (isPersistedMeta(meta) && typeof meta.nextBatch === "string") { + return meta.nextBatch; + } + + return null; + } + + override async setSyncData(syncData: ISyncResponse): Promise { + this.syncAccumulator.accumulate(syncData); + const accumulated = this.syncAccumulator.getJSON(true); + if (!accumulated.nextBatch) { + return; + } + + this.latestSavedSync = accumulated; + super.setSyncToken(accumulated.nextBatch); + super.storeAccountDataEvents( + accumulated.accountData.map((event) => new MatrixEvent(event)) + ); + this.dirty = true; + } + + override async getClientOptions(): Promise { + const existing = await super.getClientOptions(); + if (existing) { + return existing; + } + + const stored = await this.state.get(this.clientOptionsKey); + if (!stored) { + return undefined; + } + + await super.storeClientOptions(stored); + return stored; + } + + override async storeClientOptions(options: IStartClientOpts): Promise { + await super.storeClientOptions(options); + await this.state.set(this.clientOptionsKey, options, this.snapshotTtlMs); + } + + override async getOutOfBandMembers( + roomId: string + ): Promise { + const cached = this.oobMembersByRoom.get(roomId); + if (cached) { + return cached; + } + + const stored = await this.state.get( + this.oobMembersKey(roomId) + ); + if (!stored) { + return null; + } + + this.oobMembersByRoom.set(roomId, stored); + return stored; + } + + override async setOutOfBandMembers( + roomId: string, + membershipEvents: IStateEventWithRoomId[] + ): Promise { + this.oobMembersByRoom.set(roomId, membershipEvents); + await Promise.all([ + this.state.set(this.oobMembersKey(roomId), membershipEvents, this.snapshotTtlMs), + this.persistRoomIndex(this.oobMembersIndexKey, this.oobMembersByRoom), + ]); + } + + override async clearOutOfBandMembers(roomId: string): Promise { + this.oobMembersByRoom.delete(roomId); + await Promise.all([ + this.state.delete(this.oobMembersKey(roomId)), + this.persistRoomIndex(this.oobMembersIndexKey, this.oobMembersByRoom), + ]); + } + + override async getPendingEvents(roomId: string): Promise[]> { + const cached = this.pendingEventsByRoom.get(roomId); + if (cached) { + return cached; + } + + const stored = await this.state.get[] | null>( + this.pendingEventsKey(roomId) + ); + if (!stored) { + return []; + } + + this.pendingEventsByRoom.set(roomId, stored); + return stored; + } + + override async setPendingEvents( + roomId: string, + events: Partial[] + ): Promise { + if (events.length === 0) { + this.pendingEventsByRoom.delete(roomId); + await Promise.all([ + this.state.delete(this.pendingEventsKey(roomId)), + this.persistRoomIndex(this.pendingEventsIndexKey, this.pendingEventsByRoom), + ]); + return; + } + + this.pendingEventsByRoom.set(roomId, events); + await Promise.all([ + this.state.set(this.pendingEventsKey(roomId), events, this.snapshotTtlMs), + this.persistRoomIndex(this.pendingEventsIndexKey, this.pendingEventsByRoom), + ]); + } + + override async saveToDeviceBatches( + batches: ToDeviceBatchWithTxnId[] + ): Promise { + for (const batch of batches) { + this.persistedToDeviceBatches.push({ + id: this.nextToDeviceBatchID++, + eventType: batch.eventType, + txnId: batch.txnId, + batch: batch.batch, + }); + } + + await Promise.all([ + this.state.set( + this.toDeviceKey, + { batches: this.persistedToDeviceBatches }, + this.snapshotTtlMs + ), + this.persistMeta(), + ]); + } + + override async getOldestToDeviceBatch(): Promise { + return this.persistedToDeviceBatches[0] ?? null; + } + + override async removeToDeviceBatch(id: number): Promise { + this.persistedToDeviceBatches = this.persistedToDeviceBatches.filter( + (batch) => batch.id !== id + ); + + await this.state.set( + this.toDeviceKey, + { batches: this.persistedToDeviceBatches }, + this.snapshotTtlMs + ); + } + + override getFilterIdByName(filterName: string): string | null { + return this.filterIDs.get(filterName) ?? null; + } + + override setFilterIdByName(filterName: string, filterId?: string): void { + if (filterId) { + this.filterIDs.set(filterName, filterId); + } else { + this.filterIDs.delete(filterName); + } + + void this.persistMeta().catch((error) => { + this.logger.warn("Failed to persist Matrix filter IDs", { error }); + }); + } + + override async deleteAllData(): Promise { + await super.deleteAllData(); + + const oobRoomIDs = [...this.oobMembersByRoom.keys()]; + const pendingRoomIDs = [...this.pendingEventsByRoom.keys()]; + + this.latestSavedSync = null; + this.filterIDs.clear(); + this.oobMembersByRoom.clear(); + this.pendingEventsByRoom.clear(); + this.persistedToDeviceBatches = []; + this.nextToDeviceBatchID = 0; + this.dirty = false; + this.newlyCreated = true; + this.lastSavedAt = 0; + + await Promise.all([ + this.state.delete(this.metaKey), + this.state.delete(this.savedSyncKey), + this.state.delete(this.clientOptionsKey), + this.state.delete(this.toDeviceKey), + this.state.delete(this.oobMembersIndexKey), + this.state.delete(this.pendingEventsIndexKey), + ...oobRoomIDs.map((roomID) => this.state.delete(this.oobMembersKey(roomID))), + ...pendingRoomIDs.map((roomID) => this.state.delete(this.pendingEventsKey(roomID))), + ]); + } + + override async destroy(): Promise { + await this.save(true); + } + + private async loadIndexedRoomState( + oobRoomIDsRaw: unknown, + pendingRoomIDsRaw: unknown + ): Promise { + const oobRoomIDs = normalizeRoomIndex(oobRoomIDsRaw); + const pendingRoomIDs = normalizeRoomIndex(pendingRoomIDsRaw); + + await Promise.all([ + ...oobRoomIDs.map(async (roomID) => { + const members = await this.state.get( + this.oobMembersKey(roomID) + ); + if (members) { + this.oobMembersByRoom.set(roomID, members); + } + }), + ...pendingRoomIDs.map(async (roomID) => { + const pending = await this.state.get[] | null>( + this.pendingEventsKey(roomID) + ); + if (pending) { + this.pendingEventsByRoom.set(roomID, pending); + } + }), + ]); + + if (oobRoomIDs.length > 0 || pendingRoomIDs.length > 0) { + this.newlyCreated = false; + } + } + + private async persistMeta(): Promise { + const meta: PersistedMeta = { + version: STORE_VERSION, + updatedAt: new Date().toISOString(), + lastSavedAt: + this.lastSavedAt > 0 ? new Date(this.lastSavedAt).toISOString() : undefined, + nextBatch: this.latestSavedSync?.nextBatch ?? super.getSyncToken() ?? undefined, + filterIds: Object.fromEntries(this.filterIDs), + nextToDeviceBatchID: this.nextToDeviceBatchID, + }; + + await this.state.set(this.metaKey, meta, this.snapshotTtlMs); + } + + private async persistRoomIndex( + key: string, + collection: Map + ): Promise { + const value: PersistedIndex = { + roomIDs: [...collection.keys()], + }; + await this.state.set(key, value, this.snapshotTtlMs); + } + + private get metaKey(): string { + return `${this.scopeKey}:meta`; + } + + private get savedSyncKey(): string { + return `${this.scopeKey}:saved-sync`; + } + + private get clientOptionsKey(): string { + return `${this.scopeKey}:client-options`; + } + + private get toDeviceKey(): string { + return `${this.scopeKey}:to-device`; + } + + private get oobMembersIndexKey(): string { + return `${this.scopeKey}:room-index:oob-members`; + } + + private get pendingEventsIndexKey(): string { + return `${this.scopeKey}:room-index:pending-events`; + } + + private oobMembersKey(roomID: string): string { + return `${this.scopeKey}:oob-members:${encodeURIComponent(roomID)}`; + } + + private pendingEventsKey(roomID: string): string { + return `${this.scopeKey}:pending-events:${encodeURIComponent(roomID)}`; + } +} diff --git a/src/types.ts b/src/types.ts index 6bb3572..d8a9e1b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,14 +1,33 @@ -import type { IStartClientOpts, MatrixClient } from "matrix-js-sdk"; -import type { Logger } from "chat"; +import type { ICreateClientOpts, IStartClientOpts, MatrixClient } from "matrix-js-sdk"; +import type { Logger, StateAdapter } from "chat"; +import type { IStore } from "matrix-js-sdk/lib/store"; export interface MatrixE2EEConfig { cryptoDatabasePrefix?: string; enabled?: boolean; + persistSecretsBundle?: boolean; storageKey?: Uint8Array; storagePassword?: string; useIndexedDB?: boolean; } +export interface MatrixSyncStoreConfig { + enabled?: boolean; + keyPrefix?: string; + persistIntervalMs?: number; + snapshotTtlMs?: number; +} + +export interface MatrixCreateStoreOptions { + baseURL: string; + config: MatrixSyncStoreConfig; + deviceID?: string; + logger: Logger; + scopeKey: string; + state: StateAdapter; + userID: string; +} + export interface MatrixAccessTokenAuthConfig { accessToken: string; type: "accessToken"; @@ -31,15 +50,17 @@ export interface MatrixAdapterConfig { auth: MatrixAuthConfig; baseURL: string; commandPrefix?: string; + createStore?: (options: MatrixCreateStoreOptions) => IStore; createBootstrapClient?: ( options: { accessToken?: string; baseURL: string; deviceID?: string } ) => MatrixAuthBootstrapClient; - createClient?: () => MatrixClient; + createClient?: (options?: ICreateClientOpts) => MatrixClient; deviceIDPersistence?: MatrixDeviceIDPersistenceConfig; deviceID?: string; e2ee?: MatrixE2EEConfig; inviteAutoJoin?: MatrixInviteAutoJoinConfig; logger?: Logger; + matrixStore?: MatrixSyncStoreConfig; matrixSDKLogLevel?: "trace" | "debug" | "info" | "warn" | "error"; recoveryKey?: string; roomAllowlist?: string[]; From 3dc140f561ba3d2bc61732baa0dff2ba65e85a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:52:57 +0100 Subject: [PATCH 19/25] chore: require Node 22 for strip-types scripts --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 8f19573..6932d76 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "@beeper/chat-adapter-matrix", "version": "1.0.0", "description": "Matrix adapter for chat", + "engines": { + "node": ">=22" + }, "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", From f5893f5b980cbe4dc0b909b2f7aa3b2913feb71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:53:40 +0100 Subject: [PATCH 20/25] Refactor MatrixAdapter tests for deterministic DM and invite flows - Make test state adapter initialization run on explicit connect - Use fake timers for invite auto-join to remove timing flakiness - Split openDM coverage into cached, m.direct, and create-and-persist cases --- src/index.test.ts | 56 +++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 89785a5..5941852 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -294,12 +294,7 @@ function makeRoomMembers( function makeStateAdapter(initial: Record = {}): StateAdapter { const base = createMemoryState(); - const ready = (async () => { - await base.connect(); - for (const [key, value] of Object.entries(initial)) { - await base.set(key, value); - } - })(); + let ready = Promise.resolve(); const afterReady = async (run: () => Promise): Promise => { await ready; return run(); @@ -309,12 +304,21 @@ function makeStateAdapter(initial: Record = {}): StateAdapter { afterReady(() => base.set(key, value, ttlMs)); const setIfNotExists: StateAdapter["setIfNotExists"] = (key, value, ttlMs) => afterReady(() => base.setIfNotExists(key, value, ttlMs)); + const connect: StateAdapter["connect"] = async () => { + ready = (async () => { + await base.connect(); + for (const [key, value] of Object.entries(initial)) { + await base.set(key, value); + } + })(); + await ready; + }; return { acquireLock: vi.fn((threadId, ttlMs) => afterReady(() => base.acquireLock(threadId, ttlMs)) ), - connect: vi.fn(() => afterReady(() => base.connect())), + connect: vi.fn(connect), delete: vi.fn((key) => afterReady(() => base.delete(key))), disconnect: vi.fn(() => afterReady(() => base.disconnect())), extendLock: vi.fn((lock, ttlMs) => @@ -576,18 +580,23 @@ describe("MatrixAdapter", () => { await adapter.initialize(makeChatInstance()); const timelineHandler = fakeClient.__handlers.get("Room.timeline"); + vi.useFakeTimers(); - timelineHandler?.( - makeEvent({ - getType: () => EventType.RoomMember, - getSender: () => "@alice:beeper.com", - getStateKey: () => "@bot:beeper.com", - getContent: () => ({ membership: "invite" }), - }), - { roomId: "!invited:beeper.com" }, - false - ); - await new Promise((resolve) => setTimeout(resolve, 10)); + try { + timelineHandler?.( + makeEvent({ + getType: () => EventType.RoomMember, + getSender: () => "@alice:beeper.com", + getStateKey: () => "@bot:beeper.com", + getContent: () => ({ membership: "invite" }), + }), + { roomId: "!invited:beeper.com" }, + false + ); + await vi.runAllTimersAsync(); + } finally { + vi.useRealTimers(); + } expect(fakeClient.joinRoom).toHaveBeenCalledTimes(2); expect(fakeClient.joinRoom).toHaveBeenNthCalledWith(1, "!invited:beeper.com"); @@ -2057,7 +2066,7 @@ describe("MatrixAdapter", () => { } }); - it("openDM reuses cached mapping, then m.direct mapping, then creates and persists", async () => { + it("openDM reuses a cached mapping", async () => { const fakeClient = makeClient(); const cachedState = makeStateAdapter({ "matrix:dm:%40bob%3Abeeper.com": "!cached-dm:beeper.com", @@ -2071,7 +2080,11 @@ describe("MatrixAdapter", () => { const cachedThread = await adapterFromCache.openDM("@bob:beeper.com"); expect(cachedThread).toBe("matrix:!cached-dm%3Abeeper.com"); expect(fakeClient.createRoom).not.toHaveBeenCalled(); + expect(fakeClient.setAccountData).not.toHaveBeenCalled(); + }); + it("openDM reuses the m.direct mapping and caches it locally", async () => { + const fakeClient = makeClient(); const directState = makeStateAdapter(); fakeClient.getAccountDataFromServer.mockResolvedValue({ "@bob:beeper.com": ["!from-direct:beeper.com"], @@ -2088,7 +2101,12 @@ describe("MatrixAdapter", () => { "matrix:dm:%40bob%3Abeeper.com", "!from-direct:beeper.com" ); + expect(fakeClient.createRoom).not.toHaveBeenCalled(); + expect(fakeClient.setAccountData).not.toHaveBeenCalled(); + }); + it("openDM creates and persists a DM when no mapping exists", async () => { + const fakeClient = makeClient(); const createState = makeStateAdapter(); fakeClient.getAccountDataFromServer.mockResolvedValue({}); fakeClient.createRoom.mockResolvedValue({ room_id: "!created-dm:beeper.com" }); From e630f7cd00536907026fe1a997bfcd00edb0c9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:55:20 +0100 Subject: [PATCH 21/25] Stabilize pagination and state adapter readiness in tests - Fetch E2E history pages until all offline message IDs are seen or cursor ends - Make test state adapter lazily connect once before get/set operations --- e2e/e2e.test.ts | 12 ++++++++++-- src/index.test.ts | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index ac3f8f3..be23991 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -483,9 +483,10 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { expect(liveMessage.text).toContain(liveTag); const fetchedIds = new Set(); + const offlinePostIds = new Set(offlinePosts.map((post) => post.id)); let cursor: string | undefined; - for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { + while (true) { const page = await bot.adapter.fetchMessages(bot.adapter.encodeThreadId({ roomID }), { direction: "backward", limit: 5, @@ -494,10 +495,17 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { for (const message of page.messages) { fetchedIds.add(message.id); } + + if ([...offlinePostIds].every((id) => fetchedIds.has(id))) { + break; + } + cursor = page.nextCursor; + if (!cursor) { + break; + } } - expect(cursor).toBeTruthy(); expect(fetchedIds.has(livePosted.id)).toBe(true); expect(offlinePosts.every((post) => fetchedIds.has(post.id))).toBe(true); }); diff --git a/src/index.test.ts b/src/index.test.ts index 5941852..aefc04c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -294,18 +294,9 @@ function makeRoomMembers( function makeStateAdapter(initial: Record = {}): StateAdapter { const base = createMemoryState(); - let ready = Promise.resolve(); - const afterReady = async (run: () => Promise): Promise => { - await ready; - return run(); - }; - const get: StateAdapter["get"] = (key) => afterReady(() => base.get(key)); - const set: StateAdapter["set"] = (key, value, ttlMs) => - afterReady(() => base.set(key, value, ttlMs)); - const setIfNotExists: StateAdapter["setIfNotExists"] = (key, value, ttlMs) => - afterReady(() => base.setIfNotExists(key, value, ttlMs)); + let ready: Promise | null = null; const connect: StateAdapter["connect"] = async () => { - ready = (async () => { + ready ??= (async () => { await base.connect(); for (const [key, value] of Object.entries(initial)) { await base.set(key, value); @@ -313,6 +304,15 @@ function makeStateAdapter(initial: Record = {}): StateAdapter { })(); await ready; }; + const afterReady = async (run: () => Promise): Promise => { + await (ready ?? connect()); + return run(); + }; + const get: StateAdapter["get"] = (key) => afterReady(() => base.get(key)); + const set: StateAdapter["set"] = (key, value, ttlMs) => + afterReady(() => base.set(key, value, ttlMs)); + const setIfNotExists: StateAdapter["setIfNotExists"] = (key, value, ttlMs) => + afterReady(() => base.setIfNotExists(key, value, ttlMs)); return { acquireLock: vi.fn((threadId, ttlMs) => From 3bae25bc5dc2aa92b1064d2fe0682607ecdb2d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:56:25 +0100 Subject: [PATCH 22/25] Set package version to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6932d76..ea1b884 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/chat-adapter-matrix", - "version": "1.0.0", + "version": "0.2.0", "description": "Matrix adapter for chat", "engines": { "node": ">=22" From 11a1801be623dd7d7fa6aba311d25fb7d14427a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 00:58:42 +0100 Subject: [PATCH 23/25] Rewrite README with API-first usage and full config reference - Expand usage docs with env-based `createMatrixAdapter()` setup and lifecycle - Document auth modes, thread model, feature matrix, and limitations - Add comprehensive options/env var tables plus persistence/E2EE guidance --- README.md | 287 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 201 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 9d8cf0e..0e3f280 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,223 @@ # @beeper/chat-adapter-matrix -Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). - -This adapter runs over Matrix sync, so you do not need to host a webhook endpoint. - -If you use Beeper, this adapter lets your Chat SDK bot work with your Matrix/Beeper conversations (including bridged networks such as WhatsApp, Telegram, Instagram, Signal, and others). +Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). Build Chat SDK bots that run over Matrix sync instead of webhooks, including Beeper conversations and bridged networks such as WhatsApp, Telegram, Instagram, and Signal. ## Installation +Requires Node.js `>=22`. + ```bash -pnpm add chat @beeper/chat-adapter-matrix matrix-js-sdk +pnpm add chat @beeper/chat-adapter-matrix ``` -## Quick Start +## Usage + +`createMatrixAdapter()` can read its configuration from environment variables, similar to the upstream Chat SDK adapters. ```ts import { Chat } from "chat"; import { createMemoryState } from "@chat-adapter/state-memory"; import { createMatrixAdapter } from "@beeper/chat-adapter-matrix"; +const matrix = createMatrixAdapter(); + const bot = new Chat({ - userName: "beeper-bot", + userName: process.env.BOT_USER_NAME ?? "beeper-bot", state: createMemoryState(), adapters: { - matrix: createMatrixAdapter({ - baseURL: process.env.MATRIX_BASE_URL!, - auth: { - type: "accessToken", - accessToken: process.env.MATRIX_ACCESS_TOKEN!, - }, - recoveryKey: process.env.MATRIX_RECOVERY_KEY, - }), + matrix, }, }); bot.onNewMention(async (thread, message) => { await thread.subscribe(); - await thread.post(`Hi ${message.author.userName}`); + await thread.post(`Hi ${message.author.userName}. Mention me or run /ping.`); +}); + +bot.onSlashCommand("/ping", async (event) => { + await event.channel.post("pong"); +}); + +await bot.initialize(); + +process.on("SIGINT", async () => { + await matrix.shutdown(); + process.exit(0); +}); + +process.on("SIGTERM", async () => { + await matrix.shutdown(); + process.exit(0); }); ``` +Chat SDK concepts such as `Chat`, `Thread`, `Message`, subscriptions, and handlers work the same here. See the upstream docs for the core API: + +- [Getting Started](https://chat-sdk.dev/docs/getting-started) +- [Usage](https://chat-sdk.dev/docs/usage) +- [Threads, Messages, and Channels](https://chat-sdk.dev/docs/threads-messages-channels) +- [Direct Messages](https://chat-sdk.dev/docs/direct-messages) + ## Authentication -Use either access-token auth or username/password auth. +### Access token -Access token: +Best when you already have a Matrix or Beeper access token. ```ts -auth: { type: "accessToken", accessToken: process.env.MATRIX_ACCESS_TOKEN! }; +createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "accessToken", + accessToken: process.env.MATRIX_ACCESS_TOKEN!, + userID: process.env.MATRIX_USER_ID, + }, +}); ``` -Username/password: +### Username/password + +Best when you want the adapter to log in and reuse persisted sessions between restarts. ```ts -auth: { - type: "password", - username: process.env.MATRIX_USERNAME!, - password: process.env.MATRIX_PASSWORD!, -}; +createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "password", + username: process.env.MATRIX_USERNAME!, + password: process.env.MATRIX_PASSWORD!, + userID: process.env.MATRIX_USER_ID, + }, +}); ``` -## Environment +## Thread Model + +- A Matrix room is a Chat SDK channel. +- Top-level room messages belong to the channel timeline. +- Matrix threaded replies map to Chat SDK threads using `roomID + rootEventID`. +- `openDM(userId)` reuses existing direct rooms from Matrix account data when possible and creates one when needed. + +## Features + +| Feature | Supported | +|---------|-----------| +| Mentions | Yes | +| Rich text | Yes, via Matrix `formatted_body` and `m.mentions` | +| Thread replies | Yes | +| Reactions (add/remove) | Yes | +| Message edits | Yes | +| Message deletes | Yes | +| Typing indicator | Yes | +| Direct messages | Yes | +| File uploads | Yes | +| Message history | Yes | +| Channel and thread metadata | Yes | +| E2EE | Yes | +| Invite auto-join | Yes | +| Slash commands | Prefix-parsed from message text | +| Webhooks | No, this adapter uses sync polling | +| Cards | No | +| Modals | No | +| Ephemeral messages | No | +| Native streaming | No | + +## Configuration + +### Adapter options + +| Option | Required | Description | +|--------|----------|-------------| +| `baseURL` | Yes | Matrix homeserver base URL | +| `auth` | Yes | Access token or password auth config | +| `userName` | No | Bot name used for mention detection. Defaults to `MATRIX_BOT_USERNAME`, then `MOM_BOT_USERNAME`, then `"bot"` | +| `deviceID` | No | Device ID to use. If omitted, one is generated and persisted by default | +| `commandPrefix` | No | Prefix used to parse slash-style commands from message text. Defaults to `/` | +| `roomAllowlist` | No | Restrict processing to specific room IDs | +| `inviteAutoJoin` | No | Auto-join incoming invites, optionally with inviter allowlisting | +| `logger` | No | Custom Chat SDK logger | +| `deviceIDPersistence` | No | Control generated device ID persistence in Chat SDK state | +| `session` | No | Control persisted session reuse for password auth | +| `matrixStore` | No | Persist Matrix sync state in the Chat SDK state adapter | +| `e2ee` | No | Configure Matrix Rust crypto storage | +| `recoveryKey` | No | Recovery key for key backup bootstrap | +| `matrixSDKLogLevel` | No | Matrix SDK log level: `trace`, `debug`, `info`, `warn`, or `error` | +| `createClient` | No | Override Matrix client creation | +| `createBootstrapClient` | No | Override auth bootstrap client creation | +| `createStore` | No | Override Matrix sync store creation | + +### Environment variables + +When you call `createMatrixAdapter()` without arguments, these env vars are used: + +| Variable | Required | Description | +|----------|----------|-------------| +| `MATRIX_BASE_URL` | Yes | Matrix homeserver base URL | +| `MATRIX_ACCESS_TOKEN` | Yes* | Access token for access-token auth | +| `MATRIX_USERNAME` | Yes* | Username for password auth | +| `MATRIX_PASSWORD` | Yes* | Password for password auth | +| `MATRIX_USER_ID` | No | User ID hint. Recommended for stable state scoping | +| `MATRIX_BOT_USERNAME` | No | Mention-detection username | +| `MATRIX_DEVICE_ID` | No | Explicit device ID | +| `MATRIX_DEVICE_ID_PERSIST_ENABLED` | No | Enable generated device ID persistence. Defaults to `true` | +| `MATRIX_DEVICE_ID_PERSIST_KEY` | No | Override the device ID persistence key | +| `MATRIX_COMMAND_PREFIX` | No | Slash command prefix. Defaults to `/` | +| `MATRIX_INVITE_AUTOJOIN_ENABLED` | No | Enable invite auto-join. Defaults to `true` when an allowlist is set, otherwise `false` | +| `MATRIX_INVITE_AUTOJOIN_ALLOWLIST` | No | Comma-separated Matrix user IDs allowed to invite the bot | +| `MATRIX_RECOVERY_KEY` | No | Recovery key for Matrix key backup | +| `MATRIX_E2EE_ENABLED` | No | Enable E2EE. Defaults to `true` when `MATRIX_RECOVERY_KEY` is set | +| `MATRIX_E2EE_USE_INDEXEDDB` | No | Request IndexedDB-backed crypto storage when available | +| `MATRIX_E2EE_DB_PREFIX` | No | Crypto database prefix | +| `MATRIX_E2EE_STORAGE_PASSWORD` | No | Crypto storage password. Defaults to `MATRIX_RECOVERY_KEY` | +| `MATRIX_E2EE_STORAGE_KEY_BASE64` | No | Base64-encoded crypto storage key | +| `MATRIX_SESSION_ENABLED` | No | Enable persisted password sessions. Defaults to `true` | +| `MATRIX_SESSION_KEY` | No | Override the persisted session state key | +| `MATRIX_SESSION_TTL_MS` | No | TTL for persisted session entries | +| `MATRIX_SDK_LOG_LEVEL` | No | Matrix SDK log level. Defaults to `error` | + +\*Use either `MATRIX_ACCESS_TOKEN`, or `MATRIX_USERNAME` plus `MATRIX_PASSWORD`. + +## Persistence and E2EE + +For production, pair the adapter with a persistent Chat SDK state adapter such as Redis. -Required: +```ts +import { Chat } from "chat"; +import { createRedisState } from "@chat-adapter/state-redis"; +import { createMatrixAdapter } from "@beeper/chat-adapter-matrix"; -- `MATRIX_BASE_URL` -- Access token mode: `MATRIX_ACCESS_TOKEN` -- Password mode: `MATRIX_USERNAME`, `MATRIX_PASSWORD` +const matrix = createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "accessToken", + accessToken: process.env.MATRIX_ACCESS_TOKEN!, + userID: process.env.MATRIX_USER_ID, + }, + matrixStore: { + enabled: true, + }, + e2ee: { + enabled: true, + persistSecretsBundle: true, + }, +}); -Common optional: +const bot = new Chat({ + userName: "beeper-bot", + state: createRedisState({ url: process.env.REDIS_URL! }), + adapters: { matrix }, +}); +``` -- `MATRIX_USER_ID` -- `MATRIX_DEVICE_ID` -- `MATRIX_RECOVERY_KEY` -- `MATRIX_BOT_USERNAME` (mention detection display name, defaults to `MOM_BOT_USERNAME` then `bot`) -- `MATRIX_INVITE_AUTOJOIN_ENABLED` (`true`/`false`; defaults to `true` when invite allowlist is set, otherwise `false`) -- `MATRIX_INVITE_AUTOJOIN_ALLOWLIST` (comma-separated Matrix user IDs allowed to invite the bot, e.g. `@alice:beeper.com,@team-bot:beeper.com`) +What persistence covers: -Advanced options are available for device/session persistence, E2EE storage, and SDK logging (`MATRIX_SDK_LOG_LEVEL`). +- Generated device IDs can be reused across restarts. +- Password login sessions are reused by default. +- `matrixStore.enabled` persists sync snapshots so the SDK can resume faster. +- `persistSecretsBundle` stores exported E2EE secrets in Chat SDK state for later import. ## Invite Auto-Join -Enable this when you want the bot to accept incoming room invites automatically. - ```ts createMatrixAdapter({ baseURL: process.env.MATRIX_BASE_URL!, @@ -100,60 +235,40 @@ createMatrixAdapter({ Behavior: -- Only `m.room.member` invites targeted at the bot user are considered. -- If `inviterAllowlist` is set, only those inviters are accepted. -- If `roomAllowlist` is also set, both checks must pass. +- Only invites targeting the bot user are considered. +- If `roomAllowlist` is set, the room must be allowed. +- If `inviterAllowlist` is set, the inviter must be allowed. +- Rate-limited joins are retried automatically. -## Running The Example +## Message and History APIs -Copy [`examples/.env.example`](./examples/.env.example) to `examples/.env`, then run: +The adapter supports the broader Chat SDK message APIs beyond basic posting: -```bash -pnpm example -``` +- `fetchMessage(threadId, messageId)` +- `fetchMessages(threadId, options)` +- `fetchChannelMessages(channelId, options)` +- `fetchThread(threadId)` +- `fetchChannelInfo(channelId)` +- `listThreads(channelId, options)` +- `openDM(userId)` -If you need Beeper credentials, generate them interactively: +Inbound `formatted_body` is normalized into Chat SDK rich text, reply fallbacks are stripped from visible text, and outbound markdown plus Chat SDK mention placeholders are rendered back to Matrix HTML and pill mentions. -```bash -pnpm matrix-token -``` +## Limitations -The helper prints: +- `handleWebhook()` returns `501` by design because Matrix uses sync polling here. +- Cards, modals, and ephemeral messages are not implemented. +- Native streaming is not implemented at the adapter layer. +- Slash commands are parsed from plain text messages; Matrix does not provide native slash command events. -- `MATRIX_BASE_URL` -- `MATRIX_ACCESS_TOKEN` -- `MATRIX_USER_ID` -- `MATRIX_DEVICE_ID` +## Examples -Then run: - -```bash -pnpm example -``` +- [`examples/bot.ts`](./examples/bot.ts) uses in-memory state for local development. +- [`examples/bot.redis.ts`](./examples/bot.redis.ts) uses Redis-backed state. +- [`examples/.env.example`](./examples/.env.example) lists the env vars used by the examples. +- [`scripts/get-access-token.ts`](./scripts/get-access-token.ts) helps generate Beeper credentials interactively. -## Capabilities - -- `openDM(userId)` creates or reuses direct rooms using Matrix `m.direct` account data and persisted adapter state. -- `fetchMessage(threadId, messageId)` fetches a single message with thread/channel context validation. -- `fetchChannelMessages(channelId, options)` fetches top-level room timeline messages. -- `fetchMessages(threadId, options)` and `listThreads(channelId, options)` use API-first server pagination via `matrix-js-sdk`. -- Inbound Matrix rich text is normalized from `formatted_body` when present, including reply fallback stripping and Matrix pill mention parsing. -- Outbound markdown and Chat SDK mention placeholders are rendered to Matrix `formatted_body` with `org.matrix.custom.html` and `m.mentions`. -- `fetchThread()` and `fetchChannelInfo()` expose room metadata such as `roomID`, DM status, topic, canonical alias, avatar MXC URL, and encryption details when that state is available locally. -- Outbound file support: `files` and binary `attachments` are uploaded with `uploadContent()` and sent as Matrix media messages. -- URL-only attachments are appended as links in the text body. -- `postEphemeral`, `openModal`, and native `stream` are not implemented by this adapter. - -## Notes - -- `handleWebhook()` returns `501` by design, since this adapter is sync-based. -- Access-token auth resolves identity with `whoami`. -- Password auth sends the configured `device_id` during login. -- Mention sending uses Chat SDK's standard `<@userId>` placeholder syntax and is translated into Matrix pills at send time. -- Matrix reply linkage remains in the raw event/metadata path; the adapter strips the visible quoted fallback from normalized message text. -- For production, use Redis state for stable sessions and device IDs. - -For release-specific changes and migration notes, see [CHANGELOG.md](./CHANGELOG.md). +For release-specific changes, see [CHANGELOG.md](./CHANGELOG.md). ## License From bf166673b803fd78b323b1182384b6dabd58c507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 01:03:29 +0100 Subject: [PATCH 24/25] Isolate E2E history tests into dedicated Matrix rooms - Add `createIsolatedRoom` helper that creates an encrypted room and waits for encryption + both joins - Update pagination, restart catch-up, and thread-list E2E tests to use per-test rooms - Validate `createRoom` returns a `room_id` before proceeding --- e2e/e2e.test.ts | 67 ++++++++++++++++++++++++++++++++++--------------- e2e/helpers.ts | 33 ++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts index be23991..da5670a 100644 --- a/e2e/e2e.test.ts +++ b/e2e/e2e.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { stringifyMarkdown, type Message } from "chat"; import { EventType, RoomEvent, RoomMemberEvent, type MatrixEvent } from "matrix-js-sdk"; import { + createIsolatedRoom, createParticipant, createParticipantFromSession, env, @@ -372,11 +373,18 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { it("fetchMessages pagination", async () => { const tag = `e2e-page-${nonce()}`; - const threadId = bot.adapter.encodeThreadId({ roomID }); + const paginationRoomID = await createIsolatedRoom( + bot.matrixClient, + sender.matrixClient, + sender.userID, + `pagination-${tag}`, + 45_000 + ); + const threadId = bot.adapter.encodeThreadId({ roomID: paginationRoomID }); const count = 5; // Sender sends N messages - const senderThreadId = sender.adapter.encodeThreadId({ roomID }); + const senderThreadId = sender.adapter.encodeThreadId({ roomID: paginationRoomID }); for (let i = 0; i < count; i++) { await sender.adapter.postMessage(senderThreadId, `${tag} msg-${i}`); await sleep(200); @@ -415,7 +423,14 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { it("restarts on the same session and catches up paginated room history", async () => { const offlineCount = 6; const restartTag = `e2e-restart-${nonce()}`; - const roomThreadId = sender.adapter.encodeThreadId({ roomID }); + const restartRoomID = await createIsolatedRoom( + bot.matrixClient, + sender.matrixClient, + sender.userID, + `restart-${restartTag}`, + 45_000 + ); + const roomThreadId = sender.adapter.encodeThreadId({ roomID: restartRoomID }); const botSession = bot.session; const botState = bot.state; @@ -425,7 +440,7 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { ); await waitForFetchedMessage( bot.adapter, - bot.adapter.encodeThreadId({ roomID }), + bot.adapter.encodeThreadId({ roomID: restartRoomID }), baseline.id, (message) => message.text.includes(restartTag) ); @@ -450,14 +465,14 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { }); await Promise.all([ - waitForEncryptedRoom(bot.matrixClient, roomID, 45_000), - waitForJoinedMemberCount(bot.matrixClient, roomID, 2, 45_000), + waitForEncryptedRoom(bot.matrixClient, restartRoomID, 45_000), + waitForJoinedMemberCount(bot.matrixClient, restartRoomID, 2, 45_000), ]); const latestOffline = offlinePosts[offlinePosts.length - 1]; const caughtUpMessage = await waitForFetchedMessage( bot.adapter, - bot.adapter.encodeThreadId({ roomID }), + bot.adapter.encodeThreadId({ roomID: restartRoomID }), latestOffline.id, (message) => message.text.includes(restartTag), 60_000 @@ -468,7 +483,7 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { const botSawLiveMessage = waitForEvent>((cb) => { bot.onMessage((incomingThreadId, message) => { if ( - incomingThreadId === bot.adapter.encodeThreadId({ roomID }) && + incomingThreadId === bot.adapter.encodeThreadId({ roomID: restartRoomID }) && message.text.includes(liveTag) ) { cb(message); @@ -487,11 +502,14 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { let cursor: string | undefined; while (true) { - const page = await bot.adapter.fetchMessages(bot.adapter.encodeThreadId({ roomID }), { - direction: "backward", - limit: 5, - cursor, - }); + const page = await bot.adapter.fetchMessages( + bot.adapter.encodeThreadId({ roomID: restartRoomID }), + { + direction: "backward", + limit: 5, + cursor, + } + ); for (const message of page.messages) { fetchedIds.add(message.id); } @@ -580,34 +598,43 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { it("fetches channel info, channel messages, thread info, and thread lists", async () => { const rootTag = `e2e-thread-list-root-${nonce()}`; const replyTag = `e2e-thread-list-reply-${nonce()}`; - const channelId = bot.adapter.channelIdFromThreadId(bot.adapter.encodeThreadId({ roomID })); + const threadListRoomID = await createIsolatedRoom( + bot.matrixClient, + sender.matrixClient, + sender.userID, + `thread-list-${rootTag}`, + 45_000 + ); + const channelId = bot.adapter.channelIdFromThreadId( + bot.adapter.encodeThreadId({ roomID: threadListRoomID }) + ); const roomInfo = await bot.adapter.fetchChannelInfo(channelId); expect(roomInfo.id).toBe(channelId); expect(roomInfo.isDM).toBe(false); expect((roomInfo.memberCount ?? 0) >= 2).toBe(true); - expect(roomInfo.metadata?.roomID).toBe(roomID); + expect(roomInfo.metadata?.roomID).toBe(threadListRoomID); const rootPosted = await sender.adapter.postMessage( - sender.adapter.encodeThreadId({ roomID }), + sender.adapter.encodeThreadId({ roomID: threadListRoomID }), `Thread root ${rootTag}` ); const threadId = sender.adapter.encodeThreadId({ - roomID, + roomID: threadListRoomID, rootEventID: rootPosted.id, }); await sender.adapter.postMessage(threadId, `Thread reply ${replyTag}`); await waitForFetchedMessage( bot.adapter, - bot.adapter.encodeThreadId({ roomID }), + bot.adapter.encodeThreadId({ roomID: threadListRoomID }), rootPosted.id, (message) => message.text.includes(rootTag) ); await waitForMatchingMessage( bot.adapter, - bot.adapter.encodeThreadId({ roomID, rootEventID: rootPosted.id }), + bot.adapter.encodeThreadId({ roomID: threadListRoomID, rootEventID: rootPosted.id }), (message) => message.text.includes(replyTag) ); @@ -624,7 +651,7 @@ describe.skipIf(!hasCoreCredentials)("E2E Matrix Adapter", () => { expect(threadInfo.id).toBe(threadId); expect(threadInfo.channelId).toBe(channelId); expect(threadInfo.isDM).toBe(false); - expect(threadInfo.metadata?.roomID).toBe(roomID); + expect(threadInfo.metadata?.roomID).toBe(threadListRoomID); const threads = await bot.adapter.listThreads(channelId, { limit: 20 }); const summary = threads.threads.find((thread) => thread.id === threadId); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 6b97f03..b713d9e 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -198,7 +198,35 @@ export async function getOrCreateRoom( return env.roomID; } + return createEncryptedRoom(botClient, senderUserID); +} + +export async function createIsolatedRoom( + botClient: MatrixClient, + senderClient: MatrixClient, + senderUserID: string, + roomName = `matrix-chat-adapter-e2e-${nonce()}`, + timeoutMs = 30_000 +): Promise { + const roomID = await createEncryptedRoom(botClient, senderUserID, roomName); + + await Promise.all([ + waitForEncryptedRoom(botClient, roomID, timeoutMs), + waitForEncryptedRoom(senderClient, roomID, timeoutMs), + waitForJoinedMemberCount(botClient, roomID, 2, timeoutMs), + waitForJoinedMemberCount(senderClient, roomID, 2, timeoutMs), + ]); + + return roomID; +} + +async function createEncryptedRoom( + botClient: MatrixClient, + senderUserID: string, + roomName?: string +): Promise { const { room_id } = await botClient.createRoom({ + name: roomName, preset: "private_chat", invite: [senderUserID], initial_state: [ @@ -210,8 +238,9 @@ export async function getOrCreateRoom( ], }); - // sender auto-joins via inviteAutoJoin; give sync time to propagate - await sleep(2_000); + if (typeof room_id !== "string" || room_id.length === 0) { + throw new Error("Matrix createRoom did not return room_id"); + } return room_id; } From ebba963876ef33cb2aed71e98515403f7f148533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 8 Mar 2026 01:36:08 +0100 Subject: [PATCH 25/25] Simplify Matrix persistence defaults and env-based configuration - Make persistence/state-backed Matrix store automatic when Chat state is available - Enable E2EE based on recovery key or e2ee config, and persist secrets via state - Refine device ID resolution order (explicit, auth-derived, then persisted) - Update docs, examples, and tests for new env vars and invite auto-join semantics --- README.md | 213 +++++++-------- e2e/helpers.ts | 6 +- examples/.env.example | 14 +- examples/bot.redis.ts | 2 +- examples/bot.ts | 2 +- src/index.test.ts | 115 ++++++-- src/index.ts | 319 +++++++++------------- src/store/chat-state-matrix-store.test.ts | 14 +- src/types.ts | 38 +-- 9 files changed, 353 insertions(+), 370 deletions(-) diff --git a/README.md b/README.md index 0e3f280..a27cd02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @beeper/chat-adapter-matrix -Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). Build Chat SDK bots that run over Matrix sync instead of webhooks, including Beeper conversations and bridged networks such as WhatsApp, Telegram, Instagram, and Signal. +Matrix adapter for [Chat SDK](https://chat-sdk.dev/docs). It runs over Matrix sync instead of webhooks and works with Beeper conversations and bridged networks such as WhatsApp, Telegram, Instagram, and Signal. ## Installation @@ -12,7 +12,7 @@ pnpm add chat @beeper/chat-adapter-matrix ## Usage -`createMatrixAdapter()` can read its configuration from environment variables, similar to the upstream Chat SDK adapters. +`createMatrixAdapter()` reads its config from environment variables when called without arguments. ```ts import { Chat } from "chat"; @@ -22,11 +22,9 @@ import { createMatrixAdapter } from "@beeper/chat-adapter-matrix"; const matrix = createMatrixAdapter(); const bot = new Chat({ - userName: process.env.BOT_USER_NAME ?? "beeper-bot", + userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot", state: createMemoryState(), - adapters: { - matrix, - }, + adapters: { matrix }, }); bot.onNewMention(async (thread, message) => { @@ -39,16 +37,6 @@ bot.onSlashCommand("/ping", async (event) => { }); await bot.initialize(); - -process.on("SIGINT", async () => { - await matrix.shutdown(); - process.exit(0); -}); - -process.on("SIGTERM", async () => { - await matrix.shutdown(); - process.exit(0); -}); ``` Chat SDK concepts such as `Chat`, `Thread`, `Message`, subscriptions, and handlers work the same here. See the upstream docs for the core API: @@ -62,8 +50,6 @@ Chat SDK concepts such as `Chat`, `Thread`, `Message`, subscriptions, and handle ### Access token -Best when you already have a Matrix or Beeper access token. - ```ts createMatrixAdapter({ baseURL: process.env.MATRIX_BASE_URL!, @@ -77,8 +63,6 @@ createMatrixAdapter({ ### Username/password -Best when you want the adapter to log in and reuse persisted sessions between restarts. - ```ts createMatrixAdapter({ baseURL: process.env.MATRIX_BASE_URL!, @@ -91,12 +75,87 @@ createMatrixAdapter({ }); ``` +## Defaults + +- Persistence behavior is active whenever Chat provides a `state` adapter. +- Redis or another durable state adapter is recommended for restart durability. +- `deviceID` is inferred from auth when possible, then reused from state, and only generated as a last resort. +- `recoveryKey` enables E2EE. +- `inviteAutoJoin: {}` enables invite auto-join. + +## Common Options + +```ts +createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "accessToken", + accessToken: process.env.MATRIX_ACCESS_TOKEN!, + userID: process.env.MATRIX_USER_ID, + }, + recoveryKey: process.env.MATRIX_RECOVERY_KEY, + commandPrefix: "/", + roomAllowlist: ["!room:beeper.com"], + inviteAutoJoin: { + inviterAllowlist: ["@alice:beeper.com", "@ops:beeper.com"], + }, + matrixSDKLogLevel: "error", +}); +``` + +Advanced tuning stays in code config: + +```ts +createMatrixAdapter({ + baseURL: process.env.MATRIX_BASE_URL!, + auth: { + type: "accessToken", + accessToken: process.env.MATRIX_ACCESS_TOKEN!, + userID: process.env.MATRIX_USER_ID, + }, + e2ee: { + useIndexedDB: false, + cryptoDatabasePrefix: "beeper-matrix-bot", + }, + persistence: { + keyPrefix: "my-bot", + session: { + ttlMs: 86_400_000, + }, + sync: { + persistIntervalMs: 10_000, + }, + }, +}); +``` + +## Environment Variables + +`createMatrixAdapter()` with no arguments uses only these env vars: + +| Variable | Required | Description | +|----------|----------|-------------| +| `MATRIX_BASE_URL` | Yes | Matrix homeserver base URL | +| `MATRIX_ACCESS_TOKEN` | Yes* | Access token for access-token auth | +| `MATRIX_USERNAME` | Yes* | Username for password auth | +| `MATRIX_PASSWORD` | Yes* | Password for password auth | +| `MATRIX_USER_ID` | No | User ID hint | +| `MATRIX_DEVICE_ID` | No | Explicit device ID override | +| `MATRIX_RECOVERY_KEY` | No | Enables E2EE and key-backup bootstrap | +| `MATRIX_BOT_USERNAME` | No | Mention-detection username | +| `MATRIX_COMMAND_PREFIX` | No | Slash command prefix. Defaults to `/` | +| `MATRIX_INVITE_AUTOJOIN` | No | Enable invite auto-join | +| `MATRIX_INVITE_AUTOJOIN_ALLOWLIST` | No | Comma-separated Matrix user IDs allowed to invite the bot | +| `MATRIX_SDK_LOG_LEVEL` | No | Matrix SDK log level | + +\*Use either `MATRIX_ACCESS_TOKEN`, or `MATRIX_USERNAME` plus `MATRIX_PASSWORD`. + ## Thread Model - A Matrix room is a Chat SDK channel. - Top-level room messages belong to the channel timeline. - Matrix threaded replies map to Chat SDK threads using `roomID + rootEventID`. -- `openDM(userId)` reuses existing direct rooms from Matrix account data when possible and creates one when needed. +- `openDM(userId)` reuses existing direct rooms when possible and creates one when needed. ## Features @@ -122,64 +181,9 @@ createMatrixAdapter({ | Ephemeral messages | No | | Native streaming | No | -## Configuration - -### Adapter options - -| Option | Required | Description | -|--------|----------|-------------| -| `baseURL` | Yes | Matrix homeserver base URL | -| `auth` | Yes | Access token or password auth config | -| `userName` | No | Bot name used for mention detection. Defaults to `MATRIX_BOT_USERNAME`, then `MOM_BOT_USERNAME`, then `"bot"` | -| `deviceID` | No | Device ID to use. If omitted, one is generated and persisted by default | -| `commandPrefix` | No | Prefix used to parse slash-style commands from message text. Defaults to `/` | -| `roomAllowlist` | No | Restrict processing to specific room IDs | -| `inviteAutoJoin` | No | Auto-join incoming invites, optionally with inviter allowlisting | -| `logger` | No | Custom Chat SDK logger | -| `deviceIDPersistence` | No | Control generated device ID persistence in Chat SDK state | -| `session` | No | Control persisted session reuse for password auth | -| `matrixStore` | No | Persist Matrix sync state in the Chat SDK state adapter | -| `e2ee` | No | Configure Matrix Rust crypto storage | -| `recoveryKey` | No | Recovery key for key backup bootstrap | -| `matrixSDKLogLevel` | No | Matrix SDK log level: `trace`, `debug`, `info`, `warn`, or `error` | -| `createClient` | No | Override Matrix client creation | -| `createBootstrapClient` | No | Override auth bootstrap client creation | -| `createStore` | No | Override Matrix sync store creation | - -### Environment variables - -When you call `createMatrixAdapter()` without arguments, these env vars are used: - -| Variable | Required | Description | -|----------|----------|-------------| -| `MATRIX_BASE_URL` | Yes | Matrix homeserver base URL | -| `MATRIX_ACCESS_TOKEN` | Yes* | Access token for access-token auth | -| `MATRIX_USERNAME` | Yes* | Username for password auth | -| `MATRIX_PASSWORD` | Yes* | Password for password auth | -| `MATRIX_USER_ID` | No | User ID hint. Recommended for stable state scoping | -| `MATRIX_BOT_USERNAME` | No | Mention-detection username | -| `MATRIX_DEVICE_ID` | No | Explicit device ID | -| `MATRIX_DEVICE_ID_PERSIST_ENABLED` | No | Enable generated device ID persistence. Defaults to `true` | -| `MATRIX_DEVICE_ID_PERSIST_KEY` | No | Override the device ID persistence key | -| `MATRIX_COMMAND_PREFIX` | No | Slash command prefix. Defaults to `/` | -| `MATRIX_INVITE_AUTOJOIN_ENABLED` | No | Enable invite auto-join. Defaults to `true` when an allowlist is set, otherwise `false` | -| `MATRIX_INVITE_AUTOJOIN_ALLOWLIST` | No | Comma-separated Matrix user IDs allowed to invite the bot | -| `MATRIX_RECOVERY_KEY` | No | Recovery key for Matrix key backup | -| `MATRIX_E2EE_ENABLED` | No | Enable E2EE. Defaults to `true` when `MATRIX_RECOVERY_KEY` is set | -| `MATRIX_E2EE_USE_INDEXEDDB` | No | Request IndexedDB-backed crypto storage when available | -| `MATRIX_E2EE_DB_PREFIX` | No | Crypto database prefix | -| `MATRIX_E2EE_STORAGE_PASSWORD` | No | Crypto storage password. Defaults to `MATRIX_RECOVERY_KEY` | -| `MATRIX_E2EE_STORAGE_KEY_BASE64` | No | Base64-encoded crypto storage key | -| `MATRIX_SESSION_ENABLED` | No | Enable persisted password sessions. Defaults to `true` | -| `MATRIX_SESSION_KEY` | No | Override the persisted session state key | -| `MATRIX_SESSION_TTL_MS` | No | TTL for persisted session entries | -| `MATRIX_SDK_LOG_LEVEL` | No | Matrix SDK log level. Defaults to `error` | - -\*Use either `MATRIX_ACCESS_TOKEN`, or `MATRIX_USERNAME` plus `MATRIX_PASSWORD`. - -## Persistence and E2EE +## Persistence -For production, pair the adapter with a persistent Chat SDK state adapter such as Redis. +For production, pair the adapter with a durable Chat state adapter such as Redis. ```ts import { Chat } from "chat"; @@ -193,56 +197,27 @@ const matrix = createMatrixAdapter({ accessToken: process.env.MATRIX_ACCESS_TOKEN!, userID: process.env.MATRIX_USER_ID, }, - matrixStore: { - enabled: true, - }, - e2ee: { - enabled: true, - persistSecretsBundle: true, - }, + recoveryKey: process.env.MATRIX_RECOVERY_KEY, }); const bot = new Chat({ - userName: "beeper-bot", + userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot", state: createRedisState({ url: process.env.REDIS_URL! }), adapters: { matrix }, }); ``` -What persistence covers: - -- Generated device IDs can be reused across restarts. -- Password login sessions are reused by default. -- `matrixStore.enabled` persists sync snapshots so the SDK can resume faster. -- `persistSecretsBundle` stores exported E2EE secrets in Chat SDK state for later import. - -## Invite Auto-Join - -```ts -createMatrixAdapter({ - baseURL: process.env.MATRIX_BASE_URL!, - auth: { - type: "accessToken", - accessToken: process.env.MATRIX_ACCESS_TOKEN!, - userID: process.env.MATRIX_USER_ID, - }, - inviteAutoJoin: { - enabled: true, - inviterAllowlist: ["@alice:beeper.com", "@ops:beeper.com"], - }, -}); -``` - -Behavior: +Persistence covers: -- Only invites targeting the bot user are considered. -- If `roomAllowlist` is set, the room must be allowed. -- If `inviterAllowlist` is set, the inviter must be allowed. -- Rate-limited joins are retried automatically. +- generated or inferred device IDs +- password login sessions +- DM room mappings +- Matrix sync snapshots +- E2EE secrets bundles when E2EE is enabled ## Message and History APIs -The adapter supports the broader Chat SDK message APIs beyond basic posting: +The adapter supports: - `fetchMessage(threadId, messageId)` - `fetchMessages(threadId, options)` @@ -252,8 +227,6 @@ The adapter supports the broader Chat SDK message APIs beyond basic posting: - `listThreads(channelId, options)` - `openDM(userId)` -Inbound `formatted_body` is normalized into Chat SDK rich text, reply fallbacks are stripped from visible text, and outbound markdown plus Chat SDK mention placeholders are rendered back to Matrix HTML and pill mentions. - ## Limitations - `handleWebhook()` returns `501` by design because Matrix uses sync polling here. @@ -264,8 +237,8 @@ Inbound `formatted_body` is normalized into Chat SDK rich text, reply fallbacks ## Examples - [`examples/bot.ts`](./examples/bot.ts) uses in-memory state for local development. -- [`examples/bot.redis.ts`](./examples/bot.redis.ts) uses Redis-backed state. -- [`examples/.env.example`](./examples/.env.example) lists the env vars used by the examples. +- [`examples/bot.redis.ts`](./examples/bot.redis.ts) uses Redis-backed state for restart durability. +- [`examples/.env.example`](./examples/.env.example) lists the supported env vars for the examples. - [`scripts/get-access-token.ts`](./scripts/get-access-token.ts) helps generate Beeper credentials interactively. For release-specific changes, see [CHANGELOG.md](./CHANGELOG.md). diff --git a/e2e/helpers.ts b/e2e/helpers.ts index b713d9e..08f258d 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -69,14 +69,10 @@ export async function createParticipantFromSession(opts: { userID: opts.session.userID, }, deviceID: opts.session.deviceID, - inviteAutoJoin: { enabled: true }, + inviteAutoJoin: {}, e2ee: { - enabled: true, useIndexedDB: false, }, - matrixStore: { - enabled: true, - }, recoveryKey: opts.recoveryKey, }); diff --git a/examples/.env.example b/examples/.env.example index 76247bd..e21ca62 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -12,17 +12,9 @@ MATRIX_USER_ID=@bot:beeper.com # Optional MATRIX_DEVICE_ID= -MATRIX_DEVICE_ID_PERSIST_ENABLED=true -MATRIX_DEVICE_ID_PERSIST_KEY= MATRIX_RECOVERY_KEY= -MATRIX_E2EE_ENABLED= -MATRIX_E2EE_USE_INDEXEDDB=false -MATRIX_E2EE_DB_PREFIX=beeper-chat-adapter-matrix -MATRIX_E2EE_STORAGE_PASSWORD= -MATRIX_E2EE_STORAGE_KEY_BASE64= -MATRIX_SESSION_ENABLED=true -MATRIX_SESSION_KEY= -MATRIX_SESSION_TTL_MS= +MATRIX_INVITE_AUTOJOIN=false +MATRIX_INVITE_AUTOJOIN_ALLOWLIST= MATRIX_SDK_LOG_LEVEL=error -BOT_USER_NAME=beeper-bot +MATRIX_BOT_USERNAME=beeper-bot REDIS_URL=redis://localhost:6379 diff --git a/examples/bot.redis.ts b/examples/bot.redis.ts index 122f725..b2e2b88 100644 --- a/examples/bot.redis.ts +++ b/examples/bot.redis.ts @@ -15,7 +15,7 @@ const logger = new ConsoleLogger( const matrix = createMatrixAdapter(); const bot = new Chat({ - userName: process.env.BOT_USER_NAME ?? "beeper-bot", + userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot", logger, state: createRedisState({ url: redisURL }), adapters: { diff --git a/examples/bot.ts b/examples/bot.ts index e222710..a4e251c 100644 --- a/examples/bot.ts +++ b/examples/bot.ts @@ -10,7 +10,7 @@ const logger = new ConsoleLogger( const matrix = createMatrixAdapter(); const bot = new Chat({ - userName: process.env.BOT_USER_NAME ?? "beeper-bot", + userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot", logger, state: createMemoryState(), adapters: { diff --git a/src/index.test.ts b/src/index.test.ts index aefc04c..4f98493 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -151,7 +151,8 @@ function mapRawToEvent(raw: RawEventLike) { type AdapterInternals = { deviceID?: string; - e2eeConfig?: { enabled?: boolean }; + e2eeConfig?: Record; + e2eeEnabled?: boolean; getSecretStorageKeyFromRecoveryKey: (opts: { keys: Record; }) => [string, Uint8Array] | null; @@ -170,7 +171,7 @@ type AdapterInternals = { userID: string; deviceID?: string; }>; - resolveDeviceID: () => Promise; + resolveDeviceID: (...candidates: Array) => Promise; stateAdapter: StateAdapter | null; }; @@ -523,7 +524,6 @@ describe("MatrixAdapter", () => { userID: "@bot:beeper.com", }, inviteAutoJoin: { - enabled: true, inviterAllowlist: ["@alice:beeper.com"], }, createClient: () => asMatrixClient(fakeClient), @@ -571,7 +571,6 @@ describe("MatrixAdapter", () => { userID: "@bot:beeper.com", }, inviteAutoJoin: { - enabled: true, inviterAllowlist: ["@alice:beeper.com"], }, logger: logger.logger, @@ -623,7 +622,6 @@ describe("MatrixAdapter", () => { userID: "@bot:beeper.com", }, inviteAutoJoin: { - enabled: true, inviterAllowlist: ["@trusted:beeper.com"], }, createClient: () => asMatrixClient(fakeClient), @@ -774,7 +772,7 @@ describe("MatrixAdapter", () => { }, deviceID: "DEVICE1", createClient: () => asMatrixClient(fakeClient), - e2ee: { enabled: true }, + e2ee: {}, }); await adapter.initialize(makeChatInstance({ processMessage })); @@ -809,7 +807,23 @@ describe("MatrixAdapter", () => { }) ); - expect(adapter.e2eeConfig?.enabled).toBe(true); + expect(adapter.e2eeEnabled).toBe(true); + }); + + it("enables e2ee when explicit e2ee config is provided", () => { + const adapter = getInternals( + createMatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + e2ee: {}, + }) + ); + + expect(adapter.e2eeEnabled).toBe(true); }); it("decodes recovery key for secret storage callback", () => { @@ -1330,7 +1344,7 @@ describe("MatrixAdapter", () => { expect(fakeClient.stopClient).toHaveBeenCalledOnce(); }); - it("passes a chat-backed matrix store into custom client creation when enabled", async () => { + it("passes a chat-backed matrix store into custom client creation when state is available", async () => { const fakeClient = makeClient(); const createStore = vi.fn(() => ({ save: vi.fn(async () => undefined), @@ -1378,7 +1392,6 @@ describe("MatrixAdapter", () => { const adapter = new MatrixAdapter({ baseURL: "https://hs.beeper.com", auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, - matrixStore: { enabled: true }, createStore, createClient, }); @@ -1388,7 +1401,7 @@ describe("MatrixAdapter", () => { expect(createStore).toHaveBeenCalledWith( expect.objectContaining({ scopeKey: expect.stringMatching( - /^matrix:store:v1:https%3A%2F%2Fhs\.beeper\.com:%40bot%3Abeeper\.com:/ + /^matrix:store:https%3A%2F%2Fhs\.beeper\.com:%40bot%3Abeeper\.com:/ ), }) ); @@ -1404,7 +1417,7 @@ describe("MatrixAdapter", () => { const fakeClient = makeClient(); fakeClient.__crypto.importSecretsBundle.mockResolvedValue(undefined); const state = makeStateAdapter({ - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": { bundle: "persisted" }, }); const adapter = new MatrixAdapter({ @@ -1412,8 +1425,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, deviceID: "DEVICE1", createClient: () => asMatrixClient(fakeClient), - matrixStore: { enabled: true }, - e2ee: { enabled: true }, + e2ee: {}, }); await adapter.initialize(makeChatInstance({ state })); @@ -1425,7 +1437,7 @@ describe("MatrixAdapter", () => { }); expect(fakeClient.__crypto.exportSecretsBundle).toHaveBeenCalled(); expect(state.set).toHaveBeenCalledWith( - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle", + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle", { bundle: "secret" } ); }); @@ -1435,7 +1447,7 @@ describe("MatrixAdapter", () => { fakeClient.__crypto.importSecretsBundle.mockRejectedValue(new Error("boom")); const logger = makeTestLogger(); const state = makeStateAdapter({ - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:secrets-bundle": { bundle: "persisted" }, }); const adapter = new MatrixAdapter({ @@ -1443,8 +1455,7 @@ describe("MatrixAdapter", () => { auth: { type: "accessToken", accessToken: "token", userID: "@bot:beeper.com" }, deviceID: "DEVICE1", createClient: () => asMatrixClient(fakeClient), - matrixStore: { enabled: true }, - e2ee: { enabled: true }, + e2ee: {}, logger: logger.logger, }); @@ -1636,11 +1647,11 @@ describe("MatrixAdapter", () => { expect(resolved).toMatchObject({ accessToken: "fresh-token", userID: "@bot:beeper.com", - deviceID: "DEVICE2", + deviceID: "DEVICE1", }); }); - it("uses whoami device_id for access token auth", async () => { + it("prefers an explicit deviceID over auth-derived device IDs", async () => { const whoami = vi.fn(async () => ({ user_id: "@bot:beeper.com", device_id: "DEVICE_FROM_WHOAMI", @@ -1665,6 +1676,38 @@ describe("MatrixAdapter", () => { internals.stateAdapter = makeStateAdapter(); const resolved = await internals.resolveAuth(); + expect(whoami).toHaveBeenCalledOnce(); + expect(resolved).toMatchObject({ + accessToken: "token", + userID: "@bot:beeper.com", + deviceID: "DEVICE_FALLBACK", + }); + }); + + it("uses whoami device_id for access token auth when no explicit deviceID is provided", async () => { + const whoami = vi.fn(async () => ({ + user_id: "@bot:beeper.com", + device_id: "DEVICE_FROM_WHOAMI", + })); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createBootstrapClient: () => + ({ + whoami, + loginWithPassword: vi.fn(), + }), + }); + + const internals = getInternals(adapter); + internals.stateAdapter = makeStateAdapter(); + const resolved = await internals.resolveAuth(); + expect(whoami).toHaveBeenCalledOnce(); expect(resolved).toMatchObject({ accessToken: "token", @@ -1673,6 +1716,40 @@ describe("MatrixAdapter", () => { }); }); + it("falls back to a persisted deviceID when auth does not return one", async () => { + const whoami = vi.fn(async () => ({ + user_id: "@bot:beeper.com", + })); + const state = makeStateAdapter({ + "matrix:device:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com": + "DEVICE_FROM_STATE", + }); + + const adapter = new MatrixAdapter({ + baseURL: "https://hs.beeper.com", + auth: { + type: "accessToken", + accessToken: "token", + userID: "@bot:beeper.com", + }, + createBootstrapClient: () => + ({ + whoami, + loginWithPassword: vi.fn(), + }), + }); + + const internals = getInternals(adapter); + internals.stateAdapter = state; + const resolved = await internals.resolveAuth(); + + expect(resolved).toMatchObject({ + accessToken: "token", + userID: "@bot:beeper.com", + deviceID: "DEVICE_FROM_STATE", + }); + }); + it("rejects legacy cursors for API pagination", async () => { const fakeClient = makeClient(); const adapter = await makeInitializedAdapter(fakeClient); diff --git a/src/index.ts b/src/index.ts index 95c5670..f22131d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,17 +69,15 @@ import type { MatrixAdapterConfig, MatrixAuthConfig, MatrixCreateStoreOptions, - MatrixSyncStoreConfig, + MatrixPersistenceConfig, + MatrixPersistenceSyncConfig, MatrixThreadID, } from "./types"; const MATRIX_PREFIX = "matrix"; -const MATRIX_DEVICE_PREFIX = "matrix:device"; -const MATRIX_DM_PREFIX = "matrix:dm"; -const MATRIX_SESSION_PREFIX = "matrix:session"; -const MATRIX_STORE_PREFIX = "matrix:store:v1"; const MATRIX_CURSOR_PREFIX = "mxv1:"; const DEFAULT_COMMAND_PREFIX = "/"; +const DEFAULT_PERSISTENCE_KEY_PREFIX = "matrix"; const TYPING_TIMEOUT_MS = 30_000; const DEFAULT_MATRIX_STORE_PERSIST_INTERVAL_MS = 30_000; const FAST_SYNC_DEFAULTS: NonNullable = { @@ -138,11 +136,11 @@ type ResolvedAuth = { userID: string; }; -type ResolvedMatrixStoreConfig = { - enabled: boolean; +type ResolvedPersistenceConfig = { keyPrefix: string; - persistIntervalMs: number; - snapshotTtlMs?: number; + session: Pick, "decrypt" | "encrypt" | "ttlMs">; + sync: Required, "persistIntervalMs">> & + Pick, "snapshotTtlMs">; }; type StoredSession = { @@ -159,11 +157,6 @@ type StoredSession = { username?: string; }; -type DeviceIDPersistenceConfig = { - enabled: boolean; - key?: string; -}; - type CursorKind = "room_messages" | "thread_relations" | "thread_list"; type CursorDirection = "forward" | "backward"; @@ -240,18 +233,11 @@ export class MatrixAdapter implements Adapter { private readonly createStoreFn?: MatrixAdapterConfig["createStore"]; private readonly createBootstrapClientFn?: MatrixAdapterConfig["createBootstrapClient"]; private readonly e2eeConfig?: MatrixAdapterConfig["e2ee"]; - private readonly matrixStoreConfig: ResolvedMatrixStoreConfig; + private readonly e2eeEnabled: boolean; + private readonly persistenceConfig: ResolvedPersistenceConfig; private readonly recoveryKey?: string; private readonly matrixSDKLogLevel?: MatrixAdapterConfig["matrixSDKLogLevel"]; - private readonly deviceIDPersistence: DeviceIDPersistenceConfig; private readonly loggerProvided: boolean; - private readonly sessionConfig: Required< - Pick, "enabled"> - > & - Pick< - NonNullable, - "decrypt" | "encrypt" | "key" | "ttlMs" - >; private logger: Logger; private chat: ChatInstance | null = null; @@ -283,8 +269,7 @@ export class MatrixAdapter implements Adapter { const inviteAutoJoinInviterAllowlist = normalizeStringList( config.inviteAutoJoin?.inviterAllowlist ); - this.inviteAutoJoinEnabled = - config.inviteAutoJoin?.enabled ?? Boolean(config.inviteAutoJoin); + this.inviteAutoJoinEnabled = Boolean(config.inviteAutoJoin); this.inviteAutoJoinInviterAllowlist = inviteAutoJoinInviterAllowlist.length > 0 ? new Set(inviteAutoJoinInviterAllowlist) @@ -293,36 +278,15 @@ export class MatrixAdapter implements Adapter { this.createClientFn = config.createClient; this.createStoreFn = config.createStore; this.createBootstrapClientFn = config.createBootstrapClient; + this.e2eeEnabled = Boolean(config.recoveryKey || config.e2ee); this.e2eeConfig = { ...config.e2ee, - enabled: config.e2ee?.enabled ?? Boolean(config.recoveryKey), - persistSecretsBundle: - config.e2ee?.persistSecretsBundle ?? - Boolean(config.matrixStore?.enabled && (config.e2ee?.enabled ?? Boolean(config.recoveryKey))), storagePassword: config.e2ee?.storagePassword ?? config.recoveryKey, }; - this.matrixStoreConfig = { - enabled: config.matrixStore?.enabled ?? false, - keyPrefix: normalizeOptionalString(config.matrixStore?.keyPrefix) ?? MATRIX_STORE_PREFIX, - persistIntervalMs: - config.matrixStore?.persistIntervalMs ?? - DEFAULT_MATRIX_STORE_PERSIST_INTERVAL_MS, - snapshotTtlMs: config.matrixStore?.snapshotTtlMs, - }; + this.persistenceConfig = normalizePersistenceConfig(config.persistence); this.recoveryKey = normalizeOptionalString(config.recoveryKey); this.matrixSDKLogLevel = config.matrixSDKLogLevel; - this.deviceIDPersistence = { - enabled: config.deviceIDPersistence?.enabled ?? true, - key: normalizeOptionalString(config.deviceIDPersistence?.key), - }; - this.sessionConfig = { - decrypt: config.session?.decrypt, - enabled: config.session?.enabled ?? true, - encrypt: config.session?.encrypt, - key: config.session?.key, - ttlMs: config.session?.ttlMs, - }; this.loggerProvided = Boolean(config.logger); this.logger = config.logger ?? new ConsoleLogger("info").child("matrix"); } @@ -338,14 +302,12 @@ export class MatrixAdapter implements Adapter { } this.stateAdapter = chat.getState(); this.configureMatrixSDKLogging(); - await this.resolveDeviceID(); let resolvedAuth: ResolvedAuth | null = null; if (this.createClientFn) { + await this.resolveDeviceID(); const store = await this.maybeCreateMatrixStore(); - if (this.persistSecretsBundleEnabled) { - this.matrixStoreScopeKey = this.resolveMatrixStoreContext()?.scopeKey ?? null; - } + this.matrixStoreScopeKey = this.resolveMatrixStoreContext()?.scopeKey ?? null; const clientOptions = this.buildCustomClientOptions(store); this.client = this.createClientFn(clientOptions); } else { @@ -353,10 +315,8 @@ export class MatrixAdapter implements Adapter { this.userID = resolvedAuth.userID; this.deviceID = normalizeOptionalString(resolvedAuth.deviceID) ?? this.deviceID; const store = await this.maybeCreateMatrixStore(resolvedAuth); - if (!store && this.persistSecretsBundleEnabled) { - this.matrixStoreScopeKey = - this.resolveMatrixStoreContext(resolvedAuth)?.scopeKey ?? null; - } + this.matrixStoreScopeKey = + this.resolveMatrixStoreContext(resolvedAuth)?.scopeKey ?? null; this.client = this.buildClient(resolvedAuth, store); } @@ -1117,7 +1077,7 @@ export class MatrixAdapter implements Adapter { } private getDMStorageKey(userID: string): string { - return `${MATRIX_DM_PREFIX}:${encodeURIComponent(userID)}`; + return `${this.persistenceConfig.keyPrefix}:dm:${encodeURIComponent(userID)}`; } private async loadPersistedDMRoomID(userID: string): Promise { @@ -1270,10 +1230,11 @@ export class MatrixAdapter implements Adapter { if (this.auth.type === "accessToken") { const whoami = await this.lookupWhoAmIFromAccessToken(this.auth.accessToken); const userID = this.auth.userID ?? whoami.userID; + const deviceID = await this.resolveDeviceID(whoami.deviceID); const resolved: ResolvedAuth = { accessToken: this.auth.accessToken, userID, - deviceID: whoami.deviceID ?? this.deviceID, + deviceID, }; await Promise.all([ this.persistDeviceIDForResolvedUser(userID, resolved.deviceID), @@ -1286,10 +1247,14 @@ export class MatrixAdapter implements Adapter { if (restored?.accessToken) { try { const whoami = await this.lookupWhoAmIFromAccessToken(restored.accessToken); + const deviceID = await this.resolveDeviceID( + whoami.deviceID, + restored.deviceID + ); const resolved: ResolvedAuth = { accessToken: restored.accessToken, userID: whoami.userID, - deviceID: this.deviceID ?? whoami.deviceID ?? restored.deviceID, + deviceID, }; await Promise.all([ this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID), @@ -1325,15 +1290,23 @@ export class MatrixAdapter implements Adapter { this.auth.password ); - const userID = this.auth.userID ?? loginResponse.user_id; + let userID = this.auth.userID ?? loginResponse.user_id; if (!userID) { throw new Error("Password login succeeded but no user ID was returned."); } + let authDeviceID = normalizeOptionalString(loginResponse.device_id); + if (!authDeviceID) { + const whoami = await this.lookupWhoAmIFromAccessToken(loginResponse.access_token); + userID = userID ?? whoami.userID; + authDeviceID = whoami.deviceID; + } + + const deviceID = await this.resolveDeviceID(authDeviceID); const resolved: ResolvedAuth = { accessToken: loginResponse.access_token, userID, - deviceID: loginResponse.device_id ?? this.deviceID ?? undefined, + deviceID, }; await Promise.all([ this.persistDeviceIDForResolvedUser(resolved.userID, resolved.deviceID), @@ -1363,7 +1336,7 @@ export class MatrixAdapter implements Adapter { private buildClient(auth: ResolvedAuth, store?: IStore): MatrixClient { const cryptoCallbacks = - this.recoveryKey && this.e2eeConfig?.enabled + this.recoveryKey && this.e2eeEnabled ? { getSecretStorageKey: async ( opts: { keys: Record }, @@ -1427,7 +1400,7 @@ export class MatrixAdapter implements Adapter { } private async maybeCreateMatrixStore(resolvedAuth?: ResolvedAuth): Promise { - if (!this.stateAdapter || !this.matrixStoreConfig.enabled) { + if (!this.stateAdapter) { return undefined; } @@ -1441,7 +1414,7 @@ export class MatrixAdapter implements Adapter { const createStoreOptions: MatrixCreateStoreOptions = { baseURL: this.baseURL, - config: { ...this.matrixStoreConfig }, + config: { ...this.persistenceConfig.sync }, deviceID, logger: this.logger, scopeKey, @@ -1457,8 +1430,8 @@ export class MatrixAdapter implements Adapter { state: this.stateAdapter, scopeKey, logger: this.logger, - persistIntervalMs: this.matrixStoreConfig.persistIntervalMs, - snapshotTtlMs: this.matrixStoreConfig.snapshotTtlMs, + persistIntervalMs: this.persistenceConfig.sync.persistIntervalMs, + snapshotTtlMs: this.persistenceConfig.sync.snapshotTtlMs, }); } @@ -1470,7 +1443,7 @@ export class MatrixAdapter implements Adapter { normalizeOptionalString(this.userID); if (!userID) { this.logger.warn( - "Matrix sync store is enabled, but no user ID is available for store scoping. Continuing without persistent sync store." + "No user ID is available for Matrix persistence scoping. Continuing without persistent sync store." ); return null; } @@ -1484,8 +1457,7 @@ export class MatrixAdapter implements Adapter { } private buildMatrixStoreScopeKey(userID: string, deviceID?: string): string { - const keyPrefix = this.matrixStoreConfig.keyPrefix; - return `${keyPrefix}:${encodeURIComponent(this.baseURL)}:${encodeURIComponent(userID)}:${encodeURIComponent(deviceID ?? "default")}`; + return `${this.persistenceStorePrefix}:${encodeURIComponent(this.baseURL)}:${encodeURIComponent(userID)}:${encodeURIComponent(deviceID ?? "default")}`; } private async maybeFlushMatrixStore(): Promise { @@ -1599,7 +1571,7 @@ export class MatrixAdapter implements Adapter { } private async maybeInitE2EE(): Promise { - if (!this.e2eeConfig?.enabled) { + if (!this.e2eeEnabled) { return; } @@ -1609,7 +1581,8 @@ export class MatrixAdapter implements Adapter { ); } - const requestedIndexedDB = this.e2eeConfig.useIndexedDB; + const e2eeConfig = this.e2eeConfig ?? {}; + const requestedIndexedDB = e2eeConfig.useIndexedDB; const useIndexedDB = requestedIndexedDB === true && !hasIndexedDB() ? false @@ -1623,9 +1596,9 @@ export class MatrixAdapter implements Adapter { await this.requireClient().initRustCrypto({ useIndexedDB, - cryptoDatabasePrefix: this.e2eeConfig.cryptoDatabasePrefix, - storagePassword: this.e2eeConfig.storagePassword, - storageKey: this.e2eeConfig.storageKey, + cryptoDatabasePrefix: e2eeConfig.cryptoDatabasePrefix, + storagePassword: e2eeConfig.storagePassword, + storageKey: e2eeConfig.storageKey, }); await this.maybeImportPersistedSecretsBundle(); void this.maybeLoadKeyBackupFromRecoveryKey(); @@ -1633,7 +1606,7 @@ export class MatrixAdapter implements Adapter { this.logger.info("Matrix E2EE initialized", { useIndexedDB: useIndexedDB !== false, - cryptoDatabasePrefix: this.e2eeConfig.cryptoDatabasePrefix, + cryptoDatabasePrefix: e2eeConfig.cryptoDatabasePrefix, }); } @@ -1660,7 +1633,7 @@ export class MatrixAdapter implements Adapter { } private get persistSecretsBundleEnabled(): boolean { - return Boolean(this.e2eeConfig?.enabled && this.e2eeConfig?.persistSecretsBundle); + return Boolean(this.e2eeEnabled && this.stateAdapter); } private getSecretsBundleStorageKey(): string | null { @@ -1749,7 +1722,7 @@ export class MatrixAdapter implements Adapter { } private async tryDecryptEvent(event: MatrixEvent): Promise { - if (!this.e2eeConfig?.enabled) { + if (!this.e2eeEnabled) { return; } @@ -3088,27 +3061,31 @@ export class MatrixAdapter implements Adapter { return `${threadId}::${messageId}::${rawEmoji}`; } + private get persistenceSessionPrefix(): string { + return `${this.persistenceConfig.keyPrefix}:session:${encodeURIComponent(this.baseURL)}`; + } + + private get persistenceStorePrefix(): string { + return `${this.persistenceConfig.keyPrefix}:store`; + } + private get sessionBasePrefix(): string { - return `${MATRIX_SESSION_PREFIX}:${encodeURIComponent(this.baseURL)}`; + return this.persistenceSessionPrefix; } private getSessionStorageKey(userID: string): string { - if (this.sessionConfig.key) { - return this.sessionConfig.key; - } - return `${this.sessionBasePrefix}:user:${encodeURIComponent(userID)}`; } private getSessionUsernameTemporaryKey(): string | null { - if (this.sessionConfig.key || this.auth.type !== "password") { + if (this.auth.type !== "password") { return null; } return `${this.sessionBasePrefix}:username:${encodeURIComponent(this.auth.username)}`; } private async loadPersistedSession(): Promise { - if (!this.sessionConfig.enabled || !this.stateAdapter) { + if (!this.stateAdapter) { return null; } @@ -3135,7 +3112,7 @@ export class MatrixAdapter implements Adapter { } private async persistSession(auth: ResolvedAuth, existingCreatedAt?: string): Promise { - if (!this.sessionConfig.enabled || !this.stateAdapter) { + if (!this.stateAdapter) { return; } @@ -3146,7 +3123,7 @@ export class MatrixAdapter implements Adapter { baseURL: this.baseURL, createdAt: existingCreatedAt ?? now, deviceID: auth.deviceID, - e2eeEnabled: Boolean(this.e2eeConfig?.enabled), + e2eeEnabled: this.e2eeEnabled, recoveryKeyPresent: Boolean(this.e2eeConfig?.storagePassword), updatedAt: now, userID: auth.userID, @@ -3158,21 +3135,26 @@ export class MatrixAdapter implements Adapter { await this.stateAdapter.set( sessionKey, encodedSession, - this.sessionConfig.ttlMs + this.persistenceConfig.session.ttlMs ); const temporaryKey = this.getSessionUsernameTemporaryKey(); if (temporaryKey && temporaryKey !== sessionKey) { - await this.stateAdapter.set(temporaryKey, encodedSession, this.sessionConfig.ttlMs); + await this.stateAdapter.set( + temporaryKey, + encodedSession, + this.persistenceConfig.session.ttlMs + ); } } private encodeStoredSession(session: StoredSession): StoredSession { - if (!this.sessionConfig.encrypt) { + if (!this.persistenceConfig.session.encrypt) { return session; } - const encryptedPayload = this.sessionConfig.encrypt(JSON.stringify(session)); + const encryptedPayload = + this.persistenceConfig.session.encrypt(JSON.stringify(session)); const { accessToken, ...metadata } = session; return { ...metadata, encryptedPayload }; } @@ -3184,12 +3166,13 @@ export class MatrixAdapter implements Adapter { return session; } - if (!this.sessionConfig.decrypt) { + if (!this.persistenceConfig.session.decrypt) { return null; } try { - const decryptedJSON = this.sessionConfig.decrypt(session.encryptedPayload); + const decryptedJSON = + this.persistenceConfig.session.decrypt(session.encryptedPayload); const parsed: unknown = JSON.parse(decryptedJSON); if (!this.isValidStoredSession(parsed)) { return null; @@ -3242,53 +3225,62 @@ export class MatrixAdapter implements Adapter { if (!config.baseURL?.trim()) { throw new Error("baseURL is required."); } - if (config.session?.ttlMs !== undefined && config.session.ttlMs <= 0) { - throw new Error("session.ttlMs must be a positive number."); + if (config.persistence?.session?.ttlMs !== undefined && config.persistence.session.ttlMs <= 0) { + throw new Error("persistence.session.ttlMs must be a positive number."); } if ( - config.matrixStore?.persistIntervalMs !== undefined && - config.matrixStore.persistIntervalMs <= 0 + config.persistence?.sync?.persistIntervalMs !== undefined && + config.persistence.sync.persistIntervalMs <= 0 ) { - throw new Error("matrixStore.persistIntervalMs must be a positive number."); + throw new Error("persistence.sync.persistIntervalMs must be a positive number."); } if ( - config.matrixStore?.snapshotTtlMs !== undefined && - config.matrixStore.snapshotTtlMs <= 0 + config.persistence?.sync?.snapshotTtlMs !== undefined && + config.persistence.sync.snapshotTtlMs <= 0 ) { - throw new Error("matrixStore.snapshotTtlMs must be a positive number."); + throw new Error("persistence.sync.snapshotTtlMs must be a positive number."); } if ( - (config.session?.encrypt && !config.session?.decrypt) || - (!config.session?.encrypt && config.session?.decrypt) + (config.persistence?.session?.encrypt && !config.persistence?.session?.decrypt) || + (!config.persistence?.session?.encrypt && config.persistence?.session?.decrypt) ) { throw new Error( - "session.encrypt and session.decrypt must be provided together." + "persistence.session.encrypt and persistence.session.decrypt must be provided together." ); } } - private async resolveDeviceID(): Promise { - if (this.deviceID) { - return; + private async resolveDeviceID(...candidates: Array): Promise { + const configuredDeviceID = normalizeOptionalString(this.deviceID); + if (configuredDeviceID) { + this.deviceID = configuredDeviceID; + return configuredDeviceID; + } + + for (const candidate of candidates) { + const normalized = normalizeOptionalString(candidate); + if (normalized) { + this.deviceID = normalized; + await this.persistDeviceID(normalized); + return normalized; + } } const persisted = await this.loadPersistedDeviceID(); if (persisted) { this.deviceID = persisted; - return; + return persisted; } const generated = generateDeviceID(); this.deviceID = generated; await this.persistDeviceID(generated); + return generated; } private getDeviceIDStorageKey(identityHint?: string): string { - if (this.deviceIDPersistence.key) { - return this.deviceIDPersistence.key; - } - - const basePrefix = `${MATRIX_DEVICE_PREFIX}:${encodeURIComponent(this.baseURL)}`; + const basePrefix = + `${this.persistenceConfig.keyPrefix}:device:${encodeURIComponent(this.baseURL)}`; const hint = identityHint ?? this.auth.userID ?? @@ -3297,7 +3289,7 @@ export class MatrixAdapter implements Adapter { } private async loadPersistedDeviceID(): Promise { - if (!this.deviceIDPersistence.enabled || !this.stateAdapter) { + if (!this.stateAdapter) { return null; } @@ -3321,7 +3313,7 @@ export class MatrixAdapter implements Adapter { } private async persistDeviceID(deviceID: string, identityHint?: string): Promise { - if (!this.deviceIDPersistence.enabled || !this.stateAdapter) { + if (!this.stateAdapter) { return; } @@ -3342,12 +3334,7 @@ export class MatrixAdapter implements Adapter { const temporaryKey = this.getDeviceIDStorageKey(); const canonicalKey = this.getDeviceIDStorageKey(userID); - if ( - this.deviceIDPersistence.enabled && - this.stateAdapter && - !this.deviceIDPersistence.key && - temporaryKey !== canonicalKey - ) { + if (this.stateAdapter && temporaryKey !== canonicalKey) { await this.stateAdapter.delete(temporaryKey); } } @@ -3392,13 +3379,11 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter } const recoveryKey = process.env.MATRIX_RECOVERY_KEY; - const e2eeEnabled = - Boolean(recoveryKey) || envBool(process.env.MATRIX_E2EE_ENABLED); const inviteAutoJoinInviterAllowlist = parseEnvList( process.env.MATRIX_INVITE_AUTOJOIN_ALLOWLIST ); const inviteAutoJoinEnabled = envBool( - process.env.MATRIX_INVITE_AUTOJOIN_ENABLED, + process.env.MATRIX_INVITE_AUTOJOIN, inviteAutoJoinInviterAllowlist.length > 0 ); @@ -3407,39 +3392,15 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter return new MatrixAdapter({ baseURL, auth, - userName: - process.env.MATRIX_BOT_USERNAME ?? - process.env.MOM_BOT_USERNAME ?? - "bot", + userName: process.env.MATRIX_BOT_USERNAME ?? "bot", deviceID: normalizeOptionalString(process.env.MATRIX_DEVICE_ID), - deviceIDPersistence: { - enabled: envBool(process.env.MATRIX_DEVICE_ID_PERSIST_ENABLED, true), - key: normalizeOptionalString(process.env.MATRIX_DEVICE_ID_PERSIST_KEY), - }, commandPrefix: process.env.MATRIX_COMMAND_PREFIX, recoveryKey, - inviteAutoJoin: { - enabled: inviteAutoJoinEnabled, - inviterAllowlist: inviteAutoJoinInviterAllowlist, - }, - e2ee: { - enabled: e2eeEnabled, - useIndexedDB: envBool( - process.env.MATRIX_E2EE_USE_INDEXEDDB, - hasIndexedDB() - ), - cryptoDatabasePrefix: process.env.MATRIX_E2EE_DB_PREFIX, - storagePassword: process.env.MATRIX_E2EE_STORAGE_PASSWORD ?? recoveryKey, - storageKey: decodeBase64( - process.env.MATRIX_E2EE_STORAGE_KEY_BASE64, - "MATRIX_E2EE_STORAGE_KEY_BASE64" - ), - }, - session: { - enabled: envBool(process.env.MATRIX_SESSION_ENABLED, true), - key: process.env.MATRIX_SESSION_KEY, - ttlMs: parseEnvNumber(process.env.MATRIX_SESSION_TTL_MS), - }, + inviteAutoJoin: inviteAutoJoinEnabled + ? { + inviterAllowlist: inviteAutoJoinInviterAllowlist, + } + : undefined, matrixSDKLogLevel: parseSDKLogLevel(process.env.MATRIX_SDK_LOG_LEVEL) ?? "error", }); @@ -3448,10 +3409,31 @@ export function createMatrixAdapter(config?: MatrixAdapterConfig): MatrixAdapter export type { MatrixAdapterConfig, MatrixCreateStoreOptions, - MatrixSyncStoreConfig, + MatrixPersistenceConfig, + MatrixPersistenceSyncConfig, MatrixThreadID, } from "./types"; +function normalizePersistenceConfig( + config?: MatrixPersistenceConfig +): ResolvedPersistenceConfig { + return { + keyPrefix: + normalizeOptionalString(config?.keyPrefix) ?? DEFAULT_PERSISTENCE_KEY_PREFIX, + session: { + decrypt: config?.session?.decrypt, + encrypt: config?.session?.encrypt, + ttlMs: config?.session?.ttlMs, + }, + sync: { + persistIntervalMs: + config?.sync?.persistIntervalMs ?? + DEFAULT_MATRIX_STORE_PERSIST_INTERVAL_MS, + snapshotTtlMs: config?.sync?.snapshotTtlMs, + }, + }; +} + function resolveAuthFromEnv(): MatrixAuthConfig { const username = process.env.MATRIX_USERNAME; const password = process.env.MATRIX_PASSWORD; @@ -3497,35 +3479,6 @@ function envBool(value: string | undefined, fallback = false): boolean { ); } -function decodeBase64( - value: string | undefined, - envName: string -): Uint8Array | undefined { - if (!value) { - return undefined; - } - - const bytes = Buffer.from(value, "base64"); - if (bytes.length === 0) { - throw new Error(`${envName} is set but could not be decoded as base64.`); - } - - return bytes; -} - -function parseEnvNumber(value: string | undefined): number | undefined { - if (!value) { - return undefined; - } - - const parsed = Number(value); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new Error(`Expected a positive number, got: ${value}`); - } - - return parsed; -} - function parseEnvList(value: string | undefined): string[] { if (!value) { return []; diff --git a/src/store/chat-state-matrix-store.test.ts b/src/store/chat-state-matrix-store.test.ts index 3d242a9..6c713db 100644 --- a/src/store/chat-state-matrix-store.test.ts +++ b/src/store/chat-state-matrix-store.test.ts @@ -62,7 +62,7 @@ async function makeStore( ) { const store = new ChatStateMatrixStore({ state, - scopeKey: "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1", + scopeKey: "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1", logger: makeLogger(), persistIntervalMs: 30_000, ...options, @@ -83,7 +83,7 @@ describe("ChatStateMatrixStore", () => { it("loads persisted saved sync during startup", async () => { const scope = - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; const state = await makeState({ [`${scope}:meta`]: { version: 1, @@ -105,7 +105,7 @@ describe("ChatStateMatrixStore", () => { it("reads saved sync token from meta without loading the full snapshot", async () => { const scope = - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; const state = await makeState({ [`${scope}:meta`]: { version: 1, @@ -130,7 +130,7 @@ describe("ChatStateMatrixStore", () => { it("marks sync data dirty and only persists on a forced save before the interval", async () => { const scope = - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1"; const state = await makeState({ [`${scope}:meta`]: { version: 1, @@ -233,15 +233,15 @@ describe("ChatStateMatrixStore", () => { await store.deleteAllData(); - expect(await state.get("matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:meta")).toBeNull(); + expect(await state.get("matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:meta")).toBeNull(); expect( await state.get( - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:saved-sync" + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:saved-sync" ) ).toBeNull(); expect( await state.get( - "matrix:store:v1:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:pending-events:%21room%3Abeeper.com" + "matrix:store:https%3A%2F%2Fhs.beeper.com:%40bot%3Abeeper.com:DEVICE1:pending-events:%21room%3Abeeper.com" ) ).toBeNull(); }); diff --git a/src/types.ts b/src/types.ts index d8a9e1b..5e818a0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,23 +4,31 @@ import type { IStore } from "matrix-js-sdk/lib/store"; export interface MatrixE2EEConfig { cryptoDatabasePrefix?: string; - enabled?: boolean; - persistSecretsBundle?: boolean; storageKey?: Uint8Array; storagePassword?: string; useIndexedDB?: boolean; } -export interface MatrixSyncStoreConfig { - enabled?: boolean; - keyPrefix?: string; +export interface MatrixPersistenceSyncConfig { persistIntervalMs?: number; snapshotTtlMs?: number; } +export interface MatrixPersistenceSessionConfig { + decrypt?: (value: string) => string; + encrypt?: (value: string) => string; + ttlMs?: number; +} + +export interface MatrixPersistenceConfig { + keyPrefix?: string; + session?: MatrixPersistenceSessionConfig; + sync?: MatrixPersistenceSyncConfig; +} + export interface MatrixCreateStoreOptions { baseURL: string; - config: MatrixSyncStoreConfig; + config: MatrixPersistenceSyncConfig; deviceID?: string; logger: Logger; scopeKey: string; @@ -55,22 +63,19 @@ export interface MatrixAdapterConfig { options: { accessToken?: string; baseURL: string; deviceID?: string } ) => MatrixAuthBootstrapClient; createClient?: (options?: ICreateClientOpts) => MatrixClient; - deviceIDPersistence?: MatrixDeviceIDPersistenceConfig; deviceID?: string; e2ee?: MatrixE2EEConfig; inviteAutoJoin?: MatrixInviteAutoJoinConfig; logger?: Logger; - matrixStore?: MatrixSyncStoreConfig; matrixSDKLogLevel?: "trace" | "debug" | "info" | "warn" | "error"; + persistence?: MatrixPersistenceConfig; recoveryKey?: string; roomAllowlist?: string[]; - session?: MatrixSessionConfig; sync?: IStartClientOpts; userName?: string; } export interface MatrixInviteAutoJoinConfig { - enabled?: boolean; inviterAllowlist?: string[]; } @@ -79,19 +84,6 @@ export interface MatrixThreadID { rootEventID?: string; } -export interface MatrixSessionConfig { - enabled?: boolean; - encrypt?: (value: string) => string; - key?: string; - ttlMs?: number; - decrypt?: (value: string) => string; -} - -export interface MatrixDeviceIDPersistenceConfig { - enabled?: boolean; - key?: string; -} - export interface MatrixAuthBootstrapClient { loginRequest?: (data: { type: "m.login.password";