@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.
- package/CHANGELOG.md +102 -0
- package/dist/application.d.ts +8 -0
- package/dist/application.js +20 -0
- package/dist/container/container.d.ts +25 -0
- package/dist/container/container.js +67 -11
- package/dist/container/disposable.js +1 -1
- 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 +10 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- package/dist/http/handler-executor.js +22 -19
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.js +28 -19
- package/dist/i18n/i18n.module.js +4 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +9 -2
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +12 -0
- package/dist/module/configurable-module.builder.js +21 -114
- package/dist/module/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +3 -1
- package/dist/module/index.js +3 -1
- 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/registry/metadata.registry.d.ts +9 -3
- package/dist/registry/metadata.registry.js +55 -25
- 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/seeder.module.d.ts +8 -10
- package/dist/seeder/seeder.module.js +16 -15
- package/dist/websocket/index.d.ts +1 -1
- package/dist/websocket/index.js +1 -1
- package/dist/websocket/websocket.module.d.ts +20 -3
- package/dist/websocket/websocket.module.js +50 -39
- package/dist/websocket/websocket.tokens.d.ts +2 -1
- package/dist/websocket/websocket.tokens.js +3 -2
- package/dist/websocket/ws-dispatcher.d.ts +21 -9
- package/dist/websocket/ws-dispatcher.js +44 -38
- package/dist/websocket-node/register-gateways.js +3 -3
- package/package.json +1 -1
- package/dist/http/crud-bridge.d.ts +0 -90
- 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
|
+
}
|
package/dist/module/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/module/index.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/openapi/document.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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()
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
227
|
-
if (
|
|
225
|
+
const meta = getMetadata(contributor.claimsMetaKey, controller);
|
|
226
|
+
if (meta === undefined) continue;
|
|
228
227
|
const controllerPrefix = MetadataRegistry.getControllerPath(controller);
|
|
229
|
-
const
|
|
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(
|
|
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
|
|
236
|
-
//
|
|
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
|
|
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
|
}
|