@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +91 -0
  3. package/rspack.config.mjs +65 -0
  4. package/src/assertions/index.ts +37 -0
  5. package/src/codegen/SlotPath.ts +20 -0
  6. package/src/codegen/blocks.ts +578 -0
  7. package/src/codegen/handlers/CloneHandlerBuilder.ts +13 -0
  8. package/src/codegen/handlers/CompareHandlerBuilder.ts +13 -0
  9. package/src/codegen/handlers/DeserializeHandlerBuilder.ts +20 -0
  10. package/src/codegen/handlers/EnableChangeTrackingHandlerBuilder.ts +13 -0
  11. package/src/codegen/handlers/EnrichmentHandlerBuilder.ts +28 -0
  12. package/src/codegen/handlers/FreezeHandlerBuilder.ts +13 -0
  13. package/src/codegen/handlers/HashHandlerBuilder.ts +25 -0
  14. package/src/codegen/handlers/HashTypeHandlerBuilder.ts +11 -0
  15. package/src/codegen/handlers/IdSelectorHandlerBuilder.ts +10 -0
  16. package/src/codegen/handlers/MergeHandlerBuilder.ts +19 -0
  17. package/src/codegen/handlers/PrepareHandlerBuilder.ts +23 -0
  18. package/src/codegen/handlers/SerializeHandlerBuilder.ts +15 -0
  19. package/src/codegen/handlers/StripHandlerBuilder.ts +18 -0
  20. package/src/codegen/handlers/clone/CloneObjectHandler.ts +29 -0
  21. package/src/codegen/handlers/clone/CloneValueHandler.ts +33 -0
  22. package/src/codegen/handlers/compare/CompareObjectHandler.ts +15 -0
  23. package/src/codegen/handlers/compare/CompareValueHandler.ts +27 -0
  24. package/src/codegen/handlers/deserialize/DeserializeComputedValueHandler.ts +16 -0
  25. package/src/codegen/handlers/deserialize/DeserializeDateHandler.ts +45 -0
  26. package/src/codegen/handlers/deserialize/DeserializeFunctionHandler.ts +16 -0
  27. package/src/codegen/handlers/deserialize/DeserializeObjectHandler.ts +29 -0
  28. package/src/codegen/handlers/deserialize/DeserializeValueHandler.ts +33 -0
  29. package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingObjectHandler.ts +20 -0
  30. package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingPrimitiveValueHandler.ts +15 -0
  31. package/src/codegen/handlers/enrichment/EnrichmentComputedValueHandler.ts +37 -0
  32. package/src/codegen/handlers/enrichment/EnrichmentDefaultFunctionHandler.ts +50 -0
  33. package/src/codegen/handlers/enrichment/EnrichmentDefaultValueHandler.ts +18 -0
  34. package/src/codegen/handlers/enrichment/EnrichmentFunctionHandler.ts +38 -0
  35. package/src/codegen/handlers/enrichment/EnrichmentNullableObjectHandler.ts +44 -0
  36. package/src/codegen/handlers/enrichment/EnrichmentObjectHandler.ts +29 -0
  37. package/src/codegen/handlers/enrichment/EnrichmentObjectIdentityHandler.ts +16 -0
  38. package/src/codegen/handlers/enrichment/EnrichmentPrimitiveHandler.ts +13 -0
  39. package/src/codegen/handlers/enrichment/EnrichmentPrimitiveIdentityHandler.ts +21 -0
  40. package/src/codegen/handlers/freeze/FreezeObjectHandler.ts +20 -0
  41. package/src/codegen/handlers/freeze/FreezePrimitiveValueHandler.ts +15 -0
  42. package/src/codegen/handlers/hash/HashComputedValueHandler.ts +15 -0
  43. package/src/codegen/handlers/hash/HashDateHandler.ts +26 -0
  44. package/src/codegen/handlers/hash/HashFunctionHandler.ts +16 -0
  45. package/src/codegen/handlers/hash/HashIdentityHandler.ts +15 -0
  46. package/src/codegen/handlers/hash/HashKeyHandler.ts +26 -0
  47. package/src/codegen/handlers/hash/HashValueHandler.ts +26 -0
  48. package/src/codegen/handlers/hashType/HashTypeValueHandler.ts +20 -0
  49. package/src/codegen/handlers/idSelector/IdSelectorValueHandler.ts +28 -0
  50. package/src/codegen/handlers/index.ts +13 -0
  51. package/src/codegen/handlers/merge/MergeComputedValueHandler.ts +37 -0
  52. package/src/codegen/handlers/merge/MergeDefaultFunctionHandler.ts +40 -0
  53. package/src/codegen/handlers/merge/MergeDefaultValueHandler.ts +32 -0
  54. package/src/codegen/handlers/merge/MergeFunctionHandler.ts +16 -0
  55. package/src/codegen/handlers/merge/MergePrimitiveHandler.ts +21 -0
  56. package/src/codegen/handlers/prepare/PrepareComputedValueHandler.ts +16 -0
  57. package/src/codegen/handlers/prepare/PrepareFunctionHandler.ts +16 -0
  58. package/src/codegen/handlers/prepare/PrepareIdentityHandler.ts +21 -0
  59. package/src/codegen/handlers/prepare/PrepareKeyHandler.ts +21 -0
  60. package/src/codegen/handlers/prepare/PrepareObjectHandler.ts +38 -0
  61. package/src/codegen/handlers/prepare/PrepareValueHandler.ts +36 -0
  62. package/src/codegen/handlers/serialize/SerializeDateHandler.ts +45 -0
  63. package/src/codegen/handlers/serialize/SerializeObjectHandler.ts +28 -0
  64. package/src/codegen/handlers/serialize/SerializeValueHandler.ts +33 -0
  65. package/src/codegen/handlers/strip/StripIdentityHandler.ts +15 -0
  66. package/src/codegen/handlers/strip/StripKeyHandler.ts +15 -0
  67. package/src/codegen/handlers/strip/StripObjectHandler.ts +35 -0
  68. package/src/codegen/handlers/strip/StripValueHandler.ts +36 -0
  69. package/src/codegen/handlers/types.ts +119 -0
  70. package/src/codegen/index.ts +2 -0
  71. package/src/codegen/types.ts +2 -0
  72. package/src/codegen/utils.ts +74 -0
  73. package/src/collections/Changes.test.ts +337 -0
  74. package/src/collections/Changes.ts +177 -0
  75. package/src/collections/IdSet.ts +28 -0
  76. package/src/collections/MemoryDataCollection.test.ts +424 -0
  77. package/src/collections/MemoryDataCollection.ts +134 -0
  78. package/src/collections/SchemaCollection.ts +22 -0
  79. package/src/collections/TagCollection.test.ts +443 -0
  80. package/src/collections/TagCollection.ts +62 -0
  81. package/src/collections/index.ts +5 -0
  82. package/src/errors/SchemaError.ts +6 -0
  83. package/src/errors/index.ts +1 -0
  84. package/src/expressions/index.ts +3 -0
  85. package/src/expressions/parser.test.ts +913 -0
  86. package/src/expressions/parser.ts +661 -0
  87. package/src/expressions/types.ts +184 -0
  88. package/src/expressions/utils.test.ts +346 -0
  89. package/src/expressions/utils.ts +59 -0
  90. package/src/index.ts +12 -0
  91. package/src/performance/index.ts +26 -0
  92. package/src/pipeline/SyncronousQueue.test.ts +269 -0
  93. package/src/pipeline/SyncronousQueue.ts +30 -0
  94. package/src/pipeline/TrampolinePipeline.test.ts +374 -0
  95. package/src/pipeline/TrampolinePipeline.ts +437 -0
  96. package/src/pipeline/index.ts +2 -0
  97. package/src/plugins/EphemeralDataPlugin.ts +132 -0
  98. package/src/plugins/capabilities/DbPluginCapability.ts +111 -0
  99. package/src/plugins/capabilities/index.ts +2 -0
  100. package/src/plugins/capabilities/logging/DbPluginLoggingCapability.ts +261 -0
  101. package/src/plugins/capabilities/logging/index.ts +1 -0
  102. package/src/plugins/index.ts +6 -0
  103. package/src/plugins/query/Query.ts +67 -0
  104. package/src/plugins/query/QueryOptionsCollection.test.ts +86 -0
  105. package/src/plugins/query/QueryOptionsCollection.ts +154 -0
  106. package/src/plugins/query/index.ts +3 -0
  107. package/src/plugins/query/types.ts +46 -0
  108. package/src/plugins/replication/OptimisticReplicationDbPlugin.ts +202 -0
  109. package/src/plugins/replication/ReplicationDbPlugin.ts +137 -0
  110. package/src/plugins/replication/index.ts +3 -0
  111. package/src/plugins/replication/types.ts +5 -0
  112. package/src/plugins/translators/DataTranslator.ts +46 -0
  113. package/src/plugins/translators/JsonTranslator.test.ts +618 -0
  114. package/src/plugins/translators/JsonTranslator.ts +211 -0
  115. package/src/plugins/translators/SqlTranslator.ts +45 -0
  116. package/src/plugins/translators/index.ts +3 -0
  117. package/src/plugins/types.ts +114 -0
  118. package/src/results/Result.ts +91 -0
  119. package/src/results/index.ts +3 -0
  120. package/src/results/types.ts +17 -0
  121. package/src/results/utils.ts +15 -0
  122. package/src/schema/PropertyInfo.test.ts +479 -0
  123. package/src/schema/PropertyInfo.ts +374 -0
  124. package/src/schema/SchemaDefinition.ts +626 -0
  125. package/src/schema/builder.ts +18 -0
  126. package/src/schema/index.ts +7 -0
  127. package/src/schema/property/base/SchemaBase.ts +49 -0
  128. package/src/schema/property/base/index.ts +1 -0
  129. package/src/schema/property/modifiers/SchemaDefault.ts +29 -0
  130. package/src/schema/property/modifiers/SchemaDeserialize.ts +23 -0
  131. package/src/schema/property/modifiers/SchemaDistinct.ts +14 -0
  132. package/src/schema/property/modifiers/SchemaFrom.ts +54 -0
  133. package/src/schema/property/modifiers/SchemaIdentity.ts +13 -0
  134. package/src/schema/property/modifiers/SchemaIndex.ts +60 -0
  135. package/src/schema/property/modifiers/SchemaKey.ts +28 -0
  136. package/src/schema/property/modifiers/SchemaNullable.ts +18 -0
  137. package/src/schema/property/modifiers/SchemaOptional.ts +19 -0
  138. package/src/schema/property/modifiers/SchemaReadonly.ts +34 -0
  139. package/src/schema/property/modifiers/SchemaSerialize.ts +18 -0
  140. package/src/schema/property/modifiers/SchemaTracked.ts +14 -0
  141. package/src/schema/property/modifiers/index.ts +12 -0
  142. package/src/schema/property/types/SchemaArray.ts +50 -0
  143. package/src/schema/property/types/SchemaBoolean.ts +59 -0
  144. package/src/schema/property/types/SchemaDate.ts +58 -0
  145. package/src/schema/property/types/SchemaNumber.ts +69 -0
  146. package/src/schema/property/types/SchemaObject.ts +44 -0
  147. package/src/schema/property/types/SchemaString.ts +69 -0
  148. package/src/schema/property/types/index.ts +6 -0
  149. package/src/schema/table/SchemaComputed.ts +20 -0
  150. package/src/schema/table/SchemaFunction.ts +15 -0
  151. package/src/schema/table/index.ts +2 -0
  152. package/src/schema/types.ts +238 -0
  153. package/src/types/index.ts +5 -0
  154. package/src/utilities/arrays.test.ts +312 -0
  155. package/src/utilities/arrays.ts +12 -0
  156. package/src/utilities/dates.test.ts +388 -0
  157. package/src/utilities/dates.ts +11 -0
  158. package/src/utilities/dbPluginEventUtils.ts +44 -0
  159. package/src/utilities/index.ts +10 -0
  160. package/src/utilities/objects.ts +7 -0
  161. package/src/utilities/queryOptionsCollection.ts +16 -0
  162. package/src/utilities/replication.ts +23 -0
  163. package/src/utilities/runtime.ts +3 -0
  164. package/src/utilities/strings.ts +18 -0
  165. package/src/utilities/types.ts +1 -0
  166. package/src/utilities/uuid.ts +56 -0
  167. package/tsconfig.json +28 -0
  168. package/vitest.config.ts +11 -0
@@ -0,0 +1,437 @@
1
+ import { CallbackResult, Result, ResultType } from "../results";
2
+
3
+ /**
4
+ * Type definition for an asynchronous function that takes data and a callback.
5
+ * TIn: The input data type.
6
+ * TOut: The output data type (passed to the callback).
7
+ */
8
+ export type Processor<TIn, TOut> = (data: TIn, callback: (result: TOut, error?: any) => void) => void;
9
+
10
+ // Return type for a step execution: Either the next step function or null if waiting/done.
11
+ type StepResult<TData> = TrampolineStep<TData> | null;
12
+ type TrampolineStep<TData> = () => StepResult<TData>;
13
+
14
+ export class TrampolinePipeline<TInitial, TCurrent = TInitial> {
15
+ private _list: Processor<any, any>[] = [];
16
+ private _hasErrored: boolean = false; // Flag to prevent calling done on error
17
+
18
+ filter<TFinal>(initialData: TInitial, done: (data: TFinal, error?: any) => void) {
19
+
20
+ this._hasErrored = false; // Reset error flag on new execution
21
+
22
+ if (this._list.length === 0) {
23
+ queueMicrotask(() => done(initialData as any as TFinal));
24
+ return;
25
+ }
26
+
27
+ let index = 0;
28
+ let currentData: any = initialData;
29
+ let isRunning = false; // Guard against overlapping trampoline calls
30
+
31
+ try {
32
+ // --- Revised Completion Logic --- (Moved up for clarity)
33
+ const finalStepSentinel = (): StepResult<any> => { // A special step function for the very end
34
+ // Only call done if no error has occurred
35
+ if (!this._hasErrored) {
36
+ queueMicrotask(() => done(currentData as TFinal));
37
+ }
38
+ return null; // Stop the trampoline
39
+ };
40
+
41
+ const createStepRevised = (idx: number): TrampolineStep<any> => {
42
+ return () => {
43
+ if (this._hasErrored) return null; // Stop if an error occurred elsewhere
44
+
45
+ if (idx >= this._list.length) {
46
+ return finalStepSentinel(); // Execute the dedicated final step
47
+ }
48
+
49
+ const processor = this._list[idx];
50
+ // Initialize syncCallbackResult to null to satisfy StepResult type
51
+ let syncCallbackResult: StepResult<any> = null;
52
+ let calledSync = false;
53
+
54
+ try {
55
+ processor(currentData, (result, error) => {
56
+ // --- Error Handling ---
57
+ if (error) {
58
+ console.error(`Error reported by processor at index ${idx}:`, error);
59
+ this._hasErrored = true; // Set flag
60
+ // Throw the error to be caught by outer try...catch blocks
61
+ throw error;
62
+ }
63
+ // --- /Error Handling ---
64
+
65
+ // If no error, proceed as before
66
+ currentData = result;
67
+ index = idx + 1; // Update index for the next step
68
+ const nextStep = createStepRevised(index); // Use updated index
69
+
70
+ if (isRunning) {
71
+ // Callback was synchronous
72
+ syncCallbackResult = nextStep; // Store next step function
73
+ calledSync = true;
74
+ } else {
75
+ // Callback was asynchronous, restart trampoline
76
+ trampoline(nextStep);
77
+ }
78
+ });
79
+ } catch (error) {
80
+ if (!this._hasErrored) { // Check flag to avoid double logging if error was from callback
81
+ console.error(`Error thrown by processor at index ${idx} or its callback:`, error);
82
+ this._hasErrored = true;
83
+ }
84
+ // Rethrow to be caught by the trampoline's catch block
85
+ throw error;
86
+ }
87
+
88
+ if (calledSync) {
89
+ // Return the next step function for the sync loop
90
+ return syncCallbackResult;
91
+ } else {
92
+ // Pause trampoline for async, loop will stop as step returns null
93
+ return null;
94
+ }
95
+ };
96
+ };
97
+
98
+ // The trampoline loop
99
+ const trampoline = (step: TrampolineStep<any> | null) => {
100
+
101
+ if (isRunning) {
102
+ return;
103
+ }
104
+
105
+ isRunning = true;
106
+ let currentStep = step;
107
+
108
+ while (typeof currentStep === 'function') {
109
+ try {
110
+ // Stop immediately if an error was flagged elsewhere
111
+ if (this._hasErrored) {
112
+ currentStep = null;
113
+ break;
114
+ }
115
+ currentStep = currentStep(); // Execute step, get next step or null
116
+ } catch (trampolineError) {
117
+ // Catch errors propagated from step execution (processor or callback errors)
118
+ if (!this._hasErrored) { // Avoid double logging
119
+ console.error("Error during trampoline step execution:", trampolineError);
120
+ this._hasErrored = true;
121
+ }
122
+ currentStep = null; // Stop the loop
123
+ // We don't call `done` here because an error occurred.
124
+ // The application should handle the uncaught exception if desired.
125
+ break; // Explicitly break loop on error
126
+ }
127
+ }
128
+ // Loop ends when currentStep is null or loop is broken by error
129
+ isRunning = false;
130
+
131
+ // Completion check is now handled by finalStepSentinel ensuring `done` isn't called on error.
132
+ };
133
+
134
+ // --- Start the process ---
135
+ index = 0; // Reset index
136
+ currentData = initialData; // Reset data
137
+ trampoline(createStepRevised(0)); // Start with the revised step creator
138
+ } catch (error: any) {
139
+ done(currentData, error);
140
+ }
141
+ }
142
+
143
+ pipe<TNext>(processor: Processor<TCurrent, TNext>) {
144
+ this._list.push(processor);
145
+ return this as unknown as TrampolinePipeline<TInitial, TNext>;
146
+ }
147
+
148
+ pipeEach(items: TCurrent[], fn: (payload: ResultType<TCurrent>, done: CallbackResult<TCurrent>) => void, map: (previous: ResultType<TCurrent>, current: ResultType<TCurrent>) => ResultType<TCurrent>) {
149
+ for (let i = 0, length = items.length; i < length; i++) {
150
+
151
+ this.pipe<ResultType<TCurrent>>((previous, done) => {
152
+
153
+ fn(map(previous as ResultType<TCurrent>, Result.success(items[i])), done);
154
+ });
155
+ }
156
+ }
157
+ }
158
+
159
+ export type AsyncUnitOfWork<TData, TResult> = (payload: TData, done: CallbackResult<TResult>) => void;
160
+ export class AsyncPipeline<TData, TResult> {
161
+ private _list: [TData, AsyncUnitOfWork<TData, TResult>][] = [];
162
+ private _hasErrored: boolean = false; // Flag to prevent calling done on error
163
+
164
+ filter(done: CallbackResult<TResult[]>) {
165
+
166
+ this._hasErrored = false; // Reset error flag on new execution
167
+ let currentData: TResult[] = [];
168
+
169
+ if (this._list.length === 0) {
170
+ queueMicrotask(() => done(Result.success()));
171
+ return;
172
+ }
173
+
174
+ let index = 0;
175
+ let isRunning = false; // Guard against overlapping trampoline calls
176
+
177
+ try {
178
+ // --- Revised Completion Logic --- (Moved up for clarity)
179
+ const finalStepSentinel = (): StepResult<any> => { // A special step function for the very end
180
+ // Only call done if no error has occurred
181
+ if (!this._hasErrored) {
182
+ queueMicrotask(() => done(Result.success(currentData)));
183
+ }
184
+ return null; // Stop the trampoline
185
+ };
186
+
187
+ const createStepRevised = (idx: number): TrampolineStep<any> => {
188
+ return () => {
189
+ if (this._hasErrored) return null; // Stop if an error occurred elsewhere
190
+
191
+ if (idx >= this._list.length) {
192
+ return finalStepSentinel(); // Execute the dedicated final step
193
+ }
194
+
195
+ const [payload, processor] = this._list[idx];
196
+ // Initialize syncCallbackResult to null to satisfy StepResult type
197
+ let syncCallbackResult: StepResult<any> = null;
198
+ let calledSync = false;
199
+
200
+ try {
201
+ processor(payload, (result) => {
202
+ // --- Error Handling ---
203
+ if (result.ok === Result.ERROR) {
204
+ console.error(`Error reported by AsyncPipeline at index ${idx}:`, result.error);
205
+ this._hasErrored = true; // Set flag
206
+ // Throw the error to be caught by outer try...catch blocks
207
+ throw result.error;
208
+ }
209
+ // --- /Error Handling ---
210
+
211
+ // If no error, proceed as before
212
+ currentData.push(result.data);
213
+ index = idx + 1; // Update index for the next step
214
+ const nextStep = createStepRevised(index); // Use updated index
215
+
216
+ if (isRunning) {
217
+ // Callback was synchronous
218
+ syncCallbackResult = nextStep; // Store next step function
219
+ calledSync = true;
220
+ } else {
221
+ // Callback was asynchronous, restart trampoline
222
+ trampoline(nextStep);
223
+ }
224
+ });
225
+ } catch (error) {
226
+ if (!this._hasErrored) { // Check flag to avoid double logging if error was from callback
227
+ console.error(`Error thrown by processor at index ${idx} or its callback:`, error);
228
+ this._hasErrored = true;
229
+ }
230
+ // Rethrow to be caught by the trampoline's catch block
231
+ throw error;
232
+ }
233
+
234
+ if (calledSync) {
235
+ // Return the next step function for the sync loop
236
+ return syncCallbackResult;
237
+ } else {
238
+ // Pause trampoline for async, loop will stop as step returns null
239
+ return null;
240
+ }
241
+ };
242
+ };
243
+
244
+ // The trampoline loop
245
+ const trampoline = (step: TrampolineStep<any> | null) => {
246
+
247
+ if (isRunning) {
248
+ return;
249
+ }
250
+
251
+ isRunning = true;
252
+ let currentStep = step;
253
+
254
+ while (typeof currentStep === 'function') {
255
+ try {
256
+ // Stop immediately if an error was flagged elsewhere
257
+ if (this._hasErrored) {
258
+ currentStep = null;
259
+ break;
260
+ }
261
+ currentStep = currentStep(); // Execute step, get next step or null
262
+ } catch (trampolineError) {
263
+ // Catch errors propagated from step execution (processor or callback errors)
264
+ if (!this._hasErrored) { // Avoid double logging
265
+ console.error("Error during trampoline step execution:", trampolineError);
266
+ this._hasErrored = true;
267
+ }
268
+ currentStep = null; // Stop the loop
269
+ // We don't call `done` here because an error occurred.
270
+ // The application should handle the uncaught exception if desired.
271
+ break; // Explicitly break loop on error
272
+ }
273
+ }
274
+ // Loop ends when currentStep is null or loop is broken by error
275
+ isRunning = false;
276
+
277
+ // Completion check is now handled by finalStepSentinel ensuring `done` isn't called on error.
278
+ };
279
+
280
+ // --- Start the process ---
281
+ index = 0; // Reset index
282
+ trampoline(createStepRevised(0)); // Start with the revised step creator
283
+ } catch (error: any) {
284
+ done(Result.error(error));
285
+ }
286
+ }
287
+
288
+ pipe(data: TData, processor: AsyncUnitOfWork<TData, TResult>) {
289
+ this._list.push([data, processor]);
290
+ }
291
+
292
+ pipeEach(items: TData[], processor: AsyncUnitOfWork<TData, TResult>) {
293
+ for (let i = 0, length = items.length; i < length; i++) {
294
+ this.pipe(items[i], processor);
295
+ }
296
+ }
297
+ }
298
+
299
+ export type UnitOfWork = (done: CallbackResult<never>) => void;
300
+
301
+ /**
302
+ * Processes functions with callbacks asynchronously.
303
+ *
304
+ * This pipeline handles work items that contain callback functions,
305
+ * executing them in an asynchronous manner while maintaining proper
306
+ * flow control and error handling.
307
+ */
308
+ export class WorkPipeline {
309
+ private unitsOfWork: UnitOfWork[] = [];
310
+ private _hasErrored: boolean = false; // Flag to prevent calling done on error
311
+
312
+ filter(done: CallbackResult<never>) {
313
+
314
+ this._hasErrored = false; // Reset error flag on new execution
315
+
316
+ if (this.unitsOfWork.length === 0) {
317
+ queueMicrotask(() => done(Result.success()));
318
+ return;
319
+ }
320
+
321
+ let index = 0;
322
+ let isRunning = false; // Guard against overlapping trampoline calls
323
+
324
+ try {
325
+ // --- Revised Completion Logic --- (Moved up for clarity)
326
+ const finalStepSentinel = (): StepResult<never> => { // A special step function for the very end
327
+ // Only call done if no error has occurred
328
+ if (!this._hasErrored) {
329
+ queueMicrotask(() => done(Result.success()));
330
+ }
331
+ return null; // Stop the trampoline
332
+ };
333
+
334
+ const createStepRevised = (idx: number): TrampolineStep<any> => {
335
+ return () => {
336
+ if (this._hasErrored) return null; // Stop if an error occurred elsewhere
337
+
338
+ if (idx >= this.unitsOfWork.length) {
339
+ return finalStepSentinel(); // Execute the dedicated final step
340
+ }
341
+
342
+ const processor = this.unitsOfWork[idx];
343
+ // Initialize syncCallbackResult to null to satisfy StepResult type
344
+ let syncCallbackResult: StepResult<any> = null;
345
+ let calledSync = false;
346
+
347
+ try {
348
+ processor((result) => {
349
+ // --- Error Handling ---
350
+ if (result.ok === Result.ERROR) {
351
+ console.error(`Error reported by AsyncPipeline at index ${idx}:`, result.error);
352
+ this._hasErrored = true; // Set flag
353
+ // Throw the error to be caught by outer try...catch blocks
354
+ throw result.error;
355
+ }
356
+ // --- /Error Handling ---
357
+
358
+ // If no error, proceed as before
359
+ index = idx + 1; // Update index for the next step
360
+ const nextStep = createStepRevised(index); // Use updated index
361
+
362
+ if (isRunning) {
363
+ // Callback was synchronous
364
+ syncCallbackResult = nextStep; // Store next step function
365
+ calledSync = true;
366
+ } else {
367
+ // Callback was asynchronous, restart trampoline
368
+ trampoline(nextStep);
369
+ }
370
+ });
371
+ } catch (error) {
372
+ if (!this._hasErrored) { // Check flag to avoid double logging if error was from callback
373
+ console.error(`Error thrown by processor at index ${idx} or its callback:`, error);
374
+ this._hasErrored = true;
375
+ }
376
+ // Rethrow to be caught by the trampoline's catch block
377
+ throw error;
378
+ }
379
+
380
+ if (calledSync) {
381
+ // Return the next step function for the sync loop
382
+ return syncCallbackResult;
383
+ } else {
384
+ // Pause trampoline for async, loop will stop as step returns null
385
+ return null;
386
+ }
387
+ };
388
+ };
389
+
390
+ // The trampoline loop
391
+ const trampoline = (step: TrampolineStep<any> | null) => {
392
+
393
+ if (isRunning) {
394
+ return;
395
+ }
396
+
397
+ isRunning = true;
398
+ let currentStep = step;
399
+
400
+ while (typeof currentStep === 'function') {
401
+ try {
402
+ // Stop immediately if an error was flagged elsewhere
403
+ if (this._hasErrored) {
404
+ currentStep = null;
405
+ break;
406
+ }
407
+ currentStep = currentStep(); // Execute step, get next step or null
408
+ } catch (trampolineError) {
409
+ // Catch errors propagated from step execution (processor or callback errors)
410
+ if (!this._hasErrored) { // Avoid double logging
411
+ console.error("Error during trampoline step execution:", trampolineError);
412
+ this._hasErrored = true;
413
+ }
414
+ currentStep = null; // Stop the loop
415
+ // We don't call `done` here because an error occurred.
416
+ // The application should handle the uncaught exception if desired.
417
+ break; // Explicitly break loop on error
418
+ }
419
+ }
420
+ // Loop ends when currentStep is null or loop is broken by error
421
+ isRunning = false;
422
+
423
+ // Completion check is now handled by finalStepSentinel ensuring `done` isn't called on error.
424
+ };
425
+
426
+ // --- Start the process ---
427
+ index = 0; // Reset index
428
+ trampoline(createStepRevised(0)); // Start with the revised step creator
429
+ } catch (error: any) {
430
+ done(Result.error(error));
431
+ }
432
+ }
433
+
434
+ pipe(work: UnitOfWork) {
435
+ this.unitsOfWork.push(work);
436
+ }
437
+ }
@@ -0,0 +1,2 @@
1
+ export * from './SyncronousQueue';
2
+ export * from './TrampolinePipeline';
@@ -0,0 +1,132 @@
1
+ import { assertIsNotNull } from '../assertions';
2
+ import { BulkPersistResult } from '../collections';
3
+ import { WorkPipeline } from '../pipeline';
4
+ import { DbPluginBulkPersistEvent, DbPluginEvent, DbPluginQueryEvent, IDbPlugin, JsonTranslator } from '.';
5
+ import { PluginEventCallbackPartialResult, PluginEventCallbackResult, PluginEventResult, Result } from '../results';
6
+ import { CompiledSchema, InferCreateType } from '../schema';
7
+ import { DeepPartial } from '../types';
8
+ import { MemoryDataCollection } from '../collections/MemoryDataCollection';
9
+ import { UnknownRecord } from '../utilities';
10
+
11
+ export abstract class EphemeralDataPlugin implements IDbPlugin {
12
+
13
+ protected databaseName: string;
14
+
15
+ constructor(databaseName: string) {
16
+ this.databaseName = databaseName;
17
+ }
18
+
19
+ protected abstract resolveCollection<TEntity extends {}>(schema: CompiledSchema<TEntity>): MemoryDataCollection;
20
+
21
+ bulkPersist(event: DbPluginBulkPersistEvent, done: PluginEventCallbackPartialResult<BulkPersistResult>) {
22
+ try {
23
+ const pipeline = new WorkPipeline();
24
+ const bulkPersistResult = event.operation.toResult();
25
+
26
+ for (const [schemaId, changes] of event.operation) {
27
+
28
+ pipeline.pipe((d) => {
29
+ try {
30
+
31
+ const { adds, hasItems, removes, updates } = changes;
32
+
33
+ if (hasItems === false) {
34
+ d(Result.success());
35
+ return;
36
+ }
37
+
38
+ const result = bulkPersistResult.get(schemaId);
39
+ const schema = event.schemas.get(schemaId);
40
+
41
+ assertIsNotNull(schema);
42
+
43
+ const collection = this.resolveCollection(schema);
44
+ collection.load(readResult => {
45
+
46
+ if (readResult.ok === Result.ERROR) {
47
+ d(readResult);
48
+ return;
49
+ }
50
+
51
+ for (let i = 0, length = adds.length; i < length; i++) {
52
+ collection.add(adds[i]);
53
+ result.adds.push(adds[i] as DeepPartial<InferCreateType<UnknownRecord>>);
54
+ }
55
+
56
+ for (let i = 0, length = updates.length; i < length; i++) {
57
+ collection.update(updates[i].entity);
58
+ result.updates.push(updates[i].entity);
59
+ }
60
+
61
+ for (let i = 0, length = removes.length; i < length; i++) {
62
+ collection.remove(removes[i]);
63
+ result.removes.push(removes[i]);
64
+ }
65
+
66
+ collection.save(saveResult => {
67
+
68
+ if (saveResult.ok === Result.ERROR) {
69
+ d(saveResult);
70
+ return;
71
+ }
72
+
73
+ d(Result.success());
74
+ });
75
+ })
76
+
77
+ } catch (e) {
78
+ d(Result.error(e));
79
+ }
80
+ });
81
+ }
82
+
83
+ let successCount = 0;
84
+
85
+ pipeline.filter((asyncResult) => {
86
+
87
+ if (asyncResult.ok !== PluginEventResult.SUCCESS) {
88
+
89
+ if (successCount === 0) {
90
+ done(PluginEventResult.error(event.id, asyncResult.error))
91
+ return;
92
+ }
93
+
94
+ done(PluginEventResult.partial(event.id, bulkPersistResult, asyncResult.error))
95
+ return;
96
+ }
97
+
98
+ successCount++;
99
+
100
+ done(PluginEventResult.success(event.id, bulkPersistResult));
101
+ });
102
+ } catch (e: any) {
103
+ done(PluginEventResult.error(event.id, e))
104
+ }
105
+ }
106
+
107
+ query<TEntity extends {}, TShape extends any = TEntity>(event: DbPluginQueryEvent<TEntity, TShape>, done: PluginEventCallbackResult<TShape>): void {
108
+
109
+ try {
110
+ const { operation } = event;
111
+ const translator = new JsonTranslator<TEntity, TShape>(operation);
112
+ const collection = this.resolveCollection(operation.schema);
113
+
114
+ // translate if we are doing any operations like count/sum/min/max/skip/take
115
+ collection.load(r => {
116
+
117
+ if (r.ok === Result.ERROR) {
118
+ done(PluginEventResult.error(event.id, r.error));
119
+ return;
120
+ }
121
+
122
+ const translated = translator.translate(collection.records);
123
+
124
+ done(PluginEventResult.success(event.id, translated));
125
+ })
126
+ } catch (e) {
127
+ done(PluginEventResult.error(event.id, e));
128
+ }
129
+ }
130
+
131
+ abstract destroy(event: DbPluginEvent, done: PluginEventCallbackResult<never>): void;
132
+ }
@@ -0,0 +1,111 @@
1
+ import { BulkPersistResult } from "../../collections";
2
+ import { PluginEventPartialResultType, PluginEventResultType } from "../../results";
3
+ import { DbPluginBulkPersistEvent, DbPluginEvent, DbPluginQueryEvent, IDbPlugin } from "../types";
4
+
5
+ export type DbPluginCapabilityEvent = "queryStart" | "queryComplete" | "destroyStart" | "destroyComplete" | "bulkPersistStart" | "bulkPersistComplete";
6
+
7
+ export interface IDbPluginCapability {
8
+ apply<T extends IDbPlugin>(plugin: T): void;
9
+ }
10
+
11
+ /**
12
+ * Extends plugin functionality through hooks and event handlers without
13
+ * changing the plugin's type (mixin). Essential for maintaining type safety
14
+ * in routier's core systems.
15
+ */
16
+ export class DbPluginCapability {
17
+
18
+ private events: Record<string, { before?: Function, after?: Function }> = {};
19
+
20
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "queryStart", callback: (event: DbPluginQueryEvent<TRoot, TShape>) => void): DbPluginCapability;
21
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "queryComplete", callback: (event: PluginEventResultType<TShape>) => void): DbPluginCapability;
22
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "destroyStart", callback: (event: DbPluginEvent) => void): DbPluginCapability;
23
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "destroyComplete", callback: (event: PluginEventResultType<never>) => void): DbPluginCapability;
24
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "bulkPersistStart", callback: (event: DbPluginBulkPersistEvent) => void): DbPluginCapability;
25
+ add<TRoot extends {}, TShape extends any = TRoot>(name: "bulkPersistComplete", callback: (event: PluginEventPartialResultType<BulkPersistResult>) => void): DbPluginCapability;
26
+ add<TRoot extends {}, TShape extends any = TRoot>(name: DbPluginCapabilityEvent, callback: Function): DbPluginCapability {
27
+
28
+ this.resolve(name);
29
+
30
+ switch (name) {
31
+ case "queryStart":
32
+ this.events["query"].before = callback;
33
+ break;
34
+ case "queryComplete":
35
+ this.events["query"].after = callback;
36
+ break;
37
+ case "destroyStart":
38
+ this.events["destroy"].before = callback;
39
+ break;
40
+ case "destroyComplete":
41
+ this.events["destroy"].after = callback;
42
+ break;
43
+ case "bulkPersistStart":
44
+ this.events["bulkPersist"].before = callback;
45
+ break;
46
+ case "bulkPersistComplete":
47
+ this.events["bulkPersist"].after = callback;
48
+ break;
49
+ }
50
+
51
+ return this;
52
+ }
53
+
54
+ private resolve(name: DbPluginCapabilityEvent) {
55
+
56
+ switch (name) {
57
+ case "queryStart":
58
+ case "queryComplete":
59
+ if (!this.events["query"]) {
60
+ this.events["query"] = {};
61
+ }
62
+ return;
63
+ case "destroyStart":
64
+ case "destroyComplete":
65
+ if (!this.events["destroy"]) {
66
+ this.events["destroy"] = {};
67
+ }
68
+ return;
69
+ case "bulkPersistStart":
70
+ case "bulkPersistComplete":
71
+ if (!this.events["bulkPersist"]) {
72
+ this.events["bulkPersist"] = {};
73
+ }
74
+ return;
75
+
76
+ default:
77
+ throw new Error("Exhaustive check")
78
+ }
79
+ }
80
+
81
+ apply<T extends IDbPlugin>(plugin: T) {
82
+
83
+ const methodWrappers: { method: keyof IDbPlugin, events: { before?: Function, after?: Function } }[] = [
84
+ { method: 'query', events: this.events.query },
85
+ { method: 'destroy', events: this.events.destroy },
86
+ { method: 'bulkPersist', events: this.events.bulkPersist }
87
+ ];
88
+
89
+ // apply the mixins
90
+ for (let i = 0, length = methodWrappers.length; i < length; i++) {
91
+
92
+ const { events, method } = methodWrappers[i];
93
+
94
+ if (events?.before || events?.after) {
95
+ const original = plugin[method].bind(plugin);
96
+ plugin[method] = ((event: unknown, done: Function) => {
97
+ events.before?.(event, done);
98
+
99
+ if (events.after) {
100
+ return original(event, (result: unknown) => {
101
+ events.after(result);
102
+ done(result);
103
+ });
104
+ }
105
+
106
+ return original(event, done);
107
+ });
108
+ }
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,2 @@
1
+ export * from './DbPluginCapability';
2
+ export * from './logging';