@vertz/core 0.0.2 → 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.d.ts CHANGED
@@ -18,6 +18,23 @@ interface NamedMiddlewareDef<
18
18
  > extends MiddlewareDef<TRequires, TProvides> {
19
19
  name: string;
20
20
  }
21
+ /**
22
+ * Identity type for middleware type accumulation.
23
+ * Uses `{}` intentionally as it is the identity element for intersection types.
24
+ */
25
+ type EmptyProvides = {};
26
+ /**
27
+ * Extracts TProvides from a NamedMiddlewareDef by inspecting the handler return type.
28
+ * Uses the handler signature to extract the provides type without variance issues.
29
+ */
30
+ type ExtractProvides<T> = T extends {
31
+ handler: (...args: never[]) => Promise<infer P> | infer P;
32
+ } ? P extends Record<string, unknown> ? P : EmptyProvides : EmptyProvides;
33
+ /**
34
+ * Recursively accumulates the TProvides types from a tuple of middleware definitions
35
+ * into an intersection type. Given [M1<P1>, M2<P2>, M3<P3>], produces P1 & P2 & P3.
36
+ */
37
+ type AccumulateProvides<T extends readonly NamedMiddlewareDef<any, any>[]> = T extends readonly [infer First, ...infer Rest extends readonly NamedMiddlewareDef<any, any>[]] ? ExtractProvides<First> & AccumulateProvides<Rest> : EmptyProvides;
21
38
  declare function createMiddleware<
22
39
  TRequires extends Record<string, unknown> = Record<string, unknown>,
23
40
  TProvides extends Record<string, unknown> = Record<string, unknown>
@@ -34,16 +51,20 @@ interface ModuleDef<
34
51
  interface ServiceDef<
35
52
  TDeps = unknown,
36
53
  TState = unknown,
37
- TMethods = unknown
54
+ TMethods = unknown,
55
+ TOptions extends Record<string, unknown> = Record<string, unknown>,
56
+ TEnv extends Record<string, unknown> = Record<string, unknown>
38
57
  > {
39
58
  inject?: Record<string, unknown>;
40
- onInit?: (deps: TDeps) => Promise<TState> | TState;
41
- methods: (deps: TDeps, state: TState) => TMethods;
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;
42
63
  onDestroy?: (deps: TDeps, state: TState) => Promise<void> | void;
43
64
  }
44
- interface RouterDef {
65
+ interface RouterDef<TInject extends Record<string, unknown> = Record<string, unknown>> {
45
66
  prefix: string;
46
- inject?: Record<string, unknown>;
67
+ inject?: TInject;
47
68
  }
48
69
  interface Module<TDef extends ModuleDef = ModuleDef> {
49
70
  definition: TDef;
@@ -63,7 +84,7 @@ interface RawRequest {
63
84
  interface HandlerCtx {
64
85
  params: Record<string, unknown>;
65
86
  body: unknown;
66
- query: Record<string, unknown>;
87
+ query: Record<string, string>;
67
88
  headers: Record<string, unknown>;
68
89
  raw: RawRequest;
69
90
  options: Record<string, unknown>;
@@ -72,88 +93,116 @@ interface HandlerCtx {
72
93
  }
73
94
  type Deps<T extends Record<string, unknown>> = DeepReadonly<T>;
74
95
  type Ctx<T extends Record<string, unknown>> = DeepReadonly<T>;
75
- type InferOutput<T> = T extends {
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 {
76
109
  _output: infer O;
77
110
  } ? O : T extends {
78
111
  parse(v: unknown): infer P;
79
- } ? P : unknown;
112
+ } ? P : TDefault;
80
113
  type TypedHandlerCtx<
81
114
  TParams = unknown,
82
115
  TQuery = unknown,
83
116
  THeaders = unknown,
84
- TBody = unknown
117
+ TBody = unknown,
118
+ TMiddleware extends Record<string, unknown> = Record<string, unknown>
85
119
  > = Omit<HandlerCtx, "params" | "query" | "headers" | "body"> & {
86
120
  params: TParams;
87
121
  query: TQuery;
88
122
  headers: THeaders;
89
123
  body: TBody;
90
- };
124
+ } & TMiddleware;
91
125
  interface RouteConfig<
92
126
  TParams = unknown,
93
127
  TQuery = unknown,
94
128
  THeaders = unknown,
95
- TBody = unknown
129
+ TBody = unknown,
130
+ TMiddleware extends Record<string, unknown> = Record<string, unknown>
96
131
  > {
97
132
  params?: TParams;
98
133
  body?: TBody;
99
134
  query?: TQuery;
100
135
  response?: unknown;
136
+ /** Error schemas for errors-as-values pattern. Keys are HTTP status codes. */
137
+ errors?: Record<number, unknown>;
101
138
  headers?: THeaders;
102
139
  middlewares?: unknown[];
103
- handler: (ctx: TypedHandlerCtx<InferOutput<TParams>, InferOutput<TQuery>, InferOutput<THeaders>, InferOutput<TBody>>) => unknown;
140
+ handler: (ctx: TypedHandlerCtx<InferOutput<TParams>, InferOutput<TQuery, Record<string, string>>, InferOutput<THeaders>, InferOutput<TBody>, TMiddleware>) => unknown;
104
141
  }
105
142
  interface Route {
106
143
  method: string;
107
144
  path: string;
108
- config: RouteConfig<unknown, unknown, unknown, unknown>;
109
- }
110
- type HttpMethodFn = <
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
+ > = <
111
162
  TParams,
112
163
  TQuery,
113
164
  THeaders,
114
165
  TBody
115
- >(path: `/${string}`, config: RouteConfig<TParams, TQuery, THeaders, TBody>) => NamedRouterDef;
116
- interface NamedRouterDef extends RouterDef {
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> {
117
171
  moduleName: string;
118
172
  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;
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>;
132
179
  }
133
180
  interface NamedModuleDef<
134
181
  TImports extends Record<string, unknown> = Record<string, unknown>,
135
- TOptions 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>
136
184
  > extends ModuleDef<TImports, TOptions> {
137
185
  service: <
138
186
  TDeps,
139
187
  TState,
140
188
  TMethods
141
189
  >(config: ServiceDef<TDeps, TState, TMethods>) => NamedServiceDef<TDeps, TState, TMethods>;
142
- router: (config: RouterDef) => NamedRouterDef;
190
+ router: <TInject extends Record<string, unknown> = Record<string, unknown>>(config: RouterDef<TInject>) => NamedRouterDef<TMiddleware, TInject>;
143
191
  }
144
192
  declare function createModuleDef<
145
193
  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>;
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>;
148
197
  interface NamedModule {
149
198
  definition: NamedModuleDef;
150
199
  services: NamedServiceDef[];
151
- routers: NamedRouterDef[];
200
+ routers: NamedRouterDef<any, any>[];
152
201
  exports: NamedServiceDef[];
153
202
  }
154
203
  declare function createModule(definition: NamedModuleDef, config: {
155
204
  services: NamedServiceDef[];
156
- routers: NamedRouterDef[];
205
+ routers: NamedRouterDef<any, any>[];
157
206
  exports: NamedServiceDef[];
158
207
  }): NamedModule;
159
208
  interface CorsConfig {
@@ -164,13 +213,29 @@ interface CorsConfig {
164
213
  maxAge?: number;
165
214
  exposedHeaders?: string[];
166
215
  }
216
+ interface DomainDefinition {
217
+ readonly name: string;
218
+ readonly type: string;
219
+ readonly table: unknown;
220
+ readonly exposedRelations: Record<string, unknown>;
221
+ readonly access: Record<string, unknown>;
222
+ readonly handlers: Record<string, unknown>;
223
+ readonly actions: Record<string, unknown>;
224
+ }
167
225
  interface AppConfig {
168
226
  basePath?: string;
169
227
  version?: string;
170
228
  cors?: CorsConfig;
229
+ /** Domain definitions for auto-CRUD route generation */
230
+ domains?: DomainDefinition[];
231
+ /** API prefix for domain routes (default: '/api/') */
232
+ apiPrefix?: string;
233
+ /** Enable response schema validation in dev mode (logs warnings but doesn't break response) */
234
+ validateResponses?: boolean;
171
235
  }
172
236
  interface ListenOptions {
173
237
  hostname?: string;
238
+ logRoutes?: boolean;
174
239
  }
175
240
  interface ServerHandle {
176
241
  readonly port: number;
@@ -180,18 +245,55 @@ interface ServerHandle {
180
245
  interface ServerAdapter {
181
246
  listen(port: number, handler: (request: Request) => Promise<Response>, options?: ListenOptions): Promise<ServerHandle>;
182
247
  }
183
- interface AppBuilder {
184
- register(module: NamedModule, options?: Record<string, unknown>): AppBuilder;
185
- middlewares(list: NamedMiddlewareDef[]): AppBuilder;
248
+ interface RouteInfo {
249
+ method: string;
250
+ path: string;
251
+ }
252
+ interface AppBuilder<TMiddlewareCtx extends Record<string, unknown> = Record<string, unknown>> {
253
+ register(module: NamedModule, options?: Record<string, unknown>): AppBuilder<TMiddlewareCtx>;
254
+ middlewares<const M extends readonly NamedMiddlewareDef<any, any>[]>(list: M): AppBuilder<AccumulateProvides<M>>;
186
255
  readonly handler: (request: Request) => Promise<Response>;
187
256
  listen(port?: number, options?: ListenOptions): Promise<ServerHandle>;
257
+ /** Exposes registered routes for testing/inspection */
258
+ readonly router: {
259
+ routes: RouteInfo[];
260
+ };
188
261
  }
189
262
  declare function createApp(config: AppConfig): AppBuilder;
263
+ type ServiceFactory<
264
+ TDeps = unknown,
265
+ TState = unknown,
266
+ TMethods = unknown,
267
+ TOptions extends Record<string, unknown> = Record<string, unknown>,
268
+ TEnv extends Record<string, unknown> = Record<string, unknown>
269
+ > = ServiceDef<TDeps, TState, TMethods, TOptions, TEnv>;
270
+ interface ServiceBootInstruction {
271
+ type: "service";
272
+ id: string;
273
+ deps: string[];
274
+ factory: ServiceFactory;
275
+ options?: Record<string, unknown>;
276
+ env?: Record<string, unknown>;
277
+ }
278
+ interface ModuleBootInstruction {
279
+ type: "module";
280
+ id: string;
281
+ services: string[];
282
+ options?: Record<string, unknown>;
283
+ }
284
+ type BootInstruction = ServiceBootInstruction | ModuleBootInstruction;
285
+ interface BootSequence {
286
+ instructions: BootInstruction[];
287
+ shutdownOrder: string[];
288
+ }
190
289
  import { Schema as Schema3 } from "@vertz/schema";
191
290
  interface EnvConfig<T = unknown> {
192
291
  load?: string[];
193
292
  schema: Schema3<T>;
194
293
  }
294
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
295
+ type HttpStatusCode = 200 | 201 | 204 | 301 | 302 | 304 | 400 | 401 | 403 | 404 | 405 | 409 | 422 | 429 | 500 | 502 | 503 | 504;
296
+ import { Infer, Infer as Infer2 } from "@vertz/schema";
195
297
  declare function createEnv<T>(config: EnvConfig<T>): T;
196
298
  declare class VertzException extends Error {
197
299
  readonly statusCode: number;
@@ -235,36 +337,137 @@ declare class ServiceUnavailableException extends VertzException {
235
337
  declare function createImmutableProxy<T extends object>(obj: T, contextName: string, rootName?: string, proxyCache?: WeakMap<object, unknown>): T;
236
338
  declare function deepFreeze<T>(obj: T, visited?: WeakSet<object>): T;
237
339
  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
340
  declare const vertz: {
264
341
  readonly env: typeof createEnv;
265
342
  readonly middleware: typeof createMiddleware;
266
343
  readonly moduleDef: typeof createModuleDef;
267
344
  readonly module: typeof createModule;
268
345
  readonly app: typeof createApp;
346
+ /** @since 0.2.0 — preferred alias for `app` */
347
+ readonly server: typeof createApp;
269
348
  };
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 };
349
+ /**
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
+ * Creates an HTTP server. Preferred entry point for building Vertz services.
466
+ * @since 0.2.0
467
+ */
468
+ declare const createServer: (config: AppConfig) => AppBuilder;
469
+ /**
470
+ * @deprecated Use `createServer` instead. `createApp` will be removed in v0.3.0.
471
+ */
472
+ 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 };