serverstruct 0.2.0 → 0.4.0
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 +124 -133
- package/dist/index.cjs +82 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.mts +73 -39
- package/dist/index.mjs +80 -67
- package/package.json +29 -26
- package/dist/index.d.ts +0 -41
- package/dist/index.js +0 -93
package/README.md
CHANGED
|
@@ -1,194 +1,185 @@
|
|
|
1
1
|
# Serverstruct
|
|
2
2
|
|
|
3
|
-
⚡️ Typesafe and modular servers with [
|
|
3
|
+
⚡️ Typesafe and modular servers with [H3](https://github.com/unjs/h3).
|
|
4
4
|
|
|
5
|
-
Serverstruct
|
|
6
|
-
|
|
7
|
-
It provides structure to your Hono application without any limitations. It also supports dependency injection using [Hollywood DI](https://github.com/eriicafes/hollywood-di).
|
|
5
|
+
Serverstruct provides simple helpers for building modular h3 applications with dependency injection using [getbox](https://github.com/eriicafes/getbox).
|
|
8
6
|
|
|
9
7
|
## Installation
|
|
10
8
|
|
|
11
|
-
Serverstruct requires you to install hono.
|
|
12
|
-
|
|
13
9
|
```sh
|
|
14
|
-
npm i serverstruct
|
|
10
|
+
npm i serverstruct h3 getbox
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
npm i serverstruct hono hollywood-di
|
|
21
|
-
```
|
|
13
|
+
## Quick Start
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
```typescript
|
|
16
|
+
import { application } from "serverstruct";
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
const app = application((app) => {
|
|
19
|
+
app.get("/", () => "Hello world!");
|
|
20
|
+
});
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
app.serve({ port: 3000 });
|
|
23
|
+
```
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
.route((app) => {
|
|
32
|
-
return app.get("/", (c) => c.text("Hello world!"));
|
|
33
|
-
})
|
|
34
|
-
.app();
|
|
25
|
+
## Composing Apps
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
```
|
|
27
|
+
Create modular h3 apps and mount them together:
|
|
38
28
|
|
|
39
|
-
|
|
29
|
+
```typescript
|
|
30
|
+
import { application } from "serverstruct";
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
// Create a users module
|
|
33
|
+
const { app: usersApp } = application((app) => {
|
|
34
|
+
const users: User[] = [];
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
app.get("/", () => users);
|
|
37
|
+
app.post("/", async (event) => {
|
|
38
|
+
const body = await readValidatedBody(event, validateUser);
|
|
39
|
+
users.push(body);
|
|
40
|
+
return body;
|
|
41
|
+
});
|
|
47
42
|
});
|
|
48
43
|
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
44
|
+
// Compose in main app
|
|
45
|
+
const app = application((app) => {
|
|
46
|
+
app.get("/ping", () => "pong");
|
|
47
|
+
app.mount("/users", usersApp);
|
|
52
48
|
});
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
.subroutes({ users, posts })
|
|
56
|
-
.route((app, container, routes) => {
|
|
57
|
-
return app
|
|
58
|
-
.get("/", (c) => c.text("Hello world!"))
|
|
59
|
-
.route("/users", routes.users)
|
|
60
|
-
.route("/posts", routes.posts);
|
|
61
|
-
})
|
|
62
|
-
.app();
|
|
63
|
-
|
|
64
|
-
export default app;
|
|
50
|
+
app.serve({ port: 3000 });
|
|
65
51
|
```
|
|
66
52
|
|
|
67
|
-
|
|
53
|
+
You can also create and return a new H3 instance to customize H3 options:
|
|
68
54
|
|
|
69
|
-
|
|
55
|
+
```typescript
|
|
56
|
+
import { H3 } from "h3";
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
58
|
+
const app = application(() => {
|
|
59
|
+
const customApp = new H3({
|
|
60
|
+
onError: (error) => {
|
|
61
|
+
console.error(error);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
customApp.get("/", () => "Hello from custom app!");
|
|
65
|
+
return customApp;
|
|
74
66
|
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Controllers with Dependency Injection
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
Use `controller()` to create reusable modules with shared dependencies. It integrates with [getbox](https://github.com/eriicafes/getbox) for dependency injection:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { application, controller } from "serverstruct";
|
|
75
|
+
|
|
76
|
+
// Define a controller
|
|
77
|
+
const usersController = controller((app) => {
|
|
78
|
+
const users: User[] = [];
|
|
79
|
+
|
|
80
|
+
app.get("/", () => users);
|
|
81
|
+
app.post("/", async (event) => {
|
|
82
|
+
const body = await readValidatedBody(event, validateUser);
|
|
83
|
+
users.push(body);
|
|
84
|
+
return body;
|
|
85
|
+
});
|
|
78
86
|
});
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
// Use it in your main app
|
|
89
|
+
const app = application((app, box) => {
|
|
90
|
+
app.get("/ping", () => "pong");
|
|
91
|
+
app.mount("/users", box.new(usersController));
|
|
82
92
|
});
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
.subroutes({ auth, users, posts })
|
|
86
|
-
.route((app, container, routes) => {
|
|
87
|
-
return app
|
|
88
|
-
.route("/auth", routes.auth)
|
|
89
|
-
.route("/users", routes.users)
|
|
90
|
-
.route("/posts", routes.posts);
|
|
91
|
-
})
|
|
92
|
-
.app();
|
|
94
|
+
app.serve({ port: 3000 });
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
The `box` parameter is a getbox `Box` instance that manages dependency instances.
|
|
96
98
|
|
|
97
|
-
##
|
|
99
|
+
## Sharing Dependencies
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
Controllers can share dependencies using the `box` parameter.
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
Use `box.get(Class)` to retrieve or create a singleton instance:
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
```typescript
|
|
106
|
+
import { application, controller } from "serverstruct";
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
// A shared service
|
|
109
|
+
class Database {
|
|
110
|
+
users: User[] = [];
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
interface Counter {
|
|
111
|
-
count: number;
|
|
112
|
-
increment(): void;
|
|
112
|
+
getUsers() { return this.users; }
|
|
113
|
+
addUser(user: User) { this.users.push(user); }
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
// Controller uses box to access the database
|
|
117
|
+
const usersController = controller((app, box) => {
|
|
118
|
+
const db = box.get(Database);
|
|
119
|
+
|
|
120
|
+
app.get("/", () => db.getUsers());
|
|
121
|
+
app.post("/", async (event) => {
|
|
122
|
+
const body = await readValidatedBody(event, validateUser);
|
|
123
|
+
db.addUser(body);
|
|
124
|
+
return body;
|
|
122
125
|
});
|
|
126
|
+
});
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.count++;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
128
|
+
// Another controller can access the same database
|
|
129
|
+
const statsController = controller((app, box) => {
|
|
130
|
+
const db = box.get(Database);
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.provide({ counter: LinearCounter }) // the main app provides the dependency
|
|
134
|
-
.subroutes({ countRoute: route })
|
|
135
|
-
.route((app, _, routes) => {
|
|
136
|
-
return app.route("/count", routes.countRoute);
|
|
137
|
-
})
|
|
138
|
-
.app();
|
|
139
|
-
|
|
140
|
-
// or as the main app
|
|
141
|
-
const container = Hollywood.create({ counter: LinearCounter }); // the container provides the dependency
|
|
142
|
-
const app = route.app(container);
|
|
143
|
-
```
|
|
132
|
+
app.get("/count", () => ({ count: db.getUsers().length }));
|
|
133
|
+
});
|
|
144
134
|
|
|
145
|
-
|
|
135
|
+
const app = application((app, box) => {
|
|
136
|
+
app.mount("/users", box.new(usersController));
|
|
137
|
+
app.mount("/stats", box.new(statsController));
|
|
138
|
+
});
|
|
146
139
|
|
|
147
|
-
|
|
140
|
+
await app.serve({ port: 3000 });
|
|
141
|
+
```
|
|
148
142
|
|
|
149
|
-
|
|
143
|
+
`box.get(Class)` creates the instance on first call, then caches it.
|
|
150
144
|
|
|
151
|
-
|
|
152
|
-
import { defineInit } from "hollywood-di";
|
|
153
|
-
import { createRoute } from "serverstruct";
|
|
145
|
+
## Middleware
|
|
154
146
|
|
|
155
|
-
|
|
156
|
-
class Bar {
|
|
157
|
-
public static init = defineInit(Bar).args("foo");
|
|
147
|
+
Use h3's native middleware with `app.use()`:
|
|
158
148
|
|
|
159
|
-
|
|
160
|
-
|
|
149
|
+
```typescript
|
|
150
|
+
const app = application((app) => {
|
|
151
|
+
// Global middleware
|
|
152
|
+
app.use(() => console.log("Request received"));
|
|
161
153
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
foo: Foo,
|
|
165
|
-
bar: Bar,
|
|
166
|
-
})
|
|
167
|
-
.route((app, container) => {
|
|
168
|
-
return app;
|
|
169
|
-
});
|
|
154
|
+
app.get("/", () => "Hello world!");
|
|
155
|
+
});
|
|
170
156
|
```
|
|
171
157
|
|
|
172
|
-
|
|
158
|
+
Controllers can have their own middleware:
|
|
173
159
|
|
|
174
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
const usersController = controller((app) => {
|
|
162
|
+
// Runs only for routes in this controller
|
|
163
|
+
app.use(() => console.log("Users route accessed"));
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
export const postsRoute = createRoute().route((app) => {
|
|
179
|
-
return app.get("/", (c) => {
|
|
180
|
-
return c.text("posts");
|
|
181
|
-
});
|
|
165
|
+
app.get("/", () => [...]);
|
|
166
|
+
app.post("/", async () => {...});
|
|
182
167
|
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Custom Box Instance
|
|
171
|
+
|
|
172
|
+
Pass your own Box instance to `application()` for more control:
|
|
183
173
|
|
|
184
|
-
|
|
185
|
-
import {
|
|
186
|
-
import { postsRoute } from "./routes/posts";
|
|
174
|
+
```typescript
|
|
175
|
+
import { Box } from "getbox";
|
|
187
176
|
|
|
188
|
-
const
|
|
177
|
+
const box = new Box();
|
|
189
178
|
|
|
190
|
-
|
|
191
|
-
|
|
179
|
+
// Pre-populate with dependencies
|
|
180
|
+
Box.mock(box, Database, new Database());
|
|
192
181
|
|
|
193
|
-
|
|
182
|
+
const app = application((app, box) => {
|
|
183
|
+
app.mount("/users", box.new(usersController));
|
|
184
|
+
}, box);
|
|
194
185
|
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
let h3 = require("h3");
|
|
2
|
+
let getbox = require("getbox");
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Creates an h3 application with dependency injection support.
|
|
7
|
+
*
|
|
8
|
+
* @param fn - Function that configures the app. Receives a fresh H3 instance
|
|
9
|
+
* and Box instance. Can add routes to the provided app, or create
|
|
10
|
+
* and return a new H3 instance.
|
|
11
|
+
* @param box - Optional Box instance. If not provided, creates a new one.
|
|
12
|
+
* @returns Object with `app` (H3 instance) and `serve` method.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const app = application((app) => {
|
|
17
|
+
* app.get("/", () => "Hello world!");
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* await app.serve({ port: 3000 });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With dependency injection
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const app = application((app, box) => {
|
|
26
|
+
* app.get("/ping", () => "pong");
|
|
27
|
+
* app.mount("/users", box.new(usersController));
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* await app.serve({ port: 3000 });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function application(fn, box = new getbox.Box()) {
|
|
34
|
+
const defaultApp = new h3.H3();
|
|
35
|
+
const app = fn(defaultApp, box) || defaultApp;
|
|
36
|
+
return {
|
|
37
|
+
app,
|
|
38
|
+
serve: (options) => (0, h3.serve)(app, options)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a Constructor that produces an h3 app when resolved.
|
|
43
|
+
*
|
|
44
|
+
* Use `box.new(controller)` to create fresh controller instances.
|
|
45
|
+
* Controllers can use `box.get()` to access shared dependencies.
|
|
46
|
+
*
|
|
47
|
+
* @param fn - Function that configures the controller.
|
|
48
|
+
* @returns A Constructor that can be resolved via `box.new(controller)`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Define a controller
|
|
53
|
+
* const usersController = controller((app, box) => {
|
|
54
|
+
* app.get("/", () => ["Alice", "Bob"]);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Use it in your app
|
|
58
|
+
* const app = application((app, box) => {
|
|
59
|
+
* app.mount("/users", box.new(usersController));
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Controller with shared dependencies
|
|
66
|
+
* class Database {
|
|
67
|
+
* getUsers() { return ["Alice", "Bob"]; }
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* const usersController = controller((app, box) => {
|
|
71
|
+
* const db = box.get(Database);
|
|
72
|
+
* app.get("/", () => db.getUsers());
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function controller(fn) {
|
|
77
|
+
return (0, getbox.factory)((box) => application(fn, box).app);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
exports.application = application;
|
|
82
|
+
exports.controller = controller;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { H3, serve } from "h3";
|
|
2
|
+
import { Box, Constructor } from "getbox";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
type ServeOptions = Parameters<typeof serve>[1];
|
|
6
|
+
/**
|
|
7
|
+
* Creates an h3 application with dependency injection support.
|
|
8
|
+
*
|
|
9
|
+
* @param fn - Function that configures the app. Receives a fresh H3 instance
|
|
10
|
+
* and Box instance. Can add routes to the provided app, or create
|
|
11
|
+
* and return a new H3 instance.
|
|
12
|
+
* @param box - Optional Box instance. If not provided, creates a new one.
|
|
13
|
+
* @returns Object with `app` (H3 instance) and `serve` method.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const app = application((app) => {
|
|
18
|
+
* app.get("/", () => "Hello world!");
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await app.serve({ port: 3000 });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example With dependency injection
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const app = application((app, box) => {
|
|
27
|
+
* app.get("/ping", () => "pong");
|
|
28
|
+
* app.mount("/users", box.new(usersController));
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* await app.serve({ port: 3000 });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
|
|
35
|
+
app: H3;
|
|
36
|
+
serve: (options?: ServeOptions) => ReturnType<typeof serve>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Creates a Constructor that produces an h3 app when resolved.
|
|
40
|
+
*
|
|
41
|
+
* Use `box.new(controller)` to create fresh controller instances.
|
|
42
|
+
* Controllers can use `box.get()` to access shared dependencies.
|
|
43
|
+
*
|
|
44
|
+
* @param fn - Function that configures the controller.
|
|
45
|
+
* @returns A Constructor that can be resolved via `box.new(controller)`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Define a controller
|
|
50
|
+
* const usersController = controller((app, box) => {
|
|
51
|
+
* app.get("/", () => ["Alice", "Bob"]);
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Use it in your app
|
|
55
|
+
* const app = application((app, box) => {
|
|
56
|
+
* app.mount("/users", box.new(usersController));
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // Controller with shared dependencies
|
|
63
|
+
* class Database {
|
|
64
|
+
* getUsers() { return ["Alice", "Bob"]; }
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* const usersController = controller((app, box) => {
|
|
68
|
+
* const db = box.get(Database);
|
|
69
|
+
* app.get("/", () => db.getUsers());
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function controller(fn: (app: H3, box: Box) => H3 | void): Constructor<H3>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { ServeOptions, application, controller };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,41 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { BlankEnv, BlankSchema } from 'hono/types';
|
|
1
|
+
import { H3, serve } from "h3";
|
|
2
|
+
import { Box, Constructor } from "getbox";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
type ServeOptions = Parameters<typeof serve>[1];
|
|
6
|
+
/**
|
|
7
|
+
* Creates an h3 application with dependency injection support.
|
|
8
|
+
*
|
|
9
|
+
* @param fn - Function that configures the app. Receives a fresh H3 instance
|
|
10
|
+
* and Box instance. Can add routes to the provided app, or create
|
|
11
|
+
* and return a new H3 instance.
|
|
12
|
+
* @param box - Optional Box instance. If not provided, creates a new one.
|
|
13
|
+
* @returns Object with `app` (H3 instance) and `serve` method.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const app = application((app) => {
|
|
18
|
+
* app.get("/", () => "Hello world!");
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await app.serve({ port: 3000 });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example With dependency injection
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const app = application((app, box) => {
|
|
27
|
+
* app.get("/ping", () => "pong");
|
|
28
|
+
* app.mount("/users", box.new(usersController));
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* await app.serve({ port: 3000 });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
|
|
35
|
+
app: H3;
|
|
36
|
+
serve: (options?: ServeOptions) => ReturnType<typeof serve>;
|
|
14
37
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Creates a Constructor that produces an h3 app when resolved.
|
|
40
|
+
*
|
|
41
|
+
* Use `box.new(controller)` to create fresh controller instances.
|
|
42
|
+
* Controllers can use `box.get()` to access shared dependencies.
|
|
43
|
+
*
|
|
44
|
+
* @param fn - Function that configures the controller.
|
|
45
|
+
* @returns A Constructor that can be resolved via `box.new(controller)`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Define a controller
|
|
50
|
+
* const usersController = controller((app, box) => {
|
|
51
|
+
* app.get("/", () => ["Alice", "Bob"]);
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Use it in your app
|
|
55
|
+
* const app = application((app, box) => {
|
|
56
|
+
* app.mount("/users", box.new(usersController));
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // Controller with shared dependencies
|
|
63
|
+
* class Database {
|
|
64
|
+
* getUsers() { return ["Alice", "Bob"]; }
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* const usersController = controller((app, box) => {
|
|
68
|
+
* const db = box.get(Database);
|
|
69
|
+
* app.get("/", () => db.getUsers());
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function controller(fn: (app: H3, box: Box) => H3 | void): Constructor<H3>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { ServeOptions, application, controller };
|
package/dist/index.mjs
CHANGED
|
@@ -1,68 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
constructor() {
|
|
41
|
-
this.context = [];
|
|
42
|
-
}
|
|
43
|
-
use() {
|
|
44
|
-
return this;
|
|
45
|
-
}
|
|
46
|
-
provide(tokens, options) {
|
|
47
|
-
this.context.push([{ tokens, options }, {}]);
|
|
48
|
-
return this;
|
|
49
|
-
}
|
|
50
|
-
subroutes(routes) {
|
|
51
|
-
if (!this.context.length)
|
|
52
|
-
this.context.push([void 0, routes]);
|
|
53
|
-
else {
|
|
54
|
-
const [, subroutes] = this.context[this.context.length - 1];
|
|
55
|
-
Object.assign(subroutes, routes);
|
|
56
|
-
}
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
route(fn) {
|
|
60
|
-
return new Route(fn, this.context);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
function createRoute() {
|
|
64
|
-
return new RouteBuilder();
|
|
1
|
+
import { H3, serve } from "h3";
|
|
2
|
+
import { Box, factory } from "getbox";
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Creates an h3 application with dependency injection support.
|
|
7
|
+
*
|
|
8
|
+
* @param fn - Function that configures the app. Receives a fresh H3 instance
|
|
9
|
+
* and Box instance. Can add routes to the provided app, or create
|
|
10
|
+
* and return a new H3 instance.
|
|
11
|
+
* @param box - Optional Box instance. If not provided, creates a new one.
|
|
12
|
+
* @returns Object with `app` (H3 instance) and `serve` method.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const app = application((app) => {
|
|
17
|
+
* app.get("/", () => "Hello world!");
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* await app.serve({ port: 3000 });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With dependency injection
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const app = application((app, box) => {
|
|
26
|
+
* app.get("/ping", () => "pong");
|
|
27
|
+
* app.mount("/users", box.new(usersController));
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* await app.serve({ port: 3000 });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function application(fn, box = new Box()) {
|
|
34
|
+
const defaultApp = new H3();
|
|
35
|
+
const app = fn(defaultApp, box) || defaultApp;
|
|
36
|
+
return {
|
|
37
|
+
app,
|
|
38
|
+
serve: (options) => serve(app, options)
|
|
39
|
+
};
|
|
65
40
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Creates a Constructor that produces an h3 app when resolved.
|
|
43
|
+
*
|
|
44
|
+
* Use `box.new(controller)` to create fresh controller instances.
|
|
45
|
+
* Controllers can use `box.get()` to access shared dependencies.
|
|
46
|
+
*
|
|
47
|
+
* @param fn - Function that configures the controller.
|
|
48
|
+
* @returns A Constructor that can be resolved via `box.new(controller)`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Define a controller
|
|
53
|
+
* const usersController = controller((app, box) => {
|
|
54
|
+
* app.get("/", () => ["Alice", "Bob"]);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Use it in your app
|
|
58
|
+
* const app = application((app, box) => {
|
|
59
|
+
* app.mount("/users", box.new(usersController));
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Controller with shared dependencies
|
|
66
|
+
* class Database {
|
|
67
|
+
* getUsers() { return ["Alice", "Bob"]; }
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* const usersController = controller((app, box) => {
|
|
71
|
+
* const db = box.get(Database);
|
|
72
|
+
* app.get("/", () => db.getUsers());
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function controller(fn) {
|
|
77
|
+
return factory((box) => application(fn, box).app);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { application, controller };
|
package/package.json
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverstruct",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Type safe and modular servers with
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Type safe and modular servers with H3",
|
|
5
5
|
"private": false,
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
8
|
-
"types": "./dist/index.d.
|
|
8
|
+
"types": "./dist/index.d.cts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
14
19
|
}
|
|
15
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && tsdown src/index.ts --format esm,cjs",
|
|
23
|
+
"release": "pnpm run build && changeset publish",
|
|
24
|
+
"watch": "vitest",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:coverage": "vitest run --coverage"
|
|
27
|
+
},
|
|
16
28
|
"keywords": [
|
|
17
|
-
"
|
|
29
|
+
"h3",
|
|
18
30
|
"server",
|
|
19
|
-
"
|
|
31
|
+
"DI",
|
|
20
32
|
"typescript"
|
|
21
33
|
],
|
|
22
34
|
"author": "Eric Afes <eriicafes@gmail.com>",
|
|
@@ -30,24 +42,15 @@
|
|
|
30
42
|
},
|
|
31
43
|
"homepage": "https://github.com/eriicafes/serverstruct#readme",
|
|
32
44
|
"devDependencies": {
|
|
33
|
-
"@changesets/cli": "^2.
|
|
34
|
-
"@types/node": "^
|
|
35
|
-
"@vitest/coverage-v8": "^
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"vitest": "^1.6.0"
|
|
45
|
+
"@changesets/cli": "^2.29.8",
|
|
46
|
+
"@types/node": "^25.0.3",
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
48
|
+
"tsdown": "^0.18.3",
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"vitest": "^4.0.16"
|
|
40
51
|
},
|
|
41
52
|
"peerDependencies": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"prebuild": "shx rm -rf dist",
|
|
47
|
-
"build": "tsc --noEmit && tsup src/index.ts --format esm,cjs --dts",
|
|
48
|
-
"release": "pnpm run build && changeset publish",
|
|
49
|
-
"watch": "vitest",
|
|
50
|
-
"test": "vitest run",
|
|
51
|
-
"test:coverage": "vitest run --coverage"
|
|
53
|
+
"getbox": ">= 0.2.0",
|
|
54
|
+
"h3": ">=2.0.1-rc.6 <3.0.0"
|
|
52
55
|
}
|
|
53
56
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { AnyHollywood, HollywoodOf, RegisterTokens, InferContainer, ContainerOptions, Hollywood } from 'hollywood-di';
|
|
2
|
-
import { Env, Schema, Hono } from 'hono';
|
|
3
|
-
import { BlankEnv, BlankSchema } from 'hono/types';
|
|
4
|
-
|
|
5
|
-
type Merge<T, To> = T & Omit<To, keyof T>;
|
|
6
|
-
type IsEmptyObject<T> = keyof T extends never ? true : false;
|
|
7
|
-
type KnownKey<T> = string extends T ? never : number extends T ? never : symbol extends T ? never : T;
|
|
8
|
-
type KnownMappedKeys<T> = {
|
|
9
|
-
[K in keyof T as KnownKey<K>]: T[K];
|
|
10
|
-
} & {};
|
|
11
|
-
|
|
12
|
-
type Subroute<T extends Record<string, any> = any> = {
|
|
13
|
-
app: (container: IsEmptyObject<T> extends true ? HollywoodOf<any> | undefined : HollywoodOf<T>) => Hono<any, any>;
|
|
14
|
-
};
|
|
15
|
-
type Subroutes<TContainer extends Record<string, any> = any> = Record<string, Subroute<TContainer>>;
|
|
16
|
-
type InferSubroutes<R extends Subroutes = {}> = {
|
|
17
|
-
[K in keyof R]: R[K] extends Route<any, infer App, any> ? App : never;
|
|
18
|
-
} & {};
|
|
19
|
-
declare class Route<Deps extends Record<string, any>, App extends Hono<any, any>, Routes extends Subroutes> {
|
|
20
|
-
private route;
|
|
21
|
-
private context;
|
|
22
|
-
constructor(route: (app: Hono<any, any>, container: any, routes: InferSubroutes<Routes>) => App, context: [
|
|
23
|
-
tokens: {
|
|
24
|
-
tokens: RegisterTokens<any, any>;
|
|
25
|
-
options?: ContainerOptions;
|
|
26
|
-
} | undefined,
|
|
27
|
-
routes: Subroutes<any>
|
|
28
|
-
][]);
|
|
29
|
-
app(container: IsEmptyObject<Deps> extends true ? HollywoodOf<any> | undefined : HollywoodOf<Deps>): App;
|
|
30
|
-
app(container?: never): IsEmptyObject<Deps> extends true ? App : never;
|
|
31
|
-
}
|
|
32
|
-
declare class RouteBuilder<Deps extends Record<string, any>, Container extends AnyHollywood | undefined, E extends Env, S extends Schema, Routes extends Subroutes> {
|
|
33
|
-
private context;
|
|
34
|
-
use<T extends Container extends AnyHollywood ? never : Record<string, any>>(): Container extends AnyHollywood ? never : RouteBuilder<T, HollywoodOf<T>, E, S, Routes>;
|
|
35
|
-
provide<T extends Record<string, any> = {}>(tokens: RegisterTokens<T, Container extends AnyHollywood ? InferContainer<Container> : Deps>, options?: ContainerOptions): RouteBuilder<Deps, Container extends AnyHollywood ? Hollywood<T, InferContainer<Container>> : Hollywood<T, {}>, E, S, Routes>;
|
|
36
|
-
subroutes<T extends Subroutes<Container extends AnyHollywood ? KnownMappedKeys<InferContainer<Container>> : Deps>>(routes: T): RouteBuilder<Deps, Container, E, S, Merge<T, Routes> extends infer T_1 ? { [K in keyof T_1]: Merge<T, Routes>[K]; } : never>;
|
|
37
|
-
route<App extends Hono<any, any>>(fn: (app: Hono<E, S>, container: Container extends AnyHollywood ? KnownMappedKeys<InferContainer<Container>> : Deps, routes: InferSubroutes<Routes>) => App): Route<Deps, App, Routes>;
|
|
38
|
-
}
|
|
39
|
-
declare function createRoute<E extends Env = BlankEnv>(): RouteBuilder<{}, undefined, E, BlankSchema, {}>;
|
|
40
|
-
|
|
41
|
-
export { createRoute };
|
package/dist/index.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
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
|
-
createRoute: () => createRoute
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(src_exports);
|
|
26
|
-
|
|
27
|
-
// src/route.ts
|
|
28
|
-
var import_hollywood_di = require("hollywood-di");
|
|
29
|
-
var import_hono = require("hono");
|
|
30
|
-
var Route = class {
|
|
31
|
-
constructor(route, context) {
|
|
32
|
-
this.route = route;
|
|
33
|
-
this.context = context;
|
|
34
|
-
}
|
|
35
|
-
app(container) {
|
|
36
|
-
var _a;
|
|
37
|
-
let currentContainer = container;
|
|
38
|
-
const resolvedRoutes = {};
|
|
39
|
-
for (const [tokens, subroutes] of this.context) {
|
|
40
|
-
let subContainer = currentContainer;
|
|
41
|
-
if (tokens && subContainer) {
|
|
42
|
-
subContainer = import_hollywood_di.Hollywood.createWithParent(
|
|
43
|
-
subContainer,
|
|
44
|
-
tokens.tokens,
|
|
45
|
-
tokens.options
|
|
46
|
-
);
|
|
47
|
-
} else if (tokens) {
|
|
48
|
-
subContainer = import_hollywood_di.Hollywood.create(tokens.tokens, tokens.options);
|
|
49
|
-
}
|
|
50
|
-
currentContainer = subContainer;
|
|
51
|
-
for (const [key, subroute] of Object.entries(subroutes)) {
|
|
52
|
-
resolvedRoutes[key] = subroute.app(subContainer);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const instances = (_a = currentContainer == null ? void 0 : currentContainer.instances) != null ? _a : {};
|
|
56
|
-
return this.route(
|
|
57
|
-
new import_hono.Hono(),
|
|
58
|
-
instances,
|
|
59
|
-
resolvedRoutes
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
var RouteBuilder = class {
|
|
64
|
-
constructor() {
|
|
65
|
-
this.context = [];
|
|
66
|
-
}
|
|
67
|
-
use() {
|
|
68
|
-
return this;
|
|
69
|
-
}
|
|
70
|
-
provide(tokens, options) {
|
|
71
|
-
this.context.push([{ tokens, options }, {}]);
|
|
72
|
-
return this;
|
|
73
|
-
}
|
|
74
|
-
subroutes(routes) {
|
|
75
|
-
if (!this.context.length)
|
|
76
|
-
this.context.push([void 0, routes]);
|
|
77
|
-
else {
|
|
78
|
-
const [, subroutes] = this.context[this.context.length - 1];
|
|
79
|
-
Object.assign(subroutes, routes);
|
|
80
|
-
}
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
route(fn) {
|
|
84
|
-
return new Route(fn, this.context);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
function createRoute() {
|
|
88
|
-
return new RouteBuilder();
|
|
89
|
-
}
|
|
90
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
91
|
-
0 && (module.exports = {
|
|
92
|
-
createRoute
|
|
93
|
-
});
|