@statezero/core 0.2.52 → 0.2.53
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/vue/components/StateZeroDebugPanel.js +465 -457
- package/dist/adaptors/vue/composables.d.ts +1 -1
- package/dist/adaptors/vue/composables.js +8 -2
- package/dist/adaptors/vue/reactivity.js +3 -3
- package/dist/syncEngine/cache/cache.d.ts +1 -1
- package/dist/syncEngine/cache/cache.js +8 -1
- package/dist/syncEngine/registries/metricRegistry.d.ts +14 -3
- package/dist/syncEngine/registries/metricRegistry.js +20 -6
- package/dist/syncEngine/stores/querysetStore.js +18 -6
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export function useQueryset(querysetFactory: any): import("vue").ComputedRef<any>;
|
|
1
|
+
export function useQueryset(querysetFactory: any): import("vue").Ref<any, any> | import("vue").ComputedRef<any>;
|
|
2
2
|
export const querysets: Map<any, any>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { computed, getCurrentInstance, onBeforeUnmount } from 'vue';
|
|
1
|
+
import { computed, isRef, getCurrentInstance, onBeforeUnmount } from 'vue';
|
|
2
2
|
import { querysetStoreRegistry } from '../../syncEngine/registries/querysetStoreRegistry.js';
|
|
3
|
-
import { metricRegistry } from '../../syncEngine/registries/metricRegistry.js';
|
|
3
|
+
import { metricRegistry, LiveMetric } from '../../syncEngine/registries/metricRegistry.js';
|
|
4
4
|
import { syncManager } from '../../syncEngine/sync.js';
|
|
5
5
|
import { registerAdapterReset } from '../../reset.js';
|
|
6
6
|
import { v7 } from 'uuid';
|
|
@@ -22,6 +22,12 @@ export function useQueryset(querysetFactory) {
|
|
|
22
22
|
querysets.delete(composableId);
|
|
23
23
|
updateSyncManager();
|
|
24
24
|
});
|
|
25
|
+
const result = querysetFactory();
|
|
26
|
+
// Metrics: the adaptor already returns a ref(scalar) — return it directly.
|
|
27
|
+
// Wrapping in another computed would cause .value.value in templates.
|
|
28
|
+
if (isRef(result) && result.original instanceof LiveMetric) {
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
25
31
|
return computed(() => {
|
|
26
32
|
const result = querysetFactory();
|
|
27
33
|
const original = result?.original || result;
|
|
@@ -173,7 +173,7 @@ export function MetricAdaptor(metric) {
|
|
|
173
173
|
return wrappedMetricCache.get(cacheKey);
|
|
174
174
|
}
|
|
175
175
|
// Create a reactive reference with the initial value
|
|
176
|
-
const wrapper = ref(metric.
|
|
176
|
+
const wrapper = ref(metric.valueOf());
|
|
177
177
|
wrapper.original = metric;
|
|
178
178
|
// Single handler for metric render events
|
|
179
179
|
const metricRenderHandler = (eventData) => {
|
|
@@ -183,9 +183,9 @@ export function MetricAdaptor(metric) {
|
|
|
183
183
|
eventData.field === metric.field &&
|
|
184
184
|
eventData.ast === hash(querysetAst) &&
|
|
185
185
|
eventData.valueChanged === true) {
|
|
186
|
-
console.log(`[sz] metric update: ${modelName}.${metric.field} (${metric.metricType}) =`, metric.
|
|
186
|
+
console.log(`[sz] metric update: ${modelName}.${metric.field} (${metric.metricType}) =`, metric.valueOf());
|
|
187
187
|
// Update the wrapper value with the latest metric value
|
|
188
|
-
wrapper.value = metric.
|
|
188
|
+
wrapper.value = metric.valueOf();
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
191
|
// Only listen for metric render events
|
|
@@ -288,7 +288,14 @@ export class IndexedDBStore {
|
|
|
288
288
|
}
|
|
289
289
|
export class Cache {
|
|
290
290
|
constructor(dbName, options = {}, onHydrated = null) {
|
|
291
|
-
|
|
291
|
+
// Share IndexedDBStore instances across Cache objects with the same dbName
|
|
292
|
+
if (dbConnections.has(dbName)) {
|
|
293
|
+
this.store = dbConnections.get(dbName);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
this.store = new IndexedDBStore(dbName, options);
|
|
297
|
+
dbConnections.set(dbName, this.store);
|
|
298
|
+
}
|
|
292
299
|
this.localMap = new Map();
|
|
293
300
|
// don't await - will hydrate during app setup
|
|
294
301
|
this.hydrate()
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LiveMetric
|
|
3
|
-
*
|
|
2
|
+
* LiveMetric wraps a metric scalar (count, sum, etc.) and stays live.
|
|
3
|
+
*
|
|
4
|
+
* Uses valueOf()/toString() so it coerces to the scalar naturally:
|
|
5
|
+
* count + 0 // → 5
|
|
6
|
+
* `${count}` // → "5"
|
|
7
|
+
* count > 3 // → true
|
|
8
|
+
*
|
|
9
|
+
* Note: typeof returns "object" and === compares identity, not value.
|
|
10
|
+
* Use == for loose comparison, or +metric for explicit coercion.
|
|
4
11
|
*/
|
|
5
12
|
export class LiveMetric {
|
|
6
13
|
constructor(queryset: any, metricType: any, field?: null);
|
|
@@ -14,8 +21,12 @@ export class LiveMetric {
|
|
|
14
21
|
*/
|
|
15
22
|
refreshFromDb(): any;
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
24
|
+
* Returns the current metric value from the store.
|
|
25
|
+
* Called implicitly by JS when coercing to a primitive (arithmetic, template literals, etc.)
|
|
18
26
|
*/
|
|
27
|
+
valueOf(): any;
|
|
28
|
+
toString(): string;
|
|
29
|
+
/** @deprecated Use valueOf() coercion instead (e.g. +metric, `${metric}`, metric + 0) */
|
|
19
30
|
get value(): any;
|
|
20
31
|
}
|
|
21
32
|
/**
|
|
@@ -5,8 +5,15 @@ import { QueryExecutor } from '../../flavours/django/queryExecutor.js';
|
|
|
5
5
|
import { wrapReactiveMetric } from '../../reactiveAdaptor.js';
|
|
6
6
|
import hash from 'object-hash';
|
|
7
7
|
/**
|
|
8
|
-
* LiveMetric
|
|
9
|
-
*
|
|
8
|
+
* LiveMetric wraps a metric scalar (count, sum, etc.) and stays live.
|
|
9
|
+
*
|
|
10
|
+
* Uses valueOf()/toString() so it coerces to the scalar naturally:
|
|
11
|
+
* count + 0 // → 5
|
|
12
|
+
* `${count}` // → "5"
|
|
13
|
+
* count > 3 // → true
|
|
14
|
+
*
|
|
15
|
+
* Note: typeof returns "object" and === compares identity, not value.
|
|
16
|
+
* Use == for loose comparison, or +metric for explicit coercion.
|
|
10
17
|
*/
|
|
11
18
|
export class LiveMetric {
|
|
12
19
|
constructor(queryset, metricType, field = null) {
|
|
@@ -26,17 +33,24 @@ export class LiveMetric {
|
|
|
26
33
|
return store.sync(true);
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
|
-
*
|
|
36
|
+
* Returns the current metric value from the store.
|
|
37
|
+
* Called implicitly by JS when coercing to a primitive (arithmetic, template literals, etc.)
|
|
30
38
|
*/
|
|
31
|
-
|
|
32
|
-
// Get the latest store from the registry
|
|
39
|
+
valueOf() {
|
|
33
40
|
const store = metricRegistry.getStore(this.metricType, this.queryset, this.field);
|
|
34
41
|
if (!store) {
|
|
35
42
|
return null;
|
|
36
43
|
}
|
|
37
|
-
// Render the current value
|
|
38
44
|
return store.render();
|
|
39
45
|
}
|
|
46
|
+
toString() {
|
|
47
|
+
const val = this.valueOf();
|
|
48
|
+
return val === null ? '' : String(val);
|
|
49
|
+
}
|
|
50
|
+
/** @deprecated Use valueOf() coercion instead (e.g. +metric, `${metric}`, metric + 0) */
|
|
51
|
+
get value() {
|
|
52
|
+
return this.valueOf();
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
55
|
/**
|
|
42
56
|
* Registry to manage metric stores
|
|
@@ -46,11 +46,17 @@ export class QuerysetStore {
|
|
|
46
46
|
return this.queryset.semanticKey;
|
|
47
47
|
}
|
|
48
48
|
onHydrated(hydratedData) {
|
|
49
|
-
if
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// Only use cached data if we haven't synced yet and have no ground truth.
|
|
50
|
+
// This prevents stale cache from overwriting real server data.
|
|
51
|
+
if (this.lastSync !== null)
|
|
52
|
+
return;
|
|
53
|
+
if (this.groundTruthPks.length > 0 || this.operationsMap.size > 0)
|
|
54
|
+
return;
|
|
55
|
+
const cached = this.qsCache.get(this.cacheKey);
|
|
56
|
+
if (!isNil(cached) && !isEmpty(cached)) {
|
|
57
|
+
// Set ground truth WITHOUT updating lastSync — hydration is not a real sync
|
|
58
|
+
this.groundTruthPks = Array.isArray(cached) ? cached : [];
|
|
59
|
+
this._emitRenderEvent();
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
setCache(result) {
|
|
@@ -207,7 +213,13 @@ export class QuerysetStore {
|
|
|
207
213
|
// If no ground truth AND hasn't been synced, render from model store
|
|
208
214
|
// This handles chained optimistic filters, newly created stores, etc.
|
|
209
215
|
// (If synced with empty results, that's valid ground truth)
|
|
210
|
-
|
|
216
|
+
// Skip model store seeding for paginated querysets (offset > 0) — the model
|
|
217
|
+
// store contains items from other pages, so seeding would show wrong results.
|
|
218
|
+
const offset = this.queryset.build().serializerOptions?.offset;
|
|
219
|
+
const canSeedFromModelStore = this.groundTruthPks.length === 0
|
|
220
|
+
&& this.lastSync === null
|
|
221
|
+
&& !(offset != null && offset > 0);
|
|
222
|
+
const pks = canSeedFromModelStore
|
|
211
223
|
? this.renderFromModelStore()
|
|
212
224
|
: this.renderFromData(optimistic);
|
|
213
225
|
// Validate against model store and apply local filtering/sorting
|
package/package.json
CHANGED