@statezero/core 0.2.53 → 0.2.55

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.
@@ -26,6 +26,11 @@ export function useQueryset(querysetFactory) {
26
26
  // Metrics: the adaptor already returns a ref(scalar) — return it directly.
27
27
  // Wrapping in another computed would cause .value.value in templates.
28
28
  if (isRef(result) && result.original instanceof LiveMetric) {
29
+ const liveMetric = result.original;
30
+ metricRegistry.followMetric(liveMetric.metricType, liveMetric.queryset, liveMetric.field);
31
+ onBeforeUnmount(() => {
32
+ metricRegistry.unfollowMetric(liveMetric.metricType, liveMetric.queryset, liveMetric.field);
33
+ });
29
34
  return result;
30
35
  }
31
36
  return computed(() => {
package/dist/core.css CHANGED
@@ -1 +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}
1
+ .szd[data-v-cf94aafd]{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-cf94aafd]{border-bottom:1px solid #e5e7eb;background:#f9fafb}.szd-header__top[data-v-cf94aafd]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:12px}.szd-header__left[data-v-cf94aafd]{display:flex;align-items:center;gap:8px}.szd-header__right[data-v-cf94aafd]{display:flex;align-items:center;gap:6px}.szd-header__title[data-v-cf94aafd]{font-weight:600;font-size:14px}.szd-header__model[data-v-cf94aafd]{font-size:12px;color:#1f2937;background:#e5e7eb;padding:2px 8px;border-radius:4px}.szd-header__model--empty[data-v-cf94aafd]{color:#9ca3af;background:transparent}.szd-header__badge[data-v-cf94aafd]{font-size:11px;color:#6b7280;background:#f3f4f6;padding:2px 6px;border-radius:4px}.szd-header__badge--syncing[data-v-cf94aafd]{color:#059669;background:#d1fae5}.szd-tabs[data-v-cf94aafd]{display:flex;gap:0;padding:0 12px;border-top:1px solid #e5e7eb}.szd-tabs__tab[data-v-cf94aafd]{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-cf94aafd]:hover{color:#1f2937}.szd-tabs__tab--active[data-v-cf94aafd]{color:#2563eb;border-bottom-color:#2563eb}.szd-content[data-v-cf94aafd]{flex:1;overflow:auto;padding:16px}.szd-panel__section[data-v-cf94aafd]{margin-bottom:20px}.szd-panel__section[data-v-cf94aafd]:last-child{margin-bottom:0}.szd-panel__heading[data-v-cf94aafd]{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin:0 0 10px}.szd-kv__row[data-v-cf94aafd]{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #f3f4f6}.szd-kv__row[data-v-cf94aafd]:last-child{border-bottom:none}.szd-kv__key[data-v-cf94aafd]{font-weight:500;color:#6b7280;min-width:100px}.szd-kv__value[data-v-cf94aafd]{color:#1f2937}.szd-kv__value--mono[data-v-cf94aafd]{font-family:ui-monospace,monospace;font-size:12px}.szd-kv__note[data-v-cf94aafd]{color:#9ca3af;font-size:11px}.szd-stats[data-v-cf94aafd]{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:12px}.szd-stat[data-v-cf94aafd]{background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:12px;text-align:center}.szd-stat__value[data-v-cf94aafd]{font-size:24px;font-weight:600;color:#1f2937}.szd-stat__label[data-v-cf94aafd]{font-size:11px;color:#6b7280;margin-top:4px}.szd-event-preview[data-v-cf94aafd]{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-cf94aafd]:hover{background:#f3f4f6}.szd-event-preview__badge[data-v-cf94aafd]{padding:2px 8px;border-radius:4px;color:#fff;font-size:11px;font-weight:500}.szd-event-preview__text[data-v-cf94aafd]{flex:1}.szd-event-preview__time[data-v-cf94aafd]{color:#9ca3af;font-size:12px}.szd-timeline-filters[data-v-cf94aafd]{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;align-items:center}.szd-filter-chip[data-v-cf94aafd]{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-cf94aafd]{color:#fff}.szd-filter-chip__input[data-v-cf94aafd]{display:none}.szd-timeline[data-v-cf94aafd]{display:flex;flex-direction:column;gap:4px;max-height:350px;overflow:auto}.szd-timeline__item[data-v-cf94aafd]{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:4px;cursor:pointer;transition:background .1s}.szd-timeline__item[data-v-cf94aafd]:hover{background:#f3f4f6}.szd-timeline__time[data-v-cf94aafd]{font-size:11px;color:#9ca3af;min-width:70px}.szd-timeline__badge[data-v-cf94aafd]{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-cf94aafd]{flex:1;font-size:12px}.szd-preview-header[data-v-cf94aafd]{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.szd-preview-header .szd-panel__heading[data-v-cf94aafd]{margin:0}.szd-data-grid[data-v-cf94aafd]{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.szd-data-card[data-v-cf94aafd]{border:1px solid #e5e7eb;border-radius:6px;overflow:hidden}.szd-data-card__header[data-v-cf94aafd]{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-cf94aafd]{font-size:10px;padding:2px 6px;border-radius:4px;background:#d1fae5;color:#059669}.szd-data-card__badge--absent[data-v-cf94aafd]{background:#fee2e2;color:#dc2626}.szd-data-card__content[data-v-cf94aafd]{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-cf94aafd]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}.szd-pipeline__stage[data-v-cf94aafd]{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-cf94aafd]:hover{background:#eff6ff;border-color:#2563eb}.szd-pipeline__label[data-v-cf94aafd]{font-size:11px;color:#6b7280}.szd-pipeline__count[data-v-cf94aafd]{font-size:20px;font-weight:600;color:#1f2937}.szd-pipeline__arrow[data-v-cf94aafd]{color:#9ca3af;font-size:18px}.szd-ast[data-v-cf94aafd]{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-cf94aafd]{max-height:none;flex:1}.szd-detail__panel--sm[data-v-cf94aafd]{max-width:440px}.szd-settings-section[data-v-cf94aafd]{margin-bottom:20px}.szd-settings-section[data-v-cf94aafd]:last-child{margin-bottom:0}.szd-settings-label[data-v-cf94aafd]{display:block;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin-bottom:8px}.szd-settings-row[data-v-cf94aafd]{display:flex;align-items:center;gap:8px}.szd-settings-code[data-v-cf94aafd]{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-cf94aafd]{display:flex;gap:8px}.szd-select--full[data-v-cf94aafd]{width:100%}.szd-detail[data-v-cf94aafd]{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px}.szd-detail__backdrop[data-v-cf94aafd]{position:absolute;inset:0;background:#00000080}.szd-detail__panel[data-v-cf94aafd]{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-cf94aafd]{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-cf94aafd]{margin:0;font-size:16px;font-weight:600}.szd-detail__body[data-v-cf94aafd]{flex:1;padding:20px;overflow:auto}.szd-slide-enter-active[data-v-cf94aafd],.szd-slide-leave-active[data-v-cf94aafd]{transition:opacity .2s}.szd-slide-enter-active .szd-detail__panel[data-v-cf94aafd],.szd-slide-leave-active .szd-detail__panel[data-v-cf94aafd]{transition:transform .2s,opacity .2s}.szd-slide-enter-from[data-v-cf94aafd],.szd-slide-leave-to[data-v-cf94aafd]{opacity:0}.szd-slide-enter-from .szd-detail__panel[data-v-cf94aafd],.szd-slide-leave-to .szd-detail__panel[data-v-cf94aafd]{transform:scale(.95);opacity:0}.szd-select[data-v-cf94aafd]{padding:6px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;background:#fff;min-width:180px}.szd-btn[data-v-cf94aafd]{padding:6px 12px;border:1px solid #d1d5db;border-radius:6px;background:#fff;font-size:13px;cursor:pointer;transition:all .1s}.szd-btn[data-v-cf94aafd]:hover:not(:disabled){background:#f3f4f6}.szd-btn[data-v-cf94aafd]:disabled{opacity:.5;cursor:not-allowed}.szd-btn--sm[data-v-cf94aafd]{padding:4px 8px;font-size:11px}.szd-btn--icon[data-v-cf94aafd]{padding:6px;display:flex;align-items:center;justify-content:center}.szd-toggle[data-v-cf94aafd]{display:flex;align-items:center;gap:4px;font-size:12px;cursor:pointer}.szd-empty[data-v-cf94aafd]{padding:40px;text-align:center;color:#9ca3af}
@@ -1,79 +1,10 @@
1
1
  /**
2
- * Base class for metric calculation strategies with operations
3
- */
4
- export class MetricCalculationStrategy {
5
- /**
6
- * Calculate metric based on ground truth and operations
7
- */
8
- calculateWithOperations(baseValue: any, operations: any, field: any, ModelClass: any): any;
9
- /**
10
- * Get initial value for the metric type
11
- * Override in subclasses if needed
12
- */
13
- getInitialValue(): number;
14
- /**
15
- * Process a single operation - implement in subclasses
16
- */
17
- reduceOperation(currentValue: any, operation: any, field: any): void;
18
- /**
19
- * Safely get a numeric value from an object
20
- */
21
- safeGetValue(obj: any, field: any): number;
22
- }
23
- /**
24
- * Count strategy implementation
25
- */
26
- export class CountStrategy extends MetricCalculationStrategy {
27
- reduceOperation(currentCount: any, operation: any, field: any): any;
28
- }
29
- /**
30
- * Sum strategy implementation
31
- */
32
- export class SumStrategy extends MetricCalculationStrategy {
33
- reduceOperation(currentSum: any, operation: any, field: any): any;
34
- }
35
- /**
36
- * Min strategy implementation
37
- */
38
- export class MinStrategy extends MetricCalculationStrategy {
39
- reduceOperation(currentMin: any, operation: any, field: any): any;
40
- }
41
- /**
42
- * Max strategy implementation
43
- */
44
- export class MaxStrategy extends MetricCalculationStrategy {
45
- reduceOperation(currentMax: any, operation: any, field: any): any;
46
- }
47
- /**
48
- * Factory class for creating the appropriate strategy
49
- */
50
- export class MetricStrategyFactory {
51
- static "__#private@#customStrategies": Map<any, any>;
52
- static "__#private@#defaultStrategies": Map<string, () => CountStrategy>;
53
- /**
54
- * Clear all custom strategy overrides
55
- */
56
- static clearCustomStrategies(): void;
57
- /**
58
- * Generate a unique key for the strategy map
59
- * @private
60
- * @param {string} metricType - The type of metric (count, sum, min, max)
61
- * @param {Function} ModelClass - The model class
62
- * @returns {string} A unique key
63
- */
64
- private static "__#private@#generateStrategyKey";
65
- /**
66
- * Override a strategy for a specific metric type and model class
67
- * @param {string} metricType - The type of metric (count, sum, min, max)
68
- * @param {Function|null} ModelClass - The model class or null for a generic override
69
- * @param {MetricCalculationStrategy} strategy - The strategy to use
70
- */
71
- static overrideStrategy(metricType: string, ModelClass: Function | null, strategy: MetricCalculationStrategy): void;
72
- /**
73
- * Get the appropriate strategy for a model class and metric type
74
- * @param {string} metricType - The type of metric (count, sum, min, max)
75
- * @param {Function} ModelClass - The model class
76
- * @returns {MetricCalculationStrategy} The appropriate strategy
77
- */
78
- static getStrategy(metricType: string, ModelClass: Function): MetricCalculationStrategy;
79
- }
2
+ * Compute a metric value from a queryset store's current rendered PKs.
3
+ *
4
+ * @param {string} metricType - 'count', 'sum', 'min', 'max'
5
+ * @param {QuerysetStore} querysetStore - The queryset store to compute from
6
+ * @param {string|null} field - The field to aggregate (null for count)
7
+ * @param {Function} ModelClass - The model class for instance lookups
8
+ * @returns {number} The computed metric value
9
+ */
10
+ export function computeMetricFromQueryset(metricType: string, querysetStore: QuerysetStore, field: string | null, ModelClass: Function): number;
@@ -1,284 +1,31 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var _a, _MetricStrategyFactory_customStrategies, _MetricStrategyFactory_defaultStrategies, _MetricStrategyFactory_generateStrategyKey;
7
- import { Status, Type } from '../stores/operation.js';
8
1
  import { isNil } from 'lodash-es';
9
2
  /**
10
- * Base class for metric calculation strategies with operations
3
+ * Compute a metric value from a queryset store's current rendered PKs.
4
+ *
5
+ * @param {string} metricType - 'count', 'sum', 'min', 'max'
6
+ * @param {QuerysetStore} querysetStore - The queryset store to compute from
7
+ * @param {string|null} field - The field to aggregate (null for count)
8
+ * @param {Function} ModelClass - The model class for instance lookups
9
+ * @returns {number} The computed metric value
11
10
  */
12
- export class MetricCalculationStrategy {
13
- /**
14
- * Calculate metric based on ground truth and operations
15
- */
16
- calculateWithOperations(baseValue, operations, field, ModelClass) {
17
- // Initialize the current value based on the metric type
18
- let currentValue = baseValue === null ? this.getInitialValue() : baseValue;
19
- // Process operations sequentially
20
- for (const op of operations) {
21
- // Skip rejected operations
22
- if (op.status === Status.REJECTED)
23
- continue;
24
- // Process each instance in the operation
25
- op.frozenInstances.forEach((originalData, index) => {
26
- let pk = originalData[ModelClass.primaryKeyField];
27
- // Get the updated data for this instance (for UPDATE operations)
28
- const updatedData = op.instances[index] || null;
29
- // Create operation data object for the reducer
30
- const singleOp = {
31
- originalData, // Pre-operation state
32
- updatedData, // Post-operation state (for updates)
33
- type: op.type,
34
- status: op.status
35
- };
36
- // Apply this single operation using the strategy-specific reducer
37
- currentValue = this.reduceOperation(currentValue, singleOp, field);
38
- });
39
- }
40
- return currentValue;
41
- }
42
- /**
43
- * Get initial value for the metric type
44
- * Override in subclasses if needed
45
- */
46
- getInitialValue() {
47
- return 0; // Default for count and sum
48
- }
49
- /**
50
- * Process a single operation - implement in subclasses
51
- */
52
- reduceOperation(currentValue, operation, field) {
53
- throw new Error('reduceOperation must be implemented by subclass');
54
- }
55
- /**
56
- * Safely get a numeric value from an object
57
- */
58
- safeGetValue(obj, field) {
59
- if (!obj || !field)
11
+ export function computeMetricFromQueryset(metricType, querysetStore, field, ModelClass) {
12
+ const pks = querysetStore.render();
13
+ if (metricType === 'count')
14
+ return pks.length;
15
+ const values = pks.map(pk => {
16
+ const inst = ModelClass.fromPk(pk);
17
+ if (!inst || !field)
60
18
  return 0;
61
- const value = obj[field];
62
- if (isNil(value))
19
+ const val = inst[field];
20
+ if (isNil(val))
63
21
  return 0;
64
- const numValue = parseFloat(value);
65
- return isNaN(numValue) ? 0 : numValue;
66
- }
67
- }
68
- /**
69
- * Count strategy implementation
70
- */
71
- export class CountStrategy extends MetricCalculationStrategy {
72
- reduceOperation(currentCount, operation, field) {
73
- // Skip rejected operations
74
- if (operation.status === Status.REJECTED) {
75
- return currentCount;
76
- }
77
- const { type } = operation;
78
- // Handle operation types
79
- if (type === Type.CREATE) {
80
- return currentCount + 1;
81
- }
82
- else if ([Type.DELETE, Type.DELETE_INSTANCE].includes(type)) {
83
- return Math.max(0, currentCount - 1);
84
- }
85
- // Other operation types don't affect the count
86
- return currentCount;
87
- }
88
- }
89
- /**
90
- * Sum strategy implementation
91
- */
92
- export class SumStrategy extends MetricCalculationStrategy {
93
- reduceOperation(currentSum, operation, field) {
94
- // Skip rejected operations
95
- if (operation.status === Status.REJECTED) {
96
- return currentSum;
97
- }
98
- if (!field) {
99
- throw new Error('SumStrategy requires a field parameter');
100
- }
101
- const { type, originalData, updatedData } = operation;
102
- switch (type) {
103
- case Type.CREATE:
104
- // For CREATE, add the value to the sum
105
- return currentSum + this.safeGetValue(originalData, field);
106
- case Type.CHECKPOINT:
107
- case Type.UPDATE:
108
- // For UPDATE, subtract old value and add new value
109
- if (updatedData) {
110
- const oldValue = this.safeGetValue(originalData, field);
111
- const newValue = this.safeGetValue(updatedData, field);
112
- return currentSum - oldValue + newValue;
113
- }
114
- return currentSum;
115
- case Type.DELETE:
116
- case Type.DELETE_INSTANCE:
117
- // For DELETE, subtract the value from the sum
118
- return currentSum - this.safeGetValue(originalData, field);
119
- default:
120
- return currentSum;
121
- }
122
- }
123
- }
124
- /**
125
- * Min strategy implementation
126
- */
127
- export class MinStrategy extends MetricCalculationStrategy {
128
- getInitialValue() {
129
- return Infinity;
130
- }
131
- reduceOperation(currentMin, operation, field) {
132
- // Skip rejected operations
133
- if (operation.status === Status.REJECTED) {
134
- return currentMin;
135
- }
136
- if (!field) {
137
- throw new Error('MinStrategy requires a field parameter');
138
- }
139
- const { type, originalData, updatedData } = operation;
140
- if (type === Type.CREATE) {
141
- // For CREATE, check if the new value is smaller than current min
142
- const value = this.safeGetValue(originalData, field);
143
- return Math.min(currentMin, value);
144
- }
145
- else if ((type === Type.UPDATE || type.CHECKPOINT) && updatedData) {
146
- // For UPDATE, first check if we're updating the minimum value
147
- const oldValue = this.safeGetValue(originalData, field);
148
- const newValue = this.safeGetValue(updatedData, field);
149
- if (oldValue === currentMin) {
150
- // We're updating the current minimum, need to find the new minimum
151
- if (newValue <= oldValue) {
152
- // Simple case: new value is still the minimum
153
- return newValue;
154
- }
155
- else {
156
- // Harder case: need to recalculate minimum
157
- // For now, conservatively use the new value
158
- return newValue;
159
- }
160
- }
161
- else if (newValue < currentMin) {
162
- // The updated value is a new minimum
163
- return newValue;
164
- }
165
- }
166
- // For other cases, maintain current min
167
- return currentMin;
168
- }
169
- }
170
- /**
171
- * Max strategy implementation
172
- */
173
- export class MaxStrategy extends MetricCalculationStrategy {
174
- getInitialValue() {
175
- return -Infinity;
176
- }
177
- reduceOperation(currentMax, operation, field) {
178
- // Skip rejected operations
179
- if (operation.status === Status.REJECTED) {
180
- return currentMax;
181
- }
182
- if (!field) {
183
- throw new Error('MaxStrategy requires a field parameter');
184
- }
185
- const { type, originalData, updatedData } = operation;
186
- if (type === Type.CREATE) {
187
- // For CREATE, check if the new value is larger than current max
188
- const value = this.safeGetValue(originalData, field);
189
- return Math.max(currentMax, value);
190
- }
191
- else if ((type === Type.UPDATE || type === Type.CHECKPOINT) && updatedData) {
192
- // For UPDATE, first check if we're updating the maximum value
193
- const oldValue = this.safeGetValue(originalData, field);
194
- const newValue = this.safeGetValue(updatedData, field);
195
- if (oldValue === currentMax) {
196
- // We're updating the current maximum, need to find the new maximum
197
- if (newValue >= oldValue) {
198
- // Simple case: new value is still the maximum
199
- return newValue;
200
- }
201
- else {
202
- // Harder case: need to recalculate maximum
203
- // For now, conservatively use the new value
204
- return newValue;
205
- }
206
- }
207
- else if (newValue > currentMax) {
208
- // The updated value is a new maximum
209
- return newValue;
210
- }
211
- }
212
- // For other cases, maintain current max
213
- return currentMax;
214
- }
215
- }
216
- /**
217
- * Factory class for creating the appropriate strategy
218
- */
219
- export class MetricStrategyFactory {
220
- /**
221
- * Clear all custom strategy overrides
222
- */
223
- static clearCustomStrategies() {
224
- __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).clear();
225
- }
226
- /**
227
- * Override a strategy for a specific metric type and model class
228
- * @param {string} metricType - The type of metric (count, sum, min, max)
229
- * @param {Function|null} ModelClass - The model class or null for a generic override
230
- * @param {MetricCalculationStrategy} strategy - The strategy to use
231
- */
232
- static overrideStrategy(metricType, ModelClass, strategy) {
233
- if (!metricType || !strategy) {
234
- throw new Error('overrideStrategy requires metricType and strategy');
235
- }
236
- if (!(strategy instanceof MetricCalculationStrategy)) {
237
- throw new Error('strategy must be an instance of MetricCalculationStrategy');
238
- }
239
- let key;
240
- if (ModelClass) {
241
- // Model-specific override
242
- key = __classPrivateFieldGet(this, _a, "m", _MetricStrategyFactory_generateStrategyKey).call(this, metricType, ModelClass);
243
- }
244
- else {
245
- // Generic override for all models
246
- key = `${metricType}::*::*`;
247
- }
248
- __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).set(key, strategy);
249
- }
250
- /**
251
- * Get the appropriate strategy for a model class and metric type
252
- * @param {string} metricType - The type of metric (count, sum, min, max)
253
- * @param {Function} ModelClass - The model class
254
- * @returns {MetricCalculationStrategy} The appropriate strategy
255
- */
256
- static getStrategy(metricType, ModelClass) {
257
- const normalizedMetricType = metricType.toLowerCase();
258
- // Check for model-specific override first
259
- const specificKey = __classPrivateFieldGet(this, _a, "m", _MetricStrategyFactory_generateStrategyKey).call(this, normalizedMetricType, ModelClass);
260
- if (__classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).has(specificKey)) {
261
- return __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).get(specificKey);
262
- }
263
- // Check for metric-only override (works across all models)
264
- const genericKey = `${normalizedMetricType}::*::*`;
265
- if (__classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).has(genericKey)) {
266
- return __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_customStrategies).get(genericKey);
267
- }
268
- // Otherwise, return the default strategy based on the metric type
269
- const strategyCreator = __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_defaultStrategies).get(normalizedMetricType) || __classPrivateFieldGet(this, _a, "f", _MetricStrategyFactory_defaultStrategies).get('count');
270
- return strategyCreator();
22
+ const num = parseFloat(val);
23
+ return isNaN(num) ? 0 : num;
24
+ });
25
+ switch (metricType) {
26
+ case 'sum': return values.reduce((a, b) => a + b, 0);
27
+ case 'min': return values.length ? Math.min(...values) : 0;
28
+ case 'max': return values.length ? Math.max(...values) : 0;
29
+ default: return pks.length;
271
30
  }
272
31
  }
273
- _a = MetricStrategyFactory, _MetricStrategyFactory_generateStrategyKey = function _MetricStrategyFactory_generateStrategyKey(metricType, ModelClass) {
274
- return `${metricType}::${ModelClass.configKey}::${ModelClass.modelName}`;
275
- };
276
- // Collection of custom strategy overrides
277
- _MetricStrategyFactory_customStrategies = { value: new Map() };
278
- // Default strategy map
279
- _MetricStrategyFactory_defaultStrategies = { value: new Map([
280
- ['count', () => new CountStrategy()],
281
- ['sum', () => new SumStrategy()],
282
- ['min', () => new MinStrategy()],
283
- ['max', () => new MaxStrategy()]
284
- ]) };
@@ -34,8 +34,11 @@ export class LiveMetric {
34
34
  */
35
35
  export class MetricRegistry {
36
36
  _stores: Map<any, any>;
37
+ followedMetrics: Set<any>;
37
38
  syncManager: any;
38
39
  clear(): void;
40
+ followMetric(metricType: any, queryset: any, field?: null): void;
41
+ unfollowMetric(metricType: any, queryset: any, field?: null): void;
39
42
  setSyncManager(syncManager: any): void;
40
43
  /**
41
44
  * Get all metric stores that match a specific queryset
@@ -59,6 +59,7 @@ export class MetricRegistry {
59
59
  constructor() {
60
60
  // Store both the store and a reference to the queryset as a tuple
61
61
  this._stores = new Map();
62
+ this.followedMetrics = new Set();
62
63
  this.syncManager = null;
63
64
  }
64
65
  clear() {
@@ -66,6 +67,15 @@ export class MetricRegistry {
66
67
  this.syncManager.unfollowModel(this, entry.store.modelClass);
67
68
  }
68
69
  this._stores = new Map();
70
+ this.followedMetrics = new Set();
71
+ }
72
+ followMetric(metricType, queryset, field = null) {
73
+ const key = this._makeKey(metricType, queryset, field);
74
+ this.followedMetrics.add(key);
75
+ }
76
+ unfollowMetric(metricType, queryset, field = null) {
77
+ const key = this._makeKey(metricType, queryset, field);
78
+ this.followedMetrics.delete(key);
69
79
  }
70
80
  setSyncManager(syncManager) {
71
81
  this.syncManager = syncManager;
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Store for managing a single metric with optimistic updates
2
+ * Store for managing a single metric with optimistic updates.
3
+ *
4
+ * Uses a delta-based approach: groundTruthValue + sum(deltas) = current value.
5
+ * Deltas are computed externally (by processMetricStores) and stored by operationId.
3
6
  */
4
7
  export class MetricStore {
5
8
  constructor(metricType: any, modelClass: any, queryset: any, field: null | undefined, ast: null | undefined, fetchFn: any);
@@ -11,40 +14,37 @@ export class MetricStore {
11
14
  fetchFn: any;
12
15
  groundTruthValue: any;
13
16
  isSyncing: boolean;
14
- strategy: import("../metrics/metricOptCalcs.js").MetricCalculationStrategy;
15
- operations: any[];
16
- confirmedOps: Set<any>;
17
+ deltas: Map<any, any>;
17
18
  metricCache: Cache;
18
19
  _lastCalculatedValue: any;
19
20
  reset(): void;
20
21
  get cacheKey(): string;
21
22
  /**
22
- * Add an operation to this metric store
23
- * @param {Operation} operation - The operation to add
23
+ * Add a delta for an operation
24
24
  */
25
- addOperation(operation: Operation): void;
25
+ addDelta(opId: any, delta: any): void;
26
26
  /**
27
- * Update an operation in this metric store
28
- * @param {Operation} operation - The operation to update
27
+ * Mark a delta as confirmed
29
28
  */
30
- updateOperation(operation: Operation): void;
29
+ confirmDelta(opId: any): void;
31
30
  /**
32
- * Confirm an operation in this metric store
33
- * @param {Operation} operation - The operation to confirm
31
+ * Remove a rejected delta
34
32
  */
35
- confirm(operation: Operation): void;
33
+ rejectDelta(opId: any): void;
36
34
  /**
37
- * Reject an operation in this metric store
38
- * @param {Operation} operation - The operation to reject
35
+ * Update a delta (e.g., when operation is mutated)
39
36
  */
40
- reject(operation: Operation): void;
37
+ updateDelta(opId: any, delta: any): void;
38
+ addOperation(operation: any): void;
39
+ updateOperation(operation: any): void;
40
+ confirm(operation: any): void;
41
+ reject(operation: any): void;
41
42
  onHydrated(): void;
42
43
  setCache(): void;
43
44
  clearCache(): void;
44
45
  setGroundTruth(value: any): void;
45
46
  /**
46
- * Render the metric with current operations
47
- * @returns {any} Calculated metric value
47
+ * Render: groundTruthValue + sum of all deltas
48
48
  */
49
49
  render(): any;
50
50
  /**