@vertz/core 0.1.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/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,43 @@ interface CorsConfig {
164
213
  maxAge?: number;
165
214
  exposedHeaders?: string[];
166
215
  }
216
+ interface EntityDefinition {
217
+ readonly kind?: string;
218
+ readonly name: string;
219
+ readonly model: unknown;
220
+ readonly access: Record<string, unknown>;
221
+ readonly before: Record<string, unknown>;
222
+ readonly after: Record<string, unknown>;
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>;
234
+ }
167
235
  interface AppConfig {
168
236
  basePath?: string;
169
237
  version?: string;
170
238
  cors?: CorsConfig;
239
+ /** Entity definitions for auto-CRUD route generation */
240
+ entities?: EntityDefinition[];
241
+ /** API prefix for entity routes (default: '/api/') */
242
+ apiPrefix?: string;
243
+ /** Enable response schema validation in dev mode (logs warnings but doesn't break response) */
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;
171
249
  }
172
250
  interface ListenOptions {
173
251
  hostname?: string;
252
+ logRoutes?: boolean;
174
253
  }
175
254
  interface ServerHandle {
176
255
  readonly port: number;
@@ -180,25 +259,69 @@ interface ServerHandle {
180
259
  interface ServerAdapter {
181
260
  listen(port: number, handler: (request: Request) => Promise<Response>, options?: ListenOptions): Promise<ServerHandle>;
182
261
  }
183
- interface AppBuilder {
184
- register(module: NamedModule, options?: Record<string, unknown>): AppBuilder;
185
- middlewares(list: NamedMiddlewareDef[]): AppBuilder;
262
+ interface RouteInfo {
263
+ method: string;
264
+ path: string;
265
+ }
266
+ interface AppBuilder<TMiddlewareCtx extends Record<string, unknown> = Record<string, unknown>> {
267
+ register(module: NamedModule, options?: Record<string, unknown>): AppBuilder<TMiddlewareCtx>;
268
+ middlewares<const M extends readonly NamedMiddlewareDef<any, any>[]>(list: M): AppBuilder<AccumulateProvides<M>>;
186
269
  readonly handler: (request: Request) => Promise<Response>;
187
270
  listen(port?: number, options?: ListenOptions): Promise<ServerHandle>;
271
+ /** Exposes registered routes for testing/inspection */
272
+ readonly router: {
273
+ routes: RouteInfo[];
274
+ };
188
275
  }
189
276
  declare function createApp(config: AppConfig): AppBuilder;
277
+ type ServiceFactory<
278
+ TDeps = unknown,
279
+ TState = unknown,
280
+ TMethods = unknown,
281
+ TOptions extends Record<string, unknown> = Record<string, unknown>,
282
+ TEnv extends Record<string, unknown> = Record<string, unknown>
283
+ > = ServiceDef<TDeps, TState, TMethods, TOptions, TEnv>;
284
+ interface ServiceBootInstruction {
285
+ type: "service";
286
+ id: string;
287
+ deps: string[];
288
+ factory: ServiceFactory;
289
+ options?: Record<string, unknown>;
290
+ env?: Record<string, unknown>;
291
+ }
292
+ interface ModuleBootInstruction {
293
+ type: "module";
294
+ id: string;
295
+ services: string[];
296
+ options?: Record<string, unknown>;
297
+ }
298
+ type BootInstruction = ServiceBootInstruction | ModuleBootInstruction;
299
+ interface BootSequence {
300
+ instructions: BootInstruction[];
301
+ shutdownOrder: string[];
302
+ }
190
303
  import { Schema as Schema3 } from "@vertz/schema";
191
304
  interface EnvConfig<T = unknown> {
192
305
  load?: string[];
193
306
  schema: Schema3<T>;
307
+ env?: Record<string, string | undefined>;
194
308
  }
309
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
310
+ type HttpStatusCode = 200 | 201 | 204 | 301 | 302 | 304 | 400 | 401 | 403 | 404 | 405 | 409 | 422 | 429 | 500 | 502 | 503 | 504;
311
+ import { Infer, Infer as Infer2 } from "@vertz/schema";
195
312
  declare function createEnv<T>(config: EnvConfig<T>): T;
196
313
  declare class VertzException extends Error {
197
314
  readonly statusCode: number;
198
315
  readonly code: string;
199
316
  readonly details?: unknown;
200
317
  constructor(message: string, statusCode?: number, code?: string, details?: unknown);
201
- toJSON(): Record<string, unknown>;
318
+ toJSON(): {
319
+ error: {
320
+ code: string;
321
+ message: string;
322
+ details?: unknown;
323
+ };
324
+ };
202
325
  }
203
326
  declare class BadRequestException extends VertzException {
204
327
  constructor(message: string, details?: unknown);
@@ -224,7 +347,16 @@ declare class ValidationException extends VertzException {
224
347
  path: string;
225
348
  message: string;
226
349
  }>);
227
- 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
+ };
228
360
  }
229
361
  declare class InternalServerErrorException extends VertzException {
230
362
  constructor(message: string, details?: unknown);
@@ -235,36 +367,22 @@ declare class ServiceUnavailableException extends VertzException {
235
367
  declare function createImmutableProxy<T extends object>(obj: T, contextName: string, rootName?: string, proxyCache?: WeakMap<object, unknown>): T;
236
368
  declare function deepFreeze<T>(obj: T, visited?: WeakSet<object>): T;
237
369
  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
370
  declare const vertz: {
264
371
  readonly env: typeof createEnv;
265
372
  readonly middleware: typeof createMiddleware;
266
373
  readonly moduleDef: typeof createModuleDef;
267
374
  readonly module: typeof createModule;
268
375
  readonly app: typeof createApp;
376
+ /** @since 0.2.0 — preferred alias for `app` */
377
+ readonly server: typeof createApp;
269
378
  };
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 };
379
+ /**
380
+ * Creates an HTTP server. Preferred entry point for building Vertz services.
381
+ * @since 0.2.0
382
+ */
383
+ declare const createServer: (config: AppConfig) => AppBuilder;
384
+ /**
385
+ * @deprecated Use `createServer` instead. `createApp` will be removed in v0.3.0.
386
+ */
387
+ declare const createApp2: (config: AppConfig) => AppBuilder;
388
+ export { vertz, makeImmutable, deepFreeze, createServer, createModuleDef, createModule, createMiddleware, createImmutableProxy, createEnv, createApp2 as createApp, VertzException, ValidationException, UnauthorizedException, ServiceUnavailableException, ServiceFactory, ServiceDef, ServiceBootInstruction, ServerHandle, ServerAdapter, RouterDef, RouteInfo, ResolveInjectMap, RawRequest, NotFoundException, NamedServiceDef, NamedRouterDef, NamedModuleDef, NamedModule, NamedMiddlewareDef, ModuleDef, ModuleBootInstruction, Module, MiddlewareDef, ListenOptions, InternalServerErrorException, Infer2 as InferSchema, Infer, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, ExtractMethods, EnvConfig, EntityRouteEntry, Deps, DeepReadonly, Ctx, CorsConfig, ConflictException, BootSequence, BootInstruction, BadRequestException, AppConfig, AppBuilder, AccumulateProvides };