@velajs/vela 1.10.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 (77) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/application.d.ts +8 -0
  3. package/dist/application.js +20 -0
  4. package/dist/container/container.d.ts +25 -0
  5. package/dist/container/container.js +67 -11
  6. package/dist/container/disposable.js +1 -1
  7. package/dist/cors/cors.module.d.ts +5 -5
  8. package/dist/cors/cors.module.js +26 -25
  9. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  10. package/dist/discovery/discoverable.decorator.js +42 -0
  11. package/dist/discovery/discovery.service.d.ts +94 -0
  12. package/dist/discovery/discovery.service.js +166 -0
  13. package/dist/discovery/index.d.ts +2 -0
  14. package/dist/discovery/index.js +2 -0
  15. package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
  16. package/dist/entrypoint/entrypoint.registry.js +106 -0
  17. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  18. package/dist/entrypoint/entrypoint.types.js +3 -0
  19. package/dist/entrypoint/execution-context.d.ts +15 -0
  20. package/dist/entrypoint/execution-context.js +21 -0
  21. package/dist/entrypoint/execution-scope.d.ts +21 -0
  22. package/dist/entrypoint/execution-scope.js +28 -0
  23. package/dist/entrypoint/index.d.ts +4 -0
  24. package/dist/entrypoint/index.js +4 -0
  25. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  26. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  27. package/dist/factory/adapter.d.ts +36 -0
  28. package/dist/factory/adapter.js +18 -0
  29. package/dist/factory/bootstrap.js +10 -2
  30. package/dist/factory.d.ts +11 -1
  31. package/dist/factory.js +23 -2
  32. package/dist/http/handler-executor.js +22 -19
  33. package/dist/http/route-contributor.d.ts +68 -0
  34. package/dist/http/route-contributor.js +22 -0
  35. package/dist/http/route.manager.js +28 -19
  36. package/dist/i18n/i18n.module.js +4 -1
  37. package/dist/index.d.ts +12 -3
  38. package/dist/index.js +9 -2
  39. package/dist/internal.d.ts +0 -2
  40. package/dist/internal.js +0 -7
  41. package/dist/module/configurable-module.builder.d.ts +12 -0
  42. package/dist/module/configurable-module.builder.js +21 -114
  43. package/dist/module/define-module.d.ts +105 -0
  44. package/dist/module/define-module.js +227 -0
  45. package/dist/module/index.d.ts +3 -1
  46. package/dist/module/index.js +3 -1
  47. package/dist/module/lazy-provider.d.ts +65 -0
  48. package/dist/module/lazy-provider.js +111 -0
  49. package/dist/openapi/document.js +16 -15
  50. package/dist/pipeline/component.manager.d.ts +15 -9
  51. package/dist/pipeline/component.manager.js +32 -46
  52. package/dist/pipeline/index.d.ts +2 -0
  53. package/dist/pipeline/index.js +1 -0
  54. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  55. package/dist/pipeline/pipeline-runner.js +40 -0
  56. package/dist/registry/metadata.registry.d.ts +9 -3
  57. package/dist/registry/metadata.registry.js +55 -25
  58. package/dist/schedule/schedule.module.d.ts +5 -0
  59. package/dist/schedule/schedule.module.js +18 -8
  60. package/dist/schedule/schedule.registry.d.ts +3 -3
  61. package/dist/schedule/schedule.registry.js +23 -44
  62. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  63. package/dist/schedule-node/schedule-node.module.js +20 -10
  64. package/dist/seeder/seeder.module.d.ts +8 -10
  65. package/dist/seeder/seeder.module.js +16 -15
  66. package/dist/websocket/index.d.ts +1 -1
  67. package/dist/websocket/index.js +1 -1
  68. package/dist/websocket/websocket.module.d.ts +20 -3
  69. package/dist/websocket/websocket.module.js +50 -39
  70. package/dist/websocket/websocket.tokens.d.ts +2 -1
  71. package/dist/websocket/websocket.tokens.js +3 -2
  72. package/dist/websocket/ws-dispatcher.d.ts +21 -9
  73. package/dist/websocket/ws-dispatcher.js +44 -38
  74. package/dist/websocket-node/register-gateways.js +3 -3
  75. package/package.json +1 -1
  76. package/dist/http/crud-bridge.d.ts +0 -90
  77. package/dist/http/crud-bridge.js +0 -25
@@ -0,0 +1,105 @@
1
+ import { InjectionToken, type ProviderOptions, type Token, type Type } from '../container/types';
2
+ import type { ComponentInstance, ModuleImport } from '../registry/types';
3
+ import type { ConfigurableModuleAsyncOptions, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost } from './configurable-module.types';
4
+ /** The `global:` slot's component groups, lowered to `APP_*` registrations. */
5
+ export interface GlobalComponentSlot {
6
+ guards?: Array<Type | ComponentInstance>;
7
+ pipes?: Array<Type | ComponentInstance>;
8
+ interceptors?: Array<Type | ComponentInstance>;
9
+ filters?: Array<Type | ComponentInstance>;
10
+ middleware?: Array<Type | ComponentInstance>;
11
+ }
12
+ /** What a module instance contributes, computed from its call-time options. */
13
+ export interface ModuleContributions {
14
+ providers?: Array<Type | ProviderOptions>;
15
+ controllers?: Type[];
16
+ imports?: ModuleImport[];
17
+ exports?: Array<Type | InjectionToken>;
18
+ /**
19
+ * Standardized global-component registration — the one idiom replacing both
20
+ * the `@Module({ providers: [{ provide: APP_GUARD, useExisting: X }] })`
21
+ * pattern and the conditional-push-into-forRoot pattern. Class entries are
22
+ * registered as providers and wired via `useExisting`; instances via `useValue`.
23
+ */
24
+ global?: GlobalComponentSlot;
25
+ }
26
+ export interface ModuleSetupContext<Opts> {
27
+ /** The options token — derived providers do `inject: [OPTIONS]`. */
28
+ readonly OPTIONS: InjectionToken<Opts>;
29
+ /**
30
+ * Structural options known at call time. For `forRoot` this is the full
31
+ * bag; for `forRootAsync` it is only the structural fields passed alongside
32
+ * the factory (the DI-resolved options exist only at resolution time —
33
+ * read them through `OPTIONS`, never here).
34
+ */
35
+ readonly options: Partial<Opts>;
36
+ /** The instance key in effect (for deriving per-instance token names). */
37
+ readonly key: string;
38
+ }
39
+ export interface DefineModuleSpec<Opts, Extras extends ConfigurableModuleExtras = {
40
+ isGlobal?: boolean;
41
+ }, MethodKey extends string = 'forRoot', FactoryMethodKey extends string = 'create'> {
42
+ /** Names the generated base class, the minted options token, and diagnostics. */
43
+ name: string;
44
+ /**
45
+ * Reuse an existing options token instead of minting one — critical for
46
+ * migrations so the module's public token keeps its identity.
47
+ */
48
+ optionsToken?: InjectionToken<Opts>;
49
+ /**
50
+ * Structural contributions as a function of the call-time options. Runs at
51
+ * `forRoot`/`forRootAsync` call time, once per module instance.
52
+ */
53
+ setup?: (ctx: ModuleSetupContext<Opts>) => ModuleContributions;
54
+ /**
55
+ * Dedup key from the structural options. Defaults to `stableHash(options)`.
56
+ * Modules whose options carry stateful instances (drivers, registries)
57
+ * should derive from the stable identifying subset — or document that
58
+ * callers pass an explicit `key` for multi-instance setups.
59
+ */
60
+ key?: (options: Partial<Opts>) => string;
61
+ /** Call-site extras defaults (default `{ isGlobal: false }`). */
62
+ extras?: Extras;
63
+ /** Reshape the definition from resolved extras (default: `isGlobal` → `global: true`). */
64
+ transform?: ConfigurableModuleExtrasTransform<Extras>;
65
+ /** Rename the sync static (default `forRoot`); the async static becomes `<name>Async`. */
66
+ methodName?: MethodKey;
67
+ /** Method a `useClass`/`useExisting` options factory must implement (default `create`). */
68
+ factoryMethodName?: FactoryMethodKey;
69
+ }
70
+ /**
71
+ * The one blessed module-authoring engine. Generates `forRoot` AND
72
+ * `forRootAsync` statics with: `stableHash` key derivation (multi-instance
73
+ * dedup that survives HMR), typed `inject` tuple inference on the async
74
+ * factory, an `isGlobal` extra, and — the piece `ConfigurableModuleBuilder`
75
+ * could not express — providers/controllers/imports/exports/global components
76
+ * computed **as functions of the options**.
77
+ *
78
+ * ```ts
79
+ * const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = defineModule<CorsOptions>({
80
+ * name: 'Cors',
81
+ * setup: ({ OPTIONS }) => ({
82
+ * providers: [{
83
+ * provide: APP_MIDDLEWARE,
84
+ * useFactory: (o: CorsOptions) => buildCorsMiddleware(o),
85
+ * inject: [OPTIONS],
86
+ * }],
87
+ * exports: [OPTIONS],
88
+ * }),
89
+ * });
90
+ * export class CorsModule extends ConfigurableModuleClass {}
91
+ * ```
92
+ *
93
+ * `ConfigurableModuleBuilder` remains supported (NestJS parity) and is a thin
94
+ * adapter over this engine.
95
+ */
96
+ export declare function defineModule<Opts, Extras extends ConfigurableModuleExtras = {
97
+ isGlobal?: boolean;
98
+ }, MethodKey extends string = 'forRoot', FactoryMethodKey extends string = 'create'>(spec: DefineModuleSpec<Opts, Extras, MethodKey, FactoryMethodKey>): ConfigurableModuleHost<Opts, MethodKey, FactoryMethodKey, Extras>;
99
+ /**
100
+ * Lower `useFactory`/`useClass`/`useExisting` async options into provider
101
+ * registrations. Structural fields from the call site merge UNDER the resolved
102
+ * options (`{ ...structural, ...resolved }`) so sync-declared fields act as
103
+ * defaults and the factory stays authoritative.
104
+ */
105
+ export declare function buildAsyncOptionsProviders<Opts>(optionsToken: Token, factoryMethodName: string, async: Pick<ConfigurableModuleAsyncOptions<Opts>, 'inject' | 'useFactory' | 'useClass' | 'useExisting'>, structural?: Record<string, unknown>): Array<ProviderOptions>;
@@ -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,6 +1,8 @@
1
1
  export { Global, Module, isModule, getModuleMetadata, defineDynamicModule, } from './decorators';
2
2
  export { stableHash } from './stable-hash';
3
- export { ConfigurableModuleBuilder, defineConfigurableModule, } from './configurable-module.builder';
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';
4
6
  export type { ConfigurableModuleAsyncOptions, ConfigurableModuleBuilderOptions, ConfigurableModuleClassType, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost, ConfigurableModuleOptionsFactory, DefineConfigurableModuleSpec, } from './configurable-module.types';
5
7
  export { ModuleLoader } from './module-loader';
6
8
  export { MiddlewareBuilder } from './middleware';
@@ -1,5 +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 } from "./configurable-module.builder.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";
4
6
  export { ModuleLoader } from "./module-loader.js";
5
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
  }