bakit 2.0.0-alpha.27 → 2.0.0-alpha.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { resolve, join } from 'path';
3
+ import { existsSync } from 'fs';
1
4
  import { config } from 'dotenv';
2
5
  import { program } from 'commander';
3
6
  import { fork } from 'child_process';
4
- import path, { resolve } from 'path';
5
7
  import chokidar from 'chokidar';
6
- import { RPC } from 'bakit';
8
+ import { loadConfig, RPC } from 'bakit';
7
9
 
8
- // src/cli/bin.ts
9
10
  var DevProcessManager = class {
10
11
  constructor(options) {
11
12
  this.options = options;
@@ -18,12 +19,13 @@ var DevProcessManager = class {
18
19
  startChild() {
19
20
  if (this.rpc)
20
21
  return;
21
- let entry = path.resolve(this.options.entry), child = fork(entry, {
22
+ let { entryFile } = this.options, child = fork(this.options.entryFile, {
22
23
  execArgv: ["--import", "bakit/register"],
23
24
  stdio: "inherit",
24
25
  env: {
25
26
  ...process.env,
26
- NODE_ENV: "development"
27
+ NODE_ENV: "development",
28
+ BAKIT_ENTRY_FILE: entryFile
27
29
  }
28
30
  });
29
31
  this.rpc = new RPC(child), this.rpc.on("restart", (fileUpdated) => this.scheduleRestart(fileUpdated));
@@ -37,18 +39,18 @@ var DevProcessManager = class {
37
39
  }), child.kill("SIGTERM");
38
40
  }
39
41
  startWatcher() {
40
- let { rootDir } = this.options, watcher = chokidar.watch(rootDir, {
42
+ let { entryDirectory } = this.options, watcher = chokidar.watch(entryDirectory, {
41
43
  ignoreInitial: true,
42
44
  awaitWriteFinish: {
43
45
  stabilityThreshold: 200,
44
46
  pollInterval: 50
45
47
  }
46
48
  });
47
- watcher.on("change", (path2) => this.onFileUpdate("fileChange", path2)), watcher.on("unlink", (path2) => this.onFileUpdate("fileRemove", path2));
49
+ watcher.on("change", (path) => this.onFileUpdate("fileChange", path)), watcher.on("unlink", (path) => this.onFileUpdate("fileRemove", path));
48
50
  }
49
- onFileUpdate(type, path2) {
51
+ onFileUpdate(type, path) {
50
52
  try {
51
- this.rpc?.send(type, resolve(path2));
53
+ this.rpc?.send(type, resolve(path));
52
54
  } catch {
53
55
  this.scheduleRestart(true);
54
56
  }
@@ -59,14 +61,17 @@ var DevProcessManager = class {
59
61
  }, 150);
60
62
  }
61
63
  };
62
-
63
- // src/cli/bin.ts
64
64
  program.name("bakit");
65
- program.command("dev").action(() => {
66
- config({ path: [".env.local", ".env"], quiet: true }), new DevProcessManager({
67
- rootDir: "src",
68
- entry: "src/index.ts",
69
- hotDirs: ["commands", "listeners"]
65
+ program.command("dev").action(async () => {
66
+ config({ path: [".env.local", ".env"], quiet: true });
67
+ let config$1 = await loadConfig(), entryDirectory = resolve(config$1.entryDirectory);
68
+ new DevProcessManager({
69
+ entryFile: getEntryFile(entryDirectory),
70
+ entryDirectory
70
71
  }).start();
71
72
  });
73
+ function getEntryFile(entryDirectory) {
74
+ let index = join(entryDirectory, "index");
75
+ return existsSync(index + ".ts") ? index : index + ".js";
76
+ }
72
77
  program.parse();
package/dist/index.d.ts CHANGED
@@ -258,15 +258,21 @@ declare function defineCommand<const ParamsList extends readonly AnyParam<any>[]
258
258
  params?: ParamsList;
259
259
  }) | string): Command<ParamsList>;
260
260
 
261
- declare class BaseClientManager {
262
- client: BakitClient;
263
- constructor(client: BakitClient);
261
+ declare abstract class HotReloadable {
262
+ entryDirectory: string;
263
+ constructor(entryDirectory: string);
264
+ abstract unload(path: string): Promise<unknown>;
265
+ abstract load(path: string): Promise<unknown>;
266
+ abstract reload(path: string): Promise<unknown>;
267
+ protected unloadFile(path: string): Promise<boolean>;
264
268
  }
265
269
 
266
- declare class CommandManager extends BaseClientManager {
270
+ declare class CommandManager extends HotReloadable {
271
+ client: BakitClient;
267
272
  commands: Collection<string, Command<any[]>>;
268
273
  entries: Collection<string, Command<any[]>>;
269
- loadModules(entryDir: string): Promise<Command[]>;
274
+ constructor(client: BakitClient);
275
+ loadModules(): Promise<Command[]>;
270
276
  /**
271
277
  * Load the file and add the command to the registry.
272
278
  * @param path The path to the command file.
@@ -315,11 +321,13 @@ declare class Listener<K extends EventKey = EventKey> extends LifecycleManager<C
315
321
  }
316
322
  declare function defineListener<const K extends EventKey = EventKey>(options: K | ListenerOptions<K>): Listener<K>;
317
323
 
318
- declare class ListenerManager extends BaseClientManager {
324
+ declare class ListenerManager extends HotReloadable {
325
+ client: BakitClient;
319
326
  listeners: Listener[];
320
327
  entries: Collection<string, Listener<keyof BakitClientEvents>>;
321
328
  private executors;
322
- loadModules(entryDir: string): Promise<Listener[]>;
329
+ constructor(client: BakitClient);
330
+ loadModules(): Promise<Listener[]>;
323
331
  /**
324
332
  * Load the file and add the listener to the registry.
325
333
  * @param path The path to the listener file.
@@ -370,20 +378,12 @@ declare class ProjectCacheManager {
370
378
  declare class Instance {
371
379
  client: BakitClient;
372
380
  cache: ProjectCacheManager;
373
- private rpc;
374
381
  constructor();
375
382
  start(): Promise<void>;
376
383
  private initProcess;
377
384
  private loadModules;
378
385
  private initIntents;
379
- private isInHotDirectory;
380
- private restart;
381
386
  shutdown(): Promise<void>;
382
- private containsEntryPoint;
383
- private containsHotModule;
384
- private onFileRemove;
385
- private onFileChange;
386
- private unloadModule;
387
387
  }
388
388
  declare function useApp(): Instance;
389
389
 
@@ -416,6 +416,11 @@ declare class BakitClient<Ready extends boolean = boolean> extends Client<Ready>
416
416
  [inspect.custom](): string;
417
417
  }
418
418
 
419
+ declare class BaseClientManager {
420
+ client: BakitClient;
421
+ constructor(client: BakitClient);
422
+ }
423
+
419
424
  declare const Params: {
420
425
  readonly string: <Required extends boolean = true>(options: string | {
421
426
  name: string;
@@ -459,14 +464,74 @@ declare function tokenize(content: string): string[];
459
464
  declare function extractSnowflakeId(input: string): string | null;
460
465
 
461
466
  declare function getTopLevelDirectory(path: string, entryDir: string): string | null;
467
+ declare function getEntryDirectory(): string;
468
+ declare function getEntryFile(): string;
462
469
 
463
- declare function $initLoader(): void;
464
- declare function $unloadFile(path: string): Promise<boolean>;
465
- declare function getImporters(path: string, createNew: true): Set<string>;
470
+ declare const hotReloaders: Collection<string, HotReloadable>;
471
+ /**
472
+ * Initliazie the loader
473
+ */
474
+ declare function init(): void;
475
+ /**
476
+ * Register a reloader for HMR.
477
+ * @param reloader Reloader extended from HotReloadable.
478
+ */
479
+ declare function addHotReloader(reloader: HotReloadable): void;
480
+ /**
481
+ * Remove the previous version of the file.
482
+ * @param path Path to unload.
483
+ * @returns `true` for unloaded successfully, `false` for unload failed.
484
+ */
485
+ declare function unload(path: string): Promise<boolean>;
486
+ /**
487
+ * Get a list of the files which imported the target.
488
+ * @param path Target file path to get.
489
+ * @param createNew Create a new Set cache for the target path.
490
+ */
466
491
  declare function getImporters(path: string, createNew?: false): Set<string> | undefined;
492
+ declare function getImporters(path: string, createNew: true): Set<string>;
493
+ /**
494
+ * Get a list of the files which imported the target.
495
+ * @param path Target file path to get.
496
+ * @param createNew Create a new Set cache for the target path.
497
+ */
498
+ declare function getImports(path: string): string[];
499
+ /**
500
+ * Get a chain of dependencies for affected files.
501
+ * @param path
502
+ * @returns An array of affected dependencies.
503
+ */
467
504
  declare function getDependencyChain(path: string): string[];
505
+ /**
506
+ * Checks if the target file imports any of the files in the 'targets' set.
507
+ * @param path The file path to check.
508
+ * @param targets The list of the files to check.
509
+ */
510
+ declare function importsAny(path: string, targets: Set<string>): boolean;
468
511
  declare function isImported(path: string): boolean;
469
512
  declare function isImportedBy(path: string, matcher: string | RegExp | ((path: string) => boolean)): boolean;
513
+ declare function isInHotDirectory(path: string): boolean;
514
+ declare function isEntryFile(path: string): boolean;
515
+ declare function containsEntryFile(chain: string[]): boolean;
516
+ declare function containsHotModule(chain: string[]): boolean;
517
+
518
+ declare const loader_addHotReloader: typeof addHotReloader;
519
+ declare const loader_containsEntryFile: typeof containsEntryFile;
520
+ declare const loader_containsHotModule: typeof containsHotModule;
521
+ declare const loader_getDependencyChain: typeof getDependencyChain;
522
+ declare const loader_getImporters: typeof getImporters;
523
+ declare const loader_getImports: typeof getImports;
524
+ declare const loader_hotReloaders: typeof hotReloaders;
525
+ declare const loader_importsAny: typeof importsAny;
526
+ declare const loader_init: typeof init;
527
+ declare const loader_isEntryFile: typeof isEntryFile;
528
+ declare const loader_isImported: typeof isImported;
529
+ declare const loader_isImportedBy: typeof isImportedBy;
530
+ declare const loader_isInHotDirectory: typeof isInHotDirectory;
531
+ declare const loader_unload: typeof unload;
532
+ declare namespace loader {
533
+ export { loader_addHotReloader as addHotReloader, loader_containsEntryFile as containsEntryFile, loader_containsHotModule as containsHotModule, loader_getDependencyChain as getDependencyChain, loader_getImporters as getImporters, loader_getImports as getImports, loader_hotReloaders as hotReloaders, loader_importsAny as importsAny, loader_init as init, loader_isEntryFile as isEntryFile, loader_isImported as isImported, loader_isImportedBy as isImportedBy, loader_isInHotDirectory as isInHotDirectory, loader_unload as unload };
534
+ }
470
535
 
471
536
  declare const RPC_RESPONSE_MARK = "$DONE:";
472
537
  declare const RPC_RESPONSE_TIMEOUT = 5000;
@@ -501,7 +566,7 @@ declare class RPC extends EventEmitter {
501
566
  private onMessage;
502
567
  private handleResponseMessage;
503
568
  private handleRequestMessage;
504
- send<Data extends Serializable>(type: string, data: Data, id?: string): void;
569
+ send<Data extends Serializable>(type: string, data?: Data, id?: string): void;
505
570
  success<Data extends Serializable>(id: string, data: Data): void;
506
571
  error(id: string, error: string): void;
507
572
  request<Data extends Serializable, Output extends Serializable>(type: string, data: Data, id?: string): Promise<Output>;
@@ -516,6 +581,7 @@ declare const ProjectConfigSchema: z.ZodObject<{
516
581
  clientOptions: z.ZodOptional<z.ZodCustom<Omit<ClientOptions, "intents">, Omit<ClientOptions, "intents">>>;
517
582
  prefixes: z.ZodDefault<z.ZodArray<z.ZodString>>;
518
583
  token: z.ZodString;
584
+ entryDirectory: z.ZodDefault<z.ZodString>;
519
585
  }, z.z.core.$strip>;
520
586
  type ProjectConfigInput = z.input<typeof ProjectConfigSchema>;
521
587
  type ProjectConfig = z.output<typeof ProjectConfigSchema>;
@@ -569,4 +635,4 @@ interface LoadResult {
569
635
  type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>;
570
636
  type NextLoad = (url: string, context: LoadContext) => Promise<LoadResult>;
571
637
 
572
- export { $initLoader, $unloadFile, type AnyParam, ArgumentError, BakitClient, type BakitClientEvents, BakitError, BaseClientManager, BaseCommandContext, BaseParam, type BaseParamOptions, BaseParamSchema, type BaseRPCMessage, type BaseRPCResponse, ChatInputContext, type ChatInputContextSendOptions, Command, type CommandContext, CommandManager, type CommandOptions, type CommandOptionsInput, CommandOptionsSchema, Context, type ContextSendOptions, EVENT_INTENT_MAPPING, type ErrorHookCallback, type GetPrefixFunction, HookOrder, HookState, type InferParamTuple, type InferParamValue, type InitializeData, Instance, LifecycleManager, Listener, ListenerManager, type ListenerOptions, ListenerOptionsSchema, type LoadContext, type LoadResult, type MainHookCallback, MessageContext, type MessageContextSendOptions, type NextLoad, type NextResolve, type NumberOptions, NumberParam, NumberParamSchema, type ParamResolvedOutputType, ParamUserType, Params, ProjectCacheManager, type ProjectConfig, type ProjectConfigInput, ProjectConfigSchema, RPC, type RPCErrorResponse, type RPCMessage, type RPCPendingPromise, type RPCRequest, type RPCResponse, type RPCSuccessResponse, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, type ResolveContext, type ResolveResult, type StringOptions, StringParam, StringParamSchema, type UserOptions, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getDependencyChain, getImporters, getTopLevelDirectory, isImported, isImportedBy, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
638
+ export { type AnyParam, ArgumentError, BakitClient, type BakitClientEvents, BakitError, BaseClientManager, BaseCommandContext, BaseParam, type BaseParamOptions, BaseParamSchema, type BaseRPCMessage, type BaseRPCResponse, ChatInputContext, type ChatInputContextSendOptions, Command, type CommandContext, CommandManager, type CommandOptions, type CommandOptionsInput, CommandOptionsSchema, Context, type ContextSendOptions, EVENT_INTENT_MAPPING, type ErrorHookCallback, type GetPrefixFunction, HookOrder, HookState, HotReloadable, type InferParamTuple, type InferParamValue, type InitializeData, Instance, LifecycleManager, Listener, ListenerManager, type ListenerOptions, ListenerOptionsSchema, type LoadContext, type LoadResult, loader as Loader, type MainHookCallback, MessageContext, type MessageContextSendOptions, type NextLoad, type NextResolve, type NumberOptions, NumberParam, NumberParamSchema, type ParamResolvedOutputType, ParamUserType, Params, ProjectCacheManager, type ProjectConfig, type ProjectConfigInput, ProjectConfigSchema, RPC, type RPCErrorResponse, type RPCMessage, type RPCPendingPromise, type RPCRequest, type RPCResponse, type RPCSuccessResponse, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, type ResolveContext, type ResolveResult, type StringOptions, StringParam, StringParamSchema, type UserOptions, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getEntryDirectory, getEntryFile, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { GatewayIntentBits, Events, Client, Collection, IntentsBitField, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
1
+ import { GatewayIntentBits, Events, Collection, Client, IntentsBitField, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
2
2
  import { inspect } from 'util';
3
3
  import { pathToFileURL, fileURLToPath } from 'url';
4
- import { resolve, posix, relative, sep, join, dirname } from 'path';
4
+ import { join, resolve, relative, sep, dirname } from 'path';
5
5
  import glob from 'tiny-glob';
6
- import z4 from 'zod';
6
+ import z2 from 'zod';
7
7
  import { register } from 'module';
8
8
  import { MessageChannel } from 'worker_threads';
9
9
  import { randomUUID, createHash } from 'crypto';
@@ -11,17 +11,21 @@ import EventEmitter from 'events';
11
11
  import { existsSync, mkdirSync, rmSync } from 'fs';
12
12
  import { mkdir, writeFile, readFile, rm } from 'fs/promises';
13
13
 
14
- // src/core/client/BakitClient.ts
15
- var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z4.object({
16
- name: z4.string(),
17
- description: z4.string().optional(),
18
- required: z4.boolean().default(true)
14
+ var __defProp = Object.defineProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z2.object({
20
+ name: z2.string(),
21
+ description: z2.string().optional(),
22
+ required: z2.boolean().default(true)
19
23
  }), StringParamSchema = BaseParamSchema.extend({
20
- maxLength: z4.number().min(1).optional(),
21
- minLength: z4.number().min(1).optional()
24
+ maxLength: z2.number().min(1).optional(),
25
+ minLength: z2.number().min(1).optional()
22
26
  }), NumberParamSchema = BaseParamSchema.extend({
23
- maxValue: z4.number().optional(),
24
- minValue: z4.number().optional()
27
+ maxValue: z2.number().optional(),
28
+ minValue: z2.number().optional()
25
29
  }), UserParamSchema = BaseParamSchema.extend({});
26
30
  var INTENT_GROUPS = {
27
31
  [GatewayIntentBits.Guilds]: [
@@ -162,9 +166,90 @@ function extractSnowflakeId(input) {
162
166
  let idMatch = /^(\d{17,20})$/.exec(input);
163
167
  return idMatch?.[1] ? idMatch[1] : null;
164
168
  }
169
+ var CONFIG_EXTENSIONS = ["ts", "js"], ProjectConfigSchema = z2.object({
170
+ /**
171
+ * The gateway intents to use for the Discord client.
172
+ *
173
+ * - `auto` — automatically determine the required intents.
174
+ * - bigint — a raw bitfield value representing the combined intents.
175
+ * - array — a list of individual intent flags from `GatewayIntentBits`.
176
+ *
177
+ * @defaultvalue `auto`
178
+ */
179
+ intents: z2.union([z2.literal("auto"), z2.bigint(), z2.array(z2.enum(GatewayIntentBits))]).default("auto"),
180
+ /**
181
+ * Optional custom client options for Discord.js (excluding `intents`).
182
+ *
183
+ * These are passed directly to the `Client` constructor when initializing the bot.
184
+ *
185
+ * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
186
+ */
187
+ clientOptions: z2.custom().optional(),
188
+ /**
189
+ * Your bot prefixes to trigger the commands.
190
+ */
191
+ prefixes: z2.array(z2.string()).default([]),
192
+ /**
193
+ * Your Discord bot token.
194
+ */
195
+ token: z2.string(),
196
+ /**
197
+ * The main source directory.
198
+ * @defaultvalue `src`
199
+ */
200
+ entryDirectory: z2.string().default("src")
201
+ });
202
+ function defineConfig(config) {
203
+ return config;
204
+ }
205
+ var _config;
206
+ async function loadConfig(cwd = process.cwd()) {
207
+ if (_config)
208
+ return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
209
+ let globPattern = `bakit.config.{${CONFIG_EXTENSIONS.join(",")}}`, [configPath, other] = await glob(globPattern, {
210
+ cwd: cwd.replace(/\\/g, "/"),
211
+ // ensure the path uses `/` instead of `\` on Windows
212
+ absolute: true
213
+ });
214
+ if (!configPath)
215
+ throw new Error("Missing config file");
216
+ other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
217
+ let config = (await import(pathToFileURL(configPath).href)).default;
218
+ return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
219
+ }
220
+ function getConfig() {
221
+ if (!_config)
222
+ throw new Error("Project config is not loaded.");
223
+ return _config;
224
+ }
165
225
  function getTopLevelDirectory(path, entryDir) {
166
226
  return relative(entryDir, path).split(sep)[0] ?? null;
167
227
  }
228
+ function getEntryDirectory() {
229
+ return resolve(getConfig().entryDirectory);
230
+ }
231
+ function getEntryFile() {
232
+ return process.env.BAKIT_ENTRY_FILE;
233
+ }
234
+
235
+ // src/lib/loader/loader.ts
236
+ var loader_exports = {};
237
+ __export(loader_exports, {
238
+ addHotReloader: () => addHotReloader,
239
+ containsEntryFile: () => containsEntryFile,
240
+ containsHotModule: () => containsHotModule,
241
+ getDependencyChain: () => getDependencyChain,
242
+ getImporters: () => getImporters,
243
+ getImports: () => getImports,
244
+ hotReloaders: () => hotReloaders,
245
+ importsAny: () => importsAny,
246
+ init: () => init,
247
+ isEntryFile: () => isEntryFile,
248
+ isImported: () => isImported,
249
+ isImportedBy: () => isImportedBy,
250
+ isInHotDirectory: () => isInHotDirectory,
251
+ unload: () => unload
252
+ });
168
253
  var RPC_RESPONSE_MARK = "$DONE:", RPC_RESPONSE_TIMEOUT = 5e3, RPC = class extends EventEmitter {
169
254
  constructor(transport) {
170
255
  super();
@@ -190,13 +275,13 @@ var RPC_RESPONSE_MARK = "$DONE:", RPC_RESPONSE_TIMEOUT = 5e3, RPC = class extend
190
275
  let id = message.id.slice(RPC_RESPONSE_MARK.length), request = this.requests.get(id);
191
276
  if (!request)
192
277
  return;
193
- let { reject, resolve: resolve5, timeout } = request;
194
- this.requests.delete(id), clearTimeout(timeout), "data" in message ? resolve5(message.data) : reject(new Error(message.error));
278
+ let { reject, resolve: resolve6, timeout } = request;
279
+ this.requests.delete(id), clearTimeout(timeout), "data" in message ? resolve6(message.data) : reject(new Error(message.error));
195
280
  }
196
281
  handleRequestMessage(message) {
197
282
  this.emit("message", message), this.emit(message.type, message.id, message.data);
198
283
  }
199
- send(type, data, id = randomUUID()) {
284
+ send(type, data = {}, id = randomUUID()) {
200
285
  let message = {
201
286
  id,
202
287
  type,
@@ -219,38 +304,52 @@ var RPC_RESPONSE_MARK = "$DONE:", RPC_RESPONSE_TIMEOUT = 5e3, RPC = class extend
219
304
  this.postMessage(message);
220
305
  }
221
306
  request(type, data, id = randomUUID()) {
222
- return new Promise((resolve5, reject) => {
307
+ return new Promise((resolve6, reject) => {
223
308
  let timeout = setTimeout(() => {
224
309
  this.requests.delete(id) && reject(new Error("Request timed out"));
225
310
  }, RPC_RESPONSE_TIMEOUT);
226
311
  this.requests.set(id, {
227
- resolve: resolve5,
312
+ resolve: resolve6,
228
313
  reject,
229
314
  timeout
230
315
  }), this.send(type, data, id);
231
316
  });
232
317
  }
233
318
  };
234
-
235
- // src/lib/loader/loader.ts
236
- var rpc, reverseDependencyGraph = /* @__PURE__ */ new Map();
237
- function $initLoader() {
319
+ var hooksRPC, processRPC, hotReloaders = new Collection(), reverseDependencyGraph = new Collection(), forwardDependencyGraph = new Collection();
320
+ function init() {
321
+ initProcess(), initHooks();
322
+ }
323
+ function initProcess() {
324
+ processRPC = new RPC(process), processRPC.on("fileChange", (_, path) => onFileChange(path)), processRPC.on("fileRemove", (_, path) => onFileRemove(path));
325
+ }
326
+ function initHooks() {
238
327
  let { port1, port2 } = new MessageChannel(), hookPath = new URL("./hooks.js", import.meta.url).href;
239
328
  register(hookPath, import.meta.url, {
240
329
  data: { port: port1 },
241
330
  transferList: [port1]
242
- }), rpc = new RPC(port2), rpc.on("dependencyAdd", (_id, data) => onDependencyAdd(data)), port2.unref();
331
+ }), hooksRPC = new RPC(port2), hooksRPC.on("dependencyAdd", (_, data) => onDependencyAdd(data)), port2.unref();
243
332
  }
244
- function $unloadFile(path) {
245
- if (!rpc)
333
+ function addHotReloader(reloader) {
334
+ hotReloaders.set(reloader.entryDirectory, reloader);
335
+ }
336
+ function unload(path) {
337
+ if (!hooksRPC)
246
338
  throw new Error("Loader isn't initialized");
247
- return rpc.request("unload", resolve(path));
339
+ return hooksRPC.request("unload", resolve(path));
248
340
  }
249
341
  function getImporters(path, createNew = false) {
250
342
  path = resolve(path);
251
343
  let entry = reverseDependencyGraph.get(path);
252
344
  return createNew && !entry && (entry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, entry)), entry;
253
345
  }
346
+ function getImports(path) {
347
+ path = resolve(path);
348
+ let imports = [];
349
+ for (let [target, importers] of reverseDependencyGraph)
350
+ importers.has(path) && imports.push(target);
351
+ return imports;
352
+ }
254
353
  function getDependencyChain(path) {
255
354
  path = resolve(path);
256
355
  let queue = [path], visited = /* @__PURE__ */ new Set();
@@ -259,12 +358,19 @@ function getDependencyChain(path) {
259
358
  if (!current || visited.has(current) || (visited.add(current), current.includes("/node_modules/")))
260
359
  continue;
261
360
  let parents = getImporters(current);
262
- if (parents)
263
- for (let parent of parents)
264
- visited.has(parent) || queue.push(parent);
361
+ if (!parents)
362
+ continue;
363
+ for (let parent of parents)
364
+ visited.has(parent) || queue.push(parent);
365
+ let children = getImports(current);
366
+ for (let child of children)
367
+ visited.has(child) || importsAny(child, visited) && queue.push(child);
265
368
  }
266
369
  return Array.from(visited);
267
370
  }
371
+ function importsAny(path, targets) {
372
+ return getImports(path).some((imp) => targets.has(imp));
373
+ }
268
374
  function isImported(path) {
269
375
  return !!getImporters(path)?.size;
270
376
  }
@@ -275,8 +381,72 @@ function isImportedBy(path, matcher) {
275
381
  });
276
382
  }
277
383
  function onDependencyAdd(data) {
278
- let { url, parentURL } = data, path = fileURLToPath(url), parentPath = fileURLToPath(parentURL), entry = reverseDependencyGraph.get(path);
279
- entry || (entry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, entry)), entry.add(parentPath);
384
+ let { url, parentURL } = data, path = fileURLToPath(url), parentPath = fileURLToPath(parentURL), reverseEntry = reverseDependencyGraph.get(path);
385
+ reverseEntry || (reverseEntry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, reverseEntry)), reverseEntry.add(parentPath);
386
+ let forwardEntry = forwardDependencyGraph.get(parentPath);
387
+ forwardEntry || (forwardEntry = /* @__PURE__ */ new Set(), forwardDependencyGraph.set(parentPath, forwardEntry)), forwardEntry.add(path);
388
+ }
389
+ function isInHotDirectory(path) {
390
+ let sourceRoot = getEntryDirectory();
391
+ if (!path.startsWith(sourceRoot))
392
+ return false;
393
+ let topLevelDir = getTopLevelDirectory(path, sourceRoot);
394
+ return !!topLevelDir && hotReloaders.some((m) => m.entryDirectory === topLevelDir);
395
+ }
396
+ function isEntryFile(path) {
397
+ return path === getEntryFile();
398
+ }
399
+ function containsEntryFile(chain) {
400
+ return chain.some((x) => isEntryFile(x));
401
+ }
402
+ function containsHotModule(chain) {
403
+ return chain.some((x) => isInHotDirectory(x));
404
+ }
405
+ function restartProcess() {
406
+ processRPC?.send("restart");
407
+ }
408
+ async function unloadModule(path, reload = false) {
409
+ let topLevel = getTopLevelDirectory(path, getEntryFile());
410
+ if (!topLevel)
411
+ return;
412
+ let directory = resolve(getEntryDirectory(), topLevel), reloader = hotReloaders.get(directory);
413
+ if (!reloader) {
414
+ await unload(path);
415
+ return;
416
+ }
417
+ reloader[reload ? "reload" : "unload"](path);
418
+ }
419
+ async function onFileRemove(path) {
420
+ if (isEntryFile(path)) {
421
+ restartProcess();
422
+ return;
423
+ }
424
+ if (!isImported(path))
425
+ return;
426
+ let chain = getDependencyChain(path);
427
+ if (containsEntryFile(chain)) {
428
+ restartProcess();
429
+ return;
430
+ }
431
+ if (containsHotModule(chain))
432
+ for (let path2 of chain.reverse())
433
+ await unloadModule(path2);
434
+ }
435
+ async function onFileChange(path) {
436
+ if (isEntryFile(path)) {
437
+ restartProcess();
438
+ return;
439
+ }
440
+ if (!isImported(path))
441
+ return;
442
+ let chain = getDependencyChain(path);
443
+ if (containsEntryFile(chain)) {
444
+ restartProcess();
445
+ return;
446
+ }
447
+ if (containsHotModule(chain))
448
+ for (let path2 of chain.toReversed())
449
+ await unloadModule(path2, true);
280
450
  }
281
451
 
282
452
  // src/core/structures/param/Param.ts
@@ -498,12 +668,12 @@ function validateParamsOrder(params) {
498
668
  seenOptional = true;
499
669
  return true;
500
670
  }
501
- var CommandOptionsSchema = z4.object({
502
- name: z4.string().readonly(),
503
- description: z4.string().min(1).max(100).optional().readonly(),
504
- nsfw: z4.boolean().default(false).readonly(),
505
- params: z4.array(z4.instanceof(BaseParam)).default([]).readonly(),
506
- quotes: z4.boolean().default(true).readonly()
671
+ var CommandOptionsSchema = z2.object({
672
+ name: z2.string().readonly(),
673
+ description: z2.string().min(1).max(100).optional().readonly(),
674
+ nsfw: z2.boolean().default(false).readonly(),
675
+ params: z2.array(z2.instanceof(BaseParam)).default([]).readonly(),
676
+ quotes: z2.boolean().default(true).readonly()
507
677
  }).transform((data) => ({
508
678
  ...data,
509
679
  description: data.description ?? `Command ${data.name}`
@@ -553,20 +723,26 @@ var CommandOptionsSchema = z4.object({
553
723
  function defineCommand(options) {
554
724
  return new Command(options);
555
725
  }
556
-
557
- // src/core/managers/BaseClientManager.ts
558
- var BaseClientManager = class {
559
- constructor(client) {
560
- this.client = client;
726
+ var HotReloadable2 = class {
727
+ constructor(entryDirectory) {
728
+ this.entryDirectory = entryDirectory;
729
+ entryDirectory = resolve(entryDirectory);
730
+ }
731
+ unloadFile(path) {
732
+ return loader_exports.unload(path);
561
733
  }
562
734
  };
563
735
 
564
736
  // src/core/managers/CommandManager.ts
565
- var CommandManager = class extends BaseClientManager {
737
+ var CommandManager = class extends HotReloadable2 {
738
+ constructor(client) {
739
+ super(join(getEntryDirectory(), "commands"));
740
+ this.client = client;
741
+ }
566
742
  commands = new Collection();
567
743
  entries = new Collection();
568
- async loadModules(entryDir) {
569
- let pattern = posix.join(posix.resolve(entryDir), "commands", "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((c) => !!c);
744
+ async loadModules() {
745
+ let pattern = join(this.entryDirectory, "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((c) => !!c);
570
746
  return console.log(`[Loader] Loaded ${filtered.length}/${files.length} command(s)`), filtered;
571
747
  }
572
748
  /**
@@ -595,7 +771,7 @@ var CommandManager = class extends BaseClientManager {
595
771
  async unload(path) {
596
772
  path = resolve(path);
597
773
  let command = this.entries.get(path);
598
- if (this.entries.delete(path), await $unloadFile(path), !!command)
774
+ if (this.entries.delete(path), await this.unloadFile(path), !!command)
599
775
  return this.remove(command);
600
776
  }
601
777
  async reload(path) {
@@ -639,9 +815,9 @@ var CommandManager = class extends BaseClientManager {
639
815
  return this.commands.get(name);
640
816
  }
641
817
  };
642
- var ListenerOptionsSchema = z4.object({
643
- name: z4.enum(Events),
644
- once: z4.boolean().default(false)
818
+ var ListenerOptionsSchema = z2.object({
819
+ name: z2.enum(Events),
820
+ once: z2.boolean().default(false)
645
821
  }), Listener = class extends LifecycleManager {
646
822
  options;
647
823
  constructor(options) {
@@ -662,12 +838,16 @@ var Context = class {
662
838
  };
663
839
 
664
840
  // src/core/managers/ListenerManager.ts
665
- var ListenerManager = class extends BaseClientManager {
841
+ var ListenerManager = class extends HotReloadable2 {
842
+ constructor(client) {
843
+ super(join(getEntryDirectory(), "listeners"));
844
+ this.client = client;
845
+ }
666
846
  listeners = [];
667
847
  entries = new Collection();
668
848
  executors = /* @__PURE__ */ new WeakMap();
669
- async loadModules(entryDir) {
670
- let pattern = posix.join(posix.resolve(entryDir), "listeners", "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((l) => !!l);
849
+ async loadModules() {
850
+ let pattern = join(this.entryDirectory, "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((l) => !!l);
671
851
  return console.log(`[Loader] Loaded ${filtered.length}/${files.length} listener(s)`), filtered;
672
852
  }
673
853
  /**
@@ -696,7 +876,7 @@ var ListenerManager = class extends BaseClientManager {
696
876
  async unload(path) {
697
877
  path = resolve(path);
698
878
  let listener = this.entries.get(path);
699
- if (this.entries.delete(path), await $unloadFile(path), !!listener)
879
+ if (this.entries.delete(path), await this.unloadFile(path), !!listener)
700
880
  return this.remove(listener)?.[0];
701
881
  }
702
882
  async reload(path) {
@@ -796,6 +976,13 @@ var BakitClient = class extends Client {
796
976
  return `${this.constructor.name} {}`;
797
977
  }
798
978
  };
979
+
980
+ // src/core/managers/BaseClientManager.ts
981
+ var BaseClientManager = class {
982
+ constructor(client) {
983
+ this.client = client;
984
+ }
985
+ };
799
986
  var BaseCommandContext = class extends Context {
800
987
  constructor(source) {
801
988
  super();
@@ -859,57 +1046,6 @@ var BaseCommandContext = class extends Context {
859
1046
  return await channel.send(options);
860
1047
  }
861
1048
  };
862
- var CONFIG_EXTENSIONS = ["ts", "js"], ProjectConfigSchema = z4.object({
863
- /**
864
- * The gateway intents to use for the Discord client.
865
- *
866
- * - `auto` — automatically determine the required intents.
867
- * - bigint — a raw bitfield value representing the combined intents.
868
- * - array — a list of individual intent flags from `GatewayIntentBits`.
869
- *
870
- * @defaultvalue `auto`
871
- */
872
- intents: z4.union([z4.literal("auto"), z4.bigint(), z4.array(z4.enum(GatewayIntentBits))]).default("auto"),
873
- /**
874
- * Optional custom client options for Discord.js (excluding `intents`).
875
- *
876
- * These are passed directly to the `Client` constructor when initializing the bot.
877
- *
878
- * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
879
- */
880
- clientOptions: z4.custom().optional(),
881
- /**
882
- * Your bot prefixes to trigger the commands.
883
- */
884
- prefixes: z4.array(z4.string()).default([]),
885
- /**
886
- * Your Discord bot token.
887
- */
888
- token: z4.string()
889
- });
890
- function defineConfig(config) {
891
- return config;
892
- }
893
- var _config;
894
- async function loadConfig(cwd = process.cwd()) {
895
- if (_config)
896
- return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
897
- let globPattern = `bakit.config.{${CONFIG_EXTENSIONS.join(",")}}`, [configPath, other] = await glob(globPattern, {
898
- cwd: cwd.replace(/\\/g, "/"),
899
- // ensure the path uses `/` instead of `\` on Windows
900
- absolute: true
901
- });
902
- if (!configPath)
903
- throw new Error("Missing config file");
904
- other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
905
- let config = (await import(pathToFileURL(configPath).href)).default;
906
- return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
907
- }
908
- function getConfig() {
909
- if (!_config)
910
- throw new Error("Project config is not loaded.");
911
- return _config;
912
- }
913
1049
  var ProjectCacheManager = class {
914
1050
  rootDir;
915
1051
  constructor(root = process.cwd()) {
@@ -1003,12 +1139,11 @@ chatInputCommandHandler.main(async (_, interaction) => {
1003
1139
  });
1004
1140
 
1005
1141
  // src/core/internal/Instance.ts
1006
- var HOT_DIRECTORIES = ["listeners", "commands"], SOURCE_ROOT = resolve(process.cwd(), "src"), APP_ENTRY_POINTS = /* @__PURE__ */ new Set([resolve(SOURCE_ROOT, "index.ts"), resolve(SOURCE_ROOT, "index.js")]), Instance = class {
1142
+ var Instance = class {
1007
1143
  client;
1008
1144
  cache;
1009
- rpc;
1010
1145
  constructor() {
1011
- this.cache = new ProjectCacheManager(), this.rpc = new RPC(process);
1146
+ this.cache = new ProjectCacheManager();
1012
1147
  }
1013
1148
  async start() {
1014
1149
  await loadConfig();
@@ -1022,80 +1157,19 @@ var HOT_DIRECTORIES = ["listeners", "commands"], SOURCE_ROOT = resolve(process.c
1022
1157
  ), await this.loadModules(), this.initIntents(), await this.client.login(config.token), this.initProcess();
1023
1158
  }
1024
1159
  initProcess() {
1025
- process.env.NODE_ENV === "development" && (this.rpc.on("fileRemove", (_id, path) => this.onFileRemove(path)), this.rpc.on("fileChange", (_id, path) => this.onFileChange(path))), process.on("SIGINT", () => this.shutdown()), process.on("SIGTERM", () => this.shutdown());
1160
+ process.on("SIGINT", () => this.shutdown()), process.on("SIGTERM", () => this.shutdown());
1026
1161
  }
1027
1162
  loadModules() {
1028
1163
  let { managers } = this.client, { commands, listeners } = managers;
1029
- return listeners.add(chatInputCommandHandler), listeners.add(messageCommandHandler), listeners.add(registerCommandsHandler), Promise.all([commands.loadModules("src"), listeners.loadModules("src")]);
1164
+ return listeners.add(chatInputCommandHandler), listeners.add(messageCommandHandler), listeners.add(registerCommandsHandler), addHotReloader(commands), addHotReloader(listeners), Promise.all([commands.loadModules(), listeners.loadModules()]);
1030
1165
  }
1031
1166
  initIntents() {
1032
1167
  let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
1033
1168
  config.intents === "auto" ? intents = listeners.getNeededIntents() : (intents = listeners.getBaseIntents(), typeof config.intents == "bigint" ? intents.bitfield = Number(config.intents) : intents.add(...config.intents)), options.intents = intents;
1034
1169
  }
1035
- isInHotDirectory(path) {
1036
- if (!path.startsWith(SOURCE_ROOT))
1037
- return false;
1038
- let topLevelDir = getTopLevelDirectory(path, SOURCE_ROOT);
1039
- return !!topLevelDir && HOT_DIRECTORIES.includes(topLevelDir);
1040
- }
1041
- restart() {
1042
- this.rpc.send("restart", {});
1043
- }
1044
1170
  async shutdown() {
1045
1171
  this.client && await this.client.destroy().catch(() => null), process.exit(0);
1046
1172
  }
1047
- containsEntryPoint(chain) {
1048
- return chain.some((x) => APP_ENTRY_POINTS.has(x));
1049
- }
1050
- containsHotModule(chain) {
1051
- return chain.some((x) => this.isInHotDirectory(x));
1052
- }
1053
- async onFileRemove(path) {
1054
- if (APP_ENTRY_POINTS.has(path)) {
1055
- this.restart();
1056
- return;
1057
- }
1058
- if (!isImported(path))
1059
- return;
1060
- let chain = getDependencyChain(path);
1061
- if (this.containsEntryPoint(chain)) {
1062
- this.restart();
1063
- return;
1064
- }
1065
- if (this.containsHotModule(chain))
1066
- for (let path2 of chain.reverse())
1067
- await this.unloadModule(path2);
1068
- }
1069
- async onFileChange(path) {
1070
- if (APP_ENTRY_POINTS.has(path)) {
1071
- this.restart();
1072
- return;
1073
- }
1074
- if (!isImported(path))
1075
- return;
1076
- let chain = getDependencyChain(path);
1077
- if (this.containsEntryPoint(chain)) {
1078
- this.restart();
1079
- return;
1080
- }
1081
- if (this.containsHotModule(chain))
1082
- for (let path2 of chain.reverse())
1083
- await this.unloadModule(path2, true);
1084
- }
1085
- async unloadModule(path, reload = false) {
1086
- let { listeners, commands } = this.client.managers;
1087
- switch (getTopLevelDirectory(path, SOURCE_ROOT)) {
1088
- case "listeners":
1089
- await listeners[reload ? "reload" : "unload"](path);
1090
- break;
1091
- case "commands":
1092
- await commands[reload ? "reload" : "unload"](path);
1093
- break;
1094
- default:
1095
- await $unloadFile(path);
1096
- break;
1097
- }
1098
- }
1099
1173
  };
1100
1174
  function useApp() {
1101
1175
  return new Instance();
@@ -1111,4 +1185,4 @@ var Params = {
1111
1185
  user: createFactory(UserParam)
1112
1186
  };
1113
1187
 
1114
- export { $initLoader, $unloadFile, ArgumentError, BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectCacheManager, ProjectConfigSchema, RPC, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getDependencyChain, getImporters, getTopLevelDirectory, isImported, isImportedBy, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
1188
+ export { ArgumentError, BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, HotReloadable2 as HotReloadable, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, loader_exports as Loader, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectCacheManager, ProjectConfigSchema, RPC, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getEntryDirectory, getEntryFile, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };
package/dist/register.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $initLoader } from 'bakit';
1
+ import { Loader } from 'bakit';
2
2
 
3
3
  // src/lib/loader/register.ts
4
- $initLoader();
4
+ Loader.init();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bakit",
3
- "version": "2.0.0-alpha.27",
3
+ "version": "2.0.0-alpha.29",
4
4
  "description": "A framework for discord.js",
5
5
  "type": "module",
6
6
  "exports": {