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 +34 -23
- package/dist/index.cjs +32 -2
- package/dist/index.d.cts +9 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.mjs +32 -2
- package/package.json +1 -1
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
|
-
|
|
109
|
-
import {
|
|
110
|
-
import {
|
|
111
|
-
import {
|
|
112
|
-
import {
|
|
113
|
-
import {
|
|
114
|
-
import {
|
|
115
|
-
import {
|
|
116
|
-
import {
|
|
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:
|
|
121
|
-
signin:
|
|
122
|
-
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:
|
|
126
|
-
|
|
127
|
-
list:
|
|
128
|
-
find:
|
|
129
|
-
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:
|
|
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
|
-
|
|
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
|
-
|
|
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