svoose 0.1.4 → 0.1.6

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 (39) hide show
  1. package/README.md +148 -14
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +3 -3
  6. package/dist/metrics/index.d.ts +5 -0
  7. package/dist/metrics/index.d.ts.map +1 -0
  8. package/dist/metrics/index.js +2 -0
  9. package/dist/metrics/index.js.map +7 -0
  10. package/dist/metrics/metric.d.ts +44 -0
  11. package/dist/metrics/metric.d.ts.map +1 -0
  12. package/dist/metrics/metric.js +2 -0
  13. package/dist/metrics/metric.js.map +7 -0
  14. package/dist/observe/observe.svelte.d.ts.map +1 -1
  15. package/dist/observe/observe.svelte.js +1 -1
  16. package/dist/observe/observe.svelte.js.map +3 -3
  17. package/dist/observe/sampling.d.ts +2 -20
  18. package/dist/observe/sampling.d.ts.map +1 -1
  19. package/dist/observe/sampling.js +1 -1
  20. package/dist/observe/sampling.js.map +2 -2
  21. package/dist/observe/session.d.ts +33 -0
  22. package/dist/observe/session.d.ts.map +1 -0
  23. package/dist/observe/session.js +2 -0
  24. package/dist/observe/session.js.map +7 -0
  25. package/dist/observe/vitals.d.ts +36 -2
  26. package/dist/observe/vitals.d.ts.map +1 -1
  27. package/dist/observe/vitals.js +1 -1
  28. package/dist/observe/vitals.js.map +3 -3
  29. package/dist/svelte/index.svelte.js +2 -0
  30. package/dist/svelte/index.svelte.js.map +7 -0
  31. package/dist/transport/fetch.d.ts +1 -1
  32. package/dist/transport/fetch.d.ts.map +1 -1
  33. package/dist/transport/fetch.js +1 -1
  34. package/dist/transport/fetch.js.map +1 -1
  35. package/dist/transport/transport.d.ts +2 -10
  36. package/dist/transport/transport.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +44 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/package.json +6 -1
package/README.md CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  > Svelte + Goose = **svoose** — the goose that sees everything
4
4
 
5
- Lightweight observability + state machines for Svelte 5. Zero dependencies. Tree-shakeable. **< 3KB gzipped**.
5
+ Lightweight observability + state machines for Svelte 5. Zero dependencies. Tree-shakeable. **< 5KB gzipped** (core ~3.5KB).
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Web Vitals** — CLS, LCP, FID, INP, FCP, TTFB (no external deps)
10
10
  - **Error Tracking** — global errors + unhandled rejections
11
+ - **Custom Metrics** — `metric()` for custom analytics events (v0.1.6+)
12
+ - **Session Tracking** — automatic sessionId with timeout (v0.1.5+)
13
+ - **Sampling** — per-event-type rate limiting (v0.1.3+)
11
14
  - **State Machines** — minimal FSM with TypeScript inference
12
15
  - **Svelte 5 Native** — reactive `useMachine()` hook with $state runes
13
16
  - **Tree-shakeable** — pay only for what you use
@@ -61,10 +64,12 @@ Start collecting Web Vitals and errors.
61
64
 
62
65
  ```typescript
63
66
  const cleanup = observe({
64
- // Where to send data
67
+ // Where to send data (Option 1: endpoint)
65
68
  endpoint: '/api/metrics',
66
69
 
67
- // Or use custom transport
70
+ // Or use custom transport (Option 2: transport)
71
+ // NOTE: endpoint and transport are mutually exclusive
72
+ // If transport is provided, endpoint is ignored
68
73
  transport: myTransport,
69
74
 
70
75
  // What to collect
@@ -75,9 +80,7 @@ const cleanup = observe({
75
80
  batchSize: 10,
76
81
  flushInterval: 5000,
77
82
 
78
- // Sampling (v0.1.3+)
79
- sampling: 0.1, // 10% of all events
80
- // or per-event-type (recommended)
83
+ // Sampling (v0.1.3+) — number or per-event-type config
81
84
  sampling: {
82
85
  vitals: 0.1, // 10% — sufficient for statistics
83
86
  errors: 1.0, // 100% — all errors matter
@@ -85,6 +88,9 @@ const cleanup = observe({
85
88
  transitions: 0.0, // disabled
86
89
  },
87
90
 
91
+ // Sessions (v0.1.5+)
92
+ session: true, // or { timeout: 30 * 60 * 1000, storage: 'sessionStorage' }
93
+
88
94
  // Debug
89
95
  debug: false,
90
96
  });
@@ -93,6 +99,8 @@ const cleanup = observe({
93
99
  cleanup();
94
100
  ```
95
101
 
102
+ > **Note**: If neither `endpoint` nor `transport` is provided, defaults to `endpoint: '/api/observe'`.
103
+
96
104
  #### Sampling (v0.1.3+)
97
105
 
98
106
  Control what percentage of events are sent to your backend:
@@ -112,13 +120,115 @@ observe({
112
120
  errors: 1.0, // 100% — capture all errors
113
121
  custom: 0.5, // 50% of custom metrics
114
122
  transitions: 0.0, // disabled — no state machine events
115
- identify: 1.0, // 100% — always track user identification
116
123
  },
117
124
  });
118
125
  ```
119
126
 
120
127
  > **Note**: `sampleRate` is deprecated. Use `sampling` instead.
121
128
 
129
+ #### Sessions (v0.1.5+)
130
+
131
+ Automatic session tracking with configurable timeout:
132
+
133
+ ```typescript
134
+ observe({
135
+ endpoint: '/api/metrics',
136
+
137
+ // Enable with defaults (30 min timeout, sessionStorage)
138
+ session: true,
139
+
140
+ // Or custom config
141
+ session: {
142
+ timeout: 60 * 60 * 1000, // 1 hour in milliseconds = new session after 1h inactivity
143
+ storage: 'localStorage', // 'sessionStorage' | 'localStorage' | 'memory'
144
+ },
145
+ });
146
+
147
+ // All events now include sessionId:
148
+ // { type: 'vital', name: 'LCP', value: 1234, sessionId: '1706123456789-abc123def' }
149
+ ```
150
+
151
+ > **Note**: `timeout` is in **milliseconds**. Common values: `30 * 60 * 1000` (30 min), `60 * 60 * 1000` (1 hour).
152
+
153
+ **Storage options:**
154
+ - `sessionStorage` (default) — session per browser tab
155
+ - `localStorage` — session persists across tabs
156
+ - `memory` — no persistence, new session on page reload
157
+
158
+ **Features:**
159
+ - Automatic session ID generation (timestamp + random)
160
+ - Session expires after inactivity timeout (default: 30 min)
161
+ - Graceful degradation in private mode
162
+ - SSR safe
163
+
164
+ #### Web Vitals (v0.1.5+)
165
+
166
+ svoose collects all Core Web Vitals using the standard [web-vitals](https://github.com/GoogleChrome/web-vitals) algorithm:
167
+
168
+ | Metric | What it measures | When reported |
169
+ |--------|------------------|---------------|
170
+ | **CLS** | Visual stability (layout shifts) | On page hide/visibility change |
171
+ | **LCP** | Loading performance | On user input or visibility change |
172
+ | **INP** | Responsiveness (max interaction) | On page hide/visibility change |
173
+ | **FCP** | First content painted | Once |
174
+ | **TTFB** | Server response time | Once |
175
+ | **FID** | First input delay (deprecated) | Once |
176
+
177
+ **Web Vitals Reporting (v0.1.5+)**:
178
+
179
+ All vitals follow the [web-vitals](https://github.com/GoogleChrome/web-vitals) standard:
180
+
181
+ **CLS (Cumulative Layout Shift)**:
182
+ - Groups shifts into sessions (max 5s, max 1s gap)
183
+ - Reports maximum session value on page hide
184
+
185
+ **LCP (Largest Contentful Paint)**:
186
+ - Tracks largest content element painted
187
+ - Finalized on first user interaction (click/keydown) or visibility change
188
+
189
+ **INP (Interaction to Next Paint)**:
190
+ - Tracks maximum interaction duration
191
+ - Only counts discrete events with `interactionId` (ignores scroll, etc.)
192
+ - Reports on page hide
193
+
194
+ ```typescript
195
+ // All vitals report automatically on page lifecycle events
196
+ observe({ vitals: true });
197
+
198
+ // Select specific vitals
199
+ observe({ vitals: ['CLS', 'LCP', 'INP'] });
200
+ ```
201
+
202
+ > **Note (v0.1.5 breaking change)**: CLS, LCP, and INP now report once per page lifecycle instead of on every update. This matches Chrome DevTools and Google Search Console behavior.
203
+
204
+ #### Custom Metrics (v0.1.6+)
205
+
206
+ Track custom events for analytics:
207
+
208
+ ```typescript
209
+ import { metric } from 'svoose';
210
+
211
+ // Basic usage
212
+ metric('checkout_started', { step: 1, cartTotal: 99.99 });
213
+ metric('button_clicked', { id: 'submit-btn' });
214
+ metric('feature_used', { name: 'dark_mode', enabled: true });
215
+ ```
216
+
217
+ Events are automatically batched with other metrics. You can control the sampling rate:
218
+
219
+ ```typescript
220
+ observe({
221
+ endpoint: '/api/metrics',
222
+ sampling: {
223
+ custom: 0.5, // 50% of custom metrics
224
+ vitals: 0.1,
225
+ errors: 1.0,
226
+ },
227
+ });
228
+ ```
229
+
230
+ **Buffer behavior**: If `metric()` is called before `observe()`, events are buffered (max 100). They're automatically flushed when `observe()` initializes.
231
+
122
232
  ### `createMachine(config)`
123
233
 
124
234
  Create a state machine.
@@ -239,25 +349,43 @@ const transport = createFetchTransport('/api/metrics', {
239
349
  headers: { 'Authorization': 'Bearer xxx' },
240
350
  onError: (err) => console.error(err),
241
351
  });
352
+ observe({ transport });
242
353
 
243
- // Console (for development)
354
+ // Console only (for development) — no network requests
244
355
  observe({ transport: createConsoleTransport({ pretty: true }) });
245
356
 
246
- // Custom transport
357
+ // Noop (silent, for production without backend)
358
+ observe({ transport: { send: () => {} } });
359
+
360
+ // Custom transport (Sentry, Datadog, etc.)
247
361
  const myTransport = {
248
362
  async send(events) {
249
363
  await myApi.track(events);
250
364
  },
251
365
  };
366
+ observe({ transport: myTransport });
367
+
368
+ // Dev vs Prod pattern
369
+ const isDev = import.meta.env.DEV;
370
+ observe({
371
+ transport: isDev
372
+ ? createConsoleTransport({ pretty: true })
373
+ : createFetchTransport('/api/metrics'),
374
+ });
252
375
  ```
253
376
 
254
377
  ## Bundle Size
255
378
 
379
+ Tree-shakeable — pay only for what you use:
380
+
256
381
  | Import | Size (gzip) |
257
382
  |--------|-------------|
258
- | Full bundle | ~3.0 KB |
259
- | `observe()` only | ~2.1 KB |
383
+ | `observe()` + vitals + errors + metrics | ~3.5 KB |
260
384
  | `createMachine()` only | ~0.8 KB |
385
+ | Full bundle (v0.1.x) | ~4.5 KB |
386
+ | Full production (v0.2.0+) | ~6 KB |
387
+
388
+ > Most apps only need `observe()` core (~3.5 KB). Compare: Sentry ~20KB, PostHog ~40KB.
261
389
 
262
390
  ## TypeScript
263
391
 
@@ -402,10 +530,16 @@ const machine = createMachine({
402
530
 
403
531
  ## Roadmap
404
532
 
405
- - **v0.1.3-v0.1.9** — Incremental features (sampling, sessions, custom metrics, retry, privacy)
406
- - **v0.2.0** — Production-Ready Observability (network awareness, offline queue, user identification)
533
+ - **v0.1.3** Sampling (per-event-type rate limiting)
534
+ - **v0.1.4** Hotfix (missing sampling.js)
535
+ - **v0.1.5** ✅ — Session Tracking + CLS Session Windows fix
536
+ - **v0.1.6** ✅ — Custom metrics (`metric()` API)
537
+ - **v0.1.7** — Extended Metrics (counter/gauge/histogram + typed API)
538
+ - **v0.1.8** — Beacon + Hybrid Transport
539
+ - **v0.1.9** — Retry Logic
540
+ - **v0.1.10** — Privacy Utilities
541
+ - **v0.2.0** — Production-Ready Observability + Bundle Restructure (modular entry points)
407
542
  - **v0.3.0** — SvelteKit Integration (Vite plugin, hooks, route tracking)
408
- - **v0.4.0** — Developer Experience (CLI, dashboard template)
409
543
  - **v1.0.0** — Stable Release (Q1 2027)
410
544
 
411
545
  > **Note**: FSM is a lightweight bonus feature, not an XState competitor. For complex state machines, use XState.
package/dist/index.d.ts CHANGED
@@ -15,5 +15,6 @@ export { createFetchTransport, createConsoleTransport } from './transport/index.
15
15
  export type { Transport, TransportOptions } from './transport/index.js';
16
16
  export { createMachine, createEvent } from './machine/index.js';
17
17
  export type { MachineConfig, Machine, EventObject, StateNode, TransitionConfig, InferStates, InferEvents, InferContext, } from './machine/index.js';
18
- export type { VitalEvent, TransitionEvent, ObserveEvent, } from './types/index.js';
18
+ export { metric } from './metrics/index.js';
19
+ export type { VitalEvent, TransitionEvent, CustomMetricEvent, ObserveEvent, } from './types/index.js';
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKzD,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAKrG,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,iBAAiB,EACjB,UAAU,EACV,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK3E,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACpF,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,oBAAoB,CAAC;AAK5B,YAAY,EACV,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKzD,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAKrG,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,iBAAiB,EACjB,UAAU,EACV,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK3E,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACpF,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAKxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAK5C,YAAY,EACV,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{observe as t}from"./observe/index.js";import{createSampler as n,eventTypeToSamplingType as s}from"./observe/index.js";import{observeErrors as i,registerMachineContext as a,unregisterMachineContext as v}from"./observe/index.js";import{observeCLS as m,observeLCP as f,observeFID as b,observeINP as c,observeFCP as E,observeTTFB as T,vitalObservers as y}from"./observe/index.js";import{createFetchTransport as g,createConsoleTransport as l}from"./transport/index.js";import{createMachine as j,createEvent as S}from"./machine/index.js";export{l as createConsoleTransport,S as createEvent,g as createFetchTransport,j as createMachine,n as createSampler,s as eventTypeToSamplingType,t as observe,m as observeCLS,i as observeErrors,E as observeFCP,b as observeFID,c as observeINP,f as observeLCP,T as observeTTFB,a as registerMachineContext,v as unregisterMachineContext,y as vitalObservers};
1
+ import{observe as t}from"./observe/index.js";import{createSampler as n,eventTypeToSamplingType as s}from"./observe/index.js";import{observeErrors as i,registerMachineContext as a,unregisterMachineContext as v}from"./observe/index.js";import{observeCLS as x,observeLCP as f,observeFID as c,observeINP as b,observeFCP as E,observeTTFB as T,vitalObservers as y}from"./observe/index.js";import{createFetchTransport as d,createConsoleTransport as g}from"./transport/index.js";import{createMachine as j,createEvent as M}from"./machine/index.js";import{metric as h}from"./metrics/index.js";export{g as createConsoleTransport,M as createEvent,d as createFetchTransport,j as createMachine,n as createSampler,s as eventTypeToSamplingType,h as metric,t as observe,x as observeCLS,i as observeErrors,E as observeFCP,c as observeFID,b as observeINP,f as observeLCP,T as observeTTFB,a as registerMachineContext,v as unregisterMachineContext,y as vitalObservers};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/**\n * \uD83E\uDEBF svoose - Observability + State Machines for Svelte 5\n *\n * @packageDocumentation\n */\n\n// ============================================\n// Core Observability\n// ============================================\nexport { observe } from './observe/index.js';\nexport type { ObserveOptions } from './observe/index.js';\n\n// ============================================\n// Sampling\n// ============================================\nexport { createSampler, eventTypeToSamplingType } from './observe/index.js';\nexport type { SamplingConfig, SamplingOption, SamplingEventType, Sampler } from './observe/index.js';\n\n// ============================================\n// Error Tracking\n// ============================================\nexport {\n observeErrors,\n registerMachineContext,\n unregisterMachineContext,\n} from './observe/index.js';\nexport type {\n ObserveErrorEvent,\n ErrorEvent,\n UnhandledRejectionEvent,\n} from './observe/index.js';\n\n// ============================================\n// Web Vitals\n// ============================================\nexport {\n observeCLS,\n observeLCP,\n observeFID,\n observeINP,\n observeFCP,\n observeTTFB,\n vitalObservers,\n} from './observe/index.js';\nexport type { Metric, MetricName, MetricRating } from './observe/index.js';\n\n// ============================================\n// Transport\n// ============================================\nexport { createFetchTransport, createConsoleTransport } from './transport/index.js';\nexport type { Transport, TransportOptions } from './transport/index.js';\n\n// ============================================\n// State Machines\n// ============================================\nexport { createMachine, createEvent } from './machine/index.js';\nexport type {\n MachineConfig,\n Machine,\n EventObject,\n StateNode,\n TransitionConfig,\n InferStates,\n InferEvents,\n InferContext,\n} from './machine/index.js';\n\n// ============================================\n// Shared Types\n// ============================================\nexport type {\n VitalEvent,\n TransitionEvent,\n ObserveEvent,\n} from './types/index.js';\n"],
5
- "mappings": "AASA,OAAS,WAAAA,MAAe,qBAMxB,OAAS,iBAAAC,EAAe,2BAAAC,MAA+B,qBAMvD,OACE,iBAAAC,EACA,0BAAAC,EACA,4BAAAC,MACK,qBAUP,OACE,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,eAAAC,EACA,kBAAAC,MACK,qBAMP,OAAS,wBAAAC,EAAsB,0BAAAC,MAA8B,uBAM7D,OAAS,iBAAAC,EAAe,eAAAC,MAAmB",
6
- "names": ["observe", "createSampler", "eventTypeToSamplingType", "observeErrors", "registerMachineContext", "unregisterMachineContext", "observeCLS", "observeLCP", "observeFID", "observeINP", "observeFCP", "observeTTFB", "vitalObservers", "createFetchTransport", "createConsoleTransport", "createMachine", "createEvent"]
4
+ "sourcesContent": ["/**\n * \uD83E\uDEBF svoose - Observability + State Machines for Svelte 5\n *\n * @packageDocumentation\n */\n\n// ============================================\n// Core Observability\n// ============================================\nexport { observe } from './observe/index.js';\nexport type { ObserveOptions } from './observe/index.js';\n\n// ============================================\n// Sampling\n// ============================================\nexport { createSampler, eventTypeToSamplingType } from './observe/index.js';\nexport type { SamplingConfig, SamplingOption, SamplingEventType, Sampler } from './observe/index.js';\n\n// ============================================\n// Error Tracking\n// ============================================\nexport {\n observeErrors,\n registerMachineContext,\n unregisterMachineContext,\n} from './observe/index.js';\nexport type {\n ObserveErrorEvent,\n ErrorEvent,\n UnhandledRejectionEvent,\n} from './observe/index.js';\n\n// ============================================\n// Web Vitals\n// ============================================\nexport {\n observeCLS,\n observeLCP,\n observeFID,\n observeINP,\n observeFCP,\n observeTTFB,\n vitalObservers,\n} from './observe/index.js';\nexport type { Metric, MetricName, MetricRating } from './observe/index.js';\n\n// ============================================\n// Transport\n// ============================================\nexport { createFetchTransport, createConsoleTransport } from './transport/index.js';\nexport type { Transport, TransportOptions } from './transport/index.js';\n\n// ============================================\n// State Machines\n// ============================================\nexport { createMachine, createEvent } from './machine/index.js';\nexport type {\n MachineConfig,\n Machine,\n EventObject,\n StateNode,\n TransitionConfig,\n InferStates,\n InferEvents,\n InferContext,\n} from './machine/index.js';\n\n// ============================================\n// Custom Metrics\n// ============================================\nexport { metric } from './metrics/index.js';\n\n// ============================================\n// Shared Types\n// ============================================\nexport type {\n VitalEvent,\n TransitionEvent,\n CustomMetricEvent,\n ObserveEvent,\n} from './types/index.js';\n"],
5
+ "mappings": "AASA,OAAS,WAAAA,MAAe,qBAMxB,OAAS,iBAAAC,EAAe,2BAAAC,MAA+B,qBAMvD,OACE,iBAAAC,EACA,0BAAAC,EACA,4BAAAC,MACK,qBAUP,OACE,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,cAAAC,EACA,eAAAC,EACA,kBAAAC,MACK,qBAMP,OAAS,wBAAAC,EAAsB,0BAAAC,MAA8B,uBAM7D,OAAS,iBAAAC,EAAe,eAAAC,MAAmB,qBAe3C,OAAS,UAAAC,MAAc",
6
+ "names": ["observe", "createSampler", "eventTypeToSamplingType", "observeErrors", "registerMachineContext", "unregisterMachineContext", "observeCLS", "observeLCP", "observeFID", "observeINP", "observeFCP", "observeTTFB", "vitalObservers", "createFetchTransport", "createConsoleTransport", "createMachine", "createEvent", "metric"]
7
7
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Metrics module exports
3
+ */
4
+ export { metric, setMetricEmitter, getMetricEmitter, _getPendingEventsCount, _clearPendingEvents, } from './metric.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{metric as r,setMetricEmitter as i,getMetricEmitter as n,_getPendingEventsCount as c,_clearPendingEvents as g}from"./metric.js";export{g as _clearPendingEvents,c as _getPendingEventsCount,n as getMetricEmitter,r as metric,i as setMetricEmitter};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/metrics/index.ts"],
4
+ "sourcesContent": ["/**\n * Metrics module exports\n */\n\nexport {\n metric,\n setMetricEmitter,\n getMetricEmitter,\n _getPendingEventsCount,\n _clearPendingEvents,\n} from './metric.js';\n"],
5
+ "mappings": "AAIA,OACE,UAAAA,EACA,oBAAAC,EACA,oBAAAC,EACA,0BAAAC,EACA,uBAAAC,MACK",
6
+ "names": ["metric", "setMetricEmitter", "getMetricEmitter", "_getPendingEventsCount", "_clearPendingEvents"]
7
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Custom metrics module
3
+ *
4
+ * Provides metric() function for sending custom events with pending buffer support.
5
+ */
6
+ import type { ObserveEvent } from '../types/index.js';
7
+ /**
8
+ * Set the metric emitter function
9
+ * Called by observe() to wire up the metric system
10
+ *
11
+ * @param emit - The emit function from observe(), or null to disconnect
12
+ */
13
+ export declare function setMetricEmitter(emit: ((event: ObserveEvent) => void) | null): void;
14
+ /**
15
+ * Send a custom metric event
16
+ *
17
+ * Events are automatically batched with other metrics and sent to your backend.
18
+ * If called before observe() is initialized, events are buffered (max 100).
19
+ *
20
+ * @param name - Metric name (e.g., 'checkout_started', 'button_clicked')
21
+ * @param data - Optional data payload
22
+ *
23
+ * @example
24
+ * metric('checkout_started', { step: 1, cartTotal: 99.99 });
25
+ * metric('button_clicked', { id: 'submit-btn' });
26
+ * metric('feature_used', { name: 'dark_mode', enabled: true });
27
+ */
28
+ /**
29
+ * Get the current metric emitter function (for internal use)
30
+ * @internal
31
+ */
32
+ export declare function getMetricEmitter(): ((event: ObserveEvent) => void) | null;
33
+ export declare function metric(name: string, data?: Record<string, unknown>): void;
34
+ /**
35
+ * Get number of pending events (for testing)
36
+ * @internal
37
+ */
38
+ export declare function _getPendingEventsCount(): number;
39
+ /**
40
+ * Clear pending events (for testing)
41
+ * @internal
42
+ */
43
+ export declare function _clearPendingEvents(): void;
44
+ //# sourceMappingURL=metric.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric.d.ts","sourceRoot":"","sources":["../../src/metrics/metric.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAqB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAWzE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAUnF;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAI,CAEzE;AAkBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAuB7E;AAMD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
@@ -0,0 +1,2 @@
1
+ let t=null;const e=[];function i(r){if(t=r,t&&e.length>0){const o=e.splice(0,e.length);for(const n of o)t(n)}}function v(){return t}function s(){try{return typeof process<"u",import.meta.env?.DEV===!0}catch{return!1}}function u(r,o={}){const n={type:"custom",name:r,data:o,timestamp:Date.now()};if(t)t(n);else{if(e.length>=100){s()&&console.warn("[svoose] metric() buffer full (100 events). Call observe() to start sending events. New events are being dropped.");return}e.push(n)}}function c(){return e.length}function l(){e.length=0}export{l as _clearPendingEvents,c as _getPendingEventsCount,v as getMetricEmitter,u as metric,i as setMetricEmitter};
2
+ //# sourceMappingURL=metric.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/metrics/metric.ts"],
4
+ "sourcesContent": ["/**\n * Custom metrics module\n *\n * Provides metric() function for sending custom events with pending buffer support.\n */\n\nimport type { CustomMetricEvent, ObserveEvent } from '../types/index.js';\n\n// Maximum pending events before dropping (with warning in dev)\nconst MAX_PENDING_EVENTS = 100;\n\n// Emitter function set by observe()\nlet emitter: ((event: ObserveEvent) => void) | null = null;\n\n// Pending events buffer for events sent before observe() is initialized\nconst pendingEvents: CustomMetricEvent[] = [];\n\n/**\n * Set the metric emitter function\n * Called by observe() to wire up the metric system\n *\n * @param emit - The emit function from observe(), or null to disconnect\n */\nexport function setMetricEmitter(emit: ((event: ObserveEvent) => void) | null): void {\n emitter = emit;\n\n // Flush pending events when emitter is set\n if (emitter && pendingEvents.length > 0) {\n const events = pendingEvents.splice(0, pendingEvents.length);\n for (const event of events) {\n emitter(event);\n }\n }\n}\n\n/**\n * Send a custom metric event\n *\n * Events are automatically batched with other metrics and sent to your backend.\n * If called before observe() is initialized, events are buffered (max 100).\n *\n * @param name - Metric name (e.g., 'checkout_started', 'button_clicked')\n * @param data - Optional data payload\n *\n * @example\n * metric('checkout_started', { step: 1, cartTotal: 99.99 });\n * metric('button_clicked', { id: 'submit-btn' });\n * metric('feature_used', { name: 'dark_mode', enabled: true });\n */\n/**\n * Get the current metric emitter function (for internal use)\n * @internal\n */\nexport function getMetricEmitter(): ((event: ObserveEvent) => void) | null {\n return emitter;\n}\n\ndeclare const process: { env: Record<string, string | undefined> } | undefined;\n\nfunction isDev(): boolean {\n try {\n if (typeof process !== 'undefined' && process?.env?.NODE_ENV === 'development') {\n return true;\n }\n if ((import.meta as any).env?.DEV === true) {\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport function metric(name: string, data: Record<string, unknown> = {}): void {\n const event: CustomMetricEvent = {\n type: 'custom',\n name,\n data,\n timestamp: Date.now(),\n };\n\n if (emitter) {\n emitter(event);\n } else {\n // Buffer event until observe() is initialized\n if (pendingEvents.length >= MAX_PENDING_EVENTS) {\n if (isDev()) {\n console.warn(\n `[svoose] metric() buffer full (${MAX_PENDING_EVENTS} events). ` +\n `Call observe() to start sending events. New events are being dropped.`\n );\n }\n return;\n }\n pendingEvents.push(event);\n }\n}\n\n// ============================================\n// Test helpers (not exported in production builds)\n// ============================================\n\n/**\n * Get number of pending events (for testing)\n * @internal\n */\nexport function _getPendingEventsCount(): number {\n return pendingEvents.length;\n}\n\n/**\n * Clear pending events (for testing)\n * @internal\n */\nexport function _clearPendingEvents(): void {\n pendingEvents.length = 0;\n}\n"],
5
+ "mappings": "AAYA,IAAIA,EAAkD,KAGtD,MAAMC,EAAqC,CAAC,EAQrC,SAASC,EAAiBC,EAAoD,CAInF,GAHAH,EAAUG,EAGNH,GAAWC,EAAc,OAAS,EAAG,CACvC,MAAMG,EAASH,EAAc,OAAO,EAAGA,EAAc,MAAM,EAC3D,UAAWI,KAASD,EAClBJ,EAAQK,CAAK,CAEjB,CACF,CAoBO,SAASC,GAA2D,CACzE,OAAON,CACT,CAIA,SAASO,GAAiB,CACxB,GAAI,CAIF,OAHI,OAAO,QAAY,IAGlB,YAAoB,KAAK,MAAQ,EAIxC,MAAQ,CACN,MAAO,EACT,CACF,CAEO,SAASC,EAAOC,EAAcC,EAAgC,CAAC,EAAS,CAC7E,MAAML,EAA2B,CAC/B,KAAM,SACN,KAAAI,EACA,KAAAC,EACA,UAAW,KAAK,IAAI,CACtB,EAEA,GAAIV,EACFA,EAAQK,CAAK,MACR,CAEL,GAAIJ,EAAc,QAAU,IAAoB,CAC1CM,EAAM,GACR,QAAQ,KACN,mHAEF,EAEF,MACF,CACAN,EAAc,KAAKI,CAAK,CAC1B,CACF,CAUO,SAASM,GAAiC,CAC/C,OAAOV,EAAc,MACvB,CAMO,SAASW,GAA4B,CAC1CX,EAAc,OAAS,CACzB",
6
+ "names": ["emitter", "pendingEvents", "setMetricEmitter", "emit", "events", "event", "getMetricEmitter", "isDev", "metric", "name", "data", "_getPendingEventsCount", "_clearPendingEvents"]
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"observe.svelte.d.ts","sourceRoot":"","sources":["../../src/observe/observe.svelte.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EAAE,cAAc,EAAc,YAAY,EAAa,MAAM,mBAAmB,CAAC;AAc7F,QAAA,IAAI,sBAAsB,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,sBAAsB,GAAG,IAAI,CAE/E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,sBAAsB,CAEjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,IAAI,CAkJhE;AAED,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"observe.svelte.d.ts","sourceRoot":"","sources":["../../src/observe/observe.svelte.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,OAAO,KAAK,EAAE,cAAc,EAAc,YAAY,EAAa,MAAM,mBAAmB,CAAC;AAc7F,QAAA,IAAI,sBAAsB,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,sBAAsB,GAAG,IAAI,CAE/E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,sBAAsB,CAEjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,IAAI,CA+KhE;AAED,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -1,2 +1,2 @@
1
- import{vitalObservers as h}from"./vitals.js";import{observeErrors as y}from"./errors.js";import{createFetchTransport as g}from"../transport/fetch.js";import{createSampler as O,eventTypeToSamplingType as E}from"./sampling.js";const u={endpoint:"/api/observe",vitals:!0,errors:!0,batchSize:10,flushInterval:5e3,sampleRate:1,debug:!1};let c=null;function f(i){c=i}function C(){return c}function L(i={}){if(Math.random()>(i.sampleRate??u.sampleRate))return()=>{};const t={...u,...i},d=t.transport??g(t.endpoint),p=t.sampling!=null?O(t.sampling):null,n=[],o=[];let a=null;const b=()=>{try{return typeof location<"u"?location.href:""}catch{return""}},v=e=>{if(!(t.filter&&!t.filter(e))){if(p){const r=E(e.type);if(r&&!p.shouldSample(r))return}t.debug&&console.log("[svoose]",e),o.push(e),o.length>=t.batchSize&&s()}},s=()=>{if(o.length===0)return;const e=o.splice(0,o.length),r=d.send(e);r&&typeof r.catch=="function"&&r.catch(l=>{t.debug&&console.error("[svoose] transport error:",l)})},m=e=>{const r={type:"vital",name:e.name,value:e.value,rating:e.rating,delta:e.delta,timestamp:e.timestamp,url:b()};v(r)};if(t.vitals){const e=t.vitals===!0?["CLS","LCP","FID","INP","FCP","TTFB"]:t.vitals;for(const r of e){const l=h[r];l&&n.push(l(m))}}if(t.errors&&n.push(y(e=>{v(e)})),f(v),n.push(()=>f(null)),a=setInterval(s,t.flushInterval),n.push(()=>{a&&clearInterval(a)}),typeof document<"u"){const e=()=>{document.visibilityState==="hidden"&&s()};document.addEventListener("visibilitychange",e),n.push(()=>{document.removeEventListener("visibilitychange",e)})}if(typeof window<"u"){const e=()=>{s()};window.addEventListener("beforeunload",e),n.push(()=>{window.removeEventListener("beforeunload",e)})}return()=>{s(),n.forEach(e=>e())}}export{C as getGlobalObserver,L as observe,f as setGlobalObserver};
1
+ import{vitalObservers as y}from"./vitals.js";import{observeErrors as O}from"./errors.js";import{createFetchTransport as E}from"../transport/fetch.js";import{createSampler as S,eventTypeToSamplingType as M}from"./sampling.js";import{createSessionManager as T}from"./session.js";import{setMetricEmitter as f,getMetricEmitter as I}from"../metrics/index.js";const c={endpoint:"/api/observe",vitals:!0,errors:!0,batchSize:10,flushInterval:5e3,sampleRate:1,debug:!1};let b=null;function d(l){b=l}function w(){return b}function N(l={}){if(Math.random()>(l.sampleRate??c.sampleRate))return()=>{};const t={...c,...l},m=t.transport??E(t.endpoint),u=t.sampling!=null?S(t.sampling):null,a=t.session!=null?T(t.session):null,n=[];a&&n.push(()=>a.destroy());const o=[];let v=null;const g=()=>{try{return typeof location<"u"?location.href:""}catch{return""}},s=e=>{if(!(t.filter&&!t.filter(e))){if(u){const r=M(e.type);if(r&&!u.shouldSample(r))return}a&&(e.sessionId=a.getSessionId()),t.debug&&console.log("[svoose]",e),o.push(e),o.length>=t.batchSize&&i()}},i=()=>{if(o.length===0)return;const e=o.splice(0,o.length),r=m.send(e);r&&typeof r.catch=="function"&&r.catch(p=>{t.debug&&console.error("[svoose] transport error:",p)})},h=e=>{const r={type:"vital",name:e.name,value:e.value,rating:e.rating,delta:e.delta,timestamp:e.timestamp,url:g()};s(r)};if(t.vitals){const e=t.vitals===!0?["CLS","LCP","FID","INP","FCP","TTFB"]:t.vitals;for(const r of e){const p=y[r];p&&n.push(p(h))}}if(t.errors&&n.push(O(e=>{s(e)})),d(s),n.push(()=>{w()===s&&d(null)}),f(s),n.push(()=>{I()===s&&f(null)}),v=setInterval(i,t.flushInterval),n.push(()=>{v&&clearInterval(v)}),typeof document<"u"){const e=()=>{document.visibilityState==="hidden"&&i()};document.addEventListener("visibilitychange",e),n.push(()=>{document.removeEventListener("visibilitychange",e)})}if(typeof window<"u"){const e=()=>{i()};window.addEventListener("beforeunload",e),n.push(()=>{window.removeEventListener("beforeunload",e)})}return()=>{i(),n.forEach(e=>e())}}export{w as getGlobalObserver,N as observe,d as setGlobalObserver};
2
2
  //# sourceMappingURL=observe.svelte.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/observe/observe.svelte.ts"],
4
- "sourcesContent": ["/**\n * Main observe() function - combines vitals, errors, and transport\n */\n\nimport { vitalObservers, type Metric, type MetricName } from './vitals.js';\nimport { observeErrors, type ObserveErrorEvent } from './errors.js';\nimport { createFetchTransport } from '../transport/fetch.js';\nimport {\n createSampler,\n eventTypeToSamplingType,\n type Sampler,\n} from './sampling.js';\nimport type { ObserveOptions, VitalEvent, ObserveEvent, Transport } from '../types/index.js';\n\n// Default configuration\nconst defaults = {\n endpoint: '/api/observe',\n vitals: true as const,\n errors: true,\n batchSize: 10,\n flushInterval: 5000,\n sampleRate: 1,\n debug: false,\n} satisfies Required<Omit<ObserveOptions, 'transport' | 'filter' | 'sampling'>>;\n\n// Global observer callback for state machines\nlet globalObserverCallback: ((event: ObserveEvent) => void) | null = null;\n\n/**\n * Set global observer callback for state machines\n * Called internally to connect machines to observe()\n */\nexport function setGlobalObserver(callback: typeof globalObserverCallback): void {\n globalObserverCallback = callback;\n}\n\n/**\n * Get global observer callback\n * Used by createMachine to send transition events\n */\nexport function getGlobalObserver(): typeof globalObserverCallback {\n return globalObserverCallback;\n}\n\n/**\n * Main observe function - starts collecting metrics and errors\n *\n * @param options - Configuration options\n * @returns Cleanup function to stop observing\n *\n * @example\n * // Basic usage\n * observe();\n *\n * @example\n * // With options\n * observe({\n * endpoint: '/api/metrics',\n * vitals: ['CLS', 'LCP', 'INP'],\n * errors: true,\n * debug: true,\n * });\n */\nexport function observe(options: ObserveOptions = {}): () => void {\n // Legacy sampleRate support (deprecated) - skip entire observer\n if (Math.random() > (options.sampleRate ?? defaults.sampleRate)) {\n return () => {};\n }\n\n const config = { ...defaults, ...options };\n const transport: Transport = config.transport ?? createFetchTransport(config.endpoint);\n\n // Create sampler if sampling option is provided\n const sampler: Sampler | null = config.sampling != null\n ? createSampler(config.sampling)\n : null;\n\n const cleanups: (() => void)[] = [];\n const buffer: ObserveEvent[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n // Get current URL\n const getUrl = (): string => {\n try {\n return typeof location !== 'undefined' ? location.href : '';\n } catch {\n return '';\n }\n };\n\n // Buffer an event and potentially flush\n const bufferEvent = (event: ObserveEvent): void => {\n // Apply filter if provided\n if (config.filter && !config.filter(event)) {\n return;\n }\n\n // Apply per-event-type sampling\n if (sampler) {\n const samplingType = eventTypeToSamplingType(event.type);\n if (samplingType && !sampler.shouldSample(samplingType)) {\n return;\n }\n }\n\n if (config.debug) {\n console.log('[svoose]', event);\n }\n\n buffer.push(event);\n\n if (buffer.length >= config.batchSize) {\n flush();\n }\n };\n\n // Send buffered events to transport\n const flush = (): void => {\n if (buffer.length === 0) return;\n\n const events = buffer.splice(0, buffer.length);\n // Handle both Promise and non-Promise returns from transport.send()\n const result = transport.send(events);\n if (result && typeof result.catch === 'function') {\n result.catch((err) => {\n if (config.debug) {\n console.error('[svoose] transport error:', err);\n }\n });\n }\n };\n\n // Convert metric to vital event\n const handleMetric = (metric: Metric): void => {\n const vitalEvent: VitalEvent = {\n type: 'vital',\n name: metric.name,\n value: metric.value,\n rating: metric.rating,\n delta: metric.delta,\n timestamp: metric.timestamp,\n url: getUrl(),\n };\n bufferEvent(vitalEvent);\n };\n\n // Setup vitals observers\n if (config.vitals) {\n const vitalsToObserve: MetricName[] =\n config.vitals === true\n ? ['CLS', 'LCP', 'FID', 'INP', 'FCP', 'TTFB']\n : config.vitals;\n\n for (const name of vitalsToObserve) {\n const observer = vitalObservers[name];\n if (observer) {\n cleanups.push(observer(handleMetric));\n }\n }\n }\n\n // Setup error observer\n if (config.errors) {\n cleanups.push(\n observeErrors((event: ObserveErrorEvent) => {\n bufferEvent(event);\n })\n );\n }\n\n // Setup global observer for state machines\n setGlobalObserver(bufferEvent);\n cleanups.push(() => setGlobalObserver(null));\n\n // Setup flush interval\n flushTimer = setInterval(flush, config.flushInterval);\n cleanups.push(() => {\n if (flushTimer) clearInterval(flushTimer);\n });\n\n // Flush on page visibility change (user navigating away)\n if (typeof document !== 'undefined') {\n const visibilityHandler = (): void => {\n if (document.visibilityState === 'hidden') {\n flush();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n cleanups.push(() => {\n document.removeEventListener('visibilitychange', visibilityHandler);\n });\n }\n\n // Flush on beforeunload\n if (typeof window !== 'undefined') {\n const unloadHandler = (): void => {\n flush();\n };\n window.addEventListener('beforeunload', unloadHandler);\n cleanups.push(() => {\n window.removeEventListener('beforeunload', unloadHandler);\n });\n }\n\n // Return cleanup function\n return () => {\n flush();\n cleanups.forEach((fn) => fn());\n };\n}\n\nexport type { ObserveOptions };\n"],
5
- "mappings": "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",
6
- "names": ["vitalObservers", "observeErrors", "createFetchTransport", "createSampler", "eventTypeToSamplingType", "defaults", "globalObserverCallback", "setGlobalObserver", "callback", "getGlobalObserver", "observe", "options", "config", "transport", "sampler", "cleanups", "buffer", "flushTimer", "getUrl", "bufferEvent", "event", "samplingType", "flush", "events", "result", "err", "handleMetric", "metric", "vitalEvent", "vitalsToObserve", "name", "observer", "visibilityHandler", "unloadHandler", "fn"]
4
+ "sourcesContent": ["/**\n * Main observe() function - combines vitals, errors, and transport\n */\n\nimport { vitalObservers, type Metric, type MetricName } from './vitals.js';\nimport { observeErrors, type ObserveErrorEvent } from './errors.js';\nimport { createFetchTransport } from '../transport/fetch.js';\nimport {\n createSampler,\n eventTypeToSamplingType,\n type Sampler,\n} from './sampling.js';\nimport { createSessionManager, type SessionManager } from './session.js';\nimport { setMetricEmitter, getMetricEmitter } from '../metrics/index.js';\nimport type { ObserveOptions, VitalEvent, ObserveEvent, Transport } from '../types/index.js';\n\n// Default configuration\nconst defaults = {\n endpoint: '/api/observe',\n vitals: true as const,\n errors: true,\n batchSize: 10,\n flushInterval: 5000,\n sampleRate: 1,\n debug: false,\n} satisfies Required<Omit<ObserveOptions, 'transport' | 'filter' | 'sampling' | 'session'>>;\n\n// Global observer callback for state machines\nlet globalObserverCallback: ((event: ObserveEvent) => void) | null = null;\n\n/**\n * Set global observer callback for state machines\n * Called internally to connect machines to observe()\n */\nexport function setGlobalObserver(callback: typeof globalObserverCallback): void {\n globalObserverCallback = callback;\n}\n\n/**\n * Get global observer callback\n * Used by createMachine to send transition events\n */\nexport function getGlobalObserver(): typeof globalObserverCallback {\n return globalObserverCallback;\n}\n\n/**\n * Main observe function - starts collecting metrics and errors\n *\n * @param options - Configuration options\n * @returns Cleanup function to stop observing\n *\n * @example\n * // Basic usage\n * observe();\n *\n * @example\n * // With options\n * observe({\n * endpoint: '/api/metrics',\n * vitals: ['CLS', 'LCP', 'INP'],\n * errors: true,\n * debug: true,\n * });\n */\nexport function observe(options: ObserveOptions = {}): () => void {\n // Legacy sampleRate support (deprecated) - skip entire observer\n if (Math.random() > (options.sampleRate ?? defaults.sampleRate)) {\n return () => {};\n }\n\n const config = { ...defaults, ...options };\n const transport: Transport = config.transport ?? createFetchTransport(config.endpoint);\n\n // Create sampler if sampling option is provided\n const sampler: Sampler | null = config.sampling != null\n ? createSampler(config.sampling)\n : null;\n\n // Create session manager if session option is provided\n const sessionManager: SessionManager | null = config.session != null\n ? createSessionManager(config.session)\n : null;\n\n const cleanups: (() => void)[] = [];\n\n // Cleanup session manager on destroy\n if (sessionManager) {\n cleanups.push(() => sessionManager.destroy());\n }\n const buffer: ObserveEvent[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n // Get current URL\n const getUrl = (): string => {\n try {\n return typeof location !== 'undefined' ? location.href : '';\n } catch {\n return '';\n }\n };\n\n // Buffer an event and potentially flush\n const bufferEvent = (event: ObserveEvent): void => {\n // Apply filter if provided\n if (config.filter && !config.filter(event)) {\n return;\n }\n\n // Apply per-event-type sampling\n if (sampler) {\n const samplingType = eventTypeToSamplingType(event.type);\n if (samplingType && !sampler.shouldSample(samplingType)) {\n return;\n }\n }\n\n // Add sessionId if session manager is enabled\n if (sessionManager) {\n (event as ObserveEvent & { sessionId?: string }).sessionId = sessionManager.getSessionId();\n }\n\n if (config.debug) {\n console.log('[svoose]', event);\n }\n\n buffer.push(event);\n\n if (buffer.length >= config.batchSize) {\n flush();\n }\n };\n\n // Send buffered events to transport\n const flush = (): void => {\n if (buffer.length === 0) return;\n\n const events = buffer.splice(0, buffer.length);\n // Handle both Promise and non-Promise returns from transport.send()\n const result = transport.send(events);\n if (result && typeof result.catch === 'function') {\n result.catch((err) => {\n if (config.debug) {\n console.error('[svoose] transport error:', err);\n }\n });\n }\n };\n\n // Convert metric to vital event\n const handleMetric = (metric: Metric): void => {\n const vitalEvent: VitalEvent = {\n type: 'vital',\n name: metric.name,\n value: metric.value,\n rating: metric.rating,\n delta: metric.delta,\n timestamp: metric.timestamp,\n url: getUrl(),\n };\n bufferEvent(vitalEvent);\n };\n\n // Setup vitals observers\n if (config.vitals) {\n const vitalsToObserve: MetricName[] =\n config.vitals === true\n ? ['CLS', 'LCP', 'FID', 'INP', 'FCP', 'TTFB']\n : config.vitals;\n\n for (const name of vitalsToObserve) {\n const observer = vitalObservers[name];\n if (observer) {\n cleanups.push(observer(handleMetric));\n }\n }\n }\n\n // Setup error observer\n if (config.errors) {\n cleanups.push(\n observeErrors((event: ObserveErrorEvent) => {\n bufferEvent(event);\n })\n );\n }\n\n // Setup global observer for state machines\n setGlobalObserver(bufferEvent);\n cleanups.push(() => {\n // Only clear if we're still the active observer\n if (getGlobalObserver() === bufferEvent) {\n setGlobalObserver(null);\n }\n });\n\n // Setup metric emitter for custom metrics\n setMetricEmitter(bufferEvent);\n cleanups.push(() => {\n // Only clear if we're still the active emitter\n if (getMetricEmitter() === bufferEvent) {\n setMetricEmitter(null);\n }\n });\n\n // Setup flush interval\n flushTimer = setInterval(flush, config.flushInterval);\n cleanups.push(() => {\n if (flushTimer) clearInterval(flushTimer);\n });\n\n // Flush on page visibility change (user navigating away)\n if (typeof document !== 'undefined') {\n const visibilityHandler = (): void => {\n if (document.visibilityState === 'hidden') {\n flush();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n cleanups.push(() => {\n document.removeEventListener('visibilitychange', visibilityHandler);\n });\n }\n\n // Flush on beforeunload\n if (typeof window !== 'undefined') {\n const unloadHandler = (): void => {\n flush();\n };\n window.addEventListener('beforeunload', unloadHandler);\n cleanups.push(() => {\n window.removeEventListener('beforeunload', unloadHandler);\n });\n }\n\n // Return cleanup function\n return () => {\n flush();\n cleanups.forEach((fn) => fn());\n };\n}\n\nexport type { ObserveOptions };\n"],
5
+ "mappings": "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",
6
+ "names": ["vitalObservers", "observeErrors", "createFetchTransport", "createSampler", "eventTypeToSamplingType", "createSessionManager", "setMetricEmitter", "getMetricEmitter", "defaults", "globalObserverCallback", "setGlobalObserver", "callback", "getGlobalObserver", "observe", "options", "config", "transport", "sampler", "sessionManager", "cleanups", "buffer", "flushTimer", "getUrl", "bufferEvent", "event", "samplingType", "flush", "events", "result", "err", "handleMetric", "metric", "vitalEvent", "vitalsToObserve", "name", "observer", "visibilityHandler", "unloadHandler", "fn"]
7
7
  }
@@ -1,26 +1,8 @@
1
1
  /**
2
2
  * Sampling utilities for rate limiting events by type
3
3
  */
4
- /**
5
- * Per-event-type sampling rates
6
- * Each rate is a number between 0 and 1 (0 = disabled, 1 = all)
7
- */
8
- export interface SamplingConfig {
9
- /** Sampling rate for Web Vitals events (default: 1) */
10
- vitals?: number;
11
- /** Sampling rate for error events (default: 1) */
12
- errors?: number;
13
- /** Sampling rate for custom metric events (default: 1) */
14
- custom?: number;
15
- /** Sampling rate for state machine transition events (default: 1) */
16
- transitions?: number;
17
- /** Sampling rate for identify events (default: 1) */
18
- identify?: number;
19
- }
20
- /**
21
- * Sampling option - either a single rate for all events or per-type config
22
- */
23
- export type SamplingOption = number | SamplingConfig;
4
+ import type { SamplingConfig, SamplingOption } from '../types/index.js';
5
+ export type { SamplingConfig, SamplingOption };
24
6
  /**
25
7
  * Event types that can be sampled
26
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.d.ts","sourceRoot":"","sources":["../../src/observe/sampling.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,cAAc,CAAC;AAMrD;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAEpD;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,CAAC;CAC/C;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAmC7D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB,iBAAiB,GAAG,IAAI,CAgB1B"}
1
+ {"version":3,"file":"sampling.d.ts","sourceRoot":"","sources":["../../src/observe/sampling.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAEpD;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,CAAC;CAC/C;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAmC7D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB,iBAAiB,GAAG,IAAI,CAgB1B"}
@@ -1,2 +1,2 @@
1
- function t(e){return e<=0?0:e>=1?1:e}function a(e){const i=typeof e=="number"?{vitals:t(e),errors:t(e),custom:t(e),transitions:t(e),identify:1}:{vitals:t(e.vitals??1),errors:t(e.errors??1),custom:t(e.custom??1),transitions:t(e.transitions??1),identify:t(e.identify??1)};return{shouldSample(n){const r=i[n];return r>=1?!0:r<=0?!1:Math.random()<r},getRate(n){return i[n]}}}function u(e){switch(e){case"vital":return"vitals";case"error":case"unhandled-rejection":return"errors";case"custom":return"custom";case"transition":return"transitions";case"identify":return"identify";default:return null}}export{a as createSampler,u as eventTypeToSamplingType};
1
+ function t(e){return e<=0?0:e>=1?1:e}function a(e){const i=typeof e=="number"?{vitals:t(e),errors:t(e),custom:t(e),transitions:t(e),identify:1}:{vitals:t(e.vitals??1),errors:t(e.errors??1),custom:t(e.custom??1),transitions:t(e.transitions??1),identify:t(e.identify??1)};return{shouldSample(n){const r=i[n];return r>=1?!0:r<=0?!1:Math.random()<r},getRate(n){return i[n]}}}function p(e){switch(e){case"vital":return"vitals";case"error":case"unhandled-rejection":return"errors";case"custom":return"custom";case"transition":return"transitions";case"identify":return"identify";default:return null}}export{a as createSampler,p as eventTypeToSamplingType};
2
2
  //# sourceMappingURL=sampling.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/observe/sampling.ts"],
4
- "sourcesContent": ["/**\n * Sampling utilities for rate limiting events by type\n */\n\n// ============================================\n// Types\n// ============================================\n\n/**\n * Per-event-type sampling rates\n * Each rate is a number between 0 and 1 (0 = disabled, 1 = all)\n */\nexport interface SamplingConfig {\n /** Sampling rate for Web Vitals events (default: 1) */\n vitals?: number;\n /** Sampling rate for error events (default: 1) */\n errors?: number;\n /** Sampling rate for custom metric events (default: 1) */\n custom?: number;\n /** Sampling rate for state machine transition events (default: 1) */\n transitions?: number;\n /** Sampling rate for identify events (default: 1) */\n identify?: number;\n}\n\n/**\n * Sampling option - either a single rate for all events or per-type config\n */\nexport type SamplingOption = number | SamplingConfig;\n\n/**\n * Event types that can be sampled\n */\nexport type SamplingEventType = keyof SamplingConfig;\n\n// ============================================\n// Sampler\n// ============================================\n\n/**\n * Sampler interface returned by createSampler\n */\nexport interface Sampler {\n /**\n * Check if an event should be sampled (included)\n * @param eventType - The type of event\n * @returns true if the event should be included, false if dropped\n */\n shouldSample(eventType: SamplingEventType): boolean;\n\n /**\n * Get the sampling rate for an event type\n * @param eventType - The type of event\n * @returns The rate (0-1)\n */\n getRate(eventType: SamplingEventType): number;\n}\n\n/**\n * Normalize a sampling rate to be between 0 and 1\n */\nfunction normalizeRate(rate: number): number {\n if (rate <= 0) return 0;\n if (rate >= 1) return 1;\n return rate;\n}\n\n/**\n * Create a sampler instance for filtering events by sampling rate\n *\n * @param config - Sampling configuration (number or per-type config)\n * @returns Sampler instance\n *\n * @example\n * // Simple - same rate for all events\n * const sampler = createSampler(0.1); // 10% of all events\n *\n * @example\n * // Per-event-type rates\n * const sampler = createSampler({\n * vitals: 0.1, // 10% of vitals\n * errors: 1.0, // 100% of errors\n * custom: 0.5, // 50% of custom metrics\n * transitions: 0.0, // disabled\n * });\n */\nexport function createSampler(config: SamplingOption): Sampler {\n // Build rates object with defaults\n const rates: Required<SamplingConfig> =\n typeof config === 'number'\n ? {\n vitals: normalizeRate(config),\n errors: normalizeRate(config),\n custom: normalizeRate(config),\n transitions: normalizeRate(config),\n identify: 1, // Always send identify events by default\n }\n : {\n vitals: normalizeRate(config.vitals ?? 1),\n errors: normalizeRate(config.errors ?? 1),\n custom: normalizeRate(config.custom ?? 1),\n transitions: normalizeRate(config.transitions ?? 1),\n identify: normalizeRate(config.identify ?? 1),\n };\n\n return {\n shouldSample(eventType: SamplingEventType): boolean {\n const rate = rates[eventType];\n\n // Fast paths\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n\n // Random sampling\n return Math.random() < rate;\n },\n\n getRate(eventType: SamplingEventType): number {\n return rates[eventType];\n },\n };\n}\n\n/**\n * Map ObserveEvent.type to SamplingEventType\n */\nexport function eventTypeToSamplingType(\n eventType: string\n): SamplingEventType | null {\n switch (eventType) {\n case 'vital':\n return 'vitals';\n case 'error':\n case 'unhandled-rejection':\n return 'errors';\n case 'custom':\n return 'custom';\n case 'transition':\n return 'transitions';\n case 'identify':\n return 'identify';\n default:\n return null;\n }\n}\n"],
5
- "mappings": "AA6DA,SAASA,EAAcC,EAAsB,CAC3C,OAAIA,GAAQ,EAAU,EAClBA,GAAQ,EAAU,EACfA,CACT,CAqBO,SAASC,EAAcC,EAAiC,CAE7D,MAAMC,EACJ,OAAOD,GAAW,SACd,CACE,OAAQH,EAAcG,CAAM,EAC5B,OAAQH,EAAcG,CAAM,EAC5B,OAAQH,EAAcG,CAAM,EAC5B,YAAaH,EAAcG,CAAM,EACjC,SAAU,CACZ,EACA,CACE,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,YAAaH,EAAcG,EAAO,aAAe,CAAC,EAClD,SAAUH,EAAcG,EAAO,UAAY,CAAC,CAC9C,EAEN,MAAO,CACL,aAAaE,EAAuC,CAClD,MAAMJ,EAAOG,EAAMC,CAAS,EAG5B,OAAIJ,GAAQ,EAAU,GAClBA,GAAQ,EAAU,GAGf,KAAK,OAAO,EAAIA,CACzB,EAEA,QAAQI,EAAsC,CAC5C,OAAOD,EAAMC,CAAS,CACxB,CACF,CACF,CAKO,SAASC,EACdD,EAC0B,CAC1B,OAAQA,EAAW,CACjB,IAAK,QACH,MAAO,SACT,IAAK,QACL,IAAK,sBACH,MAAO,SACT,IAAK,SACH,MAAO,SACT,IAAK,aACH,MAAO,cACT,IAAK,WACH,MAAO,WACT,QACE,OAAO,IACX,CACF",
4
+ "sourcesContent": ["/**\n * Sampling utilities for rate limiting events by type\n */\n\nimport type { SamplingConfig, SamplingOption } from '../types/index.js';\n\n// Re-export types from canonical source\nexport type { SamplingConfig, SamplingOption };\n\n/**\n * Event types that can be sampled\n */\nexport type SamplingEventType = keyof SamplingConfig;\n\n/**\n * Sampler interface returned by createSampler\n */\nexport interface Sampler {\n /**\n * Check if an event should be sampled (included)\n * @param eventType - The type of event\n * @returns true if the event should be included, false if dropped\n */\n shouldSample(eventType: SamplingEventType): boolean;\n\n /**\n * Get the sampling rate for an event type\n * @param eventType - The type of event\n * @returns The rate (0-1)\n */\n getRate(eventType: SamplingEventType): number;\n}\n\n/**\n * Normalize a sampling rate to be between 0 and 1\n */\nfunction normalizeRate(rate: number): number {\n if (rate <= 0) return 0;\n if (rate >= 1) return 1;\n return rate;\n}\n\n/**\n * Create a sampler instance for filtering events by sampling rate\n *\n * @param config - Sampling configuration (number or per-type config)\n * @returns Sampler instance\n *\n * @example\n * // Simple - same rate for all events\n * const sampler = createSampler(0.1); // 10% of all events\n *\n * @example\n * // Per-event-type rates\n * const sampler = createSampler({\n * vitals: 0.1, // 10% of vitals\n * errors: 1.0, // 100% of errors\n * custom: 0.5, // 50% of custom metrics\n * transitions: 0.0, // disabled\n * });\n */\nexport function createSampler(config: SamplingOption): Sampler {\n // Build rates object with defaults\n const rates: Required<SamplingConfig> =\n typeof config === 'number'\n ? {\n vitals: normalizeRate(config),\n errors: normalizeRate(config),\n custom: normalizeRate(config),\n transitions: normalizeRate(config),\n identify: 1, // Always send identify events by default\n }\n : {\n vitals: normalizeRate(config.vitals ?? 1),\n errors: normalizeRate(config.errors ?? 1),\n custom: normalizeRate(config.custom ?? 1),\n transitions: normalizeRate(config.transitions ?? 1),\n identify: normalizeRate(config.identify ?? 1),\n };\n\n return {\n shouldSample(eventType: SamplingEventType): boolean {\n const rate = rates[eventType];\n\n // Fast paths\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n\n // Random sampling\n return Math.random() < rate;\n },\n\n getRate(eventType: SamplingEventType): number {\n return rates[eventType];\n },\n };\n}\n\n/**\n * Map ObserveEvent.type to SamplingEventType\n */\nexport function eventTypeToSamplingType(\n eventType: string\n): SamplingEventType | null {\n switch (eventType) {\n case 'vital':\n return 'vitals';\n case 'error':\n case 'unhandled-rejection':\n return 'errors';\n case 'custom':\n return 'custom';\n case 'transition':\n return 'transitions';\n case 'identify':\n return 'identify';\n default:\n return null;\n }\n}\n"],
5
+ "mappings": "AAoCA,SAASA,EAAcC,EAAsB,CAC3C,OAAIA,GAAQ,EAAU,EAClBA,GAAQ,EAAU,EACfA,CACT,CAqBO,SAASC,EAAcC,EAAiC,CAE7D,MAAMC,EACJ,OAAOD,GAAW,SACd,CACE,OAAQH,EAAcG,CAAM,EAC5B,OAAQH,EAAcG,CAAM,EAC5B,OAAQH,EAAcG,CAAM,EAC5B,YAAaH,EAAcG,CAAM,EACjC,SAAU,CACZ,EACA,CACE,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,OAAQH,EAAcG,EAAO,QAAU,CAAC,EACxC,YAAaH,EAAcG,EAAO,aAAe,CAAC,EAClD,SAAUH,EAAcG,EAAO,UAAY,CAAC,CAC9C,EAEN,MAAO,CACL,aAAaE,EAAuC,CAClD,MAAMJ,EAAOG,EAAMC,CAAS,EAG5B,OAAIJ,GAAQ,EAAU,GAClBA,GAAQ,EAAU,GAGf,KAAK,OAAO,EAAIA,CACzB,EAEA,QAAQI,EAAsC,CAC5C,OAAOD,EAAMC,CAAS,CACxB,CACF,CACF,CAKO,SAASC,EACdD,EAC0B,CAC1B,OAAQA,EAAW,CACjB,IAAK,QACH,MAAO,SACT,IAAK,QACL,IAAK,sBACH,MAAO,SACT,IAAK,SACH,MAAO,SACT,IAAK,aACH,MAAO,cACT,IAAK,WACH,MAAO,WACT,QACE,OAAO,IACX,CACF",
6
6
  "names": ["normalizeRate", "rate", "createSampler", "config", "rates", "eventType", "eventTypeToSamplingType"]
7
7
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Session Tracking - automatic sessionId generation with timeout
3
+ */
4
+ import type { SessionConfig, SessionOption } from '../types/index.js';
5
+ export type { SessionConfig, SessionOption };
6
+ export interface SessionManager {
7
+ /** Get current session ID (creates new session if expired) */
8
+ getSessionId: () => string;
9
+ /** Force create a new session */
10
+ reset: () => string;
11
+ /** Destroy session manager and clear storage */
12
+ destroy: () => void;
13
+ }
14
+ /**
15
+ * Create a session manager
16
+ *
17
+ * @param config - Session configuration (true = defaults, false = disabled)
18
+ * @returns SessionManager or null if disabled
19
+ *
20
+ * @example
21
+ * // Enable with defaults
22
+ * const session = createSessionManager(true);
23
+ * session?.getSessionId(); // "1706123456789-abc123def"
24
+ *
25
+ * @example
26
+ * // Custom config
27
+ * const session = createSessionManager({
28
+ * timeout: 60 * 60 * 1000, // 1 hour
29
+ * storage: 'localStorage',
30
+ * });
31
+ */
32
+ export declare function createSessionManager(config: SessionOption): SessionManager | null;
33
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/observe/session.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGtE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAW7C,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,iCAAiC;IACjC,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,gDAAgD;IAChD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAYD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc,GAAG,IAAI,CAuIjF"}
@@ -0,0 +1,2 @@
1
+ const a="svoose_session";function S(){const s=Date.now(),o=Math.random().toString(36).slice(2,11);return`${s}-${o}`}function f(s){if(s===!1)return null;const o=s===!0?{timeout:18e5,storage:"sessionStorage"}:{timeout:s.timeout??18e5,storage:s.storage??"sessionStorage"};let e=null;function i(){if(o.storage==="memory"||typeof window>"u")return null;try{const t=o.storage==="localStorage"?localStorage:sessionStorage,n="__svoose_test__";return t.setItem(n,"1"),t.removeItem(n),t}catch{return null}}function c(){const t=i();if(!t)return null;try{const n=t.getItem(a);if(!n)return null;const r=JSON.parse(n);return typeof r.id!="string"||typeof r.startedAt!="number"||typeof r.lastActivity!="number"?null:r}catch{return null}}function u(t){const n=i();if(n)try{n.setItem(a,JSON.stringify(t))}catch{}}function l(){const t=Date.now(),n={id:S(),startedAt:t,lastActivity:t};return u(n),n}function g(t){return Date.now()-t.lastActivity>o.timeout}return{getSessionId(){const t=Date.now();return e||(e=c()),!e||g(e)?(e=l(),e.id):(e.lastActivity=t,u(e),e.id)},reset(){return e=l(),e.id},destroy(){e=null;const t=i();if(t)try{t.removeItem(a)}catch{}}}}export{f as createSessionManager};
2
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/observe/session.ts"],
4
+ "sourcesContent": ["/**\n * Session Tracking - automatic sessionId generation with timeout\n */\n\nimport type { SessionConfig, SessionOption } from '../types/index.js';\n\n// Re-export types from canonical source\nexport type { SessionConfig, SessionOption };\n\ninterface Session {\n id: string;\n startedAt: number;\n lastActivity: number;\n}\n\nconst DEFAULT_TIMEOUT = 30 * 60 * 1000; // 30 minutes\nconst STORAGE_KEY = 'svoose_session';\n\nexport interface SessionManager {\n /** Get current session ID (creates new session if expired) */\n getSessionId: () => string;\n /** Force create a new session */\n reset: () => string;\n /** Destroy session manager and clear storage */\n destroy: () => void;\n}\n\n/**\n * Generate a unique session ID\n * Format: timestamp-randomString (e.g., \"1706123456789-abc123def\")\n */\nfunction generateId(): string {\n const timestamp = Date.now();\n const random = Math.random().toString(36).slice(2, 11);\n return `${timestamp}-${random}`;\n}\n\n/**\n * Create a session manager\n *\n * @param config - Session configuration (true = defaults, false = disabled)\n * @returns SessionManager or null if disabled\n *\n * @example\n * // Enable with defaults\n * const session = createSessionManager(true);\n * session?.getSessionId(); // \"1706123456789-abc123def\"\n *\n * @example\n * // Custom config\n * const session = createSessionManager({\n * timeout: 60 * 60 * 1000, // 1 hour\n * storage: 'localStorage',\n * });\n */\nexport function createSessionManager(config: SessionOption): SessionManager | null {\n // Disabled\n if (config === false) return null;\n\n // Normalize config\n const opts: SessionConfig = config === true\n ? { timeout: DEFAULT_TIMEOUT, storage: 'sessionStorage' }\n : {\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n storage: config.storage ?? 'sessionStorage',\n };\n\n let currentSession: Session | null = null;\n\n /**\n * Get storage instance (with SSR and private mode safety)\n */\n function getStorage(): Storage | null {\n if (opts.storage === 'memory') return null;\n if (typeof window === 'undefined') return null;\n\n try {\n const storage = opts.storage === 'localStorage' ? localStorage : sessionStorage;\n // Test if storage is available (throws in private mode on some browsers)\n const testKey = '__svoose_test__';\n storage.setItem(testKey, '1');\n storage.removeItem(testKey);\n return storage;\n } catch {\n return null;\n }\n }\n\n /**\n * Load session from storage\n */\n function load(): Session | null {\n const storage = getStorage();\n if (!storage) return null;\n\n try {\n const data = storage.getItem(STORAGE_KEY);\n if (!data) return null;\n\n const session = JSON.parse(data) as Session;\n\n // Validate session structure\n if (\n typeof session.id !== 'string' ||\n typeof session.startedAt !== 'number' ||\n typeof session.lastActivity !== 'number'\n ) {\n return null;\n }\n\n return session;\n } catch {\n return null;\n }\n }\n\n /**\n * Save session to storage\n */\n function save(session: Session): void {\n const storage = getStorage();\n if (!storage) return;\n\n try {\n storage.setItem(STORAGE_KEY, JSON.stringify(session));\n } catch {\n // Quota exceeded - fail silently\n }\n }\n\n /**\n * Create a new session\n */\n function createNew(): Session {\n const now = Date.now();\n const session: Session = {\n id: generateId(),\n startedAt: now,\n lastActivity: now,\n };\n save(session);\n return session;\n }\n\n /**\n * Check if session is expired\n */\n function isExpired(session: Session): boolean {\n const now = Date.now();\n return now - session.lastActivity > opts.timeout;\n }\n\n return {\n getSessionId(): string {\n const now = Date.now();\n\n // Try to load from storage if not in memory\n if (!currentSession) {\n currentSession = load();\n }\n\n // Create new session if none exists or expired\n if (!currentSession || isExpired(currentSession)) {\n currentSession = createNew();\n return currentSession.id;\n }\n\n // Update last activity\n currentSession.lastActivity = now;\n save(currentSession);\n return currentSession.id;\n },\n\n reset(): string {\n currentSession = createNew();\n return currentSession.id;\n },\n\n destroy(): void {\n currentSession = null;\n const storage = getStorage();\n if (storage) {\n try {\n storage.removeItem(STORAGE_KEY);\n } catch {\n // Ignore errors\n }\n }\n },\n };\n}\n"],
5
+ "mappings": "AAgBA,MAAMA,EAAc,iBAepB,SAASC,GAAqB,CAC5B,MAAMC,EAAY,KAAK,IAAI,EACrBC,EAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,EACrD,MAAO,GAAGD,CAAS,IAAIC,CAAM,EAC/B,CAoBO,SAASC,EAAqBC,EAA8C,CAEjF,GAAIA,IAAW,GAAO,OAAO,KAG7B,MAAMC,EAAsBD,IAAW,GACnC,CAAE,QAAS,KAAiB,QAAS,gBAAiB,EACtD,CACE,QAASA,EAAO,SAAW,KAC3B,QAASA,EAAO,SAAW,gBAC7B,EAEJ,IAAIE,EAAiC,KAKrC,SAASC,GAA6B,CAEpC,GADIF,EAAK,UAAY,UACjB,OAAO,OAAW,IAAa,OAAO,KAE1C,GAAI,CACF,MAAMG,EAAUH,EAAK,UAAY,eAAiB,aAAe,eAE3DI,EAAU,kBAChB,OAAAD,EAAQ,QAAQC,EAAS,GAAG,EAC5BD,EAAQ,WAAWC,CAAO,EACnBD,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAKA,SAASE,GAAuB,CAC9B,MAAMF,EAAUD,EAAW,EAC3B,GAAI,CAACC,EAAS,OAAO,KAErB,GAAI,CACF,MAAMG,EAAOH,EAAQ,QAAQT,CAAW,EACxC,GAAI,CAACY,EAAM,OAAO,KAElB,MAAMC,EAAU,KAAK,MAAMD,CAAI,EAG/B,OACE,OAAOC,EAAQ,IAAO,UACtB,OAAOA,EAAQ,WAAc,UAC7B,OAAOA,EAAQ,cAAiB,SAEzB,KAGFA,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAKA,SAASC,EAAKD,EAAwB,CACpC,MAAMJ,EAAUD,EAAW,EAC3B,GAAKC,EAEL,GAAI,CACFA,EAAQ,QAAQT,EAAa,KAAK,UAAUa,CAAO,CAAC,CACtD,MAAQ,CAER,CACF,CAKA,SAASE,GAAqB,CAC5B,MAAMC,EAAM,KAAK,IAAI,EACfH,EAAmB,CACvB,GAAIZ,EAAW,EACf,UAAWe,EACX,aAAcA,CAChB,EACA,OAAAF,EAAKD,CAAO,EACLA,CACT,CAKA,SAASI,EAAUJ,EAA2B,CAE5C,OADY,KAAK,IAAI,EACRA,EAAQ,aAAeP,EAAK,OAC3C,CAEA,MAAO,CACL,cAAuB,CACrB,MAAMU,EAAM,KAAK,IAAI,EAQrB,OALKT,IACHA,EAAiBI,EAAK,GAIpB,CAACJ,GAAkBU,EAAUV,CAAc,GAC7CA,EAAiBQ,EAAU,EACpBR,EAAe,KAIxBA,EAAe,aAAeS,EAC9BF,EAAKP,CAAc,EACZA,EAAe,GACxB,EAEA,OAAgB,CACd,OAAAA,EAAiBQ,EAAU,EACpBR,EAAe,EACxB,EAEA,SAAgB,CACdA,EAAiB,KACjB,MAAME,EAAUD,EAAW,EAC3B,GAAIC,EACF,GAAI,CACFA,EAAQ,WAAWT,CAAW,CAChC,MAAQ,CAER,CAEJ,CACF,CACF",
6
+ "names": ["STORAGE_KEY", "generateId", "timestamp", "random", "createSessionManager", "config", "opts", "currentSession", "getStorage", "storage", "testKey", "load", "data", "session", "save", "createNew", "now", "isExpired"]
7
+ }
@@ -5,12 +5,27 @@
5
5
  import type { MetricName, MetricRating, Metric } from '../types/index.js';
6
6
  /**
7
7
  * CLS - Cumulative Layout Shift
8
- * Measures visual stability
8
+ * Measures visual stability using session windows algorithm (web-vitals standard)
9
+ *
10
+ * Session window rules:
11
+ * - Max duration: 5 seconds
12
+ * - Max gap between shifts: 1 second
13
+ * - Reports final value on visibility change (hidden) or pagehide
14
+ *
15
+ * @see https://web.dev/cls/#what-is-a-good-cls-score
16
+ * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onCLS.ts
9
17
  */
10
18
  export declare function observeCLS(callback: (metric: Metric) => void): () => void;
11
19
  /**
12
20
  * LCP - Largest Contentful Paint
13
21
  * Measures loading performance
22
+ *
23
+ * LCP is finalized when:
24
+ * - User interacts with the page (click, keydown, scroll, etc.)
25
+ * - Page becomes hidden (visibility change)
26
+ *
27
+ * @see https://web.dev/lcp/
28
+ * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onLCP.ts
14
29
  */
15
30
  export declare function observeLCP(callback: (metric: Metric) => void): () => void;
16
31
  /**
@@ -21,6 +36,17 @@ export declare function observeFID(callback: (metric: Metric) => void): () => vo
21
36
  /**
22
37
  * INP - Interaction to Next Paint
23
38
  * Measures responsiveness (replaced FID as Core Web Vital)
39
+ *
40
+ * INP tracks the worst interaction latency during the page lifecycle.
41
+ * Reports on visibility change (hidden) or pagehide.
42
+ *
43
+ * Simplified algorithm (vs web-vitals p98):
44
+ * - Tracks max interaction duration
45
+ * - Only counts discrete events (click, keydown, pointerdown)
46
+ * - Reports once on page hide
47
+ *
48
+ * @see https://web.dev/inp/
49
+ * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onINP.ts
24
50
  */
25
51
  export declare function observeINP(callback: (metric: Metric) => void): () => void;
26
52
  /**
@@ -30,7 +56,15 @@ export declare function observeINP(callback: (metric: Metric) => void): () => vo
30
56
  export declare function observeFCP(callback: (metric: Metric) => void): () => void;
31
57
  /**
32
58
  * TTFB - Time to First Byte
33
- * Measures server response time
59
+ * Measures server response time from navigation start
60
+ *
61
+ * TTFB = responseStart - activationStart (or 0 if no bfcache)
62
+ *
63
+ * activationStart is non-zero for bfcache restores, ensuring we measure
64
+ * the actual server response time, not time in cache.
65
+ *
66
+ * @see https://web.dev/ttfb/
67
+ * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onTTFB.ts
34
68
  */
35
69
  export declare function observeTTFB(callback: (metric: Metric) => void): () => void;
36
70
  export declare const vitalObservers: {
@@ -1 +1 @@
1
- {"version":3,"file":"vitals.d.ts","sourceRoot":"","sources":["../../src/observe/vitals.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA2C1E;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAmBzE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAkBzE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAczE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAmBzE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAczE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAc1E;AASD,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAC;AAEX,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"vitals.d.ts","sourceRoot":"","sources":["../../src/observe/vitals.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA2C1E;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CA4FzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CA8DzE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAczE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CA8DzE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAczE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAgB1E;AAcD,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAC;AAEX,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC"}
@@ -1,2 +1,2 @@
1
- const f={CLS:[.1,.25],LCP:[2500,4e3],FID:[100,300],INP:[200,500],FCP:[1800,3e3],TTFB:[800,1800]};function u(r,e){const[n,t]=f[r];return e<=n?"good":e<=t?"needs-improvement":"poor"}function s(r,e,n=0){return{name:r,value:e,rating:u(r,e),delta:e-n,timestamp:Date.now()}}function c(r){if(typeof PerformanceObserver>"u")return!1;try{return PerformanceObserver.supportedEntryTypes?.includes(r)??!1}catch{return!1}}function v(r){if(!c("layout-shift"))return()=>{};let e=0,n=0;const t=new PerformanceObserver(o=>{for(const i of o.getEntries())i.hadRecentInput||(e+=i.value);r(s("CLS",e,n)),n=e});return t.observe({type:"layout-shift",buffered:!0}),()=>t.disconnect()}function d(r){if(!c("largest-contentful-paint"))return()=>{};let e=0;const n=new PerformanceObserver(t=>{const o=t.getEntries(),i=o[o.length-1];if(i){const a=i.startTime;r(s("LCP",a,e)),e=a}});return n.observe({type:"largest-contentful-paint",buffered:!0}),()=>n.disconnect()}function m(r){if(!c("first-input"))return()=>{};const e=new PerformanceObserver(n=>{const t=n.getEntries()[0];if(t){const o=t.processingStart-t.startTime;r(s("FID",o)),e.disconnect()}});return e.observe({type:"first-input",buffered:!0}),()=>e.disconnect()}function p(r){if(!c("event"))return()=>{};let e=0,n=0;const t=new PerformanceObserver(o=>{for(const i of o.getEntries()){const a=i.duration;a>e&&(e=a,r(s("INP",e,n)),n=e)}});return t.observe({type:"event",buffered:!0}),()=>t.disconnect()}function b(r){if(!c("paint"))return()=>{};const e=new PerformanceObserver(n=>{for(const t of n.getEntries())t.name==="first-contentful-paint"&&(r(s("FCP",t.startTime)),e.disconnect())});return e.observe({type:"paint",buffered:!0}),()=>e.disconnect()}function l(r){if(!c("navigation"))return()=>{};const e=new PerformanceObserver(n=>{const t=n.getEntries()[0];if(t){const o=t.responseStart-t.requestStart;r(s("TTFB",o)),e.disconnect()}});return e.observe({type:"navigation",buffered:!0}),()=>e.disconnect()}const y={CLS:v,LCP:d,FID:m,INP:p,FCP:b,TTFB:l};export{v as observeCLS,b as observeFCP,m as observeFID,p as observeINP,d as observeLCP,l as observeTTFB,y as vitalObservers};
1
+ const P={CLS:[.1,.25],LCP:[2500,4e3],FID:[100,300],INP:[200,500],FCP:[1800,3e3],TTFB:[800,1800]};function h(r,e){const[n,t]=P[r];return e<=n?"good":e<=t?"needs-improvement":"poor"}function v(r,e,n=0){return{name:r,value:e,rating:h(r,e),delta:e-n,timestamp:Date.now()}}function m(r){if(typeof PerformanceObserver>"u")return!1;try{return PerformanceObserver.supportedEntryTypes?.includes(r)??!1}catch{return!1}}function L(r){if(!m("layout-shift"))return()=>{};let e=0,n=-1,t=0,i=0,o=0,a=!1;const d=1e3,u=5e3,f=l=>{for(const E of l){const g=E;if(g.hadRecentInput)continue;const b=g.startTime;(n===-1||b-t>d||b-n>u)&&(n=b,e=0),e+=g.value,t=b,e>i&&(i=e)}},s=()=>{i>0&&i!==o&&(r(v("CLS",i,o)),o=i,a=!0)},c=()=>{document.visibilityState==="hidden"&&s()},p=()=>{s()},y=new PerformanceObserver(l=>{f(l.getEntries())});return y.observe({type:"layout-shift",buffered:!0}),document.addEventListener("visibilitychange",c),window.addEventListener("pagehide",p),()=>{y.disconnect(),document.removeEventListener("visibilitychange",c),window.removeEventListener("pagehide",p),!a&&i>0&&s()}}function S(r){if(!m("largest-contentful-paint"))return()=>{};let e=0,n=0,t=!1;const i=()=>{e>0&&!t&&(r(v("LCP",e,n)),n=e,t=!0)},o=new PerformanceObserver(s=>{const c=s.getEntries(),p=c[c.length-1];p&&(e=p.startTime)}),a=()=>{i(),o.disconnect(),f()},d=()=>a(),u=()=>{document.visibilityState==="hidden"&&a()},f=()=>{document.removeEventListener("visibilitychange",u),["keydown","click","pointerdown"].forEach(s=>{document.removeEventListener(s,d,{capture:!0})})};return o.observe({type:"largest-contentful-paint",buffered:!0}),document.addEventListener("visibilitychange",u),["keydown","click","pointerdown"].forEach(s=>{document.addEventListener(s,d,{capture:!0,once:!0})}),()=>{!t&&e>0&&i(),o.disconnect(),f()}}function T(r){if(!m("first-input"))return()=>{};const e=new PerformanceObserver(n=>{const t=n.getEntries()[0];if(t){const i=t.processingStart-t.startTime;r(v("FID",i)),e.disconnect()}});return e.observe({type:"first-input",buffered:!0}),()=>e.disconnect()}function I(r){if(!m("event"))return()=>{};let e=0,n=0,t=!1;const i=new Set,o=()=>{e>0&&!t&&(r(v("INP",e,n)),n=e,t=!0)},a=new PerformanceObserver(f=>{for(const s of f.getEntries()){const c=s;c.interactionId&&(i.has(c.interactionId)||(i.add(c.interactionId),c.duration>e&&(e=c.duration)))}}),d=()=>{document.visibilityState==="hidden"&&o()},u=()=>{o()};return a.observe({type:"event",buffered:!0,durationThreshold:16}),document.addEventListener("visibilitychange",d),window.addEventListener("pagehide",u),()=>{a.disconnect(),document.removeEventListener("visibilitychange",d),window.removeEventListener("pagehide",u),!t&&e>0&&o()}}function w(r){if(!m("paint"))return()=>{};const e=new PerformanceObserver(n=>{for(const t of n.getEntries())t.name==="first-contentful-paint"&&(r(v("FCP",t.startTime)),e.disconnect())});return e.observe({type:"paint",buffered:!0}),()=>e.disconnect()}function M(r){if(!m("navigation"))return()=>{};const e=new PerformanceObserver(n=>{const t=n.getEntries()[0];if(t){const i=t.activationStart??0,o=Math.max(t.responseStart-i,0);r(v("TTFB",o)),e.disconnect()}});return e.observe({type:"navigation",buffered:!0}),()=>e.disconnect()}const C={CLS:L,LCP:S,FID:T,INP:I,FCP:w,TTFB:M};export{L as observeCLS,w as observeFCP,T as observeFID,I as observeINP,S as observeLCP,M as observeTTFB,C as vitalObservers};
2
2
  //# sourceMappingURL=vitals.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/observe/vitals.ts"],
4
- "sourcesContent": ["/**\n * Web Vitals collection - NO external dependencies\n * Uses PerformanceObserver API directly\n */\n\nimport type { MetricName, MetricRating, Metric } from '../types/index.js';\n\n// Google's official thresholds\nconst THRESHOLDS: Record<MetricName, [number, number]> = {\n CLS: [0.1, 0.25],\n LCP: [2500, 4000],\n FID: [100, 300],\n INP: [200, 500],\n FCP: [1800, 3000],\n TTFB: [800, 1800],\n};\n\nfunction getRating(name: MetricName, value: number): MetricRating {\n const [good, poor] = THRESHOLDS[name];\n if (value <= good) return 'good';\n if (value <= poor) return 'needs-improvement';\n return 'poor';\n}\n\nfunction createMetric(\n name: MetricName,\n value: number,\n prevValue: number = 0\n): Metric {\n return {\n name,\n value,\n rating: getRating(name, value),\n delta: value - prevValue,\n timestamp: Date.now(),\n };\n}\n\n// Check if PerformanceObserver supports given entry type\nfunction isSupported(type: string): boolean {\n if (typeof PerformanceObserver === 'undefined') return false;\n try {\n return PerformanceObserver.supportedEntryTypes?.includes(type) ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * CLS - Cumulative Layout Shift\n * Measures visual stability\n */\nexport function observeCLS(callback: (metric: Metric) => void): () => void {\n if (!isSupported('layout-shift')) return () => {};\n\n let clsValue = 0;\n let prevValue = 0;\n\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n // Only count shifts without recent user input\n if (!(entry as LayoutShiftEntry).hadRecentInput) {\n clsValue += (entry as LayoutShiftEntry).value;\n }\n }\n callback(createMetric('CLS', clsValue, prevValue));\n prevValue = clsValue;\n });\n\n observer.observe({ type: 'layout-shift', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * LCP - Largest Contentful Paint\n * Measures loading performance\n */\nexport function observeLCP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('largest-contentful-paint')) return () => {};\n\n let prevValue = 0;\n\n const observer = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n // LCP can have multiple entries, we want the last one\n const lastEntry = entries[entries.length - 1];\n if (lastEntry) {\n const value = lastEntry.startTime;\n callback(createMetric('LCP', value, prevValue));\n prevValue = value;\n }\n });\n\n observer.observe({ type: 'largest-contentful-paint', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * FID - First Input Delay\n * Measures interactivity (deprecated in favor of INP)\n */\nexport function observeFID(callback: (metric: Metric) => void): () => void {\n if (!isSupported('first-input')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n const entry = list.getEntries()[0] as PerformanceEventTiming | undefined;\n if (entry) {\n const value = entry.processingStart - entry.startTime;\n callback(createMetric('FID', value));\n observer.disconnect();\n }\n });\n\n observer.observe({ type: 'first-input', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * INP - Interaction to Next Paint\n * Measures responsiveness (replaced FID as Core Web Vital)\n */\nexport function observeINP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('event')) return () => {};\n\n let maxINP = 0;\n let prevValue = 0;\n\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const duration = (entry as PerformanceEventTiming).duration;\n if (duration > maxINP) {\n maxINP = duration;\n callback(createMetric('INP', maxINP, prevValue));\n prevValue = maxINP;\n }\n }\n });\n\n observer.observe({ type: 'event', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * FCP - First Contentful Paint\n * Measures when first content is painted\n */\nexport function observeFCP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('paint')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n callback(createMetric('FCP', entry.startTime));\n observer.disconnect();\n }\n }\n });\n\n observer.observe({ type: 'paint', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * TTFB - Time to First Byte\n * Measures server response time\n */\nexport function observeTTFB(callback: (metric: Metric) => void): () => void {\n if (!isSupported('navigation')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n const entry = list.getEntries()[0] as PerformanceNavigationTiming | undefined;\n if (entry) {\n const value = entry.responseStart - entry.requestStart;\n callback(createMetric('TTFB', value));\n observer.disconnect();\n }\n });\n\n observer.observe({ type: 'navigation', buffered: true });\n return () => observer.disconnect();\n}\n\n// Type for layout-shift entries (not in standard lib)\ninterface LayoutShiftEntry extends PerformanceEntry {\n value: number;\n hadRecentInput: boolean;\n}\n\n// Export observer map for easy access\nexport const vitalObservers = {\n CLS: observeCLS,\n LCP: observeLCP,\n FID: observeFID,\n INP: observeINP,\n FCP: observeFCP,\n TTFB: observeTTFB,\n} as const;\n\nexport type { Metric, MetricName, MetricRating };\n"],
5
- "mappings": "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",
6
- "names": ["THRESHOLDS", "getRating", "name", "value", "good", "poor", "createMetric", "prevValue", "isSupported", "type", "observeCLS", "callback", "clsValue", "observer", "list", "entry", "observeLCP", "entries", "lastEntry", "observeFID", "observeINP", "maxINP", "duration", "observeFCP", "observeTTFB", "vitalObservers"]
4
+ "sourcesContent": ["/**\n * Web Vitals collection - NO external dependencies\n * Uses PerformanceObserver API directly\n */\n\nimport type { MetricName, MetricRating, Metric } from '../types/index.js';\n\n// Google's official thresholds\nconst THRESHOLDS: Record<MetricName, [number, number]> = {\n CLS: [0.1, 0.25],\n LCP: [2500, 4000],\n FID: [100, 300],\n INP: [200, 500],\n FCP: [1800, 3000],\n TTFB: [800, 1800],\n};\n\nfunction getRating(name: MetricName, value: number): MetricRating {\n const [good, poor] = THRESHOLDS[name];\n if (value <= good) return 'good';\n if (value <= poor) return 'needs-improvement';\n return 'poor';\n}\n\nfunction createMetric(\n name: MetricName,\n value: number,\n prevValue: number = 0\n): Metric {\n return {\n name,\n value,\n rating: getRating(name, value),\n delta: value - prevValue,\n timestamp: Date.now(),\n };\n}\n\n// Check if PerformanceObserver supports given entry type\nfunction isSupported(type: string): boolean {\n if (typeof PerformanceObserver === 'undefined') return false;\n try {\n return PerformanceObserver.supportedEntryTypes?.includes(type) ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * CLS - Cumulative Layout Shift\n * Measures visual stability using session windows algorithm (web-vitals standard)\n *\n * Session window rules:\n * - Max duration: 5 seconds\n * - Max gap between shifts: 1 second\n * - Reports final value on visibility change (hidden) or pagehide\n *\n * @see https://web.dev/cls/#what-is-a-good-cls-score\n * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onCLS.ts\n */\nexport function observeCLS(callback: (metric: Metric) => void): () => void {\n if (!isSupported('layout-shift')) return () => {};\n\n // Session window state\n let sessionValue = 0;\n let sessionStart = -1;\n let lastEntryTime = 0;\n\n // Track max session for final CLS value\n let maxSessionValue = 0;\n let prevReportedValue = 0;\n let hasReported = false;\n\n const SESSION_GAP = 1000; // 1 second max gap\n const SESSION_MAX = 5000; // 5 seconds max duration\n\n const processEntries = (entries: PerformanceEntryList) => {\n for (const entry of entries) {\n const shift = entry as LayoutShiftEntry;\n\n // Ignore shifts with recent user input (clicks, taps, key presses)\n if (shift.hadRecentInput) continue;\n\n const entryTime = shift.startTime;\n\n // Start new session if:\n // 1. First entry ever\n // 2. Gap from last entry > 1 second\n // 3. Session duration would exceed 5 seconds\n if (\n sessionStart === -1 ||\n entryTime - lastEntryTime > SESSION_GAP ||\n entryTime - sessionStart > SESSION_MAX\n ) {\n // Start new session\n sessionStart = entryTime;\n sessionValue = 0;\n }\n\n // Accumulate shift in current session\n sessionValue += shift.value;\n lastEntryTime = entryTime;\n\n // Track maximum session value (this is the CLS score)\n if (sessionValue > maxSessionValue) {\n maxSessionValue = sessionValue;\n }\n }\n };\n\n const reportCLS = () => {\n // Only report if we have a value and it changed\n if (maxSessionValue > 0 && maxSessionValue !== prevReportedValue) {\n callback(createMetric('CLS', maxSessionValue, prevReportedValue));\n prevReportedValue = maxSessionValue;\n hasReported = true;\n }\n };\n\n // Report on visibility change (user switches tab or minimizes)\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') {\n reportCLS();\n }\n };\n\n // Report on page unload (closing tab, navigation away)\n const onPageHide = () => {\n reportCLS();\n };\n\n const observer = new PerformanceObserver((list) => {\n processEntries(list.getEntries());\n });\n\n observer.observe({ type: 'layout-shift', buffered: true });\n\n // Listen for lifecycle events to report final CLS\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n // Cleanup function\n return () => {\n observer.disconnect();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n\n // Report final value on disconnect if not already reported\n if (!hasReported && maxSessionValue > 0) {\n reportCLS();\n }\n };\n}\n\n/**\n * LCP - Largest Contentful Paint\n * Measures loading performance\n *\n * LCP is finalized when:\n * - User interacts with the page (click, keydown, scroll, etc.)\n * - Page becomes hidden (visibility change)\n *\n * @see https://web.dev/lcp/\n * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onLCP.ts\n */\nexport function observeLCP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('largest-contentful-paint')) return () => {};\n\n let lcpValue = 0;\n let prevReportedValue = 0;\n let hasReported = false;\n\n const reportLCP = () => {\n if (lcpValue > 0 && !hasReported) {\n callback(createMetric('LCP', lcpValue, prevReportedValue));\n prevReportedValue = lcpValue;\n hasReported = true;\n }\n };\n\n const observer = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n // LCP can have multiple entries, track the last one\n const lastEntry = entries[entries.length - 1];\n if (lastEntry) {\n lcpValue = lastEntry.startTime;\n }\n });\n\n // LCP is finalized on first user input\n const stopListening = () => {\n reportLCP();\n observer.disconnect();\n removeEventListeners();\n };\n\n // User input events that finalize LCP\n const onInput = () => stopListening();\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') {\n stopListening();\n }\n };\n\n const removeEventListeners = () => {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n // Use capture to catch events before they're handled\n ['keydown', 'click', 'pointerdown'].forEach((type) => {\n document.removeEventListener(type, onInput, { capture: true } as EventListenerOptions);\n });\n };\n\n observer.observe({ type: 'largest-contentful-paint', buffered: true });\n\n // Listen for events that finalize LCP\n document.addEventListener('visibilitychange', onVisibilityChange);\n ['keydown', 'click', 'pointerdown'].forEach((type) => {\n document.addEventListener(type, onInput, { capture: true, once: true });\n });\n\n return () => {\n if (!hasReported && lcpValue > 0) {\n reportLCP();\n }\n observer.disconnect();\n removeEventListeners();\n };\n}\n\n/**\n * FID - First Input Delay\n * Measures interactivity (deprecated in favor of INP)\n */\nexport function observeFID(callback: (metric: Metric) => void): () => void {\n if (!isSupported('first-input')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n const entry = list.getEntries()[0] as PerformanceEventTiming | undefined;\n if (entry) {\n const value = entry.processingStart - entry.startTime;\n callback(createMetric('FID', value));\n observer.disconnect();\n }\n });\n\n observer.observe({ type: 'first-input', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * INP - Interaction to Next Paint\n * Measures responsiveness (replaced FID as Core Web Vital)\n *\n * INP tracks the worst interaction latency during the page lifecycle.\n * Reports on visibility change (hidden) or pagehide.\n *\n * Simplified algorithm (vs web-vitals p98):\n * - Tracks max interaction duration\n * - Only counts discrete events (click, keydown, pointerdown)\n * - Reports once on page hide\n *\n * @see https://web.dev/inp/\n * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onINP.ts\n */\nexport function observeINP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('event')) return () => {};\n\n let maxINP = 0;\n let prevReportedValue = 0;\n let hasReported = false;\n\n // Track interactions by interactionId to avoid counting same interaction twice\n const processedInteractions = new Set<number>();\n\n const reportINP = () => {\n if (maxINP > 0 && !hasReported) {\n callback(createMetric('INP', maxINP, prevReportedValue));\n prevReportedValue = maxINP;\n hasReported = true;\n }\n };\n\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const eventEntry = entry as PerformanceEventTimingExtended;\n\n // Only count discrete events (not scroll, mousemove, etc.)\n // interactionId is 0 or undefined for non-interaction events\n if (!eventEntry.interactionId) continue;\n\n // Avoid counting the same interaction multiple times\n // (e.g., pointerdown + pointerup for same click)\n if (processedInteractions.has(eventEntry.interactionId)) continue;\n processedInteractions.add(eventEntry.interactionId);\n\n // Track maximum interaction duration\n if (eventEntry.duration > maxINP) {\n maxINP = eventEntry.duration;\n }\n }\n });\n\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') {\n reportINP();\n }\n };\n\n const onPageHide = () => {\n reportINP();\n };\n\n observer.observe({ type: 'event', buffered: true, durationThreshold: 16 } as PerformanceObserverInit);\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n window.addEventListener('pagehide', onPageHide);\n\n return () => {\n observer.disconnect();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n window.removeEventListener('pagehide', onPageHide);\n\n if (!hasReported && maxINP > 0) {\n reportINP();\n }\n };\n}\n\n/**\n * FCP - First Contentful Paint\n * Measures when first content is painted\n */\nexport function observeFCP(callback: (metric: Metric) => void): () => void {\n if (!isSupported('paint')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n callback(createMetric('FCP', entry.startTime));\n observer.disconnect();\n }\n }\n });\n\n observer.observe({ type: 'paint', buffered: true });\n return () => observer.disconnect();\n}\n\n/**\n * TTFB - Time to First Byte\n * Measures server response time from navigation start\n *\n * TTFB = responseStart - activationStart (or 0 if no bfcache)\n *\n * activationStart is non-zero for bfcache restores, ensuring we measure\n * the actual server response time, not time in cache.\n *\n * @see https://web.dev/ttfb/\n * @see https://github.com/GoogleChrome/web-vitals/blob/main/src/onTTFB.ts\n */\nexport function observeTTFB(callback: (metric: Metric) => void): () => void {\n if (!isSupported('navigation')) return () => {};\n\n const observer = new PerformanceObserver((list) => {\n const entry = list.getEntries()[0] as PerformanceNavigationTiming | undefined;\n if (entry) {\n // activationStart is non-zero for bfcache restores\n const activationStart = (entry as PerformanceNavigationTiming & { activationStart?: number }).activationStart ?? 0;\n const value = Math.max(entry.responseStart - activationStart, 0);\n callback(createMetric('TTFB', value));\n observer.disconnect();\n }\n });\n\n observer.observe({ type: 'navigation', buffered: true });\n return () => observer.disconnect();\n}\n\n// Type for layout-shift entries (not in standard lib)\ninterface LayoutShiftEntry extends PerformanceEntry {\n value: number;\n hadRecentInput: boolean;\n}\n\n// Extended PerformanceEventTiming with interactionId (not in all TS versions)\ninterface PerformanceEventTimingExtended extends PerformanceEventTiming {\n interactionId?: number;\n}\n\n// Export observer map for easy access\nexport const vitalObservers = {\n CLS: observeCLS,\n LCP: observeLCP,\n FID: observeFID,\n INP: observeINP,\n FCP: observeFCP,\n TTFB: observeTTFB,\n} as const;\n\nexport type { Metric, MetricName, MetricRating };\n"],
5
+ "mappings": "AAQA,MAAMA,EAAmD,CACvD,IAAK,CAAC,GAAK,GAAI,EACf,IAAK,CAAC,KAAM,GAAI,EAChB,IAAK,CAAC,IAAK,GAAG,EACd,IAAK,CAAC,IAAK,GAAG,EACd,IAAK,CAAC,KAAM,GAAI,EAChB,KAAM,CAAC,IAAK,IAAI,CAClB,EAEA,SAASC,EAAUC,EAAkBC,EAA6B,CAChE,KAAM,CAACC,EAAMC,CAAI,EAAIL,EAAWE,CAAI,EACpC,OAAIC,GAASC,EAAa,OACtBD,GAASE,EAAa,oBACnB,MACT,CAEA,SAASC,EACPJ,EACAC,EACAI,EAAoB,EACZ,CACR,MAAO,CACL,KAAAL,EACA,MAAAC,EACA,OAAQF,EAAUC,EAAMC,CAAK,EAC7B,MAAOA,EAAQI,EACf,UAAW,KAAK,IAAI,CACtB,CACF,CAGA,SAASC,EAAYC,EAAuB,CAC1C,GAAI,OAAO,oBAAwB,IAAa,MAAO,GACvD,GAAI,CACF,OAAO,oBAAoB,qBAAqB,SAASA,CAAI,GAAK,EACpE,MAAQ,CACN,MAAO,EACT,CACF,CAcO,SAASC,EAAWC,EAAgD,CACzE,GAAI,CAACH,EAAY,cAAc,EAAG,MAAO,IAAM,CAAC,EAGhD,IAAII,EAAe,EACfC,EAAe,GACfC,EAAgB,EAGhBC,EAAkB,EAClBC,EAAoB,EACpBC,EAAc,GAElB,MAAMC,EAAc,IACdC,EAAc,IAEdC,EAAkBC,GAAkC,CACxD,UAAWC,KAASD,EAAS,CAC3B,MAAME,EAAQD,EAGd,GAAIC,EAAM,eAAgB,SAE1B,MAAMC,EAAYD,EAAM,WAOtBV,IAAiB,IACjBW,EAAYV,EAAgBI,GAC5BM,EAAYX,EAAeM,KAG3BN,EAAeW,EACfZ,EAAe,GAIjBA,GAAgBW,EAAM,MACtBT,EAAgBU,EAGZZ,EAAeG,IACjBA,EAAkBH,EAEtB,CACF,EAEMa,EAAY,IAAM,CAElBV,EAAkB,GAAKA,IAAoBC,IAC7CL,EAASL,EAAa,MAAOS,EAAiBC,CAAiB,CAAC,EAChEA,EAAoBD,EACpBE,EAAc,GAElB,EAGMS,EAAqB,IAAM,CAC3B,SAAS,kBAAoB,UAC/BD,EAAU,CAEd,EAGME,EAAa,IAAM,CACvBF,EAAU,CACZ,EAEMG,EAAW,IAAI,oBAAqBC,GAAS,CACjDT,EAAeS,EAAK,WAAW,CAAC,CAClC,CAAC,EAED,OAAAD,EAAS,QAAQ,CAAE,KAAM,eAAgB,SAAU,EAAK,CAAC,EAGzD,SAAS,iBAAiB,mBAAoBF,CAAkB,EAChE,OAAO,iBAAiB,WAAYC,CAAU,EAGvC,IAAM,CACXC,EAAS,WAAW,EACpB,SAAS,oBAAoB,mBAAoBF,CAAkB,EACnE,OAAO,oBAAoB,WAAYC,CAAU,EAG7C,CAACV,GAAeF,EAAkB,GACpCU,EAAU,CAEd,CACF,CAaO,SAASK,EAAWnB,EAAgD,CACzE,GAAI,CAACH,EAAY,0BAA0B,EAAG,MAAO,IAAM,CAAC,EAE5D,IAAIuB,EAAW,EACXf,EAAoB,EACpBC,EAAc,GAElB,MAAMe,EAAY,IAAM,CAClBD,EAAW,GAAK,CAACd,IACnBN,EAASL,EAAa,MAAOyB,EAAUf,CAAiB,CAAC,EACzDA,EAAoBe,EACpBd,EAAc,GAElB,EAEMW,EAAW,IAAI,oBAAqBC,GAAS,CACjD,MAAMR,EAAUQ,EAAK,WAAW,EAE1BI,EAAYZ,EAAQA,EAAQ,OAAS,CAAC,EACxCY,IACFF,EAAWE,EAAU,UAEzB,CAAC,EAGKC,EAAgB,IAAM,CAC1BF,EAAU,EACVJ,EAAS,WAAW,EACpBO,EAAqB,CACvB,EAGMC,EAAU,IAAMF,EAAc,EAC9BR,EAAqB,IAAM,CAC3B,SAAS,kBAAoB,UAC/BQ,EAAc,CAElB,EAEMC,EAAuB,IAAM,CACjC,SAAS,oBAAoB,mBAAoBT,CAAkB,EAEnE,CAAC,UAAW,QAAS,aAAa,EAAE,QAASjB,GAAS,CACpD,SAAS,oBAAoBA,EAAM2B,EAAS,CAAE,QAAS,EAAK,CAAyB,CACvF,CAAC,CACH,EAEA,OAAAR,EAAS,QAAQ,CAAE,KAAM,2BAA4B,SAAU,EAAK,CAAC,EAGrE,SAAS,iBAAiB,mBAAoBF,CAAkB,EAChE,CAAC,UAAW,QAAS,aAAa,EAAE,QAASjB,GAAS,CACpD,SAAS,iBAAiBA,EAAM2B,EAAS,CAAE,QAAS,GAAM,KAAM,EAAK,CAAC,CACxE,CAAC,EAEM,IAAM,CACP,CAACnB,GAAec,EAAW,GAC7BC,EAAU,EAEZJ,EAAS,WAAW,EACpBO,EAAqB,CACvB,CACF,CAMO,SAASE,EAAW1B,EAAgD,CACzE,GAAI,CAACH,EAAY,aAAa,EAAG,MAAO,IAAM,CAAC,EAE/C,MAAMoB,EAAW,IAAI,oBAAqBC,GAAS,CACjD,MAAMP,EAAQO,EAAK,WAAW,EAAE,CAAC,EACjC,GAAIP,EAAO,CACT,MAAMnB,EAAQmB,EAAM,gBAAkBA,EAAM,UAC5CX,EAASL,EAAa,MAAOH,CAAK,CAAC,EACnCyB,EAAS,WAAW,CACtB,CACF,CAAC,EAED,OAAAA,EAAS,QAAQ,CAAE,KAAM,cAAe,SAAU,EAAK,CAAC,EACjD,IAAMA,EAAS,WAAW,CACnC,CAiBO,SAASU,EAAW3B,EAAgD,CACzE,GAAI,CAACH,EAAY,OAAO,EAAG,MAAO,IAAM,CAAC,EAEzC,IAAI+B,EAAS,EACTvB,EAAoB,EACpBC,EAAc,GAGlB,MAAMuB,EAAwB,IAAI,IAE5BC,EAAY,IAAM,CAClBF,EAAS,GAAK,CAACtB,IACjBN,EAASL,EAAa,MAAOiC,EAAQvB,CAAiB,CAAC,EACvDA,EAAoBuB,EACpBtB,EAAc,GAElB,EAEMW,EAAW,IAAI,oBAAqBC,GAAS,CACjD,UAAWP,KAASO,EAAK,WAAW,EAAG,CACrC,MAAMa,EAAapB,EAIdoB,EAAW,gBAIZF,EAAsB,IAAIE,EAAW,aAAa,IACtDF,EAAsB,IAAIE,EAAW,aAAa,EAG9CA,EAAW,SAAWH,IACxBA,EAASG,EAAW,WAExB,CACF,CAAC,EAEKhB,EAAqB,IAAM,CAC3B,SAAS,kBAAoB,UAC/Be,EAAU,CAEd,EAEMd,EAAa,IAAM,CACvBc,EAAU,CACZ,EAEA,OAAAb,EAAS,QAAQ,CAAE,KAAM,QAAS,SAAU,GAAM,kBAAmB,EAAG,CAA4B,EAEpG,SAAS,iBAAiB,mBAAoBF,CAAkB,EAChE,OAAO,iBAAiB,WAAYC,CAAU,EAEvC,IAAM,CACXC,EAAS,WAAW,EACpB,SAAS,oBAAoB,mBAAoBF,CAAkB,EACnE,OAAO,oBAAoB,WAAYC,CAAU,EAE7C,CAACV,GAAesB,EAAS,GAC3BE,EAAU,CAEd,CACF,CAMO,SAASE,EAAWhC,EAAgD,CACzE,GAAI,CAACH,EAAY,OAAO,EAAG,MAAO,IAAM,CAAC,EAEzC,MAAMoB,EAAW,IAAI,oBAAqBC,GAAS,CACjD,UAAWP,KAASO,EAAK,WAAW,EAC9BP,EAAM,OAAS,2BACjBX,EAASL,EAAa,MAAOgB,EAAM,SAAS,CAAC,EAC7CM,EAAS,WAAW,EAG1B,CAAC,EAED,OAAAA,EAAS,QAAQ,CAAE,KAAM,QAAS,SAAU,EAAK,CAAC,EAC3C,IAAMA,EAAS,WAAW,CACnC,CAcO,SAASgB,EAAYjC,EAAgD,CAC1E,GAAI,CAACH,EAAY,YAAY,EAAG,MAAO,IAAM,CAAC,EAE9C,MAAMoB,EAAW,IAAI,oBAAqBC,GAAS,CACjD,MAAMP,EAAQO,EAAK,WAAW,EAAE,CAAC,EACjC,GAAIP,EAAO,CAET,MAAMuB,EAAmBvB,EAAqE,iBAAmB,EAC3GnB,EAAQ,KAAK,IAAImB,EAAM,cAAgBuB,EAAiB,CAAC,EAC/DlC,EAASL,EAAa,OAAQH,CAAK,CAAC,EACpCyB,EAAS,WAAW,CACtB,CACF,CAAC,EAED,OAAAA,EAAS,QAAQ,CAAE,KAAM,aAAc,SAAU,EAAK,CAAC,EAChD,IAAMA,EAAS,WAAW,CACnC,CAcO,MAAMkB,EAAiB,CAC5B,IAAKpC,EACL,IAAKoB,EACL,IAAKO,EACL,IAAKC,EACL,IAAKK,EACL,KAAMC,CACR",
6
+ "names": ["THRESHOLDS", "getRating", "name", "value", "good", "poor", "createMetric", "prevValue", "isSupported", "type", "observeCLS", "callback", "sessionValue", "sessionStart", "lastEntryTime", "maxSessionValue", "prevReportedValue", "hasReported", "SESSION_GAP", "SESSION_MAX", "processEntries", "entries", "entry", "shift", "entryTime", "reportCLS", "onVisibilityChange", "onPageHide", "observer", "list", "observeLCP", "lcpValue", "reportLCP", "lastEntry", "stopListening", "removeEventListeners", "onInput", "observeFID", "observeINP", "maxINP", "processedInteractions", "reportINP", "eventEntry", "observeFCP", "observeTTFB", "activationStart", "vitalObservers"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ import{createMachine as r}from"../machine/index.js";function s(o){const e=r(o);let n=$state(e.state),a=$state(e.context);const c=e.send.bind(e);return{get state(){return n},get context(){return a},matches(t){return n===t},matchesAny(...t){return t.includes(n)},can(t){return e.can(t)},send:t=>{c(t),n=e.state,a=e.context},destroy:e.destroy.bind(e)}}const v=s;export{v as fromMachine,s as useMachine};
2
+ //# sourceMappingURL=index.svelte.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/svelte/index.svelte.ts"],
4
+ "sourcesContent": ["/**\n * Svelte 5 specific utilities for svoose\n *\n * Provides reactive wrappers and hooks for state machines\n */\n\n/// <reference path=\"./runes.d.ts\" />\n\nimport { createMachine, type MachineConfig, type EventObject } from '../machine/index.js';\n\n/**\n * Reactive machine state for Svelte 5 components\n */\nexport interface ReactiveMachine<\n TContext extends object,\n TState extends string,\n TEvent extends EventObject\n> {\n /** Current state (reactive - triggers re-render on change) */\n readonly state: TState;\n /** Current context (reactive - triggers re-render on change) */\n readonly context: TContext;\n /** Check if machine is in given state */\n matches(state: TState): boolean;\n /** Check if machine is in any of given states */\n matchesAny(...states: TState[]): boolean;\n /** Check if event can be sent */\n can(eventType: TEvent['type']): boolean;\n /** Send event to machine */\n send(event: TEvent | TEvent['type']): void;\n /** Cleanup machine */\n destroy(): void;\n}\n\n/**\n * Create a reactive state machine for Svelte 5 components\n *\n * This is the recommended way to use svoose in Svelte 5 components.\n * The returned machine's state and context are automatically reactive\n * and will trigger component re-renders when they change.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useMachine } from 'svoose/svelte';\n *\n * const toggle = useMachine({\n * id: 'toggle',\n * initial: 'off',\n * states: {\n * off: { on: { TOGGLE: 'on' } },\n * on: { on: { TOGGLE: 'off' } },\n * },\n * });\n * </script>\n *\n * <button onclick={() => toggle.send('TOGGLE')}>\n * {toggle.state}\n * </button>\n * ```\n */\nexport function useMachine<\n TContext extends object,\n TState extends string,\n TEvent extends EventObject\n>(config: MachineConfig<TContext, TState, TEvent>): ReactiveMachine<TContext, TState, TEvent> {\n // Create base machine\n const machine = createMachine(config);\n\n // Reactive state using Svelte 5 $state rune\n let reactiveState = $state<TState>(machine.state);\n let reactiveContext = $state<TContext>(machine.context);\n\n // Wrap send to update reactive state\n const originalSend = machine.send.bind(machine);\n\n const reactiveSend = (event: TEvent | TEvent['type']): void => {\n originalSend(event);\n // Update reactive state after transition\n reactiveState = machine.state;\n reactiveContext = machine.context;\n };\n\n // Return reactive wrapper\n return {\n get state(): TState {\n return reactiveState;\n },\n get context(): TContext {\n return reactiveContext;\n },\n matches(state: TState): boolean {\n return reactiveState === state;\n },\n matchesAny(...states: TState[]): boolean {\n return states.includes(reactiveState);\n },\n can(eventType: TEvent['type']): boolean {\n return machine.can(eventType);\n },\n send: reactiveSend,\n destroy: machine.destroy.bind(machine),\n };\n}\n\n/**\n * Create a machine from an existing config with reactive state\n *\n * Use this when you have a pre-defined machine config that you want\n * to instantiate as a reactive machine in a component.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { fromMachine } from 'svoose/svelte';\n * import { toggleConfig } from './machines/toggle';\n *\n * const toggle = fromMachine(toggleConfig);\n * </script>\n * ```\n */\nexport const fromMachine = useMachine;\n\n// Re-export useful types\nexport type { MachineConfig, EventObject } from '../machine/index.js';\n"],
5
+ "mappings": "AAQA,OAAS,iBAAAA,MAA2D,sBAqD7D,SAASC,EAIdC,EAA4F,CAE5F,MAAMC,EAAUH,EAAcE,CAAM,EAGpC,IAAIE,EAAgB,OAAeD,EAAQ,KAAK,EAC5CE,EAAkB,OAAiBF,EAAQ,OAAO,EAGtD,MAAMG,EAAeH,EAAQ,KAAK,KAAKA,CAAO,EAU9C,MAAO,CACL,IAAI,OAAgB,CAClB,OAAOC,CACT,EACA,IAAI,SAAoB,CACtB,OAAOC,CACT,EACA,QAAQE,EAAwB,CAC9B,OAAOH,IAAkBG,CAC3B,EACA,cAAcC,EAA2B,CACvC,OAAOA,EAAO,SAASJ,CAAa,CACtC,EACA,IAAIK,EAAoC,CACtC,OAAON,EAAQ,IAAIM,CAAS,CAC9B,EACA,KAxBoBC,GAAyC,CAC7DJ,EAAaI,CAAK,EAElBN,EAAgBD,EAAQ,MACxBE,EAAkBF,EAAQ,OAC5B,EAoBE,QAASA,EAAQ,QAAQ,KAAKA,CAAO,CACvC,CACF,CAkBO,MAAMQ,EAAcV",
6
+ "names": ["createMachine", "useMachine", "config", "machine", "reactiveState", "reactiveContext", "originalSend", "state", "states", "eventType", "event", "fromMachine"]
7
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fetch-based transport with sendBeacon fallback
3
3
  */
4
- import type { Transport, TransportOptions } from './transport.js';
4
+ import type { Transport, TransportOptions } from '../types/index.js';
5
5
  /**
6
6
  * Create a fetch-based transport
7
7
  * Uses sendBeacon for page unload, fetch otherwise
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/transport/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAwCX;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAYpF"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/transport/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAwCX;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAYpF"}
@@ -1,2 +1,2 @@
1
- function a(o,n={}){return{async send(e){if(e.length!==0)try{const r=JSON.stringify(e);if(typeof document<"u"&&document.visibilityState==="hidden"&&typeof navigator<"u"&&navigator.sendBeacon){const t=new Blob([r],{type:"application/json"});if(!navigator.sendBeacon(o,t))throw new Error("sendBeacon failed");return}await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...n.headers},body:r,keepalive:!0})}catch(r){n.onError?.(r)}}}}function i(o={}){return{async send(n){for(const e of n)o.pretty?console.log("[svoose]",JSON.stringify(e,null,2)):console.log("[svoose]",e)}}}export{i as createConsoleTransport,a as createFetchTransport};
1
+ function a(o,n={}){return{async send(e){if(e.length!==0)try{const t=JSON.stringify(e);if(typeof document<"u"&&document.visibilityState==="hidden"&&typeof navigator<"u"&&navigator.sendBeacon){const r=new Blob([t],{type:"application/json"});if(!navigator.sendBeacon(o,r))throw new Error("sendBeacon failed");return}await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...n.headers},body:t,keepalive:!0})}catch(t){n.onError?.(t)}}}}function i(o={}){return{async send(n){for(const e of n)o.pretty?console.log("[svoose]",JSON.stringify(e,null,2)):console.log("[svoose]",e)}}}export{i as createConsoleTransport,a as createFetchTransport};
2
2
  //# sourceMappingURL=fetch.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/transport/fetch.ts"],
4
- "sourcesContent": ["/**\n * Fetch-based transport with sendBeacon fallback\n */\n\nimport type { Transport, TransportOptions } from './transport.js';\n\n/**\n * Create a fetch-based transport\n * Uses sendBeacon for page unload, fetch otherwise\n *\n * @param endpoint - URL to send events to\n * @param options - Transport options (headers, error callback)\n */\nexport function createFetchTransport(\n endpoint: string,\n options: TransportOptions = {}\n): Transport {\n return {\n async send(events) {\n if (events.length === 0) return;\n\n try {\n const payload = JSON.stringify(events);\n\n // Use sendBeacon when page is hidden (e.g., user navigating away)\n // sendBeacon is more reliable for unload scenarios\n if (\n typeof document !== 'undefined' &&\n document.visibilityState === 'hidden' &&\n typeof navigator !== 'undefined' &&\n navigator.sendBeacon\n ) {\n const blob = new Blob([payload], { type: 'application/json' });\n const success = navigator.sendBeacon(endpoint, blob);\n if (!success) {\n throw new Error('sendBeacon failed');\n }\n return;\n }\n\n // Use fetch for normal operation\n await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: payload,\n // keepalive ensures request completes even if page is closed\n keepalive: true,\n });\n } catch (error) {\n options.onError?.(error as Error);\n }\n },\n };\n}\n\n/**\n * Create a console transport for development/debugging\n */\nexport function createConsoleTransport(options: { pretty?: boolean } = {}): Transport {\n return {\n async send(events) {\n for (const event of events) {\n if (options.pretty) {\n console.log('[svoose]', JSON.stringify(event, null, 2));\n } else {\n console.log('[svoose]', event);\n }\n }\n },\n };\n}\n"],
4
+ "sourcesContent": ["/**\n * Fetch-based transport with sendBeacon fallback\n */\n\nimport type { Transport, TransportOptions } from '../types/index.js';\n\n/**\n * Create a fetch-based transport\n * Uses sendBeacon for page unload, fetch otherwise\n *\n * @param endpoint - URL to send events to\n * @param options - Transport options (headers, error callback)\n */\nexport function createFetchTransport(\n endpoint: string,\n options: TransportOptions = {}\n): Transport {\n return {\n async send(events) {\n if (events.length === 0) return;\n\n try {\n const payload = JSON.stringify(events);\n\n // Use sendBeacon when page is hidden (e.g., user navigating away)\n // sendBeacon is more reliable for unload scenarios\n if (\n typeof document !== 'undefined' &&\n document.visibilityState === 'hidden' &&\n typeof navigator !== 'undefined' &&\n navigator.sendBeacon\n ) {\n const blob = new Blob([payload], { type: 'application/json' });\n const success = navigator.sendBeacon(endpoint, blob);\n if (!success) {\n throw new Error('sendBeacon failed');\n }\n return;\n }\n\n // Use fetch for normal operation\n await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: payload,\n // keepalive ensures request completes even if page is closed\n keepalive: true,\n });\n } catch (error) {\n options.onError?.(error as Error);\n }\n },\n };\n}\n\n/**\n * Create a console transport for development/debugging\n */\nexport function createConsoleTransport(options: { pretty?: boolean } = {}): Transport {\n return {\n async send(events) {\n for (const event of events) {\n if (options.pretty) {\n console.log('[svoose]', JSON.stringify(event, null, 2));\n } else {\n console.log('[svoose]', event);\n }\n }\n },\n };\n}\n"],
5
5
  "mappings": "AAaO,SAASA,EACdC,EACAC,EAA4B,CAAC,EAClB,CACX,MAAO,CACL,MAAM,KAAKC,EAAQ,CACjB,GAAIA,EAAO,SAAW,EAEtB,GAAI,CACF,MAAMC,EAAU,KAAK,UAAUD,CAAM,EAIrC,GACE,OAAO,SAAa,KACpB,SAAS,kBAAoB,UAC7B,OAAO,UAAc,KACrB,UAAU,WACV,CACA,MAAME,EAAO,IAAI,KAAK,CAACD,CAAO,EAAG,CAAE,KAAM,kBAAmB,CAAC,EAE7D,GAAI,CADY,UAAU,WAAWH,EAAUI,CAAI,EAEjD,MAAM,IAAI,MAAM,mBAAmB,EAErC,MACF,CAGA,MAAM,MAAMJ,EAAU,CACpB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,GAAGC,EAAQ,OACb,EACA,KAAME,EAEN,UAAW,EACb,CAAC,CACH,OAASE,EAAO,CACdJ,EAAQ,UAAUI,CAAc,CAClC,CACF,CACF,CACF,CAKO,SAASC,EAAuBL,EAAgC,CAAC,EAAc,CACpF,MAAO,CACL,MAAM,KAAKC,EAAQ,CACjB,UAAWK,KAASL,EACdD,EAAQ,OACV,QAAQ,IAAI,WAAY,KAAK,UAAUM,EAAO,KAAM,CAAC,CAAC,EAEtD,QAAQ,IAAI,WAAYA,CAAK,CAGnC,CACF,CACF",
6
6
  "names": ["createFetchTransport", "endpoint", "options", "events", "payload", "blob", "error", "createConsoleTransport", "event"]
7
7
  }
@@ -1,13 +1,5 @@
1
1
  /**
2
- * Transport interface - defines how events are sent to backend
2
+ * Transport interface - re-exported from types for convenience
3
3
  */
4
- import type { ObserveEvent } from '../types/index.js';
5
- export interface Transport {
6
- /** Send events to backend. Can return Promise or void for sync transports. */
7
- send(events: ObserveEvent[]): Promise<void> | void;
8
- }
9
- export interface TransportOptions {
10
- headers?: Record<string, string>;
11
- onError?: (error: Error) => void;
12
- }
4
+ export type { Transport, TransportOptions } from '../types/index.js';
13
5
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,SAAS;IACxB,8EAA8E;IAC9E,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -16,6 +16,7 @@ export interface ErrorEvent {
16
16
  colno?: number;
17
17
  timestamp: number;
18
18
  url: string;
19
+ sessionId?: string;
19
20
  machineId?: string;
20
21
  machineState?: string;
21
22
  }
@@ -24,6 +25,7 @@ export interface UnhandledRejectionEvent {
24
25
  reason: string;
25
26
  timestamp: number;
26
27
  url: string;
28
+ sessionId?: string;
27
29
  machineId?: string;
28
30
  machineState?: string;
29
31
  }
@@ -36,6 +38,7 @@ export interface VitalEvent {
36
38
  delta: number;
37
39
  timestamp: number;
38
40
  url: string;
41
+ sessionId?: string;
39
42
  }
40
43
  export interface TransitionEvent {
41
44
  type: 'transition';
@@ -44,9 +47,17 @@ export interface TransitionEvent {
44
47
  to: string;
45
48
  event: string;
46
49
  timestamp: number;
50
+ sessionId?: string;
47
51
  context?: Record<string, unknown>;
48
52
  }
49
- export type ObserveEvent = VitalEvent | ObserveErrorEvent | TransitionEvent;
53
+ export interface CustomMetricEvent {
54
+ type: 'custom';
55
+ name: string;
56
+ data: Record<string, unknown>;
57
+ timestamp: number;
58
+ sessionId?: string;
59
+ }
60
+ export type ObserveEvent = VitalEvent | ObserveErrorEvent | TransitionEvent | CustomMetricEvent;
50
61
  export interface Transport {
51
62
  /** Send events to backend. Can return Promise or void for sync transports. */
52
63
  send(events: ObserveEvent[]): Promise<void> | void;
@@ -55,6 +66,19 @@ export interface TransportOptions {
55
66
  headers?: Record<string, string>;
56
67
  onError?: (error: Error) => void;
57
68
  }
69
+ /**
70
+ * Session configuration
71
+ */
72
+ export interface SessionConfig {
73
+ /** Session timeout in milliseconds (default: 30 minutes) */
74
+ timeout: number;
75
+ /** Storage type for session persistence */
76
+ storage: 'sessionStorage' | 'localStorage' | 'memory';
77
+ }
78
+ /**
79
+ * Session option - true = defaults, false = disabled, object = custom config
80
+ */
81
+ export type SessionOption = boolean | Partial<SessionConfig>;
58
82
  /**
59
83
  * Per-event-type sampling rates
60
84
  * Each rate is a number between 0 and 1 (0 = disabled, 1 = all)
@@ -107,6 +131,25 @@ export interface ObserveOptions {
107
131
  * }
108
132
  */
109
133
  sampling?: SamplingOption;
134
+ /**
135
+ * Session tracking configuration
136
+ *
137
+ * @example
138
+ * // Enable with defaults (30 min timeout, sessionStorage)
139
+ * session: true
140
+ *
141
+ * @example
142
+ * // Custom config
143
+ * session: {
144
+ * timeout: 60 * 60 * 1000, // 1 hour
145
+ * storage: 'localStorage',
146
+ * }
147
+ *
148
+ * @example
149
+ * // Disable explicitly
150
+ * session: false
151
+ */
152
+ session?: SessionOption;
110
153
  /**
111
154
  * @deprecated Use `sampling` instead. Will be removed in v0.3.0.
112
155
  * Global sampling rate (0-1) - applies to entire observer
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svoose",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Observability + State Machines for Svelte 5 — the goose that sees everything",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,6 +55,11 @@
55
55
  "peerDependencies": {
56
56
  "svelte": "^5.0.0"
57
57
  },
58
+ "peerDependenciesMeta": {
59
+ "svelte": {
60
+ "optional": true
61
+ }
62
+ },
58
63
  "devDependencies": {
59
64
  "@vitest/coverage-v8": "^2.1.9",
60
65
  "esbuild": "^0.27.2",