From 7a1ff7ef0cb96145948d766ac0494c9a9192e640 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 22 May 2025 02:45:38 +0700 Subject: [PATCH] feat(lib/plugins): add API for storages --- lib/plugins/src/_internal.ts | 60 ++++++++++++++++++++++++++++-------- lib/plugins/src/constants.ts | 2 ++ lib/plugins/src/types.ts | 33 ++++++++++++++------ 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/lib/plugins/src/_internal.ts b/lib/plugins/src/_internal.ts index fb2de64..ce84876 100644 --- a/lib/plugins/src/_internal.ts +++ b/lib/plugins/src/_internal.ts @@ -1,16 +1,18 @@ +import { getStorage, type StorageOptions } from '@revenge-mod/storage' import { allSettled, sleepReject } from '@revenge-mod/utils/promises' import { _uapi } from './apis' -import { PluginFlags as Flag, PluginStatus as Status } from './constants' +import { PluginFlags as Flag, PluginsStorageDirectory, PluginStatus as Status } from './constants' +import type { AnyObject } from '@revenge-mod/utils/types' import type { InitPluginApi, Plugin, PluginApi, PluginCleanup, - PluginLifecycles, PluginManifest, + PluginOptions, PreInitPluginApi, } from './types' @@ -25,15 +27,26 @@ export const _metas = new Map< ] >() -export function registerPlugin(manifest: PluginManifest, lifecycles: PluginLifecycles, flags: number, iflags: number) { +export function registerPlugin( + manifest: PluginManifest, + options: PluginOptions, + flags: number, + iflags: number, +) { // TODO(plugins): verify plugin manifest if (_plugins.has(manifest.id)) throw new Error(`Plugin with ID "${manifest.id}" already registered`) const plugin: InternalPlugin = { - cleanups: [], + _c: [], + _s: options.storage, errors: [], manifest, - lifecycles, + lifecycles: { + preInit: options.preInit, + init: options.init, + start: options.start, + stop: options.stop, + }, status: 0, flags, disable: () => disablePlugin(plugin), @@ -65,7 +78,7 @@ function preparePluginPreInit(id: PluginManifest['id']) { meta[0] = { cleanup: (...items) => { - plugin.cleanups.push(...items) + plugin._c.push(...items) }, plugin, unscoped: _uapi, @@ -74,11 +87,33 @@ function preparePluginPreInit(id: PluginManifest['id']) { meta[3] = PluginApiLevel.PreInit } -function preparePluginInit(id: PluginManifest['id']) { +function preparePluginInit(id: PluginManifest['id'], storageOptions?: StorageOptions) { const meta = _metas.get(id)! const api = meta[0] as InitPluginApi - api.logger = new api.unscoped.discord.common.Logger(`Revenge > Plugins (${id})`) + Object.defineProperties(api, { + logger: { + configurable: true, + get() { + // @ts-expect-error + // biome-ignore lint/performance/noDelete: + delete api.logger + return (api.logger = new api.unscoped.discord.common.Logger(`Revenge > Plugins (${id})`)) + }, + }, + storage: { + configurable: true, + get() { + // @ts-expect-error + // biome-ignore lint/performance/noDelete: + delete api.storage + return (api.storage = getStorage(`${PluginsStorageDirectory}/${id}.json`, { + ...storageOptions, + directory: 'documents', + })) + }, + }, + }) meta[3] = PluginApiLevel.Init } @@ -224,7 +259,7 @@ export async function stopPlugin(plugin: InternalPlugin) { throw new Error(`Plugin "${manifest.id}" is not running`) if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id) - if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id) + if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id, plugin._s) if (apiLevel < PluginApiLevel.Start) preparePluginStart(manifest.id) plugin.status |= Status.Stopping @@ -239,7 +274,7 @@ export async function stopPlugin(plugin: InternalPlugin) { await handlePluginError(e, plugin) } finally { // Run cleanups - const results = await allSettled(plugin.cleanups.map(cleanup => cleanup())) + const results = await allSettled(plugin._c.map(cleanup => cleanup())) for (const result of results) if (result.status === 'rejected') { await handlePluginError(result.reason, plugin) @@ -253,7 +288,7 @@ export async function stopPlugin(plugin: InternalPlugin) { // Clear temp data meta[1] = [] - plugin.cleanups = [] + plugin._c = [] // Reset status plugin.status = 0 @@ -261,7 +296,8 @@ export async function stopPlugin(plugin: InternalPlugin) { } export interface InternalPlugin extends Plugin { - cleanups: PluginCleanup[] + _s?: StorageOptions + _c: PluginCleanup[] } export const InternalPluginFlags = { diff --git a/lib/plugins/src/constants.ts b/lib/plugins/src/constants.ts index eb77332..c87111f 100644 --- a/lib/plugins/src/constants.ts +++ b/lib/plugins/src/constants.ts @@ -27,3 +27,5 @@ export const PluginStatus = { Started: 1 << 5, Stopping: 1 << 6, } + +export const PluginsStorageDirectory = 'revenge/plugins/storage' diff --git a/lib/plugins/src/types.ts b/lib/plugins/src/types.ts index ba800e3..67e9332 100644 --- a/lib/plugins/src/types.ts +++ b/lib/plugins/src/types.ts @@ -2,6 +2,9 @@ import type { PluginFlags, PluginStatus } from './constants' import type { DiscordModules } from '@revenge-mod/discord/types' +import type { Storage, StorageOptions } from '@revenge-mod/storage' +import type { AnyObject } from '@revenge-mod/utils/types' + import type { PluginApiDiscord } from './apis/discord' import type { PluginApiExternals } from './apis/externals' import type { PluginApiModules } from './apis/modules' @@ -55,17 +58,25 @@ export interface PreInitPluginApi { * The plugin API (limited). * Available in the `init` phase. */ -export interface InitPluginApi extends PreInitPluginApi { +export interface InitPluginApi extends PreInitPluginApi { unscoped: UnscopedInitPluginApi logger: InstanceType + /** + * The plugin storage. + * + * Note that the instance is only created when the plugin accesses the API. + * This is to prevent unnecessary storage instances from being created. + * + * To preload storage, simply call `api.storage.get()`. + */ + storage: Storage } /** * The plugin API. * Available in the `start` and `stop` phase. */ -export interface PluginApi extends InitPluginApi { - // settings: typeof import('@revenge-mod/settings') +export interface PluginApi extends InitPluginApi { unscoped: UnscopedPluginApi } @@ -108,7 +119,11 @@ export interface PluginManifest { icon?: string } -export interface PluginLifecycles { +export interface PluginOptions extends PluginLifecycles { + storage?: Omit, 'directory'> +} + +export interface PluginLifecycles { /** * Runs as soon as possible with very limited APIs. * Before the index module (module 0)'s factory is run. @@ -122,22 +137,22 @@ export interface PluginLifecycles { * * @param api Plugin API (limited). */ - init?: (api: InitPluginApi) => any + init?: (api: InitPluginApi) => any /** * Runs when the plugin can be started with all APIs available. * * @param api Plugin API. */ - start?: (api: PluginApi) => any + start?: (api: PluginApi) => any /** * Runs when the plugin is stopped. * * @param api Plugin API. */ - stop?: (api: PluginApi) => any + stop?: (api: PluginApi) => any } -export interface Plugin { +export interface Plugin { // TODO(plugins): support plugin bundles // /** // * The plugin bundle this plugin belongs to. @@ -150,7 +165,7 @@ export interface Plugin { /** * The plugin lifecycles. */ - lifecycles: PluginLifecycles + lifecycles: PluginLifecycles /** * The plugin flags.