@routier/core 0.0.1-alpha.1
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/CHANGELOG.md +8 -0
- package/package.json +91 -0
- package/rspack.config.mjs +65 -0
- package/src/assertions/index.ts +37 -0
- package/src/codegen/SlotPath.ts +20 -0
- package/src/codegen/blocks.ts +578 -0
- package/src/codegen/handlers/CloneHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/CompareHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/DeserializeHandlerBuilder.ts +20 -0
- package/src/codegen/handlers/EnableChangeTrackingHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/EnrichmentHandlerBuilder.ts +28 -0
- package/src/codegen/handlers/FreezeHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/HashHandlerBuilder.ts +25 -0
- package/src/codegen/handlers/HashTypeHandlerBuilder.ts +11 -0
- package/src/codegen/handlers/IdSelectorHandlerBuilder.ts +10 -0
- package/src/codegen/handlers/MergeHandlerBuilder.ts +19 -0
- package/src/codegen/handlers/PrepareHandlerBuilder.ts +23 -0
- package/src/codegen/handlers/SerializeHandlerBuilder.ts +15 -0
- package/src/codegen/handlers/StripHandlerBuilder.ts +18 -0
- package/src/codegen/handlers/clone/CloneObjectHandler.ts +29 -0
- package/src/codegen/handlers/clone/CloneValueHandler.ts +33 -0
- package/src/codegen/handlers/compare/CompareObjectHandler.ts +15 -0
- package/src/codegen/handlers/compare/CompareValueHandler.ts +27 -0
- package/src/codegen/handlers/deserialize/DeserializeComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeDateHandler.ts +45 -0
- package/src/codegen/handlers/deserialize/DeserializeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeObjectHandler.ts +29 -0
- package/src/codegen/handlers/deserialize/DeserializeValueHandler.ts +33 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingObjectHandler.ts +20 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingPrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/enrichment/EnrichmentComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultFunctionHandler.ts +50 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultValueHandler.ts +18 -0
- package/src/codegen/handlers/enrichment/EnrichmentFunctionHandler.ts +38 -0
- package/src/codegen/handlers/enrichment/EnrichmentNullableObjectHandler.ts +44 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectHandler.ts +29 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectIdentityHandler.ts +16 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveHandler.ts +13 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveIdentityHandler.ts +21 -0
- package/src/codegen/handlers/freeze/FreezeObjectHandler.ts +20 -0
- package/src/codegen/handlers/freeze/FreezePrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashComputedValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashDateHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashFunctionHandler.ts +16 -0
- package/src/codegen/handlers/hash/HashIdentityHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashKeyHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashValueHandler.ts +26 -0
- package/src/codegen/handlers/hashType/HashTypeValueHandler.ts +20 -0
- package/src/codegen/handlers/idSelector/IdSelectorValueHandler.ts +28 -0
- package/src/codegen/handlers/index.ts +13 -0
- package/src/codegen/handlers/merge/MergeComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/merge/MergeDefaultFunctionHandler.ts +40 -0
- package/src/codegen/handlers/merge/MergeDefaultValueHandler.ts +32 -0
- package/src/codegen/handlers/merge/MergeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/merge/MergePrimitiveHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareFunctionHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareIdentityHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareKeyHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareObjectHandler.ts +38 -0
- package/src/codegen/handlers/prepare/PrepareValueHandler.ts +36 -0
- package/src/codegen/handlers/serialize/SerializeDateHandler.ts +45 -0
- package/src/codegen/handlers/serialize/SerializeObjectHandler.ts +28 -0
- package/src/codegen/handlers/serialize/SerializeValueHandler.ts +33 -0
- package/src/codegen/handlers/strip/StripIdentityHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripKeyHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripObjectHandler.ts +35 -0
- package/src/codegen/handlers/strip/StripValueHandler.ts +36 -0
- package/src/codegen/handlers/types.ts +119 -0
- package/src/codegen/index.ts +2 -0
- package/src/codegen/types.ts +2 -0
- package/src/codegen/utils.ts +74 -0
- package/src/collections/Changes.test.ts +337 -0
- package/src/collections/Changes.ts +177 -0
- package/src/collections/IdSet.ts +28 -0
- package/src/collections/MemoryDataCollection.test.ts +424 -0
- package/src/collections/MemoryDataCollection.ts +134 -0
- package/src/collections/SchemaCollection.ts +22 -0
- package/src/collections/TagCollection.test.ts +443 -0
- package/src/collections/TagCollection.ts +62 -0
- package/src/collections/index.ts +5 -0
- package/src/errors/SchemaError.ts +6 -0
- package/src/errors/index.ts +1 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.test.ts +913 -0
- package/src/expressions/parser.ts +661 -0
- package/src/expressions/types.ts +184 -0
- package/src/expressions/utils.test.ts +346 -0
- package/src/expressions/utils.ts +59 -0
- package/src/index.ts +12 -0
- package/src/performance/index.ts +26 -0
- package/src/pipeline/SyncronousQueue.test.ts +269 -0
- package/src/pipeline/SyncronousQueue.ts +30 -0
- package/src/pipeline/TrampolinePipeline.test.ts +374 -0
- package/src/pipeline/TrampolinePipeline.ts +437 -0
- package/src/pipeline/index.ts +2 -0
- package/src/plugins/EphemeralDataPlugin.ts +132 -0
- package/src/plugins/capabilities/DbPluginCapability.ts +111 -0
- package/src/plugins/capabilities/index.ts +2 -0
- package/src/plugins/capabilities/logging/DbPluginLoggingCapability.ts +261 -0
- package/src/plugins/capabilities/logging/index.ts +1 -0
- package/src/plugins/index.ts +6 -0
- package/src/plugins/query/Query.ts +67 -0
- package/src/plugins/query/QueryOptionsCollection.test.ts +86 -0
- package/src/plugins/query/QueryOptionsCollection.ts +154 -0
- package/src/plugins/query/index.ts +3 -0
- package/src/plugins/query/types.ts +46 -0
- package/src/plugins/replication/OptimisticReplicationDbPlugin.ts +202 -0
- package/src/plugins/replication/ReplicationDbPlugin.ts +137 -0
- package/src/plugins/replication/index.ts +3 -0
- package/src/plugins/replication/types.ts +5 -0
- package/src/plugins/translators/DataTranslator.ts +46 -0
- package/src/plugins/translators/JsonTranslator.test.ts +618 -0
- package/src/plugins/translators/JsonTranslator.ts +211 -0
- package/src/plugins/translators/SqlTranslator.ts +45 -0
- package/src/plugins/translators/index.ts +3 -0
- package/src/plugins/types.ts +114 -0
- package/src/results/Result.ts +91 -0
- package/src/results/index.ts +3 -0
- package/src/results/types.ts +17 -0
- package/src/results/utils.ts +15 -0
- package/src/schema/PropertyInfo.test.ts +479 -0
- package/src/schema/PropertyInfo.ts +374 -0
- package/src/schema/SchemaDefinition.ts +626 -0
- package/src/schema/builder.ts +18 -0
- package/src/schema/index.ts +7 -0
- package/src/schema/property/base/SchemaBase.ts +49 -0
- package/src/schema/property/base/index.ts +1 -0
- package/src/schema/property/modifiers/SchemaDefault.ts +29 -0
- package/src/schema/property/modifiers/SchemaDeserialize.ts +23 -0
- package/src/schema/property/modifiers/SchemaDistinct.ts +14 -0
- package/src/schema/property/modifiers/SchemaFrom.ts +54 -0
- package/src/schema/property/modifiers/SchemaIdentity.ts +13 -0
- package/src/schema/property/modifiers/SchemaIndex.ts +60 -0
- package/src/schema/property/modifiers/SchemaKey.ts +28 -0
- package/src/schema/property/modifiers/SchemaNullable.ts +18 -0
- package/src/schema/property/modifiers/SchemaOptional.ts +19 -0
- package/src/schema/property/modifiers/SchemaReadonly.ts +34 -0
- package/src/schema/property/modifiers/SchemaSerialize.ts +18 -0
- package/src/schema/property/modifiers/SchemaTracked.ts +14 -0
- package/src/schema/property/modifiers/index.ts +12 -0
- package/src/schema/property/types/SchemaArray.ts +50 -0
- package/src/schema/property/types/SchemaBoolean.ts +59 -0
- package/src/schema/property/types/SchemaDate.ts +58 -0
- package/src/schema/property/types/SchemaNumber.ts +69 -0
- package/src/schema/property/types/SchemaObject.ts +44 -0
- package/src/schema/property/types/SchemaString.ts +69 -0
- package/src/schema/property/types/index.ts +6 -0
- package/src/schema/table/SchemaComputed.ts +20 -0
- package/src/schema/table/SchemaFunction.ts +15 -0
- package/src/schema/table/index.ts +2 -0
- package/src/schema/types.ts +238 -0
- package/src/types/index.ts +5 -0
- package/src/utilities/arrays.test.ts +312 -0
- package/src/utilities/arrays.ts +12 -0
- package/src/utilities/dates.test.ts +388 -0
- package/src/utilities/dates.ts +11 -0
- package/src/utilities/dbPluginEventUtils.ts +44 -0
- package/src/utilities/index.ts +10 -0
- package/src/utilities/objects.ts +7 -0
- package/src/utilities/queryOptionsCollection.ts +16 -0
- package/src/utilities/replication.ts +23 -0
- package/src/utilities/runtime.ts +3 -0
- package/src/utilities/strings.ts +18 -0
- package/src/utilities/types.ts +1 -0
- package/src/utilities/uuid.ts +56 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { SyncronousQueue, SyncronousUnitOfWork } from './SyncronousQueue';
|
|
3
|
+
|
|
4
|
+
describe('SyncronousQueue', () => {
|
|
5
|
+
describe('enqueue', () => {
|
|
6
|
+
it('should add unit of work to queue and start processing', () => {
|
|
7
|
+
const queue = new SyncronousQueue();
|
|
8
|
+
const mockUnitOfWork = vi.fn((done: () => void) => {
|
|
9
|
+
done();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
queue.enqueue(mockUnitOfWork);
|
|
13
|
+
|
|
14
|
+
expect(mockUnitOfWork).toHaveBeenCalledTimes(1);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should process multiple units of work in order', () => {
|
|
18
|
+
const queue = new SyncronousQueue();
|
|
19
|
+
const executionOrder: number[] = [];
|
|
20
|
+
|
|
21
|
+
const unitOfWork1 = vi.fn((done: () => void) => {
|
|
22
|
+
executionOrder.push(1);
|
|
23
|
+
done();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const unitOfWork2 = vi.fn((done: () => void) => {
|
|
27
|
+
executionOrder.push(2);
|
|
28
|
+
done();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
queue.enqueue(unitOfWork1);
|
|
32
|
+
queue.enqueue(unitOfWork2);
|
|
33
|
+
|
|
34
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
35
|
+
expect(unitOfWork1).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(unitOfWork2).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not start processing if already processing', () => {
|
|
40
|
+
const queue = new SyncronousQueue();
|
|
41
|
+
let doneCalled = false;
|
|
42
|
+
|
|
43
|
+
const slowUnitOfWork = vi.fn((done: () => void) => {
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
doneCalled = true;
|
|
46
|
+
done();
|
|
47
|
+
}, 10);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const fastUnitOfWork = vi.fn((done: () => void) => {
|
|
51
|
+
done();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
queue.enqueue(slowUnitOfWork);
|
|
55
|
+
queue.enqueue(fastUnitOfWork);
|
|
56
|
+
|
|
57
|
+
expect(slowUnitOfWork).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(fastUnitOfWork).toHaveBeenCalledTimes(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should process next unit of work after current one completes', async () => {
|
|
62
|
+
const queue = new SyncronousQueue();
|
|
63
|
+
const executionOrder: number[] = [];
|
|
64
|
+
|
|
65
|
+
const slowUnitOfWork = vi.fn((done: () => void) => {
|
|
66
|
+
executionOrder.push(1);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
done();
|
|
69
|
+
}, 10);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const fastUnitOfWork = vi.fn((done: () => void) => {
|
|
73
|
+
executionOrder.push(2);
|
|
74
|
+
done();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
queue.enqueue(slowUnitOfWork);
|
|
78
|
+
queue.enqueue(fastUnitOfWork);
|
|
79
|
+
|
|
80
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
81
|
+
|
|
82
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
83
|
+
expect(slowUnitOfWork).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(fastUnitOfWork).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('queue behavior', () => {
|
|
89
|
+
it('should handle empty queue', () => {
|
|
90
|
+
const queue = new SyncronousQueue();
|
|
91
|
+
|
|
92
|
+
expect(() => {
|
|
93
|
+
queue.enqueue(vi.fn((done: () => void) => done()));
|
|
94
|
+
}).not.toThrow();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle unit of work that never calls done', () => {
|
|
98
|
+
const queue = new SyncronousQueue();
|
|
99
|
+
const stuckUnitOfWork = vi.fn((done: () => void) => {
|
|
100
|
+
// Never calls done
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const secondUnitOfWork = vi.fn((done: () => void) => {
|
|
104
|
+
done();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
queue.enqueue(stuckUnitOfWork);
|
|
108
|
+
queue.enqueue(secondUnitOfWork);
|
|
109
|
+
|
|
110
|
+
expect(stuckUnitOfWork).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(secondUnitOfWork).toHaveBeenCalledTimes(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle unit of work that calls done multiple times', () => {
|
|
115
|
+
const queue = new SyncronousQueue();
|
|
116
|
+
const executionOrder: number[] = [];
|
|
117
|
+
|
|
118
|
+
const multiDoneUnitOfWork = vi.fn((done: () => void) => {
|
|
119
|
+
executionOrder.push(1);
|
|
120
|
+
done();
|
|
121
|
+
done(); // Call done multiple times
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const secondUnitOfWork = vi.fn((done: () => void) => {
|
|
125
|
+
executionOrder.push(2);
|
|
126
|
+
done();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
queue.enqueue(multiDoneUnitOfWork);
|
|
130
|
+
queue.enqueue(secondUnitOfWork);
|
|
131
|
+
|
|
132
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
133
|
+
expect(multiDoneUnitOfWork).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(secondUnitOfWork).toHaveBeenCalledTimes(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle rapid enqueueing of multiple units', () => {
|
|
138
|
+
const queue = new SyncronousQueue();
|
|
139
|
+
const executionOrder: number[] = [];
|
|
140
|
+
|
|
141
|
+
const createUnitOfWork = (id: number) => vi.fn((done: () => void) => {
|
|
142
|
+
executionOrder.push(id);
|
|
143
|
+
done();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
for (let i = 1; i <= 5; i++) {
|
|
147
|
+
queue.enqueue(createUnitOfWork(i));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
expect(executionOrder).toEqual([1, 2, 3, 4, 5]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle unit of work that throws error', () => {
|
|
154
|
+
const queue = new SyncronousQueue();
|
|
155
|
+
const errorUnitOfWork = vi.fn((done: () => void) => {
|
|
156
|
+
throw new Error('Test error');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const secondUnitOfWork = vi.fn((done: () => void) => {
|
|
160
|
+
done();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(() => {
|
|
164
|
+
queue.enqueue(errorUnitOfWork);
|
|
165
|
+
}).toThrow('Test error');
|
|
166
|
+
|
|
167
|
+
expect(errorUnitOfWork).toHaveBeenCalledTimes(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should not process next unit after error', () => {
|
|
171
|
+
const queue = new SyncronousQueue();
|
|
172
|
+
const errorUnitOfWork = vi.fn((done: () => void) => {
|
|
173
|
+
throw new Error('Test error');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const secondUnitOfWork = vi.fn((done: () => void) => {
|
|
177
|
+
done();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
queue.enqueue(errorUnitOfWork);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// Error is thrown, queue stops processing
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
queue.enqueue(secondUnitOfWork);
|
|
187
|
+
|
|
188
|
+
expect(errorUnitOfWork).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(secondUnitOfWork).toHaveBeenCalledTimes(0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle unit of work that calls done asynchronously', async () => {
|
|
193
|
+
const queue = new SyncronousQueue();
|
|
194
|
+
const executionOrder: number[] = [];
|
|
195
|
+
|
|
196
|
+
const asyncUnitOfWork = vi.fn((done: () => void) => {
|
|
197
|
+
executionOrder.push(1);
|
|
198
|
+
Promise.resolve().then(() => {
|
|
199
|
+
done();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const syncUnitOfWork = vi.fn((done: () => void) => {
|
|
204
|
+
executionOrder.push(2);
|
|
205
|
+
done();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
queue.enqueue(asyncUnitOfWork);
|
|
209
|
+
queue.enqueue(syncUnitOfWork);
|
|
210
|
+
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
212
|
+
|
|
213
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
214
|
+
expect(asyncUnitOfWork).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(syncUnitOfWork).toHaveBeenCalledTimes(1);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('edge cases', () => {
|
|
220
|
+
it('should handle null done callback', () => {
|
|
221
|
+
const queue = new SyncronousQueue();
|
|
222
|
+
const unitOfWork = vi.fn((done: () => void) => {
|
|
223
|
+
done();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(() => {
|
|
227
|
+
queue.enqueue(unitOfWork);
|
|
228
|
+
}).not.toThrow();
|
|
229
|
+
|
|
230
|
+
expect(unitOfWork).toHaveBeenCalledTimes(1);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should handle unit of work that enqueues another unit of work', () => {
|
|
234
|
+
const queue = new SyncronousQueue();
|
|
235
|
+
const executionOrder: number[] = [];
|
|
236
|
+
|
|
237
|
+
const recursiveUnitOfWork = vi.fn((done: () => void) => {
|
|
238
|
+
executionOrder.push(1);
|
|
239
|
+
queue.enqueue(vi.fn((innerDone: () => void) => {
|
|
240
|
+
executionOrder.push(2);
|
|
241
|
+
innerDone();
|
|
242
|
+
}));
|
|
243
|
+
done();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
queue.enqueue(recursiveUnitOfWork);
|
|
247
|
+
|
|
248
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
249
|
+
expect(recursiveUnitOfWork).toHaveBeenCalledTimes(1);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should handle multiple rapid enqueue operations', () => {
|
|
253
|
+
const queue = new SyncronousQueue();
|
|
254
|
+
const executionCount = { count: 0 };
|
|
255
|
+
|
|
256
|
+
const unitOfWork = vi.fn((done: () => void) => {
|
|
257
|
+
executionCount.count++;
|
|
258
|
+
done();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < 100; i++) {
|
|
262
|
+
queue.enqueue(unitOfWork);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
expect(executionCount.count).toBe(100);
|
|
266
|
+
expect(unitOfWork).toHaveBeenCalledTimes(100);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type SyncronousUnitOfWork = (done: () => void) => void
|
|
2
|
+
|
|
3
|
+
export class SyncronousQueue {
|
|
4
|
+
|
|
5
|
+
private readonly _queue: SyncronousUnitOfWork[] = [];
|
|
6
|
+
private _current: SyncronousUnitOfWork | null = null;
|
|
7
|
+
|
|
8
|
+
enqueue(unitOfWork: SyncronousUnitOfWork) {
|
|
9
|
+
this._queue.push(unitOfWork);
|
|
10
|
+
this._next();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private _next() {
|
|
14
|
+
if (this._current != null || this._queue.length === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Take first item
|
|
19
|
+
this._current = this._queue.shift() ?? null;
|
|
20
|
+
|
|
21
|
+
if (this._current == null) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this._current(() => {
|
|
26
|
+
this._current = null;
|
|
27
|
+
this._next();
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { TrampolinePipeline, AsyncPipeline, Processor, AsyncUnitOfWork } from './TrampolinePipeline';
|
|
3
|
+
import { Result } from '../results';
|
|
4
|
+
|
|
5
|
+
describe('TrampolinePipeline', () => {
|
|
6
|
+
describe('filter', () => {
|
|
7
|
+
it('should call done immediately when no processors are added', async () => {
|
|
8
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
9
|
+
const initialData = 'test data';
|
|
10
|
+
|
|
11
|
+
return new Promise<void>((resolve) => {
|
|
12
|
+
pipeline.filter(initialData, (result, error) => {
|
|
13
|
+
expect(error).toBeUndefined();
|
|
14
|
+
expect(result).toBe(initialData);
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should process single processor synchronously', async () => {
|
|
21
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
22
|
+
const processor: Processor<string, string> = vi.fn((data, callback) => {
|
|
23
|
+
expect(data).toBe('test');
|
|
24
|
+
callback('result');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
pipeline.pipe(processor);
|
|
28
|
+
|
|
29
|
+
return new Promise<void>((resolve) => {
|
|
30
|
+
pipeline.filter('test', (result, error) => {
|
|
31
|
+
expect(error).toBeUndefined();
|
|
32
|
+
expect(result).toBe('result');
|
|
33
|
+
expect(processor).toHaveBeenCalledTimes(1);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should process multiple processors in order', async () => {
|
|
40
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
41
|
+
const executionOrder: number[] = [];
|
|
42
|
+
|
|
43
|
+
const processor1: Processor<string, number> = vi.fn((data, callback) => {
|
|
44
|
+
executionOrder.push(1);
|
|
45
|
+
expect(data).toBe('test');
|
|
46
|
+
callback(42);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const processor2: Processor<number, string> = vi.fn((data, callback) => {
|
|
50
|
+
executionOrder.push(2);
|
|
51
|
+
expect(data).toBe(42);
|
|
52
|
+
callback('result');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
pipeline.pipe(processor1).pipe(processor2);
|
|
56
|
+
|
|
57
|
+
return new Promise<void>((resolve) => {
|
|
58
|
+
pipeline.filter('test', (result, error) => {
|
|
59
|
+
expect(error).toBeUndefined();
|
|
60
|
+
expect(result).toBe('result');
|
|
61
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
62
|
+
expect(processor1).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(processor2).toHaveBeenCalledTimes(1);
|
|
64
|
+
resolve();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle asynchronous processors', async () => {
|
|
70
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
71
|
+
const processor: Processor<string, string> = vi.fn((data, callback) => {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
callback(data.toUpperCase());
|
|
74
|
+
}, 10);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
pipeline.pipe(processor);
|
|
78
|
+
|
|
79
|
+
return new Promise<void>((resolve) => {
|
|
80
|
+
pipeline.filter('test', (result, error) => {
|
|
81
|
+
expect(error).toBeUndefined();
|
|
82
|
+
expect(result).toBe('TEST');
|
|
83
|
+
expect(processor).toHaveBeenCalledTimes(1);
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle mixed sync and async processors', async () => {
|
|
90
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
91
|
+
const executionOrder: number[] = [];
|
|
92
|
+
|
|
93
|
+
const syncProcessor: Processor<string, number> = vi.fn((data, callback) => {
|
|
94
|
+
executionOrder.push(1);
|
|
95
|
+
callback(data.length);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const asyncProcessor: Processor<number, string> = vi.fn((data, callback) => {
|
|
99
|
+
executionOrder.push(2);
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
callback(`Length: ${data}`);
|
|
102
|
+
}, 10);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
pipeline.pipe(syncProcessor).pipe(asyncProcessor);
|
|
106
|
+
|
|
107
|
+
return new Promise<void>((resolve) => {
|
|
108
|
+
pipeline.filter('test', (result, error) => {
|
|
109
|
+
expect(error).toBeUndefined();
|
|
110
|
+
expect(result).toBe('Length: 4');
|
|
111
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
112
|
+
expect(syncProcessor).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(asyncProcessor).toHaveBeenCalledTimes(1);
|
|
114
|
+
resolve();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle overlapping filter calls', async () => {
|
|
120
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
121
|
+
const processor: Processor<string, string> = vi.fn((data, callback) => {
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
callback(data.toUpperCase());
|
|
124
|
+
}, 10);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
pipeline.pipe(processor);
|
|
128
|
+
|
|
129
|
+
return new Promise<void>((resolve) => {
|
|
130
|
+
let completedCount = 0;
|
|
131
|
+
const checkDone = () => {
|
|
132
|
+
completedCount++;
|
|
133
|
+
if (completedCount === 2) {
|
|
134
|
+
resolve();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Start two filter operations simultaneously
|
|
139
|
+
pipeline.filter('test1', (result, error) => {
|
|
140
|
+
expect(error).toBeUndefined();
|
|
141
|
+
expect(result).toBe('TEST1');
|
|
142
|
+
checkDone();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
pipeline.filter('test2', (result, error) => {
|
|
146
|
+
expect(error).toBeUndefined();
|
|
147
|
+
expect(result).toBe('TEST2');
|
|
148
|
+
checkDone();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('pipe', () => {
|
|
155
|
+
it('should return pipeline instance for chaining', () => {
|
|
156
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
157
|
+
const processor: Processor<string, string> = vi.fn((data, callback) => {
|
|
158
|
+
callback(data);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const result = pipeline.pipe(processor);
|
|
162
|
+
|
|
163
|
+
expect(result).toBe(pipeline);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should add processor to the list', async () => {
|
|
167
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
168
|
+
const processor: Processor<string, string> = vi.fn((data, callback) => {
|
|
169
|
+
callback(data);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
pipeline.pipe(processor);
|
|
173
|
+
|
|
174
|
+
return new Promise<void>((resolve) => {
|
|
175
|
+
pipeline.filter('test', (result, error) => {
|
|
176
|
+
expect(processor).toHaveBeenCalledTimes(1);
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('pipeEach', () => {
|
|
184
|
+
it('should process each item in array', async () => {
|
|
185
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
186
|
+
const items = ['a', 'b', 'c'];
|
|
187
|
+
const processedItems: string[] = [];
|
|
188
|
+
|
|
189
|
+
pipeline.pipeEach(
|
|
190
|
+
items,
|
|
191
|
+
(payload, done) => {
|
|
192
|
+
if (payload.ok === Result.SUCCESS) {
|
|
193
|
+
processedItems.push(payload.data);
|
|
194
|
+
done(Result.success(payload.data.toUpperCase()));
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
(previous, current) => current
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return new Promise<void>((resolve) => {
|
|
201
|
+
pipeline.filter('initial', (result, error) => {
|
|
202
|
+
expect(error).toBeUndefined();
|
|
203
|
+
// The result should be a Result object with the last processed item
|
|
204
|
+
expect(result).toEqual({ ok: 'success', data: 'C' });
|
|
205
|
+
expect(processedItems).toEqual(['a', 'b', 'c']);
|
|
206
|
+
resolve();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should handle empty array in pipeEach', async () => {
|
|
212
|
+
const pipeline = new TrampolinePipeline<string, string>();
|
|
213
|
+
|
|
214
|
+
pipeline.pipeEach(
|
|
215
|
+
[],
|
|
216
|
+
(payload, done) => {
|
|
217
|
+
if (payload.ok === Result.SUCCESS) {
|
|
218
|
+
done(Result.success(payload.data));
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
(previous, current) => current
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return new Promise<void>((resolve) => {
|
|
225
|
+
pipeline.filter('initial', (result, error) => {
|
|
226
|
+
expect(error).toBeUndefined();
|
|
227
|
+
expect(result).toBe('initial');
|
|
228
|
+
resolve();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('AsyncPipeline', () => {
|
|
236
|
+
describe('filter', () => {
|
|
237
|
+
it('should call done with empty array when no processors are added', async () => {
|
|
238
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
239
|
+
|
|
240
|
+
return new Promise<void>((resolve) => {
|
|
241
|
+
pipeline.filter((result) => {
|
|
242
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
243
|
+
if (result.ok === Result.SUCCESS) {
|
|
244
|
+
// When no processors are added, data should be undefined, not empty array
|
|
245
|
+
expect(result.data).toBeUndefined();
|
|
246
|
+
}
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should process single unit of work', async () => {
|
|
253
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
254
|
+
const unitOfWork: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
255
|
+
expect(payload).toBe('test');
|
|
256
|
+
done(Result.success('result'));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
pipeline.pipe('test', unitOfWork);
|
|
260
|
+
|
|
261
|
+
return new Promise<void>((resolve) => {
|
|
262
|
+
pipeline.filter((result) => {
|
|
263
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
264
|
+
if (result.ok === Result.SUCCESS) {
|
|
265
|
+
expect(result.data).toEqual(['result']);
|
|
266
|
+
}
|
|
267
|
+
expect(unitOfWork).toHaveBeenCalledTimes(1);
|
|
268
|
+
resolve();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should process multiple units of work', async () => {
|
|
274
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
275
|
+
const executionOrder: number[] = [];
|
|
276
|
+
|
|
277
|
+
const unitOfWork1: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
278
|
+
executionOrder.push(1);
|
|
279
|
+
done(Result.success(`result1-${payload}`));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const unitOfWork2: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
283
|
+
executionOrder.push(2);
|
|
284
|
+
done(Result.success(`result2-${payload}`));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
pipeline.pipe('test1', unitOfWork1);
|
|
288
|
+
pipeline.pipe('test2', unitOfWork2);
|
|
289
|
+
|
|
290
|
+
return new Promise<void>((resolve) => {
|
|
291
|
+
pipeline.filter((result) => {
|
|
292
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
293
|
+
if (result.ok === Result.SUCCESS) {
|
|
294
|
+
expect(result.data).toEqual(['result1-test1', 'result2-test2']);
|
|
295
|
+
}
|
|
296
|
+
expect(executionOrder).toEqual([1, 2]);
|
|
297
|
+
expect(unitOfWork1).toHaveBeenCalledTimes(1);
|
|
298
|
+
expect(unitOfWork2).toHaveBeenCalledTimes(1);
|
|
299
|
+
resolve();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('pipe', () => {
|
|
306
|
+
it('should add unit of work to the list', async () => {
|
|
307
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
308
|
+
const unitOfWork: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
309
|
+
done(Result.success(payload));
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
pipeline.pipe('test', unitOfWork);
|
|
313
|
+
|
|
314
|
+
return new Promise<void>((resolve) => {
|
|
315
|
+
pipeline.filter((result) => {
|
|
316
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
317
|
+
if (result.ok === Result.SUCCESS) {
|
|
318
|
+
expect(result.data).toEqual(['test']);
|
|
319
|
+
}
|
|
320
|
+
expect(unitOfWork).toHaveBeenCalledTimes(1);
|
|
321
|
+
resolve();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('pipeEach', () => {
|
|
328
|
+
it('should process each item in array', async () => {
|
|
329
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
330
|
+
const items = ['a', 'b', 'c'];
|
|
331
|
+
const processedItems: string[] = [];
|
|
332
|
+
|
|
333
|
+
const unitOfWork: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
334
|
+
processedItems.push(payload);
|
|
335
|
+
done(Result.success(payload.toUpperCase()));
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
pipeline.pipeEach(items, unitOfWork);
|
|
339
|
+
|
|
340
|
+
return new Promise<void>((resolve) => {
|
|
341
|
+
pipeline.filter((result) => {
|
|
342
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
343
|
+
if (result.ok === Result.SUCCESS) {
|
|
344
|
+
expect(result.data).toEqual(['A', 'B', 'C']);
|
|
345
|
+
}
|
|
346
|
+
expect(processedItems).toEqual(['a', 'b', 'c']);
|
|
347
|
+
expect(unitOfWork).toHaveBeenCalledTimes(3);
|
|
348
|
+
resolve();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should handle empty array in pipeEach', async () => {
|
|
354
|
+
const pipeline = new AsyncPipeline<string, string>();
|
|
355
|
+
const unitOfWork: AsyncUnitOfWork<string, string> = vi.fn((payload, done) => {
|
|
356
|
+
done(Result.success(payload));
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
pipeline.pipeEach([], unitOfWork);
|
|
360
|
+
|
|
361
|
+
return new Promise<void>((resolve) => {
|
|
362
|
+
pipeline.filter((result) => {
|
|
363
|
+
expect(result.ok).toBe(Result.SUCCESS);
|
|
364
|
+
if (result.ok === Result.SUCCESS) {
|
|
365
|
+
// When pipeEach is called with empty array, data should be undefined, not empty array
|
|
366
|
+
expect(result.data).toBeUndefined();
|
|
367
|
+
}
|
|
368
|
+
expect(unitOfWork).toHaveBeenCalledTimes(0);
|
|
369
|
+
resolve();
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|