diff --git a/package.json b/package.json index bf063ac..e985a5a 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,14 @@ "license": "MIT", "dependencies": { "callsites": "^3.1.0", + "node-cron": "^3.0.3", "rxjs": "^7.8.0", "ts-results-es": "^4.1.0" }, "devDependencies": { "@faker-js/faker": "^8.0.1", - "@types/node": "~18.17.11", + "@types/node": "^20.0.0", + "@types/node-cron": "^3.0.11", "@typescript-eslint/eslint-plugin": "5.58.0", "@typescript-eslint/parser": "5.59.1", "discord.js": "^14.11.0", @@ -76,7 +78,10 @@ "allowTemplateLiterals": true } ], - "semi": [ "error", "always" ], + "semi": [ + "error", + "always" + ], "@typescript-eslint/no-empty-interface": 0, "@typescript-eslint/ban-types": 0, "@typescript-eslint/no-explicit-any": "off" @@ -87,7 +92,7 @@ "url": "git+https://github.com/sern-handler/handler.git" }, "engines": { - "node": ">= 18.17.x" + "node": ">= 20.0.x" }, "homepage": "https://sern.dev" } diff --git a/src/core/_internal.ts b/src/core/_internal.ts index 439c170..a234734 100644 --- a/src/core/_internal.ts +++ b/src/core/_internal.ts @@ -1,15 +1,7 @@ import type { Result } from 'ts-results-es' +import { CommandType, EventType, Plugin } from '..'; +import { AnyFunction } from '../types/utility'; +import { Module } from '../types/core-modules'; -export * from './functions'; - -export type _Module = { - meta: { - id: string, - absPath: string - } - name: string, - execute : Function - [key: PropertyKey]: unknown -} export type VoidResult = Result; diff --git a/src/core/interfaces.ts b/src/core/interfaces.ts index 9c1ab4f..70b9b8a 100644 --- a/src/core/interfaces.ts +++ b/src/core/interfaces.ts @@ -21,6 +21,7 @@ export interface Emitter { addListener(eventName: string | symbol, listener: AnyFunction): this; removeListener(eventName: string | symbol, listener: AnyFunction): this; emit(eventName: string | symbol, ...payload: any[]): boolean; + on(eventName: string | symbol, listener: AnyFunction): this } diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index a33643b..6ab3464 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -75,6 +75,8 @@ async function composeRoot( __add_container('@sern/logger', new __Services.DefaultLogging()); } __add_container('@sern/errors', new __Services.DefaultErrorHandling()); + __add_container('@sern/cron', {}) + __add_container('@sern/modules', new Map()) //Build the container based on the callback provided by the user conf.build(container as Container); @@ -99,6 +101,7 @@ export async function makeDependencies (conf: ValidDependencyConfig) { __add_container('@sern/logger', new __Services.DefaultLogging); } __add_container('@sern/errors', new __Services.DefaultErrorHandling()); + __add_container('@sern/cron', {}) await useContainerRaw().ready(); } else { await composeRoot(useContainerRaw(), conf); diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index 91a66a0..1151db6 100644 --- a/src/core/ioc/container.ts +++ b/src/core/ioc/container.ts @@ -4,7 +4,7 @@ import * as __Services from '../structures/default-services'; * A semi-generic container that provides error handling, emitter, and module store. * For the handler to operate correctly, The only user provided dependency needs to be @sern/client */ -export function hasCallableMethod(obj: object, name: PropertyKey) { +function hasCallableMethod(obj: object, name: PropertyKey) { //@ts-ignore return Object.hasOwn(obj, name) && typeof obj[name] == 'function'; } diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index cc7dc46..9a3c438 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -3,7 +3,7 @@ import { existsSync } from 'fs'; import { readdir } from 'fs/promises'; import assert from 'node:assert'; import * as Id from './id' -import type { _Module } from './_internal'; +import { Module } from '../types/core-modules'; export const parseCallsite = (site: string) => { const pathobj = path.parse(site.replace(/file:\\?/, "") @@ -38,11 +38,11 @@ export const shouldHandle = (pth: string, filenam: string) => { export async function importModule(absPath: string) { let fileModule = await import(absPath); - let commandModule: _Module = fileModule.default; + let commandModule: Module = fileModule.default; assert(commandModule , `No export @ ${absPath}. Forgot to ignore with "!"? (!${path.basename(absPath)})?`); if ('default' in commandModule) { - commandModule = commandModule.default as _Module; + commandModule = commandModule.default as Module; } const p = path.parse(absPath) commandModule.name ??= p.name; commandModule.description ??= "..."; @@ -51,21 +51,18 @@ export async function importModule(absPath: string) { id: Id.create(commandModule.name, commandModule.type), absPath, }; - return { module: commandModule } as T; + return { module: commandModule as T }; } export async function* readRecursive(dir: string): AsyncGenerator { - const files = await readdir(dir, { withFileTypes: true, recursive: true }); + const files = await readdir(dir, { recursive: true, withFileTypes: true }); for (const file of files) { - const fullPath = path.join(file.path, file.name); + const fullPath = path.join(file.parentPath, file.name); if(!file.name.startsWith('!') && !file.isDirectory()) { yield fullPath; } } } -export const fmtFileName = (fileName: string) => path.parse(fileName).name; - -export const filename = (p: string) => fmtFileName(path.basename(p)); diff --git a/src/core/modules.ts b/src/core/modules.ts index e2be1f4..3e13c67 100644 --- a/src/core/modules.ts +++ b/src/core/modules.ts @@ -4,37 +4,35 @@ import type { AnyEventPlugin, } from '../types/core-plugin'; import type { InputCommand, InputEvent, + Module, } from '../types/core-modules'; -import { type _Module, partitionPlugins } from './_internal'; +import { partitionPlugins } from './functions' import type { Awaitable } from '../types/utility'; /** * @since 1.0.0 The wrapper function to define command modules for sern * @param mod */ -export function commandModule(mod: InputCommand): _Module { +export function commandModule(mod: InputCommand): Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); - //@ts-ignore return { ...mod, onEvent, plugins, - }; + } as Module; } /** * @since 1.0.0 * The wrapper function to define event modules for sern * @param mod */ -export function eventModule(mod: InputEvent): _Module { +export function eventModule(mod: InputEvent): Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); - - //@ts-ignore return { ...mod, plugins, onEvent, - }; + } as Module; } /** Create event modules from discord.js client events, diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index d77302b..763bc8b 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -15,10 +15,6 @@ import { } from 'rxjs'; import { type VoidResult, - resultPayload, - isAutocomplete, - treeSearch, - _Module, } from '../core/_internal'; import * as Id from '../core/id' import type { Emitter, ErrorHandling, Logging } from '../core/interfaces'; @@ -42,7 +38,7 @@ function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[ const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.options]; return [ctx, args] as [Context, Args]; } - +import { resultPayload, isAutocomplete, treeSearch } from '../core/functions' function intoPayload(module: Processed, ) { return pipe(map(arrayifySource), @@ -127,7 +123,7 @@ export function fmt(msg: string, prefix: string): string[] { */ export function createInteractionHandler( source: Observable, - mg: Map, //TODO + mg: Map, //TODO ) { return createGenericHandler, void>>( source, @@ -135,7 +131,7 @@ export function createInteractionHandler( const possibleIds = Id.reconstruct(event); let fullPaths= possibleIds .map(id => mg.get(id)) - .filter((id): id is _Module => id !== undefined); + .filter((id): id is Module => id !== undefined); if(fullPaths.length == 0) { return Err.EMPTY; @@ -230,25 +226,6 @@ export function createResultResolver< }; }; -/** - * Calls a module's init plugins and checks for Err. If so, call { onStop } and - * ignore the module - */ -export function callInitPlugins>(sernEmitter: Emitter) { - return concatMap( - createResultResolver({ - createStream: args => from(args.module.plugins).pipe(callPlugin(args)), - onStop: (module: T) => { - sernEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); - }, - onNext: (payload) => { - sernEmitter.emit('module.register', resultPayload(PayloadType.Success, payload.module)); - return payload as { module: T; metadata: CommandMeta }; - }, - }), - ); -} - /** * Creates an executable task ( execute the command ) if all control plugins are successful * @param onStop emits a failure response to the SernEmitter @@ -264,19 +241,16 @@ export function makeModuleExecutor< }); return createResultResolver({ onStop, - createStream: ({ args, module }) => - from(module.onEvent) - .pipe(callPlugin(args)), + createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), onNext, }) } export const handleCrash = (err: ErrorHandling,sernemitter: Emitter, log?: Logging) => - pipe( - catchError(handleError(err, sernemitter, log)), + pipe(catchError(handleError(err, sernemitter, log)), finalize(() => { log?.info({ message: 'A stream closed or reached end of lifetime', }); disposeAll(log); - })); + })) diff --git a/src/handlers/interaction.ts b/src/handlers/interaction.ts index ffe9d2e..7c86da3 100644 --- a/src/handlers/interaction.ts +++ b/src/handlers/interaction.ts @@ -2,16 +2,11 @@ import type { Interaction } from 'discord.js'; import { mergeMap, merge, concatMap } from 'rxjs'; import { PayloadType } from '../core/structures/enums'; import { filterTap, sharedEventStream } from '../core/operators' -import { - isAutocomplete, - isCommand, - isMessageComponent, - isModal, - resultPayload, -} from '../core/_internal'; import { createInteractionHandler, executeModule, makeModuleExecutor } from './event-utils'; import type { DependencyList } from '../types/ioc'; import { SernError } from '../core/structures/enums' +import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload, } from '../core/functions' + export function interactionHandler([emitter, err, log, client]: DependencyList) { const interactionStream$ = sharedEventStream(client, 'interactionCreate'); const modules = new Map(); @@ -25,5 +20,5 @@ export function interactionHandler([emitter, err, log, client]: DependencyList) .pipe(filterTap(e => emitter.emit('warning', resultPayload(PayloadType.Warning, undefined, e))), concatMap(makeModuleExecutor(module => emitter.emit('module.activate', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)))), - mergeMap(payload => executeModule(emitter, log, err, payload))); + mergeMap(payload => executeModule(emitter, log, err, payload))); } diff --git a/src/handlers/message.ts b/src/handlers/message.ts index 8046ac1..8c851e5 100644 --- a/src/handlers/message.ts +++ b/src/handlers/message.ts @@ -20,16 +20,15 @@ function hasPrefix(prefix: string, content: string) { } export function messageHandler( - [emitter, err, log, client]: DependencyList, + [emitter, err, log, client, commands]: DependencyList, defaultPrefix: string | undefined, ) { if (!defaultPrefix) { log?.debug({ message: 'No prefix found. message handler shutting down' }); return EMPTY; } - const modules = new Map() const messageStream$ = sharedEventStream(client, 'messageCreate'); - const handle = createMessageHandler(messageStream$, defaultPrefix, modules); + const handle = createMessageHandler(messageStream$, defaultPrefix, commands); const msgCommands$ = handle(isNonBot(defaultPrefix)); diff --git a/src/handlers/presence.ts b/src/handlers/presence.ts index b393e3d..f492a69 100644 --- a/src/handlers/presence.ts +++ b/src/handlers/presence.ts @@ -23,11 +23,8 @@ const parseConfig = async (conf: Promise) => { }; export const presenceHandler = (path: string, setPresence: SetPresence) => { - interface PresenceModule { - module: PresenceConfig<(keyof Dependencies)[]> - } const presence = Files - .importModule(path) + .importModule>(path) .then(({ module }) => { //fetch services with the order preserved, passing it to the execute fn const fetchedServices = Services(...module.inject ?? []); diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts deleted file mode 100644 index 22ea02a..0000000 --- a/src/handlers/ready-event.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { concat, first, fromEvent, ignoreElements, pipe, tap } from 'rxjs'; -import { _Module } from '../core/_internal'; -import { Logging } from '../core/interfaces'; -import type { DependencyList } from '../types/ioc'; -import { callInitPlugins } from './event-utils'; - -const once = (log: Logging | undefined) => - pipe(tap(() => { log?.info({ message: "Waiting on discord client to be ready..." }) }), - first(), - ignoreElements()) - -export function readyHandler( - [sEmitter, , log, client]: DependencyList, -) { - //Todo: add module manager on on ready - const ready$ = fromEvent(client!, 'ready').pipe(once(log)); - - return concat(ready$).pipe(callInitPlugins(sEmitter)).subscribe(); - -// const validModuleType = module.type >= 0 && module.type <= 1 << 10; -// assert.ok(validModuleType, -// `Found ${module.name} at ${module.meta.fullPath}, which does not have a valid type`); -} - - diff --git a/src/handlers/ready.ts b/src/handlers/ready.ts new file mode 100644 index 0000000..38ba398 --- /dev/null +++ b/src/handlers/ready.ts @@ -0,0 +1,30 @@ +import type { DependencyList } from '../types/ioc'; +import * as Files from '../core/module-loading' +import { once } from 'events'; +import { resultPayload } from '../core/functions'; +import { PayloadType } from '..'; +import { SernError } from '../core/structures/enums'; +import { Module } from '../types/core-modules'; + +export default async function(dir: string, [sEmitter,, log, client, commands]: DependencyList) { + log?.info({ message: "Waiting on discord client to be ready..." }) + await once(client, "ready"); + log?.info({ message: "Client signaled ready, registering modules" }); + for await (const path of Files.readRecursive(dir)) { + const { module } = await Files.importModule(path); + const validModuleType = module.type >= 0 && module.type <= 1 << 10; + if(!validModuleType) { + throw Error(`Found ${module.name} at ${module.meta.absPath}, which has an incorrect \`type\``); + } + for(const plugin of module.plugins) { + const res = await plugin.execute({ module, absPath: module.meta.absPath }); + if(res.isErr()) { + sEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); + throw Error("Plugin failed with controller.stop()"); + } + } + commands.set(module.meta.id, module); + sEmitter.emit('module.register', resultPayload(PayloadType.Success, module)); + } + sEmitter.emit('modulesLoaded'); +} diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts index 31bd390..d1ff2fe 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -1,14 +1,10 @@ -import { ObservableInput, map, mergeAll } from 'rxjs'; import { EventType, SernError } from '../core/structures/enums'; -import { callInitPlugins, eventDispatcher, handleCrash } from './event-utils' +import { eventDispatcher } from './event-utils' import { Service } from '../core/ioc'; import type { DependencyList } from '../types/ioc'; import type { EventModule, Processed } from '../types/core-modules'; -export function eventsHandler( - [emitter, err, log, client]: DependencyList, - //allPaths: ObservableInput, -) { +export default function( [emitter, err, log, client]: DependencyList, eventDir: string) { //code smell const intoDispatcher = (e: { module: Processed }) => { switch (e.module.type) { @@ -18,6 +14,9 @@ export function eventsHandler( return eventDispatcher(e.module, client); case EventType.External: return eventDispatcher(e.module, Service(e.module.emitter)); + case EventType.Cron: + //@ts-ignore + return eventDispatcher(e.module, Service('@sern/cron')) default: throw Error(SernError.InvalidModuleType + ' while creating event handler'); } diff --git a/src/index.ts b/src/index.ts index 6aac3e6..b752b22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,7 +38,7 @@ export type { export type { Args, SlashOptions, Payload, SernEventsMapping } from './types/utility'; -export type { Singleton, Transient, CoreDependencies } from './types/ioc'; +export type { CoreDependencies } from './types/ioc'; export { commandModule, diff --git a/src/sern.ts b/src/sern.ts index 35875e9..8e865a6 100644 --- a/src/sern.ts +++ b/src/sern.ts @@ -2,8 +2,8 @@ import callsites from 'callsites'; import * as Files from './core/module-loading'; import { merge } from 'rxjs'; import { Services } from './core/ioc'; -import { eventsHandler } from './handlers/user-defined-events'; -import { readyHandler } from './handlers/ready-event'; +import eventsHandler from './handlers/user-defined-events'; +import ready from './handlers/ready'; import { messageHandler } from './handlers/message'; import { interactionHandler } from './handlers/interaction'; import { presenceHandler } from './handlers/presence'; @@ -11,9 +11,9 @@ import { Client } from 'discord.js'; import { handleCrash } from './handlers/event-utils'; interface Wrapper { - commands?: string; + commands: string; defaultPrefix?: string; - events?: string; + events: string; } /** * @since 1.0.0 @@ -27,37 +27,38 @@ interface Wrapper { * }) * ``` */ -export function init(wrapper: Wrapper = { commands: "./dist/commands", events: "./dist/events" }) { +export function init(maybeWrapper: Wrapper = { commands: "./dist/commands", events: "./dist/events" }) { const startTime = performance.now(); const dependencies = Services('@sern/emitter', '@sern/errors', '@sern/logger', - '@sern/client'); + '@sern/client', + '@sern/modules'); const logger = dependencies[2], errorHandler = dependencies[1]; - - if (wrapper.events !== undefined) { - eventsHandler(dependencies); + + if (maybeWrapper.events !== undefined) { + eventsHandler(dependencies, maybeWrapper.events); } const initCallsite = callsites()[1].getFileName(); const presencePath = Files.shouldHandle(initCallsite!, "presence"); //Ready event: load all modules and when finished, time should be taken and logged - readyHandler(dependencies) - .add(() => { - logger?.info({ message: "Client signaled ready, registering modules" }); + ready(maybeWrapper.commands, dependencies) + .then(() => { const time = ((performance.now() - startTime) / 1000).toFixed(2); - dependencies[0].emit('modulesLoaded'); logger?.info({ message: `sern: registered in ${time} s`, }); if(presencePath.exists) { const setPresence = async (p: any) => { + //@ts-ignore return (dependencies[3] as Client).user?.setPresence(p); } presenceHandler(presencePath.path, setPresence).subscribe(); } - }); + }) + .catch(err => { throw err }); - const messages$ = messageHandler(dependencies, wrapper.defaultPrefix); + const messages$ = messageHandler(dependencies, maybeWrapper.defaultPrefix); const interactions$ = interactionHandler(dependencies); // listening to the message stream and interaction stream merge(messages$, interactions$).pipe(handleCrash(errorHandler, dependencies[0], logger)).subscribe(); diff --git a/src/types/core-modules.ts b/src/types/core-modules.ts index c594022..ed71086 100644 --- a/src/types/core-modules.ts +++ b/src/types/core-modules.ts @@ -35,6 +35,10 @@ export interface Module { onEvent: ControlPlugin[]; plugins: InitPlugin[]; description?: string; + meta: { + id: string; + absPath: string; + } execute(...args: any[]): Awaitable; } @@ -44,12 +48,18 @@ export interface SernEventCommand; } + export interface ExternalEventCommand extends Module { name?: string; emitter: keyof Dependencies; type: EventType.External; execute(...args: unknown[]): Awaitable; } +export interface CronEventCommand extends Module { + name?: string; + type: EventType.Cron; + execute(...args: unknown[]): Awaitable; +} export interface ContextMenuUser extends Module { type: CommandType.CtxUser; @@ -127,7 +137,7 @@ export interface BothCommand extends Module { execute: (ctx: Context, args: Args) => Awaitable; } -export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand; +export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand | CronEventCommand; export type CommandModule = | TextCommand | SlashCommand @@ -178,10 +188,10 @@ export interface SernAutocompleteData } type CommandModuleNoPlugins = { - [T in CommandType]: Omit; + [T in CommandType]: Omit; }; type EventModulesNoPlugins = { - [T in EventType]: Omit; + [T in EventType]: Omit; }; export type InputEvent = { diff --git a/src/types/ioc.ts b/src/types/ioc.ts index 8ce0aca..8fc92e3 100644 --- a/src/types/ioc.ts +++ b/src/types/ioc.ts @@ -1,29 +1,22 @@ import type { Container } from '../core/ioc/container'; import * as Contracts from '../core/interfaces'; import type { UnpackFunction } from './utility' -/** - * Type to annotate that something is a singleton. - * T is created once and lazily. - */ -export type Singleton = () => T; -/** - * Type to annotate that something is transient. - * Every time this is called, a new object is created - */ -export type Transient = () => () => T; - +import type { Client } from 'discord.js' +import { Module } from './core-modules'; export type DependencyList = [ Contracts.Emitter, Contracts.ErrorHandling, Contracts.Logging | undefined, - Contracts.Emitter, + Client, + Map ]; export interface CoreDependencies { - '@sern/client': () => Contracts.Emitter; + '@sern/client': () => Client; '@sern/emitter': () => Contracts.Emitter; '@sern/errors': () => Contracts.ErrorHandling; '@sern/logger'?: () => Contracts.Logging; + '@sern/modules': () => Map } export type DependencyFromKey = Dependencies[T]; diff --git a/src/types/utility.ts b/src/types/utility.ts index 4a705dd..60fad87 100644 --- a/src/types/utility.ts +++ b/src/types/utility.ts @@ -4,7 +4,7 @@ import type { Module } from './core-modules'; export type Awaitable = PromiseLike | T; -export type AnyFunction = (...args: unknown[]) => unknown; +export type AnyFunction = (...args: never[]) => unknown; // Thanks to @kelsny type ParseType = { diff --git a/yarn.lock b/yarn.lock index 43cc46b..25b4e63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -235,12 +235,14 @@ __metadata: resolution: "@sern/handler@workspace:." dependencies: "@faker-js/faker": ^8.0.1 - "@types/node": ~18.17.11 + "@types/node": ^20.0.0 + "@types/node-cron": ^3.0.11 "@typescript-eslint/eslint-plugin": 5.58.0 "@typescript-eslint/parser": 5.59.1 callsites: ^3.1.0 discord.js: ^14.11.0 eslint: 8.39.0 + node-cron: ^3.0.3 prettier: 2.8.8 rxjs: ^7.8.0 ts-results-es: ^4.1.0 @@ -255,6 +257,13 @@ __metadata: languageName: node linkType: hard +"@types/node-cron@npm:^3.0.11": + version: 3.0.11 + resolution: "@types/node-cron@npm:3.0.11" + checksum: a73f69bcca52a5f3b1671cfb00a8e4a1d150d0aef36a611564a2f94e66b6981bade577e267ceeeca6fcee241768902d55eb8cf3a81f9ef4ed767a23112fdb16d + languageName: node + linkType: hard + "@types/node@npm:*": version: 20.5.9 resolution: "@types/node@npm:20.5.9" @@ -262,10 +271,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:~18.17.11": - version: 18.17.19 - resolution: "@types/node@npm:18.17.19" - checksum: 6ab47127cd7534511aa199550659d56b44e5a6dbec9df054d0cde279926b4d43f0e6438f92c8392b039ab4e2a85aa0f698b95926430aff860e23bfc36c96576c +"@types/node@npm:^20.0.0": + version: 20.12.12 + resolution: "@types/node@npm:20.12.12" + dependencies: + undici-types: ~5.26.4 + checksum: 5373983874b9af7c216e7ca5d26b32a8d9829c703a69f1e66f2113598b5be8582c0e009ca97369f1ec9a6282b3f92812208d06eb1e9fc3bd9b939b022303d042 languageName: node linkType: hard @@ -1198,6 +1209,15 @@ __metadata: languageName: node linkType: hard +"node-cron@npm:^3.0.3": + version: 3.0.3 + resolution: "node-cron@npm:3.0.3" + dependencies: + uuid: 8.3.2 + checksum: 351c37491ebf717d0ae69cc941465de118e5c2ef5d48bc3f87c98556241b060f100402c8a618c7b86f9f626b44756b20d8b5385b70e52f80716f21e55db0f1c5 + languageName: node + linkType: hard + "once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -1506,6 +1526,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 3192ef6f3fd5df652f2dc1cd782b49d6ff14dc98e5dced492aa8a8c65425227da5da6aafe22523c67f035a272c599bb89cfe803c1db6311e44bed3042fc25487 + languageName: node + linkType: hard + "undici@npm:5.27.2": version: 5.27.2 resolution: "undici@npm:5.27.2" @@ -1524,6 +1551,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2"