diff --git a/package-lock.json b/package-lock.json
index fff37683..4e40ede6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,8 @@
"dependencies": {
"jsonwebtoken": "^8.5.1",
"lambert-db": "^1.1.7",
- "missing-native-js-functions": "^1.2.2"
+ "missing-native-js-functions": "^1.2.2",
+ "mongodb": "^3.6.4"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.0",
diff --git a/package.json b/package.json
index be9e0907..4e2b4c1e 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
"dependencies": {
"jsonwebtoken": "^8.5.1",
"lambert-db": "^1.1.7",
- "missing-native-js-functions": "^1.2.2"
+ "missing-native-js-functions": "^1.2.2",
+ "mongodb": "^3.6.4"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.0",
diff --git a/src/models/Activity.ts b/src/models/Activity.ts
index e9e4224f..ee7e87cd 100644
--- a/src/models/Activity.ts
+++ b/src/models/Activity.ts
@@ -1,5 +1,6 @@
import { User } from "..";
import { ClientStatus, Status } from "./Status";
+import { Schema, model, Types, Document } from "mongoose";
export interface Presence {
user: User;
@@ -45,6 +46,44 @@ export interface Activity {
flags?: bigint;
}
+export const Activity = {
+ name: String,
+ type: Number,
+ url: String,
+ created_at: Number,
+ timestamps: [
+ {
+ start: Number,
+ end: Number,
+ },
+ ],
+ application_id: Types.Long,
+ details: String,
+ state: String,
+ emoji: {
+ name: String,
+ id: Types.Long,
+ amimated: Boolean,
+ },
+ party: {
+ id: String,
+ size: [Number, Number],
+ },
+ assets: {
+ large_image: String,
+ large_text: String,
+ small_image: String,
+ small_text: String,
+ },
+ secrets: {
+ join: String,
+ spectate: String,
+ match: String,
+ },
+ instance: Boolean,
+ flags: Types.Long,
+};
+
export enum ActivityType {
GAME = 0,
STREAMING = 1,
diff --git a/src/models/Channel.ts b/src/models/Channel.ts
index 068f6b67..36f5e83d 100644
--- a/src/models/Channel.ts
+++ b/src/models/Channel.ts
@@ -1,19 +1,44 @@
+import { Schema, model, Types, Document } from "mongoose";
+
+export interface ChannelDocument extends Channel, DMChannel, TextChannel, VoiceChannel, Document {
+ id: bigint;
+}
+
+export const ChannelSchema = new Schema({
+ id: Types.Long,
+ created_at: { type: Schema.Types.Date, required: true },
+ name: { type: String, required: true },
+ type: { type: Number, required: true },
+ guild_id: Types.Long,
+ owner_id: Types.Long,
+ parent_id: Types.Long,
+ recipients: [Types.Long],
+ position: Number,
+ last_message_id: Types.Long,
+ last_pin_timestamp: Date,
+ nsfw: Boolean,
+ rate_limit_per_user: Number,
+ topic: String,
+ permission_overwrites: [
+ {
+ allow: Types.Long,
+ deny: Types.Long,
+ id: Types.Long,
+ type: Number,
+ },
+ ],
+});
+
+export const ChannelModel = model<ChannelDocument>("Channel", ChannelSchema, "channels");
+
export interface Channel {
id: bigint;
created_at: number;
name: string;
type: number;
- read_state: ReadState[];
-}
-
-export interface ReadState {
- last_message_id: bigint;
- last_pin_timestamp: number;
- mention_count: number;
}
export interface TextBasedChannel {
- messages: any[];
last_message_id?: bigint;
last_pin_timestamp?: number;
}
diff --git a/src/models/Emoji.ts b/src/models/Emoji.ts
index 1facc252..bbed9323 100644
--- a/src/models/Emoji.ts
+++ b/src/models/Emoji.ts
@@ -1,12 +1,27 @@
-export interface Emoji {
- allNamesString: string; // e.g. :thonk:
+import { Schema, model, Types, Document } from "mongoose";
+
+export interface Emoji extends Document {
+ id: bigint;
animated: boolean;
available: boolean;
- guildId: bigint;
- id: bigint;
+ guild_id: bigint;
managed: boolean;
name: string;
require_colons: boolean;
url: string;
- roles: [];
+ roles: bigint[]; // roles this emoji is whitelisted to
}
+
+export const EmojiSchema = new Schema({
+ id: Types.Long,
+ animated: Boolean,
+ available: Boolean,
+ guild_id: Types.Long,
+ managed: Boolean,
+ name: String,
+ require_colons: Boolean,
+ url: String,
+ roles: [Types.Long],
+});
+
+export const EmojiModel = model<Emoji>("Emoji", EmojiSchema, "emojis");
diff --git a/src/models/Event.ts b/src/models/Event.ts
index 4925c7ca..e8dfe11e 100644
--- a/src/models/Event.ts
+++ b/src/models/Event.ts
@@ -10,8 +10,9 @@ import { Message, PartialEmoji } from "./Message";
import { VoiceState } from "./VoiceState";
import { ApplicationCommand } from "./Application";
import { Interaction } from "./Interaction";
+import { Schema, model, Types, Document } from "mongoose";
-export interface Event {
+export interface Event extends Document {
guild_id?: bigint;
user_id?: bigint;
channel_id?: bigint;
@@ -20,6 +21,17 @@ export interface Event {
data?: any;
}
+export const EventSchema = new Schema({
+ guild_id: Types.Long,
+ user_id: Types.Long,
+ channel_id: Types.Long,
+ created_at: { type: Number, required: true },
+ event: { type: String, required: true },
+ data: Object,
+});
+
+export const EventModel = model<Event>("Event", EventSchema, "events");
+
// ! Custom Events that shouldn't get sent to the client but processed by the server
export interface InvalidatedEvent extends Event {
@@ -120,7 +132,10 @@ export interface GuildUpdateEvent extends Event {
export interface GuildDeleteEvent extends Event {
event: "GUILD_DELETE";
- data: Guild;
+ data: {
+ id: bigint;
+ unavailable?: boolean;
+ };
}
export interface GuildBanAddEvent extends Event {
diff --git a/src/models/Guild.ts b/src/models/Guild.ts
index 348f8c7c..c9a55301 100644
--- a/src/models/Guild.ts
+++ b/src/models/Guild.ts
@@ -1,9 +1,7 @@
-import { GuildChannel } from "./Channel";
-import { Emoji } from "./Emoji";
-import { Member } from "./Member";
-import { Role } from "./Role";
+import { Schema, model, Types, Document } from "mongoose";
-export interface Guild {
+export interface Guild extends Document {
+ id: bigint;
afk_channel_id?: bigint;
afk_timeout?: number;
application_id?: bigint;
@@ -11,12 +9,9 @@ export interface Guild {
default_message_notifications?: number;
description?: string;
discovery_splash?: string;
- emojis: Emoji[];
explicit_content_filter?: number;
features: string[];
icon?: string;
- id: bigint;
- // joined_at?: number; \n // owner?: boolean; // ! member specific should be removed
large?: boolean;
max_members?: number; // e.g. default 100.000
max_presences?: number;
@@ -24,8 +19,9 @@ export interface Guild {
member_count?: number;
presence_count?: number; // users online
// members?: Member[]; // * Members are stored in a seperate collection
- // roles: Role[]; // * Role are stroed in a seperate collection
- // channels: GuildChannel[]; // * Channels are stroed in a seperate collection
+ // roles: Role[]; // * Role are stored in a seperate collection
+ // channels: GuildChannel[]; // * Channels are stored in a seperate collection
+ // emojis: Emoji[]; // * Emojis are stored in a seperate collection
mfa_level?: number;
name: string;
owner_id: bigint;
@@ -46,3 +42,44 @@ export interface Guild {
widget_channel_id?: bigint;
widget_enabled?: boolean;
}
+
+export const GuildSchema = new Schema({
+ afk_channel_id: Types.Long,
+ afk_timeout: Number,
+ application_id: Types.Long,
+ banner: String,
+ default_message_notifications: Number,
+ description: String,
+ discovery_splash: String,
+ explicit_content_filter: Number,
+ features: { type: [String], default: [] },
+ icon: String,
+ id: { type: Types.Long, required: true },
+ large: Boolean,
+ max_members: { type: Number, default: 100000 },
+ max_presences: Number,
+ max_video_channel_users: { type: Number, default: 25 },
+ member_count: Number,
+ presence_count: Number,
+ mfa_level: Number,
+ name: { type: String, required: true },
+ owner_id: { type: Types.Long, required: true },
+ preferred_locale: String,
+ premium_subscription_count: Number,
+ premium_tier: Number,
+ public_updates_channel_id: Types.Long,
+ region: String,
+ rules_channel_id: Types.Long,
+ splash: String,
+ system_channel_flags: Number,
+ system_channel_id: Types.Long,
+ unavailable: Boolean,
+ vanity_url_code: String,
+ verification_level: Number,
+ voice_states: { type: [Object], default: [] },
+ welcome_screen: { type: [Object], default: [] },
+ widget_channel_id: Types.Long,
+ widget_enabled: Boolean,
+});
+
+export const GuildModel = model<Guild>("Guild", GuildSchema, "guilds");
diff --git a/src/models/Invite.ts b/src/models/Invite.ts
index df1286f5..b4dbb8bc 100644
--- a/src/models/Invite.ts
+++ b/src/models/Invite.ts
@@ -1,4 +1,6 @@
-export interface Invite {
+import { Schema, model, Types, Document } from "mongoose";
+
+export interface Invite extends Document {
code: string;
temporary: boolean;
uses: number;
@@ -19,7 +21,6 @@ export interface Invite {
name: string;
type: number;
};
-
inviter: {
id: bigint;
username: string;
@@ -34,3 +35,42 @@ export interface Invite {
};
target_user_type: number;
}
+
+export const InviteSchema = new Schema({
+ code: String,
+ temporary: Boolean,
+ uses: Number,
+ max_uses: Number,
+ max_age: Number,
+ created_at: Number,
+ guild: {
+ id: Types.Long,
+ name: String,
+ splash: String,
+ description: String,
+ icon: String,
+ features: Object,
+ verification_level: Number,
+ },
+ channel: {
+ id: Types.Long,
+ name: String,
+ type: Number,
+ },
+
+ inviter: {
+ id: Types.Long,
+ username: String,
+ avatar: String,
+ discriminator: Number,
+ },
+ target_user: {
+ id: Types.Long,
+ username: String,
+ avatar: String,
+ discriminator: Number,
+ },
+ target_user_type: Number,
+});
+
+export const InviteModel = model<Invite>("Invite", InviteSchema, "invites");
diff --git a/src/models/Member.ts b/src/models/Member.ts
index a38a5ca3..d9f34ac8 100644
--- a/src/models/Member.ts
+++ b/src/models/Member.ts
@@ -1,6 +1,7 @@
import { PublicUser } from "./User";
+import { Schema, model, Types, Document } from "mongoose";
-export interface Member {
+export interface Member extends Document {
id: bigint;
nick?: string;
roles: bigint[];
@@ -13,10 +14,6 @@ export interface Member {
settings: UserGuildSettings;
}
-export interface PublicMember extends Omit<Member, "settings" | "id"> {
- user: PublicUser;
-}
-
export interface UserGuildSettings {
channel_overrides: {
channel_id: bigint;
@@ -37,3 +34,43 @@ export interface MuteConfig {
end_time: number;
selected_time_window: number;
}
+
+const MuteConfig = {
+ end_time: Number,
+ selected_time_window: Number,
+};
+
+export const MemberSchema = new Schema({
+ id: Types.Long,
+ nick: String,
+ roles: [Types.Long],
+ joined_at: Number,
+ premium_since: Number,
+ deaf: Boolean,
+ mute: Boolean,
+ pending: Boolean,
+ permissions: Types.Long,
+ settings: {
+ channel_overrides: [
+ {
+ channel_id: Types.Long,
+ message_notifications: Number,
+ mute_config: MuteConfig,
+ muted: Boolean,
+ },
+ ],
+ message_notifications: Number,
+ mobile_push: Boolean,
+ mute_config: MuteConfig,
+ muted: Boolean,
+ suppress_everyone: Boolean,
+ suppress_roles: Boolean,
+ version: Number,
+ },
+});
+
+export const MemberModel = model<Member>("Member", MemberSchema, "members");
+
+export interface PublicMember extends Omit<Member, "settings" | "id"> {
+ user: PublicUser;
+}
diff --git a/src/models/Message.ts b/src/models/Message.ts
index 45aefd34..22569d8f 100644
--- a/src/models/Message.ts
+++ b/src/models/Message.ts
@@ -1,6 +1,7 @@
+import { Schema, model, Types, Document } from "mongoose";
import { ChannelType } from "./Channel";
-export interface Message {
+export interface Message extends Document {
id: bigint;
author_id?: bigint;
webhook_id?: bigint;
@@ -27,7 +28,7 @@ export interface Message {
activity?: {
type: number;
party_id: string;
- }[];
+ };
flags?: bigint;
stickers?: [];
message_reference?: {
@@ -124,3 +125,104 @@ export interface AllowedMentions {
users?: bigint[];
replied_user?: boolean;
}
+
+const Attachment = {
+ id: Types.Long, // attachment id
+ filename: String, // name of file attached
+ size: Number, // size of file in bytes
+ url: String, // source url of file
+ proxy_url: String, // a proxied url of file
+ height: Number, // height of file (if image)
+ width: Number, // width of file (if image)
+};
+
+const EmbedImage = {
+ url: String,
+ proxy_url: String,
+ height: Number,
+ width: Number,
+};
+
+const Reaction = {
+ count: Number,
+ emoji: {
+ id: Types.Long,
+ name: String,
+ animated: Boolean,
+ },
+};
+
+const Embed = {
+ title: String, //title of embed
+ type: String, // type of embed (always "rich" for webhook embeds)
+ description: String, // description of embed
+ url: String, // url of embed
+ timestamp: Number, // timestamp of embed content
+ color: Number, // color code of the embed
+ footer: {
+ text: String,
+ icon_url: String,
+ proxy_icon_url: String,
+ }, // footer object footer information
+ image: EmbedImage, // image object image information
+ thumbnail: EmbedImage, // thumbnail object thumbnail information
+ video: EmbedImage, // video object video information
+ provider: {
+ name: String,
+ url: String,
+ }, // provider object provider information
+ author: {
+ name: String,
+ url: String,
+ icon_url: String,
+ proxy_icon_url: String,
+ }, // author object author information
+ fields: [
+ {
+ name: String,
+ value: String,
+ inline: Boolean,
+ },
+ ],
+};
+
+export const MessageSchema = new Schema({
+ id: Types.Long,
+ author_id: Types.Long,
+ webhook_id: Types.Long,
+ application_id: Types.Long,
+ content: String,
+ timestamp: Number,
+ edited_timestamp: Number,
+ tts: Boolean,
+ mention_everyone: Boolean,
+ mentions: [Types.Long],
+ mention_roles: [Types.Long],
+ mention_channels: [
+ {
+ id: Types.Long,
+ guild_id: Types.Long,
+ type: ChannelType,
+ name: String,
+ },
+ ],
+ attachments: [Attachment],
+ embeds: [Embed],
+ reactions: [Reaction],
+ nonce: Schema.Types.Mixed, // can be a long or a string
+ pinned: Boolean,
+ type: MessageType,
+ activity: {
+ type: Number,
+ party_id: String,
+ },
+ flags: Types.Long,
+ stickers: [],
+ message_reference: {
+ message_id: Types.Long,
+ channel_id: Types.Long,
+ guild_id: Types.Long,
+ },
+});
+
+export const MessageModel = model<Message>("Message", MessageSchema, "messages");
diff --git a/src/models/Role.ts b/src/models/Role.ts
index e0f2f863..d35bd57c 100644
--- a/src/models/Role.ts
+++ b/src/models/Role.ts
@@ -1,4 +1,6 @@
-export interface Role {
+import { Schema, model, Types, Document } from "mongoose";
+
+export interface Role extends Document {
id: bigint;
color: number;
hoist: boolean;
@@ -11,3 +13,19 @@ export interface Role {
bot_id?: bigint;
};
}
+
+export const RoleSchema = new Schema({
+ id: Types.Long,
+ color: Number,
+ hoist: Boolean,
+ managed: Boolean,
+ mentionable: Boolean,
+ name: String,
+ permissions: Types.Long,
+ position: Number,
+ tags: {
+ bot_id: Types.Long,
+ },
+});
+
+export const RoleModel = model<Role>("Role", RoleSchema, "roles");
diff --git a/src/models/Status.ts b/src/models/Status.ts
index c4dab586..5a9bf2ca 100644
--- a/src/models/Status.ts
+++ b/src/models/Status.ts
@@ -5,3 +5,9 @@ export interface ClientStatus {
mobile?: string; // e.g. iOS/Android
web?: string; // e.g. browser, bot account
}
+
+export const ClientStatus = {
+ desktop: String,
+ mobile: String,
+ web: String,
+};
diff --git a/src/models/User.ts b/src/models/User.ts
index f591d26e..d79e7e0c 100644
--- a/src/models/User.ts
+++ b/src/models/User.ts
@@ -1,7 +1,8 @@
import { Activity } from "./Activity";
import { ClientStatus, Status } from "./Status";
+import { Schema, model, Types, Document } from "mongoose";
-export interface User {
+export interface User extends Document {
id: bigint;
username: string;
discriminator: string;
@@ -103,3 +104,100 @@ export interface UserSettings {
theme: "dark" | "white"; // dark
timezone_offset: number; // e.g -60
}
+
+export const UserSchema = new Schema({
+ id: Types.Long,
+ username: String,
+ discriminator: String,
+ avatar: String,
+ phone: String,
+ desktop: Boolean,
+ mobile: Boolean,
+ premium: Boolean,
+ premium_type: Number,
+ bot: Boolean,
+ system: Boolean,
+ nsfw_allowed: Boolean,
+ mfa_enabled: Boolean,
+ created_at: Number,
+ verified: Boolean,
+ email: String,
+ flags: Types.Long, // TODO: automatically convert Types.Long to BitField of UserFlags
+ public_flags: Types.Long,
+ hash: String, // hash of the password, salt is saved in password (bcrypt)
+ guilds: [Types.Long], // array of guild ids the user is part of
+ valid_tokens_since: Number, // all tokens with a previous issue date are invalid
+ user_settings: {
+ afk_timeout: Number,
+ allow_accessibility_detection: Boolean,
+ animate_emoji: Boolean,
+ animate_stickers: Number,
+ contact_sync_enabled: Boolean,
+ convert_emoticons: Boolean,
+ custom_status: {
+ emoji_id: Types.Long,
+ emoji_name: String,
+ expires_at: Number,
+ text: String,
+ },
+ default_guilds_restricted: Boolean,
+ detect_platform_accounts: Boolean,
+ developer_mode: Boolean,
+ disable_games_tab: Boolean,
+ enable_tts_command: Boolean,
+ explicit_content_filter: Number,
+ friend_source_flags: { all: Boolean },
+ gif_auto_play: Boolean,
+ // every top guild is displayed as a "folder"
+ guild_folders: [
+ {
+ color: Number,
+ guild_ids: [Types.Long],
+ id: Number,
+ name: String,
+ },
+ ],
+ guild_positions: [Types.Long], // guild ids ordered by position
+ inline_attachment_media: Boolean,
+ inline_embed_media: Boolean,
+ locale: String, // en_US
+ message_display_compact: Boolean,
+ native_phone_integration_enabled: Boolean,
+ render_embeds: Boolean,
+ render_reactions: Boolean,
+ restricted_guilds: [Types.Long],
+ show_current_game: Boolean,
+ status: String,
+ stream_notifications_enabled: Boolean,
+ theme: String, // dark
+ timezone_offset: Number, // e.g -60,
+ },
+ relationships: [
+ {
+ id: Types.Long,
+ nickname: String,
+ type: Number,
+ user_id: Types.Long,
+ },
+ ],
+ connected_accounts: [
+ {
+ access_token: String,
+ friend_sync: Boolean,
+ id: String,
+ name: String,
+ revoked: Boolean,
+ show_activity: Boolean,
+ type: String,
+ verifie: Boolean,
+ visibility: Number,
+ },
+ ],
+ presence: {
+ status: String,
+ activities: [Activity],
+ client_status: ClientStatus,
+ },
+});
+
+export const UserModel = model<User>("User", UserSchema, "users");
diff --git a/src/models/VoiceState.ts b/src/models/VoiceState.ts
index 19a1bf27..8ac5ae19 100644
--- a/src/models/VoiceState.ts
+++ b/src/models/VoiceState.ts
@@ -1,6 +1,7 @@
import { PublicMember } from "./Member";
+import { Schema, model, Types, Document } from "mongoose";
-export interface VoiceState {
+export interface VoiceState extends Document {
guild_id?: bigint;
channel_id: bigint;
user_id: bigint;
@@ -13,3 +14,19 @@ export interface VoiceState {
self_video: boolean;
suppress: boolean; // whether this user is muted by the current user
}
+
+export const VoiceSateSchema = new Schema({
+ guild_id: Types.Long,
+ channel_id: Types.Long,
+ user_id: Types.Long,
+ session_id: String,
+ deaf: Boolean,
+ mute: Boolean,
+ self_deaf: Boolean,
+ self_mute: Boolean,
+ self_stream: Boolean,
+ self_video: Boolean,
+ suppress: Boolean, // whether this user is muted by the current user
+});
+
+export const VoiceStateModel = model<VoiceState>("VoiceState", VoiceSateSchema, "voicestates");
diff --git a/src/util/Config.ts b/src/util/Config.ts
index 5886b268..91ffda01 100644
--- a/src/util/Config.ts
+++ b/src/util/Config.ts
@@ -5,6 +5,7 @@ var Config: ProviderCache;
export default {
init: async function init(opts: DefaultOptions = DefaultOptions) {
+ await db.collection("config").findOne({});
Config = await db.data.config({}).cache();
await Config.init();
await Config.set(opts.merge(Config.cache || {}));
diff --git a/src/util/Database.ts b/src/util/Database.ts
index ed45a9ad..56f53f9a 100644
--- a/src/util/Database.ts
+++ b/src/util/Database.ts
@@ -1,9 +1,89 @@
-import { MongoDatabase } from "lambert-db";
+import "./MongoBigInt";
+import mongoose, { Collection } from "mongoose";
+import { ChangeStream, ChangeEvent, Long } from "mongodb";
+import EventEmitter from "events";
+const uri = process.env.MONGO_URL || "mongodb://localhost:27017/fosscord?readPreference=secondaryPreferred";
-// TODO: load url from config
-const db = new MongoDatabase("mongodb://127.0.0.1:27017/lambert?readPreference=secondaryPreferred", {
- useNewUrlParser: true,
- useUnifiedTopology: false,
-});
+const connection = mongoose.createConnection(uri, { autoIndex: true });
-export default db;
+export default connection;
+
+export interface MongooseCache {
+ on(event: "delete", listener: (id: string) => void): this;
+ on(event: "change", listener: (data: any) => void): this;
+ on(event: "insert", listener: (data: any) => void): this;
+ on(event: "close", listener: () => void): this;
+}
+
+export class MongooseCache extends EventEmitter {
+ public stream: ChangeStream;
+ public data: any;
+
+ constructor(
+ public collection: Collection,
+ public pipeline: Array<Record<string, unknown>>,
+ public opts: {
+ onlyEvents: boolean;
+ }
+ ) {
+ super();
+ }
+
+ async init() {
+ this.stream = this.collection.watch(this.pipeline, { fullDocument: "updateLookup" });
+
+ this.stream.on("change", this.change);
+ this.stream.on("close", this.destroy);
+ this.stream.on("error", console.error);
+
+ if (!this.opts.onlyEvents) {
+ this.data = await this.collection.aggregate(this.pipeline).toArray();
+ }
+ }
+
+ convertResult(obj: any) {
+ if (obj instanceof Long) return BigInt(obj.toString());
+ if (typeof obj === "object") {
+ Object.keys(obj).forEach((key) => {
+ obj[key] = this.convertResult(obj[key]);
+ });
+ }
+
+ return obj;
+ }
+
+ change = (doc: ChangeEvent) => {
+ // @ts-ignore
+ if (doc.fullDocument) {
+ // @ts-ignore
+ if (!this.opts.onlyEvents) this.data = doc.fullDocument;
+ }
+
+ switch (doc.operationType) {
+ case "dropDatabase":
+ return this.destroy();
+ case "drop":
+ return this.destroy();
+ case "delete":
+ return this.emit("delete", doc.documentKey._id.toHexString());
+ case "insert":
+ return this.emit("insert", doc.fullDocument);
+ case "update":
+ case "replace":
+ return this.emit("change", doc.fullDocument);
+ case "invalidate":
+ return this.destroy();
+ default:
+ return;
+ }
+ };
+
+ destroy() {
+ this.stream.off("change", this.change);
+ this.emit("close");
+
+ if (this.stream.isClosed()) return;
+
+ return this.stream.close();
+ }
+}
diff --git a/src/util/MongoBigInt.ts b/src/util/MongoBigInt.ts
new file mode 100644
index 00000000..cc185bed
--- /dev/null
+++ b/src/util/MongoBigInt.ts
@@ -0,0 +1,76 @@
+import mongoose from "mongoose";
+
+class LongSchema extends mongoose.SchemaType {
+ public $conditionalHandlers = {
+ $lt: this.handleSingle,
+ $lte: this.handleSingle,
+ $gt: this.handleSingle,
+ $gte: this.handleSingle,
+ $ne: this.handleSingle,
+ $in: this.handleArray,
+ $nin: this.handleArray,
+ $mod: this.handleArray,
+ $all: this.handleArray,
+ $bitsAnySet: this.handleArray,
+ $bitsAllSet: this.handleArray,
+ };
+
+ handleSingle(val: any) {
+ return this.cast(val);
+ }
+
+ handleArray(val: any) {
+ var self = this;
+ return val.map(function (m: any) {
+ return self.cast(m);
+ });
+ }
+
+ checkRequired(val: any) {
+ return null != val;
+ }
+
+ cast(val: any, scope?: any, init?: any) {
+ if (null === val) return val;
+ if ("" === val) return null;
+
+ if (val instanceof mongoose.mongo.Long) return BigInt(val.toString());
+ if (val instanceof Number || "number" == typeof val) return BigInt(val);
+ if (!Array.isArray(val) && val.toString) return BigInt(val.toString());
+
+ // @ts-ignore
+ throw new SchemaType.CastError("Long", val);
+ }
+
+ castForQuery($conditional: string, value: any) {
+ var handler;
+ if (2 === arguments.length) {
+ // @ts-ignore
+ handler = this.$conditionalHandlers[$conditional];
+ if (!handler) {
+ throw new Error("Can't use " + $conditional + " with Long.");
+ }
+ return handler.call(this, value);
+ } else {
+ return this.cast($conditional);
+ }
+ }
+}
+
+LongSchema.cast = mongoose.SchemaType.cast;
+LongSchema.set = mongoose.SchemaType.set;
+LongSchema.get = mongoose.SchemaType.get;
+
+declare module "mongoose" {
+ namespace Types {
+ class Long extends mongoose.mongo.Long {}
+ }
+ namespace Schema {
+ namespace Types {
+ class Long extends LongSchema {}
+ }
+ }
+}
+
+mongoose.Schema.Types.Long = LongSchema;
+mongoose.Types.Long = mongoose.mongo.Long;
|