@statezero/core 0.2.46 → 0.2.48
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/adaptors/vue/components/LayoutRenderer.d.ts +1 -0
- package/dist/adaptors/vue/components/StateZeroDebugPanel.d.ts +1 -0
- package/dist/adaptors/vue/components/StateZeroDebugPanel.js +5086 -0
- package/dist/adaptors/vue/components/defaults/AlertElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/DisplayElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/DividerElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/ErrorBlock.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/GroupElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/LabelElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/TabsElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
- package/dist/adaptors/vue/components/index.d.ts +2 -0
- package/dist/adaptors/vue/components/index.js +1 -0
- package/dist/adaptors/vue/composables.js +0 -1
- package/dist/adaptors/vue/index.d.ts +1 -1
- package/dist/adaptors/vue/index.js +1 -1
- package/dist/config.js +11 -1
- package/dist/core/eventReceivers.d.ts +3 -3
- package/dist/core/eventReceivers.js +6 -3
- package/dist/core.css +1 -0
- package/dist/debug/statezeroDebug.d.ts +8 -0
- package/dist/debug/statezeroDebug.js +118 -0
- package/dist/flavours/django/errors.js +0 -1
- package/dist/flavours/django/makeApiCall.js +71 -0
- package/dist/reset.js +0 -2
- package/dist/syncEngine/cache/cache.js +0 -1
- package/dist/syncEngine/registries/querysetStoreRegistry.js +42 -0
- package/dist/syncEngine/stores/metricStore.js +0 -4
- package/dist/syncEngine/stores/modelStore.js +0 -3
- package/dist/syncEngine/stores/operation.js +0 -1
- package/dist/syncEngine/stores/operationEventHandlers.js +87 -2
- package/dist/syncEngine/stores/querysetStore.js +56 -4
- package/dist/syncEngine/sync.js +11 -10
- package/dist/vue-entry.d.ts +2 -1
- package/dist/vue-entry.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default AlertElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default DisplayElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default DividerElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ErrorBlock;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default GroupElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default LabelElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default TabsElement;
|
|
@@ -5,4 +5,11 @@
|
|
|
5
5
|
* @returns {Object} Components registry for LayoutRenderer
|
|
6
6
|
*/
|
|
7
7
|
export function createDefaultComponents(options?: Object): Object;
|
|
8
|
+
import AlertElement from './AlertElement.js';
|
|
9
|
+
import LabelElement from './LabelElement.js';
|
|
10
|
+
import DividerElement from './DividerElement.js';
|
|
11
|
+
import DisplayElement from './DisplayElement.js';
|
|
12
|
+
import GroupElement from './GroupElement.js';
|
|
13
|
+
import TabsElement from './TabsElement.js';
|
|
14
|
+
import ErrorBlock from './ErrorBlock.js';
|
|
8
15
|
export { AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock };
|
|
@@ -1 +1,3 @@
|
|
|
1
|
+
export { default as LayoutRenderer } from "./LayoutRenderer.js";
|
|
2
|
+
export { default as StateZeroDebugPanel } from "./StateZeroDebugPanel.js";
|
|
1
3
|
export { AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from "./defaults/index.js";
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// Main layout renderer
|
|
5
5
|
export { default as LayoutRenderer } from './LayoutRenderer.js';
|
|
6
|
+
export { default as StateZeroDebugPanel } from './StateZeroDebugPanel.js';
|
|
6
7
|
// Default component implementations
|
|
7
8
|
export { AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './defaults/index.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { useQueryset, querysets } from "./composables.js";
|
|
2
2
|
export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor } from "./reactivity.js";
|
|
3
|
-
export { LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from "./components/index.js";
|
|
3
|
+
export { LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from "./components/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { useQueryset, querysets } from './composables.js';
|
|
2
2
|
export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor } from './reactivity.js';
|
|
3
3
|
// Layout components
|
|
4
|
-
export { LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './components/index.js';
|
|
4
|
+
export { LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './components/index.js';
|
package/dist/config.js
CHANGED
|
@@ -159,9 +159,19 @@ export function initializeEventReceiver(backendKey = 'default') {
|
|
|
159
159
|
if (!backendConfig.events.pusher.clientOptions.authEndpoint) {
|
|
160
160
|
throw new ConfigError('Pusher auth endpoint is required for Pusher event receiver.');
|
|
161
161
|
}
|
|
162
|
+
const baseGetAuthHeaders = backendConfig.events.pusher.clientOptions.getAuthHeaders ||
|
|
163
|
+
backendConfig.getAuthHeaders;
|
|
164
|
+
const syncToken = backendConfig.SYNC_TOKEN;
|
|
165
|
+
const getAuthHeaders = () => {
|
|
166
|
+
const headers = baseGetAuthHeaders ? baseGetAuthHeaders() : {};
|
|
167
|
+
if (syncToken) {
|
|
168
|
+
return { ...headers, "X-StateZero-Sync-Token": syncToken };
|
|
169
|
+
}
|
|
170
|
+
return headers;
|
|
171
|
+
};
|
|
162
172
|
const clientOptions = {
|
|
163
173
|
...backendConfig.events.pusher.clientOptions,
|
|
164
|
-
getAuthHeaders
|
|
174
|
+
getAuthHeaders
|
|
165
175
|
};
|
|
166
176
|
// Pass the backendKey to the constructor
|
|
167
177
|
receiver = new PusherEventReceiver({ clientOptions }, backendKey);
|
|
@@ -65,9 +65,9 @@ export namespace EventType {
|
|
|
65
65
|
*/
|
|
66
66
|
export class PusherEventReceiver {
|
|
67
67
|
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
* @param {PusherReceiverOptions} options
|
|
69
|
+
* @param {string} configKey - The backend configuration key
|
|
70
|
+
*/
|
|
71
71
|
constructor(options: PusherReceiverOptions, configKey: string);
|
|
72
72
|
configKey: string;
|
|
73
73
|
connectionTimeoutId: NodeJS.Timeout;
|
|
@@ -58,9 +58,9 @@ export const EventType = {
|
|
|
58
58
|
*/
|
|
59
59
|
export class PusherEventReceiver {
|
|
60
60
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
* @param {PusherReceiverOptions} options
|
|
62
|
+
* @param {string} configKey - The backend configuration key
|
|
63
|
+
*/
|
|
64
64
|
constructor(options, configKey) {
|
|
65
65
|
const { clientOptions, formatChannelName, namespaceResolver } = options;
|
|
66
66
|
const CONNECTION_TIMEOUT = 10000; // 10 seconds
|
|
@@ -152,6 +152,9 @@ Common causes:
|
|
|
152
152
|
});
|
|
153
153
|
channel.bind("pusher:subscription_error", (status) => {
|
|
154
154
|
console.error(`Subscription error for channel: ${channelName}. Status:`, status);
|
|
155
|
+
if (status === 409) {
|
|
156
|
+
console.error(`%cSync token mismatch on auth for backend ${this.configKey}. Check scenario/prod config.`, "color: red; font-weight: bold;");
|
|
157
|
+
}
|
|
155
158
|
if (status.status === 401 || status.status === 403) {
|
|
156
159
|
console.error(`%cAuthentication failed for channel ${channelName}. Check your authEndpoint and server-side permissions.`, "color: orange; font-weight: bold;");
|
|
157
160
|
}
|
package/dist/core.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.szd[data-v-6d80920c]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:#1f2937;background:#fff;border:1px solid #e5e7eb;border-radius:8px;display:flex;flex-direction:column;max-height:600px;overflow:hidden}.szd-header[data-v-6d80920c]{border-bottom:1px solid #e5e7eb;background:#f9fafb}.szd-header__top[data-v-6d80920c]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:12px}.szd-header__left[data-v-6d80920c]{display:flex;align-items:center;gap:8px}.szd-header__right[data-v-6d80920c]{display:flex;align-items:center;gap:6px}.szd-header__title[data-v-6d80920c]{font-weight:600;font-size:14px}.szd-header__model[data-v-6d80920c]{font-size:12px;color:#1f2937;background:#e5e7eb;padding:2px 8px;border-radius:4px}.szd-header__model--empty[data-v-6d80920c]{color:#9ca3af;background:transparent}.szd-header__badge[data-v-6d80920c]{font-size:11px;color:#6b7280;background:#f3f4f6;padding:2px 6px;border-radius:4px}.szd-header__badge--syncing[data-v-6d80920c]{color:#059669;background:#d1fae5}.szd-tabs[data-v-6d80920c]{display:flex;gap:0;padding:0 12px;border-top:1px solid #e5e7eb}.szd-tabs__tab[data-v-6d80920c]{padding:10px 16px;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;font-size:13px;font-weight:500;color:#6b7280;transition:all .15s}.szd-tabs__tab[data-v-6d80920c]:hover{color:#1f2937}.szd-tabs__tab--active[data-v-6d80920c]{color:#2563eb;border-bottom-color:#2563eb}.szd-content[data-v-6d80920c]{flex:1;overflow:auto;padding:16px}.szd-panel__section[data-v-6d80920c]{margin-bottom:20px}.szd-panel__section[data-v-6d80920c]:last-child{margin-bottom:0}.szd-panel__heading[data-v-6d80920c]{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin:0 0 10px}.szd-kv__row[data-v-6d80920c]{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #f3f4f6}.szd-kv__row[data-v-6d80920c]:last-child{border-bottom:none}.szd-kv__key[data-v-6d80920c]{font-weight:500;color:#6b7280;min-width:100px}.szd-kv__value[data-v-6d80920c]{color:#1f2937}.szd-kv__value--mono[data-v-6d80920c]{font-family:ui-monospace,monospace;font-size:12px}.szd-kv__note[data-v-6d80920c]{color:#9ca3af;font-size:11px}.szd-stats[data-v-6d80920c]{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:12px}.szd-stat[data-v-6d80920c]{background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:12px;text-align:center}.szd-stat__value[data-v-6d80920c]{font-size:24px;font-weight:600;color:#1f2937}.szd-stat__label[data-v-6d80920c]{font-size:11px;color:#6b7280;margin-top:4px}.szd-event-preview[data-v-6d80920c]{display:flex;align-items:center;gap:10px;width:100%;padding:10px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;text-align:left}.szd-event-preview[data-v-6d80920c]:hover{background:#f3f4f6}.szd-event-preview__badge[data-v-6d80920c]{padding:2px 8px;border-radius:4px;color:#fff;font-size:11px;font-weight:500}.szd-event-preview__text[data-v-6d80920c]{flex:1}.szd-event-preview__time[data-v-6d80920c]{color:#9ca3af;font-size:12px}.szd-timeline-filters[data-v-6d80920c]{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;align-items:center}.szd-filter-chip[data-v-6d80920c]{display:inline-flex;align-items:center;padding:4px 10px;border:1px solid #d1d5db;border-radius:999px;font-size:12px;cursor:pointer;transition:all .15s;-webkit-user-select:none;user-select:none}.szd-filter-chip--active[data-v-6d80920c]{color:#fff}.szd-filter-chip__input[data-v-6d80920c]{display:none}.szd-timeline[data-v-6d80920c]{display:flex;flex-direction:column;gap:4px;max-height:350px;overflow:auto}.szd-timeline__item[data-v-6d80920c]{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:4px;cursor:pointer;transition:background .1s}.szd-timeline__item[data-v-6d80920c]:hover{background:#f3f4f6}.szd-timeline__time[data-v-6d80920c]{font-size:11px;color:#9ca3af;min-width:70px}.szd-timeline__badge[data-v-6d80920c]{padding:2px 8px;border-radius:4px;color:#fff;font-size:10px;font-weight:500;min-width:70px;text-align:center}.szd-timeline__text[data-v-6d80920c]{flex:1;font-size:12px}.szd-preview-header[data-v-6d80920c]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.szd-preview-header .szd-panel__heading[data-v-6d80920c]{margin:0}.szd-data-grid[data-v-6d80920c]{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.szd-data-card[data-v-6d80920c]{border:1px solid #e5e7eb;border-radius:6px;overflow:hidden}.szd-data-card__header[data-v-6d80920c]{display:flex;align-items:center;justify-content:space-between;background:#f9fafb;padding:8px 10px;font-size:12px;font-weight:500;border-bottom:1px solid #e5e7eb}.szd-data-card__badge[data-v-6d80920c]{font-size:10px;padding:2px 6px;border-radius:4px;background:#d1fae5;color:#059669}.szd-data-card__badge--absent[data-v-6d80920c]{background:#fee2e2;color:#dc2626}.szd-data-card__content[data-v-6d80920c]{padding:8px 10px;font-size:11px;font-family:ui-monospace,monospace;background:#1f2937;color:#f3f4f6;margin:0;max-height:150px;overflow:auto}.szd-pipeline[data-v-6d80920c]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}.szd-pipeline__stage[data-v-6d80920c]{display:flex;flex-direction:column;align-items:center;padding:12px 16px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;transition:all .15s;min-width:90px}.szd-pipeline__stage[data-v-6d80920c]:hover{background:#eff6ff;border-color:#2563eb}.szd-pipeline__label[data-v-6d80920c]{font-size:11px;color:#6b7280}.szd-pipeline__count[data-v-6d80920c]{font-size:20px;font-weight:600;color:#1f2937}.szd-pipeline__arrow[data-v-6d80920c]{color:#9ca3af;font-size:18px}.szd-ast[data-v-6d80920c]{background:#1f2937;color:#f3f4f6;padding:12px;border-radius:6px;font-size:11px;font-family:ui-monospace,monospace;margin:0;overflow:auto;max-height:400px}.szd-detail__body .szd-ast[data-v-6d80920c]{max-height:none;flex:1}.szd-detail__panel--sm[data-v-6d80920c]{max-width:440px}.szd-settings-section[data-v-6d80920c]{margin-bottom:20px}.szd-settings-section[data-v-6d80920c]:last-child{margin-bottom:0}.szd-settings-label[data-v-6d80920c]{display:block;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-bottom:8px}.szd-settings-row[data-v-6d80920c]{display:flex;align-items:center;gap:8px}.szd-settings-code[data-v-6d80920c]{flex:1;font-size:11px;font-family:ui-monospace,monospace;background:#f3f4f6;padding:8px 10px;border-radius:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.szd-settings-actions[data-v-6d80920c]{display:flex;gap:8px}.szd-select--full[data-v-6d80920c]{width:100%}.szd-detail[data-v-6d80920c]{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px}.szd-detail__backdrop[data-v-6d80920c]{position:absolute;inset:0;background:#00000080}.szd-detail__panel[data-v-6d80920c]{position:relative;width:100%;max-width:700px;max-height:80vh;background:#fff;border-radius:12px;box-shadow:0 20px 50px #00000040;display:flex;flex-direction:column;overflow:hidden}.szd-detail__header[data-v-6d80920c]{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e5e7eb;background:#f9fafb;border-radius:12px 12px 0 0}.szd-detail__title[data-v-6d80920c]{margin:0;font-size:16px;font-weight:600}.szd-detail__body[data-v-6d80920c]{flex:1;padding:20px;overflow:auto}.szd-slide-enter-active[data-v-6d80920c],.szd-slide-leave-active[data-v-6d80920c]{transition:opacity .2s}.szd-slide-enter-active .szd-detail__panel[data-v-6d80920c],.szd-slide-leave-active .szd-detail__panel[data-v-6d80920c]{transition:transform .2s,opacity .2s}.szd-slide-enter-from[data-v-6d80920c],.szd-slide-leave-to[data-v-6d80920c]{opacity:0}.szd-slide-enter-from .szd-detail__panel[data-v-6d80920c],.szd-slide-leave-to .szd-detail__panel[data-v-6d80920c]{transform:scale(.95);opacity:0}.szd-select[data-v-6d80920c]{padding:6px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;background:#fff;min-width:180px}.szd-btn[data-v-6d80920c]{padding:6px 12px;border:1px solid #d1d5db;border-radius:6px;background:#fff;font-size:13px;cursor:pointer;transition:all .1s}.szd-btn[data-v-6d80920c]:hover:not(:disabled){background:#f3f4f6}.szd-btn[data-v-6d80920c]:disabled{opacity:.5;cursor:not-allowed}.szd-btn--sm[data-v-6d80920c]{padding:4px 8px;font-size:11px}.szd-btn--icon[data-v-6d80920c]{padding:6px;display:flex;align-items:center;justify-content:center}.szd-toggle[data-v-6d80920c]{display:flex;align-items:center;gap:4px;font-size:12px;cursor:pointer}.szd-empty[data-v-6d80920c]{padding:40px;text-align:center;color:#9ca3af}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function recordDebugEvent(entry: any): void;
|
|
2
|
+
export function subscribeDebugEvents(handler: any): () => any;
|
|
3
|
+
export function subscribeDebugClear(handler: any): () => any;
|
|
4
|
+
export function getDebugEntries(): any;
|
|
5
|
+
export function clearDebugEntries(): void;
|
|
6
|
+
export function setDebugEnabled(value: any): void;
|
|
7
|
+
export function setDebugMaxEntries(value: any): void;
|
|
8
|
+
export function startStateZeroDebug(): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import mitt from "mitt";
|
|
2
|
+
import { operationEvents, Status } from "../syncEngine/stores/operation.js";
|
|
3
|
+
const GLOBAL_KEY = "__STATEZERO_DEBUG__";
|
|
4
|
+
const globalState = globalThis[GLOBAL_KEY] || {
|
|
5
|
+
emitter: mitt(),
|
|
6
|
+
buffer: [],
|
|
7
|
+
enabled: true,
|
|
8
|
+
started: false,
|
|
9
|
+
maxEntries: 500,
|
|
10
|
+
nextId: 1,
|
|
11
|
+
};
|
|
12
|
+
globalThis[GLOBAL_KEY] = globalState;
|
|
13
|
+
const emitter = globalState.emitter;
|
|
14
|
+
const buffer = globalState.buffer;
|
|
15
|
+
function pushEntry(entry) {
|
|
16
|
+
if (!globalState.enabled)
|
|
17
|
+
return;
|
|
18
|
+
const item = {
|
|
19
|
+
id: globalState.nextId++,
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
...entry,
|
|
22
|
+
};
|
|
23
|
+
buffer.push(item);
|
|
24
|
+
if (buffer.length > globalState.maxEntries) {
|
|
25
|
+
buffer.shift();
|
|
26
|
+
}
|
|
27
|
+
emitter.emit("record", item);
|
|
28
|
+
}
|
|
29
|
+
export function recordDebugEvent(entry) {
|
|
30
|
+
pushEntry(entry);
|
|
31
|
+
}
|
|
32
|
+
export function subscribeDebugEvents(handler) {
|
|
33
|
+
emitter.on("record", handler);
|
|
34
|
+
return () => emitter.off("record", handler);
|
|
35
|
+
}
|
|
36
|
+
export function subscribeDebugClear(handler) {
|
|
37
|
+
emitter.on("clear", handler);
|
|
38
|
+
return () => emitter.off("clear", handler);
|
|
39
|
+
}
|
|
40
|
+
export function getDebugEntries() {
|
|
41
|
+
return buffer.slice();
|
|
42
|
+
}
|
|
43
|
+
export function clearDebugEntries() {
|
|
44
|
+
buffer.length = 0;
|
|
45
|
+
emitter.emit("clear");
|
|
46
|
+
}
|
|
47
|
+
export function setDebugEnabled(value) {
|
|
48
|
+
globalState.enabled = Boolean(value);
|
|
49
|
+
}
|
|
50
|
+
export function setDebugMaxEntries(value) {
|
|
51
|
+
const next = Number(value);
|
|
52
|
+
if (!Number.isFinite(next) || next <= 0)
|
|
53
|
+
return;
|
|
54
|
+
globalState.maxEntries = Math.floor(next);
|
|
55
|
+
if (buffer.length > globalState.maxEntries) {
|
|
56
|
+
buffer.splice(0, buffer.length - globalState.maxEntries);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function startStateZeroDebug() {
|
|
60
|
+
if (globalState.started)
|
|
61
|
+
return;
|
|
62
|
+
globalState.started = true;
|
|
63
|
+
operationEvents.on(Status.CREATED, (operation) => {
|
|
64
|
+
pushEntry({
|
|
65
|
+
type: "operation",
|
|
66
|
+
subtype: Status.CREATED,
|
|
67
|
+
operationId: operation.operationId,
|
|
68
|
+
operationType: operation.type,
|
|
69
|
+
status: operation.status,
|
|
70
|
+
semanticKey: operation.queryset?.semanticKey,
|
|
71
|
+
modelName: operation.queryset?.ModelClass?.modelName,
|
|
72
|
+
configKey: operation.queryset?.ModelClass?.configKey,
|
|
73
|
+
instanceCount: operation.instances?.length ?? 0,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
operationEvents.on(Status.MUTATED, (operation) => {
|
|
77
|
+
pushEntry({
|
|
78
|
+
type: "operation",
|
|
79
|
+
subtype: Status.MUTATED,
|
|
80
|
+
operationId: operation.operationId,
|
|
81
|
+
operationType: operation.type,
|
|
82
|
+
status: operation.status,
|
|
83
|
+
semanticKey: operation.queryset?.semanticKey,
|
|
84
|
+
modelName: operation.queryset?.ModelClass?.modelName,
|
|
85
|
+
configKey: operation.queryset?.ModelClass?.configKey,
|
|
86
|
+
instanceCount: operation.instances?.length ?? 0,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
operationEvents.on(Status.CONFIRMED, (operation) => {
|
|
90
|
+
pushEntry({
|
|
91
|
+
type: "operation",
|
|
92
|
+
subtype: Status.CONFIRMED,
|
|
93
|
+
operationId: operation.operationId,
|
|
94
|
+
operationType: operation.type,
|
|
95
|
+
status: operation.status,
|
|
96
|
+
semanticKey: operation.queryset?.semanticKey,
|
|
97
|
+
modelName: operation.queryset?.ModelClass?.modelName,
|
|
98
|
+
configKey: operation.queryset?.ModelClass?.configKey,
|
|
99
|
+
instanceCount: operation.instances?.length ?? 0,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
operationEvents.on(Status.REJECTED, (operation) => {
|
|
103
|
+
pushEntry({
|
|
104
|
+
type: "operation",
|
|
105
|
+
subtype: Status.REJECTED,
|
|
106
|
+
operationId: operation.operationId,
|
|
107
|
+
operationType: operation.type,
|
|
108
|
+
status: operation.status,
|
|
109
|
+
semanticKey: operation.queryset?.semanticKey,
|
|
110
|
+
modelName: operation.queryset?.ModelClass?.modelName,
|
|
111
|
+
configKey: operation.queryset?.ModelClass?.configKey,
|
|
112
|
+
instanceCount: operation.instances?.length ?? 0,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
operationEvents.on(Status.CLEAR, () => {
|
|
116
|
+
pushEntry({ type: "operation", subtype: Status.CLEAR });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -154,7 +154,6 @@ export class ConfigError extends StateZeroError {
|
|
|
154
154
|
* @returns {StateZeroError} An instance of a StateZeroError subclass.
|
|
155
155
|
*/
|
|
156
156
|
export function parseStateZeroError(errorResponse) {
|
|
157
|
-
console.log(JSON.stringify(errorResponse));
|
|
158
157
|
const { status, type, detail } = errorResponse;
|
|
159
158
|
// Handle undefined type/status case (like in permission denied)
|
|
160
159
|
if (type === undefined && detail === 'Invalid token.') {
|
|
@@ -5,6 +5,7 @@ import { replaceTempPks } from './tempPk.js';
|
|
|
5
5
|
import { parseStateZeroError, MultipleObjectsReturned, DoesNotExist } from './errors.js';
|
|
6
6
|
import { FileObject } from './files.js';
|
|
7
7
|
import { querysetStoreRegistry } from '../../syncEngine/registries/querysetStoreRegistry.js';
|
|
8
|
+
import { recordDebugEvent } from '../../debug/statezeroDebug.js';
|
|
8
9
|
// Namespace-based queues: separate queues for different operation types
|
|
9
10
|
// This prevents sync operations from blocking user-initiated app operations
|
|
10
11
|
const queues = new Map();
|
|
@@ -130,6 +131,7 @@ export async function makeApiCall(querySet, operationType, args = {}, operationI
|
|
|
130
131
|
const baseUrl = backend.API_URL.replace(/\/+$/, "");
|
|
131
132
|
const finalUrl = `${baseUrl}/${ModelClass.modelName}/`;
|
|
132
133
|
const headers = backend.getAuthHeaders ? backend.getAuthHeaders() : {};
|
|
134
|
+
const semanticKey = querySet.semanticKey;
|
|
133
135
|
if (operationId) {
|
|
134
136
|
headers["X-Operation-ID"] = operationId;
|
|
135
137
|
}
|
|
@@ -139,13 +141,82 @@ export async function makeApiCall(querySet, operationType, args = {}, operationI
|
|
|
139
141
|
// Use the queue for write operations, bypass for read operations
|
|
140
142
|
const apiCall = async () => {
|
|
141
143
|
try {
|
|
144
|
+
recordDebugEvent({
|
|
145
|
+
type: "request",
|
|
146
|
+
modelName: ModelClass.modelName,
|
|
147
|
+
configKey: ModelClass.configKey,
|
|
148
|
+
semanticKey,
|
|
149
|
+
operationType,
|
|
150
|
+
operationId,
|
|
151
|
+
canonicalId,
|
|
152
|
+
url: finalUrl,
|
|
153
|
+
payload,
|
|
154
|
+
namespace,
|
|
155
|
+
});
|
|
142
156
|
let response = await axios.post(finalUrl, replaceTempPks(payload), { headers });
|
|
143
157
|
if (typeof beforeExit === 'function' && response?.data) {
|
|
144
158
|
await beforeExit(response.data);
|
|
145
159
|
}
|
|
160
|
+
const rawData = response?.data?.data;
|
|
161
|
+
const rawIncluded = response?.data?.included;
|
|
162
|
+
const dataCount = Array.isArray(rawData)
|
|
163
|
+
? rawData.length
|
|
164
|
+
: rawData != null
|
|
165
|
+
? 1
|
|
166
|
+
: 0;
|
|
167
|
+
const includedCount = rawIncluded ? Object.keys(rawIncluded).length : 0;
|
|
168
|
+
const maxItems = 100;
|
|
169
|
+
const data = Array.isArray(rawData) && rawData.length > maxItems
|
|
170
|
+
? rawData.slice(0, maxItems)
|
|
171
|
+
: rawData;
|
|
172
|
+
const dataTruncated = Array.isArray(rawData) && rawData.length > maxItems;
|
|
173
|
+
let included = rawIncluded;
|
|
174
|
+
let includedTruncated = false;
|
|
175
|
+
if (rawIncluded && typeof rawIncluded === "object") {
|
|
176
|
+
const limited = {};
|
|
177
|
+
let count = 0;
|
|
178
|
+
for (const [key, value] of Object.entries(rawIncluded)) {
|
|
179
|
+
limited[key] = value;
|
|
180
|
+
count += 1;
|
|
181
|
+
if (count >= maxItems) {
|
|
182
|
+
includedTruncated = true;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
included = includedTruncated ? limited : rawIncluded;
|
|
187
|
+
}
|
|
188
|
+
recordDebugEvent({
|
|
189
|
+
type: "response",
|
|
190
|
+
modelName: ModelClass.modelName,
|
|
191
|
+
configKey: ModelClass.configKey,
|
|
192
|
+
semanticKey,
|
|
193
|
+
operationType,
|
|
194
|
+
operationId,
|
|
195
|
+
canonicalId,
|
|
196
|
+
url: finalUrl,
|
|
197
|
+
status: response.status,
|
|
198
|
+
dataCount,
|
|
199
|
+
includedCount,
|
|
200
|
+
data,
|
|
201
|
+
included,
|
|
202
|
+
dataTruncated,
|
|
203
|
+
includedTruncated,
|
|
204
|
+
});
|
|
146
205
|
return response.data;
|
|
147
206
|
}
|
|
148
207
|
catch (error) {
|
|
208
|
+
recordDebugEvent({
|
|
209
|
+
type: "error",
|
|
210
|
+
modelName: ModelClass.modelName,
|
|
211
|
+
configKey: ModelClass.configKey,
|
|
212
|
+
semanticKey,
|
|
213
|
+
operationType,
|
|
214
|
+
operationId,
|
|
215
|
+
canonicalId,
|
|
216
|
+
url: finalUrl,
|
|
217
|
+
status: error?.response?.status,
|
|
218
|
+
message: error?.message,
|
|
219
|
+
});
|
|
149
220
|
if (error?.code === "ECONNREFUSED") {
|
|
150
221
|
const hint = "Connection refused. If you're running tests, start the test server with `python manage.py statezero_testserver`.";
|
|
151
222
|
throw new Error(`${hint} (${finalUrl})`);
|
package/dist/reset.js
CHANGED
|
@@ -31,7 +31,6 @@ export function unregisterAdapterReset(resetFn) {
|
|
|
31
31
|
* Call this after login to ensure clean authenticated state.
|
|
32
32
|
*/
|
|
33
33
|
export async function resetStateZero() {
|
|
34
|
-
console.log("🔄 StateZero: Resetting...");
|
|
35
34
|
try {
|
|
36
35
|
// 1. Clear ALL operations first
|
|
37
36
|
operationRegistry.clear();
|
|
@@ -88,7 +87,6 @@ export async function resetStateZero() {
|
|
|
88
87
|
querysetStoreRegistry.setSyncManager(syncManager);
|
|
89
88
|
modelStoreRegistry.setSyncManager(syncManager);
|
|
90
89
|
metricRegistry.setSyncManager(syncManager);
|
|
91
|
-
console.log("✅ StateZero reset complete - everything cleared");
|
|
92
90
|
}
|
|
93
91
|
catch (error) {
|
|
94
92
|
console.error("❌ StateZero reset failed:", error);
|
|
@@ -38,7 +38,6 @@ export class IndexedDBStore {
|
|
|
38
38
|
try {
|
|
39
39
|
// Delete the database
|
|
40
40
|
await this._deleteDatabase();
|
|
41
|
-
console.log(`[IndexedDBStore] Successfully deleted database "${this.dbName}", attempting to reopen...`);
|
|
42
41
|
// Try to open it again at the desired version
|
|
43
42
|
return await this._openDatabase();
|
|
44
43
|
}
|
|
@@ -22,6 +22,7 @@ import { isNil, pick } from 'lodash-es';
|
|
|
22
22
|
import hash from 'object-hash';
|
|
23
23
|
import { Operation } from '../stores/operation.js';
|
|
24
24
|
import { Cache } from '../cache/cache.js';
|
|
25
|
+
import { recordDebugEvent } from '../../debug/statezeroDebug.js';
|
|
25
26
|
/**
|
|
26
27
|
* A dynamic wrapper that always returns the latest queryset results
|
|
27
28
|
* This class proxies array operations to always reflect the current state
|
|
@@ -304,6 +305,16 @@ export class QuerysetStoreRegistry {
|
|
|
304
305
|
// Find the dbSynced root
|
|
305
306
|
const { isRoot, root: rootKey } = this.querysetStoreGraph.findRoot(queryset, subset);
|
|
306
307
|
const iAmRoot = isRoot || rootKey === semanticKey;
|
|
308
|
+
recordDebugEvent({
|
|
309
|
+
type: "groupSync",
|
|
310
|
+
phase: "start",
|
|
311
|
+
operationId,
|
|
312
|
+
semanticKey,
|
|
313
|
+
rootKey,
|
|
314
|
+
iAmRoot,
|
|
315
|
+
modelName: ModelClass?.modelName,
|
|
316
|
+
configKey: ModelClass?.configKey,
|
|
317
|
+
});
|
|
307
318
|
// Get or create cache entry - whoever arrives first creates it
|
|
308
319
|
if (!this._groupSyncCache.has(operationId)) {
|
|
309
320
|
let resolve;
|
|
@@ -322,6 +333,18 @@ export class QuerysetStoreRegistry {
|
|
|
322
333
|
await store.sync(canonical_id);
|
|
323
334
|
cached.pks = store.groundTruthPks;
|
|
324
335
|
cached.resolve();
|
|
336
|
+
recordDebugEvent({
|
|
337
|
+
type: "groupSync",
|
|
338
|
+
phase: "rootSynced",
|
|
339
|
+
operationId,
|
|
340
|
+
semanticKey,
|
|
341
|
+
rootKey,
|
|
342
|
+
modelName: ModelClass?.modelName,
|
|
343
|
+
configKey: ModelClass?.configKey,
|
|
344
|
+
dataCount: Array.isArray(store.groundTruthPks)
|
|
345
|
+
? store.groundTruthPks.length
|
|
346
|
+
: 0,
|
|
347
|
+
});
|
|
325
348
|
}
|
|
326
349
|
else {
|
|
327
350
|
// Wait for root to finish with timeout to prevent deadlocks
|
|
@@ -350,6 +373,15 @@ export class QuerysetStoreRegistry {
|
|
|
350
373
|
if (timedOut || !cached.pks) {
|
|
351
374
|
console.warn(`[groupSync] Falling back to direct sync for: ${semanticKey.substring(0, 60)}`);
|
|
352
375
|
await store.sync(canonical_id);
|
|
376
|
+
recordDebugEvent({
|
|
377
|
+
type: "groupSync",
|
|
378
|
+
phase: "fallbackSync",
|
|
379
|
+
operationId,
|
|
380
|
+
semanticKey,
|
|
381
|
+
rootKey,
|
|
382
|
+
modelName: ModelClass?.modelName,
|
|
383
|
+
configKey: ModelClass?.configKey,
|
|
384
|
+
});
|
|
353
385
|
return;
|
|
354
386
|
}
|
|
355
387
|
// Filter from cached root data
|
|
@@ -360,6 +392,16 @@ export class QuerysetStoreRegistry {
|
|
|
360
392
|
store.setGroundTruth(filteredPks);
|
|
361
393
|
store.setOperations(store.getInflightOperations());
|
|
362
394
|
store.lastSync = Date.now();
|
|
395
|
+
recordDebugEvent({
|
|
396
|
+
type: "groupSync",
|
|
397
|
+
phase: "filteredFromRoot",
|
|
398
|
+
operationId,
|
|
399
|
+
semanticKey,
|
|
400
|
+
rootKey,
|
|
401
|
+
modelName: ModelClass?.modelName,
|
|
402
|
+
configKey: ModelClass?.configKey,
|
|
403
|
+
dataCount: Array.isArray(filteredPks) ? filteredPks.length : 0,
|
|
404
|
+
});
|
|
363
405
|
}
|
|
364
406
|
}
|
|
365
407
|
}
|
|
@@ -124,7 +124,6 @@ export class MetricStore {
|
|
|
124
124
|
if (this.groundTruthValue === null) {
|
|
125
125
|
const cached = this.metricCache.get(this.cacheKey);
|
|
126
126
|
if (!isNil(cached) && !isEmpty(cached)) {
|
|
127
|
-
console.log(`[MetricStore] Hydrated ${this.metricType} metric for ${this.modelClass.modelName} from cache`);
|
|
128
127
|
this.setGroundTruth(cached?.value);
|
|
129
128
|
}
|
|
130
129
|
}
|
|
@@ -164,7 +163,6 @@ export class MetricStore {
|
|
|
164
163
|
render() {
|
|
165
164
|
// Check if ground truth value is null
|
|
166
165
|
if (isNil(this.groundTruthValue)) {
|
|
167
|
-
console.log(`groundTruthValue is null, returning null`);
|
|
168
166
|
return null;
|
|
169
167
|
}
|
|
170
168
|
// Calculate the new value using the operations-based approach
|
|
@@ -193,7 +191,6 @@ export class MetricStore {
|
|
|
193
191
|
}
|
|
194
192
|
this.isSyncing = true;
|
|
195
193
|
try {
|
|
196
|
-
console.log(`[MetricStore] Syncing ${this.metricType} metric for ${this.modelClass.modelName}`);
|
|
197
194
|
// Use fetchFn to get server metric value
|
|
198
195
|
const result = await this.fetchFn({
|
|
199
196
|
metricType: this.metricType,
|
|
@@ -208,7 +205,6 @@ export class MetricStore {
|
|
|
208
205
|
}
|
|
209
206
|
// Update ground truth
|
|
210
207
|
this.setGroundTruth(result);
|
|
211
|
-
console.log(`[MetricStore] Synced ${this.metricType} metric with value:`, result);
|
|
212
208
|
return result;
|
|
213
209
|
}
|
|
214
210
|
catch (error) {
|
|
@@ -137,7 +137,6 @@ export class ModelStore {
|
|
|
137
137
|
onHydrated() {
|
|
138
138
|
if (this.groundTruthArray.length === 0 && this.operationsMap.size === 0) {
|
|
139
139
|
let cached = this.modelCache.get(this.cacheKey);
|
|
140
|
-
console.log(`[ModelStore] Hydrated ${this.modelClass.modelName} with ${(cached || []).length} items from cache`);
|
|
141
140
|
if (!isNil(cached) && !isEmpty(cached)) {
|
|
142
141
|
this.setGroundTruth(cached);
|
|
143
142
|
}
|
|
@@ -322,7 +321,6 @@ export class ModelStore {
|
|
|
322
321
|
queryset: this.modelClass.objects.all(),
|
|
323
322
|
});
|
|
324
323
|
this.operationsMap.set(checkpointOperation.operationId, checkpointOperation);
|
|
325
|
-
console.log(`[ModelStore ${this.modelClass.modelName}] Created CHECKPOINT operation for ${checkpointInstances.length} existing instances`);
|
|
326
324
|
}
|
|
327
325
|
// reactivity - use all the newly added instances (both new and updated)
|
|
328
326
|
emitEvents(this, EventData.fromInstances([...checkpointInstances, ...Array.from(pkMap.values())], this.modelClass));
|
|
@@ -450,7 +448,6 @@ export class ModelStore {
|
|
|
450
448
|
});
|
|
451
449
|
const removedCount = this.groundTruthArray.length - filteredGroundTruth.length;
|
|
452
450
|
if (removedCount > 0) {
|
|
453
|
-
console.log(`[ModelStore ${modelName}] Pruned ${removedCount} unreferenced instances (${filteredGroundTruth.length} remaining)`);
|
|
454
451
|
this.groundTruthArray = filteredGroundTruth;
|
|
455
452
|
// Update the cache to reflect the pruned data
|
|
456
453
|
this.setCache(filteredGroundTruth);
|
|
@@ -270,7 +270,6 @@ class OperationRegistry {
|
|
|
270
270
|
* Clears all operations from the registry.
|
|
271
271
|
*/
|
|
272
272
|
clear() {
|
|
273
|
-
console.log("OperationRegistry: Clearing all operations.");
|
|
274
273
|
this._operations.clear();
|
|
275
274
|
this._querysetStates.clear();
|
|
276
275
|
operationEvents.emit(Status.CLEAR);
|