@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,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,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
|
+
}
|