serverstruct 0.3.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 +121 -147
- package/dist/index.cjs +82 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.mts +73 -44
- package/dist/index.mjs +80 -71
- package/package.json +29 -26
- package/dist/index.d.ts +0 -46
- package/dist/index.js +0 -97
package/README.md
CHANGED
|
@@ -1,211 +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
|
|
5
|
+
Serverstruct provides simple helpers for building modular h3 applications with dependency injection using [getbox](https://github.com/eriicafes/getbox).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Serverstruct requires you to install hono.
|
|
10
|
-
|
|
11
9
|
```sh
|
|
12
|
-
npm i serverstruct
|
|
10
|
+
npm i serverstruct h3 getbox
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
npm i serverstruct hono hollywood-di
|
|
19
|
-
```
|
|
13
|
+
## Quick Start
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
```typescript
|
|
16
|
+
import { application } from "serverstruct";
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
const app = application((app) => {
|
|
19
|
+
app.get("/", () => "Hello world!");
|
|
20
|
+
});
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
app.serve({ port: 3000 });
|
|
23
|
+
```
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
.route((app) => {
|
|
30
|
-
return app.get("/", (c) => c.text("Hello world!"));
|
|
31
|
-
})
|
|
32
|
-
.app();
|
|
25
|
+
## Composing Apps
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
```
|
|
27
|
+
Create modular h3 apps and mount them together:
|
|
36
28
|
|
|
37
|
-
|
|
29
|
+
```typescript
|
|
30
|
+
import { application } from "serverstruct";
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
// Create a users module
|
|
33
|
+
const { app: usersApp } = application((app) => {
|
|
34
|
+
const users: User[] = [];
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
app.get("/", () => users);
|
|
37
|
+
app.post("/", async (event) => {
|
|
38
|
+
const body = await readValidatedBody(event, validateUser);
|
|
39
|
+
users.push(body);
|
|
40
|
+
return body;
|
|
41
|
+
});
|
|
45
42
|
});
|
|
46
43
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
|
|
44
|
+
// Compose in main app
|
|
45
|
+
const app = application((app) => {
|
|
46
|
+
app.get("/ping", () => "pong");
|
|
47
|
+
app.mount("/users", usersApp);
|
|
50
48
|
});
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
.submodules({ users, posts })
|
|
54
|
-
.route((app, container, modules) => {
|
|
55
|
-
return app
|
|
56
|
-
.get("/", (c) => c.text("Hello world!"))
|
|
57
|
-
.route("/users", modules.users)
|
|
58
|
-
.route("/posts", modules.posts);
|
|
59
|
-
})
|
|
60
|
-
.app();
|
|
61
|
-
|
|
62
|
-
export default app;
|
|
50
|
+
app.serve({ port: 3000 });
|
|
63
51
|
```
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
You can also create and return a new H3 instance to customize H3 options:
|
|
66
54
|
|
|
67
|
-
|
|
55
|
+
```typescript
|
|
56
|
+
import { H3 } from "h3";
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return app; // chain route handlers here
|
|
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;
|
|
78
66
|
});
|
|
79
|
-
|
|
80
|
-
const app = createModule()
|
|
81
|
-
.submodules({ auth, users })
|
|
82
|
-
.route((app, container, modules) => {
|
|
83
|
-
return app.route("/auth", modules.auth).route("/users", modules.users);
|
|
84
|
-
})
|
|
85
|
-
.app();
|
|
86
67
|
```
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
## Controllers with Dependency Injection
|
|
70
|
+
|
|
71
|
+
Use `controller()` to create reusable modules with shared dependencies. It integrates with [getbox](https://github.com/eriicafes/getbox) for dependency injection:
|
|
89
72
|
|
|
90
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
import { application, controller } from "serverstruct";
|
|
91
75
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
// Define a controller
|
|
77
|
+
const usersController = controller((app) => {
|
|
78
|
+
const users: User[] = [];
|
|
95
79
|
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
app.get("/", () => users);
|
|
81
|
+
app.post("/", async (event) => {
|
|
82
|
+
const body = await readValidatedBody(event, validateUser);
|
|
83
|
+
users.push(body);
|
|
84
|
+
return body;
|
|
85
|
+
});
|
|
98
86
|
});
|
|
99
87
|
|
|
100
|
-
|
|
101
|
-
|
|
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));
|
|
102
92
|
});
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
.submodules({ auth, users })
|
|
106
|
-
.route((app, container, modules) => {
|
|
107
|
-
return app.route("", modules.auth).route("", modules.users);
|
|
108
|
-
})
|
|
109
|
-
.app();
|
|
94
|
+
app.serve({ port: 3000 });
|
|
110
95
|
```
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Serverstruct is designed to work with [Hollywood DI](https://github.com/eriicafes/hollywood-di). Modules can define their dependencies using `use`, and also register new tokens using `provide`.
|
|
97
|
+
The `box` parameter is a getbox `Box` instance that manages dependency instances.
|
|
115
98
|
|
|
116
|
-
|
|
99
|
+
## Sharing Dependencies
|
|
117
100
|
|
|
118
|
-
|
|
101
|
+
Controllers can share dependencies using the `box` parameter.
|
|
119
102
|
|
|
120
|
-
|
|
103
|
+
Use `box.get(Class)` to retrieve or create a singleton instance:
|
|
121
104
|
|
|
122
|
-
|
|
105
|
+
```typescript
|
|
106
|
+
import { application, controller } from "serverstruct";
|
|
123
107
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
// A shared service
|
|
109
|
+
class Database {
|
|
110
|
+
users: User[] = [];
|
|
127
111
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
increment(): void;
|
|
112
|
+
getUsers() { return this.users; }
|
|
113
|
+
addUser(user: User) { this.users.push(user); }
|
|
131
114
|
}
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
125
|
});
|
|
126
|
+
});
|
|
141
127
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.count++;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
128
|
+
// Another controller can access the same database
|
|
129
|
+
const statsController = controller((app, box) => {
|
|
130
|
+
const db = box.get(Database);
|
|
148
131
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.provide({ counter: LinearCounter })
|
|
152
|
-
.submodules({ count: countModule })
|
|
153
|
-
.route((app, _, modules) => {
|
|
154
|
-
return app.route("", modules.count);
|
|
155
|
-
})
|
|
156
|
-
.app();
|
|
157
|
-
|
|
158
|
-
// or as the main app
|
|
159
|
-
const container = Hollywood.create({ counter: LinearCounter });
|
|
160
|
-
const app = countModule.app(container);
|
|
161
|
-
```
|
|
132
|
+
app.get("/count", () => ({ count: db.getUsers().length }));
|
|
133
|
+
});
|
|
162
134
|
|
|
163
|
-
|
|
135
|
+
const app = application((app, box) => {
|
|
136
|
+
app.mount("/users", box.new(usersController));
|
|
137
|
+
app.mount("/stats", box.new(statsController));
|
|
138
|
+
});
|
|
164
139
|
|
|
165
|
-
|
|
140
|
+
await app.serve({ port: 3000 });
|
|
141
|
+
```
|
|
166
142
|
|
|
167
|
-
|
|
143
|
+
`box.get(Class)` creates the instance on first call, then caches it.
|
|
168
144
|
|
|
169
|
-
|
|
170
|
-
import { createModule } from "serverstruct";
|
|
145
|
+
## Middleware
|
|
171
146
|
|
|
172
|
-
|
|
173
|
-
class Bar {
|
|
174
|
-
constructor(public ctx: { foo: Foo }) {}
|
|
175
|
-
}
|
|
147
|
+
Use h3's native middleware with `app.use()`:
|
|
176
148
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
})
|
|
182
|
-
.module((app, container) => {
|
|
183
|
-
return app;
|
|
184
|
-
});
|
|
185
|
-
```
|
|
149
|
+
```typescript
|
|
150
|
+
const app = application((app) => {
|
|
151
|
+
// Global middleware
|
|
152
|
+
app.use(() => console.log("Request received"));
|
|
186
153
|
|
|
187
|
-
|
|
154
|
+
app.get("/", () => "Hello world!");
|
|
155
|
+
});
|
|
156
|
+
```
|
|
188
157
|
|
|
189
|
-
|
|
158
|
+
Controllers can have their own middleware:
|
|
190
159
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
const usersController = controller((app) => {
|
|
162
|
+
// Runs only for routes in this controller
|
|
163
|
+
app.use(() => console.log("Users route accessed"));
|
|
194
164
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return c.text("posts");
|
|
198
|
-
});
|
|
165
|
+
app.get("/", () => [...]);
|
|
166
|
+
app.post("/", async () => {...});
|
|
199
167
|
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Custom Box Instance
|
|
171
|
+
|
|
172
|
+
Pass your own Box instance to `application()` for more control:
|
|
200
173
|
|
|
201
|
-
|
|
202
|
-
import {
|
|
203
|
-
import { postsModule } from "./modules/posts";
|
|
174
|
+
```typescript
|
|
175
|
+
import { Box } from "getbox";
|
|
204
176
|
|
|
205
|
-
const
|
|
177
|
+
const box = new Box();
|
|
206
178
|
|
|
207
|
-
|
|
208
|
-
|
|
179
|
+
// Pre-populate with dependencies
|
|
180
|
+
Box.mock(box, Database, new Database());
|
|
209
181
|
|
|
210
|
-
|
|
182
|
+
const app = application((app, box) => {
|
|
183
|
+
app.mount("/users", box.new(usersController));
|
|
184
|
+
}, box);
|
|
211
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,46 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { H3, serve } from "h3";
|
|
2
|
+
import { Box, Constructor } from "getbox";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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>;
|
|
16
37
|
};
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,72 +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
|
-
this.context = [];
|
|
41
|
-
}
|
|
42
|
-
use() {
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
provide(tokens, options) {
|
|
46
|
-
this.context.push([{ tokens, options }, {}]);
|
|
47
|
-
return this;
|
|
48
|
-
}
|
|
49
|
-
submodules(modules) {
|
|
50
|
-
if (!this.context.length) this.context.push([void 0, modules]);
|
|
51
|
-
else {
|
|
52
|
-
const [, submodules] = this.context[this.context.length - 1];
|
|
53
|
-
Object.assign(submodules, modules);
|
|
54
|
-
}
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
route(fn) {
|
|
58
|
-
return new Module(this.context, (container, modules) => {
|
|
59
|
-
return fn(
|
|
60
|
-
this.app,
|
|
61
|
-
container,
|
|
62
|
-
modules
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
function createModule(app) {
|
|
68
|
-
return new ModuleBuilder(app ?? new Hono());
|
|
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
|
+
};
|
|
69
40
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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": "^3.0.5"
|
|
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,46 +0,0 @@
|
|
|
1
|
-
import { HollywoodOf, AnyHollywood, RegisterTokens, InferContainer, ContainerOptions, Hollywood } from 'hollywood-di';
|
|
2
|
-
import { Hono } from 'hono';
|
|
3
|
-
|
|
4
|
-
type Spread<T> = {
|
|
5
|
-
[K in keyof T]: T[K];
|
|
6
|
-
} & {};
|
|
7
|
-
type Merge<T, To> = T & Omit<To, keyof T>;
|
|
8
|
-
type IsEmptyObject<T> = keyof T extends never ? true : false;
|
|
9
|
-
type KnownKey<T> = string extends T ? never : number extends T ? never : symbol extends T ? never : T;
|
|
10
|
-
type KnownMappedKeys<T> = {
|
|
11
|
-
[K in keyof T as KnownKey<K>]: T[K];
|
|
12
|
-
} & {};
|
|
13
|
-
|
|
14
|
-
type SubModule<T extends Record<string, any> = any> = {
|
|
15
|
-
app: (container: IsEmptyObject<T> extends true ? HollywoodOf<any> | undefined : HollywoodOf<T>) => Hono<any, any, any>;
|
|
16
|
-
};
|
|
17
|
-
type SubModules<TContainer extends Record<string, any> = any> = Record<string, SubModule<TContainer>>;
|
|
18
|
-
type InferSubModules<R extends SubModules = {}> = {
|
|
19
|
-
[K in keyof R]: R[K] extends Module<infer App, any, any> ? App : never;
|
|
20
|
-
} & {};
|
|
21
|
-
declare class Module<App extends Hono<any, any, any>, Deps extends Record<string, any>, Modules extends SubModules> {
|
|
22
|
-
private _context;
|
|
23
|
-
private _route;
|
|
24
|
-
constructor(_context: [
|
|
25
|
-
tokens: {
|
|
26
|
-
tokens: RegisterTokens<any, any>;
|
|
27
|
-
options?: ContainerOptions;
|
|
28
|
-
} | undefined,
|
|
29
|
-
routes: SubModules<any>
|
|
30
|
-
][], _route: (container: Deps, modules: InferSubModules<Modules>) => App);
|
|
31
|
-
app(container: IsEmptyObject<Deps> extends true ? HollywoodOf<any> | undefined : HollywoodOf<Deps>): App;
|
|
32
|
-
app(): IsEmptyObject<Deps> extends true ? App : never;
|
|
33
|
-
}
|
|
34
|
-
declare class ModuleBuilder<App extends Hono<any, any, any>, Deps extends Record<string, any>, Modules extends SubModules, Container extends AnyHollywood | undefined> {
|
|
35
|
-
private app;
|
|
36
|
-
constructor(app: App);
|
|
37
|
-
private context;
|
|
38
|
-
use<T extends Container extends AnyHollywood ? never : Record<string, any>>(): Container extends AnyHollywood ? never : ModuleBuilder<App, T, Modules, HollywoodOf<T>>;
|
|
39
|
-
provide<T extends Record<string, any> = {}>(tokens: RegisterTokens<T, Container extends AnyHollywood ? InferContainer<Container> : Deps>, options?: ContainerOptions): ModuleBuilder<App, Deps, Modules, Container extends AnyHollywood ? Hollywood<T, InferContainer<Container>> : Hollywood<T, {}>>;
|
|
40
|
-
submodules<T extends SubModules<Container extends AnyHollywood ? KnownMappedKeys<InferContainer<Container>> : Deps>>(modules: T): ModuleBuilder<App, Deps, Spread<Merge<T, Modules>>, Container>;
|
|
41
|
-
route<T extends Hono<any, any, any>>(fn: (app: App, container: Container extends AnyHollywood ? KnownMappedKeys<InferContainer<Container>> : Deps, modules: InferSubModules<Modules>) => T): Module<T, Deps, Modules>;
|
|
42
|
-
}
|
|
43
|
-
declare function createModule<App extends Hono<any, any, any> = Hono>(app: App): ModuleBuilder<App, {}, {}, undefined>;
|
|
44
|
-
declare function createModule(): ModuleBuilder<Hono, {}, {}, undefined>;
|
|
45
|
-
|
|
46
|
-
export { createModule };
|
package/dist/index.js
DELETED
|
@@ -1,97 +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 index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
createModule: () => createModule
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
|
|
27
|
-
// src/module.ts
|
|
28
|
-
var import_hollywood_di = require("hollywood-di");
|
|
29
|
-
var import_hono = require("hono");
|
|
30
|
-
var Module = class {
|
|
31
|
-
constructor(_context, _route) {
|
|
32
|
-
this._context = _context;
|
|
33
|
-
this._route = _route;
|
|
34
|
-
}
|
|
35
|
-
app(container) {
|
|
36
|
-
let currentContainer = container;
|
|
37
|
-
const resolvedModules = {};
|
|
38
|
-
for (const [tokens, submodules] of this._context) {
|
|
39
|
-
let subContainer = currentContainer;
|
|
40
|
-
if (tokens && subContainer) {
|
|
41
|
-
subContainer = import_hollywood_di.Hollywood.createWithParent(
|
|
42
|
-
subContainer,
|
|
43
|
-
tokens.tokens,
|
|
44
|
-
tokens.options
|
|
45
|
-
);
|
|
46
|
-
} else if (tokens) {
|
|
47
|
-
subContainer = import_hollywood_di.Hollywood.create(tokens.tokens, tokens.options);
|
|
48
|
-
}
|
|
49
|
-
currentContainer = subContainer;
|
|
50
|
-
for (const [key, submodule] of Object.entries(submodules)) {
|
|
51
|
-
resolvedModules[key] = submodule.app(subContainer);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const instances = currentContainer?.instances ?? {};
|
|
55
|
-
return this._route(
|
|
56
|
-
instances,
|
|
57
|
-
resolvedModules
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var ModuleBuilder = class {
|
|
62
|
-
constructor(app) {
|
|
63
|
-
this.app = app;
|
|
64
|
-
this.context = [];
|
|
65
|
-
}
|
|
66
|
-
use() {
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
provide(tokens, options) {
|
|
70
|
-
this.context.push([{ tokens, options }, {}]);
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
submodules(modules) {
|
|
74
|
-
if (!this.context.length) this.context.push([void 0, modules]);
|
|
75
|
-
else {
|
|
76
|
-
const [, submodules] = this.context[this.context.length - 1];
|
|
77
|
-
Object.assign(submodules, modules);
|
|
78
|
-
}
|
|
79
|
-
return this;
|
|
80
|
-
}
|
|
81
|
-
route(fn) {
|
|
82
|
-
return new Module(this.context, (container, modules) => {
|
|
83
|
-
return fn(
|
|
84
|
-
this.app,
|
|
85
|
-
container,
|
|
86
|
-
modules
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
function createModule(app) {
|
|
92
|
-
return new ModuleBuilder(app ?? new import_hono.Hono());
|
|
93
|
-
}
|
|
94
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
95
|
-
0 && (module.exports = {
|
|
96
|
-
createModule
|
|
97
|
-
});
|