feat: work towards spec compliance

This commit is contained in:
Paul Makles
2023-04-08 13:22:11 +01:00
parent 200a3e5fa6
commit 8ef5806d03
13 changed files with 712 additions and 183 deletions
+4 -1
View File
@@ -1,7 +1,9 @@
{
"name": "revolt.js",
"version": "7.0.0-beta.0",
"main": "lib/index.js",
"main": "lib/cjs/index.js",
"module": "src/index.ts",
"types": "src/index.ts",
"repository": "https://github.com/revoltchat/revolt.js",
"author": "Paul Makles <insrt.uk>",
"license": "MIT",
@@ -20,6 +22,7 @@
"dependencies": {
"@solid-primitives/map": "^0.4.3",
"@solid-primitives/set": "^0.4.3",
"eventemitter3": "^5.0.0",
"isomorphic-ws": "^5.0.0",
"revolt-api": "^0.5.17",
"solid-js": "^1.7.2",
+101 -21
View File
@@ -1,8 +1,11 @@
import { API } from "revolt-api";
import { Accessor, Setter, createSignal } from "solid-js";
import { API, Metadata } from "revolt-api";
import type { DataLogin, RevoltConfig } from "revolt-api";
import channelClassFactory from "./classes/Channel";
import emojiClassFactory from "./classes/Emoji";
import messageClassFactory from "./classes/Message";
import serverClassFactory from "./classes/Server";
import serverMemberClassFactory from "./classes/ServerMember";
import userClassFactory from "./classes/User";
@@ -11,28 +14,24 @@ import { EventClient, createEventClient } from "./events/client";
// eslint-disable-next-line
type Obj<T extends (...args: any) => any> = InstanceType<ReturnType<T>>;
export type Emoji = Obj<typeof emojiClassFactory>;
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 Session = { token: string } | string;
export type Session = { token: string; user_id: string } | string;
/**
* Revolt.js Client
*/
export class Client {
#Emoji = emojiClassFactory(this);
#Channel = channelClassFactory(this);
#User = userClassFactory(this);
#Server = serverClassFactory(this);
#ServerMember = serverMemberClassFactory(this);
readonly emojis = this.#Emoji;
readonly channels = this.#Channel;
readonly users = this.#User;
readonly servers = this.#Server;
readonly serverMembers = this.#ServerMember;
readonly channels;
readonly emojis;
readonly messages;
readonly users;
readonly servers;
readonly serverMembers;
readonly api: API;
readonly baseURL: string;
@@ -40,17 +39,32 @@ export class Client {
configuration: RevoltConfig | undefined;
session: Session | undefined;
user: User | undefined;
readonly ready: Accessor<boolean>;
#setReady: Setter<boolean>;
/**
* Create Revolt.js Client
*/
constructor(baseURL: string) {
this.baseURL = baseURL;
constructor(baseURL?: string) {
this.baseURL = baseURL ?? "https://api.revolt.chat";
this.api = new API({
baseURL,
});
this.events = createEventClient(1);
const [ready, setReady] = createSignal(false);
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);
}
/**
@@ -62,27 +76,31 @@ export class Client {
if (event.type === "Ready") {
console.time("load users");
for (const user of event.users) {
new this.#User(user._id, user);
const u = new this.users(user._id, user);
if (u.relationship === "User") {
this.user = u;
}
}
console.timeEnd("load users");
console.time("load servers");
for (const server of event.servers) {
new this.#Server(server._id, server);
new this.servers(server._id, server);
}
console.timeEnd("load servers");
console.time("load memberships");
for (const member of event.members) {
new this.#ServerMember(member._id, member);
new this.serverMembers(member._id, member);
}
console.timeEnd("load memberships");
console.time("load channels");
for (const channel of event.channels) {
new this.#Channel(channel._id, channel);
new this.channels(channel._id, channel);
}
console.timeEnd("load channels");
console.time("load emojis");
for (const emoji of event.emojis) {
new this.#Emoji(emoji._id, emoji);
new this.emojis(emoji._id, emoji);
}
console.timeEnd("load emojis");
@@ -100,6 +118,8 @@ export class Client {
this.serverMembers.get({ server: lounge.id, user: lounge.owner!.id })
?.joinedAt
);
this.#setReady(true);
}
});
@@ -171,4 +191,64 @@ export class Client {
this.#updateHeaders();
this.connect();
}
/**
* Generates a URL to a given file with given options.
* @param attachment Partial of attachment object
* @param options Optional query parameters to modify object
* @param allowAnimation Returns GIF if applicable, no operations occur on image
* @param fallback Fallback URL
* @returns Generated URL or nothing
*/
generateFileURL(
attachment?: {
tag: string;
_id: string;
content_type?: string;
metadata?: Metadata;
},
...args: FileArgs
) {
const [options, allowAnimation, fallback] = args;
const autumn = this.configuration?.features.autumn;
if (!autumn?.enabled) return fallback;
if (!attachment) return fallback;
const { tag, _id, content_type, metadata } = attachment;
// TODO: server-side
if (metadata?.type === "Image") {
if (
Math.min(metadata.width, metadata.height) <= 0 ||
(content_type === "image/gif" &&
Math.max(metadata.width, metadata.height) >= 4096)
)
return fallback;
}
let query = "";
if (options) {
if (!allowAnimation || content_type !== "image/gif") {
query =
"?" +
Object.keys(options)
.map((k) => `${k}=${options[k as keyof FileArgs[0]]}`)
.join("&");
}
}
return `${autumn.url}/${tag}/${_id}${query}`;
}
}
export type FileArgs = [
options?: {
max_side?: number;
size?: number;
width?: number;
height?: number;
},
allowAnimation?: boolean,
fallback?: string
];
+168 -10
View File
@@ -1,7 +1,9 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Channel as ApiChannel } from "revolt-api";
import { decodeTime } from "ulid";
import { Client } from "../Client";
import { Client, FileArgs } from "../Client";
import { hydrate } from "../hydration";
import { HydratedChannel } from "../hydration/channel";
import { ObjectStorage } from "../storage/ObjectStorage";
@@ -11,17 +13,86 @@ export default (client: Client) =>
*/
class Channel {
static #storage = new ObjectStorage<HydratedChannel>();
static #objects: Record<string, Channel> = {};
static {
client.events.on("event", (event) => {
switch (event.type) {
case "ChannelUpdate": {
this.#storage.set(event.id, hydrate("channel", event.data));
break;
}
}
});
}
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing Channel
* @param id Channel ID
* @returns Channel
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): Channel | undefined {
return Channel.#objects[id];
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
@@ -42,9 +113,13 @@ export default (client: Client) =>
* @param id Channel Id
*/
constructor(id: string, data?: ApiChannel) {
Channel.#storage.hydrate(id, "channel", data);
Channel.#objects[id] = this;
this.id = id;
Channel.#storage.hydrate(id, "channel", data);
Channel.#objects.set(id, this);
}
updateSomething() {
Channel.#storage.set(this.id, "name", "troling");
}
/**
@@ -98,6 +173,22 @@ export default (client: Client) =>
.recipientIds.map((id) => client.users.get(id)!);
}
/**
* Find recipient of this DM
*/
get recipient() {
return this.type === "DirectMessage"
? this.recipients.find((user) => user.id !== client.user!.id)
: undefined;
}
/**
* User ID
*/
get userId() {
return Channel.#storage.get(this.id).userId!;
}
/**
* User this channel belongs to
*/
@@ -105,6 +196,13 @@ export default (client: Client) =>
return client.users.get(Channel.#storage.get(this.id).userId!);
}
/**
* Owner ID
*/
get ownerId() {
return Channel.#storage.get(this.id).ownerId!;
}
/**
* Owner of the group
*/
@@ -112,11 +210,18 @@ export default (client: Client) =>
return client.users.get(Channel.#storage.get(this.id).ownerId!);
}
/**
* Server ID
*/
get serverId() {
return Channel.#storage.get(this.id).serverId!;
}
/**
* Server this channel is in
*/
get server() {
return client.users.get(Channel.#storage.get(this.id).serverId!);
return client.servers.get(Channel.#storage.get(this.id).serverId!);
}
/**
@@ -154,5 +259,58 @@ export default (client: Client) =>
return Channel.#storage.get(this.id).lastMessageId;
}
/**
* Time when the last message was sent
*/
get lastMessageAt() {
return this.lastMessageId
? new Date(decodeTime(this.lastMessageId))
: undefined;
}
/**
* Time when the channel was last updated (either created or a message was sent)
*/
get updatedAt() {
return this.lastMessageAt ?? this.createdAt;
}
// TODO: lastMessage
get unread() {
return false;
}
get mentions() {
return [];
}
/**
* URL to the channel icon
*/
get iconURL() {
return client.generateFileURL(this.icon ?? this.recipient?.avatar, {
max_side: 256,
});
}
/**
* URL to a small variant of the channel icon
*/
get smallIconURL() {
return client.generateFileURL(this.icon ?? this.recipient?.avatar, {
max_side: 64,
});
}
/**
* URL to the animated channel icon
*/
get animatedIconURL() {
return client.generateFileURL(
this.icon ?? this.recipient?.avatar,
{ max_side: 256 },
true
);
}
};
+67 -8
View File
@@ -1,3 +1,4 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Emoji as ApiEmoji } from "revolt-api";
import { decodeTime } from "ulid";
@@ -11,17 +12,75 @@ export default (client: Client) =>
*/
class Emoji {
static #storage = new ObjectStorage<HydratedEmoji>();
static #objects: Record<string, Emoji> = {};
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing Emoji
* @param id Emoji ID
* @returns Emoji
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): Emoji | undefined {
return Emoji.#objects[id];
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 emoji by ID
* @param id ID
@@ -42,9 +101,9 @@ export default (client: Client) =>
* @param id Emoji Id
*/
constructor(id: string, data?: ApiEmoji) {
Emoji.#storage.hydrate(id, "emoji", data);
Emoji.#objects[id] = this;
this.id = id;
Emoji.#storage.hydrate(id, "emoji", data);
Emoji.#objects.set(id, this);
}
/**
+81 -8
View File
@@ -1,3 +1,4 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Message as ApiMessage } from "revolt-api";
import { decodeTime } from "ulid";
@@ -11,17 +12,75 @@ export default (client: Client) =>
*/
class Message {
static #storage = new ObjectStorage<HydratedMessage>();
static #objects: Record<string, Message> = {};
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing Message
* @param id Message ID
* @returns Message
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): Message | undefined {
return Message.#objects[id];
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 message by ID
* @param channelId Channel ID
@@ -48,9 +107,9 @@ export default (client: Client) =>
* @param id Message Id
*/
constructor(id: string, data?: ApiMessage) {
Message.#storage.hydrate(id, "message", data);
Message.#objects[id] = this;
this.id = id;
Message.#storage.hydrate(id, "message", data);
Message.#objects.set(id, this);
}
/**
@@ -67,6 +126,13 @@ export default (client: Client) =>
return Message.#storage.get(this.id).nonce;
}
/**
* Id of channel this message was sent in
*/
get channelId() {
return Message.#storage.get(this.id).channelId;
}
/**
* Channel this message was sent in
*/
@@ -74,6 +140,13 @@ export default (client: Client) =>
return client.channels.get(Message.#storage.get(this.id).channelId);
}
/**
* Id of user this message was sent by
*/
get authorId() {
return Message.#storage.get(this.id).authorId;
}
/**
* User this message was sent by
*/
+98 -8
View File
@@ -1,3 +1,4 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Server as ApiServer, Category } from "revolt-api";
import { decodeTime } from "ulid";
@@ -11,17 +12,75 @@ export default (client: Client) =>
*/
class Server {
static #storage = new ObjectStorage<HydratedServer>();
static #objects: Record<string, Server> = {};
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing Server
* @param id Server ID
* @returns Server
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): Server | undefined {
return Server.#objects[id];
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 server by ID
* @param id ID
@@ -42,9 +101,9 @@ export default (client: Client) =>
* @param id Server Id
*/
constructor(id: string, data?: ApiServer) {
Server.#storage.hydrate(id, "server", data);
Server.#objects[id] = this;
this.id = id;
Server.#storage.hydrate(id, "server", data);
Server.#objects.set(id, this);
}
/**
@@ -232,4 +291,35 @@ export default (client: Client) =>
.sort((a, b) => (a.rank || 0) - (b.rank || 0))
: [];
}
get unread() {
return false;
}
get mentions() {
return [];
}
/**
* URL to the server's icon
*/
get iconURL() {
return client.generateFileURL(this.icon, { max_side: 256 });
}
/**
* URL to the server's animated icon
*/
get animatedIconURL() {
return client.generateFileURL(this.icon, { max_side: 256 }, true);
}
/**
* URL to the server's banner
*/
get bannerURL() {
return client.generateFileURL(this.banner, {
max_side: 256,
});
}
};
+62 -3
View File
@@ -1,3 +1,4 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { Member as ApiMember, MemberCompositeKey } from "revolt-api";
import { Client } from "../Client";
@@ -19,7 +20,9 @@ export default (client: Client) =>
*/
class ServerMember {
static #storage = new ObjectStorage<HydratedServerMember>();
static #objects: Record<string, ServerMember> = {};
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Deterministic conversion of member composite key to string ID
@@ -34,9 +37,65 @@ export default (client: Client) =>
* @returns Member
*/
static get(id: MemberCompositeKey): ServerMember | undefined {
return ServerMember.#objects[key(id)];
return ServerMember.#objects.get(key(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 member by ID
* @param id Member ID
@@ -62,7 +121,7 @@ export default (client: Client) =>
*/
constructor(id: MemberCompositeKey, data?: ApiMember) {
ServerMember.#storage.hydrate(key(id), "serverMember", data);
ServerMember.#objects[key(id)] = this;
ServerMember.#objects.set(key(id), this);
this.id = id;
}
+101 -10
View File
@@ -1,7 +1,8 @@
import { ReactiveMap } from "@solid-primitives/map";
import type { User as ApiUser } from "revolt-api";
import { decodeTime } from "ulid";
import { Client } from "../Client";
import { Client, FileArgs } from "../Client";
import { HydratedUser } from "../hydration/user";
import { ObjectStorage } from "../storage/ObjectStorage";
@@ -11,24 +12,82 @@ export default (client: Client) =>
*/
class User {
static #storage = new ObjectStorage<HydratedUser>();
static #objects: Record<string, User> = {};
// * Object Map Definition
static #objects = new ReactiveMap<string, InstanceType<typeof this>>();
/**
* Get an existing User
* @param id User ID
* @returns User
* Get an existing object
* @param id ID
* @returns Object
*/
static get(id: string): User | undefined {
return User.#objects[id];
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 user by ID
* @param id ID
* @returns User
*/
static async fetch(id: string): Promise<User | undefined> {
const user = User.get(id);
const user = this.get(id);
if (user) return user;
const data = await client.api.get(`/users/${id as ""}`);
@@ -42,9 +101,9 @@ export default (client: Client) =>
* @param id User Id
*/
constructor(id: string, data?: ApiUser) {
User.#storage.hydrate(id, "user", data);
User.#objects[id] = this;
this.id = id;
User.#storage.hydrate(id, "user", data);
User.#objects.set(id, this);
}
/**
@@ -116,4 +175,36 @@ export default (client: Client) =>
get bot() {
return User.#storage.get(this.id).bot;
}
/**
* URL to the user's default avatar
*/
get defaultAvatarURL() {
return `${client.baseURL}/users/${this.id}/default_avatar`;
}
/**
* URL to the user's avatar
*/
get avatarURL() {
return this.#generateAvatarURL({ max_side: 256 });
}
/**
* URL to the user's animated avatar
*/
get animatedAvatarURL() {
return this.#generateAvatarURL({ max_side: 256 }, true);
}
/**
* Generate avatar URL
* @param args File args
* @returns URL
*/
#generateAvatarURL(...args: FileArgs) {
return (
client.generateFileURL(this.avatar, ...args) ?? this.defaultAvatarURL
);
}
};
+6 -6
View File
@@ -1,6 +1,6 @@
import { Accessor, Setter, createSignal } from "solid-js";
import EventEmitter from "events";
import EventEmitter from "eventemitter3";
import WebSocket from "isomorphic-ws";
import type TypedEmitter from "typed-emitter";
@@ -37,7 +37,7 @@ class Client<
#heartbeatInterval: number;
#pongTimeout: number;
#state: Accessor<ConnectionState>;
readonly state: Accessor<ConnectionState>;
#setStateSetter: Setter<ConnectionState>;
#socket: WebSocket | undefined;
@@ -65,7 +65,7 @@ class Client<
this.#pongTimeout = pongTimeout;
const [state, setState] = createSignal(ConnectionState.Idle);
this.#state = state;
this.state = state;
this.#setStateSetter = setState;
this.disconnect = this.disconnect.bind(this);
@@ -161,7 +161,7 @@ class Client<
return;
}
switch (this.#state()) {
switch (this.state()) {
case ConnectionState.Connecting:
if (event.type === "Authenticated") {
// no-op
@@ -182,7 +182,7 @@ class Client<
default:
throw `Unreachable code. Received ${
event.type
} in state ${this.#state()}.`;
} in state ${this.state()}.`;
}
}
}
@@ -217,5 +217,5 @@ export function createEventClient<
transportFormat,
heartbeatInterval,
pongTimeout
) as EventClient<T>;
) as never as EventClient<T>;
}
+1 -1
View File
@@ -78,7 +78,7 @@ type ExtractOutput<T> = T extends Hydrate<any, infer O> ? O : never;
*/
export function hydrate<T extends keyof Hydrators>(
type: T,
input: ExtractInput<Hydrators[T]>
input: Partial<ExtractInput<Hydrators[T]>>
) {
return hydrateInternal(hydrators[type] as never, input) as ExtractOutput<
Hydrators[T]
+4 -2
View File
@@ -1,5 +1,6 @@
export {
Client,
export { Client } from "./Client";
export type {
Emoji,
Channel,
Server,
@@ -8,4 +9,5 @@ export {
Session,
} from "./Client";
export * from "./lib/regex";
export * as API from "revolt-api";
+9
View File
@@ -0,0 +1,9 @@
/**
* Regular expression for mentions.
*/
export const RE_MENTIONS = /<@([A-z0-9]{26})>/g;
/**
* Regular expression for spoilers.
*/
export const RE_SPOILER = /!!.+!!/g;
+10 -105
View File
@@ -1,109 +1,14 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
"declarationMap": true /* Create sourcemaps for d.ts files. */,
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./lib" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"target": "es2016",
"module": "commonjs",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"outDir": "./lib/cjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}