@unito/integration-sdk 5.0.0 → 5.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.
Files changed (37) hide show
  1. package/dist/src/handler.js +11 -0
  2. package/dist/src/index.cjs +114 -5
  3. package/dist/src/index.d.ts +2 -0
  4. package/dist/src/index.js +2 -0
  5. package/dist/src/integration.d.ts +1 -0
  6. package/dist/src/integration.js +2 -0
  7. package/dist/src/middlewares/finish.d.ts +0 -1
  8. package/dist/src/middlewares/finish.js +4 -1
  9. package/dist/src/middlewares/start.d.ts +2 -1
  10. package/dist/src/middlewares/start.js +2 -1
  11. package/dist/src/resources/context.d.ts +6 -0
  12. package/dist/src/resources/provider.d.ts +3 -0
  13. package/dist/src/resources/provider.js +15 -3
  14. package/dist/src/resources/requestMetrics.d.ts +24 -0
  15. package/dist/src/resources/requestMetrics.js +33 -0
  16. package/dist/src/resources/tracer.d.ts +16 -0
  17. package/dist/src/resources/tracer.js +45 -0
  18. package/dist/test/middlewares/finish.test.js +5 -4
  19. package/dist/test/middlewares/start.test.js +13 -2
  20. package/dist/test/resources/provider.test.js +95 -0
  21. package/dist/test/resources/requestMetrics.test.d.ts +1 -0
  22. package/dist/test/resources/requestMetrics.test.js +45 -0
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +2 -1
  25. package/src/handler.ts +11 -0
  26. package/src/index.ts +2 -0
  27. package/src/integration.ts +3 -0
  28. package/src/middlewares/finish.ts +4 -2
  29. package/src/middlewares/start.ts +3 -2
  30. package/src/resources/context.ts +6 -0
  31. package/src/resources/provider.ts +20 -3
  32. package/src/resources/requestMetrics.ts +37 -0
  33. package/src/resources/tracer.ts +50 -0
  34. package/test/middlewares/finish.test.ts +13 -12
  35. package/test/middlewares/start.test.ts +13 -2
  36. package/test/resources/provider.test.ts +126 -0
  37. package/test/resources/requestMetrics.test.ts +50 -0
@@ -2,6 +2,7 @@ import express from 'express';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import onFinish from '../../src/middlewares/finish.js';
5
+ import { RequestMetrics } from '../../src/resources/requestMetrics.js';
5
6
 
6
7
  describe('finish middleware', () => {
7
8
  it('logs info', () => {
@@ -10,11 +11,11 @@ describe('finish middleware', () => {
10
11
 
11
12
  const request = { originalUrl: '/' } as express.Request;
12
13
  const response = {
13
- on: (_event, func: () => void) => {
14
+ on: (_event: string, func: () => void) => {
14
15
  eventHandler = func;
15
16
  },
16
17
  locals: {
17
- requestStartTime: 0n,
18
+ requestMetrics: RequestMetrics.startRequest(),
18
19
  logger: {
19
20
  info: (_message: string) => {
20
21
  expected = 'works!';
@@ -22,7 +23,7 @@ describe('finish middleware', () => {
22
23
  },
23
24
  },
24
25
  statusCode: 200,
25
- } as express.Response;
26
+ } as unknown as express.Response;
26
27
 
27
28
  onFinish(request, response, () => {});
28
29
  eventHandler();
@@ -36,11 +37,11 @@ describe('finish middleware', () => {
36
37
 
37
38
  const request = { originalUrl: '/' } as express.Request;
38
39
  const response = {
39
- on: (_event, func: () => void) => {
40
+ on: (_event: string, func: () => void) => {
40
41
  eventHandler = func;
41
42
  },
42
43
  locals: {
43
- requestStartTime: 0n,
44
+ requestMetrics: RequestMetrics.startRequest(),
44
45
  logger: {
45
46
  error: (_message: string) => {
46
47
  expected = 'works!';
@@ -48,7 +49,7 @@ describe('finish middleware', () => {
48
49
  },
49
50
  },
50
51
  statusCode: 500,
51
- } as express.Response;
52
+ } as unknown as express.Response;
52
53
 
53
54
  onFinish(request, response, () => {});
54
55
  eventHandler();
@@ -62,11 +63,11 @@ describe('finish middleware', () => {
62
63
 
63
64
  const request = { originalUrl: '/health' } as express.Request;
64
65
  const response = {
65
- on: (_event, func: () => void) => {
66
+ on: (_event: string, func: () => void) => {
66
67
  eventHandler = func;
67
68
  },
68
69
  locals: {
69
- requestStartTime: 0n,
70
+ requestMetrics: RequestMetrics.startRequest(),
70
71
  logger: {
71
72
  info: (_message: string) => {
72
73
  expected = 'ohoh!';
@@ -74,7 +75,7 @@ describe('finish middleware', () => {
74
75
  },
75
76
  },
76
77
  statusCode: 200,
77
- } as express.Response;
78
+ } as unknown as express.Response;
78
79
 
79
80
  onFinish(request, response, () => {});
80
81
  eventHandler();
@@ -88,11 +89,11 @@ describe('finish middleware', () => {
88
89
 
89
90
  const request = { originalUrl: '/health' } as express.Request;
90
91
  const response = {
91
- on: (_event, func: () => void) => {
92
+ on: (_event: string, func: () => void) => {
92
93
  eventHandler = func;
93
94
  },
94
95
  locals: {
95
- requestStartTime: 0n,
96
+ requestMetrics: RequestMetrics.startRequest(),
96
97
  logger: {
97
98
  error: (_message: string) => {
98
99
  expected = 'works!';
@@ -100,7 +101,7 @@ describe('finish middleware', () => {
100
101
  },
101
102
  },
102
103
  statusCode: 500,
103
- } as express.Response;
104
+ } as unknown as express.Response;
104
105
 
105
106
  onFinish(request, response, () => {});
106
107
  eventHandler();
@@ -2,13 +2,24 @@ import express from 'express';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import start from '../../src/middlewares/start.js';
5
+ import { RequestMetrics } from '../../src/resources/requestMetrics.js';
5
6
 
6
7
  describe('start middleware', () => {
7
- it('adds request start time to locals', () => {
8
+ it('initializes request metrics on locals', () => {
8
9
  const request = {} as express.Request;
9
10
  const response = { locals: {} } as express.Response;
11
+ start(request, response, () => {});
12
+ assert.ok(response.locals.requestMetrics instanceof RequestMetrics);
13
+ });
10
14
 
15
+ it('records request start time in metrics', () => {
16
+ const request = {} as express.Request;
17
+ const response = { locals: {} } as express.Response;
18
+ const before = process.hrtime.bigint();
11
19
  start(request, response, () => {});
12
- assert.ok(response.locals.requestStartTime);
20
+ const result = response.locals.requestMetrics.endRequest();
21
+ const after = process.hrtime.bigint();
22
+ assert.ok(result.durationNs >= 0n);
23
+ assert.ok(result.durationNs <= after - before);
13
24
  });
14
25
  });
@@ -8,6 +8,7 @@ import https from 'https';
8
8
  import { Provider } from '../../src/resources/provider.js';
9
9
  import * as HttpErrors from '../../src/httpErrors.js';
10
10
  import Logger from '../../src/resources/logger.js';
11
+ import { RequestMetrics } from '../../src/resources/requestMetrics.js';
11
12
 
12
13
  // There is currently an issue with node 20.12 and fetch mocking. A quick fix is to first call fetch so it's getter
13
14
  // get properly instantiated, which allow it to be mocked properly.
@@ -1586,4 +1587,129 @@ describe('Provider', () => {
1586
1587
  }
1587
1588
  });
1588
1589
  });
1590
+
1591
+ describe('request metrics', () => {
1592
+ it('records api call duration in requestMetrics via fetchWrapper', async context => {
1593
+ const metrics = RequestMetrics.startRequest();
1594
+ const response = new Response('{"data": "value"}', {
1595
+ status: 200,
1596
+ headers: new Headers({ 'Content-Type': 'application/json' }),
1597
+ });
1598
+
1599
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
1600
+
1601
+ await provider.get('/endpoint', {
1602
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1603
+ logger,
1604
+ requestMetrics: metrics,
1605
+ });
1606
+
1607
+ const result = metrics.endRequest();
1608
+ assert.equal(result.apiCallCount, 1);
1609
+ assert.ok(result.totalApiDurationNs >= 0, 'Duration should be recorded');
1610
+ });
1611
+
1612
+ it('records multiple api calls in the same requestMetrics', async context => {
1613
+ const metrics = RequestMetrics.startRequest();
1614
+
1615
+ context.mock.method(global, 'fetch', () =>
1616
+ Promise.resolve(
1617
+ new Response('{"data": "value"}', {
1618
+ status: 200,
1619
+ headers: new Headers({ 'Content-Type': 'application/json' }),
1620
+ }),
1621
+ ),
1622
+ );
1623
+
1624
+ await provider.get('/endpoint1', {
1625
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1626
+ logger,
1627
+ requestMetrics: metrics,
1628
+ });
1629
+
1630
+ await provider.post(
1631
+ '/endpoint2',
1632
+ { key: 'value' },
1633
+ {
1634
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1635
+ logger,
1636
+ requestMetrics: metrics,
1637
+ },
1638
+ );
1639
+
1640
+ const result = metrics.endRequest();
1641
+ assert.equal(result.apiCallCount, 2);
1642
+ assert.ok(result.totalApiDurationNs >= 0);
1643
+ });
1644
+
1645
+ it('works without requestMetrics (backward compatible)', async context => {
1646
+ const response = new Response('{"data": "value"}', {
1647
+ status: 200,
1648
+ headers: new Headers({ 'Content-Type': 'application/json' }),
1649
+ });
1650
+
1651
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
1652
+
1653
+ // No requestMetrics passed — should not throw
1654
+ const result = await provider.get('/endpoint', {
1655
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1656
+ logger,
1657
+ });
1658
+
1659
+ assert.equal(result.status, 200);
1660
+ });
1661
+
1662
+ it('records api call duration from postForm', async () => {
1663
+ const metrics = RequestMetrics.startRequest();
1664
+ const httpsProvider = new Provider({
1665
+ prepareRequest: () => ({
1666
+ url: 'https://www.myApi.com',
1667
+ headers: {},
1668
+ }),
1669
+ });
1670
+
1671
+ const scope = nock('https://www.myApi.com').post('/upload').reply(201, { success: true });
1672
+
1673
+ const FormData = (await import('form-data')).default;
1674
+ const form = new FormData();
1675
+ form.append('file', Buffer.from('test data'), 'test.txt');
1676
+
1677
+ await httpsProvider.postForm('/upload', form, {
1678
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1679
+ logger,
1680
+ requestMetrics: metrics,
1681
+ });
1682
+
1683
+ const postFormResult = metrics.endRequest();
1684
+ assert.equal(postFormResult.apiCallCount, 1);
1685
+ assert.ok(postFormResult.totalApiDurationNs > 0);
1686
+ scope.isDone();
1687
+ });
1688
+
1689
+ it('records api call duration from postStream', async () => {
1690
+ const metrics = RequestMetrics.startRequest();
1691
+ const testData = 'binary stream data';
1692
+ const httpsProvider = new Provider({
1693
+ prepareRequest: () => ({
1694
+ url: 'https://www.myApi.com',
1695
+ headers: {},
1696
+ }),
1697
+ });
1698
+
1699
+ const scope = nock('https://www.myApi.com').post('/upload', testData).reply(201, { success: true });
1700
+
1701
+ const stream = Readable.from(Buffer.from(testData));
1702
+
1703
+ await httpsProvider.postStream('/upload', stream, {
1704
+ credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
1705
+ logger,
1706
+ requestMetrics: metrics,
1707
+ });
1708
+
1709
+ const postStreamResult = metrics.endRequest();
1710
+ assert.equal(postStreamResult.apiCallCount, 1);
1711
+ assert.ok(postStreamResult.totalApiDurationNs > 0);
1712
+ scope.isDone();
1713
+ });
1714
+ });
1589
1715
  });
@@ -0,0 +1,50 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import { RequestMetrics } from '../../src/resources/requestMetrics.js';
4
+
5
+ describe('RequestMetrics', () => {
6
+ describe('initial state', () => {
7
+ it('starts with zero api call count', () => {
8
+ const metrics = RequestMetrics.startRequest();
9
+ const result = metrics.endRequest();
10
+ assert.equal(result.apiCallCount, 0);
11
+ });
12
+
13
+ it('starts with zero total api duration', () => {
14
+ const metrics = RequestMetrics.startRequest();
15
+ const result = metrics.endRequest();
16
+ assert.equal(result.totalApiDurationNs, 0);
17
+ });
18
+ });
19
+
20
+ describe('endRequest', () => {
21
+ it('returns elapsed duration since construction', () => {
22
+ const before = process.hrtime.bigint();
23
+ const metrics = RequestMetrics.startRequest();
24
+ const result = metrics.endRequest();
25
+ const after = process.hrtime.bigint();
26
+ assert.ok(result.durationNs >= 0n);
27
+ assert.ok(result.durationNs <= after - before);
28
+ });
29
+ });
30
+
31
+ describe('recordExternalApiCall', () => {
32
+ it('increments api call count', () => {
33
+ const metrics = RequestMetrics.startRequest();
34
+ const start1 = process.hrtime.bigint();
35
+ metrics.recordExternalApiCall(start1);
36
+ const start2 = process.hrtime.bigint();
37
+ metrics.recordExternalApiCall(start2);
38
+ const result = metrics.endRequest();
39
+ assert.equal(result.apiCallCount, 2);
40
+ });
41
+
42
+ it('accumulates api duration', () => {
43
+ const metrics = RequestMetrics.startRequest();
44
+ const start = process.hrtime.bigint();
45
+ metrics.recordExternalApiCall(start);
46
+ const result = metrics.endRequest();
47
+ assert.ok(result.totalApiDurationNs >= 0);
48
+ });
49
+ });
50
+ });