@vertz/core 0.2.0 → 0.2.1

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
@@ -629,6 +629,50 @@ router.post('/send', {
629
629
  });
630
630
  ```
631
631
 
632
+ ### Environment Validation
633
+
634
+ `createEnv` validates environment variables against a schema at startup, returning a frozen, typed configuration object.
635
+
636
+ ```typescript
637
+ import { createEnv } from '@vertz/core';
638
+ import { s } from '@vertz/schema';
639
+
640
+ const env = createEnv({
641
+ schema: s.object({
642
+ DATABASE_URL: s.string(),
643
+ PORT: s.coerce.number().default(3000),
644
+ NODE_ENV: s.enum(['development', 'production', 'test']),
645
+ }),
646
+ });
647
+
648
+ // env.DATABASE_URL — fully typed, validated, immutable
649
+ ```
650
+
651
+ By default, `createEnv` reads from `process.env`. You can pass an explicit `env` record instead — useful for edge runtimes (Cloudflare Workers, Deno Deploy) or testing:
652
+
653
+ ```typescript
654
+ // Edge runtime — pass env explicitly
655
+ const env = createEnv({
656
+ schema: s.object({
657
+ DATABASE_URL: s.string(),
658
+ API_KEY: s.string(),
659
+ }),
660
+ env: context.env, // Cloudflare Workers env bindings
661
+ });
662
+
663
+ // Testing — inject controlled values
664
+ const env = createEnv({
665
+ schema: s.object({ PORT: s.coerce.number() }),
666
+ env: { PORT: '4000' },
667
+ });
668
+ ```
669
+
670
+ | Option | Type | Description |
671
+ |--------|------|-------------|
672
+ | `schema` | `Schema<T>` | A `@vertz/schema` schema to validate against |
673
+ | `env` | `Record<string, string \| undefined>` | Explicit env record. Defaults to `process.env` with a `typeof process` guard for non-Node runtimes |
674
+ | `load` | `string[]` | Dotenv file paths to load before validation |
675
+
632
676
  ### Custom Server Adapters
633
677
 
634
678
  Use the `.handler` property to integrate with custom servers:
package/dist/index.d.ts CHANGED
@@ -213,25 +213,39 @@ interface CorsConfig {
213
213
  maxAge?: number;
214
214
  exposedHeaders?: string[];
215
215
  }
216
- interface DomainDefinition {
216
+ interface EntityDefinition {
217
+ readonly kind?: string;
217
218
  readonly name: string;
218
- readonly type: string;
219
- readonly table: unknown;
220
- readonly exposedRelations: Record<string, unknown>;
219
+ readonly model: unknown;
221
220
  readonly access: Record<string, unknown>;
222
- readonly handlers: Record<string, unknown>;
221
+ readonly before: Record<string, unknown>;
222
+ readonly after: Record<string, unknown>;
223
223
  readonly actions: Record<string, unknown>;
224
+ readonly relations: Record<string, unknown>;
225
+ }
226
+ /**
227
+ * An entity route entry generated by @vertz/server's route generator.
228
+ * Core doesn't know about entity internals — it just registers these as handlers.
229
+ */
230
+ interface EntityRouteEntry {
231
+ method: string;
232
+ path: string;
233
+ handler: (ctx: Record<string, unknown>) => Promise<Response>;
224
234
  }
225
235
  interface AppConfig {
226
236
  basePath?: string;
227
237
  version?: string;
228
238
  cors?: CorsConfig;
229
- /** Domain definitions for auto-CRUD route generation */
230
- domains?: DomainDefinition[];
231
- /** API prefix for domain routes (default: '/api/') */
239
+ /** Entity definitions for auto-CRUD route generation */
240
+ entities?: EntityDefinition[];
241
+ /** API prefix for entity routes (default: '/api/') */
232
242
  apiPrefix?: string;
233
243
  /** Enable response schema validation in dev mode (logs warnings but doesn't break response) */
234
244
  validateResponses?: boolean;
245
+ /** Internal: pre-built entity route handlers injected by @vertz/server */
246
+ _entityRoutes?: EntityRouteEntry[];
247
+ /** Internal: factory for creating DB adapters per entity (used by @vertz/server) */
248
+ _entityDbFactory?: (entityDef: EntityDefinition) => unknown;
235
249
  }
236
250
  interface ListenOptions {
237
251
  hostname?: string;
@@ -290,6 +304,7 @@ import { Schema as Schema3 } from "@vertz/schema";
290
304
  interface EnvConfig<T = unknown> {
291
305
  load?: string[];
292
306
  schema: Schema3<T>;
307
+ env?: Record<string, string | undefined>;
293
308
  }
294
309
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
295
310
  type HttpStatusCode = 200 | 201 | 204 | 301 | 302 | 304 | 400 | 401 | 403 | 404 | 405 | 409 | 422 | 429 | 500 | 502 | 503 | 504;
@@ -300,7 +315,13 @@ declare class VertzException extends Error {
300
315
  readonly code: string;
301
316
  readonly details?: unknown;
302
317
  constructor(message: string, statusCode?: number, code?: string, details?: unknown);
303
- toJSON(): Record<string, unknown>;
318
+ toJSON(): {
319
+ error: {
320
+ code: string;
321
+ message: string;
322
+ details?: unknown;
323
+ };
324
+ };
304
325
  }
305
326
  declare class BadRequestException extends VertzException {
306
327
  constructor(message: string, details?: unknown);
@@ -326,7 +347,16 @@ declare class ValidationException extends VertzException {
326
347
  path: string;
327
348
  message: string;
328
349
  }>);
329
- toJSON(): Record<string, unknown>;
350
+ toJSON(): {
351
+ error: {
352
+ code: string;
353
+ message: string;
354
+ details?: ReadonlyArray<{
355
+ path: string;
356
+ message: string;
357
+ }>;
358
+ };
359
+ };
330
360
  }
331
361
  declare class InternalServerErrorException extends VertzException {
332
362
  constructor(message: string, details?: unknown);
@@ -347,121 +377,6 @@ declare const vertz: {
347
377
  readonly server: typeof createApp;
348
378
  };
349
379
  /**
350
- * Symbol used to brand Result objects to prevent accidental matches with user data.
351
- * Using a Symbol makes it impossible for user objects to accidentally match isResult().
352
- */
353
- declare const RESULT_BRAND: unique symbol;
354
- /**
355
- * Result type for explicit error handling in route handlers.
356
- *
357
- * This provides an alternative to exception-based error handling,
358
- * making error cases visible in type signatures.
359
- *
360
- * @example
361
- * ```typescript
362
- * router.get('/:id', {
363
- * handler: async (ctx) => {
364
- * const user = await ctx.userService.find(ctx.params.id);
365
- * if (!user) {
366
- * return err(404, { message: 'User not found' });
367
- * }
368
- * return ok({ id: user.id, name: user.name });
369
- * }
370
- * });
371
- * ```
372
- */
373
- /**
374
- * Represents a successful result containing data.
375
- */
376
- interface Ok<T> {
377
- readonly ok: true;
378
- readonly data: T;
379
- readonly [RESULT_BRAND]: true;
380
- }
381
- /**
382
- * Represents an error result containing status code and error body.
383
- */
384
- interface Err<E> {
385
- readonly ok: false;
386
- readonly status: number;
387
- readonly body: E;
388
- readonly [RESULT_BRAND]: true;
389
- }
390
- /**
391
- * A discriminated union type representing either a success (Ok) or failure (Err).
392
- *
393
- * @typeParam T - The type of the success data
394
- * @typeParam E - The type of the error body
395
- *
396
- * @example
397
- * ```typescript
398
- * type UserResult = Result<{ id: number; name: string }, { message: string }>;
399
- * ```
400
- */
401
- type Result<
402
- T,
403
- E = unknown
404
- > = Ok<T> | Err<E>;
405
- /**
406
- * Creates a successful Result containing the given data.
407
- *
408
- * @param data - The success data
409
- * @returns An Ok result with the data
410
- *
411
- * @example
412
- * ```typescript
413
- * return ok({ id: 1, name: 'John' });
414
- * ```
415
- */
416
- declare function ok<T>(data: T): Ok<T>;
417
- /**
418
- * Creates an error Result with the given status code and body.
419
- *
420
- * @param status - HTTP status code for the error
421
- * @param body - Error body/response
422
- * @returns An Err result with status and body
423
- *
424
- * @example
425
- * ```typescript
426
- * return err(404, { message: 'Not found' });
427
- * ```
428
- */
429
- declare function err<E>(status: number, body: E): Err<E>;
430
- /**
431
- * Type guard to check if a Result is Ok.
432
- *
433
- * @param result - The result to check
434
- * @returns True if the result is Ok
435
- *
436
- * @example
437
- * ```typescript
438
- * if (isOk(result)) {
439
- * console.log(result.data);
440
- * }
441
- * ```
442
- */
443
- declare function isOk<
444
- T,
445
- E
446
- >(result: Result<T, E>): result is Ok<T>;
447
- /**
448
- * Type guard to check if a Result is Err.
449
- *
450
- * @param result - The result to check
451
- * @returns True if the result is Err
452
- *
453
- * @example
454
- * ```typescript
455
- * if (isErr(result)) {
456
- * console.log(result.status, result.body);
457
- * }
458
- * ```
459
- */
460
- declare function isErr<
461
- T,
462
- E
463
- >(result: Result<T, E>): result is Err<E>;
464
- /**
465
380
  * Creates an HTTP server. Preferred entry point for building Vertz services.
466
381
  * @since 0.2.0
467
382
  */
@@ -470,4 +385,4 @@ declare const createServer: (config: AppConfig) => AppBuilder;
470
385
  * @deprecated Use `createServer` instead. `createApp` will be removed in v0.3.0.
471
386
  */
472
387
  declare const createApp2: (config: AppConfig) => AppBuilder;
473
- export { vertz, ok, makeImmutable, isOk, isErr, err, deepFreeze, createServer, createModuleDef, createModule, createMiddleware, createImmutableProxy, createEnv, createApp2 as createApp, VertzException, ValidationException, UnauthorizedException, ServiceUnavailableException, ServiceFactory, ServiceDef, ServiceBootInstruction, ServerHandle, ServerAdapter, RouterDef, RouteInfo, Result, ResolveInjectMap, RawRequest, Ok, NotFoundException, NamedServiceDef, NamedRouterDef, NamedModuleDef, NamedModule, NamedMiddlewareDef, ModuleDef, ModuleBootInstruction, Module, MiddlewareDef, ListenOptions, InternalServerErrorException, Infer2 as InferSchema, Infer, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, ExtractMethods, Err, EnvConfig, Deps, DeepReadonly, Ctx, CorsConfig, ConflictException, BootSequence, BootInstruction, BadRequestException, AppConfig, AppBuilder, AccumulateProvides };
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 };
package/dist/index.js CHANGED
@@ -18,7 +18,19 @@ import {
18
18
  parseBody,
19
19
  parseRequest,
20
20
  runMiddlewareChain
21
- } from "./shared/chunk-c77pg5gx.js";
21
+ } from "./shared/chunk-k596zpc6.js";
22
+
23
+ // src/result.ts
24
+ var RESULT_BRAND = Symbol.for("vertz.result");
25
+ function isOk(result) {
26
+ return result.ok === true;
27
+ }
28
+ function isResult(value) {
29
+ if (value === null || typeof value !== "object")
30
+ return false;
31
+ const obj = value;
32
+ return obj[RESULT_BRAND] === true;
33
+ }
22
34
 
23
35
  // src/server/cors.ts
24
36
  var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
@@ -77,27 +89,6 @@ function applyCorsHeaders(config, request, response) {
77
89
  });
78
90
  }
79
91
 
80
- // src/result.ts
81
- var RESULT_BRAND = Symbol.for("vertz.result");
82
- function ok(data) {
83
- return { ok: true, data, [RESULT_BRAND]: true };
84
- }
85
- function err(status, body) {
86
- return { ok: false, status, body, [RESULT_BRAND]: true };
87
- }
88
- function isOk(result) {
89
- return result.ok === true;
90
- }
91
- function isErr(result) {
92
- return result.ok === false;
93
- }
94
- function isResult(value) {
95
- if (value === null || typeof value !== "object")
96
- return false;
97
- const obj = value;
98
- return obj[RESULT_BRAND] === true;
99
- }
100
-
101
92
  // src/app/app-runner.ts
102
93
  function createResponseWithCors(data, status, config, request) {
103
94
  const response = createJsonResponse(data, status);
@@ -107,14 +98,12 @@ function createResponseWithCors(data, status, config, request) {
107
98
  return response;
108
99
  }
109
100
  function validateSchema(schema, value, label) {
110
- try {
111
- return schema.parse(value);
112
- } catch (error) {
113
- if (error instanceof BadRequestException)
114
- throw error;
115
- const message = error instanceof Error ? error.message : `Invalid ${label}`;
101
+ const result = schema.parse(value);
102
+ if (!result.ok) {
103
+ const message = result.error instanceof Error ? result.error.message : `Invalid ${label}`;
116
104
  throw new BadRequestException(message);
117
105
  }
106
+ return result.data;
118
107
  }
119
108
  function resolveServices(registrations) {
120
109
  const serviceMap = new Map;
@@ -124,7 +113,7 @@ function resolveServices(registrations) {
124
113
  let parsedOptions = {};
125
114
  if (service.options && options) {
126
115
  const parsed = service.options.safeParse(options);
127
- if (parsed.success) {
116
+ if (parsed.ok) {
128
117
  parsedOptions = parsed.data;
129
118
  } else {
130
119
  throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
@@ -189,6 +178,17 @@ function buildHandler(config, registrations, globalMiddlewares) {
189
178
  const resolvedMiddlewares = resolveMiddlewares(globalMiddlewares);
190
179
  const serviceMap = resolveServices(registrations);
191
180
  registerRoutes(trie, basePath, registrations, serviceMap);
181
+ if (config._entityRoutes) {
182
+ for (const route of config._entityRoutes) {
183
+ const entry = {
184
+ handler: route.handler,
185
+ options: {},
186
+ services: {},
187
+ middlewares: []
188
+ };
189
+ trie.add(route.method, route.path, entry);
190
+ }
191
+ }
192
192
  return async (request) => {
193
193
  try {
194
194
  if (config.cors) {
@@ -201,9 +201,9 @@ function buildHandler(config, registrations, globalMiddlewares) {
201
201
  if (!match) {
202
202
  const allowed = trie.getAllowedMethods(parsed.path);
203
203
  if (allowed.length > 0) {
204
- return createJsonResponse({ error: "MethodNotAllowed", message: "Method Not Allowed", statusCode: 405 }, 405, { allow: allowed.join(", ") });
204
+ return createJsonResponse({ error: { code: "MethodNotAllowed", message: "Method Not Allowed" } }, 405, { allow: allowed.join(", ") });
205
205
  }
206
- return createJsonResponse({ error: "NotFound", message: "Not Found", statusCode: 404 }, 404);
206
+ return createJsonResponse({ error: { code: "NotFound", message: "Not Found" } }, 404);
207
207
  }
208
208
  const body = await parseBody(request);
209
209
  const raw = {
@@ -246,10 +246,9 @@ function buildHandler(config, registrations, globalMiddlewares) {
246
246
  if (isOk(result)) {
247
247
  const data = result.data;
248
248
  if (config.validateResponses && entry.responseSchema) {
249
- try {
250
- entry.responseSchema.parse(data);
251
- } catch (error) {
252
- const message = error instanceof Error ? error.message : "Response schema validation failed";
249
+ const validation = entry.responseSchema.parse(data);
250
+ if (!validation.ok) {
251
+ const message = validation.error instanceof Error ? validation.error.message : "Response schema validation failed";
253
252
  console.warn(`[vertz] Response validation warning: ${message}`);
254
253
  }
255
254
  }
@@ -260,10 +259,9 @@ function buildHandler(config, registrations, globalMiddlewares) {
260
259
  if (config.validateResponses && entry.errorsSchema) {
261
260
  const errorSchema = entry.errorsSchema[errorStatus];
262
261
  if (errorSchema) {
263
- try {
264
- errorSchema.parse(errorBody);
265
- } catch (error) {
266
- const message = error instanceof Error ? `Error schema validation failed for status ${errorStatus}: ${error.message}` : `Error schema validation failed for status ${errorStatus}`;
262
+ const validation = errorSchema.parse(errorBody);
263
+ if (!validation.ok) {
264
+ const message = validation.error instanceof Error ? `Error schema validation failed for status ${errorStatus}: ${validation.error.message}` : `Error schema validation failed for status ${errorStatus}`;
267
265
  console.warn(`[vertz] Response validation warning: ${message}`);
268
266
  }
269
267
  }
@@ -272,14 +270,13 @@ function buildHandler(config, registrations, globalMiddlewares) {
272
270
  }
273
271
  }
274
272
  if (config.validateResponses && entry.responseSchema) {
275
- try {
276
- entry.responseSchema.parse(result);
277
- } catch (error) {
278
- const message = error instanceof Error ? error.message : "Response schema validation failed";
273
+ const validation = entry.responseSchema.parse(result);
274
+ if (!validation.ok) {
275
+ const message = validation.error instanceof Error ? validation.error.message : "Response schema validation failed";
279
276
  console.warn(`[vertz] Response validation warning: ${message}`);
280
277
  }
281
278
  }
282
- const response = result === undefined ? new Response(null, { status: 204 }) : createJsonResponse(result);
279
+ const response = result === undefined ? new Response(null, { status: 204 }) : result instanceof Response ? result : createJsonResponse(result);
283
280
  if (config.cors) {
284
281
  return applyCorsHeaders(config.cors, request, response);
285
282
  }
@@ -367,6 +364,7 @@ function createApp(config) {
367
364
  let globalMiddlewares = [];
368
365
  let cachedHandler = null;
369
366
  const registeredRoutes = [];
367
+ const entityRoutes = [];
370
368
  const builder = {
371
369
  register(module, options) {
372
370
  registrations.push({ module, options });
@@ -395,35 +393,47 @@ function createApp(config) {
395
393
  const adapter = detectAdapter();
396
394
  const serverHandle = await adapter.listen(port ?? DEFAULT_PORT, builder.handler, options);
397
395
  if (options?.logRoutes !== false) {
398
- const routes = collectRoutes(config.basePath ?? "", registrations);
396
+ const moduleRoutes = collectRoutes(config.basePath ?? "", registrations);
397
+ const routes = [...moduleRoutes, ...entityRoutes];
399
398
  const url = `http://${serverHandle.hostname}:${serverHandle.port}`;
400
399
  console.log(formatRouteLog(url, routes));
401
400
  }
402
401
  return serverHandle;
403
402
  }
404
403
  };
405
- if (config.domains && config.domains.length > 0) {
404
+ if (config._entityRoutes) {
405
+ for (const route of config._entityRoutes) {
406
+ const info = { method: route.method, path: route.path };
407
+ registeredRoutes.push(info);
408
+ entityRoutes.push(info);
409
+ }
410
+ } else if (config.entities && config.entities.length > 0) {
406
411
  const rawPrefix = config.apiPrefix === undefined ? "/api/" : config.apiPrefix;
407
- for (const domain of config.domains) {
408
- const domainPath = rawPrefix === "" ? "/" + domain.name : (rawPrefix.endsWith("/") ? rawPrefix : rawPrefix + "/") + domain.name;
409
- registeredRoutes.push({ method: "GET", path: domainPath });
410
- registeredRoutes.push({ method: "GET", path: `${domainPath}/:id` });
411
- registeredRoutes.push({ method: "POST", path: domainPath });
412
- registeredRoutes.push({ method: "PUT", path: `${domainPath}/:id` });
413
- registeredRoutes.push({ method: "DELETE", path: `${domainPath}/:id` });
414
- if (domain.actions) {
415
- for (const actionName of Object.keys(domain.actions)) {
416
- registeredRoutes.push({ method: "POST", path: `${domainPath}/:id/${actionName}` });
412
+ for (const entity of config.entities) {
413
+ const entityPath = rawPrefix === "" ? `/${entity.name}` : (rawPrefix.endsWith("/") ? rawPrefix : `${rawPrefix}/`) + entity.name;
414
+ const routes = [
415
+ { method: "GET", path: entityPath },
416
+ { method: "GET", path: `${entityPath}/:id` },
417
+ { method: "POST", path: entityPath },
418
+ { method: "PATCH", path: `${entityPath}/:id` },
419
+ { method: "DELETE", path: `${entityPath}/:id` }
420
+ ];
421
+ if (entity.actions) {
422
+ for (const actionName of Object.keys(entity.actions)) {
423
+ routes.push({ method: "POST", path: `${entityPath}/:id/${actionName}` });
417
424
  }
418
425
  }
426
+ registeredRoutes.push(...routes);
427
+ entityRoutes.push(...routes);
419
428
  }
420
429
  }
421
430
  return builder;
422
431
  }
423
432
  // src/env/env-validator.ts
424
433
  function createEnv(config) {
425
- const result = config.schema.safeParse(process.env);
426
- if (!result.success) {
434
+ const envRecord = config.env ?? (typeof process !== "undefined" ? process.env : {});
435
+ const result = config.schema.safeParse(envRecord);
436
+ if (!result.ok) {
427
437
  throw new Error(`Environment validation failed:
428
438
  ${result.error.message}`);
429
439
  }
@@ -511,11 +521,7 @@ var createApp2 = (...args) => {
511
521
  };
512
522
  export {
513
523
  vertz,
514
- ok,
515
524
  makeImmutable,
516
- isOk,
517
- isErr,
518
- err,
519
525
  deepFreeze,
520
526
  createServer,
521
527
  createModuleDef,
package/dist/internals.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  parseBody,
7
7
  parseRequest,
8
8
  runMiddlewareChain
9
- } from "./shared/chunk-c77pg5gx.js";
9
+ } from "./shared/chunk-k596zpc6.js";
10
10
  export {
11
11
  runMiddlewareChain,
12
12
  parseRequest,
@@ -41,7 +41,7 @@ function deepFreeze(obj, visited = new WeakSet) {
41
41
 
42
42
  // src/immutability/make-immutable.ts
43
43
  function makeImmutable(obj, contextName) {
44
- if (true) {
44
+ if (typeof process !== "undefined" && true) {
45
45
  return createImmutableProxy(obj, contextName);
46
46
  }
47
47
  return obj;
@@ -65,7 +65,7 @@ function validateCollisions(config) {
65
65
  }
66
66
  }
67
67
  function buildCtx(config) {
68
- if (true) {
68
+ if (typeof process !== "undefined" && true) {
69
69
  validateCollisions(config);
70
70
  }
71
71
  return makeImmutable({
@@ -98,11 +98,11 @@ class VertzException extends Error {
98
98
  }
99
99
  toJSON() {
100
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 }
101
+ error: {
102
+ code: this.code,
103
+ message: this.message,
104
+ ...this.details !== undefined && { details: this.details }
105
+ }
106
106
  };
107
107
  }
108
108
  }
@@ -110,57 +110,60 @@ class VertzException extends Error {
110
110
  // src/exceptions/http-exceptions.ts
111
111
  class BadRequestException extends VertzException {
112
112
  constructor(message, details) {
113
- super(message, 400, undefined, details);
113
+ super(message, 400, "BadRequest", details);
114
114
  }
115
115
  }
116
116
 
117
117
  class UnauthorizedException extends VertzException {
118
118
  constructor(message, details) {
119
- super(message, 401, undefined, details);
119
+ super(message, 401, "Unauthorized", details);
120
120
  }
121
121
  }
122
122
 
123
123
  class ForbiddenException extends VertzException {
124
124
  constructor(message, details) {
125
- super(message, 403, undefined, details);
125
+ super(message, 403, "Forbidden", details);
126
126
  }
127
127
  }
128
128
 
129
129
  class NotFoundException extends VertzException {
130
130
  constructor(message, details) {
131
- super(message, 404, undefined, details);
131
+ super(message, 404, "NotFound", details);
132
132
  }
133
133
  }
134
134
 
135
135
  class ConflictException extends VertzException {
136
136
  constructor(message, details) {
137
- super(message, 409, undefined, details);
137
+ super(message, 409, "Conflict", details);
138
138
  }
139
139
  }
140
140
 
141
141
  class ValidationException extends VertzException {
142
142
  errors;
143
143
  constructor(errors) {
144
- super("Validation failed", 422, undefined, undefined);
144
+ super("Validation failed", 422, "ValidationError", undefined);
145
145
  this.errors = errors;
146
146
  }
147
147
  toJSON() {
148
148
  return {
149
- ...super.toJSON(),
150
- errors: this.errors
149
+ error: {
150
+ code: this.code,
151
+ message: this.message,
152
+ details: this.errors
153
+ }
151
154
  };
152
155
  }
153
156
  }
154
157
 
155
158
  class InternalServerErrorException extends VertzException {
156
159
  constructor(message, details) {
157
- super(message, 500, undefined, details);
160
+ super(message, 500, "InternalError", details);
158
161
  }
159
162
  }
160
163
 
161
164
  class ServiceUnavailableException extends VertzException {
162
165
  constructor(message, details) {
163
- super(message, 503, undefined, details);
166
+ super(message, 503, "ServiceUnavailable", details);
164
167
  }
165
168
  }
166
169
  // src/middleware/middleware-runner.ts
@@ -334,7 +337,7 @@ function createErrorResponse(error) {
334
337
  if (error instanceof VertzException) {
335
338
  return createJsonResponse(error.toJSON(), error.statusCode);
336
339
  }
337
- return createJsonResponse({ error: "InternalServerError", message: "Internal Server Error", statusCode: 500 }, 500);
340
+ return createJsonResponse({ error: { code: "InternalServerError", message: "Internal Server Error" } }, 500);
338
341
  }
339
342
 
340
343
  export { createImmutableProxy, deepFreeze, makeImmutable, buildCtx, VertzException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, ValidationException, InternalServerErrorException, ServiceUnavailableException, runMiddlewareChain, Trie, parseRequest, parseBody, createJsonResponse, createErrorResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz core framework primitives",
@@ -30,15 +30,16 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "build": "bunup",
33
- "test": "vitest run",
33
+ "test": "bun test",
34
+ "test:coverage": "vitest run --coverage",
34
35
  "test:watch": "vitest",
35
36
  "typecheck": "tsc --noEmit"
36
37
  },
37
38
  "dependencies": {
38
- "@vertz/schema": "workspace:*"
39
+ "@vertz/schema": "0.2.1"
39
40
  },
40
41
  "devDependencies": {
41
- "@types/node": "^22.0.0",
42
+ "@types/node": "^25.3.1",
42
43
  "@vitest/coverage-v8": "^4.0.18",
43
44
  "bunup": "latest",
44
45
  "typescript": "^5.7.0",