@antelopejs/dms 0.7.9 → 0.7.10

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 (172) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/config.d.ts +2 -0
  3. package/dist/config.js +9 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/implementations/dms/api-base-url.d.ts +1 -0
  6. package/dist/implementations/dms/api-base-url.js +6 -0
  7. package/dist/implementations/dms/api-base-url.js.map +1 -0
  8. package/dist/implementations/dms/frontend-modules.js +7 -6
  9. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  10. package/dist/implementations/dms/page.js +8 -2
  11. package/dist/implementations/dms/page.js.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/test/unit/api-base-url.test.d.ts +1 -0
  15. package/dist/test/unit/api-base-url.test.js +31 -0
  16. package/dist/test/unit/api-base-url.test.js.map +1 -0
  17. package/dist/test/unit/interfaces/dms/page-layout-query.test.d.ts +1 -0
  18. package/dist/test/unit/interfaces/dms/page-layout-query.test.js +106 -0
  19. package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -0
  20. package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -0
  21. package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
  22. package/dist/test/unit/interfaces/dms-base/block-parity-options.test.d.ts +1 -0
  23. package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js +46 -0
  24. package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js.map +1 -0
  25. package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.d.ts +1 -0
  26. package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +26 -0
  27. package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -0
  28. package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +10 -0
  29. package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
  30. package/dist/test/unit/interfaces/dms-base/guard.test.d.ts +1 -0
  31. package/dist/test/unit/interfaces/dms-base/guard.test.js +53 -0
  32. package/dist/test/unit/interfaces/dms-base/guard.test.js.map +1 -0
  33. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +23 -0
  34. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  35. package/dist/test/unit/interfaces/dms-base/page-live.test.js +55 -0
  36. package/dist/test/unit/interfaces/dms-base/page-live.test.js.map +1 -1
  37. package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js +17 -0
  38. package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js.map +1 -1
  39. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -0
  40. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  41. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +28 -0
  42. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
  43. package/frontend-vue/layers/dms-core/app/build/composables/user/preferencesCookie.ts +149 -0
  44. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +321 -0
  45. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +4 -3
  46. package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +4 -3
  47. package/frontend-vue/layers/dms-core/app/composables/period/types.ts +3 -1
  48. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +2 -2
  49. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +9 -2
  50. package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +48 -16
  51. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +21 -4
  52. package/frontend-vue/layers/dms-core/app/composables/user/usePreferences.ts +81 -49
  53. package/frontend-vue/layers/dms-core/app/types/confirm-dialog.ts +35 -0
  54. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +2 -0
  55. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +2 -0
  56. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +10 -1
  57. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +32 -1
  58. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +13 -3
  59. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +27 -3
  60. package/frontend-vue/layers/dms-layout/app/build/composables/layout/rowSidePanel.ts +16 -0
  61. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +75 -0
  62. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +8 -1
  63. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +13 -2
  64. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +7 -0
  65. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +7 -0
  66. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +7 -4
  67. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +59 -0
  68. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +5 -1
  69. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +5 -1
  70. package/frontend-vue/layers/dms-ui/app/build/components/activity-feed/activityFeedDays.ts +2 -0
  71. package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockInlineStatus.vue +67 -0
  72. package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockStatus.vue +60 -20
  73. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +15 -8
  74. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +14 -0
  75. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +29 -4
  76. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +76 -28
  77. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useBlockItems.ts +26 -2
  78. package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useGuard.ts +115 -0
  79. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +1 -1
  80. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +58 -3
  81. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/secondaryTarget.ts +86 -0
  82. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +5 -0
  83. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +6 -0
  84. package/frontend-vue/layers/dms-ui/app/build/composables/live/liveCadence.ts +42 -0
  85. package/frontend-vue/layers/dms-ui/app/build/composables/live/liveHolds.ts +66 -0
  86. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +42 -0
  87. package/frontend-vue/layers/dms-ui/app/build/composables/live/liveStorage.ts +42 -0
  88. package/frontend-vue/layers/dms-ui/app/build/composables/live/missedTally.ts +65 -0
  89. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageLiveController.ts +247 -88
  90. package/frontend-vue/layers/dms-ui/app/build/composables/live/useLivePeriodSpan.ts +33 -0
  91. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +79 -5
  92. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparisonControl.ts +70 -0
  93. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +55 -0
  94. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +41 -5
  95. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +22 -9
  96. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodLocation.ts +13 -6
  97. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +1 -0
  98. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +27 -2
  99. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/clearTableFilters.ts +18 -3
  100. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/usePersistedPagination.ts +19 -26
  101. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +66 -0
  102. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +7 -1
  103. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +65 -11
  104. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTablePreferences.ts +149 -0
  105. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +39 -8
  106. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +1 -0
  107. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +16 -11
  108. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +39 -16
  109. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +29 -0
  110. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +18 -5
  111. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/sortableColumns.ts +17 -0
  112. package/frontend-vue/layers/dms-ui/app/build/utils/fetchNotFound.ts +79 -0
  113. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +46 -1
  114. package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +4 -0
  115. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +40 -2
  116. package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +11 -1
  117. package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +12 -2
  118. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +129 -4
  119. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +6 -0
  120. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +6 -1
  121. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +33 -0
  122. package/frontend-vue/layers/dms-ui/app/components/form/components/ChoiceCards.vue +22 -4
  123. package/frontend-vue/layers/dms-ui/app/components/guard/Guard.vue +74 -0
  124. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -17
  125. package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +15 -0
  126. package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +12 -15
  127. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +104 -16
  128. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +104 -98
  129. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +11 -1
  130. package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +2 -0
  131. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +11 -1
  132. package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +15 -1
  133. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +86 -10
  134. package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewShiftF.ts +20 -0
  135. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +12 -0
  136. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +5 -0
  137. package/frontend-vue/layers/dms-ui/app/composables/table-view/useTableViewEmptyState.ts +55 -0
  138. package/frontend-vue/layers/dms-ui/app/types/live.ts +47 -1
  139. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +13 -4
  140. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +12 -3
  141. package/frontend-vue/tests/activity-feed-fill-height.test.ts +1 -0
  142. package/frontend-vue/tests/activity-feed-head.test.ts +140 -0
  143. package/frontend-vue/tests/block-not-found.test.ts +220 -0
  144. package/frontend-vue/tests/choice-cards-columns.test.ts +60 -0
  145. package/frontend-vue/tests/confirm-secondary-action.test.ts +269 -0
  146. package/frontend-vue/tests/guard.test.ts +242 -0
  147. package/frontend-vue/tests/header-action-page-query.test.ts +286 -0
  148. package/frontend-vue/tests/live-toggle.test.ts +87 -1
  149. package/frontend-vue/tests/page-layout-registration.test.ts +1 -0
  150. package/frontend-vue/tests/page-live-parity.test.ts +367 -0
  151. package/frontend-vue/tests/page-realtime-remount.test.ts +185 -0
  152. package/frontend-vue/tests/period-selector-parity.test.ts +138 -0
  153. package/frontend-vue/tests/period-selector-url.test.ts +84 -6
  154. package/frontend-vue/tests/period-url-query.test.ts +52 -0
  155. package/frontend-vue/tests/preferences-cookie-size.test.ts +311 -0
  156. package/frontend-vue/tests/row-side-panel.test.ts +109 -10
  157. package/frontend-vue/tests/table-actions-search-label.test.ts +57 -1
  158. package/frontend-vue/tests/table-clear-filters.test.ts +99 -1
  159. package/frontend-vue/tests/table-density-menu.test.ts +4 -3
  160. package/frontend-vue/tests/table-live-hold.test.ts +95 -5
  161. package/frontend-vue/tests/table-row-action-labels.test.ts +181 -0
  162. package/frontend-vue/tests/table-source-period-scope.test.ts +31 -3
  163. package/frontend-vue/tests/table-view-hidden-filters-page.test.ts +14 -17
  164. package/frontend-vue/tests/table-view-pinned-rows.test.ts +143 -0
  165. package/frontend-vue/tests/table-view-sortable-columns.test.ts +45 -0
  166. package/frontend-vue/tests/table-view-views.test.ts +56 -15
  167. package/frontend-vue/tests/top-list-card-value-suffix.test.ts +107 -0
  168. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  169. package/package.json +2 -2
  170. package/skills/dms/SKILL.md +9 -3
  171. package/skills/dms-module/SKILL.md +5 -0
  172. package/skills/dms-pages/REFERENCE.md +23 -2
@@ -0,0 +1,42 @@
1
+ import type { PageLiveOptions, PageLivePeriod } from "../../../types/live";
2
+
3
+ const MS_PER_SECOND = 1000;
4
+ const MS_PER_UNIT: Record<string, number> = {
5
+ m: 60 * MS_PER_SECOND,
6
+ h: 60 * 60 * MS_PER_SECOND,
7
+ d: 24 * 60 * 60 * MS_PER_SECOND,
8
+ };
9
+ const PERIOD_PATTERN = /^(\d+)([mhd])$/;
10
+
11
+ /**
12
+ * The length of a `slowRateAfter.period` in ms, `null` when it does not read
13
+ * like `"30m"`, `"24h"` or `"7d"`.
14
+ *
15
+ * @internal
16
+ */
17
+ export function livePeriodMs(period: PageLivePeriod | string): number | null {
18
+ const match = PERIOD_PATTERN.exec(period);
19
+ if (!match) return null;
20
+ const [, amount, unit] = match;
21
+ return Number(amount) * (MS_PER_UNIT[unit!] ?? 0) || null;
22
+ }
23
+
24
+ /**
25
+ * The shortest interval between two reads of one block, in ms, for a page
26
+ * showing a period spanning `periodSpanMs` (`null` without a period): the
27
+ * slower `slowRateAfter` once the span reaches its period, `maxRate` else,
28
+ * 0 for no cap.
29
+ *
30
+ * @internal
31
+ */
32
+ export function liveIntervalMs(
33
+ options: PageLiveOptions,
34
+ periodSpanMs: number | null,
35
+ ): number {
36
+ const slow = options.slowRateAfter;
37
+ const slowFrom = slow ? livePeriodMs(slow.period) : null;
38
+ if (slow && slowFrom !== null && periodSpanMs !== null) {
39
+ if (periodSpanMs >= slowFrom) return slow.maxRate * MS_PER_SECOND;
40
+ }
41
+ return (options.maxRate ?? 0) * MS_PER_SECOND;
42
+ }
@@ -0,0 +1,66 @@
1
+ import { computed, shallowRef, type ComputedRef } from "vue";
2
+ import type { PageLiveNote, PageLiveNoteInput } from "../../../types/live";
3
+
4
+ interface HoldEntry {
5
+ note: PageLiveNote;
6
+ /** The user resumed over this hold: it no longer pauses the page. */
7
+ isLifted: boolean;
8
+ }
9
+
10
+ /**
11
+ * A note given as its text alone, as a full note.
12
+ *
13
+ * @internal
14
+ */
15
+ export function toLiveNote(input: PageLiveNoteInput): PageLiveNote {
16
+ return typeof input === "string" ? { text: input } : input;
17
+ }
18
+
19
+ /**
20
+ * The components holding a live page paused, each with its reason. A hold the
21
+ * user resumed over is lifted: it stays lifted while its owner keeps holding,
22
+ * and pauses again once released and taken anew.
23
+ *
24
+ * @internal
25
+ */
26
+ export class LiveHolds {
27
+ private readonly entries = shallowRef(new Map<symbol, HoldEntry>());
28
+
29
+ /** The reason of the first hold still pausing the page, `null` without one. */
30
+ readonly activeNote: ComputedRef<PageLiveNote | null> = computed(() => {
31
+ for (const entry of this.entries.value.values()) {
32
+ if (!entry.isLifted) return entry.note;
33
+ }
34
+ return null;
35
+ });
36
+
37
+ get isActive(): boolean {
38
+ return this.activeNote.value !== null;
39
+ }
40
+
41
+ set(owner: symbol, note: PageLiveNoteInput): void {
42
+ const isLifted = this.entries.value.get(owner)?.isLifted ?? false;
43
+ this.replace((entries) =>
44
+ entries.set(owner, { note: toLiveNote(note), isLifted }),
45
+ );
46
+ }
47
+
48
+ release(owner: symbol): void {
49
+ if (!this.entries.value.has(owner)) return;
50
+ this.replace((entries) => entries.delete(owner));
51
+ }
52
+
53
+ liftAll(): void {
54
+ this.replace((entries) => {
55
+ for (const entry of entries.values()) entry.isLifted = true;
56
+ });
57
+ }
58
+
59
+ private replace(change: (entries: Map<symbol, HoldEntry>) => void): void {
60
+ const next = new Map(
61
+ [...this.entries.value].map(([owner, entry]) => [owner, { ...entry }]),
62
+ );
63
+ change(next);
64
+ this.entries.value = next;
65
+ }
66
+ }
@@ -0,0 +1,42 @@
1
+ import { readonly, ref, type Ref } from "vue";
2
+
3
+ /**
4
+ * Reads the page's blocks on a clock while the realtime stream is down: the
5
+ * fallback of a live page with a cadence. Each tick asks the interval again,
6
+ * so a period changed meanwhile applies from the next one.
7
+ *
8
+ * @internal
9
+ */
10
+ export class LivePoller {
11
+ private readonly active = ref(false);
12
+ private timer: ReturnType<typeof setTimeout> | null = null;
13
+ /** The poller is running: the stream is down and the page reads by itself. */
14
+ readonly isPolling: Readonly<Ref<boolean>> = readonly(this.active);
15
+
16
+ constructor(
17
+ private readonly intervalMs: () => number,
18
+ private readonly poll: () => void,
19
+ ) {}
20
+
21
+ /** Runs while `shouldRun` holds and the page has a cadence; stops else. */
22
+ update(shouldRun: boolean): void {
23
+ const isRunning = shouldRun && this.intervalMs() > 0;
24
+ if (isRunning === this.active.value) return;
25
+ this.active.value = isRunning;
26
+ if (isRunning) this.schedule();
27
+ else this.stop();
28
+ }
29
+
30
+ stop(): void {
31
+ if (this.timer) clearTimeout(this.timer);
32
+ this.timer = null;
33
+ this.active.value = false;
34
+ }
35
+
36
+ private schedule(): void {
37
+ this.timer = setTimeout(() => {
38
+ this.poll();
39
+ if (this.active.value) this.schedule();
40
+ }, this.intervalMs());
41
+ }
42
+ }
@@ -0,0 +1,42 @@
1
+ const STORAGE_KEY_PREFIX = "dms.live.paused:";
2
+ const REMEMBER_KEY_PREFIX = "key:";
3
+ const PAUSED_VALUE = "1";
4
+
5
+ /** Browser storage of the paused pages; absent on the server or when denied. */
6
+ function liveStorage(): Storage | undefined {
7
+ try {
8
+ return typeof window === "undefined" ? undefined : window.localStorage;
9
+ } catch {
10
+ return undefined;
11
+ }
12
+ }
13
+
14
+ /**
15
+ * The storage key of a page's Live / Pause choice: its `live.rememberKey` when it
16
+ * shares one, its own id else.
17
+ *
18
+ * @internal
19
+ */
20
+ export function liveStorageKey(pageId: string, rememberKey?: string): string {
21
+ return (
22
+ STORAGE_KEY_PREFIX +
23
+ (rememberKey ? REMEMBER_KEY_PREFIX + rememberKey : pageId)
24
+ );
25
+ }
26
+
27
+ /** @internal */
28
+ export function readStoredPause(key: string): boolean {
29
+ return liveStorage()?.getItem(key) === PAUSED_VALUE;
30
+ }
31
+
32
+ /** @internal */
33
+ export function storePause(key: string, isPaused: boolean): void {
34
+ const storage = liveStorage();
35
+ if (!storage) return;
36
+ try {
37
+ if (isPaused) storage.setItem(key, PAUSED_VALUE);
38
+ else storage.removeItem(key);
39
+ } catch {
40
+ // A full or locked storage only loses the memory of the choice.
41
+ }
42
+ }
@@ -0,0 +1,65 @@
1
+ import { readonly, ref, type Ref } from "vue";
2
+
3
+ interface PublishedEvent {
4
+ topic?: unknown;
5
+ payload?: { ids?: unknown; count?: unknown };
6
+ }
7
+
8
+ function rowKeysOf(event: PublishedEvent): string[] {
9
+ const ids = event.payload?.ids;
10
+ if (!Array.isArray(ids)) return [];
11
+ const topic = typeof event.topic === "string" ? event.topic : "";
12
+ return ids
13
+ .filter((id) => typeof id === "string" || typeof id === "number")
14
+ .map((id) => JSON.stringify([topic, id]));
15
+ }
16
+
17
+ function batchCountOf(event: PublishedEvent): number | null {
18
+ const count = event.payload?.count;
19
+ return typeof count === "number" && Number.isInteger(count) && count > 0
20
+ ? count
21
+ : null;
22
+ }
23
+
24
+ /**
25
+ * What the events received since the pause stand for, published rows rather
26
+ * than messages: a message naming rows in `payload.ids` counts each row once
27
+ * (a table view's row events, a bulk write), a batch announcing
28
+ * `payload.count` counts that many (a module publishing its requests every
29
+ * second or so), anything else counts one. The same event object reaching
30
+ * the tally twice (one event, several blocks) counts once.
31
+ *
32
+ * @internal
33
+ */
34
+ export class MissedTally {
35
+ private readonly count = ref(0);
36
+ private events = new WeakSet<object>();
37
+ private rows = new Set<string>();
38
+ /** The rows and events counted since the last reset. */
39
+ readonly total: Readonly<Ref<number>> = readonly(this.count);
40
+
41
+ add(event: object | undefined): void {
42
+ if (event) {
43
+ if (this.events.has(event)) return;
44
+ this.events.add(event);
45
+ }
46
+ this.count.value += this.weightOf(event as PublishedEvent | undefined);
47
+ }
48
+
49
+ reset(): void {
50
+ this.count.value = 0;
51
+ this.events = new WeakSet();
52
+ this.rows = new Set();
53
+ }
54
+
55
+ private weightOf(event: PublishedEvent | undefined): number {
56
+ if (!event) return 1;
57
+ const rowKeys = rowKeysOf(event);
58
+ if (rowKeys.length > 0) {
59
+ const fresh = rowKeys.filter((key) => !this.rows.has(key));
60
+ fresh.forEach((key) => this.rows.add(key));
61
+ return fresh.length;
62
+ }
63
+ return batchCountOf(event) ?? 1;
64
+ }
65
+ }