@alt-javascript/camel-lite-component-direct 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,57 @@
1
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
2
+
3
+ ## What
4
+
5
+ Synchronous in-process routing between routes. `direct:` delivers exchanges immediately in the calling thread — zero queue, zero latency. Cycle detection is built in; circular routes throw a `CamelError` at dispatch time.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install camel-lite-component-direct
11
+ ```
12
+
13
+ ## URI Syntax
14
+
15
+ ```
16
+ direct:name
17
+ ```
18
+
19
+ | Segment | Description |
20
+ |---------|-------------|
21
+ | `name` | Route name (case-sensitive). Must match the `from('direct:name')` of the target route. |
22
+
23
+ No query parameters.
24
+
25
+ ## Usage
26
+
27
+ ```js
28
+ import { CamelContext } from 'camel-lite-core';
29
+ import { DirectComponent } from 'camel-lite-component-direct';
30
+
31
+ const context = new CamelContext();
32
+ context.addComponent('direct', new DirectComponent());
33
+
34
+ context.addRoutes({
35
+ configure(ctx) {
36
+ ctx.from('direct:greet')
37
+ .process(exchange => {
38
+ exchange.in.body = `Hello, ${exchange.in.body}!`;
39
+ });
40
+
41
+ ctx.from('direct:start')
42
+ .to('direct:greet');
43
+ }
44
+ });
45
+
46
+ await context.start();
47
+
48
+ const template = context.createProducerTemplate();
49
+ const exchange = await template.send('direct:start', ex => { ex.in.body = 'World'; });
50
+ console.log(exchange.in.body); // Hello, World!
51
+
52
+ await context.stop();
53
+ ```
54
+
55
+ ## See Also
56
+
57
+ [camel-lite — root README](../../README.md)
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@alt-javascript/camel-lite-component-direct",
3
+ "version": "1.0.2",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.js"
7
+ },
8
+ "dependencies": {
9
+ "@alt-javascript/common": "^3.0.7",
10
+ "@alt-javascript/config": "^3.0.7",
11
+ "@alt-javascript/logger": "^3.0.7",
12
+ "@alt-javascript/camel-lite-core": "1.0.2"
13
+ },
14
+ "scripts": {
15
+ "test": "node --test"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/alt-javascript/camel-lite"
20
+ },
21
+ "author": "Craig Parravicini",
22
+ "contributors": [
23
+ "Claude (Anthropic)",
24
+ "Apache Camel — design inspiration and pattern source"
25
+ ],
26
+ "keywords": [
27
+ "alt-javascript",
28
+ "camel",
29
+ "camel-lite",
30
+ "eai",
31
+ "eip",
32
+ "integration",
33
+ "direct",
34
+ "routing",
35
+ "component"
36
+ ],
37
+ "publishConfig": {
38
+ "registry": "https://registry.npmjs.org/",
39
+ "access": "public"
40
+ }
41
+ }
@@ -0,0 +1,11 @@
1
+ import { Component } from '@alt-javascript/camel-lite-core';
2
+ import DirectEndpoint from './DirectEndpoint.js';
3
+
4
+ class DirectComponent extends Component {
5
+ createEndpoint(uri, remaining, parameters, context) {
6
+ return new DirectEndpoint(uri, context);
7
+ }
8
+ }
9
+
10
+ export { DirectComponent };
11
+ export default DirectComponent;
@@ -0,0 +1,33 @@
1
+ import { Consumer } from '@alt-javascript/camel-lite-core';
2
+
3
+ class DirectConsumer extends Consumer {
4
+ #uri;
5
+ #context;
6
+ #pipeline;
7
+
8
+ constructor(uri, context, pipeline) {
9
+ super();
10
+ this.#uri = uri;
11
+ this.#context = context;
12
+ this.#pipeline = pipeline;
13
+ }
14
+
15
+ get uri() {
16
+ return this.#uri;
17
+ }
18
+
19
+ async start() {
20
+ this.#context.registerConsumer(this.#uri, this);
21
+ }
22
+
23
+ async stop() {
24
+ this.#context.registerConsumer(this.#uri, null);
25
+ }
26
+
27
+ async process(exchange) {
28
+ return this.#pipeline.run(exchange);
29
+ }
30
+ }
31
+
32
+ export { DirectConsumer };
33
+ export default DirectConsumer;
@@ -0,0 +1,29 @@
1
+ import { Endpoint } from '@alt-javascript/camel-lite-core';
2
+ import DirectProducer from './DirectProducer.js';
3
+ import DirectConsumer from './DirectConsumer.js';
4
+
5
+ class DirectEndpoint extends Endpoint {
6
+ #uri;
7
+ #context;
8
+
9
+ constructor(uri, context) {
10
+ super();
11
+ this.#uri = uri;
12
+ this.#context = context;
13
+ }
14
+
15
+ get uri() {
16
+ return this.#uri;
17
+ }
18
+
19
+ createProducer() {
20
+ return new DirectProducer(this.#uri, this.#context);
21
+ }
22
+
23
+ createConsumer(pipeline) {
24
+ return new DirectConsumer(this.#uri, this.#context, pipeline);
25
+ }
26
+ }
27
+
28
+ export { DirectEndpoint };
29
+ export default DirectEndpoint;
@@ -0,0 +1,51 @@
1
+ import { Producer } from '@alt-javascript/camel-lite-core';
2
+ import { CycleDetectedError } from '@alt-javascript/camel-lite-core';
3
+ import { LoggerFactory } from '@alt-javascript/logger';
4
+
5
+ const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/DirectProducer');
6
+
7
+ const STACK_KEY = 'camel.directStack';
8
+
9
+ class DirectProducer extends Producer {
10
+ #uri;
11
+ #context;
12
+
13
+ constructor(uri, context) {
14
+ super();
15
+ this.#uri = uri;
16
+ this.#context = context;
17
+ }
18
+
19
+ get uri() {
20
+ return this.#uri;
21
+ }
22
+
23
+ async send(exchange) {
24
+ const stack = exchange.getProperty(STACK_KEY) || [];
25
+
26
+ if (stack.includes(this.#uri)) {
27
+ log.error(`Cycle detected sending to: ${this.#uri}`);
28
+ throw new CycleDetectedError(this.#uri);
29
+ }
30
+
31
+ log.debug(`Dispatching exchange to: ${this.#uri}`);
32
+
33
+ const newStack = [...stack, this.#uri];
34
+ exchange.setProperty(STACK_KEY, newStack);
35
+
36
+ const consumer = this.#context.getConsumer(this.#uri);
37
+ if (!consumer) {
38
+ exchange.setProperty(STACK_KEY, stack);
39
+ throw new Error('No consumer registered for: ' + this.#uri);
40
+ }
41
+
42
+ try {
43
+ await consumer.process(exchange);
44
+ } finally {
45
+ exchange.setProperty(STACK_KEY, stack);
46
+ }
47
+ }
48
+ }
49
+
50
+ export { DirectProducer };
51
+ export default DirectProducer;
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { DirectComponent } from './DirectComponent.js';
2
+ export { DirectEndpoint } from './DirectEndpoint.js';
3
+ export { DirectProducer } from './DirectProducer.js';
4
+ export { DirectConsumer } from './DirectConsumer.js';
5
+
6
+ import DirectComponent from './DirectComponent.js';
7
+ export default DirectComponent;
@@ -0,0 +1,144 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Exchange, CamelContext, Pipeline, CycleDetectedError } from '@alt-javascript/camel-lite-core';
4
+ import { DirectComponent, DirectEndpoint, DirectProducer, DirectConsumer } from '@alt-javascript/camel-lite-component-direct';
5
+
6
+ // Helper: build a minimal pipeline that sets a property on the exchange
7
+ function mutatingPipeline(key, value) {
8
+ return new Pipeline([
9
+ async (exchange) => {
10
+ exchange.setProperty(key, value);
11
+ },
12
+ ]);
13
+ }
14
+
15
+ describe('DirectComponent', () => {
16
+ it('can be constructed', () => {
17
+ const dc = new DirectComponent();
18
+ assert.ok(dc instanceof DirectComponent);
19
+ });
20
+
21
+ it('createEndpoint returns DirectEndpoint', () => {
22
+ const dc = new DirectComponent();
23
+ const ctx = new CamelContext();
24
+ const ep = dc.createEndpoint('direct:foo', 'foo', {}, ctx);
25
+ assert.ok(ep instanceof DirectEndpoint);
26
+ assert.equal(ep.uri, 'direct:foo');
27
+ });
28
+ });
29
+
30
+ describe('DirectEndpoint', () => {
31
+ it('createProducer returns DirectProducer', () => {
32
+ const ctx = new CamelContext();
33
+ const ep = new DirectEndpoint('direct:foo', ctx);
34
+ const producer = ep.createProducer();
35
+ assert.ok(producer instanceof DirectProducer);
36
+ assert.equal(producer.uri, 'direct:foo');
37
+ });
38
+
39
+ it('createConsumer returns DirectConsumer', () => {
40
+ const ctx = new CamelContext();
41
+ const ep = new DirectEndpoint('direct:foo', ctx);
42
+ const pipeline = mutatingPipeline('hit', true);
43
+ const consumer = ep.createConsumer(pipeline);
44
+ assert.ok(consumer instanceof DirectConsumer);
45
+ assert.equal(consumer.uri, 'direct:foo');
46
+ });
47
+ });
48
+
49
+ describe('DirectConsumer', () => {
50
+ it('start() registers with context; getConsumer returns it', async () => {
51
+ const ctx = new CamelContext();
52
+ const pipeline = mutatingPipeline('hit', true);
53
+ const consumer = new DirectConsumer('direct:bar', ctx, pipeline);
54
+
55
+ await consumer.start();
56
+ assert.strictEqual(ctx.getConsumer('direct:bar'), consumer);
57
+ });
58
+
59
+ it('stop() deregisters from context', async () => {
60
+ const ctx = new CamelContext();
61
+ const pipeline = mutatingPipeline('hit', true);
62
+ const consumer = new DirectConsumer('direct:bar', ctx, pipeline);
63
+
64
+ await consumer.start();
65
+ await consumer.stop();
66
+ assert.equal(ctx.getConsumer('direct:bar'), null);
67
+ });
68
+
69
+ it('process() runs the pipeline on the exchange', async () => {
70
+ const ctx = new CamelContext();
71
+ const pipeline = mutatingPipeline('processed', 42);
72
+ const consumer = new DirectConsumer('direct:baz', ctx, pipeline);
73
+
74
+ const exchange = new Exchange();
75
+ await consumer.process(exchange);
76
+ assert.equal(exchange.getProperty('processed'), 42);
77
+ });
78
+ });
79
+
80
+ describe('DirectProducer', () => {
81
+ it('send() dispatches through consumer and exchange is mutated', async () => {
82
+ const ctx = new CamelContext();
83
+ const pipeline = mutatingPipeline('answer', 99);
84
+ const consumer = new DirectConsumer('direct:qux', ctx, pipeline);
85
+ await consumer.start();
86
+
87
+ const producer = new DirectProducer('direct:qux', ctx);
88
+ const exchange = new Exchange();
89
+ await producer.send(exchange);
90
+
91
+ assert.equal(exchange.getProperty('answer'), 99);
92
+ });
93
+
94
+ it('send() throws an error when no consumer is registered', async () => {
95
+ const ctx = new CamelContext();
96
+ const producer = new DirectProducer('direct:missing', ctx);
97
+ const exchange = new Exchange();
98
+
99
+ await assert.rejects(
100
+ () => producer.send(exchange),
101
+ { message: 'No consumer registered for: direct:missing' }
102
+ );
103
+ });
104
+
105
+ it('send() throws CycleDetectedError when cycle detected', async () => {
106
+ const ctx = new CamelContext();
107
+
108
+ // A consumer whose pipeline calls back into the same URI — simulates a cycle
109
+ const cyclingPipeline = new Pipeline([
110
+ async (exchange) => {
111
+ // Directly invoke the producer again to create the cycle
112
+ const innerProducer = new DirectProducer('direct:cycle', ctx);
113
+ await innerProducer.send(exchange);
114
+ },
115
+ ]);
116
+
117
+ const consumer = new DirectConsumer('direct:cycle', ctx, cyclingPipeline);
118
+ await consumer.start();
119
+
120
+ const producer = new DirectProducer('direct:cycle', ctx);
121
+ const exchange = new Exchange();
122
+
123
+ // Pipeline catches errors into exchange.exception
124
+ await producer.send(exchange);
125
+ assert.ok(exchange.isFailed(), 'exchange should be failed');
126
+ assert.ok(exchange.exception instanceof CycleDetectedError,
127
+ `expected CycleDetectedError, got ${exchange.exception?.constructor?.name}`);
128
+ });
129
+
130
+ it('direct call stack is restored after send() completes', async () => {
131
+ const ctx = new CamelContext();
132
+ const pipeline = mutatingPipeline('done', true);
133
+ const consumer = new DirectConsumer('direct:restore', ctx, pipeline);
134
+ await consumer.start();
135
+
136
+ const producer = new DirectProducer('direct:restore', ctx);
137
+ const exchange = new Exchange();
138
+ await producer.send(exchange);
139
+
140
+ // After send completes, stack should be back to empty/original
141
+ const stack = exchange.getProperty('camel.directStack');
142
+ assert.deepEqual(stack, []);
143
+ });
144
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Exchange, Message, CamelContext, Component, Pipeline, CycleDetectedError } from '@alt-javascript/camel-lite-core';
4
+ import { DirectComponent, DirectEndpoint, DirectProducer, DirectConsumer } from '@alt-javascript/camel-lite-component-direct';
5
+
6
+ describe('cross-package import integration', () => {
7
+ it('Exchange imported from camel-lite-core constructs correctly', () => {
8
+ const ex = new Exchange();
9
+ assert.equal(ex.pattern, 'InOnly');
10
+ assert.ok(ex.in instanceof Message);
11
+ });
12
+
13
+ it('Message has a messageId', () => {
14
+ const msg = new Message();
15
+ assert.ok(typeof msg.messageId === 'string');
16
+ assert.ok(msg.messageId.length > 0);
17
+ });
18
+
19
+ it('DirectComponent is a subclass of Component', () => {
20
+ const dc = new DirectComponent();
21
+ assert.ok(dc instanceof Component);
22
+ });
23
+
24
+ it('DirectComponent.createEndpoint returns a DirectEndpoint', () => {
25
+ const dc = new DirectComponent();
26
+ const ctx = new CamelContext();
27
+ const ep = dc.createEndpoint('direct:foo', 'foo', {}, ctx);
28
+ assert.ok(ep instanceof DirectEndpoint);
29
+ });
30
+ });