dirent-router 1.0.0

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.
Files changed (66) hide show
  1. package/CLAUDE.md +33 -0
  2. package/README.md +102 -0
  3. package/bin/bin.d.ts +2 -0
  4. package/bin/bin.d.ts.map +1 -0
  5. package/bin/bin.js +24 -0
  6. package/bin/bin.js.map +1 -0
  7. package/bin/common/index.d.ts +2 -0
  8. package/bin/common/index.d.ts.map +1 -0
  9. package/bin/common/index.js +18 -0
  10. package/bin/common/index.js.map +1 -0
  11. package/bin/common/types.d.ts +4 -0
  12. package/bin/common/types.d.ts.map +1 -0
  13. package/bin/common/types.js +3 -0
  14. package/bin/common/types.js.map +1 -0
  15. package/bin/endpoint.d.ts +6 -0
  16. package/bin/endpoint.d.ts.map +1 -0
  17. package/bin/endpoint.js +42 -0
  18. package/bin/endpoint.js.map +1 -0
  19. package/bin/router.d.ts +21 -0
  20. package/bin/router.d.ts.map +1 -0
  21. package/bin/router.js +90 -0
  22. package/bin/router.js.map +1 -0
  23. package/bin/util.d.ts +3 -0
  24. package/bin/util.d.ts.map +1 -0
  25. package/bin/util.js +24 -0
  26. package/bin/util.js.map +1 -0
  27. package/dist/common/index.d.ts +2 -0
  28. package/dist/common/index.d.ts.map +1 -0
  29. package/dist/common/index.js +18 -0
  30. package/dist/common/index.js.map +1 -0
  31. package/dist/common/types.d.ts +4 -0
  32. package/dist/common/types.d.ts.map +1 -0
  33. package/dist/common/types.js +3 -0
  34. package/dist/common/types.js.map +1 -0
  35. package/dist/endpoint.d.ts +6 -0
  36. package/dist/endpoint.d.ts.map +1 -0
  37. package/dist/endpoint.js +42 -0
  38. package/dist/endpoint.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/router.d.ts +21 -0
  44. package/dist/router.d.ts.map +1 -0
  45. package/dist/router.js +90 -0
  46. package/dist/router.js.map +1 -0
  47. package/dist/util.d.ts +3 -0
  48. package/dist/util.d.ts.map +1 -0
  49. package/dist/util.js +24 -0
  50. package/dist/util.js.map +1 -0
  51. package/package.json +29 -0
  52. package/sample-routes/companies/[id]/index.ts +3 -0
  53. package/sample-routes/companies/index.ts +7 -0
  54. package/sample-routes/companies/search.ts +3 -0
  55. package/sample-routes/index.ts +3 -0
  56. package/src/bin.ts +27 -0
  57. package/src/common/index.ts +1 -0
  58. package/src/common/types.ts +3 -0
  59. package/src/endpoint.test.ts +60 -0
  60. package/src/endpoint.ts +46 -0
  61. package/src/index.ts +2 -0
  62. package/src/router.ts +139 -0
  63. package/src/util.ts +30 -0
  64. package/tsconfig.bin.json +24 -0
  65. package/tsconfig.json +24 -0
  66. package/tsconfig.lib.json +24 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,33 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ dirent-router is a TypeScript library and CLI that generates type-safe route definitions from a file-system directory structure (Next.js-style routing). It scans a directory for TypeScript files, extracts exported functions as HTTP method handlers, and produces a flat array of route definitions.
8
+
9
+ ## Commands
10
+
11
+ - **Build**: `pnpm build` (runs both `pnpm build:lib` and `pnpm build:bin`)
12
+ - **Type check**: `pnpm check`
13
+ - **Test**: `pnpm test` (vitest)
14
+ - **Run single test**: `pnpm vitest run src/endpoint.test.ts`
15
+ - **Format**: `pnpm prettier --write .`
16
+
17
+ ## Architecture
18
+
19
+ The pipeline flows: **scan directory → build file tree → parse endpoints → import modules → flatten routes**.
20
+
21
+ - `src/router.ts` — Core logic. `findRouteStructure()` recursively walks the routes directory building a `RouteStructure` tree. `findRouteDefs()` converts file paths to endpoint strings (stripping `.ts`/`index.ts`). `flattenRoutes()` dynamically imports each module, extracts exported functions as HTTP methods, and deduplicates by endpoint. Public API: `getRoutes(config)`.
22
+ - `src/endpoint.ts` — Parses endpoint path strings into `EndpointPart[]` segments typed as `path` or `variable` (bracket syntax like `[id]`).
23
+ - `src/bin.ts` — CLI entry point. Takes a directory argument, calls `getRoutes()`, outputs JSON.
24
+ - `src/index.ts` — Library entry re-exporting `endpoint.ts` and `router.ts`.
25
+
26
+ Route files export named functions corresponding to HTTP methods (e.g., `export const get = ...`, `export const post = ...`). Directory segments wrapped in brackets (e.g., `[id]/`) become route variables.
27
+
28
+ ## TypeScript Configuration
29
+
30
+ - Module system: `nodenext` with `esnext` target
31
+ - Strict mode with additional strictness: `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`
32
+ - Library builds to `dist/`, CLI builds to `bin/`
33
+ - Package manager: pnpm
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # dirent-router
2
+ Generate routes from a directory
3
+
4
+ ## Install
5
+ ```
6
+ npm install dirent-router
7
+ # or
8
+ pnpm install dirent-router
9
+ ```
10
+
11
+ ## Route Directory Structure
12
+
13
+ Each `.ts` file in your routes directory becomes an endpoint. Exported function names correspond to HTTP methods (`get`, `post`, `put`, `delete`, etc.).
14
+
15
+ - `index.ts` files map to the parent directory path
16
+ - Other `.ts` files map to a path segment matching their filename
17
+ - Directory names wrapped in brackets (e.g., `[id]`) become route variables
18
+
19
+ **Example:**
20
+
21
+ ```
22
+ routes/
23
+ ├── index.ts → /
24
+ ├── companies/
25
+ │ ├── index.ts → /companies
26
+ │ ├── search.ts → /companies/search
27
+ │ └── [id]/
28
+ │ └── index.ts → /companies/[id]
29
+ ```
30
+
31
+ ```typescript
32
+ // routes/index.ts
33
+ export const get = () => {
34
+ return 'foo';
35
+ }
36
+
37
+ // routes/companies/index.ts
38
+ export const get = () => {
39
+ return [{ id: 'a' }, { id: 'b' }];
40
+ }
41
+
42
+ export const post = () => {
43
+ return { ok: true };
44
+ }
45
+
46
+ // routes/companies/search.ts
47
+ export const get = (searchQuery: string) => {
48
+ return { searchQuery };
49
+ }
50
+
51
+ // routes/companies/[id]/index.ts
52
+ export const get = (id: string) => {
53
+ return { id };
54
+ }
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Integration
60
+ ```typescript
61
+ import fs from "fs";
62
+ import { getRoutes } from "dirent-router";
63
+
64
+ const routes = await getRoutes({
65
+ routesDir: "./path/to/your/routes",
66
+ });
67
+
68
+ console.log(
69
+ JSON.stringify(
70
+ routes,
71
+ (_, v) => (typeof v === "function" ? `${v.name}(...)` : v),
72
+ 2,
73
+ ),
74
+ );
75
+
76
+ // Output example
77
+ [
78
+ {
79
+ "filepath": "sample-routes/companies/[id]",
80
+ "relativepath": "companies/[id]",
81
+ "endpoint": "/companies/[id]",
82
+ "args": [
83
+ {
84
+ "type": "path",
85
+ "value": "companies"
86
+ },
87
+ {
88
+ "type": "variable",
89
+ "value": "id"
90
+ }
91
+ ],
92
+ "methods": {
93
+ get: "[Function]"
94
+ }
95
+ }
96
+ ]
97
+ ```
98
+
99
+ ### CLI
100
+ ```
101
+ dirent-router ./path/to/your/routes
102
+ ```
package/bin/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/bin/bin.js ADDED
@@ -0,0 +1,24 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const router_1 = require("./router");
8
+ const main = async () => {
9
+ const routesDir = process.argv[2];
10
+ if (!routesDir) {
11
+ console.error(`Please provide directory where routes are stored`);
12
+ return;
13
+ }
14
+ if (!fs_1.default.existsSync(routesDir)) {
15
+ console.error(`${routesDir}: No such file or directory`);
16
+ return;
17
+ }
18
+ const routes = await (0, router_1.getRoutes)({
19
+ routesDir,
20
+ });
21
+ console.log(JSON.stringify(routes, (_, v) => (typeof v === "function" ? `${v.name}(...)` : v), 2));
22
+ };
23
+ main().catch((e) => console.error(e));
24
+ //# sourceMappingURL=bin.js.map
package/bin/bin.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";;;;;AAAA,4CAAoB;AACpB,qCAAqC;AAErC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;IACtB,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAClE,OAAO;IACT,CAAC;IACD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,6BAA6B,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAS,EAAC;QAC7B,SAAS;KACV,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,MAAM,EACN,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAC1D,CAAC,CACF,CACF,CAAC;AACJ,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB"}
@@ -0,0 +1,4 @@
1
+ export type LooseSyncFunction = (...args: any[]) => any;
2
+ export type LooseAsyncFunction = (...args: any[]) => Promise<any>;
3
+ export type LooseFunction = LooseSyncFunction | LooseAsyncFunction;
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export type EndpointPart = {
2
+ type: 'path' | 'variable';
3
+ value: string;
4
+ };
5
+ export declare const parseEndpoint: (buff: string) => EndpointPart[];
6
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,YAAY,EAwCxD,CAAC"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEndpoint = void 0;
4
+ const parseEndpoint = (buff) => {
5
+ const slices = [];
6
+ const len = buff.length;
7
+ let i = 0;
8
+ let c = buff[i++];
9
+ const advance = () => {
10
+ c = buff[i++];
11
+ return c;
12
+ };
13
+ while (i <= len) {
14
+ if (c !== "[" && c !== '/' && i <= len) {
15
+ let param = "";
16
+ while (c !== "/" && c !== "[" && i <= len) {
17
+ param += c;
18
+ c = advance();
19
+ }
20
+ slices.push({
21
+ type: 'path',
22
+ value: param,
23
+ });
24
+ }
25
+ if (c === "[") {
26
+ let param = "";
27
+ c = advance();
28
+ while (c !== "]" && i < len) {
29
+ param += c;
30
+ c = advance();
31
+ }
32
+ slices.push({
33
+ type: 'variable',
34
+ value: param,
35
+ });
36
+ }
37
+ c = advance();
38
+ }
39
+ return slices;
40
+ };
41
+ exports.parseEndpoint = parseEndpoint;
42
+ //# sourceMappingURL=endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":";;;AAKO,MAAM,aAAa,GAAG,CAAC,IAAY,EAAkB,EAAE;IAC5D,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,GAAW,CAAC,CAAC;IAClB,IAAI,CAAC,GAAW,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACvC,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC1C,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,CAAC,GAAG,OAAO,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;gBAC5B,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QAED,CAAC,GAAG,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAxCW,QAAA,aAAa,iBAwCxB"}
@@ -0,0 +1,21 @@
1
+ import { LooseFunction } from "./common";
2
+ import { EndpointPart } from "./endpoint";
3
+ export interface RouterConfig {
4
+ routesDir: string;
5
+ }
6
+ export type RouteStructure = {
7
+ filepath: string;
8
+ children: RouteStructure[];
9
+ };
10
+ export type RouteMethods = Record<string, LooseFunction>;
11
+ export type RouteDefinition = {
12
+ filepath: string;
13
+ relativepath: string;
14
+ endpoint: string;
15
+ args: EndpointPart[];
16
+ methods: RouteMethods;
17
+ children: RouteDefinition[];
18
+ };
19
+ export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
20
+ export declare const getRoutes: (cfg: RouterConfig) => Promise<FlatRouteDefinition[]>;
21
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAiB,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAsGpE,eAAO,MAAM,SAAS,GACpB,KAAK,YAAY,KAChB,OAAO,CAAC,mBAAmB,EAAE,CAK/B,CAAC"}
package/bin/router.js ADDED
@@ -0,0 +1,90 @@
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.getRoutes = void 0;
7
+ const util_1 = require("./util");
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const endpoint_1 = require("./endpoint");
11
+ // ---- implementation ----
12
+ const getEntries = async (path) => {
13
+ const entries = (await promises_1.default.readdir(path, {
14
+ recursive: false,
15
+ withFileTypes: true,
16
+ })).filter((entry) => {
17
+ return ((entry.isDirectory() || entry.name.endsWith(".ts")) &&
18
+ !entry.name.includes("#") &&
19
+ !entry.name.includes("~"));
20
+ });
21
+ return entries;
22
+ };
23
+ const findRouteStructure = async (path) => {
24
+ const entries = await getEntries(path);
25
+ const children = (await Promise.all(entries.map(async (entry) => {
26
+ const abspath = path_1.default.join(entry.parentPath, entry.name);
27
+ if (entry.isDirectory()) {
28
+ return await findRouteStructure(abspath);
29
+ }
30
+ return {
31
+ filepath: abspath,
32
+ children: [],
33
+ };
34
+ }))).flat();
35
+ return {
36
+ filepath: path,
37
+ children: children,
38
+ };
39
+ };
40
+ const findRouteDefs = async (rootPath, structure) => {
41
+ const relativepath = path_1.default.relative(rootPath, structure.filepath);
42
+ const endpoint = "/" +
43
+ [relativepath]
44
+ .map((x) => x.replace("index.ts", "").replace(".ts", ""))
45
+ .map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0];
46
+ return {
47
+ filepath: structure.filepath,
48
+ relativepath: relativepath,
49
+ endpoint: endpoint,
50
+ args: (0, endpoint_1.parseEndpoint)(endpoint),
51
+ methods: {},
52
+ children: await Promise.all(structure.children.map(async (child) => {
53
+ return findRouteDefs(rootPath, child);
54
+ })),
55
+ };
56
+ };
57
+ const flattenRoutes = async (defs) => {
58
+ const items = [];
59
+ const q = [defs];
60
+ const seen = new Set();
61
+ while (q.length > 0) {
62
+ const next = q.pop();
63
+ if (seen.has(next.endpoint))
64
+ continue;
65
+ seen.add(next.endpoint);
66
+ const methods = {};
67
+ try {
68
+ const module = await import(next.filepath);
69
+ for (const [k, v] of Object.entries(module)) {
70
+ if (typeof v === "function") {
71
+ methods[k] = v;
72
+ }
73
+ }
74
+ }
75
+ catch (e) {
76
+ console.error(e);
77
+ }
78
+ items.push((0, util_1.omit)({ ...next, methods }, "children"));
79
+ q.push(...next.children);
80
+ }
81
+ return (0, util_1.uniqueBy)(items, (x) => x.endpoint);
82
+ };
83
+ const getRoutes = async (cfg) => {
84
+ const rootPath = path_1.default.resolve(cfg.routesDir);
85
+ const structure = await findRouteStructure(rootPath);
86
+ const defs = await findRouteDefs(rootPath, structure);
87
+ return await flattenRoutes(defs);
88
+ };
89
+ exports.getRoutes = getRoutes;
90
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/bin/util.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const omit: <T extends Record<PropertyKey, unknown>, Keys extends keyof T>(obj: T, ...keys: Keys[]) => Omit<T, Keys>;
2
+ export declare const uniqueBy: <T, Key extends (item: T) => unknown>(arr: T[], key: Key) => T[];
3
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,GACf,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,EACtC,IAAI,SAAS,MAAM,CAAC,EAEpB,KAAK,CAAC,EACN,GAAG,MAAM,IAAI,EAAE,KACd,IAAI,CAAC,CAAC,EAAE,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,EAAE,EACR,KAAK,GAAG,KACP,CAAC,EAUH,CAAC"}
package/bin/util.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uniqueBy = exports.omit = void 0;
4
+ const omit = (obj, ...keys) => {
5
+ const result = { ...obj };
6
+ for (const k of keys) {
7
+ delete result[k];
8
+ }
9
+ return result;
10
+ };
11
+ exports.omit = omit;
12
+ const uniqueBy = (arr, key) => {
13
+ const seen = new Set();
14
+ return arr.filter((x) => {
15
+ const k = key(x);
16
+ if (seen.has(k)) {
17
+ return false;
18
+ }
19
+ seen.add(k);
20
+ return true;
21
+ });
22
+ };
23
+ exports.uniqueBy = uniqueBy;
24
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAIlB,GAAM,EACN,GAAG,IAAY,EACA,EAAE;IACjB,MAAM,MAAM,GAAkB,EAAE,GAAG,GAAG,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,CAAmC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAdW,QAAA,IAAI,QAcf;AAEK,MAAM,QAAQ,GAAG,CACtB,GAAQ,EACR,GAAQ,EACH,EAAE;IACP,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAbW,QAAA,QAAQ,YAanB"}
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB"}
@@ -0,0 +1,4 @@
1
+ export type LooseSyncFunction = (...args: any[]) => any;
2
+ export type LooseAsyncFunction = (...args: any[]) => Promise<any>;
3
+ export type LooseFunction = LooseSyncFunction | LooseAsyncFunction;
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export type EndpointPart = {
2
+ type: 'path' | 'variable';
3
+ value: string;
4
+ };
5
+ export declare const parseEndpoint: (buff: string) => EndpointPart[];
6
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,YAAY,EAwCxD,CAAC"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEndpoint = void 0;
4
+ const parseEndpoint = (buff) => {
5
+ const slices = [];
6
+ const len = buff.length;
7
+ let i = 0;
8
+ let c = buff[i++];
9
+ const advance = () => {
10
+ c = buff[i++];
11
+ return c;
12
+ };
13
+ while (i <= len) {
14
+ if (c !== "[" && c !== '/' && i <= len) {
15
+ let param = "";
16
+ while (c !== "/" && c !== "[" && i <= len) {
17
+ param += c;
18
+ c = advance();
19
+ }
20
+ slices.push({
21
+ type: 'path',
22
+ value: param,
23
+ });
24
+ }
25
+ if (c === "[") {
26
+ let param = "";
27
+ c = advance();
28
+ while (c !== "]" && i < len) {
29
+ param += c;
30
+ c = advance();
31
+ }
32
+ slices.push({
33
+ type: 'variable',
34
+ value: param,
35
+ });
36
+ }
37
+ c = advance();
38
+ }
39
+ return slices;
40
+ };
41
+ exports.parseEndpoint = parseEndpoint;
42
+ //# sourceMappingURL=endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":";;;AAKO,MAAM,aAAa,GAAG,CAAC,IAAY,EAAkB,EAAE;IAC5D,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,GAAW,CAAC,CAAC;IAClB,IAAI,CAAC,GAAW,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAE,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACvC,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC1C,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,KAAK,GAAW,EAAE,CAAC;YACvB,CAAC,GAAG,OAAO,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;gBAC5B,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QAED,CAAC,GAAG,OAAO,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAxCW,QAAA,aAAa,iBAwCxB"}
@@ -0,0 +1,3 @@
1
+ export * from './endpoint';
2
+ export * from './router';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./endpoint"), exports);
18
+ __exportStar(require("./router"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,2CAAyB"}
@@ -0,0 +1,21 @@
1
+ import { LooseFunction } from "./common";
2
+ import { EndpointPart } from "./endpoint";
3
+ export interface RouterConfig {
4
+ routesDir: string;
5
+ }
6
+ export type RouteStructure = {
7
+ filepath: string;
8
+ children: RouteStructure[];
9
+ };
10
+ export type RouteMethods = Record<string, LooseFunction>;
11
+ export type RouteDefinition = {
12
+ filepath: string;
13
+ relativepath: string;
14
+ endpoint: string;
15
+ args: EndpointPart[];
16
+ methods: RouteMethods;
17
+ children: RouteDefinition[];
18
+ };
19
+ export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
20
+ export declare const getRoutes: (cfg: RouterConfig) => Promise<FlatRouteDefinition[]>;
21
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAiB,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAsGpE,eAAO,MAAM,SAAS,GACpB,KAAK,YAAY,KAChB,OAAO,CAAC,mBAAmB,EAAE,CAK/B,CAAC"}
package/dist/router.js ADDED
@@ -0,0 +1,90 @@
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.getRoutes = void 0;
7
+ const util_1 = require("./util");
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const endpoint_1 = require("./endpoint");
11
+ // ---- implementation ----
12
+ const getEntries = async (path) => {
13
+ const entries = (await promises_1.default.readdir(path, {
14
+ recursive: false,
15
+ withFileTypes: true,
16
+ })).filter((entry) => {
17
+ return ((entry.isDirectory() || entry.name.endsWith(".ts")) &&
18
+ !entry.name.includes("#") &&
19
+ !entry.name.includes("~"));
20
+ });
21
+ return entries;
22
+ };
23
+ const findRouteStructure = async (path) => {
24
+ const entries = await getEntries(path);
25
+ const children = (await Promise.all(entries.map(async (entry) => {
26
+ const abspath = path_1.default.join(entry.parentPath, entry.name);
27
+ if (entry.isDirectory()) {
28
+ return await findRouteStructure(abspath);
29
+ }
30
+ return {
31
+ filepath: abspath,
32
+ children: [],
33
+ };
34
+ }))).flat();
35
+ return {
36
+ filepath: path,
37
+ children: children,
38
+ };
39
+ };
40
+ const findRouteDefs = async (rootPath, structure) => {
41
+ const relativepath = path_1.default.relative(rootPath, structure.filepath);
42
+ const endpoint = "/" +
43
+ [relativepath]
44
+ .map((x) => x.replace("index.ts", "").replace(".ts", ""))
45
+ .map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0];
46
+ return {
47
+ filepath: structure.filepath,
48
+ relativepath: relativepath,
49
+ endpoint: endpoint,
50
+ args: (0, endpoint_1.parseEndpoint)(endpoint),
51
+ methods: {},
52
+ children: await Promise.all(structure.children.map(async (child) => {
53
+ return findRouteDefs(rootPath, child);
54
+ })),
55
+ };
56
+ };
57
+ const flattenRoutes = async (defs) => {
58
+ const items = [];
59
+ const q = [defs];
60
+ const seen = new Set();
61
+ while (q.length > 0) {
62
+ const next = q.pop();
63
+ if (seen.has(next.endpoint))
64
+ continue;
65
+ seen.add(next.endpoint);
66
+ const methods = {};
67
+ try {
68
+ const module = await import(next.filepath);
69
+ for (const [k, v] of Object.entries(module)) {
70
+ if (typeof v === "function") {
71
+ methods[k] = v;
72
+ }
73
+ }
74
+ }
75
+ catch (e) {
76
+ console.error(e);
77
+ }
78
+ items.push((0, util_1.omit)({ ...next, methods }, "children"));
79
+ q.push(...next.children);
80
+ }
81
+ return (0, util_1.uniqueBy)(items, (x) => x.endpoint);
82
+ };
83
+ const getRoutes = async (cfg) => {
84
+ const rootPath = path_1.default.resolve(cfg.routesDir);
85
+ const structure = await findRouteStructure(rootPath);
86
+ const defs = await findRouteDefs(rootPath, structure);
87
+ return await flattenRoutes(defs);
88
+ };
89
+ exports.getRoutes = getRoutes;
90
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/dist/util.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare const omit: <T extends Record<PropertyKey, unknown>, Keys extends keyof T>(obj: T, ...keys: Keys[]) => Omit<T, Keys>;
2
+ export declare const uniqueBy: <T, Key extends (item: T) => unknown>(arr: T[], key: Key) => T[];
3
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,GACf,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,EACtC,IAAI,SAAS,MAAM,CAAC,EAEpB,KAAK,CAAC,EACN,GAAG,MAAM,IAAI,EAAE,KACd,IAAI,CAAC,CAAC,EAAE,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,EAAE,EACR,KAAK,GAAG,KACP,CAAC,EAUH,CAAC"}
package/dist/util.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uniqueBy = exports.omit = void 0;
4
+ const omit = (obj, ...keys) => {
5
+ const result = { ...obj };
6
+ for (const k of keys) {
7
+ delete result[k];
8
+ }
9
+ return result;
10
+ };
11
+ exports.omit = omit;
12
+ const uniqueBy = (arr, key) => {
13
+ const seen = new Set();
14
+ return arr.filter((x) => {
15
+ const k = key(x);
16
+ if (seen.has(k)) {
17
+ return false;
18
+ }
19
+ seen.add(k);
20
+ return true;
21
+ });
22
+ };
23
+ exports.uniqueBy = uniqueBy;
24
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAIlB,GAAM,EACN,GAAG,IAAY,EACA,EAAE;IACjB,MAAM,MAAM,GAAkB,EAAE,GAAG,GAAG,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC,CAAmC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAdW,QAAA,IAAI,QAcf;AAEK,MAAM,QAAQ,GAAG,CACtB,GAAQ,EACR,GAAQ,EACH,EAAE;IACP,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAbW,QAAA,QAAQ,YAanB"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "dirent-router",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "./src/index.ts",
6
+ "types": "./dist/index.d.ts",
7
+ "typings": "./dist/index.d.ts",
8
+ "bin": {
9
+ "dirent-router": "./bin/bin.js"
10
+ },
11
+ "scripts": {
12
+ "test": "vitest",
13
+ "build:lib": "tsc -p ./tsconfig.lib.json",
14
+ "build:bin": "tsc -p ./tsconfig.bin.json",
15
+ "build": "pnpm build:lib && pnpm build:bin",
16
+ "check": "tsc -p ./tsconfig.json --noEmit"
17
+ },
18
+ "keywords": [],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "packageManager": "pnpm@10.28.2",
22
+ "devDependencies": {
23
+ "@types/node": "^25.2.3",
24
+ "prettier": "^3.8.1",
25
+ "tsx": "^4.21.0",
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^4.0.18"
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export const get = (id: string) => {
2
+ return { id };
3
+ }
@@ -0,0 +1,7 @@
1
+ export const get = () => {
2
+ return [{ id: 'a' }, { id: 'b' }]
3
+ }
4
+
5
+ export const post = () => {
6
+ return { ok: true };
7
+ }
@@ -0,0 +1,3 @@
1
+ export const get = (searchQuery: string) => {
2
+ return { searchQuery };
3
+ }
@@ -0,0 +1,3 @@
1
+ export const get = () => {
2
+ return 'foo';
3
+ }
package/src/bin.ts ADDED
@@ -0,0 +1,27 @@
1
+ import fs from "fs";
2
+ import { getRoutes } from "./router";
3
+
4
+ const main = async () => {
5
+ const routesDir = process.argv[2];
6
+ if (!routesDir) {
7
+ console.error(`Please provide directory where routes are stored`);
8
+ return;
9
+ }
10
+ if (!fs.existsSync(routesDir)) {
11
+ console.error(`${routesDir}: No such file or directory`);
12
+ return;
13
+ }
14
+ const routes = await getRoutes({
15
+ routesDir,
16
+ });
17
+
18
+ console.log(
19
+ JSON.stringify(
20
+ routes,
21
+ (_, v) => (typeof v === "function" ? `${v.name}(...)` : v),
22
+ 2,
23
+ ),
24
+ );
25
+ };
26
+
27
+ main().catch((e) => console.error(e));
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export type LooseSyncFunction = (...args: any[]) => any;
2
+ export type LooseAsyncFunction = (...args: any[]) => Promise<any>;
3
+ export type LooseFunction = LooseSyncFunction | LooseAsyncFunction;
@@ -0,0 +1,60 @@
1
+ import { describe, assert } from "vitest";
2
+ import { EndpointPart, parseEndpoint } from "./endpoint";
3
+
4
+ type ParseExpect = {
5
+ input: string;
6
+ output: EndpointPart[];
7
+ };
8
+
9
+ const EXPECTED_RESULTS: Array<ParseExpect> = [
10
+ {
11
+ input: "/index.ts",
12
+ output: [
13
+ {
14
+ type: "path",
15
+ value: "index.ts",
16
+ },
17
+ ],
18
+ },
19
+ {
20
+ input: "/user/[id]/index.ts",
21
+ output: [
22
+ { type: "path", value: "user" },
23
+ { type: "variable", value: "id" },
24
+ { type: "path", value: "index.ts" },
25
+ ],
26
+ },
27
+ {
28
+ input: "/user/foobar.ts",
29
+ output: [
30
+ { type: "path", value: "user" },
31
+ { type: "path", value: "foobar.ts" },
32
+ ],
33
+ },
34
+ {
35
+ input: "/user/[id]/entity/[id]/index.ts",
36
+ output: [
37
+ { type: "path", value: "user" },
38
+ { type: "variable", value: "id" },
39
+ { type: "path", value: "entity" },
40
+ { type: "variable", value: "id" },
41
+ { type: "path", value: "index.ts" },
42
+ ],
43
+ },
44
+ ];
45
+
46
+ describe("Parse route path", (it) => {
47
+ it("should create correct slices", () => {
48
+ for (const expected of EXPECTED_RESULTS) {
49
+ const output = parseEndpoint(expected.input);
50
+ assert.strictEqual(output.length, expected.output.length);
51
+ for (let i = 0; i < output.length; i++) {
52
+ const a = output[i]!;
53
+ const b = expected.output[i]!;
54
+
55
+ assert.strictEqual(a.value, b.value);
56
+ assert.strictEqual(a.type, b.type);
57
+ }
58
+ }
59
+ });
60
+ });
@@ -0,0 +1,46 @@
1
+ export type EndpointPart = {
2
+ type: 'path' | 'variable';
3
+ value: string;
4
+ };
5
+
6
+ export const parseEndpoint = (buff: string): EndpointPart[] => {
7
+ const slices: EndpointPart[] = [];
8
+ const len = buff.length;
9
+ let i: number = 0;
10
+ let c: string = buff[i++]!;
11
+
12
+ const advance = () => {
13
+ c = buff[i++]!;
14
+ return c;
15
+ };
16
+
17
+ while (i <= len) {
18
+ if (c !== "[" && c !== '/' && i <= len) {
19
+ let param: string = "";
20
+ while (c !== "/" && c !== "[" && i <= len) {
21
+ param += c;
22
+ c = advance();
23
+ }
24
+ slices.push({
25
+ type: 'path',
26
+ value: param,
27
+ });
28
+ }
29
+ if (c === "[") {
30
+ let param: string = "";
31
+ c = advance();
32
+ while (c !== "]" && i < len) {
33
+ param += c;
34
+ c = advance();
35
+ }
36
+ slices.push({
37
+ type: 'variable',
38
+ value: param,
39
+ });
40
+ }
41
+
42
+ c = advance();
43
+ }
44
+
45
+ return slices;
46
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './endpoint';
2
+ export * from './router';
package/src/router.ts ADDED
@@ -0,0 +1,139 @@
1
+ import { omit, uniqueBy } from "./util";
2
+ import { Dirent } from "fs";
3
+ import fs from "fs/promises";
4
+ import pathlib from "path";
5
+ import { LooseFunction } from "./common";
6
+ import { EndpointPart, parseEndpoint } from "./endpoint";
7
+
8
+ // ---- types ----
9
+
10
+ export interface RouterConfig {
11
+ routesDir: string;
12
+ }
13
+
14
+ export type RouteStructure = {
15
+ filepath: string;
16
+ children: RouteStructure[];
17
+ };
18
+
19
+ export type RouteMethods = Record<string, LooseFunction>;
20
+
21
+ export type RouteDefinition = {
22
+ filepath: string;
23
+ relativepath: string;
24
+ endpoint: string;
25
+ args: EndpointPart[];
26
+ methods: RouteMethods;
27
+ children: RouteDefinition[];
28
+ };
29
+
30
+ export type FlatRouteDefinition = Omit<RouteDefinition, "children">;
31
+
32
+ // ---- implementation ----
33
+
34
+ const getEntries = async (path: string) => {
35
+ const entries: Dirent<string>[] = (
36
+ await fs.readdir(path, {
37
+ recursive: false,
38
+ withFileTypes: true,
39
+ })
40
+ ).filter((entry) => {
41
+ return (
42
+ (entry.isDirectory() || entry.name.endsWith(".ts")) &&
43
+ !entry.name.includes("#") &&
44
+ !entry.name.includes("~")
45
+ );
46
+ });
47
+ return entries;
48
+ };
49
+
50
+ const findRouteStructure = async (path: string): Promise<RouteStructure> => {
51
+ const entries = await getEntries(path);
52
+ const children = (
53
+ await Promise.all(
54
+ entries.map(async (entry): Promise<RouteStructure> => {
55
+ const abspath = pathlib.join(entry.parentPath, entry.name);
56
+ if (entry.isDirectory()) {
57
+ return await findRouteStructure(abspath);
58
+ }
59
+
60
+ return {
61
+ filepath: abspath,
62
+ children: [],
63
+ };
64
+ }),
65
+ )
66
+ ).flat();
67
+
68
+ return {
69
+ filepath: path,
70
+ children: children,
71
+ };
72
+ };
73
+
74
+ const findRouteDefs = async (
75
+ rootPath: string,
76
+ structure: RouteStructure,
77
+ ): Promise<RouteDefinition> => {
78
+ const relativepath = pathlib.relative(rootPath, structure.filepath);
79
+ const endpoint =
80
+ "/" +
81
+ [relativepath]
82
+ .map((x) => x.replace("index.ts", "").replace(".ts", ""))
83
+ .map((x) => (x.endsWith("/") ? x.slice(0, -1) : x))[0]!;
84
+
85
+ return {
86
+ filepath: structure.filepath,
87
+ relativepath: relativepath,
88
+ endpoint: endpoint,
89
+ args: parseEndpoint(endpoint),
90
+ methods: {},
91
+ children: await Promise.all(
92
+ structure.children.map(async (child) => {
93
+ return findRouteDefs(rootPath, child);
94
+ }),
95
+ ),
96
+ };
97
+ };
98
+
99
+ const flattenRoutes = async (
100
+ defs: RouteDefinition,
101
+ ): Promise<FlatRouteDefinition[]> => {
102
+ const items: FlatRouteDefinition[] = [];
103
+ const q: Array<RouteDefinition> = [defs];
104
+ const seen = new Set<string>();
105
+
106
+ while (q.length > 0) {
107
+ const next = q.pop()!;
108
+ if (seen.has(next.endpoint)) continue;
109
+ seen.add(next.endpoint);
110
+
111
+ const methods: RouteMethods = {};
112
+
113
+ try {
114
+ const module = await import(next.filepath);
115
+
116
+ for (const [k, v] of Object.entries(module)) {
117
+ if (typeof v === "function") {
118
+ (methods as Record<string, unknown>)[k] = v;
119
+ }
120
+ }
121
+ } catch (e) {
122
+ console.error(e);
123
+ }
124
+
125
+ items.push(omit({ ...next, methods }, "children"));
126
+ q.push(...next.children);
127
+ }
128
+
129
+ return uniqueBy(items, (x) => x.endpoint);
130
+ };
131
+
132
+ export const getRoutes = async (
133
+ cfg: RouterConfig,
134
+ ): Promise<FlatRouteDefinition[]> => {
135
+ const rootPath = pathlib.resolve(cfg.routesDir);
136
+ const structure = await findRouteStructure(rootPath);
137
+ const defs = await findRouteDefs(rootPath, structure);
138
+ return await flattenRoutes(defs);
139
+ };
package/src/util.ts ADDED
@@ -0,0 +1,30 @@
1
+ export const omit = <
2
+ T extends Record<PropertyKey, unknown>,
3
+ Keys extends keyof T,
4
+ >(
5
+ obj: T,
6
+ ...keys: Keys[]
7
+ ): Omit<T, Keys> => {
8
+ const result: Omit<T, Keys> = { ...obj };
9
+
10
+ for (const k of keys) {
11
+ delete result[k as unknown as keyof typeof result];
12
+ }
13
+
14
+ return result;
15
+ };
16
+
17
+ export const uniqueBy = <T, Key extends (item: T) => unknown>(
18
+ arr: T[],
19
+ key: Key,
20
+ ): T[] => {
21
+ const seen = new Set<unknown>();
22
+ return arr.filter((x) => {
23
+ const k = key(x);
24
+ if (seen.has(k)) {
25
+ return false;
26
+ }
27
+ seen.add(k);
28
+ return true;
29
+ });
30
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./bin",
5
+ "module": "nodenext",
6
+ "target": "esnext",
7
+ "lib": ["esnext"],
8
+ "types": ["node"],
9
+ "sourceMap": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "strict": true,
15
+ "jsx": "react-jsx",
16
+ "isolatedModules": true,
17
+ "noUncheckedSideEffectImports": true,
18
+ "moduleDetection": "force",
19
+ "esModuleInterop": true,
20
+ "skipLibCheck": true
21
+ },
22
+ "include": ["./src/bin.ts"],
23
+ "exclude": ["./src/**/*.test.ts"]
24
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./dist",
5
+ "emit": false,
6
+ "module": "nodenext",
7
+ "target": "esnext",
8
+ "lib": ["esnext"],
9
+ "types": ["node"],
10
+ "sourceMap": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "noUncheckedIndexedAccess": true,
14
+ "exactOptionalPropertyTypes": true,
15
+ "strict": true,
16
+ "jsx": "react-jsx",
17
+ "isolatedModules": true,
18
+ "noUncheckedSideEffectImports": true,
19
+ "moduleDetection": "force",
20
+ "esModuleInterop": true,
21
+ "skipLibCheck": true
22
+ },
23
+ "include": ["./src/**/*.ts"]
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./dist",
5
+ "module": "nodenext",
6
+ "target": "esnext",
7
+ "lib": ["esnext"],
8
+ "types": ["node"],
9
+ "sourceMap": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "strict": true,
15
+ "jsx": "react-jsx",
16
+ "isolatedModules": true,
17
+ "noUncheckedSideEffectImports": true,
18
+ "moduleDetection": "force",
19
+ "esModuleInterop": true,
20
+ "skipLibCheck": true
21
+ },
22
+ "include": ["./src/**/*.ts"],
23
+ "exclude": ["./src/**/*.test.ts", "./src/bin.ts"]
24
+ }