@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.
Files changed (161) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
  3. package/dist/application.d.ts +18 -0
  4. package/dist/application.js +45 -0
  5. package/dist/cache/cache.module.d.ts +5 -7
  6. package/dist/cache/cache.module.js +44 -66
  7. package/dist/cache/cache.types.d.ts +26 -0
  8. package/dist/cache/cache.types.js +1 -1
  9. package/dist/cache/index.d.ts +2 -1
  10. package/dist/cache/index.js +1 -0
  11. package/dist/cache/tiered-cache.store.d.ts +19 -0
  12. package/dist/cache/tiered-cache.store.js +40 -0
  13. package/dist/config/config.module.d.ts +6 -5
  14. package/dist/config/config.module.js +46 -49
  15. package/dist/container/container.d.ts +58 -0
  16. package/dist/container/container.js +223 -18
  17. package/dist/container/disposable.d.ts +5 -0
  18. package/dist/container/disposable.js +33 -0
  19. package/dist/container/types.d.ts +8 -0
  20. package/dist/cors/cors.module.d.ts +5 -5
  21. package/dist/cors/cors.module.js +26 -25
  22. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  23. package/dist/discovery/discoverable.decorator.js +42 -0
  24. package/dist/discovery/discovery.service.d.ts +94 -0
  25. package/dist/discovery/discovery.service.js +166 -0
  26. package/dist/discovery/index.d.ts +2 -0
  27. package/dist/discovery/index.js +2 -0
  28. package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
  29. package/dist/entrypoint/entrypoint.registry.js +106 -0
  30. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  31. package/dist/entrypoint/entrypoint.types.js +3 -0
  32. package/dist/entrypoint/execution-context.d.ts +15 -0
  33. package/dist/entrypoint/execution-context.js +21 -0
  34. package/dist/entrypoint/execution-scope.d.ts +21 -0
  35. package/dist/entrypoint/execution-scope.js +28 -0
  36. package/dist/entrypoint/index.d.ts +4 -0
  37. package/dist/entrypoint/index.js +4 -0
  38. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  39. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  40. package/dist/factory/adapter.d.ts +36 -0
  41. package/dist/factory/adapter.js +18 -0
  42. package/dist/factory/bootstrap.js +20 -2
  43. package/dist/factory.d.ts +11 -1
  44. package/dist/factory.js +23 -2
  45. package/dist/fetch/fetch.module.d.ts +5 -6
  46. package/dist/fetch/fetch.module.js +9 -42
  47. package/dist/http/ambient.d.ts +17 -0
  48. package/dist/http/ambient.js +44 -0
  49. package/dist/http/execution-context.js +4 -1
  50. package/dist/http/handler-executor.js +22 -19
  51. package/dist/http/index.d.ts +1 -0
  52. package/dist/http/index.js +1 -0
  53. package/dist/http/route-contributor.d.ts +68 -0
  54. package/dist/http/route-contributor.js +22 -0
  55. package/dist/http/route.manager.d.ts +19 -0
  56. package/dist/http/route.manager.js +112 -19
  57. package/dist/i18n/deep-merge.d.ts +2 -0
  58. package/dist/i18n/deep-merge.js +15 -0
  59. package/dist/i18n/i18n.middleware.d.ts +15 -0
  60. package/dist/i18n/i18n.middleware.js +94 -0
  61. package/dist/i18n/i18n.module.d.ts +13 -0
  62. package/dist/i18n/i18n.module.js +64 -0
  63. package/dist/i18n/i18n.options.d.ts +30 -0
  64. package/dist/i18n/i18n.options.js +16 -0
  65. package/dist/i18n/i18n.service.d.ts +20 -0
  66. package/dist/i18n/i18n.service.js +49 -0
  67. package/dist/i18n/i18n.tokens.d.ts +6 -0
  68. package/dist/i18n/i18n.tokens.js +3 -0
  69. package/dist/i18n/i18n.types.d.ts +6 -0
  70. package/dist/i18n/i18n.types.js +1 -0
  71. package/dist/i18n/index.d.ts +10 -0
  72. package/dist/i18n/index.js +8 -0
  73. package/dist/i18n/message-loader.service.d.ts +21 -0
  74. package/dist/i18n/message-loader.service.js +90 -0
  75. package/dist/i18n/message-registry.d.ts +16 -0
  76. package/dist/i18n/message-registry.js +49 -0
  77. package/dist/index.d.ts +17 -5
  78. package/dist/index.js +14 -3
  79. package/dist/internal.d.ts +0 -2
  80. package/dist/internal.js +0 -7
  81. package/dist/module/configurable-module.builder.d.ts +56 -0
  82. package/dist/module/configurable-module.builder.js +84 -0
  83. package/dist/module/configurable-module.types.d.ts +93 -0
  84. package/dist/module/configurable-module.types.js +5 -0
  85. package/dist/module/define-module.d.ts +105 -0
  86. package/dist/module/define-module.js +227 -0
  87. package/dist/module/index.d.ts +4 -0
  88. package/dist/module/index.js +3 -0
  89. package/dist/module/lazy-provider.d.ts +65 -0
  90. package/dist/module/lazy-provider.js +111 -0
  91. package/dist/openapi/document.js +16 -15
  92. package/dist/pipeline/component.manager.d.ts +15 -9
  93. package/dist/pipeline/component.manager.js +32 -46
  94. package/dist/pipeline/index.d.ts +2 -0
  95. package/dist/pipeline/index.js +1 -0
  96. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  97. package/dist/pipeline/pipeline-runner.js +40 -0
  98. package/dist/pipeline/types.d.ts +21 -4
  99. package/dist/registry/metadata.registry.d.ts +24 -18
  100. package/dist/registry/metadata.registry.js +137 -49
  101. package/dist/schedule/schedule.module.d.ts +5 -0
  102. package/dist/schedule/schedule.module.js +18 -8
  103. package/dist/schedule/schedule.registry.d.ts +3 -3
  104. package/dist/schedule/schedule.registry.js +23 -44
  105. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  106. package/dist/schedule-node/schedule-node.module.js +20 -10
  107. package/dist/seeder/index.d.ts +5 -0
  108. package/dist/seeder/index.js +4 -0
  109. package/dist/seeder/seeder.decorator.d.ts +7 -0
  110. package/dist/seeder/seeder.decorator.js +15 -0
  111. package/dist/seeder/seeder.module.d.ts +16 -0
  112. package/dist/seeder/seeder.module.js +41 -0
  113. package/dist/seeder/seeder.registry.d.ts +23 -0
  114. package/dist/seeder/seeder.registry.js +83 -0
  115. package/dist/seeder/seeder.tokens.d.ts +2 -0
  116. package/dist/seeder/seeder.tokens.js +1 -0
  117. package/dist/seeder/seeder.types.d.ts +20 -0
  118. package/dist/seeder/seeder.types.js +1 -0
  119. package/dist/storage/index.d.ts +4 -0
  120. package/dist/storage/index.js +2 -0
  121. package/dist/storage/path-template.d.ts +9 -0
  122. package/dist/storage/path-template.js +20 -0
  123. package/dist/storage/signed-url.d.ts +12 -0
  124. package/dist/storage/signed-url.js +55 -0
  125. package/dist/storage/storage.types.d.ts +41 -0
  126. package/dist/storage/storage.types.js +5 -0
  127. package/dist/throttler/throttler.module.d.ts +5 -8
  128. package/dist/throttler/throttler.module.js +27 -55
  129. package/dist/websocket/index.d.ts +12 -0
  130. package/dist/websocket/index.js +16 -0
  131. package/dist/websocket/websocket.decorators.d.ts +33 -0
  132. package/dist/websocket/websocket.decorators.js +58 -0
  133. package/dist/websocket/websocket.module.d.ts +35 -0
  134. package/dist/websocket/websocket.module.js +63 -0
  135. package/dist/websocket/websocket.tokens.d.ts +23 -0
  136. package/dist/websocket/websocket.tokens.js +21 -0
  137. package/dist/websocket/websocket.types.d.ts +86 -0
  138. package/dist/websocket/websocket.types.js +4 -0
  139. package/dist/websocket/ws-argument-resolver.d.ts +13 -0
  140. package/dist/websocket/ws-argument-resolver.js +40 -0
  141. package/dist/websocket/ws-dispatcher.d.ts +45 -0
  142. package/dist/websocket/ws-dispatcher.js +259 -0
  143. package/dist/websocket/ws-exception.d.ts +16 -0
  144. package/dist/websocket/ws-exception.js +32 -0
  145. package/dist/websocket/ws-execution-context.d.ts +9 -0
  146. package/dist/websocket/ws-execution-context.js +24 -0
  147. package/dist/websocket/ws-server.d.ts +31 -0
  148. package/dist/websocket/ws-server.js +74 -0
  149. package/dist/websocket/ws-sync.d.ts +47 -0
  150. package/dist/websocket/ws-sync.js +74 -0
  151. package/dist/websocket-node/index.d.ts +4 -0
  152. package/dist/websocket-node/index.js +5 -0
  153. package/dist/websocket-node/node-ws-client.d.ts +24 -0
  154. package/dist/websocket-node/node-ws-client.js +49 -0
  155. package/dist/websocket-node/redis-sync.d.ts +33 -0
  156. package/dist/websocket-node/redis-sync.js +56 -0
  157. package/dist/websocket-node/register-gateways.d.ts +20 -0
  158. package/dist/websocket-node/register-gateways.js +61 -0
  159. package/package.json +31 -1
  160. package/dist/http/crud-bridge.d.ts +0 -90
  161. package/dist/http/crud-bridge.js +0 -25
@@ -0,0 +1,227 @@
1
+ import { InjectionToken } from "../container/types.js";
2
+ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
3
+ import { stableHash } from "./stable-hash.js";
4
+ const DEFAULT_EXTRAS = {
5
+ isGlobal: false
6
+ };
7
+ const DEFAULT_TRANSFORM = (def, extras)=>extras.isGlobal ? {
8
+ ...def,
9
+ global: true
10
+ } : def;
11
+ const GLOBAL_SLOT_TOKENS = {
12
+ guards: APP_GUARD,
13
+ pipes: APP_PIPE,
14
+ interceptors: APP_INTERCEPTOR,
15
+ filters: APP_FILTER,
16
+ middleware: APP_MIDDLEWARE
17
+ };
18
+ /** Lower a `global:` slot to `APP_*` provider registrations. */ function lowerGlobalSlot(slot) {
19
+ const out = [];
20
+ for (const kind of Object.keys(GLOBAL_SLOT_TOKENS)){
21
+ for (const component of slot[kind] ?? []){
22
+ const token = GLOBAL_SLOT_TOKENS[kind];
23
+ if (typeof component === 'function') {
24
+ out.push(component, {
25
+ provide: token,
26
+ useExisting: component
27
+ });
28
+ } else {
29
+ out.push({
30
+ provide: token,
31
+ useValue: component
32
+ });
33
+ }
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+ const ASYNC_OPTION_KEYS = new Set([
39
+ 'key',
40
+ 'imports',
41
+ 'inject',
42
+ 'useFactory',
43
+ 'useClass',
44
+ 'useExisting'
45
+ ]);
46
+ /**
47
+ * The one blessed module-authoring engine. Generates `forRoot` AND
48
+ * `forRootAsync` statics with: `stableHash` key derivation (multi-instance
49
+ * dedup that survives HMR), typed `inject` tuple inference on the async
50
+ * factory, an `isGlobal` extra, and — the piece `ConfigurableModuleBuilder`
51
+ * could not express — providers/controllers/imports/exports/global components
52
+ * computed **as functions of the options**.
53
+ *
54
+ * ```ts
55
+ * const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = defineModule<CorsOptions>({
56
+ * name: 'Cors',
57
+ * setup: ({ OPTIONS }) => ({
58
+ * providers: [{
59
+ * provide: APP_MIDDLEWARE,
60
+ * useFactory: (o: CorsOptions) => buildCorsMiddleware(o),
61
+ * inject: [OPTIONS],
62
+ * }],
63
+ * exports: [OPTIONS],
64
+ * }),
65
+ * });
66
+ * export class CorsModule extends ConfigurableModuleClass {}
67
+ * ```
68
+ *
69
+ * `ConfigurableModuleBuilder` remains supported (NestJS parity) and is a thin
70
+ * adapter over this engine.
71
+ */ export function defineModule(spec) {
72
+ const optionsToken = spec.optionsToken ?? new InjectionToken(`${spec.name}_MODULE_OPTIONS`);
73
+ const syncName = spec.methodName ?? 'forRoot';
74
+ const asyncName = `${syncName}Async`;
75
+ const factoryMethodName = spec.factoryMethodName ?? 'create';
76
+ const extrasDefaults = spec.extras ?? DEFAULT_EXTRAS;
77
+ const transform = spec.transform ?? DEFAULT_TRANSFORM;
78
+ const deriveKey = (explicit, structural)=>explicit ?? spec.key?.(structural) ?? stableHash(structural);
79
+ const applyContributions = (definition, structural, key)=>{
80
+ if (!spec.setup) return definition;
81
+ const contributions = spec.setup({
82
+ OPTIONS: optionsToken,
83
+ options: structural,
84
+ key
85
+ });
86
+ const providers = [
87
+ ...definition.providers ?? [],
88
+ ...contributions.providers ?? [],
89
+ ...contributions.global ? lowerGlobalSlot(contributions.global) : []
90
+ ];
91
+ return {
92
+ ...definition,
93
+ providers,
94
+ controllers: [
95
+ ...definition.controllers ?? [],
96
+ ...contributions.controllers ?? []
97
+ ],
98
+ imports: [
99
+ ...definition.imports ?? [],
100
+ ...contributions.imports ?? []
101
+ ],
102
+ exports: [
103
+ ...definition.exports ?? [],
104
+ ...contributions.exports ?? []
105
+ ]
106
+ };
107
+ };
108
+ let GeneratedModuleClass = class GeneratedModuleClass {
109
+ };
110
+ Object.defineProperty(GeneratedModuleClass, 'name', {
111
+ value: `${spec.name}ModuleHost`
112
+ });
113
+ Object.defineProperty(GeneratedModuleClass, syncName, {
114
+ configurable: true,
115
+ writable: true,
116
+ enumerable: false,
117
+ value (options = {}) {
118
+ const { key: explicitKey, ...rest } = options;
119
+ const key = deriveKey(explicitKey, rest);
120
+ const definition = {
121
+ module: this,
122
+ key,
123
+ providers: [
124
+ {
125
+ provide: optionsToken,
126
+ useValue: rest
127
+ }
128
+ ]
129
+ };
130
+ return transform(applyContributions(definition, rest, key), {
131
+ ...extrasDefaults,
132
+ ...rest
133
+ });
134
+ }
135
+ });
136
+ Object.defineProperty(GeneratedModuleClass, asyncName, {
137
+ configurable: true,
138
+ writable: true,
139
+ enumerable: false,
140
+ value (options = {}) {
141
+ const bag = options;
142
+ const structural = {};
143
+ for (const [k, v] of Object.entries(bag)){
144
+ if (!ASYNC_OPTION_KEYS.has(k)) structural[k] = v;
145
+ }
146
+ const key = deriveKey(bag.key, // Async factories aren't structurally hashable in a useful way, so the
147
+ // default key hashes the async wiring + structural fields — same
148
+ // instance-identity semantics ConfigurableModuleBuilder always had.
149
+ spec.key ? structural : {
150
+ inject: bag.inject,
151
+ useFactory: bag.useFactory,
152
+ useClass: bag.useClass,
153
+ useExisting: bag.useExisting,
154
+ ...structural
155
+ });
156
+ const definition = {
157
+ module: this,
158
+ key,
159
+ imports: bag.imports ?? [],
160
+ providers: buildAsyncOptionsProviders(optionsToken, factoryMethodName, bag, structural)
161
+ };
162
+ return transform(applyContributions(definition, structural, key), {
163
+ ...extrasDefaults,
164
+ ...structural
165
+ });
166
+ }
167
+ });
168
+ return {
169
+ ConfigurableModuleClass: GeneratedModuleClass,
170
+ MODULE_OPTIONS_TOKEN: optionsToken,
171
+ // Type-only sentinels — never read at runtime.
172
+ OPTIONS_TYPE: undefined,
173
+ ASYNC_OPTIONS_TYPE: undefined
174
+ };
175
+ }
176
+ /**
177
+ * Lower `useFactory`/`useClass`/`useExisting` async options into provider
178
+ * registrations. Structural fields from the call site merge UNDER the resolved
179
+ * options (`{ ...structural, ...resolved }`) so sync-declared fields act as
180
+ * defaults and the factory stays authoritative.
181
+ */ export function buildAsyncOptionsProviders(optionsToken, factoryMethodName, async, structural = {}) {
182
+ const hasStructural = Object.keys(structural).length > 0;
183
+ const merge = (resolved)=>resolved instanceof Promise ? resolved.then((o)=>({
184
+ ...structural,
185
+ ...o
186
+ })) : {
187
+ ...structural,
188
+ ...resolved
189
+ };
190
+ if (async.useFactory) {
191
+ const factory = async.useFactory;
192
+ return [
193
+ {
194
+ provide: optionsToken,
195
+ // No structural fields → pass the caller's factory through untouched
196
+ // (function identity preserved; nothing to merge).
197
+ useFactory: hasStructural ? (...deps)=>merge(factory(...deps)) : factory,
198
+ inject: async.inject ?? []
199
+ }
200
+ ];
201
+ }
202
+ if (async.useClass) {
203
+ const factoryClass = async.useClass;
204
+ return [
205
+ factoryClass,
206
+ {
207
+ provide: optionsToken,
208
+ useFactory: (instance)=>merge(instance[factoryMethodName]()),
209
+ inject: [
210
+ factoryClass
211
+ ]
212
+ }
213
+ ];
214
+ }
215
+ if (async.useExisting) {
216
+ return [
217
+ {
218
+ provide: optionsToken,
219
+ useFactory: (instance)=>merge(instance[factoryMethodName]()),
220
+ inject: [
221
+ async.useExisting
222
+ ]
223
+ }
224
+ ];
225
+ }
226
+ throw new Error('Async module options require one of `useFactory`, `useClass`, or `useExisting`.');
227
+ }
@@ -1,5 +1,9 @@
1
1
  export { Global, Module, isModule, getModuleMetadata, defineDynamicModule, } from './decorators';
2
2
  export { stableHash } from './stable-hash';
3
+ export { ConfigurableModuleBuilder, defineConfigurableModule, moduleKey, } from './configurable-module.builder';
4
+ export { defineModule, buildAsyncOptionsProviders, type DefineModuleSpec, type GlobalComponentSlot, type ModuleContributions, type ModuleSetupContext, } from './define-module';
5
+ export { lazyProvider, moduleToken, provideGlobal, sideEffectModule, type LazyProviderSpec, } from './lazy-provider';
6
+ export type { ConfigurableModuleAsyncOptions, ConfigurableModuleBuilderOptions, ConfigurableModuleClassType, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost, ConfigurableModuleOptionsFactory, DefineConfigurableModuleSpec, } from './configurable-module.types';
3
7
  export { ModuleLoader } from './module-loader';
4
8
  export { MiddlewareBuilder } from './middleware';
5
9
  export type { MiddlewareConsumer, MiddlewareConfigProxy, MiddlewareRouteDefinition, NestModule, RouteInfo, } from './middleware';
@@ -1,4 +1,7 @@
1
1
  export { Global, Module, isModule, getModuleMetadata, defineDynamicModule } from "./decorators.js";
2
2
  export { stableHash } from "./stable-hash.js";
3
+ export { ConfigurableModuleBuilder, defineConfigurableModule, moduleKey } from "./configurable-module.builder.js";
4
+ export { defineModule, buildAsyncOptionsProviders } from "./define-module.js";
5
+ export { lazyProvider, moduleToken, provideGlobal, sideEffectModule } from "./lazy-provider.js";
3
6
  export { ModuleLoader } from "./module-loader.js";
4
7
  export { MiddlewareBuilder } from "./middleware.js";
@@ -0,0 +1,65 @@
1
+ import { InjectionToken, type InferTokens, type ProviderOptions, type Token, type Type } from '../container/types';
2
+ import type { ComponentType, DynamicModule } from '../registry/types';
3
+ import type { ModuleContributions } from './define-module';
4
+ export interface LazyProviderSpec<T, Inject extends readonly Token<unknown>[]> {
5
+ /** Token under which the memoized thunk `() => T` is provided. */
6
+ provide: Token<() => T>;
7
+ inject?: Inject;
8
+ useFactory: (...deps: InferTokens<Inject>) => T;
9
+ /** Memoize the first call's result (default true). */
10
+ memoize?: boolean;
11
+ }
12
+ /**
13
+ * Provide a zero-arg thunk `() => T` whose factory runs on FIRST CALL, not at
14
+ * provider construction — for values that don't exist yet when the module
15
+ * graph is built (Cloudflare bindings are only live at request time).
16
+ *
17
+ * The shared primitive replacing the hand-rolled
18
+ * `useFactory: (...deps) => () => build(...deps)` closure that auth and
19
+ * storage each copy-pasted.
20
+ *
21
+ * ```ts
22
+ * lazyProvider({
23
+ * provide: STORAGE_DRIVER_BUILDER,
24
+ * inject: [MODULE_OPTIONS_TOKEN],
25
+ * useFactory: (options) => options.driver(),
26
+ * })
27
+ * ```
28
+ */
29
+ export declare function lazyProvider<T, const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(spec: LazyProviderSpec<T, Inject>): ProviderOptions;
30
+ /**
31
+ * The one idiom for registering an app-wide component from a module's
32
+ * providers. Returns registrations to spread:
33
+ *
34
+ * ```ts
35
+ * providers: [MyService, ...provideGlobal('guard', AuthGuard)]
36
+ * ```
37
+ *
38
+ * Class components are registered as providers and wired via `useExisting`
39
+ * (so DI constructs them with their dependencies); instances via `useValue`.
40
+ * Inside `defineModule`, prefer the equivalent `global:` contribution slot.
41
+ */
42
+ export declare function provideGlobal(kind: ComponentType, component: Type | object): Array<Type | ProviderOptions>;
43
+ /**
44
+ * A first-class side-effect-only module: contributes providers/exports without
45
+ * being a configurable module — the supported form of the "empty marker
46
+ * module" trick (i18n's `registerMessages`). Content-derived `key` makes
47
+ * identical contributions dedup (HMR-idempotent) while distinct ones coexist.
48
+ *
49
+ * ```ts
50
+ * export function registerMessages(messages: Messages): DynamicModule {
51
+ * return sideEffectModule('I18nMessages', {
52
+ * providers: [{ provide: I18N_MESSAGES, useValue: messages }],
53
+ * exports: [I18N_MESSAGES],
54
+ * });
55
+ * }
56
+ * ```
57
+ */
58
+ export declare function sideEffectModule(name: string, contributions?: Omit<ModuleContributions, 'global'> & {
59
+ key?: string;
60
+ }): DynamicModule;
61
+ /**
62
+ * Blessed token-minting convention: always an `InjectionToken` (never a raw
63
+ * string), named for diagnostics. Namespace it `'<pkg>:<area>:<thing>'`.
64
+ */
65
+ export declare function moduleToken<T>(name: string): InjectionToken<T>;
@@ -0,0 +1,111 @@
1
+ import { InjectionToken } from "../container/types.js";
2
+ import { Module } from "./decorators.js";
3
+ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
4
+ import { stableHash } from "./stable-hash.js";
5
+ /**
6
+ * Provide a zero-arg thunk `() => T` whose factory runs on FIRST CALL, not at
7
+ * provider construction — for values that don't exist yet when the module
8
+ * graph is built (Cloudflare bindings are only live at request time).
9
+ *
10
+ * The shared primitive replacing the hand-rolled
11
+ * `useFactory: (...deps) => () => build(...deps)` closure that auth and
12
+ * storage each copy-pasted.
13
+ *
14
+ * ```ts
15
+ * lazyProvider({
16
+ * provide: STORAGE_DRIVER_BUILDER,
17
+ * inject: [MODULE_OPTIONS_TOKEN],
18
+ * useFactory: (options) => options.driver(),
19
+ * })
20
+ * ```
21
+ */ export function lazyProvider(spec) {
22
+ const memoize = spec.memoize ?? true;
23
+ return {
24
+ provide: spec.provide,
25
+ inject: [
26
+ ...spec.inject ?? []
27
+ ],
28
+ useFactory: (...deps)=>{
29
+ const build = ()=>spec.useFactory(...deps);
30
+ if (!memoize) return build;
31
+ let cached;
32
+ return ()=>(cached ??= {
33
+ value: build()
34
+ }).value;
35
+ }
36
+ };
37
+ }
38
+ const GLOBAL_COMPONENT_TOKENS = {
39
+ guard: APP_GUARD,
40
+ pipe: APP_PIPE,
41
+ interceptor: APP_INTERCEPTOR,
42
+ filter: APP_FILTER,
43
+ middleware: APP_MIDDLEWARE
44
+ };
45
+ /**
46
+ * The one idiom for registering an app-wide component from a module's
47
+ * providers. Returns registrations to spread:
48
+ *
49
+ * ```ts
50
+ * providers: [MyService, ...provideGlobal('guard', AuthGuard)]
51
+ * ```
52
+ *
53
+ * Class components are registered as providers and wired via `useExisting`
54
+ * (so DI constructs them with their dependencies); instances via `useValue`.
55
+ * Inside `defineModule`, prefer the equivalent `global:` contribution slot.
56
+ */ export function provideGlobal(kind, component) {
57
+ const token = GLOBAL_COMPONENT_TOKENS[kind];
58
+ if (typeof component === 'function') {
59
+ return [
60
+ component,
61
+ {
62
+ provide: token,
63
+ useExisting: component
64
+ }
65
+ ];
66
+ }
67
+ return [
68
+ {
69
+ provide: token,
70
+ useValue: component
71
+ }
72
+ ];
73
+ }
74
+ /**
75
+ * A first-class side-effect-only module: contributes providers/exports without
76
+ * being a configurable module — the supported form of the "empty marker
77
+ * module" trick (i18n's `registerMessages`). Content-derived `key` makes
78
+ * identical contributions dedup (HMR-idempotent) while distinct ones coexist.
79
+ *
80
+ * ```ts
81
+ * export function registerMessages(messages: Messages): DynamicModule {
82
+ * return sideEffectModule('I18nMessages', {
83
+ * providers: [{ provide: I18N_MESSAGES, useValue: messages }],
84
+ * exports: [I18N_MESSAGES],
85
+ * });
86
+ * }
87
+ * ```
88
+ */ export function sideEffectModule(name, contributions = {}) {
89
+ const { key, ...rest } = contributions;
90
+ // Computed-property-name idiom: mints a class whose .name is `name` without
91
+ // dynamic code evaluation (edge-safe; no `new Function`).
92
+ const moduleClass = {
93
+ [name]: class {
94
+ }
95
+ }[name];
96
+ Module({})(moduleClass);
97
+ return {
98
+ module: moduleClass,
99
+ key: key ?? stableHash(rest),
100
+ providers: rest.providers ?? [],
101
+ controllers: rest.controllers ?? [],
102
+ imports: rest.imports ?? [],
103
+ exports: rest.exports ?? []
104
+ };
105
+ }
106
+ /**
107
+ * Blessed token-minting convention: always an `InjectionToken` (never a raw
108
+ * string), named for diagnostics. Namespace it `'<pkg>:<area>:<thing>'`.
109
+ */ export function moduleToken(name) {
110
+ return new InjectionToken(name);
111
+ }
@@ -1,5 +1,5 @@
1
- import { METADATA_KEYS, ParamType } from "../constants.js";
2
- import { getCrudBridge } from "../http/crud-bridge.js";
1
+ import { ParamType } from "../constants.js";
2
+ import { getRouteContributors } from "../http/route-contributor.js";
3
3
  import { getMetadata } from "../metadata.js";
4
4
  import { collectControllers } from "../module/graph.js";
5
5
  import { MetadataRegistry } from "../registry/metadata.registry.js";
@@ -215,25 +215,26 @@ export function createOpenApiDocument(rootModule, options = {}) {
215
215
  paths[pathString] = pathItem;
216
216
  }
217
217
  }
218
- // Second pass: include `@Crud()`-generated routes via the registered
219
- // CrudBridge. The bridge knows how to turn its own metadata config into
220
- // OpenAPI path items; this loop is silent when no bridge is registered or
221
- // no controller carries `vela:crud` metadata, so consumers without
222
- // `@velajs/crud` see no behavioral change.
223
- const bridge = getCrudBridge();
224
- if (bridge) {
218
+ // Second pass: include contributor-generated routes (e.g. `@Crud()` via
219
+ // `@velajs/crud`'s registered RouteContributor). Silent when no contributor
220
+ // is registered or no controller carries claiming metadata, so consumers
221
+ // without contributor packages see no behavioral change.
222
+ for (const contributor of getRouteContributors()){
223
+ if (!contributor.buildOpenApiPaths) continue;
225
224
  for (const controller of controllers){
226
- const crudConfig = getMetadata(METADATA_KEYS.CRUD, controller);
227
- if (!crudConfig) continue;
225
+ const meta = getMetadata(contributor.claimsMetaKey, controller);
226
+ if (meta === undefined) continue;
228
227
  const controllerPrefix = MetadataRegistry.getControllerPath(controller);
229
- const crudPaths = bridge.buildOpenApiPaths(controller, crudConfig, {
228
+ const contributedPaths = contributor.buildOpenApiPaths({
229
+ controller: controller,
230
+ meta,
230
231
  globalPrefix,
231
232
  controllerPrefix
232
233
  });
233
- for (const [pathKey, pathItem] of Object.entries(crudPaths)){
234
+ for (const [pathKey, pathItem] of Object.entries(contributedPaths)){
234
235
  // Verb-level merge: a hand-written `@Get('/')` on the same controller
235
- // is preserved when the bridge contributes `post`/`patch`/etc. on
236
- // the same path key.
236
+ // is preserved when the contributor adds `post`/`patch`/etc. on the
237
+ // same path key.
237
238
  paths[pathKey] = {
238
239
  ...paths[pathKey] ?? {},
239
240
  ...pathItem
@@ -1,19 +1,25 @@
1
1
  import type { Container } from '../container/container';
2
2
  import type { ComponentType, ComponentTypeMap, Constructor, FilterType, GuardType, InterceptorType, MiddlewareType, PipeType } from '../registry/types';
3
3
  import type { ArgumentMetadata, CanActivate, ExceptionFilter, ExecutionContext, NestInterceptor, NestMiddleware, PipeTransform } from './types';
4
+ /**
5
+ * Component registration + resolution for the controller/handler tiers.
6
+ * App-wide (global) components have exactly ONE source: the per-app
7
+ * `RouteManager` (`APP_*` provider tokens + `useGlobalX()`) — callers merge
8
+ * `routeManager.getGlobalComponents()` with {@link getScopedComponents}.
9
+ *
10
+ * Stateless by design: no process-global container (two apps in one process
11
+ * never cross-talk) — every `resolve*` takes the resolving container.
12
+ */
4
13
  export declare class ComponentManager {
5
- private static container;
6
- static init(container: Container): void;
7
- static registerGlobal<T extends ComponentType>(type: T, ...components: ComponentTypeMap[T][]): void;
8
14
  static registerController<T extends ComponentType>(type: T, controller: Constructor, ...components: ComponentTypeMap[T][]): void;
9
15
  static registerHandler<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol, ...components: ComponentTypeMap[T][]): void;
10
- static getComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
16
+ static getScopedComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
11
17
  private static resolveAll;
12
- static resolveMiddleware(items: MiddlewareType[]): NestMiddleware[];
13
- static resolveGuards(items: GuardType[]): CanActivate[];
14
- static resolvePipes(items: PipeType[]): PipeTransform[];
15
- static resolveInterceptors(items: InterceptorType[]): NestInterceptor[];
16
- static resolveFilters(items: FilterType[]): ExceptionFilter[];
18
+ static resolveMiddleware(items: MiddlewareType[], container: Container): NestMiddleware[];
19
+ static resolveGuards(items: GuardType[], container: Container): CanActivate[];
20
+ static resolvePipes(items: PipeType[], container: Container): PipeTransform[];
21
+ static resolveInterceptors(items: InterceptorType[], container: Container): NestInterceptor[];
22
+ static resolveFilters(items: FilterType[], container: Container): ExceptionFilter[];
17
23
  static executePipes(value: unknown, metadata: ArgumentMetadata, pipes: PipeTransform[]): Promise<unknown>;
18
24
  static runInterceptorChain(interceptors: NestInterceptor[], context: ExecutionContext, coreHandler: () => Promise<unknown>): Promise<unknown>;
19
25
  }
@@ -1,18 +1,18 @@
1
1
  import { MetadataRegistry } from "../registry/metadata.registry.js";
2
+ import { PipelineRunner } from "./pipeline-runner.js";
2
3
  function isObject(value) {
3
4
  return typeof value === 'object' && value !== null;
4
5
  }
5
- export class ComponentManager {
6
- static container;
7
- static init(container) {
8
- this.container = container;
9
- }
10
- // 3-level registration
11
- static registerGlobal(type, ...components) {
12
- for (const component of components){
13
- MetadataRegistry.registerGlobal(type, component);
14
- }
15
- }
6
+ /**
7
+ * Component registration + resolution for the controller/handler tiers.
8
+ * App-wide (global) components have exactly ONE source: the per-app
9
+ * `RouteManager` (`APP_*` provider tokens + `useGlobalX()`) — callers merge
10
+ * `routeManager.getGlobalComponents()` with {@link getScopedComponents}.
11
+ *
12
+ * Stateless by design: no process-global container (two apps in one process
13
+ * never cross-talk) — every `resolve*` takes the resolving container.
14
+ */ export class ComponentManager {
15
+ // Scoped registration: controller → handler
16
16
  static registerController(type, controller, ...components) {
17
17
  for (const component of components){
18
18
  MetadataRegistry.registerController(type, controller, component);
@@ -23,34 +23,32 @@ export class ComponentManager {
23
23
  MetadataRegistry.registerHandler(type, controller, handlerName, component);
24
24
  }
25
25
  }
26
- // 3-level resolution: globalcontroller handler
27
- static getComponents(type, controller, handlerName) {
28
- const controllerComponents = MetadataRegistry.getController(type, controller);
29
- const handlerComponents = MetadataRegistry.getHandler(type, controller, handlerName);
26
+ // Scoped resolution: controllerhandler. App-wide components come from
27
+ // RouteManager (APP_* tokens + useGlobalX()) — merged by the caller.
28
+ static getScopedComponents(type, controller, handlerName) {
30
29
  return [
31
- ...MetadataRegistry.getGlobal(type),
32
- ...controllerComponents,
33
- ...handlerComponents
30
+ ...MetadataRegistry.getController(type, controller),
31
+ ...MetadataRegistry.getHandler(type, controller, handlerName)
34
32
  ];
35
33
  }
36
- // Type-specific resolvers
37
- static resolveAll(items, methodKey) {
38
- return items.map((item)=>isObject(item) && methodKey in item ? item : this.container.resolve(item));
34
+ // Type-specific resolvers — explicit container, always.
35
+ static resolveAll(items, methodKey, container) {
36
+ return items.map((item)=>isObject(item) && methodKey in item ? item : container.resolve(item));
39
37
  }
40
- static resolveMiddleware(items) {
41
- return this.resolveAll(items, 'use');
38
+ static resolveMiddleware(items, container) {
39
+ return this.resolveAll(items, 'use', container);
42
40
  }
43
- static resolveGuards(items) {
44
- return this.resolveAll(items, 'canActivate');
41
+ static resolveGuards(items, container) {
42
+ return this.resolveAll(items, 'canActivate', container);
45
43
  }
46
- static resolvePipes(items) {
47
- return this.resolveAll(items, 'transform');
44
+ static resolvePipes(items, container) {
45
+ return this.resolveAll(items, 'transform', container);
48
46
  }
49
- static resolveInterceptors(items) {
50
- return this.resolveAll(items, 'intercept');
47
+ static resolveInterceptors(items, container) {
48
+ return this.resolveAll(items, 'intercept', container);
51
49
  }
52
- static resolveFilters(items) {
53
- return this.resolveAll(items, 'catch');
50
+ static resolveFilters(items, container) {
51
+ return this.resolveAll(items, 'catch', container);
54
52
  }
55
53
  // Pipe execution
56
54
  static async executePipes(value, metadata, pipes) {
@@ -60,21 +58,9 @@ export class ComponentManager {
60
58
  }
61
59
  return transformed;
62
60
  }
63
- // Interceptor chain (onion pattern)
61
+ // Interceptor chain (onion pattern) — canonical implementation lives in
62
+ // PipelineRunner; kept here as a stable alias.
64
63
  static async runInterceptorChain(interceptors, context, coreHandler) {
65
- if (interceptors.length === 0) {
66
- return coreHandler();
67
- }
68
- let next = {
69
- handle: coreHandler
70
- };
71
- for(let i = interceptors.length - 1; i >= 0; i--){
72
- const interceptor = interceptors[i];
73
- const currentNext = next;
74
- next = {
75
- handle: ()=>interceptor.intercept(context, currentNext)
76
- };
77
- }
78
- return next.handle();
64
+ return PipelineRunner.chainInterceptors(interceptors, context, coreHandler);
79
65
  }
80
66
  }
@@ -1,4 +1,6 @@
1
1
  export { ComponentManager } from './component.manager';
2
+ export { PipelineRunner } from './pipeline-runner';
3
+ export type { PipelineRunOptions } from './pipeline-runner';
2
4
  export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, getCatchTypes, shouldFilterCatch, } from './decorators';
3
5
  export { SetMetadata, Reflector } from './reflector';
4
6
  export type { ReflectableDecorator, CreateDecoratorOptions } from './reflector';
@@ -1,4 +1,5 @@
1
1
  export { ComponentManager } from "./component.manager.js";
2
+ export { PipelineRunner } from "./pipeline-runner.js";
2
3
  export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, getCatchTypes, shouldFilterCatch } from "./decorators.js";
3
4
  export { SetMetadata, Reflector } from "./reflector.js";
4
5
  export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./tokens.js";
@@ -0,0 +1,31 @@
1
+ import type { CanActivate, ExecutionContext, NestInterceptor } from './types';
2
+ export interface PipelineRunOptions {
3
+ context: ExecutionContext;
4
+ /** Pre-merged (global ++ scoped), pre-instantiated. */
5
+ guards: CanActivate[];
6
+ interceptors: NestInterceptor[];
7
+ /** Extract handler arguments (pipes already bound inside the closure). */
8
+ resolveArgs: () => Promise<unknown[]>;
9
+ /** Call the actual handler. */
10
+ invoke: (args: unknown[]) => Promise<unknown>;
11
+ /**
12
+ * HTTP runs argument extraction + pipes BEFORE guards (a deliberate vela
13
+ * deviation — see handler-executor); WebSocket runs guards first. Default
14
+ * false (guards first).
15
+ */
16
+ argsBeforeGuards?: boolean;
17
+ /** Error thrown when a guard rejects (default `ForbiddenException`). */
18
+ onGuardReject?: () => Error;
19
+ }
20
+ /**
21
+ * The transport-agnostic guard → pipe → interceptor execution core shared by
22
+ * the HTTP `HandlerExecutor` and the WebSocket `WsDispatcher` (and any future
23
+ * entrypoint dispatcher: queue consumers, scheduled jobs, …). Exception
24
+ * FILTERS stay with the caller — their terminal behavior is transport-specific
25
+ * (HTTP maps to a Response, WS sends an error frame).
26
+ */
27
+ export declare class PipelineRunner {
28
+ static run(options: PipelineRunOptions): Promise<unknown>;
29
+ /** Interceptor onion — innermost is the core handler. */
30
+ static chainInterceptors(interceptors: NestInterceptor[], context: ExecutionContext, coreHandler: () => Promise<unknown>): Promise<unknown>;
31
+ }