serverstruct 0.3.0 → 1.0.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 CHANGED
@@ -1,211 +1,185 @@
1
1
  # Serverstruct
2
2
 
3
- ⚡️ Typesafe and modular servers with [Hono](https://github.com/honojs/hono).
3
+ ⚡️ Typesafe and modular servers with [H3](https://github.com/unjs/h3).
4
4
 
5
- Serverstruct is a simple tool for building fast, modular and typesafe server applications with Hono and [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).
6
6
 
7
7
  ## Installation
8
8
 
9
- Serverstruct requires you to install hono.
10
-
11
9
  ```sh
12
- npm i serverstruct hono
10
+ npm i serverstruct h3 getbox
13
11
  ```
14
12
 
15
- To make use of dependency injection provided by serverstruct, also install hollywood-di.
16
-
17
- ```sh
18
- npm i serverstruct hono hollywood-di
19
- ```
13
+ ## Quick Start
20
14
 
21
- ## Usage
15
+ ```typescript
16
+ import { application } from "serverstruct";
22
17
 
23
- ### A simple app
18
+ const app = application((app) => {
19
+ app.get("/", () => "Hello world!");
20
+ });
24
21
 
25
- ```ts
26
- import { createModule } from "serverstruct";
22
+ app.serve({ port: 3000 });
23
+ ```
27
24
 
28
- const app = createModule()
29
- .route((app) => {
30
- return app.get("/", (c) => c.text("Hello world!"));
31
- })
32
- .app();
25
+ ## Composing Apps
33
26
 
34
- export default app;
35
- ```
27
+ Create modular h3 apps and mount them together:
36
28
 
37
- ### An app with submodules
29
+ ```typescript
30
+ import { application } from "serverstruct";
38
31
 
39
- ```ts
40
- import { createModule } from "serverstruct";
32
+ // Create a users module
33
+ const { app: usersApp } = application((app) => {
34
+ const users: User[] = [];
41
35
 
42
- // users routes
43
- const users = createModule().route((app) => {
44
- return app.get("/", (c) => c.text("users"));
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
- // posts routes
48
- const posts = createModule().route((app) => {
49
- return app.get("/", (c) => c.text("posts"));
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
- const app = createModule()
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
- ## Module
53
+ You can also create and return a new H3 instance to customize H3 options:
66
54
 
67
- A module is a Hono app that may require dependencies or provide dependencies of it's own. A module may compose other submodules. A module with dependencies can only be used as a submodule to another module if that module satisfies it's dependencies.
55
+ ```typescript
56
+ import { H3 } from "h3";
68
57
 
69
- ```ts
70
- import { createModule } from "serverstruct";
71
-
72
- const auth = createModule().route((app) => {
73
- return app; // chain route handlers here
74
- });
75
-
76
- const users = createModule().route((app) => {
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
- Submodules are not automatically added to the Hono app, you will have to manually mount each route. This helps in preserving Hono's type inference through method chaining.
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
- You may also pass a custom Hono app to createModule.
73
+ ```typescript
74
+ import { application, controller } from "serverstruct";
91
75
 
92
- ```ts
93
- import { Hono } from "hono";
94
- import { createModule } from "serverstruct";
76
+ // Define a controller
77
+ const usersController = controller((app) => {
78
+ const users: User[] = [];
95
79
 
96
- const auth = createModule(new Hono().basePath("/auth")).route((app) => {
97
- return app; // chain route handlers here
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
- const users = createModule(new Hono().basePath("/users")).route((app) => {
101
- return app; // chain route handlers here
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
- const app = createModule()
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
- ## Dependency Injection
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
- A root container can also be passed to a module. If no container is explicitly provided the first call to provide creates a root container and futher calls to provide will inherit from it.
99
+ ## Sharing Dependencies
117
100
 
118
- ### Use
101
+ Controllers can share dependencies using the `box` parameter.
119
102
 
120
- Define module dependencies. The module can then only be used in a context that satisfies it's dependencies.
103
+ Use `box.get(Class)` to retrieve or create a singleton instance:
121
104
 
122
- You can only call `use` once and only before calling `provide`.
105
+ ```typescript
106
+ import { application, controller } from "serverstruct";
123
107
 
124
- ```ts
125
- import { Hollywood } from "hollywood-di";
126
- import { createModule } from "serverstruct";
108
+ // A shared service
109
+ class Database {
110
+ users: User[] = [];
127
111
 
128
- interface Counter {
129
- count: number;
130
- increment(): void;
112
+ getUsers() { return this.users; }
113
+ addUser(user: User) { this.users.push(user); }
131
114
  }
132
115
 
133
- const countModule = createModule()
134
- .use<{ counter: Counter }>()
135
- .route((app, container) => {
136
- return app.get("/", (c) => {
137
- container.counter.increment();
138
- return c.text(`Count is: ${container.counter.count}`);
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
- class LinearCounter {
143
- public count = 0;
144
- public increment() {
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
- // as a submodule
150
- const app = createModule()
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
- Calling `Module.app()` returns the Hono app, so if the module has dependencies (by calling `use`), a container that satisfies those dependencies must be provided as seen in the example above.
135
+ const app = application((app, box) => {
136
+ app.mount("/users", box.new(usersController));
137
+ app.mount("/stats", box.new(statsController));
138
+ });
164
139
 
165
- ### Provide
140
+ await app.serve({ port: 3000 });
141
+ ```
166
142
 
167
- Provide creates a new child container. Registered tokens can then be used in the module and in futher calls to `provide`. See more about register tokens in [Hollywood DI](https://github.com/eriicafes/hollywood-di#tokens).
143
+ `box.get(Class)` creates the instance on first call, then caches it.
168
144
 
169
- ```ts
170
- import { createModule } from "serverstruct";
145
+ ## Middleware
171
146
 
172
- class Foo {}
173
- class Bar {
174
- constructor(public ctx: { foo: Foo }) {}
175
- }
147
+ Use h3's native middleware with `app.use()`:
176
148
 
177
- const module = createModule()
178
- .provide({
179
- foo: Foo,
180
- bar: Bar,
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
- ## Incremental Adoption
154
+ app.get("/", () => "Hello world!");
155
+ });
156
+ ```
188
157
 
189
- Serverstruct can be added to an existing Hono app.
158
+ Controllers can have their own middleware:
190
159
 
191
- ```ts
192
- // modules/posts.ts
193
- import { createModule } from "serverstruct";
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
- export const postsModule = createModule().route((app) => {
196
- return app.get("/", (c) => {
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
- // main.ts
202
- import { Hono } from "hono";
203
- import { postsModule } from "./modules/posts";
174
+ ```typescript
175
+ import { Box } from "getbox";
204
176
 
205
- const app = new Hono();
177
+ const box = new Box();
206
178
 
207
- app.get("/", (c) => c.text("Hello world!"));
208
- app.route("/posts", postsModule.app());
179
+ // Pre-populate with dependencies
180
+ Box.mock(box, Database, new Database());
209
181
 
210
- export default app;
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;
@@ -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 { HollywoodOf, AnyHollywood, RegisterTokens, InferContainer, ContainerOptions, Hollywood } from 'hollywood-di';
2
- import { Hono } from 'hono';
1
+ import { H3, serve } from "h3";
2
+ import { Box, Constructor } from "getbox";
3
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>;
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
- 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 };
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
- // src/module.ts
2
- import {
3
- Hollywood
4
- } from "hollywood-di";
5
- import { Hono } from "hono";
6
- var Module = class {
7
- constructor(_context, _route) {
8
- this._context = _context;
9
- this._route = _route;
10
- }
11
- app(container) {
12
- let currentContainer = container;
13
- const resolvedModules = {};
14
- for (const [tokens, submodules] of this._context) {
15
- let subContainer = currentContainer;
16
- if (tokens && subContainer) {
17
- subContainer = Hollywood.createWithParent(
18
- subContainer,
19
- tokens.tokens,
20
- tokens.options
21
- );
22
- } else if (tokens) {
23
- subContainer = Hollywood.create(tokens.tokens, tokens.options);
24
- }
25
- currentContainer = subContainer;
26
- for (const [key, submodule] of Object.entries(submodules)) {
27
- resolvedModules[key] = submodule.app(subContainer);
28
- }
29
- }
30
- const instances = currentContainer?.instances ?? {};
31
- return this._route(
32
- instances,
33
- resolvedModules
34
- );
35
- }
36
- };
37
- var ModuleBuilder = class {
38
- constructor(app) {
39
- this.app = app;
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
- export {
71
- createModule
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.3.0",
4
- "description": "Type safe and modular servers with Hono",
3
+ "version": "1.0.0",
4
+ "description": "Type safe and modular servers with H3",
5
5
  "private": false,
6
- "main": "./dist/index.js",
6
+ "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.mjs",
8
- "types": "./dist/index.d.ts",
8
+ "types": "./dist/index.d.cts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.mjs",
13
- "default": "./dist/index.js"
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
- "hono",
29
+ "h3",
18
30
  "server",
19
- "hollywood-di",
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.27.12",
34
- "@types/node": "^20.17.17",
35
- "@vitest/coverage-v8": "^3.0.5",
36
- "shx": "^0.3.4",
37
- "tsup": "^8.3.6",
38
- "typescript": "^5.7.3",
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
- "hollywood-di": ">= 0.6.1",
43
- "hono": ">= 4.0.0"
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": ">= 1.0.0 <2.0.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
- });