orpc-file-based-router 0.0.8 → 0.0.10
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 +69 -38
- package/dist/index.cjs +15 -10
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +15 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# orpc-file-based-router
|
|
2
2
|
|
|
3
|
-
A plugin for [oRPC](https://orpc.unnoq.com) that automatically creates an oRPC router configuration based on your file
|
|
3
|
+
A plugin for [oRPC](https://orpc.unnoq.com) that automatically creates an oRPC router configuration based on your file
|
|
4
|
+
structure, similar to Next.js, express-file-routing
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Highlights
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- 📁 File-based structure: Organize your API endpoints intuitively through your filesystem
|
|
9
|
+
- 🔄 Zero configuration: Generate routes automatically based on your directory structure
|
|
10
|
+
- ⚡️ Development speed: Eliminate boilerplate code and reduce maintenance overhead
|
|
11
|
+
- 🔍 Dynamic routing: Support for path parameters using {param} syntax in file names
|
|
12
|
+
- 📑 Index handling: Support for index routes via index.ts files
|
|
13
|
+
|
|
14
|
+
> ⚠️ **IMPORTANT:** At this time, the plugin's functionality is only guaranteed
|
|
15
|
+
> in nodejs runtime
|
|
16
|
+
|
|
17
|
+
## Get started
|
|
18
|
+
|
|
19
|
+
1. Install package
|
|
8
20
|
|
|
9
21
|
```bash
|
|
10
22
|
npm install orpc-file-based-router
|
|
@@ -12,22 +24,9 @@ npm install orpc-file-based-router
|
|
|
12
24
|
yarn add orpc-file-based-router
|
|
13
25
|
```
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { RPCHandler } from '@orpc/server/node'
|
|
19
|
-
import { generateRouter } from 'orpc-file-based-router'
|
|
20
|
-
|
|
21
|
-
const routesDir = new URL('./routes', import.meta.url).pathname
|
|
22
|
-
const outputFile = new URL('./router.ts', import.meta.url).pathname
|
|
23
|
-
const router = await generateRouter(routesDir, outputFile)
|
|
27
|
+
2. Create a routes directory structure (for example):
|
|
24
28
|
|
|
25
|
-
const handler = new RPCHandler(router)
|
|
26
29
|
```
|
|
27
|
-
|
|
28
|
-
### File Structure Example
|
|
29
|
-
|
|
30
|
-
```
|
|
31
30
|
src/routes
|
|
32
31
|
├── auth
|
|
33
32
|
│ ├── me.ts
|
|
@@ -43,37 +42,69 @@ src/routes
|
|
|
43
42
|
│ ├── index.ts
|
|
44
43
|
│ └── list.ts
|
|
45
44
|
│
|
|
46
|
-
└── sse.ts
|
|
45
|
+
└── sse.ts
|
|
47
46
|
```
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
3. Each file should export a oRPC handler function
|
|
49
|
+
|
|
50
|
+
4. Simply replace router in your handlers with the result of the `createRouter`
|
|
51
|
+
function:
|
|
50
52
|
|
|
51
53
|
```typescript
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
import { RPCHandler } from "@orpc/server/node";
|
|
55
|
+
import { createRouter } from "orpc-file-based-router";
|
|
56
|
+
|
|
57
|
+
const routesDir = new URL("./routes", import.meta.url).pathname;
|
|
58
|
+
const router = await createRouter(routesDir);
|
|
59
|
+
|
|
60
|
+
const handler = new RPCHandler(router);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## How to generate configuration file
|
|
64
|
+
|
|
65
|
+
1. Call `generateRouter`
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { generateRouter } from "orpc-file-based-router";
|
|
69
|
+
|
|
70
|
+
const routesDir = new URL("./routes", import.meta.url).pathname;
|
|
71
|
+
const outputFile = new URL("./router.ts", import.meta.url).pathname;
|
|
72
|
+
generateRouter(routesDir, outputFile);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Generated configuration is ready to use in router client:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// router.ts
|
|
79
|
+
import { me } from "./routes/auth/me";
|
|
80
|
+
import { signin } from "./routes/auth/signin";
|
|
81
|
+
import { signup } from "./routes/auth/signup";
|
|
82
|
+
import { createPlanet } from "./routes/planets/create";
|
|
83
|
+
import { indexRoute } from "./routes/planets";
|
|
84
|
+
import { listPlanets } from "./routes/planets/list";
|
|
85
|
+
import { findPlanet } from "./routes/planets/{id}/find";
|
|
86
|
+
import { updatePlanet } from "./routes/planets/{id}/update";
|
|
87
|
+
import { sse } from "./routes/sse";
|
|
61
88
|
|
|
62
89
|
export const router = {
|
|
63
90
|
auth: {
|
|
64
|
-
me: me.route({ path:
|
|
65
|
-
signin: signin.route({ path:
|
|
66
|
-
signup: signup.route({ path:
|
|
91
|
+
me: me.route({ path: "/auth/me" }),
|
|
92
|
+
signin: signin.route({ path: "/auth/signin" }),
|
|
93
|
+
signup: signup.route({ path: "/auth/signup" }),
|
|
67
94
|
},
|
|
68
95
|
planets: {
|
|
69
|
-
create: createPlanet.route({ path:
|
|
70
|
-
indexRoute: indexRoute.route({ path:
|
|
71
|
-
list: listPlanets.route({ path:
|
|
72
|
-
find: findPlanet.route({ path:
|
|
73
|
-
update: updatePlanet.route({ path:
|
|
96
|
+
create: createPlanet.route({ path: "/planets/create" }),
|
|
97
|
+
indexRoute: indexRoute.route({ path: "/planets" }),
|
|
98
|
+
list: listPlanets.route({ path: "/planets/list" }),
|
|
99
|
+
find: findPlanet.route({ path: "/planets/{id}/find" }),
|
|
100
|
+
update: updatePlanet.route({ path: "/planets/{id}/update" }),
|
|
74
101
|
},
|
|
75
|
-
sse: sse.route({ path:
|
|
76
|
-
}
|
|
102
|
+
sse: sse.route({ path: "/sse" }),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// lib/orpc.ts
|
|
106
|
+
const client: RouterClient<typeof router> = createORPCClient(link)
|
|
107
|
+
|
|
77
108
|
```
|
|
78
109
|
|
|
79
110
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,15 @@ function walkTree(directory, tree = []) {
|
|
|
27
27
|
function mergePaths(...paths) {
|
|
28
28
|
return `/${paths.map((path2) => path2.replace(/^\/|\/$/g, "")).filter((path2) => path2 !== "").join("/")}`;
|
|
29
29
|
}
|
|
30
|
+
async function createRouter(routesDir) {
|
|
31
|
+
const files = walkTree(
|
|
32
|
+
routesDir
|
|
33
|
+
);
|
|
34
|
+
const exports = await generateRoutes(files);
|
|
35
|
+
return buildRouter(exports, (r, e) => {
|
|
36
|
+
return r.exports[e].route({ path: `${r.path}` });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
30
39
|
async function generateRouter(routesDir, outputFile) {
|
|
31
40
|
const files = walkTree(
|
|
32
41
|
routesDir
|
|
@@ -43,10 +52,6 @@ async function generateRouter(routesDir, outputFile) {
|
|
|
43
52
|
routerContent += "\n\nexport const router = ";
|
|
44
53
|
routerContent += JSON.stringify(content, null, 2).replace(/"/g, "");
|
|
45
54
|
node_fs.writeFileSync(path.join(outputFile), routerContent);
|
|
46
|
-
const router = buildRouter(exports, (r, e) => {
|
|
47
|
-
return r.exports[e].route({ path: `${r.path}` });
|
|
48
|
-
});
|
|
49
|
-
return router;
|
|
50
55
|
}
|
|
51
56
|
function buildRoutePath(parsedFile) {
|
|
52
57
|
const directory = parsedFile.dir === parsedFile.root ? "" : parsedFile.dir;
|
|
@@ -78,12 +83,11 @@ function simplifyRouter(router) {
|
|
|
78
83
|
for (const key in router) {
|
|
79
84
|
if (isLeaf(router[key])) {
|
|
80
85
|
simplifiedRouter[key] = router[key];
|
|
86
|
+
} else if (hasSingleLeaf(router[key])) {
|
|
87
|
+
const childKey = Object.keys(router[key])[0];
|
|
88
|
+
simplifiedRouter[key] = router[key][childKey];
|
|
81
89
|
} else {
|
|
82
90
|
simplifiedRouter[key] = simplifyRouter(router[key]);
|
|
83
|
-
if (isSingleChild(simplifiedRouter[key])) {
|
|
84
|
-
const childKey = Object.keys(simplifiedRouter[key])[0];
|
|
85
|
-
simplifiedRouter[key] = simplifiedRouter[key][childKey];
|
|
86
|
-
}
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
return simplifiedRouter;
|
|
@@ -94,9 +98,9 @@ function isORPCProcedure(obj) {
|
|
|
94
98
|
function isLeaf(obj) {
|
|
95
99
|
return typeof obj === "string" || isORPCProcedure(obj);
|
|
96
100
|
}
|
|
97
|
-
function
|
|
101
|
+
function hasSingleLeaf(obj) {
|
|
98
102
|
const keys = Object.keys(obj);
|
|
99
|
-
return keys.length === 1;
|
|
103
|
+
return keys.length === 1 && isLeaf(obj[keys[0]]);
|
|
100
104
|
}
|
|
101
105
|
const isCjs = () => typeof module !== "undefined" && !!module?.exports;
|
|
102
106
|
const IS_ESM = !isCjs();
|
|
@@ -115,4 +119,5 @@ async function generateRoutes(files) {
|
|
|
115
119
|
return routes;
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
exports.createRouter = createRouter;
|
|
118
123
|
exports.generateRouter = generateRouter;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
declare function
|
|
1
|
+
declare function createRouter(routesDir: string): Promise<Router>;
|
|
2
|
+
declare function generateRouter(routesDir: string, outputFile: string): Promise<void>;
|
|
2
3
|
type Router = Record<string, any>;
|
|
3
4
|
|
|
4
|
-
export { generateRouter };
|
|
5
|
+
export { createRouter, generateRouter };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
declare function
|
|
1
|
+
declare function createRouter(routesDir: string): Promise<Router>;
|
|
2
|
+
declare function generateRouter(routesDir: string, outputFile: string): Promise<void>;
|
|
2
3
|
type Router = Record<string, any>;
|
|
3
4
|
|
|
4
|
-
export { generateRouter };
|
|
5
|
+
export { createRouter, generateRouter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
declare function
|
|
1
|
+
declare function createRouter(routesDir: string): Promise<Router>;
|
|
2
|
+
declare function generateRouter(routesDir: string, outputFile: string): Promise<void>;
|
|
2
3
|
type Router = Record<string, any>;
|
|
3
4
|
|
|
4
|
-
export { generateRouter };
|
|
5
|
+
export { createRouter, generateRouter };
|
package/dist/index.mjs
CHANGED
|
@@ -21,6 +21,15 @@ function walkTree(directory, tree = []) {
|
|
|
21
21
|
function mergePaths(...paths) {
|
|
22
22
|
return `/${paths.map((path2) => path2.replace(/^\/|\/$/g, "")).filter((path2) => path2 !== "").join("/")}`;
|
|
23
23
|
}
|
|
24
|
+
async function createRouter(routesDir) {
|
|
25
|
+
const files = walkTree(
|
|
26
|
+
routesDir
|
|
27
|
+
);
|
|
28
|
+
const exports = await generateRoutes(files);
|
|
29
|
+
return buildRouter(exports, (r, e) => {
|
|
30
|
+
return r.exports[e].route({ path: `${r.path}` });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
24
33
|
async function generateRouter(routesDir, outputFile) {
|
|
25
34
|
const files = walkTree(
|
|
26
35
|
routesDir
|
|
@@ -37,10 +46,6 @@ async function generateRouter(routesDir, outputFile) {
|
|
|
37
46
|
routerContent += "\n\nexport const router = ";
|
|
38
47
|
routerContent += JSON.stringify(content, null, 2).replace(/"/g, "");
|
|
39
48
|
writeFileSync(join(outputFile), routerContent);
|
|
40
|
-
const router = buildRouter(exports, (r, e) => {
|
|
41
|
-
return r.exports[e].route({ path: `${r.path}` });
|
|
42
|
-
});
|
|
43
|
-
return router;
|
|
44
49
|
}
|
|
45
50
|
function buildRoutePath(parsedFile) {
|
|
46
51
|
const directory = parsedFile.dir === parsedFile.root ? "" : parsedFile.dir;
|
|
@@ -72,12 +77,11 @@ function simplifyRouter(router) {
|
|
|
72
77
|
for (const key in router) {
|
|
73
78
|
if (isLeaf(router[key])) {
|
|
74
79
|
simplifiedRouter[key] = router[key];
|
|
80
|
+
} else if (hasSingleLeaf(router[key])) {
|
|
81
|
+
const childKey = Object.keys(router[key])[0];
|
|
82
|
+
simplifiedRouter[key] = router[key][childKey];
|
|
75
83
|
} else {
|
|
76
84
|
simplifiedRouter[key] = simplifyRouter(router[key]);
|
|
77
|
-
if (isSingleChild(simplifiedRouter[key])) {
|
|
78
|
-
const childKey = Object.keys(simplifiedRouter[key])[0];
|
|
79
|
-
simplifiedRouter[key] = simplifiedRouter[key][childKey];
|
|
80
|
-
}
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
return simplifiedRouter;
|
|
@@ -88,9 +92,9 @@ function isORPCProcedure(obj) {
|
|
|
88
92
|
function isLeaf(obj) {
|
|
89
93
|
return typeof obj === "string" || isORPCProcedure(obj);
|
|
90
94
|
}
|
|
91
|
-
function
|
|
95
|
+
function hasSingleLeaf(obj) {
|
|
92
96
|
const keys = Object.keys(obj);
|
|
93
|
-
return keys.length === 1;
|
|
97
|
+
return keys.length === 1 && isLeaf(obj[keys[0]]);
|
|
94
98
|
}
|
|
95
99
|
const isCjs = () => typeof module !== "undefined" && !!module?.exports;
|
|
96
100
|
const IS_ESM = !isCjs();
|
|
@@ -109,4 +113,4 @@ async function generateRoutes(files) {
|
|
|
109
113
|
return routes;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
export { generateRouter };
|
|
116
|
+
export { createRouter, generateRouter };
|
package/package.json
CHANGED