@alt-javascript/camel-lite-component-nosql 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 +63 -0
- package/package.json +43 -0
- package/src/NosqlComponent.js +142 -0
- package/src/NosqlConsumer.js +39 -0
- package/src/NosqlEndpoint.js +43 -0
- package/src/NosqlProducer.js +88 -0
- package/src/index.js +8 -0
- package/test/nosql.test.js +446 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
|
+
|
|
3
|
+
## What
|
|
4
|
+
|
|
5
|
+
NoSQL collection operations via [`@alt-javascript/jsnosqlc`](https://www.npmjs.com/package/@alt-javascript/jsnosqlc). Supports insert, find, update, delete, and count against named collections.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install camel-lite-component-nosql @alt-javascript/jsnosqlc
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## URI Syntax
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
nosql:collectionName[?url=jsnosqlc:memory:&operation=insert]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Parameter | Default | Description |
|
|
20
|
+
|--------------|------------------|-------------|
|
|
21
|
+
| `url` | *(required)* | jsnosqlc connection URL (e.g. `jsnosqlc:memory:` or a file path). |
|
|
22
|
+
| `operation` | `insert` | Collection operation: `insert`, `find`, `update`, `delete`, or `count`. |
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { CamelContext } from 'camel-lite-core';
|
|
28
|
+
import { NosqlComponent } from 'camel-lite-component-nosql';
|
|
29
|
+
|
|
30
|
+
const context = new CamelContext();
|
|
31
|
+
context.addComponent('nosql', new NosqlComponent());
|
|
32
|
+
|
|
33
|
+
context.addRoutes({
|
|
34
|
+
configure(ctx) {
|
|
35
|
+
// Insert exchange body as a document
|
|
36
|
+
ctx.from('direct:storeEvent')
|
|
37
|
+
.to('nosql:events?url=jsnosqlc:memory:&operation=insert');
|
|
38
|
+
|
|
39
|
+
// Find documents — exchange body used as the query filter
|
|
40
|
+
ctx.from('direct:queryEvents')
|
|
41
|
+
.to('nosql:events?url=jsnosqlc:memory:&operation=find');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await context.start();
|
|
46
|
+
|
|
47
|
+
const template = context.createProducerTemplate();
|
|
48
|
+
|
|
49
|
+
// Insert
|
|
50
|
+
await template.sendBody('direct:storeEvent', { type: 'login', userId: 7 });
|
|
51
|
+
|
|
52
|
+
// Find — pass a filter object as body
|
|
53
|
+
const exchange = await template.send('direct:queryEvents', ex => {
|
|
54
|
+
ex.in.body = { type: 'login' };
|
|
55
|
+
});
|
|
56
|
+
console.log('Found:', exchange.in.body);
|
|
57
|
+
|
|
58
|
+
await context.stop();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## See Also
|
|
62
|
+
|
|
63
|
+
[camel-lite — root README](../../README.md)
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alt-javascript/camel-lite-component-nosql",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@alt-javascript/jsnosqlc-core": "^1.1.1",
|
|
10
|
+
"@alt-javascript/jsnosqlc-memory": "^1.1.1",
|
|
11
|
+
"@alt-javascript/logger": "^3.0.7",
|
|
12
|
+
"@alt-javascript/config": "^3.0.7",
|
|
13
|
+
"@alt-javascript/common": "^3.0.7",
|
|
14
|
+
"@alt-javascript/camel-lite-core": "1.0.2"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/alt-javascript/camel-lite"
|
|
22
|
+
},
|
|
23
|
+
"author": "Craig Parravicini",
|
|
24
|
+
"contributors": [
|
|
25
|
+
"Claude (Anthropic)",
|
|
26
|
+
"Apache Camel — design inspiration and pattern source"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"alt-javascript",
|
|
30
|
+
"camel",
|
|
31
|
+
"camel-lite",
|
|
32
|
+
"eai",
|
|
33
|
+
"eip",
|
|
34
|
+
"integration",
|
|
35
|
+
"nosql",
|
|
36
|
+
"mongodb",
|
|
37
|
+
"component"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"registry": "https://registry.npmjs.org/",
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Component } from '@alt-javascript/camel-lite-core';
|
|
2
|
+
import { LoggerFactory } from '@alt-javascript/logger';
|
|
3
|
+
import { NosqlEndpoint } from './NosqlEndpoint.js';
|
|
4
|
+
|
|
5
|
+
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/NosqlComponent');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* NosqlComponent — executes NoSQL operations as a pipeline step via
|
|
9
|
+
* the @alt-javascript/jsnosqlc abstraction layer.
|
|
10
|
+
*
|
|
11
|
+
* Datasource resolution (three-step chain, evaluated at send() time):
|
|
12
|
+
*
|
|
13
|
+
* 1. Component-internal map — setDatasource(name, factory) takes priority.
|
|
14
|
+
*
|
|
15
|
+
* 2. Context bean by name — context.getBean(name) is checked when the
|
|
16
|
+
* component map has no match. The bean may be a ClientDataSource
|
|
17
|
+
* (has .getClient()) or a jsnosqlc Client directly.
|
|
18
|
+
*
|
|
19
|
+
* 3. Auto-select — when no explicit name is given (or named lookup
|
|
20
|
+
* found nothing) and context.getBeans() has exactly one entry, that bean
|
|
21
|
+
* is used automatically.
|
|
22
|
+
*
|
|
23
|
+
* URI formats:
|
|
24
|
+
* nosql:collection?datasource=myDsBean&operation=insert
|
|
25
|
+
* nosql:collection?operation=insert ← auto-select when 1 bean in ctx
|
|
26
|
+
*
|
|
27
|
+
* Operations (jsnosqlc Collection API):
|
|
28
|
+
* get — exchange.in.body = key (string) → exchange.in.body = doc | null
|
|
29
|
+
* store — exchange.in.body = { key, doc } → exchange.in.body = undefined
|
|
30
|
+
* delete — exchange.in.body = key (string) → exchange.in.body = undefined
|
|
31
|
+
* insert — exchange.in.body = doc (object) → exchange.in.body = assigned key
|
|
32
|
+
* update — exchange.in.body = { key, patch } → exchange.in.body = undefined
|
|
33
|
+
* find — exchange.in.body = Filter (built AST) → exchange.in.body = doc array
|
|
34
|
+
*/
|
|
35
|
+
class NosqlComponent extends Component {
|
|
36
|
+
#datasources = new Map(); // name → factory function
|
|
37
|
+
#clients = new Map(); // name → cached Client (from component-map factories)
|
|
38
|
+
#endpoints = new Map();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a named datasource factory on the component.
|
|
42
|
+
* Factory is () => ClientDataSource | Client (sync or async result).
|
|
43
|
+
* @param {string} name
|
|
44
|
+
* @param {function} factory
|
|
45
|
+
*/
|
|
46
|
+
setDatasource(name, factory) {
|
|
47
|
+
this.#datasources.set(name, factory);
|
|
48
|
+
log.info(`NosqlComponent: datasource '${name}' registered on component`);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve and return a jsnosqlc Client using the three-step chain.
|
|
54
|
+
* Clients from the component-internal map are cached.
|
|
55
|
+
* @param {string|null} name
|
|
56
|
+
* @param {CamelContext|null} context
|
|
57
|
+
* @returns {Promise<import('@alt-javascript/jsnosqlc-core').Client>}
|
|
58
|
+
*/
|
|
59
|
+
async getClient(name, context = null) {
|
|
60
|
+
// Step 1: component-internal map
|
|
61
|
+
if (name && this.#datasources.has(name)) {
|
|
62
|
+
if (this.#clients.has(name)) {
|
|
63
|
+
return this.#clients.get(name);
|
|
64
|
+
}
|
|
65
|
+
log.debug(`NosqlComponent: datasource '${name}' resolved from component map`);
|
|
66
|
+
const factoryResult = this.#datasources.get(name)();
|
|
67
|
+
const client = typeof factoryResult.getClient === 'function'
|
|
68
|
+
? await factoryResult.getClient()
|
|
69
|
+
: await factoryResult;
|
|
70
|
+
this.#clients.set(name, client);
|
|
71
|
+
log.info(`NosqlComponent: client acquired for datasource '${name}'`);
|
|
72
|
+
return client;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (context) {
|
|
76
|
+
// Step 2: context bean by name
|
|
77
|
+
if (name) {
|
|
78
|
+
const bean = context.getBean(name);
|
|
79
|
+
if (bean != null) {
|
|
80
|
+
const cacheKey = `__ctx__:${name}`;
|
|
81
|
+
if (this.#clients.has(cacheKey)) {
|
|
82
|
+
return this.#clients.get(cacheKey);
|
|
83
|
+
}
|
|
84
|
+
log.debug(`NosqlComponent: datasource '${name}' resolved from context bean`);
|
|
85
|
+
const client = typeof bean.getClient === 'function' ? await bean.getClient() : bean;
|
|
86
|
+
this.#clients.set(cacheKey, client);
|
|
87
|
+
return client;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Step 3: single-bean auto-select
|
|
92
|
+
const allBeans = context.getBeans();
|
|
93
|
+
if (allBeans.length === 1) {
|
|
94
|
+
const [beanName, bean] = allBeans[0];
|
|
95
|
+
const cacheKey = `__ctx__:${beanName}`;
|
|
96
|
+
if (this.#clients.has(cacheKey)) {
|
|
97
|
+
return this.#clients.get(cacheKey);
|
|
98
|
+
}
|
|
99
|
+
log.debug(`NosqlComponent: datasource auto-selected (single bean in context: '${beanName}')`);
|
|
100
|
+
const client = typeof bean.getClient === 'function' ? await bean.getClient() : bean;
|
|
101
|
+
this.#clients.set(cacheKey, client);
|
|
102
|
+
return client;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
`NosqlComponent: cannot resolve datasource '${name ?? '(none)'}'. ` +
|
|
108
|
+
`Register via component.setDatasource(), context.registerBean(), ` +
|
|
109
|
+
`or ensure exactly one bean is registered in context.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Close all cached clients from the component-internal map.
|
|
115
|
+
*/
|
|
116
|
+
async close() {
|
|
117
|
+
for (const [name, client] of this.#clients) {
|
|
118
|
+
await client.close().catch(() => {});
|
|
119
|
+
log.info(`NosqlComponent: client closed for datasource '${name}'`);
|
|
120
|
+
}
|
|
121
|
+
this.#clients.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
createEndpoint(uri, remaining, parameters, context) {
|
|
125
|
+
if (this.#endpoints.has(uri)) {
|
|
126
|
+
return this.#endpoints.get(uri);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const collection = remaining.replace(/^\/+/, '') || 'default';
|
|
130
|
+
const datasource = parameters.get('datasource') ?? null; // null = auto-select
|
|
131
|
+
const operation = parameters.get('operation') ?? 'get';
|
|
132
|
+
|
|
133
|
+
log.info(`NosqlComponent creating endpoint: collection=${collection}, datasource=${datasource ?? '(auto)'}, op=${operation}`);
|
|
134
|
+
|
|
135
|
+
const endpoint = new NosqlEndpoint(uri, collection, datasource, operation, context, this);
|
|
136
|
+
this.#endpoints.set(uri, endpoint);
|
|
137
|
+
return endpoint;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { NosqlComponent };
|
|
142
|
+
export default NosqlComponent;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Consumer } from '@alt-javascript/camel-lite-core';
|
|
2
|
+
import { LoggerFactory } from '@alt-javascript/logger';
|
|
3
|
+
|
|
4
|
+
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/NosqlConsumer');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NosqlConsumer — stub implementation.
|
|
8
|
+
* nosql: is primarily producer-oriented.
|
|
9
|
+
* Change-stream / poll consumer is deferred to a future milestone.
|
|
10
|
+
*/
|
|
11
|
+
class NosqlConsumer extends Consumer {
|
|
12
|
+
#endpoint;
|
|
13
|
+
#pipeline;
|
|
14
|
+
|
|
15
|
+
constructor(endpoint, pipeline) {
|
|
16
|
+
super();
|
|
17
|
+
this.#endpoint = endpoint;
|
|
18
|
+
this.#pipeline = pipeline;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async start() {
|
|
22
|
+
const { uri, context } = this.#endpoint;
|
|
23
|
+
context.registerConsumer(uri, this);
|
|
24
|
+
log.info(`NosqlConsumer started: ${uri}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async stop() {
|
|
28
|
+
const { uri, context } = this.#endpoint;
|
|
29
|
+
context.registerConsumer(uri, null);
|
|
30
|
+
log.info(`NosqlConsumer stopped: ${uri}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async process(exchange) {
|
|
34
|
+
return this.#pipeline.run(exchange);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { NosqlConsumer };
|
|
39
|
+
export default NosqlConsumer;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Endpoint } from '@alt-javascript/camel-lite-core';
|
|
2
|
+
import { NosqlProducer } from './NosqlProducer.js';
|
|
3
|
+
import { NosqlConsumer } from './NosqlConsumer.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NosqlEndpoint holds the parsed URI state for a nosql: endpoint.
|
|
7
|
+
*/
|
|
8
|
+
class NosqlEndpoint extends Endpoint {
|
|
9
|
+
#uri;
|
|
10
|
+
#collection;
|
|
11
|
+
#datasource;
|
|
12
|
+
#operation;
|
|
13
|
+
#context;
|
|
14
|
+
#component;
|
|
15
|
+
|
|
16
|
+
constructor(uri, collection, datasource, operation, context, component) {
|
|
17
|
+
super();
|
|
18
|
+
this.#uri = uri;
|
|
19
|
+
this.#collection = collection;
|
|
20
|
+
this.#datasource = datasource;
|
|
21
|
+
this.#operation = operation;
|
|
22
|
+
this.#context = context;
|
|
23
|
+
this.#component = component;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get uri() { return this.#uri; }
|
|
27
|
+
get collection() { return this.#collection; }
|
|
28
|
+
get datasource() { return this.#datasource; }
|
|
29
|
+
get operation() { return this.#operation; }
|
|
30
|
+
get context() { return this.#context; }
|
|
31
|
+
get component() { return this.#component; }
|
|
32
|
+
|
|
33
|
+
createProducer() {
|
|
34
|
+
return new NosqlProducer(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createConsumer(pipeline) {
|
|
38
|
+
return new NosqlConsumer(this, pipeline);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { NosqlEndpoint };
|
|
43
|
+
export default NosqlEndpoint;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Producer } from '@alt-javascript/camel-lite-core';
|
|
2
|
+
import { LoggerFactory } from '@alt-javascript/logger';
|
|
3
|
+
|
|
4
|
+
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/NosqlProducer');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NosqlProducer — executes a jsnosqlc Collection operation as a pipeline step.
|
|
8
|
+
*
|
|
9
|
+
* Operation dispatch (endpoint.operation):
|
|
10
|
+
*
|
|
11
|
+
* get — exchange.in.body = key (string)
|
|
12
|
+
* → exchange.in.body = document | null
|
|
13
|
+
*
|
|
14
|
+
* store — exchange.in.body = { key: string, doc: object }
|
|
15
|
+
* → exchange.in.body = undefined (fire-and-forget upsert)
|
|
16
|
+
*
|
|
17
|
+
* delete — exchange.in.body = key (string)
|
|
18
|
+
* → exchange.in.body = undefined
|
|
19
|
+
*
|
|
20
|
+
* insert — exchange.in.body = document (object)
|
|
21
|
+
* → exchange.in.body = assigned key (string)
|
|
22
|
+
*
|
|
23
|
+
* update — exchange.in.body = { key: string, patch: object }
|
|
24
|
+
* → exchange.in.body = undefined (patch merge)
|
|
25
|
+
*
|
|
26
|
+
* find — exchange.in.body = Filter (built AST from Filter.where()...build())
|
|
27
|
+
* → exchange.in.body = document[] (from cursor.getDocuments())
|
|
28
|
+
*/
|
|
29
|
+
class NosqlProducer extends Producer {
|
|
30
|
+
#endpoint;
|
|
31
|
+
|
|
32
|
+
constructor(endpoint) {
|
|
33
|
+
super();
|
|
34
|
+
this.#endpoint = endpoint;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async send(exchange) {
|
|
38
|
+
const { collection: collectionName, datasource, operation, component, context } = this.#endpoint;
|
|
39
|
+
|
|
40
|
+
log.debug(`NosqlProducer: ${operation} on ${datasource ?? '(auto)'}/${collectionName}`);
|
|
41
|
+
|
|
42
|
+
const client = await component.getClient(datasource, context);
|
|
43
|
+
const col = client.getCollection(collectionName);
|
|
44
|
+
const body = exchange.in.body;
|
|
45
|
+
|
|
46
|
+
switch (operation) {
|
|
47
|
+
case 'get': {
|
|
48
|
+
exchange.in.body = await col.get(body);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'store': {
|
|
52
|
+
const { key, doc } = body ?? {};
|
|
53
|
+
await col.store(key, doc);
|
|
54
|
+
exchange.in.body = undefined;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'delete': {
|
|
58
|
+
await col.delete(body);
|
|
59
|
+
exchange.in.body = undefined;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'insert': {
|
|
63
|
+
exchange.in.body = await col.insert(body);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'update': {
|
|
67
|
+
const { key, patch } = body ?? {};
|
|
68
|
+
await col.update(key, patch);
|
|
69
|
+
exchange.in.body = undefined;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'find': {
|
|
73
|
+
// body should be a built Filter AST from Filter.where()...build()
|
|
74
|
+
// null/undefined body means find all — pass null to driver
|
|
75
|
+
const cursor = await col.find(body ?? null);
|
|
76
|
+
exchange.in.body = cursor.getDocuments();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
throw new Error(`NosqlProducer: unknown operation '${operation}'`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
log.debug(`NosqlProducer: ${operation} complete on ${datasource}/${collectionName}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { NosqlProducer };
|
|
88
|
+
export default NosqlProducer;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NosqlComponent } from './NosqlComponent.js';
|
|
2
|
+
export { NosqlEndpoint } from './NosqlEndpoint.js';
|
|
3
|
+
export { NosqlProducer } from './NosqlProducer.js';
|
|
4
|
+
export { NosqlConsumer } from './NosqlConsumer.js';
|
|
5
|
+
|
|
6
|
+
// Re-export Filter and ClientDataSource from jsnosqlc-core for caller convenience.
|
|
7
|
+
// Callers need Filter to build expressions for find operations.
|
|
8
|
+
export { Filter, ClientDataSource } from '@alt-javascript/jsnosqlc-core';
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { describe, it, before } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { Exchange, CamelContext } from '@alt-javascript/camel-lite-core';
|
|
4
|
+
import { ClientDataSource } from '@alt-javascript/jsnosqlc-core';
|
|
5
|
+
// Self-registers the in-memory driver with DriverManager on import
|
|
6
|
+
import '@alt-javascript/jsnosqlc-memory';
|
|
7
|
+
import { NosqlComponent, Filter } from '@alt-javascript/camel-lite-component-nosql';
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
function makeExchange(body) {
|
|
14
|
+
const ex = new Exchange();
|
|
15
|
+
ex.in.body = body;
|
|
16
|
+
return ex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Build a NosqlComponent backed by a fresh in-memory store. */
|
|
20
|
+
function makeComponent(dsName = 'store') {
|
|
21
|
+
const comp = new NosqlComponent();
|
|
22
|
+
comp.setDatasource(dsName, () => new ClientDataSource({ url: 'jsnosqlc:memory:' }));
|
|
23
|
+
return comp;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeEndpoint(comp, ctx, collection, operation, dsName = 'store') {
|
|
27
|
+
const paramStr = `datasource=${dsName}&operation=${operation}`;
|
|
28
|
+
const uri = `nosql:${collection}?${paramStr}`;
|
|
29
|
+
const params = new URLSearchParams(paramStr);
|
|
30
|
+
return comp.createEndpoint(uri, collection, params, ctx);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// URI parsing
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
describe('NosqlComponent: URI parsing', () => {
|
|
38
|
+
it('parses collection, datasource, and operation from URI', () => {
|
|
39
|
+
const comp = makeComponent();
|
|
40
|
+
const ctx = new CamelContext();
|
|
41
|
+
const ep = makeEndpoint(comp, ctx, 'users', 'insert');
|
|
42
|
+
assert.equal(ep.collection, 'users');
|
|
43
|
+
assert.equal(ep.datasource, 'store');
|
|
44
|
+
assert.equal(ep.operation, 'insert');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('defaults operation to get when not specified', () => {
|
|
48
|
+
const comp = makeComponent();
|
|
49
|
+
const ctx = new CamelContext();
|
|
50
|
+
const uri = 'nosql:items?datasource=store';
|
|
51
|
+
const ep = comp.createEndpoint(uri, 'items', new URLSearchParams('datasource=store'), ctx);
|
|
52
|
+
assert.equal(ep.operation, 'get');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns cached endpoint on duplicate URI', () => {
|
|
56
|
+
const comp = makeComponent();
|
|
57
|
+
const ctx = new CamelContext();
|
|
58
|
+
const ep1 = makeEndpoint(comp, ctx, 'col', 'insert');
|
|
59
|
+
const ep2 = makeEndpoint(comp, ctx, 'col', 'insert');
|
|
60
|
+
assert.equal(ep1, ep2);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// insert → get round-trip
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
describe('NosqlProducer: insert and get', () => {
|
|
69
|
+
it('insert returns assigned key; get retrieves the document', async () => {
|
|
70
|
+
const comp = makeComponent();
|
|
71
|
+
const ctx = new CamelContext();
|
|
72
|
+
|
|
73
|
+
// insert
|
|
74
|
+
const insertEp = makeEndpoint(comp, ctx, 'products', 'insert');
|
|
75
|
+
const insertEx = makeExchange({ name: 'Widget', price: 9.99 });
|
|
76
|
+
await insertEp.createProducer().send(insertEx);
|
|
77
|
+
const key = insertEx.in.body;
|
|
78
|
+
assert.equal(typeof key, 'string', 'insert should return a string key');
|
|
79
|
+
|
|
80
|
+
// get
|
|
81
|
+
const getEp = makeEndpoint(comp, ctx, 'products', 'get');
|
|
82
|
+
const getEx = makeExchange(key);
|
|
83
|
+
await getEp.createProducer().send(getEx);
|
|
84
|
+
assert.equal(getEx.in.body.name, 'Widget');
|
|
85
|
+
assert.equal(getEx.in.body.price, 9.99);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('get returns null for a missing key', async () => {
|
|
89
|
+
const comp = makeComponent();
|
|
90
|
+
const ctx = new CamelContext();
|
|
91
|
+
const ep = makeEndpoint(comp, ctx, 'empty', 'get');
|
|
92
|
+
const ex = makeExchange('nonexistent-key');
|
|
93
|
+
await ep.createProducer().send(ex);
|
|
94
|
+
assert.equal(ex.in.body, null);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// store and get
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
describe('NosqlProducer: store and get', () => {
|
|
103
|
+
it('store upserts under caller-supplied key; get retrieves it', async () => {
|
|
104
|
+
const comp = makeComponent();
|
|
105
|
+
const ctx = new CamelContext();
|
|
106
|
+
|
|
107
|
+
const storeEp = makeEndpoint(comp, ctx, 'sessions', 'store');
|
|
108
|
+
const storeEx = makeExchange({ key: 'sess-abc', doc: { userId: 42, role: 'admin' } });
|
|
109
|
+
await storeEp.createProducer().send(storeEx);
|
|
110
|
+
assert.equal(storeEx.in.body, undefined);
|
|
111
|
+
|
|
112
|
+
const getEp = makeEndpoint(comp, ctx, 'sessions', 'get');
|
|
113
|
+
const getEx = makeExchange('sess-abc');
|
|
114
|
+
await getEp.createProducer().send(getEx);
|
|
115
|
+
assert.equal(getEx.in.body.userId, 42);
|
|
116
|
+
assert.equal(getEx.in.body.role, 'admin');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// delete
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
describe('NosqlProducer: delete', () => {
|
|
125
|
+
it('delete removes document; subsequent get returns null', async () => {
|
|
126
|
+
const comp = makeComponent();
|
|
127
|
+
const ctx = new CamelContext();
|
|
128
|
+
|
|
129
|
+
// store first
|
|
130
|
+
await makeEndpoint(comp, ctx, 'cache', 'store').createProducer()
|
|
131
|
+
.send(makeExchange({ key: 'temp', doc: { x: 1 } }));
|
|
132
|
+
|
|
133
|
+
// delete
|
|
134
|
+
const delEp = makeEndpoint(comp, ctx, 'cache', 'delete');
|
|
135
|
+
const delEx = makeExchange('temp');
|
|
136
|
+
await delEp.createProducer().send(delEx);
|
|
137
|
+
assert.equal(delEx.in.body, undefined);
|
|
138
|
+
|
|
139
|
+
// get → null
|
|
140
|
+
const getEx = makeExchange('temp');
|
|
141
|
+
await makeEndpoint(comp, ctx, 'cache', 'get').createProducer().send(getEx);
|
|
142
|
+
assert.equal(getEx.in.body, null);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// update
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
describe('NosqlProducer: update', () => {
|
|
151
|
+
it('update patches document fields; others preserved', async () => {
|
|
152
|
+
const comp = makeComponent();
|
|
153
|
+
const ctx = new CamelContext();
|
|
154
|
+
|
|
155
|
+
// insert a document
|
|
156
|
+
const insertEx = makeExchange({ name: 'Alice', age: 30, role: 'user' });
|
|
157
|
+
await makeEndpoint(comp, ctx, 'users', 'insert').createProducer().send(insertEx);
|
|
158
|
+
const key = insertEx.in.body;
|
|
159
|
+
|
|
160
|
+
// update — patch age and role, name should be preserved
|
|
161
|
+
const updateEx = makeExchange({ key, patch: { age: 31, role: 'admin' } });
|
|
162
|
+
await makeEndpoint(comp, ctx, 'users', 'update').createProducer().send(updateEx);
|
|
163
|
+
assert.equal(updateEx.in.body, undefined);
|
|
164
|
+
|
|
165
|
+
// verify patch applied and name preserved
|
|
166
|
+
const getEx = makeExchange(key);
|
|
167
|
+
await makeEndpoint(comp, ctx, 'users', 'get').createProducer().send(getEx);
|
|
168
|
+
assert.equal(getEx.in.body.name, 'Alice');
|
|
169
|
+
assert.equal(getEx.in.body.age, 31);
|
|
170
|
+
assert.equal(getEx.in.body.role, 'admin');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// find
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
describe('NosqlProducer: find', () => {
|
|
179
|
+
it('find with Filter returns matching documents as array', async () => {
|
|
180
|
+
const comp = makeComponent();
|
|
181
|
+
const ctx = new CamelContext();
|
|
182
|
+
|
|
183
|
+
// insert 3 documents
|
|
184
|
+
const insertEp = makeEndpoint(comp, ctx, 'items', 'insert');
|
|
185
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Widget', price: 9.99 }));
|
|
186
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Gadget', price: 24.99 }));
|
|
187
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Donut', price: 1.99 }));
|
|
188
|
+
|
|
189
|
+
// find where price < 10
|
|
190
|
+
const filter = Filter.where('price').lt(10).build();
|
|
191
|
+
const findEp = makeEndpoint(comp, ctx, 'items', 'find');
|
|
192
|
+
const findEx = makeExchange(filter);
|
|
193
|
+
await findEp.createProducer().send(findEx);
|
|
194
|
+
|
|
195
|
+
assert.ok(Array.isArray(findEx.in.body), 'result should be an array');
|
|
196
|
+
assert.equal(findEx.in.body.length, 2);
|
|
197
|
+
const names = findEx.in.body.map(d => d.name).sort();
|
|
198
|
+
assert.deepEqual(names, ['Donut', 'Widget']);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('find with eq filter returns exact matches', async () => {
|
|
202
|
+
const comp = makeComponent();
|
|
203
|
+
const ctx = new CamelContext();
|
|
204
|
+
|
|
205
|
+
const insertEp = makeEndpoint(comp, ctx, 'users', 'insert');
|
|
206
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Alice', role: 'admin' }));
|
|
207
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Bob', role: 'user' }));
|
|
208
|
+
await insertEp.createProducer().send(makeExchange({ name: 'Carol', role: 'admin' }));
|
|
209
|
+
|
|
210
|
+
const filter = Filter.where('role').eq('admin').build();
|
|
211
|
+
const findEx = makeExchange(filter);
|
|
212
|
+
await makeEndpoint(comp, ctx, 'users', 'find').createProducer().send(findEx);
|
|
213
|
+
|
|
214
|
+
assert.equal(findEx.in.body.length, 2);
|
|
215
|
+
assert.ok(findEx.in.body.every(d => d.role === 'admin'));
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Multiple named datasources
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
describe('NosqlComponent: multiple datasources', () => {
|
|
224
|
+
it('routes operations to the correct named datasource', async () => {
|
|
225
|
+
const comp = new NosqlComponent();
|
|
226
|
+
comp.setDatasource('ds1', () => new ClientDataSource({ url: 'jsnosqlc:memory:' }));
|
|
227
|
+
comp.setDatasource('ds2', () => new ClientDataSource({ url: 'jsnosqlc:memory:' }));
|
|
228
|
+
|
|
229
|
+
const ctx = new CamelContext();
|
|
230
|
+
|
|
231
|
+
const ep1 = comp.createEndpoint('nosql:col?datasource=ds1&operation=insert', 'col', new URLSearchParams('datasource=ds1&operation=insert'), ctx);
|
|
232
|
+
const ep2 = comp.createEndpoint('nosql:col?datasource=ds2&operation=insert', 'col', new URLSearchParams('datasource=ds2&operation=insert'), ctx);
|
|
233
|
+
|
|
234
|
+
const ex1 = makeExchange({ from: 'ds1' });
|
|
235
|
+
const ex2 = makeExchange({ from: 'ds2' });
|
|
236
|
+
|
|
237
|
+
await ep1.createProducer().send(ex1);
|
|
238
|
+
await ep2.createProducer().send(ex2);
|
|
239
|
+
|
|
240
|
+
// Keys are different strings — just check both were created
|
|
241
|
+
assert.equal(typeof ex1.in.body, 'string');
|
|
242
|
+
assert.equal(typeof ex2.in.body, 'string');
|
|
243
|
+
assert.notEqual(ex1.in.body, ex2.in.body);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Error cases
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
describe('NosqlComponent: error cases', () => {
|
|
252
|
+
it('throws on unknown operation', async () => {
|
|
253
|
+
const comp = makeComponent();
|
|
254
|
+
const ctx = new CamelContext();
|
|
255
|
+
const ep = comp.createEndpoint('nosql:col?datasource=store&operation=truncate', 'col', new URLSearchParams('datasource=store&operation=truncate'), ctx);
|
|
256
|
+
|
|
257
|
+
await assert.rejects(
|
|
258
|
+
() => ep.createProducer().send(makeExchange(null)),
|
|
259
|
+
/unknown operation 'truncate'/i
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('throws when datasource is not registered', async () => {
|
|
264
|
+
const comp = new NosqlComponent(); // no datasources registered
|
|
265
|
+
const ctx = new CamelContext();
|
|
266
|
+
const ep = makeEndpoint(comp, ctx, 'col', 'insert', 'ghost');
|
|
267
|
+
|
|
268
|
+
await assert.rejects(
|
|
269
|
+
() => ep.createProducer().send(makeExchange({ x: 1 })),
|
|
270
|
+
/cannot resolve datasource 'ghost'/i
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// NosqlComponent.close() releases clients
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
describe('NosqlComponent: close', () => {
|
|
280
|
+
it('close() closes all cached clients', async () => {
|
|
281
|
+
const comp = makeComponent();
|
|
282
|
+
const ctx = new CamelContext();
|
|
283
|
+
|
|
284
|
+
// Trigger client creation
|
|
285
|
+
const ep = makeEndpoint(comp, ctx, 'col', 'insert');
|
|
286
|
+
await ep.createProducer().send(makeExchange({ x: 1 }));
|
|
287
|
+
|
|
288
|
+
// close
|
|
289
|
+
await comp.close();
|
|
290
|
+
|
|
291
|
+
// Subsequent calls re-create client from factory (not throw)
|
|
292
|
+
// — just verify close() does not throw
|
|
293
|
+
// No assertion beyond no-throw is needed here
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Integration tests (conditional on NOSQL_URL)
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
const NOSQL_URL = process.env.NOSQL_URL;
|
|
302
|
+
|
|
303
|
+
if (NOSQL_URL) {
|
|
304
|
+
describe('NoSQL integration (live backend via NOSQL_URL)', () => {
|
|
305
|
+
it('round-trips insert → get → delete via live driver', async () => {
|
|
306
|
+
const comp = new NosqlComponent();
|
|
307
|
+
comp.setDatasource('live', () => new ClientDataSource({ url: NOSQL_URL }));
|
|
308
|
+
const ctx = new CamelContext();
|
|
309
|
+
|
|
310
|
+
const collName = `camel_lite_test`;
|
|
311
|
+
const insertEp = makeEndpoint(comp, ctx, collName, 'insert', 'live');
|
|
312
|
+
const getEp = makeEndpoint(comp, ctx, collName, 'get', 'live');
|
|
313
|
+
const deleteEp = makeEndpoint(comp, ctx, collName, 'delete', 'live');
|
|
314
|
+
|
|
315
|
+
const insertEx = makeExchange({ testField: 'integration', ts: Date.now() });
|
|
316
|
+
await insertEp.createProducer().send(insertEx);
|
|
317
|
+
const key = insertEx.in.body;
|
|
318
|
+
assert.ok(key, 'should have a key after insert');
|
|
319
|
+
|
|
320
|
+
const getEx = makeExchange(key);
|
|
321
|
+
await getEp.createProducer().send(getEx);
|
|
322
|
+
assert.equal(getEx.in.body.testField, 'integration');
|
|
323
|
+
|
|
324
|
+
const delEx = makeExchange(key);
|
|
325
|
+
await deleteEp.createProducer().send(delEx);
|
|
326
|
+
|
|
327
|
+
await comp.close();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
describe('NoSQL integration (skipped — set NOSQL_URL=jsnosqlc:mongodb://... to enable)', () => {
|
|
332
|
+
it('skipped', () => { /* no-op */ });
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Context-aware datasource resolution (three-step chain)
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
describe('NosqlComponent: context bean resolution', () => {
|
|
341
|
+
function makeDs() {
|
|
342
|
+
return new ClientDataSource({ url: 'jsnosqlc:memory:' });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function seedCollection(ds, collName, docs) {
|
|
346
|
+
const client = await ds.getClient();
|
|
347
|
+
const col = client.getCollection(collName);
|
|
348
|
+
for (const doc of docs) await col.insert(doc);
|
|
349
|
+
// Note: we intentionally don't close the client here — the component will cache it.
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
it('step 2: resolves datasource from context.getBean(name) when not in component map', async () => {
|
|
353
|
+
const ds = makeDs();
|
|
354
|
+
const comp = new NosqlComponent(); // no setDatasource()
|
|
355
|
+
const ctx = new CamelContext();
|
|
356
|
+
ctx.registerBean('myStore', ds);
|
|
357
|
+
|
|
358
|
+
// Seed data through the component so it uses the cached client
|
|
359
|
+
const insertParams = new URLSearchParams('datasource=myStore&operation=insert');
|
|
360
|
+
const insertEp = comp.createEndpoint('nosql:things?datasource=myStore&operation=insert', 'things', insertParams, ctx);
|
|
361
|
+
await insertEp.createProducer().send(makeExchange({ x: 42 }));
|
|
362
|
+
|
|
363
|
+
const params = new URLSearchParams('datasource=myStore&operation=find');
|
|
364
|
+
const ep = comp.createEndpoint('nosql:things?datasource=myStore&operation=find', 'things', params, ctx);
|
|
365
|
+
|
|
366
|
+
const ex = makeExchange(null);
|
|
367
|
+
await ep.createProducer().send(ex);
|
|
368
|
+
|
|
369
|
+
assert.ok(Array.isArray(ex.in.body));
|
|
370
|
+
assert.equal(ex.in.body.length, 1);
|
|
371
|
+
assert.equal(ex.in.body[0].x, 42);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('step 3: auto-selects single context bean when no datasource given', async () => {
|
|
375
|
+
const ds = makeDs();
|
|
376
|
+
const comp = new NosqlComponent();
|
|
377
|
+
const ctx = new CamelContext();
|
|
378
|
+
ctx.registerBean('onlyStore', ds); // exactly one bean
|
|
379
|
+
|
|
380
|
+
// Seed via component (auto-select)
|
|
381
|
+
const insertParams = new URLSearchParams('operation=insert');
|
|
382
|
+
const insertEp = comp.createEndpoint('nosql:items?operation=insert', 'items', insertParams, ctx);
|
|
383
|
+
await insertEp.createProducer().send(makeExchange({ name: 'auto' }));
|
|
384
|
+
|
|
385
|
+
const params = new URLSearchParams('operation=find');
|
|
386
|
+
const ep = comp.createEndpoint('nosql:items?operation=find', 'items', params, ctx);
|
|
387
|
+
|
|
388
|
+
const ex = makeExchange(null);
|
|
389
|
+
await ep.createProducer().send(ex);
|
|
390
|
+
|
|
391
|
+
assert.equal(ex.in.body.length, 1);
|
|
392
|
+
assert.equal(ex.in.body[0].name, 'auto');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('step 1 takes priority over context bean when component map has same name', async () => {
|
|
396
|
+
const dsInMap = makeDs();
|
|
397
|
+
const dsInContext = makeDs();
|
|
398
|
+
|
|
399
|
+
const comp = new NosqlComponent();
|
|
400
|
+
comp.setDatasource('store', () => dsInMap);
|
|
401
|
+
|
|
402
|
+
const ctx = new CamelContext();
|
|
403
|
+
ctx.registerBean('store', dsInContext);
|
|
404
|
+
|
|
405
|
+
// Insert via component map path
|
|
406
|
+
const insertParams = new URLSearchParams('datasource=store&operation=insert');
|
|
407
|
+
const insertEp = comp.createEndpoint('nosql:col?datasource=store&operation=insert', 'col', insertParams, ctx);
|
|
408
|
+
await insertEp.createProducer().send(makeExchange({ from: 'map' }));
|
|
409
|
+
|
|
410
|
+
const params = new URLSearchParams('datasource=store&operation=find');
|
|
411
|
+
const ep = comp.createEndpoint('nosql:col?datasource=store&operation=find', 'col', params, ctx);
|
|
412
|
+
const ex = makeExchange(null);
|
|
413
|
+
await ep.createProducer().send(ex);
|
|
414
|
+
|
|
415
|
+
// Should read from dsInMap (1 doc), not dsInContext (0 docs)
|
|
416
|
+
assert.equal(ex.in.body.length, 1);
|
|
417
|
+
assert.equal(ex.in.body[0].from, 'map');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('throws descriptive error when no resolution path succeeds', async () => {
|
|
421
|
+
const comp = new NosqlComponent();
|
|
422
|
+
const ctx = new CamelContext(); // no beans
|
|
423
|
+
const params = new URLSearchParams('datasource=ghost&operation=insert');
|
|
424
|
+
const ep = comp.createEndpoint('nosql:col?datasource=ghost&operation=insert', 'col', params, ctx);
|
|
425
|
+
|
|
426
|
+
await assert.rejects(
|
|
427
|
+
() => ep.createProducer().send(makeExchange({ x: 1 })),
|
|
428
|
+
/cannot resolve datasource 'ghost'/i
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('throws when no name given and context has multiple beans', async () => {
|
|
433
|
+
const comp = new NosqlComponent();
|
|
434
|
+
const ctx = new CamelContext();
|
|
435
|
+
ctx.registerBean('ds1', makeDs());
|
|
436
|
+
ctx.registerBean('ds2', makeDs()); // two beans — no auto-select
|
|
437
|
+
|
|
438
|
+
const params = new URLSearchParams('operation=insert');
|
|
439
|
+
const ep = comp.createEndpoint('nosql:col?operation=insert', 'col', params, ctx);
|
|
440
|
+
|
|
441
|
+
await assert.rejects(
|
|
442
|
+
() => ep.createProducer().send(makeExchange({ x: 1 })),
|
|
443
|
+
/cannot resolve datasource/i
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
});
|