@statezero/core 0.2.45 → 0.2.47

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 (39) hide show
  1. package/dist/adaptors/vue/components/LayoutRenderer.d.ts +1 -0
  2. package/dist/adaptors/vue/components/StateZeroDebugPanel.d.ts +1 -0
  3. package/dist/adaptors/vue/components/StateZeroDebugPanel.js +781 -0
  4. package/dist/adaptors/vue/components/defaults/AlertElement.d.ts +1 -0
  5. package/dist/adaptors/vue/components/defaults/DisplayElement.d.ts +1 -0
  6. package/dist/adaptors/vue/components/defaults/DividerElement.d.ts +1 -0
  7. package/dist/adaptors/vue/components/defaults/ErrorBlock.d.ts +1 -0
  8. package/dist/adaptors/vue/components/defaults/GroupElement.d.ts +1 -0
  9. package/dist/adaptors/vue/components/defaults/LabelElement.d.ts +1 -0
  10. package/dist/adaptors/vue/components/defaults/TabsElement.d.ts +1 -0
  11. package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
  12. package/dist/adaptors/vue/components/index.d.ts +2 -0
  13. package/dist/adaptors/vue/components/index.js +1 -0
  14. package/dist/adaptors/vue/composables.js +0 -1
  15. package/dist/adaptors/vue/index.d.ts +1 -1
  16. package/dist/adaptors/vue/index.js +1 -1
  17. package/dist/config.js +11 -1
  18. package/dist/core/eventReceivers.d.ts +3 -3
  19. package/dist/core/eventReceivers.js +6 -3
  20. package/dist/core.css +1 -0
  21. package/dist/debug/statezeroDebug.d.ts +8 -0
  22. package/dist/debug/statezeroDebug.js +118 -0
  23. package/dist/flavours/django/errors.js +0 -1
  24. package/dist/flavours/django/makeApiCall.js +71 -0
  25. package/dist/reset.js +0 -2
  26. package/dist/syncEngine/cache/cache.js +0 -1
  27. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +1 -1
  28. package/dist/syncEngine/registries/querysetStoreRegistry.js +45 -3
  29. package/dist/syncEngine/stores/metricStore.js +0 -4
  30. package/dist/syncEngine/stores/modelStore.js +0 -3
  31. package/dist/syncEngine/stores/operation.js +0 -1
  32. package/dist/syncEngine/stores/operationEventHandlers.js +87 -2
  33. package/dist/syncEngine/stores/querysetStore.d.ts +1 -1
  34. package/dist/syncEngine/stores/querysetStore.js +58 -5
  35. package/dist/syncEngine/sync.d.ts +2 -0
  36. package/dist/syncEngine/sync.js +17 -11
  37. package/dist/vue-entry.d.ts +2 -1
  38. package/dist/vue-entry.js +2 -2
  39. package/package.json +1 -1
@@ -7,6 +7,7 @@ import { QuerySet } from '../../flavours/django/querySet.js';
7
7
  import { isEqual, isNil } from 'lodash-es';
8
8
  import hash from 'object-hash';
9
9
  import { filter, applyOrderBy } from '../../filtering/localFiltering.js';
10
+ import { recordDebugEvent } from '../../debug/statezeroDebug.js';
10
11
  /**
11
12
  * Check if two model classes represent the same model.
12
13
  * Uses property comparison instead of reference equality to handle
@@ -38,11 +39,31 @@ function routeCreateOperation(operation, applyAction) {
38
39
  Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
39
40
  if (!isSameModel(store.modelClass, modelClass))
40
41
  return;
42
+ recordDebugEvent({
43
+ type: "routing",
44
+ operationId: operation.operationId,
45
+ operationType: operation.type,
46
+ semanticKey,
47
+ modelName: store.modelClass?.modelName,
48
+ configKey: store.modelClass?.configKey,
49
+ phase: "start",
50
+ });
41
51
  const serializerOptions = store.queryset?._serializerOptions || {};
42
52
  const { offset, limit } = serializerOptions;
43
53
  // Offset > 0: can't determine position based on slicing
44
54
  if (offset != null && offset > 0) {
45
55
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, true));
56
+ recordDebugEvent({
57
+ type: "routing",
58
+ operationId: operation.operationId,
59
+ operationType: operation.type,
60
+ semanticKey,
61
+ modelName: store.modelClass?.modelName,
62
+ configKey: store.modelClass?.configKey,
63
+ decision: "offset>0",
64
+ optimistic: false,
65
+ verify: true,
66
+ });
46
67
  return;
47
68
  }
48
69
  // Evaluate if instances match this queryset's filter
@@ -53,6 +74,17 @@ function routeCreateOperation(operation, applyAction) {
53
74
  if (matchingInstances.length === 0) {
54
75
  // Local filter says no match - trust it (local filtering is robust)
55
76
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
77
+ recordDebugEvent({
78
+ type: "routing",
79
+ operationId: operation.operationId,
80
+ operationType: operation.type,
81
+ semanticKey,
82
+ modelName: store.modelClass?.modelName,
83
+ configKey: store.modelClass?.configKey,
84
+ decision: "filter:no-match",
85
+ optimistic: false,
86
+ verify: false,
87
+ });
56
88
  return;
57
89
  }
58
90
  // Item matches filter - check limit
@@ -75,6 +107,17 @@ function routeCreateOperation(operation, applyAction) {
75
107
  // If some ground truth items couldn't be rendered, we can't trust local sort
76
108
  if (currentItems.length < store.groundTruthPks.length) {
77
109
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, true));
110
+ recordDebugEvent({
111
+ type: "routing",
112
+ operationId: operation.operationId,
113
+ operationType: operation.type,
114
+ semanticKey,
115
+ modelName: store.modelClass?.modelName,
116
+ configKey: store.modelClass?.configKey,
117
+ decision: "limit:ordering:incomplete-render",
118
+ optimistic: false,
119
+ verify: true,
120
+ });
78
121
  return;
79
122
  }
80
123
  const allItems = [...currentItems, ...matchingInstances];
@@ -88,14 +131,47 @@ function routeCreateOperation(operation, applyAction) {
88
131
  if (anyInTopN) {
89
132
  applyAction(store);
90
133
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
134
+ recordDebugEvent({
135
+ type: "routing",
136
+ operationId: operation.operationId,
137
+ operationType: operation.type,
138
+ semanticKey,
139
+ modelName: store.modelClass?.modelName,
140
+ configKey: store.modelClass?.configKey,
141
+ decision: "limit:ordering:in-top",
142
+ optimistic: true,
143
+ verify: false,
144
+ });
91
145
  }
92
146
  else {
93
147
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
148
+ recordDebugEvent({
149
+ type: "routing",
150
+ operationId: operation.operationId,
151
+ operationType: operation.type,
152
+ semanticKey,
153
+ modelName: store.modelClass?.modelName,
154
+ configKey: store.modelClass?.configKey,
155
+ decision: "limit:ordering:out",
156
+ optimistic: false,
157
+ verify: false,
158
+ });
94
159
  }
95
160
  }
96
161
  else {
97
162
  // No ordering - new items go at end, won't be in first N
98
163
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
164
+ recordDebugEvent({
165
+ type: "routing",
166
+ operationId: operation.operationId,
167
+ operationType: operation.type,
168
+ semanticKey,
169
+ modelName: store.modelClass?.modelName,
170
+ configKey: store.modelClass?.configKey,
171
+ decision: "limit:no-ordering",
172
+ optimistic: false,
173
+ verify: false,
174
+ });
99
175
  }
100
176
  return;
101
177
  }
@@ -104,6 +180,17 @@ function routeCreateOperation(operation, applyAction) {
104
180
  // Matches filter, has room or no limit - DEFINITELY_YES
105
181
  applyAction(store);
106
182
  operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
183
+ recordDebugEvent({
184
+ type: "routing",
185
+ operationId: operation.operationId,
186
+ operationType: operation.type,
187
+ semanticKey,
188
+ modelName: store.modelClass?.modelName,
189
+ configKey: store.modelClass?.configKey,
190
+ decision: "match:has-room",
191
+ optimistic: true,
192
+ verify: false,
193
+ });
107
194
  });
108
195
  }
109
196
  /**
@@ -351,7 +438,6 @@ export function initEventHandler() {
351
438
  operationEvents.on(Status.CONFIRMED, handleOperationConfirmed);
352
439
  operationEvents.on(Status.REJECTED, handleOperationRejected);
353
440
  operationEvents.on(Status.MUTATED, handleOperationMutated);
354
- console.log('Operation event handler initialized');
355
441
  }
356
442
  /**
357
443
  * Clean up by removing all event listeners
@@ -362,5 +448,4 @@ export function cleanupEventHandler() {
362
448
  operationEvents.off(Status.CONFIRMED, handleOperationConfirmed);
363
449
  operationEvents.off(Status.REJECTED, handleOperationRejected);
364
450
  operationEvents.off(Status.MUTATED, handleOperationMutated);
365
- console.log('Operation event handler cleaned up');
366
451
  }
@@ -55,6 +55,6 @@ export class QuerysetStore {
55
55
  * Sync this queryset with the database.
56
56
  * Fetches from DB and sets ground truth.
57
57
  */
58
- sync(): Promise<void>;
58
+ sync(canonical_id?: null): Promise<void>;
59
59
  }
60
60
  import { Cache } from '../cache/cache.js';
@@ -6,6 +6,7 @@ import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
6
6
  import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
7
7
  import { Cache } from '../cache/cache.js';
8
8
  import { filter } from "../../filtering/localFiltering.js";
9
+ import { recordDebugEvent } from "../../debug/statezeroDebug.js";
9
10
  export class QuerysetStore {
10
11
  constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
11
12
  this.modelClass = modelClass;
@@ -48,7 +49,6 @@ export class QuerysetStore {
48
49
  if (this.groundTruthPks.length === 0 && this.operationsMap.size === 0) {
49
50
  const cached = this.qsCache.get(this.cacheKey);
50
51
  if (!isNil(cached) && !isEmpty(cached)) {
51
- console.log(`[QuerysetStore] Hydrated ${this.modelClass.modelName} queryset with ${cached.length} items from cache`);
52
52
  this.setGroundTruth(cached);
53
53
  }
54
54
  }
@@ -186,6 +186,17 @@ export class QuerysetStore {
186
186
  if (fromCache) {
187
187
  const cachedResult = this.qsCache.get(this.cacheKey);
188
188
  if (Array.isArray(cachedResult)) {
189
+ recordDebugEvent({
190
+ type: "render",
191
+ source: "cache",
192
+ semanticKey: this.queryset.semanticKey,
193
+ modelName: this.modelClass.modelName,
194
+ configKey: this.modelClass.configKey,
195
+ optimistic,
196
+ groundTruthCount: this.groundTruthPks.length,
197
+ operationsCount: this.operationsMap.size,
198
+ resultCount: cachedResult.length,
199
+ });
189
200
  return cachedResult;
190
201
  }
191
202
  }
@@ -197,12 +208,29 @@ export class QuerysetStore {
197
208
  : this.renderFromData(optimistic);
198
209
  // Validate against model store and apply local filtering/sorting
199
210
  let result = this._getValidatedAndFilteredPks(pks);
211
+ const preLimitCount = result.length;
200
212
  // Apply pagination limit
201
213
  const limit = this.queryset.build().serializerOptions?.limit;
202
214
  if (limit) {
203
215
  result = result.slice(0, limit);
204
216
  }
205
217
  this.setCache(result);
218
+ recordDebugEvent({
219
+ type: "render",
220
+ source: this.groundTruthPks.length === 0 && this.lastSync === null
221
+ ? "modelStore"
222
+ : "groundTruth",
223
+ semanticKey: this.queryset.semanticKey,
224
+ modelName: this.modelClass.modelName,
225
+ configKey: this.modelClass.configKey,
226
+ optimistic,
227
+ groundTruthCount: this.groundTruthPks.length,
228
+ operationsCount: this.operationsMap.size,
229
+ rawCount: Array.isArray(pks) ? pks.length : 0,
230
+ filteredCount: preLimitCount,
231
+ resultCount: result.length,
232
+ limit,
233
+ });
206
234
  return result;
207
235
  }
208
236
  renderFromData(optimistic = true) {
@@ -258,22 +286,29 @@ export class QuerysetStore {
258
286
  * Sync this queryset with the database.
259
287
  * Fetches from DB and sets ground truth.
260
288
  */
261
- async sync() {
289
+ async sync(canonical_id = null) {
262
290
  const id = this.modelClass.modelName;
263
291
  if (this.isSyncing) {
264
292
  console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
265
293
  return;
266
294
  }
267
295
  this.isSyncing = true;
268
- console.log(`[${id}] Starting sync...`);
296
+ recordDebugEvent({
297
+ type: "sync",
298
+ phase: "start",
299
+ semanticKey: this.queryset.semanticKey,
300
+ modelName: this.modelClass.modelName,
301
+ configKey: this.modelClass.configKey,
302
+ canonicalId: canonical_id,
303
+ });
269
304
  try {
270
305
  const response = await this.fetchFn({
271
306
  ast: this.queryset.build(),
272
307
  modelClass: this.modelClass,
308
+ canonical_id,
273
309
  });
274
310
  const { data, included } = response;
275
311
  if (!isNil(data)) {
276
- console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
277
312
  // Clear previous included PKs tracking before processing new data
278
313
  this.includedPks.clear();
279
314
  // Persist all instances (including nested) to the model store
@@ -282,10 +317,28 @@ export class QuerysetStore {
282
317
  }
283
318
  this.setOperations(this.getInflightOperations());
284
319
  this.lastSync = Date.now();
285
- console.log(`[${id}] Sync completed.`);
320
+ recordDebugEvent({
321
+ type: "sync",
322
+ phase: "success",
323
+ semanticKey: this.queryset.semanticKey,
324
+ modelName: this.modelClass.modelName,
325
+ configKey: this.modelClass.configKey,
326
+ canonicalId: canonical_id,
327
+ dataCount: Array.isArray(data) ? data.length : 0,
328
+ includedCount: included ? Object.keys(included).length : 0,
329
+ });
286
330
  }
287
331
  catch (e) {
288
332
  console.error(`[${id}] Failed to sync ground truth:`, e);
333
+ recordDebugEvent({
334
+ type: "sync",
335
+ phase: "error",
336
+ semanticKey: this.queryset.semanticKey,
337
+ modelName: this.modelClass.modelName,
338
+ configKey: this.modelClass.configKey,
339
+ canonicalId: canonical_id,
340
+ message: e?.message,
341
+ });
289
342
  }
290
343
  finally {
291
344
  this.isSyncing = false;
@@ -5,6 +5,8 @@ export class EventPayload {
5
5
  operation_id: any;
6
6
  pk_field_name: any;
7
7
  configKey: any;
8
+ canonical_id: any;
9
+ server_ts_ms: any;
8
10
  instances: any;
9
11
  _cachedInstances: any;
10
12
  get modelClass(): Function | null;
@@ -2,6 +2,7 @@ import { getAllEventReceivers } from "../core/eventReceivers.js";
2
2
  import { operationRegistry } from "./stores/operation.js";
3
3
  import { initializeAllEventReceivers } from "../config.js";
4
4
  import { getEventReceiver } from "../core/eventReceivers.js";
5
+ import { recordDebugEvent } from "../debug/statezeroDebug.js";
5
6
  import { querysetStoreRegistry, QuerysetStoreRegistry, } from "./registries/querysetStoreRegistry.js";
6
7
  import { modelStoreRegistry, ModelStoreRegistry, } from "./registries/modelStoreRegistry.js";
7
8
  import { metricRegistry, MetricRegistry } from "./registries/metricRegistry.js";
@@ -16,6 +17,8 @@ export class EventPayload {
16
17
  this.operation_id = data.operation_id;
17
18
  this.pk_field_name = data.pk_field_name;
18
19
  this.configKey = data.configKey;
20
+ this.canonical_id = data.canonical_id || null;
21
+ this.server_ts_ms = data.server_ts_ms || null;
19
22
  // Parse PK fields to numbers in instances
20
23
  this.instances = data.instances?.map(instance => {
21
24
  if (instance && this.pk_field_name && instance[this.pk_field_name] != null) {
@@ -48,6 +51,16 @@ export class EventPayload {
48
51
  export class SyncManager {
49
52
  constructor() {
50
53
  this.handleEvent = (event) => {
54
+ recordDebugEvent({
55
+ type: "event",
56
+ modelName: event.model,
57
+ configKey: event.configKey,
58
+ semanticKey: `${event.configKey || "default"}::${event.model}`,
59
+ eventType: event.type || event.event,
60
+ instanceCount: Array.isArray(event.instances) ? event.instances.length : 0,
61
+ operationId: event.operation_id,
62
+ socketId: event.socket_id,
63
+ });
51
64
  let payload = new EventPayload(event);
52
65
  let isLocalOperation = operationRegistry.has(payload.operation_id);
53
66
  // Always process metrics immediately (they're lightweight)
@@ -141,18 +154,15 @@ export class SyncManager {
141
154
  const intervalSeconds = config.periodicSyncIntervalSeconds;
142
155
  // If null or undefined, don't start periodic sync
143
156
  if (!intervalSeconds) {
144
- console.log("[SyncManager] Periodic sync disabled (set to null)");
145
157
  return;
146
158
  }
147
159
  const intervalMs = intervalSeconds * 1000;
148
160
  this.periodicSyncTimer = setInterval(() => {
149
161
  this.syncStaleQuerysets();
150
162
  }, intervalMs);
151
- console.log(`[SyncManager] Periodic sync started: ${intervalSeconds}s intervals`);
152
163
  }
153
164
  catch (error) {
154
165
  // If no config, don't start periodic sync by default
155
- console.log("[SyncManager] No config found, periodic sync disabled by default");
156
166
  }
157
167
  }
158
168
  async syncStaleQuerysets() {
@@ -174,7 +184,6 @@ export class SyncManager {
174
184
  }
175
185
  }
176
186
  if (storesToSync.length > 0) {
177
- console.log(`[SyncManager] Periodic sync: syncing ${storesToSync.length} stores`);
178
187
  // Run all groupSync calls in parallel - they coordinate via shared promise cache
179
188
  await Promise.all(storesToSync.map(store => querysetRegistry.groupSync(store.queryset, operationId, dbSyncedKeys)));
180
189
  }
@@ -266,7 +275,6 @@ export class SyncManager {
266
275
  }
267
276
  if (storesToSync.length === 0)
268
277
  return;
269
- console.log(`[SyncManager] Syncing ${storesToSync.length} querysets needing verification for operation ${operationId}`);
270
278
  const syncOperationId = `maybe-sync-${uuidv7()}`;
271
279
  const dbSyncedKeys = new Set([...this.followedQuerysets]
272
280
  .map(qs => qs.semanticKey)
@@ -289,7 +297,6 @@ export class SyncManager {
289
297
  const waitTime = Date.now() - this.batchStartTime;
290
298
  this.eventBatch.clear();
291
299
  this.batchStartTime = null;
292
- console.log(`[SyncManager] Processing batch of ${events.length} events (reason: ${reason}, waited: ${waitTime}ms)`);
293
300
  // Group events by model for efficient processing
294
301
  const eventsByModel = new Map();
295
302
  events.forEach((event) => {
@@ -345,8 +352,9 @@ export class SyncManager {
345
352
  }
346
353
  if (storesToSync.length === 0)
347
354
  return;
348
- // Sync all relevant stores for this model
349
- console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
355
+ // Pick canonical_id from the latest event by server_ts_ms
356
+ const latestEvent = allEvents.reduce((latest, evt) => (evt.server_ts_ms || 0) > (latest.server_ts_ms || 0) ? evt : latest, allEvents[0]);
357
+ const canonical_id = latestEvent.canonical_id;
350
358
  // Generate operationId for this batch - querysets in same chain will coordinate
351
359
  const operationId = `remote-event-${uuidv7()}`;
352
360
  // Get dbSynced keys (followed querysets that have stores)
@@ -356,7 +364,7 @@ export class SyncManager {
356
364
  .map(qs => qs.semanticKey)
357
365
  .filter(key => registry._stores.has(key)));
358
366
  // Run all groupSync calls in parallel - they coordinate via shared promise cache
359
- Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
367
+ Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys, canonical_id)));
360
368
  }
361
369
  processMetrics(event) {
362
370
  const registry = this.registries.get(MetricRegistry);
@@ -371,7 +379,6 @@ export class SyncManager {
371
379
  }
372
380
  // Check if this queryset (or any parent) is being followed
373
381
  if (this.isQuerysetFollowed(entry.queryset)) {
374
- console.log(`syncing metric store for key: ${key}`);
375
382
  entry.store.sync();
376
383
  }
377
384
  }
@@ -417,7 +424,6 @@ export class SyncManager {
417
424
  }
418
425
  });
419
426
  if (pksToSync.length > 0) {
420
- console.log(`[SyncManager] Syncing ${pksToSync.length} nested-only PKs for ${event.model}: ${pksToSync}`);
421
427
  modelStore.sync(pksToSync);
422
428
  }
423
429
  }
@@ -4,6 +4,7 @@ import { MetricAdaptor } from './adaptors/vue/index.js';
4
4
  import { useQueryset } from './adaptors/vue/index.js';
5
5
  import { querysets } from './adaptors/vue/index.js';
6
6
  import { LayoutRenderer } from './adaptors/vue/index.js';
7
+ import { StateZeroDebugPanel } from './adaptors/vue/index.js';
7
8
  import { AlertElement } from './adaptors/vue/index.js';
8
9
  import { LabelElement } from './adaptors/vue/index.js';
9
10
  import { DividerElement } from './adaptors/vue/index.js';
@@ -12,4 +13,4 @@ import { GroupElement } from './adaptors/vue/index.js';
12
13
  import { TabsElement } from './adaptors/vue/index.js';
13
14
  import { ErrorBlock } from './adaptors/vue/index.js';
14
15
  import { createDefaultComponents } from './adaptors/vue/index.js';
15
- export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets, LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
16
+ export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets, LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
package/dist/vue-entry.js CHANGED
@@ -1,7 +1,7 @@
1
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';
2
+ import { LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './adaptors/vue/index.js';
3
3
  export {
4
4
  // Reactivity
5
5
  ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets,
6
6
  // Layout components
7
- LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
7
+ LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.2.45",
3
+ "version": "0.2.47",
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",