/** * Folder-based module loader. Imports every file in a directory and * returns the picked export from each one as an array. Use it instead of * a long list of explicit imports for things like controllers, cron * jobs, listeners, or commands. * * Works on both Bun and Node by combining `@tekir/runtime`'s * `readDirRecursive` (which uses `Bun.Glob` on Bun, `fs` on Node) with a * dynamic `import()` of each file's `file://` URL. * * @example Load all controllers and pass them to the router * ```ts * import { loadDir } from '@tekir/core' * * const controllers = await loadDir('app/controllers') * router.register(...controllers) * ``` * * @example Custom picker (named export instead of default) * ```ts * const jobs = await loadDir('app/jobs', { * pick: (mod) => mod.Job ?? mod.default, * }) * ``` * * Note: dynamic imports with computed paths cannot be statically traced * by `bun build --compile`. If you ship a single-executable build, keep * the explicit-imports list (or generate it from the directory at build * time). For `bun run` and `node` runtimes this loader works as-is. */ export interface LoadDirOptions { /** Allowed file extensions. Defaults to `['.ts', '.tsx', '.js', '.jsx', '.mjs']`. */ extensions?: string[]; /** Skip files whose name (without extension) does not match this pattern. */ match?: RegExp; /** Skip files whose name (without extension) matches this pattern. */ ignore?: RegExp; /** Walk into subdirectories. Defaults to `false`. */ recursive?: boolean; /** * Pick the export(s) to return per file. The default tries (in order): * 1. `mod.default` if defined * 2. The single named export when there is exactly one * 3. A function whose static metadata looks decorator-tagged * (`__prefix`, `__routes`, `__schedules`, `__listeners`) * 4. The first function-typed named export * 5. Falls through to the namespace `mod` itself * * That covers `export default Class`, `export class FooController`, * `export const handler = (router) => {...}`, and decorator-only files * without forcing the user to write a custom picker. */ pick?: (mod: Record, file: string) => unknown; /** Drop entries where the picker returned `undefined` or `null`. Defaults to `true`. */ filterEmpty?: boolean; /** Sort the returned entries by filename (alphabetical). Defaults to `true`. */ sort?: boolean; /** * Base directory for resolving a relative `dir` argument. Accepts an * absolute filesystem path (a directory, or a file whose parent dir * is used) or a `file://` URL string — typically `import.meta.url`, * which is portable across Bun and Node ESM. * * Defaults to `process.cwd()`. The `*.registerDir()` wrappers on * router/cron/emitter automatically capture their caller's file via * stack inspection when `from` is omitted, so a relative * `registerDir('./controllers')` resolves against the caller's own * directory at runtime — the same way the AST inliner does at build * time. */ from?: string | URL; } /** * One entry in the result of {@link loadDirEntries}: the file the module * came from, the namespace import, and the picked export. Carries the * file path so callers (router/cron/emitter `registerDir`) can mention * the source file in their warnings. */ export interface LoadDirEntry { file: string; module: Record; picked: T; } /** * Load every module file in a directory and return their picked exports. * * @param dir Directory to load. Absolute paths are used as-is. Relative * paths resolve against `options.from` (if provided) or the current * working directory. * @param options See {@link LoadDirOptions}. * @returns Array of picked exports, one entry per imported file. */ export declare function loadDir(dir: string, options?: LoadDirOptions): Promise; /** * @internal * Walk the call stack to find the file that called `boundary`. Used by * `*.registerDir()` so a relative path argument resolves against the * caller's own directory at runtime, matching the AST inliner's * file-relative resolution at build time. * * Both Bun and Node honor `Error.captureStackTrace(obj, fn)` to drop * frames at and above `fn` from the captured stack. The frame format * differs (Bun emits raw filesystem paths, Node emits `file://` URLs); * the parser accepts both. Returns `undefined` if no usable user frame * shows up — callers fall back to `process.cwd()` in that case. */ export declare function captureCallerFile(boundary: Function): string | undefined; /** * Like {@link loadDir} but keeps the source file path on every result * so callers can mention it when something looks off (registerDir uses * this for "skipped : unrecognized export shape" warnings). * * @param dir Directory to load. Absolute paths are used as-is. Relative * paths resolve against `options.from` (if provided) or the * current working directory. * @param options See {@link LoadDirOptions}. */ export declare function loadDirEntries(dir: string, options?: LoadDirOptions): Promise[]>;