@efebia/fastify-auto-import 1.1.1 → 1.2.1
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/README.md +24 -0
- package/lib/cjs/globber.d.ts +10 -1
- package/lib/cjs/globber.js +26 -2
- package/lib/cjs/index.js +15 -2
- package/lib/cjs/router.d.ts +16 -0
- package/lib/cjs/router.js +34 -0
- package/lib/esm/globber.d.ts +10 -1
- package/lib/esm/globber.js +22 -1
- package/lib/esm/index.js +16 -3
- package/lib/esm/router.d.ts +16 -0
- package/lib/esm/router.js +25 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ A `fastify-plugin` compatible plugin. Register it with `fastify.register()`.
|
|
|
70
70
|
| `routeFile` | `string` | `'routes'` | File name (without extension) to import from each subdirectory. |
|
|
71
71
|
| `excludedDirectories` | `string[]` | `[]` | Subdirectory names to skip. |
|
|
72
72
|
| `recursive` | `boolean` | `false` | Scan nested subdirectories for route files, not just direct children. |
|
|
73
|
+
| `fileBasedRouting` | `boolean` | `false` | Derive each folder's URL prefix from its path. See below. |
|
|
73
74
|
| `log` | `pino.BaseLogger` | Fastify logger | Logger instance for import errors. |
|
|
74
75
|
|
|
75
76
|
## Recursive mode
|
|
@@ -104,6 +105,29 @@ Without `recursive`, only `users/routes.ts` and `orders/routes.ts` would be regi
|
|
|
104
105
|
|
|
105
106
|
`excludedDirectories` still applies at every depth — a matching directory name is skipped along with all its descendants.
|
|
106
107
|
|
|
108
|
+
## File-based routing
|
|
109
|
+
|
|
110
|
+
Set `fileBasedRouting: true` to make folders the URL. It walks the whole tree (no need for `recursive`).
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
src/routes/
|
|
114
|
+
api/
|
|
115
|
+
users/
|
|
116
|
+
routes.ts ← /api/users
|
|
117
|
+
get.ts ← plain handler, imported by routes.ts (not auto-loaded)
|
|
118
|
+
[userId]/
|
|
119
|
+
routes.ts ← /api/users/:userId
|
|
120
|
+
(private)/
|
|
121
|
+
tasks/routes.ts ← /api/tasks ((private) adds no segment)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- The full folder path is the URL. Each `routes.ts` is registered flat, in its own encapsulated scope, with that path as `prefix`, so it only declares paths relative to its folder (`app.get('/', ...)`, `app.get('/export', ...)`).
|
|
125
|
+
- Scopes are independent: hooks in a parent folder's `routes.ts` never apply to child folders (and vice versa).
|
|
126
|
+
- `[id]` becomes `:id`. Folders named `_x` or `(group)` add no segment.
|
|
127
|
+
- Only the `routes` file is loaded. Anything else in the folder (handlers, schemas) is yours to import from it.
|
|
128
|
+
- Middleware is not built in. Add hooks or `preHandler`s in each `routes.ts` that needs them.
|
|
129
|
+
- `excludedDirectories` and `routeFile` behave as usual.
|
|
130
|
+
|
|
107
131
|
## License
|
|
108
132
|
|
|
109
133
|
MIT
|
package/lib/cjs/globber.d.ts
CHANGED
|
@@ -6,7 +6,16 @@ export type Globber = {
|
|
|
6
6
|
log?: pino.BaseLogger;
|
|
7
7
|
excludedDirectories?: string[];
|
|
8
8
|
recursive?: boolean;
|
|
9
|
+
fileBasedRouting?: boolean;
|
|
9
10
|
};
|
|
10
|
-
export declare const
|
|
11
|
+
export declare const getPackageType: (directory: string) => Promise<any>;
|
|
12
|
+
export declare const resolvePluginsDirectory: (opts: Pick<Globber, "directory" | "startingDirectory"> & {
|
|
13
|
+
directory: string;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
packageType: any;
|
|
16
|
+
pluginsDirectory: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const importPlugin: (dir: string, fileName: string, packageType: string, entries: string[], log?: pino.BaseLogger) => Promise<any | undefined>;
|
|
19
|
+
export declare const globFiles: (opts: Required<Omit<Globber, "fileBasedRouting">> & {
|
|
11
20
|
log?: pino.BaseLogger;
|
|
12
21
|
}) => Promise<any[]>;
|
package/lib/cjs/globber.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.globFiles = void 0;
|
|
39
|
+
exports.globFiles = exports.importPlugin = exports.resolvePluginsDirectory = exports.getPackageType = void 0;
|
|
40
40
|
const promises_1 = require("node:fs/promises");
|
|
41
41
|
const node_path_1 = __importDefault(require("node:path"));
|
|
42
42
|
const node_url_1 = __importStar(require("node:url"));
|
|
@@ -51,6 +51,30 @@ const getPackageType = async (directory) => {
|
|
|
51
51
|
return JSON.parse(file).type;
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
+
exports.getPackageType = getPackageType;
|
|
55
|
+
const resolvePluginsDirectory = async (opts) => {
|
|
56
|
+
const packageType = await (0, exports.getPackageType)(opts.directory);
|
|
57
|
+
const startingDirectory = packageType === 'module' ? node_path_1.default.dirname((0, node_url_1.fileURLToPath)(opts.startingDirectory)) : opts.startingDirectory;
|
|
58
|
+
return { packageType, pluginsDirectory: node_path_1.default.join(startingDirectory, opts.directory) };
|
|
59
|
+
};
|
|
60
|
+
exports.resolvePluginsDirectory = resolvePluginsDirectory;
|
|
61
|
+
const importPlugin = async (dir, fileName, packageType, entries, log) => {
|
|
62
|
+
const name = fileName.replace('.js', '');
|
|
63
|
+
if (!entries.some((f) => f === `${name}.js` || f === `${name}.ts`))
|
|
64
|
+
return undefined;
|
|
65
|
+
const osPath = node_path_1.default.join(dir, packageType === 'module' ? `${name}.js` : name);
|
|
66
|
+
try {
|
|
67
|
+
if (packageType === 'module') {
|
|
68
|
+
return (await Promise.resolve(`${node_url_1.default.pathToFileURL(osPath).href}`).then(s => __importStar(require(s)))).default;
|
|
69
|
+
}
|
|
70
|
+
return require(osPath);
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
log === null || log === void 0 ? void 0 : log.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.importPlugin = importPlugin;
|
|
54
78
|
const collectRecursivePlugins = async (dir, opts) => {
|
|
55
79
|
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
56
80
|
const dirEntries = entries.filter((entry) => entry.isDirectory() && !opts.excludedDirectories.includes(entry.name));
|
|
@@ -87,7 +111,7 @@ const collectRecursivePlugins = async (dir, opts) => {
|
|
|
87
111
|
return perDirResults.flat();
|
|
88
112
|
};
|
|
89
113
|
const globFiles = async (opts) => {
|
|
90
|
-
const packageType = await getPackageType(opts.directory);
|
|
114
|
+
const packageType = await (0, exports.getPackageType)(opts.directory);
|
|
91
115
|
const startingDirectory = packageType === 'module' ? node_path_1.default.dirname((0, node_url_1.fileURLToPath)(opts.startingDirectory)) : opts.startingDirectory;
|
|
92
116
|
const pluginsDirectory = node_path_1.default.join(startingDirectory, opts.directory);
|
|
93
117
|
if (opts.recursive) {
|
package/lib/cjs/index.js
CHANGED
|
@@ -5,9 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
7
7
|
const globber_js_1 = require("./globber.js");
|
|
8
|
+
const router_js_1 = require("./router.js");
|
|
8
9
|
const plugin = (0, fastify_plugin_1.default)(async (fastify, opts) => {
|
|
9
|
-
var _a, _b, _c, _d;
|
|
10
|
-
|
|
10
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
11
|
+
if (opts.fileBasedRouting) {
|
|
12
|
+
const directory = (_a = opts.directory) !== null && _a !== void 0 ? _a : 'src/plugins';
|
|
13
|
+
const { packageType, pluginsDirectory } = await (0, globber_js_1.resolvePluginsDirectory)(Object.assign(Object.assign({}, opts), { directory }));
|
|
14
|
+
const routes = await (0, router_js_1.scanRoutes)(pluginsDirectory, {
|
|
15
|
+
routeFile: (_b = opts.routeFile) !== null && _b !== void 0 ? _b : 'routes',
|
|
16
|
+
packageType,
|
|
17
|
+
excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [],
|
|
18
|
+
log: opts.log || fastify.log,
|
|
19
|
+
});
|
|
20
|
+
await (0, router_js_1.registerRoutes)(fastify, routes);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const globbedFiles = await (0, globber_js_1.globFiles)(Object.assign(Object.assign({}, opts), { routeFile: (_d = opts.routeFile) !== null && _d !== void 0 ? _d : 'routes', directory: (_e = opts.directory) !== null && _e !== void 0 ? _e : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_f = opts.excludedDirectories) !== null && _f !== void 0 ? _f : [], recursive: (_g = opts.recursive) !== null && _g !== void 0 ? _g : false }));
|
|
11
24
|
await Promise.all(globbedFiles.map(async (globbedFile) => {
|
|
12
25
|
await fastify.register(globbedFile);
|
|
13
26
|
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
2
|
+
import type pino from "pino";
|
|
3
|
+
export type RouteEntry = {
|
|
4
|
+
prefix: string;
|
|
5
|
+
plugin: FastifyPluginAsync;
|
|
6
|
+
};
|
|
7
|
+
export declare const segmentToPrefix: (name: string) => string;
|
|
8
|
+
type ScanOptions = {
|
|
9
|
+
routeFile: string;
|
|
10
|
+
packageType: string;
|
|
11
|
+
excludedDirectories: string[];
|
|
12
|
+
log?: pino.BaseLogger;
|
|
13
|
+
};
|
|
14
|
+
export declare const scanRoutes: (dir: string, opts: ScanOptions, prefix?: string) => Promise<RouteEntry[]>;
|
|
15
|
+
export declare const registerRoutes: (fastify: FastifyInstance, routes: RouteEntry[]) => Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerRoutes = exports.scanRoutes = exports.segmentToPrefix = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const globber_js_1 = require("./globber.js");
|
|
10
|
+
const segmentToPrefix = (name) => {
|
|
11
|
+
if (name.startsWith("_") || /^\(.*\)$/.test(name))
|
|
12
|
+
return "";
|
|
13
|
+
const param = name.match(/^\[(.+)\]$/);
|
|
14
|
+
if (param)
|
|
15
|
+
return `/:${param[1]}`;
|
|
16
|
+
return `/${name}`;
|
|
17
|
+
};
|
|
18
|
+
exports.segmentToPrefix = segmentToPrefix;
|
|
19
|
+
const scanRoutes = async (dir, opts, prefix = "") => {
|
|
20
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
21
|
+
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
22
|
+
const plugin = await (0, globber_js_1.importPlugin)(dir, opts.routeFile, opts.packageType, files, opts.log);
|
|
23
|
+
const nested = await Promise.all(entries
|
|
24
|
+
.filter((e) => e.isDirectory() && !opts.excludedDirectories.includes(e.name))
|
|
25
|
+
.map((e) => (0, exports.scanRoutes)(node_path_1.default.join(dir, e.name), opts, prefix + (0, exports.segmentToPrefix)(e.name))));
|
|
26
|
+
return [...(plugin ? [{ prefix, plugin }] : []), ...nested.flat()];
|
|
27
|
+
};
|
|
28
|
+
exports.scanRoutes = scanRoutes;
|
|
29
|
+
const registerRoutes = async (fastify, routes) => {
|
|
30
|
+
for (const { prefix, plugin } of routes) {
|
|
31
|
+
await fastify.register(plugin, prefix ? { prefix } : {});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.registerRoutes = registerRoutes;
|
package/lib/esm/globber.d.ts
CHANGED
|
@@ -6,7 +6,16 @@ export type Globber = {
|
|
|
6
6
|
log?: pino.BaseLogger;
|
|
7
7
|
excludedDirectories?: string[];
|
|
8
8
|
recursive?: boolean;
|
|
9
|
+
fileBasedRouting?: boolean;
|
|
9
10
|
};
|
|
10
|
-
export declare const
|
|
11
|
+
export declare const getPackageType: (directory: string) => Promise<any>;
|
|
12
|
+
export declare const resolvePluginsDirectory: (opts: Pick<Globber, "directory" | "startingDirectory"> & {
|
|
13
|
+
directory: string;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
packageType: any;
|
|
16
|
+
pluginsDirectory: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const importPlugin: (dir: string, fileName: string, packageType: string, entries: string[], log?: pino.BaseLogger) => Promise<any | undefined>;
|
|
19
|
+
export declare const globFiles: (opts: Required<Omit<Globber, "fileBasedRouting">> & {
|
|
11
20
|
log?: pino.BaseLogger;
|
|
12
21
|
}) => Promise<any[]>;
|
package/lib/esm/globber.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, readdir } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import url, { fileURLToPath } from "node:url";
|
|
4
4
|
import pkgUp from "pkg-up";
|
|
5
|
-
const getPackageType = async (directory) => {
|
|
5
|
+
export const getPackageType = async (directory) => {
|
|
6
6
|
const nearestPackage = await pkgUp({ cwd: directory });
|
|
7
7
|
if (nearestPackage) {
|
|
8
8
|
if ('require' in global) {
|
|
@@ -12,6 +12,27 @@ const getPackageType = async (directory) => {
|
|
|
12
12
|
return JSON.parse(file).type;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
export const resolvePluginsDirectory = async (opts) => {
|
|
16
|
+
const packageType = await getPackageType(opts.directory);
|
|
17
|
+
const startingDirectory = packageType === 'module' ? path.dirname(fileURLToPath(opts.startingDirectory)) : opts.startingDirectory;
|
|
18
|
+
return { packageType, pluginsDirectory: path.join(startingDirectory, opts.directory) };
|
|
19
|
+
};
|
|
20
|
+
export const importPlugin = async (dir, fileName, packageType, entries, log) => {
|
|
21
|
+
const name = fileName.replace('.js', '');
|
|
22
|
+
if (!entries.some((f) => f === `${name}.js` || f === `${name}.ts`))
|
|
23
|
+
return undefined;
|
|
24
|
+
const osPath = path.join(dir, packageType === 'module' ? `${name}.js` : name);
|
|
25
|
+
try {
|
|
26
|
+
if (packageType === 'module') {
|
|
27
|
+
return (await import(url.pathToFileURL(osPath).href)).default;
|
|
28
|
+
}
|
|
29
|
+
return require(osPath);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
log === null || log === void 0 ? void 0 : log.error({ error: e, path: osPath }, '@efebia/fastify-auto-import error on importing plugin');
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
15
36
|
const collectRecursivePlugins = async (dir, opts) => {
|
|
16
37
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
17
38
|
const dirEntries = entries.filter((entry) => entry.isDirectory() && !opts.excludedDirectories.includes(entry.name));
|
package/lib/esm/index.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
|
-
import { globFiles } from './globber.js';
|
|
2
|
+
import { globFiles, resolvePluginsDirectory } from './globber.js';
|
|
3
|
+
import { registerRoutes, scanRoutes } from './router.js';
|
|
3
4
|
const plugin = fp(async (fastify, opts) => {
|
|
4
|
-
var _a, _b, _c, _d;
|
|
5
|
-
|
|
5
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
6
|
+
if (opts.fileBasedRouting) {
|
|
7
|
+
const directory = (_a = opts.directory) !== null && _a !== void 0 ? _a : 'src/plugins';
|
|
8
|
+
const { packageType, pluginsDirectory } = await resolvePluginsDirectory(Object.assign(Object.assign({}, opts), { directory }));
|
|
9
|
+
const routes = await scanRoutes(pluginsDirectory, {
|
|
10
|
+
routeFile: (_b = opts.routeFile) !== null && _b !== void 0 ? _b : 'routes',
|
|
11
|
+
packageType,
|
|
12
|
+
excludedDirectories: (_c = opts.excludedDirectories) !== null && _c !== void 0 ? _c : [],
|
|
13
|
+
log: opts.log || fastify.log,
|
|
14
|
+
});
|
|
15
|
+
await registerRoutes(fastify, routes);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const globbedFiles = await globFiles(Object.assign(Object.assign({}, opts), { routeFile: (_d = opts.routeFile) !== null && _d !== void 0 ? _d : 'routes', directory: (_e = opts.directory) !== null && _e !== void 0 ? _e : 'src/plugins', log: opts.log || fastify.log, excludedDirectories: (_f = opts.excludedDirectories) !== null && _f !== void 0 ? _f : [], recursive: (_g = opts.recursive) !== null && _g !== void 0 ? _g : false }));
|
|
6
19
|
await Promise.all(globbedFiles.map(async (globbedFile) => {
|
|
7
20
|
await fastify.register(globbedFile);
|
|
8
21
|
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
2
|
+
import type pino from "pino";
|
|
3
|
+
export type RouteEntry = {
|
|
4
|
+
prefix: string;
|
|
5
|
+
plugin: FastifyPluginAsync;
|
|
6
|
+
};
|
|
7
|
+
export declare const segmentToPrefix: (name: string) => string;
|
|
8
|
+
type ScanOptions = {
|
|
9
|
+
routeFile: string;
|
|
10
|
+
packageType: string;
|
|
11
|
+
excludedDirectories: string[];
|
|
12
|
+
log?: pino.BaseLogger;
|
|
13
|
+
};
|
|
14
|
+
export declare const scanRoutes: (dir: string, opts: ScanOptions, prefix?: string) => Promise<RouteEntry[]>;
|
|
15
|
+
export declare const registerRoutes: (fastify: FastifyInstance, routes: RouteEntry[]) => Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { importPlugin } from "./globber.js";
|
|
4
|
+
export const segmentToPrefix = (name) => {
|
|
5
|
+
if (name.startsWith("_") || /^\(.*\)$/.test(name))
|
|
6
|
+
return "";
|
|
7
|
+
const param = name.match(/^\[(.+)\]$/);
|
|
8
|
+
if (param)
|
|
9
|
+
return `/:${param[1]}`;
|
|
10
|
+
return `/${name}`;
|
|
11
|
+
};
|
|
12
|
+
export const scanRoutes = async (dir, opts, prefix = "") => {
|
|
13
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
14
|
+
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
15
|
+
const plugin = await importPlugin(dir, opts.routeFile, opts.packageType, files, opts.log);
|
|
16
|
+
const nested = await Promise.all(entries
|
|
17
|
+
.filter((e) => e.isDirectory() && !opts.excludedDirectories.includes(e.name))
|
|
18
|
+
.map((e) => scanRoutes(path.join(dir, e.name), opts, prefix + segmentToPrefix(e.name))));
|
|
19
|
+
return [...(plugin ? [{ prefix, plugin }] : []), ...nested.flat()];
|
|
20
|
+
};
|
|
21
|
+
export const registerRoutes = async (fastify, routes) => {
|
|
22
|
+
for (const { prefix, plugin } of routes) {
|
|
23
|
+
await fastify.register(plugin, prefix ? { prefix } : {});
|
|
24
|
+
}
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efebia/fastify-auto-import",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -31,5 +31,10 @@
|
|
|
31
31
|
"require": "./lib/cjs/index.js",
|
|
32
32
|
"import": "./lib/esm/index.js"
|
|
33
33
|
}
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/efebia-com/be-libs.git",
|
|
38
|
+
"directory": "packages/auto-import"
|
|
34
39
|
}
|
|
35
40
|
}
|