@velajs/vela 1.8.8 → 1.10.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 +20 -0
- package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
- package/dist/application.d.ts +10 -0
- package/dist/application.js +25 -0
- package/dist/cache/cache.module.d.ts +5 -7
- package/dist/cache/cache.module.js +44 -66
- package/dist/cache/cache.types.d.ts +26 -0
- package/dist/cache/cache.types.js +1 -1
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +1 -0
- package/dist/cache/tiered-cache.store.d.ts +19 -0
- package/dist/cache/tiered-cache.store.js +40 -0
- package/dist/config/config.module.d.ts +6 -5
- package/dist/config/config.module.js +46 -49
- package/dist/container/container.d.ts +33 -0
- package/dist/container/container.js +156 -7
- package/dist/container/disposable.d.ts +5 -0
- package/dist/container/disposable.js +33 -0
- package/dist/container/types.d.ts +8 -0
- package/dist/factory/bootstrap.js +10 -0
- package/dist/fetch/fetch.module.d.ts +5 -6
- package/dist/fetch/fetch.module.js +9 -42
- package/dist/http/ambient.d.ts +17 -0
- package/dist/http/ambient.js +44 -0
- package/dist/http/execution-context.js +4 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/route.manager.d.ts +19 -0
- package/dist/http/route.manager.js +84 -0
- package/dist/i18n/deep-merge.d.ts +2 -0
- package/dist/i18n/deep-merge.js +15 -0
- package/dist/i18n/i18n.middleware.d.ts +15 -0
- package/dist/i18n/i18n.middleware.js +94 -0
- package/dist/i18n/i18n.module.d.ts +13 -0
- package/dist/i18n/i18n.module.js +61 -0
- package/dist/i18n/i18n.options.d.ts +30 -0
- package/dist/i18n/i18n.options.js +16 -0
- package/dist/i18n/i18n.service.d.ts +20 -0
- package/dist/i18n/i18n.service.js +49 -0
- package/dist/i18n/i18n.tokens.d.ts +6 -0
- package/dist/i18n/i18n.tokens.js +3 -0
- package/dist/i18n/i18n.types.d.ts +6 -0
- package/dist/i18n/i18n.types.js +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/message-loader.service.d.ts +21 -0
- package/dist/i18n/message-loader.service.js +90 -0
- package/dist/i18n/message-registry.d.ts +16 -0
- package/dist/i18n/message-registry.js +49 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -2
- package/dist/module/configurable-module.builder.d.ts +44 -0
- package/dist/module/configurable-module.builder.js +177 -0
- package/dist/module/configurable-module.types.d.ts +93 -0
- package/dist/module/configurable-module.types.js +5 -0
- package/dist/module/index.d.ts +2 -0
- package/dist/module/index.js +1 -0
- package/dist/openapi/document.js +20 -2
- package/dist/openapi/types.d.ts +54 -0
- package/dist/pipeline/types.d.ts +21 -4
- package/dist/registry/metadata.registry.d.ts +16 -16
- package/dist/registry/metadata.registry.js +90 -32
- package/dist/seeder/index.d.ts +5 -0
- package/dist/seeder/index.js +4 -0
- package/dist/seeder/seeder.decorator.d.ts +7 -0
- package/dist/seeder/seeder.decorator.js +15 -0
- package/dist/seeder/seeder.module.d.ts +18 -0
- package/dist/seeder/seeder.module.js +40 -0
- package/dist/seeder/seeder.registry.d.ts +23 -0
- package/dist/seeder/seeder.registry.js +83 -0
- package/dist/seeder/seeder.tokens.d.ts +2 -0
- package/dist/seeder/seeder.tokens.js +1 -0
- package/dist/seeder/seeder.types.d.ts +20 -0
- package/dist/seeder/seeder.types.js +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/path-template.d.ts +9 -0
- package/dist/storage/path-template.js +20 -0
- package/dist/storage/signed-url.d.ts +12 -0
- package/dist/storage/signed-url.js +55 -0
- package/dist/storage/storage.types.d.ts +41 -0
- package/dist/storage/storage.types.js +5 -0
- package/dist/throttler/throttler.module.d.ts +5 -8
- package/dist/throttler/throttler.module.js +27 -55
- package/dist/websocket/index.d.ts +12 -0
- package/dist/websocket/index.js +16 -0
- package/dist/websocket/websocket.decorators.d.ts +33 -0
- package/dist/websocket/websocket.decorators.js +58 -0
- package/dist/websocket/websocket.module.d.ts +18 -0
- package/dist/websocket/websocket.module.js +52 -0
- package/dist/websocket/websocket.tokens.d.ts +22 -0
- package/dist/websocket/websocket.tokens.js +20 -0
- package/dist/websocket/websocket.types.d.ts +86 -0
- package/dist/websocket/websocket.types.js +4 -0
- package/dist/websocket/ws-argument-resolver.d.ts +13 -0
- package/dist/websocket/ws-argument-resolver.js +40 -0
- package/dist/websocket/ws-dispatcher.d.ts +33 -0
- package/dist/websocket/ws-dispatcher.js +253 -0
- package/dist/websocket/ws-exception.d.ts +16 -0
- package/dist/websocket/ws-exception.js +32 -0
- package/dist/websocket/ws-execution-context.d.ts +9 -0
- package/dist/websocket/ws-execution-context.js +24 -0
- package/dist/websocket/ws-server.d.ts +31 -0
- package/dist/websocket/ws-server.js +74 -0
- package/dist/websocket/ws-sync.d.ts +47 -0
- package/dist/websocket/ws-sync.js +74 -0
- package/dist/websocket-node/index.d.ts +4 -0
- package/dist/websocket-node/index.js +5 -0
- package/dist/websocket-node/node-ws-client.d.ts +24 -0
- package/dist/websocket-node/node-ws-client.js +49 -0
- package/dist/websocket-node/redis-sync.d.ts +33 -0
- package/dist/websocket-node/redis-sync.js +56 -0
- package/dist/websocket-node/register-gateways.d.ts +20 -0
- package/dist/websocket-node/register-gateways.js +61 -0
- package/package.json +31 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.0 (2026-07-01)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **WebSocket support** (`@velajs/vela/websocket` + `@velajs/vela/websocket-node`): `@WebSocketGateway`, `@SubscribeMessage`, gateways run through the same global guard/pipe/interceptor/filter tiers as HTTP (`RouteManager.getGlobalComponents()`).
|
|
8
|
+
|
|
9
|
+
- **`ConfigurableModuleBuilder`** (NestJS-parity) + the lower-level `defineConfigurableModule` engine. Generates `forRoot`/`forRootAsync` (with `key`, `global`, `useClass`/`useExisting` async options, and the options token) from a tiny spec, so a new module is just its tokens + options type + service + a `@Module({...})` bag — while keeping vela's module encapsulation and multi-instance `key` dedup. `CacheModule`, `ConfigModule`, `ThrottlerModule`, and `HttpModule` are migrated onto it; `@velajs/cloudflare`'s binding modules reuse the engine.
|
|
10
|
+
- **Opt-in ambient request access**: `getCurrentContainer()` / `getCurrentRequestContext()` + `enableAmbientContainer()`, wired via `VelaFactory.create(module, { ambientContainer: true })`. Backed by Hono's `hono/context-storage` (no `node:async_hooks` import in core); OFF by default, explicit child-container path unchanged. On Cloudflare Workers it requires the `nodejs_als` (or `nodejs_compat`) flag — validated on real workerd.
|
|
11
|
+
- **Container + application disposal**: `Container.dispose()` (LIFO; `Symbol.asyncDispose`/`Symbol.dispose`/`.dispose()`, idempotent), `VelaApplication.dispose()` and `Symbol.asyncDispose` (enables `await using`). The root disposes shared singletons; the per-request child container is disposed automatically at the end of each HTTP request — streaming-safe (deferred until the response body drains) and zero-overhead when a request has no request-scoped disposables.
|
|
12
|
+
- **Async cache stores (additive, non-breaking).** New `AsyncCacheStore` interface + `TieredCacheStore` (read-through + backfill + write-through over N sync/async tiers) in core, and `KVCacheStore` in `@velajs/cloudflare`, for a memory→KV cache. The existing synchronous `CacheStore`/`CacheService`/`CacheInterceptor` are **unchanged**; `CacheModuleOptions` gains an optional sync `store`. Use the async stores programmatically (inject under your own token).
|
|
13
|
+
- **i18n** at the `@velajs/vela/i18n` subpath (keeps core lean; `intl-messageformat` is an optional peer dep): `I18nModule.forRoot()` + `registerMessages()` (deep-merged, HMR-safe globalThis registry), ICU formatting, header/query/cookie locale detection. `I18nService` is request-scoped and reads the per-request locale; injecting it into a controller is safe thanks to request-scope bubbling (no `ambientContainer` flag needed).
|
|
14
|
+
- **Storage abstractions** at the `@velajs/vela/storage` subpath (dep-free, Web Crypto only): the `StorageDriver` contract, `expandPathTemplate`/`joinStoragePath`, and HMAC `signUrl`/`verifySignedUrl`. `@velajs/cloudflare` builds on them with a multi-disk `StorageModule` over R2 (`StorageService.put/get/delete/exists/url`, per-disk roots with `{date}`/`{year}`/… templates, bucket-by-name via `EnvService`) plus a signature-gated `StorageController` presign-proxy (R2 has no native presign).
|
|
15
|
+
- **Seeders** at the `@velajs/vela/seeder` subpath: `@Seeder({ order })`, `SeederModule` (`forRoot({ seeders })`), and `SeederRegistry`/`runSeeders(app)` — decorator discovery at bootstrap (mirrors `ScheduleRegistry`), each seeder run in a request-scoped child. Paired with the **new `@velajs/cli` package** (clipanion) providing `vela db seed` driven by a `vela.config.{js,mjs,ts}` app factory (Node-side; kept out of the Worker bundle).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Request-scope bubbling.** A provider (including controllers, which are singletons) that transitively depends on a request-scoped provider is now automatically treated as request-scoped — rebuilt per request instead of capturing the first request's instance. Matches NestJS; computed once at bootstrap (`Container.computeEffectiveScopes`), governs caching + eager instantiation. Fixes the captive-dependency hazard for request-scoped services injected into controllers.
|
|
20
|
+
- **HMR-safe `MetadataRegistry`**: all backing state is now anchored on `globalThis` via `Symbol.for('vela:registry:v1')`, so a Vite dev re-eval reuses the state classes were decorated against (no split-brain: lost routes / spurious "not @Injectable" warnings / duplicated globals). No API change.
|
|
21
|
+
- **`CacheModule`** now accepts an optional custom sync `store` in its options.
|
|
22
|
+
|
|
3
23
|
## 1.8.1 (2026-05-14)
|
|
4
24
|
|
|
5
25
|
### Revert
|
package/dist/application.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class VelaApplication {
|
|
|
9
9
|
private readonly routeManager;
|
|
10
10
|
private instances;
|
|
11
11
|
private honoApp;
|
|
12
|
+
private disposed;
|
|
12
13
|
constructor(container: Container, routeManager: RouteManager);
|
|
13
14
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
|
|
14
15
|
initRoutes(): Promise<void>;
|
|
@@ -44,4 +45,13 @@ export declare class VelaApplication {
|
|
|
44
45
|
callOnModuleInit(): Promise<void>;
|
|
45
46
|
callOnApplicationBootstrap(): Promise<void>;
|
|
46
47
|
close(signal?: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
|
50
|
+
* container-held instances (LIFO) and clear cached singletons. Use for
|
|
51
|
+
* graceful shutdown and dev HMR so old and new DI graphs never coexist.
|
|
52
|
+
*
|
|
53
|
+
* On runtimes/TS supporting explicit resource management this is also exposed
|
|
54
|
+
* as `Symbol.asyncDispose`, enabling `await using app = await VelaFactory.create(...)`.
|
|
55
|
+
*/
|
|
56
|
+
dispose(signal?: string): Promise<void>;
|
|
47
57
|
}
|
package/dist/application.js
CHANGED
|
@@ -7,6 +7,7 @@ export class VelaApplication {
|
|
|
7
7
|
routeManager;
|
|
8
8
|
instances = [];
|
|
9
9
|
honoApp = null;
|
|
10
|
+
disposed = false;
|
|
10
11
|
constructor(container, routeManager){
|
|
11
12
|
this.container = container;
|
|
12
13
|
this.routeManager = routeManager;
|
|
@@ -157,4 +158,28 @@ export class VelaApplication {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
|
163
|
+
* container-held instances (LIFO) and clear cached singletons. Use for
|
|
164
|
+
* graceful shutdown and dev HMR so old and new DI graphs never coexist.
|
|
165
|
+
*
|
|
166
|
+
* On runtimes/TS supporting explicit resource management this is also exposed
|
|
167
|
+
* as `Symbol.asyncDispose`, enabling `await using app = await VelaFactory.create(...)`.
|
|
168
|
+
*/ async dispose(signal) {
|
|
169
|
+
// Idempotent: `await using` + an explicit dispose(), or repeated shutdown
|
|
170
|
+
// signals, must not re-run shutdown lifecycle hooks.
|
|
171
|
+
if (this.disposed) return;
|
|
172
|
+
this.disposed = true;
|
|
173
|
+
await this.close(signal);
|
|
174
|
+
await this.container.dispose();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Attach the well-known async-dispose symbol at runtime (it is not in the
|
|
178
|
+
// ES2022 lib the project compiles against) so `await using` works where
|
|
179
|
+
// supported, without a type dependency on esnext.disposable.
|
|
180
|
+
const ASYNC_DISPOSE = Symbol.asyncDispose;
|
|
181
|
+
if (ASYNC_DISPOSE) {
|
|
182
|
+
VelaApplication.prototype[ASYNC_DISPOSE] = function() {
|
|
183
|
+
return this.dispose();
|
|
184
|
+
};
|
|
160
185
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import type { AsyncModuleOptions, DynamicModule } from '../module/types';
|
|
2
1
|
import type { CacheModuleOptions } from './cache.types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
key?: string;
|
|
8
|
-
}): DynamicModule;
|
|
2
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<CacheModuleOptions, "forRoot", "create", {
|
|
3
|
+
isGlobal: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare class CacheModule extends ConfigurableModuleClass {
|
|
9
6
|
}
|
|
7
|
+
export {};
|
|
@@ -1,80 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Module } from "../module/decorators.js";
|
|
8
|
+
import { ConfigurableModuleBuilder } from "../module/configurable-module.builder.js";
|
|
2
9
|
import { APP_INTERCEPTOR } from "../pipeline/tokens.js";
|
|
3
10
|
import { CacheInterceptor } from "./cache.interceptor.js";
|
|
4
11
|
import { CacheService } from "./cache.service.js";
|
|
5
12
|
import { MemoryCacheStore } from "./cache.store.js";
|
|
6
13
|
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from "./cache.tokens.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Reuse the public CACHE_MODULE_OPTIONS token so its identity (and the
|
|
15
|
+
// index.ts export) is unchanged. `isGlobal` here means "register the interceptor
|
|
16
|
+
// globally as APP_INTERCEPTOR" — NOT `DynamicModule.global` — so the extras
|
|
17
|
+
// transform is customized rather than using the default isGlobal→global.
|
|
18
|
+
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder({
|
|
19
|
+
moduleName: 'Cache',
|
|
20
|
+
optionsInjectionToken: CACHE_MODULE_OPTIONS
|
|
21
|
+
}).setExtras({
|
|
22
|
+
isGlobal: false
|
|
23
|
+
}, (definition, { isGlobal })=>isGlobal ? {
|
|
24
|
+
...definition,
|
|
25
|
+
providers: [
|
|
26
|
+
...definition.providers ?? [],
|
|
16
27
|
{
|
|
17
|
-
provide: CACHE_MODULE_OPTIONS,
|
|
18
|
-
useValue: options
|
|
19
|
-
},
|
|
20
|
-
CacheService,
|
|
21
|
-
CacheInterceptor
|
|
22
|
-
];
|
|
23
|
-
if (isGlobal) {
|
|
24
|
-
providers.push({
|
|
25
28
|
provide: APP_INTERCEPTOR,
|
|
26
29
|
useExisting: CacheInterceptor
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
static forRootAsync(options) {
|
|
42
|
-
const providers = [
|
|
43
|
-
{
|
|
44
|
-
provide: CACHE_MODULE_OPTIONS,
|
|
45
|
-
useFactory: options.useFactory,
|
|
46
|
-
inject: options.inject ?? []
|
|
47
|
-
},
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
} : definition).build();
|
|
33
|
+
export class CacheModule extends ConfigurableModuleClass {
|
|
34
|
+
}
|
|
35
|
+
CacheModule = _ts_decorate([
|
|
36
|
+
Module({
|
|
37
|
+
providers: [
|
|
38
|
+
CacheService,
|
|
39
|
+
CacheInterceptor,
|
|
40
|
+
// One options-injecting provider serves BOTH forRoot and forRootAsync.
|
|
41
|
+
// A custom `store` (e.g. TieredCacheStore / KVCacheStore) overrides the
|
|
42
|
+
// default in-memory store.
|
|
48
43
|
{
|
|
49
44
|
provide: CACHE_MANAGER,
|
|
50
|
-
useFactory: (
|
|
45
|
+
useFactory: (options)=>options.store ?? new MemoryCacheStore(options.ttl ?? 5, options.max ?? 100),
|
|
51
46
|
inject: [
|
|
52
|
-
|
|
47
|
+
MODULE_OPTIONS_TOKEN
|
|
53
48
|
]
|
|
54
|
-
}
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
exports: [
|
|
52
|
+
CACHE_MANAGER,
|
|
53
|
+
CACHE_MODULE_OPTIONS,
|
|
55
54
|
CacheService,
|
|
56
55
|
CacheInterceptor
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
provide: APP_INTERCEPTOR,
|
|
61
|
-
useExisting: CacheInterceptor
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
module: CacheModule,
|
|
66
|
-
key: options.key ?? stableHash({
|
|
67
|
-
inject: options.inject,
|
|
68
|
-
useFactory: options.useFactory
|
|
69
|
-
}),
|
|
70
|
-
imports: options.imports ?? [],
|
|
71
|
-
providers,
|
|
72
|
-
exports: [
|
|
73
|
-
CACHE_MANAGER,
|
|
74
|
-
CACHE_MODULE_OPTIONS,
|
|
75
|
-
CacheService,
|
|
76
|
-
CacheInterceptor
|
|
77
|
-
]
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
], CacheModule);
|
|
@@ -1,14 +1,40 @@
|
|
|
1
|
+
/** A value that may be returned synchronously or as a promise. */
|
|
2
|
+
export type Awaitable<T> = T | Promise<T>;
|
|
1
3
|
export interface CacheModuleOptions {
|
|
2
4
|
ttl?: number;
|
|
3
5
|
max?: number;
|
|
4
6
|
isGlobal?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Custom SYNC backing store (replaces the default in-memory store). For async
|
|
9
|
+
* (KV/tiered) caching use {@link AsyncCacheStore} + `TieredCacheStore`
|
|
10
|
+
* programmatically — the interceptor/CacheService path is synchronous.
|
|
11
|
+
*/
|
|
12
|
+
store?: CacheStore;
|
|
5
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Synchronous cache backing store. This is the original, stable contract used by
|
|
16
|
+
* `CacheService` and `CacheInterceptor` — unchanged.
|
|
17
|
+
*/
|
|
6
18
|
export interface CacheStore {
|
|
7
19
|
get<T = unknown>(key: string): T | undefined;
|
|
8
20
|
set<T = unknown>(key: string, value: T, ttl?: number): void;
|
|
9
21
|
del(key: string): void;
|
|
10
22
|
clear(): void;
|
|
11
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Asynchronous cache backing store (additive). Implemented by remote/tiered
|
|
26
|
+
* stores (`TieredCacheStore`, and `KVCacheStore` in `@velajs/cloudflare`) where
|
|
27
|
+
* reads/writes are inherently async. Use it programmatically — inject the store
|
|
28
|
+
* under your own token — rather than as the synchronous `CACHE_MANAGER`.
|
|
29
|
+
*/
|
|
30
|
+
export interface AsyncCacheStore {
|
|
31
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
32
|
+
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>;
|
|
33
|
+
del(key: string): Promise<void>;
|
|
34
|
+
clear(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** A store that may be sync or async (used to compose tiers). */
|
|
37
|
+
export type AnyCacheStore = CacheStore | AsyncCacheStore;
|
|
12
38
|
export interface CacheEntry<T = unknown> {
|
|
13
39
|
value: T;
|
|
14
40
|
expiresAt: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { };
|
|
1
|
+
/** A value that may be returned synchronously or as a promise. */ export { };
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { CacheModule } from './cache.module';
|
|
|
2
2
|
export { CacheService } from './cache.service';
|
|
3
3
|
export { CacheInterceptor } from './cache.interceptor';
|
|
4
4
|
export { MemoryCacheStore } from './cache.store';
|
|
5
|
+
export { TieredCacheStore } from './tiered-cache.store';
|
|
5
6
|
export { CacheKey, CacheTTL } from './cache.decorators';
|
|
6
7
|
export { CACHE_MANAGER, CACHE_MODULE_OPTIONS, CACHE_KEY_METADATA, CACHE_TTL_METADATA } from './cache.tokens';
|
|
7
|
-
export type { CacheModuleOptions, CacheStore, CacheEntry } from './cache.types';
|
|
8
|
+
export type { Awaitable, CacheModuleOptions, CacheStore, AsyncCacheStore, AnyCacheStore, CacheEntry, } from './cache.types';
|
package/dist/cache/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { CacheModule } from "./cache.module.js";
|
|
|
2
2
|
export { CacheService } from "./cache.service.js";
|
|
3
3
|
export { CacheInterceptor } from "./cache.interceptor.js";
|
|
4
4
|
export { MemoryCacheStore } from "./cache.store.js";
|
|
5
|
+
export { TieredCacheStore } from "./tiered-cache.store.js";
|
|
5
6
|
export { CacheKey, CacheTTL } from "./cache.decorators.js";
|
|
6
7
|
export { CACHE_MANAGER, CACHE_MODULE_OPTIONS, CACHE_KEY_METADATA, CACHE_TTL_METADATA } from "./cache.tokens.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AnyCacheStore, AsyncCacheStore } from './cache.types';
|
|
2
|
+
/**
|
|
3
|
+
* Composes an ordered list of cache tiers (fastest first, e.g. in-memory L1 →
|
|
4
|
+
* KV L2). Tiers may be sync ({@link CacheStore}) or async ({@link AsyncCacheStore}) —
|
|
5
|
+
* results are awaited uniformly. Reads are read-through: the first tier with a
|
|
6
|
+
* hit wins, and nearer tiers that missed are backfilled. Writes/deletes/clears
|
|
7
|
+
* fan out to all tiers. Always async ({@link AsyncCacheStore}).
|
|
8
|
+
*
|
|
9
|
+
* Runtime-agnostic — the concrete tiers (memory here, KV in `@velajs/cloudflare`)
|
|
10
|
+
* live wherever fits.
|
|
11
|
+
*/
|
|
12
|
+
export declare class TieredCacheStore implements AsyncCacheStore {
|
|
13
|
+
private readonly tiers;
|
|
14
|
+
constructor(tiers: AnyCacheStore[]);
|
|
15
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
16
|
+
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>;
|
|
17
|
+
del(key: string): Promise<void>;
|
|
18
|
+
clear(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes an ordered list of cache tiers (fastest first, e.g. in-memory L1 →
|
|
3
|
+
* KV L2). Tiers may be sync ({@link CacheStore}) or async ({@link AsyncCacheStore}) —
|
|
4
|
+
* results are awaited uniformly. Reads are read-through: the first tier with a
|
|
5
|
+
* hit wins, and nearer tiers that missed are backfilled. Writes/deletes/clears
|
|
6
|
+
* fan out to all tiers. Always async ({@link AsyncCacheStore}).
|
|
7
|
+
*
|
|
8
|
+
* Runtime-agnostic — the concrete tiers (memory here, KV in `@velajs/cloudflare`)
|
|
9
|
+
* live wherever fits.
|
|
10
|
+
*/ export class TieredCacheStore {
|
|
11
|
+
tiers;
|
|
12
|
+
constructor(tiers){
|
|
13
|
+
if (tiers.length === 0) {
|
|
14
|
+
throw new Error('TieredCacheStore requires at least one tier.');
|
|
15
|
+
}
|
|
16
|
+
this.tiers = tiers;
|
|
17
|
+
}
|
|
18
|
+
async get(key) {
|
|
19
|
+
for(let i = 0; i < this.tiers.length; i++){
|
|
20
|
+
const value = await this.tiers[i].get(key);
|
|
21
|
+
if (value !== undefined) {
|
|
22
|
+
// Backfill nearer (faster) tiers that missed.
|
|
23
|
+
for(let j = 0; j < i; j++){
|
|
24
|
+
await this.tiers[j].set(key, value);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
async set(key, value, ttl) {
|
|
32
|
+
await Promise.all(this.tiers.map((tier)=>tier.set(key, value, ttl)));
|
|
33
|
+
}
|
|
34
|
+
async del(key) {
|
|
35
|
+
await Promise.all(this.tiers.map((tier)=>tier.del(key)));
|
|
36
|
+
}
|
|
37
|
+
async clear() {
|
|
38
|
+
await Promise.all(this.tiers.map((tier)=>tier.clear()));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DynamicModule } from '../module/types';
|
|
2
2
|
import type { ConfigModuleOptions } from './config.types';
|
|
3
|
-
|
|
3
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<ConfigModuleOptions<Record<string, unknown>>, "forRoot", "create", {
|
|
4
|
+
isGlobal?: boolean;
|
|
5
|
+
}>;
|
|
6
|
+
export declare class ConfigModule extends ConfigurableModuleClass {
|
|
4
7
|
static forRoot<T extends Record<string, unknown>>(options: ConfigModuleOptions<T> & {
|
|
5
|
-
key?: string;
|
|
6
|
-
}): DynamicModule;
|
|
7
|
-
static forRootAsync<T extends Record<string, unknown>>(options: AsyncModuleOptions<ConfigModuleOptions<T>> & {
|
|
8
8
|
isGlobal?: boolean;
|
|
9
9
|
key?: string;
|
|
10
10
|
}): DynamicModule;
|
|
11
11
|
}
|
|
12
|
+
export {};
|
|
@@ -1,54 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Module } from "../module/decorators.js";
|
|
8
|
+
import { ConfigurableModuleBuilder } from "../module/configurable-module.builder.js";
|
|
2
9
|
import { ConfigService } from "./config.service.js";
|
|
3
10
|
import { CONFIG_OPTIONS } from "./config.tokens.js";
|
|
4
|
-
|
|
11
|
+
// MODULE_OPTIONS_TOKEN carries the RAW options; CONFIG_OPTIONS stays the
|
|
12
|
+
// distinct validated-record token, derived in @Module. The derived provider
|
|
13
|
+
// validates the forRootAsync path lazily (its factory is deferred); forRoot
|
|
14
|
+
// validates eagerly at call time (see the override below), matching the
|
|
15
|
+
// long-standing contract, and passes an already-validated config through.
|
|
16
|
+
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder({
|
|
17
|
+
moduleName: 'Config'
|
|
18
|
+
}).build();
|
|
19
|
+
export class ConfigModule extends ConfigurableModuleClass {
|
|
20
|
+
// Preserve (a) per-call generic inference over the config record and (b) the
|
|
21
|
+
// eager-validation contract: forRoot validates at call time so bad config
|
|
22
|
+
// fails fast. `super.forRoot` runs the generated static with `this === ConfigModule`.
|
|
5
23
|
static forRoot(options) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
ConfigService
|
|
16
|
-
],
|
|
17
|
-
exports: [
|
|
18
|
-
ConfigService,
|
|
19
|
-
CONFIG_OPTIONS
|
|
20
|
-
],
|
|
21
|
-
...options.isGlobal ? {
|
|
22
|
-
global: true
|
|
23
|
-
} : {}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
static forRootAsync(options) {
|
|
27
|
-
return {
|
|
28
|
-
module: ConfigModule,
|
|
29
|
-
key: options.key ?? stableHash({
|
|
30
|
-
inject: options.inject,
|
|
31
|
-
useFactory: options.useFactory
|
|
32
|
-
}),
|
|
33
|
-
imports: options.imports ?? [],
|
|
34
|
-
providers: [
|
|
35
|
-
{
|
|
36
|
-
provide: CONFIG_OPTIONS,
|
|
37
|
-
useFactory: async (...args)=>{
|
|
38
|
-
const opts = await options.useFactory(...args);
|
|
39
|
-
return opts.validate ? opts.validate(opts.config) : opts.config;
|
|
40
|
-
},
|
|
41
|
-
inject: options.inject ?? []
|
|
42
|
-
},
|
|
43
|
-
ConfigService
|
|
44
|
-
],
|
|
45
|
-
exports: [
|
|
46
|
-
ConfigService,
|
|
47
|
-
CONFIG_OPTIONS
|
|
48
|
-
],
|
|
49
|
-
...options.isGlobal ? {
|
|
50
|
-
global: true
|
|
51
|
-
} : {}
|
|
52
|
-
};
|
|
24
|
+
const validated = options.validate ? options.validate(options.config) : options.config;
|
|
25
|
+
// Strip `validate` so the derived CONFIG_OPTIONS provider is a passthrough
|
|
26
|
+
// (no double validation) for the already-validated config.
|
|
27
|
+
const { validate: _validate, ...rest } = options;
|
|
28
|
+
return super.forRoot({
|
|
29
|
+
...rest,
|
|
30
|
+
config: validated
|
|
31
|
+
});
|
|
53
32
|
}
|
|
54
33
|
}
|
|
34
|
+
ConfigModule = _ts_decorate([
|
|
35
|
+
Module({
|
|
36
|
+
providers: [
|
|
37
|
+
ConfigService,
|
|
38
|
+
{
|
|
39
|
+
provide: CONFIG_OPTIONS,
|
|
40
|
+
useFactory: (options)=>options.validate ? options.validate(options.config) : options.config,
|
|
41
|
+
inject: [
|
|
42
|
+
MODULE_OPTIONS_TOKEN
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
exports: [
|
|
47
|
+
ConfigService,
|
|
48
|
+
CONFIG_OPTIONS
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
], ConfigModule);
|
|
@@ -17,6 +17,8 @@ export declare class Container {
|
|
|
17
17
|
private scopes;
|
|
18
18
|
private globals;
|
|
19
19
|
private diagnostics;
|
|
20
|
+
private root;
|
|
21
|
+
private disposables;
|
|
20
22
|
constructor(options?: ContainerOptions);
|
|
21
23
|
register<T>(provider: Type<T> | ProviderOptions<T>, declaringModuleId?: string): this;
|
|
22
24
|
private registerClass;
|
|
@@ -52,6 +54,24 @@ export declare class Container {
|
|
|
52
54
|
hasInScope(token: Token, moduleId: string): boolean;
|
|
53
55
|
getProviderScope(token: Token): Scope | undefined;
|
|
54
56
|
getTokens(): Token[];
|
|
57
|
+
/**
|
|
58
|
+
* Every statically-provided (useValue) instance across ALL module buckets.
|
|
59
|
+
* Unlike resolving a token (which yields only the first bucket's registration),
|
|
60
|
+
* this surfaces per-instance values — e.g. one BindingRef per multi-instance
|
|
61
|
+
* binding module — so framework adapters can initialize all of them.
|
|
62
|
+
*/
|
|
63
|
+
getUseValues(): unknown[];
|
|
64
|
+
/**
|
|
65
|
+
* Compute request-scope bubbling for every registration (call once at
|
|
66
|
+
* bootstrap, after all providers are registered). A provider whose declared
|
|
67
|
+
* scope is not REQUEST but which (transitively) depends on a request-scoped
|
|
68
|
+
* provider is marked `effectiveScope = REQUEST`, so it is rebuilt per request
|
|
69
|
+
* instead of capturing the first request's instance — matching NestJS.
|
|
70
|
+
*/
|
|
71
|
+
computeEffectiveScopes(): void;
|
|
72
|
+
/** The dependency tokens a registration would resolve when constructed. */
|
|
73
|
+
private dependencyTokensOf;
|
|
74
|
+
private tryFindRegistration;
|
|
55
75
|
/**
|
|
56
76
|
* Create a child container for request-scoped resolution.
|
|
57
77
|
* The child shares the parent's providers but caches REQUEST-scoped
|
|
@@ -60,6 +80,19 @@ export declare class Container {
|
|
|
60
80
|
createChild(): Container;
|
|
61
81
|
createDetached(): Container;
|
|
62
82
|
clear(): void;
|
|
83
|
+
/** Whether this container has any tracked disposables (cheap request-path check). */
|
|
84
|
+
hasDisposables(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Dispose container-constructed instances (Symbol.asyncDispose /
|
|
87
|
+
* Symbol.dispose / .dispose()) in reverse creation order (LIFO). Errors are
|
|
88
|
+
* logged and skipped so one bad teardown never blocks the rest.
|
|
89
|
+
*
|
|
90
|
+
* A REQUEST child disposes only its own request-scoped instances; the root
|
|
91
|
+
* disposes shared SINGLETONs and clears their cached instance so a dev HMR
|
|
92
|
+
* generation cannot leak a stale graph. Shared provider maps are left intact
|
|
93
|
+
* for a request child (they belong to the root).
|
|
94
|
+
*/
|
|
95
|
+
dispose(): Promise<void>;
|
|
63
96
|
private resolveRegistration;
|
|
64
97
|
private resolveClass;
|
|
65
98
|
private resolveFactory;
|