@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,166 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
function _ts_param(paramIndex, decorator) {
|
|
11
|
+
return function(target, key) {
|
|
12
|
+
decorator(target, key, paramIndex);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
import { Scope } from "../constants.js";
|
|
16
|
+
import { Container } from "../container/container.js";
|
|
17
|
+
import { Inject, Injectable } from "../container/decorators.js";
|
|
18
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
19
|
+
function isMethodMetaItem(x) {
|
|
20
|
+
return typeof x === 'object' && x !== null && (typeof x.methodName === 'string' || typeof x.methodName === 'symbol');
|
|
21
|
+
}
|
|
22
|
+
function resolveKey(key) {
|
|
23
|
+
return typeof key === 'string' ? key : key.KEY;
|
|
24
|
+
}
|
|
25
|
+
export class DiscoveryService {
|
|
26
|
+
container;
|
|
27
|
+
constructor(container){
|
|
28
|
+
this.container = container;
|
|
29
|
+
}
|
|
30
|
+
/** Every class-token provider registered in the container. */ getProviders(filter) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const token of this.container.getTokens()){
|
|
33
|
+
if (typeof token !== 'function') continue;
|
|
34
|
+
const entry = this.buildEntry(token, filter, 'provider discovery');
|
|
35
|
+
if (entry) out.push(entry);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Providers whose class carries class-level metadata under `key` — both
|
|
41
|
+
* class decorators (single value) and method decorators that append
|
|
42
|
+
* class-level lists (`@Cron`, `@OnEvent`, `@SubscribeMessage`).
|
|
43
|
+
*/ providersWithMeta(key, filter) {
|
|
44
|
+
const metaKey = resolveKey(key);
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const target of this.candidatesWithClassMeta(metaKey)){
|
|
47
|
+
const entry = this.buildEntry(target, filter, `discovery of '${metaKey}'`);
|
|
48
|
+
if (!entry) continue;
|
|
49
|
+
const meta = MetadataRegistry.getCustomClassMeta(target, metaKey);
|
|
50
|
+
out.push({
|
|
51
|
+
...entry,
|
|
52
|
+
meta
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Handler methods carrying metadata under `key`. Merges the two storage
|
|
59
|
+
* conventions:
|
|
60
|
+
* - class-level appended lists whose items carry `methodName`
|
|
61
|
+
* (`@Cron`/`@OnEvent`-style method decorators), and
|
|
62
|
+
* - true handler-level metadata (`@SetMetadata`/`createDiscoverableDecorator`
|
|
63
|
+
* applied to a method).
|
|
64
|
+
*/ methodsWithMeta(key, filter) {
|
|
65
|
+
const metaKey = resolveKey(key);
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const target of this.candidatesWithClassMeta(metaKey)){
|
|
68
|
+
const classMeta = MetadataRegistry.getCustomClassMeta(target, metaKey);
|
|
69
|
+
if (!Array.isArray(classMeta) || !classMeta.some(isMethodMetaItem)) continue;
|
|
70
|
+
const entry = this.buildEntry(target, filter, `discovery of '${metaKey}'`);
|
|
71
|
+
if (!entry) continue;
|
|
72
|
+
for (const item of classMeta){
|
|
73
|
+
if (!isMethodMetaItem(item)) continue;
|
|
74
|
+
out.push({
|
|
75
|
+
class: entry,
|
|
76
|
+
methodName: item.methodName,
|
|
77
|
+
meta: item
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const target of MetadataRegistry.getClassesWithHandlerMeta(metaKey)){
|
|
82
|
+
if (!this.container.has(target)) continue;
|
|
83
|
+
const entry = this.buildEntry(target, filter, `discovery of '${metaKey}'`);
|
|
84
|
+
if (!entry) continue;
|
|
85
|
+
for (const { handler, value } of MetadataRegistry.getHandlersWithMeta(target, metaKey)){
|
|
86
|
+
out.push({
|
|
87
|
+
class: entry,
|
|
88
|
+
methodName: handler,
|
|
89
|
+
meta: value
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Index-first candidate walk with a legacy full-scan fallback: when the
|
|
97
|
+
* reverse index knows nothing about a key, an older copy of the registry
|
|
98
|
+
* module may have taken the writes (mixed dist/src or package versions in
|
|
99
|
+
* one process) — fall back to scanning container tokens the way the
|
|
100
|
+
* pre-DiscoveryService loops did.
|
|
101
|
+
*/ candidatesWithClassMeta(metaKey) {
|
|
102
|
+
const indexed = MetadataRegistry.getClassesWithClassMeta(metaKey);
|
|
103
|
+
if (indexed.size > 0) {
|
|
104
|
+
return [
|
|
105
|
+
...indexed
|
|
106
|
+
].filter((t)=>this.container.has(t));
|
|
107
|
+
}
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const token of this.container.getTokens()){
|
|
110
|
+
if (typeof token !== 'function') continue;
|
|
111
|
+
if (MetadataRegistry.getCustomClassMeta(token, metaKey) !== undefined) {
|
|
112
|
+
out.push(token);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
buildEntry(metatype, filter, label) {
|
|
118
|
+
const moduleIds = this.container.getOwnerModuleIds(metatype);
|
|
119
|
+
if (moduleIds.length === 0) return undefined;
|
|
120
|
+
if (filter?.moduleId !== undefined) {
|
|
121
|
+
const wanted = Array.isArray(filter.moduleId) ? filter.moduleId : [
|
|
122
|
+
filter.moduleId
|
|
123
|
+
];
|
|
124
|
+
if (!moduleIds.some((id)=>wanted.includes(id))) return undefined;
|
|
125
|
+
}
|
|
126
|
+
const scope = this.container.getProviderScope(metatype) ?? Scope.SINGLETON;
|
|
127
|
+
if (scope === Scope.REQUEST && !filter?.includeRequestScoped) {
|
|
128
|
+
if (this.container.getDiagnostics() === 'log') {
|
|
129
|
+
console.warn(`[vela] ${label}: ${metatype.name} is request-scoped and cannot be ` + `materialized at bootstrap — skipped. Pass { includeRequestScoped: true } to override.`);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
token: metatype,
|
|
133
|
+
metatype,
|
|
134
|
+
moduleIds,
|
|
135
|
+
scope,
|
|
136
|
+
instance: undefined
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let instance;
|
|
140
|
+
try {
|
|
141
|
+
instance = this.container.resolve(metatype);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
const mode = this.container.getDiagnostics();
|
|
144
|
+
if (mode === 'throw') throw err;
|
|
145
|
+
if (mode === 'log') {
|
|
146
|
+
console.warn(`[vela] ${label}: cannot resolve ${metatype.name}:`, err);
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
token: metatype,
|
|
152
|
+
metatype,
|
|
153
|
+
moduleIds,
|
|
154
|
+
scope,
|
|
155
|
+
instance
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
DiscoveryService = _ts_decorate([
|
|
160
|
+
Injectable(),
|
|
161
|
+
_ts_param(0, Inject(Container)),
|
|
162
|
+
_ts_metadata("design:type", Function),
|
|
163
|
+
_ts_metadata("design:paramtypes", [
|
|
164
|
+
typeof Container === "undefined" ? Object : Container
|
|
165
|
+
])
|
|
166
|
+
], DiscoveryService);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DiscoveryService } from '../discovery/discovery.service';
|
|
2
|
+
import { type Entrypoint, type EntrypointKind } from './entrypoint.types';
|
|
3
|
+
/**
|
|
4
|
+
* Declare an entrypoint kind. Idempotent per `kind` (last declaration wins so
|
|
5
|
+
* an HMR-re-evaluated module can re-declare); typically called at module
|
|
6
|
+
* import time next to the decorator definition:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* export const WS_GATEWAY_METADATA = 'vela:ws:gateway';
|
|
10
|
+
* registerEntrypointKind({ kind: 'websocket', metaKey: WS_GATEWAY_METADATA, level: 'class' });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerEntrypointKind(k: EntrypointKind): void;
|
|
14
|
+
/** All declared kinds (registration order). */
|
|
15
|
+
export declare function getEntrypointKinds(): EntrypointKind[];
|
|
16
|
+
/**
|
|
17
|
+
* Reset all kind declarations. Test-only.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function _resetEntrypointKinds(): void;
|
|
21
|
+
/**
|
|
22
|
+
* The per-application view of every entrypoint: built once after
|
|
23
|
+
* `onApplicationBootstrap` hooks have run (so `ContributesEntrypoints`
|
|
24
|
+
* providers have finished their own discovery), queried by runtime adapters:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* for (const ep of app.entrypoints.ofKind('websocket')) { ... }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Per-app (never global): two applications in one process each build their
|
|
31
|
+
* own registry from their own container.
|
|
32
|
+
*/
|
|
33
|
+
export declare class EntrypointRegistry {
|
|
34
|
+
private readonly byKind;
|
|
35
|
+
static build(discovery: DiscoveryService, eagerInstances: readonly unknown[]): Promise<EntrypointRegistry>;
|
|
36
|
+
private add;
|
|
37
|
+
ofKind<M = unknown>(kind: string): Entrypoint<M>[];
|
|
38
|
+
kinds(): string[];
|
|
39
|
+
all(): Entrypoint[];
|
|
40
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { contributesEntrypoints } from "./entrypoint.types.js";
|
|
2
|
+
// Kind declarations are process-global and import-time (a module declares its
|
|
3
|
+
// kind next to its decorator, before any app exists), so they are anchored on
|
|
4
|
+
// `globalThis` exactly like MetadataRegistry state: a Vite HMR re-eval must
|
|
5
|
+
// reuse the same store, not mint an empty one.
|
|
6
|
+
const KIND_STORE_KEY = Symbol.for('vela:entrypoints:v1');
|
|
7
|
+
function kindStore() {
|
|
8
|
+
const g = globalThis;
|
|
9
|
+
return g[KIND_STORE_KEY] ??= new Map();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Declare an entrypoint kind. Idempotent per `kind` (last declaration wins so
|
|
13
|
+
* an HMR-re-evaluated module can re-declare); typically called at module
|
|
14
|
+
* import time next to the decorator definition:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* export const WS_GATEWAY_METADATA = 'vela:ws:gateway';
|
|
18
|
+
* registerEntrypointKind({ kind: 'websocket', metaKey: WS_GATEWAY_METADATA, level: 'class' });
|
|
19
|
+
* ```
|
|
20
|
+
*/ export function registerEntrypointKind(k) {
|
|
21
|
+
kindStore().set(k.kind, k);
|
|
22
|
+
}
|
|
23
|
+
/** All declared kinds (registration order). */ export function getEntrypointKinds() {
|
|
24
|
+
return [
|
|
25
|
+
...kindStore().values()
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reset all kind declarations. Test-only.
|
|
30
|
+
* @internal
|
|
31
|
+
*/ export function _resetEntrypointKinds() {
|
|
32
|
+
kindStore().clear();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The per-application view of every entrypoint: built once after
|
|
36
|
+
* `onApplicationBootstrap` hooks have run (so `ContributesEntrypoints`
|
|
37
|
+
* providers have finished their own discovery), queried by runtime adapters:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* for (const ep of app.entrypoints.ofKind('websocket')) { ... }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Per-app (never global): two applications in one process each build their
|
|
44
|
+
* own registry from their own container.
|
|
45
|
+
*/ export class EntrypointRegistry {
|
|
46
|
+
byKind = new Map();
|
|
47
|
+
static async build(discovery, eagerInstances) {
|
|
48
|
+
const registry = new EntrypointRegistry();
|
|
49
|
+
for (const kind of getEntrypointKinds()){
|
|
50
|
+
if (kind.level === 'class') {
|
|
51
|
+
for (const found of discovery.providersWithMeta(kind.metaKey)){
|
|
52
|
+
registry.add({
|
|
53
|
+
kind: kind.kind,
|
|
54
|
+
token: found.token,
|
|
55
|
+
instance: found.instance,
|
|
56
|
+
meta: found.meta
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
for (const found of discovery.methodsWithMeta(kind.metaKey)){
|
|
61
|
+
registry.add({
|
|
62
|
+
kind: kind.kind,
|
|
63
|
+
token: found.class.token,
|
|
64
|
+
instance: found.class.instance,
|
|
65
|
+
methodName: found.methodName,
|
|
66
|
+
meta: found.meta
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Computed contributions. A contributor is AUTHORITATIVE for every kind it
|
|
72
|
+
// reports: its entries replace any metaKey-derived ones of the same kind,
|
|
73
|
+
// so a dispatcher that both carries decorator metadata and computes richer
|
|
74
|
+
// entries (path routing, aggregated handlers) never double-reports.
|
|
75
|
+
const contributed = [];
|
|
76
|
+
for (const instance of new Set(eagerInstances)){
|
|
77
|
+
if (!contributesEntrypoints(instance)) continue;
|
|
78
|
+
contributed.push(...await instance.collectEntrypoints(discovery));
|
|
79
|
+
}
|
|
80
|
+
for (const kind of new Set(contributed.map((ep)=>ep.kind))){
|
|
81
|
+
registry.byKind.delete(kind);
|
|
82
|
+
}
|
|
83
|
+
for (const ep of contributed)registry.add(ep);
|
|
84
|
+
return registry;
|
|
85
|
+
}
|
|
86
|
+
add(ep) {
|
|
87
|
+
const list = this.byKind.get(ep.kind);
|
|
88
|
+
if (list) list.push(ep);
|
|
89
|
+
else this.byKind.set(ep.kind, [
|
|
90
|
+
ep
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
ofKind(kind) {
|
|
94
|
+
return this.byKind.get(kind) ?? [];
|
|
95
|
+
}
|
|
96
|
+
kinds() {
|
|
97
|
+
return [
|
|
98
|
+
...this.byKind.keys()
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
all() {
|
|
102
|
+
return [
|
|
103
|
+
...this.byKind.values()
|
|
104
|
+
].flat();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Token } from '../container/types';
|
|
2
|
+
import type { DiscoveryService } from '../discovery/discovery.service';
|
|
3
|
+
/**
|
|
4
|
+
* A declared entrypoint kind: a named category of non-HTTP (or non-standard)
|
|
5
|
+
* entry surface that some module knows how to dispatch — WebSocket gateways,
|
|
6
|
+
* queue consumers, scheduled jobs, CLI commands. Core ships NO fixed set;
|
|
7
|
+
* any module registers its own kind and any runtime adapter can query it.
|
|
8
|
+
*
|
|
9
|
+
* `metaKey` binds the kind to a decorator's metadata key: every provider
|
|
10
|
+
* carrying that metadata becomes an entrypoint of this kind at bootstrap.
|
|
11
|
+
*/
|
|
12
|
+
export interface EntrypointKind {
|
|
13
|
+
kind: string;
|
|
14
|
+
metaKey: string;
|
|
15
|
+
/**
|
|
16
|
+
* 'class': one entrypoint per decorated provider (`@WebSocketGateway`).
|
|
17
|
+
* 'method': one entrypoint per decorated method (`@Cron`, `@QueueConsumer`
|
|
18
|
+
* handler methods) — flattened from the class-level list convention.
|
|
19
|
+
*/
|
|
20
|
+
level: 'class' | 'method';
|
|
21
|
+
}
|
|
22
|
+
/** One concrete entrypoint discovered at bootstrap. */
|
|
23
|
+
export interface Entrypoint<M = unknown> {
|
|
24
|
+
kind: string;
|
|
25
|
+
token: Token;
|
|
26
|
+
/** The resolved provider backing the entrypoint (undefined if request-scoped). */
|
|
27
|
+
instance: unknown;
|
|
28
|
+
/** Present for method-level kinds. */
|
|
29
|
+
methodName?: string | symbol;
|
|
30
|
+
meta: M;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Escape hatch for providers whose entrypoints are computed rather than purely
|
|
34
|
+
* decorator-declared (e.g. `WsDispatcher` aggregates per-gateway routing state
|
|
35
|
+
* before contributing). Implementers are detected among the app's eagerly
|
|
36
|
+
* instantiated providers after `onApplicationBootstrap` hooks have run.
|
|
37
|
+
*/
|
|
38
|
+
export interface ContributesEntrypoints {
|
|
39
|
+
collectEntrypoints(discovery: DiscoveryService): Entrypoint[] | Promise<Entrypoint[]>;
|
|
40
|
+
}
|
|
41
|
+
export declare function contributesEntrypoints(x: unknown): x is ContributesEntrypoints;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Type } from '../container/types';
|
|
2
|
+
import type { ExecutionContext } from '../pipeline/types';
|
|
3
|
+
/** ExecutionContext flavor handed to components around entrypoint dispatch. */
|
|
4
|
+
export interface EntrypointExecutionContext extends ExecutionContext {
|
|
5
|
+
/** The dispatch payload (queue batch, scheduled event, …). */
|
|
6
|
+
getPayload<T = unknown>(): T;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Entrypoint sibling of `buildExecutionContext` (HTTP) and
|
|
10
|
+
* `buildWsExecutionContext`: lets guards/interceptors/filters written against
|
|
11
|
+
* `getClass()`/`getHandler()`/`getType()` run unchanged around queue batches,
|
|
12
|
+
* scheduled ticks, and any custom entrypoint kind. Transport-specific
|
|
13
|
+
* accessors throw rather than fabricate fakes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildEntrypointExecutionContext(kind: string, targetClass: Type, handlerName: string | symbol, payload: unknown): EntrypointExecutionContext;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const WRONG_TRANSPORT = (accessor, kind)=>()=>{
|
|
2
|
+
throw new Error(`${accessor} called on a '${kind}' entrypoint ExecutionContext — this ` + `handler runs over an entrypoint dispatch, not that transport. Use ` + `getType()/getPayload() to branch.`);
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Entrypoint sibling of `buildExecutionContext` (HTTP) and
|
|
6
|
+
* `buildWsExecutionContext`: lets guards/interceptors/filters written against
|
|
7
|
+
* `getClass()`/`getHandler()`/`getType()` run unchanged around queue batches,
|
|
8
|
+
* scheduled ticks, and any custom entrypoint kind. Transport-specific
|
|
9
|
+
* accessors throw rather than fabricate fakes.
|
|
10
|
+
*/ export function buildEntrypointExecutionContext(kind, targetClass, handlerName, payload) {
|
|
11
|
+
return {
|
|
12
|
+
getType: ()=>kind,
|
|
13
|
+
getClass: ()=>targetClass,
|
|
14
|
+
getHandler: ()=>handlerName,
|
|
15
|
+
getPayload: ()=>payload,
|
|
16
|
+
getContext: WRONG_TRANSPORT('getContext()', kind),
|
|
17
|
+
getRequest: WRONG_TRANSPORT('getRequest()', kind),
|
|
18
|
+
switchToHttp: WRONG_TRANSPORT('switchToHttp()', kind),
|
|
19
|
+
switchToWs: WRONG_TRANSPORT('switchToWs()', kind)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Container } from '../container/container';
|
|
2
|
+
/**
|
|
3
|
+
* Run one unit of non-HTTP work (a queue batch, a scheduled tick, an RPC
|
|
4
|
+
* call) inside a fresh request-scoped child container — the non-HTTP
|
|
5
|
+
* equivalent of the per-request child the HTTP pipeline creates:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* await runInEntrypointScope(app.getContainer(), async (scope) => {
|
|
9
|
+
* const consumer = scope.resolve(ep.token);
|
|
10
|
+
* await consumer.process(batch);
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Request-scoped providers (and singletons bubbled to request scope) resolve
|
|
15
|
+
* per invocation instead of leaking one boot-time instance across every
|
|
16
|
+
* dispatch; the child's request-scoped disposables are disposed (LIFO) when
|
|
17
|
+
* `fn` settles — success or failure. `REQUEST_CONTEXT` is deliberately NOT
|
|
18
|
+
* seeded: it is an HTTP primitive, and resolving it here keeps throwing its
|
|
19
|
+
* explicit misuse error.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runInEntrypointScope<T>(container: Container, fn: (scope: Container) => T | Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one unit of non-HTTP work (a queue batch, a scheduled tick, an RPC
|
|
3
|
+
* call) inside a fresh request-scoped child container — the non-HTTP
|
|
4
|
+
* equivalent of the per-request child the HTTP pipeline creates:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* await runInEntrypointScope(app.getContainer(), async (scope) => {
|
|
8
|
+
* const consumer = scope.resolve(ep.token);
|
|
9
|
+
* await consumer.process(batch);
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Request-scoped providers (and singletons bubbled to request scope) resolve
|
|
14
|
+
* per invocation instead of leaking one boot-time instance across every
|
|
15
|
+
* dispatch; the child's request-scoped disposables are disposed (LIFO) when
|
|
16
|
+
* `fn` settles — success or failure. `REQUEST_CONTEXT` is deliberately NOT
|
|
17
|
+
* seeded: it is an HTTP primitive, and resolving it here keeps throwing its
|
|
18
|
+
* explicit misuse error.
|
|
19
|
+
*/ export async function runInEntrypointScope(container, fn) {
|
|
20
|
+
const scope = container.createChild();
|
|
21
|
+
try {
|
|
22
|
+
return await fn(scope);
|
|
23
|
+
} finally{
|
|
24
|
+
if (scope.hasDisposables()) {
|
|
25
|
+
await scope.dispose();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { EntrypointRegistry, getEntrypointKinds, registerEntrypointKind, } from './entrypoint.registry';
|
|
2
|
+
export { runInEntrypointScope } from './execution-scope';
|
|
3
|
+
export { buildEntrypointExecutionContext, type EntrypointExecutionContext, } from './execution-context';
|
|
4
|
+
export { contributesEntrypoints, type ContributesEntrypoints, type Entrypoint, type EntrypointKind, } from './entrypoint.types';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { EntrypointRegistry, getEntrypointKinds, registerEntrypointKind } from "./entrypoint.registry.js";
|
|
2
|
+
export { runInEntrypointScope } from "./execution-scope.js";
|
|
3
|
+
export { buildEntrypointExecutionContext } from "./execution-context.js";
|
|
4
|
+
export { contributesEntrypoints } from "./entrypoint.types.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DiscoveryService } from '../discovery/discovery.service';
|
|
2
2
|
import type { OnApplicationBootstrap } from '../lifecycle/index';
|
|
3
3
|
import { EventEmitter } from './event-emitter.service';
|
|
4
4
|
export declare class EventEmitterSubscriber implements OnApplicationBootstrap {
|
|
5
|
-
private
|
|
5
|
+
private readonly discovery;
|
|
6
6
|
private emitter;
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(discovery: DiscoveryService, emitter: EventEmitter);
|
|
8
8
|
onApplicationBootstrap(): void;
|
|
9
9
|
}
|
|
@@ -13,50 +13,33 @@ function _ts_param(paramIndex, decorator) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
import { Injectable, Inject } from "../container/index.js";
|
|
16
|
-
import {
|
|
17
|
-
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
16
|
+
import { DiscoveryService } from "../discovery/discovery.service.js";
|
|
18
17
|
import { EventEmitter } from "./event-emitter.service.js";
|
|
19
18
|
import { ON_EVENT_METADATA } from "./event-emitter.tokens.js";
|
|
20
19
|
export class EventEmitterSubscriber {
|
|
21
|
-
|
|
20
|
+
discovery;
|
|
22
21
|
emitter;
|
|
23
|
-
constructor(
|
|
24
|
-
this.
|
|
22
|
+
constructor(discovery, emitter){
|
|
23
|
+
this.discovery = discovery;
|
|
25
24
|
this.emitter = emitter;
|
|
26
25
|
}
|
|
27
26
|
onApplicationBootstrap() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let instance;
|
|
35
|
-
try {
|
|
36
|
-
instance = this.container.resolve(token);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
const mode = this.container.getDiagnostics();
|
|
39
|
-
if (mode === 'throw') throw err;
|
|
40
|
-
if (mode === 'log') {
|
|
41
|
-
console.warn(`[vela] event subscriber discovery: cannot resolve ${token.name || String(token)}:`, err);
|
|
42
|
-
}
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
for (const { event, methodName } of metadata){
|
|
46
|
-
const method = instance[methodName];
|
|
47
|
-
if (typeof method === 'function') {
|
|
48
|
-
this.emitter.on(event, (...args)=>method.apply(instance, args));
|
|
49
|
-
}
|
|
27
|
+
for (const found of this.discovery.methodsWithMeta(ON_EVENT_METADATA)){
|
|
28
|
+
const instance = found.class.instance;
|
|
29
|
+
if (!instance) continue;
|
|
30
|
+
const method = instance[found.methodName];
|
|
31
|
+
if (typeof method === 'function') {
|
|
32
|
+
this.emitter.on(found.meta.event, (...args)=>method.apply(instance, args));
|
|
50
33
|
}
|
|
51
34
|
}
|
|
52
35
|
}
|
|
53
36
|
}
|
|
54
37
|
EventEmitterSubscriber = _ts_decorate([
|
|
55
38
|
Injectable(),
|
|
56
|
-
_ts_param(0, Inject(
|
|
39
|
+
_ts_param(0, Inject(DiscoveryService)),
|
|
57
40
|
_ts_metadata("design:type", Function),
|
|
58
41
|
_ts_metadata("design:paramtypes", [
|
|
59
|
-
typeof
|
|
42
|
+
typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
|
|
60
43
|
typeof EventEmitter === "undefined" ? Object : EventEmitter
|
|
61
44
|
])
|
|
62
45
|
], EventEmitterSubscriber);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { VelaApplication } from '../application';
|
|
3
|
+
import type { Container } from '../container/container';
|
|
4
|
+
import type { DiscoveryService } from '../discovery/discovery.service';
|
|
5
|
+
import type { RouteManager } from '../http/route.manager';
|
|
6
|
+
/** The framework's resolved world, handed to adapter hooks. */
|
|
7
|
+
export interface AdapterContext {
|
|
8
|
+
app: VelaApplication;
|
|
9
|
+
container: Container;
|
|
10
|
+
routeManager: RouteManager;
|
|
11
|
+
discovery: DiscoveryService;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The contract a runtime binding implements to plug a platform into a Vela
|
|
15
|
+
* app — what `@velajs/cloudflare` (bindings + Durable Object WebSocket) and
|
|
16
|
+
* the Node/Bun WebSocket transport do, made first-class:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const app = await VelaFactory.create(AppModule, {
|
|
20
|
+
* adapters: [cloudflareAdapter({ bindings })],
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* - `requestMiddleware` is prepended to the global middleware chain (runs
|
|
25
|
+
* before consumer middleware — e.g. capture `c.env` for binding services).
|
|
26
|
+
* - `onBootstrap` runs after DI + lifecycle hooks, with `app.entrypoints`
|
|
27
|
+
* available, BEFORE routes are built — register platform services here.
|
|
28
|
+
* - `onRoutesBuilt` runs after the Hono app exists — mount platform routes
|
|
29
|
+
* (WebSocket upgrades, health endpoints) here via `ctx.app.getHonoApp()`.
|
|
30
|
+
*/
|
|
31
|
+
export interface RuntimeAdapter {
|
|
32
|
+
name: string;
|
|
33
|
+
requestMiddleware?: MiddlewareHandler[];
|
|
34
|
+
onBootstrap?(ctx: AdapterContext): void | Promise<void>;
|
|
35
|
+
onRoutesBuilt?(ctx: AdapterContext): void | Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract a runtime binding implements to plug a platform into a Vela
|
|
3
|
+
* app — what `@velajs/cloudflare` (bindings + Durable Object WebSocket) and
|
|
4
|
+
* the Node/Bun WebSocket transport do, made first-class:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const app = await VelaFactory.create(AppModule, {
|
|
8
|
+
* adapters: [cloudflareAdapter({ bindings })],
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* - `requestMiddleware` is prepended to the global middleware chain (runs
|
|
13
|
+
* before consumer middleware — e.g. capture `c.env` for binding services).
|
|
14
|
+
* - `onBootstrap` runs after DI + lifecycle hooks, with `app.entrypoints`
|
|
15
|
+
* available, BEFORE routes are built — register platform services here.
|
|
16
|
+
* - `onRoutesBuilt` runs after the Hono app exists — mount platform routes
|
|
17
|
+
* (WebSocket upgrades, health endpoints) here via `ctx.app.getHonoApp()`.
|
|
18
|
+
*/ export { };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Scope } from "../constants.js";
|
|
2
2
|
import { Container } from "../container/container.js";
|
|
3
3
|
import { ModuleRef } from "../container/module-ref.js";
|
|
4
|
+
import { DiscoveryService } from "../discovery/discovery.service.js";
|
|
4
5
|
import { REQUEST_CONTEXT } from "../http/request-context.js";
|
|
5
6
|
import { RouteManager } from "../http/route.manager.js";
|
|
6
7
|
import { ModuleLoader } from "../module/module-loader.js";
|
|
7
8
|
import { bindAppProviders } from "../pipeline/app-providers.js";
|
|
8
|
-
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
9
9
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
10
10
|
/**
|
|
11
11
|
* Wire the DI graph for `rootModule` and prepare the route manager — without
|
|
@@ -32,6 +32,15 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
32
32
|
]
|
|
33
33
|
});
|
|
34
34
|
container.markGlobalToken(ModuleRef);
|
|
35
|
+
// Decorator-driven discovery — global so any provider can inject it.
|
|
36
|
+
container.register({
|
|
37
|
+
provide: DiscoveryService,
|
|
38
|
+
useFactory: (c)=>new DiscoveryService(c),
|
|
39
|
+
inject: [
|
|
40
|
+
Container
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
container.markGlobalToken(DiscoveryService);
|
|
35
44
|
for (const t of [
|
|
36
45
|
APP_GUARD,
|
|
37
46
|
APP_PIPE,
|
|
@@ -61,7 +70,6 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
61
70
|
useValue: routeManager
|
|
62
71
|
});
|
|
63
72
|
container.markGlobalToken(RouteManager);
|
|
64
|
-
ComponentManager.init(container);
|
|
65
73
|
const loader = new ModuleLoader(container, routeManager);
|
|
66
74
|
loader.load(rootModule);
|
|
67
75
|
bindAppProviders(routeManager, container, loader);
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { VelaApplication } from './application';
|
|
2
2
|
import type { Type } from './container/types';
|
|
3
|
+
import type { RuntimeAdapter } from './factory/adapter';
|
|
3
4
|
import type { BootstrapOptions } from './factory/bootstrap';
|
|
5
|
+
export interface VelaCreateOptions extends BootstrapOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Runtime adapters binding this app to a platform (Cloudflare bindings,
|
|
8
|
+
* WebSocket transports, …). Their `requestMiddleware` is prepended to the
|
|
9
|
+
* global middleware chain; `onBootstrap`/`onRoutesBuilt` hooks run around
|
|
10
|
+
* route building. See {@link RuntimeAdapter}.
|
|
11
|
+
*/
|
|
12
|
+
adapters?: RuntimeAdapter[];
|
|
13
|
+
}
|
|
4
14
|
export declare const VelaFactory: {
|
|
5
|
-
create(rootModule: Type, options?:
|
|
15
|
+
create(rootModule: Type, options?: VelaCreateOptions): Promise<VelaApplication>;
|
|
6
16
|
};
|