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 CHANGED
@@ -1,194 +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.
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 hono
10
+ npm i serverstruct h3 getbox
15
11
  ```
16
12
 
17
- To make use of dependency injection provided by serverstruct, also install hollywood-di.
18
-
19
- ```sh
20
- npm i serverstruct hono hollywood-di
21
- ```
13
+ ## Quick Start
22
14
 
23
- ## Usage
15
+ ```typescript
16
+ import { application } from "serverstruct";
24
17
 
25
- ### A simple app
18
+ const app = application((app) => {
19
+ app.get("/", () => "Hello world!");
20
+ });
26
21
 
27
- ```ts
28
- import { createRoute } from "serverstruct";
22
+ app.serve({ port: 3000 });
23
+ ```
29
24
 
30
- const app = createRoute()
31
- .route((app) => {
32
- return app.get("/", (c) => c.text("Hello world!"));
33
- })
34
- .app();
25
+ ## Composing Apps
35
26
 
36
- export default app;
37
- ```
27
+ Create modular h3 apps and mount them together:
38
28
 
39
- ### An app with subroutes
29
+ ```typescript
30
+ import { application } from "serverstruct";
40
31
 
41
- ```ts
42
- import { createRoute } from "serverstruct";
32
+ // Create a users module
33
+ const { app: usersApp } = application((app) => {
34
+ const users: User[] = [];
43
35
 
44
- // users routes
45
- const users = createRoute().route((app) => {
46
- 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
+ });
47
42
  });
48
43
 
49
- // posts routes
50
- const posts = createRoute().route((app) => {
51
- 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);
52
48
  });
53
49
 
54
- const app = createRoute()
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
- ## Route
53
+ You can also create and return a new H3 instance to customize H3 options:
68
54
 
69
- A route returns a new Hono app and may compose other routes with `subroutes<{ ... }>()`. If you intend to utilize dependency injection, a route can require it's dependencies with `use<{ ... }>()` and provide new dependencies with `provide({ ... })`. A route with dependencies can only be added as a subroute to another route if that route satisfies it's dependencies.
55
+ ```typescript
56
+ import { H3 } from "h3";
70
57
 
71
- ```ts
72
- const auth = createRoute().route((app) => {
73
- 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;
74
66
  });
67
+ ```
68
+
69
+ ## Controllers with Dependency Injection
75
70
 
76
- const users = createRoute().route((app) => {
77
- return app; // chain route handlers here
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
- const posts = createRoute().route((app) => {
81
- 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));
82
92
  });
83
93
 
84
- const app = createRoute()
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
- Subroutes are not automatically registered. You will have to manually add the route for the desired path. This also allows for Hono's type inference through method chaining.
97
+ The `box` parameter is a getbox `Box` instance that manages dependency instances.
96
98
 
97
- ## Dependency Injection
99
+ ## Sharing Dependencies
98
100
 
99
- Serverstruct is designed to work with [Hollywood DI](https://github.com/eriicafes/hollywood-di). Routes can define their dependencies using `use`, and also register new tokens using `provide` which creates a child container.
101
+ Controllers can share dependencies using the `box` parameter.
100
102
 
101
- A root container can also be passed to the route. If no container is explicitly provided the first call to provide creates a root container and futher calls to provide will inherit from it.
103
+ Use `box.get(Class)` to retrieve or create a singleton instance:
102
104
 
103
- ### Use
105
+ ```typescript
106
+ import { application, controller } from "serverstruct";
104
107
 
105
- Define route dependencies. The route can then only be used in a context that satisfies it's dependencies.
108
+ // A shared service
109
+ class Database {
110
+ users: User[] = [];
106
111
 
107
- You can only call `use` once and only before calling `provide`.
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
- const route = createRoute()
116
- .use<{ counter: Counter }>()
117
- .route((app, container) => {
118
- return app.get("/", (c) => {
119
- container.counter.increment();
120
- return c.text(`Count is: ${container.counter.count}`);
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
- class LinearCounter {
125
- public count = 0;
126
- public increment() {
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
- // as a subroute
132
- const app = createRoute()
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
- Calling `Route.app()` returns the Hono instance from the route, therefore if the route 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
+ });
146
139
 
147
- ### Provide
140
+ await app.serve({ port: 3000 });
141
+ ```
148
142
 
149
- Provide creates a new child container. Registered tokens can then be used in the route 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.
150
144
 
151
- ```ts
152
- import { defineInit } from "hollywood-di";
153
- import { createRoute } from "serverstruct";
145
+ ## Middleware
154
146
 
155
- class Foo {}
156
- class Bar {
157
- public static init = defineInit(Bar).args("foo");
147
+ Use h3's native middleware with `app.use()`:
158
148
 
159
- constructor(public foo: Foo) {}
160
- }
149
+ ```typescript
150
+ const app = application((app) => {
151
+ // Global middleware
152
+ app.use(() => console.log("Request received"));
161
153
 
162
- const route = createRoute()
163
- .provide({
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
- ## Incremental Adoption
158
+ Controllers can have their own middleware:
173
159
 
174
- Serverstruct can be added to an existing Hono app.
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
- ```ts
177
- // routes/posts.ts
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
- import { Hono } from "hono";
185
- import { createRoute } from "serverstruct";
186
- import { postsRoute } from "./routes/posts";
174
+ ```typescript
175
+ import { Box } from "getbox";
187
176
 
188
- const app = new Hono();
177
+ const box = new Box();
189
178
 
190
- app.get("/", (c) => c.text("Hello world!"));
191
- app.route("/posts", postsRoute.app());
179
+ // Pre-populate with dependencies
180
+ Box.mock(box, Database, new Database());
192
181
 
193
- export default app;
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;
@@ -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 { AnyHollywood, HollywoodOf, RegisterTokens, InferContainer, ContainerOptions, Hollywood } from 'hollywood-di';
2
- import { Env, Schema, Hono } from 'hono';
3
- import { BlankEnv, BlankSchema } from 'hono/types';
1
+ import { H3, serve } from "h3";
2
+ import { Box, Constructor } from "getbox";
4
3
 
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>;
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
- 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 };
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
- // src/route.ts
2
- import {
3
- Hollywood
4
- } from "hollywood-di";
5
- import { Hono } from "hono";
6
- var Route = class {
7
- constructor(route, context) {
8
- this.route = route;
9
- this.context = context;
10
- }
11
- app(container) {
12
- var _a;
13
- let currentContainer = container;
14
- const resolvedRoutes = {};
15
- for (const [tokens, subroutes] of this.context) {
16
- let subContainer = currentContainer;
17
- if (tokens && subContainer) {
18
- subContainer = Hollywood.createWithParent(
19
- subContainer,
20
- tokens.tokens,
21
- tokens.options
22
- );
23
- } else if (tokens) {
24
- subContainer = Hollywood.create(tokens.tokens, tokens.options);
25
- }
26
- currentContainer = subContainer;
27
- for (const [key, subroute] of Object.entries(subroutes)) {
28
- resolvedRoutes[key] = subroute.app(subContainer);
29
- }
30
- }
31
- const instances = (_a = currentContainer == null ? void 0 : currentContainer.instances) != null ? _a : {};
32
- return this.route(
33
- new Hono(),
34
- instances,
35
- resolvedRoutes
36
- );
37
- }
38
- };
39
- var RouteBuilder = class {
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
- export {
67
- createRoute
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.2.0",
4
- "description": "Type safe and modular servers with Hono",
3
+ "version": "0.4.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.26.2",
34
- "@types/node": "^20.4.2",
35
- "@vitest/coverage-v8": "^1.6.0",
36
- "shx": "^0.3.4",
37
- "tsup": "^7.1.0",
38
- "typescript": "^5.1.6",
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
- "hollywood-di": ">= 0.5.2",
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": ">= 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
- });