@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 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
+ });