@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,261 @@
|
|
|
1
|
+
import { IDbPlugin, DbPluginQueryEvent, DbPluginBulkPersistEvent, DbPluginEvent } from "../../../plugins/types";
|
|
2
|
+
import { DbPluginCapability, IDbPluginCapability } from "../DbPluginCapability";
|
|
3
|
+
import { PluginEventResultType, PluginEventPartialResultType } from "../../../results";
|
|
4
|
+
import { now } from "../../../performance";
|
|
5
|
+
import { BulkPersistChanges, BulkPersistResult } from "../../../collections";
|
|
6
|
+
|
|
7
|
+
export class DbPluginLoggingCapability implements IDbPluginCapability {
|
|
8
|
+
private logStyle: 'minimal' | 'detailed' | 'redux' = 'redux';
|
|
9
|
+
private maxLogEntries: number = 100;
|
|
10
|
+
private logHistory: Array<{ type: string; timestamp: number; data: any }> = [];
|
|
11
|
+
private queryPerformance: Map<string, number> = new Map<string, number>();
|
|
12
|
+
|
|
13
|
+
constructor(options?: { logStyle?: 'minimal' | 'detailed' | 'redux'; maxLogEntries?: number }) {
|
|
14
|
+
this.logStyle = options?.logStyle ?? 'redux';
|
|
15
|
+
this.maxLogEntries = options?.maxLogEntries ?? 100;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply<T extends IDbPlugin>(plugin: T) {
|
|
19
|
+
const baseCapability = new DbPluginCapability();
|
|
20
|
+
|
|
21
|
+
const pluginName = plugin.constructor.name;
|
|
22
|
+
|
|
23
|
+
// Query logging
|
|
24
|
+
baseCapability
|
|
25
|
+
.add("queryStart", (event: DbPluginQueryEvent<any, any>) => {
|
|
26
|
+
this.logReduxAction('QUERY_REQUEST', event.id, {
|
|
27
|
+
plugin: pluginName,
|
|
28
|
+
collection: event.operation.schema.collectionName,
|
|
29
|
+
schemaId: event.operation.schema.id,
|
|
30
|
+
changeTracking: event.operation.changeTracking
|
|
31
|
+
}, {
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
options: this.extractQueryOptions(event.operation)
|
|
34
|
+
});
|
|
35
|
+
this.addToHistory('QUERY_REQUEST', event);
|
|
36
|
+
this.queryPerformance.set(event.id, now());
|
|
37
|
+
})
|
|
38
|
+
.add("queryComplete", (result: PluginEventResultType<any>) => {
|
|
39
|
+
const start = this.queryPerformance.get(result.id);
|
|
40
|
+
this.queryPerformance.delete(result.id)
|
|
41
|
+
const end = now();
|
|
42
|
+
const duration = start == null ? -1 : end - start;
|
|
43
|
+
const performance = this.getPerformanceIndicator(duration);
|
|
44
|
+
|
|
45
|
+
if (result.ok === 'success') {
|
|
46
|
+
this.logReduxAction('QUERY_SUCCESS', result.id, {
|
|
47
|
+
plugin: pluginName,
|
|
48
|
+
resultCount: this.getResultCount(result.data),
|
|
49
|
+
resultType: this.getResultType(result.data)
|
|
50
|
+
}, {
|
|
51
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
52
|
+
performance: performance.label,
|
|
53
|
+
timestamp: new Date().toISOString()
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
this.logReduxAction('QUERY_ERROR', result.id, {
|
|
57
|
+
plugin: pluginName,
|
|
58
|
+
error: result.error?.message || result.error,
|
|
59
|
+
isCritical: false
|
|
60
|
+
}, {
|
|
61
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
62
|
+
performance: performance.label,
|
|
63
|
+
timestamp: new Date().toISOString()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
this.addToHistory('QUERY_RESULT', { result, duration });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Bulk operations logging
|
|
70
|
+
baseCapability
|
|
71
|
+
.add("bulkPersistStart", (event: DbPluginBulkPersistEvent) => {
|
|
72
|
+
const totalOperations = event.operation.aggregate.size;
|
|
73
|
+
this.logReduxAction('BULK_OPERATIONS_REQUEST', event.id, {
|
|
74
|
+
plugin: pluginName,
|
|
75
|
+
totalOperations,
|
|
76
|
+
schemaCount: event.schemas.size,
|
|
77
|
+
operations: this.extractBulkOperations(event.operation)
|
|
78
|
+
}, {
|
|
79
|
+
timestamp: new Date().toISOString()
|
|
80
|
+
});
|
|
81
|
+
this.addToHistory('BULK_OPERATIONS_REQUEST', event);
|
|
82
|
+
this.queryPerformance.set(event.id, now());
|
|
83
|
+
})
|
|
84
|
+
.add("bulkPersistComplete", (result: PluginEventPartialResultType<BulkPersistResult>) => {
|
|
85
|
+
const start = this.queryPerformance.get(result.id);
|
|
86
|
+
this.queryPerformance.delete(result.id)
|
|
87
|
+
const end = now();
|
|
88
|
+
const duration = start == null ? -1 : end - start;
|
|
89
|
+
const performance = this.getPerformanceIndicator(duration);
|
|
90
|
+
|
|
91
|
+
if (result.ok === 'success') {
|
|
92
|
+
this.logReduxAction('BULK_OPERATIONS_SUCCESS', result.id, {
|
|
93
|
+
plugin: pluginName,
|
|
94
|
+
completedOperations: this.countCompletedOperations(result.data),
|
|
95
|
+
schemaCount: result.data.size
|
|
96
|
+
}, {
|
|
97
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
98
|
+
performance: performance.label,
|
|
99
|
+
timestamp: new Date().toISOString()
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
this.logReduxAction('BULK_OPERATIONS_ERROR', result.id, {
|
|
103
|
+
plugin: pluginName,
|
|
104
|
+
error: result.error?.message || result.error,
|
|
105
|
+
isCritical: false
|
|
106
|
+
}, {
|
|
107
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
108
|
+
performance: performance.label,
|
|
109
|
+
timestamp: new Date().toISOString()
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
this.addToHistory('BULK_OPERATIONS_RESULT', { result, duration });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Destroy logging
|
|
116
|
+
baseCapability
|
|
117
|
+
.add("destroyStart", (event: DbPluginEvent) => {
|
|
118
|
+
this.logReduxAction('DESTROY_REQUEST', event.id, {
|
|
119
|
+
plugin: pluginName,
|
|
120
|
+
schemaCount: event.schemas.size
|
|
121
|
+
}, {
|
|
122
|
+
timestamp: new Date().toISOString()
|
|
123
|
+
});
|
|
124
|
+
this.addToHistory('DESTROY_REQUEST', event);
|
|
125
|
+
this.queryPerformance.set(event.id, now());
|
|
126
|
+
})
|
|
127
|
+
.add("destroyComplete", (result: PluginEventResultType<never>) => {
|
|
128
|
+
const start = this.queryPerformance.get(result.id);
|
|
129
|
+
this.queryPerformance.delete(result.id)
|
|
130
|
+
const end = now();
|
|
131
|
+
const duration = start == null ? -1 : end - start;
|
|
132
|
+
const performance = this.getPerformanceIndicator(duration);
|
|
133
|
+
|
|
134
|
+
if (result.ok === 'success') {
|
|
135
|
+
this.logReduxAction('DESTROY_SUCCESS', result.id, {
|
|
136
|
+
plugin: pluginName
|
|
137
|
+
}, {
|
|
138
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
139
|
+
performance: performance.label,
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
this.logReduxAction('DESTROY_ERROR', result.id, {
|
|
144
|
+
plugin: pluginName,
|
|
145
|
+
error: result.error?.message || result.error
|
|
146
|
+
}, {
|
|
147
|
+
duration: `${duration.toFixed(4)}ms`,
|
|
148
|
+
performance: performance.label,
|
|
149
|
+
timestamp: new Date().toISOString()
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
this.addToHistory('DESTROY_RESULT', { result, duration });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
baseCapability.apply(plugin);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private getPerformanceIndicator(duration: number) {
|
|
159
|
+
if (duration > 1000) return { emoji: '🐌', color: '#ef4444', label: 'SLOW', level: 'error' };
|
|
160
|
+
if (duration > 500) return { emoji: '🐢', color: '#f97316', label: 'MEDIUM', level: 'warning' };
|
|
161
|
+
if (duration > 100) return { emoji: '⚡', color: '#eab308', label: 'FAST', level: 'info' };
|
|
162
|
+
return { emoji: '🚀', color: '#22c55e', label: 'INSTANT', level: 'success' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private logReduxAction(action: string, eventId: string, payload: any, meta?: any) {
|
|
166
|
+
if (this.logStyle !== 'redux') return;
|
|
167
|
+
|
|
168
|
+
const timestamp = new Date().toISOString();
|
|
169
|
+
|
|
170
|
+
console.groupCollapsed(
|
|
171
|
+
`%c${action} %c@ ${timestamp}`,
|
|
172
|
+
'color: #3b82f6; font-weight: bold; font-size: 14px;',
|
|
173
|
+
'color: #6b7280; font-size: 12px;'
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
console.group('Action');
|
|
177
|
+
console.log('Type:', action);
|
|
178
|
+
console.log('Event ID:', eventId);
|
|
179
|
+
console.log('Timestamp:', timestamp);
|
|
180
|
+
console.groupEnd();
|
|
181
|
+
|
|
182
|
+
if (payload) {
|
|
183
|
+
console.group('Payload');
|
|
184
|
+
console.log(payload);
|
|
185
|
+
console.groupEnd();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (meta) {
|
|
189
|
+
console.group('Meta');
|
|
190
|
+
console.log(meta);
|
|
191
|
+
console.groupEnd();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.groupEnd();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private extractQueryOptions(query: any) {
|
|
198
|
+
const options: Record<string, any> = {};
|
|
199
|
+
if (query.options) {
|
|
200
|
+
['skip', 'take', 'sort', 'filter', 'map', 'distinct'].forEach(type => {
|
|
201
|
+
try {
|
|
202
|
+
const values = query.options.getValues(type);
|
|
203
|
+
if (values.length > 0) options[type] = values;
|
|
204
|
+
} catch (e) {
|
|
205
|
+
// Skip if option type not supported
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return options;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private getResultCount(result: any): string {
|
|
213
|
+
if (Array.isArray(result)) return `${result.length} items`;
|
|
214
|
+
if (result !== null && typeof result === 'object') return '1 object';
|
|
215
|
+
return '1 primitive';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private getResultType(result: any): string {
|
|
219
|
+
if (Array.isArray(result)) return 'array';
|
|
220
|
+
if (result !== null && typeof result === 'object') return 'object';
|
|
221
|
+
return typeof result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private extractBulkOperations(operations: BulkPersistChanges) {
|
|
225
|
+
const aggregate = operations.aggregate;
|
|
226
|
+
return {
|
|
227
|
+
adds: aggregate.adds,
|
|
228
|
+
updates: aggregate.updates,
|
|
229
|
+
removes: aggregate.removes
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private countCompletedOperations(result: BulkPersistResult | undefined): number {
|
|
234
|
+
return result?.aggregate.size || 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private addToHistory(type: string, data: any) {
|
|
238
|
+
this.logHistory.push({
|
|
239
|
+
type,
|
|
240
|
+
timestamp: Date.now(),
|
|
241
|
+
data
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (this.logHistory.length > this.maxLogEntries) {
|
|
245
|
+
this.logHistory = this.logHistory.slice(-this.maxLogEntries);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private generateId(): string {
|
|
250
|
+
return Math.random().toString(36).substr(2, 9);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Public methods for debugging
|
|
254
|
+
getLogHistory() {
|
|
255
|
+
return [...this.logHistory];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
clearLogHistory() {
|
|
259
|
+
this.logHistory = [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DbPluginLoggingCapability';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import { CompiledSchema } from '../../schema';
|
|
3
|
+
import { IQuery } from '../types';
|
|
4
|
+
import { QueryOptionsCollection } from './QueryOptionsCollection';
|
|
5
|
+
|
|
6
|
+
export class Query<TRoot extends {}, TShape> implements IQuery<TRoot, TShape> {
|
|
7
|
+
|
|
8
|
+
readonly options: QueryOptionsCollection<TShape>;
|
|
9
|
+
readonly schema: CompiledSchema<TRoot>;
|
|
10
|
+
private enableChangeTrackingOverride?: boolean
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
options: QueryOptionsCollection<TShape>,
|
|
14
|
+
schema: CompiledSchema<TRoot>,
|
|
15
|
+
enableChangeTrackingOverride?: boolean,
|
|
16
|
+
) {
|
|
17
|
+
this.schema = schema;
|
|
18
|
+
this.options = options;
|
|
19
|
+
this.enableChangeTrackingOverride = enableChangeTrackingOverride;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// boolean value whether or not change tracking can be enabled on the query result
|
|
23
|
+
get changeTracking(): boolean {
|
|
24
|
+
|
|
25
|
+
if (this.enableChangeTrackingOverride != null) {
|
|
26
|
+
return this.enableChangeTrackingOverride;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const map = this.options.getValues("map");
|
|
30
|
+
const count = this.options.has("count");
|
|
31
|
+
const max = this.options.has("max");
|
|
32
|
+
const min = this.options.has("min");
|
|
33
|
+
const sum = this.options.has("sum");
|
|
34
|
+
|
|
35
|
+
if (map.some(x => x.fields.length > 0)) {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (count === true ||
|
|
40
|
+
max === true ||
|
|
41
|
+
min === true ||
|
|
42
|
+
sum === true) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static EMPTY<T extends {}, S>(schema: CompiledSchema<T>) {
|
|
50
|
+
return new Query<T, S>(QueryOptionsCollection.EMPTY<S>(), schema);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static isEmpty<T extends {}, S>(query: IQuery<T, S>) {
|
|
54
|
+
return QueryOptionsCollection.isEmpty(query.options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static toString<TRoot extends {}, TShape>(query: IQuery<TRoot, TShape>) {
|
|
58
|
+
return JSON.stringify({
|
|
59
|
+
options: query.options.items,
|
|
60
|
+
schema: {
|
|
61
|
+
id: query.schema.id,
|
|
62
|
+
collectionName: query.schema.collectionName
|
|
63
|
+
},
|
|
64
|
+
changeTracking: query.changeTracking
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { QueryOptionsCollection } from './QueryOptionsCollection';
|
|
3
|
+
import { QueryOrdering } from './types';
|
|
4
|
+
|
|
5
|
+
describe('QueryOptionsCollection', () => {
|
|
6
|
+
it('should create an empty collection', () => {
|
|
7
|
+
const collection = QueryOptionsCollection.EMPTY();
|
|
8
|
+
expect(collection).toBeInstanceOf(QueryOptionsCollection);
|
|
9
|
+
expect(QueryOptionsCollection.isEmpty(collection)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should add a skip option', () => {
|
|
13
|
+
const collection = QueryOptionsCollection.EMPTY();
|
|
14
|
+
collection.add('skip', 5);
|
|
15
|
+
expect(collection.has('skip')).toBe(true);
|
|
16
|
+
expect(collection.get('skip')).toHaveLength(1);
|
|
17
|
+
expect(collection.get('skip')[0].option.value).toBe(5);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should add a take option', () => {
|
|
21
|
+
const collection = QueryOptionsCollection.EMPTY();
|
|
22
|
+
collection.add('take', 10);
|
|
23
|
+
expect(collection.has('take')).toBe(true);
|
|
24
|
+
expect(collection.get('take')).toHaveLength(1);
|
|
25
|
+
expect(collection.get('take')[0].option.value).toBe(10);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should add a sort option', () => {
|
|
29
|
+
const collection = QueryOptionsCollection.EMPTY<any>();
|
|
30
|
+
const selector = (item: any) => item.id;
|
|
31
|
+
collection.add('sort', { selector, direction: QueryOrdering.Ascending, propertyName: 'id' });
|
|
32
|
+
expect(collection.has('sort')).toBe(true);
|
|
33
|
+
expect(collection.get('sort')).toHaveLength(1);
|
|
34
|
+
expect(collection.get('sort')[0].option.value.direction).toBe(QueryOrdering.Ascending);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should add a map option', () => {
|
|
38
|
+
const collection = QueryOptionsCollection.EMPTY();
|
|
39
|
+
const selector = (item: any) => ({ id: item.id });
|
|
40
|
+
collection.add('map', { selector, fields: [] });
|
|
41
|
+
expect(collection.has('map')).toBe(true);
|
|
42
|
+
expect(collection.get('map')).toHaveLength(1);
|
|
43
|
+
expect(collection.get('map')[0].option.value.selector).toBe(selector);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should add multiple options of the same type', () => {
|
|
47
|
+
const collection = QueryOptionsCollection.EMPTY();
|
|
48
|
+
collection.add('skip', 5);
|
|
49
|
+
collection.add('skip', 10);
|
|
50
|
+
expect(collection.get('skip')).toHaveLength(2);
|
|
51
|
+
expect(collection.get('skip')[0].option.value).toBe(5);
|
|
52
|
+
expect(collection.get('skip')[1].option.value).toBe(10);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should assign sequential indices to options', () => {
|
|
56
|
+
const collection = QueryOptionsCollection.EMPTY<any>();
|
|
57
|
+
collection.add('skip', 5);
|
|
58
|
+
collection.add('take', 10);
|
|
59
|
+
collection.add('sort', { selector: (item: any) => item.id, direction: QueryOrdering.Ascending, propertyName: 'id' });
|
|
60
|
+
expect(collection.get('skip')[0].index).toBe(0);
|
|
61
|
+
expect(collection.get('take')[0].index).toBe(1);
|
|
62
|
+
expect(collection.get('sort')[0].index).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('forEach iterates options in index order across types', () => {
|
|
66
|
+
const collection = QueryOptionsCollection.EMPTY<any>();
|
|
67
|
+
|
|
68
|
+
collection.add('skip', 1);
|
|
69
|
+
collection.add('take', 2);
|
|
70
|
+
collection.add('sort', { selector: (i: any) => i.id, direction: QueryOrdering.Descending, propertyName: 'id' });
|
|
71
|
+
collection.add('skip', 3);
|
|
72
|
+
collection.add('take', 4);
|
|
73
|
+
|
|
74
|
+
const namesInOrder: string[] = [];
|
|
75
|
+
collection.forEach((opt) => {
|
|
76
|
+
namesInOrder.push(opt.name);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const expectedByIndex = [...collection.items.values()]
|
|
80
|
+
.flat()
|
|
81
|
+
.sort((a, b) => a.index - b.index)
|
|
82
|
+
.map(i => i.option.name);
|
|
83
|
+
|
|
84
|
+
expect(namesInOrder).toEqual(expectedByIndex);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { forEach, isPropertyExpression } from "../../expressions/utils";
|
|
2
|
+
import { QueryOption, QueryOptionName, QueryOptionExecutionTarget, QueryOptionValueMap } from "./types";
|
|
3
|
+
|
|
4
|
+
export type QueryCollectionItem<T, K extends QueryOptionName> = { index: number, option: QueryOption<T, K> };
|
|
5
|
+
|
|
6
|
+
export class QueryOptionsCollection<T> {
|
|
7
|
+
|
|
8
|
+
private options: Map<QueryOptionName, QueryCollectionItem<any, any>[]> = new Map<QueryOptionName, QueryCollectionItem<any, any>[]>();
|
|
9
|
+
private nextExecutionTarget: QueryOptionExecutionTarget = "database";
|
|
10
|
+
private nextIndex: number = 0;
|
|
11
|
+
private enumeratedItems: QueryCollectionItem<any, any>[] = [];
|
|
12
|
+
|
|
13
|
+
get items() {
|
|
14
|
+
return this.options;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get isEmpty() {
|
|
18
|
+
return this.items.size === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static EMPTY<R>() {
|
|
22
|
+
return new QueryOptionsCollection<R>();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static isEmpty<T>(options: QueryOptionsCollection<T>) {
|
|
26
|
+
return options.isEmpty;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
add<K extends QueryOptionName>(name: K, value: QueryOption<T, K>["value"]) {
|
|
30
|
+
|
|
31
|
+
if (name === "map") {
|
|
32
|
+
const mapValue = value as QueryOptionValueMap<T>["map"];
|
|
33
|
+
|
|
34
|
+
// Evaluate the map value to see if we are renaming properties,
|
|
35
|
+
// if we are we need to perform everything after in memory
|
|
36
|
+
if (mapValue.fields.some(x => x.isRename === true) || mapValue.fields.some(x => x.property?.isUnmapped === true)) {
|
|
37
|
+
// Cut over to memory execution since we are renaming a property with .map
|
|
38
|
+
// We do not want to figure out how the new name flows through the entire query
|
|
39
|
+
this.nextExecutionTarget = "memory";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (name === "filter") {
|
|
44
|
+
// Need to check for unmapped properties
|
|
45
|
+
const filterValue = value as QueryOptionValueMap<T>["filter"];
|
|
46
|
+
|
|
47
|
+
if (filterValue.expression.type === "not-parsable") {
|
|
48
|
+
this.nextExecutionTarget = "memory";
|
|
49
|
+
} else {
|
|
50
|
+
forEach(filterValue.expression, (expression) => {
|
|
51
|
+
|
|
52
|
+
if (isPropertyExpression(expression) && expression.property.isUnmapped) {
|
|
53
|
+
// Cut over to memory execution, unmapped properties are not in the database and
|
|
54
|
+
// cannot be queried
|
|
55
|
+
this.nextExecutionTarget = "memory";
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const item: QueryCollectionItem<T, K> = {
|
|
65
|
+
index: this.nextIndex,
|
|
66
|
+
option: {
|
|
67
|
+
name,
|
|
68
|
+
target: this.nextExecutionTarget,
|
|
69
|
+
value
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.nextIndex++;
|
|
74
|
+
|
|
75
|
+
const found = this.options.get(name);
|
|
76
|
+
|
|
77
|
+
this.options.set(name, [...found ?? [], item]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
split(): { memory: QueryOptionsCollection<T>, database: QueryOptionsCollection<T> } {
|
|
81
|
+
this.resolveEnumeration();
|
|
82
|
+
|
|
83
|
+
const sortedItems = this.enumeratedItems.toSorted((a, b) => a.index - b.index);
|
|
84
|
+
const memoryQueryOptionsCollection = new QueryOptionsCollection<T>();
|
|
85
|
+
const databaseQueryOptionsCollection = new QueryOptionsCollection<T>();
|
|
86
|
+
|
|
87
|
+
for (let i = 0, length = sortedItems.length; i < length; i++) {
|
|
88
|
+
const sortedItem = sortedItems[i];
|
|
89
|
+
|
|
90
|
+
if (sortedItem.option.target === "database") {
|
|
91
|
+
databaseQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
memoryQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
memory: memoryQueryOptionsCollection,
|
|
100
|
+
database: databaseQueryOptionsCollection
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
has<K extends QueryOptionName>(name: K): boolean {
|
|
105
|
+
return this.options.has(name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get<K extends QueryOptionName>(name: K): QueryCollectionItem<T, K>[] {
|
|
109
|
+
return this.options.get(name) ?? [] as QueryCollectionItem<T, K>[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getLast<K extends QueryOptionName>(name: K): QueryOption<T, K> | null {
|
|
113
|
+
this.resolveEnumeration();
|
|
114
|
+
|
|
115
|
+
for (let i = this.enumeratedItems.length - 1; i >= 0; i--) {
|
|
116
|
+
const item = this.enumeratedItems[i];
|
|
117
|
+
|
|
118
|
+
if (item.option.name === name) {
|
|
119
|
+
return item.option;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getValues<K extends QueryOptionName>(name: K): QueryCollectionItem<T, K>["option"]["value"][] | undefined {
|
|
127
|
+
|
|
128
|
+
const found = this.options.get(name);
|
|
129
|
+
|
|
130
|
+
if (found == null) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return found.map(w => w.option.value) as QueryCollectionItem<T, K>["option"]["value"][];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private getEnumeration() {
|
|
138
|
+
return [...this.options.values()].flat().toSorted((a, b) => a.index - b.index);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private resolveEnumeration() {
|
|
142
|
+
if (this.enumeratedItems.length != this.nextIndex) {
|
|
143
|
+
this.enumeratedItems = this.getEnumeration();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
forEach(iterator: (item: QueryCollectionItem<T, any>["option"]) => void) {
|
|
148
|
+
this.resolveEnumeration();
|
|
149
|
+
|
|
150
|
+
for (let i = 0, length = this.enumeratedItems.length; i < length; i++) {
|
|
151
|
+
iterator(this.enumeratedItems[i].option);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Expression, Filter, ParamsFilter } from "../../expressions";
|
|
2
|
+
import { PropertyInfo } from "../../schema";
|
|
3
|
+
import { GenericFunction } from "../../types";
|
|
4
|
+
|
|
5
|
+
export enum QueryOrdering {
|
|
6
|
+
Descending = "desc",
|
|
7
|
+
Ascending = "asc"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Field mapping for a query result, including source and destination names and a getter function.
|
|
12
|
+
*/
|
|
13
|
+
export type QueryField = {
|
|
14
|
+
sourceName: string,
|
|
15
|
+
destinationName: string,
|
|
16
|
+
isRename: boolean;
|
|
17
|
+
property?: PropertyInfo<unknown>;
|
|
18
|
+
getter: <T>(data: Record<string, unknown>) => T;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type QueryOptionExecutionTarget = "database" | "memory";
|
|
22
|
+
export type QueryOptionName = keyof QueryOptionValueMap<unknown>;
|
|
23
|
+
|
|
24
|
+
export type QueryOption<T, K extends QueryOptionName> = {
|
|
25
|
+
name: QueryOptionName;
|
|
26
|
+
value: QueryOptionValueMap<T>[K],
|
|
27
|
+
target: QueryOptionExecutionTarget;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type QueryOptionValueMap<T extends {}> = {
|
|
31
|
+
skip: number;
|
|
32
|
+
take: number;
|
|
33
|
+
sort: { selector: GenericFunction<T, T[keyof T]>, direction: QueryOrdering, propertyName: string };
|
|
34
|
+
map: { selector: GenericFunction<T, any>, fields: QueryField[] };
|
|
35
|
+
filter: { params?: {}, filter: ParamsFilter<T, {}> | Filter<T>, expression: Expression };
|
|
36
|
+
min: true; // True or not set
|
|
37
|
+
max: true; // True or not set
|
|
38
|
+
count: true; // True or not set
|
|
39
|
+
sum: true; // True or not set
|
|
40
|
+
distinct: true; // True or not set
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sort specification for a query.
|
|
45
|
+
*/
|
|
46
|
+
export type QuerySort = { key: string, selector: (item: unknown) => unknown, direction: "asc" | "desc" };
|