Skip to content

Commit d38619f

Browse files
committed
feat: completely depend on file-folder-loader for loading modules
- update tests - update related types BREAKING CHANGE: - rename importMode option to processMode - rename preferredNamedExport option to preferredExportName
1 parent 006bead commit d38619f

File tree

3 files changed

+47
-105
lines changed

3 files changed

+47
-105
lines changed

src/__tests__/index.test.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,26 @@ describe("event-handler-loader", () => {
5050
});
5151

5252
it("load event handlers with concurrent mode", async () => {
53-
await expect(loadEventHandlers(defaultDir, eventEmitter, { importMode: "concurrent" })).resolves.toBeTruthy();
53+
await expect(loadEventHandlers(defaultDir, eventEmitter, { processMode: "concurrent" })).resolves.toBeTruthy();
5454
expect(eventEmitter.listenerCount("unhandledRejection")).toBe(1);
5555
});
5656

5757
it("load event handlers with sequential mode", async () => {
58-
await expect(loadEventHandlers(defaultDir, eventEmitter, { importMode: "sequential" })).resolves.toBeTruthy();
58+
await expect(loadEventHandlers(defaultDir, eventEmitter, { processMode: "sequential" })).resolves.toBeTruthy();
5959
expect(eventEmitter.listenerCount("unhandledRejection")).toBe(1);
6060
});
6161

6262
it("load event handlers with sequential mode and overriden callback sync and async ", async () => {
6363
await expect(
64-
loadEventHandlers(defaultDir, eventEmitter, { importMode: "sequential" }, function (emitter, moduleExport, fileUrlHref, listenerPrependedArgs) {
64+
loadEventHandlers(defaultDir, eventEmitter, { processMode: "sequential" }, function (emitter, moduleExport, fileUrlHref, listenerPrependedArgs) {
6565
expect(emitter).toBeInstanceOf(nodeEvents.EventEmitter);
6666
expect(typeof moduleExport === "object").toBeTruthy();
6767
expect(typeof fileUrlHref === "string").toBeTruthy();
6868
expect(listenerPrependedArgs.length).toBe(0);
6969
}),
7070
).resolves.toBeTruthy();
7171
await expect(
72-
loadEventHandlers(defaultDir, eventEmitter, { importMode: "sequential" }, async function (emitter, moduleExport, fileUrlHref, listenerPrependedArgs) {
72+
loadEventHandlers(defaultDir, eventEmitter, { processMode: "sequential" }, async function (emitter, moduleExport, fileUrlHref, listenerPrependedArgs) {
7373
expect(emitter).toBeInstanceOf(nodeEvents.EventEmitter);
7474
expect(typeof moduleExport === "object").toBeTruthy();
7575
expect(typeof fileUrlHref === "string").toBeTruthy();
@@ -83,7 +83,7 @@ describe("event-handler-loader", () => {
8383
});
8484

8585
it("load event handlers with sequential mode and recursively looks inside that path", async () => {
86-
await expect(loadEventHandlers(defaultDir, eventEmitter, { importMode: "sequential", isRecursive: true })).resolves.toBeTruthy();
86+
await expect(loadEventHandlers(defaultDir, eventEmitter, { processMode: "sequential", isRecursive: true })).resolves.toBeTruthy();
8787
expect(eventEmitter.listenerCount("unhandledRejection")).toBe(3);
8888
});
8989

@@ -108,12 +108,12 @@ describe("event-handler-loader", () => {
108108
});
109109

110110
it("load all module's named exports that listen the same event", async () => {
111-
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredNamedExport: "*" })).resolves.toBeTruthy();
111+
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredExportName: "*" })).resolves.toBeTruthy();
112112
expect(eventEmitter.listenerCount("multiHandlerEvent")).toBe(3);
113113
});
114114

115115
it("load all event handlers with isOnce true, then removes itself after emit", async () => {
116-
await expect(loadEventHandlers(isOnceDir, eventEmitter, { exportType: "named", preferredNamedExport: "*" })).resolves.toBeTruthy();
116+
await expect(loadEventHandlers(isOnceDir, eventEmitter, { exportType: "named", preferredExportName: "*" })).resolves.toBeTruthy();
117117
expect(eventEmitter.listenerCount("multiHandlerEvent")).toBe(2);
118118
eventEmitter.emit("multiHandlerEvent");
119119
expect(eventEmitter.listenerCount("multiHandlerEvent")).toBe(0);
@@ -171,22 +171,22 @@ describe("event-handler-loader", () => {
171171
await expect(loadEventHandlers(namedDir, eventEmitter)).rejects.toThrow();
172172
});
173173

174-
it("handle importing named exports with exportType named and preferredNamedExport default", async () => {
175-
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredNamedExport: "default" })).rejects.toThrow();
174+
it("handle importing named exports with exportType named and preferredExportName default", async () => {
175+
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredExportName: "default" })).rejects.toThrow();
176176
});
177177

178178
it("handle importing default exports with exportType named", async () => {
179179
await expect(loadEventHandlers(defaultDir, eventEmitter, { exportType: "named" })).rejects.toThrow();
180180
});
181181

182182
it("handle empty strings and non-string loadEventHandlers options", async () => {
183-
await expect(loadEventHandlers(defaultDir, eventEmitter, { importMode: "" as "sequential" })).rejects.toThrow();
183+
await expect(loadEventHandlers(defaultDir, eventEmitter, { processMode: "" as "sequential" })).rejects.toThrow();
184184
await expect(loadEventHandlers(defaultDir, eventEmitter, { exportType: "" as "default" })).rejects.toThrow();
185-
await expect(loadEventHandlers(defaultDir, eventEmitter, { preferredNamedExport: "" })).rejects.toThrow();
185+
await expect(loadEventHandlers(defaultDir, eventEmitter, { preferredExportName: "" })).rejects.toThrow();
186186
await expect(loadEventHandlers(defaultDir, eventEmitter, { isRecursive: "" as unknown as boolean })).rejects.toThrow();
187-
await expect(loadEventHandlers(defaultDir, eventEmitter, { importMode: 1 as unknown as "sequential" })).rejects.toThrow();
187+
await expect(loadEventHandlers(defaultDir, eventEmitter, { processMode: 1 as unknown as "sequential" })).rejects.toThrow();
188188
await expect(loadEventHandlers(defaultDir, eventEmitter, { exportType: 1 as unknown as "default" })).rejects.toThrow();
189-
await expect(loadEventHandlers(defaultDir, eventEmitter, { preferredNamedExport: 1 as unknown as "" })).rejects.toThrow();
189+
await expect(loadEventHandlers(defaultDir, eventEmitter, { preferredExportName: 1 as unknown as "" })).rejects.toThrow();
190190
});
191191

192192
it("handle empty strings non-string preferredEventHandlerKeys options", async () => {
@@ -242,11 +242,11 @@ describe("event-handler-loader", () => {
242242
});
243243

244244
it("handle loading preferredNameExport: uniqueEventHandler but has no matches", async () => {
245-
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredNamedExport: "uniqueEventHandler" })).rejects.toThrow();
245+
await expect(loadEventHandlers(namedDir, eventEmitter, { exportType: "named", preferredExportName: "uniqueEventHandler" })).rejects.toThrow();
246246
});
247247

248248
it("handle loading named exports inside a directory with export default modules", async () => {
249-
await expect(loadEventHandlers(defaultDir, eventEmitter, { exportType: "named", preferredNamedExport: "*" })).resolves.toBeTruthy();
249+
await expect(loadEventHandlers(defaultDir, eventEmitter, { exportType: "named", preferredExportName: "*" })).resolves.toBeTruthy();
250250
});
251251
});
252252
});

src/index.ts

+23-82
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
import * as nodeUrl from "node:url";
21
import * as nodePath from "node:path";
32
import * as nodeFsPromises from "node:fs/promises";
43
import * as nodeUtilTypes from "node:util/types";
5-
import { getModules } from "file-folder-loader";
4+
import { getModules, loadModules } from "file-folder-loader";
65
import type { EventEmitter } from "node:events";
76
import type { PathLike } from "node:fs";
8-
import type {
9-
EventExecute,
10-
EventHandlerModuleExport,
11-
EventHandlerModuleNamespace,
12-
EventHandlerKeys,
13-
LoadEventHandlersOptions,
14-
BindEventListenerOverride,
15-
} from "./types.js";
7+
import type { ProcessMode, ExportType, EventExecute, EventHandlerModuleExport, EventHandlerKeys, LoadEventHandlersOptions, BindEventListenerOverride } from "./types.js";
168

17-
const DEFAULT_EXPORT_NAME = "default";
189
const EVENT_EMITTER_ADD_LISTENER_METHOD_NAMES = ["on", "once", "addListener", "prependListener", "prependOnceListener"];
1910

2011
const DEFAULT_EVENT_HANDLER_KEY_NAMES = {
@@ -23,16 +14,16 @@ const DEFAULT_EVENT_HANDLER_KEY_NAMES = {
2314
isPrepend: "isPrepend",
2415
execute: "execute",
2516
};
26-
const DEFAULT_IMPORT_MODE = "concurrent";
17+
const DEFAULT_PROCESS_MODE = "concurrent";
2718
const DEFAULT_EXPORT_TYPE = "default";
2819
const DEFAULT_NAMED_EXPORT = "eventHandler";
2920
const DEFAULT_IMPORT_MODES = ["concurrent", "sequential"];
3021
const DEFAULT_EXPORT_TYPES = ["default", "named", "all"];
3122
const DEFAULT_LOAD_EVENT_HANDLERS_OPTIONS = {
32-
importMode: DEFAULT_IMPORT_MODE,
23+
processMode: DEFAULT_PROCESS_MODE,
3324
exportType: DEFAULT_EXPORT_TYPE,
3425
listenerPrependedArgs: [],
35-
preferredNamedExport: DEFAULT_NAMED_EXPORT,
26+
preferredExportName: DEFAULT_NAMED_EXPORT,
3627
preferredEventHandlerKeys: {},
3728
isRecursive: false,
3829
};
@@ -132,44 +123,6 @@ function bindEventListener(
132123
eventEmitterLike.on(nameValue, listener);
133124
}
134125

135-
async function importModule(fileUrlHref: string, exportType: string, preferredExportName: string) {
136-
const isNamedExportType = exportType === "named";
137-
const moduleNamespace: EventHandlerModuleNamespace = await import(fileUrlHref);
138-
if (exportType === "all") {
139-
const moduleExports: EventHandlerModuleExport[] = [];
140-
for (const exportName in moduleNamespace) {
141-
const moduleExport = moduleNamespace[exportName];
142-
if (!moduleExport || !Object.prototype.hasOwnProperty.call(moduleNamespace, exportName)) {
143-
console.error(`Invalid module export. Must be a default or named export. Unable to verify export '${exportName}'. Module: ${fileUrlHref}`);
144-
continue;
145-
}
146-
moduleExports.push(moduleExport);
147-
}
148-
return moduleExports;
149-
}
150-
if (isNamedExportType && preferredExportName === "*") {
151-
const moduleExports: EventHandlerModuleExport[] = [];
152-
for (const exportName in moduleNamespace) {
153-
const moduleExport = moduleNamespace[exportName];
154-
if (!moduleExport || !Object.prototype.hasOwnProperty.call(moduleNamespace, exportName) || exportName === DEFAULT_EXPORT_NAME) {
155-
console.error(`Invalid module export. Must be a named export. Unable to verify named export '${exportName}'. Module: ${fileUrlHref}`);
156-
continue;
157-
}
158-
moduleExports.push(moduleExport);
159-
}
160-
return moduleExports;
161-
}
162-
const exportName = isNamedExportType ? preferredExportName : DEFAULT_EXPORT_NAME;
163-
const moduleExport = moduleNamespace[exportName];
164-
if (!moduleExport || !Object.prototype.hasOwnProperty.call(moduleNamespace, exportName)) {
165-
const errorMessage = isNamedExportType
166-
? `Must be a named export called '${preferredExportName}'. ${exportName === DEFAULT_EXPORT_NAME ? "Unable to verify named export" : "Unable to verify preferred export name"} '${exportName}'.`
167-
: `Must be a default export. Unable to verify default export '${exportName}'`;
168-
throw new Error(`Invalid module export. ${errorMessage}. Module: ${fileUrlHref}`);
169-
}
170-
return [moduleExport];
171-
}
172-
173126
async function loadEventHandlers(
174127
dirPath: string,
175128
eventEmitterLike: EventEmitter,
@@ -188,10 +141,10 @@ async function loadEventHandlers(
188141
throw new Error(`Invalid options: '${options}'. Must be a an object.`);
189142
}
190143
const eventHandlerOptions = { ...DEFAULT_LOAD_EVENT_HANDLERS_OPTIONS, ...(options || {}) };
191-
const importMode = eventHandlerOptions.importMode;
144+
const processMode = eventHandlerOptions.processMode;
192145
const exportType = eventHandlerOptions.exportType;
193146
const listenerPrependedArgs = eventHandlerOptions.listenerPrependedArgs;
194-
const preferredNamedExport = eventHandlerOptions.preferredNamedExport;
147+
const preferredExportName = eventHandlerOptions.preferredExportName;
195148
const isRecursive = eventHandlerOptions.isRecursive;
196149
const preferredEventHandlerKeys = { ...DEFAULT_EVENT_HANDLER_KEY_NAMES, ...(eventHandlerOptions.preferredEventHandlerKeys || {}) };
197150
const { name: nameKeyName, isOnce: isOnceKeyName, isPrepend: isPrependKeyName, execute: executeKeyName } = preferredEventHandlerKeys;
@@ -208,46 +161,34 @@ async function loadEventHandlers(
208161
if (typeof executeKeyName !== "string" || executeKeyName.trim() === "") {
209162
throw new Error(`Invalid preferredEventHandlerKeys execute: '${executeKeyName}'. Must be a non-empty string.`);
210163
}
211-
if (!DEFAULT_IMPORT_MODES.includes(importMode)) {
212-
throw new Error(`Invalid import mode: '${importMode}'. Must be one of string: ${DEFAULT_IMPORT_MODES.join(", ")}`);
164+
if (!DEFAULT_IMPORT_MODES.includes(processMode)) {
165+
throw new Error(`Invalid process mode: '${processMode}'. Must be one of string: ${DEFAULT_IMPORT_MODES.join(", ")}`);
213166
}
214167
if (!DEFAULT_EXPORT_TYPES.includes(exportType)) {
215168
throw new Error(`Invalid export type: '${exportType}'. Must be one of string: ${DEFAULT_EXPORT_TYPES.join(", ")}`);
216169
}
217-
if (typeof preferredNamedExport !== "string" || preferredNamedExport.trim() === "") {
218-
throw new Error(`Invalid preferred named export: '${preferredNamedExport}'. Must be a non-empty string.`);
170+
if (typeof preferredExportName !== "string" || preferredExportName.trim() === "") {
171+
throw new Error(`Invalid preferred export name: '${preferredExportName}'. Must be a non-empty string.`);
219172
}
220173
if (typeof isRecursive !== "boolean") {
221174
throw new Error(`Invalid isRecursive: '${isRecursive}'. Must be a boolean.`);
222175
}
223-
const filePaths = await getModules(absolutePath, { isRecursive: isRecursive, processMode: importMode });
224-
async function loadEventHandler(filePath: string) {
225-
const fileUrlHref = nodeUrl.pathToFileURL(filePath).href;
226-
/**
227-
* Some type casts were necessary here because TypeScript
228-
* didn't consider the guard clauses outside this async function declaration
229-
* - xaya
230-
*/
231-
const moduleExports = await importModule(fileUrlHref, exportType as string, preferredNamedExport as string);
232-
for (const moduleExport of moduleExports) {
176+
const filePaths = await getModules(absolutePath, { isRecursive: isRecursive, processMode: processMode });
177+
await loadModules(
178+
filePaths,
179+
async function (moduleExport, fileUrlHref) {
233180
if (typeof bindEventListenerOverride !== "function") {
234-
bindEventListener(eventEmitterLike, moduleExport, preferredEventHandlerKeys, listenerPrependedArgs as unknown[], fileUrlHref);
235-
continue;
181+
bindEventListener(eventEmitterLike, moduleExport as EventHandlerModuleExport, preferredEventHandlerKeys, listenerPrependedArgs as unknown[], fileUrlHref);
182+
return;
236183
}
237184
if (nodeUtilTypes.isAsyncFunction(bindEventListenerOverride)) {
238-
await bindEventListenerOverride(eventEmitterLike, moduleExport, fileUrlHref, listenerPrependedArgs as unknown[]);
239-
continue;
185+
await bindEventListenerOverride(eventEmitterLike, moduleExport as EventHandlerModuleExport, fileUrlHref, listenerPrependedArgs as unknown[]);
186+
return;
240187
}
241-
bindEventListenerOverride(eventEmitterLike, moduleExport, fileUrlHref, listenerPrependedArgs as unknown[]);
242-
}
243-
}
244-
if (importMode === "concurrent") {
245-
await Promise.all(filePaths.map(loadEventHandler));
246-
return true;
247-
}
248-
for (const filePath of filePaths) {
249-
await loadEventHandler(filePath);
250-
}
188+
bindEventListenerOverride(eventEmitterLike, moduleExport as EventHandlerModuleExport, fileUrlHref, listenerPrependedArgs as unknown[]);
189+
},
190+
{ exportType: exportType as ExportType, preferredExportName: preferredExportName, processMode: processMode as ProcessMode },
191+
);
251192
return true;
252193
}
253194

src/types.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ interface EventHandlerKeys {
2424
execute?: string | "execute";
2525
}
2626

27-
type ImportModes = "concurrent" | "sequential";
27+
type ProcessMode = "concurrent" | "sequential";
2828

29-
type ExportTypes = "default" | "named" | "all";
29+
type ExportType = "default" | "named" | "all";
3030

31-
type NamedExports = string | "eventHandler" | "*";
31+
type preferredExportName = string | "eventHandler" | "*";
3232

3333
interface LoadEventHandlersOptions {
34-
importMode?: ImportModes;
35-
exportType?: ExportTypes;
34+
processMode?: ProcessMode;
35+
exportType?: ExportType;
3636
listenerPrependedArgs?: unknown[];
37-
preferredNamedExport?: NamedExports;
37+
preferredExportName?: preferredExportName;
3838
preferredEventHandlerKeys?: EventHandlerKeys;
3939
isRecursive?: boolean;
4040
}
@@ -49,11 +49,12 @@ type BindEventListenerOverride = (
4949
export type {
5050
BindEventListenerOverride,
5151
EventExecute,
52+
ExportType,
5253
EventHandlerKeys,
5354
EventHandlerModuleExport,
5455
EventHandlerModuleNamespace,
5556
EventName,
56-
ImportModes,
57+
ProcessMode,
5758
LoadEventHandlersOptions,
58-
NamedExports,
59+
preferredExportName,
5960
};

0 commit comments

Comments
 (0)