@fullevent/react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ interface WideEvent {
4
+ request_id?: string;
5
+ trace_id?: string;
6
+ timestamp: string;
7
+ method?: string;
8
+ path?: string;
9
+ status_code?: number;
10
+ duration_ms?: number;
11
+ outcome?: 'success' | 'error';
12
+ service?: string;
13
+ region?: string;
14
+ environment?: string;
15
+ user_id?: string;
16
+ user_email?: string;
17
+ user_plan?: string;
18
+ error?: {
19
+ type: string;
20
+ message: string;
21
+ stack?: string;
22
+ code?: string;
23
+ };
24
+ [key: string]: unknown;
25
+ }
26
+ type SamplingConfig = {
27
+ /** Keep 10% of normal requests (0.0 - 1.0) */
28
+ defaultRate?: number;
29
+ /** Always keep error outcomes */
30
+ alwaysKeepErrors?: boolean;
31
+ /** Always keep slow requests (>ms) */
32
+ slowRequestThresholdMs?: number;
33
+ };
34
+ type FulleventConfig = {
35
+ apiUrl: string;
36
+ apiKey: string;
37
+ debug?: boolean;
38
+ /** Service name to tag all events with */
39
+ service?: string;
40
+ /** Environment (defaults to 'browser') */
41
+ environment?: string;
42
+ /** Sampling configuration */
43
+ sampling?: SamplingConfig;
44
+ };
45
+ interface EventBuilder {
46
+ /** Set any key-value pair on the event */
47
+ set: (key: string, value: unknown) => EventBuilder;
48
+ /** Set the user ID */
49
+ setUser: (userId: string) => EventBuilder;
50
+ /** Capture an error with structured details */
51
+ setError: (err: Error | {
52
+ type?: string;
53
+ message: string;
54
+ code?: string;
55
+ }) => EventBuilder;
56
+ /** Set the status code */
57
+ setStatus: (code: number) => EventBuilder;
58
+ /** Get the underlying event object */
59
+ getEvent: () => WideEvent;
60
+ /** Get the trace ID for this event (for correlating with backend) */
61
+ getTraceId: () => string;
62
+ /** Get headers to pass to fetch() for trace correlation */
63
+ getHeaders: () => Record<string, string>;
64
+ /** Emit the event to FullEvent API */
65
+ emit: () => Promise<void>;
66
+ }
67
+ type FulleventContextType = {
68
+ /** Quick capture of a simple event */
69
+ capture: (event: string, properties?: Record<string, unknown>) => Promise<void>;
70
+ /** Create a wide event builder for accumulating context */
71
+ createEvent: (name: string) => EventBuilder;
72
+ /** Set global user context for all future events */
73
+ setUser: (userId: string) => void;
74
+ };
75
+ declare const FulleventProvider: React.FC<{
76
+ config: FulleventConfig;
77
+ children: React.ReactNode;
78
+ }>;
79
+ declare const useFullevent: () => FulleventContextType;
80
+
81
+ export { type EventBuilder, type FulleventConfig, FulleventProvider, type SamplingConfig, type WideEvent, useFullevent };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+
3
+ interface WideEvent {
4
+ request_id?: string;
5
+ trace_id?: string;
6
+ timestamp: string;
7
+ method?: string;
8
+ path?: string;
9
+ status_code?: number;
10
+ duration_ms?: number;
11
+ outcome?: 'success' | 'error';
12
+ service?: string;
13
+ region?: string;
14
+ environment?: string;
15
+ user_id?: string;
16
+ user_email?: string;
17
+ user_plan?: string;
18
+ error?: {
19
+ type: string;
20
+ message: string;
21
+ stack?: string;
22
+ code?: string;
23
+ };
24
+ [key: string]: unknown;
25
+ }
26
+ type SamplingConfig = {
27
+ /** Keep 10% of normal requests (0.0 - 1.0) */
28
+ defaultRate?: number;
29
+ /** Always keep error outcomes */
30
+ alwaysKeepErrors?: boolean;
31
+ /** Always keep slow requests (>ms) */
32
+ slowRequestThresholdMs?: number;
33
+ };
34
+ type FulleventConfig = {
35
+ apiUrl: string;
36
+ apiKey: string;
37
+ debug?: boolean;
38
+ /** Service name to tag all events with */
39
+ service?: string;
40
+ /** Environment (defaults to 'browser') */
41
+ environment?: string;
42
+ /** Sampling configuration */
43
+ sampling?: SamplingConfig;
44
+ };
45
+ interface EventBuilder {
46
+ /** Set any key-value pair on the event */
47
+ set: (key: string, value: unknown) => EventBuilder;
48
+ /** Set the user ID */
49
+ setUser: (userId: string) => EventBuilder;
50
+ /** Capture an error with structured details */
51
+ setError: (err: Error | {
52
+ type?: string;
53
+ message: string;
54
+ code?: string;
55
+ }) => EventBuilder;
56
+ /** Set the status code */
57
+ setStatus: (code: number) => EventBuilder;
58
+ /** Get the underlying event object */
59
+ getEvent: () => WideEvent;
60
+ /** Get the trace ID for this event (for correlating with backend) */
61
+ getTraceId: () => string;
62
+ /** Get headers to pass to fetch() for trace correlation */
63
+ getHeaders: () => Record<string, string>;
64
+ /** Emit the event to FullEvent API */
65
+ emit: () => Promise<void>;
66
+ }
67
+ type FulleventContextType = {
68
+ /** Quick capture of a simple event */
69
+ capture: (event: string, properties?: Record<string, unknown>) => Promise<void>;
70
+ /** Create a wide event builder for accumulating context */
71
+ createEvent: (name: string) => EventBuilder;
72
+ /** Set global user context for all future events */
73
+ setUser: (userId: string) => void;
74
+ };
75
+ declare const FulleventProvider: React.FC<{
76
+ config: FulleventConfig;
77
+ children: React.ReactNode;
78
+ }>;
79
+ declare const useFullevent: () => FulleventContextType;
80
+
81
+ export { type EventBuilder, type FulleventConfig, FulleventProvider, type SamplingConfig, type WideEvent, useFullevent };
package/dist/index.js ADDED
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.tsx
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ FulleventProvider: () => FulleventProvider,
35
+ useFullevent: () => useFullevent
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_react = __toESM(require("react"));
39
+ function shouldSample(event, config) {
40
+ const sampling = config ?? {};
41
+ const defaultRate = sampling.defaultRate ?? 1;
42
+ const alwaysKeepErrors = sampling.alwaysKeepErrors ?? true;
43
+ const slowThreshold = sampling.slowRequestThresholdMs ?? 2e3;
44
+ if (alwaysKeepErrors) {
45
+ if (event.outcome === "error") return true;
46
+ if (event.status_code && event.status_code >= 400) return true;
47
+ }
48
+ if (event.duration_ms && event.duration_ms > slowThreshold) return true;
49
+ if (event.trace_id) {
50
+ let hash = 5381;
51
+ const str = event.trace_id;
52
+ for (let i = 0; i < str.length; i++) {
53
+ hash = (hash << 5) + hash + str.charCodeAt(i);
54
+ }
55
+ const normalized = (hash >>> 0) % 1e4 / 1e4;
56
+ return normalized < defaultRate;
57
+ }
58
+ return Math.random() < defaultRate;
59
+ }
60
+ function createEventBuilder(name, sendFn, baseContext) {
61
+ const startTime = Date.now();
62
+ const traceId = crypto.randomUUID();
63
+ const event = {
64
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65
+ trace_id: traceId,
66
+ request_id: traceId,
67
+ ...baseContext
68
+ };
69
+ const builder = {
70
+ set(key, value) {
71
+ event[key] = value;
72
+ return builder;
73
+ },
74
+ setUser(userId) {
75
+ event.user_id = userId;
76
+ return builder;
77
+ },
78
+ setError(err) {
79
+ event.outcome = "error";
80
+ if (err instanceof Error) {
81
+ event.error = {
82
+ type: err.name,
83
+ message: err.message,
84
+ stack: err.stack
85
+ };
86
+ } else {
87
+ event.error = {
88
+ type: err.type || "Error",
89
+ message: err.message,
90
+ code: err.code
91
+ };
92
+ }
93
+ return builder;
94
+ },
95
+ setStatus(code) {
96
+ event.status_code = code;
97
+ event.outcome = code >= 400 ? "error" : "success";
98
+ return builder;
99
+ },
100
+ getEvent() {
101
+ return event;
102
+ },
103
+ getTraceId() {
104
+ return traceId;
105
+ },
106
+ getHeaders() {
107
+ return {
108
+ "x-fullevent-trace-id": traceId
109
+ };
110
+ },
111
+ async emit() {
112
+ event.duration_ms = Date.now() - startTime;
113
+ if (!event.outcome) {
114
+ event.outcome = "success";
115
+ }
116
+ await sendFn(name, event, event);
117
+ }
118
+ };
119
+ return builder;
120
+ }
121
+ var FulleventContext = (0, import_react.createContext)(void 0);
122
+ var FulleventProvider = ({ config, children }) => {
123
+ let globalUserId;
124
+ const baseContext = {
125
+ service: config.service,
126
+ environment: config.environment || "browser"
127
+ };
128
+ const capture = async (event, properties, wideEvent) => {
129
+ if (wideEvent && !shouldSample(wideEvent, config.sampling)) {
130
+ if (config.debug) {
131
+ console.log(`[Fullevent] Sampling dropped event: ${event}`);
132
+ }
133
+ return;
134
+ }
135
+ if (config.debug) {
136
+ console.log(`[Fullevent] Capturing event: ${event}`, properties);
137
+ }
138
+ const payload = {
139
+ ...baseContext,
140
+ ...properties,
141
+ user_id: properties?.user_id || globalUserId
142
+ };
143
+ try {
144
+ await fetch(`${config.apiUrl}/ingest`, {
145
+ method: "POST",
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ "Authorization": `Bearer ${config.apiKey}`
149
+ },
150
+ body: JSON.stringify({
151
+ event,
152
+ properties: payload,
153
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
154
+ })
155
+ });
156
+ } catch (error) {
157
+ console.error("[Fullevent] Failed to capture event:", error);
158
+ }
159
+ };
160
+ const createEvent = (name) => {
161
+ const builder = createEventBuilder(name, capture, {
162
+ ...baseContext,
163
+ user_id: globalUserId
164
+ });
165
+ return builder;
166
+ };
167
+ const setUser = (userId) => {
168
+ globalUserId = userId;
169
+ };
170
+ return /* @__PURE__ */ import_react.default.createElement(FulleventContext.Provider, { value: { capture, createEvent, setUser } }, children);
171
+ };
172
+ var useFullevent = () => {
173
+ const context = (0, import_react.useContext)(FulleventContext);
174
+ if (!context) {
175
+ throw new Error("useFullevent must be used within a FulleventProvider");
176
+ }
177
+ return context;
178
+ };
179
+ // Annotate the CommonJS export names for ESM import in node:
180
+ 0 && (module.exports = {
181
+ FulleventProvider,
182
+ useFullevent
183
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,148 @@
1
+ "use client";
2
+
3
+ // src/index.tsx
4
+ import React, { createContext, useContext } from "react";
5
+ function shouldSample(event, config) {
6
+ const sampling = config ?? {};
7
+ const defaultRate = sampling.defaultRate ?? 1;
8
+ const alwaysKeepErrors = sampling.alwaysKeepErrors ?? true;
9
+ const slowThreshold = sampling.slowRequestThresholdMs ?? 2e3;
10
+ if (alwaysKeepErrors) {
11
+ if (event.outcome === "error") return true;
12
+ if (event.status_code && event.status_code >= 400) return true;
13
+ }
14
+ if (event.duration_ms && event.duration_ms > slowThreshold) return true;
15
+ if (event.trace_id) {
16
+ let hash = 5381;
17
+ const str = event.trace_id;
18
+ for (let i = 0; i < str.length; i++) {
19
+ hash = (hash << 5) + hash + str.charCodeAt(i);
20
+ }
21
+ const normalized = (hash >>> 0) % 1e4 / 1e4;
22
+ return normalized < defaultRate;
23
+ }
24
+ return Math.random() < defaultRate;
25
+ }
26
+ function createEventBuilder(name, sendFn, baseContext) {
27
+ const startTime = Date.now();
28
+ const traceId = crypto.randomUUID();
29
+ const event = {
30
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
31
+ trace_id: traceId,
32
+ request_id: traceId,
33
+ ...baseContext
34
+ };
35
+ const builder = {
36
+ set(key, value) {
37
+ event[key] = value;
38
+ return builder;
39
+ },
40
+ setUser(userId) {
41
+ event.user_id = userId;
42
+ return builder;
43
+ },
44
+ setError(err) {
45
+ event.outcome = "error";
46
+ if (err instanceof Error) {
47
+ event.error = {
48
+ type: err.name,
49
+ message: err.message,
50
+ stack: err.stack
51
+ };
52
+ } else {
53
+ event.error = {
54
+ type: err.type || "Error",
55
+ message: err.message,
56
+ code: err.code
57
+ };
58
+ }
59
+ return builder;
60
+ },
61
+ setStatus(code) {
62
+ event.status_code = code;
63
+ event.outcome = code >= 400 ? "error" : "success";
64
+ return builder;
65
+ },
66
+ getEvent() {
67
+ return event;
68
+ },
69
+ getTraceId() {
70
+ return traceId;
71
+ },
72
+ getHeaders() {
73
+ return {
74
+ "x-fullevent-trace-id": traceId
75
+ };
76
+ },
77
+ async emit() {
78
+ event.duration_ms = Date.now() - startTime;
79
+ if (!event.outcome) {
80
+ event.outcome = "success";
81
+ }
82
+ await sendFn(name, event, event);
83
+ }
84
+ };
85
+ return builder;
86
+ }
87
+ var FulleventContext = createContext(void 0);
88
+ var FulleventProvider = ({ config, children }) => {
89
+ let globalUserId;
90
+ const baseContext = {
91
+ service: config.service,
92
+ environment: config.environment || "browser"
93
+ };
94
+ const capture = async (event, properties, wideEvent) => {
95
+ if (wideEvent && !shouldSample(wideEvent, config.sampling)) {
96
+ if (config.debug) {
97
+ console.log(`[Fullevent] Sampling dropped event: ${event}`);
98
+ }
99
+ return;
100
+ }
101
+ if (config.debug) {
102
+ console.log(`[Fullevent] Capturing event: ${event}`, properties);
103
+ }
104
+ const payload = {
105
+ ...baseContext,
106
+ ...properties,
107
+ user_id: properties?.user_id || globalUserId
108
+ };
109
+ try {
110
+ await fetch(`${config.apiUrl}/ingest`, {
111
+ method: "POST",
112
+ headers: {
113
+ "Content-Type": "application/json",
114
+ "Authorization": `Bearer ${config.apiKey}`
115
+ },
116
+ body: JSON.stringify({
117
+ event,
118
+ properties: payload,
119
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
120
+ })
121
+ });
122
+ } catch (error) {
123
+ console.error("[Fullevent] Failed to capture event:", error);
124
+ }
125
+ };
126
+ const createEvent = (name) => {
127
+ const builder = createEventBuilder(name, capture, {
128
+ ...baseContext,
129
+ user_id: globalUserId
130
+ });
131
+ return builder;
132
+ };
133
+ const setUser = (userId) => {
134
+ globalUserId = userId;
135
+ };
136
+ return /* @__PURE__ */ React.createElement(FulleventContext.Provider, { value: { capture, createEvent, setUser } }, children);
137
+ };
138
+ var useFullevent = () => {
139
+ const context = useContext(FulleventContext);
140
+ if (!context) {
141
+ throw new Error("useFullevent must be used within a FulleventProvider");
142
+ }
143
+ return context;
144
+ };
145
+ export {
146
+ FulleventProvider,
147
+ useFullevent
148
+ };
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@fullevent/react",
3
+ "version": "0.0.1",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsup src/index.tsx --format cjs,esm --dts",
9
+ "dev": "tsup src/index.tsx --format cjs,esm --dts --watch"
10
+ },
11
+ "peerDependencies": {
12
+ "react": "^19.0.0"
13
+ },
14
+ "devDependencies": {
15
+ "tsup": "^8.0.0",
16
+ "typescript": "^5.0.0",
17
+ "@types/react": "^19.0.0"
18
+ }
19
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,277 @@
1
+ "use client";
2
+ import React, { createContext, useContext } from 'react';
3
+
4
+ // ============================================================
5
+ // Types - Match Node SDK
6
+ // ============================================================
7
+
8
+ export interface WideEvent {
9
+ // Core Request Context
10
+ request_id?: string;
11
+ trace_id?: string;
12
+ timestamp: string;
13
+ method?: string;
14
+ path?: string;
15
+ status_code?: number;
16
+ duration_ms?: number;
17
+ outcome?: 'success' | 'error';
18
+
19
+ // Infrastructure Context
20
+ service?: string;
21
+ region?: string;
22
+ environment?: string;
23
+
24
+ // User Context
25
+ user_id?: string;
26
+ user_email?: string;
27
+ user_plan?: string;
28
+
29
+ // Error Details
30
+ error?: {
31
+ type: string;
32
+ message: string;
33
+ stack?: string;
34
+ code?: string;
35
+ };
36
+
37
+ // Dynamic Business Context
38
+ [key: string]: unknown;
39
+ }
40
+
41
+ export type SamplingConfig = {
42
+ /** Keep 10% of normal requests (0.0 - 1.0) */
43
+ defaultRate?: number;
44
+ /** Always keep error outcomes */
45
+ alwaysKeepErrors?: boolean;
46
+ /** Always keep slow requests (>ms) */
47
+ slowRequestThresholdMs?: number;
48
+ };
49
+
50
+ export type FulleventConfig = {
51
+ apiUrl: string;
52
+ apiKey: string;
53
+ debug?: boolean;
54
+ /** Service name to tag all events with */
55
+ service?: string;
56
+ /** Environment (defaults to 'browser') */
57
+ environment?: string;
58
+ /** Sampling configuration */
59
+ sampling?: SamplingConfig;
60
+ };
61
+
62
+ // Helper for Consistent Sampling
63
+ function shouldSample(event: WideEvent, config?: SamplingConfig): boolean {
64
+ const sampling = config ?? {};
65
+ const defaultRate = sampling.defaultRate ?? 1.0;
66
+ const alwaysKeepErrors = sampling.alwaysKeepErrors ?? true;
67
+ const slowThreshold = sampling.slowRequestThresholdMs ?? 2000;
68
+
69
+ // Always keep errors
70
+ if (alwaysKeepErrors) {
71
+ if (event.outcome === 'error') return true;
72
+ if (event.status_code && event.status_code >= 400) return true;
73
+ }
74
+
75
+ // Always keep slow requests
76
+ if (event.duration_ms && event.duration_ms > slowThreshold) return true;
77
+
78
+ // Consistent Sampling based on Trace ID
79
+ if (event.trace_id) {
80
+ let hash = 5381;
81
+ const str = event.trace_id;
82
+ for (let i = 0; i < str.length; i++) {
83
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
84
+ }
85
+ const normalized = (hash >>> 0) % 10000 / 10000;
86
+ return normalized < defaultRate;
87
+ }
88
+
89
+ return Math.random() < defaultRate;
90
+ }
91
+
92
+ // ============================================================
93
+ // Event Builder - Match Node SDK API
94
+ // ============================================================
95
+
96
+ export interface EventBuilder {
97
+ /** Set any key-value pair on the event */
98
+ set: (key: string, value: unknown) => EventBuilder;
99
+ /** Set the user ID */
100
+ setUser: (userId: string) => EventBuilder;
101
+ /** Capture an error with structured details */
102
+ setError: (err: Error | { type?: string; message: string; code?: string }) => EventBuilder;
103
+ /** Set the status code */
104
+ setStatus: (code: number) => EventBuilder;
105
+ /** Get the underlying event object */
106
+ getEvent: () => WideEvent;
107
+ /** Get the trace ID for this event (for correlating with backend) */
108
+ getTraceId: () => string;
109
+ /** Get headers to pass to fetch() for trace correlation */
110
+ getHeaders: () => Record<string, string>;
111
+ /** Emit the event to FullEvent API */
112
+ emit: () => Promise<void>;
113
+ }
114
+
115
+ function createEventBuilder(
116
+ name: string,
117
+ sendFn: (event: string, properties: Record<string, unknown>, wideEvent: WideEvent) => Promise<void>,
118
+ baseContext: Partial<WideEvent>
119
+ ): EventBuilder {
120
+ const startTime = Date.now();
121
+ // Generate a unique trace ID for this event
122
+ const traceId = crypto.randomUUID();
123
+
124
+ const event: WideEvent = {
125
+ timestamp: new Date().toISOString(),
126
+ trace_id: traceId,
127
+ request_id: traceId,
128
+ ...baseContext,
129
+ };
130
+
131
+ const builder: EventBuilder = {
132
+ set(key: string, value: unknown) {
133
+ event[key] = value;
134
+ return builder;
135
+ },
136
+
137
+ setUser(userId: string) {
138
+ event.user_id = userId;
139
+ return builder;
140
+ },
141
+
142
+ setError(err: Error | { type?: string; message: string; code?: string }) {
143
+ event.outcome = 'error';
144
+ if (err instanceof Error) {
145
+ event.error = {
146
+ type: err.name,
147
+ message: err.message,
148
+ stack: err.stack,
149
+ };
150
+ } else {
151
+ event.error = {
152
+ type: err.type || 'Error',
153
+ message: err.message,
154
+ code: err.code,
155
+ };
156
+ }
157
+ return builder;
158
+ },
159
+
160
+ setStatus(code: number) {
161
+ event.status_code = code;
162
+ event.outcome = code >= 400 ? 'error' : 'success';
163
+ return builder;
164
+ },
165
+
166
+ getEvent() {
167
+ return event;
168
+ },
169
+
170
+ getTraceId() {
171
+ return traceId;
172
+ },
173
+
174
+ getHeaders() {
175
+ return {
176
+ 'x-fullevent-trace-id': traceId,
177
+ };
178
+ },
179
+
180
+ async emit() {
181
+ event.duration_ms = Date.now() - startTime;
182
+ if (!event.outcome) {
183
+ event.outcome = 'success';
184
+ }
185
+ await sendFn(name, event as Record<string, unknown>, event);
186
+ }
187
+ };
188
+
189
+ return builder;
190
+ }
191
+
192
+ // ============================================================
193
+ // Context & Provider
194
+ // ============================================================
195
+
196
+ type FulleventContextType = {
197
+ /** Quick capture of a simple event */
198
+ capture: (event: string, properties?: Record<string, unknown>) => Promise<void>;
199
+ /** Create a wide event builder for accumulating context */
200
+ createEvent: (name: string) => EventBuilder;
201
+ /** Set global user context for all future events */
202
+ setUser: (userId: string) => void;
203
+ };
204
+
205
+ const FulleventContext = createContext<FulleventContextType | undefined>(undefined);
206
+
207
+ export const FulleventProvider: React.FC<{ config: FulleventConfig; children: React.ReactNode }> = ({ config, children }) => {
208
+ // Global user context that gets added to all events
209
+ let globalUserId: string | undefined;
210
+
211
+ const baseContext: Partial<WideEvent> = {
212
+ service: config.service,
213
+ environment: config.environment || 'browser',
214
+ };
215
+
216
+ const capture = async (event: string, properties?: Record<string, unknown>, wideEvent?: WideEvent) => {
217
+ // If it's a wide event (via createEvent), check sampling
218
+ if (wideEvent && !shouldSample(wideEvent, config.sampling)) {
219
+ if (config.debug) {
220
+ console.log(`[Fullevent] Sampling dropped event: ${event}`);
221
+ }
222
+ return;
223
+ }
224
+ if (config.debug) {
225
+ console.log(`[Fullevent] Capturing event: ${event}`, properties);
226
+ }
227
+
228
+ const payload = {
229
+ ...baseContext,
230
+ ...properties,
231
+ user_id: properties?.user_id || globalUserId,
232
+ };
233
+
234
+ try {
235
+ await fetch(`${config.apiUrl}/ingest`, {
236
+ method: 'POST',
237
+ headers: {
238
+ 'Content-Type': 'application/json',
239
+ 'Authorization': `Bearer ${config.apiKey}`,
240
+ },
241
+ body: JSON.stringify({
242
+ event,
243
+ properties: payload,
244
+ timestamp: new Date().toISOString(),
245
+ }),
246
+ });
247
+ } catch (error) {
248
+ console.error('[Fullevent] Failed to capture event:', error);
249
+ }
250
+ };
251
+
252
+ const createEvent = (name: string): EventBuilder => {
253
+ const builder = createEventBuilder(name, capture, {
254
+ ...baseContext,
255
+ user_id: globalUserId,
256
+ });
257
+ return builder;
258
+ };
259
+
260
+ const setUser = (userId: string) => {
261
+ globalUserId = userId;
262
+ };
263
+
264
+ return (
265
+ <FulleventContext.Provider value={{ capture, createEvent, setUser }}>
266
+ {children}
267
+ </FulleventContext.Provider>
268
+ );
269
+ };
270
+
271
+ export const useFullevent = () => {
272
+ const context = useContext(FulleventContext);
273
+ if (!context) {
274
+ throw new Error('useFullevent must be used within a FulleventProvider');
275
+ }
276
+ return context;
277
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "jsx": "react",
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "outDir": "./dist"
13
+ },
14
+ "include": [
15
+ "src/**/*"
16
+ ]
17
+ }