mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-04 22:16:08 +00:00
feat: limitedMemoryAdapter
This commit is contained in:
parent
aa26766b38
commit
c573ca9909
1
src/cache/adapters/index.ts
vendored
1
src/cache/adapters/index.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
export * from './default';
|
export * from './default';
|
||||||
|
export * from './limited';
|
||||||
export * from './redis';
|
export * from './redis';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './workeradapter';
|
export * from './workeradapter';
|
||||||
|
249
src/cache/adapters/limited.ts
vendored
Normal file
249
src/cache/adapters/limited.ts
vendored
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import { LimitedCollection } from '../..';
|
||||||
|
import { MergeOptions, type MakeRequired } from '../../common';
|
||||||
|
import type { Adapter } from './types';
|
||||||
|
|
||||||
|
export interface ResourceLimitedMemoryAdapter {
|
||||||
|
expire?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LimitedMemoryAdapterOptions {
|
||||||
|
default?: ResourceLimitedMemoryAdapter;
|
||||||
|
guild?: ResourceLimitedMemoryAdapter;
|
||||||
|
user?: ResourceLimitedMemoryAdapter;
|
||||||
|
member?: ResourceLimitedMemoryAdapter;
|
||||||
|
voice_state?: ResourceLimitedMemoryAdapter;
|
||||||
|
channel?: ResourceLimitedMemoryAdapter;
|
||||||
|
emoji?: ResourceLimitedMemoryAdapter;
|
||||||
|
overwrite?: ResourceLimitedMemoryAdapter;
|
||||||
|
presence?: ResourceLimitedMemoryAdapter;
|
||||||
|
role?: ResourceLimitedMemoryAdapter;
|
||||||
|
stage_instance?: ResourceLimitedMemoryAdapter;
|
||||||
|
sticker?: ResourceLimitedMemoryAdapter;
|
||||||
|
thread?: ResourceLimitedMemoryAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LimitedMemoryAdapter implements Adapter {
|
||||||
|
isAsync = false;
|
||||||
|
|
||||||
|
readonly storage = new Map<string, LimitedCollection<string, string>>();
|
||||||
|
readonly relationships = new Map<string, Map<string, string[]>>();
|
||||||
|
|
||||||
|
options: MakeRequired<LimitedMemoryAdapterOptions, 'default'>;
|
||||||
|
|
||||||
|
constructor(options: LimitedMemoryAdapterOptions) {
|
||||||
|
this.options = MergeOptions(
|
||||||
|
{
|
||||||
|
default: {
|
||||||
|
expire: undefined,
|
||||||
|
limit: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
} satisfies LimitedMemoryAdapterOptions,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(query: string, keys?: false): any[];
|
||||||
|
scan(query: string, keys: true): string[];
|
||||||
|
scan(query: string, keys = false) {
|
||||||
|
const values = [];
|
||||||
|
const sq = query.split('.');
|
||||||
|
for (const iterator of [...this.storage.values()].flatMap(x => x.entries()))
|
||||||
|
for (const [key, value] of iterator) {
|
||||||
|
if (key.split('.').every((value, i) => (sq[i] === '*' ? !!value : sq[i] === value))) {
|
||||||
|
values.push(keys ? key : JSON.parse(value.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(keys: string): any;
|
||||||
|
get(keys: string[]): any[];
|
||||||
|
get(keys: string | string[]) {
|
||||||
|
if (!Array.isArray(keys)) {
|
||||||
|
const data = this.storage.get(keys.split('.')[0])?.get(keys);
|
||||||
|
return data ? JSON.parse(data) : null;
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
.map(x => {
|
||||||
|
const data = this.storage.get(x.split('.')[0])?.get(x);
|
||||||
|
return data ? JSON.parse(data) : null;
|
||||||
|
})
|
||||||
|
.filter(x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
private __set(key: string, data: any) {
|
||||||
|
const namespace = key.split('.')[0];
|
||||||
|
const self = this;
|
||||||
|
if (!this.storage.has(namespace)) {
|
||||||
|
this.storage.set(
|
||||||
|
namespace,
|
||||||
|
new LimitedCollection({
|
||||||
|
expire: this.options[namespace as keyof LimitedMemoryAdapterOptions]?.expire ?? this.options.default.expire,
|
||||||
|
limit: this.options[namespace as keyof LimitedMemoryAdapterOptions]?.limit ?? this.options.default.limit,
|
||||||
|
resetOnDemand: true,
|
||||||
|
onDelete(k) {
|
||||||
|
const relation = self.relationships.get(namespace);
|
||||||
|
if (relation) {
|
||||||
|
switch (namespace) {
|
||||||
|
case 'guild':
|
||||||
|
case 'user':
|
||||||
|
self.removeToRelationship(namespace, k.split('.')[1]);
|
||||||
|
break;
|
||||||
|
case 'member':
|
||||||
|
case 'voice_state':
|
||||||
|
{
|
||||||
|
const split = k.split('.');
|
||||||
|
self.removeToRelationship(`${namespace}.${split[1]}`, split[2]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'channel':
|
||||||
|
case 'emoji':
|
||||||
|
case 'overwrite':
|
||||||
|
case 'presence':
|
||||||
|
case 'role':
|
||||||
|
case 'stage_instance':
|
||||||
|
case 'sticker':
|
||||||
|
case 'thread':
|
||||||
|
{
|
||||||
|
const split = k.split('.');
|
||||||
|
for (const i of relation.entries()) {
|
||||||
|
if (i[1].includes(split[1])) {
|
||||||
|
self.removeToRelationship(i[0], split[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storage.get(namespace)!.set(key, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
set(keys: string, data: any): void;
|
||||||
|
set(keys: [string, any][]): void;
|
||||||
|
set(keys: string | [string, any][], data?: any): void {
|
||||||
|
if (Array.isArray(keys)) {
|
||||||
|
for (const [key, value] of keys) {
|
||||||
|
this.__set(key, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.__set(keys, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patch(updateOnly: boolean, keys: string, data: any): void;
|
||||||
|
patch(updateOnly: boolean, keys: [string, any][]): void;
|
||||||
|
patch(updateOnly: boolean, keys: string | [string, any][], data?: any): void {
|
||||||
|
if (Array.isArray(keys)) {
|
||||||
|
for (const [key, value] of keys) {
|
||||||
|
const oldData = this.get(key);
|
||||||
|
if (updateOnly && !oldData) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.__set(key, Array.isArray(value) ? value : { ...(oldData ?? {}), ...value });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const oldData = this.get(keys);
|
||||||
|
if (updateOnly && !oldData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.__set(keys, Array.isArray(data) ? data : { ...(oldData ?? {}), ...data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values(to: string) {
|
||||||
|
const array: any[] = [];
|
||||||
|
const data = this.keys(to);
|
||||||
|
|
||||||
|
for (const key of data) {
|
||||||
|
const content = this.get(key);
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
array.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys(to: string) {
|
||||||
|
return this.getToRelationship(to).map(id => `${to}.${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
count(to: string) {
|
||||||
|
return this.getToRelationship(to).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(keys: string): void;
|
||||||
|
remove(keys: string[]): void;
|
||||||
|
remove(keys: string | string[]) {
|
||||||
|
for (const i of Array.isArray(keys) ? keys : [keys]) {
|
||||||
|
this.storage.get(i.split('.')[0])?.delete(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flush(): void {
|
||||||
|
this.storage.clear();
|
||||||
|
this.relationships.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
contains(to: string, keys: string): boolean {
|
||||||
|
return this.getToRelationship(to).includes(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
getToRelationship(to: string) {
|
||||||
|
const key = to.split('.')[0];
|
||||||
|
if (!this.relationships.has(key)) this.relationships.set(key, new Map<string, string[]>());
|
||||||
|
const relation = this.relationships.get(key)!;
|
||||||
|
if (!relation.has(to)) {
|
||||||
|
relation.set(to, []);
|
||||||
|
}
|
||||||
|
return relation!.get(to)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkAddToRelationShip(data: Record<string, string[]>) {
|
||||||
|
for (const i in data) {
|
||||||
|
this.addToRelationship(i, data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addToRelationship(to: string, keys: string | string[]) {
|
||||||
|
const key = to.split('.')[0];
|
||||||
|
if (!this.relationships.has(key)) {
|
||||||
|
this.relationships.set(key, new Map<string, string[]>());
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = this.getToRelationship(to);
|
||||||
|
|
||||||
|
for (const key of Array.isArray(keys) ? keys : [keys]) {
|
||||||
|
if (!data.includes(key)) {
|
||||||
|
data.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeToRelationship(to: string, keys: string | string[]) {
|
||||||
|
const data = this.getToRelationship(to);
|
||||||
|
if (data) {
|
||||||
|
for (const key of Array.isArray(keys) ? keys : [keys]) {
|
||||||
|
const idx = data.indexOf(key);
|
||||||
|
if (idx !== -1) {
|
||||||
|
data.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRelationship(to: string | string[]) {
|
||||||
|
for (const i of Array.isArray(to) ? to : [to]) {
|
||||||
|
this.relationships.delete(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/cache/index.ts
vendored
4
src/cache/index.ts
vendored
@ -362,8 +362,8 @@ export class Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.adapter.patch(false, allData);
|
|
||||||
await this.adapter.bulkAddToRelationShip(relationshipsData);
|
await this.adapter.bulkAddToRelationShip(relationshipsData);
|
||||||
|
await this.adapter.patch(false, allData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkSet(
|
async bulkSet(
|
||||||
@ -449,8 +449,8 @@ export class Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.adapter.set(allData);
|
|
||||||
await this.adapter.bulkAddToRelationShip(relationshipsData);
|
await this.adapter.bulkAddToRelationShip(relationshipsData);
|
||||||
|
await this.adapter.set(allData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onPacket(event: GatewayDispatchPayload) {
|
async onPacket(event: GatewayDispatchPayload) {
|
||||||
|
2
src/cache/resources/members.ts
vendored
2
src/cache/resources/members.ts
vendored
@ -39,7 +39,7 @@ export class Members extends GuildBasedResource {
|
|||||||
members
|
members
|
||||||
.map(rawMember => {
|
.map(rawMember => {
|
||||||
const user = users.find(x => x.id === rawMember.id);
|
const user = users.find(x => x.id === rawMember.id);
|
||||||
return user ? new GuildMember(this.client, rawMember, user, guild) : undefined;
|
return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
|
||||||
})
|
})
|
||||||
.filter(Boolean) as GuildMember[],
|
.filter(Boolean) as GuildMember[],
|
||||||
),
|
),
|
||||||
|
2
src/cache/resources/stage-instances.ts
vendored
2
src/cache/resources/stage-instances.ts
vendored
@ -2,5 +2,5 @@ import type { APIStageInstance } from 'discord-api-types/v10';
|
|||||||
import { GuildRelatedResource } from './default/guild-related';
|
import { GuildRelatedResource } from './default/guild-related';
|
||||||
|
|
||||||
export class StageInstances extends GuildRelatedResource<APIStageInstance> {
|
export class StageInstances extends GuildRelatedResource<APIStageInstance> {
|
||||||
namespace = 'stage_instances';
|
namespace = 'stage_instance';
|
||||||
}
|
}
|
||||||
|
@ -194,9 +194,10 @@ export class Collection<K, V> extends Map<K, V> {
|
|||||||
|
|
||||||
type LimitedCollectionData<V> = { expire: number; expireOn: number; value: V };
|
type LimitedCollectionData<V> = { expire: number; expireOn: number; value: V };
|
||||||
|
|
||||||
export interface LimitedCollectionOptions {
|
export interface LimitedCollectionOptions<K> {
|
||||||
limit: number;
|
limit: number;
|
||||||
expire: number;
|
expire: number;
|
||||||
|
onDelete?: (key: K) => void;
|
||||||
resetOnDemand: boolean;
|
resetOnDemand: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +215,7 @@ export interface LimitedCollectionOptions {
|
|||||||
* console.log(mappedArray); // Output: ['1: one', '2: two', '3: three']
|
* console.log(mappedArray); // Output: ['1: one', '2: two', '3: three']
|
||||||
*/
|
*/
|
||||||
export class LimitedCollection<K, V> {
|
export class LimitedCollection<K, V> {
|
||||||
static readonly default: LimitedCollectionOptions = {
|
static readonly default: LimitedCollectionOptions<any> = {
|
||||||
resetOnDemand: false,
|
resetOnDemand: false,
|
||||||
limit: Number.POSITIVE_INFINITY,
|
limit: Number.POSITIVE_INFINITY,
|
||||||
expire: 0,
|
expire: 0,
|
||||||
@ -222,10 +223,10 @@ export class LimitedCollection<K, V> {
|
|||||||
|
|
||||||
private readonly data = new Map<K, LimitedCollectionData<V>>();
|
private readonly data = new Map<K, LimitedCollectionData<V>>();
|
||||||
|
|
||||||
private readonly options: LimitedCollectionOptions;
|
private readonly options: LimitedCollectionOptions<K>;
|
||||||
private timeout: NodeJS.Timeout | undefined = undefined;
|
private timeout: NodeJS.Timeout | undefined = undefined;
|
||||||
|
|
||||||
constructor(options: Partial<LimitedCollectionOptions> = {}) {
|
constructor(options: Partial<LimitedCollectionOptions<K>> = {}) {
|
||||||
this.options = MergeOptions(LimitedCollection.default, options);
|
this.options = MergeOptions(LimitedCollection.default, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +259,8 @@ export class LimitedCollection<K, V> {
|
|||||||
if (this.size > this.options.limit) {
|
if (this.size > this.options.limit) {
|
||||||
const iter = this.data.keys();
|
const iter = this.data.keys();
|
||||||
while (this.size > this.options.limit) {
|
while (this.size > this.options.limit) {
|
||||||
this.delete(iter.next().value);
|
const keyValue = iter.next().value;
|
||||||
|
this.delete(keyValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +298,7 @@ export class LimitedCollection<K, V> {
|
|||||||
if (this.options.resetOnDemand && data && data.expire !== -1) {
|
if (this.options.resetOnDemand && data && data.expire !== -1) {
|
||||||
const oldExpireOn = data.expireOn;
|
const oldExpireOn = data.expireOn;
|
||||||
data.expireOn = Date.now() + data.expire;
|
data.expireOn = Date.now() + data.expire;
|
||||||
if (this.closer!.expireOn === oldExpireOn) {
|
if (this.closer?.expireOn === oldExpireOn) {
|
||||||
this.resetTimeout();
|
this.resetTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,7 +331,8 @@ export class LimitedCollection<K, V> {
|
|||||||
*/
|
*/
|
||||||
delete(key: K) {
|
delete(key: K) {
|
||||||
const value = this.raw(key);
|
const value = this.raw(key);
|
||||||
if (value && value.expireOn === this.closer!.expireOn) setImmediate(() => this.resetTimeout());
|
if (value && value.expireOn === this.closer?.expireOn) setImmediate(() => this.resetTimeout());
|
||||||
|
this.options.onDelete?.(key);
|
||||||
return this.data.delete(key);
|
return this.data.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,12 +401,30 @@ export class LimitedCollection<K, V> {
|
|||||||
}, expireOn - Date.now());
|
}, expireOn - Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keys() {
|
||||||
|
return this.data.keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
values() {
|
||||||
|
return this.data.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries() {
|
||||||
|
return this.data.entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.data.clear();
|
||||||
|
this.resetTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
private clearExpired() {
|
private clearExpired() {
|
||||||
for (const [key, value] of this.data) {
|
for (const [key, value] of this.data) {
|
||||||
if (value.expireOn === -1) {
|
if (value.expireOn === -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Date.now() >= value.expireOn) {
|
if (Date.now() >= value.expireOn) {
|
||||||
|
this.options.onDelete?.(key);
|
||||||
this.data.delete(key);
|
this.data.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user