@forgehive/hive-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +357 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +123 -0
- package/dist/test/getLog.test.d.ts +1 -0
- package/dist/test/getLog.test.js +144 -0
- package/dist/test/index.test.d.ts +1 -0
- package/dist/test/index.test.js +96 -0
- package/dist/test/sendLog.test.d.ts +1 -0
- package/dist/test/sendLog.test.js +111 -0
- package/dist/test/setQuality.test.d.ts +1 -0
- package/dist/test/setQuality.test.js +192 -0
- package/jest.config.js +11 -0
- package/package.json +25 -0
- package/src/index.ts +168 -0
- package/src/test/getLog.test.ts +181 -0
- package/src/test/index.test.ts +126 -0
- package/src/test/sendLog.test.ts +148 -0
- package/src/test/setQuality.test.ts +268 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../index");
|
|
4
|
+
describe('Hive SDK', () => {
|
|
5
|
+
const originalEnv = process.env;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.resetModules();
|
|
8
|
+
process.env = { ...originalEnv };
|
|
9
|
+
});
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
process.env = originalEnv;
|
|
12
|
+
});
|
|
13
|
+
describe('HiveLogClient constructor', () => {
|
|
14
|
+
it('should create client in silent mode when HIVE_API_KEY is missing', () => {
|
|
15
|
+
delete process.env.HIVE_API_KEY;
|
|
16
|
+
process.env.HIVE_API_SECRET = 'test-secret';
|
|
17
|
+
process.env.HIVE_HOST = 'https://test.com';
|
|
18
|
+
expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
|
|
19
|
+
});
|
|
20
|
+
it('should create client in silent mode when HIVE_API_SECRET is missing', () => {
|
|
21
|
+
process.env.HIVE_API_KEY = 'test-key';
|
|
22
|
+
delete process.env.HIVE_API_SECRET;
|
|
23
|
+
process.env.HIVE_HOST = 'https://test.com';
|
|
24
|
+
expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
|
|
25
|
+
});
|
|
26
|
+
it('should create client in silent mode when HIVE_HOST is missing', () => {
|
|
27
|
+
process.env.HIVE_API_KEY = 'test-key';
|
|
28
|
+
process.env.HIVE_API_SECRET = 'test-secret';
|
|
29
|
+
delete process.env.HIVE_HOST;
|
|
30
|
+
expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
it('should create client in silent mode when all environment variables are missing', () => {
|
|
33
|
+
delete process.env.HIVE_API_KEY;
|
|
34
|
+
delete process.env.HIVE_API_SECRET;
|
|
35
|
+
delete process.env.HIVE_HOST;
|
|
36
|
+
expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
it('should create client successfully when all environment variables are present', () => {
|
|
39
|
+
process.env.HIVE_API_KEY = 'test-key';
|
|
40
|
+
process.env.HIVE_API_SECRET = 'test-secret';
|
|
41
|
+
process.env.HIVE_HOST = 'https://test.com';
|
|
42
|
+
expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('isActive method', () => {
|
|
46
|
+
it('should return false when credentials are missing', () => {
|
|
47
|
+
delete process.env.HIVE_API_KEY;
|
|
48
|
+
delete process.env.HIVE_API_SECRET;
|
|
49
|
+
delete process.env.HIVE_HOST;
|
|
50
|
+
const client = new index_1.HiveLogClient('test-project');
|
|
51
|
+
expect(client.isActive()).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('should return true when all credentials are present', () => {
|
|
54
|
+
process.env.HIVE_API_KEY = 'test-key';
|
|
55
|
+
process.env.HIVE_API_SECRET = 'test-secret';
|
|
56
|
+
process.env.HIVE_HOST = 'https://test.com';
|
|
57
|
+
const client = new index_1.HiveLogClient('test-project');
|
|
58
|
+
expect(client.isActive()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('createHiveLogClient factory function', () => {
|
|
62
|
+
it('should create HiveLogClient instance when env vars are present', () => {
|
|
63
|
+
process.env.HIVE_API_KEY = 'test-key';
|
|
64
|
+
process.env.HIVE_API_SECRET = 'test-secret';
|
|
65
|
+
process.env.HIVE_HOST = 'https://test.com';
|
|
66
|
+
const client = (0, index_1.createHiveLogClient)('test-project');
|
|
67
|
+
expect(client).toBeInstanceOf(index_1.HiveLogClient);
|
|
68
|
+
});
|
|
69
|
+
it('should create client in silent mode when env vars are missing', () => {
|
|
70
|
+
delete process.env.HIVE_API_KEY;
|
|
71
|
+
delete process.env.HIVE_API_SECRET;
|
|
72
|
+
delete process.env.HIVE_HOST;
|
|
73
|
+
expect(() => (0, index_1.createHiveLogClient)('test-project')).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('Silent mode behavior', () => {
|
|
77
|
+
let silentClient;
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
delete process.env.HIVE_API_KEY;
|
|
80
|
+
delete process.env.HIVE_API_SECRET;
|
|
81
|
+
delete process.env.HIVE_HOST;
|
|
82
|
+
silentClient = new index_1.HiveLogClient('silent-project');
|
|
83
|
+
});
|
|
84
|
+
it('should return "silent" for sendLog in silent mode', async () => {
|
|
85
|
+
const result = await silentClient.sendLog('test-task', { data: 'test' });
|
|
86
|
+
expect(result).toBe('silent');
|
|
87
|
+
});
|
|
88
|
+
it('should throw error for getLog in silent mode', async () => {
|
|
89
|
+
await expect(silentClient.getLog('test-task', 'test-uuid')).rejects.toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
|
|
90
|
+
});
|
|
91
|
+
it('should throw error for setQuality in silent mode', async () => {
|
|
92
|
+
const quality = { score: 8, reason: 'test', suggestions: 'test' };
|
|
93
|
+
await expect(silentClient.setQuality('test-task', 'test-uuid', quality)).rejects.toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const index_1 = require("../index");
|
|
8
|
+
// Mock axios
|
|
9
|
+
jest.mock('axios');
|
|
10
|
+
const mockedAxios = axios_1.default;
|
|
11
|
+
describe('HiveLogClient sendLog', () => {
|
|
12
|
+
const originalEnv = process.env;
|
|
13
|
+
let client;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.resetModules();
|
|
16
|
+
process.env = { ...originalEnv };
|
|
17
|
+
// Set up environment variables
|
|
18
|
+
process.env.HIVE_API_KEY = 'test-api-key';
|
|
19
|
+
process.env.HIVE_API_SECRET = 'test-api-secret';
|
|
20
|
+
process.env.HIVE_HOST = 'https://test-host.com';
|
|
21
|
+
// Create client instance
|
|
22
|
+
client = new index_1.HiveLogClient('test-project');
|
|
23
|
+
// Clear all mocks
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
afterAll(() => {
|
|
27
|
+
process.env = originalEnv;
|
|
28
|
+
});
|
|
29
|
+
describe('successful sendLog', () => {
|
|
30
|
+
it('should send log successfully and return true', async () => {
|
|
31
|
+
// Mock successful axios response
|
|
32
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
33
|
+
const logItem = { input: 'test-input', output: 'test-output' };
|
|
34
|
+
const result = await client.sendLog('test-task', logItem);
|
|
35
|
+
expect(result).toBe('success');
|
|
36
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
|
|
37
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
38
|
+
projectName: 'test-project',
|
|
39
|
+
taskName: 'test-task',
|
|
40
|
+
logItem: JSON.stringify(logItem)
|
|
41
|
+
}, {
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: 'Bearer test-api-key:test-api-secret',
|
|
44
|
+
'Content-Type': 'application/json'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it('should handle complex log items', async () => {
|
|
49
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
50
|
+
const complexLogItem = {
|
|
51
|
+
input: { userId: 123, action: 'login' },
|
|
52
|
+
output: { success: true, sessionId: 'abc123' },
|
|
53
|
+
error: null,
|
|
54
|
+
boundaries: {
|
|
55
|
+
database: [{ input: 'SELECT * FROM users', output: [{ id: 123 }], error: null }]
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const result = await client.sendLog('complex-task', complexLogItem);
|
|
59
|
+
expect(result).toBe('success');
|
|
60
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
61
|
+
projectName: 'test-project',
|
|
62
|
+
taskName: 'complex-task',
|
|
63
|
+
logItem: JSON.stringify(complexLogItem)
|
|
64
|
+
}, {
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: 'Bearer test-api-key:test-api-secret',
|
|
67
|
+
'Content-Type': 'application/json'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('failed sendLog', () => {
|
|
73
|
+
it('should return false when axios throws an error', async () => {
|
|
74
|
+
// Mock axios to throw an error
|
|
75
|
+
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
|
76
|
+
const logItem = { input: 'test-input' };
|
|
77
|
+
const result = await client.sendLog('test-task', logItem);
|
|
78
|
+
expect(result).toBe('error');
|
|
79
|
+
});
|
|
80
|
+
it('should return false when server returns 500', async () => {
|
|
81
|
+
// Mock axios to throw a server error
|
|
82
|
+
const serverError = new Error('Server Error');
|
|
83
|
+
mockedAxios.post.mockRejectedValueOnce(serverError);
|
|
84
|
+
const result = await client.sendLog('test-task', { input: 'test' });
|
|
85
|
+
expect(result).toBe('error');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('sendLog parameters', () => {
|
|
89
|
+
it('should handle empty log items', async () => {
|
|
90
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
91
|
+
const result = await client.sendLog('empty-task', {});
|
|
92
|
+
expect(result).toBe('success');
|
|
93
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
94
|
+
projectName: 'test-project',
|
|
95
|
+
taskName: 'empty-task',
|
|
96
|
+
logItem: JSON.stringify({})
|
|
97
|
+
}, expect.any(Object));
|
|
98
|
+
});
|
|
99
|
+
it('should handle null/undefined values in log items', async () => {
|
|
100
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
101
|
+
const logItem = { input: null, output: undefined, error: 'some error' };
|
|
102
|
+
const result = await client.sendLog('null-task', logItem);
|
|
103
|
+
expect(result).toBe('success');
|
|
104
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
|
|
105
|
+
projectName: 'test-project',
|
|
106
|
+
taskName: 'null-task',
|
|
107
|
+
logItem: JSON.stringify(logItem)
|
|
108
|
+
}, expect.any(Object));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const index_1 = require("../index");
|
|
8
|
+
// Mock axios
|
|
9
|
+
jest.mock('axios');
|
|
10
|
+
const mockedAxios = axios_1.default;
|
|
11
|
+
describe('HiveLogClient setQuality', () => {
|
|
12
|
+
const originalEnv = process.env;
|
|
13
|
+
let client;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.resetModules();
|
|
16
|
+
process.env = { ...originalEnv };
|
|
17
|
+
// Set up environment variables
|
|
18
|
+
process.env.HIVE_API_KEY = 'test-api-key';
|
|
19
|
+
process.env.HIVE_API_SECRET = 'test-api-secret';
|
|
20
|
+
process.env.HIVE_HOST = 'https://test-host.com';
|
|
21
|
+
// Create client instance
|
|
22
|
+
client = new index_1.HiveLogClient('test-project');
|
|
23
|
+
// Clear all mocks
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
afterAll(() => {
|
|
27
|
+
process.env = originalEnv;
|
|
28
|
+
});
|
|
29
|
+
describe('successful setQuality', () => {
|
|
30
|
+
it('should set quality successfully and return true', async () => {
|
|
31
|
+
// Mock successful axios response
|
|
32
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
33
|
+
const quality = {
|
|
34
|
+
score: 8.5,
|
|
35
|
+
reason: 'Good performance with minor improvements needed',
|
|
36
|
+
suggestions: 'Consider optimizing the database query for better performance'
|
|
37
|
+
};
|
|
38
|
+
const result = await client.setQuality('test-task', 'test-uuid-123', quality);
|
|
39
|
+
expect(result).toBe(true);
|
|
40
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/test-task/logs/test-uuid-123/set-quality', {
|
|
42
|
+
quality
|
|
43
|
+
}, {
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: 'Bearer test-api-key:test-api-secret',
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it('should handle perfect score quality', async () => {
|
|
51
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
52
|
+
const perfectQuality = {
|
|
53
|
+
score: 10,
|
|
54
|
+
reason: 'Excellent implementation with no issues found',
|
|
55
|
+
suggestions: 'No improvements needed, great work!'
|
|
56
|
+
};
|
|
57
|
+
const result = await client.setQuality('perfect-task', 'perfect-uuid', perfectQuality);
|
|
58
|
+
expect(result).toBe(true);
|
|
59
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/perfect-task/logs/perfect-uuid/set-quality', {
|
|
60
|
+
quality: perfectQuality
|
|
61
|
+
}, expect.any(Object));
|
|
62
|
+
});
|
|
63
|
+
it('should handle poor score quality with detailed suggestions', async () => {
|
|
64
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
65
|
+
const poorQuality = {
|
|
66
|
+
score: 2.0,
|
|
67
|
+
reason: 'Multiple issues found including performance problems and code quality issues',
|
|
68
|
+
suggestions: 'Refactor the main function, add error handling, optimize database queries, and improve variable naming conventions'
|
|
69
|
+
};
|
|
70
|
+
const result = await client.setQuality('poor-task', 'poor-uuid', poorQuality);
|
|
71
|
+
expect(result).toBe(true);
|
|
72
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/poor-task/logs/poor-uuid/set-quality', {
|
|
73
|
+
quality: poorQuality
|
|
74
|
+
}, expect.any(Object));
|
|
75
|
+
});
|
|
76
|
+
it('should handle edge case scores', async () => {
|
|
77
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
78
|
+
const edgeCaseQuality = {
|
|
79
|
+
score: 0,
|
|
80
|
+
reason: 'Critical failure in implementation',
|
|
81
|
+
suggestions: 'Complete rewrite required'
|
|
82
|
+
};
|
|
83
|
+
const result = await client.setQuality('edge-task', 'edge-uuid', edgeCaseQuality);
|
|
84
|
+
expect(result).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('failed setQuality', () => {
|
|
88
|
+
it('should return false when axios throws an error', async () => {
|
|
89
|
+
// Mock axios to throw an error
|
|
90
|
+
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
|
91
|
+
const quality = {
|
|
92
|
+
score: 7.0,
|
|
93
|
+
reason: 'Test quality',
|
|
94
|
+
suggestions: 'Test suggestions'
|
|
95
|
+
};
|
|
96
|
+
const result = await client.setQuality('test-task', 'test-uuid', quality);
|
|
97
|
+
expect(result).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it('should return false when server returns 404', async () => {
|
|
100
|
+
// Mock axios to throw a 404 error
|
|
101
|
+
const notFoundError = new Error('Request failed with status code 404');
|
|
102
|
+
mockedAxios.post.mockRejectedValueOnce(notFoundError);
|
|
103
|
+
const quality = {
|
|
104
|
+
score: 5.0,
|
|
105
|
+
reason: 'Not found test',
|
|
106
|
+
suggestions: 'Test suggestions for not found'
|
|
107
|
+
};
|
|
108
|
+
const result = await client.setQuality('non-existent-task', 'non-existent-uuid', quality);
|
|
109
|
+
expect(result).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
it('should return false when server returns 500', async () => {
|
|
112
|
+
// Mock axios to throw a server error
|
|
113
|
+
const serverError = new Error('Internal Server Error');
|
|
114
|
+
mockedAxios.post.mockRejectedValueOnce(serverError);
|
|
115
|
+
const quality = {
|
|
116
|
+
score: 6.0,
|
|
117
|
+
reason: 'Server error test',
|
|
118
|
+
suggestions: 'Test suggestions for server error'
|
|
119
|
+
};
|
|
120
|
+
const result = await client.setQuality('test-task', 'test-uuid', quality);
|
|
121
|
+
expect(result).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
it('should return false when unauthorized', async () => {
|
|
124
|
+
// Mock axios to throw an unauthorized error
|
|
125
|
+
const unauthorizedError = new Error('Request failed with status code 401');
|
|
126
|
+
mockedAxios.post.mockRejectedValueOnce(unauthorizedError);
|
|
127
|
+
const quality = {
|
|
128
|
+
score: 3.0,
|
|
129
|
+
reason: 'Unauthorized test',
|
|
130
|
+
suggestions: 'Check API credentials'
|
|
131
|
+
};
|
|
132
|
+
const result = await client.setQuality('test-task', 'test-uuid', quality);
|
|
133
|
+
expect(result).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('setQuality parameters', () => {
|
|
137
|
+
it('should handle special characters in taskName and uuid', async () => {
|
|
138
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
139
|
+
const quality = {
|
|
140
|
+
score: 4.5,
|
|
141
|
+
reason: 'Special characters test',
|
|
142
|
+
suggestions: 'Handle special characters properly'
|
|
143
|
+
};
|
|
144
|
+
const result = await client.setQuality('task-with-special-chars-!@#', 'uuid-with-special-chars-!@#', quality);
|
|
145
|
+
expect(result).toBe(true);
|
|
146
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/task-with-special-chars-!@#/logs/uuid-with-special-chars-!@#/set-quality', { quality }, expect.any(Object));
|
|
147
|
+
});
|
|
148
|
+
it('should handle decimal scores correctly', async () => {
|
|
149
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
150
|
+
const quality = {
|
|
151
|
+
score: 7.123456789,
|
|
152
|
+
reason: 'Decimal precision test',
|
|
153
|
+
suggestions: 'Maintain precision in quality scores'
|
|
154
|
+
};
|
|
155
|
+
const result = await client.setQuality('decimal-task', 'decimal-uuid', quality);
|
|
156
|
+
expect(result).toBe(true);
|
|
157
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/decimal-task/logs/decimal-uuid/set-quality', { quality }, expect.any(Object));
|
|
158
|
+
});
|
|
159
|
+
it('should handle long reason and suggestions', async () => {
|
|
160
|
+
mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
|
|
161
|
+
const longReason = 'This is a very long reason that explains in detail what went wrong with the implementation and why the score is what it is. '.repeat(5);
|
|
162
|
+
const longSuggestions = 'Here are detailed suggestions for improvement: 1. Refactor the main function, 2. Add comprehensive error handling, 3. Optimize database queries, 4. Improve code documentation, 5. Add unit tests. '.repeat(3);
|
|
163
|
+
const quality = {
|
|
164
|
+
score: 3.5,
|
|
165
|
+
reason: longReason,
|
|
166
|
+
suggestions: longSuggestions
|
|
167
|
+
};
|
|
168
|
+
const result = await client.setQuality('long-text-task', 'long-text-uuid', quality);
|
|
169
|
+
expect(result).toBe(true);
|
|
170
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/long-text-task/logs/long-text-uuid/set-quality', { quality }, expect.any(Object));
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('silent mode behavior', () => {
|
|
174
|
+
it('should throw error when credentials are missing', async () => {
|
|
175
|
+
// Create client without credentials
|
|
176
|
+
process.env.HIVE_API_KEY = '';
|
|
177
|
+
process.env.HIVE_API_SECRET = '';
|
|
178
|
+
process.env.HIVE_HOST = '';
|
|
179
|
+
const silentClient = new index_1.HiveLogClient('silent-project');
|
|
180
|
+
const quality = {
|
|
181
|
+
score: 8.0,
|
|
182
|
+
reason: 'Test quality for silent mode',
|
|
183
|
+
suggestions: 'This should not be processed'
|
|
184
|
+
};
|
|
185
|
+
await expect(silentClient.setQuality('test-task', 'test-uuid', quality))
|
|
186
|
+
.rejects
|
|
187
|
+
.toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
|
|
188
|
+
// Verify axios was never called
|
|
189
|
+
expect(mockedAxios.post).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src'],
|
|
6
|
+
testMatch: ['**/src/test/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.ts$': 'ts-jest'
|
|
9
|
+
},
|
|
10
|
+
moduleFileExtensions: ['ts', 'js', 'json', 'node']
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgehive/hive-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/jest": "^29.5.14",
|
|
9
|
+
"@types/node": "^24.0.3",
|
|
10
|
+
"jest": "^29.7.0",
|
|
11
|
+
"ts-jest": "^29.1.2"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"axios": "^1.8.4",
|
|
18
|
+
"debug": "^4.4.1"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"test": "jest",
|
|
23
|
+
"test:watch": "jest --watch"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import debug from 'debug'
|
|
3
|
+
|
|
4
|
+
const log = debug('hive-sdk')
|
|
5
|
+
|
|
6
|
+
// API Response Types
|
|
7
|
+
export interface LogApiResponse {
|
|
8
|
+
uuid: string
|
|
9
|
+
taskName: string
|
|
10
|
+
projectName: string
|
|
11
|
+
logItem: {
|
|
12
|
+
input: unknown
|
|
13
|
+
output?: unknown
|
|
14
|
+
error?: unknown
|
|
15
|
+
boundaries?: Record<string, Array<{ input: unknown; output: unknown, error: unknown }>>
|
|
16
|
+
}
|
|
17
|
+
replayFrom?: string
|
|
18
|
+
createdAt: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ApiError {
|
|
22
|
+
error: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LogApiSuccess extends LogApiResponse {}
|
|
26
|
+
|
|
27
|
+
export type LogApiResult = LogApiSuccess | ApiError
|
|
28
|
+
|
|
29
|
+
// Quality interface for setQuality method
|
|
30
|
+
export interface Quality {
|
|
31
|
+
score: number
|
|
32
|
+
reason: string
|
|
33
|
+
suggestions: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Type guard to check if response is an error
|
|
37
|
+
export function isApiError(response: unknown): response is ApiError {
|
|
38
|
+
return response !== null && typeof response === 'object' && 'error' in response
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class HiveLogClient {
|
|
42
|
+
private apiKey: string | null
|
|
43
|
+
private apiSecret: string | null
|
|
44
|
+
private host: string | null
|
|
45
|
+
private projectName: string
|
|
46
|
+
private isInitialized: boolean
|
|
47
|
+
|
|
48
|
+
constructor(projectName: string) {
|
|
49
|
+
const apiKey = process.env.HIVE_API_KEY
|
|
50
|
+
const apiSecret = process.env.HIVE_API_SECRET
|
|
51
|
+
const host = process.env.HIVE_HOST
|
|
52
|
+
|
|
53
|
+
this.projectName = projectName
|
|
54
|
+
|
|
55
|
+
if (!apiKey || !apiSecret || !host) {
|
|
56
|
+
this.apiKey = null
|
|
57
|
+
this.apiSecret = null
|
|
58
|
+
this.host = null
|
|
59
|
+
this.isInitialized = false
|
|
60
|
+
log('HiveLogClient in silent mode for project "%s" - missing credentials (get them at https://forgehive.dev)', projectName)
|
|
61
|
+
} else {
|
|
62
|
+
this.apiKey = apiKey
|
|
63
|
+
this.apiSecret = apiSecret
|
|
64
|
+
this.host = host
|
|
65
|
+
this.isInitialized = true
|
|
66
|
+
log('HiveLogClient initialized for project "%s" with host "%s"', projectName, host)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isActive(): boolean {
|
|
71
|
+
return this.isInitialized
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async sendLog(taskName: string, logItem: unknown): Promise<'success' | 'error' | 'silent'> {
|
|
75
|
+
if (!this.isInitialized) {
|
|
76
|
+
log('Silent mode: Skipping sendLog for task "%s" - client not initialized', taskName)
|
|
77
|
+
return 'silent'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const logsUrl = `${this.host}/api/tasks/log-ingest`
|
|
82
|
+
log('Sending log for task "%s" to %s', taskName, logsUrl)
|
|
83
|
+
|
|
84
|
+
const authToken = `${this.apiKey}:${this.apiSecret}`
|
|
85
|
+
|
|
86
|
+
await axios.post(logsUrl, {
|
|
87
|
+
projectName: this.projectName,
|
|
88
|
+
taskName,
|
|
89
|
+
logItem: JSON.stringify(logItem)
|
|
90
|
+
}, {
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${authToken}`,
|
|
93
|
+
'Content-Type': 'application/json'
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
log('Success: Sent log for task "%s"', taskName)
|
|
98
|
+
return 'success'
|
|
99
|
+
} catch (e) {
|
|
100
|
+
const error = e as Error
|
|
101
|
+
log('Error: Failed to send log for task "%s": %s', taskName, error.message)
|
|
102
|
+
return 'error'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getLog(taskName: string, uuid: string): Promise<LogApiResult | null> {
|
|
107
|
+
if (!this.isInitialized) {
|
|
108
|
+
log('Error: getLog for task "%s" with uuid "%s" - missing credentials', taskName, uuid)
|
|
109
|
+
throw new Error('Missing Hive API credentials or host, get them at https://forgehive.dev')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const logUrl = `${this.host}/api/tasks/${taskName}/logs/${uuid}`
|
|
114
|
+
log('Fetching log for task "%s" with uuid "%s" from %s', taskName, uuid, logUrl)
|
|
115
|
+
|
|
116
|
+
const authToken = `${this.apiKey}:${this.apiSecret}`
|
|
117
|
+
|
|
118
|
+
const response = await axios.get(logUrl, {
|
|
119
|
+
headers: {
|
|
120
|
+
Authorization: `Bearer ${authToken}`,
|
|
121
|
+
'Content-Type': 'application/json'
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
log('Success: Fetched log for task "%s" with uuid "%s"', taskName, uuid)
|
|
126
|
+
return response.data as LogApiResult
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const error = e as Error
|
|
129
|
+
log('Error: Failed to fetch log for task "%s" with uuid "%s": %s', taskName, uuid, error.message)
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async setQuality(taskName: string, uuid: string, quality: Quality): Promise<boolean> {
|
|
135
|
+
if (!this.isInitialized) {
|
|
136
|
+
log('Error: setQuality for task "%s" with uuid "%s" - missing credentials', taskName, uuid)
|
|
137
|
+
throw new Error('Missing Hive API credentials or host, get them at https://forgehive.dev')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const qualityUrl = `${this.host}/api/tasks/${taskName}/logs/${uuid}/set-quality`
|
|
142
|
+
log('Setting quality for task "%s" with uuid "%s" (score: %d) to %s', taskName, uuid, quality.score, qualityUrl)
|
|
143
|
+
|
|
144
|
+
const authToken = `${this.apiKey}:${this.apiSecret}`
|
|
145
|
+
|
|
146
|
+
await axios.post(qualityUrl, {
|
|
147
|
+
quality
|
|
148
|
+
}, {
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${authToken}`,
|
|
151
|
+
'Content-Type': 'application/json'
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
log('Success: Set quality for task "%s" with uuid "%s" (score: %d)', taskName, uuid, quality.score)
|
|
156
|
+
return true
|
|
157
|
+
} catch (e) {
|
|
158
|
+
const error = e as Error
|
|
159
|
+
log('Error: Failed to set quality for task "%s" with uuid "%s": %s', taskName, uuid, error.message)
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const createHiveLogClient = (projectName: string): HiveLogClient => {
|
|
166
|
+
log('Creating HiveLogClient for project "%s"', projectName)
|
|
167
|
+
return new HiveLogClient(projectName)
|
|
168
|
+
}
|