@adaas/a-utils 0.1.13 → 0.1.15
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 +71 -8
- package/dist/index.d.mts +474 -9
- package/dist/index.d.ts +474 -9
- package/dist/index.js +563 -200
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +562 -202
- package/dist/index.mjs.map +1 -1
- package/examples/channel-examples.ts +518 -0
- package/package.json +23 -5
- package/src/index.ts +3 -1
- package/src/lib/A-Channel/A-Channel.component.ts +581 -32
- package/src/lib/A-Channel/A-Channel.constants.ts +76 -0
- package/src/lib/A-Channel/A-Channel.error.ts +42 -1
- package/src/lib/A-Channel/A-Channel.types.ts +11 -0
- package/src/lib/A-Channel/A-ChannelRequest.context.ts +91 -0
- package/src/lib/A-Channel/README.md +864 -0
- package/tests/A-Channel.test.ts +483 -5
package/tests/A-Channel.test.ts
CHANGED
|
@@ -1,16 +1,494 @@
|
|
|
1
1
|
import './jest.setup';
|
|
2
|
-
import { A_Context } from '@adaas/a-concept';
|
|
2
|
+
import { A_Context, A_Component, A_Feature, A_Inject } from '@adaas/a-concept';
|
|
3
|
+
import { A_Channel } from '@adaas/a-utils/lib/A-Channel/A-Channel.component';
|
|
4
|
+
import { A_ChannelFeatures } from '@adaas/a-utils/lib/A-Channel/A-Channel.constants';
|
|
5
|
+
import { A_ChannelRequest } from '@adaas/a-utils/lib/A-Channel/A-ChannelRequest.context';
|
|
6
|
+
import { A_ChannelError } from '@adaas/a-utils/lib/A-Channel/A-Channel.error';
|
|
3
7
|
|
|
4
8
|
jest.retryTimes(0);
|
|
5
9
|
|
|
6
10
|
describe('A-Channel tests', () => {
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
A_Context.reset();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('Basic Channel Creation and Properties', () => {
|
|
17
|
+
it('Should allow to create a channel', async () => {
|
|
18
|
+
const channel = new A_Channel();
|
|
19
|
+
A_Context.root.register(channel);
|
|
20
|
+
|
|
21
|
+
expect(channel).toBeInstanceOf(A_Channel);
|
|
22
|
+
expect(channel).toBeInstanceOf(A_Component);
|
|
23
|
+
expect(channel.processing).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('Should have correct initial state', async () => {
|
|
27
|
+
const channel = new A_Channel();
|
|
28
|
+
A_Context.root.register(channel);
|
|
29
|
+
|
|
30
|
+
expect(channel.processing).toBe(false);
|
|
31
|
+
expect(channel.initialize).toBeInstanceOf(Promise);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Should initialize only once', async () => {
|
|
35
|
+
const channel = new A_Channel();
|
|
36
|
+
A_Context.root.register(channel);
|
|
37
|
+
|
|
38
|
+
const init1 = channel.initialize;
|
|
39
|
+
const init2 = channel.initialize;
|
|
40
|
+
|
|
41
|
+
expect(init1).toBe(init2); // Same promise instance
|
|
42
|
+
await init1;
|
|
43
|
+
await init2;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Channel Connection Lifecycle', () => {
|
|
48
|
+
it('Should handle connection lifecycle', async () => {
|
|
49
|
+
const channel = new A_Channel();
|
|
50
|
+
A_Context.root.register(channel);
|
|
51
|
+
|
|
52
|
+
// Should connect successfully
|
|
53
|
+
await channel.connect();
|
|
54
|
+
expect(channel.initialize).toBeInstanceOf(Promise);
|
|
55
|
+
await channel.initialize;
|
|
56
|
+
|
|
57
|
+
// Should disconnect successfully
|
|
58
|
+
await channel.disconnect();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Should support custom connection logic', async () => {
|
|
62
|
+
const connectCalls: string[] = [];
|
|
63
|
+
const disconnectCalls: string[] = [];
|
|
64
|
+
|
|
65
|
+
class CustomChannel extends A_Channel {}
|
|
66
|
+
|
|
67
|
+
class ChannelConnector extends A_Component {
|
|
68
|
+
@A_Feature.Extend({ scope: [CustomChannel] })
|
|
69
|
+
async [A_ChannelFeatures.onConnect]() {
|
|
70
|
+
connectCalls.push('connected');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@A_Feature.Extend({ scope: [CustomChannel] })
|
|
74
|
+
async [A_ChannelFeatures.onDisconnect]() {
|
|
75
|
+
disconnectCalls.push('disconnected');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
A_Context.root.register(ChannelConnector);
|
|
80
|
+
|
|
81
|
+
const channel = new CustomChannel();
|
|
82
|
+
A_Context.root.register(channel);
|
|
83
|
+
|
|
84
|
+
await channel.connect();
|
|
85
|
+
expect(connectCalls).toEqual(['connected']);
|
|
86
|
+
|
|
87
|
+
await channel.disconnect();
|
|
88
|
+
expect(disconnectCalls).toEqual(['disconnected']);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Request Processing', () => {
|
|
93
|
+
it('Should handle basic request', async () => {
|
|
94
|
+
const channel = new A_Channel();
|
|
95
|
+
A_Context.root.register(channel);
|
|
96
|
+
|
|
97
|
+
const params = { action: 'test', data: 'hello' };
|
|
98
|
+
const context = await channel.request(params);
|
|
99
|
+
|
|
100
|
+
expect(context).toBeInstanceOf(A_ChannelRequest);
|
|
101
|
+
expect(context.params).toEqual(params);
|
|
102
|
+
expect(channel.processing).toBe(false); // Should reset after processing
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('Should handle request with custom logic', async () => {
|
|
106
|
+
const processingOrder: string[] = [];
|
|
107
|
+
|
|
108
|
+
class TestChannel extends A_Channel {}
|
|
109
|
+
|
|
110
|
+
class RequestProcessor extends A_Component {
|
|
111
|
+
@A_Feature.Extend({ scope: [TestChannel] })
|
|
112
|
+
async [A_ChannelFeatures.onBeforeRequest](
|
|
113
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
114
|
+
) {
|
|
115
|
+
processingOrder.push('before');
|
|
116
|
+
expect(context.params.action).toBe('test');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@A_Feature.Extend({ scope: [TestChannel] })
|
|
120
|
+
async [A_ChannelFeatures.onRequest](
|
|
121
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
122
|
+
) {
|
|
123
|
+
processingOrder.push('during');
|
|
124
|
+
// Simulate processing and setting result
|
|
125
|
+
(context as any)._result = { processed: true, original: context.params };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@A_Feature.Extend({ scope: [TestChannel] })
|
|
129
|
+
async [A_ChannelFeatures.onAfterRequest](
|
|
130
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
131
|
+
) {
|
|
132
|
+
processingOrder.push('after');
|
|
133
|
+
expect(context.data).toBeDefined();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
A_Context.root.register(RequestProcessor);
|
|
138
|
+
|
|
139
|
+
const channel = new TestChannel();
|
|
140
|
+
A_Context.root.register(channel);
|
|
141
|
+
|
|
142
|
+
const params = { action: 'test', data: 'hello' };
|
|
143
|
+
const context = await channel.request(params);
|
|
144
|
+
|
|
145
|
+
expect(processingOrder).toEqual(['before', 'during', 'after']);
|
|
146
|
+
expect(context.data).toEqual({ processed: true, original: params });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('Should handle request errors gracefully', async () => {
|
|
150
|
+
const errorCalls: string[] = [];
|
|
151
|
+
|
|
152
|
+
class ErrorChannel extends A_Channel {}
|
|
153
|
+
|
|
154
|
+
class ErrorProcessor extends A_Component {
|
|
155
|
+
@A_Feature.Extend({ scope: [ErrorChannel] })
|
|
156
|
+
async [A_ChannelFeatures.onRequest]() {
|
|
157
|
+
throw new Error('Request processing failed');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@A_Feature.Extend({ scope: [ErrorChannel] })
|
|
161
|
+
async [A_ChannelFeatures.onError](
|
|
162
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
163
|
+
) {
|
|
164
|
+
errorCalls.push('error-handled');
|
|
165
|
+
expect(context.failed).toBe(true);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
A_Context.root.register(ErrorProcessor);
|
|
170
|
+
|
|
171
|
+
const channel = new ErrorChannel();
|
|
172
|
+
A_Context.root.register(channel);
|
|
173
|
+
|
|
174
|
+
const params = { action: 'fail' };
|
|
175
|
+
const context = await channel.request(params);
|
|
176
|
+
|
|
177
|
+
expect(errorCalls).toEqual(['error-handled']);
|
|
178
|
+
expect(context.failed).toBe(true);
|
|
179
|
+
expect(channel.processing).toBe(false); // Should reset even on error
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('Should support typed requests', async () => {
|
|
183
|
+
interface TestParams {
|
|
184
|
+
userId: string;
|
|
185
|
+
action: 'create' | 'update' | 'delete';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface TestResult {
|
|
189
|
+
success: boolean;
|
|
190
|
+
userId: string;
|
|
191
|
+
timestamp: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class TypedChannel extends A_Channel {}
|
|
195
|
+
|
|
196
|
+
class TypedProcessor extends A_Component {
|
|
197
|
+
@A_Feature.Extend({ scope: [TypedChannel] })
|
|
198
|
+
async [A_ChannelFeatures.onRequest](
|
|
199
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<TestParams, TestResult>
|
|
200
|
+
) {
|
|
201
|
+
const { userId, action } = context.params;
|
|
202
|
+
(context as any)._result = {
|
|
203
|
+
success: true,
|
|
204
|
+
userId,
|
|
205
|
+
timestamp: new Date().toISOString()
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
A_Context.root.register(TypedProcessor);
|
|
211
|
+
|
|
212
|
+
const channel = new TypedChannel();
|
|
213
|
+
A_Context.root.register(channel);
|
|
214
|
+
|
|
215
|
+
const params: TestParams = { userId: '123', action: 'create' };
|
|
216
|
+
const context = await channel.request<TestParams, TestResult>(params);
|
|
217
|
+
|
|
218
|
+
expect(context.params.userId).toBe('123');
|
|
219
|
+
expect(context.params.action).toBe('create');
|
|
220
|
+
expect(context.data?.success).toBe(true);
|
|
221
|
+
expect(context.data?.userId).toBe('123');
|
|
222
|
+
expect(context.data?.timestamp).toBeDefined();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('Send (Fire-and-Forget) Operations', () => {
|
|
227
|
+
it('Should handle basic send operation', async () => {
|
|
228
|
+
const channel = new A_Channel();
|
|
229
|
+
A_Context.root.register(channel);
|
|
230
|
+
|
|
231
|
+
const message = { type: 'notification', content: 'Hello World' };
|
|
232
|
+
|
|
233
|
+
// Should not throw
|
|
234
|
+
await expect(channel.send(message)).resolves.not.toThrow();
|
|
235
|
+
expect(channel.processing).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('Should handle send with custom logic', async () => {
|
|
239
|
+
const sentMessages: any[] = [];
|
|
240
|
+
|
|
241
|
+
class SendChannel extends A_Channel {}
|
|
10
242
|
|
|
11
|
-
|
|
243
|
+
class SendProcessor extends A_Component {
|
|
244
|
+
@A_Feature.Extend({ scope: [SendChannel] })
|
|
245
|
+
async [A_ChannelFeatures.onSend](
|
|
246
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
247
|
+
) {
|
|
248
|
+
sentMessages.push(context.params);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
12
251
|
|
|
13
|
-
|
|
252
|
+
A_Context.root.register(SendProcessor);
|
|
253
|
+
|
|
254
|
+
const channel = new SendChannel();
|
|
255
|
+
A_Context.root.register(channel);
|
|
256
|
+
|
|
257
|
+
const message1 = { type: 'email', to: 'user@example.com' };
|
|
258
|
+
const message2 = { type: 'sms', to: '+1234567890' };
|
|
259
|
+
|
|
260
|
+
await channel.send(message1);
|
|
261
|
+
await channel.send(message2);
|
|
262
|
+
|
|
263
|
+
expect(sentMessages).toHaveLength(2);
|
|
264
|
+
expect(sentMessages[0]).toEqual(message1);
|
|
265
|
+
expect(sentMessages[1]).toEqual(message2);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('Should handle send errors gracefully', async () => {
|
|
269
|
+
const errorCalls: string[] = [];
|
|
270
|
+
|
|
271
|
+
class ErrorSendChannel extends A_Channel {}
|
|
272
|
+
|
|
273
|
+
class ErrorSendProcessor extends A_Component {
|
|
274
|
+
@A_Feature.Extend({ scope: [ErrorSendChannel] })
|
|
275
|
+
async [A_ChannelFeatures.onSend]() {
|
|
276
|
+
throw new Error('Send operation failed');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@A_Feature.Extend({ scope: [ErrorSendChannel] })
|
|
280
|
+
async [A_ChannelFeatures.onError](
|
|
281
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
282
|
+
) {
|
|
283
|
+
errorCalls.push('send-error-handled');
|
|
284
|
+
expect(context.failed).toBe(true);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
A_Context.root.register(ErrorSendProcessor);
|
|
289
|
+
|
|
290
|
+
const channel = new ErrorSendChannel();
|
|
291
|
+
A_Context.root.register(channel);
|
|
292
|
+
|
|
293
|
+
const message = { type: 'failing-message' };
|
|
294
|
+
|
|
295
|
+
// Should not throw, errors are handled internally
|
|
296
|
+
await expect(channel.send(message)).resolves.not.toThrow();
|
|
297
|
+
expect(errorCalls).toEqual(['send-error-handled']);
|
|
298
|
+
expect(channel.processing).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Error Handling', () => {
|
|
303
|
+
it('Should create proper channel errors', async () => {
|
|
304
|
+
const originalError = new Error('Original error message');
|
|
305
|
+
const channelError = new A_ChannelError(originalError);
|
|
306
|
+
|
|
307
|
+
expect(channelError).toBeInstanceOf(A_ChannelError);
|
|
308
|
+
expect(channelError.message).toContain('Original error message');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('Should handle multiple error types', async () => {
|
|
312
|
+
const errorTypes: string[] = [];
|
|
313
|
+
|
|
314
|
+
class MultiErrorChannel extends A_Channel {}
|
|
315
|
+
|
|
316
|
+
class MultiErrorProcessor extends A_Component {
|
|
317
|
+
@A_Feature.Extend({ scope: [MultiErrorChannel] })
|
|
318
|
+
async [A_ChannelFeatures.onRequest](
|
|
319
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
320
|
+
) {
|
|
321
|
+
const errorType = context.params.errorType;
|
|
322
|
+
switch (errorType) {
|
|
323
|
+
case 'network':
|
|
324
|
+
throw new Error('Network error');
|
|
325
|
+
case 'validation':
|
|
326
|
+
throw new Error('Validation error');
|
|
327
|
+
case 'timeout':
|
|
328
|
+
throw new Error('Timeout error');
|
|
329
|
+
default:
|
|
330
|
+
// No error
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@A_Feature.Extend({ scope: [MultiErrorChannel] })
|
|
335
|
+
async [A_ChannelFeatures.onError](
|
|
336
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
337
|
+
) {
|
|
338
|
+
errorTypes.push(context.params.errorType);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
A_Context.root.register(MultiErrorProcessor);
|
|
343
|
+
|
|
344
|
+
const channel = new MultiErrorChannel();
|
|
345
|
+
A_Context.root.register(channel);
|
|
346
|
+
|
|
347
|
+
await channel.request({ errorType: 'network' });
|
|
348
|
+
await channel.request({ errorType: 'validation' });
|
|
349
|
+
await channel.request({ errorType: 'timeout' });
|
|
350
|
+
await channel.request({ errorType: 'none' }); // Should not error
|
|
351
|
+
|
|
352
|
+
expect(errorTypes).toEqual(['network', 'validation', 'timeout']);
|
|
353
|
+
});
|
|
14
354
|
});
|
|
15
355
|
|
|
356
|
+
describe('Channel Integration and Extension', () => {
|
|
357
|
+
it('Should support multiple channel instances', async () => {
|
|
358
|
+
const channel1 = new A_Channel();
|
|
359
|
+
const channel2 = new A_Channel();
|
|
360
|
+
|
|
361
|
+
A_Context.root.register(channel1);
|
|
362
|
+
A_Context.root.register(channel2);
|
|
363
|
+
|
|
364
|
+
// Both should work independently
|
|
365
|
+
const result1 = await channel1.request({ id: 1 });
|
|
366
|
+
const result2 = await channel2.request({ id: 2 });
|
|
367
|
+
|
|
368
|
+
expect(result1.params.id).toBe(1);
|
|
369
|
+
expect(result2.params.id).toBe(2);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('Should support channel inheritance', async () => {
|
|
373
|
+
class HttpChannel extends A_Channel {
|
|
374
|
+
async makeHttpRequest(url: string, method: string = 'GET') {
|
|
375
|
+
return this.request({ url, method, timestamp: Date.now() });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
class WebSocketChannel extends A_Channel {
|
|
380
|
+
async sendMessage(message: string) {
|
|
381
|
+
return this.send({ message, type: 'websocket', timestamp: Date.now() });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const httpChannel = new HttpChannel();
|
|
386
|
+
const wsChannel = new WebSocketChannel();
|
|
387
|
+
|
|
388
|
+
A_Context.root.register(httpChannel);
|
|
389
|
+
A_Context.root.register(wsChannel);
|
|
390
|
+
|
|
391
|
+
const httpResult = await httpChannel.makeHttpRequest('https://api.example.com');
|
|
392
|
+
expect(httpResult.params.url).toBe('https://api.example.com');
|
|
393
|
+
expect(httpResult.params.method).toBe('GET');
|
|
394
|
+
|
|
395
|
+
await expect(wsChannel.sendMessage('Hello WebSocket')).resolves.not.toThrow();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('Should support feature extension with different channel types', async () => {
|
|
399
|
+
const httpCalls: string[] = [];
|
|
400
|
+
const wsCalls: string[] = [];
|
|
401
|
+
|
|
402
|
+
class HttpChannel extends A_Channel {}
|
|
403
|
+
class WebSocketChannel extends A_Channel {}
|
|
404
|
+
|
|
405
|
+
class HttpProcessor extends A_Component {
|
|
406
|
+
@A_Feature.Extend({ scope: [HttpChannel] })
|
|
407
|
+
async [A_ChannelFeatures.onRequest](
|
|
408
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
409
|
+
) {
|
|
410
|
+
httpCalls.push(`HTTP: ${context.params.method} ${context.params.url}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
class WebSocketProcessor extends A_Component {
|
|
415
|
+
@A_Feature.Extend({ scope: [WebSocketChannel] })
|
|
416
|
+
async [A_ChannelFeatures.onSend](
|
|
417
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
418
|
+
) {
|
|
419
|
+
wsCalls.push(`WS: ${context.params.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
A_Context.root.register(HttpProcessor);
|
|
424
|
+
A_Context.root.register(WebSocketProcessor);
|
|
425
|
+
|
|
426
|
+
const httpChannel = new HttpChannel();
|
|
427
|
+
const wsChannel = new WebSocketChannel();
|
|
428
|
+
|
|
429
|
+
A_Context.root.register(httpChannel);
|
|
430
|
+
A_Context.root.register(wsChannel);
|
|
431
|
+
|
|
432
|
+
await httpChannel.request({ method: 'POST', url: '/api/users' });
|
|
433
|
+
await wsChannel.send({ message: 'Hello World' });
|
|
434
|
+
|
|
435
|
+
expect(httpCalls).toEqual(['HTTP: POST /api/users']);
|
|
436
|
+
expect(wsCalls).toEqual(['WS: Hello World']);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('Performance and Concurrency', () => {
|
|
441
|
+
it('Should handle concurrent requests', async () => {
|
|
442
|
+
const processingOrder: number[] = [];
|
|
443
|
+
|
|
444
|
+
class ConcurrentChannel extends A_Channel {}
|
|
445
|
+
|
|
446
|
+
class ConcurrentProcessor extends A_Component {
|
|
447
|
+
@A_Feature.Extend({ scope: [ConcurrentChannel] })
|
|
448
|
+
async [A_ChannelFeatures.onRequest](
|
|
449
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
450
|
+
) {
|
|
451
|
+
const delay = context.params.delay || 0;
|
|
452
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
453
|
+
processingOrder.push(context.params.id);
|
|
454
|
+
(context as any)._result = { processed: context.params.id };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
A_Context.root.register(ConcurrentProcessor);
|
|
459
|
+
|
|
460
|
+
const channel = new ConcurrentChannel();
|
|
461
|
+
A_Context.root.register(channel);
|
|
462
|
+
|
|
463
|
+
// Start multiple requests concurrently
|
|
464
|
+
const requests = [
|
|
465
|
+
channel.request({ id: 1, delay: 100 }),
|
|
466
|
+
channel.request({ id: 2, delay: 50 }),
|
|
467
|
+
channel.request({ id: 3, delay: 25 })
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
const results = await Promise.all(requests);
|
|
471
|
+
|
|
472
|
+
// Results should be in completion order (3, 2, 1 due to delays)
|
|
473
|
+
expect(processingOrder).toEqual([3, 2, 1]);
|
|
474
|
+
expect(results[0].data?.processed).toBe(1);
|
|
475
|
+
expect(results[1].data?.processed).toBe(2);
|
|
476
|
+
expect(results[2].data?.processed).toBe(3);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('Should handle processing state correctly during concurrent operations', async () => {
|
|
480
|
+
const channel = new A_Channel();
|
|
481
|
+
A_Context.root.register(channel);
|
|
482
|
+
|
|
483
|
+
const request1Promise = channel.request({ id: 1 });
|
|
484
|
+
const request2Promise = channel.request({ id: 2 });
|
|
485
|
+
|
|
486
|
+
// Both requests should complete
|
|
487
|
+
const [result1, result2] = await Promise.all([request1Promise, request2Promise]);
|
|
488
|
+
|
|
489
|
+
expect(result1.params.id).toBe(1);
|
|
490
|
+
expect(result2.params.id).toBe(2);
|
|
491
|
+
expect(channel.processing).toBe(false); // Should be false after all complete
|
|
492
|
+
});
|
|
493
|
+
});
|
|
16
494
|
});
|