@forgehive/hive-sdk 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -23
- package/dist/index.js +19 -11
- package/dist/test/getLog.test.js +21 -6
- package/dist/test/index.test.js +8 -1
- package/dist/test/metadata.test.js +67 -20
- package/dist/test/sendLog.test.js +253 -30
- package/package.json +9 -2
- package/src/index.ts +25 -29
- package/src/test/getLog.test.ts +21 -6
- package/src/test/index.test.ts +8 -1
- package/src/test/metadata.test.ts +67 -20
- package/src/test/sendLog.test.ts +298 -30
|
@@ -8,7 +8,7 @@ const index_1 = require("../index");
|
|
|
8
8
|
// Mock axios
|
|
9
9
|
jest.mock('axios');
|
|
10
10
|
const mockedAxios = axios_1.default;
|
|
11
|
-
describe('HiveLogClient sendLog', () => {
|
|
11
|
+
describe('HiveLogClient sendLog with ExecutionRecord', () => {
|
|
12
12
|
let client;
|
|
13
13
|
const testConfig = {
|
|
14
14
|
projectName: 'test-project',
|
|
@@ -22,18 +22,32 @@ describe('HiveLogClient sendLog', () => {
|
|
|
22
22
|
// Clear all mocks
|
|
23
23
|
jest.clearAllMocks();
|
|
24
24
|
});
|
|
25
|
-
describe('successful sendLog', () => {
|
|
26
|
-
it('should send log successfully and return
|
|
25
|
+
describe('successful sendLog with ExecutionRecord', () => {
|
|
26
|
+
it('should send log successfully with ExecutionRecord and return success', async () => {
|
|
27
27
|
// Mock successful axios response
|
|
28
28
|
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const executionRecord = {
|
|
30
|
+
input: { value: 'test-input' },
|
|
31
|
+
output: { result: 'test-output' },
|
|
32
|
+
taskName: 'test-task',
|
|
33
|
+
type: 'success',
|
|
34
|
+
boundaries: {},
|
|
35
|
+
metadata: {}
|
|
36
|
+
};
|
|
37
|
+
const result = await client.sendLog(executionRecord);
|
|
31
38
|
expect(result).toBe('success');
|
|
32
39
|
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
|
|
33
40
|
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
34
41
|
projectName: 'test-project',
|
|
35
42
|
taskName: 'test-task',
|
|
36
|
-
logItem: JSON.stringify({
|
|
43
|
+
logItem: JSON.stringify({
|
|
44
|
+
input: { value: 'test-input' },
|
|
45
|
+
output: { result: 'test-output' },
|
|
46
|
+
taskName: 'test-task',
|
|
47
|
+
type: 'success',
|
|
48
|
+
boundaries: {},
|
|
49
|
+
metadata: {}
|
|
50
|
+
})
|
|
37
51
|
}, {
|
|
38
52
|
headers: {
|
|
39
53
|
Authorization: 'Bearer test-api-key:test-api-secret',
|
|
@@ -41,22 +55,51 @@ describe('HiveLogClient sendLog', () => {
|
|
|
41
55
|
}
|
|
42
56
|
});
|
|
43
57
|
});
|
|
44
|
-
it('should handle complex
|
|
58
|
+
it('should handle ExecutionRecord with complex boundaries', async () => {
|
|
45
59
|
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
46
|
-
const
|
|
60
|
+
const executionRecord = {
|
|
47
61
|
input: { userId: 123, action: 'login' },
|
|
48
62
|
output: { success: true, sessionId: 'abc123' },
|
|
49
|
-
|
|
63
|
+
taskName: 'complex-task',
|
|
64
|
+
type: 'success',
|
|
50
65
|
boundaries: {
|
|
51
|
-
database: [{
|
|
52
|
-
|
|
66
|
+
database: [{
|
|
67
|
+
input: ['SELECT * FROM users'],
|
|
68
|
+
output: [{ id: 123 }],
|
|
69
|
+
timing: { startTime: 1000, endTime: 1100, duration: 100 }
|
|
70
|
+
}],
|
|
71
|
+
api: [{
|
|
72
|
+
input: [{ endpoint: '/auth' }],
|
|
73
|
+
output: { token: 'jwt123' },
|
|
74
|
+
timing: { startTime: 1200, endTime: 1250, duration: 50 }
|
|
75
|
+
}]
|
|
76
|
+
},
|
|
77
|
+
metadata: { environment: 'test' }
|
|
53
78
|
};
|
|
54
|
-
const result = await client.sendLog(
|
|
79
|
+
const result = await client.sendLog(executionRecord);
|
|
55
80
|
expect(result).toBe('success');
|
|
56
81
|
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
57
82
|
projectName: 'test-project',
|
|
58
83
|
taskName: 'complex-task',
|
|
59
|
-
logItem: JSON.stringify({
|
|
84
|
+
logItem: JSON.stringify({
|
|
85
|
+
input: { userId: 123, action: 'login' },
|
|
86
|
+
output: { success: true, sessionId: 'abc123' },
|
|
87
|
+
taskName: 'complex-task',
|
|
88
|
+
type: 'success',
|
|
89
|
+
boundaries: {
|
|
90
|
+
database: [{
|
|
91
|
+
input: ['SELECT * FROM users'],
|
|
92
|
+
output: [{ id: 123 }],
|
|
93
|
+
timing: { startTime: 1000, endTime: 1100, duration: 100 }
|
|
94
|
+
}],
|
|
95
|
+
api: [{
|
|
96
|
+
input: [{ endpoint: '/auth' }],
|
|
97
|
+
output: { token: 'jwt123' },
|
|
98
|
+
timing: { startTime: 1200, endTime: 1250, duration: 50 }
|
|
99
|
+
}]
|
|
100
|
+
},
|
|
101
|
+
metadata: { environment: 'test' }
|
|
102
|
+
})
|
|
60
103
|
}, {
|
|
61
104
|
headers: {
|
|
62
105
|
Authorization: 'Bearer test-api-key:test-api-secret',
|
|
@@ -64,44 +107,224 @@ describe('HiveLogClient sendLog', () => {
|
|
|
64
107
|
}
|
|
65
108
|
});
|
|
66
109
|
});
|
|
110
|
+
it('should handle ExecutionRecord with error', async () => {
|
|
111
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
112
|
+
const executionRecord = {
|
|
113
|
+
input: { value: 'test-input' },
|
|
114
|
+
output: undefined,
|
|
115
|
+
error: 'Task execution failed',
|
|
116
|
+
taskName: 'error-task',
|
|
117
|
+
type: 'error',
|
|
118
|
+
boundaries: {},
|
|
119
|
+
metadata: {}
|
|
120
|
+
};
|
|
121
|
+
const result = await client.sendLog(executionRecord);
|
|
122
|
+
expect(result).toBe('success');
|
|
123
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
124
|
+
projectName: 'test-project',
|
|
125
|
+
taskName: 'error-task',
|
|
126
|
+
logItem: JSON.stringify({
|
|
127
|
+
input: { value: 'test-input' },
|
|
128
|
+
output: undefined,
|
|
129
|
+
error: 'Task execution failed',
|
|
130
|
+
taskName: 'error-task',
|
|
131
|
+
type: 'error',
|
|
132
|
+
boundaries: {},
|
|
133
|
+
metadata: {}
|
|
134
|
+
})
|
|
135
|
+
}, expect.any(Object));
|
|
136
|
+
});
|
|
137
|
+
it('should use "unknown-task" when taskName is missing', async () => {
|
|
138
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
139
|
+
const executionRecord = {
|
|
140
|
+
input: { value: 'test-input' },
|
|
141
|
+
output: { result: 'test-output' },
|
|
142
|
+
// taskName is missing
|
|
143
|
+
type: 'success',
|
|
144
|
+
boundaries: {},
|
|
145
|
+
metadata: {}
|
|
146
|
+
};
|
|
147
|
+
const result = await client.sendLog(executionRecord);
|
|
148
|
+
expect(result).toBe('success');
|
|
149
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
150
|
+
projectName: 'test-project',
|
|
151
|
+
taskName: 'unknown-task',
|
|
152
|
+
logItem: JSON.stringify({
|
|
153
|
+
input: { value: 'test-input' },
|
|
154
|
+
output: { result: 'test-output' },
|
|
155
|
+
type: 'success',
|
|
156
|
+
boundaries: {},
|
|
157
|
+
metadata: {},
|
|
158
|
+
taskName: 'unknown-task'
|
|
159
|
+
})
|
|
160
|
+
}, expect.any(Object));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe('sendLog with additional metadata', () => {
|
|
164
|
+
it('should merge metadata from ExecutionRecord and sendLog parameter', async () => {
|
|
165
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
166
|
+
const executionRecord = {
|
|
167
|
+
input: { value: 'test-input' },
|
|
168
|
+
output: { result: 'test-output' },
|
|
169
|
+
taskName: 'metadata-task',
|
|
170
|
+
type: 'success',
|
|
171
|
+
boundaries: {},
|
|
172
|
+
metadata: {
|
|
173
|
+
recordMeta: 'from-record',
|
|
174
|
+
sharedKey: 'record-value'
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const sendLogMetadata = {
|
|
178
|
+
sendLogMeta: 'from-sendlog',
|
|
179
|
+
sharedKey: 'sendlog-value' // This should override record value
|
|
180
|
+
};
|
|
181
|
+
const result = await client.sendLog(executionRecord, sendLogMetadata);
|
|
182
|
+
expect(result).toBe('success');
|
|
183
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
184
|
+
projectName: 'test-project',
|
|
185
|
+
taskName: 'metadata-task',
|
|
186
|
+
logItem: JSON.stringify({
|
|
187
|
+
input: { value: 'test-input' },
|
|
188
|
+
output: { result: 'test-output' },
|
|
189
|
+
taskName: 'metadata-task',
|
|
190
|
+
type: 'success',
|
|
191
|
+
boundaries: {},
|
|
192
|
+
metadata: {
|
|
193
|
+
recordMeta: 'from-record',
|
|
194
|
+
sharedKey: 'sendlog-value', // sendLog metadata takes priority
|
|
195
|
+
sendLogMeta: 'from-sendlog'
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
}, expect.any(Object));
|
|
199
|
+
});
|
|
67
200
|
});
|
|
68
201
|
describe('failed sendLog', () => {
|
|
69
|
-
it('should return
|
|
202
|
+
it('should return error when axios throws an error', async () => {
|
|
70
203
|
// Mock axios to throw an error
|
|
71
204
|
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
|
72
|
-
const
|
|
73
|
-
|
|
205
|
+
const executionRecord = {
|
|
206
|
+
input: { value: 'test-input' },
|
|
207
|
+
taskName: 'test-task',
|
|
208
|
+
type: 'success',
|
|
209
|
+
boundaries: {},
|
|
210
|
+
metadata: {}
|
|
211
|
+
};
|
|
212
|
+
const result = await client.sendLog(executionRecord);
|
|
74
213
|
expect(result).toBe('error');
|
|
75
214
|
});
|
|
76
|
-
it('should return
|
|
215
|
+
it('should return error when server returns 500', async () => {
|
|
77
216
|
// Mock axios to throw a server error
|
|
78
217
|
const serverError = new Error('Server Error');
|
|
79
218
|
mockedAxios.post.mockRejectedValueOnce(serverError);
|
|
80
|
-
const
|
|
219
|
+
const executionRecord = {
|
|
220
|
+
input: { value: 'test' },
|
|
221
|
+
taskName: 'test-task',
|
|
222
|
+
type: 'success',
|
|
223
|
+
boundaries: {},
|
|
224
|
+
metadata: {}
|
|
225
|
+
};
|
|
226
|
+
const result = await client.sendLog(executionRecord);
|
|
81
227
|
expect(result).toBe('error');
|
|
82
228
|
});
|
|
83
229
|
});
|
|
84
|
-
describe('sendLog
|
|
85
|
-
it('should
|
|
230
|
+
describe('sendLog in silent mode', () => {
|
|
231
|
+
it('should return silent when client is not initialized', async () => {
|
|
232
|
+
const uninitializedClient = new index_1.HiveLogClient({
|
|
233
|
+
projectName: 'test-project'
|
|
234
|
+
// No API credentials
|
|
235
|
+
});
|
|
236
|
+
const executionRecord = {
|
|
237
|
+
input: { value: 'test-input' },
|
|
238
|
+
taskName: 'test-task',
|
|
239
|
+
type: 'success',
|
|
240
|
+
boundaries: {},
|
|
241
|
+
metadata: {}
|
|
242
|
+
};
|
|
243
|
+
const result = await uninitializedClient.sendLog(executionRecord);
|
|
244
|
+
expect(result).toBe('silent');
|
|
245
|
+
expect(mockedAxios.post).not.toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('HiveLogClient getListener', () => {
|
|
250
|
+
let client;
|
|
251
|
+
const testConfig = {
|
|
252
|
+
projectName: 'test-project',
|
|
253
|
+
apiKey: 'test-api-key',
|
|
254
|
+
apiSecret: 'test-api-secret',
|
|
255
|
+
host: 'https://test-host.com'
|
|
256
|
+
};
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
client = new index_1.HiveLogClient(testConfig);
|
|
259
|
+
jest.clearAllMocks();
|
|
260
|
+
});
|
|
261
|
+
describe('getListener method', () => {
|
|
262
|
+
it('should return a function that calls sendLog', async () => {
|
|
86
263
|
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
87
|
-
const
|
|
88
|
-
expect(
|
|
264
|
+
const listener = client.getListener();
|
|
265
|
+
expect(typeof listener).toBe('function');
|
|
266
|
+
const executionRecord = {
|
|
267
|
+
input: { value: 'test-input' },
|
|
268
|
+
output: { result: 'test-output' },
|
|
269
|
+
taskName: 'test-task',
|
|
270
|
+
type: 'success',
|
|
271
|
+
boundaries: {},
|
|
272
|
+
metadata: {}
|
|
273
|
+
};
|
|
274
|
+
await listener(executionRecord);
|
|
275
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
|
|
89
276
|
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
90
277
|
projectName: 'test-project',
|
|
91
|
-
taskName: '
|
|
92
|
-
logItem: JSON.stringify({
|
|
278
|
+
taskName: 'test-task',
|
|
279
|
+
logItem: JSON.stringify({
|
|
280
|
+
input: { value: 'test-input' },
|
|
281
|
+
output: { result: 'test-output' },
|
|
282
|
+
taskName: 'test-task',
|
|
283
|
+
type: 'success',
|
|
284
|
+
boundaries: {},
|
|
285
|
+
metadata: {}
|
|
286
|
+
})
|
|
93
287
|
}, expect.any(Object));
|
|
94
288
|
});
|
|
95
|
-
it('should
|
|
289
|
+
it('should return a function that calls sendLog with provided metadata', async () => {
|
|
96
290
|
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
291
|
+
const listener = client.getListener();
|
|
292
|
+
const executionRecord = {
|
|
293
|
+
input: { value: 'test-input' },
|
|
294
|
+
output: { result: 'test-output' },
|
|
295
|
+
taskName: 'test-task',
|
|
296
|
+
type: 'success',
|
|
297
|
+
boundaries: {},
|
|
298
|
+
metadata: { recordMeta: 'from-record' }
|
|
299
|
+
};
|
|
300
|
+
await listener(executionRecord);
|
|
100
301
|
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
101
302
|
projectName: 'test-project',
|
|
102
|
-
taskName: '
|
|
103
|
-
logItem: JSON.stringify({
|
|
303
|
+
taskName: 'test-task',
|
|
304
|
+
logItem: JSON.stringify({
|
|
305
|
+
input: { value: 'test-input' },
|
|
306
|
+
output: { result: 'test-output' },
|
|
307
|
+
taskName: 'test-task',
|
|
308
|
+
type: 'success',
|
|
309
|
+
boundaries: {},
|
|
310
|
+
metadata: {
|
|
311
|
+
recordMeta: 'from-record'
|
|
312
|
+
}
|
|
313
|
+
})
|
|
104
314
|
}, expect.any(Object));
|
|
105
315
|
});
|
|
316
|
+
it('should handle listener errors gracefully', async () => {
|
|
317
|
+
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
|
318
|
+
const listener = client.getListener();
|
|
319
|
+
const executionRecord = {
|
|
320
|
+
input: { value: 'test-input' },
|
|
321
|
+
taskName: 'test-task',
|
|
322
|
+
type: 'success',
|
|
323
|
+
boundaries: {},
|
|
324
|
+
metadata: {}
|
|
325
|
+
};
|
|
326
|
+
// Should not throw, even if sendLog fails
|
|
327
|
+
await expect(listener(executionRecord)).resolves.toBeUndefined();
|
|
328
|
+
});
|
|
106
329
|
});
|
|
107
330
|
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/hive-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@forgehive/task": "^0.2.4"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"devDependencies": {
|
|
8
14
|
"@types/jest": "^29.5.14",
|
|
9
15
|
"@types/node": "^24.0.3",
|
|
@@ -15,7 +21,8 @@
|
|
|
15
21
|
"license": "ISC",
|
|
16
22
|
"dependencies": {
|
|
17
23
|
"axios": "^1.8.4",
|
|
18
|
-
"debug": "^4.4.1"
|
|
24
|
+
"debug": "^4.4.1",
|
|
25
|
+
"@forgehive/task": "0.2.4"
|
|
19
26
|
},
|
|
20
27
|
"scripts": {
|
|
21
28
|
"build": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import debug from 'debug'
|
|
3
|
+
import type { ExecutionRecord } from '@forgehive/task'
|
|
3
4
|
|
|
4
5
|
const log = debug('hive-sdk')
|
|
5
6
|
|
|
@@ -8,17 +9,8 @@ export interface Metadata {
|
|
|
8
9
|
[key: string]: string
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
-
export
|
|
13
|
-
input: unknown
|
|
14
|
-
output?: unknown
|
|
15
|
-
error?: unknown
|
|
16
|
-
boundaries?: unknown // Allow any boundary structure (task records have different format)
|
|
17
|
-
metadata?: Metadata
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Backward compatibility alias
|
|
21
|
-
export type LogItem = LogItemInput
|
|
12
|
+
// Re-export ExecutionRecord type from task package for convenience
|
|
13
|
+
export type { ExecutionRecord } from '@forgehive/task'
|
|
22
14
|
|
|
23
15
|
// Configuration interface for HiveLogClient
|
|
24
16
|
export interface HiveLogClientConfig {
|
|
@@ -34,13 +26,7 @@ export interface LogApiResponse {
|
|
|
34
26
|
uuid: string
|
|
35
27
|
taskName: string
|
|
36
28
|
projectName: string
|
|
37
|
-
logItem:
|
|
38
|
-
input: unknown
|
|
39
|
-
output?: unknown
|
|
40
|
-
error?: unknown
|
|
41
|
-
boundaries?: Record<string, Array<{ input: unknown; output: unknown, error: unknown }>>
|
|
42
|
-
metadata?: Metadata
|
|
43
|
-
}
|
|
29
|
+
logItem: ExecutionRecord
|
|
44
30
|
replayFrom?: string
|
|
45
31
|
createdAt: string
|
|
46
32
|
}
|
|
@@ -100,13 +86,13 @@ export class HiveLogClient {
|
|
|
100
86
|
return this.isInitialized
|
|
101
87
|
}
|
|
102
88
|
|
|
103
|
-
private mergeMetadata
|
|
89
|
+
private mergeMetadata(record: ExecutionRecord, sendLogMetadata?: Metadata): Metadata {
|
|
104
90
|
// Start with base metadata from client
|
|
105
91
|
let finalMetadata = { ...this.baseMetadata }
|
|
106
92
|
|
|
107
|
-
// Merge with
|
|
108
|
-
if (
|
|
109
|
-
finalMetadata = { ...finalMetadata, ...
|
|
93
|
+
// Merge with record metadata if it exists
|
|
94
|
+
if (record.metadata) {
|
|
95
|
+
finalMetadata = { ...finalMetadata, ...record.metadata }
|
|
110
96
|
}
|
|
111
97
|
|
|
112
98
|
// Merge with sendLog metadata (highest priority)
|
|
@@ -117,7 +103,10 @@ export class HiveLogClient {
|
|
|
117
103
|
return finalMetadata
|
|
118
104
|
}
|
|
119
105
|
|
|
120
|
-
async sendLog
|
|
106
|
+
async sendLog(record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent'> {
|
|
107
|
+
// Extract taskName from record
|
|
108
|
+
const taskName = record.taskName || 'unknown-task'
|
|
109
|
+
|
|
121
110
|
if (!this.isInitialized) {
|
|
122
111
|
log('Silent mode: Skipping sendLog for task "%s" - client not initialized', taskName)
|
|
123
112
|
return 'silent'
|
|
@@ -129,19 +118,20 @@ export class HiveLogClient {
|
|
|
129
118
|
|
|
130
119
|
const authToken = `${this.apiKey}:${this.apiSecret}`
|
|
131
120
|
|
|
132
|
-
// Merge metadata with priority: sendLog >
|
|
133
|
-
const finalMetadata = this.mergeMetadata(
|
|
121
|
+
// Merge metadata with priority: sendLog > record.metadata > client
|
|
122
|
+
const finalMetadata = this.mergeMetadata(record, metadata)
|
|
134
123
|
|
|
135
|
-
// Create
|
|
136
|
-
const
|
|
137
|
-
...
|
|
124
|
+
// Create logItem with merged metadata
|
|
125
|
+
const logItem = {
|
|
126
|
+
...record,
|
|
127
|
+
taskName,
|
|
138
128
|
metadata: finalMetadata
|
|
139
129
|
}
|
|
140
130
|
|
|
141
131
|
await axios.post(logsUrl, {
|
|
142
132
|
projectName: this.projectName,
|
|
143
133
|
taskName,
|
|
144
|
-
logItem: JSON.stringify(
|
|
134
|
+
logItem: JSON.stringify(logItem)
|
|
145
135
|
}, {
|
|
146
136
|
headers: {
|
|
147
137
|
Authorization: `Bearer ${authToken}`,
|
|
@@ -158,6 +148,12 @@ export class HiveLogClient {
|
|
|
158
148
|
}
|
|
159
149
|
}
|
|
160
150
|
|
|
151
|
+
getListener(): (record: ExecutionRecord) => Promise<void> {
|
|
152
|
+
return async (record: ExecutionRecord) => {
|
|
153
|
+
await this.sendLog(record)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
161
157
|
async getLog(taskName: string, uuid: string): Promise<LogApiResult | null> {
|
|
162
158
|
if (!this.isInitialized) {
|
|
163
159
|
log('Error: getLog for task "%s" with uuid "%s" - missing credentials', taskName, uuid)
|
package/src/test/getLog.test.ts
CHANGED
|
@@ -32,10 +32,15 @@ describe('HiveLogClient getLog', () => {
|
|
|
32
32
|
logItem: {
|
|
33
33
|
input: { userId: 123, action: 'login' },
|
|
34
34
|
output: { success: true, sessionId: 'abc123' },
|
|
35
|
-
error:
|
|
35
|
+
error: undefined,
|
|
36
36
|
boundaries: {
|
|
37
|
-
database: [{
|
|
38
|
-
|
|
37
|
+
database: [{
|
|
38
|
+
input: ['SELECT * FROM users'],
|
|
39
|
+
output: [{ id: 123 }],
|
|
40
|
+
timing: { startTime: 1000, endTime: 1100, duration: 100 }
|
|
41
|
+
}]
|
|
42
|
+
},
|
|
43
|
+
type: 'success' as const
|
|
39
44
|
},
|
|
40
45
|
replayFrom: 'some-replay-id',
|
|
41
46
|
createdAt: '2023-12-01T10:00:00Z'
|
|
@@ -78,7 +83,9 @@ describe('HiveLogClient getLog', () => {
|
|
|
78
83
|
taskName: 'minimal-task',
|
|
79
84
|
projectName: 'test-project',
|
|
80
85
|
logItem: {
|
|
81
|
-
input: 'simple input'
|
|
86
|
+
input: 'simple input',
|
|
87
|
+
boundaries: {},
|
|
88
|
+
type: 'pending' as const
|
|
82
89
|
},
|
|
83
90
|
createdAt: '2023-12-01T10:00:00Z'
|
|
84
91
|
}
|
|
@@ -129,7 +136,11 @@ describe('HiveLogClient getLog', () => {
|
|
|
129
136
|
uuid: 'uuid-with-special-chars-!@#',
|
|
130
137
|
taskName: 'task-with-special-chars-!@#',
|
|
131
138
|
projectName: 'test-project',
|
|
132
|
-
logItem: {
|
|
139
|
+
logItem: {
|
|
140
|
+
input: 'test',
|
|
141
|
+
boundaries: {},
|
|
142
|
+
type: 'pending' as const
|
|
143
|
+
},
|
|
133
144
|
createdAt: '2023-12-01T10:00:00Z'
|
|
134
145
|
}
|
|
135
146
|
|
|
@@ -157,7 +168,11 @@ describe('isApiError type guard', () => {
|
|
|
157
168
|
uuid: 'test-uuid',
|
|
158
169
|
taskName: 'test-task',
|
|
159
170
|
projectName: 'test-project',
|
|
160
|
-
logItem: {
|
|
171
|
+
logItem: {
|
|
172
|
+
input: 'test',
|
|
173
|
+
boundaries: {},
|
|
174
|
+
type: 'pending' as const
|
|
175
|
+
},
|
|
161
176
|
createdAt: '2023-12-01T10:00:00Z'
|
|
162
177
|
}
|
|
163
178
|
expect(isApiError(logResponse)).toBe(false)
|
package/src/test/index.test.ts
CHANGED
|
@@ -106,7 +106,14 @@ describe('Hive SDK', () => {
|
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
it('should return "silent" for sendLog in silent mode', async () => {
|
|
109
|
-
const
|
|
109
|
+
const executionRecord = {
|
|
110
|
+
input: { test: 'value' },
|
|
111
|
+
taskName: 'test-task',
|
|
112
|
+
type: 'success' as const,
|
|
113
|
+
boundaries: {},
|
|
114
|
+
metadata: {}
|
|
115
|
+
}
|
|
116
|
+
const result = await silentClient.sendLog(executionRecord)
|
|
110
117
|
expect(result).toBe('silent')
|
|
111
118
|
})
|
|
112
119
|
|