@statezero/core 0.1.2 → 0.1.3-9.1

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.
@@ -1,18 +1,24 @@
1
1
  import { Operation, Status, Type, operationRegistry } from './operation.js';
2
2
  import { querysetEventEmitter } from './reactivity.js';
3
- import { isNil, isEmpty, trim } from 'lodash-es';
3
+ import { isNil, isEmpty, trim, isEqual } from 'lodash-es';
4
4
  import { replaceTempPks, containsTempPk } from '../../flavours/django/tempPk.js';
5
5
  import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
6
6
  import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
7
7
  import hash from 'object-hash';
8
8
  import { Cache } from '../cache/cache.js';
9
+ import { filter } from "../../filtering/localFiltering.js";
10
+ import { mod } from 'mathjs';
9
11
  export class QuerysetStore {
10
12
  constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
11
13
  this.modelClass = modelClass;
12
14
  this.fetchFn = fetchFn;
13
15
  this.queryset = queryset;
14
16
  this.isSyncing = false;
17
+ this.lastSync = null;
18
+ this.needsSync = false;
19
+ this.isTemp = options.isTemp || false;
15
20
  this.pruneThreshold = options.pruneThreshold || 10;
21
+ this.getRootStore = options.getRootStore || null;
16
22
  this.groundTruthPks = initialGroundTruthPks || [];
17
23
  this.operationsMap = new Map();
18
24
  if (Array.isArray(initialOperations)) {
@@ -22,7 +28,12 @@ export class QuerysetStore {
22
28
  this.operationsMap.set(op.operationId, op);
23
29
  }
24
30
  }
25
- this.qsCache = new Cache('queryset-cache', {}, this.onHydrated.bind(this));
31
+ this.qsCache = new Cache("queryset-cache", {}, this.onHydrated.bind(this));
32
+ this._lastRenderedPks = null;
33
+ this.renderCallbacks = new Set();
34
+ this._rootUnregister = null;
35
+ this._currentRootStore = null;
36
+ this._ensureRootRegistration();
26
37
  }
27
38
  // Caching
28
39
  get cacheKey() {
@@ -40,7 +51,7 @@ export class QuerysetStore {
40
51
  setCache(result) {
41
52
  let nonTempPks = [];
42
53
  result.forEach((pk) => {
43
- if (typeof pk === 'string' && containsTempPk(pk)) {
54
+ if (typeof pk === "string" && containsTempPk(pk)) {
44
55
  pk = replaceTempPks(pk);
45
56
  if (isNil(pk) || isEmpty(trim(pk))) {
46
57
  return;
@@ -64,7 +75,22 @@ export class QuerysetStore {
64
75
  return new Set(this.groundTruthPks);
65
76
  }
66
77
  _emitRenderEvent() {
67
- querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
78
+ const newPks = this.render(true, false);
79
+ // 1. Always notify direct child stores to trigger their own re-evaluation.
80
+ // They will perform their own check to see if their own results have changed.
81
+ this.renderCallbacks.forEach((callback) => {
82
+ try {
83
+ callback();
84
+ }
85
+ catch (error) {
86
+ console.warn("Error in render callback:", error);
87
+ }
88
+ });
89
+ // 2. Only emit the global event for UI components if the final list of PKs has actually changed.
90
+ if (!isEqual(newPks, this._lastRenderedPks)) {
91
+ this._lastRenderedPks = newPks; // Update the cache with the new state
92
+ querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
93
+ }
68
94
  }
69
95
  async addOperation(operation) {
70
96
  this.operationsMap.set(operation.operationId, operation);
@@ -93,9 +119,7 @@ export class QuerysetStore {
93
119
  this._emitRenderEvent();
94
120
  }
95
121
  async setGroundTruth(groundTruthPks) {
96
- this.groundTruthPks = Array.isArray(groundTruthPks)
97
- ? groundTruthPks
98
- : [];
122
+ this.groundTruthPks = Array.isArray(groundTruthPks) ? groundTruthPks : [];
99
123
  this._emitRenderEvent();
100
124
  }
101
125
  async setOperations(operations) {
@@ -109,10 +133,10 @@ export class QuerysetStore {
109
133
  }
110
134
  getTrimmedOperations() {
111
135
  const cutoff = Date.now() - 1000 * 60 * 2;
112
- return this.operations.filter(op => op.timestamp > cutoff);
136
+ return this.operations.filter((op) => op.timestamp > cutoff);
113
137
  }
114
138
  getInflightOperations() {
115
- return this.operations.filter(operation => operation.status != Status.CONFIRMED &&
139
+ return this.operations.filter((operation) => operation.status != Status.CONFIRMED &&
116
140
  operation.status != Status.REJECTED);
117
141
  }
118
142
  prune() {
@@ -120,20 +144,68 @@ export class QuerysetStore {
120
144
  this.setGroundTruth(renderedPks);
121
145
  this.setOperations(this.getInflightOperations());
122
146
  }
147
+ registerRenderCallback(callback) {
148
+ this.renderCallbacks.add(callback);
149
+ return () => this.renderCallbacks.delete(callback);
150
+ }
151
+ _ensureRootRegistration() {
152
+ if (this.isTemp)
153
+ return;
154
+ const { isRoot, rootStore } = this.getRootStore(this.queryset);
155
+ // If the root store hasn't changed, nothing to do
156
+ if (this._currentRootStore === rootStore) {
157
+ return;
158
+ }
159
+ // Root store changed - clean up old registration if it exists
160
+ if (this._rootUnregister) {
161
+ this._rootUnregister();
162
+ this._rootUnregister = null;
163
+ }
164
+ // Set up new registration if we're derived and have a root store
165
+ if (!isRoot && rootStore) {
166
+ this._rootUnregister = rootStore.registerRenderCallback(() => {
167
+ this._emitRenderEvent();
168
+ });
169
+ }
170
+ // Update current root store reference (could be null now)
171
+ this._currentRootStore = rootStore;
172
+ }
173
+ /**
174
+ * Helper to validate PKs against the model store and apply local filtering/sorting.
175
+ * This is the core of the rendering logic.
176
+ * @private
177
+ */
178
+ _getValidatedAndFilteredPks(pks) {
179
+ // 1. Convert PKs to instances, filtering out any that are null (deleted).
180
+ const instances = Array.from(pks)
181
+ .map((pk) => this.modelClass.fromPk(pk, this.queryset))
182
+ .filter((instance) => modelStoreRegistry.getEntity(this.modelClass, instance.pk) !== null);
183
+ // 2. Apply the queryset's AST (filters, ordering) to the validated instances.
184
+ const ast = this.queryset.build();
185
+ const finalPks = filter(instances, ast, this.modelClass, false); // false = return PKs
186
+ return finalPks;
187
+ }
123
188
  render(optimistic = true, fromCache = false) {
189
+ this._ensureRootRegistration();
124
190
  if (fromCache) {
125
191
  const cachedResult = this.qsCache.get(this.cacheKey);
126
192
  if (Array.isArray(cachedResult)) {
127
193
  return cachedResult;
128
194
  }
129
195
  }
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);
196
+ let pks;
197
+ if (this.getRootStore &&
198
+ typeof this.getRootStore === "function" &&
199
+ !this.isTemp) {
200
+ const { isRoot, rootStore } = this.getRootStore(this.queryset);
201
+ if (!isRoot && rootStore) {
202
+ pks = this.renderFromRoot(optimistic, rootStore);
134
203
  }
135
204
  }
136
- let result = Array.from(renderedPks);
205
+ if (isNil(pks)) {
206
+ pks = this.renderFromData(optimistic);
207
+ }
208
+ let result = this._getValidatedAndFilteredPks(pks);
137
209
  let limit = this.queryset.build().serializerOptions?.limit;
138
210
  if (limit) {
139
211
  result = result.slice(0, limit);
@@ -141,12 +213,30 @@ export class QuerysetStore {
141
213
  this.setCache(result);
142
214
  return result;
143
215
  }
216
+ renderFromRoot(optimistic = true, rootStore) {
217
+ let renderedPks = rootStore.render(optimistic);
218
+ let renderedData = renderedPks.map((pk) => {
219
+ return this.modelClass.fromPk(pk, this.queryset);
220
+ });
221
+ let ast = this.queryset.build();
222
+ let result = filter(renderedData, ast, this.modelClass, false);
223
+ return result;
224
+ }
225
+ renderFromData(optimistic = true) {
226
+ const renderedPks = this.groundTruthSet;
227
+ for (const op of this.operations) {
228
+ if (op.status !== Status.REJECTED &&
229
+ (optimistic || op.status === Status.CONFIRMED)) {
230
+ this.applyOperation(op, renderedPks);
231
+ }
232
+ }
233
+ let result = Array.from(renderedPks);
234
+ return result;
235
+ }
144
236
  applyOperation(operation, currentPks) {
145
237
  const pkField = this.pkField;
146
238
  for (const instance of operation.instances) {
147
- if (!instance ||
148
- typeof instance !== 'object' ||
149
- !(pkField in instance)) {
239
+ if (!instance || typeof instance !== "object" || !(pkField in instance)) {
150
240
  console.warn(`[QuerysetStore ${this.modelClass.modelName}] Skipping instance in operation ${operation.operationId} due to missing PK '${String(pkField)}' or invalid format.`);
151
241
  continue;
152
242
  }
@@ -175,23 +265,44 @@ export class QuerysetStore {
175
265
  console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
176
266
  return;
177
267
  }
268
+ // Check if we're delegating to a root store
269
+ if (this.getRootStore &&
270
+ typeof this.getRootStore === "function" &&
271
+ !this.isTemp) {
272
+ const { isRoot, rootStore } = this.getRootStore(this.queryset);
273
+ if (!isRoot && rootStore) {
274
+ // We're delegating to a root store - don't sync, just mark as needing sync
275
+ console.log(`[${id}] Delegating to root store, marking sync needed.`);
276
+ this.needsSync = true;
277
+ this.lastSync = null; // Clear last sync since we're not actually syncing
278
+ this.setOperations(this.getInflightOperations());
279
+ return;
280
+ }
281
+ }
282
+ // We're in independent mode - proceed with normal sync
178
283
  this.isSyncing = true;
179
284
  console.log(`[${id}] Starting sync...`);
180
285
  try {
181
286
  const response = await this.fetchFn({
182
287
  ast: this.queryset.build(),
183
- modelClass: this.modelClass
288
+ modelClass: this.modelClass,
184
289
  });
185
290
  const { data, included } = response;
291
+ if (isNil(data)) {
292
+ return;
293
+ }
186
294
  console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
187
295
  // Persists all the instances (including nested instances) to the model store
188
296
  processIncludedEntities(modelStoreRegistry, included, this.modelClass);
189
297
  this.setGroundTruth(data);
190
298
  this.setOperations(this.getInflightOperations());
299
+ this.lastSync = Date.now();
300
+ this.needsSync = false;
191
301
  console.log(`[${id}] Sync completed.`);
192
302
  }
193
303
  catch (e) {
194
304
  console.error(`[${id}] Failed to sync ground truth:`, e);
305
+ this.needsSync = true; // Mark as needing sync on error
195
306
  }
196
307
  finally {
197
308
  this.isSyncing = false;
@@ -15,10 +15,15 @@ export class SyncManager {
15
15
  followedModels: Map<any, any>;
16
16
  followAllQuerysets: boolean;
17
17
  followedQuerysets: Map<any, any>;
18
+ periodicSyncTimer: NodeJS.Timeout | null;
18
19
  /**
19
20
  * Initialize event handlers for all event receivers
20
21
  */
21
22
  initialize(): void;
23
+ startPeriodicSync(): void;
24
+ syncStaleQuerysets(): void;
25
+ isStoreFollowed(registry: any, semanticKey: any): boolean;
26
+ cleanup(): void;
22
27
  followModel(registry: any, modelClass: any): void;
23
28
  unfollowModel(registry: any, modelClass: any): void;
24
29
  manageRegistry(registry: any): void;
@@ -58,6 +58,7 @@ export class SyncManager {
58
58
  // Map of querysets to keep synced
59
59
  this.followAllQuerysets = true;
60
60
  this.followedQuerysets = new Map();
61
+ this.periodicSyncTimer = null;
61
62
  }
62
63
  /**
63
64
  * Initialize event handlers for all event receivers
@@ -74,13 +75,68 @@ export class SyncManager {
74
75
  receiver.addModelEventHandler(this.handleEvent.bind(this));
75
76
  }
76
77
  });
78
+ this.startPeriodicSync();
79
+ }
80
+ startPeriodicSync() {
81
+ if (this.periodicSyncTimer)
82
+ return;
83
+ try {
84
+ const config = getConfig();
85
+ const intervalSeconds = config.periodicSyncIntervalSeconds;
86
+ // If null or undefined, don't start periodic sync
87
+ if (!intervalSeconds) {
88
+ console.log("[SyncManager] Periodic sync disabled (set to null)");
89
+ return;
90
+ }
91
+ const intervalMs = intervalSeconds * 1000;
92
+ this.periodicSyncTimer = setInterval(() => {
93
+ this.syncStaleQuerysets();
94
+ }, intervalMs);
95
+ console.log(`[SyncManager] Periodic sync started: ${intervalSeconds}s intervals`);
96
+ }
97
+ catch (error) {
98
+ // If no config, don't start periodic sync by default
99
+ console.log("[SyncManager] No config found, periodic sync disabled by default");
100
+ }
101
+ }
102
+ syncStaleQuerysets() {
103
+ let syncedCount = 0;
104
+ // Sync all followed querysets - keep it simple
105
+ const querysetRegistry = this.registries.get("QuerysetStoreRegistry");
106
+ if (querysetRegistry) {
107
+ for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
108
+ // Only sync if this store is actually being followed
109
+ const isFollowed = this.isStoreFollowed(querysetRegistry, semanticKey);
110
+ if (this.followAllQuerysets || isFollowed) {
111
+ store.sync();
112
+ syncedCount++;
113
+ }
114
+ }
115
+ }
116
+ if (syncedCount > 0) {
117
+ console.log(`[SyncManager] Periodic sync: ${syncedCount} stores synced`);
118
+ }
119
+ }
120
+ isStoreFollowed(registry, semanticKey) {
121
+ const followingQuerysets = registry.followingQuerysets.get(semanticKey);
122
+ if (!followingQuerysets)
123
+ return false;
124
+ return [...followingQuerysets].some((queryset) => {
125
+ return this.isQuerysetFollowed(queryset);
126
+ });
127
+ }
128
+ cleanup() {
129
+ if (this.periodicSyncTimer) {
130
+ clearInterval(this.periodicSyncTimer);
131
+ this.periodicSyncTimer = null;
132
+ }
77
133
  }
78
134
  followModel(registry, modelClass) {
79
135
  const models = this.followedModels.get(registry) || new Set();
80
136
  this.followedModels.set(registry, models);
81
137
  if (models.has(modelClass))
82
138
  return;
83
- const alreadyFollowed = [...this.followedModels.values()].some(set => set.has(modelClass));
139
+ const alreadyFollowed = [...this.followedModels.values()].some((set) => set.has(modelClass));
84
140
  models.add(modelClass);
85
141
  if (!alreadyFollowed) {
86
142
  getEventReceiver(modelClass.configKey)?.subscribe(modelClass.modelName, this.handleEvent);
@@ -91,7 +147,7 @@ export class SyncManager {
91
147
  if (!models)
92
148
  return;
93
149
  models.delete(modelClass);
94
- const stillFollowed = [...this.followedModels.values()].some(set => set.has(modelClass));
150
+ const stillFollowed = [...this.followedModels.values()].some((set) => set.has(modelClass));
95
151
  if (!stillFollowed) {
96
152
  getEventReceiver(modelClass.configKey)?.unsubscribe(modelClass.modelName, this.handleEvent);
97
153
  }
@@ -104,10 +160,10 @@ export class SyncManager {
104
160
  this.registries.delete(registry.constructor.name);
105
161
  }
106
162
  isQuerysetFollowed(queryset) {
163
+ const activeSemanticKeys = new Set([...this.followedQuerysets].map((qs) => qs.semanticKey));
107
164
  let current = queryset;
108
- // All followed querysets and their descendents get updated
109
165
  while (current) {
110
- if (this.followedQuerysets.has(current)) {
166
+ if (activeSemanticKeys.has(current.semanticKey)) {
111
167
  return true;
112
168
  }
113
169
  current = current.__parent;
@@ -127,7 +183,7 @@ export class SyncManager {
127
183
  const followingQuerysets = registry.followingQuerysets.get(semanticKey);
128
184
  if (followingQuerysets) {
129
185
  // Use some() to break early when we find a match
130
- const shouldSync = [...followingQuerysets].some(queryset => {
186
+ const shouldSync = [...followingQuerysets].some((queryset) => {
131
187
  return this.isQuerysetFollowed(queryset);
132
188
  });
133
189
  if (shouldSync) {
package/package.json CHANGED
@@ -1,123 +1,126 @@
1
- {
2
- "name": "@statezero/core",
3
- "version": "0.1.2",
4
- "type": "module",
5
- "module": "ESNext",
6
- "description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "bin": {
10
- "statezero": "dist/cli/index.js"
11
- },
12
- "exports": {
13
- ".": {
14
- "import": "./dist/index.js",
15
- "require": "./dist/index.js"
16
- },
17
- "./cli": {
18
- "import": "./dist/cli/index.js",
19
- "require": "./dist/cli/index.js"
20
- },
21
- "./react": {
22
- "import": "./dist/react-entry.js",
23
- "require": "./dist/react-entry.js"
24
- },
25
- "./vue": {
26
- "import": "./dist/vue-entry.js",
27
- "require": "./dist/vue-entry.js"
28
- }
29
- },
30
- "scripts": {
31
- "test": "vitest run --config=vitest.base.config.ts",
32
- "test:e2e": "vitest run --config=vitest.sequential.config.ts tests/e2e",
33
- "generate:test-apps": "ts-node scripts/generate-test-apps.js",
34
- "test:adaptors": "playwright test tests/adaptors",
35
- "test:coverage": "vitest run --coverage",
36
- "build": "tsc",
37
- "parse-queries": "node scripts/perfect-query-parser.js",
38
- "sync-models": "node src/cli/index.js sync-models",
39
- "sync-models:dev": "npx cross-env NODE_ENV=test npm run sync-models",
40
- "clean": "npx rimraf dist",
41
- "prepare": "npm run clean && npm run build",
42
- "prepublishOnly": "npm run clean && npm run build"
43
- },
44
- "keywords": [
45
- "typescript",
46
- "orm",
47
- "backend",
48
- "frontend",
49
- "database",
50
- "sql",
51
- "django",
52
- "sqlalchemy",
53
- "react",
54
- "vue",
55
- "svelte"
56
- ],
57
- "author": "Robert Herring <robert.herring@resipilot.com>",
58
- "license": "SEE LICENSE IN LICENSE",
59
- "repository": {
60
- "type": "git",
61
- "url": "git+https://github.com/state-zero/statezero-client.git"
62
- },
63
- "files": [
64
- "dist",
65
- "LICENSE",
66
- "README.md"
67
- ],
68
- "homepage": "https://www.statezero.dev",
69
- "dependencies": {
70
- "axios": "^1.7.9",
71
- "cli-progress": "^3.12.0",
72
- "cosmiconfig": "^9.0.0",
73
- "cosmiconfig-typescript-loader": "^6.1.0",
74
- "date-fns": "^4.1.0",
75
- "dotenv": "^16.4.7",
76
- "fs-extra": "^11.3.0",
77
- "handlebars": "^4.7.8",
78
- "idb": "^8.0.2",
79
- "inquirer": "^12.4.2",
80
- "lodash-es": "^4.17.21",
81
- "luxon": "^3.6.1",
82
- "mathjs": "^14.4.0",
83
- "mitt": "^3.0.1",
84
- "mobx": "^6.13.7",
85
- "mobx-utils": "^6.1.0",
86
- "object-hash": "^3.0.0",
87
- "openapi-typescript": "^6.7.1",
88
- "p-queue": "^8.1.0",
89
- "pusher-js": "^8.4.0",
90
- "rfdc": "^1.4.1",
91
- "sift": "^17.1.3",
92
- "superjson": "^2.2.2",
93
- "uuid": "^11.1.0",
94
- "yargs": "^17.7.2",
95
- "zod": "^3.24.2"
96
- },
97
- "devDependencies": {
98
- "@playwright/test": "^1.50.1",
99
- "@types/cli-progress": "^3.11.6",
100
- "@types/lodash-es": "^4.17.12",
101
- "@types/node": "^22.13.1",
102
- "@types/react": "^18.3.18",
103
- "@types/yargs": "^17.0.32",
104
- "@vitest/coverage-v8": "^3.0.5",
105
- "fake-indexeddb": "^6.0.0",
106
- "fast-glob": "^3.3.3",
107
- "react": "^18.2.0",
108
- "rimraf": "^5.0.5",
109
- "ts-node": "^10.9.2",
110
- "typescript": "^5.7.3",
111
- "vitest": "^3.0.5",
112
- "vue": "^3.2.0"
113
- },
114
- "publishConfig": {
115
- "access": "restricted"
116
- },
117
- "bugs": {
118
- "url": "https://github.com/state-zero/statezero-client/issues"
119
- },
120
- "directories": {
121
- "test": "tests"
122
- }
123
- }
1
+ {
2
+ "name": "@statezero/core",
3
+ "version": "0.1.39.1",
4
+ "type": "module",
5
+ "module": "ESNext",
6
+ "description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "statezero": "dist/cli/index.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./cli": {
18
+ "import": "./dist/cli/index.js",
19
+ "require": "./dist/cli/index.js"
20
+ },
21
+ "./react": {
22
+ "import": "./dist/react-entry.js",
23
+ "require": "./dist/react-entry.js"
24
+ },
25
+ "./vue": {
26
+ "import": "./dist/vue-entry.js",
27
+ "require": "./dist/vue-entry.js"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "test": "vitest run --config=vitest.base.config.ts",
32
+ "test:e2e": "vitest run --config=vitest.sequential.config.ts tests/e2e",
33
+ "generate:test-apps": "ts-node scripts/generate-test-apps.js",
34
+ "test:adaptors": "playwright test tests/adaptors",
35
+ "test:coverage": "vitest run --coverage",
36
+ "build": "tsc",
37
+ "parse-queries": "node scripts/perfect-query-parser.js",
38
+ "sync": "node src/cli/index.js sync",
39
+ "sync:dev": "npx cross-env NODE_ENV=test npm run sync",
40
+ "sync-models": "node src/cli/index.js sync-models",
41
+ "sync-models:dev": "npx cross-env NODE_ENV=test npm run sync-models",
42
+ "sync-actions": "node src/cli/index.js sync-actions",
43
+ "sync-actions:dev": "npx cross-env NODE_ENV=test npm run sync-actions",
44
+ "clean": "npx rimraf dist",
45
+ "prepare": "npm run clean && npm run build",
46
+ "prepublishOnly": "npm run clean && npm run build"
47
+ },
48
+ "keywords": [
49
+ "typescript",
50
+ "orm",
51
+ "backend",
52
+ "frontend",
53
+ "database",
54
+ "sql",
55
+ "django",
56
+ "sqlalchemy",
57
+ "react",
58
+ "vue",
59
+ "svelte"
60
+ ],
61
+ "author": "Robert Herring <robert.herring@resipilot.com>",
62
+ "license": "SEE LICENSE IN LICENSE",
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/state-zero/statezero-client.git"
66
+ },
67
+ "files": [
68
+ "dist",
69
+ "LICENSE",
70
+ "README.md"
71
+ ],
72
+ "homepage": "https://www.statezero.dev",
73
+ "dependencies": {
74
+ "axios": "^1.7.9",
75
+ "cli-progress": "^3.12.0",
76
+ "cosmiconfig": "^9.0.0",
77
+ "cosmiconfig-typescript-loader": "^6.1.0",
78
+ "date-fns": "^4.1.0",
79
+ "dotenv": "^16.4.7",
80
+ "fs-extra": "^11.3.0",
81
+ "graphlib": "^2.1.8",
82
+ "handlebars": "^4.7.8",
83
+ "idb": "^8.0.2",
84
+ "inquirer": "^12.4.2",
85
+ "lodash-es": "^4.17.21",
86
+ "luxon": "^3.6.1",
87
+ "mathjs": "^14.4.0",
88
+ "mitt": "^3.0.1",
89
+ "object-hash": "^3.0.0",
90
+ "openapi-typescript": "^6.7.1",
91
+ "p-queue": "^8.1.0",
92
+ "pusher-js": "^8.4.0",
93
+ "rfdc": "^1.4.1",
94
+ "sift": "^17.1.3",
95
+ "superjson": "^2.2.2",
96
+ "uuid": "^11.1.0",
97
+ "yargs": "^17.7.2",
98
+ "zod": "^3.24.2"
99
+ },
100
+ "devDependencies": {
101
+ "@playwright/test": "^1.50.1",
102
+ "@types/cli-progress": "^3.11.6",
103
+ "@types/lodash-es": "^4.17.12",
104
+ "@types/node": "^22.13.1",
105
+ "@types/react": "^18.3.18",
106
+ "@types/yargs": "^17.0.32",
107
+ "@vitest/coverage-v8": "^3.0.5",
108
+ "fake-indexeddb": "^6.0.0",
109
+ "fast-glob": "^3.3.3",
110
+ "react": "^18.2.0",
111
+ "rimraf": "^5.0.5",
112
+ "ts-node": "^10.9.2",
113
+ "typescript": "^5.7.3",
114
+ "vitest": "^3.0.5",
115
+ "vue": "^3.2.0"
116
+ },
117
+ "publishConfig": {
118
+ "access": "public"
119
+ },
120
+ "bugs": {
121
+ "url": "https://github.com/state-zero/statezero-client/issues"
122
+ },
123
+ "directories": {
124
+ "test": "tests"
125
+ }
126
+ }
package/readme.md CHANGED
@@ -192,7 +192,7 @@ npm install https://github.com/state-zero/statezero-client
192
192
  ### Generate TypeScript Models
193
193
 
194
194
  ```bash
195
- npx statezero sync-models
195
+ npx statezero sync
196
196
  ```
197
197
 
198
198
  ## Why Choose StateZero Over...