@statezero/core 0.2.6 → 0.2.8

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.
@@ -159,6 +159,13 @@ export class Manager {
159
159
  * @returns {QuerySet} A new QuerySet instance with the search applied.
160
160
  */
161
161
  search(searchQuery: string, searchFields?: string[]): QuerySet<any>;
162
+ /**
163
+ * Returns a QuerySet marked as optimistic-only, meaning operations will only
164
+ * update local state without making backend API calls.
165
+ *
166
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
167
+ */
168
+ get optimistic(): QuerySet<any>;
162
169
  }
163
170
  export type SerializerOptions = {
164
171
  /**
@@ -202,4 +202,13 @@ export class Manager {
202
202
  search(searchQuery, searchFields) {
203
203
  return this.newQuerySet().search(searchQuery, searchFields);
204
204
  }
205
+ /**
206
+ * Returns a QuerySet marked as optimistic-only, meaning operations will only
207
+ * update local state without making backend API calls.
208
+ *
209
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
210
+ */
211
+ get optimistic() {
212
+ return this.newQuerySet()._optimistic();
213
+ }
205
214
  }
@@ -29,6 +29,7 @@ export class OperationFactory {
29
29
  instances: [{ ...data, [primaryKeyField]: tempPk }],
30
30
  queryset: queryset,
31
31
  args: { data },
32
+ localOnly: queryset._optimisticOnly || false,
32
33
  });
33
34
  }
34
35
  /**
@@ -53,6 +54,7 @@ export class OperationFactory {
53
54
  instances: instances,
54
55
  queryset: queryset,
55
56
  args: { data: dataList },
57
+ localOnly: queryset._optimisticOnly || false,
56
58
  });
57
59
  }
58
60
  /**
@@ -96,6 +98,7 @@ export class OperationFactory {
96
98
  instances: optimisticInstances,
97
99
  queryset: queryset,
98
100
  args: { filter, data },
101
+ localOnly: queryset._optimisticOnly || false,
99
102
  });
100
103
  }
101
104
  /**
@@ -117,6 +120,7 @@ export class OperationFactory {
117
120
  instances: instances,
118
121
  queryset: queryset,
119
122
  args: {},
123
+ localOnly: queryset._optimisticOnly || false,
120
124
  });
121
125
  }
122
126
  /**
@@ -139,6 +143,7 @@ export class OperationFactory {
139
143
  instances: instances,
140
144
  queryset: queryset,
141
145
  args: { data },
146
+ localOnly: queryset._optimisticOnly || false,
142
147
  });
143
148
  }
144
149
  /**
@@ -156,6 +161,7 @@ export class OperationFactory {
156
161
  instances: [instanceData],
157
162
  queryset: queryset,
158
163
  args: instanceData,
164
+ localOnly: queryset._optimisticOnly || false,
159
165
  });
160
166
  }
161
167
  /**
@@ -195,6 +201,7 @@ export class OperationFactory {
195
201
  instances: [instanceData],
196
202
  queryset: queryset,
197
203
  args: { lookup, defaults },
204
+ localOnly: queryset._optimisticOnly || false,
198
205
  });
199
206
  }
200
207
  /**
@@ -235,6 +242,7 @@ export class OperationFactory {
235
242
  instances: [instanceData],
236
243
  queryset: queryset,
237
244
  args: { lookup, defaults },
245
+ localOnly: queryset._optimisticOnly || false,
238
246
  });
239
247
  }
240
248
  }
@@ -123,6 +123,11 @@ export class QueryExecutor {
123
123
  ? ModelClass.fromPk(operation.instances[0][primaryKeyField], querySet)
124
124
  : ModelClass.fromPk(operation.instances[0][primaryKeyField], querySet);
125
125
  let liveResult = new ResultTuple(live, isCreatingNew);
126
+ // If optimistic-only, skip API call and immediately confirm
127
+ if (operation.localOnly) {
128
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
129
+ return liveResult;
130
+ }
126
131
  // Make API call with original operation type for backend
127
132
  const promise = makeApiCall(querySet, operationType, apiCallArgs, operation.operationId)
128
133
  .then((response) => {
@@ -223,7 +228,12 @@ export class QueryExecutor {
223
228
  };
224
229
  const operation = OperationFactory.createUpdateOperation(querySet, apiCallArgs.data, querySet.build().filter);
225
230
  const estimatedCount = operation.instances.length;
226
- let liveResult = [estimatedCount, { [modelName]: estimatedCount }, []];
231
+ let liveResult = [estimatedCount, { [modelName]: estimatedCount }, operation.instances];
232
+ // If optimistic-only, skip API call and immediately confirm
233
+ if (operation.localOnly) {
234
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
235
+ return liveResult;
236
+ }
227
237
  const promise = makeApiCall(querySet, operationType, apiCallArgs, operation.operationId)
228
238
  .then((response) => {
229
239
  const { data, included } = response.data || {};
@@ -258,7 +268,12 @@ export class QueryExecutor {
258
268
  const operation = OperationFactory.createDeleteOperation(querySet);
259
269
  // live placeholder: assume we delete all existing pks
260
270
  const estimatedCount = operation.instances.length;
261
- let liveResult = [estimatedCount, { [modelName]: estimatedCount }, []];
271
+ let liveResult = [estimatedCount, { [modelName]: estimatedCount }, operation.instances];
272
+ // If optimistic-only, skip API call and immediately confirm
273
+ if (operation.localOnly) {
274
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
275
+ return liveResult;
276
+ }
262
277
  const promise = makeApiCall(querySet, operationType, {}, operation.operationId)
263
278
  .then((response) => {
264
279
  const deletedCount = response.metadata.deleted_count;
@@ -298,6 +313,11 @@ export class QueryExecutor {
298
313
  const tempPk = operation.instances[0][ModelClass.primaryKeyField];
299
314
  // 1) placeholder instance
300
315
  const live = ModelClass.fromPk(tempPk, querySet);
316
+ // If optimistic-only, skip API call and immediately confirm
317
+ if (operation.localOnly) {
318
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
319
+ return live;
320
+ }
301
321
  // 2) kick off the async call
302
322
  const promise = makeApiCall(querySet, operationType, apiCallArgs, operationId, async (response) => {
303
323
  const { data } = response.data;
@@ -359,6 +379,11 @@ export class QueryExecutor {
359
379
  const tempPk = instance[primaryKeyField];
360
380
  return ModelClass.fromPk(tempPk, querySet);
361
381
  });
382
+ // If optimistic-only, skip API call and immediately confirm
383
+ if (operation.localOnly) {
384
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
385
+ return liveInstances;
386
+ }
362
387
  // Kick off the async call
363
388
  const promise = makeApiCall(querySet, operationType, apiCallArgs, operationId, async (response) => {
364
389
  const { data } = response.data;
@@ -421,6 +446,11 @@ export class QueryExecutor {
421
446
  // 1) placeholder instance
422
447
  const initialPk = Array.isArray(querysetPks) ? querysetPks[0] : null;
423
448
  const live = ModelClass.fromPk(initialPk, querySet);
449
+ // If optimistic-only, skip API call and immediately confirm
450
+ if (operation.localOnly) {
451
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
452
+ return live;
453
+ }
424
454
  // 2) async call
425
455
  const promise = makeApiCall(querySet, operationType, { data }, operation.operationId)
426
456
  .then((response) => {
@@ -468,7 +498,12 @@ export class QueryExecutor {
468
498
  // Use factory to create operation
469
499
  const operation = OperationFactory.createDeleteInstanceOperation(querySet, args);
470
500
  // 1) placeholder result
471
- let liveResult = [1, { [modelName]: 1 }, []];
501
+ let liveResult = [1, { [modelName]: 1 }, operation.instances];
502
+ // If optimistic-only, skip API call and immediately confirm
503
+ if (operation.localOnly) {
504
+ operation.updateStatus(Status.CONFIRMED, operation.instances);
505
+ return liveResult;
506
+ }
472
507
  // 2) async call
473
508
  const promise = makeApiCall(querySet, operationType, args, operation.operationId)
474
509
  .then((response) => {
@@ -40,6 +40,7 @@ export class QuerySet<T> {
40
40
  _initialQueryset: string | undefined;
41
41
  _serializerOptions: any;
42
42
  _materialized: boolean;
43
+ _optimisticOnly: any;
43
44
  __uuid: string;
44
45
  __parent: any;
45
46
  __reactivityId: any;
@@ -196,6 +197,19 @@ export class QuerySet<T> {
196
197
  * @returns {QuerySet} A new QuerySet with the serializer options applied.
197
198
  */
198
199
  all(serializerOptions?: SerializerOptions): QuerySet<any>;
200
+ /**
201
+ * Internal method to create an optimistic-only QuerySet.
202
+ * @private
203
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
204
+ */
205
+ private _optimistic;
206
+ /**
207
+ * Returns a QuerySet marked as optimistic-only, meaning operations will only
208
+ * update local state without making backend API calls.
209
+ *
210
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
211
+ */
212
+ get optimistic(): QuerySet<any>;
199
213
  /**
200
214
  * Creates a new record in the QuerySet.
201
215
  * @param {Object} data - The fields and values for the new record.
@@ -36,6 +36,7 @@ export class QuerySet {
36
36
  this._initialQueryset = config.initialQueryset;
37
37
  this._serializerOptions = config.serializerOptions || {};
38
38
  this._materialized = config.materialized || false;
39
+ this._optimisticOnly = config.optimisticOnly || false;
39
40
  this.__uuid = v7();
40
41
  this.__parent = parent;
41
42
  this.__reactivityId = parent?.__reactivityId;
@@ -56,6 +57,7 @@ export class QuerySet {
56
57
  initialQueryset: this._initialQueryset,
57
58
  serializerOptions: { ...this._serializerOptions },
58
59
  materialized: this._materialized,
60
+ optimisticOnly: this._optimisticOnly,
59
61
  }, this);
60
62
  }
61
63
  get semanticKey() {
@@ -458,6 +460,27 @@ export class QuerySet {
458
460
  }
459
461
  return this;
460
462
  }
463
+ /**
464
+ * Internal method to create an optimistic-only QuerySet.
465
+ * @private
466
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
467
+ */
468
+ _optimistic() {
469
+ this.ensureNotMaterialized();
470
+ return new QuerySet(this.ModelClass, {
471
+ ...this._getConfig(),
472
+ optimisticOnly: true,
473
+ }, this);
474
+ }
475
+ /**
476
+ * Returns a QuerySet marked as optimistic-only, meaning operations will only
477
+ * update local state without making backend API calls.
478
+ *
479
+ * @returns {QuerySet} A new QuerySet with optimistic-only mode enabled.
480
+ */
481
+ get optimistic() {
482
+ return this._optimistic();
483
+ }
461
484
  /**
462
485
  * Creates a new record in the QuerySet.
463
486
  * @param {Object} data - The fields and values for the new record.
@@ -659,6 +682,7 @@ export class QuerySet {
659
682
  aggregations: this._aggregations,
660
683
  initialQueryset: this._initialQueryset,
661
684
  serializerOptions: this._serializerOptions,
685
+ optimisticOnly: this._optimisticOnly,
662
686
  };
663
687
  }
664
688
  /**
@@ -33,6 +33,7 @@ export class Operation {
33
33
  args: any;
34
34
  timestamp: any;
35
35
  doNotPropagate: any;
36
+ localOnly: any;
36
37
  /**
37
38
  * Setter for instances
38
39
  */
@@ -60,6 +60,7 @@ export class Operation {
60
60
  this.queryset = data.queryset;
61
61
  this.args = data.args;
62
62
  this.doNotPropagate = data.doNotPropagate || false;
63
+ this.localOnly = data.localOnly || false;
63
64
  let ModelClass = this.queryset.ModelClass;
64
65
  let instances = data.instances;
65
66
  // guarantee instances is an array
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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",