@hahnpro/flow-sdk 2025.2.0-beta.1 → 2025.2.0-beta.3
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/package.json +4 -4
- package/src/{index.ts → index.d.ts} +0 -3
- package/src/index.js +18 -0
- package/src/lib/ContextManager.d.ts +40 -0
- package/src/lib/ContextManager.js +105 -0
- package/src/lib/FlowApplication.d.ts +85 -0
- package/src/lib/FlowApplication.js +528 -0
- package/src/lib/FlowElement.d.ts +67 -0
- package/src/lib/FlowElement.js +178 -0
- package/src/lib/FlowEvent.d.ts +25 -0
- package/src/lib/FlowEvent.js +72 -0
- package/src/lib/FlowLogger.d.ts +44 -0
- package/src/lib/FlowLogger.js +110 -0
- package/src/lib/FlowModule.d.ts +7 -0
- package/src/lib/FlowModule.js +14 -0
- package/src/lib/RpcClient.d.ts +13 -0
- package/src/lib/RpcClient.js +88 -0
- package/src/lib/TestModule.d.ts +2 -0
- package/src/lib/TestModule.js +27 -0
- package/src/lib/amqp.d.ts +14 -0
- package/src/lib/amqp.js +12 -0
- package/src/lib/extra-validators.d.ts +1 -0
- package/src/lib/extra-validators.js +53 -0
- package/src/lib/flow.interface.d.ts +48 -0
- package/src/lib/flow.interface.js +9 -0
- package/src/lib/{index.ts → index.d.ts} +0 -3
- package/src/lib/index.js +18 -0
- package/src/lib/nats.d.ts +12 -0
- package/src/lib/nats.js +115 -0
- package/src/lib/unit-decorators.d.ts +39 -0
- package/src/lib/unit-decorators.js +156 -0
- package/src/lib/unit-utils.d.ts +8 -0
- package/src/lib/unit-utils.js +144 -0
- package/src/lib/units.d.ts +31 -0
- package/src/lib/units.js +572 -0
- package/src/lib/utils.d.ts +51 -0
- package/src/lib/utils.js +178 -0
- package/jest.config.ts +0 -10
- package/project.json +0 -41
- package/src/lib/ContextManager.ts +0 -111
- package/src/lib/FlowApplication.ts +0 -659
- package/src/lib/FlowElement.ts +0 -220
- package/src/lib/FlowEvent.ts +0 -73
- package/src/lib/FlowLogger.ts +0 -131
- package/src/lib/FlowModule.ts +0 -18
- package/src/lib/RpcClient.ts +0 -99
- package/src/lib/TestModule.ts +0 -14
- package/src/lib/__pycache__/rpc_server.cpython-310.pyc +0 -0
- package/src/lib/amqp.ts +0 -32
- package/src/lib/extra-validators.ts +0 -62
- package/src/lib/flow.interface.ts +0 -56
- package/src/lib/nats.ts +0 -140
- package/src/lib/unit-decorators.ts +0 -156
- package/src/lib/unit-utils.ts +0 -163
- package/src/lib/units.ts +0 -587
- package/src/lib/utils.ts +0 -176
- package/test/context-manager-purpose.spec.ts +0 -248
- package/test/context-manager.spec.ts +0 -55
- package/test/context.spec.ts +0 -180
- package/test/event.spec.ts +0 -155
- package/test/extra-validators.spec.ts +0 -84
- package/test/flow-logger.spec.ts +0 -104
- package/test/flow.spec.ts +0 -508
- package/test/input-stream.decorator.spec.ts +0 -379
- package/test/long-rpc.test.py +0 -14
- package/test/long-running-rpc.spec.ts +0 -60
- package/test/message.spec.ts +0 -57
- package/test/mocks/logger.mock.ts +0 -7
- package/test/mocks/nats-connection.mock.ts +0 -135
- package/test/mocks/nats-prepare.reals-nats.ts +0 -15
- package/test/rpc.spec.ts +0 -198
- package/test/rpc.test.py +0 -45
- package/test/rx.spec.ts +0 -92
- package/test/unit-decorator.spec.ts +0 -57
- package/test/utils.spec.ts +0 -210
- package/test/validation.spec.ts +0 -174
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -22
- package/tsconfig.spec.json +0 -8
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import { setTimeout } from 'timers/promises';
|
|
2
|
-
|
|
3
|
-
import { jetstreamManager } from '@nats-io/jetstream';
|
|
4
|
-
import { connect } from '@nats-io/transport-node';
|
|
5
|
-
|
|
6
|
-
import { FlowApplication, FlowEvent, FlowFunction, FlowModule, FlowResource, FlowTask, InputStream } from '../src';
|
|
7
|
-
import { loggerMock } from './mocks/logger.mock';
|
|
8
|
-
import { NatsConnectionMock } from './mocks/nats-connection.mock';
|
|
9
|
-
|
|
10
|
-
describe('InputStreamDecorator', () => {
|
|
11
|
-
let flowApp: FlowApplication;
|
|
12
|
-
let natsConnectionMock: NatsConnectionMock;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
natsConnectionMock = new NatsConnectionMock();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
if (flowApp) {
|
|
20
|
-
await flowApp.destroy(0);
|
|
21
|
-
}
|
|
22
|
-
await natsConnectionMock.close();
|
|
23
|
-
jest.resetAllMocks();
|
|
24
|
-
jest.clearAllMocks();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('FLOW.ISD.1 should return input event data', async () => {
|
|
28
|
-
const testRes = new TestResource({ id: 'test' });
|
|
29
|
-
const result = await testRes.onDefaultRessource(new FlowEvent({ id: '1' }, { test1: 'data', test2: 'otherData' }));
|
|
30
|
-
|
|
31
|
-
const data = result.getData();
|
|
32
|
-
expect(data.test1).toBeDefined();
|
|
33
|
-
expect(data.test2).toBeDefined();
|
|
34
|
-
expect(data.hello).toBeDefined();
|
|
35
|
-
}, 60000);
|
|
36
|
-
|
|
37
|
-
test('FLOW.ISD.2 should not return input event data if stopPropagation is set', async () => {
|
|
38
|
-
const testRes = new TestResourceNoProp({ id: 'test' });
|
|
39
|
-
const result = await testRes.onDefaultNoProp(new FlowEvent({ id: '1' }, { test1: 'data' }));
|
|
40
|
-
|
|
41
|
-
const data = result.getData();
|
|
42
|
-
expect(data.test1).toBeUndefined();
|
|
43
|
-
expect(data.hello).toBeDefined();
|
|
44
|
-
}, 60000);
|
|
45
|
-
|
|
46
|
-
test('FLOW.ISD.3 should overwrite input event data', async () => {
|
|
47
|
-
const testRes = new TestResource({ id: 'test' });
|
|
48
|
-
const result = await testRes.onDefaultRessource(new FlowEvent({ id: '1' }, { test1: 'data', hello: 'otherData' }));
|
|
49
|
-
|
|
50
|
-
const data = result.getData();
|
|
51
|
-
|
|
52
|
-
expect(data.test1).toBeDefined();
|
|
53
|
-
expect(data.hello).toBeDefined();
|
|
54
|
-
expect(data.hello).toEqual('world');
|
|
55
|
-
}, 60000);
|
|
56
|
-
|
|
57
|
-
test('FLOW.ISD.4 should return input event data in a flow', (done) => {
|
|
58
|
-
const flow = {
|
|
59
|
-
elements: [
|
|
60
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
61
|
-
{ id: 'testResource', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
62
|
-
],
|
|
63
|
-
connections: [{ id: 'testConnection1', source: 'testTrigger', target: 'testResource' }],
|
|
64
|
-
};
|
|
65
|
-
flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock });
|
|
66
|
-
flowApp.subscribe('testResource.default', {
|
|
67
|
-
next: (event: FlowEvent) => {
|
|
68
|
-
const data = event.getData();
|
|
69
|
-
expect(data.test1).toBeDefined();
|
|
70
|
-
expect(data.test2).toBeDefined();
|
|
71
|
-
expect(data.hello).toBeDefined();
|
|
72
|
-
|
|
73
|
-
done();
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, { test1: 'data', test2: 'otherData' }));
|
|
77
|
-
}, 60000);
|
|
78
|
-
|
|
79
|
-
test('FLOW.ISD.5 should only log partial events', async () => {
|
|
80
|
-
const flow = {
|
|
81
|
-
context: {
|
|
82
|
-
deploymentId: 'test',
|
|
83
|
-
},
|
|
84
|
-
elements: [
|
|
85
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
86
|
-
{ id: 'testResource', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
87
|
-
],
|
|
88
|
-
connections: [{ id: 'testConnection1', source: 'testTrigger', target: 'testResource' }],
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const publish = jest.fn();
|
|
92
|
-
const amqpConnection: any = {
|
|
93
|
-
managedConnection: {
|
|
94
|
-
createChannel: () => ({
|
|
95
|
-
assertExchange: jest.fn(),
|
|
96
|
-
waitForConnect: jest.fn(),
|
|
97
|
-
consume: jest.fn(),
|
|
98
|
-
publish,
|
|
99
|
-
}),
|
|
100
|
-
close: async () => {},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
flowApp = new FlowApplication([TestModule], flow, loggerMock, amqpConnection, natsConnectionMock as any, true, true);
|
|
105
|
-
const spy = jest.spyOn(flowApp, 'publishNatsEventFlowlogs');
|
|
106
|
-
await flowApp.init();
|
|
107
|
-
expect(flowApp.natsConnection).toBeDefined();
|
|
108
|
-
|
|
109
|
-
const done = new Promise<void>((resolve, reject) => {
|
|
110
|
-
flowApp.subscribe('testResource.default', {
|
|
111
|
-
next: async (event: FlowEvent) => {
|
|
112
|
-
try {
|
|
113
|
-
await setTimeout(1000);
|
|
114
|
-
expect(spy.mock.calls[0][0].getData()).toEqual({ test1: 'data' });
|
|
115
|
-
expect(spy.mock.calls[1][0].getData()).toEqual({ hello: 'world' });
|
|
116
|
-
resolve();
|
|
117
|
-
} catch (error) {
|
|
118
|
-
reject(error);
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, { test1: 'data' }));
|
|
124
|
-
return done;
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('FLOW.ISD.6 stopPropagation should work in a mixed flow', (done) => {
|
|
128
|
-
const flow = {
|
|
129
|
-
elements: [
|
|
130
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
131
|
-
{ id: 'testTask1', module: 'test-module', functionFqn: 'test.task.task1' },
|
|
132
|
-
{ id: 'testTask2', module: 'test-module', functionFqn: 'test.task.task2' },
|
|
133
|
-
{ id: 'testRessource', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
134
|
-
],
|
|
135
|
-
connections: [
|
|
136
|
-
{ id: 'c1', source: 'testTrigger', target: 'testTask1' },
|
|
137
|
-
{ id: 'c2', source: 'testTask1', target: 'testTask2' },
|
|
138
|
-
{ id: 'c3', source: 'testTask2', target: 'testRessource' },
|
|
139
|
-
],
|
|
140
|
-
};
|
|
141
|
-
flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock });
|
|
142
|
-
flowApp.subscribe('testTask1.default', {
|
|
143
|
-
next: (event: FlowEvent) => {
|
|
144
|
-
expect(event.getData()).toEqual({ input: 'data', task1: 'test' });
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
flowApp.subscribe('testTask2.default', {
|
|
148
|
-
next: (event: FlowEvent) => {
|
|
149
|
-
expect(event.getData()).toEqual({ task2: 'test' });
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
flowApp.subscribe('testRessource.default', {
|
|
153
|
-
next: (event: FlowEvent) => {
|
|
154
|
-
expect(event.getData()).toEqual({ task2: 'test', hello: 'world' });
|
|
155
|
-
done();
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, { input: 'data' }));
|
|
159
|
-
}, 20000);
|
|
160
|
-
|
|
161
|
-
test('FLOW.ISD.7 stopPropagation should work in a mixed function', async () => {
|
|
162
|
-
const testRes = new TestResource({ id: 'test' });
|
|
163
|
-
|
|
164
|
-
let result = await testRes.onDefaultRessource(new FlowEvent({ id: '1' }, { baz: 42 }));
|
|
165
|
-
expect(result.getData()).toEqual({ baz: 42, hello: 'world' });
|
|
166
|
-
|
|
167
|
-
result = await testRes.onNotDefault(new FlowEvent({ id: '2' }, { baz: 42 }));
|
|
168
|
-
expect(result.getData()).toEqual({ foo: 'bar' });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test('FLOW.ISD.8 event propagation should work for parallel events', (done) => {
|
|
172
|
-
const task = new LongRunningTask({ id: 'test' });
|
|
173
|
-
|
|
174
|
-
task.onB(new FlowEvent({ id: '2' }, { input: 'b' })).then((r) => {
|
|
175
|
-
expect(r.getData()).toEqual({ input: 'b', output: 'b' });
|
|
176
|
-
done();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
task.onA(new FlowEvent({ id: '1' }, { input: 'a' })).then((r) => {
|
|
180
|
-
expect(r.getData()).toEqual({ input: 'a', output: 'a' });
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test('FLOW.ISD.9 arrow functions should work', async () => {
|
|
185
|
-
const task = new Arrow({ id: 'test' });
|
|
186
|
-
const result = await task.onDefault(new FlowEvent({ id: '2' }, { x: 23, y: 19 }));
|
|
187
|
-
expect(result.getData()).toEqual({ x: 23, y: 19, z: 42 });
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('FLOW.ISD.10 test single function with multiple streams with different configs', (done) => {
|
|
191
|
-
const flow = {
|
|
192
|
-
elements: [
|
|
193
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
194
|
-
{ id: 'testTask1', module: 'test-module', functionFqn: 'test.task.multi' },
|
|
195
|
-
],
|
|
196
|
-
connections: [
|
|
197
|
-
{ id: 'c1', source: 'testTrigger', target: 'testTask1', targetStream: 'default' },
|
|
198
|
-
{ id: 'c2', source: 'testTrigger', target: 'testTask1', targetStream: 'other' },
|
|
199
|
-
],
|
|
200
|
-
};
|
|
201
|
-
flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock });
|
|
202
|
-
|
|
203
|
-
let bothDone = false;
|
|
204
|
-
flowApp.subscribe('testTask1.default', {
|
|
205
|
-
next: (event: FlowEvent) => {
|
|
206
|
-
const data = event.getData();
|
|
207
|
-
expect(data.default).toEqual('test');
|
|
208
|
-
expect(data.input).toBeUndefined();
|
|
209
|
-
if (bothDone) {
|
|
210
|
-
done();
|
|
211
|
-
} else {
|
|
212
|
-
bothDone = true;
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
flowApp.subscribe('testTask1.other', {
|
|
217
|
-
next: (event: FlowEvent) => {
|
|
218
|
-
const data = event.getData();
|
|
219
|
-
expect(data.input).toEqual('data');
|
|
220
|
-
expect(data.other).toEqual('test');
|
|
221
|
-
if (bothDone) {
|
|
222
|
-
done();
|
|
223
|
-
} else {
|
|
224
|
-
bothDone = true;
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, { input: 'data' }));
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('FLOW.ISD.11 stateful function should update state correctly', async () => {
|
|
233
|
-
const stateful = new Stateful({ id: 'stateful' });
|
|
234
|
-
|
|
235
|
-
expect(stateful.prop).toEqual(0);
|
|
236
|
-
await stateful.onDefault(new FlowEvent({ id: 'test1' }, {}));
|
|
237
|
-
expect(stateful.prop).toEqual(1);
|
|
238
|
-
await stateful.onDefault(new FlowEvent({ id: 'test2' }, {}));
|
|
239
|
-
expect(stateful.prop).toEqual(2);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('FLOW.ISD.12 stateful function should work in flow', (done) => {
|
|
243
|
-
const flow = {
|
|
244
|
-
elements: [
|
|
245
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
246
|
-
{ id: 'stateful', module: 'test-module', functionFqn: 'test.task.stateful' },
|
|
247
|
-
],
|
|
248
|
-
connections: [{ id: 'c1', source: 'testTrigger', target: 'stateful' }],
|
|
249
|
-
};
|
|
250
|
-
flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock });
|
|
251
|
-
|
|
252
|
-
let count = 0;
|
|
253
|
-
flowApp.subscribe('stateful.default', {
|
|
254
|
-
next: (event: FlowEvent) => {
|
|
255
|
-
const data = event.getData();
|
|
256
|
-
expect(data.prop).toEqual(count++);
|
|
257
|
-
|
|
258
|
-
if (count === 3) {
|
|
259
|
-
done();
|
|
260
|
-
}
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}));
|
|
265
|
-
|
|
266
|
-
setTimeout(100).then(() => {
|
|
267
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}));
|
|
268
|
-
// no waiting...
|
|
269
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}));
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test('FLOW.ISD.13 deprecated should still work as expected', () => {
|
|
274
|
-
const deprecated = new Deprecated({ id: 'dep' });
|
|
275
|
-
const event1 = deprecated.onDefault(new FlowEvent({ id: 'test' }, { test: 42 }));
|
|
276
|
-
|
|
277
|
-
expect(event1.getData()).toEqual({ hello: 'world' });
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
@FlowFunction('test.resource.TestResource')
|
|
282
|
-
class TestResource extends FlowResource {
|
|
283
|
-
@InputStream('default')
|
|
284
|
-
public async onDefaultRessource(event: FlowEvent) {
|
|
285
|
-
return this.emitEvent({ hello: 'world' }, event);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@InputStream('notdefault', { stopPropagation: true })
|
|
289
|
-
public async onNotDefault(event: FlowEvent) {
|
|
290
|
-
return this.emitEvent({ foo: 'bar' }, event);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
@FlowFunction('test.resource.LongRunningTask')
|
|
295
|
-
class LongRunningTask extends FlowTask {
|
|
296
|
-
@InputStream('a')
|
|
297
|
-
public async onA(event: FlowEvent) {
|
|
298
|
-
return this.emitEvent({ output: 'a' }, event);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
@InputStream('b')
|
|
302
|
-
public async onB(event: FlowEvent) {
|
|
303
|
-
await setTimeout(100);
|
|
304
|
-
return this.emitEvent({ output: 'b' }, event);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
@FlowFunction('test.task.arrow')
|
|
309
|
-
class Arrow extends FlowTask {
|
|
310
|
-
@InputStream()
|
|
311
|
-
public async onDefault(event: FlowEvent) {
|
|
312
|
-
const data = event.getData();
|
|
313
|
-
return this.calcSomething(data.x, data.y, event);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
private calcSomething = (x = 0, y = 0, event) => this.emitEvent({ z: x + y }, event);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
@FlowFunction('test.resource.TestResourceNoProp')
|
|
320
|
-
class TestResourceNoProp extends FlowResource {
|
|
321
|
-
@InputStream('default', { stopPropagation: true })
|
|
322
|
-
public async onDefaultNoProp(event: FlowEvent) {
|
|
323
|
-
return this.emitEvent({ hello: 'world' }, event);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
@FlowFunction('test.task.task1')
|
|
328
|
-
class TestTask1 extends FlowTask {
|
|
329
|
-
@InputStream('default')
|
|
330
|
-
public async onDefaultTask1(event: FlowEvent) {
|
|
331
|
-
return this.emitEvent({ task1: 'test' }, event);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
@FlowFunction('test.task.task2')
|
|
336
|
-
class TestTask2 extends FlowTask {
|
|
337
|
-
@InputStream('default', { stopPropagation: true })
|
|
338
|
-
public async onDefaultTask2(event: FlowEvent) {
|
|
339
|
-
return this.emitEvent({ task2: 'test' }, event);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
@FlowFunction('test.task.multi')
|
|
344
|
-
class MultiStream extends FlowTask {
|
|
345
|
-
@InputStream('default', { stopPropagation: true })
|
|
346
|
-
public async onDefault(event: FlowEvent) {
|
|
347
|
-
return this.emitEvent({ default: 'test' }, event, 'default');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
@InputStream('other')
|
|
351
|
-
public async onOther(event: FlowEvent) {
|
|
352
|
-
return this.emitEvent({ other: 'test' }, event, 'other');
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
@FlowFunction('test.task.stateful')
|
|
357
|
-
class Stateful extends FlowTask {
|
|
358
|
-
public prop = 0;
|
|
359
|
-
|
|
360
|
-
@InputStream('default')
|
|
361
|
-
public async onDefault(event: FlowEvent) {
|
|
362
|
-
await setTimeout(200);
|
|
363
|
-
return this.emitEvent({ prop: this.prop++ }, event);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
@FlowFunction('test.task.use-deprecated')
|
|
368
|
-
class Deprecated extends FlowTask {
|
|
369
|
-
@InputStream()
|
|
370
|
-
public onDefault(event: FlowEvent) {
|
|
371
|
-
return this.emitOutput({ hello: 'world' });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
@FlowModule({
|
|
376
|
-
name: 'test-module',
|
|
377
|
-
declarations: [TestResource, TestResourceNoProp, TestTask1, TestTask2, MultiStream, Stateful],
|
|
378
|
-
})
|
|
379
|
-
class TestModule {}
|
package/test/long-rpc.test.py
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
2
|
-
import { setTimeout } from 'timers/promises';
|
|
3
|
-
|
|
4
|
-
import type { PythonShell } from 'python-shell';
|
|
5
|
-
|
|
6
|
-
import { FlowApplication, FlowEvent, FlowFunction, FlowModule, FlowResource, InputStream } from '../src';
|
|
7
|
-
|
|
8
|
-
describe('Flow RPC long running task', () => {
|
|
9
|
-
let flowApp: FlowApplication;
|
|
10
|
-
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
const flow = {
|
|
13
|
-
elements: [{ id: 'testResource', module: 'test-module', functionFqn: 'test.resource.TestResource' }],
|
|
14
|
-
connections: [{ id: 'testConnection1', source: 'testTrigger', sourceStream: 'a', target: 'testResource', targetStream: 'a' }],
|
|
15
|
-
context: {
|
|
16
|
-
flowId: 'testFlow',
|
|
17
|
-
deploymentId: 'testDeployment',
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
flowApp = new FlowApplication([TestModule], flow, { amqpConfig: {}, skipApi: true });
|
|
21
|
-
await setTimeout(2000);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('FLOW.LRPC.1 rpc long running task', (done) => {
|
|
25
|
-
flowApp.subscribe('testResource.a', {
|
|
26
|
-
next: async (event: FlowEvent) => {
|
|
27
|
-
expect(event.getData()).toBeDefined();
|
|
28
|
-
await flowApp.destroy();
|
|
29
|
-
done();
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
flowApp.emit(new FlowEvent({ id: 'testTrigger' }, {}, 'a'));
|
|
34
|
-
}, 250000);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
@FlowFunction('test.resource.TestResource')
|
|
38
|
-
class TestResource extends FlowResource {
|
|
39
|
-
private shell: PythonShell;
|
|
40
|
-
|
|
41
|
-
constructor(context) {
|
|
42
|
-
super(context);
|
|
43
|
-
this.shell = this.runPyRpcScript(join(__dirname, 'long-rpc.test.py'));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
onDestroy = () => this.shell.kill();
|
|
47
|
-
|
|
48
|
-
@InputStream('a')
|
|
49
|
-
public async onA(event) {
|
|
50
|
-
this.callRpcFunction('testA')
|
|
51
|
-
.then((res: any) => this.emitEvent(res, event, 'a'))
|
|
52
|
-
.catch((err) => this.logger.error(err));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@FlowModule({
|
|
57
|
-
name: 'test-module',
|
|
58
|
-
declarations: [TestResource],
|
|
59
|
-
})
|
|
60
|
-
class TestModule {}
|
package/test/message.spec.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { CloudEvent } from 'cloudevents';
|
|
2
|
-
|
|
3
|
-
import { delay, FlowApplication, FlowEvent, FlowFunction, FlowModule, FlowResource, InputStream } from '../src';
|
|
4
|
-
import { natsFlowsPrefixFlowDeployment, publishNatsEvent } from '../src/lib/nats';
|
|
5
|
-
import { loggerMock } from './mocks/logger.mock';
|
|
6
|
-
import { natsPrepareForRealNats } from './mocks/nats-prepare.reals-nats';
|
|
7
|
-
|
|
8
|
-
describe('Flow SDK', () => {
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
jest.resetAllMocks();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test('FLOW.SDK.1 publish message', async () => {
|
|
14
|
-
const flow = {
|
|
15
|
-
elements: [
|
|
16
|
-
{ id: 'testTrigger', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
17
|
-
{ id: 'testResource', module: 'test-module', functionFqn: 'test.resource.TestResource' },
|
|
18
|
-
],
|
|
19
|
-
connections: [{ id: 'testConnection1', source: 'testTrigger', target: 'testResource' }],
|
|
20
|
-
context: {
|
|
21
|
-
flowId: 'testFlow',
|
|
22
|
-
deploymentId: 'testDeployment',
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const nc = await natsPrepareForRealNats(loggerMock);
|
|
27
|
-
const flowApp = new FlowApplication([TestModule], flow, { skipApi: true, logger: loggerMock, explicitInit: true, natsConnection: nc });
|
|
28
|
-
await flowApp.init();
|
|
29
|
-
|
|
30
|
-
const spy = jest.spyOn((flowApp as any)?.elements['testResource'], 'onMessage');
|
|
31
|
-
const event = new CloudEvent({
|
|
32
|
-
source: 'flowstudio/deployments',
|
|
33
|
-
type: natsFlowsPrefixFlowDeployment,
|
|
34
|
-
subject: 'testDeployment.message',
|
|
35
|
-
data: { elementId: 'testResource', test: 123 },
|
|
36
|
-
});
|
|
37
|
-
await publishNatsEvent(loggerMock, nc, event);
|
|
38
|
-
await delay(5);
|
|
39
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
40
|
-
await flowApp.destroy();
|
|
41
|
-
await nc.close();
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
@FlowFunction('test.resource.TestResource')
|
|
46
|
-
class TestResource extends FlowResource {
|
|
47
|
-
@InputStream()
|
|
48
|
-
public default(event: FlowEvent) {}
|
|
49
|
-
|
|
50
|
-
public onMessage = (msg) => {};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@FlowModule({
|
|
54
|
-
name: 'test-module',
|
|
55
|
-
declarations: [TestResource],
|
|
56
|
-
})
|
|
57
|
-
class TestModule {}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock implementation of a NATS connection for testing purposes.
|
|
3
|
-
*
|
|
4
|
-
* @details
|
|
5
|
-
* - only mocks the publish and subscribe methods.
|
|
6
|
-
* - supports basic subject matching
|
|
7
|
-
* - supports wildcard subscriptions with > are supported (e.g., 'foo.>')
|
|
8
|
-
* - single wildcard subscriptions with * are not supported (e.g., 'foo.*')
|
|
9
|
-
*/
|
|
10
|
-
export class NatsConnectionMock {
|
|
11
|
-
private stopAllSubscriptions = false;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Sends a request to a subject and waits for a response.
|
|
15
|
-
* Returns a response based on messages available in the queues.
|
|
16
|
-
*
|
|
17
|
-
* @param subject - The subject to send the request to.
|
|
18
|
-
* @param payload - The request payload (optional).
|
|
19
|
-
* @param options - Request options (optional).
|
|
20
|
-
* @returns A promise that resolves to a mock message response or rejects if no message is available.
|
|
21
|
-
*/
|
|
22
|
-
public async request(subject: string, payload?: any, options?: any): Promise<any> {
|
|
23
|
-
if (subject.startsWith('$JS.API.')) {
|
|
24
|
-
return Promise.resolve({
|
|
25
|
-
data: new TextEncoder().encode('{}'),
|
|
26
|
-
json: () => JSON.parse('{}'),
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
30
|
-
const message = this.getMessageFromQueue(subject);
|
|
31
|
-
if (message) {
|
|
32
|
-
return Promise.resolve({
|
|
33
|
-
data: message,
|
|
34
|
-
subject: subject,
|
|
35
|
-
reply: undefined,
|
|
36
|
-
headers: undefined,
|
|
37
|
-
json: () => JSON.parse(message),
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
return Promise.reject(new Error(`No message available for subject: ${subject}`));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Stores message queues for each subject.
|
|
46
|
-
* @private
|
|
47
|
-
*/
|
|
48
|
-
private queues: { [subject: string]: any[] } = {};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Publishes a message to a specific subject.
|
|
52
|
-
* If the subject does not exist, it initializes a new queue for it.
|
|
53
|
-
*
|
|
54
|
-
* @param subject - The subject to publish the message to.
|
|
55
|
-
* @param payload - The message payload to publish (optional).
|
|
56
|
-
*/
|
|
57
|
-
public publish(subject: string, payload?: any) {
|
|
58
|
-
if (!this.queues[subject]) {
|
|
59
|
-
this.queues[subject] = [];
|
|
60
|
-
}
|
|
61
|
-
this.queues[subject].push(JSON.stringify(payload));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Closes the connection
|
|
66
|
-
*/
|
|
67
|
-
public async close(): Promise<void> {
|
|
68
|
-
this.stopAllSubscriptions = true;
|
|
69
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public async drain(): Promise<void> {
|
|
73
|
-
await this.close();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Checks if the connection is closed.
|
|
78
|
-
* @returns `false` as the mock connection is always open.
|
|
79
|
-
*/
|
|
80
|
-
public isClosed() {
|
|
81
|
-
return this.stopAllSubscriptions;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private getMessageFromQueue(subject: string, queues = this.queues) {
|
|
85
|
-
if (!queues[subject]) {
|
|
86
|
-
queues[subject] = [];
|
|
87
|
-
}
|
|
88
|
-
if (subject.endsWith('.>')) {
|
|
89
|
-
for (const key in queues) {
|
|
90
|
-
if (key.startsWith(subject.slice(0, -1)) && queues[key].length > 0) {
|
|
91
|
-
return queues[key].shift();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} else if (queues[subject].length > 0) {
|
|
95
|
-
return queues[subject].shift();
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Subscribes to a specific subject and returns an async iterator for messages.
|
|
102
|
-
* Supports wildcard subjects ending with `.>` to subscribe to multiple subjects.
|
|
103
|
-
*
|
|
104
|
-
* @param subject - The subject to subscribe to.
|
|
105
|
-
* @param queues - The message queues to use (default is the internal queues).
|
|
106
|
-
* @param getter
|
|
107
|
-
* @param stopAllSubscriptions
|
|
108
|
-
* @returns An object containing the `unsubscribe` method and an async iterator for messages.
|
|
109
|
-
*/
|
|
110
|
-
public subscribe(
|
|
111
|
-
subject: string,
|
|
112
|
-
queues = this.queues,
|
|
113
|
-
getter = this.getMessageFromQueue,
|
|
114
|
-
stopAllSubscriptions = this.stopAllSubscriptions,
|
|
115
|
-
) {
|
|
116
|
-
let activeSub = true;
|
|
117
|
-
return {
|
|
118
|
-
unsubscribe: () => {
|
|
119
|
-
activeSub = false;
|
|
120
|
-
},
|
|
121
|
-
async *[Symbol.asyncIterator]() {
|
|
122
|
-
while (activeSub && !stopAllSubscriptions) {
|
|
123
|
-
const nextMsg = getter(subject, queues);
|
|
124
|
-
if (nextMsg) {
|
|
125
|
-
yield {
|
|
126
|
-
json: () => JSON.parse(nextMsg),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jetstreamManager } from '@nats-io/jetstream';
|
|
2
|
-
import { NatsConnection } from '@nats-io/nats-core';
|
|
3
|
-
import { connect } from '@nats-io/transport-node';
|
|
4
|
-
|
|
5
|
-
import { Logger } from '../../src';
|
|
6
|
-
|
|
7
|
-
export async function natsPrepareForRealNats(logger: Logger): Promise<NatsConnection> {
|
|
8
|
-
const natsConnection = await connect();
|
|
9
|
-
const jsm = await jetstreamManager(natsConnection);
|
|
10
|
-
const flowStream = await jsm.streams.info('flows').catch((err: any) => logger.error(err));
|
|
11
|
-
if (!flowStream) {
|
|
12
|
-
await jsm.streams.add({ name: 'flows', subjects: ['fs.>'] }).catch((err: any) => logger.error(err));
|
|
13
|
-
}
|
|
14
|
-
return natsConnection;
|
|
15
|
-
}
|