@zapier/zapier-sdk 0.13.3 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/api/schemas.d.ts +174 -174
  3. package/dist/constants.d.ts +4 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/constants.js +4 -0
  6. package/dist/index.cjs +436 -5
  7. package/dist/index.d.mts +380 -156
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.mjs +407 -9
  12. package/dist/plugins/api/index.d.ts +1 -3
  13. package/dist/plugins/api/index.d.ts.map +1 -1
  14. package/dist/plugins/eventEmission/builders.d.ts +13 -0
  15. package/dist/plugins/eventEmission/builders.d.ts.map +1 -0
  16. package/dist/plugins/eventEmission/builders.js +78 -0
  17. package/dist/plugins/eventEmission/index.d.ts +34 -0
  18. package/dist/plugins/eventEmission/index.d.ts.map +1 -0
  19. package/dist/plugins/eventEmission/index.js +216 -0
  20. package/dist/plugins/eventEmission/index.test.d.ts +5 -0
  21. package/dist/plugins/eventEmission/index.test.d.ts.map +1 -0
  22. package/dist/plugins/eventEmission/index.test.js +143 -0
  23. package/dist/plugins/eventEmission/transport.d.ts +37 -0
  24. package/dist/plugins/eventEmission/transport.d.ts.map +1 -0
  25. package/dist/plugins/eventEmission/transport.js +96 -0
  26. package/dist/plugins/eventEmission/transport.test.d.ts +5 -0
  27. package/dist/plugins/eventEmission/transport.test.d.ts.map +1 -0
  28. package/dist/plugins/eventEmission/transport.test.js +153 -0
  29. package/dist/plugins/eventEmission/types.d.ts +53 -0
  30. package/dist/plugins/eventEmission/types.d.ts.map +1 -0
  31. package/dist/plugins/eventEmission/types.js +1 -0
  32. package/dist/plugins/eventEmission/utils.d.ts +45 -0
  33. package/dist/plugins/eventEmission/utils.d.ts.map +1 -0
  34. package/dist/plugins/eventEmission/utils.js +114 -0
  35. package/dist/plugins/fetch/schemas.d.ts +4 -4
  36. package/dist/plugins/getAction/schemas.d.ts +2 -2
  37. package/dist/plugins/listActions/schemas.d.ts +2 -2
  38. package/dist/plugins/listInputFieldChoices/schemas.d.ts +4 -4
  39. package/dist/plugins/listInputFields/schemas.d.ts +2 -2
  40. package/dist/plugins/request/schemas.d.ts +4 -4
  41. package/dist/plugins/runAction/schemas.d.ts +2 -2
  42. package/dist/resolvers/actionType.d.ts.map +1 -1
  43. package/dist/resolvers/actionType.js +2 -3
  44. package/dist/resolvers/authenticationId.d.ts.map +1 -1
  45. package/dist/schemas/Action.d.ts +2 -2
  46. package/dist/schemas/App.d.ts +30 -30
  47. package/dist/sdk.d.ts +2 -2
  48. package/dist/sdk.d.ts.map +1 -1
  49. package/dist/sdk.js +4 -1
  50. package/dist/types/sdk.d.ts +5 -1
  51. package/dist/types/sdk.d.ts.map +1 -1
  52. package/dist/types/telemetry-events.d.ts +76 -0
  53. package/dist/types/telemetry-events.d.ts.map +1 -0
  54. package/dist/types/telemetry-events.js +8 -0
  55. package/package.json +1 -1
  56. package/src/constants.ts +6 -0
  57. package/src/index.ts +24 -0
  58. package/src/plugins/api/index.ts +1 -5
  59. package/src/plugins/eventEmission/builders.ts +115 -0
  60. package/src/plugins/eventEmission/index.test.ts +169 -0
  61. package/src/plugins/eventEmission/index.ts +294 -0
  62. package/src/plugins/eventEmission/transport.test.ts +214 -0
  63. package/src/plugins/eventEmission/transport.ts +135 -0
  64. package/src/plugins/eventEmission/types.ts +58 -0
  65. package/src/plugins/eventEmission/utils.ts +121 -0
  66. package/src/resolvers/actionType.ts +4 -3
  67. package/src/resolvers/authenticationId.ts +2 -1
  68. package/src/sdk.ts +5 -1
  69. package/src/types/sdk.ts +7 -1
  70. package/src/types/telemetry-events.ts +85 -0
  71. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Event Emission Plugin for Zapier SDK
3
+ *
4
+ * Provides silent telemetry event emission capability to the SDK.
5
+ * All events are emitted asynchronously and failures are silently handled.
6
+ */
7
+
8
+ import type { Plugin } from "../../types/plugin";
9
+ import type { BaseEvent } from "../../types/telemetry-events";
10
+ import type { EventTransport, TransportConfig } from "./transport";
11
+ import { createTransport } from "./transport";
12
+ import { generateEventId, getCurrentTimestamp, getReleaseId } from "./utils";
13
+ import {
14
+ buildApplicationLifecycleEvent,
15
+ buildErrorEventWithContext,
16
+ } from "./builders";
17
+ import { TRACKING_API_ENDPOINT } from "../../constants";
18
+
19
+ // Plugin configuration
20
+ export interface EventEmissionConfig {
21
+ enabled?: boolean;
22
+ transport?: TransportConfig;
23
+ }
24
+
25
+ // Context provided by this plugin
26
+ export interface EventEmissionContext {
27
+ eventEmission: {
28
+ transport: EventTransport;
29
+ config: EventEmissionConfig;
30
+ // Generic event emission method
31
+ emit<T extends any>(subject: string, event: T): void;
32
+ // Builder for base event properties
33
+ createBaseEvent(): BaseEvent;
34
+ };
35
+ }
36
+
37
+ export interface EventEmissionProvides {
38
+ context: EventEmissionContext;
39
+ }
40
+
41
+ const APPLICATION_LIFECYCLE_EVENT_SUBJECT =
42
+ "platform.sdk.ApplicationLifecycleEvent";
43
+ const ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
44
+
45
+ // Silent emission wrapper - ensures events never disrupt SDK operation
46
+ async function silentEmit<T extends any>(
47
+ transport: EventTransport,
48
+ subject: string,
49
+ event: T,
50
+ ): Promise<void> {
51
+ try {
52
+ // Fire and forget - don't await the transport
53
+ transport.emit(subject, event).catch(() => {
54
+ // Silently ignore transport failures
55
+ });
56
+ } catch {
57
+ // Silently ignore all errors
58
+ }
59
+ }
60
+
61
+ // Helper to get transport config from environment or defaults
62
+ function getTransportConfig(): TransportConfig {
63
+ const envTransport = process?.env?.ZAPIER_SDK_TELEMETRY_TRANSPORT;
64
+
65
+ if (envTransport === "noop" || envTransport === "disabled") {
66
+ return { type: "noop" };
67
+ }
68
+
69
+ if (envTransport === "console") {
70
+ return { type: "console" };
71
+ }
72
+
73
+ // Default to HTTP transport
74
+ const endpoint =
75
+ process?.env?.ZAPIER_SDK_TELEMETRY_ENDPOINT || TRACKING_API_ENDPOINT;
76
+
77
+ return {
78
+ type: "http",
79
+ endpoint,
80
+ };
81
+ }
82
+
83
+ export const eventEmissionPlugin: Plugin<
84
+ {},
85
+ { options: { eventEmission?: EventEmissionConfig } },
86
+ EventEmissionProvides
87
+ > = ({ context }) => {
88
+ const defaultTransport = getTransportConfig();
89
+
90
+ // Merge config: env var takes precedence over options, options take precedence over defaults
91
+ const config: EventEmissionConfig = {
92
+ enabled: context.options.eventEmission?.enabled ?? true,
93
+ transport:
94
+ // If env var is set, use it (defaultTransport will be from env)
95
+ process?.env?.ZAPIER_SDK_TELEMETRY_TRANSPORT
96
+ ? defaultTransport
97
+ : // Otherwise, use option transport or default
98
+ (context.options.eventEmission?.transport ?? defaultTransport),
99
+ };
100
+
101
+ const startupTime = Date.now();
102
+ let shutdownStartTime: number | null = null;
103
+
104
+ // If disabled, return noop implementations
105
+ if (!config.enabled) {
106
+ return {
107
+ context: {
108
+ eventEmission: {
109
+ transport: createTransport({ type: "noop" }),
110
+ config,
111
+ emit: () => {},
112
+ createBaseEvent: (): BaseEvent => ({
113
+ event_id: generateEventId(),
114
+ timestamp_ms: getCurrentTimestamp(),
115
+ release_id: getReleaseId(),
116
+ customuser_id: null,
117
+ account_id: null,
118
+ identity_id: null,
119
+ visitor_id: null,
120
+ correlation_id: null,
121
+ }),
122
+ },
123
+ },
124
+ };
125
+ }
126
+
127
+ let transport: EventTransport;
128
+ try {
129
+ transport = createTransport(config.transport || { type: "noop" });
130
+ } catch {
131
+ // If transport creation fails, fallback to noop to maintain silent operation
132
+ transport = createTransport({ type: "noop" });
133
+ }
134
+
135
+ // Helper to create base event
136
+ const createBaseEventHelper = (): BaseEvent => ({
137
+ event_id: generateEventId(),
138
+ timestamp_ms: getCurrentTimestamp(),
139
+ release_id: getReleaseId(),
140
+ customuser_id: null,
141
+ account_id: null,
142
+ identity_id: null,
143
+ visitor_id: null,
144
+ correlation_id: null,
145
+ });
146
+
147
+ // Register lifecycle event handlers if enabled
148
+ if (config.enabled) {
149
+ // Emit startup event
150
+ const startupEvent = buildApplicationLifecycleEvent({
151
+ lifecycle_event_type: "startup",
152
+ });
153
+ silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, startupEvent);
154
+
155
+ // Register process event handlers (Node.js only)
156
+ if (typeof process?.on === "function") {
157
+ // Handle normal process exit
158
+ process.on("exit", (code: number) => {
159
+ const uptime = Date.now() - startupTime;
160
+ const shutdownDuration = shutdownStartTime
161
+ ? Date.now() - shutdownStartTime
162
+ : null;
163
+
164
+ const exitEvent = buildApplicationLifecycleEvent({
165
+ lifecycle_event_type: "exit",
166
+ exit_code: code,
167
+ uptime_ms: uptime,
168
+ is_graceful_shutdown: code === 0,
169
+ shutdown_duration_ms: shutdownDuration,
170
+ });
171
+ silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, exitEvent);
172
+ });
173
+
174
+ // Handle uncaught exceptions
175
+ process.on("uncaughtException", async (error: Error) => {
176
+ const errorEvent = buildErrorEventWithContext({
177
+ error_message: error.message || "Unknown error",
178
+ error_type: "UncaughtException",
179
+ error_stack_trace: error.stack || null,
180
+ error_severity: "critical",
181
+ is_user_facing: false,
182
+ is_recoverable: false,
183
+ execution_start_time: startupTime,
184
+ });
185
+
186
+ // Wait up to 300ms for telemetry to send before allowing process to exit
187
+ try {
188
+ await Promise.race([
189
+ transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
190
+ new Promise((resolve) => setTimeout(resolve, 300)),
191
+ ]);
192
+ } catch {
193
+ // Silently ignore telemetry failures
194
+ }
195
+ // Let Node.js exit naturally after all handlers complete
196
+ });
197
+
198
+ // Handle unhandled promise rejections
199
+ process.on(
200
+ "unhandledRejection",
201
+ async (reason: any, promise: Promise<any>) => {
202
+ const errorMessage =
203
+ reason instanceof Error
204
+ ? reason.message
205
+ : typeof reason === "string"
206
+ ? reason
207
+ : "Unhandled promise rejection";
208
+
209
+ const errorStack = reason instanceof Error ? reason.stack : null;
210
+
211
+ const errorEvent = buildErrorEventWithContext({
212
+ error_message: errorMessage,
213
+ error_type: "UnhandledRejection",
214
+ error_stack_trace: errorStack,
215
+ error_severity: "critical",
216
+ is_user_facing: false,
217
+ is_recoverable: false,
218
+ execution_start_time: startupTime,
219
+ error_metadata: {
220
+ promise: String(promise),
221
+ },
222
+ });
223
+
224
+ // Wait up to 300ms for telemetry to send
225
+ try {
226
+ await Promise.race([
227
+ transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
228
+ new Promise((resolve) => setTimeout(resolve, 300)),
229
+ ]);
230
+ } catch {
231
+ // Silently ignore telemetry failures
232
+ }
233
+ },
234
+ );
235
+
236
+ // Handle termination signals
237
+ const handleSignal = async (signal: string) => {
238
+ shutdownStartTime = Date.now();
239
+ const uptime = Date.now() - startupTime;
240
+
241
+ const signalEvent = buildApplicationLifecycleEvent({
242
+ lifecycle_event_type: "signal_termination",
243
+ signal_name: signal,
244
+ uptime_ms: uptime,
245
+ is_graceful_shutdown: true,
246
+ });
247
+
248
+ // Wait up to 300ms for telemetry to send
249
+ try {
250
+ await Promise.race([
251
+ transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
252
+ new Promise((resolve) => setTimeout(resolve, 300)),
253
+ ]);
254
+ } catch {
255
+ // Silently ignore telemetry failures
256
+ }
257
+ // Let other signal handlers run and decide when to exit
258
+ };
259
+
260
+ // Register signal handlers
261
+ ["SIGINT", "SIGTERM"].forEach((signal) => {
262
+ process.on(signal as any, () => handleSignal(signal));
263
+ });
264
+ }
265
+ }
266
+
267
+ return {
268
+ context: {
269
+ eventEmission: {
270
+ transport,
271
+ config,
272
+ emit: <T extends any>(subject: string, event: T) => {
273
+ silentEmit(transport, subject, event);
274
+ },
275
+ createBaseEvent: createBaseEventHelper,
276
+ },
277
+ },
278
+ };
279
+ };
280
+
281
+ // Export types and utilities for other plugins
282
+ export type {
283
+ EventContext,
284
+ ApplicationLifecycleEventData,
285
+ EnhancedErrorEventData,
286
+ } from "./types";
287
+ export {
288
+ buildApplicationLifecycleEvent,
289
+ buildErrorEventWithContext,
290
+ buildErrorEvent,
291
+ createBaseEvent,
292
+ } from "./builders";
293
+ export type { BaseEvent } from "../../types/telemetry-events";
294
+ export * from "./utils";
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Tests for Event Transport Layer
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from "vitest";
6
+ import {
7
+ HttpTransport,
8
+ ConsoleTransport,
9
+ NoopTransport,
10
+ createTransport,
11
+ } from "./transport";
12
+ import type { TelemetryEvent } from "../../types/telemetry-events";
13
+
14
+ // Mock fetch globally
15
+ const mockFetch = vi.fn();
16
+ global.fetch = mockFetch;
17
+
18
+ // Mock console.log
19
+ const mockConsoleLog = vi.spyOn(console, "log").mockImplementation(() => {});
20
+
21
+ describe("Transport Layer", () => {
22
+ const sampleEvent: TelemetryEvent = {
23
+ event_id: "test-123",
24
+ timestamp_ms: Date.now(),
25
+ release_id: "test-release",
26
+ error_message: "Test error",
27
+ is_user_facing: false,
28
+ } as any;
29
+
30
+ const sampleSubject = "platform.sdk.ErrorOccurredEvent";
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ });
35
+
36
+ describe("HttpTransport", () => {
37
+ it("should emit events via HTTP successfully", async () => {
38
+ mockFetch.mockResolvedValueOnce({
39
+ ok: true,
40
+ status: 200,
41
+ });
42
+
43
+ const transport = new HttpTransport({
44
+ endpoint: "https://telemetry.example.com/events",
45
+ headers: { "x-api-key": "test-key" },
46
+ });
47
+
48
+ await transport.emit(sampleSubject, sampleEvent);
49
+
50
+ expect(mockFetch).toHaveBeenCalledWith(
51
+ "https://telemetry.example.com/events",
52
+ {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ "x-api-key": "test-key",
57
+ },
58
+ body: JSON.stringify({
59
+ subject: sampleSubject,
60
+ properties: sampleEvent,
61
+ }),
62
+ },
63
+ );
64
+ });
65
+
66
+ it("should retry on HTTP failures", async () => {
67
+ // Fail twice, then succeed
68
+ mockFetch
69
+ .mockResolvedValueOnce({ ok: false, status: 500 })
70
+ .mockResolvedValueOnce({ ok: false, status: 500 })
71
+ .mockResolvedValueOnce({ ok: true, status: 200 });
72
+
73
+ const transport = new HttpTransport({
74
+ endpoint: "https://telemetry.example.com/events",
75
+ retryAttempts: 3,
76
+ retryDelayMs: 10, // Short delay for testing
77
+ });
78
+
79
+ await transport.emit(sampleSubject, sampleEvent);
80
+
81
+ expect(mockFetch).toHaveBeenCalledTimes(3);
82
+ });
83
+
84
+ it("should handle network errors silently", async () => {
85
+ mockFetch.mockRejectedValue(new Error("Network error"));
86
+
87
+ const transport = new HttpTransport({
88
+ endpoint: "https://telemetry.example.com/events",
89
+ retryAttempts: 1,
90
+ retryDelayMs: 1,
91
+ });
92
+
93
+ // Should not throw despite network error
94
+ await expect(
95
+ transport.emit(sampleSubject, sampleEvent),
96
+ ).resolves.toBeUndefined();
97
+ });
98
+
99
+ it("should stop retrying after max attempts", async () => {
100
+ mockFetch.mockResolvedValue({ ok: false, status: 500 });
101
+
102
+ const transport = new HttpTransport({
103
+ endpoint: "https://telemetry.example.com/events",
104
+ retryAttempts: 2,
105
+ retryDelayMs: 1,
106
+ });
107
+
108
+ await transport.emit(sampleSubject, sampleEvent);
109
+
110
+ expect(mockFetch).toHaveBeenCalledTimes(2);
111
+ });
112
+ });
113
+
114
+ describe("ConsoleTransport", () => {
115
+ it("should log events to console", async () => {
116
+ const transport = new ConsoleTransport();
117
+
118
+ await transport.emit(sampleSubject, sampleEvent);
119
+
120
+ expect(mockConsoleLog).toHaveBeenCalledWith(
121
+ "[SDK Telemetry]",
122
+ JSON.stringify(
123
+ { subject: sampleSubject, properties: sampleEvent },
124
+ null,
125
+ 2,
126
+ ),
127
+ );
128
+ });
129
+
130
+ it("should handle console errors silently", async () => {
131
+ mockConsoleLog.mockImplementation(() => {
132
+ throw new Error("Console error");
133
+ });
134
+
135
+ const transport = new ConsoleTransport();
136
+
137
+ // Should not throw despite console error
138
+ await expect(
139
+ transport.emit(sampleSubject, sampleEvent),
140
+ ).resolves.toBeUndefined();
141
+ });
142
+ });
143
+
144
+ describe("NoopTransport", () => {
145
+ it("should do nothing when emitting events", async () => {
146
+ const transport = new NoopTransport();
147
+
148
+ await transport.emit(sampleSubject, sampleEvent);
149
+
150
+ // Verify no side effects
151
+ expect(mockFetch).not.toHaveBeenCalled();
152
+ expect(mockConsoleLog).not.toHaveBeenCalled();
153
+ });
154
+ });
155
+
156
+ describe("createTransport", () => {
157
+ it("should create HTTP transport with valid config", () => {
158
+ const transport = createTransport({
159
+ type: "http",
160
+ endpoint: "https://example.com",
161
+ headers: { "x-key": "value" },
162
+ retryAttempts: 5,
163
+ retryDelayMs: 2000,
164
+ });
165
+
166
+ expect(transport).toBeInstanceOf(HttpTransport);
167
+ });
168
+
169
+ it("should create console transport", () => {
170
+ const transport = createTransport({
171
+ type: "console",
172
+ });
173
+
174
+ expect(transport).toBeInstanceOf(ConsoleTransport);
175
+ });
176
+
177
+ it("should create noop transport by default", () => {
178
+ const transport = createTransport({
179
+ type: "noop",
180
+ });
181
+
182
+ expect(transport).toBeInstanceOf(NoopTransport);
183
+ });
184
+
185
+ it("should fallback to noop transport on invalid HTTP config", () => {
186
+ const transport = createTransport({
187
+ type: "http",
188
+ // Missing endpoint
189
+ });
190
+
191
+ expect(transport).toBeInstanceOf(NoopTransport);
192
+ });
193
+
194
+ it("should fallback to noop transport on unknown type", () => {
195
+ const transport = createTransport({
196
+ type: "unknown" as any,
197
+ });
198
+
199
+ expect(transport).toBeInstanceOf(NoopTransport);
200
+ });
201
+
202
+ it("should handle transport creation errors gracefully", () => {
203
+ // Since createTransport already handles errors internally,
204
+ // this test verifies that invalid configs don't throw
205
+ expect(() => {
206
+ const transport = createTransport({
207
+ type: "http" as const,
208
+ // Missing required endpoint - should fallback to noop
209
+ });
210
+ expect(transport).toBeInstanceOf(NoopTransport);
211
+ }).not.toThrow();
212
+ });
213
+ });
214
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Transport abstraction layer for event emission
3
+ *
4
+ * Provides configurable transport mechanisms for emitting telemetry events.
5
+ * All transports implement silent failure to prevent SDK disruption.
6
+ */
7
+
8
+ // Constants for transport configuration
9
+ const DEFAULT_RETRY_ATTEMPTS = 2;
10
+ const DEFAULT_RETRY_DELAY_MS = 300;
11
+
12
+ // Transport interface - all implementations must be async and silent
13
+ export interface EventTransport {
14
+ emit<T extends any>(subject: string, event: T): Promise<void>;
15
+ close?(): Promise<void>;
16
+ }
17
+
18
+ // Transport configuration
19
+ export interface TransportConfig {
20
+ type: "http" | "console" | "noop";
21
+ endpoint?: string;
22
+ headers?: Record<string, string>;
23
+ retryAttempts?: number;
24
+ retryDelayMs?: number;
25
+ }
26
+
27
+ // HTTP Transport - sends events to remote endpoint
28
+ export class HttpTransport implements EventTransport {
29
+ constructor(
30
+ private config: {
31
+ endpoint: string;
32
+ headers?: Record<string, string>;
33
+ retryAttempts?: number;
34
+ retryDelayMs?: number;
35
+ },
36
+ ) {}
37
+
38
+ async emit<T extends any>(subject: string, event: T): Promise<void> {
39
+ try {
40
+ await this.emitWithRetry(
41
+ subject,
42
+ event,
43
+ this.config.retryAttempts || DEFAULT_RETRY_ATTEMPTS,
44
+ );
45
+ } catch {
46
+ // Silent failure - never throw
47
+ }
48
+ }
49
+
50
+ private async emitWithRetry<T extends any>(
51
+ subject: string,
52
+ event: T,
53
+ attemptsLeft: number,
54
+ ): Promise<void> {
55
+ try {
56
+ const payload = {
57
+ subject,
58
+ properties: event,
59
+ };
60
+
61
+ const response = await fetch(this.config.endpoint, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ ...this.config.headers,
66
+ },
67
+ body: JSON.stringify(payload),
68
+ });
69
+
70
+ if (!response.ok && attemptsLeft > 1) {
71
+ await this.delay(this.config.retryDelayMs || DEFAULT_RETRY_DELAY_MS);
72
+ return this.emitWithRetry(subject, event, attemptsLeft - 1);
73
+ }
74
+ } catch (error) {
75
+ if (attemptsLeft > 1) {
76
+ await this.delay(this.config.retryDelayMs || DEFAULT_RETRY_DELAY_MS);
77
+ return this.emitWithRetry(subject, event, attemptsLeft - 1);
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ private async delay(ms: number): Promise<void> {
84
+ return new Promise((resolve) => setTimeout(resolve, ms));
85
+ }
86
+ }
87
+
88
+ // Console Transport - logs events to console (for development)
89
+ export class ConsoleTransport implements EventTransport {
90
+ async emit<T extends any>(subject: string, event: T): Promise<void> {
91
+ try {
92
+ console.log(
93
+ "[SDK Telemetry]",
94
+ JSON.stringify({ subject, properties: event }, null, 2),
95
+ );
96
+ } catch {
97
+ // Silent failure - never throw
98
+ }
99
+ }
100
+ }
101
+
102
+ // No-op Transport - discards all events (for testing/disabled state)
103
+ export class NoopTransport implements EventTransport {
104
+ async emit<T extends any>(_subject: string, _event: T): Promise<void> {
105
+ // Intentionally do nothing
106
+ }
107
+ }
108
+
109
+ // Transport factory
110
+ export function createTransport(config: TransportConfig): EventTransport {
111
+ try {
112
+ switch (config.type) {
113
+ case "http":
114
+ if (!config.endpoint) {
115
+ throw new Error("HTTP transport requires endpoint");
116
+ }
117
+ return new HttpTransport({
118
+ endpoint: config.endpoint,
119
+ headers: config.headers,
120
+ retryAttempts: config.retryAttempts,
121
+ retryDelayMs: config.retryDelayMs,
122
+ });
123
+
124
+ case "console":
125
+ return new ConsoleTransport();
126
+
127
+ case "noop":
128
+ default:
129
+ return new NoopTransport();
130
+ }
131
+ } catch {
132
+ // If transport creation fails, return noop to maintain silent operation
133
+ return new NoopTransport();
134
+ }
135
+ }
@@ -0,0 +1,58 @@
1
+ // Common context that can be injected into events
2
+ export interface EventContext {
3
+ customuser_id?: number | null;
4
+ account_id?: number | null;
5
+ identity_id?: number | null;
6
+ visitor_id?: string | null;
7
+ correlation_id?: string | null;
8
+ selected_api?: string | null;
9
+ app_id?: number | null;
10
+ app_version_id?: number | null;
11
+ zap_id?: number | null;
12
+ node_id?: number | null;
13
+ environment?: string | null;
14
+ sdk_version?: string | null;
15
+ operation_type?: string | null;
16
+ }
17
+
18
+ export interface ErrorEventData {
19
+ error_message: string;
20
+ is_user_facing: boolean;
21
+ error_type?: string | null;
22
+ error_status_code?: number | null;
23
+ error_stack_trace?: string | null;
24
+ error_source_method?: string | null;
25
+ error_source_file?: string | null;
26
+ error_line_number?: number | null;
27
+ operation_type?: string | null;
28
+ operation_key?: string | null;
29
+ error_severity?: string | null;
30
+ is_recoverable?: boolean | null;
31
+ error_metadata?: Record<string, string | null> | null;
32
+ parent_error_id?: string | null;
33
+ error_occurred_timestamp_ms?: number | null;
34
+ error_code?: string | null;
35
+ recovery_attempted?: boolean | null;
36
+ recovery_action?: string | null;
37
+ recovery_successful?: boolean | null;
38
+ execution_time_before_error_ms?: number | null;
39
+ }
40
+
41
+ // Enhanced Error Event Builder with runtime context
42
+ export interface EnhancedErrorEventData extends ErrorEventData {
43
+ execution_start_time?: number | null;
44
+ }
45
+
46
+ export interface ApplicationLifecycleEventData {
47
+ lifecycle_event_type: "startup" | "exit" | "signal_termination";
48
+ exit_code?: number | null;
49
+ signal_name?: string | null;
50
+ uptime_ms?: number | null;
51
+ is_graceful_shutdown?: boolean | null;
52
+ shutdown_duration_ms?: number | null;
53
+ // Allow overriding auto-gathered system info if needed
54
+ memory_usage_bytes?: number | null;
55
+ peak_memory_usage_bytes?: number | null;
56
+ cpu_time_ms?: number | null;
57
+ active_requests_count?: number | null;
58
+ }