@statezero/core 0.2.51 → 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 +586 -559
- package/dist/adaptors/vue/composables.d.ts +1 -1
- package/dist/adaptors/vue/composables.js +8 -2
- package/dist/adaptors/vue/reactivity.d.ts +1 -0
- package/dist/adaptors/vue/reactivity.js +20 -8
- package/dist/cli/commands/syncActions.js +33 -1
- package/dist/cli/commands/syncModels.js +44 -3
- package/dist/filtering/localFiltering.js +3 -2
- package/dist/flavours/django/errors.d.ts +12 -0
- package/dist/flavours/django/errors.js +20 -0
- package/dist/flavours/django/f.js +16 -15
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/models/backend1/django_app/author.d.ts +284 -0
- package/dist/models/backend1/django_app/author.js +69 -0
- package/dist/models/backend1/django_app/author.schema.json +281 -0
- package/dist/models/backend1/django_app/book.d.ts +262 -0
- package/dist/models/backend1/django_app/book.js +71 -0
- package/dist/models/backend1/django_app/book.schema.json +254 -0
- package/dist/models/backend1/django_app/composeditem.d.ts +140 -0
- package/dist/models/backend1/django_app/composeditem.js +69 -0
- package/dist/models/backend1/django_app/composeditem.schema.json +122 -0
- package/dist/models/backend1/django_app/comprehensivemodel.schema.json +3 -3
- package/dist/models/backend1/django_app/dailyrate.schema.json +7 -7
- package/dist/models/backend1/django_app/deepmodellevel1.schema.json +1 -1
- package/dist/models/backend1/django_app/dummymodel.schema.json +3 -3
- package/dist/models/backend1/django_app/errortestcompoundunique.d.ts +124 -0
- package/dist/models/backend1/django_app/errortestcompoundunique.js +69 -0
- package/dist/models/backend1/django_app/errortestcompoundunique.schema.json +106 -0
- package/dist/models/backend1/django_app/errortestonetoonemodel.d.ts +118 -0
- package/dist/models/backend1/django_app/errortestonetoonemodel.js +71 -0
- package/dist/models/backend1/django_app/errortestonetoonemodel.schema.json +90 -0
- package/dist/models/backend1/django_app/errortestparent.d.ts +92 -0
- package/dist/models/backend1/django_app/errortestparent.js +69 -0
- package/dist/models/backend1/django_app/errortestparent.schema.json +70 -0
- package/dist/models/backend1/django_app/errortestprotectedchild.d.ts +118 -0
- package/dist/models/backend1/django_app/errortestprotectedchild.js +71 -0
- package/dist/models/backend1/django_app/errortestprotectedchild.schema.json +94 -0
- package/dist/models/backend1/django_app/errortestuniquemodel.d.ts +108 -0
- package/dist/models/backend1/django_app/errortestuniquemodel.js +69 -0
- package/dist/models/backend1/django_app/errortestuniquemodel.schema.json +88 -0
- package/dist/models/backend1/django_app/excludeditem.d.ts +124 -0
- package/dist/models/backend1/django_app/excludeditem.js +69 -0
- package/dist/models/backend1/django_app/excludeditem.schema.json +105 -0
- package/dist/models/backend1/django_app/hfchild.d.ts +118 -0
- package/dist/models/backend1/django_app/hfchild.js +71 -0
- package/dist/models/backend1/django_app/hfchild.schema.json +94 -0
- package/dist/models/backend1/django_app/hfparent.d.ts +124 -0
- package/dist/models/backend1/django_app/hfparent.js +69 -0
- package/dist/models/backend1/django_app/hfparent.schema.json +105 -0
- package/dist/models/backend1/django_app/index.js +19 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +3 -3
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +3 -3
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +5 -5
- package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +2 -2
- package/dist/models/backend1/django_app/nodeleteitem.d.ts +124 -0
- package/dist/models/backend1/django_app/nodeleteitem.js +69 -0
- package/dist/models/backend1/django_app/nodeleteitem.schema.json +105 -0
- package/dist/models/backend1/django_app/objectlevelitem.d.ts +124 -0
- package/dist/models/backend1/django_app/objectlevelitem.js +69 -0
- package/dist/models/backend1/django_app/objectlevelitem.schema.json +105 -0
- package/dist/models/backend1/django_app/order.schema.json +7 -7
- package/dist/models/backend1/django_app/orderitem.schema.json +5 -5
- package/dist/models/backend1/django_app/product.schema.json +11 -11
- package/dist/models/backend1/django_app/productcategory.schema.json +2 -2
- package/dist/models/backend1/django_app/rateplan.schema.json +2 -2
- package/dist/models/backend1/django_app/readonlyitem.d.ts +124 -0
- package/dist/models/backend1/django_app/readonlyitem.js +69 -0
- package/dist/models/backend1/django_app/readonlyitem.schema.json +105 -0
- package/dist/models/backend1/django_app/restrictedcreateitem.d.ts +124 -0
- package/dist/models/backend1/django_app/restrictedcreateitem.js +69 -0
- package/dist/models/backend1/django_app/restrictedcreateitem.schema.json +105 -0
- package/dist/models/backend1/django_app/restrictededititem.d.ts +124 -0
- package/dist/models/backend1/django_app/restrictededititem.js +69 -0
- package/dist/models/backend1/django_app/restrictededititem.schema.json +105 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +1 -1
- package/dist/models/backend1/django_app/rowfiltereditem.d.ts +124 -0
- package/dist/models/backend1/django_app/rowfiltereditem.js +69 -0
- package/dist/models/backend1/django_app/rowfiltereditem.schema.json +105 -0
- package/dist/models/backend1/django_app/tag.d.ts +554 -0
- package/dist/models/backend1/django_app/tag.js +71 -0
- package/dist/models/backend1/django_app/tag.schema.json +541 -0
- package/dist/models/backend1/django_app/updateonlyitem.d.ts +108 -0
- package/dist/models/backend1/django_app/updateonlyitem.js +69 -0
- package/dist/models/backend1/django_app/updateonlyitem.schema.json +88 -0
- package/dist/models/default/django_app/author.d.ts +284 -0
- package/dist/models/default/django_app/author.js +69 -0
- package/dist/models/default/django_app/author.schema.json +281 -0
- package/dist/models/default/django_app/book.d.ts +262 -0
- package/dist/models/default/django_app/book.js +71 -0
- package/dist/models/default/django_app/book.schema.json +254 -0
- package/dist/models/default/django_app/composeditem.d.ts +140 -0
- package/dist/models/default/django_app/composeditem.js +69 -0
- package/dist/models/default/django_app/composeditem.schema.json +122 -0
- package/dist/models/default/django_app/comprehensivemodel.schema.json +3 -3
- package/dist/models/default/django_app/dailyrate.schema.json +7 -7
- package/dist/models/default/django_app/deepmodellevel1.d.ts +1 -13
- package/dist/models/default/django_app/deepmodellevel1.schema.json +2 -14
- package/dist/models/default/django_app/deepmodellevel2.d.ts +1 -13
- package/dist/models/default/django_app/deepmodellevel2.schema.json +1 -13
- package/dist/models/default/django_app/deepmodellevel3.d.ts +1 -13
- package/dist/models/default/django_app/deepmodellevel3.schema.json +1 -13
- package/dist/models/default/django_app/dummymodel.d.ts +1 -13
- package/dist/models/default/django_app/dummymodel.schema.json +4 -16
- package/dist/models/default/django_app/dummyrelatedmodel.d.ts +1 -13
- package/dist/models/default/django_app/dummyrelatedmodel.schema.json +1 -13
- package/dist/models/default/django_app/errortestcompoundunique.d.ts +124 -0
- package/dist/models/default/django_app/errortestcompoundunique.js +69 -0
- package/dist/models/default/django_app/errortestcompoundunique.schema.json +106 -0
- package/dist/models/default/django_app/errortestonetoonemodel.d.ts +118 -0
- package/dist/models/default/django_app/errortestonetoonemodel.js +71 -0
- package/dist/models/default/django_app/errortestonetoonemodel.schema.json +90 -0
- package/dist/models/default/django_app/errortestparent.d.ts +92 -0
- package/dist/models/default/django_app/errortestparent.js +69 -0
- package/dist/models/default/django_app/errortestparent.schema.json +70 -0
- package/dist/models/default/django_app/errortestprotectedchild.d.ts +118 -0
- package/dist/models/default/django_app/errortestprotectedchild.js +71 -0
- package/dist/models/default/django_app/errortestprotectedchild.schema.json +94 -0
- package/dist/models/default/django_app/errortestuniquemodel.d.ts +108 -0
- package/dist/models/default/django_app/errortestuniquemodel.js +69 -0
- package/dist/models/default/django_app/errortestuniquemodel.schema.json +88 -0
- package/dist/models/default/django_app/excludeditem.d.ts +124 -0
- package/dist/models/default/django_app/excludeditem.js +69 -0
- package/dist/models/default/django_app/excludeditem.schema.json +105 -0
- package/dist/models/default/django_app/filetest.d.ts +1 -13
- package/dist/models/default/django_app/filetest.schema.json +1 -13
- package/dist/models/default/django_app/hfchild.d.ts +118 -0
- package/dist/models/default/django_app/hfchild.js +71 -0
- package/dist/models/default/django_app/hfchild.schema.json +94 -0
- package/dist/models/default/django_app/hfparent.d.ts +124 -0
- package/dist/models/default/django_app/hfparent.js +69 -0
- package/dist/models/default/django_app/hfparent.schema.json +105 -0
- package/dist/models/default/django_app/index.js +19 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +3 -3
- package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +3 -3
- package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +5 -5
- package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +2 -2
- package/dist/models/default/django_app/nodeleteitem.d.ts +124 -0
- package/dist/models/default/django_app/nodeleteitem.js +69 -0
- package/dist/models/default/django_app/nodeleteitem.schema.json +105 -0
- package/dist/models/default/django_app/objectlevelitem.d.ts +124 -0
- package/dist/models/default/django_app/objectlevelitem.js +69 -0
- package/dist/models/default/django_app/objectlevelitem.schema.json +105 -0
- package/dist/models/default/django_app/order.schema.json +7 -7
- package/dist/models/default/django_app/orderitem.schema.json +5 -5
- package/dist/models/default/django_app/product.schema.json +11 -11
- package/dist/models/default/django_app/productcategory.schema.json +2 -2
- package/dist/models/default/django_app/rateplan.schema.json +2 -2
- package/dist/models/default/django_app/readonlyitem.d.ts +124 -0
- package/dist/models/default/django_app/readonlyitem.js +69 -0
- package/dist/models/default/django_app/readonlyitem.schema.json +105 -0
- package/dist/models/default/django_app/restrictedcreateitem.d.ts +124 -0
- package/dist/models/default/django_app/restrictedcreateitem.js +69 -0
- package/dist/models/default/django_app/restrictedcreateitem.schema.json +105 -0
- package/dist/models/default/django_app/restrictededititem.d.ts +124 -0
- package/dist/models/default/django_app/restrictededititem.js +69 -0
- package/dist/models/default/django_app/restrictededititem.schema.json +105 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +1 -1
- package/dist/models/default/django_app/rowfiltereditem.d.ts +124 -0
- package/dist/models/default/django_app/rowfiltereditem.js +69 -0
- package/dist/models/default/django_app/rowfiltereditem.schema.json +105 -0
- package/dist/models/default/django_app/tag.d.ts +554 -0
- package/dist/models/default/django_app/tag.js +71 -0
- package/dist/models/default/django_app/tag.schema.json +541 -0
- package/dist/models/default/django_app/updateonlyitem.d.ts +108 -0
- package/dist/models/default/django_app/updateonlyitem.js +69 -0
- package/dist/models/default/django_app/updateonlyitem.schema.json +88 -0
- package/dist/schemaIntegrity.d.ts +14 -0
- package/dist/schemaIntegrity.js +29 -0
- package/dist/setup.js +6 -0
- 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/operationEventHandlers.js +10 -8
- package/dist/syncEngine/stores/querysetStore.js +27 -6
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was auto-generated. Do not make direct changes to the file.
|
|
3
|
+
*/
|
|
4
|
+
import { Model, Manager, QuerySet, getModelClass } from '../../../../src';
|
|
5
|
+
import { wrapReactiveModel } from '../../../../src';
|
|
6
|
+
import schemaData from './updateonlyitem.schema.json';
|
|
7
|
+
/**
|
|
8
|
+
* Model-specific QuerySet implementation
|
|
9
|
+
*/
|
|
10
|
+
export class UpdateOnlyItemQuerySet extends QuerySet {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Model-specific Manager implementation
|
|
14
|
+
*/
|
|
15
|
+
export class UpdateOnlyItemManager extends Manager {
|
|
16
|
+
constructor(ModelClass) {
|
|
17
|
+
super(ModelClass, UpdateOnlyItemQuerySet);
|
|
18
|
+
}
|
|
19
|
+
newQuerySet() {
|
|
20
|
+
return new UpdateOnlyItemQuerySet(this.ModelClass);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Implementation of the UpdateOnlyItem model
|
|
25
|
+
*/
|
|
26
|
+
export class UpdateOnlyItem extends Model {
|
|
27
|
+
constructor(data) {
|
|
28
|
+
UpdateOnlyItem.validateFields(data);
|
|
29
|
+
super(data);
|
|
30
|
+
// Define getters and setters for all fields
|
|
31
|
+
this._defineProperties();
|
|
32
|
+
return wrapReactiveModel(this);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Define property getters and setters for all model fields
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
_defineProperties() {
|
|
39
|
+
// For each field, define a property that gets/sets from internal storage
|
|
40
|
+
UpdateOnlyItem.fields.forEach(field => {
|
|
41
|
+
Object.defineProperty(this, field, {
|
|
42
|
+
get: function () {
|
|
43
|
+
return this.getField(field);
|
|
44
|
+
},
|
|
45
|
+
set: function (value) {
|
|
46
|
+
this.setField(field, value);
|
|
47
|
+
},
|
|
48
|
+
enumerable: true, // Make sure fields are enumerable for serialization
|
|
49
|
+
configurable: true
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
// Add a special read-only getter for the repr field
|
|
53
|
+
Object.defineProperty(this, 'repr', {
|
|
54
|
+
get: function () {
|
|
55
|
+
return this.getField('repr');
|
|
56
|
+
},
|
|
57
|
+
enumerable: true, // Make sure repr is enumerable
|
|
58
|
+
configurable: true
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Bind this model to its backend
|
|
63
|
+
UpdateOnlyItem.configKey = 'default';
|
|
64
|
+
UpdateOnlyItem.modelName = 'django_app.updateonlyitem';
|
|
65
|
+
UpdateOnlyItem.primaryKeyField = 'id';
|
|
66
|
+
UpdateOnlyItem.objects = new UpdateOnlyItemManager(UpdateOnlyItem);
|
|
67
|
+
UpdateOnlyItem.fields = ['id', 'name', 'value'];
|
|
68
|
+
UpdateOnlyItem.schema = schemaData;
|
|
69
|
+
UpdateOnlyItem.relationshipFields = new Map([]);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"model_name": "django_app.updateonlyitem",
|
|
3
|
+
"title": "Update Only Item",
|
|
4
|
+
"class_name": "UpdateOnlyItem",
|
|
5
|
+
"plural_title": "Update Only Items",
|
|
6
|
+
"primary_key_field": "id",
|
|
7
|
+
"filterable_fields": [
|
|
8
|
+
"value",
|
|
9
|
+
"id",
|
|
10
|
+
"name"
|
|
11
|
+
],
|
|
12
|
+
"searchable_fields": [
|
|
13
|
+
"name"
|
|
14
|
+
],
|
|
15
|
+
"ordering_fields": [
|
|
16
|
+
"value",
|
|
17
|
+
"name"
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"id": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"title": "Id",
|
|
23
|
+
"required": true,
|
|
24
|
+
"description": null,
|
|
25
|
+
"nullable": false,
|
|
26
|
+
"format": "id",
|
|
27
|
+
"max_length": null,
|
|
28
|
+
"choices": null,
|
|
29
|
+
"default": null,
|
|
30
|
+
"validators": [],
|
|
31
|
+
"max_digits": null,
|
|
32
|
+
"decimal_places": null,
|
|
33
|
+
"read_only": true,
|
|
34
|
+
"ref": null
|
|
35
|
+
},
|
|
36
|
+
"name": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"title": "Name",
|
|
39
|
+
"required": true,
|
|
40
|
+
"description": null,
|
|
41
|
+
"nullable": false,
|
|
42
|
+
"format": null,
|
|
43
|
+
"max_length": 100,
|
|
44
|
+
"choices": null,
|
|
45
|
+
"default": null,
|
|
46
|
+
"validators": [],
|
|
47
|
+
"max_digits": null,
|
|
48
|
+
"decimal_places": null,
|
|
49
|
+
"read_only": false,
|
|
50
|
+
"ref": null
|
|
51
|
+
},
|
|
52
|
+
"value": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"title": "Value",
|
|
55
|
+
"required": false,
|
|
56
|
+
"description": null,
|
|
57
|
+
"nullable": false,
|
|
58
|
+
"format": null,
|
|
59
|
+
"max_length": null,
|
|
60
|
+
"choices": null,
|
|
61
|
+
"default": 0,
|
|
62
|
+
"validators": [],
|
|
63
|
+
"max_digits": null,
|
|
64
|
+
"decimal_places": null,
|
|
65
|
+
"read_only": false,
|
|
66
|
+
"ref": null
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"relationships": {},
|
|
70
|
+
"default_ordering": null,
|
|
71
|
+
"definitions": {
|
|
72
|
+
"MoneyField": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"amount": {
|
|
76
|
+
"type": "number"
|
|
77
|
+
},
|
|
78
|
+
"currency": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"datetime_format": "iso-8601",
|
|
85
|
+
"date_format": "iso-8601",
|
|
86
|
+
"time_format": "iso-8601",
|
|
87
|
+
"display": null
|
|
88
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background check that compares stored schema checksums (from sync time)
|
|
3
|
+
* against the current checksums returned by the backend.
|
|
4
|
+
*
|
|
5
|
+
* Warns via console if schemas have drifted. Silently skips on network error.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object.<string, Object>} backendConfigs - Backend configuration map from configInstance
|
|
8
|
+
* @param {Object.<string, string>} checksums - Map of backend key to checksum from sync time
|
|
9
|
+
*/
|
|
10
|
+
export function checkSchemaIntegrity(backendConfigs: {
|
|
11
|
+
[x: string]: Object;
|
|
12
|
+
}, checksums: {
|
|
13
|
+
[x: string]: string;
|
|
14
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
/**
|
|
3
|
+
* Background check that compares stored schema checksums (from sync time)
|
|
4
|
+
* against the current checksums returned by the backend.
|
|
5
|
+
*
|
|
6
|
+
* Warns via console if schemas have drifted. Silently skips on network error.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object.<string, Object>} backendConfigs - Backend configuration map from configInstance
|
|
9
|
+
* @param {Object.<string, string>} checksums - Map of backend key to checksum from sync time
|
|
10
|
+
*/
|
|
11
|
+
export async function checkSchemaIntegrity(backendConfigs, checksums) {
|
|
12
|
+
for (const [backendKey, expectedChecksum] of Object.entries(checksums)) {
|
|
13
|
+
const backend = backendConfigs[backendKey];
|
|
14
|
+
if (!backend)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
const headers = backend.getAuthHeaders ? backend.getAuthHeaders() : {};
|
|
18
|
+
const response = await axios.get(`${backend.API_URL}/models/`, { headers });
|
|
19
|
+
const currentChecksum = response.headers['x-statezero-schema-checksum'];
|
|
20
|
+
if (currentChecksum && currentChecksum !== expectedChecksum) {
|
|
21
|
+
console.warn(`[StateZero] Schema drift detected for backend '${backendKey}'. ` +
|
|
22
|
+
`Run 'npx statezero sync' to update your generated types.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Silently skip — this is a non-blocking background check
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initEventHandler } from "./syncEngine/stores/operationEventHandlers.js"
|
|
|
5
5
|
import { querysetStoreRegistry } from "./syncEngine/registries/querysetStoreRegistry.js";
|
|
6
6
|
import { modelStoreRegistry } from "./syncEngine/registries/modelStoreRegistry.js";
|
|
7
7
|
import { metricRegistry } from "./syncEngine/registries/metricRegistry.js";
|
|
8
|
+
import { checkSchemaIntegrity } from "./schemaIntegrity.js";
|
|
8
9
|
/**
|
|
9
10
|
* Initialize StateZero with the provided configuration
|
|
10
11
|
*
|
|
@@ -30,4 +31,9 @@ export function setupStateZero(config, getModelClass, adapters) {
|
|
|
30
31
|
metricRegistry,
|
|
31
32
|
};
|
|
32
33
|
}
|
|
34
|
+
// Background schema freshness check (fire-and-forget)
|
|
35
|
+
if (getModelClass.SYNC_CHECKSUMS) {
|
|
36
|
+
const cfg = configInstance.getConfig();
|
|
37
|
+
checkSchemaIntegrity(cfg.backendConfigs, getModelClass.SYNC_CHECKSUMS);
|
|
38
|
+
}
|
|
33
39
|
}
|
|
@@ -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
|
|
@@ -203,7 +203,7 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
203
203
|
*
|
|
204
204
|
* @param {Operation} operation - The UPDATE operation
|
|
205
205
|
*/
|
|
206
|
-
function routeUpdateOperation(operation) {
|
|
206
|
+
function routeUpdateOperation(operation, applyAction) {
|
|
207
207
|
const modelClass = operation.queryset.ModelClass;
|
|
208
208
|
// frozenInstances is already serialized with proper FK structure
|
|
209
209
|
const beforeInstances = operation.frozenInstances;
|
|
@@ -230,6 +230,8 @@ function routeUpdateOperation(operation) {
|
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
232
|
// Item matched before OR matches after, no offset - definitely affected
|
|
233
|
+
// Apply the operation so the queryset can add/remove PKs from its candidate set
|
|
234
|
+
applyAction(store);
|
|
233
235
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
|
|
234
236
|
});
|
|
235
237
|
}
|
|
@@ -243,7 +245,7 @@ function routeUpdateOperation(operation) {
|
|
|
243
245
|
*
|
|
244
246
|
* @param {Operation} operation - The DELETE operation
|
|
245
247
|
*/
|
|
246
|
-
function routeDeleteOperation(operation) {
|
|
248
|
+
function routeDeleteOperation(operation, applyAction) {
|
|
247
249
|
const modelClass = operation.queryset.ModelClass;
|
|
248
250
|
// frozenInstances is already serialized with proper FK structure
|
|
249
251
|
const beforeInstances = operation.frozenInstances;
|
|
@@ -267,6 +269,7 @@ function routeDeleteOperation(operation) {
|
|
|
267
269
|
return;
|
|
268
270
|
}
|
|
269
271
|
// Item matched before, no offset - definitely affected
|
|
272
|
+
applyAction(store);
|
|
270
273
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
|
|
271
274
|
});
|
|
272
275
|
}
|
|
@@ -339,15 +342,14 @@ function processQuerysetStores(operation, actionType) {
|
|
|
339
342
|
return;
|
|
340
343
|
case Type.UPDATE:
|
|
341
344
|
case Type.UPDATE_INSTANCE:
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
routeUpdateOperation(operation);
|
|
345
|
+
// Route update to affected querysets so items can move between filtered sets
|
|
346
|
+
// (e.g., an item changing name from 'beta' to 'alpha' needs to appear in the alpha queryset)
|
|
347
|
+
routeUpdateOperation(operation, applyAction);
|
|
345
348
|
return;
|
|
346
349
|
case Type.DELETE:
|
|
347
350
|
case Type.DELETE_INSTANCE:
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
routeDeleteOperation(operation);
|
|
351
|
+
// Add delete to matching queryset stores for optimistic removal
|
|
352
|
+
routeDeleteOperation(operation, applyAction);
|
|
351
353
|
return;
|
|
352
354
|
case Type.CHECKPOINT:
|
|
353
355
|
// Model store handles the change, querysets re-render via local filtering
|
|
@@ -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) {
|
|
@@ -125,6 +131,10 @@ export class QuerysetStore {
|
|
|
125
131
|
}
|
|
126
132
|
async setGroundTruth(groundTruthPks) {
|
|
127
133
|
this.groundTruthPks = Array.isArray(groundTruthPks) ? groundTruthPks : [];
|
|
134
|
+
if (this.groundTruthPks.length > 500) {
|
|
135
|
+
const limit = this.queryset?._serializerOptions?.limit;
|
|
136
|
+
console.warn(`[statezero] ⚠️ ${this.modelClass.modelName} (${this.queryset.semanticKey}) received ${this.groundTruthPks.length} items${limit ? ` (limit: ${limit})` : ' with NO limit'}. Live querysets re-render on every change — at this size, this WILL cause UI freezes on mobile. Add .fetch({ limit }) or use narrower filters.`);
|
|
137
|
+
}
|
|
128
138
|
this.lastSync = Date.now();
|
|
129
139
|
this._emitRenderEvent();
|
|
130
140
|
}
|
|
@@ -203,7 +213,13 @@ export class QuerysetStore {
|
|
|
203
213
|
// If no ground truth AND hasn't been synced, render from model store
|
|
204
214
|
// This handles chained optimistic filters, newly created stores, etc.
|
|
205
215
|
// (If synced with empty results, that's valid ground truth)
|
|
206
|
-
|
|
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
|
|
207
223
|
? this.renderFromModelStore()
|
|
208
224
|
: this.renderFromData(optimistic);
|
|
209
225
|
// Validate against model store and apply local filtering/sorting
|
|
@@ -269,8 +285,13 @@ export class QuerysetStore {
|
|
|
269
285
|
currentPks.add(pk);
|
|
270
286
|
break;
|
|
271
287
|
case Type.CHECKPOINT:
|
|
288
|
+
break;
|
|
272
289
|
case Type.UPDATE:
|
|
273
290
|
case Type.UPDATE_INSTANCE:
|
|
291
|
+
// Add PKs as candidates so items can move into this queryset
|
|
292
|
+
// when an update makes them match the filter.
|
|
293
|
+
// _getValidatedAndFilteredPks will filter out non-matching items.
|
|
294
|
+
currentPks.add(pk);
|
|
274
295
|
break;
|
|
275
296
|
case Type.DELETE:
|
|
276
297
|
case Type.DELETE_INSTANCE:
|
package/package.json
CHANGED