@seedcord/core 0.1.0-next.6 → 0.1.0-next.7
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/{AutocompleteOptions-eKhIe6Qt.d.mts → AutocompleteOptions-DLp_B8as.d.mts} +61 -112
- package/dist/HmrModuleHandler-3sNgKHdf.mjs +163 -0
- package/dist/HmrModuleHandler-3sNgKHdf.mjs.map +1 -0
- package/dist/HmrModuleHandler-CTBng7wV.cjs +170 -0
- package/dist/HmrModuleHandler-CTBng7wV.cjs.map +1 -0
- package/dist/Plugin-3rlH0uxH.d.mts +620 -0
- package/dist/{Plugin-C2-VHrsX.cjs → Plugin-Cgp1a0uQ.cjs} +13 -4
- package/dist/Plugin-Cgp1a0uQ.cjs.map +1 -0
- package/dist/{Plugin-PcdPjAzc.mjs → Plugin-j9GYdroS.mjs} +13 -4
- package/dist/Plugin-j9GYdroS.mjs.map +1 -0
- package/dist/{permissions-DSW_EZfN.cjs → Silence-DiP95iwD.cjs} +38 -307
- package/dist/Silence-DiP95iwD.cjs.map +1 -0
- package/dist/{permissions-RVCBhL2n.mjs → Silence-dbC2slOB.mjs} +35 -238
- package/dist/Silence-dbC2slOB.mjs.map +1 -0
- package/dist/basic-D7bDlgSP.d.mts +10 -0
- package/dist/hmr.index.cjs +2 -164
- package/dist/hmr.index.mjs +2 -162
- package/dist/index-Dueqztyy.d.mts +142 -0
- package/dist/index.cjs +50 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +7 -5
- package/dist/index.mjs +8 -35
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.cjs +262 -74
- package/dist/internal.index.cjs.map +1 -1
- package/dist/internal.index.d.mts +68 -4
- package/dist/internal.index.mjs +216 -35
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.cjs +123 -52
- package/dist/node-internal.index.cjs.map +1 -1
- package/dist/node-internal.index.d.mts +67 -2158
- package/dist/node-internal.index.mjs +124 -54
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/plugin.index.cjs +1 -1
- package/dist/plugin.index.d.mts +1 -1
- package/dist/plugin.index.mjs +1 -1
- package/dist/publishDefault-CCf1azzW.mjs +264 -0
- package/dist/publishDefault-CCf1azzW.mjs.map +1 -0
- package/dist/publishDefault-l0NQ-SHo.cjs +353 -0
- package/dist/publishDefault-l0NQ-SHo.cjs.map +1 -0
- package/dist/subscribers-B1G6_LlK.cjs +517 -0
- package/dist/subscribers-B1G6_LlK.cjs.map +1 -0
- package/dist/subscribers-DYqFVP1X.mjs +480 -0
- package/dist/subscribers-DYqFVP1X.mjs.map +1 -0
- package/package.json +7 -6
- package/dist/Plugin-C2-VHrsX.cjs.map +0 -1
- package/dist/Plugin-CNyRKDHT.d.mts +0 -226
- package/dist/Plugin-PcdPjAzc.mjs.map +0 -1
- package/dist/hmr.index.cjs.map +0 -1
- package/dist/hmr.index.mjs.map +0 -1
- package/dist/is-never-Ddaba1Su.d.mts +0 -58
- package/dist/permissions-DSW_EZfN.cjs.map +0 -1
- package/dist/permissions-RVCBhL2n.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { t as Constructor } from "./basic-D7bDlgSP.mjs";
|
|
2
|
+
import { A as Notice, N as AckState, W as InteractionRoutes, j as ReplyTelemetry, y as CoreBase } from "./Plugin-3rlH0uxH.mjs";
|
|
3
3
|
import { APIMessageTopLevelComponent, APIModalInteractionResponseCallbackData, ApplicationCommandType, ChannelType, Snowflake } from "discord-api-types/v10";
|
|
4
4
|
import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
|
|
5
|
-
import { DeferOpts,
|
|
5
|
+
import { DeferOpts, ReplyResponse, SendOpts } from "@seedcord/types";
|
|
6
6
|
|
|
7
7
|
//#region src/registries/SlashOptionRegistry.d.ts
|
|
8
8
|
/** A slash option kind as a plain string union (`string`, `channel`, ...), keyed to the registry. */
|
|
@@ -152,13 +152,61 @@ declare abstract class RowComponent<RowKey extends RowType> extends BaseComponen
|
|
|
152
152
|
get component(): InstantiatedActionRow<RowKey>;
|
|
153
153
|
}
|
|
154
154
|
//#endregion
|
|
155
|
-
//#region ../../node_modules/.pnpm/type-fest@5.7.0/node_modules/type-fest/source/
|
|
155
|
+
//#region ../../node_modules/.pnpm/type-fest@5.7.0/node_modules/type-fest/source/is-never.d.ts
|
|
156
156
|
/**
|
|
157
|
-
|
|
157
|
+
Returns a boolean for whether the given type is `never`.
|
|
158
158
|
|
|
159
|
-
@
|
|
159
|
+
@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
|
|
160
|
+
@link https://stackoverflow.com/a/53984913/10292952
|
|
161
|
+
@link https://www.zhenghao.io/posts/ts-never
|
|
162
|
+
|
|
163
|
+
Useful in type utilities, such as checking if something does not occur.
|
|
164
|
+
|
|
165
|
+
@example
|
|
166
|
+
```
|
|
167
|
+
import type {IsNever, And} from 'type-fest';
|
|
168
|
+
|
|
169
|
+
type A = IsNever<never>;
|
|
170
|
+
//=> true
|
|
171
|
+
|
|
172
|
+
type B = IsNever<any>;
|
|
173
|
+
//=> false
|
|
174
|
+
|
|
175
|
+
type C = IsNever<unknown>;
|
|
176
|
+
//=> false
|
|
177
|
+
|
|
178
|
+
type D = IsNever<never[]>;
|
|
179
|
+
//=> false
|
|
180
|
+
|
|
181
|
+
type E = IsNever<object>;
|
|
182
|
+
//=> false
|
|
183
|
+
|
|
184
|
+
type F = IsNever<string>;
|
|
185
|
+
//=> false
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
@example
|
|
189
|
+
```
|
|
190
|
+
import type {IsNever} from 'type-fest';
|
|
191
|
+
|
|
192
|
+
type IsTrue<T> = T extends true ? true : false;
|
|
193
|
+
|
|
194
|
+
// When a distributive conditional is instantiated with `never`, the entire conditional results in `never`.
|
|
195
|
+
type A = IsTrue<never>;
|
|
196
|
+
//=> never
|
|
197
|
+
|
|
198
|
+
// If you don't want that behaviour, you can explicitly add an `IsNever` check before the distributive conditional.
|
|
199
|
+
type IsTrueFixed<T> =
|
|
200
|
+
IsNever<T> extends true ? false : T extends true ? true : false;
|
|
201
|
+
|
|
202
|
+
type B = IsTrueFixed<never>;
|
|
203
|
+
//=> false
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
@category Type Guard
|
|
207
|
+
@category Utilities
|
|
160
208
|
*/
|
|
161
|
-
type
|
|
209
|
+
type IsNever<T> = [T] extends [never] ? true : false;
|
|
162
210
|
//#endregion
|
|
163
211
|
//#region ../../node_modules/.pnpm/type-fest@5.7.0/node_modules/type-fest/source/non-empty-tuple.d.ts
|
|
164
212
|
/**
|
|
@@ -220,29 +268,6 @@ declare function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;
|
|
|
220
268
|
*/
|
|
221
269
|
declare function RegisterCommand(scope: 'guild', guilds: string[]): (ctor: CommandCtor) => void;
|
|
222
270
|
//#endregion
|
|
223
|
-
//#region src/metadataKeys.d.ts
|
|
224
|
-
declare enum InteractionRoutes {
|
|
225
|
-
Slash = "slash",
|
|
226
|
-
Button = "button",
|
|
227
|
-
Modal = "modal",
|
|
228
|
-
StringMenu = "stringMenu",
|
|
229
|
-
UserMenu = "userMenu",
|
|
230
|
-
RoleMenu = "roleMenu",
|
|
231
|
-
ChannelMenu = "channelMenu",
|
|
232
|
-
MentionableMenu = "mentionableMenu",
|
|
233
|
-
MessageContextMenu = "messageContextMenu",
|
|
234
|
-
UserContextMenu = "userContextMenu",
|
|
235
|
-
Autocomplete = "autocomplete"
|
|
236
|
-
}
|
|
237
|
-
declare const InteractionMetadataKey: unique symbol;
|
|
238
|
-
declare const InteractionRouteKeys: Record<InteractionRoutes, symbol>;
|
|
239
|
-
declare const MiddlewareMetadataKey: unique symbol;
|
|
240
|
-
declare const EventMetadataKey: unique symbol;
|
|
241
|
-
declare const CommandMetadataKey: unique symbol;
|
|
242
|
-
declare const GatedMetadataKey: unique symbol;
|
|
243
|
-
declare const SubscribeMetadataKey: unique symbol;
|
|
244
|
-
declare const WebhookUrlMetadataKey: unique symbol;
|
|
245
|
-
//#endregion
|
|
246
271
|
//#region src/customId/Field.d.ts
|
|
247
272
|
/**
|
|
248
273
|
* One field in a customId shape, its wire kind plus the type it decodes to.
|
|
@@ -433,19 +458,6 @@ declare function storeComponentRoute(route: InteractionRoutes, defs: readonly An
|
|
|
433
458
|
declare function selectMenuRouteOf(kind: SelectMenuKind): InteractionRoutes;
|
|
434
459
|
declare function contextMenuRouteOf(kind: ApplicationCommandType.User | ApplicationCommandType.Message): InteractionRoutes;
|
|
435
460
|
//#endregion
|
|
436
|
-
//#region src/reply/AckTrace.d.ts
|
|
437
|
-
/** The first ack's stack, passed as the throw cause so a double-ack points to both lines. */
|
|
438
|
-
declare class AckTrace extends Error {
|
|
439
|
-
constructor(method: ReplyMethod);
|
|
440
|
-
}
|
|
441
|
-
//#endregion
|
|
442
|
-
//#region src/reply/ackLegality.d.ts
|
|
443
|
-
type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';
|
|
444
|
-
type ReplyMethod = 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';
|
|
445
|
-
/** Throws before any API call. `ackedBy` becomes the throw cause, its stack points at the first ack. */
|
|
446
|
-
declare function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void;
|
|
447
|
-
declare function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp';
|
|
448
|
-
//#endregion
|
|
449
461
|
//#region src/reply/serializeReply.d.ts
|
|
450
462
|
/** Serialized message data the transport wire writers consume. */
|
|
451
463
|
interface SerializedReply {
|
|
@@ -471,10 +483,13 @@ declare abstract class BaseReplySender<TMessage extends {
|
|
|
471
483
|
id: string;
|
|
472
484
|
}> {
|
|
473
485
|
protected readonly routeId: string;
|
|
486
|
+
private readonly telemetry?;
|
|
474
487
|
private state;
|
|
475
488
|
private readonly sent;
|
|
476
489
|
private ackedBy?;
|
|
477
|
-
protected constructor(routeId: string, initialState?: AckState);
|
|
490
|
+
protected constructor(routeId: string, initialState?: AckState, telemetry?: ReplyTelemetry | undefined);
|
|
491
|
+
private report;
|
|
492
|
+
private attempt;
|
|
478
493
|
reply(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage>;
|
|
479
494
|
defer(opts?: DeferOpts): Promise<void>;
|
|
480
495
|
deferUpdate(): Promise<void>;
|
|
@@ -722,72 +737,6 @@ declare function defineEffectGate<const Name extends string, Ctx extends GateCon
|
|
|
722
737
|
*/
|
|
723
738
|
type RequiredOf<TGate> = TGate extends Gate<infer Ctx> ? Ctx : never;
|
|
724
739
|
//#endregion
|
|
725
|
-
//#region src/stops/Notice.d.ts
|
|
726
|
-
/**
|
|
727
|
-
* Base class for a user-facing refusal or a reported fault.
|
|
728
|
-
*
|
|
729
|
-
* Throw a `Notice` to stop a handler and reply to the user. The framework catches it at the controller
|
|
730
|
-
* boundary and renders {@link Notice.render}, which always decides what the user sees. With `report`
|
|
731
|
-
* false that render is all that happens. With `report` true the framework also logs the fault and
|
|
732
|
-
* publishes it to the `handledException` bus. A raw, non-Notice throw shows the generic message.
|
|
733
|
-
*
|
|
734
|
-
* @example
|
|
735
|
-
* ```ts
|
|
736
|
-
* import { Notice, BuilderComponent, type RenderContext, type ReplyResponse } from '@seedcord/gateway';
|
|
737
|
-
* import { TextDisplayBuilder } from '@discordjs/builders';
|
|
738
|
-
*
|
|
739
|
-
* // reading `.component` applies the configured bot color to the container accent
|
|
740
|
-
* class TooPoorCard extends BuilderComponent<'container'> {
|
|
741
|
-
* constructor(balance: number) {
|
|
742
|
-
* super('container');
|
|
743
|
-
* this.instance.addTextDisplayComponents(
|
|
744
|
-
* new TextDisplayBuilder().setContent(`### Insufficient balance\nYou need more than ${balance} coins.`)
|
|
745
|
-
* );
|
|
746
|
-
* }
|
|
747
|
-
* }
|
|
748
|
-
*
|
|
749
|
-
* class TooPoor extends Notice {
|
|
750
|
-
* constructor(private readonly balance: number) {
|
|
751
|
-
* super(`balance ${balance} is below the cost`);
|
|
752
|
-
* }
|
|
753
|
-
*
|
|
754
|
-
* render(_ctx: RenderContext): ReplyResponse {
|
|
755
|
-
* return { components: [new TooPoorCard(this.balance).component] };
|
|
756
|
-
* }
|
|
757
|
-
* }
|
|
758
|
-
*
|
|
759
|
-
* // in a handler, throwing stops the handler and replies with render(ctx)
|
|
760
|
-
* if (wallet.balance < cost) throw new TooPoor(wallet.balance);
|
|
761
|
-
* ```
|
|
762
|
-
*/
|
|
763
|
-
declare abstract class Notice extends Error {
|
|
764
|
-
/**
|
|
765
|
-
* Whether this denial is a reported fault. True also logs it and publishes it to the `handledException`
|
|
766
|
-
* bus. The user always sees {@link Notice.render} either way.
|
|
767
|
-
*
|
|
768
|
-
* @defaultValue `false`
|
|
769
|
-
*/
|
|
770
|
-
report: boolean;
|
|
771
|
-
/**
|
|
772
|
-
* Whether the reply is ephemeral, so only the invoking user sees it. Set it false for a refusal the
|
|
773
|
-
* whole channel should see.
|
|
774
|
-
*
|
|
775
|
-
* @defaultValue `true`
|
|
776
|
-
*/
|
|
777
|
-
ephemeral: boolean;
|
|
778
|
-
/**
|
|
779
|
-
* A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
|
|
780
|
-
* lists them instead of showing a neutral message.
|
|
781
|
-
*/
|
|
782
|
-
summary?: string;
|
|
783
|
-
protected constructor(message: string, options?: ErrorOptions);
|
|
784
|
-
/**
|
|
785
|
-
* Builds what the user sees. Called fresh each time the denial is shown, so the builders are new
|
|
786
|
-
* and the bot color resolves at render time rather than at construction time.
|
|
787
|
-
*/
|
|
788
|
-
abstract render(ctx: RenderContext): ReplyResponse;
|
|
789
|
-
}
|
|
790
|
-
//#endregion
|
|
791
740
|
//#region src/gates/matching.d.ts
|
|
792
741
|
type IntersectRequired<Gates extends readonly Gate<GateContextBase>[]> = Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? RequiredOf<First> & IntersectRequired<Rest> : unknown;
|
|
793
742
|
type TwoOrMore<Item> = readonly [Item, Item, ...Item[]];
|
|
@@ -989,5 +938,5 @@ type Getter<Route extends keyof SlashOptionRegistry, Kind extends OptionKind, Me
|
|
|
989
938
|
*/
|
|
990
939
|
type AutocompleteOptions<Route extends keyof SlashOptionRegistry> = Getter<Route, 'string', 'getString'> & Getter<Route, 'integer', 'getInteger'> & Getter<Route, 'number', 'getNumber'> & Getter<Route, 'boolean', 'getBoolean'>;
|
|
991
940
|
//#endregion
|
|
992
|
-
export {
|
|
993
|
-
//# sourceMappingURL=AutocompleteOptions-
|
|
941
|
+
export { SlashOptionRegistry as $, RoutableConstructor as A, decodeFor as B, RequiredOf as C, ModalLike as D, BaseReplySender as E, selectMenuRouteOf as F, RegisterCommand as G, CustomIdField as H, storeComponentRoute as I, RowComponent as J, IsNever as K, storeInteractionRoute as L, areRoutes as M, contextMenuRouteOf as N, SerializedReply as O, interactionRoutesOf as P, SlashOption as Q, AnyCustomId as R, GuildPermissionsContext as S, defineGate as T, DecodedParams as U, prefixOf as V, CommandMeta as W, RowType as X, BuilderType as Y, OptionKind as Z, JoinNames as _, FocusedField as a, Gate as b, RequireBotPermissions as c, RequireRole as d, RequireRoleOptions as f, IntersectRequired as g, GateFitsWith as h, EntryFor as i, SelectMenuKind as j, serializeReply as k, RequirePermissions as l, pickNotice as m, AutocompleteOptions as n, PermissionNames as o, GateNoticeOptions as p, BuilderComponent as q, ChoiceValueOf as r, PermissionScope as s, AutocompletableNames as t, RequirePermissionsOptions as u, TwoOrMore as v, defineEffectGate as w, GateContextBase as x, EffectGate as y, CustomId as z };
|
|
942
|
+
//# sourceMappingURL=AutocompleteOptions-DLp_B8as.d.mts.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { formatFilePath } from "@seedcord/utils";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve, sep } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
//#region src/hmr/HmrModuleHandler.ts
|
|
8
|
+
const displayName = (unit, fallback) => unit.name ?? fallback;
|
|
9
|
+
/**
|
|
10
|
+
* Reloads THandler and optional TMiddleware modules on HMR updates.
|
|
11
|
+
*
|
|
12
|
+
* Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.
|
|
13
|
+
*
|
|
14
|
+
* This is only useful during development.
|
|
15
|
+
*/
|
|
16
|
+
var HmrModuleHandler = class {
|
|
17
|
+
store;
|
|
18
|
+
options;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = {
|
|
21
|
+
...options,
|
|
22
|
+
logger: options.logger.inChannel("hmr")
|
|
23
|
+
};
|
|
24
|
+
this.store = {
|
|
25
|
+
fileToHandlers: /* @__PURE__ */ new Map(),
|
|
26
|
+
fileToMiddlewares: /* @__PURE__ */ new Map(),
|
|
27
|
+
handlerArtifacts: /* @__PURE__ */ new Map()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */
|
|
31
|
+
async handle(event) {
|
|
32
|
+
const { file, affectedModules, type } = event;
|
|
33
|
+
const { logger, handlersDir, middlewaresDir } = this.options;
|
|
34
|
+
if (type === "delete" || type === "deleteDir") {
|
|
35
|
+
this.unload(file);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (type === "createDir") return;
|
|
39
|
+
if (type === "update" && !existsSync(file)) {
|
|
40
|
+
if (this.isTracked(file)) {
|
|
41
|
+
logger.info(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);
|
|
42
|
+
this.unload(file);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const rollbackEnabled = event.rollback ?? true;
|
|
47
|
+
const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];
|
|
48
|
+
const absHandlersDir = resolve(process.cwd(), handlersDir);
|
|
49
|
+
const absMiddlewaresDir = middlewaresDir ? resolve(process.cwd(), middlewaresDir) : null;
|
|
50
|
+
for (const fileToReload of filesToReload) {
|
|
51
|
+
const isHandler = fileToReload.startsWith(absHandlersDir + sep);
|
|
52
|
+
const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir + sep) : false;
|
|
53
|
+
if (!isHandler && !isMiddleware) continue;
|
|
54
|
+
await this.reloadWithRollback(fileToReload, rollbackEnabled);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async reloadWithRollback(file, rollbackEnabled) {
|
|
58
|
+
const { logger } = this.options;
|
|
59
|
+
const snapshot = this.snapshotUnits(file);
|
|
60
|
+
this.unload(file);
|
|
61
|
+
if (await this.reloadFile(file)) return;
|
|
62
|
+
this.unload(file);
|
|
63
|
+
if (!rollbackEnabled) return;
|
|
64
|
+
if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return;
|
|
65
|
+
this.restoreUnits(file, snapshot);
|
|
66
|
+
logger.warn(`${chalk.yellow.bold("Rolled back")} ${chalk.gray(formatFilePath(file))} to the last-good version`);
|
|
67
|
+
}
|
|
68
|
+
/** Links the handler to its source file so a later update can unload it. */
|
|
69
|
+
trackHandler(file, handler) {
|
|
70
|
+
let set = this.store.fileToHandlers.get(file);
|
|
71
|
+
if (!set) {
|
|
72
|
+
set = /* @__PURE__ */ new Set();
|
|
73
|
+
this.store.fileToHandlers.set(file, set);
|
|
74
|
+
}
|
|
75
|
+
set.add(handler);
|
|
76
|
+
if (this.options.getArtifacts) {
|
|
77
|
+
const artifacts = this.options.getArtifacts(handler);
|
|
78
|
+
this.store.handlerArtifacts.set(handler, artifacts);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Links the middleware to its source file so a later update can unload it. */
|
|
82
|
+
trackMiddleware(file, middleware) {
|
|
83
|
+
let set = this.store.fileToMiddlewares.get(file);
|
|
84
|
+
if (!set) {
|
|
85
|
+
set = /* @__PURE__ */ new Set();
|
|
86
|
+
this.store.fileToMiddlewares.set(file, set);
|
|
87
|
+
}
|
|
88
|
+
set.add(middleware);
|
|
89
|
+
}
|
|
90
|
+
isTracked(file) {
|
|
91
|
+
return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);
|
|
92
|
+
}
|
|
93
|
+
unload(file) {
|
|
94
|
+
const { logger } = this.options;
|
|
95
|
+
const handlers = this.store.fileToHandlers.get(file);
|
|
96
|
+
if (handlers) {
|
|
97
|
+
for (const handler of handlers) {
|
|
98
|
+
const artifacts = this.store.handlerArtifacts.get(handler);
|
|
99
|
+
this.options.unregisterHandler(handler, artifacts);
|
|
100
|
+
this.store.handlerArtifacts.delete(handler);
|
|
101
|
+
const name = displayName(handler, "Handler");
|
|
102
|
+
logger.info(`${chalk.red.bold("Unloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
|
|
103
|
+
}
|
|
104
|
+
this.store.fileToHandlers.delete(file);
|
|
105
|
+
}
|
|
106
|
+
const middlewares = this.store.fileToMiddlewares.get(file);
|
|
107
|
+
if (middlewares) {
|
|
108
|
+
for (const middleware of middlewares) {
|
|
109
|
+
this.options.unregisterMiddleware?.(middleware);
|
|
110
|
+
const name = displayName(middleware, "Middleware");
|
|
111
|
+
logger.info(`${chalk.red.bold("Unloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
|
|
112
|
+
}
|
|
113
|
+
this.store.fileToMiddlewares.delete(file);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async reloadFile(file) {
|
|
117
|
+
const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;
|
|
118
|
+
if (!existsSync(file)) {
|
|
119
|
+
logger.info(`File does not exist, skipping reload: ${formatFilePath(file)}`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
let fileUrl = pathToFileURL(file).href;
|
|
124
|
+
if (process.env.VITEST === "true") fileUrl += `?update=${Date.now()}`;
|
|
125
|
+
const imported = await import(fileUrl);
|
|
126
|
+
for (const val of Object.values(imported)) if (isHandler(val)) {
|
|
127
|
+
registerHandler(val, file);
|
|
128
|
+
this.trackHandler(file, val);
|
|
129
|
+
const name = displayName(val, "Handler");
|
|
130
|
+
logger.info(`${chalk.blue.bold("Reloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
|
|
131
|
+
} else if (isMiddleware && registerMiddleware && isMiddleware(val)) {
|
|
132
|
+
registerMiddleware(val, file);
|
|
133
|
+
this.trackMiddleware(file, val);
|
|
134
|
+
const name = displayName(val, "Middleware");
|
|
135
|
+
logger.info(`${chalk.blue.bold("Reloaded")} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`);
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logger.error(`Failed to reload file: ${file}`, error);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
snapshotUnits(file) {
|
|
144
|
+
return {
|
|
145
|
+
handlers: [...this.store.fileToHandlers.get(file) ?? []],
|
|
146
|
+
middlewares: [...this.store.fileToMiddlewares.get(file) ?? []]
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
restoreUnits(file, snapshot) {
|
|
150
|
+
for (const handler of snapshot.handlers) {
|
|
151
|
+
this.options.registerHandler(handler, file);
|
|
152
|
+
this.trackHandler(file, handler);
|
|
153
|
+
}
|
|
154
|
+
if (this.options.registerMiddleware) for (const middleware of snapshot.middlewares) {
|
|
155
|
+
this.options.registerMiddleware(middleware, file);
|
|
156
|
+
this.trackMiddleware(file, middleware);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
export { HmrModuleHandler as t };
|
|
163
|
+
//# sourceMappingURL=HmrModuleHandler-3sNgKHdf.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HmrModuleHandler-3sNgKHdf.mjs","names":[],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { formatFilePath } from '@seedcord/utils';\nimport chalk from 'chalk';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n // the caller's options object stays unmutated\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed, a leaf reload never reconstructs the singleton that holds these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger, handlersDir, middlewaresDir } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.unload(file);\n return;\n }\n\n // a directory is never importable, its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.info(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.unload(file);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n const absHandlersDir = resolve(process.cwd(), handlersDir);\n const absMiddlewaresDir = middlewaresDir ? resolve(process.cwd(), middlewaresDir) : null;\n\n for (const fileToReload of filesToReload) {\n // the separator keeps a sibling dir sharing the prefix (commands-extra vs commands) out\n const isHandler = fileToReload.startsWith(absHandlersDir + sep);\n const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir + sep) : false;\n\n if (!isHandler && !isMiddleware) {\n continue;\n }\n\n await this.reloadWithRollback(fileToReload, rollbackEnabled);\n }\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<void> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded) return;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return;\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return;\n\n // a typo keeps them live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${chalk.yellow.bold('Rolled back')} ${chalk.gray(formatFilePath(file))} to the last-good version`);\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): void {\n const { logger } = this.options;\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n const name = displayName(handler, 'Handler');\n logger.info(\n `${chalk.red.bold('Unloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n const name = displayName(middleware, 'Middleware');\n logger.info(\n `${chalk.red.bold('Unloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n this.store.fileToMiddlewares.delete(file);\n }\n }\n\n private async reloadFile(file: string): Promise<boolean> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.info(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return false;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph, so bust the import cache manually.\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n const name = displayName(val, 'Handler');\n logger.info(\n `${chalk.blue.bold('Reloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n const name = displayName(val, 'Middleware');\n logger.info(\n `${chalk.blue.bold('Reloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n }\n\n return true;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return false;\n }\n }\n\n // restoring re-registers the same class objects, so a live db connection survives rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
|
|
2
|
+
let _seedcord_utils = require("@seedcord/utils");
|
|
3
|
+
let chalk = require("chalk");
|
|
4
|
+
chalk = require_rolldown_runtime.__toESM(chalk, 1);
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
let node_url = require("node:url");
|
|
8
|
+
|
|
9
|
+
//#region src/hmr/HmrModuleHandler.ts
|
|
10
|
+
const displayName = (unit, fallback) => unit.name ?? fallback;
|
|
11
|
+
/**
|
|
12
|
+
* Reloads THandler and optional TMiddleware modules on HMR updates.
|
|
13
|
+
*
|
|
14
|
+
* Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.
|
|
15
|
+
*
|
|
16
|
+
* This is only useful during development.
|
|
17
|
+
*/
|
|
18
|
+
var HmrModuleHandler = class {
|
|
19
|
+
store;
|
|
20
|
+
options;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.options = {
|
|
23
|
+
...options,
|
|
24
|
+
logger: options.logger.inChannel("hmr")
|
|
25
|
+
};
|
|
26
|
+
this.store = {
|
|
27
|
+
fileToHandlers: /* @__PURE__ */ new Map(),
|
|
28
|
+
fileToMiddlewares: /* @__PURE__ */ new Map(),
|
|
29
|
+
handlerArtifacts: /* @__PURE__ */ new Map()
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */
|
|
33
|
+
async handle(event) {
|
|
34
|
+
const { file, affectedModules, type } = event;
|
|
35
|
+
const { logger, handlersDir, middlewaresDir } = this.options;
|
|
36
|
+
if (type === "delete" || type === "deleteDir") {
|
|
37
|
+
this.unload(file);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (type === "createDir") return;
|
|
41
|
+
if (type === "update" && !(0, node_fs.existsSync)(file)) {
|
|
42
|
+
if (this.isTracked(file)) {
|
|
43
|
+
logger.info(`Received update for non-existent file: ${(0, _seedcord_utils.formatFilePath)(file)}, treating as delete`);
|
|
44
|
+
this.unload(file);
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const rollbackEnabled = event.rollback ?? true;
|
|
49
|
+
const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];
|
|
50
|
+
const absHandlersDir = (0, node_path.resolve)(process.cwd(), handlersDir);
|
|
51
|
+
const absMiddlewaresDir = middlewaresDir ? (0, node_path.resolve)(process.cwd(), middlewaresDir) : null;
|
|
52
|
+
for (const fileToReload of filesToReload) {
|
|
53
|
+
const isHandler = fileToReload.startsWith(absHandlersDir + node_path.sep);
|
|
54
|
+
const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir + node_path.sep) : false;
|
|
55
|
+
if (!isHandler && !isMiddleware) continue;
|
|
56
|
+
await this.reloadWithRollback(fileToReload, rollbackEnabled);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async reloadWithRollback(file, rollbackEnabled) {
|
|
60
|
+
const { logger } = this.options;
|
|
61
|
+
const snapshot = this.snapshotUnits(file);
|
|
62
|
+
this.unload(file);
|
|
63
|
+
if (await this.reloadFile(file)) return;
|
|
64
|
+
this.unload(file);
|
|
65
|
+
if (!rollbackEnabled) return;
|
|
66
|
+
if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return;
|
|
67
|
+
this.restoreUnits(file, snapshot);
|
|
68
|
+
logger.warn(`${chalk.default.yellow.bold("Rolled back")} ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))} to the last-good version`);
|
|
69
|
+
}
|
|
70
|
+
/** Links the handler to its source file so a later update can unload it. */
|
|
71
|
+
trackHandler(file, handler) {
|
|
72
|
+
let set = this.store.fileToHandlers.get(file);
|
|
73
|
+
if (!set) {
|
|
74
|
+
set = /* @__PURE__ */ new Set();
|
|
75
|
+
this.store.fileToHandlers.set(file, set);
|
|
76
|
+
}
|
|
77
|
+
set.add(handler);
|
|
78
|
+
if (this.options.getArtifacts) {
|
|
79
|
+
const artifacts = this.options.getArtifacts(handler);
|
|
80
|
+
this.store.handlerArtifacts.set(handler, artifacts);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Links the middleware to its source file so a later update can unload it. */
|
|
84
|
+
trackMiddleware(file, middleware) {
|
|
85
|
+
let set = this.store.fileToMiddlewares.get(file);
|
|
86
|
+
if (!set) {
|
|
87
|
+
set = /* @__PURE__ */ new Set();
|
|
88
|
+
this.store.fileToMiddlewares.set(file, set);
|
|
89
|
+
}
|
|
90
|
+
set.add(middleware);
|
|
91
|
+
}
|
|
92
|
+
isTracked(file) {
|
|
93
|
+
return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);
|
|
94
|
+
}
|
|
95
|
+
unload(file) {
|
|
96
|
+
const { logger } = this.options;
|
|
97
|
+
const handlers = this.store.fileToHandlers.get(file);
|
|
98
|
+
if (handlers) {
|
|
99
|
+
for (const handler of handlers) {
|
|
100
|
+
const artifacts = this.store.handlerArtifacts.get(handler);
|
|
101
|
+
this.options.unregisterHandler(handler, artifacts);
|
|
102
|
+
this.store.handlerArtifacts.delete(handler);
|
|
103
|
+
const name = displayName(handler, "Handler");
|
|
104
|
+
logger.info(`${chalk.default.red.bold("Unloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
105
|
+
}
|
|
106
|
+
this.store.fileToHandlers.delete(file);
|
|
107
|
+
}
|
|
108
|
+
const middlewares = this.store.fileToMiddlewares.get(file);
|
|
109
|
+
if (middlewares) {
|
|
110
|
+
for (const middleware of middlewares) {
|
|
111
|
+
this.options.unregisterMiddleware?.(middleware);
|
|
112
|
+
const name = displayName(middleware, "Middleware");
|
|
113
|
+
logger.info(`${chalk.default.red.bold("Unloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
114
|
+
}
|
|
115
|
+
this.store.fileToMiddlewares.delete(file);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async reloadFile(file) {
|
|
119
|
+
const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;
|
|
120
|
+
if (!(0, node_fs.existsSync)(file)) {
|
|
121
|
+
logger.info(`File does not exist, skipping reload: ${(0, _seedcord_utils.formatFilePath)(file)}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
let fileUrl = (0, node_url.pathToFileURL)(file).href;
|
|
126
|
+
if (process.env.VITEST === "true") fileUrl += `?update=${Date.now()}`;
|
|
127
|
+
const imported = await import(fileUrl);
|
|
128
|
+
for (const val of Object.values(imported)) if (isHandler(val)) {
|
|
129
|
+
registerHandler(val, file);
|
|
130
|
+
this.trackHandler(file, val);
|
|
131
|
+
const name = displayName(val, "Handler");
|
|
132
|
+
logger.info(`${chalk.default.blue.bold("Reloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
133
|
+
} else if (isMiddleware && registerMiddleware && isMiddleware(val)) {
|
|
134
|
+
registerMiddleware(val, file);
|
|
135
|
+
this.trackMiddleware(file, val);
|
|
136
|
+
const name = displayName(val, "Middleware");
|
|
137
|
+
logger.info(`${chalk.default.blue.bold("Reloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(`Failed to reload file: ${file}`, error);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
snapshotUnits(file) {
|
|
146
|
+
return {
|
|
147
|
+
handlers: [...this.store.fileToHandlers.get(file) ?? []],
|
|
148
|
+
middlewares: [...this.store.fileToMiddlewares.get(file) ?? []]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
restoreUnits(file, snapshot) {
|
|
152
|
+
for (const handler of snapshot.handlers) {
|
|
153
|
+
this.options.registerHandler(handler, file);
|
|
154
|
+
this.trackHandler(file, handler);
|
|
155
|
+
}
|
|
156
|
+
if (this.options.registerMiddleware) for (const middleware of snapshot.middlewares) {
|
|
157
|
+
this.options.registerMiddleware(middleware, file);
|
|
158
|
+
this.trackMiddleware(file, middleware);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
Object.defineProperty(exports, 'HmrModuleHandler', {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () {
|
|
167
|
+
return HmrModuleHandler;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=HmrModuleHandler-CTBng7wV.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HmrModuleHandler-CTBng7wV.cjs","names":["sep"],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { formatFilePath } from '@seedcord/utils';\nimport chalk from 'chalk';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrStore<THandler, TMiddleware, TArtifacts> {\n fileToHandlers: Map<string, Set<THandler>>;\n fileToMiddlewares: Map<string, Set<TMiddleware>>;\n handlerArtifacts: Map<THandler, TArtifacts>;\n}\n\n// justified: units are caller-supplied classes, name is read for log labels only\nconst displayName = (unit: unknown, fallback: string): string => (unit as { name?: string }).name ?? fallback;\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlersDir: string;\n middlewaresDir?: string;\n isHandler: (val: unknown) => val is THandler;\n isMiddleware?: (val: unknown) => val is TMiddleware;\n registerHandler: (handler: THandler, file: string) => void;\n registerMiddleware?: (middleware: TMiddleware, file: string) => void;\n unregisterHandler: (handler: THandler, artifacts?: TArtifacts) => void;\n unregisterMiddleware?: (middleware: TMiddleware) => void;\n /** Snapshotted per handler so a rollback re-registers with the same artifacts after the module re-imports. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n logger: Logger;\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n private readonly store: HmrStore<THandler, TMiddleware, TArtifacts>;\n private readonly options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n // the caller's options object stays unmutated\n this.options = { ...options, logger: options.logger.inChannel('hmr') };\n\n // no import.meta.hot.data stash needed, a leaf reload never reconstructs the singleton that holds these Maps\n this.store = {\n fileToHandlers: new Map(),\n fileToMiddlewares: new Map(),\n handlerArtifacts: new Map()\n };\n }\n\n /** Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories. */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n const { logger, handlersDir, middlewaresDir } = this.options;\n\n if (type === 'delete' || type === 'deleteDir') {\n this.unload(file);\n return;\n }\n\n // a directory is never importable, its files arrive as their own create events\n if (type === 'createDir') return;\n\n if (type === 'update' && !existsSync(file)) {\n if (this.isTracked(file)) {\n logger.info(`Received update for non-existent file: ${formatFilePath(file)}, treating as delete`);\n this.unload(file);\n }\n return;\n }\n\n const rollbackEnabled = event.rollback ?? true;\n const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];\n const absHandlersDir = resolve(process.cwd(), handlersDir);\n const absMiddlewaresDir = middlewaresDir ? resolve(process.cwd(), middlewaresDir) : null;\n\n for (const fileToReload of filesToReload) {\n // the separator keeps a sibling dir sharing the prefix (commands-extra vs commands) out\n const isHandler = fileToReload.startsWith(absHandlersDir + sep);\n const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir + sep) : false;\n\n if (!isHandler && !isMiddleware) {\n continue;\n }\n\n await this.reloadWithRollback(fileToReload, rollbackEnabled);\n }\n }\n\n private async reloadWithRollback(file: string, rollbackEnabled: boolean): Promise<void> {\n const { logger } = this.options;\n const snapshot = this.snapshotUnits(file);\n this.unload(file);\n\n const reloaded = await this.reloadFile(file);\n if (reloaded) return;\n\n // a failed reload can leave a partial registration\n this.unload(file);\n if (!rollbackEnabled) return;\n if (snapshot.handlers.length === 0 && snapshot.middlewares.length === 0) return;\n\n // a typo keeps them live until the next good save\n this.restoreUnits(file, snapshot);\n logger.warn(`${chalk.yellow.bold('Rolled back')} ${chalk.gray(formatFilePath(file))} to the last-good version`);\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n let set = this.store.fileToHandlers.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToHandlers.set(file, set);\n }\n set.add(handler);\n\n if (this.options.getArtifacts) {\n const artifacts = this.options.getArtifacts(handler);\n this.store.handlerArtifacts.set(handler, artifacts);\n }\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n let set = this.store.fileToMiddlewares.get(file);\n if (!set) {\n set = new Set();\n this.store.fileToMiddlewares.set(file, set);\n }\n set.add(middleware);\n }\n\n private isTracked(file: string): boolean {\n return this.store.fileToHandlers.has(file) || this.store.fileToMiddlewares.has(file);\n }\n\n private unload(file: string): void {\n const { logger } = this.options;\n const handlers = this.store.fileToHandlers.get(file);\n if (handlers) {\n for (const handler of handlers) {\n const artifacts = this.store.handlerArtifacts.get(handler);\n this.options.unregisterHandler(handler, artifacts);\n this.store.handlerArtifacts.delete(handler);\n const name = displayName(handler, 'Handler');\n logger.info(\n `${chalk.red.bold('Unloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n this.store.fileToHandlers.delete(file);\n }\n\n const middlewares = this.store.fileToMiddlewares.get(file);\n if (middlewares) {\n for (const middleware of middlewares) {\n this.options.unregisterMiddleware?.(middleware);\n const name = displayName(middleware, 'Middleware');\n logger.info(\n `${chalk.red.bold('Unloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n this.store.fileToMiddlewares.delete(file);\n }\n }\n\n private async reloadFile(file: string): Promise<boolean> {\n const { logger, isHandler, isMiddleware, registerHandler, registerMiddleware } = this.options;\n\n if (!existsSync(file)) {\n logger.info(`File does not exist, skipping reload: ${formatFilePath(file)}`);\n return false;\n }\n\n try {\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph, so bust the import cache manually.\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(fileUrl)) as Record<string, unknown>;\n\n for (const val of Object.values(imported)) {\n if (isHandler(val)) {\n registerHandler(val, file);\n this.trackHandler(file, val);\n const name = displayName(val, 'Handler');\n logger.info(\n `${chalk.blue.bold('Reloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n } else if (isMiddleware && registerMiddleware && isMiddleware(val)) {\n registerMiddleware(val, file);\n this.trackMiddleware(file, val);\n const name = displayName(val, 'Middleware');\n logger.info(\n `${chalk.blue.bold('Reloaded')} ${chalk.cyan.bold(name)} from ${chalk.gray(formatFilePath(file))}`\n );\n }\n }\n\n return true;\n } catch (error) {\n logger.error(`Failed to reload file: ${file}`, error);\n return false;\n }\n }\n\n // restoring re-registers the same class objects, so a live db connection survives rollback\n private snapshotUnits(file: string): { handlers: THandler[]; middlewares: TMiddleware[] } {\n return {\n handlers: [...(this.store.fileToHandlers.get(file) ?? [])],\n middlewares: [...(this.store.fileToMiddlewares.get(file) ?? [])]\n };\n }\n\n private restoreUnits(file: string, snapshot: { handlers: THandler[]; middlewares: TMiddleware[] }): void {\n for (const handler of snapshot.handlers) {\n this.options.registerHandler(handler, file);\n this.trackHandler(file, handler);\n }\n\n if (this.options.registerMiddleware) {\n for (const middleware of snapshot.middlewares) {\n this.options.registerMiddleware(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n }\n}\n"],"mappings":"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"}
|