orpc-file-based-router 0.1.4 → 0.1.6

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
@@ -9,10 +9,6 @@ structure, inspired by Next.js and express-file-routing approaches.
9
9
  - 🔄 **Zero Configuration**: Generate routes automatically based on your directory structure
10
10
  - ⚡️ **Development Speed**: Eliminate boilerplate code and reduce maintenance overhead
11
11
  - 🔍 **Dynamic Routing**: Support for path parameters using `{param}` syntax in file names
12
- - 📑 **Index Routes**: 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
12
 
17
13
  ## Quickstart
18
14
 
@@ -105,31 +101,33 @@ generateRouter(routesDir, outputFile);
105
101
 
106
102
  ```typescript
107
103
  // router.ts
108
- import { me } from "./routes/auth/me";
109
- import { signin } from "./routes/auth/signin";
110
- import { signup } from "./routes/auth/signup";
111
- import { createPlanet } from "./routes/planets/create";
112
- import { indexRoute } from "./routes/planets";
113
- import { listPlanets } from "./routes/planets/list";
114
- import { findPlanet } from "./routes/planets/{id}/find";
115
- import { updatePlanet } from "./routes/planets/{id}/update";
116
- import { sse } from "./routes/sse";
104
+
105
+ import { me as auth_me__me } from "./routes/auth/me.js"
106
+ import { signin as auth_signin__signin } from "./routes/auth/signin.js"
107
+ import { signup as auth_signup__signup } from "./routes/auth/signup.js"
108
+ import { createPlanet as planets_create__createPlanet } from "./routes/planets/create.js"
109
+ import { indexRoute as planets_index__indexRoute } from "./routes/planets/index.js"
110
+ import { listPlanets as planets_list__listPlanets } from "./routes/planets/list.js"
111
+ import { findPlanet as planets_id_find__findPlanet } from "./routes/planets/{id}/find.js"
112
+ import { updatePlanet as planets_id_update__updatePlanet } from "./routes/planets/{id}/update.js"
113
+ import { sse as sse__sse } from "./routes/sse.js"
117
114
 
118
115
  export const router = {
119
116
  auth: {
120
- me: me.route({ path: "/auth/me" }),
121
- signin: signin.route({ path: "/auth/signin" }),
122
- signup: signup.route({ path: "/auth/signup" }),
117
+ me: auth_me__me.route({ path: '/auth/me', method: 'GET' }),
118
+ signin: auth_signin__signin.route({ path: '/auth/signin', method: 'POST' }),
119
+ signup: auth_signup__signup.route({ path: '/auth/signup', method: 'POST' })
123
120
  },
124
121
  planets: {
125
- create: createPlanet.route({ path: "/planets/create" }),
126
- indexRoute: indexRoute.route({ path: "/planets" }),
127
- list: listPlanets.route({ path: "/planets/list" }),
128
- find: findPlanet.route({ path: "/planets/{id}/find" }),
129
- update: updatePlanet.route({ path: "/planets/{id}/update" }),
122
+ create: planets_create__createPlanet.route({ path: '/planets/create', method: 'POST' }),
123
+ index: planets_index__indexRoute.route({ path: '/planets', method: 'GET' }),
124
+ list: planets_list__listPlanets.route({ path: '/planets/list', method: 'GET' }),
125
+ find: planets_id_find__findPlanet.route({ path: '/planets/{id}/find', method: 'GET' }),
126
+ update: planets_id_update__updatePlanet.route({ path: '/planets/{id}/update', method: 'PUT' })
130
127
  },
131
- sse: sse.route({ path: "/sse" }),
132
- };
128
+ sse: sse__sse.route({ path: '/sse', method: 'GET' })
129
+ }
130
+
133
131
 
134
132
  // lib/orpc.ts
135
133
  const client: RouterClient<typeof router> = createORPCClient(link)
@@ -143,8 +141,21 @@ When using `generateRouter`, you can provide additional options to customize the
143
141
  | Field | Type | Required | Default Value | Description |
144
142
  |-------------------|----------|--------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------|
145
143
  | `importExtension` | string | false | `""`(No extension) | File extension to append to import statements in the generated router. Useful when your build setup requires specific extensions. <br>Example: `.js` → `import { me } from "./routes/auth/me.js"` |
144
+ | `enableOpenAPI` | boolean | false | `true` | When set to true, each route will be wrapped with OpenAPI .route({ path: '...', method: '...' }) call |
145
+ | `additionalMethods` | string[] | false | `[]` | Additional HTTP methods to recognize from export names. |
146
+
146
147
 
148
+ ## Examples
149
+ ### HTTP Method Matching
150
+ If you export functions named e.g. `get`, `post`, `put`, `patch`, `delete/del` etc. from a route file, those will get matched their corresponding http method automatically.
147
151
 
152
+ ```typescript
153
+ // ./routes/planets.ts
154
+
155
+ export const get = orpc.handler(async ({ input, context }) => {})
156
+
157
+ export const post = orpc.handler(async ({ input, context }) => {})
158
+ ```
148
159
 
149
160
  ## 📄 License
150
161
 
package/dist/index.cjs CHANGED
@@ -7,6 +7,25 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
7
7
 
8
8
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
9
9
 
10
+ const METHOD_EXPORTS = [
11
+ "GET",
12
+ "POST",
13
+ "PUT",
14
+ "PATCH",
15
+ "DELETE",
16
+ "HEAD"
17
+ ];
18
+
19
+ const getMethodKey = (method) => {
20
+ let methodKey = method.toUpperCase();
21
+ if (methodKey === "DEL") return "DELETE";
22
+ return methodKey;
23
+ };
24
+ const isMethodExport = (exportName, extraMethods) => {
25
+ const methods = [...METHOD_EXPORTS, ...extraMethods].map(getMethodKey);
26
+ return methods.includes(getMethodKey(exportName));
27
+ };
28
+
10
29
  function walkTree(directory, tree = []) {
11
30
  const results = [];
12
31
  for (const fileName of node_fs.readdirSync(directory)) {
@@ -56,17 +75,28 @@ async function generateRouter(routesDir, outputFile, options) {
56
75
  const exports = await generateRoutes(files);
57
76
  const importPaths = exports.map((x) => path.relative(path.dirname(outputFile), routesDir).concat(x.path));
58
77
  const content = buildRouter(exports, (r, e) => {
59
- return `${e}.route({ path: '${r.path.replace(/\/{0,1}index$/, "")}' })`;
78
+ const alias = routePathToAlias(r.path, e);
79
+ if (options?.enableOpenAPI ?? true) {
80
+ const orpcMeta = isORPCProcedure(r.exports[e]) ? r.exports[e]["~orpc"] : void 0;
81
+ const method = isMethodExport(e, options?.additionalMethods || []) ? getMethodKey(e) : orpcMeta?.route?.method || "POST";
82
+ const config = { path: r.path.replace(/\/{0,1}index$/, ""), method };
83
+ return `${alias}.route({ path: '${config.path}', method: '${config.method}' })`;
84
+ }
85
+ return alias;
60
86
  });
61
87
  let routerContent = `// This file is auto-generated
62
88
 
63
89
  `;
64
90
  const extension = options?.importExtension || "";
65
- routerContent += importPaths.map((x, i) => `import { ${Object.keys(exports[i].exports).join(", ")} } from "./${x}${extension}"`).join("\n");
91
+ routerContent += importPaths.map((x, i) => `import { ${Object.keys(exports[i].exports).map((y) => `${y} as ${routePathToAlias(exports[i].path, y)}`).join(", ")} } from "./${x}${extension}"`).join("\n");
66
92
  routerContent += "\n\nexport const router = ";
67
93
  routerContent += JSON.stringify(content, null, 2).replace(/"/g, "").replace(/(\s*)([a-zA-Z0-9]+-[a-zA-Z0-9-]+):/g, '$1"$2":');
68
94
  node_fs.writeFileSync(path.join(outputFile), routerContent);
69
95
  }
96
+ function routePathToAlias(routePath, methodName) {
97
+ const cleanPath = routePath.replace(/\{(\w+)\}/g, "$1").replace(/-/g, "_");
98
+ return cleanPath.split("/").filter(Boolean).join("_") + "__" + methodName;
99
+ }
70
100
  function buildRoutePath(parsedFile) {
71
101
  const directory = parsedFile.dir === parsedFile.root ? "" : parsedFile.dir;
72
102
  const name = `/${parsedFile.name}`;
package/dist/index.d.cts CHANGED
@@ -11,6 +11,15 @@ type GeneratorOptions = {
11
11
  * @default "" (no extension)
12
12
  */
13
13
  importExtension?: string;
14
+ /**
15
+ * When set to true, each route will be wrapped with OpenAPI .route({ path: '...', method: '...' }) call
16
+ * @default true
17
+ */
18
+ enableOpenAPI?: boolean;
19
+ /**
20
+ * Additional HTTP methods to recognize from export names.
21
+ */
22
+ additionalMethods?: string[];
14
23
  };
15
24
  declare function generateRouter(routesDir: string, outputFile: string, options?: GeneratorOptions): Promise<void>;
16
25
  type Router = Record<string, any>;
package/dist/index.d.mts CHANGED
@@ -11,6 +11,15 @@ type GeneratorOptions = {
11
11
  * @default "" (no extension)
12
12
  */
13
13
  importExtension?: string;
14
+ /**
15
+ * When set to true, each route will be wrapped with OpenAPI .route({ path: '...', method: '...' }) call
16
+ * @default true
17
+ */
18
+ enableOpenAPI?: boolean;
19
+ /**
20
+ * Additional HTTP methods to recognize from export names.
21
+ */
22
+ additionalMethods?: string[];
14
23
  };
15
24
  declare function generateRouter(routesDir: string, outputFile: string, options?: GeneratorOptions): Promise<void>;
16
25
  type Router = Record<string, any>;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,15 @@ type GeneratorOptions = {
11
11
  * @default "" (no extension)
12
12
  */
13
13
  importExtension?: string;
14
+ /**
15
+ * When set to true, each route will be wrapped with OpenAPI .route({ path: '...', method: '...' }) call
16
+ * @default true
17
+ */
18
+ enableOpenAPI?: boolean;
19
+ /**
20
+ * Additional HTTP methods to recognize from export names.
21
+ */
22
+ additionalMethods?: string[];
14
23
  };
15
24
  declare function generateRouter(routesDir: string, outputFile: string, options?: GeneratorOptions): Promise<void>;
16
25
  type Router = Record<string, any>;
package/dist/index.mjs CHANGED
@@ -1,6 +1,25 @@
1
1
  import { writeFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import path, { relative, dirname, join } from 'node:path';
3
3
 
4
+ const METHOD_EXPORTS = [
5
+ "GET",
6
+ "POST",
7
+ "PUT",
8
+ "PATCH",
9
+ "DELETE",
10
+ "HEAD"
11
+ ];
12
+
13
+ const getMethodKey = (method) => {
14
+ let methodKey = method.toUpperCase();
15
+ if (methodKey === "DEL") return "DELETE";
16
+ return methodKey;
17
+ };
18
+ const isMethodExport = (exportName, extraMethods) => {
19
+ const methods = [...METHOD_EXPORTS, ...extraMethods].map(getMethodKey);
20
+ return methods.includes(getMethodKey(exportName));
21
+ };
22
+
4
23
  function walkTree(directory, tree = []) {
5
24
  const results = [];
6
25
  for (const fileName of readdirSync(directory)) {
@@ -50,17 +69,28 @@ async function generateRouter(routesDir, outputFile, options) {
50
69
  const exports = await generateRoutes(files);
51
70
  const importPaths = exports.map((x) => relative(dirname(outputFile), routesDir).concat(x.path));
52
71
  const content = buildRouter(exports, (r, e) => {
53
- return `${e}.route({ path: '${r.path.replace(/\/{0,1}index$/, "")}' })`;
72
+ const alias = routePathToAlias(r.path, e);
73
+ if (options?.enableOpenAPI ?? true) {
74
+ const orpcMeta = isORPCProcedure(r.exports[e]) ? r.exports[e]["~orpc"] : void 0;
75
+ const method = isMethodExport(e, options?.additionalMethods || []) ? getMethodKey(e) : orpcMeta?.route?.method || "POST";
76
+ const config = { path: r.path.replace(/\/{0,1}index$/, ""), method };
77
+ return `${alias}.route({ path: '${config.path}', method: '${config.method}' })`;
78
+ }
79
+ return alias;
54
80
  });
55
81
  let routerContent = `// This file is auto-generated
56
82
 
57
83
  `;
58
84
  const extension = options?.importExtension || "";
59
- routerContent += importPaths.map((x, i) => `import { ${Object.keys(exports[i].exports).join(", ")} } from "./${x}${extension}"`).join("\n");
85
+ routerContent += importPaths.map((x, i) => `import { ${Object.keys(exports[i].exports).map((y) => `${y} as ${routePathToAlias(exports[i].path, y)}`).join(", ")} } from "./${x}${extension}"`).join("\n");
60
86
  routerContent += "\n\nexport const router = ";
61
87
  routerContent += JSON.stringify(content, null, 2).replace(/"/g, "").replace(/(\s*)([a-zA-Z0-9]+-[a-zA-Z0-9-]+):/g, '$1"$2":');
62
88
  writeFileSync(join(outputFile), routerContent);
63
89
  }
90
+ function routePathToAlias(routePath, methodName) {
91
+ const cleanPath = routePath.replace(/\{(\w+)\}/g, "$1").replace(/-/g, "_");
92
+ return cleanPath.split("/").filter(Boolean).join("_") + "__" + methodName;
93
+ }
64
94
  function buildRoutePath(parsedFile) {
65
95
  const directory = parsedFile.dir === parsedFile.root ? "" : parsedFile.dir;
66
96
  const name = `/${parsedFile.name}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orpc-file-based-router",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",