@forgehive/hive-sdk 0.0.3 → 0.1.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/dist/index.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  export interface Metadata {
2
2
  [key: string]: string;
3
3
  }
4
- export interface LogItemInput {
5
- input: unknown;
6
- output?: unknown;
7
- error?: unknown;
8
- boundaries?: unknown;
4
+ export interface ExecutionRecord<InputType = unknown, OutputType = unknown, B = unknown> {
5
+ input: InputType;
6
+ output?: OutputType;
7
+ error?: string;
8
+ boundaries?: B;
9
+ taskName?: string;
9
10
  metadata?: Metadata;
11
+ type?: 'success' | 'error' | 'pending';
10
12
  }
11
- export type LogItem = LogItemInput;
12
13
  export interface HiveLogClientConfig {
13
14
  projectName: string;
14
15
  apiKey?: string;
@@ -20,17 +21,7 @@ export interface LogApiResponse {
20
21
  uuid: string;
21
22
  taskName: string;
22
23
  projectName: string;
23
- logItem: {
24
- input: unknown;
25
- output?: unknown;
26
- error?: unknown;
27
- boundaries?: Record<string, Array<{
28
- input: unknown;
29
- output: unknown;
30
- error: unknown;
31
- }>>;
32
- metadata?: Metadata;
33
- };
24
+ logItem: ExecutionRecord;
34
25
  replayFrom?: string;
35
26
  createdAt: string;
36
27
  }
@@ -56,11 +47,32 @@ export declare class HiveLogClient {
56
47
  constructor(config: HiveLogClientConfig);
57
48
  isActive(): boolean;
58
49
  private mergeMetadata;
59
- sendLog<T extends {
60
- input: unknown;
61
- metadata?: Metadata;
62
- }>(taskName: string, logItem: T, metadata?: Metadata): Promise<'success' | 'error' | 'silent'>;
50
+ sendLog(record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent'>;
51
+ getListener(): (record: ExecutionRecord) => Promise<void>;
63
52
  getLog(taskName: string, uuid: string): Promise<LogApiResult | null>;
64
53
  setQuality(taskName: string, uuid: string, quality: Quality): Promise<boolean>;
65
54
  }
66
55
  export declare const createHiveLogClient: (config: HiveLogClientConfig) => HiveLogClient;
56
+ export interface HiveClientConfig {
57
+ projectUuid: string;
58
+ apiKey?: string;
59
+ apiSecret?: string;
60
+ host?: string;
61
+ }
62
+ export interface InvokeResponse {
63
+ responsePayload: unknown;
64
+ }
65
+ export interface InvokeError {
66
+ error: string;
67
+ }
68
+ export type InvokeResult = InvokeResponse | InvokeError;
69
+ export declare function isInvokeError(response: unknown): response is InvokeError;
70
+ export declare class HiveClient {
71
+ private apiKey;
72
+ private apiSecret;
73
+ private host;
74
+ private projectUuid;
75
+ constructor(config: HiveClientConfig);
76
+ invoke(taskName: string, payload: unknown): Promise<InvokeResult | null>;
77
+ }
78
+ export declare const createHiveClient: (config: HiveClientConfig) => HiveClient;
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createHiveLogClient = exports.HiveLogClient = void 0;
6
+ exports.createHiveClient = exports.HiveClient = exports.createHiveLogClient = exports.HiveLogClient = void 0;
7
7
  exports.isApiError = isApiError;
8
+ exports.isInvokeError = isInvokeError;
8
9
  const axios_1 = __importDefault(require("axios"));
9
10
  const debug_1 = __importDefault(require("debug"));
10
11
  const log = (0, debug_1.default)('hive-sdk');
@@ -37,12 +38,12 @@ class HiveLogClient {
37
38
  isActive() {
38
39
  return this.isInitialized;
39
40
  }
40
- mergeMetadata(logItem, sendLogMetadata) {
41
+ mergeMetadata(record, sendLogMetadata) {
41
42
  // Start with base metadata from client
42
43
  let finalMetadata = { ...this.baseMetadata };
43
- // Merge with logItem metadata if it exists
44
- if (logItem.metadata) {
45
- finalMetadata = { ...finalMetadata, ...logItem.metadata };
44
+ // Merge with record metadata if it exists
45
+ if (record.metadata) {
46
+ finalMetadata = { ...finalMetadata, ...record.metadata };
46
47
  }
47
48
  // Merge with sendLog metadata (highest priority)
48
49
  if (sendLogMetadata) {
@@ -50,7 +51,9 @@ class HiveLogClient {
50
51
  }
51
52
  return finalMetadata;
52
53
  }
53
- async sendLog(taskName, logItem, metadata) {
54
+ async sendLog(record, metadata) {
55
+ // Extract taskName from record
56
+ const taskName = record.taskName || 'unknown-task';
54
57
  if (!this.isInitialized) {
55
58
  log('Silent mode: Skipping sendLog for task "%s" - client not initialized', taskName);
56
59
  return 'silent';
@@ -59,17 +62,18 @@ class HiveLogClient {
59
62
  const logsUrl = `${this.host}/api/tasks/log-ingest`;
60
63
  log('Sending log for task "%s" to %s', taskName, logsUrl);
61
64
  const authToken = `${this.apiKey}:${this.apiSecret}`;
62
- // Merge metadata with priority: sendLog > logItem > client
63
- const finalMetadata = this.mergeMetadata(logItem, metadata);
64
- // Create enhanced logItem with merged metadata
65
- const enhancedLogItem = {
66
- ...logItem,
65
+ // Merge metadata with priority: sendLog > record.metadata > client
66
+ const finalMetadata = this.mergeMetadata(record, metadata);
67
+ // Create logItem with merged metadata
68
+ const logItem = {
69
+ ...record,
70
+ taskName,
67
71
  metadata: finalMetadata
68
72
  };
69
73
  await axios_1.default.post(logsUrl, {
70
74
  projectName: this.projectName,
71
75
  taskName,
72
- logItem: JSON.stringify(enhancedLogItem)
76
+ logItem: JSON.stringify(logItem)
73
77
  }, {
74
78
  headers: {
75
79
  Authorization: `Bearer ${authToken}`,
@@ -85,6 +89,11 @@ class HiveLogClient {
85
89
  return 'error';
86
90
  }
87
91
  }
92
+ getListener() {
93
+ return async (record) => {
94
+ await this.sendLog(record);
95
+ };
96
+ }
88
97
  async getLog(taskName, uuid) {
89
98
  if (!this.isInitialized) {
90
99
  log('Error: getLog for task "%s" with uuid "%s" - missing credentials', taskName, uuid);
@@ -142,3 +151,54 @@ const createHiveLogClient = (config) => {
142
151
  return new HiveLogClient(config);
143
152
  };
144
153
  exports.createHiveLogClient = createHiveLogClient;
154
+ // Type guard to check if invoke response is an error
155
+ function isInvokeError(response) {
156
+ return response !== null && typeof response === 'object' && 'error' in response;
157
+ }
158
+ class HiveClient {
159
+ constructor(config) {
160
+ const apiKey = config.apiKey || process.env.HIVE_API_KEY;
161
+ const apiSecret = config.apiSecret || process.env.HIVE_API_SECRET;
162
+ const host = config.host || process.env.HIVE_HOST || 'https://forgehive.dev';
163
+ if (!apiKey || !apiSecret) {
164
+ throw new Error('Missing Hive API credentials. Please provide apiKey and apiSecret, or set HIVE_API_KEY and HIVE_API_SECRET environment variables. Get them at https://forgehive.dev');
165
+ }
166
+ this.projectUuid = config.projectUuid;
167
+ this.host = host;
168
+ this.apiKey = apiKey;
169
+ this.apiSecret = apiSecret;
170
+ log('HiveClient initialized for project "%s" with host "%s"', config.projectUuid, host);
171
+ }
172
+ async invoke(taskName, payload) {
173
+ try {
174
+ const invokeUrl = `${this.host}/api/project/${this.projectUuid}/task/${taskName}/invoke`;
175
+ log('Invoking task "%s" at %s', taskName, invokeUrl);
176
+ const authToken = `${this.apiKey}:${this.apiSecret}`;
177
+ const response = await axios_1.default.post(invokeUrl, {
178
+ payload
179
+ }, {
180
+ headers: {
181
+ Authorization: `Bearer ${authToken}`,
182
+ 'Content-Type': 'application/json'
183
+ }
184
+ });
185
+ log('Success: Invoked task "%s"', taskName);
186
+ return response.data;
187
+ }
188
+ catch (e) {
189
+ const error = e;
190
+ log('Error: Failed to invoke task "%s": %s', taskName, error.message);
191
+ // Check if it's an axios error with response data
192
+ if (axios_1.default.isAxiosError(error) && error.response?.data) {
193
+ return error.response.data;
194
+ }
195
+ return { error: error.message };
196
+ }
197
+ }
198
+ }
199
+ exports.HiveClient = HiveClient;
200
+ const createHiveClient = (config) => {
201
+ log('Creating HiveClient for project "%s"', config.projectUuid);
202
+ return new HiveClient(config);
203
+ };
204
+ exports.createHiveClient = createHiveClient;
@@ -31,7 +31,7 @@ describe('HiveLogClient getLog', () => {
31
31
  logItem: {
32
32
  input: { userId: 123, action: 'login' },
33
33
  output: { success: true, sessionId: 'abc123' },
34
- error: null,
34
+ error: undefined,
35
35
  boundaries: {
36
36
  database: [{ input: 'SELECT * FROM users', output: [{ id: 123 }], error: null }]
37
37
  }
@@ -82,7 +82,14 @@ describe('Hive SDK', () => {
82
82
  silentClient = new index_1.HiveLogClient({ projectName: 'silent-project' });
83
83
  });
84
84
  it('should return "silent" for sendLog in silent mode', async () => {
85
- const result = await silentClient.sendLog('test-task', { input: 'test' });
85
+ const executionRecord = {
86
+ input: { test: 'value' },
87
+ taskName: 'test-task',
88
+ type: 'success',
89
+ boundaries: {},
90
+ metadata: {}
91
+ };
92
+ const result = await silentClient.sendLog(executionRecord);
86
93
  expect(result).toBe('silent');
87
94
  });
88
95
  it('should throw error for getLog in silent mode', async () => {
@@ -67,8 +67,15 @@ describe('HiveLogClient Metadata', () => {
67
67
  });
68
68
  it('should send log without metadata parameter', async () => {
69
69
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
70
- const logItem = { input: 'test-input', output: 'test-output' };
71
- const result = await client.sendLog('test-task', logItem);
70
+ const executionRecord = {
71
+ input: 'test-input',
72
+ output: 'test-output',
73
+ taskName: 'test-task',
74
+ type: 'success',
75
+ boundaries: {},
76
+ metadata: {}
77
+ };
78
+ const result = await client.sendLog(executionRecord);
72
79
  expect(result).toBe('success');
73
80
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
74
81
  projectName: 'test-project',
@@ -76,6 +83,9 @@ describe('HiveLogClient Metadata', () => {
76
83
  logItem: JSON.stringify({
77
84
  input: 'test-input',
78
85
  output: 'test-output',
86
+ taskName: 'test-task',
87
+ type: 'success',
88
+ boundaries: {},
79
89
  metadata: {}
80
90
  })
81
91
  }, {
@@ -87,12 +97,19 @@ describe('HiveLogClient Metadata', () => {
87
97
  });
88
98
  it('should send log with metadata parameter', async () => {
89
99
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
90
- const logItem = { input: 'test-input', output: 'test-output' };
100
+ const executionRecord = {
101
+ input: 'test-input',
102
+ output: 'test-output',
103
+ taskName: 'test-task',
104
+ type: 'success',
105
+ boundaries: {},
106
+ metadata: {}
107
+ };
91
108
  const metadata = {
92
109
  requestId: 'req-123',
93
110
  userId: 'user-456'
94
111
  };
95
- const result = await client.sendLog('test-task', logItem, metadata);
112
+ const result = await client.sendLog(executionRecord, metadata);
96
113
  expect(result).toBe('success');
97
114
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
98
115
  projectName: 'test-project',
@@ -100,6 +117,9 @@ describe('HiveLogClient Metadata', () => {
100
117
  logItem: JSON.stringify({
101
118
  input: 'test-input',
102
119
  output: 'test-output',
120
+ taskName: 'test-task',
121
+ type: 'success',
122
+ boundaries: {},
103
123
  metadata: {
104
124
  requestId: 'req-123',
105
125
  userId: 'user-456'
@@ -115,13 +135,16 @@ describe('HiveLogClient Metadata', () => {
115
135
  it('should handle logItem with only input property', async () => {
116
136
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
117
137
  const metadata = { type: 'minimal' };
118
- const result = await client.sendLog('test-task', { input: 'simple input' }, metadata);
138
+ const result = await client.sendLog({ input: 'simple input', taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} }, metadata);
119
139
  expect(result).toBe('success');
120
140
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
121
141
  projectName: 'test-project',
122
142
  taskName: 'test-task',
123
143
  logItem: JSON.stringify({
124
144
  input: 'simple input',
145
+ taskName: 'test-task',
146
+ type: 'success',
147
+ boundaries: {},
125
148
  metadata: { type: 'minimal' }
126
149
  })
127
150
  }, expect.any(Object));
@@ -129,13 +152,16 @@ describe('HiveLogClient Metadata', () => {
129
152
  it('should handle logItem with null input values', async () => {
130
153
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
131
154
  const metadata = { type: 'null-test' };
132
- const result = await client.sendLog('test-task', { input: null }, metadata);
155
+ const result = await client.sendLog({ input: null, taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} }, metadata);
133
156
  expect(result).toBe('success');
134
157
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
135
158
  projectName: 'test-project',
136
159
  taskName: 'test-task',
137
160
  logItem: JSON.stringify({
138
161
  input: null,
162
+ taskName: 'test-task',
163
+ type: 'success',
164
+ boundaries: {},
139
165
  metadata: { type: 'null-test' }
140
166
  })
141
167
  }, expect.any(Object));
@@ -150,9 +176,12 @@ describe('HiveLogClient Metadata', () => {
150
176
  };
151
177
  const client = new index_1.HiveLogClient({ ...testConfig, metadata: baseMetadata });
152
178
  const logItem = { input: 'test' };
153
- await client.sendLog('test-task', logItem);
179
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} });
154
180
  const expectedLogItem = {
155
181
  input: 'test',
182
+ taskName: 'test-task',
183
+ type: 'success',
184
+ boundaries: {},
156
185
  metadata: {
157
186
  environment: 'production',
158
187
  version: '1.0.0'
@@ -178,14 +207,17 @@ describe('HiveLogClient Metadata', () => {
178
207
  version: '1.1.0' // This should override base version
179
208
  }
180
209
  };
181
- await client.sendLog('test-task', logItem);
210
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: logItem.metadata || {} });
182
211
  const expectedLogItem = {
183
212
  input: 'test',
184
213
  metadata: {
185
214
  environment: 'production', // from base
186
215
  version: '1.1.0', // from logItem (overrides base)
187
216
  sessionId: 'session-123' // from logItem
188
- }
217
+ },
218
+ taskName: 'test-task',
219
+ type: 'success',
220
+ boundaries: {}
189
221
  };
190
222
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
191
223
  projectName: 'test-project',
@@ -214,7 +246,7 @@ describe('HiveLogClient Metadata', () => {
214
246
  version: '1.2.0',
215
247
  priority: 'sendLog'
216
248
  };
217
- await client.sendLog('test-task', logItem, sendLogMetadata);
249
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: logItem.metadata || {} }, sendLogMetadata);
218
250
  const expectedLogItem = {
219
251
  input: 'test',
220
252
  metadata: {
@@ -223,7 +255,10 @@ describe('HiveLogClient Metadata', () => {
223
255
  priority: 'sendLog', // from sendLog (highest priority)
224
256
  sessionId: 'session-123', // from logItem
225
257
  requestId: 'req-456' // from sendLog
226
- }
258
+ },
259
+ taskName: 'test-task',
260
+ type: 'success',
261
+ boundaries: {}
227
262
  };
228
263
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
229
264
  projectName: 'test-project',
@@ -254,7 +289,7 @@ describe('HiveLogClient Metadata', () => {
254
289
  userId: 'user-123',
255
290
  version: '1.2.0' // overrides both base and logItem
256
291
  };
257
- await client.sendLog('search-task', logItem, sendLogMetadata);
292
+ await client.sendLog({ ...logItem, taskName: 'search-task', type: 'success', boundaries: {}, metadata: logItem.metadata || {} }, sendLogMetadata);
258
293
  const expectedLogItem = {
259
294
  input: { query: 'search' },
260
295
  output: { results: [] },
@@ -267,7 +302,10 @@ describe('HiveLogClient Metadata', () => {
267
302
  processingTime: '250', // from logItem
268
303
  requestId: 'req-789', // from sendLog
269
304
  userId: 'user-123' // from sendLog
270
- }
305
+ },
306
+ taskName: 'search-task',
307
+ type: 'success',
308
+ boundaries: {}
271
309
  };
272
310
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
273
311
  projectName: 'test-project',
@@ -285,9 +323,12 @@ describe('HiveLogClient Metadata', () => {
285
323
  input: 'test'
286
324
  // No metadata property
287
325
  };
288
- await client.sendLog('test-task', logItem);
326
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} });
289
327
  const expectedLogItem = {
290
328
  input: 'test',
329
+ taskName: 'test-task',
330
+ type: 'success',
331
+ boundaries: {},
291
332
  metadata: {
292
333
  environment: 'test' // Only base metadata should be used
293
334
  }
@@ -306,12 +347,15 @@ describe('HiveLogClient Metadata', () => {
306
347
  input: 'test',
307
348
  metadata: undefined
308
349
  };
309
- await client.sendLog('test-task', logItem);
350
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: logItem.metadata || {} });
310
351
  const expectedLogItem = {
311
352
  input: 'test',
312
353
  metadata: {
313
354
  environment: 'test' // Only base metadata should be used
314
- }
355
+ },
356
+ taskName: 'test-task',
357
+ type: 'success',
358
+ boundaries: {}
315
359
  };
316
360
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
317
361
  projectName: 'test-project',
@@ -323,10 +367,13 @@ describe('HiveLogClient Metadata', () => {
323
367
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
324
368
  const client = new index_1.HiveLogClient({ ...testConfig, metadata: {} });
325
369
  const logItem = { input: 'test', metadata: {} };
326
- await client.sendLog('test-task', logItem, {});
370
+ await client.sendLog({ ...logItem, taskName: 'test-task', type: 'success', boundaries: {}, metadata: logItem.metadata || {} }, {});
327
371
  const expectedLogItem = {
328
372
  input: 'test',
329
- metadata: {} // All empty metadata objects result in empty final metadata
373
+ metadata: {}, // All empty metadata objects result in empty final metadata
374
+ taskName: 'test-task',
375
+ type: 'success',
376
+ boundaries: {}
330
377
  };
331
378
  expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
332
379
  projectName: 'test-project',
@@ -337,7 +384,7 @@ describe('HiveLogClient Metadata', () => {
337
384
  it('should work in silent mode with metadata', async () => {
338
385
  const baseMetadata = { environment: 'test' };
339
386
  const silentClient = new index_1.HiveLogClient({ projectName: 'silent-project', metadata: baseMetadata });
340
- const result = await silentClient.sendLog('test-task', { input: 'test' }, { requestId: 'req-123' });
387
+ const result = await silentClient.sendLog({ input: 'test', taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} }, { requestId: 'req-123' });
341
388
  expect(result).toBe('silent');
342
389
  expect(mockedAxios.post).not.toHaveBeenCalled();
343
390
  });
@@ -345,7 +392,7 @@ describe('HiveLogClient Metadata', () => {
345
392
  mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
346
393
  const baseMetadata = { environment: 'test' };
347
394
  const client = new index_1.HiveLogClient({ ...testConfig, metadata: baseMetadata });
348
- const result = await client.sendLog('test-task', { input: 'test' }, { requestId: 'req-123' });
395
+ const result = await client.sendLog({ input: 'test', taskName: 'test-task', type: 'success', boundaries: {}, metadata: {} }, { requestId: 'req-123' });
349
396
  expect(result).toBe('error');
350
397
  });
351
398
  });