@autofleet/rapido-http-client 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.
@@ -0,0 +1,148 @@
1
+ import { MockAgent, MockCallHistoryLog, MockPool } from "undici";
2
+ import { MockInterceptor } from "undici/types/mock-interceptor";
3
+
4
+ //#region src/testing.d.ts
5
+ interface SetupRapidoHttpClientMockOptions {
6
+ /** Whether to block any real network requests that are not mocked. @default true */
7
+ blockNonMockedNetwork?: boolean;
8
+ /** Collect call history for inspection/assertion. @default false */
9
+ collectCallHistory?: boolean;
10
+ }
11
+ interface InterceptOptions {
12
+ /** Path to intercept on. */
13
+ path: string | RegExp | ((path: string) => boolean);
14
+ /** Body to intercept on. Plain objects will be JSON.stringify'd automatically. */
15
+ body?: string | Record<string, any> | RegExp | ((body: string) => boolean);
16
+ /** Headers to intercept on. */
17
+ headers?: Record<string, string | RegExp | ((body: string) => boolean)> | ((headers: Record<string, string>) => boolean);
18
+ /** Query params to intercept on */
19
+ query?: Record<string, any>;
20
+ }
21
+ interface RapidoHttpClientMocker {
22
+ /**
23
+ * Set up intercepts for a specific origin with a chainable API.
24
+ *
25
+ * Returns an object with HTTP method helpers that create MockInterceptors.
26
+ * Each method accepts either a path string or an InterceptOptions object for advanced matching.
27
+ * The returned MockInterceptor supports `.reply()`, `.replyWithError()`, `.times()`, and `.persist()`.
28
+ *
29
+ * @param target - Either a URL string, or an object with serviceName or serviceUrl
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Simple path matching
34
+ * mock.intercept('https://api.example.com')
35
+ * .get('/users')
36
+ * .reply(200, { users: [] }, { headers: { 'content-type': 'application/json' } });
37
+ *
38
+ * // Advanced matching with body, headers, query params
39
+ * mock.intercept('https://api.example.com')
40
+ * .post({ path: '/users', body: JSON.stringify({ name: 'John' }) })
41
+ * .reply(201, { id: 1, name: 'John' }, { headers: { 'content-type': 'application/json' } });
42
+ *
43
+ * // Using serviceName (reads from env)
44
+ * mock.intercept({ serviceName: 'USER' })
45
+ * .get({ path: '/profile', query: { id: '123' } })
46
+ * .reply(200, { id: 123, name: 'Alice' }, { headers: { 'content-type': 'application/json' } });
47
+ *
48
+ * // Chain with times() or persist()
49
+ * mock.intercept({ serviceUrl: 'https://api.example.com' })
50
+ * .get('/data')
51
+ * .reply(200, { data: [] }, { headers: { 'content-type': 'application/json' } })
52
+ * .times(3); // Only mock 3 times
53
+ *
54
+ * // Mock errors
55
+ * mock.intercept('https://api.example.com')
56
+ * .get('/error')
57
+ * .replyWithError(new Error('Network failure'))
58
+ * .persist(); // Always throw this error
59
+ * ```
60
+ */
61
+ intercept: (target: string | {
62
+ serviceName: string;
63
+ } | {
64
+ serviceUrl: string;
65
+ }) => {
66
+ get: (options: string | InterceptOptions) => MockInterceptor;
67
+ head: (options: string | InterceptOptions) => MockInterceptor;
68
+ delete: (options: string | InterceptOptions) => MockInterceptor;
69
+ options: (options: string | InterceptOptions) => MockInterceptor;
70
+ put: (options: string | InterceptOptions) => MockInterceptor;
71
+ post: (options: string | InterceptOptions) => MockInterceptor;
72
+ patch: (options: string | InterceptOptions) => MockInterceptor;
73
+ query: (options: string | InterceptOptions) => MockInterceptor;
74
+ };
75
+ /**
76
+ * Get the mock pool for a specific origin for advanced usage.
77
+ * This gives you direct access to the undici MockPool for full control.
78
+ *
79
+ * @param target - Either a URL string, or an object with serviceName or serviceUrl
80
+ */
81
+ getPool: (target: string | {
82
+ serviceName: string;
83
+ } | {
84
+ serviceUrl: string;
85
+ }) => MockPool;
86
+ /**
87
+ * Clean up the global mock.
88
+ * Should be called in afterEach or after tests complete.
89
+ */
90
+ cleanup: () => Promise<void>;
91
+ /**
92
+ * Access the underlying mock agent for advanced usage
93
+ */
94
+ mockAgent: MockAgent<MockAgent.Options>;
95
+ /** Call history log of all intercepted requests. */
96
+ calls: MockCallHistoryLog[];
97
+ /** Clear the call history log. */
98
+ clearCallHistory: () => void;
99
+ /** Enable call history collection. */
100
+ enableCallHistory: () => void;
101
+ /** Disable call history collection. */
102
+ disableCallHistory: () => void;
103
+ }
104
+ /**
105
+ * Global mock setup for RapidoHttpClient.
106
+ *
107
+ * This provides a nock-like global mocking experience where you can set up
108
+ * mock responses before creating RapidoHttpClient instances. The mock is automatically
109
+ * used by any RapidoHttpClient instance created after setupGlobalMock() is called.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { setupRapidoHttpClientMock } from '@autofleet/rapido-http-client/testing';
114
+ * import { RapidoHttpClient } from '@autofleet/rapido-http-client';
115
+ *
116
+ * // In your test setup (e.g., beforeEach)
117
+ * const mock = setupRapidoHttpClientMock();
118
+ *
119
+ * // Set up mocks for specific origins
120
+ * const apiMock = mock.intercept('https://api.example.com');
121
+ * apiMock.get('/users').reply(200, { users: [] });
122
+ * apiMock.post('/users', { name: 'John' }).reply(201, { id: 1, name: 'John' });
123
+ *
124
+ * // Or use the pool directly for more control
125
+ * mock.getPool('https://api.example.com')
126
+ * .intercept({ path: '/data', method: 'GET' })
127
+ * .reply(200, { data: 'test' }, { headers: { 'content-type': 'application/json' } });
128
+ *
129
+ * // Create RapidoHttpClient instances normally - mocks are automatically used!
130
+ * const client = new RapidoHttpClient({
131
+ * serviceUrl: 'https://api.example.com',
132
+ * logger: mockLogger,
133
+ * });
134
+ *
135
+ * // Use the client - it will use the mocked responses
136
+ * const response = await client.get('/users');
137
+ *
138
+ * // Clean up in afterEach
139
+ * await mock.cleanup();
140
+ * ```
141
+ */
142
+ declare function setupRapidoHttpClientMock({
143
+ blockNonMockedNetwork,
144
+ collectCallHistory
145
+ }?: SetupRapidoHttpClientMockOptions): RapidoHttpClientMocker;
146
+ //#endregion
147
+ export { RapidoHttpClientMocker, setupRapidoHttpClientMock };
148
+ //# sourceMappingURL=testing.d.cts.map
@@ -0,0 +1,148 @@
1
+ import { MockAgent, MockCallHistoryLog, MockPool } from "undici";
2
+ import { MockInterceptor } from "undici/types/mock-interceptor";
3
+
4
+ //#region src/testing.d.ts
5
+ interface SetupRapidoHttpClientMockOptions {
6
+ /** Whether to block any real network requests that are not mocked. @default true */
7
+ blockNonMockedNetwork?: boolean;
8
+ /** Collect call history for inspection/assertion. @default false */
9
+ collectCallHistory?: boolean;
10
+ }
11
+ interface InterceptOptions {
12
+ /** Path to intercept on. */
13
+ path: string | RegExp | ((path: string) => boolean);
14
+ /** Body to intercept on. Plain objects will be JSON.stringify'd automatically. */
15
+ body?: string | Record<string, any> | RegExp | ((body: string) => boolean);
16
+ /** Headers to intercept on. */
17
+ headers?: Record<string, string | RegExp | ((body: string) => boolean)> | ((headers: Record<string, string>) => boolean);
18
+ /** Query params to intercept on */
19
+ query?: Record<string, any>;
20
+ }
21
+ interface RapidoHttpClientMocker {
22
+ /**
23
+ * Set up intercepts for a specific origin with a chainable API.
24
+ *
25
+ * Returns an object with HTTP method helpers that create MockInterceptors.
26
+ * Each method accepts either a path string or an InterceptOptions object for advanced matching.
27
+ * The returned MockInterceptor supports `.reply()`, `.replyWithError()`, `.times()`, and `.persist()`.
28
+ *
29
+ * @param target - Either a URL string, or an object with serviceName or serviceUrl
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Simple path matching
34
+ * mock.intercept('https://api.example.com')
35
+ * .get('/users')
36
+ * .reply(200, { users: [] }, { headers: { 'content-type': 'application/json' } });
37
+ *
38
+ * // Advanced matching with body, headers, query params
39
+ * mock.intercept('https://api.example.com')
40
+ * .post({ path: '/users', body: JSON.stringify({ name: 'John' }) })
41
+ * .reply(201, { id: 1, name: 'John' }, { headers: { 'content-type': 'application/json' } });
42
+ *
43
+ * // Using serviceName (reads from env)
44
+ * mock.intercept({ serviceName: 'USER' })
45
+ * .get({ path: '/profile', query: { id: '123' } })
46
+ * .reply(200, { id: 123, name: 'Alice' }, { headers: { 'content-type': 'application/json' } });
47
+ *
48
+ * // Chain with times() or persist()
49
+ * mock.intercept({ serviceUrl: 'https://api.example.com' })
50
+ * .get('/data')
51
+ * .reply(200, { data: [] }, { headers: { 'content-type': 'application/json' } })
52
+ * .times(3); // Only mock 3 times
53
+ *
54
+ * // Mock errors
55
+ * mock.intercept('https://api.example.com')
56
+ * .get('/error')
57
+ * .replyWithError(new Error('Network failure'))
58
+ * .persist(); // Always throw this error
59
+ * ```
60
+ */
61
+ intercept: (target: string | {
62
+ serviceName: string;
63
+ } | {
64
+ serviceUrl: string;
65
+ }) => {
66
+ get: (options: string | InterceptOptions) => MockInterceptor;
67
+ head: (options: string | InterceptOptions) => MockInterceptor;
68
+ delete: (options: string | InterceptOptions) => MockInterceptor;
69
+ options: (options: string | InterceptOptions) => MockInterceptor;
70
+ put: (options: string | InterceptOptions) => MockInterceptor;
71
+ post: (options: string | InterceptOptions) => MockInterceptor;
72
+ patch: (options: string | InterceptOptions) => MockInterceptor;
73
+ query: (options: string | InterceptOptions) => MockInterceptor;
74
+ };
75
+ /**
76
+ * Get the mock pool for a specific origin for advanced usage.
77
+ * This gives you direct access to the undici MockPool for full control.
78
+ *
79
+ * @param target - Either a URL string, or an object with serviceName or serviceUrl
80
+ */
81
+ getPool: (target: string | {
82
+ serviceName: string;
83
+ } | {
84
+ serviceUrl: string;
85
+ }) => MockPool;
86
+ /**
87
+ * Clean up the global mock.
88
+ * Should be called in afterEach or after tests complete.
89
+ */
90
+ cleanup: () => Promise<void>;
91
+ /**
92
+ * Access the underlying mock agent for advanced usage
93
+ */
94
+ mockAgent: MockAgent<MockAgent.Options>;
95
+ /** Call history log of all intercepted requests. */
96
+ calls: MockCallHistoryLog[];
97
+ /** Clear the call history log. */
98
+ clearCallHistory: () => void;
99
+ /** Enable call history collection. */
100
+ enableCallHistory: () => void;
101
+ /** Disable call history collection. */
102
+ disableCallHistory: () => void;
103
+ }
104
+ /**
105
+ * Global mock setup for RapidoHttpClient.
106
+ *
107
+ * This provides a nock-like global mocking experience where you can set up
108
+ * mock responses before creating RapidoHttpClient instances. The mock is automatically
109
+ * used by any RapidoHttpClient instance created after setupGlobalMock() is called.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { setupRapidoHttpClientMock } from '@autofleet/rapido-http-client/testing';
114
+ * import { RapidoHttpClient } from '@autofleet/rapido-http-client';
115
+ *
116
+ * // In your test setup (e.g., beforeEach)
117
+ * const mock = setupRapidoHttpClientMock();
118
+ *
119
+ * // Set up mocks for specific origins
120
+ * const apiMock = mock.intercept('https://api.example.com');
121
+ * apiMock.get('/users').reply(200, { users: [] });
122
+ * apiMock.post('/users', { name: 'John' }).reply(201, { id: 1, name: 'John' });
123
+ *
124
+ * // Or use the pool directly for more control
125
+ * mock.getPool('https://api.example.com')
126
+ * .intercept({ path: '/data', method: 'GET' })
127
+ * .reply(200, { data: 'test' }, { headers: { 'content-type': 'application/json' } });
128
+ *
129
+ * // Create RapidoHttpClient instances normally - mocks are automatically used!
130
+ * const client = new RapidoHttpClient({
131
+ * serviceUrl: 'https://api.example.com',
132
+ * logger: mockLogger,
133
+ * });
134
+ *
135
+ * // Use the client - it will use the mocked responses
136
+ * const response = await client.get('/users');
137
+ *
138
+ * // Clean up in afterEach
139
+ * await mock.cleanup();
140
+ * ```
141
+ */
142
+ declare function setupRapidoHttpClientMock({
143
+ blockNonMockedNetwork,
144
+ collectCallHistory
145
+ }?: SetupRapidoHttpClientMockOptions): RapidoHttpClientMocker;
146
+ //#endregion
147
+ export { RapidoHttpClientMocker, setupRapidoHttpClientMock };
148
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{c as e}from"./utils-B2KjeMp7.js";import{MockAgent as t}from"undici";function n({blockNonMockedNetwork:n,collectCallHistory:r}={}){let i=new t({enableCallHistory:r??!1});(n??!0)&&i.disableNetConnect(),globalThis.__RAPIDO_HTTP_CLIENT_MOCK_AGENT__=i;let a=new Map,o=new Map,s=e=>{let t=new URL(e),n=t.origin,r=t.pathname===`/`?``:t.pathname.replace(/\/$/,``);if(!a.has(n)){let e=i.get(n);a.set(n,e)}return r&&!o.has(n)&&o.set(n,r),{pool:a.get(n),basePath:o.get(n)||``}},c=(e,t)=>{if(typeof e==`string`)return{path:t+e};let n={...e};if(typeof n.path==`string`&&(n.path=t+n.path),n.body&&typeof n.body==`object`&&!(n.body instanceof RegExp)&&typeof n.body!=`function`){let e=n.headers,t=e?.[`Content-Type`]||e?.[`content-type`];if(t&&/application\/x-www-form-urlencoded/i.test(t)){let e=new URLSearchParams;for(let[t,r]of Object.entries(n.body))Array.isArray(r)?r.forEach(n=>e.append(t,String(n))):r!=null&&e.append(t,String(r));n.body=e.toString()}else n.body=JSON.stringify(n.body)}if(n.query)for(let[e,t]of Object.entries(n.query))Array.isArray(t)&&!e.endsWith(`[]`)&&(n.query[`${e}[]`]=t,delete n.query[e]);return n};return{intercept:t=>{let{pool:n,basePath:r}=s(typeof t==`string`?t:e(t));return{get:e=>n.intercept({...c(e,r),method:`GET`}),head:e=>n.intercept({...c(e,r),method:`HEAD`}),delete:e=>n.intercept({...c(e,r),method:`DELETE`}),options:e=>n.intercept({...c(e,r),method:`OPTIONS`}),put:e=>n.intercept({...c(e,r),method:`PUT`}),post:e=>n.intercept({...c(e,r),method:`POST`}),patch:e=>n.intercept({...c(e,r),method:`PATCH`}),query:e=>n.intercept({...c(e,r),method:`QUERY`})}},getPool:t=>s(typeof t==`string`?t:e(t)).pool,cleanup:async()=>{delete globalThis.__RAPIDO_HTTP_CLIENT_MOCK_AGENT__,a.clear(),o.clear(),await i.close(),i.clearCallHistory()},get calls(){return i.getCallHistory()?.calls()??[]},clearCallHistory:()=>i.clearCallHistory(),enableCallHistory:()=>{i.enableCallHistory()},disableCallHistory:()=>{i.disableCallHistory()},mockAgent:i}}export{n as setupRapidoHttpClientMock};
2
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import { MockAgent, type MockCallHistoryLog, type MockPool } from 'undici';\nimport { resolveServiceUrl } from './utils';\nimport type { MockInterceptor } from 'undici/types/mock-interceptor';\n\ninterface SetupRapidoHttpClientMockOptions {\n /** Whether to block any real network requests that are not mocked. @default true */\n blockNonMockedNetwork?: boolean;\n /** Collect call history for inspection/assertion. @default false */\n collectCallHistory?: boolean;\n}\n\ninterface InterceptOptions {\n /** Path to intercept on. */\n path: string | RegExp | ((path: string) => boolean);\n /** Body to intercept on. Plain objects will be JSON.stringify'd automatically. */\n body?: string | Record<string, any> | RegExp | ((body: string) => boolean);\n /** Headers to intercept on. */\n headers?: Record<string, string | RegExp | ((body: string) => boolean)> | ((headers: Record<string, string>) => boolean);\n /** Query params to intercept on */\n query?: Record<string, any>;\n}\n\nexport interface RapidoHttpClientMocker {\n /**\n * Set up intercepts for a specific origin with a chainable API.\n *\n * Returns an object with HTTP method helpers that create MockInterceptors.\n * Each method accepts either a path string or an InterceptOptions object for advanced matching.\n * The returned MockInterceptor supports `.reply()`, `.replyWithError()`, `.times()`, and `.persist()`.\n *\n * @param target - Either a URL string, or an object with serviceName or serviceUrl\n *\n * @example\n * ```typescript\n * // Simple path matching\n * mock.intercept('https://api.example.com')\n * .get('/users')\n * .reply(200, { users: [] }, { headers: { 'content-type': 'application/json' } });\n *\n * // Advanced matching with body, headers, query params\n * mock.intercept('https://api.example.com')\n * .post({ path: '/users', body: JSON.stringify({ name: 'John' }) })\n * .reply(201, { id: 1, name: 'John' }, { headers: { 'content-type': 'application/json' } });\n *\n * // Using serviceName (reads from env)\n * mock.intercept({ serviceName: 'USER' })\n * .get({ path: '/profile', query: { id: '123' } })\n * .reply(200, { id: 123, name: 'Alice' }, { headers: { 'content-type': 'application/json' } });\n *\n * // Chain with times() or persist()\n * mock.intercept({ serviceUrl: 'https://api.example.com' })\n * .get('/data')\n * .reply(200, { data: [] }, { headers: { 'content-type': 'application/json' } })\n * .times(3); // Only mock 3 times\n *\n * // Mock errors\n * mock.intercept('https://api.example.com')\n * .get('/error')\n * .replyWithError(new Error('Network failure'))\n * .persist(); // Always throw this error\n * ```\n */\n intercept: (target: string | { serviceName: string; } | { serviceUrl: string; }) => {\n get: (options: string | InterceptOptions) => MockInterceptor;\n head: (options: string | InterceptOptions) => MockInterceptor;\n delete: (options: string | InterceptOptions) => MockInterceptor;\n options: (options: string | InterceptOptions) => MockInterceptor;\n put: (options: string | InterceptOptions) => MockInterceptor;\n post: (options: string | InterceptOptions) => MockInterceptor;\n patch: (options: string | InterceptOptions) => MockInterceptor;\n query: (options: string | InterceptOptions) => MockInterceptor;\n };\n /**\n * Get the mock pool for a specific origin for advanced usage.\n * This gives you direct access to the undici MockPool for full control.\n *\n * @param target - Either a URL string, or an object with serviceName or serviceUrl\n */\n getPool: (target: string | { serviceName: string; } | { serviceUrl: string; }) => MockPool;\n /**\n * Clean up the global mock.\n * Should be called in afterEach or after tests complete.\n */\n cleanup: () => Promise<void>;\n /**\n * Access the underlying mock agent for advanced usage\n */\n mockAgent: MockAgent<MockAgent.Options>;\n /** Call history log of all intercepted requests. */\n calls: MockCallHistoryLog[];\n /** Clear the call history log. */\n clearCallHistory: () => void;\n /** Enable call history collection. */\n enableCallHistory: () => void;\n /** Disable call history collection. */\n disableCallHistory: () => void;\n}\n\n/**\n * Global mock setup for RapidoHttpClient.\n *\n * This provides a nock-like global mocking experience where you can set up\n * mock responses before creating RapidoHttpClient instances. The mock is automatically\n * used by any RapidoHttpClient instance created after setupGlobalMock() is called.\n *\n * @example\n * ```typescript\n * import { setupRapidoHttpClientMock } from '@autofleet/rapido-http-client/testing';\n * import { RapidoHttpClient } from '@autofleet/rapido-http-client';\n *\n * // In your test setup (e.g., beforeEach)\n * const mock = setupRapidoHttpClientMock();\n *\n * // Set up mocks for specific origins\n * const apiMock = mock.intercept('https://api.example.com');\n * apiMock.get('/users').reply(200, { users: [] });\n * apiMock.post('/users', { name: 'John' }).reply(201, { id: 1, name: 'John' });\n *\n * // Or use the pool directly for more control\n * mock.getPool('https://api.example.com')\n * .intercept({ path: '/data', method: 'GET' })\n * .reply(200, { data: 'test' }, { headers: { 'content-type': 'application/json' } });\n *\n * // Create RapidoHttpClient instances normally - mocks are automatically used!\n * const client = new RapidoHttpClient({\n * serviceUrl: 'https://api.example.com',\n * logger: mockLogger,\n * });\n *\n * // Use the client - it will use the mocked responses\n * const response = await client.get('/users');\n *\n * // Clean up in afterEach\n * await mock.cleanup();\n * ```\n */\nexport function setupRapidoHttpClientMock({ blockNonMockedNetwork, collectCallHistory }: SetupRapidoHttpClientMockOptions = {}): RapidoHttpClientMocker {\n const mockAgent = new MockAgent({ enableCallHistory: collectCallHistory ?? false });\n if (blockNonMockedNetwork ?? true) {\n mockAgent.disableNetConnect();\n }\n\n // Store the mock agent globally for RapidoHttpClient to use\n (globalThis as any).__RAPIDO_HTTP_CLIENT_MOCK_AGENT__ = mockAgent;\n\n const mockPools = new Map<string, MockPool>();\n const basePaths = new Map<string, string>(); // Track base paths for each origin\n\n const getOrCreatePool = (urlOrOrigin: string): { pool: MockPool; basePath: string; } => {\n // MockAgent requires just the origin, not the full URL with path\n // Extract origin and pathname if a full URL is provided\n const url = new URL(urlOrOrigin);\n const origin = url.origin;\n // Remove trailing slash from base path to avoid double slashes\n const basePath = url.pathname === '/' ? '' : url.pathname.replace(/\\/$/, '');\n\n if (!mockPools.has(origin)) {\n const pool = mockAgent.get<MockPool>(origin);\n mockPools.set(origin, pool);\n }\n\n // Store or retrieve the base path for this origin\n if (basePath && !basePaths.has(origin)) {\n basePaths.set(origin, basePath);\n }\n\n return { pool: mockPools.get(origin)!, basePath: basePaths.get(origin) || '' };\n };\n\n const normalizeOptions = (options: string | InterceptOptions, basePath: string): MockInterceptor.Options => {\n if (typeof options === 'string') {\n return { path: basePath + options };\n }\n const normalized = { ...options };\n\n // Prepend base path to the path option\n if (typeof normalized.path === 'string') {\n normalized.path = basePath + normalized.path;\n }\n\n // Auto-encode object bodies based on Content-Type\n if (normalized.body && typeof normalized.body === 'object' && !(normalized.body instanceof RegExp) && typeof normalized.body !== 'function') {\n // Check if Content-Type is application/x-www-form-urlencoded\n const headers = normalized.headers as Record<string, string> | undefined;\n const contentType = headers?.['Content-Type'] || headers?.['content-type'];\n\n if (contentType && /application\\/x-www-form-urlencoded/i.test(contentType)) {\n // URL-encode the body (same logic as RapidoHttpClient's #prepareRequestBody)\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(normalized.body)) {\n if (Array.isArray(value)) {\n value.forEach(v => params.append(key, String(v)));\n } else if (value !== undefined && value !== null) {\n params.append(key, String(value));\n }\n }\n normalized.body = params.toString();\n } else {\n // Default to JSON\n normalized.body = JSON.stringify(normalized.body);\n }\n }\n // Auto-append [] to array query params\n if (normalized.query) {\n for (const [key, value] of Object.entries(normalized.query)) {\n if (Array.isArray(value) && !key.endsWith('[]')) {\n normalized.query[`${key}[]`] = value;\n delete normalized.query[key];\n }\n }\n }\n return normalized as MockInterceptor.Options;\n };\n\n return {\n intercept: (target: string | { serviceName: string; } | { serviceUrl: string; }) => {\n const origin = typeof target === 'string' ? target : resolveServiceUrl(target);\n const { pool, basePath } = getOrCreatePool(origin);\n\n return {\n get: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'GET' }),\n head: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'HEAD' }),\n delete: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'DELETE' }),\n options: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'OPTIONS' }),\n put: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'PUT' }),\n post: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'POST' }),\n patch: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'PATCH' }),\n query: (options: string | InterceptOptions) => pool.intercept({ ...normalizeOptions(options, basePath), method: 'QUERY' }),\n };\n },\n\n getPool: (target: string | { serviceName: string; } | { serviceUrl: string; }): MockPool => {\n const origin = typeof target === 'string' ? target : resolveServiceUrl(target);\n return getOrCreatePool(origin).pool;\n },\n cleanup: async (): Promise<void> => {\n delete (globalThis as any).__RAPIDO_HTTP_CLIENT_MOCK_AGENT__;\n mockPools.clear();\n basePaths.clear();\n await mockAgent.close();\n mockAgent.clearCallHistory();\n },\n\n get calls(): MockCallHistoryLog[] {\n return mockAgent.getCallHistory()?.calls() ?? [];\n },\n\n clearCallHistory: (): void => mockAgent.clearCallHistory(),\n\n enableCallHistory: (): void => {\n mockAgent.enableCallHistory();\n },\n\n disableCallHistory: (): void => {\n mockAgent.disableCallHistory();\n },\n\n mockAgent,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./index.cjs`);let t=require(`undici`),n=require(`node:process`),r=require(`node:zlib`);const i=()=>typeof r.createZstdDecompress==`function`,a={timeout:1e4,headers:{"X-AF-AUTH":`ANYONE`,"X-IAF-ORIGIN-SERVICE":n.env.AF_SERVICE_NAME||``},keepAliveTimeout:5e3,keepAliveMaxTimeout:1e4,connections:10},o={...a,headers:{...a.headers,"Accept-Encoding":`${i()?`zstd, `:``}br, gzip`}},s=Object.getOwnPropertySymbols(t.RetryHandler).find(e=>typeof Object.getOwnPropertyDescriptor(t.RetryHandler,e)?.value==`function`&&e.toString().includes(`default retry`)),c=t.RetryHandler[s],l=e=>e instanceof FormData||Object.prototype.toString.call(e)===`[object FormData]`;function u(e){if(!e.serviceUrl&&!e.serviceName)throw Error(`At least one of the settings Missing serviceUrl or serviceName`);if(e.serviceUrl)return e.serviceUrl;let t=`${e.serviceName}_SERVICE_HOST`,r=n.env[t];if(!r&&n.env.NODE_ENV!==`test`)throw Error(`Environment variable ${t} is not set`);return`http://${r}`}const d=(e,t,n)=>`[${e.toUpperCase()}] ${t??`unknown-base-url`}${n}`,f=(e,t,n)=>{if(/^https?:\/\//i.test(e)){let t=new URL(e);return{origin:t.origin,path:t.pathname+t.search+t.hash}}let r=e;if(n){let t=n.endsWith(`/`),i=e.startsWith(`/`);r=t&&i?n+e.slice(1):!t&&!i?n+`/`+e:n+e}return{origin:t,path:r}},p=(e,t)=>{let n={...e,...t};return t.headers&&(n.headers={...e.headers,...t.headers}),n};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return d}});
2
+ //# sourceMappingURL=utils-B1ZhSXeE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-B1ZhSXeE.cjs","names":["createZstdDecompress","defaultSettings: Omit<RapidoHttpClientSettings, 'logger'>","env","defaultSettingsWithDecompress: Omit<RapidoHttpClientSettings, 'logger'>","RetryHandler"],"sources":["../src/utils.ts"],"sourcesContent":["import { env } from 'node:process';\nimport { RetryHandler } from 'undici';\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore createZstdDecompress is rather new, so our @types/node is unaware of it\nimport { createZstdDecompress } from 'node:zlib';\nimport type { RapidoHttpClientSettings } from '.';\n\n/** The support for zstd in Node was added during 22 & 23, so this function checks if it's exists in runtime */\nexport const isZstdSupported = (): boolean => typeof createZstdDecompress === 'function';\n\nexport const defaultSettings: Omit<RapidoHttpClientSettings, 'logger'> = {\n timeout: 10_000,\n headers: {\n 'X-AF-AUTH': 'ANYONE',\n 'X-IAF-ORIGIN-SERVICE': env.AF_SERVICE_NAME || '',\n },\n keepAliveTimeout: 5_000,\n keepAliveMaxTimeout: 10_000,\n connections: 10,\n};\n\nexport const defaultSettingsWithDecompress: Omit<RapidoHttpClientSettings, 'logger'> = {\n ...defaultSettings,\n headers: {\n ...defaultSettings.headers,\n /* c8 ignore next */\n 'Accept-Encoding': `${isZstdSupported() ? 'zstd, ' : ''}br, gzip`,\n },\n};\n\nconst retryHandlerDefaultRetryKey = Object.getOwnPropertySymbols(RetryHandler)\n .find(k => typeof Object.getOwnPropertyDescriptor(RetryHandler, k)?.value === 'function' && k.toString().includes('default retry'))!;\nexport const defaultRetryMethod = (RetryHandler as any)[retryHandlerDefaultRetryKey] as RetryHandler.RetryCallback;\n\nexport const isFormData = (obj: unknown): obj is FormData => obj instanceof FormData || Object.prototype.toString.call(obj) === '[object FormData]';\n\n/**\n * Resolves a service configuration to a base URL\n *\n * @param config - Object with either serviceName or serviceUrl\n * @returns The resolved base URL\n * @throws Error if neither serviceName nor serviceUrl is provided, or if the environment variable for serviceName is not set\n */\nexport function resolveServiceUrl(config: { serviceName?: string; serviceUrl?: string; }): string {\n if (!config.serviceUrl && !config.serviceName) {\n throw new Error('At least one of the settings Missing serviceUrl or serviceName');\n }\n\n if (config.serviceUrl) {\n return config.serviceUrl;\n }\n\n const envServiceHostName = `${config.serviceName}_SERVICE_HOST`;\n const host = env[envServiceHostName];\n if (!host && env.NODE_ENV !== 'test') {\n throw new Error(`Environment variable ${envServiceHostName} is not set`);\n }\n return `http://${host}`;\n}\n\nexport const createRequestString = (method: string, baseURL: string | undefined, url: string): string => `[${method.toUpperCase()}] ${baseURL ?? 'unknown-base-url'}${url}`;\n\nexport const parseUrlAndOrigin = (url: string, baseURL: string, basePath?: string): { origin: string; path: string; } => {\n // Check if url is a full URL (starts with http:// or https://)\n const isFullUrl = /^https?:\\/\\//i.test(url);\n\n if (isFullUrl) {\n // Parse the full URL to extract origin and path\n const urlObj = new URL(url);\n return {\n origin: urlObj.origin,\n path: urlObj.pathname + urlObj.search + urlObj.hash,\n };\n }\n\n // Use base URL and the provided path, prepending basePath if provided\n let finalPath = url;\n if (basePath) {\n // Normalize: ensure exactly one slash between basePath and url\n const baseEndsWithSlash = basePath.endsWith('/');\n const urlStartsWithSlash = url.startsWith('/');\n\n if (baseEndsWithSlash && urlStartsWithSlash) {\n // Both have slash, remove one\n finalPath = basePath + url.slice(1);\n } else if (!baseEndsWithSlash && !urlStartsWithSlash) {\n // Neither has slash, add one\n finalPath = basePath + '/' + url;\n } else {\n // One has slash, just concatenate\n finalPath = basePath + url;\n }\n }\n return {\n origin: baseURL,\n path: finalPath,\n };\n};\n\nexport const mergeConfig = <T extends Record<string, any>, U extends Record<string, any>>(\n defaults: T,\n overrides: U,\n): T & U => {\n const result = { ...defaults, ...overrides };\n\n // Special handling for headers - merge them instead of replacing\n if (overrides.headers) {\n (result as unknown as { headers: Record<string, string>; }).headers = {\n ...defaults.headers,\n ...overrides.headers,\n };\n }\n\n return result;\n};\n"],"mappings":"wGAQA,MAAa,MAAiC,OAAOA,EAAAA,sBAAyB,WAEjEC,EAA4D,CACvE,QAAS,IACT,QAAS,CACP,YAAa,SACb,uBAAwBC,EAAAA,IAAI,iBAAmB,GAChD,CACD,iBAAkB,IAClB,oBAAqB,IACrB,YAAa,GACd,CAEYC,EAA0E,CACrF,GAAG,EACH,QAAS,CACP,GAAG,EAAgB,QAEnB,kBAAmB,GAAG,GAAiB,CAAG,SAAW,GAAG,UACzD,CACF,CAEK,EAA8B,OAAO,sBAAsBC,EAAAA,aAAa,CAC3E,KAAK,GAAK,OAAO,OAAO,yBAAyBA,EAAAA,aAAc,EAAE,EAAE,OAAU,YAAc,EAAE,UAAU,CAAC,SAAS,gBAAgB,CAAC,CACxH,EAAsBA,EAAAA,aAAqB,GAE3C,EAAc,GAAkC,aAAe,UAAY,OAAO,UAAU,SAAS,KAAK,EAAI,GAAK,oBAShI,SAAgB,EAAkB,EAAgE,CAChG,GAAI,CAAC,EAAO,YAAc,CAAC,EAAO,YAChC,MAAU,MAAM,iEAAiE,CAGnF,GAAI,EAAO,WACT,OAAO,EAAO,WAGhB,IAAM,EAAqB,GAAG,EAAO,YAAY,eAC3C,EAAOF,EAAAA,IAAI,GACjB,GAAI,CAAC,GAAQA,EAAAA,IAAI,WAAa,OAC5B,MAAU,MAAM,wBAAwB,EAAmB,aAAa,CAE1E,MAAO,UAAU,IAGnB,MAAa,GAAuB,EAAgB,EAA6B,IAAwB,IAAI,EAAO,aAAa,CAAC,IAAI,GAAW,qBAAqB,IAEzJ,GAAqB,EAAa,EAAiB,IAAyD,CAIvH,GAFkB,gBAAgB,KAAK,EAAI,CAE5B,CAEb,IAAM,EAAS,IAAI,IAAI,EAAI,CAC3B,MAAO,CACL,OAAQ,EAAO,OACf,KAAM,EAAO,SAAW,EAAO,OAAS,EAAO,KAChD,CAIH,IAAI,EAAY,EAChB,GAAI,EAAU,CAEZ,IAAM,EAAoB,EAAS,SAAS,IAAI,CAC1C,EAAqB,EAAI,WAAW,IAAI,CAE9C,AAQE,EARE,GAAqB,EAEX,EAAW,EAAI,MAAM,EAAE,CAC1B,CAAC,GAAqB,CAAC,EAEpB,EAAW,IAAM,EAGjB,EAAW,EAG3B,MAAO,CACL,OAAQ,EACR,KAAM,EACP,EAGU,GACX,EACA,IACU,CACV,IAAM,EAAS,CAAE,GAAG,EAAU,GAAG,EAAW,CAU5C,OAPI,EAAU,UACX,EAA2D,QAAU,CACpE,GAAG,EAAS,QACZ,GAAG,EAAU,QACd,EAGI"}
@@ -0,0 +1,2 @@
1
+ import{RetryHandler as e}from"undici";import{env as t}from"node:process";import{createZstdDecompress as n}from"node:zlib";const r=()=>typeof n==`function`,i={timeout:1e4,headers:{"X-AF-AUTH":`ANYONE`,"X-IAF-ORIGIN-SERVICE":t.AF_SERVICE_NAME||``},keepAliveTimeout:5e3,keepAliveMaxTimeout:1e4,connections:10},a={...i,headers:{...i.headers,"Accept-Encoding":`${r()?`zstd, `:``}br, gzip`}},o=e[Object.getOwnPropertySymbols(e).find(t=>typeof Object.getOwnPropertyDescriptor(e,t)?.value==`function`&&t.toString().includes(`default retry`))],s=e=>e instanceof FormData||Object.prototype.toString.call(e)===`[object FormData]`;function c(e){if(!e.serviceUrl&&!e.serviceName)throw Error(`At least one of the settings Missing serviceUrl or serviceName`);if(e.serviceUrl)return e.serviceUrl;let n=`${e.serviceName}_SERVICE_HOST`,r=t[n];if(!r&&t.NODE_ENV!==`test`)throw Error(`Environment variable ${n} is not set`);return`http://${r}`}const l=(e,t,n)=>`[${e.toUpperCase()}] ${t??`unknown-base-url`}${n}`,u=(e,t,n)=>{if(/^https?:\/\//i.test(e)){let t=new URL(e);return{origin:t.origin,path:t.pathname+t.search+t.hash}}let r=e;if(n){let t=n.endsWith(`/`),i=e.startsWith(`/`);r=t&&i?n+e.slice(1):!t&&!i?n+`/`+e:n+e}return{origin:t,path:r}},d=(e,t)=>{let n={...e,...t};return t.headers&&(n.headers={...e.headers,...t.headers}),n};export{s as a,c,a as i,o as n,d as o,i as r,u as s,l as t};
2
+ //# sourceMappingURL=utils-B2KjeMp7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-B2KjeMp7.js","names":["defaultSettings: Omit<RapidoHttpClientSettings, 'logger'>","defaultSettingsWithDecompress: Omit<RapidoHttpClientSettings, 'logger'>"],"sources":["../src/utils.ts"],"sourcesContent":["import { env } from 'node:process';\nimport { RetryHandler } from 'undici';\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore createZstdDecompress is rather new, so our @types/node is unaware of it\nimport { createZstdDecompress } from 'node:zlib';\nimport type { RapidoHttpClientSettings } from '.';\n\n/** The support for zstd in Node was added during 22 & 23, so this function checks if it's exists in runtime */\nexport const isZstdSupported = (): boolean => typeof createZstdDecompress === 'function';\n\nexport const defaultSettings: Omit<RapidoHttpClientSettings, 'logger'> = {\n timeout: 10_000,\n headers: {\n 'X-AF-AUTH': 'ANYONE',\n 'X-IAF-ORIGIN-SERVICE': env.AF_SERVICE_NAME || '',\n },\n keepAliveTimeout: 5_000,\n keepAliveMaxTimeout: 10_000,\n connections: 10,\n};\n\nexport const defaultSettingsWithDecompress: Omit<RapidoHttpClientSettings, 'logger'> = {\n ...defaultSettings,\n headers: {\n ...defaultSettings.headers,\n /* c8 ignore next */\n 'Accept-Encoding': `${isZstdSupported() ? 'zstd, ' : ''}br, gzip`,\n },\n};\n\nconst retryHandlerDefaultRetryKey = Object.getOwnPropertySymbols(RetryHandler)\n .find(k => typeof Object.getOwnPropertyDescriptor(RetryHandler, k)?.value === 'function' && k.toString().includes('default retry'))!;\nexport const defaultRetryMethod = (RetryHandler as any)[retryHandlerDefaultRetryKey] as RetryHandler.RetryCallback;\n\nexport const isFormData = (obj: unknown): obj is FormData => obj instanceof FormData || Object.prototype.toString.call(obj) === '[object FormData]';\n\n/**\n * Resolves a service configuration to a base URL\n *\n * @param config - Object with either serviceName or serviceUrl\n * @returns The resolved base URL\n * @throws Error if neither serviceName nor serviceUrl is provided, or if the environment variable for serviceName is not set\n */\nexport function resolveServiceUrl(config: { serviceName?: string; serviceUrl?: string; }): string {\n if (!config.serviceUrl && !config.serviceName) {\n throw new Error('At least one of the settings Missing serviceUrl or serviceName');\n }\n\n if (config.serviceUrl) {\n return config.serviceUrl;\n }\n\n const envServiceHostName = `${config.serviceName}_SERVICE_HOST`;\n const host = env[envServiceHostName];\n if (!host && env.NODE_ENV !== 'test') {\n throw new Error(`Environment variable ${envServiceHostName} is not set`);\n }\n return `http://${host}`;\n}\n\nexport const createRequestString = (method: string, baseURL: string | undefined, url: string): string => `[${method.toUpperCase()}] ${baseURL ?? 'unknown-base-url'}${url}`;\n\nexport const parseUrlAndOrigin = (url: string, baseURL: string, basePath?: string): { origin: string; path: string; } => {\n // Check if url is a full URL (starts with http:// or https://)\n const isFullUrl = /^https?:\\/\\//i.test(url);\n\n if (isFullUrl) {\n // Parse the full URL to extract origin and path\n const urlObj = new URL(url);\n return {\n origin: urlObj.origin,\n path: urlObj.pathname + urlObj.search + urlObj.hash,\n };\n }\n\n // Use base URL and the provided path, prepending basePath if provided\n let finalPath = url;\n if (basePath) {\n // Normalize: ensure exactly one slash between basePath and url\n const baseEndsWithSlash = basePath.endsWith('/');\n const urlStartsWithSlash = url.startsWith('/');\n\n if (baseEndsWithSlash && urlStartsWithSlash) {\n // Both have slash, remove one\n finalPath = basePath + url.slice(1);\n } else if (!baseEndsWithSlash && !urlStartsWithSlash) {\n // Neither has slash, add one\n finalPath = basePath + '/' + url;\n } else {\n // One has slash, just concatenate\n finalPath = basePath + url;\n }\n }\n return {\n origin: baseURL,\n path: finalPath,\n };\n};\n\nexport const mergeConfig = <T extends Record<string, any>, U extends Record<string, any>>(\n defaults: T,\n overrides: U,\n): T & U => {\n const result = { ...defaults, ...overrides };\n\n // Special handling for headers - merge them instead of replacing\n if (overrides.headers) {\n (result as unknown as { headers: Record<string, string>; }).headers = {\n ...defaults.headers,\n ...overrides.headers,\n };\n }\n\n return result;\n};\n"],"mappings":"0HAQA,MAAa,MAAiC,OAAO,GAAyB,WAEjEA,EAA4D,CACvE,QAAS,IACT,QAAS,CACP,YAAa,SACb,uBAAwB,EAAI,iBAAmB,GAChD,CACD,iBAAkB,IAClB,oBAAqB,IACrB,YAAa,GACd,CAEYC,EAA0E,CACrF,GAAG,EACH,QAAS,CACP,GAAG,EAAgB,QAEnB,kBAAmB,GAAG,GAAiB,CAAG,SAAW,GAAG,UACzD,CACF,CAIY,EAAsB,EAFC,OAAO,sBAAsB,EAAa,CAC3E,KAAK,GAAK,OAAO,OAAO,yBAAyB,EAAc,EAAE,EAAE,OAAU,YAAc,EAAE,UAAU,CAAC,SAAS,gBAAgB,CAAC,EAGxH,EAAc,GAAkC,aAAe,UAAY,OAAO,UAAU,SAAS,KAAK,EAAI,GAAK,oBAShI,SAAgB,EAAkB,EAAgE,CAChG,GAAI,CAAC,EAAO,YAAc,CAAC,EAAO,YAChC,MAAU,MAAM,iEAAiE,CAGnF,GAAI,EAAO,WACT,OAAO,EAAO,WAGhB,IAAM,EAAqB,GAAG,EAAO,YAAY,eAC3C,EAAO,EAAI,GACjB,GAAI,CAAC,GAAQ,EAAI,WAAa,OAC5B,MAAU,MAAM,wBAAwB,EAAmB,aAAa,CAE1E,MAAO,UAAU,IAGnB,MAAa,GAAuB,EAAgB,EAA6B,IAAwB,IAAI,EAAO,aAAa,CAAC,IAAI,GAAW,qBAAqB,IAEzJ,GAAqB,EAAa,EAAiB,IAAyD,CAIvH,GAFkB,gBAAgB,KAAK,EAAI,CAE5B,CAEb,IAAM,EAAS,IAAI,IAAI,EAAI,CAC3B,MAAO,CACL,OAAQ,EAAO,OACf,KAAM,EAAO,SAAW,EAAO,OAAS,EAAO,KAChD,CAIH,IAAI,EAAY,EAChB,GAAI,EAAU,CAEZ,IAAM,EAAoB,EAAS,SAAS,IAAI,CAC1C,EAAqB,EAAI,WAAW,IAAI,CAE9C,AAQE,EARE,GAAqB,EAEX,EAAW,EAAI,MAAM,EAAE,CAC1B,CAAC,GAAqB,CAAC,EAEpB,EAAW,IAAM,EAGjB,EAAW,EAG3B,MAAO,CACL,OAAQ,EACR,KAAM,EACP,EAGU,GACX,EACA,IACU,CACV,IAAM,EAAS,CAAE,GAAG,EAAU,GAAG,EAAW,CAU5C,OAPI,EAAU,UACX,EAA2D,QAAU,CACpE,GAAG,EAAS,QACZ,GAAG,EAAU,QACd,EAGI"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@autofleet/rapido-http-client",
3
+ "version": "0.0.1",
4
+ "description": "Modern undici-based HTTP client with logging, retries, and global configuration",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./testing": {
21
+ "import": {
22
+ "types": "./dist/testing.d.ts",
23
+ "default": "./dist/testing.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/testing.d.cts",
27
+ "default": "./dist/testing.cjs"
28
+ }
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/Autofleet/autorepo.git"
40
+ },
41
+ "author": "",
42
+ "license": "Proprietary",
43
+ "bugs": {
44
+ "url": "https://github.com/Autofleet/autorepo/issues"
45
+ },
46
+ "homepage": "https://github.com/Autofleet/autorepo/tree/master/packages/rapido-http-client#readme",
47
+ "dependencies": {
48
+ "undici": "^7.16.0"
49
+ },
50
+ "peerDependencies": {
51
+ "@autofleet/logger": ">=4.2.0",
52
+ "@autofleet/outbreak": ">=2",
53
+ "zod": ">=4"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "zod": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^20.0.0",
62
+ "nock": "^14.0.1",
63
+ "@autofleet/network": "^1.9.2",
64
+ "@autofleet/outbreak": "^2.5.8",
65
+ "@autofleet/logger": "^4.2.36"
66
+ },
67
+ "scripts": {
68
+ "test": "vitest",
69
+ "bench": "vitest bench",
70
+ "coverage": "vitest --coverage",
71
+ "build": "tsdown"
72
+ }
73
+ }