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.
- package/README.md +148 -14
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +3 -3
- package/dist/metrics/index.d.ts +5 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +2 -0
- package/dist/metrics/index.js.map +7 -0
- package/dist/metrics/metric.d.ts +44 -0
- package/dist/metrics/metric.d.ts.map +1 -0
- package/dist/metrics/metric.js +2 -0
- package/dist/metrics/metric.js.map +7 -0
- package/dist/observe/observe.svelte.d.ts.map +1 -1
- package/dist/observe/observe.svelte.js +1 -1
- package/dist/observe/observe.svelte.js.map +3 -3
- package/dist/observe/sampling.d.ts +2 -20
- package/dist/observe/sampling.d.ts.map +1 -1
- package/dist/observe/sampling.js +1 -1
- package/dist/observe/sampling.js.map +2 -2
- package/dist/observe/session.d.ts +33 -0
- package/dist/observe/session.d.ts.map +1 -0
- package/dist/observe/session.js +2 -0
- package/dist/observe/session.js.map +7 -0
- package/dist/observe/vitals.d.ts +36 -2
- package/dist/observe/vitals.d.ts.map +1 -1
- package/dist/observe/vitals.js +1 -1
- package/dist/observe/vitals.js.map +3 -3
- package/dist/svelte/index.svelte.js +2 -0
- package/dist/svelte/index.svelte.js.map +7 -0
- package/dist/transport/fetch.d.ts +1 -1
- package/dist/transport/fetch.d.ts.map +1 -1
- package/dist/transport/fetch.js +1 -1
- package/dist/transport/fetch.js.map +1 -1
- package/dist/transport/transport.d.ts +2 -10
- package/dist/transport/transport.d.ts.map +1 -1
- package/dist/types/index.d.ts +44 -1
- package/dist/types/index.d.ts.map +1 -1
- 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. **<
|
|
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
|
-
//
|
|
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
|
-
|
|
|
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
|
|
406
|
-
- **v0.
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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 @@
|
|
|
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;
|
|
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
|
|
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": "AAIA,OAAS,kBAAAA,MAAoD,cAC7D,OAAS,iBAAAC,MAA6C,cACtD,OAAS,wBAAAC,MAA4B,wBACrC,OACE,iBAAAC,EACA,2BAAAC,MAEK,
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/observe/sampling.js
CHANGED
|
@@ -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
|
|
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\
|
|
5
|
-
"mappings": "
|
|
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
|
+
}
|
package/dist/observe/vitals.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/observe/vitals.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const
|
|
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": "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,
|
|
6
|
-
"names": ["THRESHOLDS", "getRating", "name", "value", "good", "poor", "createMetric", "prevValue", "isSupported", "type", "observeCLS", "callback", "
|
|
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": "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",
|
|
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 '
|
|
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,
|
|
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"}
|
package/dist/transport/fetch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function a(o,n={}){return{async send(e){if(e.length!==0)try{const
|
|
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 '
|
|
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 -
|
|
2
|
+
* Transport interface - re-exported from types for convenience
|
|
3
3
|
*/
|
|
4
|
-
|
|
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,
|
|
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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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":"AAIA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAExE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,mBAAmB,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,uBAAuB,CAAC;AAMrE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;
|
|
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.
|
|
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",
|