@statezero/core 0.1.0

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.
Files changed (89) hide show
  1. package/dist/adaptors/react/composables.d.ts +1 -0
  2. package/dist/adaptors/react/composables.js +4 -0
  3. package/dist/adaptors/react/index.d.ts +1 -0
  4. package/dist/adaptors/react/index.js +1 -0
  5. package/dist/adaptors/vue/composables.d.ts +2 -0
  6. package/dist/adaptors/vue/composables.js +36 -0
  7. package/dist/adaptors/vue/index.d.ts +2 -0
  8. package/dist/adaptors/vue/index.js +2 -0
  9. package/dist/adaptors/vue/reactivity.d.ts +18 -0
  10. package/dist/adaptors/vue/reactivity.js +125 -0
  11. package/dist/cli/commands/syncModels.d.ts +132 -0
  12. package/dist/cli/commands/syncModels.js +1040 -0
  13. package/dist/cli/configFileLoader.d.ts +10 -0
  14. package/dist/cli/configFileLoader.js +85 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +14 -0
  17. package/dist/config.d.ts +52 -0
  18. package/dist/config.js +242 -0
  19. package/dist/core/eventReceivers.d.ts +179 -0
  20. package/dist/core/eventReceivers.js +210 -0
  21. package/dist/core/utils.d.ts +8 -0
  22. package/dist/core/utils.js +62 -0
  23. package/dist/filtering/localFiltering.d.ts +116 -0
  24. package/dist/filtering/localFiltering.js +834 -0
  25. package/dist/flavours/django/dates.d.ts +33 -0
  26. package/dist/flavours/django/dates.js +99 -0
  27. package/dist/flavours/django/errors.d.ts +138 -0
  28. package/dist/flavours/django/errors.js +187 -0
  29. package/dist/flavours/django/f.d.ts +6 -0
  30. package/dist/flavours/django/f.js +91 -0
  31. package/dist/flavours/django/files.d.ts +76 -0
  32. package/dist/flavours/django/files.js +338 -0
  33. package/dist/flavours/django/makeApiCall.d.ts +20 -0
  34. package/dist/flavours/django/makeApiCall.js +169 -0
  35. package/dist/flavours/django/manager.d.ts +197 -0
  36. package/dist/flavours/django/manager.js +222 -0
  37. package/dist/flavours/django/model.d.ts +112 -0
  38. package/dist/flavours/django/model.js +253 -0
  39. package/dist/flavours/django/operationFactory.d.ts +65 -0
  40. package/dist/flavours/django/operationFactory.js +216 -0
  41. package/dist/flavours/django/q.d.ts +70 -0
  42. package/dist/flavours/django/q.js +43 -0
  43. package/dist/flavours/django/queryExecutor.d.ts +131 -0
  44. package/dist/flavours/django/queryExecutor.js +468 -0
  45. package/dist/flavours/django/querySet.d.ts +412 -0
  46. package/dist/flavours/django/querySet.js +601 -0
  47. package/dist/flavours/django/tempPk.d.ts +19 -0
  48. package/dist/flavours/django/tempPk.js +48 -0
  49. package/dist/flavours/django/utils.d.ts +19 -0
  50. package/dist/flavours/django/utils.js +29 -0
  51. package/dist/index.d.ts +38 -0
  52. package/dist/index.js +38 -0
  53. package/dist/react-entry.d.ts +2 -0
  54. package/dist/react-entry.js +2 -0
  55. package/dist/reactiveAdaptor.d.ts +24 -0
  56. package/dist/reactiveAdaptor.js +38 -0
  57. package/dist/setup.d.ts +15 -0
  58. package/dist/setup.js +22 -0
  59. package/dist/syncEngine/cache/cache.d.ts +75 -0
  60. package/dist/syncEngine/cache/cache.js +341 -0
  61. package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
  62. package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
  63. package/dist/syncEngine/registries/metricRegistry.d.ts +53 -0
  64. package/dist/syncEngine/registries/metricRegistry.js +162 -0
  65. package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
  66. package/dist/syncEngine/registries/modelStoreRegistry.js +56 -0
  67. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +55 -0
  68. package/dist/syncEngine/registries/querysetStoreRegistry.js +244 -0
  69. package/dist/syncEngine/stores/metricStore.d.ts +55 -0
  70. package/dist/syncEngine/stores/metricStore.js +222 -0
  71. package/dist/syncEngine/stores/modelStore.d.ts +40 -0
  72. package/dist/syncEngine/stores/modelStore.js +405 -0
  73. package/dist/syncEngine/stores/operation.d.ts +99 -0
  74. package/dist/syncEngine/stores/operation.js +224 -0
  75. package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
  76. package/dist/syncEngine/stores/operationEventHandlers.js +239 -0
  77. package/dist/syncEngine/stores/querysetStore.d.ts +32 -0
  78. package/dist/syncEngine/stores/querysetStore.js +200 -0
  79. package/dist/syncEngine/stores/reactivity.d.ts +3 -0
  80. package/dist/syncEngine/stores/reactivity.js +4 -0
  81. package/dist/syncEngine/stores/utils.d.ts +14 -0
  82. package/dist/syncEngine/stores/utils.js +32 -0
  83. package/dist/syncEngine/sync.d.ts +32 -0
  84. package/dist/syncEngine/sync.js +169 -0
  85. package/dist/vue-entry.d.ts +6 -0
  86. package/dist/vue-entry.js +2 -0
  87. package/license.md +116 -0
  88. package/package.json +123 -0
  89. package/readme.md +222 -0
@@ -0,0 +1,200 @@
1
+ import { Operation, Status, Type, operationRegistry } from './operation.js';
2
+ import { querysetEventEmitter } from './reactivity.js';
3
+ import { isNil, isEmpty, trim } from 'lodash-es';
4
+ import { replaceTempPks, containsTempPk } from '../../flavours/django/tempPk.js';
5
+ import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
6
+ import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
7
+ import hash from 'object-hash';
8
+ import { Cache } from '../cache/cache.js';
9
+ export class QuerysetStore {
10
+ constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
11
+ this.modelClass = modelClass;
12
+ this.fetchFn = fetchFn;
13
+ this.queryset = queryset;
14
+ this.isSyncing = false;
15
+ this.pruneThreshold = options.pruneThreshold || 10;
16
+ this.groundTruthPks = initialGroundTruthPks || [];
17
+ this.operationsMap = new Map();
18
+ if (Array.isArray(initialOperations)) {
19
+ for (const opData of initialOperations) {
20
+ const existing = operationRegistry.get(opData.operationId);
21
+ const op = existing || new Operation(opData, true);
22
+ this.operationsMap.set(op.operationId, op);
23
+ }
24
+ }
25
+ this.qsCache = new Cache('queryset-cache', {}, this.onHydrated.bind(this));
26
+ }
27
+ // Caching
28
+ get cacheKey() {
29
+ return this.queryset.semanticKey;
30
+ }
31
+ onHydrated(hydratedData) {
32
+ if (this.groundTruthPks.length === 0 && this.operationsMap.size === 0) {
33
+ const cached = this.qsCache.get(this.cacheKey);
34
+ if (!isNil(cached) && !isEmpty(cached)) {
35
+ console.log(`[QuerysetStore] Hydrated ${this.modelClass.modelName} queryset with ${cached.length} items from cache`);
36
+ this.setGroundTruth(cached);
37
+ }
38
+ }
39
+ }
40
+ setCache(result) {
41
+ let nonTempPks = [];
42
+ result.forEach((pk) => {
43
+ if (typeof pk === 'string' && containsTempPk(pk)) {
44
+ pk = replaceTempPks(pk);
45
+ if (isNil(pk) || isEmpty(trim(pk))) {
46
+ return;
47
+ }
48
+ }
49
+ nonTempPks.push(pk);
50
+ });
51
+ this.qsCache.set(this.cacheKey, nonTempPks);
52
+ }
53
+ clearCache() {
54
+ this.qsCache.delete(this.cacheKey);
55
+ }
56
+ // -- Core methods --
57
+ get operations() {
58
+ return Array.from(this.operationsMap.values());
59
+ }
60
+ get pkField() {
61
+ return this.modelClass.primaryKeyField;
62
+ }
63
+ get groundTruthSet() {
64
+ return new Set(this.groundTruthPks);
65
+ }
66
+ _emitRenderEvent() {
67
+ querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
68
+ }
69
+ async addOperation(operation) {
70
+ this.operationsMap.set(operation.operationId, operation);
71
+ if (this.operationsMap.size > this.pruneThreshold) {
72
+ this.prune();
73
+ }
74
+ this._emitRenderEvent();
75
+ }
76
+ async updateOperation(operation) {
77
+ if (!this.operationsMap.has(operation.operationId))
78
+ return;
79
+ this.operationsMap.set(operation.operationId, operation);
80
+ this._emitRenderEvent();
81
+ return true;
82
+ }
83
+ async confirm(operation) {
84
+ if (!this.operationsMap.has(operation.operationId))
85
+ return;
86
+ this.operationsMap.set(operation.operationId, operation);
87
+ this._emitRenderEvent();
88
+ }
89
+ async reject(operation) {
90
+ if (!this.operationsMap.has(operation.operationId))
91
+ return;
92
+ this.operationsMap.set(operation.operationId, operation);
93
+ this._emitRenderEvent();
94
+ }
95
+ async setGroundTruth(groundTruthPks) {
96
+ this.groundTruthPks = Array.isArray(groundTruthPks)
97
+ ? groundTruthPks
98
+ : [];
99
+ this._emitRenderEvent();
100
+ }
101
+ async setOperations(operations) {
102
+ this.operationsMap.clear();
103
+ if (Array.isArray(operations)) {
104
+ for (const op of operations) {
105
+ this.operationsMap.set(op.operationId, op);
106
+ }
107
+ }
108
+ this._emitRenderEvent();
109
+ }
110
+ getTrimmedOperations() {
111
+ const cutoff = Date.now() - 1000 * 60 * 2;
112
+ return this.operations.filter(op => op.timestamp > cutoff);
113
+ }
114
+ getInflightOperations() {
115
+ return this.operations.filter(operation => operation.status != Status.CONFIRMED &&
116
+ operation.status != Status.REJECTED);
117
+ }
118
+ prune() {
119
+ const renderedPks = this.render(false);
120
+ this.setGroundTruth(renderedPks);
121
+ this.setOperations(this.getInflightOperations());
122
+ }
123
+ render(optimistic = true, fromCache = false) {
124
+ if (fromCache) {
125
+ const cachedResult = this.qsCache.get(this.cacheKey);
126
+ if (Array.isArray(cachedResult)) {
127
+ return cachedResult;
128
+ }
129
+ }
130
+ const renderedPks = this.groundTruthSet;
131
+ for (const op of this.operations) {
132
+ if (op.status !== Status.REJECTED && (optimistic || op.status === Status.CONFIRMED)) {
133
+ this.applyOperation(op, renderedPks);
134
+ }
135
+ }
136
+ let result = Array.from(renderedPks);
137
+ let limit = this.queryset.build().serializerOptions?.limit;
138
+ if (limit) {
139
+ result = result.slice(0, limit);
140
+ }
141
+ this.setCache(result);
142
+ return result;
143
+ }
144
+ applyOperation(operation, currentPks) {
145
+ const pkField = this.pkField;
146
+ for (const instance of operation.instances) {
147
+ if (!instance ||
148
+ typeof instance !== 'object' ||
149
+ !(pkField in instance)) {
150
+ console.warn(`[QuerysetStore ${this.modelClass.modelName}] Skipping instance in operation ${operation.operationId} due to missing PK '${String(pkField)}' or invalid format.`);
151
+ continue;
152
+ }
153
+ let pk = instance[pkField];
154
+ switch (operation.type) {
155
+ case Type.CREATE:
156
+ currentPks.add(pk);
157
+ break;
158
+ case Type.CHECKPOINT:
159
+ case Type.UPDATE:
160
+ case Type.UPDATE_INSTANCE:
161
+ break;
162
+ case Type.DELETE:
163
+ case Type.DELETE_INSTANCE:
164
+ currentPks.delete(pk);
165
+ break;
166
+ default:
167
+ console.error(`[QuerysetStore ${this.modelClass.modelName}] Unknown operation type: ${operation.type}`);
168
+ }
169
+ }
170
+ return currentPks;
171
+ }
172
+ async sync() {
173
+ const id = this.modelClass.modelName;
174
+ if (this.isSyncing) {
175
+ console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
176
+ return;
177
+ }
178
+ this.isSyncing = true;
179
+ console.log(`[${id}] Starting sync...`);
180
+ try {
181
+ const response = await this.fetchFn({
182
+ ast: this.queryset.build(),
183
+ modelClass: this.modelClass
184
+ });
185
+ const { data, included } = response;
186
+ console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
187
+ // Persists all the instances (including nested instances) to the model store
188
+ processIncludedEntities(modelStoreRegistry, included, this.modelClass);
189
+ this.setGroundTruth(data);
190
+ this.setOperations(this.getInflightOperations());
191
+ console.log(`[${id}] Sync completed.`);
192
+ }
193
+ catch (e) {
194
+ console.error(`[${id}] Failed to sync ground truth:`, e);
195
+ }
196
+ finally {
197
+ this.isSyncing = false;
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,3 @@
1
+ export const modelEventEmitter: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
2
+ export const querysetEventEmitter: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
3
+ export const metricEventEmitter: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
@@ -0,0 +1,4 @@
1
+ import mitt from 'mitt';
2
+ export const modelEventEmitter = mitt();
3
+ export const querysetEventEmitter = mitt();
4
+ export const metricEventEmitter = mitt();
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Guarantees an array of operations, is an array of operations. Handles
3
+ * the basic failure case of a single operation.
4
+ */
5
+ export function validateOperationsArray(operationsArray: any): any;
6
+ /**
7
+ * Extract the minimal signature we're using for “direct‐match” comparison.
8
+ */
9
+ export function getFingerprint(qs: any): {
10
+ nodes: any;
11
+ initialQueryset: any;
12
+ offset: any;
13
+ limit: any;
14
+ };
@@ -0,0 +1,32 @@
1
+ import { isNil, isEqual } from 'lodash-es';
2
+ import { filter } from '../../filtering/localFiltering';
3
+ /**
4
+ * Guarantees an array of operations, is an array of operations. Handles
5
+ * the basic failure case of a single operation.
6
+ */
7
+ export function validateOperationsArray(operationsArray) {
8
+ // verify the operations list
9
+ if (Array.isArray(operationsArray)) {
10
+ if (operationsArray.length > 0) {
11
+ if (!(operationsArray[0] instanceof Operation)) {
12
+ throw new Error(`operationsArray must be Operations not ${typeof (operationsArray[0])}`);
13
+ }
14
+ }
15
+ }
16
+ else if (!isNil(operationsArray)) {
17
+ if (!(operationsArray instanceof Operation)) {
18
+ throw new Error(`operationsArray must be Operations not ${typeof (operationsArray)}`);
19
+ }
20
+ operationsArray = [operationsArray]; // coerce it to be an array
21
+ }
22
+ return operationsArray;
23
+ }
24
+ /**
25
+ * Extract the minimal signature we're using for “direct‐match” comparison.
26
+ */
27
+ export function getFingerprint(qs) {
28
+ const { nodes } = qs;
29
+ const initialQueryset = qs._initialQueryset;
30
+ const { offset = null, limit = null } = qs._serializerOptions || {};
31
+ return { nodes, initialQueryset, offset, limit };
32
+ }
@@ -0,0 +1,32 @@
1
+ export class EventPayload {
2
+ constructor(data: any);
3
+ event: any;
4
+ model: any;
5
+ operation_id: any;
6
+ pk_field_name: any;
7
+ configKey: any;
8
+ instances: any;
9
+ _cachedInstances: any;
10
+ get modelClass(): Function | null;
11
+ getFullInstances(): Promise<any>;
12
+ }
13
+ export class SyncManager {
14
+ registries: Map<any, any>;
15
+ followedModels: Map<any, any>;
16
+ followAllQuerysets: boolean;
17
+ followedQuerysets: Map<any, any>;
18
+ /**
19
+ * Initialize event handlers for all event receivers
20
+ */
21
+ initialize(): void;
22
+ followModel(registry: any, modelClass: any): void;
23
+ unfollowModel(registry: any, modelClass: any): void;
24
+ manageRegistry(registry: any): void;
25
+ removeRegistry(registry: any): void;
26
+ handleEvent: (event: any) => void;
27
+ isQuerysetFollowed(queryset: any): boolean;
28
+ processQuerysets(event: any): void;
29
+ processMetrics(event: any): void;
30
+ processModels(event: any): void;
31
+ }
32
+ export const syncManager: SyncManager;
@@ -0,0 +1,169 @@
1
+ import { getAllEventReceivers } from "../core/eventReceivers";
2
+ import { operationRegistry } from "./stores/operation";
3
+ import { initializeAllEventReceivers } from "../config";
4
+ import { getEventReceiver } from "../core/eventReceivers";
5
+ import { querysetStoreRegistry } from "./registries/querysetStoreRegistry";
6
+ import { modelStoreRegistry } from "./registries/modelStoreRegistry";
7
+ import { metricRegistry } from "./registries/metricRegistry";
8
+ import { getModelClass, getConfig } from "../config";
9
+ import { isNil } from "lodash-es";
10
+ export class EventPayload {
11
+ constructor(data) {
12
+ this.event = data.event;
13
+ this.model = data.model;
14
+ this.operation_id = data.operation_id;
15
+ this.pk_field_name = data.pk_field_name;
16
+ this.configKey = data.configKey;
17
+ this.instances = data.instances;
18
+ this._cachedInstances = null;
19
+ }
20
+ get modelClass() {
21
+ return getModelClass(this.model, this.configKey);
22
+ }
23
+ async getFullInstances() {
24
+ if (this.event === 'delete') {
25
+ throw new Error("Cannot fetch full instances for delete operation bozo...");
26
+ }
27
+ if (isNil(this._cachedInstances)) {
28
+ this._cachedInstances = await this.modelClass.objects
29
+ .filter({
30
+ [`${this.modelClass.primaryKeyField}__in`]: this.instances.map((instance) => instance[this.modelClass.primaryKeyField]),
31
+ }).fetch();
32
+ }
33
+ return this._cachedInstances;
34
+ }
35
+ }
36
+ export class SyncManager {
37
+ constructor() {
38
+ this.handleEvent = (event) => {
39
+ let payload = new EventPayload(event);
40
+ let isLocalOperation = operationRegistry.has(payload.operation_id);
41
+ if (this.registries.has("MetricRegistry")) {
42
+ this.processMetrics(payload);
43
+ }
44
+ if (isLocalOperation) {
45
+ // This is a local operation, so don't resync querysets or models
46
+ return;
47
+ }
48
+ if (this.registries.has("QuerysetStoreRegistry")) {
49
+ this.processQuerysets(payload);
50
+ }
51
+ if (this.registries.has("ModelStoreRegistry")) {
52
+ this.processModels(payload);
53
+ }
54
+ };
55
+ this.registries = new Map();
56
+ // Map of registries to model sets
57
+ this.followedModels = new Map();
58
+ // Map of querysets to keep synced
59
+ this.followAllQuerysets = true;
60
+ this.followedQuerysets = new Map();
61
+ }
62
+ /**
63
+ * Initialize event handlers for all event receivers
64
+ */
65
+ initialize() {
66
+ // Initialize all event receivers
67
+ initializeAllEventReceivers();
68
+ // Get all registered event receivers
69
+ const eventReceivers = getAllEventReceivers();
70
+ // Register the event handlers with each receiver
71
+ eventReceivers.forEach((receiver, configKey) => {
72
+ if (receiver) {
73
+ // Model events go to handleEvent
74
+ receiver.addModelEventHandler(this.handleEvent.bind(this));
75
+ }
76
+ });
77
+ }
78
+ followModel(registry, modelClass) {
79
+ const models = this.followedModels.get(registry) || new Set();
80
+ this.followedModels.set(registry, models);
81
+ if (models.has(modelClass))
82
+ return;
83
+ const alreadyFollowed = [...this.followedModels.values()].some(set => set.has(modelClass));
84
+ models.add(modelClass);
85
+ if (!alreadyFollowed) {
86
+ getEventReceiver(modelClass.configKey)?.subscribe(modelClass.modelName, this.handleEvent);
87
+ }
88
+ }
89
+ unfollowModel(registry, modelClass) {
90
+ const models = this.followedModels.get(registry);
91
+ if (!models)
92
+ return;
93
+ models.delete(modelClass);
94
+ const stillFollowed = [...this.followedModels.values()].some(set => set.has(modelClass));
95
+ if (!stillFollowed) {
96
+ getEventReceiver(modelClass.configKey)?.unsubscribe(modelClass.modelName, this.handleEvent);
97
+ }
98
+ }
99
+ manageRegistry(registry) {
100
+ this.registries.set(registry.constructor.name, registry);
101
+ registry.setSyncManager(this);
102
+ }
103
+ removeRegistry(registry) {
104
+ this.registries.delete(registry.constructor.name);
105
+ }
106
+ isQuerysetFollowed(queryset) {
107
+ let current = queryset;
108
+ // All followed querysets and their descendents get updated
109
+ while (current) {
110
+ if (this.followedQuerysets.has(current)) {
111
+ return true;
112
+ }
113
+ current = current.__parent;
114
+ }
115
+ return false;
116
+ }
117
+ processQuerysets(event) {
118
+ const registry = this.registries.get("QuerysetStoreRegistry");
119
+ for (const [semanticKey, store] of registry._stores.entries()) {
120
+ if (store.modelClass.modelName === event.model &&
121
+ store.modelClass.configKey === event.configKey) {
122
+ if (this.followAllQuerysets) {
123
+ store.sync();
124
+ continue;
125
+ }
126
+ // Get the set of querysets following this semantic key
127
+ const followingQuerysets = registry.followingQuerysets.get(semanticKey);
128
+ if (followingQuerysets) {
129
+ // Use some() to break early when we find a match
130
+ const shouldSync = [...followingQuerysets].some(queryset => {
131
+ return this.isQuerysetFollowed(queryset);
132
+ });
133
+ if (shouldSync) {
134
+ console.log(`syncing store for semantic key: ${semanticKey}`);
135
+ store.sync();
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ processMetrics(event) {
142
+ const registry = this.registries.get("MetricRegistry");
143
+ for (const [key, entry] of registry._stores.entries()) {
144
+ // Check if the store has a queryset with the model class AND correct backend
145
+ if (entry.queryset &&
146
+ entry.queryset.modelClass.modelName === event.model &&
147
+ entry.queryset.modelClass.configKey === event.configKey) {
148
+ if (this.followAllQuerysets) {
149
+ entry.store.sync();
150
+ continue;
151
+ }
152
+ // Check if this queryset (or any parent) is being followed
153
+ if (this.isQuerysetFollowed(entry.queryset)) {
154
+ console.log(`syncing metric store for key: ${key}`);
155
+ entry.store.sync();
156
+ }
157
+ }
158
+ }
159
+ }
160
+ processModels(event) {
161
+ // No need to sync models, because everything that is live, is synced
162
+ return;
163
+ }
164
+ }
165
+ const syncManager = new SyncManager();
166
+ syncManager.manageRegistry(querysetStoreRegistry);
167
+ syncManager.manageRegistry(modelStoreRegistry);
168
+ syncManager.manageRegistry(metricRegistry);
169
+ export { syncManager };
@@ -0,0 +1,6 @@
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
+ export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets };
@@ -0,0 +1,2 @@
1
+ import { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets } from './adaptors/vue/index.js';
2
+ export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets };
package/license.md ADDED
@@ -0,0 +1,116 @@
1
+ # **StateZero License Agreement**
2
+
3
+ _Last Updated: 2025-06-29_
4
+
5
+ ## **1. Copyright and Ownership**
6
+
7
+ 1.1. **StateZero is proprietary software** developed and owned by **Hoza Holdings Pte Ltd** ("Licensor").
8
+ 1.2. **StateZero is not open-source.** All rights not expressly granted in this License are **reserved by the Licensor**.
9
+ 1.3. **Distribution**: StateZero is made available via **PyPI** (Python Package Index), **npm** (Node Package Manager), and **GitHub**.
10
+ 1.4. **Early Release Status**: This is an **early release version** of StateZero designed for community feedback. It is **not considered production ready**. Users are encouraged to provide feedback but should exercise caution when implementing in critical systems.
11
+
12
+ ## **2. Commercial No-Rugpull License**
13
+
14
+ 2.1. **No-Rugpull Guarantee**: StateZero is available under a commercial "no-rugpull" license, which means:
15
+
16
+ - You receive an **irrevocable right** to use StateZero forever for a fixed monthly fee
17
+ - We **cannot change your fee**, other than a fixed CPI (Consumer Price Index) increase that's automatically added each year
18
+ - As long as you pay the fee and don't build a competitive product, the license will continue
19
+ - If we no longer wish to be associated with your use case, we will ask you not to pay the fee, but **your license will remain valid**
20
+
21
+ 2.2. **License Philosophy**: If SaaS is like renting and open-source is like owning, our license is like a **999-year leasehold on fixed rent**. It provides the security you need to build and grow your company around StateZero, while allowing us to maintain and develop StateZero to a high standard.
22
+
23
+ 2.3. **Rate Lock Guarantee**: You lock in the **full rate card for all tiers** at the time you sign up, meaning your fees remain predictable no matter how much your revenue or our pricing changes. To lock in your rates, simply contact us at **robert.herring@resipilot.com**.
24
+
25
+ ## **3. Pricing Structure**
26
+
27
+ 3.1. **Revenue-Based Pricing**:
28
+
29
+ - **$0/month** for companies with revenue up to **$3M**
30
+ - **$75/month** for companies with revenue up to **$7.5M**
31
+ - **$200/month** for companies with revenue up to **$20M**
32
+ - **$1,000/month** for companies with revenue up to **$100M**
33
+ - **$5,000/month** for companies with revenue above **$100M**
34
+
35
+ 3.2. **Free Usage Guarantee**: StateZero will **always remain free** for companies with **annual revenue below $3 million**.
36
+
37
+ 3.3. **Annual CPI Adjustment**: Pricing may be adjusted annually based on the Consumer Price Index, but no other fee changes are permitted once you have locked in your rate.
38
+
39
+ ## **4. Grant of License**
40
+
41
+ 4.1. **Standard Usage License**:
42
+
43
+ - Licensor grants you a **non-exclusive, non-transferable, worldwide license** to download, install, and use StateZero
44
+ - StateZero **may be used** in both **development and production** environments
45
+ - You may **install StateZero** within your organization but **may not provide StateZero as a service to third parties** without explicit written permission
46
+
47
+ 4.2. **Scope and Limitations**:
48
+
49
+ - This License **does not** transfer ownership of StateZero or any intellectual property rights
50
+ - Usage is subject to the pricing structure outlined in Section 3
51
+
52
+ ## **5. LLM Training License**
53
+
54
+ 5.1. **Separate License Required**: Use of StateZero for **Large Language Model (LLM) training purposes** requires a separate license agreement.
55
+
56
+ 5.2. **LLM Training License Fee**: The annual license fee for LLM training usage is **$250,000 per year**.
57
+
58
+ 5.3. **Contact for LLM License**: Organizations requiring LLM training rights must contact **Airhome Sp Zoo** at **robert.herring@resipilot.com** to arrange a separate licensing agreement.
59
+
60
+ ## **6. Restrictions**
61
+
62
+ 6.1. **Prohibited Actions**:
63
+
64
+ - You **may not** modify, reverse engineer, decompile, or disassemble StateZero
65
+ - You **may not** remove, alter, or obscure any **proprietary notices, branding, or license enforcement mechanisms**
66
+ - You **may not** sublicense, resell, or distribute StateZero to any third party **without explicit written permission**
67
+ - You **may not** use StateZero to develop or offer **a competing product or service**
68
+ - You **may not** use StateZero to create a cloud-hosted version of our service (similar to services like Heroku, Netlify, AWS, etc.)
69
+ - You **may not** use StateZero for LLM training without the separate license outlined in Section 5
70
+
71
+ ## **7. Payment Terms**
72
+
73
+ 7.1. **Payment Obligations**: Companies exceeding the free tier revenue threshold must pay the applicable monthly fee as outlined in Section 3.
74
+
75
+ 7.2. **Revenue Tier Reporting**: You are responsible for accurately reporting your revenue tier and upgrading to the appropriate pricing tier when revenue thresholds are exceeded.
76
+
77
+ 7.3. **Rate Lock Process**: To lock in current pricing for all tiers, contact us at **robert.herring@resipilot.com**.
78
+
79
+ ## **8. Disclaimer of Warranty**
80
+
81
+ 8.1. **AS-IS Basis**: StateZero is provided **"AS IS"**, without warranties or guarantees of any kind.
82
+ 8.2. The Licensor **expressly disclaims** all implied warranties, including but not limited to **merchantability, fitness for a particular purpose, or non-infringement**.
83
+
84
+ ## **9. Limitation of Liability**
85
+
86
+ 9.1. **No Liability for Damages**: To the **maximum extent permitted by law**, the Licensor **shall not** be liable for any damages resulting from the use or inability to use StateZero, including:
87
+
88
+ - Loss of revenue, data, or business opportunities
89
+ - Incidental, consequential, punitive, or special damages, even if advised of the possibility
90
+
91
+ ## **10. Termination**
92
+
93
+ 10.1. **License Irrevocability**: Under the no-rugpull guarantee, **we cannot terminate your license to use StateZero** as long as you:
94
+
95
+ - Pay the applicable monthly fee (if any), OR
96
+ - We have asked you not to pay the fee (as outlined in Section 2.1)
97
+ - AND you comply with the restrictions in Section 6 (primarily not building a competing product)
98
+
99
+ 10.2. **Limited Termination Rights**: The Licensor may only terminate this License for material violations of Section 6 (Restrictions), and only after providing 30 days written notice and opportunity to cure such violations.
100
+
101
+ 10.3. **Upon Valid Termination**: Only in cases of valid termination under Section 10.2, you must **uninstall StateZero** and delete all copies in your possession.
102
+
103
+ 10.4. **No-Rugpull Protection**: Your license to use StateZero is **irrevocable** under normal circumstances. Even if we no longer wish to be associated with your use case, we will simply ask you to stop paying fees, but your license remains valid and enforceable.
104
+
105
+ ## **11. Governing Law**
106
+
107
+ 11.1. This License shall be governed by and construed in accordance with the **laws of Singapore**, without regard to conflict of law principles.
108
+
109
+ ## **12. Contact Information**
110
+
111
+ 12.1. For general inquiries and rate lock requests, contact **Airhome Sp Zoo** at **robert.herring@resipilot.com**.
112
+ 12.2. For LLM training license inquiries, contact **Airhome Sp Zoo** at **robert.herring@resipilot.com**.
113
+
114
+ ---
115
+
116
+ **By downloading, installing, or using StateZero, you acknowledge that you have read, understood, and agree to be bound by the terms of this License Agreement.**