cache preview version alpha

This commit is contained in:
Dragurimu 2022-09-13 12:09:09 +08:00
parent 04b856818c
commit 0daec51769
26 changed files with 1648 additions and 518 deletions

View File

@ -16,6 +16,8 @@ ignorePatterns:
- 'coverage'
- '**/*.js'
- '**/*.d.ts'
- '__tests__'
- '__test__'
parser: '@typescript-eslint/parser'

View File

@ -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
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
<img align="right" src="https://raw.githubusercontent.com/oasisjs/biscuit/main/assets/icon.svg" alt="biscuit"/>
## 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(<payloads>);
};
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/)

View File

@ -1,50 +0,0 @@
export interface CacheAdapter {
/**
* @inheritDoc
*/
get(id: string): any | Promise<any>;
get(id: string, guild?: string): string | Promise<string>;
/**
* @inheritDoc
*/
set(id: string, data: any, expire?: number): void | Promise<void>;
/**
* @inheritDoc
*/
count(to: string): number | Promise<number>;
/**
* @inheritDoc
*/
remove(id: string): void | Promise<void>;
/**
* @inheritDoc
*/
contains(to: string, id: string): boolean | Promise<boolean>;
/**
* @inheritDoc
*/
getToRelationship(to: string): string[] | Promise<string[]>;
/**
* @inheritDoc
*/
addToRelationship(to: string, id: string): void | Promise<void>;
/**
* @inheritDoc
*/
removeToRelationship(to: string, id: string): void | Promise<void>;
}

View File

@ -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<any> {
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<void> {
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<number> {
throw new Error('Method not implemented.');
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.client.del(this.composite(id));
}
/**
* @inheritDoc
*/
async contains(_to: string, _id: string): Promise<boolean> {
throw new Error('Method not implemented.');
}
/**
* @inheritDoc
*/
async getToRelationship(_to: string): Promise<string[]> {
throw new Error('Method not implemented.');
}
/**
* @inheritDoc
*/
async addToRelationship(_to: string, _id: string): Promise<void> {
throw new Error('Method not implemented.');
}
/**
* @inheritDoc
*/
async removeToRelationship(_to: string, _id: string): Promise<void> {
throw new Error('Method not implemented.');
}
/**
* @inheritDoc
*/
composite(id: string): string {
return `${this.options.namespace}:${id}`;
}
}

View File

@ -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<any> | 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<any> | 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

View File

@ -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';

View File

@ -1,63 +1,117 @@
import type { CacheAdapter } from '../adapters/cache-adapter';
export class BaseResource {
namespace = 'base';
adapter!: CacheAdapter; // replace
/* eslint-disable @typescript-eslint/naming-convention */
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
/**
* @inheritDoc
* Base class for all resources
* All Methods from BaseResource are also available on every class extends
*/
class Base<T> {
/**
* Resource name
*/
#namespace = 'base';
/**
* 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<number> {
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<boolean> {
return await this.adapter.contains(this.hashId(to), id);
async contains(
id: string,
guild: string = this.parent as string
): Promise<boolean> {
return await this.#adapter.contains(this.hashId(guild), id);
}
/**
* @inheritDoc
* Gets the resource relationships
*/
async getToRelationship(to: string): Promise<string[]> {
return await this.adapter.getToRelationship(this.hashId(to));
async getToRelationship(
id: string = this.parent as string
): Promise<string[]> {
return await this.#adapter.getToRelationship(this.hashId(id));
}
/**
* @inheritDoc
* Adds the resource to relationships
*/
async addToRelationship(to: string, id: string): Promise<void> {
await this.adapter.addToRelationship(this.hashId(to), id);
async addToRelationship(
id: string,
guild: string = this.parent as string
): Promise<void> {
await this.#adapter.addToRelationship(this.hashId(guild), id);
}
/**
* @inheritDoc // to-do replace
* Removes the relationship resource
*/
async removeToRelationship(to: string, id: string): Promise<void> {
await this.adapter.removeToRelationship(this.hashId(to), id);
async removeToRelationship(
id: string,
guild: string = this.parent as string
): Promise<void> {
await this.#adapter.removeToRelationship(this.hashId(guild), id);
}
/**
* @inheritDoc
* Construct an id consisting of namespace.id
*/
hashId(id: string): string {
return `${this.namespace}.${id}`;
protected hashId(id: string): string {
return `${this.#namespace}.${id}`;
}
}
/**
* @inheritDoc
*/
hashGuildId(id: string, guild: string): string {
return `${this.namespace}.${guild}.${id}`;
}
}
export const BaseResource = Base as new <T>(
data: string,
adapter: CacheAdapter
) => Base<T> & T;

View File

@ -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<DiscordChannel> {
#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<DiscordChannel | null> {
const kv = await this.adapter.get(this.hashId(id));
async get(id: string): Promise<ChannelResource | null> {
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<void> {
async set(id: string, data: any): Promise<void> {
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<ChannelResource[]> {
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<number> {
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<void> {
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<boolean> {
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<string[]> {
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<void> {
await this.adapter.addToRelationship(this.namespace, id);
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -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<DiscordEmoji> {
#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<DiscordEmoji | null> {
const kv = await this.adapter.get(this.hashGuildId(id, guild));
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildEmojiResource | null> {
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<void> {
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<void> {
await this.adapter.remove(this.hashGuildId(id, guild));
async items(to: string): Promise<GuildEmojiResource[]> {
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<void> {
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}`;
}
}

View File

@ -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<DiscordMember> {
#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<GuildMemberResource | null> {
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<void> {
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<void> {
await this.adapter.remove(this.hashGuildId(id, guild));
async items(to: string): Promise<GuildMemberResource[]> {
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<void> {
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}`;
}
}

View File

@ -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<DiscordGuild> {
#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<DiscordGuild | null> {
const kv = await this.adapter.get(this.hashId(id));
async get(id: string): Promise<GuildResource | null> {
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<void> {
async set(id: string, data: any): Promise<void> {
if (data.channels) {
const channels: (Promise<any> | 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<any> | 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<any> | 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<any> | 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<any> | 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<any>[] = [];
const voices: unknown[] = [];
for (const voice of data.voice_states) {
if (!voice.guild_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<GuildResource[]> {
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<number> {
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<void> {
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<boolean> {
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<string[]> {
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<void> {
await this.adapter.addToRelationship(this.namespace, id);
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getEmojis(): Promise<GuildEmojiResource[]> {
return await this.#emojis.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getMembers(): Promise<GuildMemberResource[]> {
return await this.#members.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getRoles(): Promise<GuildRoleResource[]> {
return await this.#roles.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getStickers(): Promise<GuildStickerResource[]> {
return await this.#stickers.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getVoiceStates(): Promise<GuildVoiceResource[]> {
return await this.#voices.items(this.parent as string);
}
}

View File

@ -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<DiscordRole> {
#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<DiscordRole | null> {
const kv = await this.adapter.get(this.hashGuildId(id, guild));
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildRoleResource | null> {
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<void> {
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<void> {
await this.adapter.remove(this.hashGuildId(id, guild));
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildRoleResource[]> {
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<void> {
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}`;
}
}

View File

@ -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<DiscordSticker> {
#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<DiscordSticker | null> {
const kv = await this.adapter.get(this.hashGuildId(id, guild));
async get(id: string, guild: string): Promise<GuildStickerResource | null> {
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<void> {
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<void> {
await this.adapter.remove(this.hashGuildId(id, guild));
async items(to: string): Promise<GuildStickerResource[]> {
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<void> {
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}`;
}
}

View File

@ -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<DiscordVoiceState> {
#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<GuildVoiceResource | null> {
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<void> {
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<GuildVoiceResource[]> {
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<void> {
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}`;
}
}

View File

@ -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';

View File

@ -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<DiscordPresenceUpdate> {
#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<PresenceResource | null> {
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<void> {
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<PresenceResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => new PresenceResource(this.#adapter, dt));
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.removeToRelationship(id);
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -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<DiscordUser> {
#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<DiscordUser | null> {
const kv = await this.adapter.get(this.hashId(id));
async get(id: string): Promise<UserResource | null> {
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<void> {
await this.adapter.set(this.hashId(id), data, expire);
async set(id: string, data: any): Promise<void> {
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id);
await this.#adapter.set(this.hashId(id), data);
}
/**
* @inheritDoc
*/
async items(): Promise<UserResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => new UserResource(this.#adapter, dt));
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
@ -41,6 +84,39 @@ export class UserResource extends BaseResource {
*/
async remove(id: string): Promise<void> {
await this.adapter.remove(this.hashId(id));
await this.removeToRelationship(id);
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -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<DiscordVoiceState | null> {
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<void> {
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<void> {
await this.adapter.remove(this.hashGuildId(id, guild));
}
}

View File

@ -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<any>;
get(id: string, guild: string): string | Promise<string>;
/**
* Sets the resource to adapter
*/
set(id: string, data: any): void | Promise<void>;
set(id: string, guild: string, data: any): void | Promise<void>;
/**
* Get the items of a relationship
*/
items(to?: string): any[] | Promise<any[]>;
/**
* Count how many resources there are in the relationships
*/
count(to: string): number | Promise<number>;
/**
* Removes the adapter resource
*/
remove(id: string): void | Promise<void>;
remove(id: string, guild: string): void | Promise<void>;
/**
* Check if the resource is in the relationships
*/
contains(to: string, id: string): boolean | Promise<boolean>;
/**
* Gets the resource relationships
*/
getToRelationship(to: string): string[] | Promise<string[]>;
/**
* Adds the resource to relationships
*/
addToRelationship(to: string, id: string): void | Promise<void>;
/**
* Removes the relationship resource
*/
removeToRelationship(to: string, id: string): void | Promise<void>;
}

View File

@ -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<string, string[]>();
private readonly storage = new Map<string, { data: any; expire?: number }>();
readonly relationships = new Map<string, string[]>();
readonly storage = new Map<string, { data: any; expire?: number }>();
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
*/

View File

@ -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<any> {
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<void> {
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<any[]> {
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<number> {
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<void> {
await this.client.del(this.build(id));
}
/**
* @inheritDoc
*/
async contains(to: string, id: string): Promise<boolean> {
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<string[]> {
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<void> {
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<void> {
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}`;
}
}

View File

@ -0,0 +1,7 @@
/**
* future update
*/
export interface BaseTransporter {
//
}

View File

@ -0,0 +1,3 @@
import type { BaseTransporter } from './base-transporter';
export class RedisTransporter implements BaseTransporter {}

View File

@ -0,0 +1,3 @@
import type { BaseTransporter } from './base-transporter';
export class TcpTransporter implements BaseTransporter {}

55
packages/cache/src/types.ts vendored Normal file
View File

@ -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<keyof CO, keyof typeof Cache.DEFAULTS>
> &
Partial<CO>;
export interface CO {
/**
* Adapter to be used for storing resources
* @default MemoryCacheAdapter
*/
adapter: CacheAdapter;
}
//
export type MemoryOptions = Pick<
MO,
Exclude<keyof MO, keyof typeof MemoryCacheAdapter.DEFAULTS>
> &
Partial<MO>;
export interface MO {
/**
* Time the resource will be stored
* @default 3600000
*/
expire: number;
}
//
export type RedisOptions = Pick<
RO,
Exclude<keyof RO, keyof typeof MemoryCacheAdapter.DEFAULTS>
> &
Partial<RO>;
export interface RO {
/**
* Time the resource will be stored
* @default 300
*/
expire: number;
}

48
packages/cache/src/utils/options.ts vendored Normal file
View File

@ -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);
};