feat: create separate collection classes

This commit is contained in:
Paul Makles
2023-04-08 15:17:24 +01:00
parent e45ff7fcbf
commit 2ab8b33ba9
12 changed files with 673 additions and 551 deletions
+45 -20
View File
@@ -9,23 +9,38 @@ import messageClassFactory from "./classes/Message";
import serverClassFactory from "./classes/Server";
import serverMemberClassFactory from "./classes/ServerMember";
import userClassFactory from "./classes/User";
import {
ChannelCollection,
EmojiCollection,
MessageCollection,
ServerCollection,
ServerMemberCollection,
UserCollection,
} from "./collections";
import { EventClient, createEventClient } from "./events/client";
// eslint-disable-next-line
type Obj<T extends (...args: any) => any> = InstanceType<ReturnType<T>>;
type O<T extends (...args: any) => any> = InstanceType<ReturnType<T>>;
export type Channel = Obj<typeof channelClassFactory>;
export type Emoji = Obj<typeof emojiClassFactory>;
export type Message = Obj<typeof messageClassFactory>;
export type Server = Obj<typeof serverClassFactory>;
export type ServerMember = Obj<typeof serverMemberClassFactory>;
export type User = Obj<typeof userClassFactory>;
export type Channel = O<typeof channelClassFactory>;
export type Emoji = O<typeof emojiClassFactory>;
export type Message = O<typeof messageClassFactory>;
export type Server = O<typeof serverClassFactory>;
export type ServerMember = O<typeof serverMemberClassFactory>;
export type User = O<typeof userClassFactory>;
export type Session = { token: string; user_id: string } | string;
/**
* Revolt.js Client
*/
export class Client {
readonly Channel: ReturnType<typeof channelClassFactory>;
readonly Emoji: ReturnType<typeof emojiClassFactory>;
readonly Message: ReturnType<typeof messageClassFactory>;
readonly User: ReturnType<typeof userClassFactory>;
readonly Server: ReturnType<typeof serverClassFactory>;
readonly ServerMember: ReturnType<typeof serverMemberClassFactory>;
readonly channels;
readonly emojis;
readonly messages;
@@ -59,12 +74,22 @@ export class Client {
this.ready = ready;
this.#setReady = setReady;
this.channels = channelClassFactory(this);
this.emojis = emojiClassFactory(this);
this.messages = messageClassFactory(this);
this.users = userClassFactory(this);
this.servers = serverClassFactory(this);
this.serverMembers = serverMemberClassFactory(this);
this.channels = new ChannelCollection();
this.emojis = new EmojiCollection();
this.messages = new MessageCollection();
this.users = new UserCollection();
this.servers = new ServerCollection();
this.serverMembers = new ServerMemberCollection();
this.Channel = channelClassFactory(this, this.channels as never);
this.Emoji = emojiClassFactory(this, this.emojis as never);
this.Message = messageClassFactory(this, this.messages as never);
this.User = userClassFactory(this, this.users as never);
this.Server = serverClassFactory(this, this.servers as never);
this.ServerMember = serverMemberClassFactory(
this,
this.serverMembers as never
);
}
/**
@@ -76,7 +101,7 @@ export class Client {
if (event.type === "Ready") {
console.time("load users");
for (const user of event.users) {
const u = new this.users(user._id, user);
const u = new this.User(user._id, user);
if (u.relationship === "User") {
this.user = u;
@@ -85,22 +110,22 @@ export class Client {
console.timeEnd("load users");
console.time("load servers");
for (const server of event.servers) {
new this.servers(server._id, server);
new this.Server(server._id, server);
}
console.timeEnd("load servers");
console.time("load memberships");
for (const member of event.members) {
new this.serverMembers(member._id, member);
new this.ServerMember(member._id, member);
}
console.timeEnd("load memberships");
console.time("load channels");
for (const channel of event.channels) {
new this.channels(channel._id, channel);
new this.Channel(channel._id, channel);
}
console.timeEnd("load channels");
console.time("load emojis");
for (const emoji of event.emojis) {
new this.emojis(emoji._id, emoji);
new this.Emoji(emoji._id, emoji);
}
console.timeEnd("load emojis");
@@ -113,11 +138,11 @@ export class Client {
"It has the channels:",
lounge.channels.map((channel) => channel.name)
);
console.log(
/*console.log(
"They joined at:",
this.serverMembers.get({ server: lounge.id, user: lounge.owner!.id })
?.joinedAt
);
);*/
this.#setReady(true);
}
+216 -100
View File
@@ -1,107 +1,60 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Channel as ApiChannel } from "revolt-api";
import { decodeTime } from "ulid";
import { SetStoreFunction } from "solid-js/store";
import type {
Channel as ApiChannel,
DataEditChannel,
DataMessageSend,
Member,
Message,
User,
} from "revolt-api";
import { APIRoutes } from "revolt-api/dist/routes";
import { decodeTime, ulid } from "ulid";
import { Client } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { hydrate } from "../hydration";
import { HydratedChannel } from "../hydration/channel";
import { bitwiseAndEq, calculatePermission } from "../permissions/calculator";
import { Permission } from "../permissions/definitions";
import { ObjectStorage } from "../storage/ObjectStorage";
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, HydratedChannel>
) =>
/**
* Channel Class
*/
class Channel {
static #storage = new ObjectStorage<HydratedChannel>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedChannel
>;
static #set: SetStoreFunction<Record<string, HydratedChannel>>;
static #get: (id: string) => HydratedChannel;
static {
Channel.#collection = collection as never;
Channel.#set = collection.updateUnderlyingObject as never;
Channel.#get = collection.getUnderlyingObject as never;
client.events.on("event", (event) => {
switch (event.type) {
case "ChannelUpdate": {
this.#storage.set(event.id, hydrate("channel", event.data));
this.#set(event.id, hydrate("channel", event.data));
break;
}
}
});
}
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): InstanceType<typeof this> | undefined {
return this.#objects.get(id);
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch channel by ID
* @param id ID
* @returns Channel
*/
static async fetch(id: string): Promise<Channel | undefined> {
const channel = Channel.get(id);
const channel = Channel.#collection.get(id);
if (channel) return channel;
const data = await client.api.get(`/channels/${id as ""}`);
@@ -116,12 +69,16 @@ export default (client: Client) =>
*/
constructor(id: string, data?: ApiChannel) {
this.id = id;
Channel.#storage.hydrate(id, "channel", data);
Channel.#objects.set(id, this);
Channel.#collection.create(id, "channel", this, data);
}
updateSomething() {
Channel.#storage.set(this.id, "name", "troling");
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: string, data?: ApiChannel) {
return client.channels.get(id) ?? new Channel(id, data);
}
/**
@@ -135,51 +92,51 @@ export default (client: Client) =>
* Channel type
*/
get type() {
return Channel.#storage.get(this.id).channelType;
return Channel.#get(this.id).channelType;
}
/**
* Channel name
*/
get name() {
return Channel.#storage.get(this.id).name;
return Channel.#get(this.id).name;
}
/**
* Channel description
*/
get description() {
return Channel.#storage.get(this.id).description;
return Channel.#get(this.id).description;
}
/**
* Channel icon
*/
get icon() {
return Channel.#storage.get(this.id).icon;
return Channel.#get(this.id).icon;
}
/**
* Whether the conversation is active
*/
get active() {
return Channel.#storage.get(this.id).active;
return Channel.#get(this.id).active;
}
/**
* User Ids of recipients of the group
*/
get recipientIds() {
return Channel.#storage.get(this.id).recipientIds;
return Channel.#get(this.id).recipientIds;
}
/**
* Recipients of the group
*/
get recipients() {
return Channel.#storage
.get(this.id)
.recipientIds.map((id) => client.users.get(id)!);
return Channel.#get(this.id).recipientIds.map(
(id) => client.users.get(id)!
);
}
/**
@@ -195,77 +152,77 @@ export default (client: Client) =>
* User ID
*/
get userId() {
return Channel.#storage.get(this.id).userId!;
return Channel.#get(this.id).userId!;
}
/**
* User this channel belongs to
*/
get user() {
return client.users.get(Channel.#storage.get(this.id).userId!);
return client.users.get(Channel.#get(this.id).userId!);
}
/**
* Owner ID
*/
get ownerId() {
return Channel.#storage.get(this.id).ownerId!;
return Channel.#get(this.id).ownerId!;
}
/**
* Owner of the group
*/
get owner() {
return client.users.get(Channel.#storage.get(this.id).ownerId!);
return client.users.get(Channel.#get(this.id).ownerId!);
}
/**
* Server ID
*/
get serverId() {
return Channel.#storage.get(this.id).serverId!;
return Channel.#get(this.id).serverId!;
}
/**
* Server this channel is in
*/
get server() {
return client.servers.get(Channel.#storage.get(this.id).serverId!);
return client.servers.get(Channel.#get(this.id).serverId!);
}
/**
* Permissions allowed for users in this group
*/
get permissions() {
return Channel.#storage.get(this.id).permissions;
return Channel.#get(this.id).permissions;
}
/**
* Default permissions for this server channel
*/
get defaultPermissions() {
return Channel.#storage.get(this.id).defaultPermissions;
return Channel.#get(this.id).defaultPermissions;
}
/**
* Role permissions for this server channel
*/
get rolePermissions() {
return Channel.#storage.get(this.id).rolePermissions;
return Channel.#get(this.id).rolePermissions;
}
/**
* Whether this channel is marked as mature
*/
get mature() {
return Channel.#storage.get(this.id).nsfw;
return Channel.#get(this.id).nsfw;
}
/**
* ID of the last message sent in this channel
*/
get lastMessageId() {
return Channel.#storage.get(this.id).lastMessageId;
return Channel.#get(this.id).lastMessageId;
}
/**
@@ -341,4 +298,163 @@ export default (client: Client) =>
...permission.map((x) => Permission[x])
);
}
/**
* Fetch a channel's members.
* @requires `Group`
* @returns An array of the channel's members.
*/
async fetchMembers() {
const members = await client.api.get(
`/channels/${this.id as ""}/members`
);
return members.map((user) => client.User.new(user._id, user));
}
/**
* Edit a channel
* @param data Edit data
*/
async edit(data: DataEditChannel) {
await client.api.patch(`/channels/${this.id as ""}`, data);
}
/**
* Delete a channel
* @param leave_silently Whether to not send a message on leave
* @param noRequest Whether to not send a request
* @requires `DM`, `Group`, `TextChannel`, `VoiceChannel`
*/
async delete(leave_silently?: boolean, noRequest?: boolean) {
if (!noRequest)
await client.api.delete(`/channels/${this.id as ""}`, {
leave_silently,
});
if (this.type === "DirectMessage") {
Channel.#set(this.id, "active", false);
return;
}
if (this.type === "TextChannel" || this.type === "VoiceChannel") {
const server = this.server;
if (server) {
server.channelIds.delete(this.id);
}
}
client.channels.delete(this.id);
}
/**
* Add a user to a group
* @param user_id ID of the target user
*/
async addMember(user_id: string) {
return await client.api.put(
`/channels/${this.id as ""}/recipients/${user_id as ""}`
);
}
/**
* Remove a user from a group
* @param user_id ID of the target user
*/
async removeMember(user_id: string) {
return await client.api.delete(
`/channels/${this.id as ""}/recipients/${user_id as ""}`
);
}
/**
* Send a message
* @param data Either the message as a string or message sending route data
* @returns The message
*/
async sendMessage(
data: string | DataMessageSend,
idempotencyKey: string = ulid()
) {
const msg: DataMessageSend =
typeof data === "string" ? { content: data } : data;
const message = await client.api.post(
`/channels/${this.id as ""}/messages`,
msg,
{
headers: {
"Idempotency-Key": idempotencyKey,
},
}
);
return client.Message.new(message._id, message);
}
/**
* Fetch a message by its ID
* @param messageId ID of the target message
* @returns The message
*/
async fetchMessage(messageId: string) {
const message = await client.api.get(
`/channels/${this.id as ""}/messages/${messageId as ""}`
);
return client.Message.new(message._id, message);
}
/**
* Fetch multiple messages from a channel
* @param params Message fetching route data
* @returns The messages
*/
async fetchMessages(
params?: Omit<
(APIRoutes & {
method: "get";
path: "/channels/{target}/messages";
})["params"],
"include_users"
>
) {
const messages = (await client.api.get(
`/channels/${this.id as ""}/messages`,
{ ...params }
)) as Message[];
return messages.map((message) =>
client.Message.new(message._id, message)
);
}
/**
* Fetch multiple messages from a channel including the users that sent them
* @param params Message fetching route data
* @returns Object including messages and users
*/
async fetchMessagesWithUsers(
params?: Omit<
(APIRoutes & {
method: "get";
path: "/channels/{target}/messages";
})["params"],
"include_users"
>
) {
const data = (await client.api.get(
`/channels/${this.id as ""}/messages`,
{ ...params, include_users: true }
)) as { messages: Message[]; users: User[]; members?: Member[] };
return {
messages: data.messages.map((message) =>
client.Message.new(message._id, message)
),
users: data.users.map((user) => client.User.new(user._id, user)),
members: data.members?.map((member) =>
client.ServerMember.new(member._id, member)
),
};
}
};
+33 -78
View File
@@ -1,93 +1,40 @@
import { ReactiveMap } from "@solid-primitives/map";
import { SetStoreFunction } from "solid-js/store";
import type { Emoji as ApiEmoji } from "revolt-api";
import { decodeTime } from "ulid";
import { Client } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { HydratedEmoji } from "../hydration/emoji";
import { ObjectStorage } from "../storage/ObjectStorage";
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, unknown>
) =>
/**
* Emoji Class
*/
class Emoji {
static #storage = new ObjectStorage<HydratedEmoji>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedEmoji
>;
static #set: SetStoreFunction<Record<string, HydratedEmoji>>;
static #get: (id: string) => HydratedEmoji;
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): InstanceType<typeof this> | undefined {
return this.#objects.get(id);
static {
Emoji.#collection = collection as never;
Emoji.#set = collection.updateUnderlyingObject as never;
Emoji.#get = collection.getUnderlyingObject as never;
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch emoji by ID
* @param id ID
* @returns Emoji
*/
static async fetch(id: string): Promise<Emoji | undefined> {
const emoji = Emoji.get(id);
const emoji = Emoji.#collection.get(id);
if (emoji) return emoji;
const data = await client.api.get(`/custom/emoji/${id as ""}`);
@@ -102,8 +49,16 @@ export default (client: Client) =>
*/
constructor(id: string, data?: ApiEmoji) {
this.id = id;
Emoji.#storage.hydrate(id, "emoji", data);
Emoji.#objects.set(id, this);
Emoji.#collection.create(id, "emoji", this, data);
}
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: string, data?: ApiEmoji) {
return client.emojis.get(id) ?? new Emoji(id, data);
}
/**
@@ -117,34 +72,34 @@ export default (client: Client) =>
* Information about the parent of this emoji
*/
get parent() {
return Emoji.#storage.get(this.id).parent;
return Emoji.#get(this.id).parent;
}
/**
* Creator of the emoji
*/
get creator() {
return client.users.get(Emoji.#storage.get(this.id).creatorId);
return client.users.get(Emoji.#get(this.id).creatorId);
}
/**
* Name
*/
get name() {
return Emoji.#storage.get(this.id).name;
return Emoji.#get(this.id).name;
}
/**
* Whether the emoji is animated
*/
get animated() {
return Emoji.#storage.get(this.id).animated;
return Emoji.#get(this.id).animated;
}
/**
* Whether the emoji is marked as mature
*/
get mature() {
return Emoji.#storage.get(this.id).nsfw;
return Emoji.#get(this.id).nsfw;
}
};
+43 -88
View File
@@ -1,86 +1,33 @@
import { ReactiveMap } from "@solid-primitives/map";
import { SetStoreFunction } from "solid-js/store";
import type { Message as ApiMessage } from "revolt-api";
import { decodeTime } from "ulid";
import { Client } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { HydratedMessage } from "../hydration/message";
import { ObjectStorage } from "../storage/ObjectStorage";
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, unknown>
) =>
/**
* Message Class
*/
class Message {
static #storage = new ObjectStorage<HydratedMessage>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedMessage
>;
static #set: SetStoreFunction<Record<string, HydratedMessage>>;
static #get: (id: string) => HydratedMessage;
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): InstanceType<typeof this> | undefined {
return this.#objects.get(id);
static {
Message.#collection = collection as never;
Message.#set = collection.updateUnderlyingObject as never;
Message.#get = collection.getUnderlyingObject as never;
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch message by ID
* @param channelId Channel ID
@@ -91,7 +38,7 @@ export default (client: Client) =>
channelId: string,
messageId: string
): Promise<Message | undefined> {
const message = Message.get(messageId);
const message = Message.#collection.get(messageId);
if (message) return message;
const data = await client.api.get(
@@ -108,8 +55,16 @@ export default (client: Client) =>
*/
constructor(id: string, data?: ApiMessage) {
this.id = id;
Message.#storage.hydrate(id, "message", data);
Message.#objects.set(id, this);
Message.#collection.create(id, "message", this, data);
}
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: string, data?: ApiMessage) {
return client.messages.get(id) ?? new Message(id, data);
}
/**
@@ -123,104 +78,104 @@ export default (client: Client) =>
* Nonce value
*/
get nonce() {
return Message.#storage.get(this.id).nonce;
return Message.#get(this.id).nonce;
}
/**
* Id of channel this message was sent in
*/
get channelId() {
return Message.#storage.get(this.id).channelId;
return Message.#get(this.id).channelId;
}
/**
* Channel this message was sent in
*/
get channel() {
return client.channels.get(Message.#storage.get(this.id).channelId);
return client.channels.get(Message.#get(this.id).channelId);
}
/**
* Id of user this message was sent by
*/
get authorId() {
return Message.#storage.get(this.id).authorId;
return Message.#get(this.id).authorId;
}
/**
* User this message was sent by
*/
get author() {
return client.users.get(Message.#storage.get(this.id).authorId!);
return client.users.get(Message.#get(this.id).authorId!);
}
/**
* Content
*/
get content() {
return Message.#storage.get(this.id).content;
return Message.#get(this.id).content;
}
/**
* System message content
*/
get systemMessage() {
return Message.#storage.get(this.id).systemMessage;
return Message.#get(this.id).systemMessage;
}
/**
* Attachments
*/
get attachments() {
return Message.#storage.get(this.id).attachments;
return Message.#get(this.id).attachments;
}
/**
* Time at which this message was edited
*/
get editedAt() {
return Message.#storage.get(this.id).editedAt;
return Message.#get(this.id).editedAt;
}
/**
* Embeds
*/
get embeds() {
return Message.#storage.get(this.id).embeds;
return Message.#get(this.id).embeds;
}
/**
* IDs of users this message mentions
*/
get mentionIds() {
return Message.#storage.get(this.id).mentionIds;
return Message.#get(this.id).mentionIds;
}
/**
* IDs of messages this message replies to
*/
get replyIds() {
return Message.#storage.get(this.id).replyIds;
return Message.#get(this.id).replyIds;
}
/**
* Reactions
*/
get reactions() {
return Message.#storage.get(this.id).reactions;
return Message.#get(this.id).reactions;
}
/**
* Interactions
*/
get interactions() {
return Message.#storage.get(this.id).interactions;
return Message.#get(this.id).interactions;
}
/**
* Masquerade
*/
get masquerade() {
return Message.#storage.get(this.id).masquerade;
return Message.#get(this.id).masquerade;
}
};
+54 -91
View File
@@ -1,95 +1,42 @@
import { ReactiveMap } from "@solid-primitives/map";
import { SetStoreFunction } from "solid-js/store";
import type { Server as ApiServer, Category } from "revolt-api";
import { decodeTime } from "ulid";
import { Channel, Client } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { HydratedServer } from "../hydration/server";
import { bitwiseAndEq, calculatePermission } from "../permissions/calculator";
import { Permission } from "../permissions/definitions";
import { ObjectStorage } from "../storage/ObjectStorage";
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, unknown>
) =>
/**
* Server Class
*/
class Server {
static #storage = new ObjectStorage<HydratedServer>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedServer
>;
static #set: SetStoreFunction<Record<string, HydratedServer>>;
static #get: (id: string) => HydratedServer;
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): InstanceType<typeof this> | undefined {
return this.#objects.get(id);
static {
Server.#collection = collection as never;
Server.#set = collection.updateUnderlyingObject as never;
Server.#get = collection.getUnderlyingObject as never;
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch server by ID
* @param id ID
* @returns Server
*/
static async fetch(id: string): Promise<Server | undefined> {
const server = Server.get(id);
const server = Server.#collection.get(id);
if (server) return server;
const data = await client.api.get(`/servers/${id as ""}`);
@@ -99,13 +46,22 @@ export default (client: Client) =>
readonly id: string;
/**
* Construct Server
* @param id Server Id
* Construct
* @param id Id
* @param data Data
*/
constructor(id: string, data?: ApiServer) {
this.id = id;
Server.#storage.hydrate(id, "server", data);
Server.#objects.set(id, this);
Server.#collection.create(id, "server", this, data);
}
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: string, data?: ApiServer) {
return client.servers.get(id) ?? new Server(id, data);
}
/**
@@ -119,49 +75,56 @@ export default (client: Client) =>
* Owner's user ID
*/
get ownerId() {
return Server.#storage.get(this.id).ownerId;
return Server.#get(this.id).ownerId;
}
/**
* Owner
*/
get owner() {
return client.users.get(Server.#storage.get(this.id).ownerId);
return client.users.get(Server.#get(this.id).ownerId);
}
/**
* Name
*/
get name() {
return Server.#storage.get(this.id).name;
return Server.#get(this.id).name;
}
/**
* Description
*/
get description() {
return Server.#storage.get(this.id).description;
return Server.#get(this.id).description;
}
/**
* Icon
*/
get icon() {
return Server.#storage.get(this.id).icon;
return Server.#get(this.id).icon;
}
/**
* Banner
*/
get banner() {
return Server.#storage.get(this.id).banner;
return Server.#get(this.id).banner;
}
/**
* Channel IDs
*/
get channelIds() {
return Server.#get(this.id).channelIds;
}
/**
* Channels
*/
get channels() {
return [...Server.#storage.get(this.id).channelIds.values()]
return [...Server.#get(this.id).channelIds.values()]
.map((id) => client.channels.get(id)!)
.filter((x) => x);
}
@@ -170,56 +133,56 @@ export default (client: Client) =>
* Categories
*/
get categories() {
return Server.#storage.get(this.id).categories;
return Server.#get(this.id).categories;
}
/**
* System message channels
*/
get systemMessages() {
return Server.#storage.get(this.id).systemMessages;
return Server.#get(this.id).systemMessages;
}
/**
* Roles
*/
get roles() {
return Server.#storage.get(this.id).roles;
return Server.#get(this.id).roles;
}
/**
* Default permissions
*/
get defaultPermissions() {
return Server.#storage.get(this.id).defaultPermissions;
return Server.#get(this.id).defaultPermissions;
}
/**
* Server flags
*/
get flags() {
return Server.#storage.get(this.id).flags;
return Server.#get(this.id).flags;
}
/**
* Whether analytics are enabled for this server
*/
get analytics() {
return Server.#storage.get(this.id).analytics;
return Server.#get(this.id).analytics;
}
/**
* Whether this server is publicly discoverable
*/
get discoverable() {
return Server.#storage.get(this.id).discoverable;
return Server.#get(this.id).discoverable;
}
/**
* Whether this server is marked as mature
*/
get mature() {
return Server.#storage.get(this.id).nsfw;
return Server.#get(this.id).nsfw;
}
/**
@@ -336,7 +299,7 @@ export default (client: Client) =>
* Own member object for this server
*/
get member() {
return client.serverMembers.get({
return client.serverMembers.getByKey({
server: this.id,
user: client.user!.id,
});
+36 -87
View File
@@ -1,11 +1,12 @@
import { ReactiveMap } from "@solid-primitives/map";
import { SetStoreFunction } from "solid-js/store";
import type { Member as ApiMember, MemberCompositeKey } from "revolt-api";
import { Channel, Client, Server } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { HydratedServerMember } from "../hydration/serverMember";
import { bitwiseAndEq, calculatePermission } from "../permissions/calculator";
import { Permission } from "../permissions/definitions";
import { ObjectStorage } from "../storage/ObjectStorage";
/**
* Deterministic conversion of member composite key to string ID
@@ -16,88 +17,27 @@ function key(key: MemberCompositeKey) {
return key.server + key.user;
}
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, unknown>
) =>
/**
* Server Member Class
*/
class ServerMember {
static #storage = new ObjectStorage<HydratedServerMember>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedServerMember
>;
static #set: SetStoreFunction<Record<string, HydratedServerMember>>;
static #get: (id: string) => HydratedServerMember;
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Deterministic conversion of member composite key to string ID
* @param key Key
* @returns String key
*/
static keyToString = key;
/**
* Get an existing Member
* @param id Member ID
* @returns Member
*/
static get(id: MemberCompositeKey): ServerMember | undefined {
return ServerMember.#objects.get(key(id));
static {
ServerMember.#collection = collection as never;
ServerMember.#set = collection.updateUnderlyingObject as never;
ServerMember.#get = collection.getUnderlyingObject as never;
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch member by ID
* @param id Member ID
@@ -106,7 +46,7 @@ export default (client: Client) =>
static async fetch(
id: MemberCompositeKey
): Promise<ServerMember | undefined> {
const channel = ServerMember.get(id);
const channel = ServerMember.#collection.get(key(id));
if (channel) return channel;
const data = await client.api.get(
@@ -118,13 +58,22 @@ export default (client: Client) =>
readonly id: MemberCompositeKey;
/**
* Construct Member
* @param id Member ID
* Construct
* @param id Id
* @param data Data
*/
constructor(id: MemberCompositeKey, data?: ApiMember) {
ServerMember.#storage.hydrate(key(id), "serverMember", data);
ServerMember.#objects.set(key(id), this);
this.id = id;
ServerMember.#collection.create(key(id), "serverMember", this, data);
}
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: MemberCompositeKey, data?: ApiMember) {
return client.serverMembers.get(key(id)) ?? new ServerMember(id, data);
}
/**
@@ -145,35 +94,35 @@ export default (client: Client) =>
* When this user joined the server
*/
get joinedAt() {
return ServerMember.#storage.get(key(this.id)).joinedAt;
return ServerMember.#get(key(this.id)).joinedAt;
}
/**
* Nickname
*/
get nickname() {
return ServerMember.#storage.get(key(this.id)).nickname;
return ServerMember.#get(key(this.id)).nickname;
}
/**
* Avatar
*/
get avatar() {
return ServerMember.#storage.get(key(this.id)).avatar;
return ServerMember.#get(key(this.id)).avatar;
}
/**
* List of role IDs
*/
get roles() {
return ServerMember.#storage.get(key(this.id)).roles;
return ServerMember.#get(key(this.id)).roles;
}
/**
* Time at which timeout expires
*/
get timeout() {
return ServerMember.#storage.get(key(this.id)).timeout;
return ServerMember.#get(key(this.id)).timeout;
}
/**
+40 -84
View File
@@ -1,94 +1,41 @@
import { ReactiveMap } from "@solid-primitives/map";
import { SetStoreFunction } from "solid-js/store";
import type { User as ApiUser } from "revolt-api";
import { decodeTime } from "ulid";
import { Client, FileArgs } from "../Client";
import { StoreCollection } from "../collections/Collection";
import { HydratedUser } from "../hydration/user";
import { U32_MAX, UserPermission } from "../permissions/definitions";
import { ObjectStorage } from "../storage/ObjectStorage";
export default (client: Client) =>
export default (
client: Client,
collection: StoreCollection<unknown, unknown>
) =>
/**
* User Class
*/
class User {
static #storage = new ObjectStorage<HydratedUser>();
static #collection: StoreCollection<
InstanceType<typeof this>,
HydratedUser
>;
static #set: SetStoreFunction<Record<string, HydratedUser>>;
static #get: (id: string) => HydratedUser;
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): InstanceType<typeof this> | undefined {
return this.#objects.get(id);
static {
User.#collection = collection as never;
User.#set = collection.updateUnderlyingObject as never;
User.#get = collection.getUnderlyingObject as never;
}
/**
* Number of stored objects
* @returns Size
*/
static size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
static keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
static values() {
return this.#objects.values();
}
/**
* List of values in the map
* @returns List
*/
static toList() {
return [...this.#objects.values()];
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
static entries() {
return this.#objects.entries();
}
/**
* Execute a provided function over each key, value pair in the map
* @param cb Callback for each pair
* @returns Iterable
*/
static forEach(
cb: (
value: InstanceType<typeof this>,
key: string,
map: ReactiveMap<string, InstanceType<typeof this>>
) => void
) {
return this.#objects.forEach(cb);
}
// * End Object Map Definition
/**
* Fetch user by ID
* @param id ID
* @returns User
*/
static async fetch(id: string): Promise<User | undefined> {
const user = this.get(id);
const user = this.#collection.get(id);
if (user) return user;
const data = await client.api.get(`/users/${id as ""}`);
@@ -98,13 +45,22 @@ export default (client: Client) =>
readonly id: string;
/**
* Construct User
* @param id User Id
* Construct
* @param id Id
* @param data Data
*/
constructor(id: string, data?: ApiUser) {
this.id = id;
User.#storage.hydrate(id, "user", data);
User.#objects.set(id, this);
User.#collection.create(id, "user", this, data);
}
/**
* Get or create
* @param id Id
* @param data Data
*/
static new(id: string, data?: ApiUser) {
return client.users.get(id) ?? new User(id, data);
}
/**
@@ -118,63 +74,63 @@ export default (client: Client) =>
* Username
*/
get username() {
return User.#storage.get(this.id).username;
return User.#get(this.id).username;
}
/**
* Avatar
*/
get avatar() {
return User.#storage.get(this.id).avatar;
return User.#get(this.id).avatar;
}
/**
* Badges
*/
get badges() {
return User.#storage.get(this.id).badges;
return User.#get(this.id).badges;
}
/**
* User Status
*/
get status() {
return User.#storage.get(this.id).status;
return User.#get(this.id).status;
}
/**
* Relationship with user
*/
get relationship() {
return User.#storage.get(this.id).relationship;
return User.#get(this.id).relationship;
}
/**
* Whether the user is online
*/
get online() {
return User.#storage.get(this.id).online;
return User.#get(this.id).online;
}
/**
* Whether the user is privileged
*/
get privileged() {
return User.#storage.get(this.id).privileged;
return User.#get(this.id).privileged;
}
/**
* Flags
*/
get flags() {
return User.#storage.get(this.id).flags;
return User.#get(this.id).flags;
}
/**
* Bot information
*/
get bot() {
return User.#storage.get(this.id).bot;
return User.#get(this.id).bot;
}
/**
+170
View File
@@ -0,0 +1,170 @@
import { SetStoreFunction } from "solid-js/store";
import { ReactiveMap } from "@solid-primitives/map";
import { Hydrators, hydrate } from "../hydration";
import { ObjectStorage } from "../storage/ObjectStorage";
/**
* Abstract Collection type
*/
export abstract class Collection<T> {
/**
* Get an existing object
* @param id Id
* @returns Object
*/
abstract get(id: string): T | undefined;
/**
* Check whether an id exists in the Collection
* @param id Id
* @returns Whether it exists
*/
abstract has(id: string): boolean;
/**
* Delete an 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<string>;
/**
* Iterable of values in the map
* @returns Iterable
*/
abstract values(): IterableIterator<T>;
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
abstract entries(): IterableIterator<[string, T]>;
/**
* List of values in the map
* @returns List
*/
toList() {
return [...this.values()];
}
/**
* 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: ReactiveMap<string, T>) => void
): void;
}
/**
* Collection backed by a Solid.js Store
*/
export abstract class StoreCollection<T, V> extends Collection<T> {
#storage = new ObjectStorage<V>();
#objects = new ReactiveMap<string, T>();
readonly getUnderlyingObject: (id: string) => V;
readonly updateUnderlyingObject: SetStoreFunction<Record<string, V>>;
/**
* Construct store backed collection
*/
constructor() {
super();
this.getUnderlyingObject = this.#storage.get;
this.updateUnderlyingObject = this.#storage.set;
}
/**
* Get an existing object
* @param id Id
* @returns Object
*/
get(id: string): T | undefined {
return this.#objects.get(id);
}
/**
* Check whether an id exists in the Collection
* @param id Id
* @returns Whether it exists
*/
has(id: string) {
return this.#objects.has(id);
}
/**
* Delete an object
* @param id Id
*/
delete(id: string): void {
this.#objects.delete(id);
this.updateUnderlyingObject(id, undefined as never);
}
/**
* Create a new instance of an object
* @param id Id
* @param type Type
* @param instance Instance
* @param data Data
*/
create(id: string, type: keyof Hydrators, instance: T, data?: unknown) {
this.#storage.hydrate(id, type, data);
this.#objects.set(id, instance);
}
/**
* Number of stored objects
* @returns Size
*/
size() {
return this.#objects.size;
}
/**
* Iterable of keys in the map
* @returns Iterable
*/
keys() {
return this.#objects.keys();
}
/**
* Iterable of values in the map
* @returns Iterable
*/
values() {
return this.#objects.values();
}
/**
* Iterable of key, value pairs in the map
* @returns Iterable
*/
entries() {
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: ReactiveMap<string, T>) => void) {
return this.#objects.forEach(cb);
}
}
+31
View File
@@ -0,0 +1,31 @@
import { MemberCompositeKey } from "revolt-api";
import { Channel, Emoji, Message, Server, ServerMember, User } from "..";
import { HydratedChannel } from "../hydration/channel";
import { HydratedEmoji } from "../hydration/emoji";
import { HydratedMessage } from "../hydration/message";
import { HydratedServer } from "../hydration/server";
import { HydratedServerMember } from "../hydration/serverMember";
import { HydratedUser } from "../hydration/user";
import { StoreCollection } from "./Collection";
export class ChannelCollection extends StoreCollection<
Channel,
HydratedChannel
> {}
export class EmojiCollection extends StoreCollection<Emoji, HydratedEmoji> {}
export class MessageCollection extends StoreCollection<
Message,
HydratedMessage
> {}
export class ServerCollection extends StoreCollection<Server, HydratedServer> {}
export class UserCollection extends StoreCollection<User, HydratedUser> {}
export class ServerMemberCollection extends StoreCollection<
ServerMember,
HydratedServerMember
> {
getByKey(id: MemberCompositeKey) {
return super.get(id.server + id.user);
}
}
+1
View File
@@ -7,6 +7,7 @@ export type {
ServerMember,
User,
Session,
Message,
} from "./Client";
export * from "./lib/regex";
+3 -3
View File
@@ -40,14 +40,14 @@ export function calculatePermission(
return Permission.GrantAllSafe;
}
if (target instanceof client.servers) {
if (target instanceof client.Server) {
// 1. Check if owner.
if (target.owner === user?.id) {
return Permission.GrantAllSafe;
} else {
// 2. Get ServerMember.
const member = options?.member ??
client.serverMembers.get({
client.serverMembers.getByKey({
user: user!.id,
server: target.id,
}) ?? { roles: null, timeout: null };
@@ -113,7 +113,7 @@ export function calculatePermission(
} else {
// 4. Get ServerMember.
const member = options?.member ??
client.serverMembers.get({
client.serverMembers.getByKey({
user: user!.id,
server: server.id,
}) ?? { roles: null, timeout: null };
+1
View File
@@ -16,6 +16,7 @@ export class ObjectStorage<T> {
const [store, setStore] = createStore({});
this.store = store as never;
this.set = setStore;
this.get = this.get.bind(this);
}
/**