@teneo-protocol/sdk 1.0.0
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/.dockerignore +14 -0
- package/.env.test.example +14 -0
- package/.eslintrc.json +26 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude-reviewer.yml +64 -0
- package/.github/workflows/publish-npm.yml +38 -0
- package/.github/workflows/push-to-main.yml +23 -0
- package/.node-version +1 -0
- package/.prettierrc +11 -0
- package/Dockerfile +25 -0
- package/LICENCE +661 -0
- package/README.md +709 -0
- package/dist/constants.d.ts +42 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +45 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/websocket-client.d.ts +261 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +875 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/formatters/response-formatter.d.ts +354 -0
- package/dist/formatters/response-formatter.d.ts.map +1 -0
- package/dist/formatters/response-formatter.js +575 -0
- package/dist/formatters/response-formatter.js.map +1 -0
- package/dist/handlers/message-handler-registry.d.ts +155 -0
- package/dist/handlers/message-handler-registry.d.ts.map +1 -0
- package/dist/handlers/message-handler-registry.js +216 -0
- package/dist/handlers/message-handler-registry.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
- package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/base-handler.js +83 -0
- package/dist/handlers/message-handlers/base-handler.js.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.js +47 -0
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.js +29 -0
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/index.d.ts +28 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -0
- package/dist/handlers/message-handlers/index.js +100 -0
- package/dist/handlers/message-handlers/index.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +44 -0
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/types.d.ts +51 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -0
- package/dist/handlers/message-handlers/types.js +7 -0
- package/dist/handlers/message-handlers/types.js.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +202 -0
- package/dist/handlers/webhook-handler.d.ts.map +1 -0
- package/dist/handlers/webhook-handler.js +511 -0
- package/dist/handlers/webhook-handler.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +173 -0
- package/dist/managers/agent-registry.d.ts.map +1 -0
- package/dist/managers/agent-registry.js +310 -0
- package/dist/managers/agent-registry.js.map +1 -0
- package/dist/managers/connection-manager.d.ts +134 -0
- package/dist/managers/connection-manager.d.ts.map +1 -0
- package/dist/managers/connection-manager.js +176 -0
- package/dist/managers/connection-manager.js.map +1 -0
- package/dist/managers/index.d.ts +9 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +16 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/message-router.d.ts +112 -0
- package/dist/managers/message-router.d.ts.map +1 -0
- package/dist/managers/message-router.js +260 -0
- package/dist/managers/message-router.js.map +1 -0
- package/dist/managers/room-manager.d.ts +165 -0
- package/dist/managers/room-manager.d.ts.map +1 -0
- package/dist/managers/room-manager.js +227 -0
- package/dist/managers/room-manager.js.map +1 -0
- package/dist/teneo-sdk.d.ts +703 -0
- package/dist/teneo-sdk.d.ts.map +1 -0
- package/dist/teneo-sdk.js +907 -0
- package/dist/teneo-sdk.js.map +1 -0
- package/dist/types/config.d.ts +1047 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +720 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/error-codes.d.ts +29 -0
- package/dist/types/error-codes.d.ts.map +1 -0
- package/dist/types/error-codes.js +41 -0
- package/dist/types/error-codes.js.map +1 -0
- package/dist/types/events.d.ts +616 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +261 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +6 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +123 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +3734 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +482 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/validation.d.ts +81 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +115 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/bounded-queue.d.ts +127 -0
- package/dist/utils/bounded-queue.d.ts.map +1 -0
- package/dist/utils/bounded-queue.js +181 -0
- package/dist/utils/bounded-queue.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +141 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +215 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/deduplication-cache.d.ts +110 -0
- package/dist/utils/deduplication-cache.d.ts.map +1 -0
- package/dist/utils/deduplication-cache.js +177 -0
- package/dist/utils/deduplication-cache.js.map +1 -0
- package/dist/utils/event-waiter.d.ts +101 -0
- package/dist/utils/event-waiter.d.ts.map +1 -0
- package/dist/utils/event-waiter.js +118 -0
- package/dist/utils/event-waiter.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +72 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +122 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +190 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry-policy.d.ts +191 -0
- package/dist/utils/retry-policy.d.ts.map +1 -0
- package/dist/utils/retry-policy.js +225 -0
- package/dist/utils/retry-policy.js.map +1 -0
- package/dist/utils/secure-private-key.d.ts +113 -0
- package/dist/utils/secure-private-key.d.ts.map +1 -0
- package/dist/utils/secure-private-key.js +188 -0
- package/dist/utils/secure-private-key.js.map +1 -0
- package/dist/utils/signature-verifier.d.ts +143 -0
- package/dist/utils/signature-verifier.d.ts.map +1 -0
- package/dist/utils/signature-verifier.js +238 -0
- package/dist/utils/signature-verifier.js.map +1 -0
- package/dist/utils/ssrf-validator.d.ts +36 -0
- package/dist/utils/ssrf-validator.d.ts.map +1 -0
- package/dist/utils/ssrf-validator.js +195 -0
- package/dist/utils/ssrf-validator.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/basic-usage.ts +211 -0
- package/examples/production-dashboard/.env.example +153 -0
- package/examples/production-dashboard/package.json +39 -0
- package/examples/production-dashboard/public/dashboard.html +642 -0
- package/examples/production-dashboard/server.ts +753 -0
- package/examples/webhook-integration.ts +239 -0
- package/examples/x-influencer-battle-redesign.html +1065 -0
- package/examples/x-influencer-battle-server.ts +217 -0
- package/examples/x-influencer-battle.html +787 -0
- package/package.json +65 -0
- package/src/constants.ts +43 -0
- package/src/core/websocket-client.test.ts +512 -0
- package/src/core/websocket-client.ts +1056 -0
- package/src/formatters/response-formatter.test.ts +571 -0
- package/src/formatters/response-formatter.ts +677 -0
- package/src/handlers/message-handler-registry.ts +239 -0
- package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
- package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
- package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
- package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
- package/src/handlers/message-handlers/base-handler.ts +101 -0
- package/src/handlers/message-handlers/challenge-handler.ts +57 -0
- package/src/handlers/message-handlers/error-message-handler.ts +27 -0
- package/src/handlers/message-handlers/index.ts +77 -0
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
- package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
- package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
- package/src/handlers/message-handlers/task-response-handler.ts +45 -0
- package/src/handlers/message-handlers/types.ts +77 -0
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
- package/src/handlers/webhook-handler.test.ts +789 -0
- package/src/handlers/webhook-handler.ts +576 -0
- package/src/index.ts +269 -0
- package/src/managers/agent-registry.test.ts +466 -0
- package/src/managers/agent-registry.ts +347 -0
- package/src/managers/connection-manager.ts +195 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/message-router.ts +349 -0
- package/src/managers/room-manager.ts +248 -0
- package/src/teneo-sdk.ts +1022 -0
- package/src/types/config.test.ts +325 -0
- package/src/types/config.ts +799 -0
- package/src/types/error-codes.ts +44 -0
- package/src/types/events.test.ts +302 -0
- package/src/types/events.ts +382 -0
- package/src/types/health.ts +46 -0
- package/src/types/index.ts +199 -0
- package/src/types/messages.test.ts +660 -0
- package/src/types/messages.ts +570 -0
- package/src/types/validation.ts +123 -0
- package/src/utils/bounded-queue.test.ts +356 -0
- package/src/utils/bounded-queue.ts +205 -0
- package/src/utils/circuit-breaker.test.ts +394 -0
- package/src/utils/circuit-breaker.ts +262 -0
- package/src/utils/deduplication-cache.test.ts +380 -0
- package/src/utils/deduplication-cache.ts +198 -0
- package/src/utils/event-waiter.test.ts +381 -0
- package/src/utils/event-waiter.ts +172 -0
- package/src/utils/index.ts +74 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/rate-limiter.test.ts +341 -0
- package/src/utils/rate-limiter.ts +211 -0
- package/src/utils/retry-policy.test.ts +558 -0
- package/src/utils/retry-policy.ts +272 -0
- package/src/utils/secure-private-key.test.ts +356 -0
- package/src/utils/secure-private-key.ts +205 -0
- package/src/utils/signature-verifier.test.ts +464 -0
- package/src/utils/signature-verifier.ts +298 -0
- package/src/utils/ssrf-validator.test.ts +372 -0
- package/src/utils/ssrf-validator.ts +224 -0
- package/tests/integration/real-server.test.ts +740 -0
- package/tests/integration/websocket.test.ts +381 -0
- package/tests/integration-setup.ts +16 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +42 -0
- package/vitest.integration.config.ts +23 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Event Waiter Utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { EventEmitter } from "eventemitter3";
|
|
7
|
+
import { waitForEvent, waitForAnyEvent, waitForAllEvents } from "./event-waiter";
|
|
8
|
+
|
|
9
|
+
describe("waitForEvent", () => {
|
|
10
|
+
let emitter: EventEmitter;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
emitter = new EventEmitter();
|
|
14
|
+
vi.useFakeTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
vi.useRealTimers();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("basic functionality", () => {
|
|
23
|
+
it("should resolve when event is emitted", async () => {
|
|
24
|
+
const eventData = { message: "test data" };
|
|
25
|
+
|
|
26
|
+
// Start waiting
|
|
27
|
+
const promise = waitForEvent(emitter, "test:event", { timeout: 5000 });
|
|
28
|
+
|
|
29
|
+
// Emit the event
|
|
30
|
+
emitter.emit("test:event", eventData);
|
|
31
|
+
|
|
32
|
+
// Should resolve with the event data
|
|
33
|
+
await expect(promise).resolves.toEqual(eventData);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should resolve with the correct event data type", async () => {
|
|
37
|
+
interface TestData {
|
|
38
|
+
id: number;
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const eventData: TestData = { id: 123, name: "test" };
|
|
43
|
+
|
|
44
|
+
const promise = waitForEvent<TestData>(emitter, "typed:event", {
|
|
45
|
+
timeout: 5000
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
emitter.emit("typed:event", eventData);
|
|
49
|
+
|
|
50
|
+
const result = await promise;
|
|
51
|
+
expect(result.id).toBe(123);
|
|
52
|
+
expect(result.name).toBe("test");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should work with multiple sequential waits", async () => {
|
|
56
|
+
const promise1 = waitForEvent(emitter, "event1", { timeout: 5000 });
|
|
57
|
+
emitter.emit("event1", "data1");
|
|
58
|
+
await expect(promise1).resolves.toBe("data1");
|
|
59
|
+
|
|
60
|
+
const promise2 = waitForEvent(emitter, "event2", { timeout: 5000 });
|
|
61
|
+
emitter.emit("event2", "data2");
|
|
62
|
+
await expect(promise2).resolves.toBe("data2");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("timeout handling", () => {
|
|
67
|
+
it("should reject after timeout", async () => {
|
|
68
|
+
const promise = waitForEvent(emitter, "never:happens", { timeout: 1000 });
|
|
69
|
+
|
|
70
|
+
// Advance time past timeout
|
|
71
|
+
vi.advanceTimersByTime(1001);
|
|
72
|
+
|
|
73
|
+
await expect(promise).rejects.toThrow(/Timeout waiting for event/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should use custom timeout message", async () => {
|
|
77
|
+
const customMessage = "Custom timeout error";
|
|
78
|
+
const promise = waitForEvent(emitter, "never:happens", {
|
|
79
|
+
timeout: 1000,
|
|
80
|
+
timeoutMessage: customMessage
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
vi.advanceTimersByTime(1001);
|
|
84
|
+
|
|
85
|
+
await expect(promise).rejects.toThrow(customMessage);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should not timeout if event arrives in time", async () => {
|
|
89
|
+
const promise = waitForEvent(emitter, "quick:event", { timeout: 5000 });
|
|
90
|
+
|
|
91
|
+
// Emit before timeout
|
|
92
|
+
vi.advanceTimersByTime(2000);
|
|
93
|
+
emitter.emit("quick:event", "success");
|
|
94
|
+
|
|
95
|
+
await expect(promise).resolves.toBe("success");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("filtering", () => {
|
|
100
|
+
it("should filter events and wait for matching one", async () => {
|
|
101
|
+
interface Task {
|
|
102
|
+
taskId: string;
|
|
103
|
+
result: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const promise = waitForEvent<Task>(emitter, "task:response", {
|
|
107
|
+
timeout: 5000,
|
|
108
|
+
filter: (data) => data.taskId === "task-123"
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Emit non-matching events
|
|
112
|
+
emitter.emit("task:response", { taskId: "task-999", result: "wrong" });
|
|
113
|
+
emitter.emit("task:response", { taskId: "task-888", result: "wrong" });
|
|
114
|
+
|
|
115
|
+
// Emit matching event
|
|
116
|
+
emitter.emit("task:response", { taskId: "task-123", result: "correct" });
|
|
117
|
+
|
|
118
|
+
const result = await promise;
|
|
119
|
+
expect(result.taskId).toBe("task-123");
|
|
120
|
+
expect(result.result).toBe("correct");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should timeout if no matching event arrives", async () => {
|
|
124
|
+
const promise = waitForEvent(emitter, "filtered:event", {
|
|
125
|
+
timeout: 1000,
|
|
126
|
+
filter: (data: any) => data.id === 999
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Emit non-matching events
|
|
130
|
+
emitter.emit("filtered:event", { id: 1 });
|
|
131
|
+
emitter.emit("filtered:event", { id: 2 });
|
|
132
|
+
emitter.emit("filtered:event", { id: 3 });
|
|
133
|
+
|
|
134
|
+
// Advance past timeout
|
|
135
|
+
vi.advanceTimersByTime(1001);
|
|
136
|
+
|
|
137
|
+
await expect(promise).rejects.toThrow(/Timeout/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should handle complex filter logic", async () => {
|
|
141
|
+
interface Response {
|
|
142
|
+
requestId: string;
|
|
143
|
+
status: number;
|
|
144
|
+
data?: any;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const promise = waitForEvent<Response>(emitter, "response", {
|
|
148
|
+
timeout: 5000,
|
|
149
|
+
filter: (r) => r.requestId === "req-123" && r.status === 200 && r.data !== undefined
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Non-matching: wrong requestId
|
|
153
|
+
emitter.emit("response", { requestId: "req-999", status: 200, data: "test" });
|
|
154
|
+
|
|
155
|
+
// Non-matching: wrong status
|
|
156
|
+
emitter.emit("response", { requestId: "req-123", status: 404, data: "test" });
|
|
157
|
+
|
|
158
|
+
// Non-matching: no data
|
|
159
|
+
emitter.emit("response", { requestId: "req-123", status: 200 });
|
|
160
|
+
|
|
161
|
+
// Matching
|
|
162
|
+
emitter.emit("response", { requestId: "req-123", status: 200, data: "success" });
|
|
163
|
+
|
|
164
|
+
await expect(promise).resolves.toMatchObject({
|
|
165
|
+
requestId: "req-123",
|
|
166
|
+
status: 200,
|
|
167
|
+
data: "success"
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("cleanup", () => {
|
|
173
|
+
it("should remove event listener after success", async () => {
|
|
174
|
+
const promise = waitForEvent(emitter, "cleanup:test", { timeout: 5000 });
|
|
175
|
+
|
|
176
|
+
// Check listener count before
|
|
177
|
+
expect(emitter.listenerCount("cleanup:test")).toBe(1);
|
|
178
|
+
|
|
179
|
+
// Emit event
|
|
180
|
+
emitter.emit("cleanup:test", "data");
|
|
181
|
+
await promise;
|
|
182
|
+
|
|
183
|
+
// Check listener count after
|
|
184
|
+
expect(emitter.listenerCount("cleanup:test")).toBe(0);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should remove event listener after timeout", async () => {
|
|
188
|
+
const promise = waitForEvent(emitter, "cleanup:timeout", { timeout: 1000 });
|
|
189
|
+
|
|
190
|
+
// Check listener count before
|
|
191
|
+
expect(emitter.listenerCount("cleanup:timeout")).toBe(1);
|
|
192
|
+
|
|
193
|
+
// Timeout
|
|
194
|
+
vi.advanceTimersByTime(1001);
|
|
195
|
+
|
|
196
|
+
await promise.catch(() => {}); // Ignore rejection
|
|
197
|
+
|
|
198
|
+
// Check listener count after
|
|
199
|
+
expect(emitter.listenerCount("cleanup:timeout")).toBe(0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should clear timeout after success", async () => {
|
|
203
|
+
const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
|
|
204
|
+
|
|
205
|
+
const promise = waitForEvent(emitter, "timeout:clear", { timeout: 5000 });
|
|
206
|
+
|
|
207
|
+
emitter.emit("timeout:clear", "data");
|
|
208
|
+
await promise;
|
|
209
|
+
|
|
210
|
+
// clearTimeout should have been called
|
|
211
|
+
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should not leak listeners with filter", async () => {
|
|
215
|
+
const promise = waitForEvent(emitter, "filter:cleanup", {
|
|
216
|
+
timeout: 5000,
|
|
217
|
+
filter: (data: any) => data.id === 5
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Emit multiple non-matching events
|
|
221
|
+
emitter.emit("filter:cleanup", { id: 1 });
|
|
222
|
+
emitter.emit("filter:cleanup", { id: 2 });
|
|
223
|
+
emitter.emit("filter:cleanup", { id: 3 });
|
|
224
|
+
|
|
225
|
+
// Check listener is still there
|
|
226
|
+
expect(emitter.listenerCount("filter:cleanup")).toBe(1);
|
|
227
|
+
|
|
228
|
+
// Emit matching event
|
|
229
|
+
emitter.emit("filter:cleanup", { id: 5 });
|
|
230
|
+
await promise;
|
|
231
|
+
|
|
232
|
+
// Listener should be removed
|
|
233
|
+
expect(emitter.listenerCount("filter:cleanup")).toBe(0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should handle multiple concurrent waits and cleanup correctly", async () => {
|
|
237
|
+
const promise1 = waitForEvent(emitter, "concurrent", {
|
|
238
|
+
timeout: 5000,
|
|
239
|
+
filter: (data: any) => data.id === 1
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const promise2 = waitForEvent(emitter, "concurrent", {
|
|
243
|
+
timeout: 5000,
|
|
244
|
+
filter: (data: any) => data.id === 2
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Should have 2 listeners
|
|
248
|
+
expect(emitter.listenerCount("concurrent")).toBe(2);
|
|
249
|
+
|
|
250
|
+
// Resolve first
|
|
251
|
+
emitter.emit("concurrent", { id: 1 });
|
|
252
|
+
await promise1;
|
|
253
|
+
|
|
254
|
+
// Should have 1 listener left
|
|
255
|
+
expect(emitter.listenerCount("concurrent")).toBe(1);
|
|
256
|
+
|
|
257
|
+
// Resolve second
|
|
258
|
+
emitter.emit("concurrent", { id: 2 });
|
|
259
|
+
await promise2;
|
|
260
|
+
|
|
261
|
+
// Should have 0 listeners
|
|
262
|
+
expect(emitter.listenerCount("concurrent")).toBe(0);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("waitForAnyEvent", () => {
|
|
268
|
+
let emitter: EventEmitter;
|
|
269
|
+
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
emitter = new EventEmitter();
|
|
272
|
+
vi.useFakeTimers();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
afterEach(() => {
|
|
276
|
+
vi.restoreAllMocks();
|
|
277
|
+
vi.useRealTimers();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should resolve with first event that fires", async () => {
|
|
281
|
+
const promise = waitForAnyEvent([
|
|
282
|
+
{ emitter, eventName: "event1", options: { timeout: 5000 } },
|
|
283
|
+
{ emitter, eventName: "event2", options: { timeout: 5000 } },
|
|
284
|
+
{ emitter, eventName: "event3", options: { timeout: 5000 } }
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
// Emit second event first
|
|
288
|
+
emitter.emit("event2", "second");
|
|
289
|
+
|
|
290
|
+
await expect(promise).resolves.toBe("second");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should reject if all events timeout", async () => {
|
|
294
|
+
const promise = waitForAnyEvent([
|
|
295
|
+
{ emitter, eventName: "never1", options: { timeout: 1000 } },
|
|
296
|
+
{ emitter, eventName: "never2", options: { timeout: 1000 } }
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
vi.advanceTimersByTime(1001);
|
|
300
|
+
|
|
301
|
+
await expect(promise).rejects.toThrow();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should work with different emitters", async () => {
|
|
305
|
+
const emitter1 = new EventEmitter();
|
|
306
|
+
const emitter2 = new EventEmitter();
|
|
307
|
+
|
|
308
|
+
const promise = waitForAnyEvent([
|
|
309
|
+
{ emitter: emitter1, eventName: "event", options: { timeout: 5000 } },
|
|
310
|
+
{ emitter: emitter2, eventName: "event", options: { timeout: 5000 } }
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
emitter2.emit("event", "from emitter2");
|
|
314
|
+
|
|
315
|
+
await expect(promise).resolves.toBe("from emitter2");
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe("waitForAllEvents", () => {
|
|
320
|
+
let emitter: EventEmitter;
|
|
321
|
+
|
|
322
|
+
beforeEach(() => {
|
|
323
|
+
emitter = new EventEmitter();
|
|
324
|
+
vi.useFakeTimers();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
afterEach(() => {
|
|
328
|
+
vi.restoreAllMocks();
|
|
329
|
+
vi.useRealTimers();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("should resolve when all events fire", async () => {
|
|
333
|
+
const promise = waitForAllEvents([
|
|
334
|
+
{ emitter, eventName: "event1", options: { timeout: 5000 } },
|
|
335
|
+
{ emitter, eventName: "event2", options: { timeout: 5000 } },
|
|
336
|
+
{ emitter, eventName: "event3", options: { timeout: 5000 } }
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
// Emit all events
|
|
340
|
+
emitter.emit("event1", "first");
|
|
341
|
+
emitter.emit("event2", "second");
|
|
342
|
+
emitter.emit("event3", "third");
|
|
343
|
+
|
|
344
|
+
const results = await promise;
|
|
345
|
+
expect(results).toEqual(["first", "second", "third"]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("should reject if any event times out", async () => {
|
|
349
|
+
const promise = waitForAllEvents([
|
|
350
|
+
{ emitter, eventName: "event1", options: { timeout: 5000 } },
|
|
351
|
+
{ emitter, eventName: "never", options: { timeout: 1000 } }
|
|
352
|
+
]);
|
|
353
|
+
|
|
354
|
+
emitter.emit("event1", "data");
|
|
355
|
+
vi.advanceTimersByTime(1001);
|
|
356
|
+
|
|
357
|
+
await expect(promise).rejects.toThrow();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should work with filters on all events", async () => {
|
|
361
|
+
const promise = waitForAllEvents([
|
|
362
|
+
{
|
|
363
|
+
emitter,
|
|
364
|
+
eventName: "data",
|
|
365
|
+
options: { timeout: 5000, filter: (d: any) => d.id === 1 }
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
emitter,
|
|
369
|
+
eventName: "data",
|
|
370
|
+
options: { timeout: 5000, filter: (d: any) => d.id === 2 }
|
|
371
|
+
}
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
// Emit in reverse order
|
|
375
|
+
emitter.emit("data", { id: 2, value: "second" });
|
|
376
|
+
emitter.emit("data", { id: 1, value: "first" });
|
|
377
|
+
|
|
378
|
+
const results = await promise;
|
|
379
|
+
expect(results).toEqual([{ id: 1, value: "first" }, { id: 2, value: "second" }]);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Waiter Utility
|
|
3
|
+
* Provides a clean Promise-based API for waiting for events with timeout and filtering
|
|
4
|
+
* Eliminates callback hell and ensures proper cleanup of listeners and timers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from "eventemitter3";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for waiting for an event
|
|
11
|
+
*/
|
|
12
|
+
export interface WaitForEventOptions<T> {
|
|
13
|
+
/**
|
|
14
|
+
* Timeout in milliseconds
|
|
15
|
+
*/
|
|
16
|
+
timeout: number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Optional filter function to match specific events
|
|
20
|
+
* If provided, only events that pass the filter will resolve the promise
|
|
21
|
+
*
|
|
22
|
+
* @param data - The event data
|
|
23
|
+
* @returns true if this is the event we're waiting for, false to keep waiting
|
|
24
|
+
*/
|
|
25
|
+
filter?: (data: T) => boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional custom timeout error message
|
|
29
|
+
* If not provided, a default message will be used
|
|
30
|
+
*/
|
|
31
|
+
timeoutMessage?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wait for an event to be emitted with automatic timeout and cleanup
|
|
36
|
+
*
|
|
37
|
+
* This utility encapsulates the complex pattern of:
|
|
38
|
+
* - Setting up event listeners
|
|
39
|
+
* - Managing timeouts
|
|
40
|
+
* - Cleaning up listeners and timers in all code paths
|
|
41
|
+
* - Filtering events
|
|
42
|
+
*
|
|
43
|
+
* All cleanup is guaranteed to happen, preventing memory leaks.
|
|
44
|
+
*
|
|
45
|
+
* @param emitter - The EventEmitter to listen to
|
|
46
|
+
* @param eventName - The name of the event to wait for
|
|
47
|
+
* @param options - Configuration options
|
|
48
|
+
* @returns Promise that resolves with the event data or rejects on timeout
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Simple usage: wait for any 'data' event
|
|
53
|
+
* const data = await waitForEvent(emitter, 'data', { timeout: 5000 });
|
|
54
|
+
*
|
|
55
|
+
* // With filter: wait for specific event
|
|
56
|
+
* const response = await waitForEvent(wsClient, 'agent:response', {
|
|
57
|
+
* timeout: 30000,
|
|
58
|
+
* filter: (r) => r.taskId === myTaskId,
|
|
59
|
+
* timeoutMessage: 'Agent did not respond in time'
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export async function waitForEvent<T = any>(
|
|
64
|
+
emitter: EventEmitter,
|
|
65
|
+
eventName: string,
|
|
66
|
+
options: WaitForEventOptions<T>
|
|
67
|
+
): Promise<T> {
|
|
68
|
+
return new Promise<T>((resolve, reject) => {
|
|
69
|
+
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
70
|
+
let eventHandler: ((data: T) => void) | undefined;
|
|
71
|
+
let cleaned = false;
|
|
72
|
+
|
|
73
|
+
// Cleanup function - ensures we only clean up once
|
|
74
|
+
const cleanup = () => {
|
|
75
|
+
if (cleaned) return;
|
|
76
|
+
cleaned = true;
|
|
77
|
+
|
|
78
|
+
if (timeoutHandle) {
|
|
79
|
+
clearTimeout(timeoutHandle);
|
|
80
|
+
timeoutHandle = undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (eventHandler) {
|
|
84
|
+
emitter.off(eventName, eventHandler);
|
|
85
|
+
eventHandler = undefined;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Event handler - filters and resolves
|
|
90
|
+
eventHandler = (data: T) => {
|
|
91
|
+
// If filter is provided, check if this event matches
|
|
92
|
+
if (options.filter && !options.filter(data)) {
|
|
93
|
+
// Not the event we're looking for, keep waiting
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// This is the event we want!
|
|
98
|
+
cleanup();
|
|
99
|
+
resolve(data);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Timeout handler - rejects after timeout
|
|
103
|
+
timeoutHandle = setTimeout(() => {
|
|
104
|
+
cleanup();
|
|
105
|
+
const message = options.timeoutMessage || `Timeout waiting for event '${eventName}' after ${options.timeout}ms`;
|
|
106
|
+
reject(new Error(message));
|
|
107
|
+
}, options.timeout);
|
|
108
|
+
|
|
109
|
+
// Start listening
|
|
110
|
+
emitter.on(eventName, eventHandler);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Wait for multiple events simultaneously (race condition)
|
|
116
|
+
* Resolves with the first event that fires and matches its filter
|
|
117
|
+
*
|
|
118
|
+
* @param waiters - Array of event waiter configurations
|
|
119
|
+
* @returns Promise that resolves with the first matching event
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Wait for either success or error
|
|
124
|
+
* const result = await waitForAnyEvent([
|
|
125
|
+
* { emitter: client, eventName: 'auth:success', options: { timeout: 5000 } },
|
|
126
|
+
* { emitter: client, eventName: 'auth:error', options: { timeout: 5000 } }
|
|
127
|
+
* ]);
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export async function waitForAnyEvent<T = any>(
|
|
131
|
+
waiters: Array<{
|
|
132
|
+
emitter: EventEmitter;
|
|
133
|
+
eventName: string;
|
|
134
|
+
options: WaitForEventOptions<T>;
|
|
135
|
+
}>
|
|
136
|
+
): Promise<T> {
|
|
137
|
+
return Promise.race(
|
|
138
|
+
waiters.map(({ emitter, eventName, options }) =>
|
|
139
|
+
waitForEvent<T>(emitter, eventName, options)
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Wait for all events to fire (all must complete)
|
|
146
|
+
* Useful when you need multiple events to occur before proceeding
|
|
147
|
+
*
|
|
148
|
+
* @param waiters - Array of event waiter configurations
|
|
149
|
+
* @returns Promise that resolves with array of all event data
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // Wait for both auth and connection
|
|
154
|
+
* const [authState, connState] = await waitForAllEvents([
|
|
155
|
+
* { emitter: client, eventName: 'auth:success', options: { timeout: 5000 } },
|
|
156
|
+
* { emitter: client, eventName: 'connection:open', options: { timeout: 5000 } }
|
|
157
|
+
* ]);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export async function waitForAllEvents<T = any>(
|
|
161
|
+
waiters: Array<{
|
|
162
|
+
emitter: EventEmitter;
|
|
163
|
+
eventName: string;
|
|
164
|
+
options: WaitForEventOptions<T>;
|
|
165
|
+
}>
|
|
166
|
+
): Promise<T[]> {
|
|
167
|
+
return Promise.all(
|
|
168
|
+
waiters.map(({ emitter, eventName, options }) =>
|
|
169
|
+
waitForEvent<T>(emitter, eventName, options)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions and helpers for the Teneo Protocol SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logger creation utilities
|
|
7
|
+
*/
|
|
8
|
+
export { createPinoLogger } from "./logger";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SSRF protection utilities for webhook URL validation
|
|
12
|
+
* Prevents Server-Side Request Forgery attacks
|
|
13
|
+
*/
|
|
14
|
+
export { validateWebhookUrl, isPrivateIP, isCloudMetadataEndpoint } from "./ssrf-validator";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Event waiting utilities for async event handling
|
|
18
|
+
* Wait for specific events with timeout support
|
|
19
|
+
*/
|
|
20
|
+
export {
|
|
21
|
+
waitForEvent,
|
|
22
|
+
waitForAnyEvent,
|
|
23
|
+
waitForAllEvents,
|
|
24
|
+
type WaitForEventOptions
|
|
25
|
+
} from "./event-waiter";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Memory-safe bounded queue with overflow strategies
|
|
29
|
+
* Prevents unbounded memory growth
|
|
30
|
+
*/
|
|
31
|
+
export { BoundedQueue, QueueOverflowError, type OverflowStrategy } from "./bounded-queue";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Token bucket rate limiter for request throttling
|
|
35
|
+
*/
|
|
36
|
+
export { TokenBucketRateLimiter, RateLimitError } from "./rate-limiter";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Circuit breaker pattern for fault tolerance
|
|
40
|
+
* Prevents cascading failures with automatic recovery
|
|
41
|
+
*/
|
|
42
|
+
export { CircuitBreaker, CircuitBreakerError, type CircuitState } from "./circuit-breaker";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Message deduplication cache with TTL support
|
|
46
|
+
*/
|
|
47
|
+
export { DeduplicationCache } from "./deduplication-cache";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Ethereum signature verification utilities (SEC-2)
|
|
51
|
+
* ECDSA signature validation for message authenticity
|
|
52
|
+
*/
|
|
53
|
+
export {
|
|
54
|
+
SignatureVerifier,
|
|
55
|
+
type SignatureVerificationOptions,
|
|
56
|
+
type VerificationResult
|
|
57
|
+
} from "./signature-verifier";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Secure private key storage with AES-256-GCM encryption (SEC-3)
|
|
61
|
+
* Protects private keys from memory dumps
|
|
62
|
+
*/
|
|
63
|
+
export { SecurePrivateKey } from "./secure-private-key";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Configurable retry strategies for resilient operations (REL-3)
|
|
67
|
+
* Supports exponential, linear, and constant backoff
|
|
68
|
+
*/
|
|
69
|
+
export {
|
|
70
|
+
RetryPolicy,
|
|
71
|
+
RetryStrategySchema,
|
|
72
|
+
type RetryStrategy,
|
|
73
|
+
type RetryStrategyType
|
|
74
|
+
} from "./retry-policy";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for Teneo Protocol SDK
|
|
3
|
+
* Provides pino-based logging with structured output and optional pretty printing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import pino from "pino";
|
|
7
|
+
import type { Logger, LogLevel } from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a pino-based logger that conforms to the SDK Logger interface.
|
|
11
|
+
* Automatically configures pretty printing for development environments.
|
|
12
|
+
*
|
|
13
|
+
* @param level - Log level (debug, info, warn, error, silent)
|
|
14
|
+
* @param name - Logger name for identifying log source (e.g., "TeneoSDK", "WebSocketClient")
|
|
15
|
+
* @returns Logger instance compatible with SDK Logger interface
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const logger = createPinoLogger('info', 'TeneoSDK');
|
|
20
|
+
* logger.info('SDK initialized', { wsUrl: 'wss://example.com' });
|
|
21
|
+
* logger.error('Connection failed', { code: 'CONN_FAILED', attempt: 3 });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function createPinoLogger(level: LogLevel, name?: string): Logger {
|
|
25
|
+
// Map 'silent' to pino's 'silent' level
|
|
26
|
+
const pinoLevel = level === "silent" ? "silent" : level;
|
|
27
|
+
|
|
28
|
+
// Create pino logger with optional pretty printing for development
|
|
29
|
+
const pinoLogger = pino({
|
|
30
|
+
level: pinoLevel,
|
|
31
|
+
name: name || "TeneoSDK",
|
|
32
|
+
// Use pino-pretty in development for readable logs
|
|
33
|
+
transport:
|
|
34
|
+
process.env.NODE_ENV !== "production"
|
|
35
|
+
? {
|
|
36
|
+
target: "pino-pretty",
|
|
37
|
+
options: {
|
|
38
|
+
colorize: true,
|
|
39
|
+
ignore: "pid,hostname",
|
|
40
|
+
translateTime: "HH:MM:ss.l",
|
|
41
|
+
singleLine: false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
: undefined,
|
|
45
|
+
// Production: fast JSON logs
|
|
46
|
+
formatters:
|
|
47
|
+
process.env.NODE_ENV === "production"
|
|
48
|
+
? {
|
|
49
|
+
level: (label) => {
|
|
50
|
+
return { level: label };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
: undefined
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Adapt pino's API to match our Logger interface
|
|
57
|
+
return {
|
|
58
|
+
debug: (message: string, data?: any) => {
|
|
59
|
+
if (data !== undefined) {
|
|
60
|
+
pinoLogger.debug(data, message);
|
|
61
|
+
} else {
|
|
62
|
+
pinoLogger.debug(message);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
info: (message: string, data?: any) => {
|
|
66
|
+
if (data !== undefined) {
|
|
67
|
+
pinoLogger.info(data, message);
|
|
68
|
+
} else {
|
|
69
|
+
pinoLogger.info(message);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
warn: (message: string, data?: any) => {
|
|
73
|
+
if (data !== undefined) {
|
|
74
|
+
pinoLogger.warn(data, message);
|
|
75
|
+
} else {
|
|
76
|
+
pinoLogger.warn(message);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
error: (message: string, data?: any) => {
|
|
80
|
+
if (data !== undefined) {
|
|
81
|
+
pinoLogger.error(data, message);
|
|
82
|
+
} else {
|
|
83
|
+
pinoLogger.error(message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|