@velajs/vela 1.3.0 → 1.4.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 +27 -0
- package/dist/container/container.d.ts +1 -0
- package/dist/container/container.js +8 -0
- package/dist/factory/bootstrap.js +14 -0
- package/dist/http/request-context.d.ts +13 -0
- package/dist/http/request-context.js +22 -0
- package/dist/http/route.manager.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/registry/metadata.registry.js +0 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
A sanctioned per-request injectable lands as a framework primitive, and the metadata-store unification finally has its regression tests.
|
|
6
|
+
|
|
7
|
+
### New
|
|
8
|
+
|
|
9
|
+
- **`REQUEST_CONTEXT` injectable.** A request-scoped primitive carrying a stable `id` (mirrored from inbound `x-request-id` if present, else `crypto.randomUUID()`), `receivedAt`, the raw `Request`, the Hono `Context`, and a typed `set/get/has` bag for cross-cutting metadata. Seeded by `RouteManager` into each per-request child container; resolves through `@Inject(REQUEST_CONTEXT)` from any request-scoped service. No `AsyncLocalStorage` — edge-runtime contract intact (verified live under workerd via `pnpm test:workers`).
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Inject, Injectable, Scope, REQUEST_CONTEXT } from '@velajs/vela';
|
|
13
|
+
import type { RequestContext } from '@velajs/vela';
|
|
14
|
+
|
|
15
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
16
|
+
class TenantResolver {
|
|
17
|
+
constructor(@Inject(REQUEST_CONTEXT) private readonly ctx: RequestContext) {}
|
|
18
|
+
resolve() {
|
|
19
|
+
return this.ctx.hono.req.header('x-tenant') ?? 'default';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Internal cleanup
|
|
25
|
+
|
|
26
|
+
- **Metadata stacking + funnel coverage** (audit #8 follow-up). New `metadata-stacking.test.ts` asserts `appendCustomHandlerMeta` is order-deterministic, `Reflect.defineMetadata` round-trips through `MetadataRegistry`'s typed slots, `MetadataRegistry.reset()` clears `classMeta`/`handlerMeta`, and class+handler `@SetMetadata` on the same key remain independent.
|
|
27
|
+
- **Stale `WeakMap` comment removed** from `MetadataRegistry.reset()` — the WeakMap fallback was retired in 1.1.0; the comment was documentation drift.
|
|
28
|
+
- **`Container.setRequestInstance(token, value)`** — public method to pre-seed the per-request cache. Used by `RouteManager` to populate `REQUEST_CONTEXT` before any handler resolution runs.
|
|
29
|
+
|
|
30
|
+
## 1.3.0
|
|
31
|
+
|
|
5
32
|
Module boundaries are enforced. NestJS-shape: a service cannot resolve dependencies from a module it didn't import. Bootstrap is consolidated into a single primitive, discovery failures are diagnostically routed, and a generic plugin composer is included.
|
|
6
33
|
|
|
7
34
|
### New
|
|
@@ -15,6 +15,7 @@ export declare class Container {
|
|
|
15
15
|
private recordOrigin;
|
|
16
16
|
registerScope(scope: ModuleScope): void;
|
|
17
17
|
markGlobalToken(token: Token): void;
|
|
18
|
+
setRequestInstance(token: Token, value: unknown): void;
|
|
18
19
|
getDiagnostics(): Diagnostics;
|
|
19
20
|
resolve<T>(token: Token<T>, requestingModuleId?: string): T;
|
|
20
21
|
resolveAll<T>(token: Token<T>, requestingModuleId?: string): T[];
|
|
@@ -86,6 +86,14 @@ export class Container {
|
|
|
86
86
|
markGlobalToken(token) {
|
|
87
87
|
this.globals.add(token);
|
|
88
88
|
}
|
|
89
|
+
// Pre-seed the per-request cache. Only meaningful on a child container
|
|
90
|
+
// produced by createChild() — the root's requestInstances map is unused.
|
|
91
|
+
// Used by RouteManager to populate framework-provided request-scope
|
|
92
|
+
// values (REQUEST_CONTEXT) before any handler resolution runs, so the
|
|
93
|
+
// provider's factory never fires on the request path.
|
|
94
|
+
setRequestInstance(token, value) {
|
|
95
|
+
this.requestInstances.set(token, value);
|
|
96
|
+
}
|
|
89
97
|
getDiagnostics() {
|
|
90
98
|
return this.diagnostics;
|
|
91
99
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Scope } from "../constants.js";
|
|
1
2
|
import { Container } from "../container/container.js";
|
|
2
3
|
import { ModuleRef } from "../container/module-ref.js";
|
|
4
|
+
import { REQUEST_CONTEXT } from "../http/request-context.js";
|
|
3
5
|
import { RouteManager } from "../http/route.manager.js";
|
|
4
6
|
import { ModuleLoader } from "../module/module-loader.js";
|
|
5
7
|
import { bindAppProviders } from "../pipeline/app-providers.js";
|
|
@@ -39,6 +41,18 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
39
41
|
]){
|
|
40
42
|
container.markGlobalToken(t);
|
|
41
43
|
}
|
|
44
|
+
// REQUEST_CONTEXT is seeded into each request-scoped child by RouteManager
|
|
45
|
+
// before any handler resolves it (see route.manager.ts:getRequestContainer).
|
|
46
|
+
// The factory throws so misuse outside the request path surfaces immediately
|
|
47
|
+
// instead of materializing a phantom context.
|
|
48
|
+
container.register({
|
|
49
|
+
provide: REQUEST_CONTEXT,
|
|
50
|
+
scope: Scope.REQUEST,
|
|
51
|
+
useFactory: ()=>{
|
|
52
|
+
throw new Error('REQUEST_CONTEXT can only be resolved inside a request — ' + 'it is seeded by RouteManager when the request enters the pipeline.');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
container.markGlobalToken(REQUEST_CONTEXT);
|
|
42
56
|
const routeManager = new RouteManager(container, options);
|
|
43
57
|
ComponentManager.init(container);
|
|
44
58
|
const loader = new ModuleLoader(container, routeManager);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import { InjectionToken } from '../container/types';
|
|
3
|
+
export interface RequestContext {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly receivedAt: Date;
|
|
6
|
+
readonly request: Request;
|
|
7
|
+
readonly hono: Context;
|
|
8
|
+
set<T>(key: string | symbol, value: T): void;
|
|
9
|
+
get<T>(key: string | symbol): T | undefined;
|
|
10
|
+
has(key: string | symbol): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const REQUEST_CONTEXT: InjectionToken<RequestContext>;
|
|
13
|
+
export declare function createRequestContext(c: Context): RequestContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InjectionToken } from "../container/types.js";
|
|
2
|
+
export const REQUEST_CONTEXT = new InjectionToken('vela.RequestContext');
|
|
3
|
+
export function createRequestContext(c) {
|
|
4
|
+
const bag = new Map();
|
|
5
|
+
const inboundId = c.req.raw.headers.get('x-request-id');
|
|
6
|
+
const id = inboundId && inboundId.length > 0 ? inboundId : crypto.randomUUID();
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
receivedAt: new Date(),
|
|
10
|
+
request: c.req.raw,
|
|
11
|
+
hono: c,
|
|
12
|
+
set (key, value) {
|
|
13
|
+
bag.set(key, value);
|
|
14
|
+
},
|
|
15
|
+
get (key) {
|
|
16
|
+
return bag.get(key);
|
|
17
|
+
},
|
|
18
|
+
has (key) {
|
|
19
|
+
return bag.has(key);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -4,6 +4,7 @@ import { HttpMethod, ParamType } from "../constants.js";
|
|
|
4
4
|
import { getMetadata } from "../metadata.js";
|
|
5
5
|
import { joinPaths } from "../registry/paths.js";
|
|
6
6
|
import { buildExecutionContext } from "./execution-context.js";
|
|
7
|
+
import { REQUEST_CONTEXT, createRequestContext } from "./request-context.js";
|
|
7
8
|
import { ForbiddenException, HttpException } from "../errors/http-exception.js";
|
|
8
9
|
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
9
10
|
import { shouldFilterCatch } from "../pipeline/decorators.js";
|
|
@@ -206,6 +207,7 @@ export class RouteManager {
|
|
|
206
207
|
return existing;
|
|
207
208
|
}
|
|
208
209
|
const child = this.container.createChild();
|
|
210
|
+
child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(c));
|
|
209
211
|
c.set('container', child);
|
|
210
212
|
return child;
|
|
211
213
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, fo
|
|
|
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
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';
|
|
13
|
+
export { REQUEST_CONTEXT } from './http/request-context';
|
|
14
|
+
export type { RequestContext } from './http/request-context';
|
|
13
15
|
export { Logger, LogLevel } from './services/index';
|
|
14
16
|
export type { LoggerService, ContextProvider, Writer, LoggerLevelName } from './services/index';
|
|
15
17
|
export { ConfigModule, ConfigService, CONFIG_OPTIONS } from './config/index';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, fo
|
|
|
12
12
|
export { METADATA_KEYS, HttpMethod, ParamType, Scope } from "./constants.js";
|
|
13
13
|
// HTTP Decorators
|
|
14
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";
|
|
15
|
+
// Request-scoped context primitive
|
|
16
|
+
export { REQUEST_CONTEXT } from "./http/request-context.js";
|
|
15
17
|
// Services
|
|
16
18
|
export { Logger, LogLevel } from "./services/index.js";
|
|
17
19
|
// Config
|