@buoy-gg/shared-ui 7.0.33 → 7.0.34
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/lib/commonjs/JsModal.js +57 -22
- package/lib/commonjs/hooks/index.js +26 -0
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/hooks/useChangeLog.js +55 -0
- package/lib/commonjs/hooks/useEventStoreEvents.js +65 -0
- package/lib/commonjs/hooks/usePersistedState.js +45 -10
- package/lib/commonjs/index.js +143 -0
- package/lib/commonjs/storage/definePersistedSetting.js +67 -0
- package/lib/commonjs/storage/index.js +8 -1
- package/lib/commonjs/stores/ChangeLog.js +197 -0
- package/lib/commonjs/stores/createToolStore.js +175 -0
- package/lib/commonjs/stores/index.js +15 -1
- package/lib/commonjs/sync/toolSyncAdapter.js +63 -0
- package/lib/commonjs/sync/wireForm.js +172 -0
- package/lib/commonjs/tools/defineTool.js +79 -0
- package/lib/commonjs/ui/components/ChangeRow.js +110 -0
- package/lib/commonjs/ui/components/ChipStrip.js +94 -0
- package/lib/commonjs/ui/components/DetailViewToggle.js +130 -0
- package/lib/commonjs/ui/components/DevToolsCard.js +3 -1
- package/lib/commonjs/ui/components/EntityFilterView.js +354 -0
- package/lib/commonjs/ui/components/ExportPreview.js +174 -0
- package/lib/commonjs/ui/components/InfoRow.js +90 -0
- package/lib/commonjs/ui/components/ModalHeader.js +40 -7
- package/lib/commonjs/ui/components/Pill.js +114 -0
- package/lib/commonjs/ui/components/ToggleRow.js +116 -0
- package/lib/commonjs/ui/components/ToolFooter.js +204 -0
- package/lib/commonjs/ui/components/index.js +100 -1
- package/lib/commonjs/ui/index.js +24 -2
- package/lib/commonjs/ui/listPresets.js +43 -0
- package/lib/commonjs/ui/night/primitives.js +3 -1
- package/lib/commonjs/utils/snapshotProviderKit.js +167 -0
- package/lib/commonjs/utils/stateTools.js +160 -0
- package/lib/module/JsModal.js +57 -22
- package/lib/module/hooks/index.js +3 -1
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/hooks/useChangeLog.js +50 -0
- package/lib/module/hooks/useEventStoreEvents.js +61 -0
- package/lib/module/hooks/usePersistedState.js +44 -10
- package/lib/module/index.js +11 -1
- package/lib/module/storage/definePersistedSetting.js +63 -0
- package/lib/module/storage/index.js +2 -1
- package/lib/module/stores/ChangeLog.js +192 -0
- package/lib/module/stores/createToolStore.js +171 -0
- package/lib/module/stores/index.js +3 -1
- package/lib/module/sync/toolSyncAdapter.js +58 -0
- package/lib/module/sync/wireForm.js +159 -0
- package/lib/module/tools/defineTool.js +74 -0
- package/lib/module/ui/components/ChangeRow.js +105 -0
- package/lib/module/ui/components/ChipStrip.js +91 -0
- package/lib/module/ui/components/DetailViewToggle.js +125 -0
- package/lib/module/ui/components/DevToolsCard.js +3 -1
- package/lib/module/ui/components/EntityFilterView.js +350 -0
- package/lib/module/ui/components/ExportPreview.js +169 -0
- package/lib/module/ui/components/InfoRow.js +86 -0
- package/lib/module/ui/components/ModalHeader.js +40 -7
- package/lib/module/ui/components/Pill.js +110 -0
- package/lib/module/ui/components/ToggleRow.js +112 -0
- package/lib/module/ui/components/ToolFooter.js +196 -0
- package/lib/module/ui/components/index.js +10 -1
- package/lib/module/ui/index.js +4 -1
- package/lib/module/ui/listPresets.js +39 -0
- package/lib/module/ui/night/primitives.js +3 -1
- package/lib/module/utils/snapshotProviderKit.js +156 -0
- package/lib/module/utils/stateTools.js +154 -0
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/index.d.ts +3 -1
- package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/hooks/useChangeLog.d.ts +38 -0
- package/lib/typescript/commonjs/hooks/useChangeLog.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useEventStoreEvents.d.ts +25 -0
- package/lib/typescript/commonjs/hooks/useEventStoreEvents.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/usePersistedState.d.ts +34 -5
- package/lib/typescript/commonjs/hooks/usePersistedState.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +6 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage/definePersistedSetting.d.ts +39 -0
- package/lib/typescript/commonjs/storage/definePersistedSetting.d.ts.map +1 -0
- package/lib/typescript/commonjs/storage/index.d.ts +1 -0
- package/lib/typescript/commonjs/storage/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/ChangeLog.d.ts +100 -0
- package/lib/typescript/commonjs/stores/ChangeLog.d.ts.map +1 -0
- package/lib/typescript/commonjs/stores/createToolStore.d.ts +92 -0
- package/lib/typescript/commonjs/stores/createToolStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/stores/index.d.ts +2 -0
- package/lib/typescript/commonjs/stores/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/sync/toolSyncAdapter.d.ts +66 -0
- package/lib/typescript/commonjs/sync/toolSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/commonjs/sync/wireForm.d.ts +100 -0
- package/lib/typescript/commonjs/sync/wireForm.d.ts.map +1 -0
- package/lib/typescript/commonjs/tools/defineTool.d.ts +100 -0
- package/lib/typescript/commonjs/tools/defineTool.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ChangeRow.d.ts +52 -0
- package/lib/typescript/commonjs/ui/components/ChangeRow.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ChipStrip.d.ts +41 -0
- package/lib/typescript/commonjs/ui/components/ChipStrip.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/DetailViewToggle.d.ts +36 -0
- package/lib/typescript/commonjs/ui/components/DetailViewToggle.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts +8 -1
- package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/EntityFilterView.d.ts +51 -0
- package/lib/typescript/commonjs/ui/components/EntityFilterView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ExportPreview.d.ts +48 -0
- package/lib/typescript/commonjs/ui/components/ExportPreview.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/InfoRow.d.ts +33 -0
- package/lib/typescript/commonjs/ui/components/InfoRow.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts +31 -1
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Pill.d.ts +38 -0
- package/lib/typescript/commonjs/ui/components/Pill.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ToggleRow.d.ts +33 -0
- package/lib/typescript/commonjs/ui/components/ToggleRow.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ToolFooter.d.ts +69 -0
- package/lib/typescript/commonjs/ui/components/ToolFooter.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/index.d.ts +9 -0
- package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/listPresets.d.ts +28 -0
- package/lib/typescript/commonjs/ui/listPresets.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/night/primitives.d.ts +3 -1
- package/lib/typescript/commonjs/ui/night/primitives.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/snapshotProviderKit.d.ts +81 -0
- package/lib/typescript/commonjs/utils/snapshotProviderKit.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/stateTools.d.ts +44 -0
- package/lib/typescript/commonjs/utils/stateTools.d.ts.map +1 -0
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/hooks/index.d.ts +3 -1
- package/lib/typescript/module/hooks/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/hooks/useChangeLog.d.ts +38 -0
- package/lib/typescript/module/hooks/useChangeLog.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useEventStoreEvents.d.ts +25 -0
- package/lib/typescript/module/hooks/useEventStoreEvents.d.ts.map +1 -0
- package/lib/typescript/module/hooks/usePersistedState.d.ts +34 -5
- package/lib/typescript/module/hooks/usePersistedState.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +6 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/storage/definePersistedSetting.d.ts +39 -0
- package/lib/typescript/module/storage/definePersistedSetting.d.ts.map +1 -0
- package/lib/typescript/module/storage/index.d.ts +1 -0
- package/lib/typescript/module/storage/index.d.ts.map +1 -1
- package/lib/typescript/module/stores/ChangeLog.d.ts +100 -0
- package/lib/typescript/module/stores/ChangeLog.d.ts.map +1 -0
- package/lib/typescript/module/stores/createToolStore.d.ts +92 -0
- package/lib/typescript/module/stores/createToolStore.d.ts.map +1 -0
- package/lib/typescript/module/stores/index.d.ts +2 -0
- package/lib/typescript/module/stores/index.d.ts.map +1 -1
- package/lib/typescript/module/sync/toolSyncAdapter.d.ts +66 -0
- package/lib/typescript/module/sync/toolSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/module/sync/wireForm.d.ts +100 -0
- package/lib/typescript/module/sync/wireForm.d.ts.map +1 -0
- package/lib/typescript/module/tools/defineTool.d.ts +100 -0
- package/lib/typescript/module/tools/defineTool.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ChangeRow.d.ts +52 -0
- package/lib/typescript/module/ui/components/ChangeRow.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ChipStrip.d.ts +41 -0
- package/lib/typescript/module/ui/components/ChipStrip.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/DetailViewToggle.d.ts +36 -0
- package/lib/typescript/module/ui/components/DetailViewToggle.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/DevToolsCard.d.ts +8 -1
- package/lib/typescript/module/ui/components/DevToolsCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/EntityFilterView.d.ts +51 -0
- package/lib/typescript/module/ui/components/EntityFilterView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ExportPreview.d.ts +48 -0
- package/lib/typescript/module/ui/components/ExportPreview.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/InfoRow.d.ts +33 -0
- package/lib/typescript/module/ui/components/InfoRow.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ModalHeader.d.ts +31 -1
- package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Pill.d.ts +38 -0
- package/lib/typescript/module/ui/components/Pill.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ToggleRow.d.ts +33 -0
- package/lib/typescript/module/ui/components/ToggleRow.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ToolFooter.d.ts +69 -0
- package/lib/typescript/module/ui/components/ToolFooter.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/index.d.ts +9 -0
- package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +1 -0
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/listPresets.d.ts +28 -0
- package/lib/typescript/module/ui/listPresets.d.ts.map +1 -0
- package/lib/typescript/module/ui/night/primitives.d.ts +3 -1
- package/lib/typescript/module/ui/night/primitives.d.ts.map +1 -1
- package/lib/typescript/module/utils/snapshotProviderKit.d.ts +81 -0
- package/lib/typescript/module/utils/snapshotProviderKit.d.ts.map +1 -0
- package/lib/typescript/module/utils/stateTools.d.ts +44 -0
- package/lib/typescript/module/utils/stateTools.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
Object.defineProperty(exports, "definePersistedSetting", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _definePersistedSetting.definePersistedSetting;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
6
12
|
Object.defineProperty(exports, "devToolsStorageKeys", {
|
|
7
13
|
enumerable: true,
|
|
8
14
|
get: function () {
|
|
@@ -15,4 +21,5 @@ Object.defineProperty(exports, "isDevToolsStorageKey", {
|
|
|
15
21
|
return _devToolsStorageKeys.isDevToolsStorageKey;
|
|
16
22
|
}
|
|
17
23
|
});
|
|
18
|
-
var _devToolsStorageKeys = require("./devToolsStorageKeys.js");
|
|
24
|
+
var _devToolsStorageKeys = require("./devToolsStorageKeys.js");
|
|
25
|
+
var _definePersistedSetting = require("./definePersistedSetting.js");
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ChangeLog = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* ChangeLog — the class-shaped event log the state tools share.
|
|
9
|
+
*
|
|
10
|
+
* redux (`ReduxActionStore`), zustand (`ZustandStateStore`), jotai
|
|
11
|
+
* (`JotaiStateStore`) and react-query (`ReactQueryEventStore`) each carried
|
|
12
|
+
* the same ~120 lines: a newest-first ring buffer, a `Set` of array listeners
|
|
13
|
+
* (`(items) => void`), a `Set` of clear listeners for the desktop's mirror
|
|
14
|
+
* mode, an `enabled` flag (the UI's pause button), a `captureSuppressed` flag
|
|
15
|
+
* (the dashboard must never record its own traffic), a monotonic id counter,
|
|
16
|
+
* `getById`, and hydrate-by-id for lazily fetched detail payloads.
|
|
17
|
+
*
|
|
18
|
+
* This is that core. Tool stores `extend` it and keep their public method
|
|
19
|
+
* names (`addStateChange`, `getActions`, `clearAtomChanges`, …) as thin
|
|
20
|
+
* wrappers, so no call site — including Buoy Desktop — has to change.
|
|
21
|
+
*
|
|
22
|
+
* Semantics preserved from the originals, deliberately:
|
|
23
|
+
* - `getAll()` returns a COPY (`[...items]`) — consumers seed React state
|
|
24
|
+
* from it and rely on a fresh identity per notification.
|
|
25
|
+
* - `clear()` always notifies, even when already empty, then fires the clear
|
|
26
|
+
* listeners (the dashboard forwards the clear to the device either way).
|
|
27
|
+
* - `replaceAll()` respects `enabled`: the pause button freezes a mirror.
|
|
28
|
+
* - `disableCapture()` is permanent for the store's lifetime.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
class ChangeLog {
|
|
32
|
+
/** Newest first. Subclasses may read; mutate only through the helpers. */
|
|
33
|
+
items = [];
|
|
34
|
+
itemListeners = new Set();
|
|
35
|
+
clearListeners = new Set();
|
|
36
|
+
idCounter = 0;
|
|
37
|
+
enabled = true;
|
|
38
|
+
captureSuppressed = false;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.max = options.max ?? 200;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---- Recording ----
|
|
44
|
+
|
|
45
|
+
/** True when a new item should be recorded (not paused, not a mirror). */
|
|
46
|
+
canRecord() {
|
|
47
|
+
return this.enabled && !this.captureSuppressed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `${Date.now()}-${n}` — unique per store instance, sorts by time. */
|
|
51
|
+
nextId() {
|
|
52
|
+
return `${Date.now()}-${++this.idCounter}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prepend one item and trim to `max`. Does NOT notify — subclasses often
|
|
57
|
+
* touch companion state (per-entity counters, raw-state eviction) before
|
|
58
|
+
* they notify. Call `notifyListeners()` when done, or use `record()`.
|
|
59
|
+
*/
|
|
60
|
+
push(item) {
|
|
61
|
+
this.items = [item, ...this.items].slice(0, this.max);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `push()` + notify. The common case. */
|
|
65
|
+
record(item) {
|
|
66
|
+
this.push(item);
|
|
67
|
+
this.notifyListeners();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Replace the buffer wholesale (already merged/hydrated by the caller) and notify. */
|
|
71
|
+
setItems(next) {
|
|
72
|
+
this.items = next.slice(0, this.max);
|
|
73
|
+
this.notifyListeners();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---- Reading ----
|
|
77
|
+
|
|
78
|
+
/** All items, newest first. Fresh array each call. */
|
|
79
|
+
getAll() {
|
|
80
|
+
return [...this.items];
|
|
81
|
+
}
|
|
82
|
+
getById(id) {
|
|
83
|
+
return this.items.find(item => item.id === id);
|
|
84
|
+
}
|
|
85
|
+
get size() {
|
|
86
|
+
return this.items.length;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---- Clearing ----
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Empty the buffer, notify, then fire clear listeners. Always notifies —
|
|
93
|
+
* a clear is a user action and the list must repaint even if nothing was
|
|
94
|
+
* in it. Subclasses that keep companion counters reset them first.
|
|
95
|
+
*/
|
|
96
|
+
clear() {
|
|
97
|
+
this.items = [];
|
|
98
|
+
this.notifyListeners();
|
|
99
|
+
this.clearListeners.forEach(listener => {
|
|
100
|
+
try {
|
|
101
|
+
listener();
|
|
102
|
+
} catch {
|
|
103
|
+
// Ignore listener errors
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Listen for `clear()` calls. Used in remote mirror mode to forward a
|
|
110
|
+
* clear performed in the dashboard UI to the synced device.
|
|
111
|
+
*/
|
|
112
|
+
onClear(listener) {
|
|
113
|
+
this.clearListeners.add(listener);
|
|
114
|
+
return () => {
|
|
115
|
+
this.clearListeners.delete(listener);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- Remote mirror mode ----
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Permanently suppress local capture. Use when this store acts as a mirror
|
|
123
|
+
* of a remote device (the desktop dashboard): data arrives via
|
|
124
|
+
* `replaceAll()` / a subclass snapshot method and local instrumentation
|
|
125
|
+
* must never record.
|
|
126
|
+
*/
|
|
127
|
+
disableCapture() {
|
|
128
|
+
this.captureSuppressed = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Whether the store is in remote mirror mode (capture suppressed). */
|
|
132
|
+
isCaptureSuppressed() {
|
|
133
|
+
return this.captureSuppressed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Replace the whole list from a remote snapshot and notify. Respects the
|
|
138
|
+
* enabled flag so the UI's pause button freezes the mirror. Returns false
|
|
139
|
+
* when skipped. Subclasses with hydration-preserving merges compute the
|
|
140
|
+
* merged list themselves and call `setItems()`.
|
|
141
|
+
*/
|
|
142
|
+
replaceAll(next) {
|
|
143
|
+
if (!this.enabled) return false;
|
|
144
|
+
this.setItems(next);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Patch one item in place (by id) and notify. Used to hydrate a mirrored
|
|
150
|
+
* item with detail payloads fetched on demand from the device.
|
|
151
|
+
*/
|
|
152
|
+
hydrateById(id, patch) {
|
|
153
|
+
const index = this.items.findIndex(item => item.id === id);
|
|
154
|
+
if (index === -1) return;
|
|
155
|
+
this.items = [...this.items.slice(0, index), {
|
|
156
|
+
...this.items[index],
|
|
157
|
+
...patch
|
|
158
|
+
}, ...this.items.slice(index + 1)];
|
|
159
|
+
this.notifyListeners();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- Enable / disable ----
|
|
163
|
+
|
|
164
|
+
setEnabled(enabled) {
|
|
165
|
+
this.enabled = enabled;
|
|
166
|
+
}
|
|
167
|
+
getEnabled() {
|
|
168
|
+
return this.enabled;
|
|
169
|
+
}
|
|
170
|
+
setMax(max) {
|
|
171
|
+
this.max = max;
|
|
172
|
+
if (this.items.length > max) {
|
|
173
|
+
this.items = this.items.slice(0, max);
|
|
174
|
+
this.notifyListeners();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
getMax() {
|
|
178
|
+
return this.max;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---- Subscriptions ----
|
|
182
|
+
|
|
183
|
+
subscribe(listener) {
|
|
184
|
+
this.itemListeners.add(listener);
|
|
185
|
+
return () => {
|
|
186
|
+
this.itemListeners.delete(listener);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
notifyListeners() {
|
|
190
|
+
const items = this.getAll();
|
|
191
|
+
this.itemListeners.forEach(listener => listener(items));
|
|
192
|
+
}
|
|
193
|
+
get listenerCount() {
|
|
194
|
+
return this.itemListeners.size;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
exports.ChangeLog = ChangeLog;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createToolStore = createToolStore;
|
|
7
|
+
/**
|
|
8
|
+
* createToolStore — the one bounded, subscribable buffer every tool used to
|
|
9
|
+
* hand-roll.
|
|
10
|
+
*
|
|
11
|
+
* Before this existed, console, sentry, images, assets, focus-inspector,
|
|
12
|
+
* tv-remote, redux, zustand, jotai and react-query each carried their own copy
|
|
13
|
+
* of the same ~100 lines: a `Set` of listeners, a `Set` of clear listeners
|
|
14
|
+
* (so the desktop dashboard can forward a clear to the device in mirror
|
|
15
|
+
* mode), a ring buffer with a max, an optional frame-coalesced emit, and the
|
|
16
|
+
* `__buoyFlushToolSync` escape hatch that pushes a tool's snapshot to the
|
|
17
|
+
* dashboard NOW (a crash's last words must not wait on the sync throttle).
|
|
18
|
+
*
|
|
19
|
+
* This is a plain function, not a class, on purpose: every consumer exposes a
|
|
20
|
+
* module-level functional API (`subscribe`, `getSnapshot`, `clearX`) and
|
|
21
|
+
* feeds `useSyncExternalStore`, so the store is a value the module wraps
|
|
22
|
+
* rather than something it extends. `BaseEventStore` stays for the
|
|
23
|
+
* class-based, auto-start/stop capture stores (network, storage, routes).
|
|
24
|
+
*
|
|
25
|
+
* Contract:
|
|
26
|
+
* - `getSnapshot()` returns the SAME array reference until the next
|
|
27
|
+
* mutation, so `useSyncExternalStore` never re-renders for nothing.
|
|
28
|
+
* - `order: "append"` keeps the newest item LAST (console-style scrollback);
|
|
29
|
+
* `order: "prepend"` keeps it FIRST (event-log style). Trimming drops from
|
|
30
|
+
* the opposite end. Consumers never have to `.reverse()`.
|
|
31
|
+
* - `clear()` fires `onClear` listeners even when the buffer is already
|
|
32
|
+
* empty — the dashboard's forwarded clear must reach the device regardless.
|
|
33
|
+
* - `set()` (mirror mode) never fires clear listeners; it is a replacement.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Listener signature: change-only, read the snapshot yourself. */
|
|
37
|
+
|
|
38
|
+
function scheduleFrame(cb) {
|
|
39
|
+
if (typeof requestAnimationFrame === "function") {
|
|
40
|
+
requestAnimationFrame(() => cb());
|
|
41
|
+
} else {
|
|
42
|
+
setTimeout(cb, 0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function createToolStore(options) {
|
|
46
|
+
const id = options.id;
|
|
47
|
+
const order = options.order ?? "append";
|
|
48
|
+
const coalesce = options.coalesce ?? "sync";
|
|
49
|
+
const flushMinIntervalMs = options.flushMinIntervalMs ?? 150;
|
|
50
|
+
let max = options.max;
|
|
51
|
+
let items = [];
|
|
52
|
+
let paused = false;
|
|
53
|
+
let nextId = options.firstId ?? 1;
|
|
54
|
+
let lastFlushAt = 0;
|
|
55
|
+
let emitScheduled = false;
|
|
56
|
+
const listeners = new Set();
|
|
57
|
+
const clearListeners = new Set();
|
|
58
|
+
function fire() {
|
|
59
|
+
for (const listener of listeners) {
|
|
60
|
+
try {
|
|
61
|
+
listener();
|
|
62
|
+
} catch {
|
|
63
|
+
// A broken subscriber must not take the store (or a crash path) down.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function emit() {
|
|
68
|
+
if (coalesce === "sync") {
|
|
69
|
+
fire();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (emitScheduled) return;
|
|
73
|
+
emitScheduled = true;
|
|
74
|
+
scheduleFrame(() => {
|
|
75
|
+
emitScheduled = false;
|
|
76
|
+
fire();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function trim(list) {
|
|
80
|
+
if (list.length <= max) return list;
|
|
81
|
+
return order === "append" ? list.slice(list.length - max) : list.slice(0, max);
|
|
82
|
+
}
|
|
83
|
+
const store = {
|
|
84
|
+
id,
|
|
85
|
+
subscribe(listener) {
|
|
86
|
+
listeners.add(listener);
|
|
87
|
+
return () => {
|
|
88
|
+
listeners.delete(listener);
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
getSnapshot() {
|
|
92
|
+
return items;
|
|
93
|
+
},
|
|
94
|
+
size() {
|
|
95
|
+
return items.length;
|
|
96
|
+
},
|
|
97
|
+
add(item) {
|
|
98
|
+
if (paused) return;
|
|
99
|
+
if (order === "append") {
|
|
100
|
+
items = items.length >= max ? [...items.slice(items.length - max + 1), item] : [...items, item];
|
|
101
|
+
} else {
|
|
102
|
+
items = items.length >= max ? [item, ...items.slice(0, max - 1)] : [item, ...items];
|
|
103
|
+
}
|
|
104
|
+
emit();
|
|
105
|
+
},
|
|
106
|
+
set(next) {
|
|
107
|
+
items = trim(next);
|
|
108
|
+
emit();
|
|
109
|
+
},
|
|
110
|
+
update(fn) {
|
|
111
|
+
items = trim(fn(items));
|
|
112
|
+
emit();
|
|
113
|
+
},
|
|
114
|
+
clear() {
|
|
115
|
+
for (const listener of clearListeners) {
|
|
116
|
+
try {
|
|
117
|
+
listener();
|
|
118
|
+
} catch {
|
|
119
|
+
// ignore
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (items.length === 0) return;
|
|
123
|
+
items = [];
|
|
124
|
+
emit();
|
|
125
|
+
},
|
|
126
|
+
onClear(listener) {
|
|
127
|
+
clearListeners.add(listener);
|
|
128
|
+
return () => {
|
|
129
|
+
clearListeners.delete(listener);
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
notify() {
|
|
133
|
+
emit();
|
|
134
|
+
},
|
|
135
|
+
flushSync(force = false) {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
if (!force && now - lastFlushAt < flushMinIntervalMs) return;
|
|
138
|
+
lastFlushAt = now;
|
|
139
|
+
try {
|
|
140
|
+
const set = globalThis.__buoyFlushToolSync;
|
|
141
|
+
if (!set) return;
|
|
142
|
+
for (const flush of set) flush(id);
|
|
143
|
+
} catch {
|
|
144
|
+
// Best-effort: a crash path must never crash.
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
setPaused(next) {
|
|
148
|
+
paused = next;
|
|
149
|
+
},
|
|
150
|
+
isPaused() {
|
|
151
|
+
return paused;
|
|
152
|
+
},
|
|
153
|
+
setMax(next) {
|
|
154
|
+
max = next;
|
|
155
|
+
const trimmed = trim(items);
|
|
156
|
+
if (trimmed !== items) {
|
|
157
|
+
items = trimmed;
|
|
158
|
+
emit();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
getMax() {
|
|
162
|
+
return max;
|
|
163
|
+
},
|
|
164
|
+
allocId() {
|
|
165
|
+
return nextId++;
|
|
166
|
+
},
|
|
167
|
+
bumpIdAbove(idFloor) {
|
|
168
|
+
if (idFloor + 1 > nextId) nextId = idFloor + 1;
|
|
169
|
+
},
|
|
170
|
+
listenerCount() {
|
|
171
|
+
return listeners.size;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
return store;
|
|
175
|
+
}
|
|
@@ -9,6 +9,18 @@ Object.defineProperty(exports, "BaseEventStore", {
|
|
|
9
9
|
return _BaseEventStore.BaseEventStore;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
+
Object.defineProperty(exports, "ChangeLog", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _ChangeLog.ChangeLog;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "createToolStore", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _createToolStore.createToolStore;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
12
24
|
Object.defineProperty(exports, "ignoredPatternsStore", {
|
|
13
25
|
enumerable: true,
|
|
14
26
|
get: function () {
|
|
@@ -34,4 +46,6 @@ Object.defineProperty(exports, "useIgnoredPatterns", {
|
|
|
34
46
|
}
|
|
35
47
|
});
|
|
36
48
|
var _BaseEventStore = require("./BaseEventStore.js");
|
|
37
|
-
var _ignoredPatternsStore = require("./ignoredPatternsStore");
|
|
49
|
+
var _ignoredPatternsStore = require("./ignoredPatternsStore");
|
|
50
|
+
var _createToolStore = require("./createToolStore.js");
|
|
51
|
+
var _ChangeLog = require("./ChangeLog.js");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createSyncAdapter = createSyncAdapter;
|
|
7
|
+
exports.mergeChangeSources = mergeChangeSources;
|
|
8
|
+
/**
|
|
9
|
+
* ToolSyncAdapter — the contract a tool implements to sync to Buoy Desktop /
|
|
10
|
+
* the MCP server, plus `createSyncAdapter`, the one way to build one.
|
|
11
|
+
*
|
|
12
|
+
* Every tool package used to declare its adapter as a bare object literal
|
|
13
|
+
* with the comment "structurally matches external-sync's ToolSyncAdapter so
|
|
14
|
+
* this package doesn't need a dependency on it". That kept the packages
|
|
15
|
+
* decoupled but left the contract unchecked: storage shipped `version: 3`
|
|
16
|
+
* while every sibling was on 2 and nothing noticed. The type now lives here,
|
|
17
|
+
* in shared (which every tool already depends on), and external-sync
|
|
18
|
+
* re-exports it — one definition, checked everywhere.
|
|
19
|
+
*
|
|
20
|
+
* `createSyncAdapter` is deliberately thin. It:
|
|
21
|
+
* - type-checks the literal against the contract (a wrong `subscribe`
|
|
22
|
+
* signature or a non-function action fails at compile time),
|
|
23
|
+
* - keeps the CONCRETE `actions` type, so `adapter.actions.getChangeDetail()`
|
|
24
|
+
* stays typed for tests and callers,
|
|
25
|
+
* - merges several change sources into one `subscribe` (a store plus its
|
|
26
|
+
* registry, envelopes plus drops) — the second most-copied block.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** A remote-invocable action. Thrown errors are reported back to the dashboard. */
|
|
30
|
+
|
|
31
|
+
/** Anything that can tell the bridge "something changed". */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The contract each dev tool implements to become remotely syncable.
|
|
35
|
+
* Works with BaseEventStore, ChangeLog, createToolStore, or any pattern
|
|
36
|
+
* exposing subscribe + getSnapshot.
|
|
37
|
+
*
|
|
38
|
+
* Payloads (snapshots and action results) must be JSON-serializable —
|
|
39
|
+
* no functions, circular references, or class instances.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** Subscribe to every source; the returned function unsubscribes from all. */
|
|
43
|
+
function mergeChangeSources(sources) {
|
|
44
|
+
if (sources.length === 1) return sources[0];
|
|
45
|
+
return onChange => {
|
|
46
|
+
const offs = sources.map(source => source(onChange));
|
|
47
|
+
return () => {
|
|
48
|
+
for (const off of offs) off();
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createSyncAdapter(definition) {
|
|
53
|
+
const subscribe = Array.isArray(definition.subscribe) ? mergeChangeSources(definition.subscribe) : definition.subscribe;
|
|
54
|
+
const adapter = {
|
|
55
|
+
version: definition.version,
|
|
56
|
+
getSnapshot: definition.getSnapshot,
|
|
57
|
+
subscribe,
|
|
58
|
+
...(definition.actions ? {
|
|
59
|
+
actions: definition.actions
|
|
60
|
+
} : {})
|
|
61
|
+
};
|
|
62
|
+
return adapter;
|
|
63
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.WIRE_INLINE_LIMIT_BYTES = exports.WIRE_DETAIL_LIMIT_BYTES = void 0;
|
|
7
|
+
exports.capDetail = capDetail;
|
|
8
|
+
exports.detailByIdAction = detailByIdAction;
|
|
9
|
+
exports.isOnDeviceMarker = isOnDeviceMarker;
|
|
10
|
+
exports.memoByIdentity = memoByIdentity;
|
|
11
|
+
exports.newestFitting = newestFitting;
|
|
12
|
+
exports.onDeviceMarker = onDeviceMarker;
|
|
13
|
+
exports.readParams = readParams;
|
|
14
|
+
exports.sizeOrStub = sizeOrStub;
|
|
15
|
+
exports.toWire = toWire;
|
|
16
|
+
exports.truncatedNote = truncatedNote;
|
|
17
|
+
var _wireBudget = require("./wireBudget.js");
|
|
18
|
+
/**
|
|
19
|
+
* wireForm — how a tool turns what it holds into what it may put on the wire.
|
|
20
|
+
*
|
|
21
|
+
* Every adapter that ships large values faces the same four problems, and
|
|
22
|
+
* nine of them solved each one separately:
|
|
23
|
+
*
|
|
24
|
+
* 1. A per-item cap on the snapshot stream (16 KB), with a MARKER left in
|
|
25
|
+
* the value's place so the dashboard knows to fetch it on demand.
|
|
26
|
+
* 2. A much larger cap on that on-demand detail fetch (8 MB), with a
|
|
27
|
+
* TRUNCATION NOTE when even that is exceeded.
|
|
28
|
+
* 3. An identity-keyed cache for the wire form, because a snapshot is
|
|
29
|
+
* rebuilt ~5×/sec while a dashboard watches and stores never mutate a
|
|
30
|
+
* recorded item — so converting each item once is correct and cheap.
|
|
31
|
+
* 4. For tools whose items are individually fat (console, sentry), a
|
|
32
|
+
* newest-first walk that keeps as many items as fit a snapshot budget.
|
|
33
|
+
*
|
|
34
|
+
* Marker KEY NAMES are part of the wire protocol — Buoy Desktop and the
|
|
35
|
+
* tool stores test for `__buoyStateOnDevice`, `__buoyPayloadOnDevice`,
|
|
36
|
+
* `__buoyValueOnDevice` — so the factory builds exactly those keys.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** Per-item budget on the snapshot stream. Anything larger stays on-device. */
|
|
40
|
+
const WIRE_INLINE_LIMIT_BYTES = exports.WIRE_INLINE_LIMIT_BYTES = 16 * 1024;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Upper bound for one on-demand detail response. Generous — this is a single
|
|
44
|
+
* explicit fetch, not a per-item stream — but still guarded so a pathological
|
|
45
|
+
* tree can't OOM the device through the reply emit.
|
|
46
|
+
*/
|
|
47
|
+
const WIRE_DETAIL_LIMIT_BYTES = exports.WIRE_DETAIL_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
48
|
+
|
|
49
|
+
/** A frozen `{ __buoy<Kind>OnDevice: true, note }` marker. */
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the sentinel a wire-form snapshot carries in place of a value that
|
|
53
|
+
* stays on the device. `kind` names the value ("State", "Payload", "Value",
|
|
54
|
+
* "Params", "Data", …) and becomes the key the reader tests for.
|
|
55
|
+
*/
|
|
56
|
+
function onDeviceMarker(kind, note) {
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
[`__buoy${kind}OnDevice`]: true,
|
|
59
|
+
note
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True when `value` is the on-device marker for `kind`. */
|
|
64
|
+
function isOnDeviceMarker(value, kind) {
|
|
65
|
+
return typeof value === "object" && value !== null && value[`__buoy${kind}OnDevice`] === true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The note a detail fetch returns when a single field exceeds the detail cap. */
|
|
69
|
+
function truncatedNote(limitBytes = WIRE_DETAIL_LIMIT_BYTES) {
|
|
70
|
+
const mb = limitBytes / (1024 * 1024);
|
|
71
|
+
return {
|
|
72
|
+
__buoyTruncated: true,
|
|
73
|
+
note: `Exceeds the ${mb}MB detail limit — inspect it on-device.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The value itself if it fits `limit` (and is serializable), else `marker`.
|
|
79
|
+
* `undefined` passes through untouched: an absent field must stay absent.
|
|
80
|
+
*/
|
|
81
|
+
function toWire(value, marker, limit = WIRE_INLINE_LIMIT_BYTES) {
|
|
82
|
+
if (value === undefined) return value;
|
|
83
|
+
return (0, _wireBudget.isOverWireBudget)(value, limit) ? marker : value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Detail-fetch cap: the value, or a truncation note when it is too big. */
|
|
87
|
+
function capDetail(value, limit = WIRE_DETAIL_LIMIT_BYTES) {
|
|
88
|
+
return (0, _wireBudget.isOverWireBudget)(value, limit) ? truncatedNote(limit) : value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Memoize a wire-form conversion by item identity. Correct for any store
|
|
93
|
+
* that prepends and never mutates a recorded item — the cache self-invalidates
|
|
94
|
+
* when the item is dropped from the buffer.
|
|
95
|
+
*/
|
|
96
|
+
function memoByIdentity(convert) {
|
|
97
|
+
const cache = new WeakMap();
|
|
98
|
+
return item => {
|
|
99
|
+
const cached = cache.get(item);
|
|
100
|
+
if (cached !== undefined) return cached;
|
|
101
|
+
const converted = convert(item);
|
|
102
|
+
cache.set(item, converted);
|
|
103
|
+
return converted;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Walk `items` (oldest-first) from the newest end, keeping every item until
|
|
108
|
+
* the budget is spent. Always keeps at least one so an over-budget newest
|
|
109
|
+
* item still reaches the dashboard as a stub rather than an empty panel.
|
|
110
|
+
* Returns oldest-first, matching the input.
|
|
111
|
+
*/
|
|
112
|
+
function newestFitting(items, options) {
|
|
113
|
+
const out = [];
|
|
114
|
+
let bytes = options.baseBytes ?? 16;
|
|
115
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
116
|
+
const wired = options.toWire(items[i]);
|
|
117
|
+
if (bytes + wired.bytes > options.budget && out.length > 0) break;
|
|
118
|
+
out.push(wired.value);
|
|
119
|
+
bytes += wired.bytes;
|
|
120
|
+
}
|
|
121
|
+
out.reverse();
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Size one wire-form item against a per-item cap, substituting a stub when it
|
|
127
|
+
* is too fat. One walk decides both "too big?" and "what does it cost".
|
|
128
|
+
*/
|
|
129
|
+
function sizeOrStub(wired, limit, stub) {
|
|
130
|
+
const size = (0, _wireBudget.approxJsonSize)(wired, limit);
|
|
131
|
+
if (size.bytes <= limit) return {
|
|
132
|
+
value: wired,
|
|
133
|
+
bytes: size.bytes
|
|
134
|
+
};
|
|
135
|
+
const stubbed = stub();
|
|
136
|
+
return {
|
|
137
|
+
value: stubbed,
|
|
138
|
+
bytes: (0, _wireBudget.approxJsonSize)(stubbed, limit).bytes
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `(params as T | undefined) ?? {}` — the first line of every action. */
|
|
143
|
+
function readParams(params) {
|
|
144
|
+
return params ?? {};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The on-demand detail action every change-log tool exposes: look one item
|
|
148
|
+
* up by id, and return its detail fields size-capped. Replaces the
|
|
149
|
+
* `missing id` / `unknown id` boilerplate copied into each adapter.
|
|
150
|
+
*/
|
|
151
|
+
function detailByIdAction(lookup, detail, limit = WIRE_DETAIL_LIMIT_BYTES) {
|
|
152
|
+
const cap = value => capDetail(value, limit);
|
|
153
|
+
return params => {
|
|
154
|
+
const {
|
|
155
|
+
id
|
|
156
|
+
} = readParams(params);
|
|
157
|
+
if (!id) return {
|
|
158
|
+
found: false,
|
|
159
|
+
reason: "missing id"
|
|
160
|
+
};
|
|
161
|
+
const item = lookup(id);
|
|
162
|
+
if (!item) return {
|
|
163
|
+
found: false,
|
|
164
|
+
reason: "unknown id"
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
found: true,
|
|
168
|
+
id,
|
|
169
|
+
...detail(item, cap)
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
}
|