diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bf4d12b..c4aecfc3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/eslint.config.js b/eslint.config.js index de573f89..f82ab7dc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,11 @@ import eslint from "@eslint/js"; import prettier from "eslint-plugin-prettier/recommended"; -import solid from "eslint-plugin-solid/configs/typescript"; import { defineConfig } from "eslint/config"; import tseslint from "typescript-eslint"; export default defineConfig([ eslint.configs.recommended, tseslint.configs.recommended, - solid, { rules: { "@typescript-eslint/no-unused-vars": [ diff --git a/package.json b/package.json index 63ce4ce0..68dedeff 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,8 @@ "description": "Library for interacting with the Revolt API.", "packageManager": "pnpm@9.4.0", "dependencies": { - "@solid-primitives/map": "^0.6.0", - "@solid-primitives/set": "^0.6.0", "@vladfrangu/async_event_emitter": "^2.4.6", "revolt-api": "0.8.3", - "solid-js": "^1.9.5", "ulid": "^2.3.0" }, "devDependencies": { @@ -37,10 +34,9 @@ "@types/node": "^22.13.10", "eslint": "^9.22.0", "eslint-plugin-prettier": "^5.2.3", - "eslint-plugin-solid": "^0.14.5", "prettier": "^3.5.3", "typedoc": "^0.27.9", "typescript": "^5.8.2", "typescript-eslint": "^8.26.1" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b56ebcad..6ad0f638 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,21 +8,12 @@ importers: .: dependencies: - '@solid-primitives/map': - specifier: ^0.6.0 - version: 0.6.0(solid-js@1.9.5) - '@solid-primitives/set': - specifier: ^0.6.0 - version: 0.6.0(solid-js@1.9.5) '@vladfrangu/async_event_emitter': specifier: ^2.4.6 version: 2.4.6 revolt-api: specifier: 0.8.3 version: 0.8.3 - solid-js: - specifier: ^1.9.5 - version: 1.9.5 ulid: specifier: ^2.3.0 version: 2.3.0 @@ -42,9 +33,6 @@ importers: eslint-plugin-prettier: specifier: ^5.2.3 version: 5.2.3(eslint-config-prettier@8.10.0(eslint@9.22.0))(eslint@9.22.0)(prettier@3.5.3) - eslint-plugin-solid: - specifier: ^0.14.5 - version: 0.14.5(eslint@9.22.0)(typescript@5.8.2) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -203,26 +191,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@solid-primitives/map@0.6.0': - resolution: {integrity: sha512-h8uCJNxUTvzNK/aTW9vJCd/PQeBkUL8i7AyLPvTFqMgcMnJ1I5GzAi5JODzxpxQwffxoqJyQtOE1vBqFzDv0Vw==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/set@0.6.0': - resolution: {integrity: sha512-P1Tp001m8ib5XCi/LEJHh2QsoS2uPO86HewtbrSxr2axt5te9EGc0UOwNKTPxNvNSLNHteUw5IrzDsEklW4m3g==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/trigger@1.2.0': - resolution: {integrity: sha512-sW4/3cDXSjYQampn8CIFZ11BlxgNf2li8r2fXnb3b3YWE6RdZZCl8PhvpPF38Gzl0CnryrbTPJWM7OIkseCDgQ==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/utils@6.3.0': - resolution: {integrity: sha512-e7hTlJ1Ywh2+g/Qug+n4L1mpfxsikoIS4/sHE2EK9WatQt8UJqop/vE6bsLnXlU1xuhb/jo94Ah5Y27rd4wP7A==} - peerDependencies: - solid-js: ^1.6.12 - '@trivago/prettier-plugin-sort-imports@5.2.2': resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==} engines: {node: '>18.12'} @@ -363,9 +331,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -406,13 +371,6 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-solid@0.14.5: - resolution: {integrity: sha512-nfuYK09ah5aJG/oEN6P1qziy1zLgW4PDWe75VNPi4CEFYk1x2AEqwFeQfEPR7gNn0F2jOeqKhx2E+5oNCOBYWQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - typescript: '>=4.8.4' - eslint-scope@8.3.0: resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -525,10 +483,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -541,9 +495,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inline-style-parser@0.2.4: - resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -552,10 +503,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-html@2.0.0: - resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==} - engines: {node: '>=8'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -587,15 +534,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - kebab-case@1.0.2: - resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - known-css-properties@0.30.0: - resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -721,16 +662,6 @@ packages: engines: {node: '>=10'} hasBin: true - seroval-plugins@1.2.1: - resolution: {integrity: sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - - seroval@1.2.1: - resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} - engines: {node: '>=10'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -739,16 +670,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - solid-js@1.9.5: - resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - style-to-object@1.0.8: - resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -981,25 +906,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@solid-primitives/map@0.6.0(solid-js@1.9.5)': - dependencies: - '@solid-primitives/trigger': 1.2.0(solid-js@1.9.5) - solid-js: 1.9.5 - - '@solid-primitives/set@0.6.0(solid-js@1.9.5)': - dependencies: - '@solid-primitives/trigger': 1.2.0(solid-js@1.9.5) - solid-js: 1.9.5 - - '@solid-primitives/trigger@1.2.0(solid-js@1.9.5)': - dependencies: - '@solid-primitives/utils': 6.3.0(solid-js@1.9.5) - solid-js: 1.9.5 - - '@solid-primitives/utils@6.3.0(solid-js@1.9.5)': - dependencies: - solid-js: 1.9.5 - '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3)': dependencies: '@babel/generator': 7.26.10 @@ -1166,8 +1072,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - csstype@3.1.3: {} - debug@4.4.0: dependencies: ms: 2.1.3 @@ -1192,19 +1096,6 @@ snapshots: optionalDependencies: eslint-config-prettier: 8.10.0(eslint@9.22.0) - eslint-plugin-solid@0.14.5(eslint@9.22.0)(typescript@5.8.2): - dependencies: - '@typescript-eslint/utils': 8.26.1(eslint@9.22.0)(typescript@5.8.2) - eslint: 9.22.0 - estraverse: 5.3.0 - is-html: 2.0.0 - kebab-case: 1.0.2 - known-css-properties: 0.30.0 - style-to-object: 1.0.8 - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 @@ -1330,8 +1221,6 @@ snapshots: has-flag@4.0.0: {} - html-tags@3.3.1: {} - ignore@5.3.2: {} import-fresh@3.3.1: @@ -1341,18 +1230,12 @@ snapshots: imurmurhash@0.1.4: {} - inline-style-parser@0.2.4: {} - is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - is-html@2.0.0: - dependencies: - html-tags: 3.3.1 - is-number@7.0.0: {} isexe@2.0.0: {} @@ -1373,14 +1256,10 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - kebab-case@1.0.2: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - known-css-properties@0.30.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -1492,30 +1371,14 @@ snapshots: semver@7.7.1: {} - seroval-plugins@1.2.1(seroval@1.2.1): - dependencies: - seroval: 1.2.1 - - seroval@1.2.1: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} - solid-js@1.9.5: - dependencies: - csstype: 3.1.3 - seroval: 1.2.1 - seroval-plugins: 1.2.1(seroval@1.2.1) - strip-json-comments@3.1.1: {} - style-to-object@1.0.8: - dependencies: - inline-style-parser: 0.2.4 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 diff --git a/src/Client.ts b/src/Client.ts index 77f9240c..21f14bf5 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,6 +1,3 @@ -import type { Accessor, Setter } from "solid-js"; -import { batch, createSignal } from "solid-js"; - import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter"; import { API } from "revolt-api"; import type { DataLogin, RevoltConfig, Role } from "revolt-api"; @@ -172,11 +169,9 @@ export class Client extends AsyncEventEmitter { #session: Session | undefined; user: User | undefined; - readonly ready: Accessor; - #setReady: Setter; + ready = false; + connectionFailureCount = 0; - readonly connectionFailureCount: Accessor; - #setConnectionFailureCount: Setter; #reconnectTimeout: number | undefined; /** @@ -217,14 +212,6 @@ export class Client extends AsyncEventEmitter { baseURL: this.options.baseURL, }); - const [ready, setReady] = createSignal(false); - this.ready = ready; - this.#setReady = setReady; - - const [connectionFailureCount, setConnectionFailureCount] = createSignal(0); - this.connectionFailureCount = connectionFailureCount; - this.#setConnectionFailureCount = setConnectionFailureCount; - this.account = new AccountCollection(this); this.bots = new BotCollection(this); this.channels = new ChannelCollection(this); @@ -242,11 +229,9 @@ export class Client extends AsyncEventEmitter { this.events.on("state", (state) => { switch (state) { case ConnectionState.Connected: - batch(() => { - this.servers.forEach((server) => server.resetSyncStatus()); - this.#setConnectionFailureCount(0); - this.emit("connected"); - }); + this.servers.forEach((server) => server.resetSyncStatus()); + this.connectionFailureCount = 0; + this.emit("connected"); break; case ConnectionState.Connecting: this.emit("connecting"); @@ -256,18 +241,24 @@ export class Client extends AsyncEventEmitter { if (this.options.autoReconnect) { this.#reconnectTimeout = setTimeout( () => this.connect(), - this.options.retryDelayFunction(this.connectionFailureCount()) * + this.options.retryDelayFunction(this.connectionFailureCount) * 1e3, ) as never; - this.#setConnectionFailureCount((count) => count + 1); + this.connectionFailureCount += 1; } break; } }); this.events.on("event", (event) => - handleEvent(this, event, this.#setReady), + handleEvent( + this, + event, + ((value: boolean) => { + this.ready = value; + }).bind(this), + ), ); } @@ -293,7 +284,7 @@ export class Client extends AsyncEventEmitter { connect(): void { clearTimeout(this.#reconnectTimeout); this.events.disconnect(); - this.#setReady(false); + this.ready = false; this.events.connect( this.configuration?.ws ?? "wss://ws.revolt.chat", typeof this.#session === "string" ? this.#session : this.#session!.token, diff --git a/src/classes/Channel.ts b/src/classes/Channel.ts index bbfc0c46..2bba656c 100644 --- a/src/classes/Channel.ts +++ b/src/classes/Channel.ts @@ -1,6 +1,3 @@ -import { batch } from "solid-js"; - -import type { ReactiveSet } from "@solid-primitives/set"; import type { Channel as APIChannel, Member as APIMember, @@ -136,7 +133,7 @@ export class Channel { /** * User ids of people currently typing in channel */ - get typingIds(): ReactiveSet { + get typingIds(): Set { return this.#collection.getUnderlyingObject(this.id).typingIds; } @@ -152,7 +149,7 @@ export class Channel { /** * User ids of recipients of the group */ - get recipientIds(): ReactiveSet { + get recipientIds(): Set { return this.#collection.getUnderlyingObject(this.id).recipientIds; } @@ -305,7 +302,7 @@ export class Channel { /** * Get mentions in this channel for user. */ - get mentions(): ReactiveSet | undefined { + get mentions(): Set | undefined { if (this.type === "SavedMessages" || this.type === "VoiceChannel") return undefined; @@ -391,10 +388,8 @@ export class Channel { `/channels/${this.id as ""}/members`, ); - return batch(() => - members.map((user) => - this.#collection.client.users.getOrCreate(user._id, user), - ), + return members.map((user) => + this.#collection.client.users.getOrCreate(user._id, user), ); } @@ -408,13 +403,8 @@ export class Channel { `/channels/${this.id as ""}/webhooks`, ); - return batch(() => - webhooks.map((webhook) => - this.#collection.client.channelWebhooks.getOrCreate( - webhook.id, - webhook, - ), - ), + return webhooks.map((webhook) => + this.#collection.client.channelWebhooks.getOrCreate(webhook.id, webhook), ); } @@ -428,7 +418,7 @@ export class Channel { data, ); - this.#collection.updateUnderlyingObject( + this.#collection.setUnderlyingObject( this.id, hydrate("channel", channel, this.#collection.client, false), ); @@ -445,7 +435,7 @@ export class Channel { }); if (this.type === "DirectMessage") { - this.#collection.updateUnderlyingObject(this.id, "active", false); + this.#collection.setUnderlyingKey(this.id, "active", false); return; } @@ -574,7 +564,7 @@ export class Channel { { ...params, include_users: true }, )) as { messages: APIMessage[]; users: APIUser[]; members?: APIMember[] }; - return batch(() => ({ + return { messages: data.messages.map((message) => this.#collection.client.messages.getOrCreate(message._id, message), ), @@ -584,7 +574,7 @@ export class Channel { members: data.members?.map((member) => this.#collection.client.serverMembers.getOrCreate(member._id, member), ), - })); + }; } /** @@ -601,10 +591,8 @@ export class Channel { params, )) as APIMessage[]; - return batch(() => - messages.map((message) => - this.#collection.client.messages.getOrCreate(message._id, message), - ), + return messages.map((message) => + this.#collection.client.messages.getOrCreate(message._id, message), ); } @@ -629,7 +617,7 @@ export class Channel { }, )) as { messages: APIMessage[]; users: APIUser[]; members?: APIMember[] }; - return batch(() => ({ + return { messages: data.messages.map((message) => this.#collection.client.messages.getOrCreate(message._id, message), ), @@ -639,7 +627,7 @@ export class Channel { members: data.members?.map((member) => this.#collection.client.serverMembers.getOrCreate(member._id, member), ), - })); + }; } /** @@ -702,7 +690,8 @@ export class Channel { const unreads = this.#collection.client.channelUnreads; const channelUnread = unreads.get(this.id); if (channelUnread) { - unreads.updateUnderlyingObject(this.id, { + unreads.setUnderlyingObject(this.id, { + ...unreads.getUnderlyingObject(this.id), lastMessageId, }); diff --git a/src/classes/ChannelUnread.ts b/src/classes/ChannelUnread.ts index 4b31911c..2d89e65f 100644 --- a/src/classes/ChannelUnread.ts +++ b/src/classes/ChannelUnread.ts @@ -1,5 +1,3 @@ -import type { ReactiveSet } from "@solid-primitives/set"; - import type { ChannelUnreadCollection } from "../collections/ChannelUnreadCollection.js"; /** @@ -36,7 +34,7 @@ export class ChannelUnread { /** * List of message IDs that we were mentioned in */ - get messageMentionIds(): ReactiveSet { + get messageMentionIds(): Set { return this.#collection.getUnderlyingObject(this.id).messageMentionIds; } } diff --git a/src/classes/ChannelWebhook.ts b/src/classes/ChannelWebhook.ts index 87a197c5..97ae5eb0 100644 --- a/src/classes/ChannelWebhook.ts +++ b/src/classes/ChannelWebhook.ts @@ -98,7 +98,7 @@ export class ChannelWebhook { data, ); - this.#collection.updateUnderlyingObject( + this.#collection.setUnderlyingObject( this.id, // @ts-expect-error not in prod hydrate("channelWebhook", webhook, this.#collection.client), diff --git a/src/classes/MFA.ts b/src/classes/MFA.ts index 72255332..11e58d03 100644 --- a/src/classes/MFA.ts +++ b/src/classes/MFA.ts @@ -1,6 +1,3 @@ -import type { SetStoreFunction } from "solid-js/store"; -import { createStore } from "solid-js/store"; - import type { MFAMethod, MFAResponse, @@ -15,7 +12,7 @@ import type { Client } from "../Client.js"; */ export class MFA { #client: Client; - #store: [MultiFactorStatus, SetStoreFunction]; + #store: MultiFactorStatus; /** * Construct MFA helper @@ -24,21 +21,30 @@ export class MFA { */ constructor(client: Client, state: MultiFactorStatus) { this.#client = client; - this.#store = createStore(state); + this.#store = state; + } + + /** + * Mutate the store + * @param key Key + * @param value Value + */ + #mutateStore(key: keyof MultiFactorStatus, value: boolean) { + this.#store[key] = value; } /** * Whether authenticator app is enabled */ get authenticatorEnabled(): boolean { - return this.#store[0].totp_mfa; + return this.#store.totp_mfa; } /** * Whether recovery codes are enabled */ get recoveryEnabled(): boolean { - return this.#store[0].recovery_active; + return this.#store.recovery_active; } /** @@ -61,7 +67,7 @@ export class MFA { return new MFATicket( this.#client, await this.#client.api.put("/auth/mfa/ticket", params), - this.#store[1] + this.#mutateStore.bind(this), ); } @@ -71,7 +77,7 @@ export class MFA { */ async enableAuthenticator(token: string): Promise { await this.#client.api.put("/auth/mfa/totp", { totp_code: token }); - this.#store[1]("totp_mfa", true); + this.#mutateStore("totp_mfa", true); } } @@ -81,7 +87,7 @@ export class MFA { export class MFATicket { #client: Client; #ticket: TicketType; - #mutate: SetStoreFunction; + #mutate: (key: keyof MultiFactorStatus, value: boolean) => void; #used = false; /** @@ -93,7 +99,7 @@ export class MFATicket { constructor( client: Client, ticket: TicketType, - mutate: SetStoreFunction, + mutate: (key: keyof MultiFactorStatus, value: boolean) => void, ) { this.#client = client; this.#ticket = ticket; diff --git a/src/classes/Message.ts b/src/classes/Message.ts index c07aa71d..cd2e1eaa 100644 --- a/src/classes/Message.ts +++ b/src/classes/Message.ts @@ -1,5 +1,3 @@ -import type { ReactiveMap } from "@solid-primitives/map"; -import type { ReactiveSet } from "@solid-primitives/set"; import type { Message as APIMessage, MessageWebhook as APIMessageWebhook, @@ -187,7 +185,7 @@ export class Message { /** * Reactions */ - get reactions(): ReactiveMap> { + get reactions(): Map> { return this.#collection.getUnderlyingObject(this.id).reactions; } diff --git a/src/classes/PublicInvite.ts b/src/classes/PublicInvite.ts index e6ecf9f0..c09782c8 100644 --- a/src/classes/PublicInvite.ts +++ b/src/classes/PublicInvite.ts @@ -1,5 +1,3 @@ -import { batch } from "solid-js"; - import type { Invite, InviteResponse } from "revolt-api"; import type { Client } from "../Client.js"; @@ -105,17 +103,15 @@ export class ServerPublicInvite extends PublicChannelInvite { const invite = await this.client!.api.post(`/invites/${this.code as ""}`); if (invite.type === "Server") { - return batch(() => { - for (const channel of invite.channels) { - this.client!.channels.getOrCreate(channel._id, channel); - } - - return this.client!.servers.getOrCreate( - invite.server._id, - invite.server, - true, - ); - }); + for (const channel of invite.channels) { + this.client!.channels.getOrCreate(channel._id, channel); + } + + return this.client!.servers.getOrCreate( + invite.server._id, + invite.server, + true, + ); } else { throw "unreachable"; } diff --git a/src/classes/Server.ts b/src/classes/Server.ts index 298c028b..774a246a 100644 --- a/src/classes/Server.ts +++ b/src/classes/Server.ts @@ -1,7 +1,3 @@ -import { batch } from "solid-js"; - -import type { ReactiveMap } from "@solid-primitives/map"; -import type { ReactiveSet } from "@solid-primitives/set"; import type { Server as APIServer, AllMemberResponse, @@ -120,7 +116,7 @@ export class Server { /** * Channel IDs */ - get channelIds(): ReactiveSet { + get channelIds(): Set { return this.#collection.getUnderlyingObject(this.id).channelIds; } @@ -152,7 +148,7 @@ export class Server { /** * Roles */ - get roles(): ReactiveMap< + get roles(): Map< string, { name: string; @@ -399,7 +395,7 @@ export class Server { * @param data Changes */ async edit(data: DataEditServer): Promise { - this.#collection.updateUnderlyingObject( + this.#collection.setUnderlyingObject( this.id, hydrate( "server", @@ -418,26 +414,22 @@ export class Server { * @param leaveEvent Whether we are leaving */ $delete(leaveEvent?: boolean): void { - batch(() => { - const server = this.#collection.client.servers.getUnderlyingObject( - this.id, - ); - - // Avoid race conditions - if (server.id) { - this.#collection.client.emit( - leaveEvent ? "serverLeave" : "serverDelete", - server, - ); + const server = this.#collection.client.servers.getUnderlyingObject(this.id); - for (const channel of this.channelIds) { - this.#collection.client.channels.delete(channel); - } + // Avoid race conditions + if (server.id) { + this.#collection.client.emit( + leaveEvent ? "serverLeave" : "serverDelete", + server, + ); - this.#collection.delete(this.id); + for (const channel of this.channelIds) { + this.#collection.client.channels.delete(channel); } - // TODO: delete members, emoji, etc - }); + + this.#collection.delete(this.id); + } + // TODO: delete members, emoji, etc } /** @@ -456,11 +448,9 @@ export class Server { * Mark a server as read */ async ack(): Promise { - batch(() => { - for (const channel of this.channels) { - channel.ack(undefined, false, true); - } - }); + for (const channel of this.channels) { + channel.ack(undefined, false, true); + } await this.#collection.client.api.put(`/servers/${this.id}/ack`); } @@ -630,31 +620,29 @@ export class Server { { exclude_offline: excludeOffline }, ); - batch(() => { - if (excludeOffline) { - for (let i = 0; i < data.users.length; i++) { - const user = data.users[i]; - if (user.online) { - this.#collection.client.users.getOrCreate(user._id, user); - this.#collection.client.serverMembers.getOrCreate( - data.members[i]._id, - data.members[i], - ); - } - } - } else { - for (let i = 0; i < data.users.length; i++) { - this.#collection.client.users.getOrCreate( - data.users[i]._id, - data.users[i], - ); + if (excludeOffline) { + for (let i = 0; i < data.users.length; i++) { + const user = data.users[i]; + if (user.online) { + this.#collection.client.users.getOrCreate(user._id, user); this.#collection.client.serverMembers.getOrCreate( data.members[i]._id, data.members[i], ); } } - }); + } else { + for (let i = 0; i < data.users.length; i++) { + this.#collection.client.users.getOrCreate( + data.users[i]._id, + data.users[i], + ); + this.#collection.client.serverMembers.getOrCreate( + data.members[i]._id, + data.members[i], + ); + } + } } /** @@ -674,14 +662,14 @@ export class Server { `/servers/${this.id as ""}/members`, )) as AllMemberResponse; - return batch(() => ({ + return { members: data.members.map((member) => this.#collection.client.serverMembers.getOrCreate(member._id, member), ), users: data.users.map((user) => this.#collection.client.users.getOrCreate(user._id, user), ), - })); + }; } /** @@ -700,14 +688,14 @@ export class Server { )}` as never, )) as AllMemberResponse; - return batch(() => ({ + return { members: data.members.map((member) => this.#collection.client.serverMembers.getOrCreate(member._id, member), ), users: data.users.map((user) => this.#collection.client.users.getOrCreate(user._id, user), ), - })); + }; } /** @@ -742,10 +730,8 @@ export class Server { `/servers/${this.id as ""}/emojis`, ); - return batch(() => - emojis.map((emoji) => - this.#collection.client.emojis.getOrCreate(emoji._id, emoji), - ), + return emojis.map((emoji) => + this.#collection.client.emojis.getOrCreate(emoji._id, emoji), ); } diff --git a/src/classes/Session.ts b/src/classes/Session.ts index bbf244ec..821529e6 100644 --- a/src/classes/Session.ts +++ b/src/classes/Session.ts @@ -64,7 +64,7 @@ export class Session { friendly_name: name, }); - this.#collection.updateUnderlyingObject(this.id, "name", name); + this.#collection.setUnderlyingKey(this.id, "name", name); } /** diff --git a/src/classes/User.ts b/src/classes/User.ts index 2ce6cee9..f74313a3 100644 --- a/src/classes/User.ts +++ b/src/classes/User.ts @@ -256,7 +256,7 @@ export class User { if (dm) { if (!dm.active) { - this.#collection.client.channels.updateUnderlyingObject( + this.#collection.client.channels.setUnderlyingKey( dm.id, "active", true, diff --git a/src/collections/BotCollection.ts b/src/collections/BotCollection.ts index 2b0e2ff5..c0d02f0a 100644 --- a/src/collections/BotCollection.ts +++ b/src/collections/BotCollection.ts @@ -1,17 +1,15 @@ -import { batch } from "solid-js"; - import type { Bot as APIBot, OwnedBotsResponse } from "revolt-api"; import { Bot } from "../classes/Bot.js"; import { PublicBot } from "../classes/PublicBot.js"; import type { HydratedBot } from "../hydration/bot.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Bots */ -export class BotCollection extends ClassCollection { +export class BotCollection extends Collection { /** * Fetch bot by ID * @param id Id @@ -31,12 +29,8 @@ export class BotCollection extends ClassCollection { */ async fetchOwned(): Promise { const data = (await this.client.api.get("/bots/@me")) as OwnedBotsResponse; - return batch(() => { - data.users.forEach((user) => - this.client.users.getOrCreate(user._id, user), - ); - return data.bots.map((bot) => this.getOrCreate(bot._id, bot)); - }); + data.users.forEach((user) => this.client.users.getOrCreate(user._id, user)); + return data.bots.map((bot) => this.getOrCreate(bot._id, bot)); } /** diff --git a/src/collections/ChannelCollection.ts b/src/collections/ChannelCollection.ts index 407f5359..87faf4ec 100644 --- a/src/collections/ChannelCollection.ts +++ b/src/collections/ChannelCollection.ts @@ -4,15 +4,12 @@ import { Channel } from "../classes/Channel.js"; import { User } from "../classes/User.js"; import type { HydratedChannel } from "../hydration/channel.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Channels */ -export class ChannelCollection extends ClassCollection< - Channel, - HydratedChannel -> { +export class ChannelCollection extends Collection { /** * Delete an object * @param id Id diff --git a/src/collections/ChannelUnreadCollection.ts b/src/collections/ChannelUnreadCollection.ts index d71e10c8..fb13a1a1 100644 --- a/src/collections/ChannelUnreadCollection.ts +++ b/src/collections/ChannelUnreadCollection.ts @@ -1,16 +1,14 @@ -import { batch } from "solid-js"; - import type { ChannelUnread as APIChannelUnread } from "revolt-api"; import { ChannelUnread } from "../classes/ChannelUnread.js"; import type { HydratedChannelUnread } from "../hydration/channelUnread.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Channel Unreads */ -export class ChannelUnreadCollection extends ClassCollection< +export class ChannelUnreadCollection extends Collection< ChannelUnread, HydratedChannelUnread > { @@ -19,19 +17,19 @@ export class ChannelUnreadCollection extends ClassCollection< */ async sync(): Promise { const unreads = await this.client.api.get("/sync/unreads"); - batch(() => { - this.reset(); - for (const unread of unreads) { - this.getOrCreate(unread._id.channel, unread); - } - }); + this.reset(); + for (const unread of unreads) { + this.getOrCreate(unread._id.channel, unread); + } } /** * Clear all unread data */ reset(): void { - this.updateUnderlyingObject({}); + for (const key of this.keys()) { + this.setUnderlyingObject(key, {} as never); + } } /** diff --git a/src/collections/ChannelWebhookCollection.ts b/src/collections/ChannelWebhookCollection.ts index a8cad39c..f0da0f51 100644 --- a/src/collections/ChannelWebhookCollection.ts +++ b/src/collections/ChannelWebhookCollection.ts @@ -3,12 +3,12 @@ import type { Webhook } from "revolt-api"; import { ChannelWebhook } from "../classes/ChannelWebhook.js"; import type { HydratedChannelWebhook } from "../hydration/channelWebhook.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Channel Webhooks */ -export class ChannelWebhookCollection extends ClassCollection< +export class ChannelWebhookCollection extends Collection< ChannelWebhook, HydratedChannelWebhook > { diff --git a/src/collections/Collection.ts b/src/collections/Collection.ts index 153daeee..94342b4f 100644 --- a/src/collections/Collection.ts +++ b/src/collections/Collection.ts @@ -1,150 +1,66 @@ -import type { SetStoreFunction } from "solid-js/store"; - -import { ReactiveMap } from "@solid-primitives/map"; - -import type { Client } from "../Client.js"; -import type { Hydrators } from "../hydration/index.js"; -import { ObjectStorage } from "../storage/ObjectStorage.js"; +import { Client } from "../Client.js"; +import { type Hydrators, hydrate } from "../hydration/index.js"; /** - * Abstract Collection type + * Collection backed by a store */ -export abstract class Collection { - /** - * Get an existing object - * @param id Id - * @returns Object - */ - abstract get(id: string): T | undefined; +export class Collection { + #storage = new Map(); + #objects = new Map(); /** - * Check whether an id exists in the Collection - * @param id Id - * @returns Whether it exists + * Construct store backed collection */ - abstract has(id: string): boolean; + constructor(public readonly client: Client) {} /** - * Delete an object + * Get an existing object * @param id Id */ - abstract delete(id: string): void; - - /** - * Number of stored objects - * @returns Size - */ - abstract size(): number; - - /** - * Iterable of keys in the map - * @returns Iterable - */ - abstract keys(): IterableIterator; - - /** - * Iterable of values in the map - * @returns Iterable - */ - abstract values(): IterableIterator; - - /** - * Iterable of key, value pairs in the map - * @returns Iterable - */ - abstract entries(): IterableIterator<[string, T]>; - - /** - * Execute a provided function over each key, value pair in the map - * @param cb Callback for each pair - */ - abstract forEach( - cb: (value: T, key: string, map: Map) => void, - ): void; - - /** - * List of values in the map - * @returns List - */ - toList(): T[] { - return [...this.values()]; + get(id: string): T | undefined { + return this.#objects.get(id); } /** - * Filter the collection by a given predicate - * @param predicate Predicate to satisfy + * Get an underlying object */ - filter(predicate: (value: T, key: string) => boolean): T[] { - const list: T[] = []; - for (const [key, value] of this.entries()) { - if (predicate(value, key)) { - list.push(value); - } - } - - return list; + getUnderlyingObject(id: string): V { + return this.#storage.get(id) ?? ({} as V); } /** - * Map the collection using a given callback - * @param cb Callback + * Set a key of an underlying object */ - map(cb: (value: T, key: string) => O): O[] { - const list: O[] = []; - for (const [key, value] of this.entries()) { - list.push(cb(value, key)); - } - - return list; + setUnderlyingKey(id: string, key: K, value: V[K]) { + this.#storage.set(id, { + [key]: value, + ...((this.#storage.get(id) ?? {}) as V), + }); } /** - * Find some value based on a predicate - * @param predicate Predicate to satisfy + * Set an underlying object */ - find(predicate: (value: T, key: string) => boolean): T | undefined { - for (const [key, value] of this.entries()) { - if (predicate(value, key)) { - return value; - } - } - } -} - -/** - * Collection backed by a Solid.js Store - */ -export abstract class StoreCollection extends Collection { - #storage = new ObjectStorage(); - #objects = new ReactiveMap(); - readonly getUnderlyingObject: (id: string) => V; - readonly updateUnderlyingObject: SetStoreFunction>; - - /** - * Construct store backed collection - */ - constructor() { - super(); - this.getUnderlyingObject = (key) => this.#storage.get(key) ?? ({} as V); - this.updateUnderlyingObject = this.#storage.set; + setUnderlyingObject(id: string, value: V) { + this.#storage.set(id, value); } /** - * Get an existing object + * Check whether an id exists in the Collection * @param id Id - * @returns Object + * @returns Whether it exists */ - get(id: string): T | undefined { - return this.#objects.get(id); + has(id: string): boolean { + return this.#objects.has(id); } /** - * Check whether an id exists in the Collection + * Check whether the underlying id exists * @param id Id * @returns Whether it exists */ - has(id: string): boolean { - return this.#objects.has(id); + hasUnderlying(id: string): boolean { + return !!((this.#storage.get(id) as { id: string }) ?? { id: false }).id; } /** @@ -153,7 +69,7 @@ export abstract class StoreCollection extends Collection { */ delete(id: string): void { this.#objects.delete(id); - this.updateUnderlyingObject(id, undefined as never); + this.#storage.delete(id); } /** @@ -171,7 +87,12 @@ export abstract class StoreCollection extends Collection { context: unknown, data?: unknown, ): void { - this.#storage.hydrate(id, type, context, data); + if (data) { + this.#storage.set( + id, + hydrate(type, { partial: false, ...data } as never, context, true) as V, + ); + } this.#objects.set(id, instance); } @@ -181,7 +102,9 @@ export abstract class StoreCollection extends Collection { * @returns Whether it is a partial */ isPartial(id: string): boolean { - return !!(this.getUnderlyingObject(id) as { partial: boolean }).partial; + return !!( + (this.#storage.get(id) ?? { partial: true }) as { partial: boolean } + ).partial; } /** @@ -196,7 +119,7 @@ export abstract class StoreCollection extends Collection { * Iterable of keys in the map * @returns Iterable */ - keys(): IterableIterator { + keys(): MapIterator { return this.#objects.keys(); } @@ -204,7 +127,7 @@ export abstract class StoreCollection extends Collection { * Iterable of values in the map * @returns Iterable */ - values(): IterableIterator { + values(): MapIterator { return this.#objects.values(); } @@ -212,32 +135,64 @@ export abstract class StoreCollection extends Collection { * Iterable of key, value pairs in the map * @returns Iterable */ - entries(): IterableIterator<[string, T]> { + entries(): MapIterator<[string, T]> { return this.#objects.entries(); } /** * Execute a provided function over each key, value pair in the map * @param cb Callback for each pair - * @returns Iterable */ forEach(cb: (value: T, key: string, map: Map) => void): void { return this.#objects.forEach(cb); } -} -/** - * Generic class collection backed by store - */ -export class ClassCollection extends StoreCollection { - readonly client: Client; + /** + * List of values in the map + * @returns List + */ + toList(): T[] { + return [...this.values()]; + } + + /** + * Filter the collection by a given predicate + * @param predicate Predicate to satisfy + */ + filter(predicate: (value: T, key: string) => boolean): T[] { + const list: T[] = []; + for (const [key, value] of this.entries()) { + if (predicate(value, key)) { + list.push(value); + } + } + + return list; + } /** - * Create generic class collection - * @param client Client + * Map the collection using a given callback + * @param cb Callback */ - constructor(client: Client) { - super(); - this.client = client; + map(cb: (value: T, key: string) => O): O[] { + const list: O[] = []; + for (const [key, value] of this.entries()) { + list.push(cb(value, key)); + } + + return list; + } + + /** + * Find some value based on a predicate + * @param predicate Predicate to satisfy + */ + find(predicate: (value: T, key: string) => boolean): T | undefined { + for (const [key, value] of this.entries()) { + if (predicate(value, key)) { + return value; + } + } + return undefined; } } diff --git a/src/collections/EmojiCollection.ts b/src/collections/EmojiCollection.ts index eb700f54..a661c4f1 100644 --- a/src/collections/EmojiCollection.ts +++ b/src/collections/EmojiCollection.ts @@ -3,12 +3,12 @@ import type { Emoji as APIEmoji } from "revolt-api"; import { Emoji } from "../classes/Emoji.js"; import type { HydratedEmoji } from "../hydration/emoji.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Emoji */ -export class EmojiCollection extends ClassCollection { +export class EmojiCollection extends Collection { /** * Fetch emoji by ID * @param id Id diff --git a/src/collections/MessageCollection.ts b/src/collections/MessageCollection.ts index efd351de..08b8d606 100644 --- a/src/collections/MessageCollection.ts +++ b/src/collections/MessageCollection.ts @@ -3,15 +3,12 @@ import type { Message as APIMessage } from "revolt-api"; import { Message } from "../classes/Message.js"; import type { HydratedMessage } from "../hydration/message.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Messages */ -export class MessageCollection extends ClassCollection< - Message, - HydratedMessage -> { +export class MessageCollection extends Collection { /** * Fetch message by Id * @param channelId Channel Id diff --git a/src/collections/ServerCollection.ts b/src/collections/ServerCollection.ts index e7c30c0e..2c9f658b 100644 --- a/src/collections/ServerCollection.ts +++ b/src/collections/ServerCollection.ts @@ -1,5 +1,3 @@ -import { batch } from "solid-js"; - import type { Server as APIServer, Channel, @@ -9,12 +7,12 @@ import type { import { Server } from "../classes/Server.js"; import type { HydratedServer } from "../hydration/server.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Servers */ -export class ServerCollection extends ClassCollection { +export class ServerCollection extends Collection { /** * Fetch server by ID * @@ -29,15 +27,13 @@ export class ServerCollection extends ClassCollection { include_channels: true, }); - return batch(() => { - for (const channel of data.channels as unknown as Channel[]) { - if (typeof channel !== "string") { - this.client.channels.getOrCreate(channel._id, channel); - } + for (const channel of data.channels as unknown as Channel[]) { + if (typeof channel !== "string") { + this.client.channels.getOrCreate(channel._id, channel); } + } - return this.getOrCreate(data._id, data); - }); + return this.getOrCreate(data._id, data); } /** @@ -85,12 +81,10 @@ export class ServerCollection extends ClassCollection { data, ); - return batch(() => { - for (const channel of channels) { - this.client.channels.getOrCreate(channel._id, channel); - } + for (const channel of channels) { + this.client.channels.getOrCreate(channel._id, channel); + } - return this.getOrCreate(server._id, server, true); - }); + return this.getOrCreate(server._id, server, true); } } diff --git a/src/collections/ServerMemberCollection.ts b/src/collections/ServerMemberCollection.ts index d421c2b8..9312b409 100644 --- a/src/collections/ServerMemberCollection.ts +++ b/src/collections/ServerMemberCollection.ts @@ -3,12 +3,12 @@ import type { Member, MemberCompositeKey } from "revolt-api"; import { ServerMember } from "../classes/ServerMember.js"; import type { HydratedServerMember } from "../hydration/serverMember.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Server Members */ -export class ServerMemberCollection extends ClassCollection< +export class ServerMemberCollection extends Collection< ServerMember, HydratedServerMember > { diff --git a/src/collections/SessionCollection.ts b/src/collections/SessionCollection.ts index 59274b98..0d638ea1 100644 --- a/src/collections/SessionCollection.ts +++ b/src/collections/SessionCollection.ts @@ -1,28 +1,21 @@ -import { batch } from "solid-js"; - import type { SessionInfo } from "revolt-api"; import { Session } from "../classes/Session.js"; import type { HydratedSession } from "../hydration/session.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Sessions */ -export class SessionCollection extends ClassCollection< - Session, - HydratedSession -> { +export class SessionCollection extends Collection { /** * Fetch active sessions * @returns List of sessions */ async fetch(): Promise { const data = await this.client.api.get("/auth/session/all"); - return batch(() => - data.map((session) => this.getOrCreate(session._id, session)), - ); + return data.map((session) => this.getOrCreate(session._id, session)); } /** diff --git a/src/collections/UserCollection.ts b/src/collections/UserCollection.ts index 4ae2c23e..b721fdce 100644 --- a/src/collections/UserCollection.ts +++ b/src/collections/UserCollection.ts @@ -4,12 +4,12 @@ import type { Client } from "../Client.js"; import { User } from "../classes/User.js"; import type { HydratedUser } from "../hydration/user.js"; -import { ClassCollection } from "./Collection.js"; +import { Collection } from "./Collection.js"; /** * Collection of Users */ -export class UserCollection extends ClassCollection { +export class UserCollection extends Collection { /** * Construct User collection */ diff --git a/src/events/EventClient.ts b/src/events/EventClient.ts index f9b9fced..a60029dc 100644 --- a/src/events/EventClient.ts +++ b/src/events/EventClient.ts @@ -1,6 +1,3 @@ -import type { Accessor, Setter } from "solid-js"; -import { createSignal } from "solid-js"; - import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter"; import type { Error } from "revolt-api"; @@ -82,11 +79,8 @@ export class EventClient< #protocolVersion: T; #transportFormat: "json" | "msgpack"; - readonly ping: Accessor; - #setPing: Setter; - - readonly state: Accessor; - #setStateSetter: Setter; + ping = -1; + state = ConnectionState.Idle; #socket: WebSocket | undefined; #heartbeatIntervalReference: number | undefined; @@ -120,14 +114,6 @@ export class EventClient< ...options, }; - const [state, setState] = createSignal(ConnectionState.Idle); - this.state = state; - this.#setStateSetter = setState; - - const [ping, setPing] = createSignal(-1); - this.ping = ping; - this.#setPing = setPing; - this.disconnect = this.disconnect.bind(this); } @@ -136,7 +122,7 @@ export class EventClient< * @param state state */ private setState(state: ConnectionState): void { - this.#setStateSetter(state); + this.state = state; this.emit("state", state); } @@ -232,8 +218,8 @@ export class EventClient< return; case "Pong": clearTimeout(this.#pongTimeoutReference); - this.#setPing(+new Date() - event.data); - if (this.options.debug) console.debug(`[ping] ${this.ping()}ms`); + this.ping = +new Date() - event.data; + if (this.options.debug) console.debug(`[ping] ${this.ping}ms`); return; case "Error": this.#lastError = { @@ -245,7 +231,7 @@ export class EventClient< return; } - switch (this.state()) { + switch (this.state) { case ConnectionState.Connecting: if (event.type === "Authenticated") { // no-op @@ -264,7 +250,7 @@ export class EventClient< } break; default: - throw `Unreachable code. Received ${event.type} in state ${this.state()}.`; + throw `Unreachable code. Received ${event.type} in state ${this.state}.`; } } diff --git a/src/events/v1.ts b/src/events/v1.ts index 0834c4b8..86eed30c 100644 --- a/src/events/v1.ts +++ b/src/events/v1.ts @@ -1,7 +1,3 @@ -import type { Setter } from "solid-js"; -import { batch } from "solid-js"; - -import { ReactiveSet } from "@solid-primitives/set"; import type { Channel, Emoji, @@ -190,7 +186,7 @@ type ReadyData = { export async function handleEvent( client: Client, event: ServerMessage, - setReady: Setter, + setReady: (value: boolean) => void, ): Promise { if (client.options.debug) { console.debug("[S->C]", event); @@ -204,31 +200,29 @@ export async function handleEvent( break; } case "Ready": { - batch(() => { - for (const user of event.users) { - const u = client.users.getOrCreate(user._id, user); + for (const user of event.users) { + const u = client.users.getOrCreate(user._id, user); - if (u.relationship === "User") { - client.user = u; - } + if (u.relationship === "User") { + client.user = u; } + } - for (const server of event.servers) { - client.servers.getOrCreate(server._id, server); - } + for (const server of event.servers) { + client.servers.getOrCreate(server._id, server); + } - for (const member of event.members) { - client.serverMembers.getOrCreate(member._id, member); - } + for (const member of event.members) { + client.serverMembers.getOrCreate(member._id, member); + } - for (const channel of event.channels) { - client.channels.getOrCreate(channel._id, channel); - } + for (const channel of event.channels) { + client.channels.getOrCreate(channel._id, channel); + } - for (const emoji of event.emojis) { - client.emojis.getOrCreate(emoji._id, emoji); - } - }); + for (const emoji of event.emojis) { + client.emojis.getOrCreate(emoji._id, emoji); + } if (client.options.syncUnreads) { await client.channelUnreads.sync(); @@ -241,25 +235,23 @@ export async function handleEvent( } case "Message": { if (!client.messages.has(event._id)) { - batch(() => { - if (event.member) { - client.serverMembers.getOrCreate(event.member._id, event.member); - } + if (event.member) { + client.serverMembers.getOrCreate(event.member._id, event.member); + } - if (event.user) { - client.users.getOrCreate(event.user._id, event.user); - } + if (event.user) { + client.users.getOrCreate(event.user._id, event.user); + } - delete event.member; - delete event.user; + delete event.member; + delete event.user; - client.messages.getOrCreate(event._id, event, true); - client.channels.updateUnderlyingObject( - event.channel, - "lastMessageId", - event._id, - ); - }); + client.messages.getOrCreate(event._id, event, true); + client.channels.setUnderlyingKey( + event.channel, + "lastMessageId", + event._id, + ); } break; } @@ -271,10 +263,14 @@ export async function handleEvent( channelId: event.channel, }; - client.messages.updateUnderlyingObject(event.id, { + client.messages.setUnderlyingObject(event.id, { ...hydrate( "message", - { ...event.data, channel: event.channel }, + { + ...previousMessage, + ...event.data, + channel: event.channel, + } as typeof event.data, client, false, ), @@ -293,18 +289,14 @@ export async function handleEvent( channelId: event.channel, }; - client.messages.updateUnderlyingObject(event.id, "embeds", (embeds) => [ - ...(embeds ?? []), + client.messages.setUnderlyingKey(event.id, "embeds", [ + ...(previousMessage.embeds ?? []), ...(event.append.embeds?.map((embed) => MessageEmbed.from(client, embed), ) ?? []), ]); - client.messages.updateUnderlyingObject( - event.id, - "channelId", - event.channel, - ); + client.messages.setUnderlyingKey(event.id, "channelId", event.channel); client.emit("messageUpdate", message, previousMessage); } @@ -319,22 +311,20 @@ export async function handleEvent( break; } case "BulkMessageDelete": { - batch(() => - client.emit( - "messageDeleteBulk", - event.ids - .map((id) => { - if (client.messages.has(id)) { - const message = client.messages.getUnderlyingObject(id); - client.messages.delete(id); - return message!; - } - - return undefined!; - }) - .filter((x) => x), - client.channels.get(event.channel), - ), + client.emit( + "messageDeleteBulk", + event.ids + .map((id) => { + if (client.messages.has(id)) { + const message = client.messages.getUnderlyingObject(id); + client.messages.delete(id); + return message!; + } + + return undefined!; + }) + .filter((x) => x), + client.channels.get(event.channel), ); break; } @@ -347,7 +337,7 @@ export async function handleEvent( if (set.has(event.user_id)) return; set.add(event.user_id); } else { - reactions.set(event.emoji_id, new ReactiveSet([event.user_id])); + reactions.set(event.emoji_id, new Set([event.user_id])); } client.emit( @@ -423,7 +413,7 @@ export async function handleEvent( } } - client.channels.updateUnderlyingObject(event.id, changes); + client.channels.setUnderlyingObject(event.id, changes); client.emit("channelUpdate", channel, previousChannel); } break; @@ -513,13 +503,11 @@ export async function handleEvent( } case "ServerCreate": { if (!client.servers.has(event.server._id)) { - batch(() => { - for (const channel of event.channels) { - client.channels.getOrCreate(channel._id, channel); - } + for (const channel of event.channels) { + client.channels.getOrCreate(channel._id, channel); + } - client.servers.getOrCreate(event.server._id, event.server, true); - }); + client.servers.getOrCreate(event.server._id, event.server, true); } break; } @@ -554,7 +542,7 @@ export async function handleEvent( } } - client.servers.updateUnderlyingObject(event.id, changes); + client.servers.setUnderlyingObject(event.id, changes); client.emit("serverUpdate", server, previousServer); } break; @@ -649,7 +637,7 @@ export async function handleEvent( } } - client.serverMembers.updateUnderlyingObject( + client.serverMembers.setUnderlyingObject( event.id.server + event.id.user, changes as never, ); @@ -720,7 +708,7 @@ export async function handleEvent( } } - client.users.updateUnderlyingObject(event.id, changes as never); + client.users.setUnderlyingObject(event.id, changes as never); client.emit("userUpdate", user, previousUser); } break; @@ -758,37 +746,35 @@ export async function handleEvent( break; } case "UserPlatformWipe": { - batch(() => { - handleEvent( - client, - { - type: "BulkMessageDelete", - channel: "0", - ids: client.messages - .toList() - .filter((message) => message.authorId === event.user_id) - .map((message) => message.id), - }, - setReady, - ); + handleEvent( + client, + { + type: "BulkMessageDelete", + channel: "0", + ids: client.messages + .toList() + .filter((message) => message.authorId === event.user_id) + .map((message) => message.id), + }, + setReady, + ); - handleEvent( - client, - { - type: "UserUpdate", - id: event.user_id, - data: { - username: `Deleted User`, - online: false, - flags: event.flags, - badges: 0, - relationship: "None", - }, - clear: ["Avatar", "StatusPresence", "StatusText"], + handleEvent( + client, + { + type: "UserUpdate", + id: event.user_id, + data: { + username: `Deleted User`, + online: false, + flags: event.flags, + badges: 0, + relationship: "None", }, - setReady, - ); - }); + clear: ["Avatar", "StatusPresence", "StatusText"], + }, + setReady, + ); break; } diff --git a/src/hydration/channel.ts b/src/hydration/channel.ts index 5307ff6f..9ecbe6d5 100644 --- a/src/hydration/channel.ts +++ b/src/hydration/channel.ts @@ -1,4 +1,3 @@ -import { ReactiveSet } from "@solid-primitives/set"; import type { Channel as APIChannel, OverrideField } from "revolt-api"; import type { Client } from "../Client.js"; @@ -16,8 +15,8 @@ export type HydratedChannel = { icon?: File; active: boolean; - typingIds: ReactiveSet; - recipientIds: ReactiveSet; + typingIds: Set; + recipientIds: Set; userId?: string; ownerId?: string; @@ -50,8 +49,8 @@ export const channelHydration: Hydrate, HydratedChannel> = { description: (channel) => channel.description!, icon: (channel, ctx) => new File(ctx as Client, channel.icon!), active: (channel) => channel.active || false, - typingIds: () => new ReactiveSet(), - recipientIds: (channel) => new ReactiveSet(channel.recipients), + typingIds: () => new Set(), + recipientIds: (channel) => new Set(channel.recipients), userId: (channel) => channel.user, ownerId: (channel) => channel.owner, serverId: (channel) => channel.server, @@ -62,7 +61,7 @@ export const channelHydration: Hydrate, HydratedChannel> = { lastMessageId: (channel) => channel.last_message_id!, }, initialHydration: () => ({ - typingIds: new ReactiveSet(), - recipientIds: new ReactiveSet(), + typingIds: new Set(), + recipientIds: new Set(), }), }; diff --git a/src/hydration/channelUnread.ts b/src/hydration/channelUnread.ts index b30f503c..73a98720 100644 --- a/src/hydration/channelUnread.ts +++ b/src/hydration/channelUnread.ts @@ -1,4 +1,3 @@ -import { ReactiveSet } from "@solid-primitives/set"; import type { ChannelUnread } from "revolt-api"; import type { Merge } from "../lib/merge.js"; @@ -8,7 +7,7 @@ import type { Hydrate } from "./index.js"; export type HydratedChannelUnread = { id: string; lastMessageId?: string; - messageMentionIds: ReactiveSet; + messageMentionIds: Set; }; export const channelUnreadHydration: Hydrate< @@ -23,9 +22,9 @@ export const channelUnreadHydration: Hydrate< functions: { id: (unread) => unread._id.channel, lastMessageId: (unread) => unread.last_id!, - messageMentionIds: (unread) => new ReactiveSet(unread.mentions!), + messageMentionIds: (unread) => new Set(unread.mentions!), }, initialHydration: () => ({ - messageMentionIds: new ReactiveSet(), + messageMentionIds: new Set(), }), }; diff --git a/src/hydration/message.ts b/src/hydration/message.ts index 22744988..c5b76f7b 100644 --- a/src/hydration/message.ts +++ b/src/hydration/message.ts @@ -1,5 +1,3 @@ -import { ReactiveMap } from "@solid-primitives/map"; -import { ReactiveSet } from "@solid-primitives/set"; import type { Interactions, Masquerade, Message } from "revolt-api"; import type { Client } from "../Client.js"; @@ -24,7 +22,7 @@ export type HydratedMessage = { embeds?: MessageEmbed[]; mentionIds?: string[]; replyIds?: string[]; - reactions: ReactiveMap>; + reactions: Map>; interactions?: Interactions; masquerade?: Masquerade; flags?: number; @@ -60,10 +58,10 @@ export const messageHydration: Hydrate, HydratedMessage> = { mentionIds: (message) => message.mentions!, replyIds: (message) => message.replies!, reactions: (message) => { - const map = new ReactiveMap>(); + const map = new Map>(); if (message.reactions) { for (const reaction of Object.keys(message.reactions)) { - map.set(reaction, new ReactiveSet(message.reactions![reaction])); + map.set(reaction, new Set(message.reactions![reaction])); } } return map; @@ -73,6 +71,6 @@ export const messageHydration: Hydrate, HydratedMessage> = { flags: (message) => message.flags!, }, initialHydration: () => ({ - reactions: new ReactiveMap(), + reactions: new Map(), }), }; diff --git a/src/hydration/server.ts b/src/hydration/server.ts index 0cb19b03..9401ef7c 100644 --- a/src/hydration/server.ts +++ b/src/hydration/server.ts @@ -1,5 +1,3 @@ -import { ReactiveMap } from "@solid-primitives/map"; -import { ReactiveSet } from "@solid-primitives/set"; import type { Server as APIServer, Category, @@ -22,11 +20,11 @@ export type HydratedServer = { icon?: File; banner?: File; - channelIds: ReactiveSet; + channelIds: Set; categories?: Category[]; systemMessages?: SystemMessageChannels; - roles: ReactiveMap; + roles: Map; defaultPermissions: number; flags: ServerFlags; @@ -48,13 +46,11 @@ export const serverHydration: Hydrate = { ownerId: (server) => server.owner, name: (server) => server.name, description: (server) => server.description!, - channelIds: (server) => new ReactiveSet(server.channels), + channelIds: (server) => new Set(server.channels), categories: (server) => server.categories ?? [], systemMessages: (server) => server.system_messages ?? {}, roles: (server) => - new ReactiveMap( - Object.keys(server.roles!).map((id) => [id, server.roles![id]]), - ), + new Map(Object.keys(server.roles!).map((id) => [id, server.roles![id]])), defaultPermissions: (server) => server.default_permissions, icon: (server, ctx) => new File(ctx as Client, server.icon!), banner: (server, ctx) => new File(ctx as Client, server.banner!), @@ -64,8 +60,8 @@ export const serverHydration: Hydrate = { nsfw: (server) => server.nsfw || false, }, initialHydration: () => ({ - channelIds: new ReactiveSet(), - roles: new ReactiveMap(), + channelIds: new Set(), + roles: new Map(), }), }; diff --git a/src/storage/ObjectStorage.ts b/src/storage/ObjectStorage.ts deleted file mode 100644 index 366389c1..00000000 --- a/src/storage/ObjectStorage.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { SetStoreFunction } from "solid-js/store"; -import { createStore } from "solid-js/store"; - -import { type Hydrators, hydrate } from "../hydration/index.js"; - -/** - * Wrapper around Solid.js store - */ -export class ObjectStorage { - private store: Record; - readonly set: SetStoreFunction>; - - /** - * Create new object storage - */ - constructor() { - const [store, setStore] = createStore({}); - this.store = store as never; - this.set = setStore; - this.get = this.get.bind(this); - } - - /** - * Get object by ID - * @param id ID - * @returns Object - */ - get(id: string): T | undefined { - return this.store[id]; - } - - /** - * Hydrate some data into storage - * @param id ID - * @param type Hydration type - * @param context Context - * @param data Input Data - */ - hydrate( - id: string, - type: keyof Hydrators, - context: unknown, - data?: unknown - ): void { - if (data) { - data = { partial: false, ...data }; - this.set(id, hydrate(type, data as never, context, true) as T); - } - } -} diff --git a/test.mjs b/test.mjs index 0ebc8c76..827c240d 100644 --- a/test.mjs +++ b/test.mjs @@ -10,4 +10,4 @@ client.on("disconnected", () => console.info("Disconnected.")); client.on("messageCreate", (message) => console.info(message.content)); -client.loginBot(env.TOKEN); +await client.loginBot(env.TOKEN);