diff --git a/.eslintrc.yml b/.eslintrc.yml
index 504b65d..9e5c366 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -16,6 +16,8 @@ ignorePatterns:
- 'coverage'
- '**/*.js'
- '**/*.d.ts'
+ - '__tests__'
+ - '__test__'
parser: '@typescript-eslint/parser'
diff --git a/packages/cache/README.md b/packages/cache/README.md
index c66165b..94c7157 100644
--- a/packages/cache/README.md
+++ b/packages/cache/README.md
@@ -1,13 +1,39 @@
# @biscuitland/cache
-In progress.
+## Most importantly, biscuit's cache is:
+
+A resource control cache layer, based on carriers and resource-intensive policies
[
](https://github.com/oasisjs/biscuit)
[
](https://discord.gg/XNw2RZFzaP)
+
+
+## Install (for [node18](https://nodejs.org/en/download/))
+
+```sh-session
+npm install @biscuitland/cache
+```
+
+## Example (Basic)
+
+```ts
+import { Cache, MemoryCacheAdapter } from '@biscuitland/cache';
+
+const bootstrap = async () => {
+ const cache = new Cache({
+ adapter: new MemoryCacheAdapter(),
+ });
+
+ // You can listen to the raw biscuit event
+
+ cache.start();
+};
+
+bootstrap();
+```
+
## Links
-- [Website](https://biscuitjs.com/)
- [Documentation](https://docs.biscuitjs.com/)
-- [Discord](https://discord.gg/XNw2RZFzaP)
-- [core](https://www.npmjs.com/package/@biscuitland/core) | [api-types](https://www.npmjs.com/package/@biscuitland/api-types) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)
+- [Website](https://biscuitjs.com/)
diff --git a/packages/cache/src/adapters/cache-adapter.ts b/packages/cache/src/adapters/cache-adapter.ts
deleted file mode 100644
index b944165..0000000
--- a/packages/cache/src/adapters/cache-adapter.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export interface CacheAdapter {
- /**
- * @inheritDoc
- */
-
- get(id: string): any | Promise;
- get(id: string, guild?: string): string | Promise;
-
- /**
- * @inheritDoc
- */
-
- set(id: string, data: any, expire?: number): void | Promise;
-
- /**
- * @inheritDoc
- */
-
- count(to: string): number | Promise;
-
- /**
- * @inheritDoc
- */
-
- remove(id: string): void | Promise;
-
- /**
- * @inheritDoc
- */
-
- contains(to: string, id: string): boolean | Promise;
-
- /**
- * @inheritDoc
- */
-
- getToRelationship(to: string): string[] | Promise;
-
- /**
- * @inheritDoc
- */
-
- addToRelationship(to: string, id: string): void | Promise;
-
- /**
- * @inheritDoc
- */
-
- removeToRelationship(to: string, id: string): void | Promise;
-}
diff --git a/packages/cache/src/adapters/redis-cache-adapter.ts b/packages/cache/src/adapters/redis-cache-adapter.ts
deleted file mode 100644
index 9b0bbcf..0000000
--- a/packages/cache/src/adapters/redis-cache-adapter.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import type { CacheAdapter } from './cache-adapter';
-
-import type { RedisOptions } from 'ioredis';
-import type Redis from 'ioredis';
-import IORedis from 'ioredis';
-
-interface BaseOptions {
- namespace: string;
- expire?: number;
-}
-
-interface BuildOptions extends BaseOptions, RedisOptions { }
-
-interface ClientOptions extends BaseOptions {
- client: Redis;
-}
-
-type Options = BuildOptions | ClientOptions;
-
-export class RedisCacheAdapter implements CacheAdapter {
- private static readonly DEFAULTS = {
- namespace: 'biscuitland'
- };
-
- private readonly client: Redis;
-
- readonly options: Options;
-
- constructor(options?: Options) {
- this.options = Object.assign(RedisCacheAdapter.DEFAULTS, options);
-
- if ((this.options as ClientOptions).client) {
- this.client = (this.options as ClientOptions).client;
- } else {
- const { ...redisOpt } = this.options as BuildOptions;
- this.client = new IORedis(redisOpt);
- }
- }
-
- /**
- * @inheritDoc
- */
-
- async get(id: string): Promise {
- const data = await this.client.get(this.composite(id));
-
- if (!data) {
- return null;
- }
-
- return JSON.parse(data);
- }
-
- /**
- * @inheritDoc
- */
-
- async set(id: string, data: unknown, expire = this.options.expire): Promise {
- if (expire) {
- await this.client.set(this.composite(id), JSON.stringify(data), 'EX', expire);
- } else {
- await this.client.set(this.composite(id), JSON.stringify(data));
- }
- }
-
- /**
- * @inheritDoc
- */
-
- async count(_to: string): Promise {
- throw new Error('Method not implemented.');
- }
-
- /**
- * @inheritDoc
- */
-
- async remove(id: string): Promise {
- await this.client.del(this.composite(id));
- }
-
- /**
- * @inheritDoc
- */
-
- async contains(_to: string, _id: string): Promise {
- throw new Error('Method not implemented.');
- }
-
- /**
- * @inheritDoc
- */
-
- async getToRelationship(_to: string): Promise {
- throw new Error('Method not implemented.');
- }
-
- /**
- * @inheritDoc
- */
-
- async addToRelationship(_to: string, _id: string): Promise {
- throw new Error('Method not implemented.');
- }
-
- /**
- * @inheritDoc
- */
-
- async removeToRelationship(_to: string, _id: string): Promise {
- throw new Error('Method not implemented.');
- }
-
- /**
- * @inheritDoc
- */
-
- composite(id: string): string {
- return `${this.options.namespace}:${id}`;
- }
-}
diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts
index 5ac79f5..f073969 100644
--- a/packages/cache/src/cache.ts
+++ b/packages/cache/src/cache.ts
@@ -1,6 +1,8 @@
/* eslint-disable no-case-declarations */
-import { MemoryCacheAdapter } from './adapters/memory-cache-adapter';
-// import { RedisCacheAdapter } from './adapters/redis-cache-adapter';
+import type { CacheOptions, CO } from './types';
+
+import type { CacheAdapter } from './scheme/adapters/cache-adapter';
+import { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
import {
ChannelResource,
@@ -9,26 +11,23 @@ import {
GuildResource,
GuildRoleResource,
GuildStickerResource,
+ GuildVoiceResource,
+ PresenceResource,
UserResource,
- VoiceResource
} from './resources';
-/**
- * add options and adaptor passable by options
- * @default MemoryCacheAdapter
- */
+import { Options } from './utils/options';
-/**
- * Add more adapters and options
- * Allow passing customizable resources and deleting resources
- */
-
-/**
- * Add presence system (disabled by default)
- * Add TTL option (default 7 days)
- * Add permissions resource (accessible as a subResource)
- */
export class Cache {
+ static readonly DEFAULTS = {
+ adapter: new MemoryCacheAdapter(),
+ };
+
+ readonly options: CO;
+ #adapter: CacheAdapter;
+
+ // move to resources assigned
+
readonly channels: ChannelResource;
readonly emojis: GuildEmojiResource;
@@ -36,61 +35,64 @@ export class Cache {
readonly guilds: GuildResource;
readonly roles: GuildRoleResource;
readonly stickers: GuildStickerResource;
+ readonly voices: GuildVoiceResource;
+ readonly presences: PresenceResource;
readonly users: UserResource;
- readonly voices: VoiceResource;
- ready: boolean;
+ constructor(options: CacheOptions) {
+ this.options = Options({}, Cache.DEFAULTS, options);
+ this.#adapter = this.options.adapter;
- constructor() {
- this.ready = false;
+ this.channels = new ChannelResource(this.#adapter);
- /** this change to memory */
- const adapter = new MemoryCacheAdapter();
+ this.emojis = new GuildEmojiResource(this.#adapter);
+ this.members = new GuildMemberResource(this.#adapter);
- this.channels = new ChannelResource(adapter);
+ this.guilds = new GuildResource(this.#adapter);
+ this.roles = new GuildRoleResource(this.#adapter);
- this.emojis = new GuildEmojiResource(adapter);
- this.members = new GuildMemberResource(adapter);
+ this.stickers = new GuildStickerResource(this.#adapter);
+ this.voices = new GuildVoiceResource(this.#adapter);
- this.guilds = new GuildResource(adapter);
- this.roles = new GuildRoleResource(adapter);
- this.stickers = new GuildStickerResource(adapter);
-
- this.users = new UserResource(adapter);
- this.voices = new VoiceResource(adapter);
+ this.presences = new PresenceResource(this.#adapter);
+ this.users = new UserResource(this.#adapter);
}
/**
* @inheritDoc
*/
- async start(event: { t: string; d: any }) {
+ async start(event: any) {
+ let resources: any[] = [];
+
+ let contents: any[] = [];
+
switch (event.t) {
case 'READY':
+ resources = [];
+
await this.users.set(event.d.user.id, event.d.user);
- const guilds: (Promise | undefined)[] = [];
-
for (const guild of event.d.guilds) {
- guilds.push(this.guilds.set(guild.id, guild));
+ resources.push(this.guilds.set(guild.id, guild));
}
- await Promise.all(guilds);
+ await Promise.all(resources);
- this.ready = true;
break;
case 'USER_UPDATE':
await this.users.set(event.d.id, event.d);
break;
+ case 'PRESENCE_UPDATE':
+ await this.presences.set(event.d.user?.id, event.d);
- case 'GUILD_CREATE':
- await this.guilds.set(event.d.id, event.d);
break;
+ case 'GUILD_CREATE':
case 'GUILD_UPDATE':
- this.guilds.set(event.d.id, event.d);
+ await this.guilds.set(event.d.id, event.d);
break;
case 'GUILD_DELETE':
@@ -102,10 +104,6 @@ export class Cache {
break;
case 'CHANNEL_CREATE':
- // modify [Add elimination system]
- await this.channels.set(event.d.id, event.d);
- break;
-
case 'CHANNEL_UPDATE':
// modify [Add elimination system]
await this.channels.set(event.d.id, event.d);
@@ -116,14 +114,18 @@ export class Cache {
await this.channels.remove(event.d.id);
break;
- case 'GUILD_ROLE_CREATE':
- await this.roles.set(
- event.d.role.id,
- event.d.guild_id,
- event.d.role
- );
+ case 'MESSAGE_CREATE':
+ if (event.d.webhook_id) {
+ return;
+ }
+
+ if (event.d.author) {
+ await this.users.set(event.d.author.id, event.d.author);
+ }
+
break;
+ case 'GUILD_ROLE_CREATE':
case 'GUILD_ROLE_UPDATE':
await this.roles.set(
event.d.role.id,
@@ -137,27 +139,62 @@ export class Cache {
break;
case 'GUILD_EMOJIS_UPDATE':
- // modify [Add elimination system]
- for (const v of event.d.emojis) {
- await this.emojis?.set(v.id, event.d.guild_id, v);
+ contents = [];
+ contents = await this.emojis.items(event.d.guild_id);
+
+ for (const emoji of event.d.emojis) {
+ const emote = contents.find(o => o?.id === emoji.id);
+
+ if (!emote || emote !== emoji) {
+ await this.emojis.set(
+ emoji.id,
+ event.d.guild_id,
+ emoji
+ );
+ }
}
+
+ for (const emoji of contents) {
+ const emote = event.d.emojis.find(
+ (o: any) => o.id === emoji?.id
+ );
+
+ if (!emote) {
+ await this.emojis.remove(emote.id, event.d.guild_id);
+ }
+ }
+
break;
case 'GUILD_STICKERS_UPDATE':
- // modify [Add elimination system]
- for (const v of event.d.stickers) {
- await this.stickers?.set(v.id, event.d.guild_id, v);
+ contents = [];
+ contents = await this.stickers.items(event.d.guild_id);
+
+ for (const sticker of event.d.stickers) {
+ const stick = contents.find(o => o?.id === sticker.id);
+
+ if (!stick || stick !== sticker) {
+ await this.stickers.set(
+ sticker.id,
+ event.d.guild_id,
+ sticker
+ );
+ }
}
+
+ for (const sticker of contents) {
+ const stick = event.d.stickers.find(
+ (o: any) => o.id === sticker?.id
+ );
+
+ if (!stick) {
+ await this.stickers.remove(stick.id, event.d.guild_id);
+ }
+ }
+
break;
case 'GUILD_MEMBER_ADD':
- await this.members.set(
- event.d.user.id,
- event.d.guild_id,
- event.d
- );
- break;
-
case 'GUILD_MEMBER_UPDATE':
await this.members.set(
event.d.user.id,
@@ -171,10 +208,10 @@ export class Cache {
break;
case 'GUILD_MEMBERS_CHUNK':
- const members: (Promise | undefined)[] = [];
+ resources = [];
for (const member of event.d.members) {
- members.push(
+ resources.push(
this.members.set(
member.user.id,
event.d.guild_id,
@@ -183,7 +220,7 @@ export class Cache {
);
}
- await Promise.all(members);
+ await Promise.all(resources);
break;
@@ -192,16 +229,15 @@ export class Cache {
return;
}
- if (event.d.user_id && event.d.member) {
- await this.members.set(
- event.d.user_id,
- event.d.guild_id,
- event.d.member
- );
+ if (event.d.guild_id && event.d.member && event.d.user_id) {
+ await this.members.set(event.d.user_id, event.d.guild_id, {
+ guild_id: event.d.guild_id,
+ ...event.d.member,
+ });
}
if (event.d.channel_id != null) {
- await this.voices.set(
+ await this.members.set(
event.d.user_id,
event.d.guild_id,
event.d
diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts
index ada9f80..1b88152 100644
--- a/packages/cache/src/index.ts
+++ b/packages/cache/src/index.ts
@@ -1,6 +1,7 @@
-export { CacheAdapter } from './adapters/cache-adapter';
+export { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
+export { RedisCacheAdapter } from './scheme/adapters/redis-cache-adapter';
-export { MemoryCacheAdapter } from './adapters/memory-cache-adapter';
-export { RedisCacheAdapter } from './adapters/redis-cache-adapter';
+export { CacheAdapter } from './scheme/adapters/cache-adapter';
+export { RedisOptions, MemoryOptions, CacheOptions } from './types';
export { Cache } from './cache';
diff --git a/packages/cache/src/resources/base-resource.ts b/packages/cache/src/resources/base-resource.ts
index b453e35..1457344 100644
--- a/packages/cache/src/resources/base-resource.ts
+++ b/packages/cache/src/resources/base-resource.ts
@@ -1,63 +1,117 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+/* eslint-disable @typescript-eslint/naming-convention */
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
-export class BaseResource {
- namespace = 'base';
+/**
+ * Base class for all resources
+ * All Methods from BaseResource are also available on every class extends
+ */
- adapter!: CacheAdapter; // replace
+class Base {
+ /**
+ * Resource name
+ */
+
+ #namespace = 'base';
/**
- * @inheritDoc
+ * Adapter for storage processes and operations
+ */
+
+ #adapter: CacheAdapter;
+
+ /**
+ * Guild linked and assigned to the current entity (resource)
+ */
+
+ parent?: string;
+
+ /**
+ * Constructor
+ */
+
+ constructor(namespace: string, adapter: CacheAdapter) {
+ this.#namespace = namespace;
+ this.#adapter = adapter;
+ }
+
+ /**
+ * Entity linked
+ */
+
+ setEntity(entity: T): void {
+ Object.assign(this, entity);
+ }
+
+ /**
+ * Parent linked
+ */
+
+ setParent(parent: string): void {
+ // rename
+ this.parent = parent;
+ }
+
+ /**
+ * Count how many resources there are in the relationships
*/
async count(to: string): Promise {
- return await this.adapter.count(this.hashId(to));
+ return await this.#adapter.count(this.hashId(to));
}
/**
- * @inheritDoc
+ * Check if the resource is in the relationships
*/
- async contains(to: string, id: string): Promise {
- return await this.adapter.contains(this.hashId(to), id);
+ async contains(
+ id: string,
+ guild: string = this.parent as string
+ ): Promise {
+ return await this.#adapter.contains(this.hashId(guild), id);
}
/**
- * @inheritDoc
+ * Gets the resource relationships
*/
- async getToRelationship(to: string): Promise {
- return await this.adapter.getToRelationship(this.hashId(to));
+ async getToRelationship(
+ id: string = this.parent as string
+ ): Promise {
+ return await this.#adapter.getToRelationship(this.hashId(id));
}
/**
- * @inheritDoc
+ * Adds the resource to relationships
*/
- async addToRelationship(to: string, id: string): Promise {
- await this.adapter.addToRelationship(this.hashId(to), id);
+ async addToRelationship(
+ id: string,
+ guild: string = this.parent as string
+ ): Promise {
+ await this.#adapter.addToRelationship(this.hashId(guild), id);
}
/**
- * @inheritDoc // to-do replace
+ * Removes the relationship resource
*/
- async removeToRelationship(to: string, id: string): Promise {
- await this.adapter.removeToRelationship(this.hashId(to), id);
+ async removeToRelationship(
+ id: string,
+ guild: string = this.parent as string
+ ): Promise {
+ await this.#adapter.removeToRelationship(this.hashId(guild), id);
}
/**
- * @inheritDoc
+ * Construct an id consisting of namespace.id
*/
- hashId(id: string): string {
- return `${this.namespace}.${id}`;
- }
-
- /**
- * @inheritDoc
- */
-
- hashGuildId(id: string, guild: string): string {
- return `${this.namespace}.${guild}.${id}`;
+ protected hashId(id: string): string {
+ return `${this.#namespace}.${id}`;
}
}
+
+export const BaseResource = Base as new (
+ data: string,
+ adapter: CacheAdapter
+) => Base & T;
diff --git a/packages/cache/src/resources/channel-resource.ts b/packages/cache/src/resources/channel-resource.ts
index bcc65c1..93abe8b 100644
--- a/packages/cache/src/resources/channel-resource.ts
+++ b/packages/cache/src/resources/channel-resource.ts
@@ -1,42 +1,92 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordChannel } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
+import { UserResource } from './user-resource';
-export class ChannelResource extends BaseResource {
- namespace = 'channel' as const;
+/**
+ * Resource represented by an channel of discord
+ */
- adapter: CacheAdapter;
+export class ChannelResource extends BaseResource {
+ #namespace = 'channel';
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
+ #users: UserResource;
+
+ constructor(adapter: CacheAdapter, entity?: DiscordChannel | null) {
+ super('channel', adapter);
+
+ this.#adapter = adapter;
+ this.#users = new UserResource(adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string): Promise {
- const kv = await this.adapter.get(this.hashId(id));
+ async get(id: string): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
- return kv;
+ return new ChannelResource(this.#adapter, kv);
}
return null;
}
/**
- * @inheritDoc // to-do rework
+ * @inheritDoc
*/
- async set(id: string, data: any, expire?: number): Promise {
+ async set(id: string, data: any): Promise {
+ if (data.recipients) {
+ const recipients = [];
+
+ for (const recipient of data.recipients) {
+ recipients.push(this.#users.set(recipient.id, recipient));
+ }
+
+ await Promise.all(recipients);
+ }
+
delete data.recipients;
+ delete data.permission_overwrites;
await this.addToRelationship(id);
- await this.adapter.set(this.hashId(id), data, expire);
+ await this.#adapter.set(this.hashId(id), data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(): Promise {
+ const data = await this.#adapter.items(this.#namespace);
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new ChannelResource(this.#adapter, dt);
+ resource.setParent(resource.id);
+
+ return resource;
+ });
+ }
+
+ return [];
}
/**
@@ -44,7 +94,7 @@ export class ChannelResource extends BaseResource {
*/
async count(): Promise {
- return await this.adapter.count(this.namespace);
+ return await this.#adapter.count(this.#namespace);
}
/**
@@ -52,7 +102,7 @@ export class ChannelResource extends BaseResource {
*/
async remove(id: string): Promise {
- await this.adapter.remove(this.hashId(id));
+ await this.#adapter.remove(this.hashId(id));
}
/**
@@ -60,7 +110,7 @@ export class ChannelResource extends BaseResource {
*/
async contains(id: string): Promise {
- return await this.adapter.contains(this.namespace, id);
+ return await this.#adapter.contains(this.#namespace, id);
}
/**
@@ -68,7 +118,7 @@ export class ChannelResource extends BaseResource {
*/
async getToRelationship(): Promise {
- return await this.adapter.getToRelationship(this.namespace);
+ return await this.#adapter.getToRelationship(this.#namespace);
}
/**
@@ -76,6 +126,14 @@ export class ChannelResource extends BaseResource {
*/
async addToRelationship(id: string): Promise {
- await this.adapter.addToRelationship(this.namespace, id);
+ await this.#adapter.addToRelationship(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async removeToRelationship(id: string): Promise {
+ await this.#adapter.removeToRelationship(this.#namespace, id);
}
}
diff --git a/packages/cache/src/resources/guild-emoji-resource.ts b/packages/cache/src/resources/guild-emoji-resource.ts
index accd145..85e6eba 100644
--- a/packages/cache/src/resources/guild-emoji-resource.ts
+++ b/packages/cache/src/resources/guild-emoji-resource.ts
@@ -1,28 +1,55 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordEmoji } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
+import { UserResource } from './user-resource';
-export class GuildEmojiResource extends BaseResource {
- namespace = 'emoji' as const;
+/**
+ * Resource represented by an emoji of discord
+ */
- adapter: CacheAdapter;
+export class GuildEmojiResource extends BaseResource {
+ #namespace = 'emoji' as const;
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
+ #users: UserResource;
+
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordEmoji | null,
+ parent?: string
+ ) {
+ super('emoji', adapter);
+
+ this.#adapter = adapter;
+ this.#users = new UserResource(adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string, guild: string): Promise {
- const kv = await this.adapter.get(this.hashGuildId(id, guild));
+ async get(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
- return kv;
+ return new GuildEmojiResource(this.#adapter, kv, guild);
}
return null;
@@ -34,18 +61,68 @@ export class GuildEmojiResource extends BaseResource {
async set(
id: string,
- guild: string,
- data: any,
- expire?: number
+ guild: string | undefined = this.parent,
+ data: any
): Promise {
- await this.adapter.set(this.hashGuildId(id, guild), data, expire);
+ if (data.user) {
+ await this.#users.set(data.user.id, data.user);
+ }
+
+ delete data.user;
+ delete data.roles;
+
+ if (this.parent) {
+ this.setEntity(data);
+ }
+
+ await this.addToRelationship(id, guild);
+ await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
- async remove(id: string, guild: string): Promise {
- await this.adapter.remove(this.hashGuildId(id, guild));
+ async items(to: string): Promise {
+ if (!to && this.parent) {
+ to = this.parent;
+ }
+
+ const data = await this.#adapter.items(this.hashId(to));
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildEmojiResource(this.#adapter, dt);
+ resource.setParent(to);
+
+ return resource;
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ await this.removeToRelationship(id, guild);
+ await this.#adapter.remove(this.hashGuildId(id, guild));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected hashGuildId(id: string, guild?: string): string {
+ if (!guild) {
+ return this.hashId(id);
+ }
+
+ return `${this.#namespace}.${guild}.${id}`;
}
}
diff --git a/packages/cache/src/resources/guild-member-resource.ts b/packages/cache/src/resources/guild-member-resource.ts
index 87ac4b1..603d8ca 100644
--- a/packages/cache/src/resources/guild-member-resource.ts
+++ b/packages/cache/src/resources/guild-member-resource.ts
@@ -1,20 +1,37 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordMember } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
-export class GuildMemberResource extends BaseResource {
- namespace = 'member' as const;
+/**
+ * Resource represented by an member of discord
+ */
- adapter: CacheAdapter;
- users: UserResource;
+export class GuildMemberResource extends BaseResource {
+ #namespace = 'member' as const;
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
- this.users = new UserResource(adapter);
+ #users: UserResource;
+
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordMember | null,
+ parent?: string
+ ) {
+ super('member', adapter);
+
+ this.#adapter = adapter;
+ this.#users = new UserResource(adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
}
/**
@@ -23,12 +40,16 @@ export class GuildMemberResource extends BaseResource {
async get(
id: string,
- guild: string
- ): Promise<(DiscordMember & { id: string }) | null> {
- const kv = await this.adapter.get(this.hashGuildId(id, guild));
+ guild: string | undefined = this.parent
+ ): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
- return kv;
+ return new GuildMemberResource(this.#adapter, kv, guild);
}
return null;
@@ -40,33 +61,68 @@ export class GuildMemberResource extends BaseResource {
async set(
id: string,
- guild: string,
- data: any,
- expire?: number
+ guild: string | undefined = this.parent,
+ data: any
): Promise {
- if (!data.id) {
- data.id = id;
- }
-
if (data.user) {
- await this.users.set(data.user.id, data.user);
- }
-
- if (!data.guild_id) {
- data.guild_id = guild;
+ await this.#users.set(data.user.id, data.user);
}
delete data.user;
+ delete data.roles;
+
+ if (this.parent) {
+ this.setEntity(data);
+ }
await this.addToRelationship(id, guild);
- await this.adapter.set(this.hashGuildId(id, guild), data, expire);
+ await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
- async remove(id: string, guild: string): Promise {
- await this.adapter.remove(this.hashGuildId(id, guild));
+ async items(to: string): Promise {
+ if (!to && this.parent) {
+ to = this.parent;
+ }
+
+ const data = await this.#adapter.items(this.hashId(to));
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildMemberResource(this.#adapter, dt);
+ resource.setParent(to);
+
+ return resource;
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ await this.removeToRelationship(id, guild);
+ await this.#adapter.remove(this.hashGuildId(id, guild));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected hashGuildId(id: string, guild?: string): string {
+ if (!guild) {
+ return this.hashId(id);
+ }
+
+ return `${this.#namespace}.${guild}.${id}`;
}
}
diff --git a/packages/cache/src/resources/guild-resource.ts b/packages/cache/src/resources/guild-resource.ts
index f7d80a1..3633ec0 100644
--- a/packages/cache/src/resources/guild-resource.ts
+++ b/packages/cache/src/resources/guild-resource.ts
@@ -1,4 +1,8 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordGuild } from '@biscuitland/api-types';
import { ChannelResource } from './channel-resource';
@@ -6,44 +10,90 @@ import { GuildEmojiResource } from './guild-emoji-resource';
import { GuildMemberResource } from './guild-member-resource';
import { GuildRoleResource } from './guild-role-resource';
import { GuildStickerResource } from './guild-sticker-resource';
-import { VoiceResource } from './voice-resource';
+import { GuildVoiceResource } from './guild-voice-resource';
+import { PresenceResource } from './presence-resource';
import { BaseResource } from './base-resource';
-export class GuildResource extends BaseResource {
- namespace = 'guild' as const;
+/**
+ * Resource represented by an guild of discord
+ */
- adapter: CacheAdapter;
+export class GuildResource extends BaseResource {
+ #namespace = 'guild' as const;
- private channels: ChannelResource;
- private emojis: GuildEmojiResource;
- private members: GuildMemberResource;
- private roles: GuildRoleResource;
- private stickers: GuildStickerResource;
- private voices: VoiceResource;
+ #adapter: CacheAdapter;
- constructor(adapter: CacheAdapter) {
- super();
+ #channels: ChannelResource;
+ #emojis: GuildEmojiResource;
+ #members: GuildMemberResource;
+ #roles: GuildRoleResource;
+ #stickers: GuildStickerResource;
+ #voices: GuildVoiceResource;
- this.adapter = adapter;
+ #presences: PresenceResource;
- this.channels = new ChannelResource(adapter);
- this.emojis = new GuildEmojiResource(adapter);
- this.members = new GuildMemberResource(adapter);
- this.roles = new GuildRoleResource(adapter);
- this.stickers = new GuildStickerResource(adapter);
- this.voices = new VoiceResource(adapter);
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordGuild | null,
+ parent?: string,
+ channels?: ChannelResource,
+ emojis?: GuildEmojiResource,
+ members?: GuildMemberResource,
+ roles?: GuildRoleResource,
+ stickers?: GuildStickerResource,
+ voices?: GuildVoiceResource,
+ presences?: PresenceResource
+ ) {
+ super('guild', adapter);
+
+ this.#adapter = adapter;
+
+ this.#channels = channels ?? new ChannelResource(adapter);
+
+ this.#emojis = emojis ?? new GuildEmojiResource(adapter);
+ this.#members = members ?? new GuildMemberResource(adapter);
+
+ this.#roles = roles ?? new GuildRoleResource(adapter);
+
+ this.#stickers = stickers ?? new GuildStickerResource(adapter);
+
+ this.#voices = voices ?? new GuildVoiceResource(adapter);
+ this.#presences = presences ?? new PresenceResource(adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string): Promise {
- const kv = await this.adapter.get(this.hashId(id));
+ async get(id: string): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
- return kv;
+ return new GuildResource(
+ this.#adapter,
+ kv,
+ id,
+ new ChannelResource(this.#adapter),
+ new GuildEmojiResource(this.#adapter, null, id),
+ new GuildMemberResource(this.#adapter, null, id),
+ new GuildRoleResource(this.#adapter, null, id),
+ new GuildStickerResource(this.#adapter, null, id),
+ new GuildVoiceResource(this.#adapter, null, id),
+ new PresenceResource(this.#adapter)
+ );
}
return null;
@@ -53,83 +103,125 @@ export class GuildResource extends BaseResource {
* @inheritDoc
*/
- async set(id: string, data: any, expire?: number): Promise {
+ async set(id: string, data: any): Promise {
if (data.channels) {
- const channels: (Promise | undefined)[] = [];
+ const channels: unknown[] = [];
for (const channel of data.channels) {
- await this.channels.set(channel.id, channel);
+ channel.guild_id = id;
+
+ await this.#channels.set(channel.id, channel);
}
await Promise.all(channels);
}
+ if (data.emojis) {
+ const emojis: unknown[] = [];
+
+ for (const emoji of data.emojis) {
+ emoji.guild_id = id;
+
+ await this.#emojis.set(emoji.id, id, emoji);
+ }
+
+ await Promise.all(emojis);
+ }
+
if (data.members) {
- const members: (Promise | undefined)[] = [];
+ const members: unknown[] = [];
for (const member of data.members) {
- await this.members.set(member.user.id, id, member);
+ member.guild_id = id;
+
+ await this.#members.set(member.user.id, id, member);
}
await Promise.all(members);
}
if (data.roles) {
- const roles: (Promise | undefined)[] = [];
+ const roles: unknown[] = [];
for (const role of data.roles) {
- await this.roles.set(role.id, id, role);
+ role.guild_id = id;
+
+ await this.#roles.set(role.id, id, role);
}
await Promise.all(roles);
}
if (data.stickers) {
- const stickers: (Promise | undefined)[] = [];
+ const stickers: unknown[] = [];
for (const sticker of data.stickers) {
- await this.stickers.set(sticker.id, id, sticker);
+ sticker.guild_id = id;
+
+ await this.#stickers.set(sticker.id, id, sticker);
}
await Promise.all(stickers);
}
- if (data.emojis) {
- const emojis: (Promise | undefined)[] = [];
-
- for (const emoji of data.emojis) {
- await this.emojis.set(emoji.id, id, emoji);
- }
-
- await Promise.all(emojis);
- }
-
if (data.voice_states) {
- const voices: Promise[] = [];
+ const voices: unknown[] = [];
for (const voice of data.voice_states) {
- if (!voice.guild_id) {
- voice.guild_id = id;
- }
+ voice.guild_id = id;
- voices.push(this.voices.set(voice.user_id, id, voice));
+ voices.push(this.#voices.set(voice.user_id, id, voice));
}
await Promise.all(voices);
}
+ if (data.presences) {
+ const presences: unknown[] = [];
+
+ for (const presence of data.presences) {
+ await this.#presences.set(presence.user.id, presence);
+ }
+
+ await Promise.all(presences);
+ }
+
delete data.channels;
+ delete data.emojis;
delete data.members;
delete data.roles;
delete data.stickers;
- delete data.emojis;
+
+ delete data.voice_states;
+ delete data.guild_hashes;
delete data.presences;
- delete data.voice_states;
+ if (this.parent) {
+ this.setEntity(data);
+ }
await this.addToRelationship(id);
- await this.adapter.set(this.hashId(id), data, expire);
+ await this.#adapter.set(this.hashId(id), data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(): Promise {
+ const data = await this.#adapter.items(this.#namespace);
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildResource(this.#adapter, dt);
+ resource.setParent(resource.id);
+
+ return resource;
+ });
+ }
+
+ return [];
}
/**
@@ -137,7 +229,7 @@ export class GuildResource extends BaseResource {
*/
async count(): Promise {
- return await this.adapter.count(this.namespace);
+ return await this.#adapter.count(this.#namespace);
}
/**
@@ -145,7 +237,32 @@ export class GuildResource extends BaseResource {
*/
async remove(id: string): Promise {
- await this.adapter.remove(this.hashId(id));
+ const members = await this.#members.getToRelationship(id);
+
+ for (const member of members) {
+ await this.#members.remove(member, id);
+ }
+
+ const roles = await this.#roles.getToRelationship(id);
+
+ for (const role of roles) {
+ await this.#roles.remove(role, id);
+ }
+
+ const emojis = await this.#emojis.getToRelationship(id);
+
+ for (const emoji of emojis) {
+ await this.#emojis.remove(emoji, id);
+ }
+
+ const stickers = await this.#stickers.getToRelationship(id);
+
+ for (const sticker of stickers) {
+ await this.#stickers.remove(sticker, id);
+ }
+
+ await this.removeToRelationship(id);
+ await this.#adapter.remove(this.hashId(id));
}
/**
@@ -153,7 +270,7 @@ export class GuildResource extends BaseResource {
*/
async contains(id: string): Promise {
- return await this.adapter.contains(this.namespace, id);
+ return await this.#adapter.contains(this.#namespace, id);
}
/**
@@ -161,7 +278,7 @@ export class GuildResource extends BaseResource {
*/
async getToRelationship(): Promise {
- return await this.adapter.getToRelationship(this.namespace);
+ return await this.#adapter.getToRelationship(this.#namespace);
}
/**
@@ -169,6 +286,54 @@ export class GuildResource extends BaseResource {
*/
async addToRelationship(id: string): Promise {
- await this.adapter.addToRelationship(this.namespace, id);
+ await this.#adapter.addToRelationship(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async removeToRelationship(id: string): Promise {
+ await this.#adapter.removeToRelationship(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getEmojis(): Promise {
+ return await this.#emojis.items(this.parent as string);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getMembers(): Promise {
+ return await this.#members.items(this.parent as string);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getRoles(): Promise {
+ return await this.#roles.items(this.parent as string);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getStickers(): Promise {
+ return await this.#stickers.items(this.parent as string);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getVoiceStates(): Promise {
+ return await this.#voices.items(this.parent as string);
}
}
diff --git a/packages/cache/src/resources/guild-role-resource.ts b/packages/cache/src/resources/guild-role-resource.ts
index 7c69b99..e9170f5 100644
--- a/packages/cache/src/resources/guild-role-resource.ts
+++ b/packages/cache/src/resources/guild-role-resource.ts
@@ -1,28 +1,51 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordRole } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
-export class GuildRoleResource extends BaseResource {
- namespace = 'role' as const;
+/**
+ * Resource represented by an role of discord
+ */
- adapter: CacheAdapter;
+export class GuildRoleResource extends BaseResource {
+ #namespace = 'role' as const;
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordRole | null,
+ parent?: string
+ ) {
+ super('role', adapter);
+
+ this.#adapter = adapter;
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string, guild: string): Promise {
- const kv = await this.adapter.get(this.hashGuildId(id, guild));
+ async get(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
- return kv;
+ return new GuildRoleResource(this.#adapter, kv, guild);
}
return null;
@@ -34,9 +57,8 @@ export class GuildRoleResource extends BaseResource {
async set(
id: string,
- guild: string,
- data: any,
- expire?: number
+ guild: string | undefined = this.parent,
+ data: any
): Promise {
if (!data.id) {
data.id = id;
@@ -46,14 +68,66 @@ export class GuildRoleResource extends BaseResource {
data.guild_id = guild;
}
- await this.adapter.set(this.hashGuildId(id, guild), data, expire);
+ if (this.parent) {
+ this.setEntity(data);
+ }
+
+ await this.addToRelationship(id, guild);
+ await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
- async remove(id: string, guild: string): Promise {
- await this.adapter.remove(this.hashGuildId(id, guild));
+ async count(): Promise {
+ return await this.#adapter.count(this.#namespace);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(to: string): Promise {
+ if (!to && this.parent) {
+ to = this.parent;
+ }
+
+ const data = await this.#adapter.items(this.hashId(to));
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildRoleResource(this.#adapter, dt);
+ resource.setParent(to);
+
+ return resource;
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ await this.removeToRelationship(id, guild);
+ await this.#adapter.remove(this.hashGuildId(id, guild));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected hashGuildId(id: string, guild?: string): string {
+ if (!guild) {
+ return this.hashId(id);
+ }
+
+ return `${this.#namespace}.${guild}.${id}`;
}
}
diff --git a/packages/cache/src/resources/guild-sticker-resource.ts b/packages/cache/src/resources/guild-sticker-resource.ts
index e7938b0..de8d610 100644
--- a/packages/cache/src/resources/guild-sticker-resource.ts
+++ b/packages/cache/src/resources/guild-sticker-resource.ts
@@ -1,28 +1,52 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordSticker } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
+import { UserResource } from './user-resource';
-export class GuildStickerResource extends BaseResource {
- namespace = 'sticker' as const;
+/**
+ * Resource represented by an sticker of discord
+ */
- adapter: CacheAdapter;
+export class GuildStickerResource extends BaseResource {
+ #namespace = 'sticker' as const;
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
+ #users: UserResource;
+
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordSticker | null,
+ parent?: string
+ ) {
+ super('sticker', adapter);
+
+ this.#adapter = adapter;
+ this.#users = new UserResource(adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string, guild: string): Promise {
- const kv = await this.adapter.get(this.hashGuildId(id, guild));
+ async get(id: string, guild: string): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
- return kv;
+ return new GuildStickerResource(this.#adapter, kv, guild);
}
return null;
@@ -34,26 +58,67 @@ export class GuildStickerResource extends BaseResource {
async set(
id: string,
- guild: string,
- data: any,
- expire?: number
+ guild: string | undefined = this.parent,
+ data: any
): Promise {
- if (!data.id) {
- data.id = id;
+ if (data.user) {
+ await this.#users.set(data.user.id, data.user);
}
- if (!data.guild_id) {
- data.guild_id = guild;
+ delete data.user;
+
+ if (this.parent) {
+ this.setEntity(data);
}
- await this.adapter.set(this.hashGuildId(id, guild), data, expire);
+ await this.addToRelationship(id, guild);
+ await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
- async remove(id: string, guild: string): Promise {
- await this.adapter.remove(this.hashGuildId(id, guild));
+ async items(to: string): Promise {
+ if (!to && this.parent) {
+ to = this.parent;
+ }
+
+ const data = await this.#adapter.items(this.hashId(to));
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildStickerResource(this.#adapter, dt);
+ resource.setParent(to);
+
+ return resource;
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ await this.removeToRelationship(id, guild);
+ await this.#adapter.remove(this.hashGuildId(id, guild));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected hashGuildId(id: string, guild?: string): string {
+ if (!guild) {
+ return this.hashId(id);
+ }
+
+ return `${this.#namespace}.${guild}.${id}`;
}
}
diff --git a/packages/cache/src/resources/guild-voice-resource.ts b/packages/cache/src/resources/guild-voice-resource.ts
new file mode 100644
index 0000000..0c370eb
--- /dev/null
+++ b/packages/cache/src/resources/guild-voice-resource.ts
@@ -0,0 +1,127 @@
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
+import type { DiscordVoiceState } from '@biscuitland/api-types';
+
+import { BaseResource } from './base-resource';
+
+/**
+ * Resource represented by an voice state of discord
+ */
+
+export class GuildVoiceResource extends BaseResource {
+ #namespace = 'voice' as const;
+
+ #adapter: CacheAdapter;
+
+ constructor(
+ adapter: CacheAdapter,
+ entity?: DiscordVoiceState | null,
+ parent?: string
+ ) {
+ super('voice', adapter);
+
+ this.#adapter = adapter;
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+
+ if (parent) {
+ this.setParent(parent);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async get(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashGuildId(id, guild));
+
+ if (kv) {
+ return new GuildVoiceResource(this.#adapter, kv, guild);
+ }
+
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async set(
+ id: string,
+ guild: string | undefined = this.parent,
+ data: any
+ ): Promise {
+ if (!data.guild_id) {
+ data.guild_id = guild;
+ }
+
+ delete data.member;
+
+ if (this.parent) {
+ this.setEntity(data);
+ }
+
+ await this.addToRelationship(id, guild);
+ await this.#adapter.set(this.hashGuildId(id, guild), data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(to: string): Promise {
+ if (!to && this.parent) {
+ to = this.parent;
+ }
+
+ const data = await this.#adapter.items(this.hashId(to));
+
+ if (data) {
+ return data.map(dt => {
+ const resource = new GuildVoiceResource(this.#adapter, dt);
+ resource.setParent(to);
+
+ return resource;
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(
+ id: string,
+ guild: string | undefined = this.parent
+ ): Promise {
+ await this.removeToRelationship(id, guild);
+ await this.#adapter.remove(this.hashGuildId(id, guild));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected hashGuildId(id: string, guild?: string): string {
+ if (!guild) {
+ return this.hashId(id);
+ }
+
+ return `${this.#namespace}.${guild}.${id}`;
+ }
+}
diff --git a/packages/cache/src/resources/index.ts b/packages/cache/src/resources/index.ts
index c9bdb3b..3207fa7 100644
--- a/packages/cache/src/resources/index.ts
+++ b/packages/cache/src/resources/index.ts
@@ -7,7 +7,9 @@ export { GuildMemberResource } from './guild-member-resource';
export { GuildResource } from './guild-resource';
export { GuildRoleResource } from './guild-role-resource';
-export { GuildStickerResource } from './guild-sticker-resource';
+export { GuildStickerResource } from './guild-sticker-resource';
+export { GuildVoiceResource } from './guild-voice-resource';
+
+export { PresenceResource } from './presence-resource';
export { UserResource } from './user-resource';
-export { VoiceResource } from './voice-resource';
diff --git a/packages/cache/src/resources/presence-resource.ts b/packages/cache/src/resources/presence-resource.ts
new file mode 100644
index 0000000..50ce1c1
--- /dev/null
+++ b/packages/cache/src/resources/presence-resource.ts
@@ -0,0 +1,135 @@
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
+import type { DiscordPresenceUpdate } from '@biscuitland/api-types';
+
+import { BaseResource } from './base-resource';
+import { UserResource } from './user-resource';
+
+/**
+ * Resource represented by an presence of discord
+ */
+
+export class PresenceResource extends BaseResource {
+ #namespace = 'presence' as const;
+
+ #adapter: CacheAdapter;
+
+ #users: UserResource;
+
+ constructor(adapter: CacheAdapter, entity?: DiscordPresenceUpdate | null) {
+ super('presence', adapter);
+
+ this.#adapter = adapter;
+ this.#users = new UserResource(this.#adapter);
+
+ if (entity) {
+ this.setEntity(entity);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async get(id: string): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashId(id));
+
+ if (kv) {
+ return new PresenceResource(this.#adapter, kv);
+ }
+
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async set(id: string, data: any): Promise {
+ if (data.user) {
+ await this.#users.set(data.user.id, data.user);
+ }
+
+ delete data.user;
+ delete data.roles;
+
+ delete data.guild_id;
+
+ if (this.parent) {
+ this.setEntity(data);
+ }
+
+ await this.addToRelationship(id);
+ await this.#adapter.set(this.hashId(id), data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(): Promise {
+ const data = await this.#adapter.items(this.#namespace);
+
+ if (data) {
+ return data.map(dt => new PresenceResource(this.#adapter, dt));
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async count(): Promise {
+ return await this.#adapter.count(this.#namespace);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(id: string): Promise {
+ await this.removeToRelationship(id);
+ await this.#adapter.remove(this.hashId(id));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async contains(id: string): Promise {
+ return await this.#adapter.contains(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getToRelationship(): Promise {
+ return await this.#adapter.getToRelationship(this.#namespace);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async addToRelationship(id: string): Promise {
+ await this.#adapter.addToRelationship(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async removeToRelationship(id: string): Promise {
+ await this.#adapter.removeToRelationship(this.#namespace, id);
+ }
+}
diff --git a/packages/cache/src/resources/user-resource.ts b/packages/cache/src/resources/user-resource.ts
index 310d3ef..8d100a4 100644
--- a/packages/cache/src/resources/user-resource.ts
+++ b/packages/cache/src/resources/user-resource.ts
@@ -1,28 +1,44 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordUser } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
-export class UserResource extends BaseResource {
- namespace = 'user' as const;
+/**
+ * Resource represented by an user of discord
+ */
- adapter: CacheAdapter;
+export class UserResource extends BaseResource {
+ #namespace = 'user' as const;
- constructor(adapter: CacheAdapter) {
- super();
+ #adapter: CacheAdapter;
- this.adapter = adapter;
+ constructor(adapter: CacheAdapter, entity?: DiscordUser | null) {
+ super('user', adapter);
+
+ this.#adapter = adapter;
+
+ if (entity) {
+ this.setEntity(entity);
+ }
}
/**
* @inheritDoc
*/
- async get(id: string): Promise {
- const kv = await this.adapter.get(this.hashId(id));
+ async get(id: string): Promise {
+ if (this.parent) {
+ return this;
+ }
+
+ const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
- return kv;
+ return new UserResource(this.#adapter, kv);
}
return null;
@@ -32,8 +48,35 @@ export class UserResource extends BaseResource {
* @inheritDoc
*/
- async set(id: string, data: any, expire?: number): Promise {
- await this.adapter.set(this.hashId(id), data, expire);
+ async set(id: string, data: any): Promise {
+ if (this.parent) {
+ this.setEntity(data);
+ }
+
+ await this.addToRelationship(id);
+ await this.#adapter.set(this.hashId(id), data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(): Promise {
+ const data = await this.#adapter.items(this.#namespace);
+
+ if (data) {
+ return data.map(dt => new UserResource(this.#adapter, dt));
+ }
+
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async count(): Promise {
+ return await this.#adapter.count(this.#namespace);
}
/**
@@ -41,6 +84,39 @@ export class UserResource extends BaseResource {
*/
async remove(id: string): Promise {
- await this.adapter.remove(this.hashId(id));
+ await this.removeToRelationship(id);
+ await this.#adapter.remove(this.hashId(id));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async contains(id: string): Promise {
+ return await this.#adapter.contains(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getToRelationship(): Promise {
+ return await this.#adapter.getToRelationship(this.#namespace);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async addToRelationship(id: string): Promise {
+ await this.#adapter.addToRelationship(this.#namespace, id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async removeToRelationship(id: string): Promise {
+ await this.#adapter.removeToRelationship(this.#namespace, id);
}
}
diff --git a/packages/cache/src/resources/voice-resource.ts b/packages/cache/src/resources/voice-resource.ts
deleted file mode 100644
index 20b25d5..0000000
--- a/packages/cache/src/resources/voice-resource.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import type { CacheAdapter } from '../adapters/cache-adapter';
-import type { DiscordVoiceState } from '@biscuitland/api-types';
-
-import { BaseResource } from './base-resource';
-
-export class VoiceResource extends BaseResource {
- namespace = 'voice' as const;
-
- adapter: CacheAdapter;
-
- constructor(adapter: CacheAdapter) {
- super();
-
- this.adapter = adapter;
- }
-
- /**
- * @inheritDoc
- */
-
- async get(id: string, guild: string): Promise {
- const kv = await this.adapter.get(this.hashGuildId(id, guild));
-
- if (kv) {
- return kv;
- }
-
- return null;
- }
-
- /**
- * @inheritDoc
- */
-
- async set(
- id: string,
- guild: string,
- data: any,
- expire?: number
- ): Promise {
- if (!data.guild_id) {
- data.guild_id = guild;
- }
-
- delete data.member;
-
- await this.adapter.set(this.hashGuildId(id, guild), data, expire);
- }
-
- /**
- * @inheritDoc
- */
-
- async remove(id: string, guild: string): Promise {
- await this.adapter.remove(this.hashGuildId(id, guild));
- }
-}
diff --git a/packages/cache/src/scheme/adapters/cache-adapter.ts b/packages/cache/src/scheme/adapters/cache-adapter.ts
new file mode 100644
index 0000000..c5323a2
--- /dev/null
+++ b/packages/cache/src/scheme/adapters/cache-adapter.ts
@@ -0,0 +1,63 @@
+/**
+ * Base class for all adapters
+ * All Methods from CacheAdapter are also available on every class extends
+ */
+
+export interface CacheAdapter {
+ /**
+ * Gets the resource to adapter
+ */
+
+ get(id: string): any | Promise;
+ get(id: string, guild: string): string | Promise;
+
+ /**
+ * Sets the resource to adapter
+ */
+
+ set(id: string, data: any): void | Promise;
+ set(id: string, guild: string, data: any): void | Promise;
+
+ /**
+ * Get the items of a relationship
+ */
+
+ items(to?: string): any[] | Promise;
+
+ /**
+ * Count how many resources there are in the relationships
+ */
+
+ count(to: string): number | Promise;
+
+ /**
+ * Removes the adapter resource
+ */
+
+ remove(id: string): void | Promise;
+ remove(id: string, guild: string): void | Promise;
+
+ /**
+ * Check if the resource is in the relationships
+ */
+
+ contains(to: string, id: string): boolean | Promise;
+
+ /**
+ * Gets the resource relationships
+ */
+
+ getToRelationship(to: string): string[] | Promise;
+
+ /**
+ * Adds the resource to relationships
+ */
+
+ addToRelationship(to: string, id: string): void | Promise;
+
+ /**
+ * Removes the relationship resource
+ */
+
+ removeToRelationship(to: string, id: string): void | Promise;
+}
diff --git a/packages/cache/src/adapters/memory-cache-adapter.ts b/packages/cache/src/scheme/adapters/memory-cache-adapter.ts
similarity index 62%
rename from packages/cache/src/adapters/memory-cache-adapter.ts
rename to packages/cache/src/scheme/adapters/memory-cache-adapter.ts
index 24dd640..fabb5ce 100644
--- a/packages/cache/src/adapters/memory-cache-adapter.ts
+++ b/packages/cache/src/scheme/adapters/memory-cache-adapter.ts
@@ -1,20 +1,24 @@
-import type { CacheAdapter } from './cache-adapter';
+/**
+ * refactor
+ */
-interface Options {
- expire?: number;
-}
+import type { CacheAdapter } from './cache-adapter';
+import type { MemoryOptions, MO } from '../../types';
+
+import { Options } from '../../utils/options';
export class MemoryCacheAdapter implements CacheAdapter {
- private static readonly DEFAULTS = {
+ static readonly DEFAULTS = {
+ expire: 3600000,
};
- private readonly relationships = new Map();
- private readonly storage = new Map();
+ readonly relationships = new Map();
+ readonly storage = new Map();
- readonly options: Options;
+ readonly options: MO;
- constructor(options?: Options) {
- this.options = Object.assign(MemoryCacheAdapter.DEFAULTS, options);
+ constructor(options?: MemoryOptions) {
+ this.options = Options({}, MemoryCacheAdapter.DEFAULTS, options);
}
/**
@@ -39,14 +43,40 @@ export class MemoryCacheAdapter implements CacheAdapter {
* @inheritDoc
*/
- set(id: string, data: any, expire = this.options.expire): void {
+ set(id: string, data: any): void {
+ const expire = this.options.expire;
+
if (expire) {
- this.storage.set(id, { data: JSON.stringify(data), expire: Date.now() + expire });
+ this.storage.set(id, {
+ data: JSON.stringify(data),
+ expire: Date.now() + expire,
+ });
} else {
this.storage.set(id, { data: JSON.stringify(data) });
}
}
+ /**
+ * @inheritDoc
+ */
+
+ items(to: string): any[] {
+ const array: unknown[] = [];
+ let data = this.getToRelationship(to);
+
+ data = data.map(id => `${to}.${id}`);
+
+ for (const key of data) {
+ const content = this.get(key);
+
+ if (content) {
+ array.push(content);
+ }
+ }
+
+ return array;
+ }
+
/**
* @inheritDoc
*/
diff --git a/packages/cache/src/scheme/adapters/redis-cache-adapter.ts b/packages/cache/src/scheme/adapters/redis-cache-adapter.ts
new file mode 100644
index 0000000..8a87193
--- /dev/null
+++ b/packages/cache/src/scheme/adapters/redis-cache-adapter.ts
@@ -0,0 +1,195 @@
+/**
+ * refactor
+ */
+
+import type { CacheAdapter } from './cache-adapter';
+
+import type { RedisOptions } from 'ioredis';
+import type Redis from 'ioredis';
+import IORedis from 'ioredis';
+
+interface BaseOptions {
+ namespace: string;
+ expire?: number;
+}
+
+interface BuildOptions extends BaseOptions, RedisOptions {}
+
+interface ClientOptions extends BaseOptions {
+ client: Redis;
+}
+
+type Options = BuildOptions | ClientOptions;
+
+export class RedisCacheAdapter implements CacheAdapter {
+ static readonly DEFAULTS = {
+ namespace: 'biscuitland',
+ };
+
+ readonly options: Options;
+
+ readonly client: Redis;
+
+ constructor(options?: Options) {
+ this.options = Object.assign(RedisCacheAdapter.DEFAULTS, options);
+
+ if ((this.options as ClientOptions).client) {
+ this.client = (this.options as ClientOptions).client;
+ } else {
+ const { ...redisOpt } = this.options as BuildOptions;
+ this.client = new IORedis(redisOpt);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async get(id: string): Promise {
+ const data = await this.client.get(this.build(id));
+
+ if (!data) {
+ return null;
+ }
+
+ return JSON.parse(data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async set(id: string, data: unknown): Promise {
+ const expire = this.options.expire;
+
+ if (expire) {
+ await this.client.set(
+ this.build(id),
+ JSON.stringify(data),
+ 'EX',
+ expire
+ );
+ } else {
+ await this.client.set(this.build(id), JSON.stringify(data));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async items(to: string): Promise {
+ const array: unknown[] = [];
+
+ let data = await this.getToRelationship(to);
+ data = data.map(id => this.build(`${to}.${id}`));
+
+ if (data && data.length > 0) {
+ const items = await this.client.mget(data);
+
+ for (const item of items) {
+ if (item) {
+ array.push(JSON.parse(item));
+ }
+ }
+ }
+
+ return array;
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async count(to: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.client.scard(this.build(to), (err, result) => {
+ if (err) {
+ return reject(err);
+ }
+
+ return resolve(result || 0);
+ });
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async remove(id: string): Promise {
+ await this.client.del(this.build(id));
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async contains(to: string, id: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.client.sismember(this.build(to), id, (err, result) => {
+ if (err) {
+ return reject(err);
+ }
+
+ return resolve(result === 1);
+ });
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async getToRelationship(to: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.client.smembers(this.build(to), (err, result) => {
+ if (err) {
+ reject(err);
+ }
+
+ resolve(result || []);
+ });
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async addToRelationship(to: string, id: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.client.sadd(this.build(to), id, err => {
+ if (err) {
+ reject(err);
+ }
+
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ async removeToRelationship(to: string, id: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.client.srem(this.build(to), id, err => {
+ if (err) {
+ reject(err);
+ }
+
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+
+ protected build(id: string): string {
+ return `${this.options.namespace}:${id}`;
+ }
+}
diff --git a/packages/cache/src/scheme/transporters/base-transporter.ts b/packages/cache/src/scheme/transporters/base-transporter.ts
new file mode 100644
index 0000000..674185f
--- /dev/null
+++ b/packages/cache/src/scheme/transporters/base-transporter.ts
@@ -0,0 +1,7 @@
+/**
+ * future update
+ */
+
+export interface BaseTransporter {
+ //
+}
diff --git a/packages/cache/src/scheme/transporters/redis-transporter.ts b/packages/cache/src/scheme/transporters/redis-transporter.ts
new file mode 100644
index 0000000..1b41bcf
--- /dev/null
+++ b/packages/cache/src/scheme/transporters/redis-transporter.ts
@@ -0,0 +1,3 @@
+import type { BaseTransporter } from './base-transporter';
+
+export class RedisTransporter implements BaseTransporter {}
diff --git a/packages/cache/src/scheme/transporters/tcp-transporter.ts b/packages/cache/src/scheme/transporters/tcp-transporter.ts
new file mode 100644
index 0000000..f8851c3
--- /dev/null
+++ b/packages/cache/src/scheme/transporters/tcp-transporter.ts
@@ -0,0 +1,3 @@
+import type { BaseTransporter } from './base-transporter';
+
+export class TcpTransporter implements BaseTransporter {}
diff --git a/packages/cache/src/types.ts b/packages/cache/src/types.ts
new file mode 100644
index 0000000..179491b
--- /dev/null
+++ b/packages/cache/src/types.ts
@@ -0,0 +1,55 @@
+import type { Cache } from './cache';
+import type { CacheAdapter } from './scheme/adapters/cache-adapter';
+
+import type { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
+
+//
+
+export type CacheOptions = Pick<
+ CO,
+ Exclude
+> &
+ Partial;
+
+export interface CO {
+ /**
+ * Adapter to be used for storing resources
+ * @default MemoryCacheAdapter
+ */
+
+ adapter: CacheAdapter;
+}
+
+//
+
+export type MemoryOptions = Pick<
+ MO,
+ Exclude
+> &
+ Partial;
+
+export interface MO {
+ /**
+ * Time the resource will be stored
+ * @default 3600000
+ */
+
+ expire: number;
+}
+
+//
+
+export type RedisOptions = Pick<
+ RO,
+ Exclude
+> &
+ Partial;
+
+export interface RO {
+ /**
+ * Time the resource will be stored
+ * @default 300
+ */
+
+ expire: number;
+}
diff --git a/packages/cache/src/utils/options.ts b/packages/cache/src/utils/options.ts
new file mode 100644
index 0000000..17ca5da
--- /dev/null
+++ b/packages/cache/src/utils/options.ts
@@ -0,0 +1,48 @@
+/**
+ * Needs to be moved to a common location
+ * refactor
+ */
+
+const isPlainObject = (value: any) => {
+ return (
+ (value !== null &&
+ typeof value === 'object' &&
+ typeof value.constructor === 'function' &&
+ // eslint-disable-next-line no-prototype-builtins
+ (value.constructor.prototype.hasOwnProperty('isPrototypeOf') ||
+ Object.getPrototypeOf(value.constructor.prototype) === null)) ||
+ (value && Object.getPrototypeOf(value) === null)
+ );
+};
+
+const isObject = (o: any) => {
+ return !!o && typeof o === 'object' && !Array.isArray(o);
+};
+
+export const Options = (defaults: any, ...options: any[]): any => {
+ if (!options.length) {
+ return defaults;
+ }
+
+ const source = options.shift();
+
+ if (isObject(defaults) && isPlainObject(source)) {
+ Object.entries(source).forEach(([key, value]) => {
+ if (typeof value === 'undefined') {
+ return;
+ }
+
+ if (isPlainObject(value)) {
+ if (!(key in defaults)) {
+ Object.assign(defaults, { [key]: {} });
+ }
+
+ Options(defaults[key], value);
+ } else {
+ Object.assign(defaults, { [key]: value });
+ }
+ });
+ }
+
+ return Options(defaults, ...options);
+};