@zapier/zapier-sdk 0.13.3 → 0.13.5

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 (81) hide show
  1. package/CHANGELOG.md +12 -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 +457 -11
  7. package/dist/index.d.mts +389 -158
  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 +428 -15
  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/index.test.js +3 -1
  38. package/dist/plugins/listActions/schemas.d.ts +2 -2
  39. package/dist/plugins/listAuthentications/index.test.js +3 -1
  40. package/dist/plugins/listInputFieldChoices/schemas.d.ts +4 -4
  41. package/dist/plugins/listInputFields/schemas.d.ts +2 -2
  42. package/dist/plugins/manifest/index.d.ts +9 -1
  43. package/dist/plugins/manifest/index.d.ts.map +1 -1
  44. package/dist/plugins/manifest/index.js +19 -7
  45. package/dist/plugins/manifest/index.test.js +4 -2
  46. package/dist/plugins/request/schemas.d.ts +4 -4
  47. package/dist/plugins/runAction/schemas.d.ts +2 -2
  48. package/dist/resolvers/actionType.d.ts.map +1 -1
  49. package/dist/resolvers/actionType.js +2 -3
  50. package/dist/resolvers/authenticationId.d.ts.map +1 -1
  51. package/dist/schemas/Action.d.ts +2 -2
  52. package/dist/schemas/App.d.ts +30 -30
  53. package/dist/sdk.d.ts +3 -3
  54. package/dist/sdk.d.ts.map +1 -1
  55. package/dist/sdk.js +4 -1
  56. package/dist/types/sdk.d.ts +5 -1
  57. package/dist/types/sdk.d.ts.map +1 -1
  58. package/dist/types/telemetry-events.d.ts +76 -0
  59. package/dist/types/telemetry-events.d.ts.map +1 -0
  60. package/dist/types/telemetry-events.js +8 -0
  61. package/package.json +1 -1
  62. package/src/constants.ts +6 -0
  63. package/src/index.ts +24 -0
  64. package/src/plugins/api/index.ts +1 -5
  65. package/src/plugins/eventEmission/builders.ts +115 -0
  66. package/src/plugins/eventEmission/index.test.ts +169 -0
  67. package/src/plugins/eventEmission/index.ts +294 -0
  68. package/src/plugins/eventEmission/transport.test.ts +214 -0
  69. package/src/plugins/eventEmission/transport.ts +135 -0
  70. package/src/plugins/eventEmission/types.ts +58 -0
  71. package/src/plugins/eventEmission/utils.ts +121 -0
  72. package/src/plugins/listActions/index.test.ts +3 -1
  73. package/src/plugins/listAuthentications/index.test.ts +3 -1
  74. package/src/plugins/manifest/index.test.ts +4 -4
  75. package/src/plugins/manifest/index.ts +39 -14
  76. package/src/resolvers/actionType.ts +4 -3
  77. package/src/resolvers/authenticationId.ts +2 -1
  78. package/src/sdk.ts +5 -1
  79. package/src/types/sdk.ts +7 -1
  80. package/src/types/telemetry-events.ts +85 -0
  81. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAUnE,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAGD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE;QACb,SAAS,EAAE,cAAc,CAAC;QAC1B,MAAM,EAAE,mBAAmB,CAAC;QAE5B,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;QAErD,eAAe,IAAI,SAAS,CAAC;KAC9B,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AA4CD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,EAAE,EACF;IAAE,OAAO,EAAE;QAAE,aAAa,CAAC,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAAE,EACpD,qBAAqB,CAiMtB,CAAC;AAGF,YAAY,EACV,YAAY,EACZ,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,EAC1B,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,cAAc,SAAS,CAAC"}
@@ -0,0 +1,216 @@
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
+ import { createTransport } from "./transport";
8
+ import { generateEventId, getCurrentTimestamp, getReleaseId } from "./utils";
9
+ import { buildApplicationLifecycleEvent, buildErrorEventWithContext, } from "./builders";
10
+ import { TRACKING_API_ENDPOINT } from "../../constants";
11
+ const APPLICATION_LIFECYCLE_EVENT_SUBJECT = "platform.sdk.ApplicationLifecycleEvent";
12
+ const ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
13
+ // Silent emission wrapper - ensures events never disrupt SDK operation
14
+ async function silentEmit(transport, subject, event) {
15
+ try {
16
+ // Fire and forget - don't await the transport
17
+ transport.emit(subject, event).catch(() => {
18
+ // Silently ignore transport failures
19
+ });
20
+ }
21
+ catch {
22
+ // Silently ignore all errors
23
+ }
24
+ }
25
+ // Helper to get transport config from environment or defaults
26
+ function getTransportConfig() {
27
+ const envTransport = process?.env?.ZAPIER_SDK_TELEMETRY_TRANSPORT;
28
+ if (envTransport === "noop" || envTransport === "disabled") {
29
+ return { type: "noop" };
30
+ }
31
+ if (envTransport === "console") {
32
+ return { type: "console" };
33
+ }
34
+ // Default to HTTP transport
35
+ const endpoint = process?.env?.ZAPIER_SDK_TELEMETRY_ENDPOINT || TRACKING_API_ENDPOINT;
36
+ return {
37
+ type: "http",
38
+ endpoint,
39
+ };
40
+ }
41
+ export const eventEmissionPlugin = ({ context }) => {
42
+ const defaultTransport = getTransportConfig();
43
+ // Merge config: env var takes precedence over options, options take precedence over defaults
44
+ const config = {
45
+ enabled: context.options.eventEmission?.enabled ?? true,
46
+ transport:
47
+ // If env var is set, use it (defaultTransport will be from env)
48
+ process?.env?.ZAPIER_SDK_TELEMETRY_TRANSPORT
49
+ ? defaultTransport
50
+ : // Otherwise, use option transport or default
51
+ (context.options.eventEmission?.transport ?? defaultTransport),
52
+ };
53
+ const startupTime = Date.now();
54
+ let shutdownStartTime = null;
55
+ // If disabled, return noop implementations
56
+ if (!config.enabled) {
57
+ return {
58
+ context: {
59
+ eventEmission: {
60
+ transport: createTransport({ type: "noop" }),
61
+ config,
62
+ emit: () => { },
63
+ createBaseEvent: () => ({
64
+ event_id: generateEventId(),
65
+ timestamp_ms: getCurrentTimestamp(),
66
+ release_id: getReleaseId(),
67
+ customuser_id: null,
68
+ account_id: null,
69
+ identity_id: null,
70
+ visitor_id: null,
71
+ correlation_id: null,
72
+ }),
73
+ },
74
+ },
75
+ };
76
+ }
77
+ let transport;
78
+ try {
79
+ transport = createTransport(config.transport || { type: "noop" });
80
+ }
81
+ catch {
82
+ // If transport creation fails, fallback to noop to maintain silent operation
83
+ transport = createTransport({ type: "noop" });
84
+ }
85
+ // Helper to create base event
86
+ const createBaseEventHelper = () => ({
87
+ event_id: generateEventId(),
88
+ timestamp_ms: getCurrentTimestamp(),
89
+ release_id: getReleaseId(),
90
+ customuser_id: null,
91
+ account_id: null,
92
+ identity_id: null,
93
+ visitor_id: null,
94
+ correlation_id: null,
95
+ });
96
+ // Register lifecycle event handlers if enabled
97
+ if (config.enabled) {
98
+ // Emit startup event
99
+ const startupEvent = buildApplicationLifecycleEvent({
100
+ lifecycle_event_type: "startup",
101
+ });
102
+ silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, startupEvent);
103
+ // Register process event handlers (Node.js only)
104
+ if (typeof process?.on === "function") {
105
+ // Handle normal process exit
106
+ process.on("exit", (code) => {
107
+ const uptime = Date.now() - startupTime;
108
+ const shutdownDuration = shutdownStartTime
109
+ ? Date.now() - shutdownStartTime
110
+ : null;
111
+ const exitEvent = buildApplicationLifecycleEvent({
112
+ lifecycle_event_type: "exit",
113
+ exit_code: code,
114
+ uptime_ms: uptime,
115
+ is_graceful_shutdown: code === 0,
116
+ shutdown_duration_ms: shutdownDuration,
117
+ });
118
+ silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, exitEvent);
119
+ });
120
+ // Handle uncaught exceptions
121
+ process.on("uncaughtException", async (error) => {
122
+ const errorEvent = buildErrorEventWithContext({
123
+ error_message: error.message || "Unknown error",
124
+ error_type: "UncaughtException",
125
+ error_stack_trace: error.stack || null,
126
+ error_severity: "critical",
127
+ is_user_facing: false,
128
+ is_recoverable: false,
129
+ execution_start_time: startupTime,
130
+ });
131
+ // Wait up to 300ms for telemetry to send before allowing process to exit
132
+ try {
133
+ await Promise.race([
134
+ transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
135
+ new Promise((resolve) => setTimeout(resolve, 300)),
136
+ ]);
137
+ }
138
+ catch {
139
+ // Silently ignore telemetry failures
140
+ }
141
+ // Let Node.js exit naturally after all handlers complete
142
+ });
143
+ // Handle unhandled promise rejections
144
+ process.on("unhandledRejection", async (reason, promise) => {
145
+ const errorMessage = reason instanceof Error
146
+ ? reason.message
147
+ : typeof reason === "string"
148
+ ? reason
149
+ : "Unhandled promise rejection";
150
+ const errorStack = reason instanceof Error ? reason.stack : null;
151
+ const errorEvent = buildErrorEventWithContext({
152
+ error_message: errorMessage,
153
+ error_type: "UnhandledRejection",
154
+ error_stack_trace: errorStack,
155
+ error_severity: "critical",
156
+ is_user_facing: false,
157
+ is_recoverable: false,
158
+ execution_start_time: startupTime,
159
+ error_metadata: {
160
+ promise: String(promise),
161
+ },
162
+ });
163
+ // Wait up to 300ms for telemetry to send
164
+ try {
165
+ await Promise.race([
166
+ transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
167
+ new Promise((resolve) => setTimeout(resolve, 300)),
168
+ ]);
169
+ }
170
+ catch {
171
+ // Silently ignore telemetry failures
172
+ }
173
+ });
174
+ // Handle termination signals
175
+ const handleSignal = async (signal) => {
176
+ shutdownStartTime = Date.now();
177
+ const uptime = Date.now() - startupTime;
178
+ const signalEvent = buildApplicationLifecycleEvent({
179
+ lifecycle_event_type: "signal_termination",
180
+ signal_name: signal,
181
+ uptime_ms: uptime,
182
+ is_graceful_shutdown: true,
183
+ });
184
+ // Wait up to 300ms for telemetry to send
185
+ try {
186
+ await Promise.race([
187
+ transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
188
+ new Promise((resolve) => setTimeout(resolve, 300)),
189
+ ]);
190
+ }
191
+ catch {
192
+ // Silently ignore telemetry failures
193
+ }
194
+ // Let other signal handlers run and decide when to exit
195
+ };
196
+ // Register signal handlers
197
+ ["SIGINT", "SIGTERM"].forEach((signal) => {
198
+ process.on(signal, () => handleSignal(signal));
199
+ });
200
+ }
201
+ }
202
+ return {
203
+ context: {
204
+ eventEmission: {
205
+ transport,
206
+ config,
207
+ emit: (subject, event) => {
208
+ silentEmit(transport, subject, event);
209
+ },
210
+ createBaseEvent: createBaseEventHelper,
211
+ },
212
+ },
213
+ };
214
+ };
215
+ export { buildApplicationLifecycleEvent, buildErrorEventWithContext, buildErrorEvent, createBaseEvent, } from "./builders";
216
+ export * from "./utils";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Event Emission Plugin
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Tests for Event Emission Plugin
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from "vitest";
5
+ import { eventEmissionPlugin } from "./index";
6
+ import { createTransport } from "./transport";
7
+ // Mock transport for testing
8
+ const mockTransport = {
9
+ emit: vi.fn().mockResolvedValue(undefined),
10
+ close: vi.fn().mockResolvedValue(undefined),
11
+ };
12
+ vi.mock("./transport", () => ({
13
+ createTransport: vi.fn(() => mockTransport),
14
+ }));
15
+ describe("eventEmissionPlugin", () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+ it("should create plugin with default configuration", () => {
20
+ const plugin = eventEmissionPlugin({
21
+ sdk: {},
22
+ context: {
23
+ meta: {},
24
+ options: {},
25
+ },
26
+ });
27
+ expect(plugin.context.eventEmission).toBeDefined();
28
+ expect(plugin.context.eventEmission.emit).toBeDefined();
29
+ expect(plugin.context.eventEmission.transport).toBeDefined();
30
+ expect(plugin.context.eventEmission.config).toBeDefined();
31
+ });
32
+ it("should create noop implementations when disabled", () => {
33
+ const config = { enabled: false };
34
+ const plugin = eventEmissionPlugin({
35
+ sdk: {},
36
+ context: {
37
+ meta: {},
38
+ options: { eventEmission: config },
39
+ },
40
+ });
41
+ // Should not emit any events when disabled
42
+ plugin.context.eventEmission.emit("platform.sdk.TestEvent", {
43
+ test_event: "data",
44
+ });
45
+ expect(mockTransport.emit).not.toHaveBeenCalled();
46
+ });
47
+ it("should emit events using generic emit method", async () => {
48
+ const plugin = eventEmissionPlugin({
49
+ sdk: {},
50
+ context: {
51
+ meta: {},
52
+ options: {
53
+ eventEmission: {
54
+ enabled: true,
55
+ transport: { type: "console" },
56
+ },
57
+ },
58
+ },
59
+ });
60
+ const testEvent = {
61
+ test_data: "example",
62
+ value: 123,
63
+ };
64
+ const testSubject = "test.event.TestEvent";
65
+ plugin.context.eventEmission.emit(testSubject, testEvent);
66
+ // Give async emission time to complete
67
+ await new Promise((resolve) => setTimeout(resolve, 0));
68
+ expect(mockTransport.emit).toHaveBeenCalledWith(testSubject, testEvent);
69
+ });
70
+ it("should handle transport creation failures silently", () => {
71
+ // Mock createTransport to throw an error
72
+ vi.mocked(createTransport).mockImplementationOnce(() => {
73
+ throw new Error("Transport creation failed");
74
+ });
75
+ expect(() => {
76
+ eventEmissionPlugin({
77
+ sdk: {},
78
+ context: {
79
+ meta: {},
80
+ options: {
81
+ eventEmission: {
82
+ enabled: true,
83
+ transport: { type: "http", endpoint: "invalid-url" },
84
+ },
85
+ },
86
+ },
87
+ });
88
+ }).not.toThrow();
89
+ });
90
+ it("should handle event emission failures silently", async () => {
91
+ // Mock transport to throw error
92
+ const failingTransport = {
93
+ emit: vi.fn().mockRejectedValue(new Error("Network error")),
94
+ close: vi.fn().mockResolvedValue(undefined),
95
+ };
96
+ vi.mocked(createTransport).mockReturnValueOnce(failingTransport);
97
+ const plugin = eventEmissionPlugin({
98
+ sdk: {},
99
+ context: {
100
+ meta: {},
101
+ options: {
102
+ eventEmission: {
103
+ enabled: true,
104
+ transport: {
105
+ type: "http",
106
+ endpoint: "https://example.com",
107
+ },
108
+ },
109
+ },
110
+ },
111
+ });
112
+ // Should not throw even if transport fails
113
+ expect(() => {
114
+ plugin.context.eventEmission.emit("test.event.TestEvent", {
115
+ test_event: "data",
116
+ });
117
+ }).not.toThrow();
118
+ // Give async emission time to complete
119
+ await new Promise((resolve) => setTimeout(resolve, 10));
120
+ expect(failingTransport.emit).toHaveBeenCalled();
121
+ });
122
+ it("should merge options with defaults", () => {
123
+ const plugin = eventEmissionPlugin({
124
+ sdk: {},
125
+ context: {
126
+ meta: {},
127
+ options: {
128
+ eventEmission: {
129
+ transport: {
130
+ type: "http",
131
+ endpoint: "https://example.com",
132
+ },
133
+ },
134
+ },
135
+ },
136
+ });
137
+ expect(plugin.context.eventEmission.config.enabled).toBe(true);
138
+ expect(plugin.context.eventEmission.config.transport).toEqual({
139
+ type: "http",
140
+ endpoint: "https://example.com",
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,37 @@
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
+ export interface EventTransport {
8
+ emit<T extends any>(subject: string, event: T): Promise<void>;
9
+ close?(): Promise<void>;
10
+ }
11
+ export interface TransportConfig {
12
+ type: "http" | "console" | "noop";
13
+ endpoint?: string;
14
+ headers?: Record<string, string>;
15
+ retryAttempts?: number;
16
+ retryDelayMs?: number;
17
+ }
18
+ export declare class HttpTransport implements EventTransport {
19
+ private config;
20
+ constructor(config: {
21
+ endpoint: string;
22
+ headers?: Record<string, string>;
23
+ retryAttempts?: number;
24
+ retryDelayMs?: number;
25
+ });
26
+ emit<T extends any>(subject: string, event: T): Promise<void>;
27
+ private emitWithRetry;
28
+ private delay;
29
+ }
30
+ export declare class ConsoleTransport implements EventTransport {
31
+ emit<T extends any>(subject: string, event: T): Promise<void>;
32
+ }
33
+ export declare class NoopTransport implements EventTransport {
34
+ emit<T extends any>(_subject: string, _event: T): Promise<void>;
35
+ }
36
+ export declare function createTransport(config: TransportConfig): EventTransport;
37
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAGD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAGD,qBAAa,aAAc,YAAW,cAAc;IAEhD,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;IAGG,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAYrD,aAAa;YAiCb,KAAK;CAGpB;AAGD,qBAAa,gBAAiB,YAAW,cAAc;IAC/C,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAUpE;AAGD,qBAAa,aAAc,YAAW,cAAc;IAC5C,IAAI,CAAC,CAAC,SAAS,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAGtE;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,CAyBvE"}
@@ -0,0 +1,96 @@
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
+ // Constants for transport configuration
8
+ const DEFAULT_RETRY_ATTEMPTS = 2;
9
+ const DEFAULT_RETRY_DELAY_MS = 300;
10
+ // HTTP Transport - sends events to remote endpoint
11
+ export class HttpTransport {
12
+ constructor(config) {
13
+ this.config = config;
14
+ }
15
+ async emit(subject, event) {
16
+ try {
17
+ await this.emitWithRetry(subject, event, this.config.retryAttempts || DEFAULT_RETRY_ATTEMPTS);
18
+ }
19
+ catch {
20
+ // Silent failure - never throw
21
+ }
22
+ }
23
+ async emitWithRetry(subject, event, attemptsLeft) {
24
+ try {
25
+ const payload = {
26
+ subject,
27
+ properties: event,
28
+ };
29
+ const response = await fetch(this.config.endpoint, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ ...this.config.headers,
34
+ },
35
+ body: JSON.stringify(payload),
36
+ });
37
+ if (!response.ok && attemptsLeft > 1) {
38
+ await this.delay(this.config.retryDelayMs || DEFAULT_RETRY_DELAY_MS);
39
+ return this.emitWithRetry(subject, event, attemptsLeft - 1);
40
+ }
41
+ }
42
+ catch (error) {
43
+ if (attemptsLeft > 1) {
44
+ await this.delay(this.config.retryDelayMs || DEFAULT_RETRY_DELAY_MS);
45
+ return this.emitWithRetry(subject, event, attemptsLeft - 1);
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+ async delay(ms) {
51
+ return new Promise((resolve) => setTimeout(resolve, ms));
52
+ }
53
+ }
54
+ // Console Transport - logs events to console (for development)
55
+ export class ConsoleTransport {
56
+ async emit(subject, event) {
57
+ try {
58
+ console.log("[SDK Telemetry]", JSON.stringify({ subject, properties: event }, null, 2));
59
+ }
60
+ catch {
61
+ // Silent failure - never throw
62
+ }
63
+ }
64
+ }
65
+ // No-op Transport - discards all events (for testing/disabled state)
66
+ export class NoopTransport {
67
+ async emit(_subject, _event) {
68
+ // Intentionally do nothing
69
+ }
70
+ }
71
+ // Transport factory
72
+ export function createTransport(config) {
73
+ try {
74
+ switch (config.type) {
75
+ case "http":
76
+ if (!config.endpoint) {
77
+ throw new Error("HTTP transport requires endpoint");
78
+ }
79
+ return new HttpTransport({
80
+ endpoint: config.endpoint,
81
+ headers: config.headers,
82
+ retryAttempts: config.retryAttempts,
83
+ retryDelayMs: config.retryDelayMs,
84
+ });
85
+ case "console":
86
+ return new ConsoleTransport();
87
+ case "noop":
88
+ default:
89
+ return new NoopTransport();
90
+ }
91
+ }
92
+ catch {
93
+ // If transport creation fails, return noop to maintain silent operation
94
+ return new NoopTransport();
95
+ }
96
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for Event Transport Layer
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=transport.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.test.d.ts","sourceRoot":"","sources":["../../../src/plugins/eventEmission/transport.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Tests for Event Transport Layer
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from "vitest";
5
+ import { HttpTransport, ConsoleTransport, NoopTransport, createTransport, } from "./transport";
6
+ // Mock fetch globally
7
+ const mockFetch = vi.fn();
8
+ global.fetch = mockFetch;
9
+ // Mock console.log
10
+ const mockConsoleLog = vi.spyOn(console, "log").mockImplementation(() => { });
11
+ describe("Transport Layer", () => {
12
+ const sampleEvent = {
13
+ event_id: "test-123",
14
+ timestamp_ms: Date.now(),
15
+ release_id: "test-release",
16
+ error_message: "Test error",
17
+ is_user_facing: false,
18
+ };
19
+ const sampleSubject = "platform.sdk.ErrorOccurredEvent";
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ });
23
+ describe("HttpTransport", () => {
24
+ it("should emit events via HTTP successfully", async () => {
25
+ mockFetch.mockResolvedValueOnce({
26
+ ok: true,
27
+ status: 200,
28
+ });
29
+ const transport = new HttpTransport({
30
+ endpoint: "https://telemetry.example.com/events",
31
+ headers: { "x-api-key": "test-key" },
32
+ });
33
+ await transport.emit(sampleSubject, sampleEvent);
34
+ expect(mockFetch).toHaveBeenCalledWith("https://telemetry.example.com/events", {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ "x-api-key": "test-key",
39
+ },
40
+ body: JSON.stringify({
41
+ subject: sampleSubject,
42
+ properties: sampleEvent,
43
+ }),
44
+ });
45
+ });
46
+ it("should retry on HTTP failures", async () => {
47
+ // Fail twice, then succeed
48
+ mockFetch
49
+ .mockResolvedValueOnce({ ok: false, status: 500 })
50
+ .mockResolvedValueOnce({ ok: false, status: 500 })
51
+ .mockResolvedValueOnce({ ok: true, status: 200 });
52
+ const transport = new HttpTransport({
53
+ endpoint: "https://telemetry.example.com/events",
54
+ retryAttempts: 3,
55
+ retryDelayMs: 10, // Short delay for testing
56
+ });
57
+ await transport.emit(sampleSubject, sampleEvent);
58
+ expect(mockFetch).toHaveBeenCalledTimes(3);
59
+ });
60
+ it("should handle network errors silently", async () => {
61
+ mockFetch.mockRejectedValue(new Error("Network error"));
62
+ const transport = new HttpTransport({
63
+ endpoint: "https://telemetry.example.com/events",
64
+ retryAttempts: 1,
65
+ retryDelayMs: 1,
66
+ });
67
+ // Should not throw despite network error
68
+ await expect(transport.emit(sampleSubject, sampleEvent)).resolves.toBeUndefined();
69
+ });
70
+ it("should stop retrying after max attempts", async () => {
71
+ mockFetch.mockResolvedValue({ ok: false, status: 500 });
72
+ const transport = new HttpTransport({
73
+ endpoint: "https://telemetry.example.com/events",
74
+ retryAttempts: 2,
75
+ retryDelayMs: 1,
76
+ });
77
+ await transport.emit(sampleSubject, sampleEvent);
78
+ expect(mockFetch).toHaveBeenCalledTimes(2);
79
+ });
80
+ });
81
+ describe("ConsoleTransport", () => {
82
+ it("should log events to console", async () => {
83
+ const transport = new ConsoleTransport();
84
+ await transport.emit(sampleSubject, sampleEvent);
85
+ expect(mockConsoleLog).toHaveBeenCalledWith("[SDK Telemetry]", JSON.stringify({ subject: sampleSubject, properties: sampleEvent }, null, 2));
86
+ });
87
+ it("should handle console errors silently", async () => {
88
+ mockConsoleLog.mockImplementation(() => {
89
+ throw new Error("Console error");
90
+ });
91
+ const transport = new ConsoleTransport();
92
+ // Should not throw despite console error
93
+ await expect(transport.emit(sampleSubject, sampleEvent)).resolves.toBeUndefined();
94
+ });
95
+ });
96
+ describe("NoopTransport", () => {
97
+ it("should do nothing when emitting events", async () => {
98
+ const transport = new NoopTransport();
99
+ await transport.emit(sampleSubject, sampleEvent);
100
+ // Verify no side effects
101
+ expect(mockFetch).not.toHaveBeenCalled();
102
+ expect(mockConsoleLog).not.toHaveBeenCalled();
103
+ });
104
+ });
105
+ describe("createTransport", () => {
106
+ it("should create HTTP transport with valid config", () => {
107
+ const transport = createTransport({
108
+ type: "http",
109
+ endpoint: "https://example.com",
110
+ headers: { "x-key": "value" },
111
+ retryAttempts: 5,
112
+ retryDelayMs: 2000,
113
+ });
114
+ expect(transport).toBeInstanceOf(HttpTransport);
115
+ });
116
+ it("should create console transport", () => {
117
+ const transport = createTransport({
118
+ type: "console",
119
+ });
120
+ expect(transport).toBeInstanceOf(ConsoleTransport);
121
+ });
122
+ it("should create noop transport by default", () => {
123
+ const transport = createTransport({
124
+ type: "noop",
125
+ });
126
+ expect(transport).toBeInstanceOf(NoopTransport);
127
+ });
128
+ it("should fallback to noop transport on invalid HTTP config", () => {
129
+ const transport = createTransport({
130
+ type: "http",
131
+ // Missing endpoint
132
+ });
133
+ expect(transport).toBeInstanceOf(NoopTransport);
134
+ });
135
+ it("should fallback to noop transport on unknown type", () => {
136
+ const transport = createTransport({
137
+ type: "unknown",
138
+ });
139
+ expect(transport).toBeInstanceOf(NoopTransport);
140
+ });
141
+ it("should handle transport creation errors gracefully", () => {
142
+ // Since createTransport already handles errors internally,
143
+ // this test verifies that invalid configs don't throw
144
+ expect(() => {
145
+ const transport = createTransport({
146
+ type: "http",
147
+ // Missing required endpoint - should fallback to noop
148
+ });
149
+ expect(transport).toBeInstanceOf(NoopTransport);
150
+ }).not.toThrow();
151
+ });
152
+ });
153
+ });