@statezero/core 0.2.42 → 0.2.43

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.
@@ -32,6 +32,9 @@ export function useQueryset(querysetFactory) {
32
32
  updateSyncManager();
33
33
  lastQueryset = queryset;
34
34
  }
35
+ // Access __version to establish Vue dependency tracking for watch()
36
+ // This makes the computed re-evaluate when queryset data changes
37
+ const _ = result?.__version;
35
38
  return result;
36
39
  });
37
40
  }
@@ -1,3 +1,56 @@
1
+ /**
2
+ * Vue Reactivity Adapters for StateZero
3
+ *
4
+ * This module bridges StateZero's event-based reactivity (mitt) with Vue's reactivity system.
5
+ *
6
+ * ## How It Works
7
+ *
8
+ * StateZero emits events via mitt when data changes. These adapters:
9
+ * 1. Wrap data in Vue's reactive() or ref()
10
+ * 2. Listen for mitt events
11
+ * 3. Update the reactive wrapper when events fire
12
+ *
13
+ * ## Queryset Reactivity
14
+ *
15
+ * Querysets are wrapped as **stable reactive arrays**. The same object reference is
16
+ * maintained across updates - data is mutated in place via splice/push. This is
17
+ * intentional:
18
+ *
19
+ * - Templates automatically re-render (Vue tracks array mutations)
20
+ * - Object identity stays stable (no stale references in UI code)
21
+ * - Cached wrappers are reused for the same queryset
22
+ *
23
+ * ### Watching Querysets
24
+ *
25
+ * Because the object reference is stable, Vue's shallow watch won't detect changes.
26
+ * Use one of these patterns:
27
+ *
28
+ * ```js
29
+ * const messages = useQueryset(() => baseQs.value.fetch())
30
+ *
31
+ * // Option 1: Deep watch (recommended)
32
+ * watch(messages, (newMessages) => {
33
+ * scrollToBottom()
34
+ * }, { deep: true })
35
+ *
36
+ * // Option 2: Watch a derived value
37
+ * watch(() => messages.value.length, (newLen) => {
38
+ * scrollToBottom()
39
+ * })
40
+ * ```
41
+ *
42
+ * ### The __version Mechanism
43
+ *
44
+ * Each queryset wrapper has a `__version` counter that increments on every update.
45
+ * The `useQueryset` composable accesses this to establish Vue dependency tracking,
46
+ * ensuring the computed re-evaluates when data changes. This makes `{ deep: true }`
47
+ * watches work correctly.
48
+ *
49
+ * ## Model Reactivity
50
+ *
51
+ * Models use a similar `__version` / `touch()` mechanism. When a model is updated,
52
+ * `touch()` increments the version, triggering Vue to re-render dependent components.
53
+ */
1
54
  import { reactive, ref, nextTick } from "vue";
2
55
  import { modelEventEmitter, querysetEventEmitter, metricEventEmitter } from "../../syncEngine/stores/reactivity.js";
3
56
  import { initEventHandler } from "../../syncEngine/stores/operationEventHandlers.js";
@@ -66,6 +119,7 @@ export function QuerySetAdaptor(liveQuerySet, reactivityFn = reactive) {
66
119
  // Make the queryset reactive using the specified function
67
120
  const wrapper = reactivityFn([...liveQuerySet]);
68
121
  wrapper.original = liveQuerySet;
122
+ wrapper.__version = 0;
69
123
  const eventName = `${configKey}::${modelName}::queryset::render`;
70
124
  // Handler bumps version to trigger Vue reactivity when this queryset updates
71
125
  const renderHandler = (eventData) => {
@@ -77,6 +131,8 @@ export function QuerySetAdaptor(liveQuerySet, reactivityFn = reactive) {
77
131
  wrapper.splice(0, wrapper.length);
78
132
  wrapper.push(...liveQuerySet);
79
133
  }
134
+ // Bump version so computed/watch can track changes
135
+ wrapper.__version++;
80
136
  }
81
137
  };
82
138
  // Subscribe to queryset events indefinitely
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.2.42",
3
+ "version": "0.2.43",
4
4
  "type": "module",
5
5
  "module": "ESNext",
6
6
  "description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",
@@ -115,8 +115,10 @@
115
115
  "@types/yargs": "^17.0.32",
116
116
  "@vitejs/plugin-vue": "^6.0.4",
117
117
  "@vitest/coverage-v8": "^3.0.5",
118
+ "@vue/test-utils": "^2.4.6",
118
119
  "fake-indexeddb": "^6.0.0",
119
120
  "fast-glob": "^3.3.3",
121
+ "happy-dom": "^20.5.0",
120
122
  "react": "^18.2.0",
121
123
  "rimraf": "^5.0.5",
122
124
  "ts-node": "^10.9.2",