@statezero/core 0.1.0
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/dist/adaptors/react/composables.d.ts +1 -0
- package/dist/adaptors/react/composables.js +4 -0
- package/dist/adaptors/react/index.d.ts +1 -0
- package/dist/adaptors/react/index.js +1 -0
- package/dist/adaptors/vue/composables.d.ts +2 -0
- package/dist/adaptors/vue/composables.js +36 -0
- package/dist/adaptors/vue/index.d.ts +2 -0
- package/dist/adaptors/vue/index.js +2 -0
- package/dist/adaptors/vue/reactivity.d.ts +18 -0
- package/dist/adaptors/vue/reactivity.js +125 -0
- package/dist/cli/commands/syncModels.d.ts +132 -0
- package/dist/cli/commands/syncModels.js +1040 -0
- package/dist/cli/configFileLoader.d.ts +10 -0
- package/dist/cli/configFileLoader.js +85 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +14 -0
- package/dist/config.d.ts +52 -0
- package/dist/config.js +242 -0
- package/dist/core/eventReceivers.d.ts +179 -0
- package/dist/core/eventReceivers.js +210 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +62 -0
- package/dist/filtering/localFiltering.d.ts +116 -0
- package/dist/filtering/localFiltering.js +834 -0
- package/dist/flavours/django/dates.d.ts +33 -0
- package/dist/flavours/django/dates.js +99 -0
- package/dist/flavours/django/errors.d.ts +138 -0
- package/dist/flavours/django/errors.js +187 -0
- package/dist/flavours/django/f.d.ts +6 -0
- package/dist/flavours/django/f.js +91 -0
- package/dist/flavours/django/files.d.ts +76 -0
- package/dist/flavours/django/files.js +338 -0
- package/dist/flavours/django/makeApiCall.d.ts +20 -0
- package/dist/flavours/django/makeApiCall.js +169 -0
- package/dist/flavours/django/manager.d.ts +197 -0
- package/dist/flavours/django/manager.js +222 -0
- package/dist/flavours/django/model.d.ts +112 -0
- package/dist/flavours/django/model.js +253 -0
- package/dist/flavours/django/operationFactory.d.ts +65 -0
- package/dist/flavours/django/operationFactory.js +216 -0
- package/dist/flavours/django/q.d.ts +70 -0
- package/dist/flavours/django/q.js +43 -0
- package/dist/flavours/django/queryExecutor.d.ts +131 -0
- package/dist/flavours/django/queryExecutor.js +468 -0
- package/dist/flavours/django/querySet.d.ts +412 -0
- package/dist/flavours/django/querySet.js +601 -0
- package/dist/flavours/django/tempPk.d.ts +19 -0
- package/dist/flavours/django/tempPk.js +48 -0
- package/dist/flavours/django/utils.d.ts +19 -0
- package/dist/flavours/django/utils.js +29 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +38 -0
- package/dist/react-entry.d.ts +2 -0
- package/dist/react-entry.js +2 -0
- package/dist/reactiveAdaptor.d.ts +24 -0
- package/dist/reactiveAdaptor.js +38 -0
- package/dist/setup.d.ts +15 -0
- package/dist/setup.js +22 -0
- package/dist/syncEngine/cache/cache.d.ts +75 -0
- package/dist/syncEngine/cache/cache.js +341 -0
- package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
- package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
- package/dist/syncEngine/registries/metricRegistry.d.ts +53 -0
- package/dist/syncEngine/registries/metricRegistry.js +162 -0
- package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
- package/dist/syncEngine/registries/modelStoreRegistry.js +56 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +55 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.js +244 -0
- package/dist/syncEngine/stores/metricStore.d.ts +55 -0
- package/dist/syncEngine/stores/metricStore.js +222 -0
- package/dist/syncEngine/stores/modelStore.d.ts +40 -0
- package/dist/syncEngine/stores/modelStore.js +405 -0
- package/dist/syncEngine/stores/operation.d.ts +99 -0
- package/dist/syncEngine/stores/operation.js +224 -0
- package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
- package/dist/syncEngine/stores/operationEventHandlers.js +239 -0
- package/dist/syncEngine/stores/querysetStore.d.ts +32 -0
- package/dist/syncEngine/stores/querysetStore.js +200 -0
- package/dist/syncEngine/stores/reactivity.d.ts +3 -0
- package/dist/syncEngine/stores/reactivity.js +4 -0
- package/dist/syncEngine/stores/utils.d.ts +14 -0
- package/dist/syncEngine/stores/utils.js +32 -0
- package/dist/syncEngine/sync.d.ts +32 -0
- package/dist/syncEngine/sync.js +169 -0
- package/dist/vue-entry.d.ts +6 -0
- package/dist/vue-entry.js +2 -0
- package/license.md +116 -0
- package/package.json +123 -0
- package/readme.md +222 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dynamic wrapper that always returns the latest queryset results
|
|
3
|
+
* This class proxies array operations to always reflect the current state
|
|
4
|
+
* of the underlying QuerysetStore.
|
|
5
|
+
*/
|
|
6
|
+
export class LiveQueryset {
|
|
7
|
+
constructor(queryset: any);
|
|
8
|
+
/**
|
|
9
|
+
* Serializes the lqs as a simple array of objects, for freezing e.g in the metric stores
|
|
10
|
+
*/
|
|
11
|
+
serialize(): any;
|
|
12
|
+
/**
|
|
13
|
+
* Get the current items from the store
|
|
14
|
+
* @private
|
|
15
|
+
* @returns {Array} The current items in the queryset
|
|
16
|
+
*/
|
|
17
|
+
private getCurrentItems;
|
|
18
|
+
#private;
|
|
19
|
+
}
|
|
20
|
+
export const querysetStoreRegistry: QuerysetStoreRegistry;
|
|
21
|
+
declare class QuerysetStoreRegistry {
|
|
22
|
+
_stores: Map<any, any>;
|
|
23
|
+
_tempStores: WeakMap<object, any>;
|
|
24
|
+
followingQuerysets: Map<any, any>;
|
|
25
|
+
syncManager: () => void;
|
|
26
|
+
clear(): void;
|
|
27
|
+
setSyncManager(syncManager: any): void;
|
|
28
|
+
/**
|
|
29
|
+
* Add a queryset to the following set for a semantic key
|
|
30
|
+
*/
|
|
31
|
+
addFollowingQueryset(semanticKey: any, queryset: any): void;
|
|
32
|
+
getStore(queryset: any, seed?: boolean): any;
|
|
33
|
+
/**
|
|
34
|
+
* Get the current state of the queryset, wrapped in a LiveQueryset
|
|
35
|
+
* @param {Object} queryset - The queryset
|
|
36
|
+
* @param {Boolean} seed - Should we optimistically seed the queryset with relevant items from the parent?
|
|
37
|
+
* @param {Boolean} sync - Schedule a sync of the queryset with the backend
|
|
38
|
+
* @returns {LiveQueryset} - A live view of the queryset
|
|
39
|
+
*/
|
|
40
|
+
getEntity(queryset: Object, seed?: boolean, sync?: boolean): LiveQueryset;
|
|
41
|
+
/**
|
|
42
|
+
* Set ground truth for a queryset
|
|
43
|
+
* @param {Object} queryset - The queryset
|
|
44
|
+
* @param {Array} instances - Array of instances to set as ground truth
|
|
45
|
+
* @returns {Array} - The set instances
|
|
46
|
+
*/
|
|
47
|
+
setEntity(queryset: Object, instances: any[]): any[];
|
|
48
|
+
/**
|
|
49
|
+
* Get all queryset stores for a specific model class
|
|
50
|
+
* @param {ModelClass} ModelClass - The model class to get stores for
|
|
51
|
+
* @returns {Array} - Array of queryset stores for this model
|
|
52
|
+
*/
|
|
53
|
+
getAllStoresForModel(ModelClass: any): any[];
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _LiveQueryset_queryset, _LiveQueryset_ModelClass, _LiveQueryset_proxy, _LiveQueryset_array;
|
|
13
|
+
import { QuerysetStore } from '../stores/querysetStore.js';
|
|
14
|
+
import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
15
|
+
import { wrapReactiveQuerySet } from '../../reactiveAdaptor.js';
|
|
16
|
+
import { processQuery, getRequiredFields, pickRequiredFields } from '../../filtering/localFiltering.js';
|
|
17
|
+
import { filter } from '../../filtering/localFiltering.js';
|
|
18
|
+
import { makeApiCall } from '../../flavours/django/makeApiCall.js';
|
|
19
|
+
import { isNil, pick } from 'lodash-es';
|
|
20
|
+
import hash from 'object-hash';
|
|
21
|
+
import { Operation } from '../stores/operation.js';
|
|
22
|
+
import { Cache } from '../cache/cache.js';
|
|
23
|
+
/**
|
|
24
|
+
* A dynamic wrapper that always returns the latest queryset results
|
|
25
|
+
* This class proxies array operations to always reflect the current state
|
|
26
|
+
* of the underlying QuerysetStore.
|
|
27
|
+
*/
|
|
28
|
+
export class LiveQueryset {
|
|
29
|
+
constructor(queryset) {
|
|
30
|
+
_LiveQueryset_queryset.set(this, void 0);
|
|
31
|
+
_LiveQueryset_ModelClass.set(this, void 0);
|
|
32
|
+
_LiveQueryset_proxy.set(this, void 0);
|
|
33
|
+
_LiveQueryset_array.set(this, []);
|
|
34
|
+
// used internally
|
|
35
|
+
__classPrivateFieldSet(this, _LiveQueryset_queryset, queryset, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _LiveQueryset_ModelClass, queryset.ModelClass, "f");
|
|
37
|
+
__classPrivateFieldGet(this, _LiveQueryset_array, "f").queryset = queryset;
|
|
38
|
+
__classPrivateFieldGet(this, _LiveQueryset_array, "f").ModelClass = queryset.ModelClass;
|
|
39
|
+
// Create a proxy that intercepts all array access
|
|
40
|
+
__classPrivateFieldSet(this, _LiveQueryset_proxy, new Proxy(__classPrivateFieldGet(this, _LiveQueryset_array, "f"), {
|
|
41
|
+
get: (target, prop, receiver) => {
|
|
42
|
+
// Expose the touch method through the proxy
|
|
43
|
+
if (prop === 'touch') {
|
|
44
|
+
return () => this.touch();
|
|
45
|
+
}
|
|
46
|
+
if (prop === 'serialize') {
|
|
47
|
+
return () => this.serialize();
|
|
48
|
+
}
|
|
49
|
+
// Special handling for iterators and common array methods
|
|
50
|
+
if (prop === Symbol.iterator) {
|
|
51
|
+
return () => this.getCurrentItems()[Symbol.iterator]();
|
|
52
|
+
}
|
|
53
|
+
else if (typeof prop === 'string' && ['forEach', 'map', 'filter', 'reduce', 'some', 'every', 'find'].includes(prop)) {
|
|
54
|
+
return (...args) => this.getCurrentItems()[prop](...args);
|
|
55
|
+
}
|
|
56
|
+
else if (prop === 'length') {
|
|
57
|
+
return this.getCurrentItems().length;
|
|
58
|
+
}
|
|
59
|
+
else if (typeof prop === 'string' && !isNaN(parseInt(prop))) {
|
|
60
|
+
// Handle numeric indices
|
|
61
|
+
return this.getCurrentItems()[prop];
|
|
62
|
+
}
|
|
63
|
+
return target[prop];
|
|
64
|
+
}
|
|
65
|
+
}), "f");
|
|
66
|
+
return __classPrivateFieldGet(this, _LiveQueryset_proxy, "f");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Serializes the lqs as a simple array of objects, for freezing e.g in the metric stores
|
|
70
|
+
*/
|
|
71
|
+
serialize() {
|
|
72
|
+
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
73
|
+
// Get the current primary keys from the store
|
|
74
|
+
const pks = store.render();
|
|
75
|
+
// Map primary keys to full model objects
|
|
76
|
+
return pks.map(pk => {
|
|
77
|
+
// Get the full model instance from the model store
|
|
78
|
+
const pkField = __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").primaryKeyField;
|
|
79
|
+
return __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").fromPk(pk, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f")).serialize();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the current items from the store
|
|
84
|
+
* @private
|
|
85
|
+
* @returns {Array} The current items in the queryset
|
|
86
|
+
*/
|
|
87
|
+
getCurrentItems(sortAndFilter = true) {
|
|
88
|
+
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
89
|
+
// Get the current primary keys from the store
|
|
90
|
+
const pks = store.render();
|
|
91
|
+
// Map primary keys to full model objects
|
|
92
|
+
const instances = pks.map(pk => {
|
|
93
|
+
// Get the full model instance from the model store
|
|
94
|
+
const pkField = __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").primaryKeyField;
|
|
95
|
+
return __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").fromPk(pk, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
96
|
+
});
|
|
97
|
+
if (!sortAndFilter)
|
|
98
|
+
return instances;
|
|
99
|
+
return filter(instances, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f").build(), __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f"), true);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
_LiveQueryset_queryset = new WeakMap(), _LiveQueryset_ModelClass = new WeakMap(), _LiveQueryset_proxy = new WeakMap(), _LiveQueryset_array = new WeakMap();
|
|
103
|
+
class QuerysetStoreRegistry {
|
|
104
|
+
constructor() {
|
|
105
|
+
this._stores = new Map(); // Map<semanticKey, Store>
|
|
106
|
+
this._tempStores = new WeakMap(); // WeakMap<Queryset, Store>
|
|
107
|
+
this.followingQuerysets = new Map(); // Map<semanticKey, Set<queryset>>
|
|
108
|
+
this.syncManager = () => { console.warn("SyncManager not set for QuerysetStoreRegistry"); };
|
|
109
|
+
}
|
|
110
|
+
clear() {
|
|
111
|
+
this._stores.forEach((store) => {
|
|
112
|
+
this.syncManager.unfollowModel(this, store.modelClass);
|
|
113
|
+
});
|
|
114
|
+
this._stores = new Map();
|
|
115
|
+
this.followingQuerysets = new Map();
|
|
116
|
+
}
|
|
117
|
+
setSyncManager(syncManager) {
|
|
118
|
+
this.syncManager = syncManager;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Add a queryset to the following set for a semantic key
|
|
122
|
+
*/
|
|
123
|
+
addFollowingQueryset(semanticKey, queryset) {
|
|
124
|
+
if (!this.followingQuerysets.has(semanticKey)) {
|
|
125
|
+
this.followingQuerysets.set(semanticKey, new Set());
|
|
126
|
+
}
|
|
127
|
+
this.followingQuerysets.get(semanticKey).add(queryset);
|
|
128
|
+
}
|
|
129
|
+
getStore(queryset, seed = false) {
|
|
130
|
+
if (isNil(queryset) || isNil(queryset.ModelClass)) {
|
|
131
|
+
throw new Error("QuerysetStoreRegistry.getStore requires a valid queryset");
|
|
132
|
+
}
|
|
133
|
+
// Check if we already have a temporary store for this exact QuerySet instance
|
|
134
|
+
if (this._tempStores.has(queryset)) {
|
|
135
|
+
return this._tempStores.get(queryset);
|
|
136
|
+
}
|
|
137
|
+
// Get the semanticKey
|
|
138
|
+
const semanticKey = queryset.semanticKey;
|
|
139
|
+
// Check if we have a permanent store with this semanticKey
|
|
140
|
+
if (this._stores.has(semanticKey)) {
|
|
141
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
142
|
+
return this._stores.get(semanticKey);
|
|
143
|
+
}
|
|
144
|
+
// Create a new temporary store
|
|
145
|
+
const fetchQueryset = async ({ ast, modelClass }) => {
|
|
146
|
+
// Directly assemble the request and call the API to avoid recursive logic from the
|
|
147
|
+
// queryset back to the registry / store
|
|
148
|
+
const payload = {
|
|
149
|
+
...ast,
|
|
150
|
+
type: 'list'
|
|
151
|
+
};
|
|
152
|
+
const response = await makeApiCall(queryset, 'list', payload);
|
|
153
|
+
return response.data;
|
|
154
|
+
};
|
|
155
|
+
let initialGroundTruthPks = null;
|
|
156
|
+
let ast = queryset.build();
|
|
157
|
+
let ModelClass = queryset.ModelClass;
|
|
158
|
+
if (queryset.__parent && seed) {
|
|
159
|
+
let parentLiveQuerySet = this.getEntity(queryset.__parent);
|
|
160
|
+
initialGroundTruthPks = filter(parentLiveQuerySet, ast, ModelClass, false);
|
|
161
|
+
}
|
|
162
|
+
// Get the parent registry
|
|
163
|
+
const store = new QuerysetStore(ModelClass, fetchQueryset, queryset, initialGroundTruthPks, // Initial ground truth PKs
|
|
164
|
+
null // Initial operations
|
|
165
|
+
);
|
|
166
|
+
// Store it in the temp store map
|
|
167
|
+
this._tempStores.set(queryset, store);
|
|
168
|
+
return store;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get the current state of the queryset, wrapped in a LiveQueryset
|
|
172
|
+
* @param {Object} queryset - The queryset
|
|
173
|
+
* @param {Boolean} seed - Should we optimistically seed the queryset with relevant items from the parent?
|
|
174
|
+
* @param {Boolean} sync - Schedule a sync of the queryset with the backend
|
|
175
|
+
* @returns {LiveQueryset} - A live view of the queryset
|
|
176
|
+
*/
|
|
177
|
+
getEntity(queryset, seed = true, sync = false) {
|
|
178
|
+
if (isNil(queryset))
|
|
179
|
+
throw new Error(`qsStoreRegistry: getEntity cannot be called without a queryset`);
|
|
180
|
+
const semanticKey = queryset.semanticKey;
|
|
181
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
182
|
+
let store;
|
|
183
|
+
// If we have a temporary store, promote it
|
|
184
|
+
if (this._tempStores.has(queryset)) {
|
|
185
|
+
store = this._tempStores.get(queryset);
|
|
186
|
+
this._stores.set(semanticKey, store);
|
|
187
|
+
this.syncManager.followModel(this, queryset.ModelClass);
|
|
188
|
+
}
|
|
189
|
+
// Otherwise, ensure we have a permanent store
|
|
190
|
+
else if (!this._stores.has(semanticKey)) {
|
|
191
|
+
store = this.getStore(queryset, seed);
|
|
192
|
+
this._stores.set(semanticKey, store);
|
|
193
|
+
this.syncManager.followModel(this, queryset.ModelClass);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
store = this._stores.get(semanticKey);
|
|
197
|
+
}
|
|
198
|
+
const liveQueryset = new LiveQueryset(queryset);
|
|
199
|
+
if (sync)
|
|
200
|
+
store.sync();
|
|
201
|
+
return wrapReactiveQuerySet(liveQueryset);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set ground truth for a queryset
|
|
205
|
+
* @param {Object} queryset - The queryset
|
|
206
|
+
* @param {Array} instances - Array of instances to set as ground truth
|
|
207
|
+
* @returns {Array} - The set instances
|
|
208
|
+
*/
|
|
209
|
+
setEntity(queryset, instances) {
|
|
210
|
+
if (isNil(queryset) || isNil(instances))
|
|
211
|
+
return [];
|
|
212
|
+
const semanticKey = queryset.semanticKey;
|
|
213
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
214
|
+
let store;
|
|
215
|
+
if (this._stores.has(semanticKey)) {
|
|
216
|
+
store = this._stores.get(semanticKey);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// If we have a temp store, promote it
|
|
220
|
+
if (this._tempStores.has(queryset)) {
|
|
221
|
+
store = this._tempStores.get(queryset);
|
|
222
|
+
this._stores.set(semanticKey, store);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// Create a new permanent store
|
|
226
|
+
store = this.getStore(queryset);
|
|
227
|
+
this._stores.set(semanticKey, store);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
store.setGroundTruth(instances.map(instance => instance[queryset.ModelClass.primaryKeyField] || instance));
|
|
231
|
+
return instances;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get all queryset stores for a specific model class
|
|
235
|
+
* @param {ModelClass} ModelClass - The model class to get stores for
|
|
236
|
+
* @returns {Array} - Array of queryset stores for this model
|
|
237
|
+
*/
|
|
238
|
+
getAllStoresForModel(ModelClass) {
|
|
239
|
+
if (!ModelClass)
|
|
240
|
+
return [];
|
|
241
|
+
return Array.from(this._stores.values()).filter(store => store.modelClass === ModelClass);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export const querysetStoreRegistry = new QuerysetStoreRegistry();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store for managing a single metric with optimistic updates
|
|
3
|
+
*/
|
|
4
|
+
export class MetricStore {
|
|
5
|
+
constructor(metricType: any, modelClass: any, queryset: any, field: null | undefined, ast: null | undefined, fetchFn: any);
|
|
6
|
+
metricType: any;
|
|
7
|
+
modelClass: any;
|
|
8
|
+
queryset: any;
|
|
9
|
+
field: any;
|
|
10
|
+
ast: any;
|
|
11
|
+
fetchFn: any;
|
|
12
|
+
groundTruthValue: any;
|
|
13
|
+
isSyncing: boolean;
|
|
14
|
+
strategy: import("../metrics/metricOptCalcs").MetricCalculationStrategy;
|
|
15
|
+
operations: any[];
|
|
16
|
+
confirmedOps: Set<any>;
|
|
17
|
+
metricCache: Cache;
|
|
18
|
+
_lastCalculatedValue: any;
|
|
19
|
+
reset(): void;
|
|
20
|
+
get cacheKey(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Add an operation to this metric store
|
|
23
|
+
* @param {Operation} operation - The operation to add
|
|
24
|
+
*/
|
|
25
|
+
addOperation(operation: Operation): void;
|
|
26
|
+
/**
|
|
27
|
+
* Update an operation in this metric store
|
|
28
|
+
* @param {Operation} operation - The operation to update
|
|
29
|
+
*/
|
|
30
|
+
updateOperation(operation: Operation): void;
|
|
31
|
+
/**
|
|
32
|
+
* Confirm an operation in this metric store
|
|
33
|
+
* @param {Operation} operation - The operation to confirm
|
|
34
|
+
*/
|
|
35
|
+
confirm(operation: Operation): void;
|
|
36
|
+
/**
|
|
37
|
+
* Reject an operation in this metric store
|
|
38
|
+
* @param {Operation} operation - The operation to reject
|
|
39
|
+
*/
|
|
40
|
+
reject(operation: Operation): void;
|
|
41
|
+
onHydrated(): void;
|
|
42
|
+
setCache(): void;
|
|
43
|
+
clearCache(): void;
|
|
44
|
+
setGroundTruth(value: any): void;
|
|
45
|
+
/**
|
|
46
|
+
* Render the metric with current operations
|
|
47
|
+
* @returns {any} Calculated metric value
|
|
48
|
+
*/
|
|
49
|
+
render(): any;
|
|
50
|
+
/**
|
|
51
|
+
* Sync metric with server
|
|
52
|
+
*/
|
|
53
|
+
sync(): Promise<any>;
|
|
54
|
+
}
|
|
55
|
+
import { Cache } from '../cache/cache.js';
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Cache } from '../cache/cache.js';
|
|
2
|
+
import { MetricStrategyFactory } from "../metrics/metricOptCalcs";
|
|
3
|
+
import hash from 'object-hash';
|
|
4
|
+
import { isNil, isEmpty, isEqual } from 'lodash-es';
|
|
5
|
+
import { metricEventEmitter } from './reactivity.js';
|
|
6
|
+
import { Status } from './operation.js';
|
|
7
|
+
/**
|
|
8
|
+
* Store for managing a single metric with optimistic updates
|
|
9
|
+
*/
|
|
10
|
+
export class MetricStore {
|
|
11
|
+
constructor(metricType, modelClass, queryset, field = null, ast = null, fetchFn) {
|
|
12
|
+
this.metricType = metricType;
|
|
13
|
+
this.modelClass = modelClass;
|
|
14
|
+
this.queryset = queryset;
|
|
15
|
+
this.field = field;
|
|
16
|
+
this.ast = ast;
|
|
17
|
+
this.fetchFn = fetchFn;
|
|
18
|
+
this.groundTruthValue = null;
|
|
19
|
+
this.isSyncing = false;
|
|
20
|
+
this.strategy = MetricStrategyFactory.getStrategy(metricType, modelClass);
|
|
21
|
+
// Store operations related to this metric
|
|
22
|
+
this.operations = [];
|
|
23
|
+
// Keep track of which operations have been confirmed
|
|
24
|
+
this.confirmedOps = new Set();
|
|
25
|
+
// Initialize cache with AST-specific key
|
|
26
|
+
this.metricCache = new Cache("metric-store-cache", {}, this.onHydrated.bind(this));
|
|
27
|
+
// Initialize _lastCalculatedValue
|
|
28
|
+
this._lastCalculatedValue = null;
|
|
29
|
+
}
|
|
30
|
+
reset() {
|
|
31
|
+
this.groundTruthValue = null;
|
|
32
|
+
this._lastCalculatedValue = null;
|
|
33
|
+
this.operations = [];
|
|
34
|
+
this.confirmedOps = new Set();
|
|
35
|
+
this.isSyncing = false;
|
|
36
|
+
this.clearCache();
|
|
37
|
+
}
|
|
38
|
+
get cacheKey() {
|
|
39
|
+
return `${this.modelClass.configKey}::${this.modelClass.modelName}::metric::${this.metricType}::${this.field || "null"}::${this.ast ? hash(this.ast) : "global"}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Add an operation to this metric store
|
|
43
|
+
* @param {Operation} operation - The operation to add
|
|
44
|
+
*/
|
|
45
|
+
addOperation(operation) {
|
|
46
|
+
// Only track operations for this model
|
|
47
|
+
if (operation.queryset.ModelClass !== this.modelClass) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Check if operation already exists to avoid duplicates
|
|
51
|
+
const existingIndex = this.operations.findIndex((op) => op.operationId === operation.operationId);
|
|
52
|
+
if (existingIndex !== -1) {
|
|
53
|
+
// Update existing operation instead of adding a duplicate
|
|
54
|
+
this.operations[existingIndex] = operation;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Add to our operations list
|
|
58
|
+
this.operations.push(operation);
|
|
59
|
+
}
|
|
60
|
+
// Trigger a render to update the metric value
|
|
61
|
+
if (!isNil(this.groundTruthValue)) {
|
|
62
|
+
this.render();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Update an operation in this metric store
|
|
67
|
+
* @param {Operation} operation - The operation to update
|
|
68
|
+
*/
|
|
69
|
+
updateOperation(operation) {
|
|
70
|
+
// Only track operations for this model
|
|
71
|
+
if (operation.queryset.ModelClass !== this.modelClass) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Find and update the operation - use operationId not id
|
|
75
|
+
const index = this.operations.findIndex((op) => op.operationId === operation.operationId);
|
|
76
|
+
if (index !== -1) {
|
|
77
|
+
this.operations[index] = operation;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// If not found, add it
|
|
81
|
+
this.operations.push(operation);
|
|
82
|
+
}
|
|
83
|
+
// Trigger a render to update the metric value
|
|
84
|
+
if (!isNil(this.groundTruthValue)) {
|
|
85
|
+
this.render();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Confirm an operation in this metric store
|
|
90
|
+
* @param {Operation} operation - The operation to confirm
|
|
91
|
+
*/
|
|
92
|
+
confirm(operation) {
|
|
93
|
+
// Only track operations for this model
|
|
94
|
+
if (operation.queryset.ModelClass !== this.modelClass) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Update operation status in our list and mark as confirmed - use operationId not id
|
|
98
|
+
const index = this.operations.findIndex((op) => op.operationId === operation.operationId);
|
|
99
|
+
if (index !== -1) {
|
|
100
|
+
this.operations[index] = operation;
|
|
101
|
+
this.confirmedOps.add(operation.operationId);
|
|
102
|
+
}
|
|
103
|
+
// Trigger a sync to update ground truth
|
|
104
|
+
this.render();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reject an operation in this metric store
|
|
108
|
+
* @param {Operation} operation - The operation to reject
|
|
109
|
+
*/
|
|
110
|
+
reject(operation) {
|
|
111
|
+
// Only track operations for this model
|
|
112
|
+
if (operation.queryset.ModelClass !== this.modelClass) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Remove the operation as it's now rejected - use operationId not id
|
|
116
|
+
this.operations = this.operations.filter((op) => op.operationId !== operation.operationId);
|
|
117
|
+
this.confirmedOps.delete(operation.operationId);
|
|
118
|
+
// Trigger a render to update the metric value
|
|
119
|
+
if (!isNil(this.groundTruthValue)) {
|
|
120
|
+
this.render();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
onHydrated() {
|
|
124
|
+
if (this.groundTruthValue === null) {
|
|
125
|
+
const cached = this.metricCache.get(this.cacheKey);
|
|
126
|
+
if (!isNil(cached) && !isEmpty(cached)) {
|
|
127
|
+
console.log(`[MetricStore] Hydrated ${this.metricType} metric for ${this.modelClass.modelName} from cache`);
|
|
128
|
+
this.setGroundTruth(cached?.value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
setCache() {
|
|
133
|
+
let groundTruthValue = this.groundTruthValue;
|
|
134
|
+
let serializedGroundTruth = groundTruthValue?.value
|
|
135
|
+
? groundTruthValue.value
|
|
136
|
+
: groundTruthValue;
|
|
137
|
+
this.metricCache.set(this.cacheKey, {
|
|
138
|
+
value: serializedGroundTruth,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
clearCache() {
|
|
142
|
+
this.metricCache.delete(this.cacheKey);
|
|
143
|
+
}
|
|
144
|
+
setGroundTruth(value) {
|
|
145
|
+
// Check if the value has actually changed
|
|
146
|
+
const valueChanged = !isEqual(this.groundTruthValue, value);
|
|
147
|
+
this.groundTruthValue = value;
|
|
148
|
+
this.setCache();
|
|
149
|
+
// Only emit event if the value changed
|
|
150
|
+
if (valueChanged) {
|
|
151
|
+
metricEventEmitter.emit("metric::render", {
|
|
152
|
+
metricType: this.metricType,
|
|
153
|
+
ModelClass: this.modelClass,
|
|
154
|
+
field: this.field,
|
|
155
|
+
ast: hash(this.ast),
|
|
156
|
+
valueChanged: true,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Render the metric with current operations
|
|
162
|
+
* @returns {any} Calculated metric value
|
|
163
|
+
*/
|
|
164
|
+
render() {
|
|
165
|
+
// Check if ground truth value is null
|
|
166
|
+
if (isNil(this.groundTruthValue)) {
|
|
167
|
+
console.log(`groundTruthValue is null, returning null`);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
// Calculate the new value using the operations-based approach
|
|
171
|
+
const newValue = this.strategy.calculateWithOperations(this.groundTruthValue, this.operations, this.field, this.modelClass);
|
|
172
|
+
// Check if the value has actually changed
|
|
173
|
+
if (!isEqual(this._lastCalculatedValue, newValue)) {
|
|
174
|
+
this._lastCalculatedValue = newValue;
|
|
175
|
+
// Only emit event if the value changed
|
|
176
|
+
metricEventEmitter.emit("metric::render", {
|
|
177
|
+
metricType: this.metricType,
|
|
178
|
+
ModelClass: this.modelClass,
|
|
179
|
+
field: this.field,
|
|
180
|
+
ast: hash(this.ast),
|
|
181
|
+
valueChanged: true,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return newValue;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Sync metric with server
|
|
188
|
+
*/
|
|
189
|
+
async sync() {
|
|
190
|
+
if (this.isSyncing) {
|
|
191
|
+
console.warn(`[MetricStore] Already syncing ${this.metricType} for ${this.modelClass.modelName}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.isSyncing = true;
|
|
195
|
+
try {
|
|
196
|
+
console.log(`[MetricStore] Syncing ${this.metricType} metric for ${this.modelClass.modelName}`);
|
|
197
|
+
// Use fetchFn to get server metric value
|
|
198
|
+
const result = await this.fetchFn({
|
|
199
|
+
metricType: this.metricType,
|
|
200
|
+
modelClass: this.modelClass,
|
|
201
|
+
field: this.field,
|
|
202
|
+
ast: this.ast,
|
|
203
|
+
});
|
|
204
|
+
// After syncing, remove all confirmed operations
|
|
205
|
+
if (this.confirmedOps.size > 0) {
|
|
206
|
+
this.operations = this.operations.filter((op) => !this.confirmedOps.has(op.operationId));
|
|
207
|
+
this.confirmedOps.clear();
|
|
208
|
+
}
|
|
209
|
+
// Update ground truth
|
|
210
|
+
this.setGroundTruth(result);
|
|
211
|
+
console.log(`[MetricStore] Synced ${this.metricType} metric with value:`, result);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
console.error(`[MetricStore] Failed to sync ${this.metricType} metric:`, error);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
this.isSyncing = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class ModelStore {
|
|
2
|
+
constructor(modelClass: any, fetchFn: any, initialGroundTruth?: null, initialOperations?: null, options?: {});
|
|
3
|
+
modelClass: any;
|
|
4
|
+
fetchFn: any;
|
|
5
|
+
groundTruthArray: never[];
|
|
6
|
+
operationsMap: Map<any, any>;
|
|
7
|
+
isSyncing: boolean;
|
|
8
|
+
pruneThreshold: any;
|
|
9
|
+
modelCache: Cache;
|
|
10
|
+
/**
|
|
11
|
+
* Load operations from data and add them to the operations map,
|
|
12
|
+
* reusing existing operations from the registry if they exist
|
|
13
|
+
*/
|
|
14
|
+
_loadOperations(operationsData: any): void;
|
|
15
|
+
get cacheKey(): string;
|
|
16
|
+
onHydrated(): void;
|
|
17
|
+
setCache(result: any): void;
|
|
18
|
+
clearCache(): void;
|
|
19
|
+
updateCache(items: any, requestedPks: any): void;
|
|
20
|
+
get operations(): any[];
|
|
21
|
+
get pkField(): any;
|
|
22
|
+
addOperation(operation: any): void;
|
|
23
|
+
updateOperation(operation: any): boolean;
|
|
24
|
+
confirm(operation: any): void;
|
|
25
|
+
reject(operation: any): void;
|
|
26
|
+
setOperations(operations?: any[]): void;
|
|
27
|
+
setGroundTruth(groundTruth: any): void;
|
|
28
|
+
getGroundTruth(): never[];
|
|
29
|
+
get groundTruthPks(): never[];
|
|
30
|
+
addToGroundTruth(instances: any): void;
|
|
31
|
+
_filteredOperations(pks: any, operations: any): any;
|
|
32
|
+
_filteredGroundTruth(pks: any, groundTruthArray: any): Map<any, any>;
|
|
33
|
+
applyOperation(operation: any, currentInstances: any): any;
|
|
34
|
+
getTrimmedOperations(): any[];
|
|
35
|
+
getInflightOperations(): any[];
|
|
36
|
+
prune(): void;
|
|
37
|
+
render(pks?: null, optimistic?: boolean): any[];
|
|
38
|
+
sync(pks?: null): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
import { Cache } from '../cache/cache.js';
|