@buoy-gg/route-events 7.0.40 → 7.0.42

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 (104) hide show
  1. package/README.md +11 -60
  2. package/lib/commonjs/RouteObserver.js +1 -54
  3. package/lib/commonjs/RouteParser.js +1 -314
  4. package/lib/commonjs/RouteTracker.js +1 -112
  5. package/lib/commonjs/RouteTracker.web.js +1 -0
  6. package/lib/commonjs/browserRouteRegistry.js +1 -0
  7. package/lib/commonjs/browserRoutes.js +1 -0
  8. package/lib/commonjs/components/NavigationStack.js +2 -572
  9. package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
  10. package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -0
  11. package/lib/commonjs/components/RouteEventDetail.js +1 -97
  12. package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
  13. package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
  14. package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -717
  15. package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
  16. package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
  17. package/lib/commonjs/components/RoutesSitemap.js +2 -1035
  18. package/lib/commonjs/expoRouterStore.js +1 -121
  19. package/lib/commonjs/expoRouterStore.web.js +1 -0
  20. package/lib/commonjs/hooks/useRouteEvents.js +1 -57
  21. package/lib/commonjs/index.js +1 -161
  22. package/lib/commonjs/preset.js +1 -69
  23. package/lib/commonjs/stores/navigationStackStore.js +1 -45
  24. package/lib/commonjs/stores/routeEventStore.js +1 -136
  25. package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
  26. package/lib/commonjs/useNavigationStack.js +1 -408
  27. package/lib/commonjs/useNavigationStack.web.js +1 -0
  28. package/lib/commonjs/useRouteObserver.js +1 -73
  29. package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
  30. package/lib/commonjs/useRouteSitemap.js +1 -255
  31. package/lib/commonjs/utils/routeTemplate.js +1 -28
  32. package/lib/commonjs/utils/safeReactNavigation.js +1 -194
  33. package/lib/commonjs/utils/safeReactNavigation.web.js +1 -0
  34. package/lib/commonjs/utils/testIDs.js +1 -63
  35. package/lib/module/RouteObserver.js +1 -49
  36. package/lib/module/RouteParser.js +1 -309
  37. package/lib/module/RouteTracker.js +1 -109
  38. package/lib/module/RouteTracker.web.js +1 -0
  39. package/lib/module/browserRouteRegistry.js +1 -0
  40. package/lib/module/browserRoutes.js +1 -0
  41. package/lib/module/components/NavigationStack.js +2 -568
  42. package/lib/module/components/ReactNavigationRoutes.js +1 -832
  43. package/lib/module/components/ReactNavigationRoutes.web.js +1 -0
  44. package/lib/module/components/RouteEventDetail.js +1 -93
  45. package/lib/module/components/RouteEventExpandedContent.js +1 -200
  46. package/lib/module/components/RouteEventItemCompact.js +1 -187
  47. package/lib/module/components/RouteEventsModalWithTabs.js +1 -714
  48. package/lib/module/components/RouteEventsTimeline.js +1 -103
  49. package/lib/module/components/RouteFilterViewV2.js +1 -38
  50. package/lib/module/components/RoutesSitemap.js +2 -1033
  51. package/lib/module/expoRouterStore.js +1 -114
  52. package/lib/module/expoRouterStore.web.js +1 -0
  53. package/lib/module/hooks/useRouteEvents.js +1 -54
  54. package/lib/module/index.js +1 -69
  55. package/lib/module/preset.js +1 -66
  56. package/lib/module/stores/navigationStackStore.js +1 -41
  57. package/lib/module/stores/routeEventStore.js +1 -128
  58. package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
  59. package/lib/module/useNavigationStack.js +1 -404
  60. package/lib/module/useNavigationStack.web.js +1 -0
  61. package/lib/module/useRouteObserver.js +1 -70
  62. package/lib/module/useRouteObserverReactNavigation.js +1 -106
  63. package/lib/module/useRouteSitemap.js +1 -250
  64. package/lib/module/utils/routeTemplate.js +1 -24
  65. package/lib/module/utils/safeReactNavigation.js +1 -185
  66. package/lib/module/utils/safeReactNavigation.web.js +1 -0
  67. package/lib/module/utils/testIDs.js +1 -55
  68. package/lib/typescript/RouteTracker.web.d.ts +2 -0
  69. package/lib/typescript/browserRouteRegistry.d.ts +4 -0
  70. package/lib/typescript/browserRoutes.d.ts +2 -0
  71. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts +2 -0
  72. package/lib/typescript/expoRouterStore.web.d.ts +13 -0
  73. package/lib/typescript/index.d.ts +1 -0
  74. package/lib/typescript/useNavigationStack.web.d.ts +4 -0
  75. package/lib/typescript/utils/routeTemplate.d.ts +13 -0
  76. package/lib/typescript/utils/safeReactNavigation.web.d.ts +22 -0
  77. package/lib/web/index.mjs +6 -0
  78. package/package.json +33 -13
  79. package/lib/typescript/RouteObserver.d.ts.map +0 -1
  80. package/lib/typescript/RouteParser.d.ts.map +0 -1
  81. package/lib/typescript/RouteTracker.d.ts.map +0 -1
  82. package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
  83. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
  84. package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
  85. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
  86. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
  87. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
  88. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
  89. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
  90. package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
  91. package/lib/typescript/expoRouterStore.d.ts.map +0 -1
  92. package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
  93. package/lib/typescript/index.d.ts.map +0 -1
  94. package/lib/typescript/preset.d.ts.map +0 -1
  95. package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
  96. package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
  97. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
  98. package/lib/typescript/useNavigationStack.d.ts.map +0 -1
  99. package/lib/typescript/useRouteObserver.d.ts.map +0 -1
  100. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
  101. package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
  102. package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
  103. package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
  104. package/lib/typescript/utils/testIDs.d.ts.map +0 -1
@@ -1,275 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.routeEventsSyncAdapter = exports.SNAPSHOT_PARAM_INLINE_LIMIT = void 0;
7
- var _sharedUi = require("@buoy-gg/shared-ui");
8
- var _routeEventStore = require("../stores/routeEventStore");
9
- var _navigationStackStore = require("../stores/navigationStackStore");
10
- var _RouteParser = require("../RouteParser");
11
- var _expoRouterStore = require("../expoRouterStore");
12
- /**
13
- * Navigation params larger than this are NOT streamed in the snapshot.
14
- * Apps routinely pass whole user objects (or a stringified payload) through
15
- * `navigate(..., { user })`; 500 of those on every route change exceed the
16
- * 2MB emit budget and drop the Routes panel. Pathname/segments stay inline.
17
- */
18
- const SNAPSHOT_PARAM_INLINE_LIMIT = exports.SNAPSHOT_PARAM_INLINE_LIMIT = 16 * 1024;
19
- const PARAMS_ON_DEVICE = Object.freeze({
20
- __buoyOmitted: "route-params",
21
- note: "Navigation params stay on the device."
22
- });
23
- function shouldOmitParams(params) {
24
- if (params == null) return false;
25
- return (0, _sharedUi.isOverWireBudget)(params, SNAPSHOT_PARAM_INLINE_LIMIT);
26
- }
27
- function toWireParams(params) {
28
- if (!shouldOmitParams(params)) return params;
29
- return PARAMS_ON_DEVICE;
30
- }
31
- function toWireEvent(event) {
32
- if (!shouldOmitParams(event.params)) return event;
33
- return {
34
- ...event,
35
- params: toWireParams(event.params)
36
- };
37
- }
38
- function toWireStackItem(item) {
39
- if (!shouldOmitParams(item.params)) return item;
40
- return {
41
- ...item,
42
- params: toWireParams(item.params)
43
- };
44
- }
45
-
46
- /**
47
- * Serializable snapshot of the device's route tree, sent to the dashboard so
48
- * the Routes tab can render a sitemap (the raw expo-router RouteNode itself is
49
- * not JSON-serializable — it holds components/functions — so we send the parsed
50
- * RouteInfo[] instead).
51
- */
52
-
53
- /**
54
- * Payload shape for the route-events tool (adapter version 3). Consumers must
55
- * tolerate older shapes: v1 sends a bare RouteChangeEvent[]; v2 omits `stack`.
56
- */
57
-
58
- function getSitemapSnapshot() {
59
- const metadata = (0, _expoRouterStore.getRouteNodeMetadata)();
60
- return {
61
- routes: _RouteParser.RouteParser.parseRouteTree((0, _expoRouterStore.loadRouteNode)()),
62
- source: metadata.source,
63
- lastUpdatedAt: metadata.lastLoadedAt
64
- };
65
- }
66
-
67
- /**
68
- * Sync adapter for the route-events tool, consumed by @buoy-gg/external-sync's
69
- * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
70
- * this package doesn't need a dependency on it).
71
- *
72
- * Subscribing attaches the routeObserver listener, so route changes are only
73
- * recorded while a dashboard is watching. (Route events still require a
74
- * <RouteTracker /> inside the navigation tree to be emitted at all.)
75
- *
76
- * The snapshot carries both the route-change events AND the parsed route
77
- * sitemap, so the dashboard — which has no local expo-router store — can render
78
- * the route tree. The `navigate` action lets the dashboard drive navigation on
79
- * the device.
80
- */
81
- const routeEventsSyncAdapter = exports.routeEventsSyncAdapter = {
82
- version: 3,
83
- getSnapshot: () => ({
84
- events: _routeEventStore.routeEventStore.getEvents().map(toWireEvent),
85
- sitemap: getSitemapSnapshot(),
86
- stack: _navigationStackStore.navigationStackStore.getStack().map(toWireStackItem)
87
- }),
88
- subscribe: onChange => {
89
- const unsubscribeEvents = _routeEventStore.routeEventStore.subscribeToEvents(onChange);
90
- const unsubscribeStack = _navigationStackStore.navigationStackStore.subscribe(onChange);
91
-
92
- // The expo-router route tree can still be populating when a dashboard
93
- // starts watching. Poll briefly until it's available and push a snapshot
94
- // the moment it loads, so the Routes tab fills in without needing the user
95
- // to navigate first. Stops as soon as the tree is found.
96
- let pollTimer = null;
97
- if (!(0, _expoRouterStore.loadRouteNode)()) {
98
- let retries = 0;
99
- const maxRetries = 100; // ~10s
100
- const poll = () => {
101
- retries += 1;
102
- if ((0, _expoRouterStore.loadRouteNode)()) {
103
- pollTimer = null;
104
- onChange();
105
- return;
106
- }
107
- pollTimer = retries < maxRetries ? setTimeout(poll, 100) : null;
108
- };
109
- pollTimer = setTimeout(poll, 100);
110
- }
111
- return () => {
112
- unsubscribeEvents();
113
- unsubscribeStack();
114
- if (pollTimer) clearTimeout(pollTimer);
115
- };
116
- },
117
- actions: {
118
- clearEvents: () => {
119
- _routeEventStore.routeEventStore.clearEvents();
120
- },
121
- /**
122
- * Just where the app is right now — no sitemap, no history.
123
- *
124
- * The full snapshot carries the parsed route tree, which is the wrong
125
- * thing to hand a caller that polls (the macro recorder reads this several
126
- * times a second to notice a screen change). Reading the last route event
127
- * is also what makes this honest about apps with no <RouteTracker />: they
128
- * get `{ path: null }` rather than a plausible-looking guess.
129
- */
130
- getCurrentRoute: () => {
131
- // BaseEventStore is newest-FIRST, and the store only captures while
132
- // something is subscribed.
133
- const latest = _routeEventStore.routeEventStore.getEvents()[0];
134
-
135
- /**
136
- * A RECORDED event only wins while the recorder is running.
137
- *
138
- * The store starts capturing when its first subscriber arrives and stops
139
- * with the last one, so an app with Ask Buoy on the phone and no
140
- * dashboard attached has NO route events at all — the stack fallback
141
- * below is its normal path, not the fresh-launch edge case this code was
142
- * written for. Worse, events left over from a closed capture window are
143
- * stale the moment the user navigates again: a live QA session read
144
- * `/(tabs)/menu` from an event six scenarios after the app had moved to
145
- * `/cart`. The live stack cannot be stale, so it wins whenever nothing
146
- * is recording.
147
- */
148
- if (latest && _routeEventStore.routeEventStore.isCapturing()) {
149
- return {
150
- path: latest.pathname,
151
- params: toWireParams(latest.params),
152
- at: latest.timestamp,
153
- source: "event"
154
- };
155
- }
156
-
157
- // The live stack knows even when the history is empty, which is the
158
- // difference between a macro that can run from anywhere and one that
159
- // only works if you happen to be on the screen it was recorded from.
160
- const stack = _navigationStackStore.navigationStackStore.getStack();
161
- const focused = stack.find(item => item.isFocused) ?? stack[stack.length - 1];
162
- if (focused?.pathname) {
163
- return {
164
- path: focused.pathname,
165
- params: toWireParams(focused.params ?? {}),
166
- at: null,
167
- source: "stack"
168
- };
169
- }
170
-
171
- // No live stack (no <RouteTracker/>) and the recorder is off: an old
172
- // event is all there is. Say that it may be stale rather than passing it
173
- // off as where the app is.
174
- if (latest) {
175
- return {
176
- path: latest.pathname,
177
- params: toWireParams(latest.params),
178
- at: latest.timestamp,
179
- source: "event",
180
- stale: true
181
- };
182
- }
183
- return {
184
- path: null,
185
- params: {},
186
- at: null,
187
- source: null
188
- };
189
- },
190
- /**
191
- * Navigate the device to a concrete path. The dashboard resolves any
192
- * dynamic params into a concrete path before invoking this.
193
- */
194
- navigate: params => {
195
- const p = params;
196
- const path = p?.path;
197
- if (!path) {
198
- throw new Error("navigate requires a 'path' param");
199
- }
200
- const router = (0, _sharedUi.getSafeRouter)();
201
- if (!router) {
202
- // Bare React Navigation: no paths, but the tracker captured the
203
- // container ref, so navigate by SCREEN NAME — "/Settings" and
204
- // "Settings" both resolve, and nested navigators are walked via the
205
- // root state. This is half the market; "throw off expo-router" made
206
- // navigate a dead verb for every RN CLI app.
207
- const actions = _navigationStackStore.navigationStackStore.getActions();
208
- const name = path.replace(/^\//, "");
209
- if (!actions?.navigateToRoute || !name) {
210
- throw new Error("expo-router is not available here and no navigation container was captured — is <RouteTracker /> mounted inside your NavigationContainer?");
211
- }
212
- const ok = actions.navigateToRoute(name, p?.params);
213
- if (!ok) {
214
- throw new Error(`Could not navigate to "${name}" — no navigation container is live.`);
215
- }
216
- return {
217
- navigated: path,
218
- replaced: false,
219
- via: "react-navigation"
220
- };
221
- }
222
- // Push by default so navigate-then-back flows keep working. Callers that
223
- // mean "leave this screen" (sitemap Go, agents resetting to home) pass
224
- // replace: true — otherwise "Go to home" stacks another `/` on top.
225
- const go = p?.replace === true && typeof router.replace === "function" ? router.replace : router.navigate;
226
- go(path);
227
- return {
228
- navigated: path,
229
- replaced: p?.replace === true
230
- };
231
- },
232
- // ── Stack actions: delegate to the live navigation actions captured by
233
- // <RouteTracker /> (they hold the React Navigation container ref). ──
234
- stackNavigateToIndex: params => {
235
- const index = params?.index;
236
- if (typeof index !== "number") {
237
- throw new Error("stackNavigateToIndex requires a numeric 'index'");
238
- }
239
- const actions = _navigationStackStore.navigationStackStore.getActions();
240
- if (!actions) throw new Error("navigation stack is not available");
241
- actions.navigateToIndex(index);
242
- return {
243
- navigatedToIndex: index
244
- };
245
- },
246
- stackPopToIndex: params => {
247
- const index = params?.index;
248
- if (typeof index !== "number") {
249
- throw new Error("stackPopToIndex requires a numeric 'index'");
250
- }
251
- const actions = _navigationStackStore.navigationStackStore.getActions();
252
- if (!actions) throw new Error("navigation stack is not available");
253
- actions.popToIndex(index);
254
- return {
255
- poppedToIndex: index
256
- };
257
- },
258
- stackGoBack: () => {
259
- const actions = _navigationStackStore.navigationStackStore.getActions();
260
- if (!actions) throw new Error("navigation stack is not available");
261
- actions.goBack();
262
- return {
263
- wentBack: true
264
- };
265
- },
266
- stackPopToTop: () => {
267
- const actions = _navigationStackStore.navigationStackStore.getActions();
268
- if (!actions) throw new Error("navigation stack is not available");
269
- actions.popToTop();
270
- return {
271
- poppedToTop: true
272
- };
273
- }
274
- }
275
- };
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.routeEventsSyncAdapter=exports.SNAPSHOT_PARAM_INLINE_LIMIT=void 0;var _sharedUi=require("@buoy-gg/shared-ui");var _routeEventStore=require("../stores/routeEventStore");var _navigationStackStore=require("../stores/navigationStackStore");var _RouteParser=require("../RouteParser");var _expoRouterStore=require("../expoRouterStore");const SNAPSHOT_PARAM_INLINE_LIMIT=exports.SNAPSHOT_PARAM_INLINE_LIMIT=16*1024;const PARAMS_ON_DEVICE=Object.freeze({__buoyOmitted:"route-params",note:"Navigation params stay on the device."});function shouldOmitParams(t){if(t==null)return false;return(0,_sharedUi.isOverWireBudget)(t,SNAPSHOT_PARAM_INLINE_LIMIT)}function toWireParams(t){if(!shouldOmitParams(t))return t;return PARAMS_ON_DEVICE}function toWireEvent(t){if(!shouldOmitParams(t.params))return t;return{...t,params:toWireParams(t.params)}}function toWireStackItem(t){if(!shouldOmitParams(t.params))return t;return{...t,params:toWireParams(t.params)}}function getSitemapSnapshot(){const t=(0,_expoRouterStore.getRouteNodeMetadata)();return{routes:_RouteParser.RouteParser.parseRouteTree((0,_expoRouterStore.loadRouteNode)()),source:t.source,lastUpdatedAt:t.lastLoadedAt}}const routeEventsSyncAdapter=exports.routeEventsSyncAdapter={version:3,getSnapshot:()=>({events:_routeEventStore.routeEventStore.getEvents().map(toWireEvent),sitemap:getSitemapSnapshot(),stack:_navigationStackStore.navigationStackStore.getStack().map(toWireStackItem)}),subscribe:t=>{const e=_routeEventStore.routeEventStore.subscribeToEvents(t);const a=_navigationStackStore.navigationStackStore.subscribe(t);let r=null;if(!(0,_expoRouterStore.loadRouteNode)()){let n=0;const i=100;const o=()=>{n+=1;if((0,_expoRouterStore.loadRouteNode)()){r=null;t();return}r=n<i?setTimeout(o,100):null};r=setTimeout(o,100)}return()=>{e();a();if(r)clearTimeout(r)}},actions:{clearEvents:()=>{_routeEventStore.routeEventStore.clearEvents()},getCurrentRoute:()=>{const t=_routeEventStore.routeEventStore.getEvents()[0];if(t&&_routeEventStore.routeEventStore.isCapturing()){return{path:t.pathname,params:toWireParams(t.params),at:t.timestamp,source:"event"}}const e=_navigationStackStore.navigationStackStore.getStack();const a=e.find(r=>r.isFocused)??e[e.length-1];if(a?.pathname){return{path:a.pathname,params:toWireParams(a.params??{}),at:null,source:"stack"}}if(t){return{path:t.pathname,params:toWireParams(t.params),at:t.timestamp,source:"event",stale:true}}return{path:null,params:{},at:null,source:null}},navigate:t=>{const e=t;const a=e?.path;if(!a){throw new Error("navigate requires a 'path' param")}const r=(0,_sharedUi.getSafeRouter)();if(!r){const i=_navigationStackStore.navigationStackStore.getActions();const o=a.replace(/^\//,"");if(!i?.navigateToRoute||!o){throw new Error("expo-router is not available here and no navigation container was captured \u2014 is <RouteTracker /> mounted inside your NavigationContainer?")}const s=i.navigateToRoute(o,e?.params);if(!s){throw new Error(`Could not navigate to "${o}" \u2014 no navigation container is live.`)}return{navigated:a,replaced:false,via:"react-navigation"}}const n=e?.replace===true&&typeof r.replace==="function"?r.replace:r.navigate;n(a);return{navigated:a,replaced:e?.replace===true}},stackNavigateToIndex:t=>{const e=t?.index;if(typeof e!=="number"){throw new Error("stackNavigateToIndex requires a numeric 'index'")}const a=_navigationStackStore.navigationStackStore.getActions();if(!a)throw new Error("navigation stack is not available");a.navigateToIndex(e);return{navigatedToIndex:e}},stackPopToIndex:t=>{const e=t?.index;if(typeof e!=="number"){throw new Error("stackPopToIndex requires a numeric 'index'")}const a=_navigationStackStore.navigationStackStore.getActions();if(!a)throw new Error("navigation stack is not available");a.popToIndex(e);return{poppedToIndex:e}},stackGoBack:()=>{const t=_navigationStackStore.navigationStackStore.getActions();if(!t)throw new Error("navigation stack is not available");t.goBack();return{wentBack:true}},stackPopToTop:()=>{const t=_navigationStackStore.navigationStackStore.getActions();if(!t)throw new Error("navigation stack is not available");t.popToTop();return{poppedToTop:true}}}};