@statezero/core 0.1.61 → 0.1.62

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.
@@ -110,6 +110,19 @@ export async function makeApiCall(querySet, operationType, args = {}, operationI
110
110
  throw new Error(`API call failed: ${error.message}`);
111
111
  }
112
112
  };
113
- // Queue write operations, execute read operations immediately
113
+ /*
114
+ We use p-queue for write operations to ensure that the user does not
115
+ try and perform a write operation using a temporary primary key that
116
+ exists on the local optimistic instance until the backend has replied with the
117
+ new primary key. This would result in a 400 Bad Request error from the backend.
118
+ This is effective, but does mean that write operations are queued which can lead to delays.
119
+ In general, this is not a problem because the UI will optimistically update the state so the
120
+ user does not perceive the delay, but it is something to be aware of.
121
+
122
+ In the future, we will want to implement an approach where we specifically detect
123
+ if a write operation is using a temporary pk and then await for the temporary
124
+ pk to be replaced before proceeding with the write operation. But for now,
125
+ this is a simple and effective solution.
126
+ */
114
127
  return isWriteOperation ? apiCallQueue.add(apiCall) : apiCall();
115
128
  }
@@ -35,6 +35,38 @@ function relatedQuerysets(queryset) {
35
35
  });
36
36
  return result;
37
37
  }
38
+ /**
39
+ * Returns querysets that contain any of the specified instances
40
+ * @param {Operation} operation - The operation containing instances to check
41
+ * @returns {Map<QuerySet, Store>}
42
+ */
43
+ function querysetsContainingInstances(operation) {
44
+ const ModelClass = operation.queryset.ModelClass;
45
+ const pkField = ModelClass.primaryKeyField;
46
+ const instancePks = new Set(operation.instances
47
+ .filter(instance => instance && typeof instance === 'object' && pkField in instance)
48
+ .map(instance => instance[pkField]));
49
+ if (instancePks.size === 0) {
50
+ return new Map();
51
+ }
52
+ const result = new Map();
53
+ Array.from(querysetStoreRegistry._stores.entries()).forEach(([queryset, store]) => {
54
+ if (store.modelClass !== ModelClass)
55
+ return;
56
+ try {
57
+ // Check if this queryset contains any of the instances
58
+ const renderedPks = new Set(store.render(false)); // Get without optimistic updates
59
+ const hasIntersection = [...instancePks].some(pk => renderedPks.has(pk));
60
+ if (hasIntersection) {
61
+ result.set(store.queryset, store);
62
+ }
63
+ }
64
+ catch (e) {
65
+ console.warn('Error checking queryset for instances', e);
66
+ }
67
+ });
68
+ return result;
69
+ }
38
70
  /**
39
71
  * Process an operation in the model store
40
72
  *
@@ -124,9 +156,14 @@ function processQuerysetStores(operation, actionType) {
124
156
  function processMetricStores(operation, actionType) {
125
157
  const queryset = operation.queryset;
126
158
  const ModelClass = queryset.ModelClass;
127
- // For metrics, we can use a similar strategy but might be more conservative
128
- // and always use related querysets since metrics are aggregations
159
+ // For metrics, we use related querysets as the base
129
160
  const allQuerysets = Array.from(relatedQuerysets(queryset).keys());
161
+ // For update/delete operations, also include querysets that contain the instances
162
+ if (operation.type === Type.UPDATE || operation.type === Type.UPDATE_INSTANCE ||
163
+ operation.type === Type.DELETE || operation.type === Type.DELETE_INSTANCE) {
164
+ const containingQuerysets = Array.from(querysetsContainingInstances(operation).keys());
165
+ allQuerysets.push(...containingQuerysets);
166
+ }
130
167
  let allMetricStores = new Set();
131
168
  allQuerysets.forEach(qs => {
132
169
  const stores = metricRegistry.getAllStoresForQueryset(qs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "type": "module",
5
5
  "module": "ESNext",
6
6
  "description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",