@alt-javascript/boot-camel-lite-starter 1.0.2
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/README.md +83 -0
- package/index.js +76 -0
- package/package.json +53 -0
- package/src/CamelLiteAutoConfiguration.js +338 -0
- package/test/starter.test.js +241 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# boot-camel-lite-starter
|
|
2
|
+
|
|
3
|
+
`@alt-javascript/boot` CDI auto-configuration for camel-lite core plus the eight most common components: `direct`, `seda`, `log`, `file`, `http`, `ftp`, `timer`, and `cron`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install boot-camel-lite-starter @alt-javascript/boot @alt-javascript/cdi @alt-javascript/config
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CDI Beans Registered
|
|
12
|
+
|
|
13
|
+
| Bean name | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `camelLiteContext` | `CamelContext` | The running Camel context |
|
|
16
|
+
| `routeRegistry` | `RouteRegistry` | Scans config + CDI for routes and loads them into the context |
|
|
17
|
+
| `camelComponent.direct` | `DirectComponent` | `direct:` scheme |
|
|
18
|
+
| `camelComponent.seda` | `SedaComponent` | `seda:` scheme |
|
|
19
|
+
| `camelComponent.log` | `LogComponent` | `log:` scheme |
|
|
20
|
+
| `camelComponent.file` | `FileComponent` | `file:` scheme |
|
|
21
|
+
| `camelComponent.http` | `HttpComponent` | `http:`/`https:` scheme (producer only) |
|
|
22
|
+
| `camelComponent.ftp` | `FtpComponent` | `ftp:` scheme |
|
|
23
|
+
| `camelComponent.timer` | `TimerComponent` | `timer:` scheme |
|
|
24
|
+
| `camelComponent.cron` | `CronComponent` | `cron:` scheme |
|
|
25
|
+
| `camelProducerTemplate` | `ProducerTemplate` | Send messages programmatically |
|
|
26
|
+
| `camelConsumerTemplate` | `ConsumerTemplate` | Poll endpoints programmatically |
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
All keys are nested under `boot.camel-lite`.
|
|
31
|
+
|
|
32
|
+
| Key | Type | Default | Description |
|
|
33
|
+
|-----|------|---------|-------------|
|
|
34
|
+
| `boot.camel-lite.<scheme>.enabled` | boolean | `true` | Set to `false` to skip registering that component bean |
|
|
35
|
+
| `boot.camel-lite.routes[n].definition` | object | — | An already-parsed route definition object (equivalent to a parsed YAML route file) |
|
|
36
|
+
|
|
37
|
+
### Config route definition example
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
boot:
|
|
41
|
+
camel-lite:
|
|
42
|
+
routes:
|
|
43
|
+
- definition:
|
|
44
|
+
- from: "direct:hello"
|
|
45
|
+
steps:
|
|
46
|
+
- log: "${body}"
|
|
47
|
+
- to: "direct:world"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`@alt-javascript/config` deserialises this at load time; the starter passes it to `RouteLoader.loadObject()` to avoid a double-parse.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { camelLiteStarter } from 'boot-camel-lite-starter';
|
|
56
|
+
|
|
57
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
58
|
+
|
|
59
|
+
const ctx = applicationContext.get('camelLiteContext');
|
|
60
|
+
await ctx.ready();
|
|
61
|
+
|
|
62
|
+
const pt = applicationContext.get('camelProducerTemplate');
|
|
63
|
+
await pt.sendBody('direct:hello', 'world');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## CDI RouteBuilder Discovery
|
|
67
|
+
|
|
68
|
+
Any CDI bean that exposes a `configure(camelContext)` method is treated as a RouteBuilder and discovered automatically by `RouteRegistry`. No explicit registration is required — declare the bean in your CDI config array and give it a `configure` method:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
class GreetingRoutes {
|
|
72
|
+
configure(camelContext) {
|
|
73
|
+
camelContext
|
|
74
|
+
.from('direct:greet')
|
|
75
|
+
.log('${body}');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## See Also
|
|
81
|
+
|
|
82
|
+
- [camel-lite root README](../../README.md)
|
|
83
|
+
- [boot-camel-lite-extras-starter README](../boot-camel-lite-extras-starter/README.md)
|
package/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* boot-camel-lite-starter
|
|
3
|
+
*
|
|
4
|
+
* CDI auto-configuration for camel-lite.
|
|
5
|
+
* Bundles: core + direct, seda, log, file, http, ftp, timer, cron.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { camelLiteStarter } from '@alt-javascript/boot-camel-lite-starter';
|
|
9
|
+
*
|
|
10
|
+
* const { applicationContext } = await camelLiteStarter({
|
|
11
|
+
* config: {
|
|
12
|
+
* 'boot.camel-lite.routes': [
|
|
13
|
+
* { definition: { route: { from: { uri: 'direct:hello', steps: [{ log: { simple: '${body}' } }] } } } }
|
|
14
|
+
* ]
|
|
15
|
+
* },
|
|
16
|
+
* contexts: [new Context([new Singleton(MyRouteBuilder)])],
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const ctx = applicationContext.get('camelLiteContext');
|
|
20
|
+
* await ctx.ready();
|
|
21
|
+
*
|
|
22
|
+
* const pt = applicationContext.get('camelProducerTemplate');
|
|
23
|
+
* await pt.sendBody('direct:hello', 'world');
|
|
24
|
+
*
|
|
25
|
+
* Config keys (prefix: boot.camel-lite):
|
|
26
|
+
* boot.camel-lite.<scheme>.enabled — enable/disable bundled component (default: true)
|
|
27
|
+
* boot.camel-lite.routes[n].definition — already-parsed route definition object
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { ApplicationContext } from '@alt-javascript/cdi';
|
|
31
|
+
import { EphemeralConfig } from '@alt-javascript/config';
|
|
32
|
+
import {
|
|
33
|
+
camelLiteAutoConfiguration,
|
|
34
|
+
CamelLiteContext,
|
|
35
|
+
RouteRegistry,
|
|
36
|
+
CdiProducerTemplate,
|
|
37
|
+
CdiConsumerTemplate,
|
|
38
|
+
} from './src/CamelLiteAutoConfiguration.js';
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
camelLiteAutoConfiguration,
|
|
42
|
+
CamelLiteContext,
|
|
43
|
+
RouteRegistry,
|
|
44
|
+
CdiProducerTemplate,
|
|
45
|
+
CdiConsumerTemplate,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { PREFIX } from './src/CamelLiteAutoConfiguration.js';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Boot the application with camel-lite auto-configuration.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} options
|
|
54
|
+
* @param {Array} [options.contexts] — CDI Context array (your beans)
|
|
55
|
+
* @param {object} [options.config] — config object or POJO
|
|
56
|
+
* @param {object} [options.startOptions] — forwarded to ApplicationContext.start()
|
|
57
|
+
* @returns {Promise<{applicationContext: ApplicationContext}>}
|
|
58
|
+
*/
|
|
59
|
+
export async function camelLiteStarter(options = {}) {
|
|
60
|
+
const { contexts = [], config, startOptions = {} } = options;
|
|
61
|
+
|
|
62
|
+
const cfg = config instanceof Object && typeof config.has === 'function'
|
|
63
|
+
? config
|
|
64
|
+
: new EphemeralConfig(config ?? {});
|
|
65
|
+
|
|
66
|
+
const autoConfig = await camelLiteAutoConfiguration(options);
|
|
67
|
+
|
|
68
|
+
const applicationContext = new ApplicationContext({
|
|
69
|
+
config: cfg,
|
|
70
|
+
contexts: [...contexts, autoConfig],
|
|
71
|
+
...startOptions,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await applicationContext.start();
|
|
75
|
+
return { applicationContext };
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alt-javascript/boot-camel-lite-starter",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@alt-javascript/boot": "^3.0.7",
|
|
10
|
+
"@alt-javascript/cdi": "^3.0.7",
|
|
11
|
+
"@alt-javascript/config": "^3.0.7",
|
|
12
|
+
"@alt-javascript/logger": "^3.0.7",
|
|
13
|
+
"@alt-javascript/common": "^3.0.7",
|
|
14
|
+
"@alt-javascript/camel-lite-core": "1.0.2",
|
|
15
|
+
"@alt-javascript/camel-lite-component-direct": "1.0.2",
|
|
16
|
+
"@alt-javascript/camel-lite-component-seda": "1.0.2",
|
|
17
|
+
"@alt-javascript/camel-lite-component-log": "1.0.2",
|
|
18
|
+
"@alt-javascript/camel-lite-component-file": "1.0.2",
|
|
19
|
+
"@alt-javascript/camel-lite-component-http": "1.0.2",
|
|
20
|
+
"@alt-javascript/camel-lite-component-ftp": "1.0.2",
|
|
21
|
+
"@alt-javascript/camel-lite-component-timer": "1.0.2",
|
|
22
|
+
"@alt-javascript/camel-lite-component-cron": "1.0.2"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/alt-javascript/camel-lite"
|
|
30
|
+
},
|
|
31
|
+
"author": "Craig Parravicini",
|
|
32
|
+
"contributors": [
|
|
33
|
+
"Claude (Anthropic)",
|
|
34
|
+
"Apache Camel — design inspiration and pattern source"
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"alt-javascript",
|
|
38
|
+
"camel",
|
|
39
|
+
"camel-lite",
|
|
40
|
+
"eai",
|
|
41
|
+
"eip",
|
|
42
|
+
"integration",
|
|
43
|
+
"boot",
|
|
44
|
+
"starter",
|
|
45
|
+
"cdi",
|
|
46
|
+
"dependency-injection",
|
|
47
|
+
"autoconfigure"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org/",
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CamelLiteAutoConfiguration — CDI auto-configuration for camel-lite.
|
|
3
|
+
*
|
|
4
|
+
* Registers the following CDI beans:
|
|
5
|
+
*
|
|
6
|
+
* camelLiteContext — wraps CamelContext; async start via init()/ready()/destroy()
|
|
7
|
+
* routeRegistry — discovers CDI RouteBuilder beans + config route objects
|
|
8
|
+
* direct/seda/log/... — one ConfiguredComponent per scheme (8 bundled)
|
|
9
|
+
* camelProducerTemplate — ProducerTemplate CDI bean
|
|
10
|
+
* camelConsumerTemplate — ConsumerTemplate CDI bean
|
|
11
|
+
*
|
|
12
|
+
* Config prefix: boot.camel-lite
|
|
13
|
+
*
|
|
14
|
+
* Per-scheme enable/disable:
|
|
15
|
+
* boot.camel-lite.direct.enabled = false → DirectComponent not registered
|
|
16
|
+
* (default: true for all bundled schemes)
|
|
17
|
+
*
|
|
18
|
+
* Route definitions from config:
|
|
19
|
+
* boot.camel-lite.routes[0].definition: { route: { from: { uri: ..., steps: [...] } } }
|
|
20
|
+
* (already-parsed JS object — @alt-javascript/config deserialises at load time)
|
|
21
|
+
*
|
|
22
|
+
* CDI RouteBuilder discovery:
|
|
23
|
+
* Any CDI bean with a configure(camelContext) method is treated as a RouteBuilder.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { LoggerFactory } from '@alt-javascript/logger';
|
|
27
|
+
import { CamelContext, RouteLoader, ProducerTemplate, ConsumerTemplate } from '@alt-javascript/camel-lite-core';
|
|
28
|
+
|
|
29
|
+
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/boot/CamelLiteAutoConfiguration');
|
|
30
|
+
|
|
31
|
+
export const PREFIX = 'boot.camel-lite';
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// CamelLiteContext
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export class CamelLiteContext {
|
|
38
|
+
constructor() {
|
|
39
|
+
this._applicationContext = null;
|
|
40
|
+
this._camelContext = null;
|
|
41
|
+
this._startPromise = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setApplicationContext(ctx) {
|
|
45
|
+
this._applicationContext = ctx;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get camelContext() {
|
|
49
|
+
return this._camelContext;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
init() {
|
|
53
|
+
this._camelContext = new CamelContext();
|
|
54
|
+
|
|
55
|
+
// Wire registered components from CDI context into the CamelContext
|
|
56
|
+
const registry = this._applicationContext.get('routeRegistry');
|
|
57
|
+
registry.applyComponents(this._camelContext, this._applicationContext);
|
|
58
|
+
registry.applyRoutes(this._camelContext, this._applicationContext.config);
|
|
59
|
+
|
|
60
|
+
this._startPromise = this._camelContext.start();
|
|
61
|
+
log.info('CamelLiteContext: starting CamelContext (async)');
|
|
62
|
+
return this._startPromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Await full CamelContext start (routes active, consumers registered).
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
async ready() {
|
|
70
|
+
if (this._startPromise) await this._startPromise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async destroy() {
|
|
74
|
+
if (this._camelContext) {
|
|
75
|
+
await this._camelContext.stop();
|
|
76
|
+
log.info('CamelLiteContext: CamelContext stopped');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// RouteRegistry
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
export class RouteRegistry {
|
|
86
|
+
constructor() {
|
|
87
|
+
this._applicationContext = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setApplicationContext(ctx) {
|
|
91
|
+
this._applicationContext = ctx;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
init() {
|
|
95
|
+
// No-op at init time — applyComponents and applyRoutes are called by CamelLiteContext.init()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Register camel-lite components from CDI beans into the CamelContext.
|
|
100
|
+
* Each CDI bean named 'camelComponent.<scheme>' has already registered itself;
|
|
101
|
+
* this just ensures the CamelContext receives them in the right order.
|
|
102
|
+
* Component beans call camelContext.addComponent() themselves in their init(),
|
|
103
|
+
* but since CamelLiteContext.init() runs after them (via dependsOn), the
|
|
104
|
+
* CamelContext doesn't exist yet. So we store the registrations here and
|
|
105
|
+
* apply them when CamelLiteContext calls applyComponents().
|
|
106
|
+
*
|
|
107
|
+
* @param {CamelContext} camelContext
|
|
108
|
+
* @param {ApplicationContext} appCtx
|
|
109
|
+
*/
|
|
110
|
+
applyComponents(camelContext, appCtx) {
|
|
111
|
+
const components = appCtx.components;
|
|
112
|
+
let count = 0;
|
|
113
|
+
for (const [name, def] of Object.entries(components)) {
|
|
114
|
+
if (name.startsWith('camelComponent.')) {
|
|
115
|
+
const scheme = name.slice('camelComponent.'.length);
|
|
116
|
+
const wrapper = appCtx.get(name);
|
|
117
|
+
const component = typeof wrapper.getComponent === 'function'
|
|
118
|
+
? wrapper.getComponent()
|
|
119
|
+
: wrapper;
|
|
120
|
+
camelContext.addComponent(scheme, component);
|
|
121
|
+
log.debug(`RouteRegistry: registered component '${scheme}'`);
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
log.info(`RouteRegistry: registered ${count} component(s)`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Discover CDI RouteBuilder beans and config-driven route definitions,
|
|
130
|
+
* register all on the CamelContext.
|
|
131
|
+
*
|
|
132
|
+
* @param {CamelContext} camelContext
|
|
133
|
+
* @param {import('@alt-javascript/config').IConfig} config
|
|
134
|
+
*/
|
|
135
|
+
applyRoutes(camelContext, config) {
|
|
136
|
+
let count = 0;
|
|
137
|
+
const appCtx = this._applicationContext;
|
|
138
|
+
|
|
139
|
+
// 1. CDI RouteBuilder beans — any bean with a configure(ctx) method
|
|
140
|
+
for (const [name, def] of Object.entries(appCtx.components)) {
|
|
141
|
+
// Skip internal camel-lite beans
|
|
142
|
+
if (name === 'camelLiteContext' || name === 'routeRegistry' ||
|
|
143
|
+
name === 'camelProducerTemplate' || name === 'camelConsumerTemplate' ||
|
|
144
|
+
name.startsWith('camelComponent.')) continue;
|
|
145
|
+
|
|
146
|
+
const bean = appCtx.get(name);
|
|
147
|
+
if (bean && typeof bean.configure === 'function') {
|
|
148
|
+
log.debug(`RouteRegistry: discovered CDI RouteBuilder '${name}'`);
|
|
149
|
+
camelContext.addRoutes(bean);
|
|
150
|
+
count++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Config-driven route definition objects
|
|
155
|
+
// boot.camel-lite.routes is an array; each entry has a .definition property
|
|
156
|
+
// that is an already-parsed JS object (deserialized by @alt-javascript/config)
|
|
157
|
+
const routesKey = `${PREFIX}.routes`;
|
|
158
|
+
if (config.has(routesKey)) {
|
|
159
|
+
const routes = config.get(routesKey);
|
|
160
|
+
const arr = Array.isArray(routes) ? routes : [routes];
|
|
161
|
+
for (let i = 0; i < arr.length; i++) {
|
|
162
|
+
const entry = arr[i];
|
|
163
|
+
const defKey = `${routesKey}[${i}].definition`;
|
|
164
|
+
const definition = config.has(defKey) ? config.get(defKey) : entry?.definition;
|
|
165
|
+
if (!definition) {
|
|
166
|
+
log.warn(`RouteRegistry: boot.camel-lite.routes[${i}] has no definition — skipping`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const builder = RouteLoader.loadObject(definition);
|
|
171
|
+
camelContext.addRoutes(builder);
|
|
172
|
+
log.info(`RouteRegistry: loaded config route[${i}]`);
|
|
173
|
+
count++;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
log.warn(`RouteRegistry: failed to load config route[${i}]: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
log.info(`RouteRegistry: registered ${count} route builder(s)`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Component factory — creates a CDI singleton that holds a camel-lite component
|
|
186
|
+
// instance, retrievable by CamelLiteContext via applyComponents()
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
function makeComponentClass(scheme, ComponentClass) {
|
|
190
|
+
class ConfiguredComponent {
|
|
191
|
+
constructor() {
|
|
192
|
+
this._component = new ComponentClass();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Returns the underlying camel-lite component instance. */
|
|
196
|
+
getComponent() {
|
|
197
|
+
return this._component;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Give the class a meaningful name for CDI
|
|
201
|
+
Object.defineProperty(ConfiguredComponent, 'name', { value: `CamelComponent_${scheme}` });
|
|
202
|
+
return ConfiguredComponent;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isEnabled(config, scheme) {
|
|
206
|
+
const key = `${PREFIX}.${scheme}.enabled`;
|
|
207
|
+
if (config.has(key)) {
|
|
208
|
+
const val = config.get(key);
|
|
209
|
+
return val !== false && val !== 'false';
|
|
210
|
+
}
|
|
211
|
+
return true; // enabled by default
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// ProducerTemplate / ConsumerTemplate CDI beans
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
export class CdiProducerTemplate {
|
|
219
|
+
constructor() {
|
|
220
|
+
this.camelLiteContext = null; // autowired
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
init() { /* wired by CDI property injection */ }
|
|
224
|
+
|
|
225
|
+
async sendBody(uri, body, headers) {
|
|
226
|
+
await this.camelLiteContext.ready();
|
|
227
|
+
const pt = new ProducerTemplate(this.camelLiteContext.camelContext);
|
|
228
|
+
return pt.sendBody(uri, body, headers);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async requestBody(uri, body, headers) {
|
|
232
|
+
await this.camelLiteContext.ready();
|
|
233
|
+
const pt = new ProducerTemplate(this.camelLiteContext.camelContext);
|
|
234
|
+
return pt.requestBody(uri, body, headers);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export class CdiConsumerTemplate {
|
|
239
|
+
constructor() {
|
|
240
|
+
this.camelLiteContext = null; // autowired
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
init() { /* wired by CDI property injection */ }
|
|
244
|
+
|
|
245
|
+
async receiveBody(uri, timeoutMs) {
|
|
246
|
+
await this.camelLiteContext.ready();
|
|
247
|
+
const ct = new ConsumerTemplate(this.camelLiteContext.camelContext);
|
|
248
|
+
return ct.receiveBody(uri, timeoutMs);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async receive(uri, timeoutMs) {
|
|
252
|
+
await this.camelLiteContext.ready();
|
|
253
|
+
const ct = new ConsumerTemplate(this.camelLiteContext.camelContext);
|
|
254
|
+
return ct.receive(uri, timeoutMs);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// camelLiteAutoConfiguration()
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
const BUNDLED_SCHEMES = ['direct', 'seda', 'log', 'file', 'http', 'ftp', 'timer', 'cron'];
|
|
263
|
+
|
|
264
|
+
const COMPONENT_IMPORTS = {
|
|
265
|
+
direct: () => import('@alt-javascript/camel-lite-component-direct').then(m => m.DirectComponent),
|
|
266
|
+
seda: () => import('@alt-javascript/camel-lite-component-seda').then(m => m.SedaComponent),
|
|
267
|
+
log: () => import('@alt-javascript/camel-lite-component-log').then(m => m.LogComponent),
|
|
268
|
+
file: () => import('@alt-javascript/camel-lite-component-file').then(m => m.FileComponent),
|
|
269
|
+
http: () => import('@alt-javascript/camel-lite-component-http').then(m => m.HttpComponent),
|
|
270
|
+
ftp: () => import('@alt-javascript/camel-lite-component-ftp').then(m => m.FtpComponent),
|
|
271
|
+
timer: () => import('@alt-javascript/camel-lite-component-timer').then(m => m.TimerComponent),
|
|
272
|
+
cron: () => import('@alt-javascript/camel-lite-component-cron').then(m => m.CronComponent),
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns CDI component definitions for the core camel-lite starter.
|
|
277
|
+
*
|
|
278
|
+
* @param {object} [options]
|
|
279
|
+
* @param {string[]} [options.schemes] — override bundled scheme list
|
|
280
|
+
* @returns {Promise<Array>} CDI component definition array
|
|
281
|
+
*/
|
|
282
|
+
export async function camelLiteAutoConfiguration(options = {}) {
|
|
283
|
+
const schemes = options.schemes ?? BUNDLED_SCHEMES;
|
|
284
|
+
const defs = [];
|
|
285
|
+
|
|
286
|
+
// --- Component beans (loaded eagerly so we have the class reference) ---
|
|
287
|
+
const componentBeanNames = [];
|
|
288
|
+
for (const scheme of schemes) {
|
|
289
|
+
const beanName = `camelComponent.${scheme}`;
|
|
290
|
+
componentBeanNames.push(beanName);
|
|
291
|
+
|
|
292
|
+
const ComponentClass = await COMPONENT_IMPORTS[scheme]();
|
|
293
|
+
const ConfiguredComponent = makeComponentClass(scheme, ComponentClass);
|
|
294
|
+
|
|
295
|
+
defs.push({
|
|
296
|
+
name: beanName,
|
|
297
|
+
Reference: ConfiguredComponent,
|
|
298
|
+
scope: 'singleton',
|
|
299
|
+
condition: (config) => isEnabled(config, scheme),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --- RouteRegistry ---
|
|
304
|
+
defs.push({
|
|
305
|
+
name: 'routeRegistry',
|
|
306
|
+
Reference: RouteRegistry,
|
|
307
|
+
scope: 'singleton',
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// --- CamelLiteContext (depends on routeRegistry only — component beans are
|
|
311
|
+
// optional and scanned dynamically by RouteRegistry.applyComponents()) ---
|
|
312
|
+
defs.push({
|
|
313
|
+
name: 'camelLiteContext',
|
|
314
|
+
Reference: CamelLiteContext,
|
|
315
|
+
scope: 'singleton',
|
|
316
|
+
dependsOn: 'routeRegistry',
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// --- ProducerTemplate ---
|
|
320
|
+
defs.push({
|
|
321
|
+
name: 'camelProducerTemplate',
|
|
322
|
+
Reference: CdiProducerTemplate,
|
|
323
|
+
scope: 'singleton',
|
|
324
|
+
properties: [{ name: 'camelLiteContext', reference: 'camelLiteContext' }],
|
|
325
|
+
dependsOn: 'camelLiteContext',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// --- ConsumerTemplate ---
|
|
329
|
+
defs.push({
|
|
330
|
+
name: 'camelConsumerTemplate',
|
|
331
|
+
Reference: CdiConsumerTemplate,
|
|
332
|
+
scope: 'singleton',
|
|
333
|
+
properties: [{ name: 'camelLiteContext', reference: 'camelLiteContext' }],
|
|
334
|
+
dependsOn: 'camelLiteContext',
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return defs;
|
|
338
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, it, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { EphemeralConfig } from '@alt-javascript/config';
|
|
4
|
+
import { camelLiteStarter, camelLiteAutoConfiguration, CamelLiteContext } from '../index.js';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const contexts = []; // track for cleanup
|
|
11
|
+
|
|
12
|
+
async function boot(config = {}) {
|
|
13
|
+
const cfg = new EphemeralConfig(config);
|
|
14
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
15
|
+
contexts.push(applicationContext);
|
|
16
|
+
return applicationContext;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
after(async () => {
|
|
20
|
+
for (const ctx of contexts) {
|
|
21
|
+
try {
|
|
22
|
+
const c = ctx.get('camelLiteContext');
|
|
23
|
+
await c.camelContext?.stop();
|
|
24
|
+
} catch { /* ignore */ }
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// 1. Auto-configuration shape
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
describe('camelLiteAutoConfiguration: shape', () => {
|
|
33
|
+
it('returns an array of CDI component definitions', async () => {
|
|
34
|
+
const defs = await camelLiteAutoConfiguration();
|
|
35
|
+
assert.ok(Array.isArray(defs));
|
|
36
|
+
const names = defs.map(d => d.name);
|
|
37
|
+
assert.ok(names.includes('camelLiteContext'));
|
|
38
|
+
assert.ok(names.includes('routeRegistry'));
|
|
39
|
+
assert.ok(names.includes('camelProducerTemplate'));
|
|
40
|
+
assert.ok(names.includes('camelConsumerTemplate'));
|
|
41
|
+
// bundled schemes
|
|
42
|
+
for (const scheme of ['direct','seda','log','file','http','ftp','timer','cron']) {
|
|
43
|
+
assert.ok(names.includes(`camelComponent.${scheme}`), `missing camelComponent.${scheme}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('camelLiteContext dependsOn routeRegistry', async () => {
|
|
48
|
+
const defs = await camelLiteAutoConfiguration();
|
|
49
|
+
const ctxDef = defs.find(d => d.name === 'camelLiteContext');
|
|
50
|
+
const deps = Array.isArray(ctxDef.dependsOn)
|
|
51
|
+
? ctxDef.dependsOn
|
|
52
|
+
: [ctxDef.dependsOn];
|
|
53
|
+
assert.ok(deps.includes('routeRegistry'));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 2. ApplicationContext boots and CamelLiteContext is ready
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
describe('camelLiteStarter: boot lifecycle', () => {
|
|
62
|
+
it('camelLiteContext bean is present in ApplicationContext', async () => {
|
|
63
|
+
const appCtx = await boot();
|
|
64
|
+
const ctx = appCtx.get('camelLiteContext');
|
|
65
|
+
assert.ok(ctx instanceof CamelLiteContext);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('ready() resolves without throwing', async () => {
|
|
69
|
+
const appCtx = await boot();
|
|
70
|
+
const ctx = appCtx.get('camelLiteContext');
|
|
71
|
+
await assert.doesNotReject(() => ctx.ready());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('CamelContext is started after ready()', async () => {
|
|
75
|
+
const appCtx = await boot();
|
|
76
|
+
const ctx = appCtx.get('camelLiteContext');
|
|
77
|
+
await ctx.ready();
|
|
78
|
+
assert.equal(ctx.camelContext.started, true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// 3. ProducerTemplate CDI bean
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
describe('camelProducerTemplate: sendBody through direct: route', () => {
|
|
87
|
+
it('sends message through a route and returns exchange with no exception', async () => {
|
|
88
|
+
// Boot with a config route using a direct: endpoint
|
|
89
|
+
const cfg = new EphemeralConfig({
|
|
90
|
+
boot: {
|
|
91
|
+
'camel-lite': {
|
|
92
|
+
routes: [{
|
|
93
|
+
definition: {
|
|
94
|
+
route: {
|
|
95
|
+
from: { uri: 'direct:hello', steps: [{ setBody: { constant: 'response' } }] }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
104
|
+
contexts.push(applicationContext);
|
|
105
|
+
const clCtx = applicationContext.get('camelLiteContext');
|
|
106
|
+
await clCtx.ready();
|
|
107
|
+
|
|
108
|
+
const pt = applicationContext.get('camelProducerTemplate');
|
|
109
|
+
const exchange = await pt.sendBody('direct:hello', 'input');
|
|
110
|
+
assert.equal(exchange.exception, null);
|
|
111
|
+
assert.equal(exchange.in.body, 'response');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 4. CDI RouteBuilder bean discovery
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
describe('RouteRegistry: CDI RouteBuilder bean discovery', () => {
|
|
120
|
+
it('discovers a CDI bean with configure(ctx) and activates its routes', async () => {
|
|
121
|
+
const { ApplicationContext } = await import('@alt-javascript/cdi');
|
|
122
|
+
const { EphemeralConfig } = await import('@alt-javascript/config');
|
|
123
|
+
const { camelLiteAutoConfiguration } = await import('../index.js');
|
|
124
|
+
const { RouteBuilder } = await import('@alt-javascript/camel-lite-core');
|
|
125
|
+
|
|
126
|
+
const received = [];
|
|
127
|
+
|
|
128
|
+
// CamelContext.addRoutes(builder) calls builder.configure(this) then iterates
|
|
129
|
+
// builder.getRoutes(). A RouteBuilder subclass that calls this.from() in
|
|
130
|
+
// configure() is the standard CDI RouteBuilder pattern.
|
|
131
|
+
class TestRouteBuilder extends RouteBuilder {
|
|
132
|
+
configure(camelCtx) {
|
|
133
|
+
this.from('direct:cdi-discovered').process(ex => {
|
|
134
|
+
received.push(ex.in.body);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const autoConfig = await camelLiteAutoConfiguration();
|
|
140
|
+
const appCtx = new ApplicationContext({
|
|
141
|
+
config: new EphemeralConfig({}),
|
|
142
|
+
contexts: [
|
|
143
|
+
[{ name: 'testRouteBuilder', Reference: TestRouteBuilder, scope: 'singleton' }],
|
|
144
|
+
autoConfig,
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
await appCtx.start();
|
|
148
|
+
contexts.push(appCtx);
|
|
149
|
+
|
|
150
|
+
const clCtx = appCtx.get('camelLiteContext');
|
|
151
|
+
await clCtx.ready();
|
|
152
|
+
|
|
153
|
+
const pt = appCtx.get('camelProducerTemplate');
|
|
154
|
+
await pt.sendBody('direct:cdi-discovered', 'discovery-test');
|
|
155
|
+
|
|
156
|
+
assert.deepEqual(received, ['discovery-test']);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// 5. Config-driven route definition objects
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
describe('RouteRegistry: config route objects via RouteLoader.loadObject', () => {
|
|
165
|
+
it('loads route from boot.camel-lite.routes[0].definition object', async () => {
|
|
166
|
+
const fired = [];
|
|
167
|
+
const cfg = new EphemeralConfig({
|
|
168
|
+
boot: {
|
|
169
|
+
'camel-lite': {
|
|
170
|
+
routes: [{
|
|
171
|
+
definition: {
|
|
172
|
+
route: {
|
|
173
|
+
from: {
|
|
174
|
+
uri: 'direct:config-route',
|
|
175
|
+
steps: [{ setBody: { constant: 'from-config' } }]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
185
|
+
contexts.push(applicationContext);
|
|
186
|
+
const clCtx = applicationContext.get('camelLiteContext');
|
|
187
|
+
await clCtx.ready();
|
|
188
|
+
|
|
189
|
+
const pt = applicationContext.get('camelProducerTemplate');
|
|
190
|
+
const exchange = await pt.sendBody('direct:config-route', 'input');
|
|
191
|
+
assert.equal(exchange.in.body, 'from-config');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('loads multiple config routes', async () => {
|
|
195
|
+
const cfg = new EphemeralConfig({
|
|
196
|
+
boot: {
|
|
197
|
+
'camel-lite': {
|
|
198
|
+
routes: [
|
|
199
|
+
{ definition: { route: { from: { uri: 'direct:multi-a', steps: [{ setBody: { constant: 'a' } }] } } } },
|
|
200
|
+
{ definition: { route: { from: { uri: 'direct:multi-b', steps: [{ setBody: { constant: 'b' } }] } } } },
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
207
|
+
contexts.push(applicationContext);
|
|
208
|
+
const clCtx = applicationContext.get('camelLiteContext');
|
|
209
|
+
await clCtx.ready();
|
|
210
|
+
|
|
211
|
+
const pt = applicationContext.get('camelProducerTemplate');
|
|
212
|
+
const exA = await pt.sendBody('direct:multi-a', 'x');
|
|
213
|
+
const exB = await pt.sendBody('direct:multi-b', 'x');
|
|
214
|
+
assert.equal(exA.in.body, 'a');
|
|
215
|
+
assert.equal(exB.in.body, 'b');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// 6. Component enable/disable
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
describe('Component enable/disable via config', () => {
|
|
224
|
+
it('direct: component registered by default (no config flag)', async () => {
|
|
225
|
+
const appCtx = await boot({});
|
|
226
|
+
const clCtx = appCtx.get('camelLiteContext');
|
|
227
|
+
await clCtx.ready();
|
|
228
|
+
assert.ok(clCtx.camelContext.getComponent('direct'), 'direct component should be registered');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('direct: component absent when boot.camel-lite.direct.enabled=false', async () => {
|
|
232
|
+
const cfg = new EphemeralConfig({
|
|
233
|
+
boot: { 'camel-lite': { direct: { enabled: false } } }
|
|
234
|
+
});
|
|
235
|
+
const { applicationContext } = await camelLiteStarter({ config: cfg });
|
|
236
|
+
contexts.push(applicationContext);
|
|
237
|
+
const clCtx = applicationContext.get('camelLiteContext');
|
|
238
|
+
await clCtx.ready();
|
|
239
|
+
assert.equal(clCtx.camelContext.getComponent('direct'), undefined);
|
|
240
|
+
});
|
|
241
|
+
});
|