serverstruct 1.0.0 → 1.1.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
@@ -22,7 +22,7 @@ const app = application((app) => {
22
22
  app.serve({ port: 3000 });
23
23
  ```
24
24
 
25
- ## Composing Apps
25
+ ## Apps
26
26
 
27
27
  Create modular h3 apps and mount them together:
28
28
 
@@ -66,9 +66,26 @@ const app = application(() => {
66
66
  });
67
67
  ```
68
68
 
69
- ## Controllers with Dependency Injection
69
+ ### Accessing the Box Instance
70
70
 
71
- Use `controller()` to create reusable modules with shared dependencies. It integrates with [getbox](https://github.com/eriicafes/getbox) for dependency injection:
71
+ The `application()` function creates a new Box instance by default and returns it along with `app` and `serve`. You can access the box instance to retrieve dependencies:
72
+
73
+ ```typescript
74
+ import { constant } from "getbox";
75
+
76
+ const Port = constant(5000);
77
+
78
+ const { box, serve } = application((app, box) => {
79
+ app.get("/", () => "Hello world!");
80
+ });
81
+
82
+ const port = box.get(Port);
83
+ serve({ port });
84
+ ```
85
+
86
+ ## Controllers
87
+
88
+ Use `controller()` to create h3 app constructors:
72
89
 
73
90
  ```typescript
74
91
  import { application, controller } from "serverstruct";
@@ -94,89 +111,161 @@ const app = application((app, box) => {
94
111
  app.serve({ port: 3000 });
95
112
  ```
96
113
 
97
- The `box` parameter is a getbox `Box` instance that manages dependency instances.
114
+ ## Handlers
98
115
 
99
- ## Sharing Dependencies
116
+ Use `handler()` to create h3 handler constructors:
100
117
 
101
- Controllers can share dependencies using the `box` parameter.
118
+ ```typescript
119
+ import { application, handler } from "serverstruct";
102
120
 
103
- Use `box.get(Class)` to retrieve or create a singleton instance:
121
+ class UserService {
122
+ getUser(id: string) {
123
+ return { id, name: "Alice" };
124
+ }
125
+ }
104
126
 
105
- ```typescript
106
- import { application, controller } from "serverstruct";
127
+ // Define a handler
128
+ const getUserHandler = handler((event, box) => {
129
+ const userService = box.get(UserService);
130
+ const id = event.context.params?.id;
131
+ return userService.getUser(id);
132
+ });
133
+
134
+ // Use it in your app
135
+ const app = application((app, box) => {
136
+ app.get("/users/:id", box.get(getUserHandler));
137
+ });
138
+ ```
139
+
140
+ ### Event Handlers
141
+
142
+ Use `eventHandler()` to create h3 handlers with additional options like metadata and middleware:
107
143
 
108
- // A shared service
109
- class Database {
110
- users: User[] = [];
144
+ ```typescript
145
+ import { application, eventHandler } from "serverstruct";
111
146
 
112
- getUsers() { return this.users; }
113
- addUser(user: User) { this.users.push(user); }
147
+ class UserService {
148
+ getUser(id: string) {
149
+ return { id, name: "Alice" };
150
+ }
114
151
  }
115
152
 
116
- // Controller uses box to access the database
117
- const usersController = controller((app, box) => {
118
- const db = box.get(Database);
153
+ // Define an event handler with middleware and metadata
154
+ const getUserHandler = eventHandler((box) => ({
155
+ handler(event) {
156
+ const userService = box.get(UserService);
157
+ const id = event.context.params?.id;
158
+ return userService.getUser(id);
159
+ },
160
+ meta: { auth: true },
161
+ middleware: [
162
+ (event) => {
163
+ const token = event.headers.get("authorization");
164
+ if (!token || token !== "secret-token") {
165
+ throw new Error("Unauthorized");
166
+ }
167
+ },
168
+ ],
169
+ }));
119
170
 
120
- app.get("/", () => db.getUsers());
121
- app.post("/", async (event) => {
122
- const body = await readValidatedBody(event, validateUser);
123
- db.addUser(body);
124
- return body;
125
- });
171
+ // Use it in your app
172
+ const app = application((app, box) => {
173
+ app.get("/users/:id", box.get(getUserHandler));
126
174
  });
175
+ ```
176
+
177
+ ## Middleware
178
+
179
+ Use `middleware()` to create h3 middleware constructors:
127
180
 
128
- // Another controller can access the same database
129
- const statsController = controller((app, box) => {
130
- const db = box.get(Database);
181
+ ```typescript
182
+ import { application, middleware } from "serverstruct";
183
+
184
+ class Logger {
185
+ log(message: string) {
186
+ console.log(message);
187
+ }
188
+ }
131
189
 
132
- app.get("/count", () => ({ count: db.getUsers().length }));
190
+ // Define a middleware
191
+ const logMiddleware = middleware((event, next, box) => {
192
+ const logger = box.get(Logger);
193
+ logger.log("Request received");
133
194
  });
134
195
 
196
+ // Use it in your app
135
197
  const app = application((app, box) => {
136
- app.mount("/users", box.new(usersController));
137
- app.mount("/stats", box.new(statsController));
198
+ app.use(box.get(logMiddleware));
199
+ app.get("/", () => "Hello world!");
138
200
  });
139
-
140
- await app.serve({ port: 3000 });
141
201
  ```
142
202
 
143
- `box.get(Class)` creates the instance on first call, then caches it.
203
+ ## Context
144
204
 
145
- ## Middleware
146
-
147
- Use h3's native middleware with `app.use()`:
205
+ The `context()` function creates a request-scoped, type-safe store for per-request values. Each request gets its own isolated context that is automatically cleaned up when the request completes.
148
206
 
149
207
  ```typescript
208
+ import { application, context } from "serverstruct";
209
+
210
+ interface User {
211
+ id: string;
212
+ name: string;
213
+ }
214
+
215
+ // Create a context store
216
+ const userContext = context<User>();
217
+
150
218
  const app = application((app) => {
151
- // Global middleware
152
- app.use(() => console.log("Request received"));
219
+ // Set context in middleware
220
+ app.use((event) => {
221
+ const user = { id: "123", name: "Alice" };
222
+ userContext.set(event, user);
223
+ });
153
224
 
154
- app.get("/", () => "Hello world!");
225
+ // Access context in handlers
226
+ app.get("/profile", (event) => {
227
+ const user = userContext.get(event);
228
+ return { profile: user };
229
+ });
155
230
  });
156
231
  ```
157
232
 
158
- Controllers can have their own middleware:
233
+ ### Context Methods
234
+
235
+ - `set(event, value)` - Store a value for the current request
236
+ - `get(event)` - Retrieve the value for the current request (throws if not found)
237
+ - `lookup(event)` - Retrieve the value or `undefined` if not found
159
238
 
160
239
  ```typescript
161
- const usersController = controller((app) => {
162
- // Runs only for routes in this controller
163
- app.use(() => console.log("Users route accessed"));
240
+ const requestIdContext = context<string>();
164
241
 
165
- app.get("/", () => [...]);
166
- app.post("/", async () => {...});
242
+ const app = application((app) => {
243
+ app.use((event) => {
244
+ requestIdContext.set(event, crypto.randomUUID());
245
+ });
246
+
247
+ app.get("/", (event) => {
248
+ // Safe access - throws if not set
249
+ const id = requestIdContext.get(event);
250
+
251
+ // Optional access - returns undefined if not set
252
+ const maybeId = requestIdContext.lookup(event);
253
+
254
+ return { requestId: id };
255
+ });
167
256
  });
168
257
  ```
169
258
 
170
259
  ## Custom Box Instance
171
260
 
172
- Pass your own Box instance to `application()` for more control:
261
+ You can also pass your own Box instance to share dependencies across multiple applications or mock dependencies:
173
262
 
174
263
  ```typescript
175
264
  import { Box } from "getbox";
176
265
 
177
266
  const box = new Box();
178
267
 
179
- // Pre-populate with dependencies
268
+ // Mock a dependency
180
269
  Box.mock(box, Database, new Database());
181
270
 
182
271
  const app = application((app, box) => {
package/dist/index.cjs CHANGED
@@ -1,15 +1,15 @@
1
- let h3 = require("h3");
2
1
  let getbox = require("getbox");
2
+ let h3 = require("h3");
3
3
 
4
4
  //#region src/index.ts
5
5
  /**
6
- * Creates an h3 application with dependency injection support.
6
+ * Creates an h3 application.
7
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.
8
+ * @param setup - 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
11
  * @param box - Optional Box instance. If not provided, creates a new one.
12
- * @returns Object with `app` (H3 instance) and `serve` method.
12
+ * @returns Object with `app` (H3 instance), `box` (Box instance), and `serve` method.
13
13
  *
14
14
  * @example
15
15
  * ```typescript
@@ -30,28 +30,33 @@ let getbox = require("getbox");
30
30
  * await app.serve({ port: 3000 });
31
31
  * ```
32
32
  */
33
- function application(fn, box = new getbox.Box()) {
33
+ function application(setup, box = new getbox.Box()) {
34
34
  const defaultApp = new h3.H3();
35
- const app = fn(defaultApp, box) || defaultApp;
35
+ const app = setup(defaultApp, box) || defaultApp;
36
36
  return {
37
37
  app,
38
+ box,
38
39
  serve: (options) => (0, h3.serve)(app, options)
39
40
  };
40
41
  }
41
42
  /**
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.
43
+ * Creates an h3 app constructor.
46
44
  *
47
- * @param fn - Function that configures the controller.
48
- * @returns A Constructor that can be resolved via `box.new(controller)`.
45
+ * @param setup - Function that configures the app.
46
+ * @returns A Constructor that produces an h3 app.
49
47
  *
50
48
  * @example
51
49
  * ```typescript
50
+ * import { application, controller } from "serverstruct";
51
+ *
52
+ * class Database {
53
+ * getUsers() { return ["Alice", "Bob"]; }
54
+ * }
55
+ *
52
56
  * // Define a controller
53
57
  * const usersController = controller((app, box) => {
54
- * app.get("/", () => ["Alice", "Bob"]);
58
+ * const db = box.get(Database);
59
+ * app.get("/", () => db.getUsers());
55
60
  * });
56
61
  *
57
62
  * // Use it in your app
@@ -59,24 +64,188 @@ function application(fn, box = new getbox.Box()) {
59
64
  * app.mount("/users", box.new(usersController));
60
65
  * });
61
66
  * ```
67
+ */
68
+ function controller(setup) {
69
+ return (0, getbox.factory)((box) => application(setup, box).app);
70
+ }
71
+ /**
72
+ * Creates a handler constructor.
73
+ *
74
+ * @param setup - Handler function that receives the event and Box instance.
75
+ * @returns A Constructor that produces an h3 handler.
62
76
  *
63
77
  * @example
64
78
  * ```typescript
65
- * // Controller with shared dependencies
66
- * class Database {
67
- * getUsers() { return ["Alice", "Bob"]; }
79
+ * import { application, handler } from "serverstruct";
80
+ *
81
+ * class UserService {
82
+ * getUser(id: string) { return { id, name: "Alice" }; }
68
83
  * }
69
84
  *
70
- * const usersController = controller((app, box) => {
71
- * const db = box.get(Database);
72
- * app.get("/", () => db.getUsers());
85
+ * // Define a handler
86
+ * const getUserHandler = handler((event, box) => {
87
+ * const userService = box.get(UserService);
88
+ * const id = event.context.params?.id;
89
+ * return userService.getUser(id);
90
+ * });
91
+ *
92
+ * // Use it in your app
93
+ * const app = application((app, box) => {
94
+ * app.get("/users/:id", box.get(getUserHandler));
95
+ * });
96
+ * ```
97
+ */
98
+ function handler(setup) {
99
+ return (0, getbox.factory)((box) => (0, h3.defineHandler)((event) => setup(event, box)));
100
+ }
101
+ /**
102
+ * Creates an event handler constructor from a setup function.
103
+ *
104
+ * @param setup - Function that receives Box instance and returns an event handler object.
105
+ * @returns A Constructor that produces an h3 event handler.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * import { application, eventHandler } from "serverstruct";
110
+ *
111
+ * class UserService {
112
+ * getUser(id: string) { return { id, name: "Alice" }; }
113
+ * }
114
+ *
115
+ * // Define an event handler
116
+ * const getUserHandler = eventHandler((box) => ({
117
+ * handler(event) {
118
+ * const userService = box.get(UserService);
119
+ * const id = event.context.params?.id;
120
+ * return userService.getUser(id);
121
+ * },
122
+ * meta: { auth: true }
123
+ * }));
124
+ *
125
+ * // Use it in your app
126
+ * const app = application((app, box) => {
127
+ * app.get("/users/:id", box.get(getUserHandler));
128
+ * });
129
+ * ```
130
+ */
131
+ function eventHandler(setup) {
132
+ return (0, getbox.factory)((box) => (0, h3.defineHandler)(setup(box)));
133
+ }
134
+ /**
135
+ * Creates a middleware constructor.
136
+ *
137
+ * @param setup - Middleware function that receives the event, next function, and Box instance.
138
+ * @returns A Constructor that produces an h3 middleware.
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * import { application, middleware } from "serverstruct";
143
+ *
144
+ * class AuthService {
145
+ * validateToken(token: string) { return token === "valid"; }
146
+ * }
147
+ *
148
+ * // Define a middleware
149
+ * const authMiddleware = middleware((event, next, box) => {
150
+ * const authService = box.get(AuthService);
151
+ * const token = event.headers.get("authorization");
152
+ * if (!token || !authService.validateToken(token)) {
153
+ * throw new Error("Unauthorized");
154
+ * }
155
+ * });
156
+ *
157
+ * // Use it in your app
158
+ * const app = application((app, box) => {
159
+ * app.use(box.get(authMiddleware));
160
+ * app.get("/", () => "Hello world!");
161
+ * });
162
+ * ```
163
+ */
164
+ function middleware(setup) {
165
+ return (0, getbox.factory)((box) => (0, h3.defineMiddleware)((event, next) => setup(event, next, box)));
166
+ }
167
+ /**
168
+ * A request-scoped context store for associating values with H3 events.
169
+ *
170
+ * Each request gets its own isolated context that is automatically cleaned up
171
+ * when the request completes. Uses a WeakMap internally to ensure values are
172
+ * garbage collected with their events.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * import { application, Context } from "serverstruct";
177
+ *
178
+ * const userContext = new Context<User>();
179
+ *
180
+ * const app = application((app) => {
181
+ * app.use((event) => {
182
+ * userContext.set(event, { id: "123", name: "Alice" });
183
+ * });
184
+ * app.get("/user", (event) => {
185
+ * const user = userContext.get(event);
186
+ * return user;
187
+ * });
188
+ * });
189
+ * ```
190
+ */
191
+ var Context = class {
192
+ #map = /* @__PURE__ */ new WeakMap();
193
+ /**
194
+ * Sets a value for the given event.
195
+ */
196
+ set(event, value) {
197
+ this.#map.set(event, value);
198
+ }
199
+ /**
200
+ * Gets the value for the given event.
201
+ * @throws Error if no value is set for the event.
202
+ */
203
+ get(event) {
204
+ if (this.#map.has(event)) return this.#map.get(event);
205
+ throw new Error("context not found");
206
+ }
207
+ /**
208
+ * Gets the value for the given event, or undefined if not set.
209
+ */
210
+ lookup(event) {
211
+ return this.#map.get(event);
212
+ }
213
+ };
214
+ /**
215
+ * Creates a request-scoped context store for associating values with H3 events.
216
+ *
217
+ * Each request gets its own isolated context that is automatically cleaned up
218
+ * when the request completes. Uses a WeakMap internally to ensure values are
219
+ * garbage collected with their events.
220
+ *
221
+ * @returns A Context instance.
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { application, context } from "serverstruct";
226
+ *
227
+ * const userContext = context<User>();
228
+ *
229
+ * const app = application((app) => {
230
+ * app.use((event) => {
231
+ * userContext.set(event, { id: "123", name: "Alice" });
232
+ * });
233
+ * app.get("/user", (event) => {
234
+ * const user = userContext.get(event);
235
+ * return user;
236
+ * });
73
237
  * });
74
238
  * ```
75
239
  */
76
- function controller(fn) {
77
- return (0, getbox.factory)((box) => application(fn, box).app);
240
+ function context() {
241
+ return new Context();
78
242
  }
79
243
 
80
244
  //#endregion
245
+ exports.Context = Context;
81
246
  exports.application = application;
82
- exports.controller = controller;
247
+ exports.context = context;
248
+ exports.controller = controller;
249
+ exports.eventHandler = eventHandler;
250
+ exports.handler = handler;
251
+ exports.middleware = middleware;
package/dist/index.d.cts CHANGED
@@ -1,16 +1,19 @@
1
- import { H3, serve } from "h3";
1
+ import * as h30 from "h3";
2
+ import { EventHandlerObject, EventHandlerRequest, H3, H3Event, Middleware, serve } from "h3";
2
3
  import { Box, Constructor } from "getbox";
3
4
 
4
5
  //#region src/index.d.ts
6
+ type MaybePromise<T = unknown> = T | Promise<T>;
7
+ type Server = ReturnType<typeof serve>;
5
8
  type ServeOptions = Parameters<typeof serve>[1];
6
9
  /**
7
- * Creates an h3 application with dependency injection support.
10
+ * Creates an h3 application.
8
11
  *
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 setup - Function that configures the app. Receives a fresh H3 instance
13
+ * and Box instance. Can add routes to the provided app, or create
14
+ * and return a new H3 instance.
12
15
  * @param box - Optional Box instance. If not provided, creates a new one.
13
- * @returns Object with `app` (H3 instance) and `serve` method.
16
+ * @returns Object with `app` (H3 instance), `box` (Box instance), and `serve` method.
14
17
  *
15
18
  * @example
16
19
  * ```typescript
@@ -31,24 +34,29 @@ type ServeOptions = Parameters<typeof serve>[1];
31
34
  * await app.serve({ port: 3000 });
32
35
  * ```
33
36
  */
34
- declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
37
+ declare function application(setup: (app: H3, box: Box) => H3 | void, box?: Box): {
35
38
  app: H3;
36
- serve: (options?: ServeOptions) => ReturnType<typeof serve>;
39
+ box: Box;
40
+ serve: (options?: ServeOptions) => Server;
37
41
  };
38
42
  /**
39
- * Creates a Constructor that produces an h3 app when resolved.
43
+ * Creates an h3 app constructor.
40
44
  *
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)`.
45
+ * @param setup - Function that configures the app.
46
+ * @returns A Constructor that produces an h3 app.
46
47
  *
47
48
  * @example
48
49
  * ```typescript
50
+ * import { application, controller } from "serverstruct";
51
+ *
52
+ * class Database {
53
+ * getUsers() { return ["Alice", "Bob"]; }
54
+ * }
55
+ *
49
56
  * // Define a controller
50
57
  * const usersController = controller((app, box) => {
51
- * app.get("/", () => ["Alice", "Bob"]);
58
+ * const db = box.get(Database);
59
+ * app.get("/", () => db.getUsers());
52
60
  * });
53
61
  *
54
62
  * // Use it in your app
@@ -56,20 +64,164 @@ declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
56
64
  * app.mount("/users", box.new(usersController));
57
65
  * });
58
66
  * ```
67
+ */
68
+ declare function controller(setup: (app: H3, box: Box) => H3 | void): Constructor<H3>;
69
+ /**
70
+ * Creates a handler constructor.
71
+ *
72
+ * @param setup - Handler function that receives the event and Box instance.
73
+ * @returns A Constructor that produces an h3 handler.
59
74
  *
60
75
  * @example
61
76
  * ```typescript
62
- * // Controller with shared dependencies
63
- * class Database {
64
- * getUsers() { return ["Alice", "Bob"]; }
77
+ * import { application, handler } from "serverstruct";
78
+ *
79
+ * class UserService {
80
+ * getUser(id: string) { return { id, name: "Alice" }; }
65
81
  * }
66
82
  *
67
- * const usersController = controller((app, box) => {
68
- * const db = box.get(Database);
69
- * app.get("/", () => db.getUsers());
83
+ * // Define a handler
84
+ * const getUserHandler = handler((event, box) => {
85
+ * const userService = box.get(UserService);
86
+ * const id = event.context.params?.id;
87
+ * return userService.getUser(id);
88
+ * });
89
+ *
90
+ * // Use it in your app
91
+ * const app = application((app, box) => {
92
+ * app.get("/users/:id", box.get(getUserHandler));
93
+ * });
94
+ * ```
95
+ */
96
+ declare function handler<Res = unknown, Req extends EventHandlerRequest = EventHandlerRequest>(setup: (event: H3Event<Req>, box: Box) => Res): Constructor<h30.EventHandlerWithFetch<Req, Res>>;
97
+ /**
98
+ * Creates an event handler constructor from a setup function.
99
+ *
100
+ * @param setup - Function that receives Box instance and returns an event handler object.
101
+ * @returns A Constructor that produces an h3 event handler.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * import { application, eventHandler } from "serverstruct";
106
+ *
107
+ * class UserService {
108
+ * getUser(id: string) { return { id, name: "Alice" }; }
109
+ * }
110
+ *
111
+ * // Define an event handler
112
+ * const getUserHandler = eventHandler((box) => ({
113
+ * handler(event) {
114
+ * const userService = box.get(UserService);
115
+ * const id = event.context.params?.id;
116
+ * return userService.getUser(id);
117
+ * },
118
+ * meta: { auth: true }
119
+ * }));
120
+ *
121
+ * // Use it in your app
122
+ * const app = application((app, box) => {
123
+ * app.get("/users/:id", box.get(getUserHandler));
124
+ * });
125
+ * ```
126
+ */
127
+ declare function eventHandler<Res = unknown, Req extends EventHandlerRequest = EventHandlerRequest>(setup: (box: Box) => EventHandlerObject<Req, Res>): Constructor<h30.EventHandlerWithFetch<Req, Res>>;
128
+ /**
129
+ * Creates a middleware constructor.
130
+ *
131
+ * @param setup - Middleware function that receives the event, next function, and Box instance.
132
+ * @returns A Constructor that produces an h3 middleware.
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * import { application, middleware } from "serverstruct";
137
+ *
138
+ * class AuthService {
139
+ * validateToken(token: string) { return token === "valid"; }
140
+ * }
141
+ *
142
+ * // Define a middleware
143
+ * const authMiddleware = middleware((event, next, box) => {
144
+ * const authService = box.get(AuthService);
145
+ * const token = event.headers.get("authorization");
146
+ * if (!token || !authService.validateToken(token)) {
147
+ * throw new Error("Unauthorized");
148
+ * }
149
+ * });
150
+ *
151
+ * // Use it in your app
152
+ * const app = application((app, box) => {
153
+ * app.use(box.get(authMiddleware));
154
+ * app.get("/", () => "Hello world!");
155
+ * });
156
+ * ```
157
+ */
158
+ declare function middleware(setup: (event: H3Event, next: () => MaybePromise<unknown | undefined>, box: Box) => MaybePromise<unknown | undefined>): Constructor<Middleware>;
159
+ /**
160
+ * A request-scoped context store for associating values with H3 events.
161
+ *
162
+ * Each request gets its own isolated context that is automatically cleaned up
163
+ * when the request completes. Uses a WeakMap internally to ensure values are
164
+ * garbage collected with their events.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * import { application, Context } from "serverstruct";
169
+ *
170
+ * const userContext = new Context<User>();
171
+ *
172
+ * const app = application((app) => {
173
+ * app.use((event) => {
174
+ * userContext.set(event, { id: "123", name: "Alice" });
175
+ * });
176
+ * app.get("/user", (event) => {
177
+ * const user = userContext.get(event);
178
+ * return user;
179
+ * });
180
+ * });
181
+ * ```
182
+ */
183
+ declare class Context<T> {
184
+ #private;
185
+ /**
186
+ * Sets a value for the given event.
187
+ */
188
+ set(event: H3Event<any>, value: T): void;
189
+ /**
190
+ * Gets the value for the given event.
191
+ * @throws Error if no value is set for the event.
192
+ */
193
+ get(event: H3Event<any>): T;
194
+ /**
195
+ * Gets the value for the given event, or undefined if not set.
196
+ */
197
+ lookup(event: H3Event<any>): T | undefined;
198
+ }
199
+ /**
200
+ * Creates a request-scoped context store for associating values with H3 events.
201
+ *
202
+ * Each request gets its own isolated context that is automatically cleaned up
203
+ * when the request completes. Uses a WeakMap internally to ensure values are
204
+ * garbage collected with their events.
205
+ *
206
+ * @returns A Context instance.
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * import { application, context } from "serverstruct";
211
+ *
212
+ * const userContext = context<User>();
213
+ *
214
+ * const app = application((app) => {
215
+ * app.use((event) => {
216
+ * userContext.set(event, { id: "123", name: "Alice" });
217
+ * });
218
+ * app.get("/user", (event) => {
219
+ * const user = userContext.get(event);
220
+ * return user;
221
+ * });
70
222
  * });
71
223
  * ```
72
224
  */
73
- declare function controller(fn: (app: H3, box: Box) => H3 | void): Constructor<H3>;
225
+ declare function context<T>(): Context<T>;
74
226
  //#endregion
75
- export { ServeOptions, application, controller };
227
+ export { Context, ServeOptions, Server, application, context, controller, eventHandler, handler, middleware };
package/dist/index.d.mts CHANGED
@@ -1,16 +1,19 @@
1
- import { H3, serve } from "h3";
2
1
  import { Box, Constructor } from "getbox";
2
+ import * as h30 from "h3";
3
+ import { EventHandlerObject, EventHandlerRequest, H3, H3Event, Middleware, serve } from "h3";
3
4
 
4
5
  //#region src/index.d.ts
6
+ type MaybePromise<T = unknown> = T | Promise<T>;
7
+ type Server = ReturnType<typeof serve>;
5
8
  type ServeOptions = Parameters<typeof serve>[1];
6
9
  /**
7
- * Creates an h3 application with dependency injection support.
10
+ * Creates an h3 application.
8
11
  *
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 setup - Function that configures the app. Receives a fresh H3 instance
13
+ * and Box instance. Can add routes to the provided app, or create
14
+ * and return a new H3 instance.
12
15
  * @param box - Optional Box instance. If not provided, creates a new one.
13
- * @returns Object with `app` (H3 instance) and `serve` method.
16
+ * @returns Object with `app` (H3 instance), `box` (Box instance), and `serve` method.
14
17
  *
15
18
  * @example
16
19
  * ```typescript
@@ -31,24 +34,29 @@ type ServeOptions = Parameters<typeof serve>[1];
31
34
  * await app.serve({ port: 3000 });
32
35
  * ```
33
36
  */
34
- declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
37
+ declare function application(setup: (app: H3, box: Box) => H3 | void, box?: Box): {
35
38
  app: H3;
36
- serve: (options?: ServeOptions) => ReturnType<typeof serve>;
39
+ box: Box;
40
+ serve: (options?: ServeOptions) => Server;
37
41
  };
38
42
  /**
39
- * Creates a Constructor that produces an h3 app when resolved.
43
+ * Creates an h3 app constructor.
40
44
  *
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)`.
45
+ * @param setup - Function that configures the app.
46
+ * @returns A Constructor that produces an h3 app.
46
47
  *
47
48
  * @example
48
49
  * ```typescript
50
+ * import { application, controller } from "serverstruct";
51
+ *
52
+ * class Database {
53
+ * getUsers() { return ["Alice", "Bob"]; }
54
+ * }
55
+ *
49
56
  * // Define a controller
50
57
  * const usersController = controller((app, box) => {
51
- * app.get("/", () => ["Alice", "Bob"]);
58
+ * const db = box.get(Database);
59
+ * app.get("/", () => db.getUsers());
52
60
  * });
53
61
  *
54
62
  * // Use it in your app
@@ -56,20 +64,164 @@ declare function application(fn: (app: H3, box: Box) => H3 | void, box?: Box): {
56
64
  * app.mount("/users", box.new(usersController));
57
65
  * });
58
66
  * ```
67
+ */
68
+ declare function controller(setup: (app: H3, box: Box) => H3 | void): Constructor<H3>;
69
+ /**
70
+ * Creates a handler constructor.
71
+ *
72
+ * @param setup - Handler function that receives the event and Box instance.
73
+ * @returns A Constructor that produces an h3 handler.
59
74
  *
60
75
  * @example
61
76
  * ```typescript
62
- * // Controller with shared dependencies
63
- * class Database {
64
- * getUsers() { return ["Alice", "Bob"]; }
77
+ * import { application, handler } from "serverstruct";
78
+ *
79
+ * class UserService {
80
+ * getUser(id: string) { return { id, name: "Alice" }; }
65
81
  * }
66
82
  *
67
- * const usersController = controller((app, box) => {
68
- * const db = box.get(Database);
69
- * app.get("/", () => db.getUsers());
83
+ * // Define a handler
84
+ * const getUserHandler = handler((event, box) => {
85
+ * const userService = box.get(UserService);
86
+ * const id = event.context.params?.id;
87
+ * return userService.getUser(id);
88
+ * });
89
+ *
90
+ * // Use it in your app
91
+ * const app = application((app, box) => {
92
+ * app.get("/users/:id", box.get(getUserHandler));
93
+ * });
94
+ * ```
95
+ */
96
+ declare function handler<Res = unknown, Req extends EventHandlerRequest = EventHandlerRequest>(setup: (event: H3Event<Req>, box: Box) => Res): Constructor<h30.EventHandlerWithFetch<Req, Res>>;
97
+ /**
98
+ * Creates an event handler constructor from a setup function.
99
+ *
100
+ * @param setup - Function that receives Box instance and returns an event handler object.
101
+ * @returns A Constructor that produces an h3 event handler.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * import { application, eventHandler } from "serverstruct";
106
+ *
107
+ * class UserService {
108
+ * getUser(id: string) { return { id, name: "Alice" }; }
109
+ * }
110
+ *
111
+ * // Define an event handler
112
+ * const getUserHandler = eventHandler((box) => ({
113
+ * handler(event) {
114
+ * const userService = box.get(UserService);
115
+ * const id = event.context.params?.id;
116
+ * return userService.getUser(id);
117
+ * },
118
+ * meta: { auth: true }
119
+ * }));
120
+ *
121
+ * // Use it in your app
122
+ * const app = application((app, box) => {
123
+ * app.get("/users/:id", box.get(getUserHandler));
124
+ * });
125
+ * ```
126
+ */
127
+ declare function eventHandler<Res = unknown, Req extends EventHandlerRequest = EventHandlerRequest>(setup: (box: Box) => EventHandlerObject<Req, Res>): Constructor<h30.EventHandlerWithFetch<Req, Res>>;
128
+ /**
129
+ * Creates a middleware constructor.
130
+ *
131
+ * @param setup - Middleware function that receives the event, next function, and Box instance.
132
+ * @returns A Constructor that produces an h3 middleware.
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * import { application, middleware } from "serverstruct";
137
+ *
138
+ * class AuthService {
139
+ * validateToken(token: string) { return token === "valid"; }
140
+ * }
141
+ *
142
+ * // Define a middleware
143
+ * const authMiddleware = middleware((event, next, box) => {
144
+ * const authService = box.get(AuthService);
145
+ * const token = event.headers.get("authorization");
146
+ * if (!token || !authService.validateToken(token)) {
147
+ * throw new Error("Unauthorized");
148
+ * }
149
+ * });
150
+ *
151
+ * // Use it in your app
152
+ * const app = application((app, box) => {
153
+ * app.use(box.get(authMiddleware));
154
+ * app.get("/", () => "Hello world!");
155
+ * });
156
+ * ```
157
+ */
158
+ declare function middleware(setup: (event: H3Event, next: () => MaybePromise<unknown | undefined>, box: Box) => MaybePromise<unknown | undefined>): Constructor<Middleware>;
159
+ /**
160
+ * A request-scoped context store for associating values with H3 events.
161
+ *
162
+ * Each request gets its own isolated context that is automatically cleaned up
163
+ * when the request completes. Uses a WeakMap internally to ensure values are
164
+ * garbage collected with their events.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * import { application, Context } from "serverstruct";
169
+ *
170
+ * const userContext = new Context<User>();
171
+ *
172
+ * const app = application((app) => {
173
+ * app.use((event) => {
174
+ * userContext.set(event, { id: "123", name: "Alice" });
175
+ * });
176
+ * app.get("/user", (event) => {
177
+ * const user = userContext.get(event);
178
+ * return user;
179
+ * });
180
+ * });
181
+ * ```
182
+ */
183
+ declare class Context<T> {
184
+ #private;
185
+ /**
186
+ * Sets a value for the given event.
187
+ */
188
+ set(event: H3Event<any>, value: T): void;
189
+ /**
190
+ * Gets the value for the given event.
191
+ * @throws Error if no value is set for the event.
192
+ */
193
+ get(event: H3Event<any>): T;
194
+ /**
195
+ * Gets the value for the given event, or undefined if not set.
196
+ */
197
+ lookup(event: H3Event<any>): T | undefined;
198
+ }
199
+ /**
200
+ * Creates a request-scoped context store for associating values with H3 events.
201
+ *
202
+ * Each request gets its own isolated context that is automatically cleaned up
203
+ * when the request completes. Uses a WeakMap internally to ensure values are
204
+ * garbage collected with their events.
205
+ *
206
+ * @returns A Context instance.
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * import { application, context } from "serverstruct";
211
+ *
212
+ * const userContext = context<User>();
213
+ *
214
+ * const app = application((app) => {
215
+ * app.use((event) => {
216
+ * userContext.set(event, { id: "123", name: "Alice" });
217
+ * });
218
+ * app.get("/user", (event) => {
219
+ * const user = userContext.get(event);
220
+ * return user;
221
+ * });
70
222
  * });
71
223
  * ```
72
224
  */
73
- declare function controller(fn: (app: H3, box: Box) => H3 | void): Constructor<H3>;
225
+ declare function context<T>(): Context<T>;
74
226
  //#endregion
75
- export { ServeOptions, application, controller };
227
+ export { Context, ServeOptions, Server, application, context, controller, eventHandler, handler, middleware };
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
- import { H3, serve } from "h3";
2
1
  import { Box, factory } from "getbox";
2
+ import { H3, defineHandler, defineMiddleware, serve } from "h3";
3
3
 
4
4
  //#region src/index.ts
5
5
  /**
6
- * Creates an h3 application with dependency injection support.
6
+ * Creates an h3 application.
7
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.
8
+ * @param setup - 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
11
  * @param box - Optional Box instance. If not provided, creates a new one.
12
- * @returns Object with `app` (H3 instance) and `serve` method.
12
+ * @returns Object with `app` (H3 instance), `box` (Box instance), and `serve` method.
13
13
  *
14
14
  * @example
15
15
  * ```typescript
@@ -30,28 +30,33 @@ import { Box, factory } from "getbox";
30
30
  * await app.serve({ port: 3000 });
31
31
  * ```
32
32
  */
33
- function application(fn, box = new Box()) {
33
+ function application(setup, box = new Box()) {
34
34
  const defaultApp = new H3();
35
- const app = fn(defaultApp, box) || defaultApp;
35
+ const app = setup(defaultApp, box) || defaultApp;
36
36
  return {
37
37
  app,
38
+ box,
38
39
  serve: (options) => serve(app, options)
39
40
  };
40
41
  }
41
42
  /**
42
- * Creates a Constructor that produces an h3 app when resolved.
43
+ * Creates an h3 app constructor.
43
44
  *
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)`.
45
+ * @param setup - Function that configures the app.
46
+ * @returns A Constructor that produces an h3 app.
49
47
  *
50
48
  * @example
51
49
  * ```typescript
50
+ * import { application, controller } from "serverstruct";
51
+ *
52
+ * class Database {
53
+ * getUsers() { return ["Alice", "Bob"]; }
54
+ * }
55
+ *
52
56
  * // Define a controller
53
57
  * const usersController = controller((app, box) => {
54
- * app.get("/", () => ["Alice", "Bob"]);
58
+ * const db = box.get(Database);
59
+ * app.get("/", () => db.getUsers());
55
60
  * });
56
61
  *
57
62
  * // Use it in your app
@@ -59,23 +64,182 @@ function application(fn, box = new Box()) {
59
64
  * app.mount("/users", box.new(usersController));
60
65
  * });
61
66
  * ```
67
+ */
68
+ function controller(setup) {
69
+ return factory((box) => application(setup, box).app);
70
+ }
71
+ /**
72
+ * Creates a handler constructor.
73
+ *
74
+ * @param setup - Handler function that receives the event and Box instance.
75
+ * @returns A Constructor that produces an h3 handler.
62
76
  *
63
77
  * @example
64
78
  * ```typescript
65
- * // Controller with shared dependencies
66
- * class Database {
67
- * getUsers() { return ["Alice", "Bob"]; }
79
+ * import { application, handler } from "serverstruct";
80
+ *
81
+ * class UserService {
82
+ * getUser(id: string) { return { id, name: "Alice" }; }
68
83
  * }
69
84
  *
70
- * const usersController = controller((app, box) => {
71
- * const db = box.get(Database);
72
- * app.get("/", () => db.getUsers());
85
+ * // Define a handler
86
+ * const getUserHandler = handler((event, box) => {
87
+ * const userService = box.get(UserService);
88
+ * const id = event.context.params?.id;
89
+ * return userService.getUser(id);
90
+ * });
91
+ *
92
+ * // Use it in your app
93
+ * const app = application((app, box) => {
94
+ * app.get("/users/:id", box.get(getUserHandler));
95
+ * });
96
+ * ```
97
+ */
98
+ function handler(setup) {
99
+ return factory((box) => defineHandler((event) => setup(event, box)));
100
+ }
101
+ /**
102
+ * Creates an event handler constructor from a setup function.
103
+ *
104
+ * @param setup - Function that receives Box instance and returns an event handler object.
105
+ * @returns A Constructor that produces an h3 event handler.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * import { application, eventHandler } from "serverstruct";
110
+ *
111
+ * class UserService {
112
+ * getUser(id: string) { return { id, name: "Alice" }; }
113
+ * }
114
+ *
115
+ * // Define an event handler
116
+ * const getUserHandler = eventHandler((box) => ({
117
+ * handler(event) {
118
+ * const userService = box.get(UserService);
119
+ * const id = event.context.params?.id;
120
+ * return userService.getUser(id);
121
+ * },
122
+ * meta: { auth: true }
123
+ * }));
124
+ *
125
+ * // Use it in your app
126
+ * const app = application((app, box) => {
127
+ * app.get("/users/:id", box.get(getUserHandler));
128
+ * });
129
+ * ```
130
+ */
131
+ function eventHandler(setup) {
132
+ return factory((box) => defineHandler(setup(box)));
133
+ }
134
+ /**
135
+ * Creates a middleware constructor.
136
+ *
137
+ * @param setup - Middleware function that receives the event, next function, and Box instance.
138
+ * @returns A Constructor that produces an h3 middleware.
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * import { application, middleware } from "serverstruct";
143
+ *
144
+ * class AuthService {
145
+ * validateToken(token: string) { return token === "valid"; }
146
+ * }
147
+ *
148
+ * // Define a middleware
149
+ * const authMiddleware = middleware((event, next, box) => {
150
+ * const authService = box.get(AuthService);
151
+ * const token = event.headers.get("authorization");
152
+ * if (!token || !authService.validateToken(token)) {
153
+ * throw new Error("Unauthorized");
154
+ * }
155
+ * });
156
+ *
157
+ * // Use it in your app
158
+ * const app = application((app, box) => {
159
+ * app.use(box.get(authMiddleware));
160
+ * app.get("/", () => "Hello world!");
161
+ * });
162
+ * ```
163
+ */
164
+ function middleware(setup) {
165
+ return factory((box) => defineMiddleware((event, next) => setup(event, next, box)));
166
+ }
167
+ /**
168
+ * A request-scoped context store for associating values with H3 events.
169
+ *
170
+ * Each request gets its own isolated context that is automatically cleaned up
171
+ * when the request completes. Uses a WeakMap internally to ensure values are
172
+ * garbage collected with their events.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * import { application, Context } from "serverstruct";
177
+ *
178
+ * const userContext = new Context<User>();
179
+ *
180
+ * const app = application((app) => {
181
+ * app.use((event) => {
182
+ * userContext.set(event, { id: "123", name: "Alice" });
183
+ * });
184
+ * app.get("/user", (event) => {
185
+ * const user = userContext.get(event);
186
+ * return user;
187
+ * });
188
+ * });
189
+ * ```
190
+ */
191
+ var Context = class {
192
+ #map = /* @__PURE__ */ new WeakMap();
193
+ /**
194
+ * Sets a value for the given event.
195
+ */
196
+ set(event, value) {
197
+ this.#map.set(event, value);
198
+ }
199
+ /**
200
+ * Gets the value for the given event.
201
+ * @throws Error if no value is set for the event.
202
+ */
203
+ get(event) {
204
+ if (this.#map.has(event)) return this.#map.get(event);
205
+ throw new Error("context not found");
206
+ }
207
+ /**
208
+ * Gets the value for the given event, or undefined if not set.
209
+ */
210
+ lookup(event) {
211
+ return this.#map.get(event);
212
+ }
213
+ };
214
+ /**
215
+ * Creates a request-scoped context store for associating values with H3 events.
216
+ *
217
+ * Each request gets its own isolated context that is automatically cleaned up
218
+ * when the request completes. Uses a WeakMap internally to ensure values are
219
+ * garbage collected with their events.
220
+ *
221
+ * @returns A Context instance.
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { application, context } from "serverstruct";
226
+ *
227
+ * const userContext = context<User>();
228
+ *
229
+ * const app = application((app) => {
230
+ * app.use((event) => {
231
+ * userContext.set(event, { id: "123", name: "Alice" });
232
+ * });
233
+ * app.get("/user", (event) => {
234
+ * const user = userContext.get(event);
235
+ * return user;
236
+ * });
73
237
  * });
74
238
  * ```
75
239
  */
76
- function controller(fn) {
77
- return factory((box) => application(fn, box).app);
240
+ function context() {
241
+ return new Context();
78
242
  }
79
243
 
80
244
  //#endregion
81
- export { application, controller };
245
+ export { Context, application, context, controller, eventHandler, handler, middleware };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverstruct",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Type safe and modular servers with H3",
5
5
  "private": false,
6
6
  "main": "./dist/index.cjs",
@@ -18,13 +18,6 @@
18
18
  }
19
19
  }
20
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
- },
28
21
  "keywords": [
29
22
  "h3",
30
23
  "server",
@@ -52,5 +45,12 @@
52
45
  "peerDependencies": {
53
46
  "getbox": ">= 1.0.0 <2.0.0",
54
47
  "h3": ">=2.0.1-rc.6 <3.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc && tsdown src/index.ts --format esm,cjs",
51
+ "release": "pnpm run build && changeset publish",
52
+ "watch": "vitest",
53
+ "test": "vitest run",
54
+ "test:coverage": "vitest run --coverage"
55
55
  }
56
56
  }