@vertz/core 0.2.1 → 0.2.4

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/dist/index.d.ts CHANGED
@@ -39,172 +39,6 @@ declare function createMiddleware<
39
39
  TRequires extends Record<string, unknown> = Record<string, unknown>,
40
40
  TProvides extends Record<string, unknown> = Record<string, unknown>
41
41
  >(def: NamedMiddlewareDef<TRequires, TProvides>): NamedMiddlewareDef<TRequires, TProvides>;
42
- import { Schema as Schema2 } from "@vertz/schema";
43
- interface ModuleDef<
44
- TImports extends Record<string, unknown> = Record<string, unknown>,
45
- TOptions extends Record<string, unknown> = Record<string, unknown>
46
- > {
47
- name: string;
48
- imports?: TImports;
49
- options?: Schema2<TOptions>;
50
- }
51
- interface ServiceDef<
52
- TDeps = unknown,
53
- TState = unknown,
54
- TMethods = unknown,
55
- TOptions extends Record<string, unknown> = Record<string, unknown>,
56
- TEnv extends Record<string, unknown> = Record<string, unknown>
57
- > {
58
- inject?: Record<string, unknown>;
59
- options?: Schema2<TOptions>;
60
- env?: Schema2<TEnv>;
61
- onInit?: (deps: TDeps, opts: TOptions, env: TEnv) => Promise<TState> | TState;
62
- methods: (deps: TDeps, state: TState, opts: TOptions, env: TEnv) => TMethods;
63
- onDestroy?: (deps: TDeps, state: TState) => Promise<void> | void;
64
- }
65
- interface RouterDef<TInject extends Record<string, unknown> = Record<string, unknown>> {
66
- prefix: string;
67
- inject?: TInject;
68
- }
69
- interface Module<TDef extends ModuleDef = ModuleDef> {
70
- definition: TDef;
71
- services: ServiceDef[];
72
- routers: RouterDef[];
73
- exports: ServiceDef[];
74
- }
75
- type Primitive = string | number | boolean | bigint | symbol | undefined | null;
76
- type BuiltinObject = Date | RegExp | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | Request | Response | Headers | ReadableStream | WritableStream;
77
- type DeepReadonly<T> = unknown extends T ? T : T extends Primitive ? T : T extends BuiltinObject ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : { readonly [K in keyof T] : DeepReadonly<T[K]> };
78
- interface RawRequest {
79
- readonly request: Request;
80
- readonly method: string;
81
- readonly url: string;
82
- readonly headers: Headers;
83
- }
84
- interface HandlerCtx {
85
- params: Record<string, unknown>;
86
- body: unknown;
87
- query: Record<string, string>;
88
- headers: Record<string, unknown>;
89
- raw: RawRequest;
90
- options: Record<string, unknown>;
91
- env: Record<string, unknown>;
92
- [key: string]: unknown;
93
- }
94
- type Deps<T extends Record<string, unknown>> = DeepReadonly<T>;
95
- type Ctx<T extends Record<string, unknown>> = DeepReadonly<T>;
96
- interface NamedServiceDef<
97
- TDeps = unknown,
98
- TState = unknown,
99
- TMethods = unknown,
100
- TOptions extends Record<string, unknown> = Record<string, unknown>,
101
- TEnv extends Record<string, unknown> = Record<string, unknown>
102
- > extends ServiceDef<TDeps, TState, TMethods, TOptions, TEnv> {
103
- moduleName: string;
104
- }
105
- type InferOutput<
106
- T,
107
- TDefault = unknown
108
- > = T extends {
109
- _output: infer O;
110
- } ? O : T extends {
111
- parse(v: unknown): infer P;
112
- } ? P : TDefault;
113
- type TypedHandlerCtx<
114
- TParams = unknown,
115
- TQuery = unknown,
116
- THeaders = unknown,
117
- TBody = unknown,
118
- TMiddleware extends Record<string, unknown> = Record<string, unknown>
119
- > = Omit<HandlerCtx, "params" | "query" | "headers" | "body"> & {
120
- params: TParams;
121
- query: TQuery;
122
- headers: THeaders;
123
- body: TBody;
124
- } & TMiddleware;
125
- interface RouteConfig<
126
- TParams = unknown,
127
- TQuery = unknown,
128
- THeaders = unknown,
129
- TBody = unknown,
130
- TMiddleware extends Record<string, unknown> = Record<string, unknown>
131
- > {
132
- params?: TParams;
133
- body?: TBody;
134
- query?: TQuery;
135
- response?: unknown;
136
- /** Error schemas for errors-as-values pattern. Keys are HTTP status codes. */
137
- errors?: Record<number, unknown>;
138
- headers?: THeaders;
139
- middlewares?: unknown[];
140
- handler: (ctx: TypedHandlerCtx<InferOutput<TParams>, InferOutput<TQuery, Record<string, string>>, InferOutput<THeaders>, InferOutput<TBody>, TMiddleware>) => unknown;
141
- }
142
- interface Route {
143
- method: string;
144
- path: string;
145
- config: RouteConfig<unknown, unknown, unknown, unknown, Record<string, unknown>>;
146
- }
147
- /**
148
- * Extracts the TMethods type from a NamedServiceDef or ServiceDef.
149
- * Returns `unknown` for non-service types.
150
- */
151
- type ExtractMethods<T> = T extends NamedServiceDef<any, any, infer M> ? M : T extends ServiceDef<any, any, infer M> ? M : unknown;
152
- /**
153
- * Resolves an inject map by extracting TMethods from each NamedServiceDef value.
154
- * Given `{ userService: NamedServiceDef<..., ..., UserMethods> }`,
155
- * produces `{ userService: UserMethods }`.
156
- */
157
- type ResolveInjectMap<T extends Record<string, unknown>> = { [K in keyof T] : ExtractMethods<T[K]> };
158
- type HttpMethodFn<
159
- TMiddleware extends Record<string, unknown> = Record<string, unknown>,
160
- TInject extends Record<string, unknown> = Record<string, unknown>
161
- > = <
162
- TParams,
163
- TQuery,
164
- THeaders,
165
- TBody
166
- >(path: `/${string}`, config: RouteConfig<TParams, TQuery, THeaders, TBody, TMiddleware & ResolveInjectMap<TInject>>) => NamedRouterDef<TMiddleware, TInject>;
167
- interface NamedRouterDef<
168
- TMiddleware extends Record<string, unknown> = Record<string, unknown>,
169
- TInject extends Record<string, unknown> = Record<string, unknown>
170
- > extends RouterDef<TInject> {
171
- moduleName: string;
172
- routes: Route[];
173
- get: HttpMethodFn<TMiddleware, TInject>;
174
- post: HttpMethodFn<TMiddleware, TInject>;
175
- put: HttpMethodFn<TMiddleware, TInject>;
176
- patch: HttpMethodFn<TMiddleware, TInject>;
177
- delete: HttpMethodFn<TMiddleware, TInject>;
178
- head: HttpMethodFn<TMiddleware, TInject>;
179
- }
180
- interface NamedModuleDef<
181
- TImports extends Record<string, unknown> = Record<string, unknown>,
182
- TOptions extends Record<string, unknown> = Record<string, unknown>,
183
- TMiddleware extends Record<string, unknown> = Record<string, unknown>
184
- > extends ModuleDef<TImports, TOptions> {
185
- service: <
186
- TDeps,
187
- TState,
188
- TMethods
189
- >(config: ServiceDef<TDeps, TState, TMethods>) => NamedServiceDef<TDeps, TState, TMethods>;
190
- router: <TInject extends Record<string, unknown> = Record<string, unknown>>(config: RouterDef<TInject>) => NamedRouterDef<TMiddleware, TInject>;
191
- }
192
- declare function createModuleDef<
193
- TImports extends Record<string, unknown> = Record<string, unknown>,
194
- TOptions extends Record<string, unknown> = Record<string, unknown>,
195
- TMiddleware extends Record<string, unknown> = Record<string, unknown>
196
- >(config: ModuleDef<TImports, TOptions>): NamedModuleDef<TImports, TOptions, TMiddleware>;
197
- interface NamedModule {
198
- definition: NamedModuleDef;
199
- services: NamedServiceDef[];
200
- routers: NamedRouterDef<any, any>[];
201
- exports: NamedServiceDef[];
202
- }
203
- declare function createModule(definition: NamedModuleDef, config: {
204
- services: NamedServiceDef[];
205
- routers: NamedRouterDef<any, any>[];
206
- exports: NamedServiceDef[];
207
- }): NamedModule;
208
42
  interface CorsConfig {
209
43
  origins?: string | string[] | boolean;
210
44
  methods?: string[];
@@ -223,6 +57,13 @@ interface EntityDefinition {
223
57
  readonly actions: Record<string, unknown>;
224
58
  readonly relations: Record<string, unknown>;
225
59
  }
60
+ type SchemaLike = {
61
+ parse(value: unknown): {
62
+ ok: boolean;
63
+ data?: unknown;
64
+ error?: unknown;
65
+ };
66
+ };
226
67
  /**
227
68
  * An entity route entry generated by @vertz/server's route generator.
228
69
  * Core doesn't know about entity internals — it just registers these as handlers.
@@ -230,7 +71,13 @@ interface EntityDefinition {
230
71
  interface EntityRouteEntry {
231
72
  method: string;
232
73
  path: string;
233
- handler: (ctx: Record<string, unknown>) => Promise<Response>;
74
+ handler: (ctx: Record<string, unknown>) => unknown;
75
+ paramsSchema?: SchemaLike;
76
+ bodySchema?: SchemaLike;
77
+ querySchema?: SchemaLike;
78
+ headersSchema?: SchemaLike;
79
+ responseSchema?: SchemaLike;
80
+ errorsSchema?: Record<number, SchemaLike>;
234
81
  }
235
82
  interface AppConfig {
236
83
  basePath?: string;
@@ -263,8 +110,7 @@ interface RouteInfo {
263
110
  method: string;
264
111
  path: string;
265
112
  }
266
- interface AppBuilder<TMiddlewareCtx extends Record<string, unknown> = Record<string, unknown>> {
267
- register(module: NamedModule, options?: Record<string, unknown>): AppBuilder<TMiddlewareCtx>;
113
+ interface AppBuilder<_TMiddlewareCtx extends Record<string, unknown> = Record<string, unknown>> {
268
114
  middlewares<const M extends readonly NamedMiddlewareDef<any, any>[]>(list: M): AppBuilder<AccumulateProvides<M>>;
269
115
  readonly handler: (request: Request) => Promise<Response>;
270
116
  listen(port?: number, options?: ListenOptions): Promise<ServerHandle>;
@@ -274,36 +120,31 @@ interface AppBuilder<TMiddlewareCtx extends Record<string, unknown> = Record<str
274
120
  };
275
121
  }
276
122
  declare function createApp(config: AppConfig): AppBuilder;
277
- type ServiceFactory<
278
- TDeps = unknown,
279
- TState = unknown,
280
- TMethods = unknown,
281
- TOptions extends Record<string, unknown> = Record<string, unknown>,
282
- TEnv extends Record<string, unknown> = Record<string, unknown>
283
- > = ServiceDef<TDeps, TState, TMethods, TOptions, TEnv>;
284
- interface ServiceBootInstruction {
285
- type: "service";
286
- id: string;
287
- deps: string[];
288
- factory: ServiceFactory;
289
- options?: Record<string, unknown>;
290
- env?: Record<string, unknown>;
291
- }
292
- interface ModuleBootInstruction {
293
- type: "module";
294
- id: string;
295
- services: string[];
296
- options?: Record<string, unknown>;
123
+ type Primitive = string | number | boolean | bigint | symbol | undefined | null;
124
+ type BuiltinObject = Date | RegExp | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | Request | Response | Headers | ReadableStream | WritableStream;
125
+ type DeepReadonly<T> = unknown extends T ? T : T extends Primitive ? T : T extends BuiltinObject ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> : { readonly [K in keyof T] : DeepReadonly<T[K]> };
126
+ interface RawRequest {
127
+ readonly request: Request;
128
+ readonly method: string;
129
+ readonly url: string;
130
+ readonly headers: Headers;
297
131
  }
298
- type BootInstruction = ServiceBootInstruction | ModuleBootInstruction;
299
- interface BootSequence {
300
- instructions: BootInstruction[];
301
- shutdownOrder: string[];
132
+ interface HandlerCtx {
133
+ params: Record<string, unknown>;
134
+ body: unknown;
135
+ query: Record<string, string>;
136
+ headers: Record<string, unknown>;
137
+ raw: RawRequest;
138
+ options: Record<string, unknown>;
139
+ env: Record<string, unknown>;
140
+ [key: string]: unknown;
302
141
  }
303
- import { Schema as Schema3 } from "@vertz/schema";
142
+ type Deps<T extends Record<string, unknown>> = DeepReadonly<T>;
143
+ type Ctx<T extends Record<string, unknown>> = DeepReadonly<T>;
144
+ import { Schema as Schema2 } from "@vertz/schema";
304
145
  interface EnvConfig<T = unknown> {
305
146
  load?: string[];
306
- schema: Schema3<T>;
147
+ schema: Schema2<T>;
307
148
  env?: Record<string, string | undefined>;
308
149
  }
309
150
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
@@ -370,8 +211,6 @@ declare function makeImmutable<T extends object>(obj: T, contextName: string): D
370
211
  declare const vertz: {
371
212
  readonly env: typeof createEnv;
372
213
  readonly middleware: typeof createMiddleware;
373
- readonly moduleDef: typeof createModuleDef;
374
- readonly module: typeof createModule;
375
214
  readonly app: typeof createApp;
376
215
  /** @since 0.2.0 — preferred alias for `app` */
377
216
  readonly server: typeof createApp;
@@ -385,4 +224,4 @@ declare const createServer: (config: AppConfig) => AppBuilder;
385
224
  * @deprecated Use `createServer` instead. `createApp` will be removed in v0.3.0.
386
225
  */
387
226
  declare const createApp2: (config: AppConfig) => AppBuilder;
388
- export { vertz, makeImmutable, deepFreeze, createServer, createModuleDef, createModule, createMiddleware, createImmutableProxy, createEnv, createApp2 as createApp, VertzException, ValidationException, UnauthorizedException, ServiceUnavailableException, ServiceFactory, ServiceDef, ServiceBootInstruction, ServerHandle, ServerAdapter, RouterDef, RouteInfo, ResolveInjectMap, RawRequest, NotFoundException, NamedServiceDef, NamedRouterDef, NamedModuleDef, NamedModule, NamedMiddlewareDef, ModuleDef, ModuleBootInstruction, Module, MiddlewareDef, ListenOptions, InternalServerErrorException, Infer2 as InferSchema, Infer, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, ExtractMethods, EnvConfig, EntityRouteEntry, Deps, DeepReadonly, Ctx, CorsConfig, ConflictException, BootSequence, BootInstruction, BadRequestException, AppConfig, AppBuilder, AccumulateProvides };
227
+ export { vertz, makeImmutable, deepFreeze, createServer, createMiddleware, createImmutableProxy, createEnv, createApp2 as createApp, VertzException, ValidationException, UnauthorizedException, ServiceUnavailableException, ServerHandle, ServerAdapter, RouteInfo, RawRequest, NotFoundException, NamedMiddlewareDef, MiddlewareDef, ListenOptions, InternalServerErrorException, Infer2 as InferSchema, Infer, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, EnvConfig, EntityRouteEntry, Deps, DeepReadonly, Ctx, CorsConfig, ConflictException, BadRequestException, AppConfig, AppBuilder, AccumulateProvides };
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  parseBody,
19
19
  parseRequest,
20
20
  runMiddlewareChain
21
- } from "./shared/chunk-k596zpc6.js";
21
+ } from "./shared/chunk-m3w3ytn5.js";
22
22
 
23
23
  // src/result.ts
24
24
  var RESULT_BRAND = Symbol.for("vertz.result");
@@ -49,27 +49,34 @@ function resolveOrigin(config, requestOrigin) {
49
49
  return null;
50
50
  }
51
51
  function handleCors(config, request) {
52
+ if (config.credentials && (config.origins === true || config.origins === "*")) {
53
+ throw new Error("CORS misconfiguration: credentials cannot be used with wildcard origins. " + "Browsers will reject the response. Use an explicit origin allowlist instead.");
54
+ }
52
55
  const requestOrigin = request.headers.get("origin");
53
56
  const origin = resolveOrigin(config, requestOrigin);
54
57
  if (request.method === "OPTIONS") {
55
58
  const headers = {};
56
- if (origin)
59
+ if (origin) {
57
60
  headers["access-control-allow-origin"] = origin;
58
- const methods = config.methods ?? DEFAULT_METHODS;
59
- headers["access-control-allow-methods"] = methods.join(", ");
60
- const allowHeaders = config.headers ?? DEFAULT_HEADERS;
61
- headers["access-control-allow-headers"] = allowHeaders.join(", ");
62
- if (config.maxAge !== undefined) {
63
- headers["access-control-max-age"] = String(config.maxAge);
64
- }
65
- if (config.credentials) {
66
- headers["access-control-allow-credentials"] = "true";
61
+ const methods = config.methods ?? DEFAULT_METHODS;
62
+ headers["access-control-allow-methods"] = methods.join(", ");
63
+ const allowHeaders = config.headers ?? DEFAULT_HEADERS;
64
+ headers["access-control-allow-headers"] = allowHeaders.join(", ");
65
+ if (config.maxAge !== undefined) {
66
+ headers["access-control-max-age"] = String(config.maxAge);
67
+ }
68
+ if (config.credentials) {
69
+ headers["access-control-allow-credentials"] = "true";
70
+ }
67
71
  }
68
72
  return new Response(null, { status: 204, headers });
69
73
  }
70
74
  return null;
71
75
  }
72
76
  function applyCorsHeaders(config, request, response) {
77
+ if (config.credentials && (config.origins === true || config.origins === "*")) {
78
+ throw new Error("CORS misconfiguration: credentials cannot be used with wildcard origins. " + "Browsers will reject the response. Use an explicit origin allowlist instead.");
79
+ }
73
80
  const requestOrigin = request.headers.get("origin");
74
81
  const origin = resolveOrigin(config, requestOrigin);
75
82
  if (!origin)
@@ -97,35 +104,41 @@ function createResponseWithCors(data, status, config, request) {
97
104
  }
98
105
  return response;
99
106
  }
107
+ var INTERNAL_DETAIL_PATTERNS = [
108
+ /(?:^|[\s:])\/[\w./-]+/,
109
+ /[A-Z]:\\[\w\\.-]+/,
110
+ /node_modules/,
111
+ /at\s+\w+\s+\(/,
112
+ /\.ts:\d+:\d+/,
113
+ /\.js:\d+:\d+/
114
+ ];
115
+ function sanitizeValidationMessage(error, label) {
116
+ if (error != null && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
117
+ const issues = error.issues;
118
+ const messages = issues.map((issue) => typeof issue.message === "string" ? issue.message : "").filter(Boolean);
119
+ if (messages.length > 0) {
120
+ return messages.join(", ");
121
+ }
122
+ }
123
+ const message = error instanceof Error ? error.message : "";
124
+ if (!message) {
125
+ return `Invalid ${label}`;
126
+ }
127
+ for (const pattern of INTERNAL_DETAIL_PATTERNS) {
128
+ if (pattern.test(message)) {
129
+ return `Invalid ${label}`;
130
+ }
131
+ }
132
+ return message;
133
+ }
100
134
  function validateSchema(schema, value, label) {
101
135
  const result = schema.parse(value);
102
136
  if (!result.ok) {
103
- const message = result.error instanceof Error ? result.error.message : `Invalid ${label}`;
137
+ const message = sanitizeValidationMessage(result.error, label);
104
138
  throw new BadRequestException(message);
105
139
  }
106
140
  return result.data;
107
141
  }
108
- function resolveServices(registrations) {
109
- const serviceMap = new Map;
110
- for (const { module, options } of registrations) {
111
- for (const service of module.services) {
112
- if (!serviceMap.has(service)) {
113
- let parsedOptions = {};
114
- if (service.options && options) {
115
- const parsed = service.options.safeParse(options);
116
- if (parsed.ok) {
117
- parsedOptions = parsed.data;
118
- } else {
119
- throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
120
- }
121
- }
122
- const env = {};
123
- serviceMap.set(service, service.methods({}, undefined, parsedOptions, env));
124
- }
125
- }
126
- }
127
- return serviceMap;
128
- }
129
142
  function resolveMiddlewares(globalMiddlewares) {
130
143
  return globalMiddlewares.map((mw) => ({
131
144
  name: mw.name,
@@ -133,58 +146,22 @@ function resolveMiddlewares(globalMiddlewares) {
133
146
  resolvedInject: {}
134
147
  }));
135
148
  }
136
- function resolveRouterServices(inject, serviceMap) {
137
- if (!inject)
138
- return {};
139
- const resolved = {};
140
- for (const [name, serviceDef] of Object.entries(inject)) {
141
- const methods = serviceMap.get(serviceDef);
142
- if (methods)
143
- resolved[name] = methods;
144
- }
145
- return resolved;
146
- }
147
- function registerRoutes(trie, basePath, registrations, serviceMap) {
148
- for (const { module, options } of registrations) {
149
- for (const router of module.routers) {
150
- const resolvedServices = resolveRouterServices(router.inject, serviceMap);
151
- for (const route of router.routes) {
152
- const fullPath = basePath + router.prefix + route.path;
153
- const routeMiddlewares = (route.config.middlewares ?? []).map((mw) => ({
154
- name: mw.name,
155
- handler: mw.handler,
156
- resolvedInject: {}
157
- }));
158
- const entry = {
159
- handler: route.config.handler,
160
- options: options ?? {},
161
- services: resolvedServices,
162
- middlewares: routeMiddlewares,
163
- paramsSchema: route.config.params,
164
- bodySchema: route.config.body,
165
- querySchema: route.config.query,
166
- headersSchema: route.config.headers,
167
- responseSchema: route.config.response,
168
- errorsSchema: route.config.errors
169
- };
170
- trie.add(route.method, fullPath, entry);
171
- }
172
- }
173
- }
174
- }
175
- function buildHandler(config, registrations, globalMiddlewares) {
149
+ function buildHandler(config, globalMiddlewares) {
176
150
  const trie = new Trie;
177
- const basePath = config.basePath ?? "";
178
151
  const resolvedMiddlewares = resolveMiddlewares(globalMiddlewares);
179
- const serviceMap = resolveServices(registrations);
180
- registerRoutes(trie, basePath, registrations, serviceMap);
181
152
  if (config._entityRoutes) {
182
153
  for (const route of config._entityRoutes) {
183
154
  const entry = {
184
155
  handler: route.handler,
185
156
  options: {},
186
157
  services: {},
187
- middlewares: []
158
+ middlewares: [],
159
+ paramsSchema: route.paramsSchema,
160
+ bodySchema: route.bodySchema,
161
+ querySchema: route.querySchema,
162
+ headersSchema: route.headersSchema,
163
+ responseSchema: route.responseSchema,
164
+ errorsSchema: route.errorsSchema
188
165
  };
189
166
  trie.add(route.method, route.path, entry);
190
167
  }
@@ -224,7 +201,11 @@ function buildHandler(config, registrations, globalMiddlewares) {
224
201
  if (entry.middlewares.length > 0) {
225
202
  const routeCtx = { ...requestCtx, ...middlewareState };
226
203
  const routeState = await runMiddlewareChain(entry.middlewares, routeCtx);
227
- Object.assign(middlewareState, routeState);
204
+ for (const key of Object.keys(routeState)) {
205
+ if (key !== "__proto__" && key !== "constructor" && key !== "prototype") {
206
+ middlewareState[key] = routeState[key];
207
+ }
208
+ }
228
209
  }
229
210
  const validatedParams = entry.paramsSchema ? validateSchema(entry.paramsSchema, match.params, "params") : match.params;
230
211
  const validatedBody = entry.bodySchema ? validateSchema(entry.bodySchema, body, "body") : body;
@@ -320,27 +301,6 @@ function detectAdapter(hints) {
320
301
  }
321
302
 
322
303
  // src/app/route-log.ts
323
- function normalizePath(path) {
324
- let normalized = path.replace(/\/+/g, "/");
325
- if (normalized.length > 1 && normalized.endsWith("/")) {
326
- normalized = normalized.slice(0, -1);
327
- }
328
- return normalized || "/";
329
- }
330
- function collectRoutes(basePath, registrations) {
331
- const routes = [];
332
- for (const { module } of registrations) {
333
- for (const router of module.routers) {
334
- for (const route of router.routes) {
335
- routes.push({
336
- method: route.method,
337
- path: normalizePath(basePath + router.prefix + route.path)
338
- });
339
- }
340
- }
341
- }
342
- return routes;
343
- }
344
304
  function formatRouteLog(listenUrl, routes) {
345
305
  const header = `vertz server listening on ${listenUrl}`;
346
306
  if (routes.length === 0) {
@@ -360,29 +320,18 @@ function formatRouteLog(listenUrl, routes) {
360
320
  // src/app/app-builder.ts
361
321
  var DEFAULT_PORT = 3000;
362
322
  function createApp(config) {
363
- const registrations = [];
364
323
  let globalMiddlewares = [];
365
324
  let cachedHandler = null;
366
325
  const registeredRoutes = [];
367
326
  const entityRoutes = [];
368
327
  const builder = {
369
- register(module, options) {
370
- registrations.push({ module, options });
371
- for (const router of module.routers) {
372
- for (const route of router.routes) {
373
- registeredRoutes.push({ method: route.method, path: router.prefix + route.path });
374
- }
375
- }
376
- cachedHandler = null;
377
- return builder;
378
- },
379
328
  middlewares(list) {
380
329
  globalMiddlewares = [...list];
381
330
  return builder;
382
331
  },
383
332
  get handler() {
384
333
  if (!cachedHandler) {
385
- cachedHandler = buildHandler(config, registrations, globalMiddlewares);
334
+ cachedHandler = buildHandler(config, globalMiddlewares);
386
335
  }
387
336
  return cachedHandler;
388
337
  },
@@ -393,10 +342,8 @@ function createApp(config) {
393
342
  const adapter = detectAdapter();
394
343
  const serverHandle = await adapter.listen(port ?? DEFAULT_PORT, builder.handler, options);
395
344
  if (options?.logRoutes !== false) {
396
- const moduleRoutes = collectRoutes(config.basePath ?? "", registrations);
397
- const routes = [...moduleRoutes, ...entityRoutes];
398
345
  const url = `http://${serverHandle.hostname}:${serverHandle.port}`;
399
- console.log(formatRouteLog(url, routes));
346
+ console.log(formatRouteLog(url, entityRoutes));
400
347
  }
401
348
  return serverHandle;
402
349
  }
@@ -443,72 +390,10 @@ ${result.error.message}`);
443
390
  function createMiddleware(def) {
444
391
  return deepFreeze(def);
445
392
  }
446
- // src/module/module.ts
447
- function validateOwnership(items, kind, expectedModule) {
448
- for (const item of items) {
449
- if (item.moduleName !== expectedModule) {
450
- throw new Error(`${kind} belongs to module "${item.moduleName}", cannot add to module "${expectedModule}"`);
451
- }
452
- }
453
- }
454
- function createModule(definition, config) {
455
- validateOwnership(config.services, "Service", definition.name);
456
- validateOwnership(config.routers, "Router", definition.name);
457
- for (const exp of config.exports) {
458
- if (!config.services.includes(exp)) {
459
- throw new Error("exports must be a subset of services");
460
- }
461
- }
462
- return deepFreeze({
463
- definition,
464
- ...config
465
- });
466
- }
467
- // src/module/router-def.ts
468
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head"];
469
- function createRouterDef(moduleName, config) {
470
- const routes = [];
471
- function addRoute(method, path, routeConfig) {
472
- if (!path.startsWith("/")) {
473
- throw new Error(`Route path must start with '/', got '${path}'`);
474
- }
475
- routes.push({ method, path, config: routeConfig });
476
- return router;
477
- }
478
- const router = {
479
- ...config,
480
- moduleName,
481
- routes
482
- };
483
- for (const method of HTTP_METHODS) {
484
- router[method] = (path, cfg) => addRoute(method.toUpperCase(), path, cfg);
485
- }
486
- return router;
487
- }
488
-
489
- // src/module/service.ts
490
- function createServiceDef(moduleName, config) {
491
- return deepFreeze({
492
- ...config,
493
- moduleName
494
- });
495
- }
496
-
497
- // src/module/module-def.ts
498
- function createModuleDef(config) {
499
- const def = {
500
- ...config,
501
- service: (serviceConfig) => createServiceDef(config.name, serviceConfig),
502
- router: (routerConfig) => createRouterDef(config.name, routerConfig)
503
- };
504
- return deepFreeze(def);
505
- }
506
393
  // src/vertz.ts
507
394
  var vertz = /* @__PURE__ */ deepFreeze({
508
395
  env: createEnv,
509
396
  middleware: createMiddleware,
510
- moduleDef: createModuleDef,
511
- module: createModule,
512
397
  app: createApp,
513
398
  server: createApp
514
399
  });
@@ -524,8 +409,6 @@ export {
524
409
  makeImmutable,
525
410
  deepFreeze,
526
411
  createServer,
527
- createModuleDef,
528
- createModule,
529
412
  createMiddleware,
530
413
  createImmutableProxy,
531
414
  createEnv,
@@ -53,7 +53,7 @@ interface ParsedRequest {
53
53
  raw: Request;
54
54
  }
55
55
  declare function parseRequest(request: Request): ParsedRequest;
56
- declare function parseBody(request: Request): Promise<unknown>;
56
+ declare function parseBody(request: Request, maxBodySize?: number): Promise<unknown>;
57
57
  declare function createJsonResponse(data: unknown, status?: number, headers?: Record<string, string>): Response;
58
58
  declare function createErrorResponse(error: unknown): Response;
59
59
  export { runMiddlewareChain, parseRequest, parseBody, createJsonResponse, createErrorResponse, buildCtx, Trie, ResolvedMiddleware };
package/dist/internals.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  parseBody,
7
7
  parseRequest,
8
8
  runMiddlewareChain
9
- } from "./shared/chunk-k596zpc6.js";
9
+ } from "./shared/chunk-m3w3ytn5.js";
10
10
  export {
11
11
  runMiddlewareChain,
12
12
  parseRequest,
@@ -167,16 +167,24 @@ class ServiceUnavailableException extends VertzException {
167
167
  }
168
168
  }
169
169
  // src/middleware/middleware-runner.ts
170
+ var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
170
171
  function isPlainObject(value) {
171
172
  return value !== null && typeof value === "object" && !Array.isArray(value);
172
173
  }
174
+ function safeAssign(target, source) {
175
+ for (const key of Object.keys(source)) {
176
+ if (!DANGEROUS_KEYS.has(key)) {
177
+ target[key] = source[key];
178
+ }
179
+ }
180
+ }
173
181
  async function runMiddlewareChain(middlewares, requestCtx) {
174
- const accumulated = {};
182
+ const accumulated = Object.create(null);
175
183
  for (const mw of middlewares) {
176
184
  const ctx = { ...requestCtx, ...mw.resolvedInject, ...accumulated };
177
185
  const contribution = await mw.handler(ctx);
178
186
  if (isPlainObject(contribution)) {
179
- Object.assign(accumulated, contribution);
187
+ safeAssign(accumulated, contribution);
180
188
  }
181
189
  }
182
190
  return accumulated;
@@ -300,11 +308,16 @@ function parseRequest(request) {
300
308
  method: request.method,
301
309
  path: url.pathname,
302
310
  query: Object.fromEntries(url.searchParams),
303
- headers: Object.fromEntries(request.headers),
311
+ headers: Object.fromEntries([...request.headers].map(([k, v]) => [k.toLowerCase(), v])),
304
312
  raw: request
305
313
  };
306
314
  }
307
- async function parseBody(request) {
315
+ var DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
316
+ async function parseBody(request, maxBodySize = DEFAULT_MAX_BODY_SIZE) {
317
+ const contentLength = parseInt(request.headers.get("content-length") ?? "0", 10);
318
+ if (maxBodySize && contentLength > maxBodySize) {
319
+ throw new BadRequestException("Request body too large");
320
+ }
308
321
  const contentType = request.headers.get("content-type") ?? "";
309
322
  if (contentType.includes("application/json")) {
310
323
  try {
@@ -328,7 +341,7 @@ function createJsonResponse(data, status = 200, headers) {
328
341
  return new Response(JSON.stringify(data), {
329
342
  status,
330
343
  headers: {
331
- "content-type": "application/json",
344
+ "content-type": "application/json; charset=utf-8",
332
345
  ...headers
333
346
  }
334
347
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz core framework primitives",
@@ -36,12 +36,12 @@
36
36
  "typecheck": "tsc --noEmit"
37
37
  },
38
38
  "dependencies": {
39
- "@vertz/schema": "0.2.1"
39
+ "@vertz/schema": "0.2.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^25.3.1",
43
43
  "@vitest/coverage-v8": "^4.0.18",
44
- "bunup": "latest",
44
+ "bunup": "^0.16.31",
45
45
  "typescript": "^5.7.0",
46
46
  "vitest": "^4.0.18"
47
47
  },