@vertz/core 0.0.2

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.
@@ -0,0 +1,270 @@
1
+ import { Schema } from "@vertz/schema";
2
+ interface MiddlewareDef<
3
+ TRequires extends Record<string, unknown> = Record<string, unknown>,
4
+ TProvides extends Record<string, unknown> = Record<string, unknown>
5
+ > {
6
+ inject?: Record<string, unknown>;
7
+ headers?: Schema<unknown>;
8
+ params?: Schema<unknown>;
9
+ query?: Schema<unknown>;
10
+ body?: Schema<unknown>;
11
+ requires?: Schema<TRequires>;
12
+ provides?: Schema<TProvides>;
13
+ handler: (ctx: Record<string, unknown>) => Promise<TProvides> | TProvides;
14
+ }
15
+ interface NamedMiddlewareDef<
16
+ TRequires extends Record<string, unknown> = Record<string, unknown>,
17
+ TProvides extends Record<string, unknown> = Record<string, unknown>
18
+ > extends MiddlewareDef<TRequires, TProvides> {
19
+ name: string;
20
+ }
21
+ declare function createMiddleware<
22
+ TRequires extends Record<string, unknown> = Record<string, unknown>,
23
+ TProvides extends Record<string, unknown> = Record<string, unknown>
24
+ >(def: NamedMiddlewareDef<TRequires, TProvides>): NamedMiddlewareDef<TRequires, TProvides>;
25
+ import { Schema as Schema2 } from "@vertz/schema";
26
+ interface ModuleDef<
27
+ TImports extends Record<string, unknown> = Record<string, unknown>,
28
+ TOptions extends Record<string, unknown> = Record<string, unknown>
29
+ > {
30
+ name: string;
31
+ imports?: TImports;
32
+ options?: Schema2<TOptions>;
33
+ }
34
+ interface ServiceDef<
35
+ TDeps = unknown,
36
+ TState = unknown,
37
+ TMethods = unknown
38
+ > {
39
+ inject?: Record<string, unknown>;
40
+ onInit?: (deps: TDeps) => Promise<TState> | TState;
41
+ methods: (deps: TDeps, state: TState) => TMethods;
42
+ onDestroy?: (deps: TDeps, state: TState) => Promise<void> | void;
43
+ }
44
+ interface RouterDef {
45
+ prefix: string;
46
+ inject?: Record<string, unknown>;
47
+ }
48
+ interface Module<TDef extends ModuleDef = ModuleDef> {
49
+ definition: TDef;
50
+ services: ServiceDef[];
51
+ routers: RouterDef[];
52
+ exports: ServiceDef[];
53
+ }
54
+ type Primitive = string | number | boolean | bigint | symbol | undefined | null;
55
+ type BuiltinObject = Date | RegExp | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | Request | Response | Headers | ReadableStream | WritableStream;
56
+ 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]> };
57
+ interface RawRequest {
58
+ readonly request: Request;
59
+ readonly method: string;
60
+ readonly url: string;
61
+ readonly headers: Headers;
62
+ }
63
+ interface HandlerCtx {
64
+ params: Record<string, unknown>;
65
+ body: unknown;
66
+ query: Record<string, unknown>;
67
+ headers: Record<string, unknown>;
68
+ raw: RawRequest;
69
+ options: Record<string, unknown>;
70
+ env: Record<string, unknown>;
71
+ [key: string]: unknown;
72
+ }
73
+ type Deps<T extends Record<string, unknown>> = DeepReadonly<T>;
74
+ type Ctx<T extends Record<string, unknown>> = DeepReadonly<T>;
75
+ type InferOutput<T> = T extends {
76
+ _output: infer O;
77
+ } ? O : T extends {
78
+ parse(v: unknown): infer P;
79
+ } ? P : unknown;
80
+ type TypedHandlerCtx<
81
+ TParams = unknown,
82
+ TQuery = unknown,
83
+ THeaders = unknown,
84
+ TBody = unknown
85
+ > = Omit<HandlerCtx, "params" | "query" | "headers" | "body"> & {
86
+ params: TParams;
87
+ query: TQuery;
88
+ headers: THeaders;
89
+ body: TBody;
90
+ };
91
+ interface RouteConfig<
92
+ TParams = unknown,
93
+ TQuery = unknown,
94
+ THeaders = unknown,
95
+ TBody = unknown
96
+ > {
97
+ params?: TParams;
98
+ body?: TBody;
99
+ query?: TQuery;
100
+ response?: unknown;
101
+ headers?: THeaders;
102
+ middlewares?: unknown[];
103
+ handler: (ctx: TypedHandlerCtx<InferOutput<TParams>, InferOutput<TQuery>, InferOutput<THeaders>, InferOutput<TBody>>) => unknown;
104
+ }
105
+ interface Route {
106
+ method: string;
107
+ path: string;
108
+ config: RouteConfig<unknown, unknown, unknown, unknown>;
109
+ }
110
+ type HttpMethodFn = <
111
+ TParams,
112
+ TQuery,
113
+ THeaders,
114
+ TBody
115
+ >(path: `/${string}`, config: RouteConfig<TParams, TQuery, THeaders, TBody>) => NamedRouterDef;
116
+ interface NamedRouterDef extends RouterDef {
117
+ moduleName: string;
118
+ routes: Route[];
119
+ get: HttpMethodFn;
120
+ post: HttpMethodFn;
121
+ put: HttpMethodFn;
122
+ patch: HttpMethodFn;
123
+ delete: HttpMethodFn;
124
+ head: HttpMethodFn;
125
+ }
126
+ interface NamedServiceDef<
127
+ TDeps = unknown,
128
+ TState = unknown,
129
+ TMethods = unknown
130
+ > extends ServiceDef<TDeps, TState, TMethods> {
131
+ moduleName: string;
132
+ }
133
+ interface NamedModuleDef<
134
+ TImports extends Record<string, unknown> = Record<string, unknown>,
135
+ TOptions extends Record<string, unknown> = Record<string, unknown>
136
+ > extends ModuleDef<TImports, TOptions> {
137
+ service: <
138
+ TDeps,
139
+ TState,
140
+ TMethods
141
+ >(config: ServiceDef<TDeps, TState, TMethods>) => NamedServiceDef<TDeps, TState, TMethods>;
142
+ router: (config: RouterDef) => NamedRouterDef;
143
+ }
144
+ declare function createModuleDef<
145
+ TImports extends Record<string, unknown> = Record<string, unknown>,
146
+ TOptions extends Record<string, unknown> = Record<string, unknown>
147
+ >(config: ModuleDef<TImports, TOptions>): NamedModuleDef<TImports, TOptions>;
148
+ interface NamedModule {
149
+ definition: NamedModuleDef;
150
+ services: NamedServiceDef[];
151
+ routers: NamedRouterDef[];
152
+ exports: NamedServiceDef[];
153
+ }
154
+ declare function createModule(definition: NamedModuleDef, config: {
155
+ services: NamedServiceDef[];
156
+ routers: NamedRouterDef[];
157
+ exports: NamedServiceDef[];
158
+ }): NamedModule;
159
+ interface CorsConfig {
160
+ origins?: string | string[] | boolean;
161
+ methods?: string[];
162
+ headers?: string[];
163
+ credentials?: boolean;
164
+ maxAge?: number;
165
+ exposedHeaders?: string[];
166
+ }
167
+ interface AppConfig {
168
+ basePath?: string;
169
+ version?: string;
170
+ cors?: CorsConfig;
171
+ }
172
+ interface ListenOptions {
173
+ hostname?: string;
174
+ }
175
+ interface ServerHandle {
176
+ readonly port: number;
177
+ readonly hostname: string;
178
+ close(): Promise<void>;
179
+ }
180
+ interface ServerAdapter {
181
+ listen(port: number, handler: (request: Request) => Promise<Response>, options?: ListenOptions): Promise<ServerHandle>;
182
+ }
183
+ interface AppBuilder {
184
+ register(module: NamedModule, options?: Record<string, unknown>): AppBuilder;
185
+ middlewares(list: NamedMiddlewareDef[]): AppBuilder;
186
+ readonly handler: (request: Request) => Promise<Response>;
187
+ listen(port?: number, options?: ListenOptions): Promise<ServerHandle>;
188
+ }
189
+ declare function createApp(config: AppConfig): AppBuilder;
190
+ import { Schema as Schema3 } from "@vertz/schema";
191
+ interface EnvConfig<T = unknown> {
192
+ load?: string[];
193
+ schema: Schema3<T>;
194
+ }
195
+ declare function createEnv<T>(config: EnvConfig<T>): T;
196
+ declare class VertzException extends Error {
197
+ readonly statusCode: number;
198
+ readonly code: string;
199
+ readonly details?: unknown;
200
+ constructor(message: string, statusCode?: number, code?: string, details?: unknown);
201
+ toJSON(): Record<string, unknown>;
202
+ }
203
+ declare class BadRequestException extends VertzException {
204
+ constructor(message: string, details?: unknown);
205
+ }
206
+ declare class UnauthorizedException extends VertzException {
207
+ constructor(message: string, details?: unknown);
208
+ }
209
+ declare class ForbiddenException extends VertzException {
210
+ constructor(message: string, details?: unknown);
211
+ }
212
+ declare class NotFoundException extends VertzException {
213
+ constructor(message: string, details?: unknown);
214
+ }
215
+ declare class ConflictException extends VertzException {
216
+ constructor(message: string, details?: unknown);
217
+ }
218
+ declare class ValidationException extends VertzException {
219
+ readonly errors: ReadonlyArray<{
220
+ path: string;
221
+ message: string;
222
+ }>;
223
+ constructor(errors: Array<{
224
+ path: string;
225
+ message: string;
226
+ }>);
227
+ toJSON(): Record<string, unknown>;
228
+ }
229
+ declare class InternalServerErrorException extends VertzException {
230
+ constructor(message: string, details?: unknown);
231
+ }
232
+ declare class ServiceUnavailableException extends VertzException {
233
+ constructor(message: string, details?: unknown);
234
+ }
235
+ declare function createImmutableProxy<T extends object>(obj: T, contextName: string, rootName?: string, proxyCache?: WeakMap<object, unknown>): T;
236
+ declare function deepFreeze<T>(obj: T, visited?: WeakSet<object>): T;
237
+ declare function makeImmutable<T extends object>(obj: T, contextName: string): DeepReadonly<T>;
238
+ type ServiceFactory<
239
+ TDeps = unknown,
240
+ TState = unknown,
241
+ TMethods = unknown
242
+ > = ServiceDef<TDeps, TState, TMethods>;
243
+ interface ServiceBootInstruction {
244
+ type: "service";
245
+ id: string;
246
+ deps: string[];
247
+ factory: ServiceFactory;
248
+ }
249
+ interface ModuleBootInstruction {
250
+ type: "module";
251
+ id: string;
252
+ services: string[];
253
+ options?: Record<string, unknown>;
254
+ }
255
+ type BootInstruction = ServiceBootInstruction | ModuleBootInstruction;
256
+ interface BootSequence {
257
+ instructions: BootInstruction[];
258
+ shutdownOrder: string[];
259
+ }
260
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
261
+ type HttpStatusCode = 200 | 201 | 204 | 301 | 302 | 304 | 400 | 401 | 403 | 404 | 405 | 409 | 422 | 429 | 500 | 502 | 503 | 504;
262
+ import { Infer, Infer as Infer2 } from "@vertz/schema";
263
+ declare const vertz: {
264
+ readonly env: typeof createEnv;
265
+ readonly middleware: typeof createMiddleware;
266
+ readonly moduleDef: typeof createModuleDef;
267
+ readonly module: typeof createModule;
268
+ readonly app: typeof createApp;
269
+ };
270
+ export { vertz, makeImmutable, deepFreeze, createModuleDef, createModule, createMiddleware, createImmutableProxy, createEnv, createApp, VertzException, ValidationException, UnauthorizedException, ServiceUnavailableException, ServiceFactory, ServiceDef, ServiceBootInstruction, ServerHandle, ServerAdapter, RouterDef, RawRequest, NotFoundException, NamedServiceDef, NamedRouterDef, NamedModuleDef, NamedModule, NamedMiddlewareDef, ModuleDef, ModuleBootInstruction, Module, MiddlewareDef, ListenOptions, InternalServerErrorException, Infer2 as InferSchema, Infer, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, EnvConfig, Deps, DeepReadonly, Ctx, CorsConfig, ConflictException, BootSequence, BootInstruction, BadRequestException, AppConfig, AppBuilder };
package/dist/index.js ADDED
@@ -0,0 +1,365 @@
1
+ import {
2
+ BadRequestException,
3
+ ConflictException,
4
+ ForbiddenException,
5
+ InternalServerErrorException,
6
+ NotFoundException,
7
+ ServiceUnavailableException,
8
+ Trie,
9
+ UnauthorizedException,
10
+ ValidationException,
11
+ VertzException,
12
+ buildCtx,
13
+ createErrorResponse,
14
+ createImmutableProxy,
15
+ createJsonResponse,
16
+ deepFreeze,
17
+ makeImmutable,
18
+ parseBody,
19
+ parseRequest,
20
+ runMiddlewareChain
21
+ } from "./shared/chunk-c77pg5gx.js";
22
+
23
+ // src/server/cors.ts
24
+ var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
25
+ var DEFAULT_HEADERS = ["Content-Type", "Authorization"];
26
+ function resolveOrigin(config, requestOrigin) {
27
+ if (config.origins === true || config.origins === "*")
28
+ return "*";
29
+ if (!requestOrigin)
30
+ return null;
31
+ if (Array.isArray(config.origins)) {
32
+ return config.origins.includes(requestOrigin) ? requestOrigin : null;
33
+ }
34
+ if (typeof config.origins === "string") {
35
+ return config.origins === requestOrigin ? requestOrigin : null;
36
+ }
37
+ return null;
38
+ }
39
+ function handleCors(config, request) {
40
+ const requestOrigin = request.headers.get("origin");
41
+ const origin = resolveOrigin(config, requestOrigin);
42
+ if (request.method === "OPTIONS") {
43
+ const headers = {};
44
+ if (origin)
45
+ headers["access-control-allow-origin"] = origin;
46
+ const methods = config.methods ?? DEFAULT_METHODS;
47
+ headers["access-control-allow-methods"] = methods.join(", ");
48
+ const allowHeaders = config.headers ?? DEFAULT_HEADERS;
49
+ headers["access-control-allow-headers"] = allowHeaders.join(", ");
50
+ if (config.maxAge !== undefined) {
51
+ headers["access-control-max-age"] = String(config.maxAge);
52
+ }
53
+ if (config.credentials) {
54
+ headers["access-control-allow-credentials"] = "true";
55
+ }
56
+ return new Response(null, { status: 204, headers });
57
+ }
58
+ return null;
59
+ }
60
+ function applyCorsHeaders(config, request, response) {
61
+ const requestOrigin = request.headers.get("origin");
62
+ const origin = resolveOrigin(config, requestOrigin);
63
+ if (!origin)
64
+ return response;
65
+ const headers = new Headers(response.headers);
66
+ headers.set("access-control-allow-origin", origin);
67
+ if (config.credentials) {
68
+ headers.set("access-control-allow-credentials", "true");
69
+ }
70
+ if (config.exposedHeaders?.length) {
71
+ headers.set("access-control-expose-headers", config.exposedHeaders.join(", "));
72
+ }
73
+ return new Response(response.body, {
74
+ status: response.status,
75
+ statusText: response.statusText,
76
+ headers
77
+ });
78
+ }
79
+
80
+ // src/app/app-runner.ts
81
+ function validateSchema(schema, value, label) {
82
+ try {
83
+ return schema.parse(value);
84
+ } catch (error) {
85
+ if (error instanceof BadRequestException)
86
+ throw error;
87
+ const message = error instanceof Error ? error.message : `Invalid ${label}`;
88
+ throw new BadRequestException(message);
89
+ }
90
+ }
91
+ function resolveServices(registrations) {
92
+ const serviceMap = new Map;
93
+ for (const { module } of registrations) {
94
+ for (const service of module.services) {
95
+ if (!serviceMap.has(service)) {
96
+ serviceMap.set(service, service.methods({}, undefined));
97
+ }
98
+ }
99
+ }
100
+ return serviceMap;
101
+ }
102
+ function resolveMiddlewares(globalMiddlewares) {
103
+ return globalMiddlewares.map((mw) => ({
104
+ name: mw.name,
105
+ handler: mw.handler,
106
+ resolvedInject: {}
107
+ }));
108
+ }
109
+ function resolveRouterServices(inject, serviceMap) {
110
+ if (!inject)
111
+ return {};
112
+ const resolved = {};
113
+ for (const [name, serviceDef] of Object.entries(inject)) {
114
+ const methods = serviceMap.get(serviceDef);
115
+ if (methods)
116
+ resolved[name] = methods;
117
+ }
118
+ return resolved;
119
+ }
120
+ function registerRoutes(trie, basePath, registrations, serviceMap) {
121
+ for (const { module, options } of registrations) {
122
+ for (const router of module.routers) {
123
+ const resolvedServices = resolveRouterServices(router.inject, serviceMap);
124
+ for (const route of router.routes) {
125
+ const fullPath = basePath + router.prefix + route.path;
126
+ const entry = {
127
+ handler: route.config.handler,
128
+ options: options ?? {},
129
+ services: resolvedServices,
130
+ paramsSchema: route.config.params,
131
+ bodySchema: route.config.body,
132
+ querySchema: route.config.query,
133
+ headersSchema: route.config.headers
134
+ };
135
+ trie.add(route.method, fullPath, entry);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ function buildHandler(config, registrations, globalMiddlewares) {
141
+ const trie = new Trie;
142
+ const basePath = config.basePath ?? "";
143
+ const resolvedMiddlewares = resolveMiddlewares(globalMiddlewares);
144
+ const serviceMap = resolveServices(registrations);
145
+ registerRoutes(trie, basePath, registrations, serviceMap);
146
+ return async (request) => {
147
+ try {
148
+ if (config.cors) {
149
+ const corsResponse = handleCors(config.cors, request);
150
+ if (corsResponse)
151
+ return corsResponse;
152
+ }
153
+ const parsed = parseRequest(request);
154
+ const match = trie.match(parsed.method, parsed.path);
155
+ if (!match) {
156
+ const allowed = trie.getAllowedMethods(parsed.path);
157
+ if (allowed.length > 0) {
158
+ return createJsonResponse({ error: "MethodNotAllowed", message: "Method Not Allowed", statusCode: 405 }, 405, { allow: allowed.join(", ") });
159
+ }
160
+ return createJsonResponse({ error: "NotFound", message: "Not Found", statusCode: 404 }, 404);
161
+ }
162
+ const body = await parseBody(request);
163
+ const raw = {
164
+ request: parsed.raw,
165
+ method: parsed.method,
166
+ url: parsed.raw.url,
167
+ headers: parsed.raw.headers
168
+ };
169
+ const requestCtx = {
170
+ params: match.params,
171
+ body,
172
+ query: parsed.query,
173
+ headers: parsed.headers,
174
+ raw
175
+ };
176
+ const middlewareState = await runMiddlewareChain(resolvedMiddlewares, requestCtx);
177
+ const entry = match.handler;
178
+ const validatedParams = entry.paramsSchema ? validateSchema(entry.paramsSchema, match.params, "params") : match.params;
179
+ const validatedBody = entry.bodySchema ? validateSchema(entry.bodySchema, body, "body") : body;
180
+ const validatedQuery = entry.querySchema ? validateSchema(entry.querySchema, parsed.query, "query") : parsed.query;
181
+ const validatedHeaders = entry.headersSchema ? validateSchema(entry.headersSchema, parsed.headers, "headers") : parsed.headers;
182
+ const ctx = buildCtx({
183
+ params: validatedParams,
184
+ body: validatedBody,
185
+ query: validatedQuery,
186
+ headers: validatedHeaders,
187
+ raw,
188
+ middlewareState,
189
+ services: entry.services,
190
+ options: entry.options,
191
+ env: {}
192
+ });
193
+ const result = await entry.handler(ctx);
194
+ const response = result === undefined ? new Response(null, { status: 204 }) : createJsonResponse(result);
195
+ if (config.cors) {
196
+ return applyCorsHeaders(config.cors, request, response);
197
+ }
198
+ return response;
199
+ } catch (error) {
200
+ return createErrorResponse(error);
201
+ }
202
+ };
203
+ }
204
+
205
+ // src/app/bun-adapter.ts
206
+ function createBunAdapter() {
207
+ return {
208
+ async listen(port, handler, options) {
209
+ const server = Bun.serve({
210
+ port,
211
+ hostname: options?.hostname,
212
+ fetch: handler
213
+ });
214
+ return {
215
+ port: server.port,
216
+ hostname: server.hostname,
217
+ async close() {
218
+ server.stop(true);
219
+ }
220
+ };
221
+ }
222
+ };
223
+ }
224
+
225
+ // src/app/detect-adapter.ts
226
+ function detectRuntime() {
227
+ return { hasBun: "Bun" in globalThis };
228
+ }
229
+ function detectAdapter(hints) {
230
+ const runtime = hints ?? detectRuntime();
231
+ if (runtime.hasBun) {
232
+ return createBunAdapter();
233
+ }
234
+ throw new Error("No supported server runtime detected. Vertz requires Bun to use app.listen().");
235
+ }
236
+
237
+ // src/app/app-builder.ts
238
+ var DEFAULT_PORT = 3000;
239
+ function createApp(config) {
240
+ const registrations = [];
241
+ let globalMiddlewares = [];
242
+ let cachedHandler = null;
243
+ const builder = {
244
+ register(module, options) {
245
+ registrations.push({ module, options });
246
+ return builder;
247
+ },
248
+ middlewares(list) {
249
+ globalMiddlewares = list;
250
+ return builder;
251
+ },
252
+ get handler() {
253
+ if (!cachedHandler) {
254
+ cachedHandler = buildHandler(config, registrations, globalMiddlewares);
255
+ }
256
+ return cachedHandler;
257
+ },
258
+ async listen(port, options) {
259
+ const adapter = detectAdapter();
260
+ return adapter.listen(port ?? DEFAULT_PORT, builder.handler, options);
261
+ }
262
+ };
263
+ return builder;
264
+ }
265
+ // src/env/env-validator.ts
266
+ function createEnv(config) {
267
+ const result = config.schema.safeParse(process.env);
268
+ if (!result.success) {
269
+ throw new Error(`Environment validation failed:
270
+ ${result.error.message}`);
271
+ }
272
+ return deepFreeze(result.data);
273
+ }
274
+ // src/middleware/middleware-def.ts
275
+ function createMiddleware(def) {
276
+ return deepFreeze(def);
277
+ }
278
+ // src/module/module.ts
279
+ function validateOwnership(items, kind, expectedModule) {
280
+ for (const item of items) {
281
+ if (item.moduleName !== expectedModule) {
282
+ throw new Error(`${kind} belongs to module "${item.moduleName}", cannot add to module "${expectedModule}"`);
283
+ }
284
+ }
285
+ }
286
+ function createModule(definition, config) {
287
+ validateOwnership(config.services, "Service", definition.name);
288
+ validateOwnership(config.routers, "Router", definition.name);
289
+ for (const exp of config.exports) {
290
+ if (!config.services.includes(exp)) {
291
+ throw new Error("exports must be a subset of services");
292
+ }
293
+ }
294
+ return deepFreeze({
295
+ definition,
296
+ ...config
297
+ });
298
+ }
299
+ // src/module/router-def.ts
300
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head"];
301
+ function createRouterDef(moduleName, config) {
302
+ const routes = [];
303
+ function addRoute(method, path, routeConfig) {
304
+ if (!path.startsWith("/")) {
305
+ throw new Error(`Route path must start with '/', got '${path}'`);
306
+ }
307
+ routes.push({ method, path, config: routeConfig });
308
+ return router;
309
+ }
310
+ const router = {
311
+ ...config,
312
+ moduleName,
313
+ routes
314
+ };
315
+ for (const method of HTTP_METHODS) {
316
+ router[method] = (path, cfg) => addRoute(method.toUpperCase(), path, cfg);
317
+ }
318
+ return router;
319
+ }
320
+
321
+ // src/module/service.ts
322
+ function createServiceDef(moduleName, config) {
323
+ return deepFreeze({
324
+ ...config,
325
+ moduleName
326
+ });
327
+ }
328
+
329
+ // src/module/module-def.ts
330
+ function createModuleDef(config) {
331
+ const def = {
332
+ ...config,
333
+ service: (serviceConfig) => createServiceDef(config.name, serviceConfig),
334
+ router: (routerConfig) => createRouterDef(config.name, routerConfig)
335
+ };
336
+ return deepFreeze(def);
337
+ }
338
+ // src/vertz.ts
339
+ var vertz = deepFreeze({
340
+ env: createEnv,
341
+ middleware: createMiddleware,
342
+ moduleDef: createModuleDef,
343
+ module: createModule,
344
+ app: createApp
345
+ });
346
+ export {
347
+ vertz,
348
+ makeImmutable,
349
+ deepFreeze,
350
+ createModuleDef,
351
+ createModule,
352
+ createMiddleware,
353
+ createImmutableProxy,
354
+ createEnv,
355
+ createApp,
356
+ VertzException,
357
+ ValidationException,
358
+ UnauthorizedException,
359
+ ServiceUnavailableException,
360
+ NotFoundException,
361
+ InternalServerErrorException,
362
+ ForbiddenException,
363
+ ConflictException,
364
+ BadRequestException
365
+ };
@@ -0,0 +1,59 @@
1
+ interface RawRequest {
2
+ readonly request: Request;
3
+ readonly method: string;
4
+ readonly url: string;
5
+ readonly headers: Headers;
6
+ }
7
+ interface HandlerCtx {
8
+ params: Record<string, unknown>;
9
+ body: unknown;
10
+ query: Record<string, unknown>;
11
+ headers: Record<string, unknown>;
12
+ raw: RawRequest;
13
+ options: Record<string, unknown>;
14
+ env: Record<string, unknown>;
15
+ [key: string]: unknown;
16
+ }
17
+ interface CtxConfig {
18
+ params: Record<string, unknown>;
19
+ body: unknown;
20
+ query: Record<string, unknown>;
21
+ headers: Record<string, unknown>;
22
+ raw: RawRequest;
23
+ middlewareState: Record<string, unknown>;
24
+ services: Record<string, unknown>;
25
+ options: Record<string, unknown>;
26
+ env: Record<string, unknown>;
27
+ }
28
+ declare function buildCtx(config: CtxConfig): HandlerCtx;
29
+ interface ResolvedMiddleware {
30
+ name: string;
31
+ handler: (ctx: Record<string, unknown>) => Promise<unknown> | unknown;
32
+ resolvedInject: Record<string, unknown>;
33
+ }
34
+ declare function runMiddlewareChain(middlewares: ResolvedMiddleware[], requestCtx: Record<string, unknown>): Promise<Record<string, unknown>>;
35
+ interface MatchResult<T = unknown> {
36
+ handler: T;
37
+ params: Record<string, string>;
38
+ }
39
+ declare class Trie<T = unknown> {
40
+ private root;
41
+ add(method: string, path: string, handler: T): void;
42
+ match(method: string, path: string): MatchResult<T> | null;
43
+ getAllowedMethods(path: string): string[];
44
+ private resolveChild;
45
+ private findNode;
46
+ private matchNode;
47
+ }
48
+ interface ParsedRequest {
49
+ method: string;
50
+ path: string;
51
+ query: Record<string, string>;
52
+ headers: Record<string, string>;
53
+ raw: Request;
54
+ }
55
+ declare function parseRequest(request: Request): ParsedRequest;
56
+ declare function parseBody(request: Request): Promise<unknown>;
57
+ declare function createJsonResponse(data: unknown, status?: number, headers?: Record<string, string>): Response;
58
+ declare function createErrorResponse(error: unknown): Response;
59
+ export { runMiddlewareChain, parseRequest, parseBody, createJsonResponse, createErrorResponse, buildCtx, Trie, ResolvedMiddleware };
@@ -0,0 +1,18 @@
1
+ import {
2
+ Trie,
3
+ buildCtx,
4
+ createErrorResponse,
5
+ createJsonResponse,
6
+ parseBody,
7
+ parseRequest,
8
+ runMiddlewareChain
9
+ } from "./shared/chunk-c77pg5gx.js";
10
+ export {
11
+ runMiddlewareChain,
12
+ parseRequest,
13
+ parseBody,
14
+ createJsonResponse,
15
+ createErrorResponse,
16
+ buildCtx,
17
+ Trie
18
+ };
@@ -0,0 +1,340 @@
1
+ // src/immutability/dev-proxy.ts
2
+ function createImmutableProxy(obj, contextName, rootName, proxyCache = new WeakMap) {
3
+ if (proxyCache.has(obj)) {
4
+ return proxyCache.get(obj);
5
+ }
6
+ const root = rootName ?? contextName;
7
+ const proxy = new Proxy(obj, {
8
+ get(target, property, receiver) {
9
+ const value = Reflect.get(target, property, receiver);
10
+ if (value !== null && typeof value === "object" && typeof property === "string") {
11
+ return createImmutableProxy(value, `${contextName}.${property}`, root, proxyCache);
12
+ }
13
+ return value;
14
+ },
15
+ set(_target, property) {
16
+ throw new TypeError(`Cannot set property "${String(property)}" on ${contextName}. ${root} is immutable.`);
17
+ },
18
+ deleteProperty(_target, property) {
19
+ throw new TypeError(`Cannot delete property "${String(property)}" on ${contextName}. ${root} is immutable.`);
20
+ }
21
+ });
22
+ proxyCache.set(obj, proxy);
23
+ return proxy;
24
+ }
25
+
26
+ // src/immutability/freeze.ts
27
+ function deepFreeze(obj, visited = new WeakSet) {
28
+ if (obj === null || typeof obj !== "object") {
29
+ return obj;
30
+ }
31
+ if (visited.has(obj)) {
32
+ return obj;
33
+ }
34
+ visited.add(obj);
35
+ Object.freeze(obj);
36
+ for (const value of Object.values(obj)) {
37
+ deepFreeze(value, visited);
38
+ }
39
+ return obj;
40
+ }
41
+
42
+ // src/immutability/make-immutable.ts
43
+ function makeImmutable(obj, contextName) {
44
+ if (true) {
45
+ return createImmutableProxy(obj, contextName);
46
+ }
47
+ return obj;
48
+ }
49
+ // src/context/ctx-builder.ts
50
+ var RESERVED_KEYS = ["params", "body", "query", "headers", "raw", "options", "env"];
51
+ function validateCollisions(config) {
52
+ const middlewareKeys = Object.keys(config.middlewareState);
53
+ for (const key of middlewareKeys) {
54
+ if (RESERVED_KEYS.includes(key)) {
55
+ throw new Error(`Middleware cannot provide reserved ctx key: "${key}"`);
56
+ }
57
+ }
58
+ for (const key of Object.keys(config.services)) {
59
+ if (RESERVED_KEYS.includes(key)) {
60
+ throw new Error(`Service name cannot shadow reserved ctx key: "${key}"`);
61
+ }
62
+ if (middlewareKeys.includes(key)) {
63
+ throw new Error(`Service name "${key}" collides with middleware-provided key`);
64
+ }
65
+ }
66
+ }
67
+ function buildCtx(config) {
68
+ if (true) {
69
+ validateCollisions(config);
70
+ }
71
+ return makeImmutable({
72
+ params: config.params,
73
+ body: config.body,
74
+ query: config.query,
75
+ headers: config.headers,
76
+ raw: config.raw,
77
+ options: config.options,
78
+ env: config.env,
79
+ ...config.middlewareState,
80
+ ...config.services
81
+ }, "ctx");
82
+ }
83
+
84
+ // src/exceptions/vertz-exception.ts
85
+ class VertzException extends Error {
86
+ statusCode;
87
+ code;
88
+ details;
89
+ constructor(message, statusCode = 500, code, details) {
90
+ super(message);
91
+ this.name = this.constructor.name;
92
+ this.statusCode = statusCode;
93
+ this.code = code ?? this.name;
94
+ this.details = details;
95
+ if (typeof Error.captureStackTrace === "function") {
96
+ Error.captureStackTrace(this, this.constructor);
97
+ }
98
+ }
99
+ toJSON() {
100
+ return {
101
+ error: this.name,
102
+ message: this.message,
103
+ statusCode: this.statusCode,
104
+ code: this.code,
105
+ ...this.details !== undefined && { details: this.details }
106
+ };
107
+ }
108
+ }
109
+
110
+ // src/exceptions/http-exceptions.ts
111
+ class BadRequestException extends VertzException {
112
+ constructor(message, details) {
113
+ super(message, 400, undefined, details);
114
+ }
115
+ }
116
+
117
+ class UnauthorizedException extends VertzException {
118
+ constructor(message, details) {
119
+ super(message, 401, undefined, details);
120
+ }
121
+ }
122
+
123
+ class ForbiddenException extends VertzException {
124
+ constructor(message, details) {
125
+ super(message, 403, undefined, details);
126
+ }
127
+ }
128
+
129
+ class NotFoundException extends VertzException {
130
+ constructor(message, details) {
131
+ super(message, 404, undefined, details);
132
+ }
133
+ }
134
+
135
+ class ConflictException extends VertzException {
136
+ constructor(message, details) {
137
+ super(message, 409, undefined, details);
138
+ }
139
+ }
140
+
141
+ class ValidationException extends VertzException {
142
+ errors;
143
+ constructor(errors) {
144
+ super("Validation failed", 422, undefined, undefined);
145
+ this.errors = errors;
146
+ }
147
+ toJSON() {
148
+ return {
149
+ ...super.toJSON(),
150
+ errors: this.errors
151
+ };
152
+ }
153
+ }
154
+
155
+ class InternalServerErrorException extends VertzException {
156
+ constructor(message, details) {
157
+ super(message, 500, undefined, details);
158
+ }
159
+ }
160
+
161
+ class ServiceUnavailableException extends VertzException {
162
+ constructor(message, details) {
163
+ super(message, 503, undefined, details);
164
+ }
165
+ }
166
+ // src/middleware/middleware-runner.ts
167
+ function isPlainObject(value) {
168
+ return value !== null && typeof value === "object" && !Array.isArray(value);
169
+ }
170
+ async function runMiddlewareChain(middlewares, requestCtx) {
171
+ const accumulated = {};
172
+ for (const mw of middlewares) {
173
+ const ctx = { ...requestCtx, ...mw.resolvedInject, ...accumulated };
174
+ const contribution = await mw.handler(ctx);
175
+ if (isPlainObject(contribution)) {
176
+ Object.assign(accumulated, contribution);
177
+ }
178
+ }
179
+ return accumulated;
180
+ }
181
+
182
+ // src/router/trie.ts
183
+ function createNode() {
184
+ return {
185
+ staticChildren: new Map,
186
+ paramChild: null,
187
+ wildcardChild: null,
188
+ handlers: new Map
189
+ };
190
+ }
191
+ function splitPath(path) {
192
+ return path.split("/").filter(Boolean);
193
+ }
194
+
195
+ class Trie {
196
+ root = createNode();
197
+ add(method, path, handler) {
198
+ const segments = splitPath(path);
199
+ let node = this.root;
200
+ for (const segment of segments) {
201
+ node = this.resolveChild(node, segment);
202
+ }
203
+ node.handlers.set(method, handler);
204
+ }
205
+ match(method, path) {
206
+ const segments = splitPath(path);
207
+ return this.matchNode(this.root, segments, 0, method, {});
208
+ }
209
+ getAllowedMethods(path) {
210
+ const segments = splitPath(path);
211
+ const node = this.findNode(this.root, segments, 0);
212
+ if (!node)
213
+ return [];
214
+ return Array.from(node.handlers.keys());
215
+ }
216
+ resolveChild(node, segment) {
217
+ if (segment === "*") {
218
+ node.wildcardChild ??= createNode();
219
+ return node.wildcardChild;
220
+ }
221
+ if (segment.startsWith(":")) {
222
+ const name = segment.slice(1);
223
+ if (!node.paramChild) {
224
+ node.paramChild = { name, node: createNode() };
225
+ } else if (node.paramChild.name !== name) {
226
+ throw new Error(`Param name mismatch: existing ":${node.paramChild.name}" conflicts with ":${name}"`);
227
+ }
228
+ return node.paramChild.node;
229
+ }
230
+ let child = node.staticChildren.get(segment);
231
+ if (!child) {
232
+ child = createNode();
233
+ node.staticChildren.set(segment, child);
234
+ }
235
+ return child;
236
+ }
237
+ findNode(node, segments, index) {
238
+ if (index === segments.length)
239
+ return node;
240
+ const segment = segments[index];
241
+ if (segment === undefined)
242
+ return null;
243
+ const staticChild = node.staticChildren.get(segment);
244
+ if (staticChild) {
245
+ const result = this.findNode(staticChild, segments, index + 1);
246
+ if (result)
247
+ return result;
248
+ }
249
+ if (node.paramChild) {
250
+ const result = this.findNode(node.paramChild.node, segments, index + 1);
251
+ if (result)
252
+ return result;
253
+ }
254
+ if (node.wildcardChild)
255
+ return node.wildcardChild;
256
+ return null;
257
+ }
258
+ matchNode(node, segments, index, method, params) {
259
+ if (index === segments.length) {
260
+ const handler = node.handlers.get(method);
261
+ if (!handler)
262
+ return null;
263
+ return { handler, params: { ...params } };
264
+ }
265
+ const segment = segments[index];
266
+ if (segment === undefined)
267
+ return null;
268
+ const staticChild = node.staticChildren.get(segment);
269
+ if (staticChild) {
270
+ const result = this.matchNode(staticChild, segments, index + 1, method, params);
271
+ if (result)
272
+ return result;
273
+ }
274
+ if (node.paramChild) {
275
+ const result = this.matchNode(node.paramChild.node, segments, index + 1, method, {
276
+ ...params,
277
+ [node.paramChild.name]: segment
278
+ });
279
+ if (result)
280
+ return result;
281
+ }
282
+ if (node.wildcardChild) {
283
+ const handler = node.wildcardChild.handlers.get(method);
284
+ if (handler) {
285
+ const rest = segments.slice(index).join("/");
286
+ return { handler, params: { ...params, "*": rest } };
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+ }
292
+
293
+ // src/server/request-utils.ts
294
+ function parseRequest(request) {
295
+ const url = new URL(request.url);
296
+ return {
297
+ method: request.method,
298
+ path: url.pathname,
299
+ query: Object.fromEntries(url.searchParams),
300
+ headers: Object.fromEntries(request.headers),
301
+ raw: request
302
+ };
303
+ }
304
+ async function parseBody(request) {
305
+ const contentType = request.headers.get("content-type") ?? "";
306
+ if (contentType.includes("application/json")) {
307
+ try {
308
+ return await request.json();
309
+ } catch {
310
+ throw new BadRequestException("Invalid JSON body");
311
+ }
312
+ }
313
+ if (contentType.startsWith("text/")) {
314
+ return request.text();
315
+ }
316
+ if (contentType.includes("application/x-www-form-urlencoded")) {
317
+ const text = await request.text();
318
+ return Object.fromEntries(new URLSearchParams(text));
319
+ }
320
+ return;
321
+ }
322
+
323
+ // src/server/response-utils.ts
324
+ function createJsonResponse(data, status = 200, headers) {
325
+ return new Response(JSON.stringify(data), {
326
+ status,
327
+ headers: {
328
+ "content-type": "application/json",
329
+ ...headers
330
+ }
331
+ });
332
+ }
333
+ function createErrorResponse(error) {
334
+ if (error instanceof VertzException) {
335
+ return createJsonResponse(error.toJSON(), error.statusCode);
336
+ }
337
+ return createJsonResponse({ error: "InternalServerError", message: "Internal Server Error", statusCode: 500 }, 500);
338
+ }
339
+
340
+ export { createImmutableProxy, deepFreeze, makeImmutable, buildCtx, VertzException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, ValidationException, InternalServerErrorException, ServiceUnavailableException, runMiddlewareChain, Trie, parseRequest, parseBody, createJsonResponse, createErrorResponse };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@vertz/core",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./internals": {
13
+ "import": "./dist/internals.js",
14
+ "types": "./dist/internals.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "bunup",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@vertz/schema": "0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.0.0",
31
+ "bunup": "latest",
32
+ "typescript": "^5.7.0",
33
+ "vitest": "^3.0.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=22"
37
+ }
38
+ }