@vertz/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -77,7 +77,35 @@ function applyCorsHeaders(config, request, response) {
77
77
  });
78
78
  }
79
79
 
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
+
80
101
  // src/app/app-runner.ts
102
+ function createResponseWithCors(data, status, config, request) {
103
+ const response = createJsonResponse(data, status);
104
+ if (config.cors) {
105
+ return applyCorsHeaders(config.cors, request, response);
106
+ }
107
+ return response;
108
+ }
81
109
  function validateSchema(schema, value, label) {
82
110
  try {
83
111
  return schema.parse(value);
@@ -90,10 +118,20 @@ function validateSchema(schema, value, label) {
90
118
  }
91
119
  function resolveServices(registrations) {
92
120
  const serviceMap = new Map;
93
- for (const { module } of registrations) {
121
+ for (const { module, options } of registrations) {
94
122
  for (const service of module.services) {
95
123
  if (!serviceMap.has(service)) {
96
- serviceMap.set(service, service.methods({}, undefined));
124
+ let parsedOptions = {};
125
+ if (service.options && options) {
126
+ const parsed = service.options.safeParse(options);
127
+ if (parsed.success) {
128
+ parsedOptions = parsed.data;
129
+ } else {
130
+ throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
131
+ }
132
+ }
133
+ const env = {};
134
+ serviceMap.set(service, service.methods({}, undefined, parsedOptions, env));
97
135
  }
98
136
  }
99
137
  }
@@ -123,14 +161,22 @@ function registerRoutes(trie, basePath, registrations, serviceMap) {
123
161
  const resolvedServices = resolveRouterServices(router.inject, serviceMap);
124
162
  for (const route of router.routes) {
125
163
  const fullPath = basePath + router.prefix + route.path;
164
+ const routeMiddlewares = (route.config.middlewares ?? []).map((mw) => ({
165
+ name: mw.name,
166
+ handler: mw.handler,
167
+ resolvedInject: {}
168
+ }));
126
169
  const entry = {
127
170
  handler: route.config.handler,
128
171
  options: options ?? {},
129
172
  services: resolvedServices,
173
+ middlewares: routeMiddlewares,
130
174
  paramsSchema: route.config.params,
131
175
  bodySchema: route.config.body,
132
176
  querySchema: route.config.query,
133
- headersSchema: route.config.headers
177
+ headersSchema: route.config.headers,
178
+ responseSchema: route.config.response,
179
+ errorsSchema: route.config.errors
134
180
  };
135
181
  trie.add(route.method, fullPath, entry);
136
182
  }
@@ -175,6 +221,11 @@ function buildHandler(config, registrations, globalMiddlewares) {
175
221
  };
176
222
  const middlewareState = await runMiddlewareChain(resolvedMiddlewares, requestCtx);
177
223
  const entry = match.handler;
224
+ if (entry.middlewares.length > 0) {
225
+ const routeCtx = { ...requestCtx, ...middlewareState };
226
+ const routeState = await runMiddlewareChain(entry.middlewares, routeCtx);
227
+ Object.assign(middlewareState, routeState);
228
+ }
178
229
  const validatedParams = entry.paramsSchema ? validateSchema(entry.paramsSchema, match.params, "params") : match.params;
179
230
  const validatedBody = entry.bodySchema ? validateSchema(entry.bodySchema, body, "body") : body;
180
231
  const validatedQuery = entry.querySchema ? validateSchema(entry.querySchema, parsed.query, "query") : parsed.query;
@@ -191,6 +242,43 @@ function buildHandler(config, registrations, globalMiddlewares) {
191
242
  env: {}
192
243
  });
193
244
  const result = await entry.handler(ctx);
245
+ if (isResult(result)) {
246
+ if (isOk(result)) {
247
+ const data = result.data;
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";
253
+ console.warn(`[vertz] Response validation warning: ${message}`);
254
+ }
255
+ }
256
+ return createResponseWithCors(data, 200, config, request);
257
+ } else {
258
+ const errorStatus = result.status;
259
+ const errorBody = result.body;
260
+ if (config.validateResponses && entry.errorsSchema) {
261
+ const errorSchema = entry.errorsSchema[errorStatus];
262
+ 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}`;
267
+ console.warn(`[vertz] Response validation warning: ${message}`);
268
+ }
269
+ }
270
+ }
271
+ return createResponseWithCors(errorBody, errorStatus, config, request);
272
+ }
273
+ }
274
+ 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";
279
+ console.warn(`[vertz] Response validation warning: ${message}`);
280
+ }
281
+ }
194
282
  const response = result === undefined ? new Response(null, { status: 204 }) : createJsonResponse(result);
195
283
  if (config.cors) {
196
284
  return applyCorsHeaders(config.cors, request, response);
@@ -234,19 +322,64 @@ function detectAdapter(hints) {
234
322
  throw new Error("No supported server runtime detected. Vertz requires Bun to use app.listen().");
235
323
  }
236
324
 
325
+ // src/app/route-log.ts
326
+ function normalizePath(path) {
327
+ let normalized = path.replace(/\/+/g, "/");
328
+ if (normalized.length > 1 && normalized.endsWith("/")) {
329
+ normalized = normalized.slice(0, -1);
330
+ }
331
+ return normalized || "/";
332
+ }
333
+ function collectRoutes(basePath, registrations) {
334
+ const routes = [];
335
+ for (const { module } of registrations) {
336
+ for (const router of module.routers) {
337
+ for (const route of router.routes) {
338
+ routes.push({
339
+ method: route.method,
340
+ path: normalizePath(basePath + router.prefix + route.path)
341
+ });
342
+ }
343
+ }
344
+ }
345
+ return routes;
346
+ }
347
+ function formatRouteLog(listenUrl, routes) {
348
+ const header = `vertz server listening on ${listenUrl}`;
349
+ if (routes.length === 0) {
350
+ return header;
351
+ }
352
+ const sorted = [...routes].sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
353
+ const MIN_METHOD_WIDTH = 6;
354
+ const maxMethodLen = Math.max(MIN_METHOD_WIDTH, ...sorted.map((r) => r.method.length));
355
+ const lines = sorted.map((r) => {
356
+ const paddedMethod = r.method.padEnd(maxMethodLen);
357
+ return ` ${paddedMethod} ${r.path}`;
358
+ });
359
+ return [header, "", ...lines].join(`
360
+ `);
361
+ }
362
+
237
363
  // src/app/app-builder.ts
238
364
  var DEFAULT_PORT = 3000;
239
365
  function createApp(config) {
240
366
  const registrations = [];
241
367
  let globalMiddlewares = [];
242
368
  let cachedHandler = null;
369
+ const registeredRoutes = [];
243
370
  const builder = {
244
371
  register(module, options) {
245
372
  registrations.push({ module, options });
373
+ for (const router of module.routers) {
374
+ for (const route of router.routes) {
375
+ registeredRoutes.push({ method: route.method, path: router.prefix + route.path });
376
+ }
377
+ }
378
+ cachedHandler = null;
246
379
  return builder;
247
380
  },
248
381
  middlewares(list) {
249
- globalMiddlewares = list;
382
+ globalMiddlewares = [...list];
250
383
  return builder;
251
384
  },
252
385
  get handler() {
@@ -255,11 +388,36 @@ function createApp(config) {
255
388
  }
256
389
  return cachedHandler;
257
390
  },
391
+ get router() {
392
+ return { routes: registeredRoutes };
393
+ },
258
394
  async listen(port, options) {
259
395
  const adapter = detectAdapter();
260
- return adapter.listen(port ?? DEFAULT_PORT, builder.handler, options);
396
+ const serverHandle = await adapter.listen(port ?? DEFAULT_PORT, builder.handler, options);
397
+ if (options?.logRoutes !== false) {
398
+ const routes = collectRoutes(config.basePath ?? "", registrations);
399
+ const url = `http://${serverHandle.hostname}:${serverHandle.port}`;
400
+ console.log(formatRouteLog(url, routes));
401
+ }
402
+ return serverHandle;
261
403
  }
262
404
  };
405
+ if (config.domains && config.domains.length > 0) {
406
+ 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}` });
417
+ }
418
+ }
419
+ }
420
+ }
263
421
  return builder;
264
422
  }
265
423
  // src/env/env-validator.ts
@@ -336,23 +494,36 @@ function createModuleDef(config) {
336
494
  return deepFreeze(def);
337
495
  }
338
496
  // src/vertz.ts
339
- var vertz = deepFreeze({
497
+ var vertz = /* @__PURE__ */ deepFreeze({
340
498
  env: createEnv,
341
499
  middleware: createMiddleware,
342
500
  moduleDef: createModuleDef,
343
501
  module: createModule,
344
- app: createApp
502
+ app: createApp,
503
+ server: createApp
345
504
  });
505
+
506
+ // src/index.ts
507
+ var createServer = createApp;
508
+ var createApp2 = (...args) => {
509
+ console.warn("⚠️ createApp() is deprecated. Use createServer() from @vertz/server instead.");
510
+ return createApp(...args);
511
+ };
346
512
  export {
347
513
  vertz,
514
+ ok,
348
515
  makeImmutable,
516
+ isOk,
517
+ isErr,
518
+ err,
349
519
  deepFreeze,
520
+ createServer,
350
521
  createModuleDef,
351
522
  createModule,
352
523
  createMiddleware,
353
524
  createImmutableProxy,
354
525
  createEnv,
355
- createApp,
526
+ createApp2 as createApp,
356
527
  VertzException,
357
528
  ValidationException,
358
529
  UnauthorizedException,
@@ -7,7 +7,7 @@ interface RawRequest {
7
7
  interface HandlerCtx {
8
8
  params: Record<string, unknown>;
9
9
  body: unknown;
10
- query: Record<string, unknown>;
10
+ query: Record<string, string>;
11
11
  headers: Record<string, unknown>;
12
12
  raw: RawRequest;
13
13
  options: Record<string, unknown>;
@@ -17,7 +17,7 @@ interface HandlerCtx {
17
17
  interface CtxConfig {
18
18
  params: Record<string, unknown>;
19
19
  body: unknown;
20
- query: Record<string, unknown>;
20
+ query: Record<string, string>;
21
21
  headers: Record<string, unknown>;
22
22
  raw: RawRequest;
23
23
  middlewareState: Record<string, unknown>;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@vertz/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
+ "description": "Vertz core framework primitives",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "https://github.com/vertz-dev/vertz.git",
@@ -38,11 +39,13 @@
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/node": "^22.0.0",
42
+ "@vitest/coverage-v8": "^4.0.18",
41
43
  "bunup": "latest",
42
44
  "typescript": "^5.7.0",
43
- "vitest": "^3.0.0"
45
+ "vitest": "^4.0.18"
44
46
  },
45
47
  "engines": {
46
48
  "node": ">=22"
47
- }
49
+ },
50
+ "sideEffects": false
48
51
  }