@statezero/core 0.2.38 → 0.2.40
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/actions/backend1/django_app/calculate-hash.d.ts +57 -0
- package/dist/actions/backend1/django_app/calculate-hash.js +80 -0
- package/dist/actions/backend1/django_app/calculate-hash.schema.json +148 -0
- package/dist/actions/backend1/django_app/get-current-username.d.ts +29 -0
- package/dist/actions/backend1/django_app/get-current-username.js +65 -0
- package/dist/actions/backend1/django_app/get-current-username.schema.json +47 -0
- package/dist/actions/backend1/django_app/get-server-status.d.ts +38 -0
- package/dist/actions/backend1/django_app/get-server-status.js +68 -0
- package/dist/actions/backend1/django_app/get-server-status.schema.json +93 -0
- package/dist/actions/backend1/django_app/get-user-info.d.ts +44 -0
- package/dist/actions/backend1/django_app/get-user-info.js +70 -0
- package/dist/actions/backend1/django_app/get-user-info.schema.json +127 -0
- package/dist/actions/backend1/django_app/index.d.ts +1 -0
- package/dist/actions/backend1/django_app/index.js +6 -0
- package/dist/actions/backend1/django_app/process-data.d.ts +51 -0
- package/dist/actions/backend1/django_app/process-data.js +78 -0
- package/dist/actions/backend1/django_app/process-data.schema.json +117 -0
- package/dist/actions/backend1/django_app/send-notification.d.ts +55 -0
- package/dist/actions/backend1/django_app/send-notification.js +81 -0
- package/dist/actions/backend1/django_app/send-notification.schema.json +175 -0
- package/dist/actions/backend1/index.d.ts +1 -0
- package/dist/actions/backend1/index.js +1 -0
- package/dist/actions/default/django_app/calculate-hash.d.ts +57 -0
- package/dist/actions/default/django_app/calculate-hash.js +80 -0
- package/dist/actions/default/django_app/calculate-hash.schema.json +148 -0
- package/dist/actions/default/django_app/get-current-username.d.ts +29 -0
- package/dist/actions/default/django_app/get-current-username.js +65 -0
- package/dist/actions/default/django_app/get-current-username.schema.json +47 -0
- package/dist/actions/default/django_app/get-server-status.d.ts +38 -0
- package/dist/actions/default/django_app/get-server-status.js +68 -0
- package/dist/actions/default/django_app/get-server-status.schema.json +93 -0
- package/dist/actions/default/django_app/get-user-info.d.ts +44 -0
- package/dist/actions/default/django_app/get-user-info.js +70 -0
- package/dist/actions/default/django_app/get-user-info.schema.json +127 -0
- package/dist/actions/default/django_app/index.d.ts +1 -0
- package/dist/actions/default/django_app/index.js +6 -0
- package/dist/actions/default/django_app/process-data.d.ts +51 -0
- package/dist/actions/default/django_app/process-data.js +78 -0
- package/dist/actions/default/django_app/process-data.schema.json +117 -0
- package/dist/actions/default/django_app/send-notification.d.ts +55 -0
- package/dist/actions/default/django_app/send-notification.js +81 -0
- package/dist/actions/default/django_app/send-notification.schema.json +175 -0
- package/dist/actions/default/index.d.ts +1 -0
- package/dist/actions/default/index.js +1 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +5 -0
- package/dist/adaptors/react/composables.d.ts +1 -0
- package/dist/adaptors/react/composables.js +4 -0
- package/dist/adaptors/react/index.d.ts +1 -0
- package/dist/adaptors/react/index.js +1 -0
- package/dist/adaptors/vue/components/LayoutRenderer.js +46 -49
- package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
- package/dist/adaptors/vue/components/defaults/index.js +31 -0
- package/dist/adaptors/vue/components/index.d.ts +1 -0
- package/dist/adaptors/vue/components/index.js +7 -0
- package/dist/adaptors/vue/composables.d.ts +2 -0
- package/dist/adaptors/vue/composables.js +44 -0
- package/dist/adaptors/vue/index.d.ts +3 -0
- package/dist/adaptors/vue/index.js +4 -0
- package/dist/adaptors/vue/reactivity.d.ts +18 -0
- package/dist/adaptors/vue/reactivity.js +132 -0
- package/dist/cli/commands/sync.d.ts +6 -0
- package/dist/cli/commands/sync.js +30 -0
- package/dist/cli/commands/syncActions.d.ts +46 -0
- package/dist/cli/commands/syncActions.js +717 -0
- package/dist/cli/commands/syncModels.d.ts +132 -0
- package/dist/cli/commands/syncModels.js +1120 -0
- package/dist/cli/configFileLoader.d.ts +10 -0
- package/dist/cli/configFileLoader.js +85 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +22 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +273 -0
- package/dist/core/eventReceivers.d.ts +185 -0
- package/dist/core/eventReceivers.js +266 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +62 -0
- package/dist/errorHandler.d.ts +21 -0
- package/dist/errorHandler.js +27 -0
- package/dist/filtering/localFiltering.d.ts +110 -0
- package/dist/filtering/localFiltering.js +1080 -0
- package/dist/flavours/django/dates.d.ts +34 -0
- package/dist/flavours/django/dates.js +113 -0
- package/dist/flavours/django/errors.d.ts +138 -0
- package/dist/flavours/django/errors.js +195 -0
- package/dist/flavours/django/f.d.ts +6 -0
- package/dist/flavours/django/f.js +91 -0
- package/dist/flavours/django/files.d.ts +62 -0
- package/dist/flavours/django/files.js +355 -0
- package/dist/flavours/django/makeApiCall.d.ts +36 -0
- package/dist/flavours/django/makeApiCall.js +169 -0
- package/dist/flavours/django/manager.d.ts +204 -0
- package/dist/flavours/django/manager.js +222 -0
- package/dist/flavours/django/model.d.ts +137 -0
- package/dist/flavours/django/model.js +366 -0
- package/dist/flavours/django/operationFactory.d.ts +73 -0
- package/dist/flavours/django/operationFactory.js +248 -0
- package/dist/flavours/django/q.d.ts +70 -0
- package/dist/flavours/django/q.js +43 -0
- package/dist/flavours/django/queryExecutor.d.ts +149 -0
- package/dist/flavours/django/queryExecutor.js +590 -0
- package/dist/flavours/django/querySet.d.ts +301 -0
- package/dist/flavours/django/querySet.js +736 -0
- package/dist/flavours/django/serializers.d.ts +39 -0
- package/dist/flavours/django/serializers.js +296 -0
- package/dist/flavours/django/tempPk.d.ts +31 -0
- package/dist/flavours/django/tempPk.js +92 -0
- package/dist/flavours/django/utils.d.ts +19 -0
- package/dist/flavours/django/utils.js +29 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +48 -0
- package/dist/models/backend1/django_app/comprehensivemodel.d.ts +894 -0
- package/dist/models/backend1/django_app/comprehensivemodel.js +71 -0
- package/dist/models/backend1/django_app/comprehensivemodel.schema.json +870 -0
- package/dist/models/backend1/django_app/custompkmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/custompkmodel.js +69 -0
- package/dist/models/backend1/django_app/custompkmodel.schema.json +71 -0
- package/dist/models/backend1/django_app/dailyrate.d.ts +230 -0
- package/dist/models/backend1/django_app/dailyrate.js +71 -0
- package/dist/models/backend1/django_app/dailyrate.schema.json +212 -0
- package/dist/models/backend1/django_app/deepmodellevel1.d.ts +140 -0
- package/dist/models/backend1/django_app/deepmodellevel1.js +72 -0
- package/dist/models/backend1/django_app/deepmodellevel1.schema.json +114 -0
- package/dist/models/backend1/django_app/deepmodellevel2.d.ts +118 -0
- package/dist/models/backend1/django_app/deepmodellevel2.js +71 -0
- package/dist/models/backend1/django_app/deepmodellevel2.schema.json +92 -0
- package/dist/models/backend1/django_app/deepmodellevel3.d.ts +92 -0
- package/dist/models/backend1/django_app/deepmodellevel3.js +69 -0
- package/dist/models/backend1/django_app/deepmodellevel3.schema.json +69 -0
- package/dist/models/backend1/django_app/dummymodel.d.ts +134 -0
- package/dist/models/backend1/django_app/dummymodel.js +71 -0
- package/dist/models/backend1/django_app/dummymodel.schema.json +109 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.js +69 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.schema.json +69 -0
- package/dist/models/backend1/django_app/filetest.d.ts +140 -0
- package/dist/models/backend1/django_app/filetest.js +69 -0
- package/dist/models/backend1/django_app/filetest.schema.json +111 -0
- package/dist/models/backend1/django_app/index.d.ts +1 -0
- package/dist/models/backend1/django_app/index.js +21 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.d.ts +118 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +94 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.d.ts +118 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +94 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.d.ts +134 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +112 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.d.ts +118 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.js +71 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.schema.json +93 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.d.ts +134 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.js +71 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +111 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.js +69 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.schema.json +71 -0
- package/dist/models/backend1/django_app/order.d.ts +220 -0
- package/dist/models/backend1/django_app/order.js +71 -0
- package/dist/models/backend1/django_app/order.schema.json +203 -0
- package/dist/models/backend1/django_app/orderitem.d.ts +172 -0
- package/dist/models/backend1/django_app/orderitem.js +72 -0
- package/dist/models/backend1/django_app/orderitem.schema.json +149 -0
- package/dist/models/backend1/django_app/product.d.ts +254 -0
- package/dist/models/backend1/django_app/product.js +71 -0
- package/dist/models/backend1/django_app/product.schema.json +277 -0
- package/dist/models/backend1/django_app/productcategory.d.ts +92 -0
- package/dist/models/backend1/django_app/productcategory.js +69 -0
- package/dist/models/backend1/django_app/productcategory.schema.json +70 -0
- package/dist/models/backend1/django_app/rateplan.d.ts +92 -0
- package/dist/models/backend1/django_app/rateplan.js +69 -0
- package/dist/models/backend1/django_app/rateplan.schema.json +70 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.js +69 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
- package/dist/models/backend1/fileobject.d.ts +4 -0
- package/dist/models/backend1/fileobject.js +9 -0
- package/dist/models/backend1/index.d.ts +2 -0
- package/dist/models/backend1/index.js +2 -0
- package/dist/models/default/django_app/comprehensivemodel.d.ts +894 -0
- package/dist/models/default/django_app/comprehensivemodel.js +71 -0
- package/dist/models/default/django_app/comprehensivemodel.schema.json +870 -0
- package/dist/models/default/django_app/custompkmodel.d.ts +92 -0
- package/dist/models/default/django_app/custompkmodel.js +69 -0
- package/dist/models/default/django_app/custompkmodel.schema.json +71 -0
- package/dist/models/default/django_app/dailyrate.d.ts +230 -0
- package/dist/models/default/django_app/dailyrate.js +71 -0
- package/dist/models/default/django_app/dailyrate.schema.json +212 -0
- package/dist/models/default/django_app/deepmodellevel1.d.ts +128 -0
- package/dist/models/default/django_app/deepmodellevel1.js +72 -0
- package/dist/models/default/django_app/deepmodellevel1.schema.json +102 -0
- package/dist/models/default/django_app/deepmodellevel2.d.ts +106 -0
- package/dist/models/default/django_app/deepmodellevel2.js +71 -0
- package/dist/models/default/django_app/deepmodellevel2.schema.json +80 -0
- package/dist/models/default/django_app/deepmodellevel3.d.ts +80 -0
- package/dist/models/default/django_app/deepmodellevel3.js +69 -0
- package/dist/models/default/django_app/deepmodellevel3.schema.json +57 -0
- package/dist/models/default/django_app/dummymodel.d.ts +122 -0
- package/dist/models/default/django_app/dummymodel.js +71 -0
- package/dist/models/default/django_app/dummymodel.schema.json +97 -0
- package/dist/models/default/django_app/dummyrelatedmodel.d.ts +80 -0
- package/dist/models/default/django_app/dummyrelatedmodel.js +69 -0
- package/dist/models/default/django_app/dummyrelatedmodel.schema.json +57 -0
- package/dist/models/default/django_app/filetest.d.ts +128 -0
- package/dist/models/default/django_app/filetest.js +69 -0
- package/dist/models/default/django_app/filetest.schema.json +99 -0
- package/dist/models/default/django_app/index.d.ts +1 -0
- package/dist/models/default/django_app/index.js +21 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.d.ts +118 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +94 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.d.ts +118 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +94 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.d.ts +134 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +112 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.d.ts +118 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.js +71 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.schema.json +93 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.d.ts +134 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.js +71 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +111 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.d.ts +92 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.js +69 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.schema.json +71 -0
- package/dist/models/default/django_app/order.d.ts +220 -0
- package/dist/models/default/django_app/order.js +71 -0
- package/dist/models/default/django_app/order.schema.json +203 -0
- package/dist/models/default/django_app/orderitem.d.ts +172 -0
- package/dist/models/default/django_app/orderitem.js +72 -0
- package/dist/models/default/django_app/orderitem.schema.json +149 -0
- package/dist/models/default/django_app/product.d.ts +254 -0
- package/dist/models/default/django_app/product.js +71 -0
- package/dist/models/default/django_app/product.schema.json +277 -0
- package/dist/models/default/django_app/productcategory.d.ts +92 -0
- package/dist/models/default/django_app/productcategory.js +69 -0
- package/dist/models/default/django_app/productcategory.schema.json +70 -0
- package/dist/models/default/django_app/rateplan.d.ts +92 -0
- package/dist/models/default/django_app/rateplan.js +69 -0
- package/dist/models/default/django_app/rateplan.schema.json +70 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.js +69 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
- package/dist/models/default/fileobject.d.ts +4 -0
- package/dist/models/default/fileobject.js +9 -0
- package/dist/models/default/index.d.ts +2 -0
- package/dist/models/default/index.js +2 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +5 -0
- package/dist/react-entry.d.ts +2 -0
- package/dist/react-entry.js +2 -0
- package/dist/reactiveAdaptor.d.ts +24 -0
- package/dist/reactiveAdaptor.js +38 -0
- package/dist/reset.d.ts +15 -0
- package/dist/reset.js +97 -0
- package/dist/setup.d.ts +15 -0
- package/dist/setup.js +33 -0
- package/dist/syncEngine/cache/cache.d.ts +75 -0
- package/dist/syncEngine/cache/cache.js +355 -0
- package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
- package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
- package/dist/syncEngine/registries/metricRegistry.d.ts +58 -0
- package/dist/syncEngine/registries/metricRegistry.js +171 -0
- package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
- package/dist/syncEngine/registries/modelStoreRegistry.js +63 -0
- package/dist/syncEngine/registries/querysetStoreGraph.d.ts +41 -0
- package/dist/syncEngine/registries/querysetStoreGraph.js +174 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +72 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.js +335 -0
- package/dist/syncEngine/stores/metricStore.d.ts +55 -0
- package/dist/syncEngine/stores/metricStore.js +222 -0
- package/dist/syncEngine/stores/modelStore.d.ts +53 -0
- package/dist/syncEngine/stores/modelStore.js +565 -0
- package/dist/syncEngine/stores/operation.d.ts +139 -0
- package/dist/syncEngine/stores/operation.js +291 -0
- package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
- package/dist/syncEngine/stores/operationEventHandlers.js +348 -0
- package/dist/syncEngine/stores/querysetStore.d.ts +60 -0
- package/dist/syncEngine/stores/querysetStore.js +294 -0
- package/dist/syncEngine/stores/reactivity.d.ts +3 -0
- package/dist/syncEngine/stores/reactivity.js +4 -0
- package/dist/syncEngine/stores/utils.d.ts +14 -0
- package/dist/syncEngine/stores/utils.js +32 -0
- package/dist/syncEngine/sync.d.ts +51 -0
- package/dist/syncEngine/sync.js +419 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.js +175 -0
- package/dist/vue-entry.d.ts +15 -0
- package/dist/vue-entry.js +7 -0
- package/package.json +6 -7
- package/dist/adaptors/vue/components/layout.tailwind.css +0 -51
- /package/{dist → src}/adaptors/vue/components/layout.css +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { getAllEventReceivers } from "../core/eventReceivers.js";
|
|
2
|
+
import { operationRegistry, OperationMembership } from "./stores/operation.js";
|
|
3
|
+
import { initializeAllEventReceivers } from "../config.js";
|
|
4
|
+
import { getEventReceiver } from "../core/eventReceivers.js";
|
|
5
|
+
import { querysetStoreRegistry, QuerysetStoreRegistry, } from "./registries/querysetStoreRegistry.js";
|
|
6
|
+
import { modelStoreRegistry, ModelStoreRegistry, } from "./registries/modelStoreRegistry.js";
|
|
7
|
+
import { metricRegistry, MetricRegistry } from "./registries/metricRegistry.js";
|
|
8
|
+
import { getModelClass, getConfig } from "../config.js";
|
|
9
|
+
import { isNil } from "lodash-es";
|
|
10
|
+
import { QuerysetStore } from "./stores/querysetStore.js";
|
|
11
|
+
import { v7 as uuidv7 } from "uuid";
|
|
12
|
+
export class EventPayload {
|
|
13
|
+
constructor(data) {
|
|
14
|
+
this.event = data.event;
|
|
15
|
+
this.model = data.model;
|
|
16
|
+
this.operation_id = data.operation_id;
|
|
17
|
+
this.pk_field_name = data.pk_field_name;
|
|
18
|
+
this.configKey = data.configKey;
|
|
19
|
+
// Parse PK fields to numbers in instances
|
|
20
|
+
this.instances = data.instances?.map(instance => {
|
|
21
|
+
if (instance && this.pk_field_name && instance[this.pk_field_name] != null) {
|
|
22
|
+
return {
|
|
23
|
+
...instance,
|
|
24
|
+
[this.pk_field_name]: Number(instance[this.pk_field_name])
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return instance;
|
|
28
|
+
}) || data.instances;
|
|
29
|
+
this._cachedInstances = null;
|
|
30
|
+
}
|
|
31
|
+
get modelClass() {
|
|
32
|
+
return getModelClass(this.model, this.configKey);
|
|
33
|
+
}
|
|
34
|
+
async getFullInstances() {
|
|
35
|
+
if (this.event === "delete") {
|
|
36
|
+
throw new Error("Cannot fetch full instances for delete operation bozo...");
|
|
37
|
+
}
|
|
38
|
+
if (isNil(this._cachedInstances)) {
|
|
39
|
+
this._cachedInstances = await this.modelClass.objects
|
|
40
|
+
.filter({
|
|
41
|
+
[`${this.modelClass.primaryKeyField}__in`]: this.instances.map((instance) => instance[this.modelClass.primaryKeyField]),
|
|
42
|
+
})
|
|
43
|
+
.fetch();
|
|
44
|
+
}
|
|
45
|
+
return this._cachedInstances;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class SyncManager {
|
|
49
|
+
constructor() {
|
|
50
|
+
this.handleEvent = (event) => {
|
|
51
|
+
let payload = new EventPayload(event);
|
|
52
|
+
let isLocalOperation = operationRegistry.has(payload.operation_id);
|
|
53
|
+
// Always process metrics immediately (they're lightweight)
|
|
54
|
+
if (this.registries.has(MetricRegistry)) {
|
|
55
|
+
this.processMetrics(payload);
|
|
56
|
+
}
|
|
57
|
+
if (isLocalOperation) {
|
|
58
|
+
// Check if any querysets were marked MAYBE for this operation
|
|
59
|
+
// These need to be synced since we couldn't determine membership client-side
|
|
60
|
+
this.syncMaybeQuerysets(payload.operation_id);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Add to batch for queryset/model processing
|
|
64
|
+
const key = `${event.model}::${event.configKey}`;
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
// If this is the first event in the batch, start max wait timer
|
|
67
|
+
if (this.eventBatch.size === 0) {
|
|
68
|
+
this.batchStartTime = now;
|
|
69
|
+
this.maxWaitTimer = setTimeout(() => {
|
|
70
|
+
this.processBatch("maxWait");
|
|
71
|
+
}, this.maxWaitMs);
|
|
72
|
+
}
|
|
73
|
+
this.eventBatch.set(key, {
|
|
74
|
+
event: payload,
|
|
75
|
+
firstSeen: this.eventBatch.has(key)
|
|
76
|
+
? this.eventBatch.get(key).firstSeen
|
|
77
|
+
: now,
|
|
78
|
+
});
|
|
79
|
+
// Reset debounce timer
|
|
80
|
+
if (this.debounceTimer) {
|
|
81
|
+
clearTimeout(this.debounceTimer);
|
|
82
|
+
}
|
|
83
|
+
this.debounceTimer = setTimeout(() => {
|
|
84
|
+
this.processBatch("debounce");
|
|
85
|
+
}, this.debounceMs);
|
|
86
|
+
};
|
|
87
|
+
this.registries = new Map();
|
|
88
|
+
// Map of registries to model sets
|
|
89
|
+
this.followedModels = new Map();
|
|
90
|
+
// Map of querysets to keep synced
|
|
91
|
+
this.followAllQuerysets = true;
|
|
92
|
+
this.followedQuerysets = new Map();
|
|
93
|
+
this.periodicSyncTimer = null;
|
|
94
|
+
// Batching for event processing
|
|
95
|
+
this.eventBatch = new Map(); // model::configKey -> { event, firstSeen }
|
|
96
|
+
this.debounceTimer = null;
|
|
97
|
+
this.maxWaitTimer = null;
|
|
98
|
+
this.debounceMs = 100; // Wait for rapid events to settle
|
|
99
|
+
this.maxWaitMs = 2000; // Maximum time to hold events
|
|
100
|
+
this.batchStartTime = null;
|
|
101
|
+
}
|
|
102
|
+
withTimeout(promise, ms) {
|
|
103
|
+
// If no timeout specified, use 2x the periodic sync interval, or 30s as fallback
|
|
104
|
+
if (!ms) {
|
|
105
|
+
try {
|
|
106
|
+
const config = getConfig();
|
|
107
|
+
const intervalSeconds = config.periodicSyncIntervalSeconds;
|
|
108
|
+
ms = intervalSeconds ? intervalSeconds * 2000 : 30000; // 2x interval in ms, or 30s default
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
ms = 30000; // 30s fallback if no config
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return Promise.race([
|
|
115
|
+
promise,
|
|
116
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Sync timeout after ${ms}ms`)), ms)),
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Initialize event handlers for all event receivers
|
|
121
|
+
*/
|
|
122
|
+
initialize() {
|
|
123
|
+
// Initialize all event receivers
|
|
124
|
+
initializeAllEventReceivers();
|
|
125
|
+
// Get all registered event receivers
|
|
126
|
+
const eventReceivers = getAllEventReceivers();
|
|
127
|
+
// Register the event handlers with each receiver
|
|
128
|
+
eventReceivers.forEach((receiver, configKey) => {
|
|
129
|
+
if (receiver) {
|
|
130
|
+
// Model events go to handleEvent
|
|
131
|
+
receiver.addModelEventHandler(this.handleEvent.bind(this));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
this.startPeriodicSync();
|
|
135
|
+
}
|
|
136
|
+
startPeriodicSync() {
|
|
137
|
+
if (this.periodicSyncTimer)
|
|
138
|
+
return;
|
|
139
|
+
try {
|
|
140
|
+
const config = getConfig();
|
|
141
|
+
const intervalSeconds = config.periodicSyncIntervalSeconds;
|
|
142
|
+
// If null or undefined, don't start periodic sync
|
|
143
|
+
if (!intervalSeconds) {
|
|
144
|
+
console.log("[SyncManager] Periodic sync disabled (set to null)");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const intervalMs = intervalSeconds * 1000;
|
|
148
|
+
this.periodicSyncTimer = setInterval(() => {
|
|
149
|
+
this.syncStaleQuerysets();
|
|
150
|
+
}, intervalMs);
|
|
151
|
+
console.log(`[SyncManager] Periodic sync started: ${intervalSeconds}s intervals`);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// If no config, don't start periodic sync by default
|
|
155
|
+
console.log("[SyncManager] No config found, periodic sync disabled by default");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async syncStaleQuerysets() {
|
|
159
|
+
const querysetRegistry = this.registries.get(QuerysetStoreRegistry);
|
|
160
|
+
if (!querysetRegistry)
|
|
161
|
+
return;
|
|
162
|
+
// Generate operationId for this sync batch - querysets in same chain will coordinate
|
|
163
|
+
const operationId = `periodic-sync-${uuidv7()}`;
|
|
164
|
+
// Get dbSynced keys (followed querysets)
|
|
165
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
166
|
+
// Collect all stores to sync
|
|
167
|
+
const storesToSync = [];
|
|
168
|
+
for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
|
|
169
|
+
const isFollowed = this.isStoreFollowed(querysetRegistry, semanticKey);
|
|
170
|
+
if (this.followAllQuerysets || isFollowed) {
|
|
171
|
+
storesToSync.push(store);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (storesToSync.length > 0) {
|
|
175
|
+
console.log(`[SyncManager] Periodic sync: syncing ${storesToSync.length} stores`);
|
|
176
|
+
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
177
|
+
await Promise.all(storesToSync.map(store => querysetRegistry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
178
|
+
}
|
|
179
|
+
// Prune unreferenced model instances
|
|
180
|
+
this.pruneUnreferencedModels();
|
|
181
|
+
}
|
|
182
|
+
pruneUnreferencedModels() {
|
|
183
|
+
const modelRegistry = this.registries.get(ModelStoreRegistry);
|
|
184
|
+
const querysetRegistry = this.registries.get(QuerysetStoreRegistry);
|
|
185
|
+
if (!modelRegistry || !querysetRegistry)
|
|
186
|
+
return;
|
|
187
|
+
// Prune each model store
|
|
188
|
+
for (const [modelClass, store] of modelRegistry._stores.entries()) {
|
|
189
|
+
store.pruneUnreferencedInstances(querysetRegistry);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
isStoreFollowed(registry, semanticKey) {
|
|
193
|
+
const followingQuerysets = registry.followingQuerysets.get(semanticKey);
|
|
194
|
+
if (!followingQuerysets)
|
|
195
|
+
return false;
|
|
196
|
+
return [...followingQuerysets].some((queryset) => {
|
|
197
|
+
return this.isQuerysetFollowed(queryset);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
cleanup() {
|
|
201
|
+
if (this.periodicSyncTimer) {
|
|
202
|
+
clearInterval(this.periodicSyncTimer);
|
|
203
|
+
this.periodicSyncTimer = null;
|
|
204
|
+
}
|
|
205
|
+
// Clean up batch timers
|
|
206
|
+
if (this.debounceTimer) {
|
|
207
|
+
clearTimeout(this.debounceTimer);
|
|
208
|
+
this.debounceTimer = null;
|
|
209
|
+
}
|
|
210
|
+
if (this.maxWaitTimer) {
|
|
211
|
+
clearTimeout(this.maxWaitTimer);
|
|
212
|
+
this.maxWaitTimer = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
followModel(registry, modelClass) {
|
|
216
|
+
const models = this.followedModels.get(registry) || new Set();
|
|
217
|
+
this.followedModels.set(registry, models);
|
|
218
|
+
if (models.has(modelClass))
|
|
219
|
+
return;
|
|
220
|
+
const alreadyFollowed = [...this.followedModels.values()].some((set) => set.has(modelClass));
|
|
221
|
+
models.add(modelClass);
|
|
222
|
+
if (!alreadyFollowed) {
|
|
223
|
+
getEventReceiver(modelClass.configKey)?.subscribe(modelClass.modelName, this.handleEvent);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
unfollowModel(registry, modelClass) {
|
|
227
|
+
const models = this.followedModels.get(registry);
|
|
228
|
+
if (!models)
|
|
229
|
+
return;
|
|
230
|
+
models.delete(modelClass);
|
|
231
|
+
const stillFollowed = [...this.followedModels.values()].some((set) => set.has(modelClass));
|
|
232
|
+
if (!stillFollowed) {
|
|
233
|
+
getEventReceiver(modelClass.configKey)?.unsubscribe(modelClass.modelName, this.handleEvent);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
manageRegistry(registry) {
|
|
237
|
+
this.registries.set(registry.constructor, registry);
|
|
238
|
+
registry.setSyncManager(this);
|
|
239
|
+
}
|
|
240
|
+
removeRegistry(registry) {
|
|
241
|
+
this.registries.delete(registry.constructor);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Sync querysets that were marked MAYBE for a local operation.
|
|
245
|
+
* Called when a backend event arrives for an operation we initiated locally.
|
|
246
|
+
*/
|
|
247
|
+
syncMaybeQuerysets(operationId) {
|
|
248
|
+
const states = operationRegistry.getQuerysetStates(operationId);
|
|
249
|
+
if (!states)
|
|
250
|
+
return;
|
|
251
|
+
const registry = this.registries.get(QuerysetStoreRegistry);
|
|
252
|
+
if (!registry)
|
|
253
|
+
return;
|
|
254
|
+
const storesToSync = [];
|
|
255
|
+
for (const [semanticKey, membership] of states.entries()) {
|
|
256
|
+
if (membership === OperationMembership.MAYBE) {
|
|
257
|
+
const store = registry._stores.get(semanticKey);
|
|
258
|
+
if (store) {
|
|
259
|
+
storesToSync.push(store);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (storesToSync.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
console.log(`[SyncManager] Syncing ${storesToSync.length} MAYBE querysets for local operation ${operationId}`);
|
|
266
|
+
const syncOperationId = `maybe-sync-${uuidv7()}`;
|
|
267
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
268
|
+
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, syncOperationId, dbSyncedKeys)));
|
|
269
|
+
}
|
|
270
|
+
processBatch(reason = "unknown") {
|
|
271
|
+
if (this.eventBatch.size === 0)
|
|
272
|
+
return;
|
|
273
|
+
// Clear timers
|
|
274
|
+
if (this.debounceTimer) {
|
|
275
|
+
clearTimeout(this.debounceTimer);
|
|
276
|
+
this.debounceTimer = null;
|
|
277
|
+
}
|
|
278
|
+
if (this.maxWaitTimer) {
|
|
279
|
+
clearTimeout(this.maxWaitTimer);
|
|
280
|
+
this.maxWaitTimer = null;
|
|
281
|
+
}
|
|
282
|
+
const events = Array.from(this.eventBatch.values()).map((item) => item.event);
|
|
283
|
+
const waitTime = Date.now() - this.batchStartTime;
|
|
284
|
+
this.eventBatch.clear();
|
|
285
|
+
this.batchStartTime = null;
|
|
286
|
+
console.log(`[SyncManager] Processing batch of ${events.length} events (reason: ${reason}, waited: ${waitTime}ms)`);
|
|
287
|
+
// Group events by model for efficient processing
|
|
288
|
+
const eventsByModel = new Map();
|
|
289
|
+
events.forEach((event) => {
|
|
290
|
+
const key = `${event.model}::${event.configKey}`;
|
|
291
|
+
if (!eventsByModel.has(key)) {
|
|
292
|
+
eventsByModel.set(key, []);
|
|
293
|
+
}
|
|
294
|
+
eventsByModel.get(key).push(event);
|
|
295
|
+
});
|
|
296
|
+
// Process each model's events as a batch
|
|
297
|
+
eventsByModel.forEach((modelEvents, modelKey) => {
|
|
298
|
+
const event = modelEvents[0]; // Use first event as representative
|
|
299
|
+
if (this.registries.has(QuerysetStoreRegistry)) {
|
|
300
|
+
this.processQuerysetsBatch(event, modelEvents);
|
|
301
|
+
}
|
|
302
|
+
if (this.registries.has(ModelStoreRegistry)) {
|
|
303
|
+
this.processModels(event);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
isQuerysetFollowed(queryset) {
|
|
308
|
+
const activeSemanticKeys = new Set([...this.followedQuerysets].map((qs) => qs.semanticKey));
|
|
309
|
+
let current = queryset;
|
|
310
|
+
while (current) {
|
|
311
|
+
if (activeSemanticKeys.has(current.semanticKey)) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
current = current.__parent;
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
processQuerysetsBatch(representativeEvent, allEvents) {
|
|
319
|
+
const registry = this.registries.get(QuerysetStoreRegistry);
|
|
320
|
+
// Collect all stores that need syncing for this model
|
|
321
|
+
const storesToSync = [];
|
|
322
|
+
for (const [semanticKey, store] of registry._stores.entries()) {
|
|
323
|
+
if (store.modelClass.modelName === representativeEvent.model &&
|
|
324
|
+
store.modelClass.configKey === representativeEvent.configKey) {
|
|
325
|
+
if (this.followAllQuerysets) {
|
|
326
|
+
storesToSync.push(store);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const followingQuerysets = registry.followingQuerysets.get(semanticKey);
|
|
330
|
+
if (followingQuerysets) {
|
|
331
|
+
const shouldSync = [...followingQuerysets].some((queryset) => {
|
|
332
|
+
return this.isQuerysetFollowed(queryset);
|
|
333
|
+
});
|
|
334
|
+
if (shouldSync) {
|
|
335
|
+
storesToSync.push(store);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (storesToSync.length === 0)
|
|
341
|
+
return;
|
|
342
|
+
// Sync all relevant stores for this model
|
|
343
|
+
console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
|
|
344
|
+
// Generate operationId for this batch - querysets in same chain will coordinate
|
|
345
|
+
const operationId = `remote-event-${uuidv7()}`;
|
|
346
|
+
// Get dbSynced keys (followed querysets)
|
|
347
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
348
|
+
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
349
|
+
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
350
|
+
}
|
|
351
|
+
processMetrics(event) {
|
|
352
|
+
const registry = this.registries.get(MetricRegistry);
|
|
353
|
+
for (const [key, entry] of registry._stores.entries()) {
|
|
354
|
+
// Check if the store has a queryset with the model class AND correct backend
|
|
355
|
+
if (entry.queryset &&
|
|
356
|
+
entry.queryset.modelClass.modelName === event.model &&
|
|
357
|
+
entry.queryset.modelClass.configKey === event.configKey) {
|
|
358
|
+
if (this.followAllQuerysets) {
|
|
359
|
+
entry.store.sync();
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
// Check if this queryset (or any parent) is being followed
|
|
363
|
+
if (this.isQuerysetFollowed(entry.queryset)) {
|
|
364
|
+
console.log(`syncing metric store for key: ${key}`);
|
|
365
|
+
entry.store.sync();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
processModels(event) {
|
|
371
|
+
const registry = this.registries.get(ModelStoreRegistry);
|
|
372
|
+
if (!registry)
|
|
373
|
+
return;
|
|
374
|
+
const modelStore = registry.getStore(event.modelClass);
|
|
375
|
+
if (!modelStore)
|
|
376
|
+
return;
|
|
377
|
+
// Get PKs from the event
|
|
378
|
+
const eventPks = new Set((event.instances || [])
|
|
379
|
+
.filter(inst => inst && inst[event.pk_field_name] != null)
|
|
380
|
+
.map(inst => inst[event.pk_field_name]));
|
|
381
|
+
if (eventPks.size === 0)
|
|
382
|
+
return;
|
|
383
|
+
// Get PKs that are already in the model store's ground truth
|
|
384
|
+
const groundTruthPks = new Set(modelStore.groundTruthPks);
|
|
385
|
+
// Get PKs that are top-level in ANY queryset for this model (not just followed ones)
|
|
386
|
+
const querysetRegistry = this.registries.get(QuerysetStoreRegistry);
|
|
387
|
+
const topLevelQuerysetPks = new Set();
|
|
388
|
+
if (querysetRegistry) {
|
|
389
|
+
for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
|
|
390
|
+
// Check ALL querysets for this model (permanent and temporary)
|
|
391
|
+
if (store.modelClass.modelName === event.model &&
|
|
392
|
+
store.modelClass.configKey === event.configKey) {
|
|
393
|
+
// Get this queryset's ground truth PKs (top-level instances)
|
|
394
|
+
// Don't use render() as it applies operations - we just want ground truth
|
|
395
|
+
store.groundTruthPks.forEach(pk => topLevelQuerysetPks.add(pk));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Find PKs that are:
|
|
400
|
+
// 1. In the event
|
|
401
|
+
// 2. Already in ground truth (we're tracking them)
|
|
402
|
+
// 3. NOT top-level in any queryset (they're only nested/included)
|
|
403
|
+
const pksToSync = [];
|
|
404
|
+
eventPks.forEach(pk => {
|
|
405
|
+
if (groundTruthPks.has(pk) && !topLevelQuerysetPks.has(pk)) {
|
|
406
|
+
pksToSync.push(pk);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
if (pksToSync.length > 0) {
|
|
410
|
+
console.log(`[SyncManager] Syncing ${pksToSync.length} nested-only PKs for ${event.model}: ${pksToSync}`);
|
|
411
|
+
modelStore.sync(pksToSync);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const syncManager = new SyncManager();
|
|
416
|
+
syncManager.manageRegistry(querysetStoreRegistry);
|
|
417
|
+
syncManager.manageRegistry(modelStoreRegistry);
|
|
418
|
+
syncManager.manageRegistry(metricRegistry);
|
|
419
|
+
export { syncManager };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function createTestConfig({ apiUrl, backendKey, generatedTypesDir, generatedActionsDir, getAuthHeaders, testAuthUserId, testAuthHeaders, testAuthHeaderName, eventsType, fileRootURL, fileUploadMode, }: {
|
|
2
|
+
apiUrl: any;
|
|
3
|
+
backendKey?: string | undefined;
|
|
4
|
+
generatedTypesDir?: string | undefined;
|
|
5
|
+
generatedActionsDir: any;
|
|
6
|
+
getAuthHeaders: any;
|
|
7
|
+
testAuthUserId: any;
|
|
8
|
+
testAuthHeaders: any;
|
|
9
|
+
testAuthHeaderName?: string | undefined;
|
|
10
|
+
eventsType?: string | undefined;
|
|
11
|
+
fileRootURL: any;
|
|
12
|
+
fileUploadMode: any;
|
|
13
|
+
}): {
|
|
14
|
+
config: {
|
|
15
|
+
backendConfigs: {
|
|
16
|
+
[backendKey]: {
|
|
17
|
+
API_URL: any;
|
|
18
|
+
GENERATED_TYPES_DIR: string;
|
|
19
|
+
getAuthHeaders: () => any;
|
|
20
|
+
events: {
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
testHeaders: {
|
|
27
|
+
setSeeding(enabled: any): void;
|
|
28
|
+
setReset(enabled: any): void;
|
|
29
|
+
setAuthUserId(userId: any): void;
|
|
30
|
+
withSeeding(fn: any): any;
|
|
31
|
+
withReset(fn: any): any;
|
|
32
|
+
withAuthUserId(userId: any, fn: any): any;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export function setupTestStateZero({ apiUrl, backendKey, generatedTypesDir, generatedActionsDir, getAuthHeaders, testAuthUserId, testAuthHeaders, testAuthHeaderName, eventsType, fileRootURL, fileUploadMode, getModelClass, adapters, }: {
|
|
36
|
+
apiUrl: any;
|
|
37
|
+
backendKey?: string | undefined;
|
|
38
|
+
generatedTypesDir?: string | undefined;
|
|
39
|
+
generatedActionsDir: any;
|
|
40
|
+
getAuthHeaders: any;
|
|
41
|
+
testAuthUserId: any;
|
|
42
|
+
testAuthHeaders: any;
|
|
43
|
+
testAuthHeaderName: any;
|
|
44
|
+
eventsType?: string | undefined;
|
|
45
|
+
fileRootURL: any;
|
|
46
|
+
fileUploadMode: any;
|
|
47
|
+
getModelClass: any;
|
|
48
|
+
adapters: any;
|
|
49
|
+
}): {
|
|
50
|
+
setSeeding(enabled: any): void;
|
|
51
|
+
setReset(enabled: any): void;
|
|
52
|
+
setAuthUserId(userId: any): void;
|
|
53
|
+
withSeeding(fn: any): any;
|
|
54
|
+
withReset(fn: any): any;
|
|
55
|
+
withAuthUserId(userId: any, fn: any): any;
|
|
56
|
+
};
|
|
57
|
+
export function createActionMocker(actionRegistry: any): {
|
|
58
|
+
mock(actionName: any, handler: any, backendKey?: string): void;
|
|
59
|
+
restore(actionName: any, backendKey?: string): void;
|
|
60
|
+
restoreAll(): void;
|
|
61
|
+
};
|
|
62
|
+
export function seedRemote(testHeaders: any, seedFn: any): Promise<any>;
|
|
63
|
+
export function resetRemote(testHeaders: any, resetFn: any): Promise<any>;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { setupStateZero } from "./setup.js";
|
|
2
|
+
function _normalizeHeaders(headers) {
|
|
3
|
+
return headers && typeof headers === "object" ? { ...headers } : {};
|
|
4
|
+
}
|
|
5
|
+
function _withFlag(state, key, fn) {
|
|
6
|
+
const previous = state[key];
|
|
7
|
+
state[key] = true;
|
|
8
|
+
try {
|
|
9
|
+
const result = fn();
|
|
10
|
+
if (result && typeof result.then === "function") {
|
|
11
|
+
return result.finally(() => {
|
|
12
|
+
state[key] = previous;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
state[key] = previous;
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
state[key] = previous;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createTestConfig({ apiUrl, backendKey = "default", generatedTypesDir = "./src/models/", generatedActionsDir, getAuthHeaders, testAuthUserId, testAuthHeaders, testAuthHeaderName = "X-TEST-USER-ID", eventsType = "none", fileRootURL, fileUploadMode, }) {
|
|
24
|
+
const state = {
|
|
25
|
+
seeding: false,
|
|
26
|
+
reset: false,
|
|
27
|
+
authUserId: undefined,
|
|
28
|
+
};
|
|
29
|
+
const testHeaders = {
|
|
30
|
+
setSeeding(enabled) {
|
|
31
|
+
state.seeding = Boolean(enabled);
|
|
32
|
+
},
|
|
33
|
+
setReset(enabled) {
|
|
34
|
+
state.reset = Boolean(enabled);
|
|
35
|
+
},
|
|
36
|
+
setAuthUserId(userId) {
|
|
37
|
+
state.authUserId = userId;
|
|
38
|
+
},
|
|
39
|
+
withSeeding(fn) {
|
|
40
|
+
return _withFlag(state, "seeding", fn);
|
|
41
|
+
},
|
|
42
|
+
withReset(fn) {
|
|
43
|
+
return _withFlag(state, "reset", fn);
|
|
44
|
+
},
|
|
45
|
+
withAuthUserId(userId, fn) {
|
|
46
|
+
const previous = state.authUserId;
|
|
47
|
+
state.authUserId = userId;
|
|
48
|
+
try {
|
|
49
|
+
const result = fn();
|
|
50
|
+
if (result && typeof result.then === "function") {
|
|
51
|
+
return result.finally(() => {
|
|
52
|
+
state.authUserId = previous;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
state.authUserId = previous;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
state.authUserId = previous;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const wrappedGetAuthHeaders = () => {
|
|
65
|
+
const headers = _normalizeHeaders(getAuthHeaders ? getAuthHeaders() : {});
|
|
66
|
+
const extraAuthHeaders = typeof testAuthHeaders === "function"
|
|
67
|
+
? testAuthHeaders()
|
|
68
|
+
: testAuthHeaders;
|
|
69
|
+
if (extraAuthHeaders && typeof extraAuthHeaders === "object") {
|
|
70
|
+
Object.assign(headers, extraAuthHeaders);
|
|
71
|
+
}
|
|
72
|
+
const activeUserId = state.authUserId ?? testAuthUserId;
|
|
73
|
+
if (activeUserId !== undefined && activeUserId !== null) {
|
|
74
|
+
headers[testAuthHeaderName] = String(activeUserId);
|
|
75
|
+
}
|
|
76
|
+
if (state.seeding) {
|
|
77
|
+
headers["X-TEST-SEEDING"] = "1";
|
|
78
|
+
}
|
|
79
|
+
if (state.reset) {
|
|
80
|
+
headers["X-TEST-RESET"] = "1";
|
|
81
|
+
}
|
|
82
|
+
return headers;
|
|
83
|
+
};
|
|
84
|
+
const backendConfig = {
|
|
85
|
+
API_URL: apiUrl,
|
|
86
|
+
GENERATED_TYPES_DIR: generatedTypesDir,
|
|
87
|
+
getAuthHeaders: wrappedGetAuthHeaders,
|
|
88
|
+
events: { type: eventsType },
|
|
89
|
+
};
|
|
90
|
+
if (generatedActionsDir) {
|
|
91
|
+
backendConfig.GENERATED_ACTIONS_DIR = generatedActionsDir;
|
|
92
|
+
}
|
|
93
|
+
if (fileRootURL) {
|
|
94
|
+
backendConfig.fileRootURL = fileRootURL;
|
|
95
|
+
}
|
|
96
|
+
if (fileUploadMode) {
|
|
97
|
+
backendConfig.fileUploadMode = fileUploadMode;
|
|
98
|
+
}
|
|
99
|
+
const config = {
|
|
100
|
+
backendConfigs: {
|
|
101
|
+
[backendKey]: backendConfig,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
return { config, testHeaders };
|
|
105
|
+
}
|
|
106
|
+
export function setupTestStateZero({ apiUrl, backendKey = "default", generatedTypesDir = "./src/models/", generatedActionsDir, getAuthHeaders, testAuthUserId, testAuthHeaders, testAuthHeaderName, eventsType = "none", fileRootURL, fileUploadMode, getModelClass, adapters, }) {
|
|
107
|
+
const { config, testHeaders } = createTestConfig({
|
|
108
|
+
apiUrl,
|
|
109
|
+
backendKey,
|
|
110
|
+
generatedTypesDir,
|
|
111
|
+
generatedActionsDir,
|
|
112
|
+
getAuthHeaders,
|
|
113
|
+
testAuthUserId,
|
|
114
|
+
testAuthHeaders,
|
|
115
|
+
testAuthHeaderName,
|
|
116
|
+
eventsType,
|
|
117
|
+
fileRootURL,
|
|
118
|
+
fileUploadMode,
|
|
119
|
+
});
|
|
120
|
+
setupStateZero(config, getModelClass, adapters);
|
|
121
|
+
return testHeaders;
|
|
122
|
+
}
|
|
123
|
+
export function createActionMocker(actionRegistry) {
|
|
124
|
+
const original = new Map();
|
|
125
|
+
function resolveRegistry(backendKey) {
|
|
126
|
+
if (!actionRegistry) {
|
|
127
|
+
throw new Error("actionRegistry is required");
|
|
128
|
+
}
|
|
129
|
+
return actionRegistry[backendKey] || actionRegistry;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
mock(actionName, handler, backendKey = "default") {
|
|
133
|
+
const registry = resolveRegistry(backendKey);
|
|
134
|
+
if (!registry || !registry[actionName]) {
|
|
135
|
+
throw new Error(`Action '${actionName}' not found in registry`);
|
|
136
|
+
}
|
|
137
|
+
const key = `${backendKey}:${actionName}`;
|
|
138
|
+
if (!original.has(key)) {
|
|
139
|
+
original.set(key, registry[actionName]);
|
|
140
|
+
}
|
|
141
|
+
registry[actionName] = handler;
|
|
142
|
+
},
|
|
143
|
+
restore(actionName, backendKey = "default") {
|
|
144
|
+
const registry = resolveRegistry(backendKey);
|
|
145
|
+
const key = `${backendKey}:${actionName}`;
|
|
146
|
+
if (!original.has(key)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
registry[actionName] = original.get(key);
|
|
150
|
+
original.delete(key);
|
|
151
|
+
},
|
|
152
|
+
restoreAll() {
|
|
153
|
+
for (const [key, fn] of original.entries()) {
|
|
154
|
+
const [backendKey, actionName] = key.split(":");
|
|
155
|
+
const registry = resolveRegistry(backendKey);
|
|
156
|
+
if (registry && actionName in registry) {
|
|
157
|
+
registry[actionName] = fn;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
original.clear();
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export async function seedRemote(testHeaders, seedFn) {
|
|
165
|
+
if (!testHeaders || typeof testHeaders.withSeeding !== "function") {
|
|
166
|
+
throw new Error("seedRemote requires testHeaders from createTestConfig/setupTestStateZero");
|
|
167
|
+
}
|
|
168
|
+
return testHeaders.withSeeding(seedFn);
|
|
169
|
+
}
|
|
170
|
+
export async function resetRemote(testHeaders, resetFn) {
|
|
171
|
+
if (!testHeaders || typeof testHeaders.withReset !== "function") {
|
|
172
|
+
throw new Error("resetRemote requires testHeaders from createTestConfig/setupTestStateZero");
|
|
173
|
+
}
|
|
174
|
+
return testHeaders.withReset(resetFn);
|
|
175
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ModelAdaptor } from './adaptors/vue/index.js';
|
|
2
|
+
import { QuerySetAdaptor } from './adaptors/vue/index.js';
|
|
3
|
+
import { MetricAdaptor } from './adaptors/vue/index.js';
|
|
4
|
+
import { useQueryset } from './adaptors/vue/index.js';
|
|
5
|
+
import { querysets } from './adaptors/vue/index.js';
|
|
6
|
+
import { LayoutRenderer } from './adaptors/vue/index.js';
|
|
7
|
+
import { AlertElement } from './adaptors/vue/index.js';
|
|
8
|
+
import { LabelElement } from './adaptors/vue/index.js';
|
|
9
|
+
import { DividerElement } from './adaptors/vue/index.js';
|
|
10
|
+
import { DisplayElement } from './adaptors/vue/index.js';
|
|
11
|
+
import { GroupElement } from './adaptors/vue/index.js';
|
|
12
|
+
import { TabsElement } from './adaptors/vue/index.js';
|
|
13
|
+
import { ErrorBlock } from './adaptors/vue/index.js';
|
|
14
|
+
import { createDefaultComponents } from './adaptors/vue/index.js';
|
|
15
|
+
export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets, LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets } from './adaptors/vue/index.js';
|
|
2
|
+
import { LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './adaptors/vue/index.js';
|
|
3
|
+
export {
|
|
4
|
+
// Reactivity
|
|
5
|
+
ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets,
|
|
6
|
+
// Layout components
|
|
7
|
+
LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|