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 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 structure.
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
- > ⚠️ **IMPORTANT:** At this time, the plugin's functionality is only guaranteed in nodejs runtime
6
+ ## Highlights
6
7
 
7
- ## Installation
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
- ## Usage
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
- ### Generated result
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 { me } from './routes/auth/me'
53
- import { signin } from './routes/auth/signin'
54
- import { signup } from './routes/auth/signup'
55
- import { createPlanet } from './routes/planets/create'
56
- import { indexRoute } from './routes/planets'
57
- import { listPlanets } from './routes/planets/list'
58
- import { findPlanet } from './routes/planets/{id}/find'
59
- import { updatePlanet } from './routes/planets/{id}/update'
60
- import { sse } from './routes/sse'
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: '/auth/me' }),
65
- signin: signin.route({ path: '/auth/signin' }),
66
- signup: signup.route({ path: '/auth/signup' }),
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: '/planets/create' }),
70
- indexRoute: indexRoute.route({ path: '/planets' }),
71
- list: listPlanets.route({ path: '/planets/list' }),
72
- find: findPlanet.route({ path: '/planets/{id}/find' }),
73
- update: updatePlanet.route({ path: '/planets/{id}/update' }),
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: '/sse' }),
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 isSingleChild(obj) {
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 generateRouter(routesDir: string, outputFile: string): Promise<Router>;
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 generateRouter(routesDir: string, outputFile: string): Promise<Router>;
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 generateRouter(routesDir: string, outputFile: string): Promise<Router>;
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 isSingleChild(obj) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orpc-file-based-router",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "File-based router plugin for oRPC - automatically generate oRPC router from your file structure",
5
5
  "author": "zeeeeby",
6
6
  "license": "MIT",