@statezero/core 0.1.93 → 0.1.95
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/core/eventReceivers.d.ts +1 -0
- package/dist/core/eventReceivers.js +1 -0
- package/dist/flavours/django/makeApiCall.js +1 -1
- package/dist/flavours/django/manager.d.ts +7 -7
- package/dist/flavours/django/manager.js +9 -11
- package/dist/flavours/django/operationFactory.d.ts +8 -0
- package/dist/flavours/django/operationFactory.js +24 -0
- package/dist/flavours/django/queryExecutor.d.ts +9 -0
- package/dist/flavours/django/queryExecutor.js +71 -0
- package/dist/flavours/django/querySet.d.ts +7 -0
- package/dist/flavours/django/querySet.js +20 -0
- package/dist/syncEngine/stores/modelStore.js +1 -0
- package/dist/syncEngine/stores/operation.d.ts +1 -0
- package/dist/syncEngine/stores/operation.js +1 -0
- package/dist/syncEngine/stores/operationEventHandlers.js +30 -31
- package/dist/syncEngine/stores/querysetStore.js +4 -1
- package/package.json +1 -1
|
@@ -80,7 +80,7 @@ export async function makeApiCall(querySet, operationType, args = {}, operationI
|
|
|
80
80
|
}
|
|
81
81
|
// Determine if this is a write operation that needs FileObject processing
|
|
82
82
|
const writeOperations = [
|
|
83
|
-
"create", "update", "delete", "update_instance", "delete_instance",
|
|
83
|
+
"create", "bulk_create", "update", "delete", "update_instance", "delete_instance",
|
|
84
84
|
"get_or_create", "update_or_create"
|
|
85
85
|
];
|
|
86
86
|
const isWriteOperation = writeOperations.includes(operationType);
|
|
@@ -28,13 +28,6 @@ export class Manager {
|
|
|
28
28
|
* @returns {QuerySet} A new QuerySet instance for the model.
|
|
29
29
|
*/
|
|
30
30
|
newQuerySet(): QuerySet<any>;
|
|
31
|
-
/**
|
|
32
|
-
* Creates a new custom QuerySet instance with an initial name.
|
|
33
|
-
*
|
|
34
|
-
* @param {string} name - The initial queryset name.
|
|
35
|
-
* @returns {QuerySet} A new QuerySet instance for the model.
|
|
36
|
-
*/
|
|
37
|
-
customQueryset(name: string): QuerySet<any>;
|
|
38
31
|
/**
|
|
39
32
|
* Retrieves a single model instance matching the provided filters.
|
|
40
33
|
*
|
|
@@ -126,6 +119,13 @@ export class Manager {
|
|
|
126
119
|
* @returns {Promise<*>} A promise that resolves to the newly created model instance.
|
|
127
120
|
*/
|
|
128
121
|
create(data: any): Promise<any>;
|
|
122
|
+
/**
|
|
123
|
+
* Creates multiple model instances using the provided data array.
|
|
124
|
+
*
|
|
125
|
+
* @param {Array<Object>} dataList - Array of data objects to create model instances.
|
|
126
|
+
* @returns {Promise<Array<*>>} A promise that resolves to an array of newly created model instances.
|
|
127
|
+
*/
|
|
128
|
+
bulkCreate(dataList: Array<Object>): Promise<Array<any>>;
|
|
129
129
|
/**
|
|
130
130
|
* Fetches all records using the current QuerySet.
|
|
131
131
|
*
|
|
@@ -35,17 +35,6 @@ export class Manager {
|
|
|
35
35
|
newQuerySet() {
|
|
36
36
|
return new this.QuerySetClass(this.ModelClass);
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Creates a new custom QuerySet instance with an initial name.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} name - The initial queryset name.
|
|
42
|
-
* @returns {QuerySet} A new QuerySet instance for the model.
|
|
43
|
-
*/
|
|
44
|
-
customQueryset(name) {
|
|
45
|
-
return new this.QuerySetClass(this.ModelClass, {
|
|
46
|
-
initialQueryset: name
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
38
|
/**
|
|
50
39
|
* Retrieves a single model instance matching the provided filters.
|
|
51
40
|
*
|
|
@@ -163,6 +152,15 @@ export class Manager {
|
|
|
163
152
|
async create(data) {
|
|
164
153
|
return this.newQuerySet().create(data);
|
|
165
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Creates multiple model instances using the provided data array.
|
|
157
|
+
*
|
|
158
|
+
* @param {Array<Object>} dataList - Array of data objects to create model instances.
|
|
159
|
+
* @returns {Promise<Array<*>>} A promise that resolves to an array of newly created model instances.
|
|
160
|
+
*/
|
|
161
|
+
async bulkCreate(dataList) {
|
|
162
|
+
return this.newQuerySet().bulkCreate(dataList);
|
|
163
|
+
}
|
|
166
164
|
/**
|
|
167
165
|
* Fetches all records using the current QuerySet.
|
|
168
166
|
*
|
|
@@ -11,6 +11,14 @@ export class OperationFactory {
|
|
|
11
11
|
* @returns {Operation} The created operation
|
|
12
12
|
*/
|
|
13
13
|
static createCreateOperation(queryset: QuerySet, data?: Object, operationId?: string): Operation;
|
|
14
|
+
/**
|
|
15
|
+
* Create a BULK_CREATE operation
|
|
16
|
+
* @param {QuerySet} queryset - The queryset context
|
|
17
|
+
* @param {Array<Object>} dataList - Array of data objects for the new instances
|
|
18
|
+
* @param {string} [operationId] - Optional operation ID (for hotpath events)
|
|
19
|
+
* @returns {Operation} The created operation
|
|
20
|
+
*/
|
|
21
|
+
static createBulkCreateOperation(queryset: QuerySet, dataList?: Array<Object>, operationId?: string): Operation;
|
|
14
22
|
/**
|
|
15
23
|
* Create an UPDATE operation with optimistic instance updates
|
|
16
24
|
* @param {QuerySet} queryset - The queryset context
|
|
@@ -31,6 +31,30 @@ export class OperationFactory {
|
|
|
31
31
|
args: { data },
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a BULK_CREATE operation
|
|
36
|
+
* @param {QuerySet} queryset - The queryset context
|
|
37
|
+
* @param {Array<Object>} dataList - Array of data objects for the new instances
|
|
38
|
+
* @param {string} [operationId] - Optional operation ID (for hotpath events)
|
|
39
|
+
* @returns {Operation} The created operation
|
|
40
|
+
*/
|
|
41
|
+
static createBulkCreateOperation(queryset, dataList = [], operationId = null) {
|
|
42
|
+
const ModelClass = queryset.ModelClass;
|
|
43
|
+
const primaryKeyField = ModelClass.primaryKeyField;
|
|
44
|
+
const opId = operationId || `${uuid7()}`;
|
|
45
|
+
// Create temp PKs for each instance
|
|
46
|
+
const instances = dataList.map((data, index) => {
|
|
47
|
+
const tempPk = createTempPk(`${opId}_${index}`);
|
|
48
|
+
return { ...data, [primaryKeyField]: tempPk };
|
|
49
|
+
});
|
|
50
|
+
return new Operation({
|
|
51
|
+
operationId: opId,
|
|
52
|
+
type: Type.BULK_CREATE,
|
|
53
|
+
instances: instances,
|
|
54
|
+
queryset: queryset,
|
|
55
|
+
args: { data: dataList },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
34
58
|
/**
|
|
35
59
|
* Create an UPDATE operation with optimistic instance updates
|
|
36
60
|
* @param {QuerySet} queryset - The queryset context
|
|
@@ -101,6 +101,15 @@ export class QueryExecutor {
|
|
|
101
101
|
* @returns {LiveThenable<Object>} The live Model instance which resolves to the created model.
|
|
102
102
|
*/
|
|
103
103
|
static executeCreate(querySet: QuerySet, operationType?: string, args?: Object): LiveThenable<Object>;
|
|
104
|
+
/**
|
|
105
|
+
* Executes a bulk_create operation with the QuerySet.
|
|
106
|
+
*
|
|
107
|
+
* @param {QuerySet} querySet - The QuerySet to execute.
|
|
108
|
+
* @param {string} operationType - The operation type (always 'bulk_create' for this method).
|
|
109
|
+
* @param {Object} args - Additional arguments for the operation.
|
|
110
|
+
* @returns {LiveThenable<Array>} Array of live Model instances.
|
|
111
|
+
*/
|
|
112
|
+
static executeBulkCreate(querySet: QuerySet, operationType?: string, args?: Object): LiveThenable<any[]>;
|
|
104
113
|
/**
|
|
105
114
|
* Executes an update_instance operation with the QuerySet.
|
|
106
115
|
*
|
|
@@ -333,6 +333,75 @@ export class QueryExecutor {
|
|
|
333
333
|
// 3) return the live‑thenable
|
|
334
334
|
return makeLiveThenable(live, promise);
|
|
335
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Executes a bulk_create operation with the QuerySet.
|
|
338
|
+
*
|
|
339
|
+
* @param {QuerySet} querySet - The QuerySet to execute.
|
|
340
|
+
* @param {string} operationType - The operation type (always 'bulk_create' for this method).
|
|
341
|
+
* @param {Object} args - Additional arguments for the operation.
|
|
342
|
+
* @returns {LiveThenable<Array>} Array of live Model instances.
|
|
343
|
+
*/
|
|
344
|
+
static executeBulkCreate(querySet, operationType = "bulk_create", args = {}) {
|
|
345
|
+
const ModelClass = querySet.ModelClass;
|
|
346
|
+
const operationId = `${uuid7()}`;
|
|
347
|
+
const primaryKeyField = ModelClass.primaryKeyField;
|
|
348
|
+
const apiCallArgs = {
|
|
349
|
+
data: args.data || [],
|
|
350
|
+
};
|
|
351
|
+
if (isNil(args.data) || !Array.isArray(args.data)) {
|
|
352
|
+
console.warn(`executeBulkCreate was called with invalid data`);
|
|
353
|
+
return Promise.resolve([]);
|
|
354
|
+
}
|
|
355
|
+
// Use factory to create operation
|
|
356
|
+
const operation = OperationFactory.createBulkCreateOperation(querySet, apiCallArgs.data, operationId);
|
|
357
|
+
// Create placeholder instances for each item
|
|
358
|
+
const liveInstances = operation.instances.map(instance => {
|
|
359
|
+
const tempPk = instance[primaryKeyField];
|
|
360
|
+
return ModelClass.fromPk(tempPk, querySet);
|
|
361
|
+
});
|
|
362
|
+
// Kick off the async call
|
|
363
|
+
const promise = makeApiCall(querySet, operationType, apiCallArgs, operationId, async (response) => {
|
|
364
|
+
const { data } = response.data;
|
|
365
|
+
const pks = Array.isArray(data) ? data : [];
|
|
366
|
+
// Set real PKs for all instances
|
|
367
|
+
pks.forEach((pk, index) => {
|
|
368
|
+
const tempPkKey = `${operationId}_${index}`;
|
|
369
|
+
setRealPk(tempPkKey, pk);
|
|
370
|
+
});
|
|
371
|
+
})
|
|
372
|
+
.then((response) => {
|
|
373
|
+
const { data, included, model_name } = response.data;
|
|
374
|
+
// Process included entities
|
|
375
|
+
processIncludedEntities(modelStoreRegistry, included, ModelClass);
|
|
376
|
+
// Get the real PKs
|
|
377
|
+
const pks = Array.isArray(data) ? data : [];
|
|
378
|
+
// Update each live instance with its real PK
|
|
379
|
+
pks.forEach((pk, index) => {
|
|
380
|
+
if (liveInstances[index]) {
|
|
381
|
+
liveInstances[index].pk = pk;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
// Get full entity data for all created instances
|
|
385
|
+
const entityMap = included[model_name] || {};
|
|
386
|
+
const confirmedInstances = pks.map(pk => entityMap[pk]).filter(Boolean);
|
|
387
|
+
// Confirm operation with full entity data
|
|
388
|
+
operation.mutate({
|
|
389
|
+
instances: confirmedInstances,
|
|
390
|
+
status: Status.CONFIRMED,
|
|
391
|
+
});
|
|
392
|
+
// Freeze all live instances
|
|
393
|
+
liveInstances.forEach(instance => breakThenable(instance));
|
|
394
|
+
// Also break the thenable on the array itself
|
|
395
|
+
breakThenable(liveInstances);
|
|
396
|
+
return liveInstances;
|
|
397
|
+
})
|
|
398
|
+
.catch((error) => {
|
|
399
|
+
operation.updateStatus(Status.REJECTED);
|
|
400
|
+
throw error;
|
|
401
|
+
});
|
|
402
|
+
// Return a live-thenable array
|
|
403
|
+
return makeLiveThenable(liveInstances, promise);
|
|
404
|
+
}
|
|
336
405
|
/**
|
|
337
406
|
* Executes an update_instance operation with the QuerySet.
|
|
338
407
|
*
|
|
@@ -447,6 +516,8 @@ export class QueryExecutor {
|
|
|
447
516
|
return this.executeDelete(querySet, operationType, args);
|
|
448
517
|
case "create":
|
|
449
518
|
return this.executeCreate(querySet, operationType, args);
|
|
519
|
+
case "bulk_create":
|
|
520
|
+
return this.executeBulkCreate(querySet, operationType, args);
|
|
450
521
|
case "get_or_create":
|
|
451
522
|
case "update_or_create":
|
|
452
523
|
return this.executeOrCreate(querySet, operationType, args);
|
|
@@ -194,6 +194,13 @@ export class QuerySet<T> {
|
|
|
194
194
|
* @returns {Promise<any>} The created model instance.
|
|
195
195
|
*/
|
|
196
196
|
create(data: Object): Promise<any>;
|
|
197
|
+
/**
|
|
198
|
+
* Creates multiple model instances using the provided data array.
|
|
199
|
+
*
|
|
200
|
+
* @param {Array<Object>} dataList - Array of data objects to create model instances.
|
|
201
|
+
* @returns {Promise<Array<any>>} A promise that resolves to an array of newly created model instances.
|
|
202
|
+
*/
|
|
203
|
+
bulkCreate(dataList: Array<Object>): Promise<Array<any>>;
|
|
197
204
|
/**
|
|
198
205
|
* Updates records in the QuerySet.
|
|
199
206
|
*
|
|
@@ -425,6 +425,26 @@ export class QuerySet {
|
|
|
425
425
|
}, this);
|
|
426
426
|
return QueryExecutor.execute(newQs, "create", { data: serializedData });
|
|
427
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Creates multiple model instances using the provided data array.
|
|
430
|
+
*
|
|
431
|
+
* @param {Array<Object>} dataList - Array of data objects to create model instances.
|
|
432
|
+
* @returns {Promise<Array<any>>} A promise that resolves to an array of newly created model instances.
|
|
433
|
+
*/
|
|
434
|
+
async bulkCreate(dataList) {
|
|
435
|
+
this.ensureNotMaterialized();
|
|
436
|
+
if (!Array.isArray(dataList)) {
|
|
437
|
+
throw new Error("bulkCreate expects an array of data objects");
|
|
438
|
+
}
|
|
439
|
+
// Serialize each data object before sending to backend
|
|
440
|
+
const serializedDataList = dataList.map(data => this._serializer.toInternal(data));
|
|
441
|
+
// Materialize for bulk create
|
|
442
|
+
const newQs = new QuerySet(this.ModelClass, {
|
|
443
|
+
...this._getConfig(),
|
|
444
|
+
materialized: true,
|
|
445
|
+
}, this);
|
|
446
|
+
return QueryExecutor.execute(newQs, "bulk_create", { data: serializedDataList });
|
|
447
|
+
}
|
|
428
448
|
/**
|
|
429
449
|
* Updates records in the QuerySet.
|
|
430
450
|
*
|
|
@@ -7,30 +7,26 @@ import { QuerySet } from '../../flavours/django/querySet.js';
|
|
|
7
7
|
import { isEqual, isNil } from 'lodash-es';
|
|
8
8
|
import hash from 'object-hash';
|
|
9
9
|
/**
|
|
10
|
-
* Returns querysets that
|
|
10
|
+
* Returns querysets that are in root mode (materialized with no materialized parent)
|
|
11
|
+
* Since filtered querysets render by filtering their parent's data, we only need
|
|
12
|
+
* to route operations to root querysets. Filtered children will see the operations
|
|
13
|
+
* when they filter their parent's rendered data.
|
|
11
14
|
* @param {QuerySet} queryset
|
|
12
15
|
* @returns {Map<QuerySet, Store>}
|
|
13
16
|
*/
|
|
14
|
-
function
|
|
15
|
-
// Collect ancestor nodes for comparison
|
|
16
|
-
let ancestorNodes = [];
|
|
17
|
-
let current = queryset;
|
|
18
|
-
while (current) {
|
|
19
|
-
ancestorNodes.push(current.nodes);
|
|
20
|
-
current = current.__parent;
|
|
21
|
-
}
|
|
17
|
+
function getRootQuerysets(queryset) {
|
|
22
18
|
const modelClass = queryset.ModelClass;
|
|
23
19
|
const result = new Map();
|
|
24
|
-
|
|
20
|
+
// Route only to querysets that are in root mode
|
|
21
|
+
// Note: _stores is Map<semanticKey, Store>, so we get the queryset from store.queryset
|
|
22
|
+
Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
|
|
25
23
|
if (store.modelClass !== modelClass)
|
|
26
24
|
return;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
catch (e) {
|
|
33
|
-
console.warn('Error comparing nodes for related querysets', e);
|
|
25
|
+
// Use the graph to determine if this store is in root mode
|
|
26
|
+
const { isRoot, root } = querysetStoreRegistry.querysetStoreGraph.findRoot(store.queryset);
|
|
27
|
+
// A queryset is in root mode if isRoot=true and root is its own semantic key
|
|
28
|
+
if (isRoot && root === store.queryset.semanticKey) {
|
|
29
|
+
result.set(store.queryset, store);
|
|
34
30
|
}
|
|
35
31
|
});
|
|
36
32
|
return result;
|
|
@@ -92,10 +88,11 @@ function processQuerysetStores(operation, actionType) {
|
|
|
92
88
|
// Different routing strategies based on operation type
|
|
93
89
|
switch (operation.type) {
|
|
94
90
|
case Type.CREATE:
|
|
91
|
+
case Type.BULK_CREATE:
|
|
95
92
|
case Type.GET_OR_CREATE:
|
|
96
93
|
case Type.UPDATE_OR_CREATE:
|
|
97
|
-
// For creates, route to
|
|
98
|
-
querysetStoreMap =
|
|
94
|
+
// For creates, route to root querysets (they might want to include the new item)
|
|
95
|
+
querysetStoreMap = getRootQuerysets(queryset);
|
|
99
96
|
break;
|
|
100
97
|
case Type.UPDATE:
|
|
101
98
|
case Type.UPDATE_INSTANCE:
|
|
@@ -109,32 +106,34 @@ function processQuerysetStores(operation, actionType) {
|
|
|
109
106
|
querysetStoreMap = new Map();
|
|
110
107
|
break;
|
|
111
108
|
default:
|
|
112
|
-
// For other operation types, use the existing
|
|
113
|
-
querysetStoreMap =
|
|
109
|
+
// For other operation types, use the existing root querysets logic
|
|
110
|
+
querysetStoreMap = getRootQuerysets(queryset);
|
|
114
111
|
break;
|
|
115
112
|
}
|
|
116
113
|
Array.from(querysetStoreMap.values()).forEach(applyAction);
|
|
117
114
|
}
|
|
118
115
|
/**
|
|
119
|
-
* Process an operation in the metric stores
|
|
116
|
+
* Process an operation in the metric stores
|
|
117
|
+
*
|
|
118
|
+
* For metrics, we route operations UP the family tree - any metric on an ancestor
|
|
119
|
+
* queryset should receive the operation so it can check if it affects the metric.
|
|
120
120
|
*
|
|
121
121
|
* @param {Operation} operation - The operation to process
|
|
122
122
|
* @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject')
|
|
123
123
|
*/
|
|
124
124
|
function processMetricStores(operation, actionType) {
|
|
125
125
|
const queryset = operation.queryset;
|
|
126
|
-
const
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
allQuerysets.forEach(qs => {
|
|
132
|
-
const stores = metricRegistry.getAllStoresForQueryset(qs);
|
|
126
|
+
const allMetricStores = new Set();
|
|
127
|
+
// Walk up the queryset family tree and collect all metrics
|
|
128
|
+
let current = queryset;
|
|
129
|
+
while (current) {
|
|
130
|
+
const stores = metricRegistry.getAllStoresForQueryset(current);
|
|
133
131
|
if (stores && stores.length > 0) {
|
|
134
132
|
stores.forEach(store => allMetricStores.add(store));
|
|
135
133
|
}
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
current = current.__parent;
|
|
135
|
+
}
|
|
136
|
+
if (allMetricStores.size === 0) {
|
|
138
137
|
return;
|
|
139
138
|
}
|
|
140
139
|
// Apply the action to each matching metric store
|
|
@@ -202,7 +202,9 @@ export class QuerysetStore {
|
|
|
202
202
|
typeof this.getRootStore === "function" &&
|
|
203
203
|
!this.isTemp) {
|
|
204
204
|
const { isRoot, rootStore } = this.getRootStore(this.queryset);
|
|
205
|
-
|
|
205
|
+
// If we're not in root mode and have a root store, ALWAYS render from it
|
|
206
|
+
// since operations are only routed to root stores now
|
|
207
|
+
if (!isRoot && rootStore) {
|
|
206
208
|
pks = this.renderFromRoot(optimistic, rootStore);
|
|
207
209
|
}
|
|
208
210
|
}
|
|
@@ -247,6 +249,7 @@ export class QuerysetStore {
|
|
|
247
249
|
let pk = instance[pkField];
|
|
248
250
|
switch (operation.type) {
|
|
249
251
|
case Type.CREATE:
|
|
252
|
+
case Type.BULK_CREATE:
|
|
250
253
|
currentPks.add(pk);
|
|
251
254
|
break;
|
|
252
255
|
case Type.CHECKPOINT:
|
package/package.json
CHANGED