@velajs/vela 1.5.1 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.0]
4
+
5
+ The exception-filter chain now reaches into attached middlewares for full NestJS parity, and a new `createLazyParamDecorator` helper closes the parameter-decorator-vs-guard ordering hazard at the public-API surface.
6
+
7
+ ### Added
8
+
9
+ - **`createLazyParamDecorator((data, ctx) => T)`.** Custom parameter decorators whose factory runs the *first time the handler reads a property on the resolved value* — not during argument extraction. Vela's argument resolver runs before guards by design (`extract args → guards → handler`), which means a `createParamDecorator` factory that depends on guard-populated state observes an empty slot. The lazy variant returns a `Proxy` whose traps invoke the factory on demand; the `get` trap short-circuits `prop === 'then'` so `await value` does not consider the proxy a thenable and therefore does not trigger eager resolution. Method results are auto-bound to the resolved real target so detached calls keep `this`; `ownKeys` + `getOwnPropertyDescriptor` are implemented so `JSON.stringify(value)` works after one access. Exported from the root barrel and from `@velajs/vela/internal` via the same surface as `createParamDecorator`. Documented in README under *Custom parameter decorators with deferred resolution*.
10
+
11
+ ### Changed
12
+
13
+ - **Exception-filter chain now catches errors thrown inside vela-attached middlewares.** Previously the `APP_FILTER` / `@UseFilters` chain only handled errors thrown from `@Controller` handler methods; errors from middlewares (global, `MiddlewareConsumer.apply(...).forRoutes(...)`, and per-route attached) bypassed the chain and surfaced as Hono's outer 500. They now flow through the same filter resolution as handler exceptions, with a synthesized `ExecutionContext` whose `getClass()` returns the `VelaMiddlewareHost` marker and whose `getHandler()` returns the `Symbol.for('vela.middleware')` sentinel — `getType()` stays `'http'` for NestJS parity. Only global filters apply at the middleware boundary (per-handler `@UseFilters` requires a controller call frame); for thrown `HttpException`s with no catching filter, the chain renders the exception's own response/status (matching handler-thrown semantics). Non-`HttpException` throws with no catching filter re-throw to preserve the existing default-500 path. Non-throwing middleware paths (returning a `Response`, calling `await next()`, resolving a promise) are byte-identical to before.
14
+
3
15
  ## Unreleased
4
16
 
5
17
  A sanctioned per-request injectable lands as a framework primitive, the metadata-store unification finally has its regression tests, and dynamic module identity becomes consistent — closing the last open audit item.
package/README.md CHANGED
@@ -143,6 +143,51 @@ class MyModule {
143
143
 
144
144
  `forRootAsync` callers should pass `key` explicitly when the same module needs multiple async instances — factories aren't structurally hashable.
145
145
 
146
+ ## Custom parameter decorators with deferred resolution
147
+
148
+ Vela's argument resolver runs **before** guards (`extract args → guards → handler`). A custom parameter decorator built with `createParamDecorator` therefore observes any state populated by a guard as still empty — its factory has already fired by the time the guard runs.
149
+
150
+ When a parameter's value depends on guard output (a `REQUEST_CONTEXT`-stored user, a tenant resolved from a JWT, etc.), use `createLazyParamDecorator` instead. The factory does not run during argument extraction; it runs the first time the handler reads a property on the resolved value:
151
+
152
+ ```ts
153
+ import {
154
+ createLazyParamDecorator,
155
+ Inject,
156
+ Injectable,
157
+ REQUEST_CONTEXT,
158
+ Scope,
159
+ UseGuards,
160
+ } from '@velajs/vela';
161
+ import type { CanActivate, ExecutionContext, RequestContext } from '@velajs/vela';
162
+
163
+ const USER_KEY = Symbol.for('app.user');
164
+
165
+ @Injectable({ scope: Scope.REQUEST })
166
+ class AuthGuard implements CanActivate {
167
+ constructor(@Inject(REQUEST_CONTEXT) private readonly ctx: RequestContext) {}
168
+ canActivate(_e: ExecutionContext): boolean {
169
+ this.ctx.set(USER_KEY, { id: 'u-1', name: 'ada' }); // populated here
170
+ return true;
171
+ }
172
+ }
173
+
174
+ const CurrentUser = createLazyParamDecorator((_data, ctx: ExecutionContext) => {
175
+ const reqCtx = ctx
176
+ .getContext()
177
+ .get('container')
178
+ .resolve<RequestContext>(REQUEST_CONTEXT);
179
+ return reqCtx.get(USER_KEY);
180
+ });
181
+
182
+ @UseGuards(AuthGuard)
183
+ @Get('/me')
184
+ me(@CurrentUser() user: { id: string; name: string }) {
185
+ return { id: user.id }; // factory runs here, AFTER AuthGuard
186
+ }
187
+ ```
188
+
189
+ The proxy short-circuits `then` on its `get` trap so `await value` returns the proxy itself rather than triggering eager resolution. Method results are auto-bound to the resolved real target, so detached method calls keep `this`. `JSON.stringify(value)` works after one access (the proxy implements `ownKeys` + `getOwnPropertyDescriptor`).
190
+
146
191
  ## Companion packages
147
192
 
148
193
  | Package | Purpose |
@@ -2,3 +2,7 @@ import type { Context } from 'hono';
2
2
  import type { Type } from '../container/types';
3
3
  import type { ExecutionContext } from '../pipeline/types';
4
4
  export declare function buildExecutionContext(c: Context, controller: Type, handlerName: string | symbol): ExecutionContext;
5
+ export declare class VelaMiddlewareHost {
6
+ }
7
+ export declare const VELA_MIDDLEWARE_HANDLER: unique symbol;
8
+ export declare function buildMiddlewareExecutionContext(c: Context): ExecutionContext;
@@ -13,3 +13,20 @@ export function buildExecutionContext(c, controller, handlerName) {
13
13
  })
14
14
  };
15
15
  }
16
+ // Sentinel marker exposed via `ExecutionContext.getClass()` when the host is a
17
+ // vela-attached middleware rather than a controller handler. Filters that
18
+ // branch on the calling class (rare) can detect the middleware origin without
19
+ // introducing a separate `getType()` value — `getType()` stays `'http'` for
20
+ // NestJS parity.
21
+ export class VelaMiddlewareHost {
22
+ }
23
+ // Synthesizes an ExecutionContext for an exception thrown inside a
24
+ // vela-attached middleware. There is no controller class or handler method
25
+ // on the call stack at that point, so `getClass()` returns the
26
+ // `VelaMiddlewareHost` marker and `getHandler()` returns the
27
+ // `vela.middleware` symbol — both stable, comparable values that filter
28
+ // authors can pattern-match against if needed.
29
+ export const VELA_MIDDLEWARE_HANDLER = Symbol.for('vela.middleware');
30
+ export function buildMiddlewareExecutionContext(c) {
31
+ return buildExecutionContext(c, VelaMiddlewareHost, VELA_MIDDLEWARE_HANDLER);
32
+ }
@@ -3,4 +3,5 @@ export type { RouteManagerOptions } from './route.manager';
3
3
  export { MiddlewareBuilder } from '../module/middleware';
4
4
  export type { MiddlewareConsumer, MiddlewareConfigProxy, RouteInfo, NestModule, MiddlewareRouteDefinition } from '../module/middleware';
5
5
  export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, isController, } from './decorators';
6
+ export { createLazyParamDecorator } from './lazy-param.decorator';
6
7
  export type { RouteMetadata, ControllerMetadata, ControllerOptions, ParamMetadata, ControllerRegistration, } from './types';
@@ -1,3 +1,4 @@
1
1
  export { RouteManager } from "./route.manager.js";
2
2
  export { MiddlewareBuilder } from "../module/middleware.js";
3
3
  export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, isController } from "./decorators.js";
4
+ export { createLazyParamDecorator } from "./lazy-param.decorator.js";
@@ -0,0 +1,37 @@
1
+ import type { PipeType } from '../registry/types';
2
+ import type { ExecutionContext } from '../pipeline/types';
3
+ /**
4
+ * Factory for parameter decorators whose value materializes lazily — the
5
+ * factory does not run during argument extraction; it runs the first time
6
+ * the handler reads a property on the resolved value.
7
+ *
8
+ * Why this exists: vela's argument resolver runs *before* guards
9
+ * (handler-executor order: extract args → guards → handler). A custom
10
+ * `createParamDecorator` factory that depends on guard-populated state
11
+ * (e.g. a value a guard places into `REQUEST_CONTEXT`) therefore observes
12
+ * an empty slot. `createLazyParamDecorator` defers factory execution to
13
+ * first property access on the proxied result, by which point guards have
14
+ * run and the slot is populated.
15
+ *
16
+ * The proxy explicitly short-circuits `prop === 'then'` so `await value`
17
+ * does not consider the proxy a thenable, which would otherwise trigger
18
+ * eager resolution. This single invariant is what makes the helper safe to
19
+ * pass through `async` boundaries without surprise.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const CurrentUser = createLazyParamDecorator(
24
+ * (_data: unknown, ctx: ExecutionContext) => {
25
+ * const reqCtx = ctx.getContext().get('container').resolve(REQUEST_CONTEXT);
26
+ * return reqCtx.get('user'); // populated by AuthGuard
27
+ * },
28
+ * );
29
+ *
30
+ * @UseGuards(AuthGuard)
31
+ * @Get('/me')
32
+ * me(@CurrentUser() user: User) {
33
+ * return { id: user.id }; // factory runs here, after AuthGuard
34
+ * }
35
+ * ```
36
+ */
37
+ export declare function createLazyParamDecorator<TData = unknown>(factory: (data: TData, ctx: ExecutionContext) => unknown): (data?: TData, ...pipes: PipeType[]) => ParameterDecorator;
@@ -0,0 +1,117 @@
1
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
2
+ import { buildExecutionContext } from "./execution-context.js";
3
+ const CUSTOM_PARAM_TYPE = 'custom';
4
+ /**
5
+ * Factory for parameter decorators whose value materializes lazily — the
6
+ * factory does not run during argument extraction; it runs the first time
7
+ * the handler reads a property on the resolved value.
8
+ *
9
+ * Why this exists: vela's argument resolver runs *before* guards
10
+ * (handler-executor order: extract args → guards → handler). A custom
11
+ * `createParamDecorator` factory that depends on guard-populated state
12
+ * (e.g. a value a guard places into `REQUEST_CONTEXT`) therefore observes
13
+ * an empty slot. `createLazyParamDecorator` defers factory execution to
14
+ * first property access on the proxied result, by which point guards have
15
+ * run and the slot is populated.
16
+ *
17
+ * The proxy explicitly short-circuits `prop === 'then'` so `await value`
18
+ * does not consider the proxy a thenable, which would otherwise trigger
19
+ * eager resolution. This single invariant is what makes the helper safe to
20
+ * pass through `async` boundaries without surprise.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const CurrentUser = createLazyParamDecorator(
25
+ * (_data: unknown, ctx: ExecutionContext) => {
26
+ * const reqCtx = ctx.getContext().get('container').resolve(REQUEST_CONTEXT);
27
+ * return reqCtx.get('user'); // populated by AuthGuard
28
+ * },
29
+ * );
30
+ *
31
+ * @UseGuards(AuthGuard)
32
+ * @Get('/me')
33
+ * me(@CurrentUser() user: User) {
34
+ * return { id: user.id }; // factory runs here, after AuthGuard
35
+ * }
36
+ * ```
37
+ */ export function createLazyParamDecorator(factory) {
38
+ return (data, ...pipes)=>{
39
+ return (target, propertyKey, parameterIndex)=>{
40
+ if (propertyKey === undefined) {
41
+ throw new Error('Parameter decorators can only be used on method parameters');
42
+ }
43
+ MetadataRegistry.addParameter(target.constructor, propertyKey, {
44
+ index: parameterIndex,
45
+ type: CUSTOM_PARAM_TYPE,
46
+ name: undefined,
47
+ factory: (_unused, ctx)=>{
48
+ const honoCtx = ctx;
49
+ const execCtx = buildExecutionContext(honoCtx, target.constructor, propertyKey);
50
+ return createLazyProxy(()=>factory(data, execCtx));
51
+ },
52
+ ...pipes.length > 0 ? {
53
+ pipes
54
+ } : {}
55
+ });
56
+ };
57
+ };
58
+ }
59
+ // Single-shot resolution: the factory runs at most once; subsequent reads
60
+ // share the cached real value. The proxy's traps all funnel through
61
+ // `materialize` to keep that invariant in one place.
62
+ function createLazyProxy(produce) {
63
+ let resolved = false;
64
+ let value;
65
+ const materialize = ()=>{
66
+ if (!resolved) {
67
+ value = produce();
68
+ resolved = true;
69
+ }
70
+ return value;
71
+ };
72
+ // Target is an empty null-prototype object — proxies still need a
73
+ // backing target for the runtime to forward to, but using a real object
74
+ // would leak its own properties (`hasOwnProperty`, etc.) into trap
75
+ // results. `Object.create(null)` keeps the namespace clean.
76
+ const target = Object.create(null);
77
+ const handler = {
78
+ get (_t, prop, receiver) {
79
+ // Critical: do NOT report the proxy as thenable. If we let the JS
80
+ // runtime read `then` (during `await proxy`), the engine would
81
+ // observe a function-shaped value, treat the proxy as a promise,
82
+ // call `then(resolve, reject)` and trigger eager resolution before
83
+ // the consumer ever touches a real property. Returning `undefined`
84
+ // here makes `await proxy` return the proxy itself, untouched.
85
+ if (prop === 'then') return undefined;
86
+ const real = materialize();
87
+ if (real === null || real === undefined) return undefined;
88
+ const descriptor = Reflect.get(real, prop, receiver);
89
+ // Bind functions back to the real target so `this` works as the
90
+ // consumer expects on instance methods.
91
+ if (typeof descriptor === 'function') {
92
+ return descriptor.bind(real);
93
+ }
94
+ return descriptor;
95
+ },
96
+ has (_t, prop) {
97
+ const real = materialize();
98
+ return real !== null && real !== undefined && Reflect.has(real, prop);
99
+ },
100
+ ownKeys () {
101
+ const real = materialize();
102
+ if (real === null || real === undefined) return [];
103
+ return Reflect.ownKeys(real);
104
+ },
105
+ getOwnPropertyDescriptor (_t, prop) {
106
+ const real = materialize();
107
+ if (real === null || real === undefined) return undefined;
108
+ const descriptor = Reflect.getOwnPropertyDescriptor(real, prop);
109
+ // Proxy invariants require returned descriptors to be configurable
110
+ // when the underlying target lacks the property, which is always
111
+ // true for our null-prototype empty target.
112
+ if (descriptor) descriptor.configurable = true;
113
+ return descriptor;
114
+ }
115
+ };
116
+ return new Proxy(target, handler);
117
+ }
@@ -37,6 +37,8 @@ export declare class RouteManager {
37
37
  useGlobalFilterTokens(...filterTokens: Array<Token<ExceptionFilter>>): this;
38
38
  private getMiddlewarePriority;
39
39
  private getRequestContainer;
40
+ private wrapMiddlewareWithFilters;
41
+ private mapMiddlewareError;
40
42
  registerController(controller: Type): this;
41
43
  build(): Promise<Hono>;
42
44
  private registerRoute;
@@ -1,12 +1,16 @@
1
1
  import { Hono } from "hono";
2
2
  import { HttpMethod } from "../constants.js";
3
+ import { HttpException } from "../errors/http-exception.js";
3
4
  import { getMetadata } from "../metadata.js";
4
5
  import { joinPaths } from "../registry/paths.js";
5
6
  import { ArgumentResolver } from "./argument-resolver.js";
7
+ import { buildMiddlewareExecutionContext } from "./execution-context.js";
6
8
  import { HandlerExecutor } from "./handler-executor.js";
7
9
  import { instantiate, instantiateMany } from "./instantiate.js";
8
10
  import { REQUEST_CONTEXT, createRequestContext } from "./request-context.js";
11
+ import { mapResponse } from "./response-mapper.js";
9
12
  import { ComponentManager } from "../pipeline/component.manager.js";
13
+ import { shouldFilterCatch } from "../pipeline/decorators.js";
10
14
  import { MetadataRegistry } from "../registry/metadata.registry.js";
11
15
  const defaultGetClientIp = (c)=>c.req.raw.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? c.req.raw.headers.get('x-real-ip') ?? null;
12
16
  export class RouteManager {
@@ -146,6 +150,54 @@ export class RouteManager {
146
150
  c.set('container', child);
147
151
  return child;
148
152
  }
153
+ // Wraps an attached middleware so a thrown error flows through the
154
+ // exception-filter chain instead of bypassing it on the way to Hono's
155
+ // outer error handler. Mirrors the catch tail in HandlerExecutor — global
156
+ // filters are the only scope reachable here, since per-handler filters
157
+ // require a controller call frame that the middleware boundary lacks.
158
+ // Non-throwing middleware paths (returning a Response, calling `next()`,
159
+ // resolving a promise) are byte-identical to before — the wrapper only
160
+ // intercepts thrown / rejected values.
161
+ wrapMiddlewareWithFilters(handler) {
162
+ return async (c, next)=>{
163
+ try {
164
+ return await handler(c, next);
165
+ } catch (error) {
166
+ const response = await this.mapMiddlewareError(c, error);
167
+ // Force-replace any handler-set response. A middleware that throws
168
+ // *after* `await next()` has the handler's body already attached
169
+ // to `c.res`; without overwriting it, Hono would emit the
170
+ // pre-throw success response and the filter's render would be
171
+ // dropped.
172
+ c.res = response;
173
+ return response;
174
+ }
175
+ };
176
+ }
177
+ async mapMiddlewareError(c, error) {
178
+ const requestContainer = this.getRequestContainer(c);
179
+ const filters = instantiateMany(this.globalFilters, requestContainer);
180
+ const host = buildMiddlewareExecutionContext(c);
181
+ for (const filter of filters){
182
+ if (shouldFilterCatch(filter, error)) {
183
+ try {
184
+ const filtered = await filter.catch(error, host);
185
+ return mapResponse(c, filtered);
186
+ } catch {
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ if (error instanceof HttpException) {
192
+ const response = error.getResponse();
193
+ const status = error.getStatus();
194
+ return c.json(response, status);
195
+ }
196
+ // Non-HttpException with no catching filter: re-throw so Hono's
197
+ // outer error handler produces the same default-500 response it
198
+ // produced before this wrapping was introduced.
199
+ throw error;
200
+ }
149
201
  registerController(controller) {
150
202
  const prefix = MetadataRegistry.getControllerPath(controller);
151
203
  const options = MetadataRegistry.getControllerOptions(controller);
@@ -176,11 +228,11 @@ export class RouteManager {
176
228
  priority: this.getMiddlewarePriority(entry)
177
229
  })).sort((a, b)=>a.priority - b.priority || a.index - b.index);
178
230
  for (const { entry } of sortedGlobal){
179
- app.use('*', (c, next)=>{
231
+ app.use('*', this.wrapMiddlewareWithFilters((c, next)=>{
180
232
  const requestContainer = this.getRequestContainer(c);
181
233
  const resolved = instantiate(entry, requestContainer);
182
234
  return resolved.use(c, next);
183
- });
235
+ }));
184
236
  }
185
237
  const sortedConsumer = this.consumerMiddlewareDefinitions.map((def, index)=>({
186
238
  def,
@@ -190,7 +242,7 @@ export class RouteManager {
190
242
  for (const { def } of sortedConsumer){
191
243
  const matchRoute = this.compileRouteMatcher(def.routes);
192
244
  const matchExclude = this.compileRouteMatcher(def.excludes);
193
- app.use('*', (c, next)=>{
245
+ app.use('*', this.wrapMiddlewareWithFilters((c, next)=>{
194
246
  const path = c.req.path;
195
247
  const method = c.req.method;
196
248
  if (!matchRoute(path, method) || matchExclude(path, method)) return next();
@@ -202,7 +254,7 @@ export class RouteManager {
202
254
  return instance.use(c, ()=>runChain(index + 1)).then(()=>{});
203
255
  };
204
256
  return runChain(0);
205
- });
257
+ }));
206
258
  }
207
259
  // First pass: register all custom routes (must come before CRUD /:id routes).
208
260
  for (const { controller, metadata, routes } of this.controllers){
@@ -215,11 +267,11 @@ export class RouteManager {
215
267
  const handler = this.handlerExecutor.create(route, controller, allParamMetadata);
216
268
  for (const fullPath of versionedPaths){
217
269
  for (const middlewareItem of middlewareItems){
218
- app.use(fullPath, (c, next)=>{
270
+ app.use(fullPath, this.wrapMiddlewareWithFilters((c, next)=>{
219
271
  const requestContainer = this.getRequestContainer(c);
220
272
  const resolved = instantiate(middlewareItem, requestContainer);
221
273
  return resolved.use(c, next);
222
- });
274
+ }));
223
275
  }
224
276
  this.registerRoute(app, route.method, fullPath, handler);
225
277
  }
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export type { OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter,
9
9
  export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, MultipleProvidersFoundError, ROOT_MODULE_ID, mixin, } from './container/index';
10
10
  export type { Type, Token, InjectableOptions, ProviderOptions, ModuleScope, ContainerOptions, Diagnostics, } from './container/index';
11
11
  export { METADATA_KEYS, HttpMethod, ParamType, Scope } from './constants';
12
- export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, } from './http/index';
12
+ export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, createLazyParamDecorator, applyDecorators, } from './http/index';
13
13
  export { REQUEST_CONTEXT } from './http/request-context';
14
14
  export type { RequestContext } from './http/request-context';
15
15
  export { Logger, LogLevel } from './services/index';
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, fo
11
11
  // Constants
12
12
  export { METADATA_KEYS, HttpMethod, ParamType, Scope } from "./constants.js";
13
13
  // HTTP Decorators
14
- export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators } from "./http/index.js";
14
+ export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, createLazyParamDecorator, applyDecorators } from "./http/index.js";
15
15
  // Request-scoped context primitive
16
16
  export { REQUEST_CONTEXT } from "./http/request-context.js";
17
17
  // Services
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",