@fullevent/node 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.
- package/dist/index.d.mts +1096 -0
- package/dist/index.d.ts +1096 -0
- package/dist/index.js +573 -0
- package/dist/index.mjs +543 -0
- package/package.json +23 -0
- package/scripts/generate-docs.ts +917 -0
- package/src/builder.ts +307 -0
- package/src/client.ts +325 -0
- package/src/index.ts +272 -0
- package/src/middleware/express.ts +193 -0
- package/src/middleware/hono.ts +395 -0
- package/tsconfig.json +11 -0
package/src/builder.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { WideEvent } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fluent builder for enriching wide events throughout the request lifecycle.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* While you can modify the event object directly (and that's perfectly fine),
|
|
8
|
+
* the builder provides chainable methods for cleaner code and useful helpers.
|
|
9
|
+
*
|
|
10
|
+
* ## Two Approaches
|
|
11
|
+
*
|
|
12
|
+
* **Direct access (simple):**
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const event = c.get('wideEvent');
|
|
15
|
+
* event.user = { id: user.id, plan: user.plan };
|
|
16
|
+
* event.cart = { total: cart.total, items: cart.items.length };
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Builder pattern (chainable):**
|
|
20
|
+
* ```typescript
|
|
21
|
+
* new WideEventBuilder(c.get('wideEvent'))
|
|
22
|
+
* .setContext('user', { id: user.id, plan: user.plan })
|
|
23
|
+
* .setContext('cart', { total: cart.total, items: cart.items.length })
|
|
24
|
+
* .setTiming('db_latency_ms', dbStart);
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Full Example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const builder = new WideEventBuilder(c.get('wideEvent'));
|
|
30
|
+
*
|
|
31
|
+
* builder
|
|
32
|
+
* .setUser(user.id)
|
|
33
|
+
* .setContext('user', {
|
|
34
|
+
* subscription: user.plan,
|
|
35
|
+
* account_age_days: daysSince(user.createdAt),
|
|
36
|
+
* lifetime_value_cents: user.ltv,
|
|
37
|
+
* })
|
|
38
|
+
* .setContext('cart', {
|
|
39
|
+
* id: cart.id,
|
|
40
|
+
* item_count: cart.items.length,
|
|
41
|
+
* total_cents: cart.total,
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Later, after payment processing
|
|
45
|
+
* builder
|
|
46
|
+
* .setContext('payment', { method: 'card', provider: 'stripe' })
|
|
47
|
+
* .setTiming('payment_latency_ms', paymentStart);
|
|
48
|
+
*
|
|
49
|
+
* if (paymentError) {
|
|
50
|
+
* builder.setError({
|
|
51
|
+
* type: 'PaymentError',
|
|
52
|
+
* code: paymentError.code,
|
|
53
|
+
* message: paymentError.message,
|
|
54
|
+
* stripe_decline_code: paymentError.declineCode,
|
|
55
|
+
* });
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @category Builder
|
|
60
|
+
*/
|
|
61
|
+
export class WideEventBuilder {
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new builder wrapping the given event.
|
|
64
|
+
*
|
|
65
|
+
* @param event - The wide event to enrich
|
|
66
|
+
*/
|
|
67
|
+
constructor(private event: WideEvent) {}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns the underlying event object for direct access.
|
|
71
|
+
*
|
|
72
|
+
* @returns The wrapped WideEvent
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const event = builder.getEvent();
|
|
77
|
+
* console.log(event.user_id);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
getEvent(): WideEvent {
|
|
81
|
+
return this.event;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets any key-value pair on the event.
|
|
86
|
+
*
|
|
87
|
+
* @param key - Property name
|
|
88
|
+
* @param value - Property value (any type)
|
|
89
|
+
* @returns `this` for chaining
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* builder
|
|
94
|
+
* .set('order_id', 'ord_123')
|
|
95
|
+
* .set('llm_model', 'gpt-4')
|
|
96
|
+
* .set('tokens_used', 1500);
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
set(key: string, value: unknown): this {
|
|
100
|
+
this.event[key] = value;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets a named context object on the event.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* This is the primary method for adding structured business context.
|
|
109
|
+
* Each context is a nested object that groups related properties.
|
|
110
|
+
*
|
|
111
|
+
* @param name - Context name (e.g., 'user', 'cart', 'payment')
|
|
112
|
+
* @param data - Context data as key-value pairs
|
|
113
|
+
* @returns `this` for chaining
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* builder
|
|
118
|
+
* .setContext('user', {
|
|
119
|
+
* id: 'user_456',
|
|
120
|
+
* subscription: 'premium',
|
|
121
|
+
* account_age_days: 847,
|
|
122
|
+
* })
|
|
123
|
+
* .setContext('cart', {
|
|
124
|
+
* id: 'cart_xyz',
|
|
125
|
+
* item_count: 3,
|
|
126
|
+
* total_cents: 15999,
|
|
127
|
+
* })
|
|
128
|
+
* .setContext('payment', {
|
|
129
|
+
* method: 'card',
|
|
130
|
+
* provider: 'stripe',
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
setContext(name: string, data: Record<string, unknown>): this {
|
|
135
|
+
this.event[name] = data;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Merges additional fields into an existing context object.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* Useful for progressively building context throughout the request.
|
|
144
|
+
* If the context doesn't exist, it will be created.
|
|
145
|
+
*
|
|
146
|
+
* @param name - Context name to merge into
|
|
147
|
+
* @param data - Additional data to merge
|
|
148
|
+
* @returns `this` for chaining
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* // Initial payment context
|
|
153
|
+
* builder.setContext('payment', { method: 'card', provider: 'stripe' });
|
|
154
|
+
*
|
|
155
|
+
* // After payment completes, add timing
|
|
156
|
+
* builder.mergeContext('payment', {
|
|
157
|
+
* latency_ms: Date.now() - paymentStart,
|
|
158
|
+
* attempt: 1,
|
|
159
|
+
* transaction_id: 'txn_123',
|
|
160
|
+
* });
|
|
161
|
+
*
|
|
162
|
+
* // Result: { method, provider, latency_ms, attempt, transaction_id }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
mergeContext(name: string, data: Record<string, unknown>): this {
|
|
166
|
+
const existing = this.event[name];
|
|
167
|
+
if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
|
|
168
|
+
this.event[name] = { ...(existing as Record<string, unknown>), ...data };
|
|
169
|
+
} else {
|
|
170
|
+
this.event[name] = data;
|
|
171
|
+
}
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Sets the user ID on the event.
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* This sets the top-level `user_id` field, which is commonly used
|
|
180
|
+
* for filtering and user-centric analytics. For richer user context,
|
|
181
|
+
* use `setContext('user', {...})` instead or in addition.
|
|
182
|
+
*
|
|
183
|
+
* @param userId - The user identifier
|
|
184
|
+
* @returns `this` for chaining
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* builder.setUser('usr_123');
|
|
189
|
+
*
|
|
190
|
+
* // For richer context, also set a user object:
|
|
191
|
+
* builder.setContext('user', {
|
|
192
|
+
* id: 'usr_123',
|
|
193
|
+
* plan: 'premium',
|
|
194
|
+
* ltv_cents: 50000,
|
|
195
|
+
* });
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
setUser(userId: string): this {
|
|
199
|
+
this.event.user_id = userId;
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Captures an error with structured details.
|
|
205
|
+
*
|
|
206
|
+
* @remarks
|
|
207
|
+
* Automatically sets `outcome` to 'error'. Accepts either a native
|
|
208
|
+
* Error object or a custom error object with additional fields.
|
|
209
|
+
*
|
|
210
|
+
* @param err - Error object or custom error details
|
|
211
|
+
* @returns `this` for chaining
|
|
212
|
+
*
|
|
213
|
+
* @example Native Error
|
|
214
|
+
* ```typescript
|
|
215
|
+
* try {
|
|
216
|
+
* await riskyOperation();
|
|
217
|
+
* } catch (err) {
|
|
218
|
+
* builder.setError(err);
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @example Custom Error with Context
|
|
223
|
+
* ```typescript
|
|
224
|
+
* builder.setError({
|
|
225
|
+
* type: 'PaymentError',
|
|
226
|
+
* message: 'Card declined by issuer',
|
|
227
|
+
* code: 'card_declined',
|
|
228
|
+
* stripe_decline_code: 'insufficient_funds',
|
|
229
|
+
* card_brand: 'visa',
|
|
230
|
+
* card_last4: '4242',
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
setError(
|
|
235
|
+
err: Error | { type?: string; message: string; code?: string; stack?: string; [key: string]: unknown }
|
|
236
|
+
): this {
|
|
237
|
+
this.event.outcome = 'error';
|
|
238
|
+
|
|
239
|
+
if (err instanceof Error) {
|
|
240
|
+
this.event.error = {
|
|
241
|
+
type: err.name,
|
|
242
|
+
message: err.message,
|
|
243
|
+
stack: err.stack,
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
const { type, message, code, stack, ...extra } = err;
|
|
247
|
+
this.event.error = {
|
|
248
|
+
type: type || 'Error',
|
|
249
|
+
message,
|
|
250
|
+
code,
|
|
251
|
+
stack,
|
|
252
|
+
...extra,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Sets the HTTP status code and outcome.
|
|
260
|
+
*
|
|
261
|
+
* @remarks
|
|
262
|
+
* Automatically sets `outcome` based on the status code:
|
|
263
|
+
* - `< 400` → 'success'
|
|
264
|
+
* - `>= 400` → 'error'
|
|
265
|
+
*
|
|
266
|
+
* @param code - HTTP status code
|
|
267
|
+
* @returns `this` for chaining
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* builder.setStatus(404); // outcome = 'error'
|
|
272
|
+
* builder.setStatus(200); // outcome = 'success'
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
setStatus(code: number): this {
|
|
276
|
+
this.event.status_code = code;
|
|
277
|
+
this.event.outcome = code >= 400 ? 'error' : 'success';
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Records timing for a specific operation.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* A convenience method for calculating and setting duration values.
|
|
286
|
+
* The value is calculated as `Date.now() - startTime`.
|
|
287
|
+
*
|
|
288
|
+
* @param key - Property name for the timing (e.g., 'db_latency_ms')
|
|
289
|
+
* @param startTime - Start timestamp from `Date.now()`
|
|
290
|
+
* @returns `this` for chaining
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* const dbStart = Date.now();
|
|
295
|
+
* const result = await db.query('SELECT ...');
|
|
296
|
+
* builder.setTiming('db_latency_ms', dbStart);
|
|
297
|
+
*
|
|
298
|
+
* const paymentStart = Date.now();
|
|
299
|
+
* await processPayment();
|
|
300
|
+
* builder.setTiming('payment_latency_ms', paymentStart);
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
setTiming(key: string, startTime: number): this {
|
|
304
|
+
this.event[key] = Date.now() - startTime;
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { WideEvent } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the FullEvent client.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const client = new FullEvent({
|
|
9
|
+
* apiKey: process.env.FULLEVENT_API_KEY!,
|
|
10
|
+
* baseUrl: 'https://api.fullevent.io', // optional
|
|
11
|
+
* ping: true, // send a test ping on initialization
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @category Client
|
|
16
|
+
*/
|
|
17
|
+
export interface FullEventConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Your FullEvent project API key.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Get this from your FullEvent dashboard under Project Settings → API Keys.
|
|
23
|
+
*/
|
|
24
|
+
apiKey: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base URL for the FullEvent API.
|
|
28
|
+
*
|
|
29
|
+
* @defaultValue `'https://api.fullevent.io'`
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Only override this for self-hosted deployments or local development.
|
|
33
|
+
*/
|
|
34
|
+
baseUrl?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Send a ping event on initialization to verify connection.
|
|
38
|
+
*
|
|
39
|
+
* @defaultValue `false`
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Set to `true` during initial setup to verify your SDK configuration
|
|
43
|
+
* is working correctly. The ping is sent asynchronously and won't block
|
|
44
|
+
* your application startup.
|
|
45
|
+
*/
|
|
46
|
+
ping?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Standard properties for HTTP request events.
|
|
51
|
+
*
|
|
52
|
+
* These properties are automatically extracted by FullEvent for first-class
|
|
53
|
+
* filtering, dashboards, and error rate calculations.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const props: HttpRequestProperties = {
|
|
58
|
+
* status_code: 200,
|
|
59
|
+
* method: 'POST',
|
|
60
|
+
* path: '/api/checkout',
|
|
61
|
+
* duration_ms: 234,
|
|
62
|
+
* outcome: 'success',
|
|
63
|
+
* // Plus any custom properties
|
|
64
|
+
* user_id: 'usr_123',
|
|
65
|
+
* order_total: 9999,
|
|
66
|
+
* };
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @category Client
|
|
70
|
+
*/
|
|
71
|
+
export interface HttpRequestProperties {
|
|
72
|
+
/**
|
|
73
|
+
* HTTP status code (e.g., 200, 404, 500).
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Used for automatic error rate calculations:
|
|
77
|
+
* - `< 400` = success
|
|
78
|
+
* - `>= 400` = error
|
|
79
|
+
*/
|
|
80
|
+
status_code: number;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
84
|
+
*/
|
|
85
|
+
method?: string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Request path (e.g., `/api/users`, `/checkout`)
|
|
89
|
+
*/
|
|
90
|
+
path?: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Request duration in milliseconds.
|
|
94
|
+
*/
|
|
95
|
+
duration_ms?: number;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Explicit success/error indicator.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* If set, this takes precedence over `status_code` for error rate calculations.
|
|
102
|
+
*/
|
|
103
|
+
outcome?: 'success' | 'error';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Additional custom properties.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Add any context relevant to your application.
|
|
110
|
+
*/
|
|
111
|
+
[key: string]: unknown;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The main FullEvent client for ingesting events.
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* Use this client to send events directly from your application.
|
|
119
|
+
* For automatic request logging, see the middleware integrations:
|
|
120
|
+
* - {@link wideLogger} for Hono
|
|
121
|
+
* - {@link expressWideLogger} for Express
|
|
122
|
+
*
|
|
123
|
+
* @example Basic Usage
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { FullEvent } from '@fullevent/node';
|
|
126
|
+
*
|
|
127
|
+
* const client = new FullEvent({
|
|
128
|
+
* apiKey: process.env.FULLEVENT_API_KEY!,
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // Ingest a simple event
|
|
132
|
+
* await client.ingest('user.signup', {
|
|
133
|
+
* plan: 'pro',
|
|
134
|
+
* referral: 'newsletter',
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @example Fire and Forget
|
|
139
|
+
* ```typescript
|
|
140
|
+
* // Don't await if you don't want to block
|
|
141
|
+
* client.ingest('page.view', { path: '/home' })
|
|
142
|
+
* .catch(err => console.error('FullEvent error:', err));
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @category Client
|
|
146
|
+
*/
|
|
147
|
+
export class FullEvent {
|
|
148
|
+
private apiKey: string;
|
|
149
|
+
private baseUrl: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates a new FullEvent client instance.
|
|
153
|
+
*
|
|
154
|
+
* @param config - Configuration options
|
|
155
|
+
*/
|
|
156
|
+
constructor(config: FullEventConfig) {
|
|
157
|
+
this.apiKey = config.apiKey;
|
|
158
|
+
this.baseUrl = config.baseUrl || 'https://api.fullevent.io';
|
|
159
|
+
|
|
160
|
+
// Auto-ping if enabled
|
|
161
|
+
if (config.ping) {
|
|
162
|
+
this.ping().catch((err) => {
|
|
163
|
+
console.error('[FullEvent SDK] Auto-ping failed:', err);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Ingest a generic event with any properties.
|
|
170
|
+
*
|
|
171
|
+
* @param event - Event name/type (e.g., 'user.signup', 'order.completed')
|
|
172
|
+
* @param properties - Key-value pairs of event data
|
|
173
|
+
* @param timestamp - Optional ISO timestamp (defaults to now)
|
|
174
|
+
* @returns Promise resolving to success/error status
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* Events are processed asynchronously. The promise resolves when
|
|
178
|
+
* the event is accepted by the API, not when it's fully processed.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* await client.ingest('checkout.completed', {
|
|
183
|
+
* order_id: 'ord_123',
|
|
184
|
+
* total_cents: 9999,
|
|
185
|
+
* items: 3,
|
|
186
|
+
* user: {
|
|
187
|
+
* id: 'usr_456',
|
|
188
|
+
* plan: 'premium',
|
|
189
|
+
* },
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async ingest(
|
|
194
|
+
event: string,
|
|
195
|
+
properties: Record<string, unknown> = {},
|
|
196
|
+
timestamp?: string
|
|
197
|
+
): Promise<{ success: boolean; error?: unknown }> {
|
|
198
|
+
try {
|
|
199
|
+
const response = await fetch(`${this.baseUrl}/ingest`, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
204
|
+
},
|
|
205
|
+
body: JSON.stringify({
|
|
206
|
+
event,
|
|
207
|
+
properties,
|
|
208
|
+
timestamp: timestamp || new Date().toISOString(),
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
const error = await response.json();
|
|
214
|
+
console.error('[FullEvent SDK] Ingestion failed:', error);
|
|
215
|
+
return { success: false, error };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { success: true };
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[FullEvent SDK] Network error during ingestion:', error);
|
|
221
|
+
return { success: false, error };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Ingest an HTTP request event with typed properties.
|
|
227
|
+
*
|
|
228
|
+
* @param properties - HTTP request properties with optional custom data
|
|
229
|
+
* @param timestamp - Optional ISO timestamp (defaults to now)
|
|
230
|
+
* @returns Promise resolving to success/error status
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* This is a convenience method that provides TypeScript autocomplete
|
|
234
|
+
* for standard HTTP properties. Under the hood, it calls `ingest()`
|
|
235
|
+
* with the event type `'http_request'`.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* await client.ingestHttpRequest({
|
|
240
|
+
* status_code: 200,
|
|
241
|
+
* method: 'POST',
|
|
242
|
+
* path: '/api/checkout',
|
|
243
|
+
* duration_ms: 234,
|
|
244
|
+
* outcome: 'success',
|
|
245
|
+
* // Custom properties
|
|
246
|
+
* user_id: 'usr_123',
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async ingestHttpRequest(
|
|
251
|
+
properties: HttpRequestProperties,
|
|
252
|
+
timestamp?: string
|
|
253
|
+
): Promise<{ success: boolean; error?: unknown }> {
|
|
254
|
+
return this.ingest('http_request', properties, timestamp);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Ping the FullEvent API to verify connection.
|
|
259
|
+
*
|
|
260
|
+
* @returns Promise resolving to connection status with latency
|
|
261
|
+
*
|
|
262
|
+
* @remarks
|
|
263
|
+
* Use this method to verify your SDK is correctly configured.
|
|
264
|
+
* It sends a lightweight ping event and measures round-trip latency.
|
|
265
|
+
* Commonly used during setup or in health check endpoints.
|
|
266
|
+
*
|
|
267
|
+
* @example Basic ping
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const result = await client.ping();
|
|
270
|
+
* if (result.success) {
|
|
271
|
+
* console.log(`Connected! Latency: ${result.latency_ms}ms`);
|
|
272
|
+
* } else {
|
|
273
|
+
* console.error('Connection failed:', result.error);
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
277
|
+
* @example Health check endpoint
|
|
278
|
+
* ```typescript
|
|
279
|
+
* app.get('/health', async (c) => {
|
|
280
|
+
* const ping = await fullevent.ping();
|
|
281
|
+
* return c.json({
|
|
282
|
+
* status: ping.success ? 'healthy' : 'degraded',
|
|
283
|
+
* fullevent: ping,
|
|
284
|
+
* });
|
|
285
|
+
* });
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
async ping(): Promise<{ success: boolean; latency_ms?: number; error?: unknown }> {
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const result = await this.ingest('fullevent.ping', {
|
|
293
|
+
// Standard wide event properties
|
|
294
|
+
status_code: 200,
|
|
295
|
+
outcome: 'success',
|
|
296
|
+
duration_ms: 0, // Will be updated after
|
|
297
|
+
|
|
298
|
+
// SDK info
|
|
299
|
+
sdk: '@fullevent/node',
|
|
300
|
+
sdk_version: '1.0.0',
|
|
301
|
+
|
|
302
|
+
// Runtime info
|
|
303
|
+
runtime: typeof process !== 'undefined' ? 'node' : 'browser',
|
|
304
|
+
node_version: typeof process !== 'undefined' ? process.version : undefined,
|
|
305
|
+
platform: typeof process !== 'undefined' ? process.platform : 'browser',
|
|
306
|
+
|
|
307
|
+
// Ping metadata
|
|
308
|
+
ping_type: 'connection_test',
|
|
309
|
+
message: '🎉 Your first event! FullEvent is connected and ready.',
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const latency_ms = Date.now() - start;
|
|
313
|
+
|
|
314
|
+
if (result.success) {
|
|
315
|
+
return { success: true, latency_ms };
|
|
316
|
+
} else {
|
|
317
|
+
return { success: false, latency_ms, error: result.error };
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
const latency_ms = Date.now() - start;
|
|
321
|
+
return { success: false, latency_ms, error };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|