@velajs/vela 1.9.0 → 1.12.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 +122 -0
- package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
- package/dist/application.d.ts +18 -0
- package/dist/application.js +45 -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 +58 -0
- package/dist/container/container.js +223 -18
- 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/cors/cors.module.d.ts +5 -5
- package/dist/cors/cors.module.js +26 -25
- package/dist/discovery/discoverable.decorator.d.ts +38 -0
- package/dist/discovery/discoverable.decorator.js +42 -0
- package/dist/discovery/discovery.service.d.ts +94 -0
- package/dist/discovery/discovery.service.js +166 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
- package/dist/entrypoint/entrypoint.registry.js +106 -0
- package/dist/entrypoint/entrypoint.types.d.ts +41 -0
- package/dist/entrypoint/entrypoint.types.js +3 -0
- package/dist/entrypoint/execution-context.d.ts +15 -0
- package/dist/entrypoint/execution-context.js +21 -0
- package/dist/entrypoint/execution-scope.d.ts +21 -0
- package/dist/entrypoint/execution-scope.js +28 -0
- package/dist/entrypoint/index.d.ts +4 -0
- package/dist/entrypoint/index.js +4 -0
- package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
- package/dist/event-emitter/event-emitter.subscriber.js +12 -29
- package/dist/factory/adapter.d.ts +36 -0
- package/dist/factory/adapter.js +18 -0
- package/dist/factory/bootstrap.js +20 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- 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/handler-executor.js +22 -19
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.d.ts +19 -0
- package/dist/http/route.manager.js +112 -19
- 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 +64 -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 +17 -5
- package/dist/index.js +14 -3
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +56 -0
- package/dist/module/configurable-module.builder.js +84 -0
- package/dist/module/configurable-module.types.d.ts +93 -0
- package/dist/module/configurable-module.types.js +5 -0
- package/dist/module/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +3 -0
- package/dist/module/lazy-provider.d.ts +65 -0
- package/dist/module/lazy-provider.js +111 -0
- package/dist/openapi/document.js +16 -15
- package/dist/pipeline/component.manager.d.ts +15 -9
- package/dist/pipeline/component.manager.js +32 -46
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +31 -0
- package/dist/pipeline/pipeline-runner.js +40 -0
- package/dist/pipeline/types.d.ts +21 -4
- package/dist/registry/metadata.registry.d.ts +24 -18
- package/dist/registry/metadata.registry.js +137 -49
- package/dist/schedule/schedule.module.d.ts +5 -0
- package/dist/schedule/schedule.module.js +18 -8
- package/dist/schedule/schedule.registry.d.ts +3 -3
- package/dist/schedule/schedule.registry.js +23 -44
- package/dist/schedule-node/schedule-node.module.d.ts +5 -0
- package/dist/schedule-node/schedule-node.module.js +20 -10
- 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 +16 -0
- package/dist/seeder/seeder.module.js +41 -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 +35 -0
- package/dist/websocket/websocket.module.js +63 -0
- package/dist/websocket/websocket.tokens.d.ts +23 -0
- package/dist/websocket/websocket.tokens.js +21 -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 +45 -0
- package/dist/websocket/ws-dispatcher.js +259 -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/dist/http/crud-bridge.d.ts +0 -90
- package/dist/http/crud-bridge.js +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,127 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.0 (2026-07-04)
|
|
4
|
+
|
|
5
|
+
The module-model release: one blessed authoring path plus public kernel
|
|
6
|
+
extension points, so feature modules (websocket, storage, queue, …) are built
|
|
7
|
+
entirely on the public API. See `MODULE_AUTHORING.md` for the author contract.
|
|
8
|
+
Contains deliberate breaking changes (no deprecation shims); coordinated
|
|
9
|
+
releases of `@velajs/{storage,better-auth,testing,crud,cloudflare}` accompany
|
|
10
|
+
this version.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`defineModule`** — the single module-authoring engine: generates `forRoot`
|
|
15
|
+
AND `forRootAsync` (typed `inject` inference), derives deterministic
|
|
16
|
+
`stableHash` keys (`key(options)` override + explicit `key` passthrough),
|
|
17
|
+
and accepts contributions (providers/controllers/imports/exports) **as
|
|
18
|
+
functions of the options** plus a standardized `global:` component slot.
|
|
19
|
+
`ConfigurableModuleBuilder` is now a thin adapter over it (unchanged API).
|
|
20
|
+
- **Authoring primitives**: `lazyProvider` (memoized deferred thunk — replaces
|
|
21
|
+
the hand-rolled `(...deps)=>()=>fn(...deps)` closures), `provideGlobal`
|
|
22
|
+
(the one `APP_*` wiring idiom), `sideEffectModule` (first-class
|
|
23
|
+
contribution-only modules; supported form of the i18n empty-marker trick),
|
|
24
|
+
`moduleToken`, `moduleKey`.
|
|
25
|
+
- **`DiscoveryService` + `createDiscoverableDecorator`** — public
|
|
26
|
+
decorator-driven discovery (`providersWithMeta`, `methodsWithMeta`,
|
|
27
|
+
`getProviders`) backed by a reverse metadata index inside
|
|
28
|
+
`MetadataRegistry`; honors container diagnostics in one place;
|
|
29
|
+
request-scoped providers are surfaced but not materialized (opt in with
|
|
30
|
+
`includeRequestScoped`). The event-emitter, schedule, and websocket
|
|
31
|
+
bootstrap scans now all run through it.
|
|
32
|
+
- **Open entrypoint registry** — `registerEntrypointKind({ kind, metaKey,
|
|
33
|
+
level })`, the `ContributesEntrypoints` interface, and per-application
|
|
34
|
+
`app.entrypoints` (`ofKind`/`kinds`/`all`), built at the end of
|
|
35
|
+
`callOnApplicationBootstrap()` so slim bootstrap paths (Cloudflare Durable
|
|
36
|
+
Objects) get it too. Transports query entrypoints instead of module
|
|
37
|
+
internals; a new kind (queue, cron, CLI) needs **zero core changes**.
|
|
38
|
+
- **`RouteContributor`** — public metadata-claimed route generation
|
|
39
|
+
(`registerRouteContributor`), consulted after explicit routes and during
|
|
40
|
+
OpenAPI generation with verb-level merge. Replaces the internal CrudBridge.
|
|
41
|
+
- **`RuntimeAdapter`** — `VelaFactory.create(module, { adapters: [...] })`
|
|
42
|
+
with `requestMiddleware` (prepended to the global chain), `onBootstrap`
|
|
43
|
+
(after lifecycle + entrypoints, before routes) and `onRoutesBuilt` hooks.
|
|
44
|
+
- **`PipelineRunner`** — the shared guard → pipe → interceptor execution core
|
|
45
|
+
used by HTTP and WebSocket dispatch (and any custom dispatcher);
|
|
46
|
+
configurable args/guards order, transport-specific guard-rejection error.
|
|
47
|
+
- **`Container.replaceProvider(provider, { buckets })`** — supported
|
|
48
|
+
force-replace across module buckets (what test harnesses need).
|
|
49
|
+
- **`buildEntrypointExecutionContext(kind, class, handler, payload)`** — the
|
|
50
|
+
entrypoint sibling of the HTTP/WS execution contexts, so guards/
|
|
51
|
+
interceptors/filters written against `getClass()`/`getHandler()`/`getType()`
|
|
52
|
+
run unchanged around queue batches, scheduled ticks, and custom kinds
|
|
53
|
+
(`EntrypointExecutionContext.getPayload()`). `@velajs/cloudflare` dispatches
|
|
54
|
+
queue/scheduled handlers through `PipelineRunner` with consumer-scoped
|
|
55
|
+
components (HTTP-global components deliberately do not apply; unclaimed
|
|
56
|
+
errors rethrow to preserve platform retry semantics).
|
|
57
|
+
- **`runInEntrypointScope(container, fn)`** — the non-HTTP dispatch scope
|
|
58
|
+
primitive: runs one unit of work (queue batch, scheduled tick, RPC call) in
|
|
59
|
+
a fresh request-scoped child with LIFO disposal — the per-request-child
|
|
60
|
+
equivalent for entrypoint dispatchers. `@velajs/cloudflare`'s queue and
|
|
61
|
+
scheduled handlers run on it (request-scoped consumer deps rebuild per
|
|
62
|
+
batch/tick instead of capturing boot instances).
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **Factory dependency visibility**: `useFactory`/`forRootAsync` `inject`
|
|
67
|
+
deps now resolve from the declaring module's scope FIRST (imports and
|
|
68
|
+
exports are honored), with the legacy no-requester lookup kept as fallback.
|
|
69
|
+
- **All in-core configurable modules are on the one engine**: `CorsModule` and
|
|
70
|
+
`SeederModule` rebuilt on `defineModule` (Cors gains `forRootAsync`; both
|
|
71
|
+
keep their token/key identities), `ScheduleModule`/`ScheduleNodeModule`
|
|
72
|
+
normalized to zero-config `@Module` bags with parity `forRoot()` sugar.
|
|
73
|
+
Builder-based modules (Config/Cache/I18n/Throttler/Http) already run on it
|
|
74
|
+
through the `ConfigurableModuleBuilder` adapter.
|
|
75
|
+
- **Unhandled handler errors are logged**: an error no exception filter claims
|
|
76
|
+
still maps to the generic 500 response, but the cause now lands in the logs
|
|
77
|
+
(`console.error`, gated on container diagnostics ≠ `silent`) — closing the
|
|
78
|
+
silent-500 gap.
|
|
79
|
+
- **`WebSocketModule`** rebuilt on `defineModule`: registry → driver → server
|
|
80
|
+
construction moved into chained provider factories (single shared registry
|
|
81
|
+
preserved; everything materializes at bootstrap), deterministic key
|
|
82
|
+
`ws#<sync-kind>` — **two identical `forRoot()` calls now dedup**
|
|
83
|
+
(HMR-idempotent; pass explicit `key` for exotic multi-instance),
|
|
84
|
+
`forRootAsync` available. `WsDispatcher` contributes `'websocket'`
|
|
85
|
+
entrypoints; `registerWebSocketGateways` consumes
|
|
86
|
+
`app.entrypoints.ofKind('websocket')`.
|
|
87
|
+
|
|
88
|
+
### Breaking
|
|
89
|
+
|
|
90
|
+
- **`WS_MODULE_OPTIONS`** is now a typed `InjectionToken` (was the raw string
|
|
91
|
+
`'vela:ws-module-options'`).
|
|
92
|
+
- **`ComponentManager`** is stateless: `init()` and the process-global
|
|
93
|
+
container are gone; `resolve*` methods require an explicit container;
|
|
94
|
+
`getComponents` replaced by `getScopedComponents` (controller + handler
|
|
95
|
+
only — app-wide components have one source: `RouteManager`).
|
|
96
|
+
`registerGlobal`/`MetadataRegistry.getGlobal` (a dead tier with no readers
|
|
97
|
+
on the request path) are removed; `MetadataRegistry.clear()` is now a
|
|
98
|
+
no-op.
|
|
99
|
+
- **CrudBridge removed** (`registerCrudBridge`/`getCrudBridge` and the
|
|
100
|
+
`/internal` exports): use `registerRouteContributor`. `@velajs/crud`
|
|
101
|
+
migrates in its coordinated release.
|
|
102
|
+
- `forRootAsync` structural fields (non-async keys passed alongside
|
|
103
|
+
`useFactory`) now merge under the resolved options.
|
|
104
|
+
|
|
105
|
+
## 1.10.0 (2026-07-01)
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
|
|
109
|
+
- **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()`).
|
|
110
|
+
|
|
111
|
+
- **`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.
|
|
112
|
+
- **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.
|
|
113
|
+
- **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.
|
|
114
|
+
- **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).
|
|
115
|
+
- **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).
|
|
116
|
+
- **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).
|
|
117
|
+
- **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).
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- **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.
|
|
122
|
+
- **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.
|
|
123
|
+
- **`CacheModule`** now accepts an optional custom sync `store` in its options.
|
|
124
|
+
|
|
3
125
|
## 1.8.1 (2026-05-14)
|
|
4
126
|
|
|
5
127
|
### Revert
|
package/dist/application.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import type { Container } from './container/container';
|
|
3
3
|
import type { Token } from './container/types';
|
|
4
|
+
import { EntrypointRegistry } from './entrypoint/entrypoint.registry';
|
|
4
5
|
import type { RouteManager } from './http/route.manager';
|
|
5
6
|
import type { MountOpenApiOptions } from './openapi/types';
|
|
6
7
|
import type { FilterType, GuardType, InterceptorType, PipeType } from './registry/types';
|
|
@@ -9,6 +10,8 @@ export declare class VelaApplication {
|
|
|
9
10
|
private readonly routeManager;
|
|
10
11
|
private instances;
|
|
11
12
|
private honoApp;
|
|
13
|
+
private entrypointRegistry;
|
|
14
|
+
private disposed;
|
|
12
15
|
constructor(container: Container, routeManager: RouteManager);
|
|
13
16
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
|
|
14
17
|
initRoutes(): Promise<void>;
|
|
@@ -43,5 +46,20 @@ export declare class VelaApplication {
|
|
|
43
46
|
mountOpenApi(options: MountOpenApiOptions): this;
|
|
44
47
|
callOnModuleInit(): Promise<void>;
|
|
45
48
|
callOnApplicationBootstrap(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
51
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
52
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
53
|
+
*/
|
|
54
|
+
get entrypoints(): EntrypointRegistry;
|
|
46
55
|
close(signal?: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
|
58
|
+
* container-held instances (LIFO) and clear cached singletons. Use for
|
|
59
|
+
* graceful shutdown and dev HMR so old and new DI graphs never coexist.
|
|
60
|
+
*
|
|
61
|
+
* On runtimes/TS supporting explicit resource management this is also exposed
|
|
62
|
+
* as `Symbol.asyncDispose`, enabling `await using app = await VelaFactory.create(...)`.
|
|
63
|
+
*/
|
|
64
|
+
dispose(signal?: string): Promise<void>;
|
|
47
65
|
}
|
package/dist/application.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DiscoveryService } from "./discovery/discovery.service.js";
|
|
2
|
+
import { EntrypointRegistry } from "./entrypoint/entrypoint.registry.js";
|
|
1
3
|
import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
|
|
2
4
|
import { renderScalarUi } from "./openapi/scalar-ui.js";
|
|
3
5
|
import { renderSwaggerUi } from "./openapi/swagger-ui.js";
|
|
@@ -7,6 +9,8 @@ export class VelaApplication {
|
|
|
7
9
|
routeManager;
|
|
8
10
|
instances = [];
|
|
9
11
|
honoApp = null;
|
|
12
|
+
entrypointRegistry = null;
|
|
13
|
+
disposed = false;
|
|
10
14
|
constructor(container, routeManager){
|
|
11
15
|
this.container = container;
|
|
12
16
|
this.routeManager = routeManager;
|
|
@@ -136,6 +140,23 @@ export class VelaApplication {
|
|
|
136
140
|
await instance.onApplicationBootstrap();
|
|
137
141
|
}
|
|
138
142
|
}
|
|
143
|
+
// Build the per-app entrypoint registry AFTER the hooks: dispatchers that
|
|
144
|
+
// implement ContributesEntrypoints (WsDispatcher) finish their own
|
|
145
|
+
// discovery inside onApplicationBootstrap. Built here — not in
|
|
146
|
+
// VelaFactory/initRoutes — so slim bootstrap paths that never build HTTP
|
|
147
|
+
// routes (the Cloudflare Durable Object) still get `app.entrypoints`.
|
|
148
|
+
const discovery = this.container.has(DiscoveryService) ? this.container.resolve(DiscoveryService) : new DiscoveryService(this.container);
|
|
149
|
+
this.entrypointRegistry = await EntrypointRegistry.build(discovery, this.instances);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
153
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
154
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
155
|
+
*/ get entrypoints() {
|
|
156
|
+
if (!this.entrypointRegistry) {
|
|
157
|
+
throw new Error('Entrypoints are not built yet — they are assembled at the end of ' + 'callOnApplicationBootstrap(). Finish bootstrapping before querying them.');
|
|
158
|
+
}
|
|
159
|
+
return this.entrypointRegistry;
|
|
139
160
|
}
|
|
140
161
|
async close(signal) {
|
|
141
162
|
const reversed = [
|
|
@@ -157,4 +178,28 @@ export class VelaApplication {
|
|
|
157
178
|
}
|
|
158
179
|
}
|
|
159
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
|
183
|
+
* container-held instances (LIFO) and clear cached singletons. Use for
|
|
184
|
+
* graceful shutdown and dev HMR so old and new DI graphs never coexist.
|
|
185
|
+
*
|
|
186
|
+
* On runtimes/TS supporting explicit resource management this is also exposed
|
|
187
|
+
* as `Symbol.asyncDispose`, enabling `await using app = await VelaFactory.create(...)`.
|
|
188
|
+
*/ async dispose(signal) {
|
|
189
|
+
// Idempotent: `await using` + an explicit dispose(), or repeated shutdown
|
|
190
|
+
// signals, must not re-run shutdown lifecycle hooks.
|
|
191
|
+
if (this.disposed) return;
|
|
192
|
+
this.disposed = true;
|
|
193
|
+
await this.close(signal);
|
|
194
|
+
await this.container.dispose();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Attach the well-known async-dispose symbol at runtime (it is not in the
|
|
198
|
+
// ES2022 lib the project compiles against) so `await using` works where
|
|
199
|
+
// supported, without a type dependency on esnext.disposable.
|
|
200
|
+
const ASYNC_DISPOSE = Symbol.asyncDispose;
|
|
201
|
+
if (ASYNC_DISPOSE) {
|
|
202
|
+
VelaApplication.prototype[ASYNC_DISPOSE] = function() {
|
|
203
|
+
return this.dispose();
|
|
204
|
+
};
|
|
160
205
|
}
|
|
@@ -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);
|