cosmic-eye 0.3.0 → 0.5.1

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/README.md CHANGED
@@ -1,17 +1,19 @@
1
1
  # CosmicEye
2
2
 
3
- A set of RUM metrics for SPAs. Two independent modules:
3
+ A set of RUM metrics for SPAs. Two independent modules + extensions:
4
4
 
5
5
  - **RDR** (RUM Duplicate Requests) — detects and logs duplicate API requests.
6
6
  - **RT** (Route Transition Metrics) — measures route transition timing (render + TTI).
7
+ - **Extensions** — `observeHistory`, `RouteRenderObserver`, `mobxSpy`.
7
8
 
8
9
  **Key properties:**
9
10
 
10
- - Zero runtime dependencies for the core (React is an optional peer for extensions)
11
- - RDR: deterministic sampling ~5% in production, always enabled in dev
12
- - RT: dev-only, measures `route_render_ms` and `route_tti_ms`
13
- - `RouteTracker` — "dumb" React provider for both modules (post-render)
11
+ - Zero runtime dependencies for the core (React and MobX are optional peers)
12
+ - Config-driven sampling, send functions, enrichers, tags
13
+ - All public methods return result objects with `initialized` status
14
14
  - `observeHistory` — neutral pre-render navigation observer (history v4/v5)
15
+ - `RouteRenderObserver` — post-render React provider
16
+ - `mobxSpy` — MobX spy integration (no-op if MobX absent)
15
17
 
16
18
  ## Installation
17
19
 
@@ -24,83 +26,82 @@ npm install cosmic-eye
24
26
  ### RDR
25
27
 
26
28
  ```ts
27
- import rdr, { initRDR } from 'cosmic-eye';
29
+ import { rdr, initRDR } from 'cosmic-eye';
28
30
 
29
- initRDR();
30
- rdr.reqHandler({ s: 'UserService', m: 'getProfile', p: { id: 42 }, b: {} });
31
+ const ok = initRDR({ samplingRate: 0.05, send: (p) => analytics.send('rdr', p) });
32
+
33
+ // RPC-style
34
+ rdr.reqHandlerRpc({ s: 'UserService', m: 'getProfile', p: { id: 42 }, b: {} });
35
+ // HTTP-style
36
+ rdr.reqHandlerHttp({ httpMethod: 'GET', endpoint: '/api/users/42' });
31
37
  ```
32
38
 
33
- ### RT + observeHistory + RouteTracker
39
+ ### RT + observeHistory + RouteRenderObserver
34
40
 
35
41
  ```ts
36
42
  import { initRT, rt, observeHistory } from 'cosmic-eye';
37
43
  import { createBrowserHistory } from 'history';
38
44
 
39
45
  const history = createBrowserHistory();
40
- initRT();
46
+ initRT({ send: (p) => analytics.send('rt', p), includePathname: true });
41
47
 
42
- // Pre-render: observer emits navigation events before React render
43
48
  const observer = observeHistory(history);
44
49
  observer.subscribe(({ pathname, search }) => {
45
50
  rt.startTransition(pathname, search);
46
- rdr.resetTiming();
47
- rdr.resetActions();
48
51
  });
49
52
  ```
50
53
 
51
54
  ```tsx
52
- import { RouteTracker } from 'cosmic-eye/react';
53
-
54
- // Post-render: RouteTracker fires after React commit
55
- <Router history={history}>
56
- <RouteTracker
57
- onRouteChange={[
58
- (pathname) => { rt.markRendered(pathname); },
59
- ]}
60
- >
61
- <Switch>...</Switch>
62
- </RouteTracker>
63
- </Router>
55
+ import { RouteRenderObserver } from 'cosmic-eye/react';
56
+
57
+ <RouteRenderObserver
58
+ onRouteChange={[(pathname) => { rt.markRendered(pathname); }]}
59
+ >
60
+ {children}
61
+ </RouteRenderObserver>
64
62
  ```
65
63
 
66
64
  ## How duplicate detection works
67
65
 
68
- 1. Each incoming payload is hashed into a `reqHash` (combining endpoint + params hash + body hash)
69
- 2. If the same `reqHash` was seen within `TIMINGS.DUPLICATE_THRESHOLD_MS` (default 1 000 ms), a log entry is created
66
+ 1. Each incoming payload is hashed into a `reqHash` (FNV-1a of endpoint + params + body)
67
+ 2. If the same `reqHash` was seen within `duplicateThresholdMs` (default 1 000 ms), a log entry is created
70
68
  3. Log entries accumulate in a buffer and are flushed:
71
- - Every `FLUSH.INTERVAL_MS` (15 s)
72
- - When buffer reaches `FLUSH.MAX_EVENTS` (50)
69
+ - Every `flushIntervalMs` (15 s)
70
+ - When buffer reaches `flushMaxEvents` (50)
73
71
  - On `visibilitychange` (tab hidden)
74
72
  - On `pagehide` (page close)
75
- - On `destroy()`
73
+ - On `destroy()` or `flush()`
76
74
 
77
75
  ## Sampling
78
76
 
79
- In **production** (`NODE_ENV=production`), only ~5% of users are sampled. The decision is deterministic and stable across page reloads a client ID is persisted in `localStorage` under key `rum_user_id`, hashed, and checked against the sampling rate.
80
-
81
- In **development** (`NODE_ENV !== 'production'`), sampling is always enabled.
77
+ Sampling is **config-driven** via `samplingRate` (0..1). Default is `1` (always enabled). The decision is deterministic when a stable client ID is available (`clientId` from config or persisted `localStorage`). If storage is unavailable and no explicit `clientId` is provided, fallback ID generation may produce non-deterministic results between calls.
82
78
 
83
79
  ## API
84
80
 
85
81
  ### RDR
86
82
 
87
- | Function | Description |
88
- |----------|-------------|
89
- | `initRDR()` | Initialize. Safe to call multiple times. |
90
- | `rdr.reqHandler(payload)` | Pass an API request. `{ s, m, p?, b? }` |
91
- | `rdr.resetTiming()` | Reset timer (SPA route change) |
92
- | `rdr.resetActions()` | Clear action buffer |
93
- | `rdr.destroy()` | Stop timers, flush, clean up |
83
+ | Function | Returns | Description |
84
+ |----------|---------|-------------|
85
+ | `initRDR(config?)` | `boolean` | Initialize with optional config. |
86
+ | `rdr.isInitialized()` | `boolean` | Check if active. |
87
+ | `rdr.reqHandlerRpc(payload)` | `RdrReqHandlerResult` | Process RPC request. |
88
+ | `rdr.reqHandlerHttp(payload)` | `RdrReqHandlerResult` | Process HTTP request. |
89
+ | `rdr.flush(trigger?, meta?)` | `RdrFlushResult` | Manual flush. |
90
+ | `rdr.resetTiming()` | `RdrResetTimingResult` | Reset page load timestamp. |
91
+ | `rdr.resetActions()` | `RdrResetActionsResult` | Clear action buffer. |
92
+ | `rdr.destroy()` | `RdrDestroyResult` | Stop, flush, clean up. |
94
93
 
95
94
  ### RT
96
95
 
97
- | Function | Description |
98
- |----------|-------------|
99
- | `initRT()` | Enable module (dev-only) |
100
- | `rt.startTransition(pathname, search)` | Start transition timing (pre-render) |
101
- | `rt.markRendered(pathname)` | Mark route render (post-render) |
102
- | `trackCritical(promise?)` | Track a critical async operation |
103
- | `rt.destroy()` | Clean up |
96
+ | Function | Returns | Description |
97
+ |----------|---------|-------------|
98
+ | `initRT(config?)` | `boolean` | Initialize with optional config. |
99
+ | `rt.isInitialized()` | `boolean` | Check if active. |
100
+ | `rt.startTransition(pathname, search?)` | `RtStartTransitionResult` | Start transition timing. |
101
+ | `rt.markRendered(pathname)` | `RtMarkRenderedResult` | Mark render completion. |
102
+ | `rt.trackCritical(promise?)` | `RtTrackCriticalResult` | Track critical operation. |
103
+ | `rt.abortPending(reason?)` | `RtAbortPendingResult` | Abort active transition. |
104
+ | `rt.destroy()` | `RtDestroyResult` | Clean up. |
104
105
 
105
106
  ### observeHistory
106
107
 
@@ -110,7 +111,7 @@ In **development** (`NODE_ENV !== 'production'`), sampling is always enabled.
110
111
  | `observer.subscribe(listener)` | Subscribe to `INIT/PUSH/REPLACE/POP`. Returns `unsubscribe` |
111
112
  | `observer.unpatch()` | Remove patch, clean up |
112
113
 
113
- ### RouteTracker (`cosmic-eye/react`)
114
+ ### RouteRenderObserver (`cosmic-eye/react`)
114
115
 
115
116
  | Prop | Type | Description |
116
117
  |------|------|-------------|
@@ -125,15 +126,17 @@ See [src/rdr/README.md](src/rdr/README.md) and [src/rt/README.md](src/rt/README.
125
126
  src/
126
127
  index.ts — public API (re-exports only, no React)
127
128
  react.ts — React extensions entry point (cosmic-eye/react)
129
+ shared/ — shared utilities (time, hash, sampling, enrichers, env)
128
130
  rdr/ — RDR module: duplicate request detection
129
131
  rt/ — RT module: route transition metrics
130
132
  extensions/
131
133
  history-route-observer/ — navigation observer (pre-render)
132
- route-tracker/ RouteTracker React component (post-render)
134
+ route-render-observer/ RouteRenderObserver React component (post-render)
135
+ mobx-spy/ — MobX spy extension (optional peer)
133
136
  tests/
134
- rdr/ — RDR tests (49)
135
- rt/ — RT tests (19)
136
- extensions/ — extension tests (32)
137
+ rdr/ — RDR tests
138
+ rt/ — RT tests
139
+ extensions/ — extension tests (observer, route-render, mobx-spy)
137
140
  ```
138
141
 
139
142
  ## Test commands
@@ -153,7 +156,8 @@ Each module has its own README with integration guide, configuration reference,
153
156
  - [src/rdr/README.md](src/rdr/README.md) — RDR documentation
154
157
  - [src/rt/README.md](src/rt/README.md) — RT documentation
155
158
  - [src/extensions/history-route-observer/README.md](src/extensions/history-route-observer/README.md) — observer documentation
156
- - [src/extensions/route-tracker/README.md](src/extensions/route-tracker/README.md) — RouteTracker documentation
159
+ - [src/extensions/route-render-observer/README.md](src/extensions/route-render-observer/README.md) — RouteRenderObserver documentation
160
+ - [src/extensions/mobx-spy/README.md](src/extensions/mobx-spy/README.md) — mobxSpy documentation
157
161
  - [CHANGELOG.md](CHANGELOG.md) — change history
158
162
 
159
163
  ## License