elysia-autoload 0.1.9 → 0.2.1

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,164 +1,164 @@
1
- # elysia-autoload
2
-
3
- Plugin for [Elysia](https://elysiajs.com/) which autoload all routes in directory and code-generate types for [Eden](https://elysiajs.com/eden/overview.html)
4
-
5
- **Currently, Eden types generation is broken!!**
6
-
7
- ## Installation
8
-
9
- ### Start new project with [create-elysiajs](https://github.com/kravetsone/create-elysiajs)
10
-
11
- ```bash
12
- bun create elysiajs <directory-name>
13
- ```
14
-
15
- and select `Autoload` in plugins
16
-
17
- ### Manual
18
-
19
- ```bash
20
- bun install elysia-autoload
21
- ```
22
-
23
- ## Usage
24
-
25
- ## Register the plugin
26
-
27
- ```ts
28
- import { Elysia } from "elysia";
29
- import { autoload } from "elysia-autoload";
30
-
31
- const app = new Elysia().use(autoload()).listen(3000);
32
-
33
- export type ElysiaApp = typeof app;
34
- ```
35
-
36
- ## Create route
37
-
38
- ```ts
39
- // routes/index.ts
40
- import type { ElysiaApp } from "app";
41
-
42
- export default (app: ElysiaApp) => app.get("/", { hello: "world" });
43
- ```
44
-
45
- ### Directory structure
46
-
47
- Guide how `elysia-autoload` match routes
48
-
49
- ```
50
- ├── app.ts
51
- ├── routes
52
- ├── index.ts // index routes
53
- ├── posts
54
- ├── index.ts
55
- └── [id].ts // dynamic params
56
- ├── likes
57
- └── [...].ts // wildcard
58
- ├── domains
59
- ├── @[...] // wildcard with @ prefix
60
- └──index.ts
61
- ├── frontend
62
- └──index.tsx // usage of tsx extension
63
- └── users.ts
64
- └── package.json
65
- ```
66
-
67
- - /routes/index.ts → /
68
- - /routes/posts/index.ts → /posts
69
- - /routes/posts/[id].ts → /posts/:id
70
- - /routes/users.ts → /users
71
- - /routes/likes/[...].ts → /likes/\*
72
- - /routes/domains/@[...]/index.ts → /domains/@\*
73
- - /routes/frontend/index.tsx → /frontend
74
-
75
- ## Options
76
-
77
- | Key | Type | Default | Description |
78
- | -------- | ------------------------------------------ | ---------------------------------- | ----------------------------------------------------------------------------------- |
79
- | pattern? | string | "\*\*\/\*.{ts,tsx,js,jsx,mjs,cjs}" | [Glob patterns](<https://en.wikipedia.org/wiki/Glob_(programming)>) |
80
- | dir? | string | "./routes" | The folder where routes are located |
81
- | prefix? | string | | Prefix for routes |
82
- | types? | boolean \| [Types Options](#types-options) | false | Options to configure type code-generation. if boolean - enables/disables generation |
83
- | schema? | Function | | Handler for providing routes guard schema |
84
-
85
- ### Types Options
86
-
87
- | Key | Type | Default | Description |
88
- | ---------- | ------------------ | ------------------- | --------------------------------------------------------------------------------------- |
89
- | output? | string \| string[] | "./routes-types.ts" | Type code-generation output. It can be an array |
90
- | typeName? | string | "Routes" | Name for code-generated global type for [Eden](https://elysiajs.com/eden/overview.html) |
91
- | useExport? | boolean | false | Use export instead of global type |
92
-
93
- ### Usage of types code-generation for [Eden](https://elysiajs.com/eden/overview.html)
94
-
95
- ```ts
96
- // app.ts
97
- import { Elysia } from "elysia";
98
- import { autoload } from "elysia-autoload";
99
-
100
- const app = new Elysia()
101
- .use(
102
- autoload({
103
- types: {
104
- output: "./routes.ts",
105
- typeName: "Routes",
106
- }, // or pass true for use default params
107
- })
108
- )
109
- .listen(3000);
110
-
111
- export type ElysiaApp = typeof app;
112
- ```
113
-
114
- ```ts
115
- // client.ts
116
-
117
- import { edenTreaty } from "@elysiajs/eden";
118
-
119
- // Routes are a global type so you don't need to import it.
120
-
121
- const app = edenTreaty<Routes>("http://localhost:3002");
122
-
123
- const { data } = await app.test["some-path-param"].get({
124
- $query: {
125
- key: 2,
126
- },
127
- });
128
-
129
- console.log(data);
130
- ```
131
-
132
- Example of app with types code-generation you can see in [example](https://github.com/kravetsone/elysia-autoload/tree/main/example)
133
-
134
- ### Usage of schema handler
135
-
136
- ```ts
137
- import swagger from "@elysiajs/swagger";
138
- import Elysia from "elysia";
139
- import { autoload } from "elysia-autoload";
140
-
141
- const app = new Elysia()
142
- .use(
143
- autoload({
144
- schema: ({ path, url }) => {
145
- const tag = url.split("/").at(1)!;
146
-
147
- return {
148
- beforeHandle: ({ request }) => {
149
- console.log(request.url);
150
- },
151
- detail: {
152
- description: `Route autoloaded from ${path}`,
153
- tags: [tag],
154
- },
155
- };
156
- },
157
- })
158
- )
159
- .use(swagger());
160
-
161
- export type ElysiaApp = typeof app;
162
-
163
- app.listen(3001, console.log);
164
- ```
1
+ # elysia-autoload
2
+
3
+ Plugin for [Elysia](https://elysiajs.com/) which autoload all routes in directory and code-generate types for [Eden](https://elysiajs.com/eden/overview.html)
4
+
5
+ **Currently, Eden types generation is broken!!**
6
+
7
+ ## Installation
8
+
9
+ ### Start new project with [create-elysiajs](https://github.com/kravetsone/create-elysiajs)
10
+
11
+ ```bash
12
+ bun create elysiajs <directory-name>
13
+ ```
14
+
15
+ and select `Autoload` in plugins
16
+
17
+ ### Manual
18
+
19
+ ```bash
20
+ bun install elysia-autoload
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ## Register the plugin
26
+
27
+ ```ts
28
+ import { Elysia } from "elysia";
29
+ import { autoload } from "elysia-autoload";
30
+
31
+ const app = new Elysia().use(autoload()).listen(3000);
32
+
33
+ export type ElysiaApp = typeof app;
34
+ ```
35
+
36
+ ## Create route
37
+
38
+ ```ts
39
+ // routes/index.ts
40
+ import type { ElysiaApp } from "app";
41
+
42
+ export default (app: ElysiaApp) => app.get("/", { hello: "world" });
43
+ ```
44
+
45
+ ### Directory structure
46
+
47
+ Guide how `elysia-autoload` match routes
48
+
49
+ ```
50
+ ├── app.ts
51
+ ├── routes
52
+ ├── index.ts // index routes
53
+ ├── posts
54
+ ├── index.ts
55
+ └── [id].ts // dynamic params
56
+ ├── likes
57
+ └── [...].ts // wildcard
58
+ ├── domains
59
+ ├── @[...] // wildcard with @ prefix
60
+ └──index.ts
61
+ ├── frontend
62
+ └──index.tsx // usage of tsx extension
63
+ └── users.ts
64
+ └── package.json
65
+ ```
66
+
67
+ - /routes/index.ts → /
68
+ - /routes/posts/index.ts → /posts
69
+ - /routes/posts/[id].ts → /posts/:id
70
+ - /routes/users.ts → /users
71
+ - /routes/likes/[...].ts → /likes/\*
72
+ - /routes/domains/@[...]/index.ts → /domains/@\*
73
+ - /routes/frontend/index.tsx → /frontend
74
+
75
+ ## Options
76
+
77
+ | Key | Type | Default | Description |
78
+ | -------- | ------------------------------------------ | ---------------------------------- | ----------------------------------------------------------------------------------- |
79
+ | pattern? | string | "\*\*\/\*.{ts,tsx,js,jsx,mjs,cjs}" | [Glob patterns](<https://en.wikipedia.org/wiki/Glob_(programming)>) |
80
+ | dir? | string | "./routes" | The folder where routes are located |
81
+ | prefix? | string | | Prefix for routes |
82
+ | types? | boolean \| [Types Options](#types-options) | false | Options to configure type code-generation. if boolean - enables/disables generation |
83
+ | schema? | Function | | Handler for providing routes guard schema |
84
+
85
+ ### Types Options
86
+
87
+ | Key | Type | Default | Description |
88
+ | ---------- | ------------------ | ------------------- | --------------------------------------------------------------------------------------- |
89
+ | output? | string \| string[] | "./routes-types.ts" | Type code-generation output. It can be an array |
90
+ | typeName? | string | "Routes" | Name for code-generated global type for [Eden](https://elysiajs.com/eden/overview.html) |
91
+ | useExport? | boolean | false | Use export instead of global type |
92
+
93
+ ### Usage of types code-generation for [Eden](https://elysiajs.com/eden/overview.html)
94
+
95
+ ```ts
96
+ // app.ts
97
+ import { Elysia } from "elysia";
98
+ import { autoload } from "elysia-autoload";
99
+
100
+ const app = new Elysia()
101
+ .use(
102
+ autoload({
103
+ types: {
104
+ output: "./routes.ts",
105
+ typeName: "Routes",
106
+ }, // or pass true for use default params
107
+ })
108
+ )
109
+ .listen(3000);
110
+
111
+ export type ElysiaApp = typeof app;
112
+ ```
113
+
114
+ ```ts
115
+ // client.ts
116
+
117
+ import { edenTreaty } from "@elysiajs/eden";
118
+
119
+ // Routes are a global type so you don't need to import it.
120
+
121
+ const app = edenTreaty<Routes>("http://localhost:3002");
122
+
123
+ const { data } = await app.test["some-path-param"].get({
124
+ $query: {
125
+ key: 2,
126
+ },
127
+ });
128
+
129
+ console.log(data);
130
+ ```
131
+
132
+ Example of app with types code-generation you can see in [example](https://github.com/kravetsone/elysia-autoload/tree/main/example)
133
+
134
+ ### Usage of schema handler
135
+
136
+ ```ts
137
+ import swagger from "@elysiajs/swagger";
138
+ import Elysia from "elysia";
139
+ import { autoload } from "elysia-autoload";
140
+
141
+ const app = new Elysia()
142
+ .use(
143
+ autoload({
144
+ schema: ({ path, url }) => {
145
+ const tag = url.split("/").at(1)!;
146
+
147
+ return {
148
+ beforeHandle: ({ request }) => {
149
+ console.log(request.url);
150
+ },
151
+ detail: {
152
+ description: `Route autoloaded from ${path}`,
153
+ tags: [tag],
154
+ },
155
+ };
156
+ },
157
+ })
158
+ )
159
+ .use(swagger());
160
+
161
+ export type ElysiaApp = typeof app;
162
+
163
+ app.listen(3001, console.log);
164
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ autoload: () => autoload
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+ var import_node_fs = require("fs");
27
+ var import_node_path2 = require("path");
28
+ var import_elysia = require("elysia");
29
+
30
+ // src/utils.ts
31
+ var import_node_path = require("path");
32
+ function getPath(dir) {
33
+ if ((0, import_node_path.isAbsolute)(dir))
34
+ return dir;
35
+ if ((0, import_node_path.isAbsolute)(process.argv[1]))
36
+ return (0, import_node_path.join)(process.argv[1], "..", dir);
37
+ return (0, import_node_path.join)(process.cwd(), process.argv[1], "..", dir);
38
+ }
39
+ function transformToUrl(path) {
40
+ const replacements = [
41
+ // Clean the url extensions
42
+ { regex: /\.(ts|tsx|js|jsx|mjs|cjs)$/u, replacement: "" },
43
+ // Fix windows slashes
44
+ { regex: /\\/gu, replacement: "/" },
45
+ // Handle wild card based routes - users/[...id]/profile.ts -> users/*/profile
46
+ { regex: /\[\.\.\..*\]/gu, replacement: "*" },
47
+ // Handle generic square bracket based routes - users/[id]/index.ts -> users/:id
48
+ {
49
+ regex: /\[(.*?)\]/gu,
50
+ replacement: (_, match) => `:${match}`
51
+ },
52
+ // Handle the case when multiple parameters are present in one file
53
+ // users / [id] - [name].ts to users /: id -:name and users / [id] - [name] / [age].ts to users /: id -: name /: age
54
+ { regex: /\]-\[/gu, replacement: "-:" },
55
+ { regex: /\]\//gu, replacement: "/" },
56
+ { regex: /\[/gu, replacement: "" },
57
+ { regex: /\]/gu, replacement: "" },
58
+ // remove index from end of path
59
+ { regex: /\/?index$/, replacement: "" }
60
+ ];
61
+ let url = path;
62
+ for (const { regex, replacement } of replacements) {
63
+ url = url.replace(regex, replacement);
64
+ }
65
+ return url;
66
+ }
67
+ function getParamsCount(path) {
68
+ return path.match(/\[(.*?)\]/gu)?.length || 0;
69
+ }
70
+ function sortByNestedParams(routes) {
71
+ return routes.sort((a, b) => getParamsCount(a) - getParamsCount(b));
72
+ }
73
+ function fixSlashes(prefix) {
74
+ if (!prefix?.endsWith("/"))
75
+ return prefix;
76
+ return prefix.slice(0, -1);
77
+ }
78
+
79
+ // src/index.ts
80
+ var TYPES_OUTPUT_DEFAULT = "./routes-types.ts";
81
+ var TYPES_TYPENAME_DEFAULT = "Routes";
82
+ function autoload(options = {}) {
83
+ return async () => {
84
+ const { pattern, dir, prefix, schema, types } = options;
85
+ const directoryPath = getPath(dir || "./routes");
86
+ if (!(0, import_node_fs.existsSync)(directoryPath))
87
+ throw new Error(`Directory ${directoryPath} doesn't exists`);
88
+ if (!(0, import_node_fs.statSync)(directoryPath).isDirectory())
89
+ throw new Error(`${directoryPath} isn't a directory`);
90
+ const plugin = new import_elysia.Elysia({
91
+ name: "elysia-autoload",
92
+ prefix: fixSlashes(prefix),
93
+ seed: {
94
+ pattern,
95
+ dir,
96
+ prefix,
97
+ types
98
+ }
99
+ });
100
+ const glob = new Bun.Glob(pattern || "**/*.{ts,tsx,js,jsx,mjs,cjs}");
101
+ const files = await Array.fromAsync(
102
+ glob.scan({
103
+ cwd: directoryPath
104
+ })
105
+ );
106
+ const paths = [];
107
+ for await (const path of sortByNestedParams(files)) {
108
+ const fullPath = (0, import_node_path2.join)(directoryPath, path);
109
+ const file = await import(fullPath);
110
+ if (!file.default)
111
+ throw new Error(`${path} doesn't provide default export`);
112
+ const url = transformToUrl(path);
113
+ const groupOptions = schema ? schema({ path, url }) : {};
114
+ plugin.group(url, groupOptions, file.default);
115
+ if (types)
116
+ paths.push(fullPath.replace(directoryPath, ""));
117
+ }
118
+ if (types) {
119
+ const imports = paths.map(
120
+ (x, index) => `import type Route${index} from "${(directoryPath + x.replace(".ts", "").replace(".tsx", "")).replace(/\\/gu, "/")}";`
121
+ );
122
+ for await (const outputPath of types === true || !types.output ? [TYPES_OUTPUT_DEFAULT] : Array.isArray(types.output) ? types.output : [types.output]) {
123
+ await Bun.write(
124
+ getPath(outputPath),
125
+ [
126
+ `import type { ElysiaWithBaseUrl } from "elysia-autoload";`,
127
+ imports.join("\n"),
128
+ "",
129
+ types === true || !types.useExport ? "declare global {" : "",
130
+ ` export type ${types === true || !types.typeName ? TYPES_TYPENAME_DEFAULT : types.typeName} = ${paths.map(
131
+ (x, index) => `ElysiaWithBaseUrl<"${((prefix?.endsWith("/") ? prefix.slice(0, -1) : prefix) ?? "") + transformToUrl(x) || "/"}", ReturnType<typeof Route${index}>>`
132
+ ).join("\n & ")}`,
133
+ types === true || !types.useExport ? "}" : ""
134
+ ].join("\n")
135
+ );
136
+ }
137
+ }
138
+ return plugin;
139
+ };
140
+ }
141
+ // Annotate the CommonJS export names for ESM import in node:
142
+ 0 && (module.exports = {
143
+ autoload
144
+ });
@@ -0,0 +1,47 @@
1
+ import Elysia, { RouteBase, Elysia as Elysia$1, LocalHook, InputSchema, RouteSchema, SingletonBase } from 'elysia';
2
+ import { BaseMacro } from 'elysia/dist/types';
3
+
4
+ type RemoveLastChar<T extends string> = T extends `${infer V}/` ? V : T;
5
+ type RoutesWithPrefix<Routes extends RouteBase, Prefix extends string> = {
6
+ [K in keyof Routes as `${Prefix}${RemoveLastChar<K & string>}`]: Routes[K];
7
+ };
8
+ type ElysiaWithBaseUrl<BaseUrl extends string, ElysiaType extends Elysia<any, any, any, any, any, any, any, any>> = ElysiaType extends Elysia<infer BasePath, infer Scoped, infer Singleton, infer Definitions, infer Metadata, infer Routes, infer Ephemeral, infer Volatile> ? Elysia<BasePath, Scoped, Singleton, Definitions, Metadata, RoutesWithPrefix<Routes, BaseUrl>, Ephemeral, Volatile> : never;
9
+
10
+ type TSchemaHandler = ({ path, url, }: {
11
+ path: string;
12
+ url: string;
13
+ }) => LocalHook<InputSchema, RouteSchema, SingletonBase, Record<string, Error>, BaseMacro, "">;
14
+ interface ITypesOptions {
15
+ output?: string | string[];
16
+ typeName?: string;
17
+ useExport?: boolean;
18
+ }
19
+ interface IAutoloadOptions {
20
+ pattern?: string;
21
+ dir?: string;
22
+ prefix?: string;
23
+ schema?: TSchemaHandler;
24
+ types?: ITypesOptions | true;
25
+ }
26
+ declare function autoload(options?: IAutoloadOptions): () => Promise<Elysia$1<string, false, {
27
+ decorator: {};
28
+ store: {};
29
+ derive: {};
30
+ resolve: {};
31
+ }, {
32
+ type: {};
33
+ error: {};
34
+ }, {
35
+ schema: {};
36
+ macro: {};
37
+ }, {}, {
38
+ derive: {};
39
+ resolve: {};
40
+ schema: {};
41
+ }, {
42
+ derive: {};
43
+ resolve: {};
44
+ schema: {};
45
+ }>>;
46
+
47
+ export { type ElysiaWithBaseUrl, type IAutoloadOptions, type ITypesOptions, autoload };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,29 @@
1
- import { Elysia } from "elysia";
1
+ import Elysia, { RouteBase, Elysia as Elysia$1, LocalHook, InputSchema, RouteSchema, SingletonBase } from 'elysia';
2
+ import { BaseMacro } from 'elysia/dist/types';
3
+
4
+ type RemoveLastChar<T extends string> = T extends `${infer V}/` ? V : T;
5
+ type RoutesWithPrefix<Routes extends RouteBase, Prefix extends string> = {
6
+ [K in keyof Routes as `${Prefix}${RemoveLastChar<K & string>}`]: Routes[K];
7
+ };
8
+ type ElysiaWithBaseUrl<BaseUrl extends string, ElysiaType extends Elysia<any, any, any, any, any, any, any, any>> = ElysiaType extends Elysia<infer BasePath, infer Scoped, infer Singleton, infer Definitions, infer Metadata, infer Routes, infer Ephemeral, infer Volatile> ? Elysia<BasePath, Scoped, Singleton, Definitions, Metadata, RoutesWithPrefix<Routes, BaseUrl>, Ephemeral, Volatile> : never;
9
+
2
10
  type TSchemaHandler = ({ path, url, }: {
3
11
  path: string;
4
12
  url: string;
5
- }) => Parameters<InstanceType<typeof Elysia>["group"]>[1];
6
- export interface ITypesOptions {
13
+ }) => LocalHook<InputSchema, RouteSchema, SingletonBase, Record<string, Error>, BaseMacro, "">;
14
+ interface ITypesOptions {
7
15
  output?: string | string[];
8
16
  typeName?: string;
9
17
  useExport?: boolean;
10
18
  }
11
- export interface IAutoloadOptions {
19
+ interface IAutoloadOptions {
12
20
  pattern?: string;
13
21
  dir?: string;
14
22
  prefix?: string;
15
23
  schema?: TSchemaHandler;
16
24
  types?: ITypesOptions | true;
17
25
  }
18
- export declare function autoload(options?: IAutoloadOptions): () => Promise<Elysia<string, false, {
26
+ declare function autoload(options?: IAutoloadOptions): () => Promise<Elysia$1<string, false, {
19
27
  decorator: {};
20
28
  store: {};
21
29
  derive: {};
@@ -35,4 +43,5 @@ export declare function autoload(options?: IAutoloadOptions): () => Promise<Elys
35
43
  resolve: {};
36
44
  schema: {};
37
45
  }>>;
38
- export * from "./types";
46
+
47
+ export { type ElysiaWithBaseUrl, type IAutoloadOptions, type ITypesOptions, autoload };
package/dist/index.js CHANGED
@@ -1,84 +1,121 @@
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
- exports.autoload = void 0;
18
- const node_fs_1 = require("node:fs");
19
- const node_path_1 = require("node:path");
20
- const elysia_1 = require("elysia");
21
- const utils_1 = require("./utils");
22
- const TYPES_OUTPUT_DEFAULT = "./routes-types.ts";
23
- const TYPES_TYPENAME_DEFAULT = "Routes";
1
+ // src/index.ts
2
+ import { existsSync, statSync } from "node:fs";
3
+ import { join as join2 } from "node:path";
4
+ import {
5
+ Elysia
6
+ } from "elysia";
7
+
8
+ // src/utils.ts
9
+ import { isAbsolute, join } from "node:path";
10
+ function getPath(dir) {
11
+ if (isAbsolute(dir))
12
+ return dir;
13
+ if (isAbsolute(process.argv[1]))
14
+ return join(process.argv[1], "..", dir);
15
+ return join(process.cwd(), process.argv[1], "..", dir);
16
+ }
17
+ function transformToUrl(path) {
18
+ const replacements = [
19
+ // Clean the url extensions
20
+ { regex: /\.(ts|tsx|js|jsx|mjs|cjs)$/u, replacement: "" },
21
+ // Fix windows slashes
22
+ { regex: /\\/gu, replacement: "/" },
23
+ // Handle wild card based routes - users/[...id]/profile.ts -> users/*/profile
24
+ { regex: /\[\.\.\..*\]/gu, replacement: "*" },
25
+ // Handle generic square bracket based routes - users/[id]/index.ts -> users/:id
26
+ {
27
+ regex: /\[(.*?)\]/gu,
28
+ replacement: (_, match) => `:${match}`
29
+ },
30
+ // Handle the case when multiple parameters are present in one file
31
+ // users / [id] - [name].ts to users /: id -:name and users / [id] - [name] / [age].ts to users /: id -: name /: age
32
+ { regex: /\]-\[/gu, replacement: "-:" },
33
+ { regex: /\]\//gu, replacement: "/" },
34
+ { regex: /\[/gu, replacement: "" },
35
+ { regex: /\]/gu, replacement: "" },
36
+ // remove index from end of path
37
+ { regex: /\/?index$/, replacement: "" }
38
+ ];
39
+ let url = path;
40
+ for (const { regex, replacement } of replacements) {
41
+ url = url.replace(regex, replacement);
42
+ }
43
+ return url;
44
+ }
45
+ function getParamsCount(path) {
46
+ return path.match(/\[(.*?)\]/gu)?.length || 0;
47
+ }
48
+ function sortByNestedParams(routes) {
49
+ return routes.sort((a, b) => getParamsCount(a) - getParamsCount(b));
50
+ }
51
+ function fixSlashes(prefix) {
52
+ if (!prefix?.endsWith("/"))
53
+ return prefix;
54
+ return prefix.slice(0, -1);
55
+ }
56
+
57
+ // src/index.ts
58
+ var TYPES_OUTPUT_DEFAULT = "./routes-types.ts";
59
+ var TYPES_TYPENAME_DEFAULT = "Routes";
24
60
  function autoload(options = {}) {
25
- return async () => {
26
- const { pattern, dir, prefix, schema, types } = options;
27
- const directoryPath = (0, utils_1.getPath)(dir || "./routes");
28
- if (!(0, node_fs_1.existsSync)(directoryPath))
29
- throw new Error(`Directory ${directoryPath} doesn't exists`);
30
- if (!(0, node_fs_1.statSync)(directoryPath).isDirectory())
31
- throw new Error(`${directoryPath} isn't a directory`);
32
- const plugin = new elysia_1.Elysia({
33
- name: "elysia-autoload",
34
- prefix: (0, utils_1.fixSlashes)(prefix),
35
- seed: {
36
- pattern,
37
- dir,
38
- prefix,
39
- types,
40
- },
41
- });
42
- const glob = new Bun.Glob(pattern || "**/*.{ts,tsx,js,jsx,mjs,cjs}");
43
- const files = await Array.fromAsync(glob.scan({
44
- cwd: directoryPath,
45
- }));
46
- const paths = [];
47
- for await (const path of (0, utils_1.sortByNestedParams)(files)) {
48
- const fullPath = (0, node_path_1.join)(directoryPath, path);
49
- const file = await Promise.resolve(`${fullPath}`).then(s => require(s));
50
- if (!file.default)
51
- throw new Error(`${path} doesn't provide default export`);
52
- const url = (0, utils_1.transformToUrl)(path);
53
- const groupOptions = schema ? schema({ path, url }) : {};
54
- plugin.group(url, groupOptions, file.default);
55
- if (types)
56
- paths.push(fullPath.replace(directoryPath, ""));
57
- }
58
- if (types) {
59
- const imports = paths.map((x, index) => `import type Route${index} from "${(directoryPath + x.replace(".ts", "").replace(".tsx", "")).replace(/\\/gu, "/")}";`);
60
- for await (const outputPath of types === true || !types.output
61
- ? [TYPES_OUTPUT_DEFAULT]
62
- : Array.isArray(types.output)
63
- ? types.output
64
- : [types.output]) {
65
- await Bun.write((0, utils_1.getPath)(outputPath), [
66
- `import type { ElysiaWithBaseUrl } from "elysia-autoload";`,
67
- imports.join("\n"),
68
- "",
69
- types === true || !types.useExport ? "declare global {" : "",
70
- ` export type ${types === true || !types.typeName
71
- ? TYPES_TYPENAME_DEFAULT
72
- : types.typeName} = ${paths
73
- .map((x, index) => `ElysiaWithBaseUrl<"${((prefix?.endsWith("/") ? prefix.slice(0, -1) : prefix) ??
74
- "") + (0, utils_1.transformToUrl)(x) || "/"}", ReturnType<typeof Route${index}>>`)
75
- .join("\n & ")}`,
76
- types === true || !types.useExport ? "}" : "",
77
- ].join("\n"));
78
- }
79
- }
80
- return plugin;
81
- };
61
+ return async () => {
62
+ const { pattern, dir, prefix, schema, types } = options;
63
+ const directoryPath = getPath(dir || "./routes");
64
+ if (!existsSync(directoryPath))
65
+ throw new Error(`Directory ${directoryPath} doesn't exists`);
66
+ if (!statSync(directoryPath).isDirectory())
67
+ throw new Error(`${directoryPath} isn't a directory`);
68
+ const plugin = new Elysia({
69
+ name: "elysia-autoload",
70
+ prefix: fixSlashes(prefix),
71
+ seed: {
72
+ pattern,
73
+ dir,
74
+ prefix,
75
+ types
76
+ }
77
+ });
78
+ const glob = new Bun.Glob(pattern || "**/*.{ts,tsx,js,jsx,mjs,cjs}");
79
+ const files = await Array.fromAsync(
80
+ glob.scan({
81
+ cwd: directoryPath
82
+ })
83
+ );
84
+ const paths = [];
85
+ for await (const path of sortByNestedParams(files)) {
86
+ const fullPath = join2(directoryPath, path);
87
+ const file = await import(fullPath);
88
+ if (!file.default)
89
+ throw new Error(`${path} doesn't provide default export`);
90
+ const url = transformToUrl(path);
91
+ const groupOptions = schema ? schema({ path, url }) : {};
92
+ plugin.group(url, groupOptions, file.default);
93
+ if (types)
94
+ paths.push(fullPath.replace(directoryPath, ""));
95
+ }
96
+ if (types) {
97
+ const imports = paths.map(
98
+ (x, index) => `import type Route${index} from "${(directoryPath + x.replace(".ts", "").replace(".tsx", "")).replace(/\\/gu, "/")}";`
99
+ );
100
+ for await (const outputPath of types === true || !types.output ? [TYPES_OUTPUT_DEFAULT] : Array.isArray(types.output) ? types.output : [types.output]) {
101
+ await Bun.write(
102
+ getPath(outputPath),
103
+ [
104
+ `import type { ElysiaWithBaseUrl } from "elysia-autoload";`,
105
+ imports.join("\n"),
106
+ "",
107
+ types === true || !types.useExport ? "declare global {" : "",
108
+ ` export type ${types === true || !types.typeName ? TYPES_TYPENAME_DEFAULT : types.typeName} = ${paths.map(
109
+ (x, index) => `ElysiaWithBaseUrl<"${((prefix?.endsWith("/") ? prefix.slice(0, -1) : prefix) ?? "") + transformToUrl(x) || "/"}", ReturnType<typeof Route${index}>>`
110
+ ).join("\n & ")}`,
111
+ types === true || !types.useExport ? "}" : ""
112
+ ].join("\n")
113
+ );
114
+ }
115
+ }
116
+ return plugin;
117
+ };
82
118
  }
83
- exports.autoload = autoload;
84
- __exportStar(require("./types"), exports);
119
+ export {
120
+ autoload
121
+ };
package/package.json CHANGED
@@ -1,43 +1,54 @@
1
- {
2
- "name": "elysia-autoload",
3
- "version": "0.1.9",
4
- "author": "kravetsone",
5
- "type": "commonjs",
6
- "main": "dist/index.js",
7
- "description": "Plugin for Elysia which autoload all routes in directory and code-generate types for Eden",
8
- "homepage": "https://github.com/kravetsone/elysia-autoload",
9
- "keywords": [
10
- "bun",
11
- "elysia",
12
- "autoimports",
13
- "autoload",
14
- "nextjs",
15
- "filerouter",
16
- "autoroutes",
17
- "eden",
18
- "treaty",
19
- "trpc",
20
- "codegeneration"
21
- ],
22
- "scripts": {
23
- "prepublishOnly": "bun test && rm -rf dist && tsc",
24
- "lint": "bunx @biomejs/biome check src",
25
- "lint:fix": "bun lint --apply",
26
- "prepare": "husky"
27
- },
28
- "files": [
29
- "dist"
30
- ],
31
- "devDependencies": {
32
- "@biomejs/biome": "1.6.1",
33
- "@elysiajs/eden": "^1.0.4",
34
- "@elysiajs/swagger": "^1.0.2",
35
- "@types/bun": "^1.0.8",
36
- "elysia": "^1.0.5",
37
- "typescript": "^5.4.2",
38
- "husky": "^9.0.11"
39
- },
40
- "peerDependencies": {
41
- "elysia": "^1.0.0"
42
- }
43
- }
1
+ {
2
+ "name": "elysia-autoload",
3
+ "version": "0.2.1",
4
+ "author": "kravetsone",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.mjs"
14
+ }
15
+ },
16
+ "description": "Plugin for Elysia which autoload all routes in directory and code-generate types for Eden",
17
+ "homepage": "https://github.com/kravetsone/elysia-autoload",
18
+ "keywords": [
19
+ "bun",
20
+ "elysia",
21
+ "autoimports",
22
+ "autoload",
23
+ "nextjs",
24
+ "filerouter",
25
+ "autoroutes",
26
+ "eden",
27
+ "treaty",
28
+ "trpc",
29
+ "codegeneration"
30
+ ],
31
+ "scripts": {
32
+ "prepublishOnly": "bun test && bunx tsup && rm -f ./dist/index.d.mts",
33
+ "lint": "bunx @biomejs/biome check src",
34
+ "lint:fix": "bun lint --apply",
35
+ "prepare": "husky"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "devDependencies": {
41
+ "@biomejs/biome": "1.6.3",
42
+ "@elysiajs/eden": "^1.0.7",
43
+ "@elysiajs/swagger": "^1.0.3",
44
+ "@microsoft/api-extractor": "^7.43.0",
45
+ "@types/bun": "^1.0.11",
46
+ "elysia": "^1.0.9",
47
+ "husky": "^9.0.11",
48
+ "tsup": "^8.0.2",
49
+ "typescript": "^5.4.3"
50
+ },
51
+ "peerDependencies": {
52
+ "elysia": "^1.0.0"
53
+ }
54
+ }
package/dist/types.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import type Elysia from "elysia";
2
- import type { RouteBase } from "elysia";
3
- type RemoveLastChar<T extends string> = T extends `${infer V}/` ? V : T;
4
- type RoutesWithPrefix<Routes extends RouteBase, Prefix extends string> = {
5
- [K in keyof Routes as `${Prefix}${RemoveLastChar<K & string>}`]: Routes[K];
6
- };
7
- export type ElysiaWithBaseUrl<BaseUrl extends string, ElysiaType extends Elysia<any, any, any, any, any, any, any, any>> = ElysiaType extends Elysia<infer BasePath, infer Scoped, infer Singleton, infer Definitions, infer Metadata, infer Routes, infer Ephemeral, infer Volatile> ? Elysia<BasePath, Scoped, Singleton, Definitions, Metadata, RoutesWithPrefix<Routes, BaseUrl>, Ephemeral, Volatile> : never;
8
- export {};
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export declare function getPath(dir: string): string;
2
- export declare function transformToUrl(path: string): string;
3
- export declare function sortByNestedParams(routes: string[]): string[];
4
- export declare function fixSlashes(prefix?: string): string | undefined;
package/dist/utils.js DELETED
@@ -1,57 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fixSlashes = exports.sortByNestedParams = exports.transformToUrl = exports.getPath = void 0;
4
- const node_path_1 = require("node:path");
5
- function getPath(dir) {
6
- if ((0, node_path_1.isAbsolute)(dir))
7
- return dir;
8
- if ((0, node_path_1.isAbsolute)(process.argv[1]))
9
- return (0, node_path_1.join)(process.argv[1], "..", dir);
10
- return (0, node_path_1.join)(process.cwd(), process.argv[1], "..", dir);
11
- }
12
- exports.getPath = getPath;
13
- // Inspired by https://github.com/wobsoriano/elysia-autoroutes/blob/main/src/utils/transformPathToUrl.ts#L4C31-L4C31
14
- function transformToUrl(path) {
15
- const replacements = [
16
- // Clean the url extensions
17
- { regex: /\.(ts|tsx|js|jsx|mjs|cjs)$/u, replacement: "" },
18
- // Fix windows slashes
19
- { regex: /\\/gu, replacement: "/" },
20
- // Handle wild card based routes - users/[...id]/profile.ts -> users/*/profile
21
- { regex: /\[\.\.\..*\]/gu, replacement: "*" },
22
- // Handle generic square bracket based routes - users/[id]/index.ts -> users/:id
23
- {
24
- regex: /\[(.*?)\]/gu,
25
- replacement: (_, match) => `:${match}`,
26
- },
27
- // Handle the case when multiple parameters are present in one file
28
- // users / [id] - [name].ts to users /: id -:name and users / [id] - [name] / [age].ts to users /: id -: name /: age
29
- { regex: /\]-\[/gu, replacement: "-:" },
30
- { regex: /\]\//gu, replacement: "/" },
31
- { regex: /\[/gu, replacement: "" },
32
- { regex: /\]/gu, replacement: "" },
33
- // remove index from end of path
34
- { regex: /\/?index$/, replacement: "" },
35
- ];
36
- let url = path;
37
- for (const { regex, replacement } of replacements) {
38
- url = url.replace(regex, replacement);
39
- }
40
- return url;
41
- }
42
- exports.transformToUrl = transformToUrl;
43
- function getParamsCount(path) {
44
- return path.match(/\[(.*?)\]/gu)?.length || 0;
45
- }
46
- // Is it necessary?..
47
- // Sorts by the smallest parameters
48
- function sortByNestedParams(routes) {
49
- return routes.sort((a, b) => getParamsCount(a) - getParamsCount(b));
50
- }
51
- exports.sortByNestedParams = sortByNestedParams;
52
- function fixSlashes(prefix) {
53
- if (!prefix?.endsWith("/"))
54
- return prefix;
55
- return prefix.slice(0, -1);
56
- }
57
- exports.fixSlashes = fixSlashes;