@forge/kvs

1.5.1-next.01.6.0-next.1
~

Modified (11 files)

Index: package/out/utils/error-handling.js
===================================================================
--- package/out/utils/error-handling.js
+++ package/out/utils/error-handling.js
@@ -1,7 +1,7 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.checkResponseError = exports.extractTraceId = exports.safeGetParsedBody = exports.isForgeError = void 0;
+exports.checkResponseError = exports.extractTraceId = exports.getAPIErrorResponseDetails = exports.safeGetParsedBody = exports.isForgeError = void 0;
 const errors_1 = require("../errors");
 function isForgeError(body) {
     return typeof body === 'object' && body !== null && 'code' in body && 'message' in body;
 }
@@ -14,22 +14,29 @@
         return undefined;
     }
 }
 exports.safeGetParsedBody = safeGetParsedBody;
+function getAPIErrorResponseDetails(response, responseText, requestContext) {
+    return {
+        status: response.status,
+        statusText: response.statusText,
+        traceId: extractTraceId(response),
+        httpMethod: requestContext?.httpMethod,
+        httpPath: requestContext?.httpPath,
+        responseBodyLength: responseText.length
+    };
+}
+exports.getAPIErrorResponseDetails = getAPIErrorResponseDetails;
 function extractTraceId(response) {
     return response.headers.get('x-b3-traceid') || response.headers.get('x-trace-id');
 }
 exports.extractTraceId = extractTraceId;
-async function checkResponseError(response) {
+async function checkResponseError(response, requestContext) {
     if (response.ok) {
         return;
     }
     const responseText = await response.text();
-    const details = {
-        status: response.status,
-        statusText: response.statusText,
-        traceId: extractTraceId(response)
-    };
+    const details = getAPIErrorResponseDetails(response, responseText, requestContext);
     const parsedBody = safeGetParsedBody(responseText);
     if (parsedBody && isForgeError(parsedBody)) {
         throw new errors_1.ForgeKvsAPIError(details, parsedBody);
     }
Index: package/out/utils/__test__/error-handling.test.js
===================================================================
--- package/out/utils/__test__/error-handling.test.js
+++ package/out/utils/__test__/error-handling.test.js
@@ -1,7 +1,8 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 const errors_1 = require("../../errors");
+const version_1 = require("../../version");
 const error_handling_1 = require("../error-handling");
 describe('error-handling', () => {
     it('isForgeError', () => {
         expect((0, error_handling_1.isForgeError)({ code: 'code', message: 'message' })).toBe(true);
@@ -29,57 +30,65 @@
         describe('Forge errors - ForgeKvsAPIError', () => {
             const message = 'A test error has occurred';
             const code = 'ERROR_CODE';
             it('should return a ForgeKvsAPIError when response body is a Forge error', async () => {
-                const mockResponse = new Response(JSON.stringify({ code, message }), {
+                const body = JSON.stringify({ code, message });
+                const mockResponse = new Response(body, {
                     status: 400,
                     statusText: 'Bad Request',
                     headers: { 'x-trace-id': traceId }
                 });
                 await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
                     status: 400,
                     statusText: 'Bad Request',
-                    traceId
+                    traceId,
+                    responseBodyLength: body.length
                 }, { code, message }));
             });
             it('should include context if present in the Forge error', async () => {
                 const context = { key: 'value' };
-                const mockResponse = new Response(JSON.stringify({ code, message, context }), {
+                const body = JSON.stringify({ code, message, context });
+                const mockResponse = new Response(body, {
                     status: 400,
                     statusText: 'Bad Request',
                     headers: { 'x-trace-id': traceId }
                 });
                 await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
                     status: 400,
                     statusText: 'Bad Request',
-                    traceId
+                    traceId,
+                    responseBodyLength: body.length
                 }, { code, message, context }));
             });
             it('should include top level additional fields if present in the Forge error', async () => {
                 const extraFields = { extraValue: 'value', debug: true };
-                const mockResponse = new Response(JSON.stringify({ code, message, ...extraFields }), {
+                const body = JSON.stringify({ code, message, ...extraFields });
+                const mockResponse = new Response(body, {
                     status: 400,
                     statusText: 'Bad Request',
                     headers: { 'x-trace-id': traceId }
                 });
                 await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
                     status: 400,
                     statusText: 'Bad Request',
-                    traceId
+                    traceId,
+                    responseBodyLength: body.length
                 }, { code, message, context: extraFields }));
             });
             it('should merge context and additional top level fields if both present in the Forge error', async () => {
                 const context = { key: 'value' };
                 const extraFields = { extraValue: 'value', debug: true };
-                const mockResponse = new Response(JSON.stringify({ code, message, context, ...extraFields }), {
+                const body = JSON.stringify({ code, message, context, ...extraFields });
+                const mockResponse = new Response(body, {
                     status: 400,
                     statusText: 'Bad Request',
                     headers: { 'x-trace-id': traceId }
                 });
                 await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toThrowError(new errors_1.ForgeKvsAPIError({
                     status: 400,
                     statusText: 'Bad Request',
-                    traceId
+                    traceId,
+                    responseBodyLength: body.length
                 }, { code, message, context: { ...context, ...extraFields } }));
             });
             describe('Handle non forge errors', () => {
                 it('returns an UNKNOWN_ERROR when no response body', async () => {
@@ -90,9 +99,10 @@
                     });
                     await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
                         status: 404,
                         statusText: 'Not Found',
-                        traceId
+                        traceId,
+                        responseBodyLength: 0
                     }, {
                         code: 'UNKNOWN_ERROR',
                         context: { responseText: '' },
                         message: 'Unexpected error in Forge KVS API'
@@ -107,15 +117,76 @@
                     });
                     await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
                         status: 500,
                         statusText: 'Internal Server Error',
-                        traceId
+                        traceId,
+                        responseBodyLength: body.length
                     }, {
                         code: 'UNKNOWN_ERROR',
                         context: { responseText: body },
                         message: 'Unexpected error in Forge KVS API'
                     }));
                 });
             });
         });
+        describe('with request context', () => {
+            const requestContext = {
+                httpMethod: 'POST',
+                httpPath: '/api/v1/get'
+            };
+            it('should include request context in ForgeKvsAPIError when provided', async () => {
+                const body = JSON.stringify({ code: 'ERROR', message: 'Test error' });
+                const mockResponse = new Response(body, {
+                    status: 400,
+                    statusText: 'Bad Request',
+                    headers: { 'x-trace-id': traceId }
+                });
+                await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse, requestContext)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+                    status: 400,
+                    statusText: 'Bad Request',
+                    traceId,
+                    httpMethod: 'POST',
+                    httpPath: '/api/v1/get',
+                    responseBodyLength: body.length
+                }, { code: 'ERROR', message: 'Test error' }));
+            });
+            it('should include request context in UNKNOWN_ERROR when provided', async () => {
+                const body = 'not valid json';
+                const mockResponse = new Response(body, {
+                    status: 500,
+                    statusText: 'Internal Server Error',
+                    headers: { 'x-trace-id': traceId }
+                });
+                await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse, requestContext)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+                    status: 500,
+                    statusText: 'Internal Server Error',
+                    traceId,
+                    httpMethod: 'POST',
+                    httpPath: '/api/v1/get',
+                    responseBodyLength: body.length
+                }, {
+                    code: 'UNKNOWN_ERROR',
+                    context: { responseText: body },
+                    message: 'Unexpected error in Forge KVS API'
+                }));
+            });
+        });
     });
+    describe('packageVersion', () => {
+        it('should auto-populate packageVersion on ForgeKvsError', () => {
+            const error = new errors_1.ForgeKvsError('Test error');
+            expect(error.packageVersion).toBeDefined();
+            expect(typeof error.packageVersion).toBe('string');
+            expect(error.packageVersion).toMatch(/^\d+\.\d+\.\d+/);
+        });
+        it('should auto-populate packageVersion on ForgeKvsAPIError', () => {
+            const error = new errors_1.ForgeKvsAPIError({ status: 500, statusText: 'Internal Server Error' }, { code: 'ERROR', message: 'Test error' });
+            expect(error.packageVersion).toBeDefined();
+            expect(typeof error.packageVersion).toBe('string');
+            expect(error.packageVersion).toMatch(/^\d+\.\d+\.\d+/);
+        });
+        it('should match the version in package.json', () => {
+            const error = new errors_1.ForgeKvsError('Test error');
+            expect(error.packageVersion).toBe(version_1.PACKAGE_VERSION);
+        });
+    });
 });
Index: package/out/errors.js
===================================================================
--- package/out/errors.js
+++ package/out/errors.js
@@ -1,11 +1,14 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.ForgeKvsAPIError = exports.ForgeKvsError = void 0;
+const version_1 = require("./version");
 class ForgeKvsError extends Error {
-    constructor(message) {
+    packageVersion;
+    constructor(message, packageVersion = version_1.PACKAGE_VERSION) {
         super(message);
         this.name = 'ForgeKvsError';
+        this.packageVersion = packageVersion;
     }
 }
 exports.ForgeKvsError = ForgeKvsError;
 class ForgeKvsAPIError extends ForgeKvsError {
@@ -14,10 +17,10 @@
     message;
     context;
     constructor(responseDetails, forgeError) {
         super(forgeError.message);
-        const { status, statusText, traceId } = responseDetails;
-        this.responseDetails = { status, statusText, traceId };
+        const { status, statusText, traceId, httpMethod, httpPath, responseBodyLength } = responseDetails;
+        this.responseDetails = { status, statusText, traceId, httpMethod, httpPath, responseBodyLength };
         const { code, message, context, ...bodyData } = forgeError;
         this.code = code;
         this.message = message;
         this.context = { ...context, ...bodyData };
Index: package/out/__test__/index.test.js
===================================================================
--- package/out/__test__/index.test.js
+++ package/out/__test__/index.test.js
@@ -101,27 +101,43 @@
             body: JSON.stringify({ key: 'foo' })
         }));
     });
     it('should handle unexpected metadata fields', async () => {
-        const response = new Response(JSON.stringify({
+        const body = JSON.stringify({
             code: 'BAD_REQUEST',
             message: 'Provided request body is invalid'
-        }), {
+        });
+        const response = new Response(body, {
             status: 400,
             statusText: 'Bad Request',
             headers: { 'x-trace-id': traceId }
         });
         const { sut } = prepare(response);
-        await expect(sut.get('foo', { metadataFields: ['INVALID_METADATA_FIELD'] })).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 400, statusText: 'Bad Request', traceId }, { code: 'BAD_REQUEST', message: 'Provided request body is invalid' }));
+        await expect(sut.get('foo', { metadataFields: ['INVALID_METADATA_FIELD'] })).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 400,
+            statusText: 'Bad Request',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/get',
+            responseBodyLength: body.length
+        }, { code: 'BAD_REQUEST', message: 'Provided request body is invalid' }));
     });
     it('should handle unexpected response', async () => {
-        const response = new Response(JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }), {
+        const body = JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' });
+        const response = new Response(body, {
             status: 500,
             statusText: 'Internal Server Error',
             headers: { 'x-trace-id': traceId }
         });
         const { sut } = prepare(response);
-        await expect(sut.get('foo')).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 500, statusText: 'Internal Server Error', traceId }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
+        await expect(sut.get('foo')).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 500,
+            statusText: 'Internal Server Error',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/get',
+            responseBodyLength: body.length
+        }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
     });
     it('should handle non-json response even though status is ok', async () => {
         const responseText = 'Text that will fail to parse';
         const response = new Response(responseText, {
@@ -129,9 +145,16 @@
             statusText: 'OK',
             headers: { 'x-trace-id': traceId, 'content-length': responseText.length.toString() }
         });
         const { sut } = prepare(response);
-        await expect(sut.get('foo')).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 200, statusText: 'OK', traceId }, {
+        await expect(sut.get('foo')).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 200,
+            statusText: 'OK',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/get',
+            responseBodyLength: responseText.length
+        }, {
             code: 'UNKNOWN_ERROR',
             message: 'Unexpected error in Forge KVS API. Response was not valid JSON',
             context: { contentLength: responseText.length.toString() }
         }));
@@ -1177,16 +1200,24 @@
             body: JSON.stringify(items)
         }));
     });
     it('should handle batchSet API error response', async () => {
-        const response = new Response(JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }), {
+        const body = JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' });
+        const response = new Response(body, {
             status: 500,
             statusText: 'Internal Server Error',
             headers: { 'x-trace-id': traceId }
         });
         const { sut } = prepare(response);
         const items = [{ key: 'foo', value: 'bar' }];
-        await expect(sut.batchSet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 500, statusText: 'Internal Server Error', traceId }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
+        await expect(sut.batchSet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 500,
+            statusText: 'Internal Server Error',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/batch/set',
+            responseBodyLength: body.length
+        }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
     });
     it('should handle batchDelete correctly with mixed entity and non-entity items', async () => {
         const response = new Response(JSON.stringify({
             successfulKeys: [{ key: 'foo', entityName: 'employees' }, { key: 'bar' }],
@@ -1276,16 +1307,24 @@
             body: JSON.stringify(items)
         }));
     });
     it('should handle batchDelete API error response', async () => {
-        const response = new Response(JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }), {
+        const body = JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' });
+        const response = new Response(body, {
             status: 500,
             statusText: 'Internal Server Error',
             headers: { 'x-trace-id': traceId }
         });
         const { sut } = prepare(response);
         const items = [{ key: 'foo' }];
-        await expect(sut.batchDelete(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 500, statusText: 'Internal Server Error', traceId }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
+        await expect(sut.batchDelete(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 500,
+            statusText: 'Internal Server Error',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/batch/delete',
+            responseBodyLength: body.length
+        }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
     });
     it('should handle batchGet with mixed entity and non-entity items', async () => {
         const response = new Response(JSON.stringify({
             successfulKeys: [
@@ -1450,22 +1489,31 @@
             body: JSON.stringify(items)
         }));
     });
     it('should handle batchGet API error response', async () => {
-        const response = new Response(JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }), {
+        const body = JSON.stringify({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' });
+        const response = new Response(body, {
             status: 500,
             statusText: 'Internal Server Error',
             headers: { 'x-trace-id': traceId }
         });
         const { sut } = prepare(response);
         const items = [{ key: 'foo' }];
-        await expect(sut.batchGet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 500, statusText: 'Internal Server Error', traceId }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
+        await expect(sut.batchGet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 500,
+            statusText: 'Internal Server Error',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/batch/get',
+            responseBodyLength: body.length
+        }, { code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error has occurred' }));
     });
     it('should handle batchGet with invalid metadata fields', async () => {
-        const response = new Response(JSON.stringify({
+        const body = JSON.stringify({
             code: 'BAD_REQUEST',
             message: 'Provided request body is invalid'
-        }), {
+        });
+        const response = new Response(body, {
             status: 400,
             statusText: 'Bad Request',
             headers: { 'x-trace-id': traceId }
         });
@@ -1475,7 +1523,14 @@
                 key: 'foo',
                 options: { metadataFields: ['INVALID_METADATA_FIELD'] }
             }
         ];
-        await expect(sut.batchGet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({ status: 400, statusText: 'Bad Request', traceId }, { code: 'BAD_REQUEST', message: 'Provided request body is invalid' }));
+        await expect(sut.batchGet(items)).rejects.toMatchError(new errors_1.ForgeKvsAPIError({
+            status: 400,
+            statusText: 'Bad Request',
+            traceId,
+            httpMethod: 'POST',
+            httpPath: '/api/v1/batch/get',
+            responseBodyLength: body.length
+        }, { code: 'BAD_REQUEST', message: 'Provided request body is invalid' }));
     });
 });
Index: package/out/storage-api.js
===================================================================
--- package/out/storage-api.js
+++ package/out/storage-api.js
@@ -117,9 +117,13 @@
                 'content-type': 'application/json'
             }
         };
         const response = await this.apiClient(path, requestBody);
-        await (0, error_handling_1.checkResponseError)(response);
+        const requestContext = {
+            httpMethod: requestBody.method,
+            httpPath: path
+        };
+        await (0, error_handling_1.checkResponseError)(response, requestContext);
         if (responseType === ResponseType.NONE) {
             return;
         }
         const responseText = await response.text();
@@ -127,13 +131,9 @@
             return undefined;
         }
         const parsedBody = (0, error_handling_1.safeGetParsedBody)(responseText);
         if (parsedBody === undefined) {
-            const details = {
-                status: response.status,
-                statusText: response.statusText,
-                traceId: (0, error_handling_1.extractTraceId)(response)
-            };
+            const details = (0, error_handling_1.getAPIErrorResponseDetails)(response, responseText, requestContext);
             throw new errors_1.ForgeKvsAPIError(details, {
                 code: 'UNKNOWN_ERROR',
                 message: 'Unexpected error in Forge KVS API. Response was not valid JSON',
                 context: { contentLength: response.headers.get('content-length') }
Index: package/package.json
===================================================================
--- package/package.json
+++ package/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@forge/kvs",
-  "version": "1.5.1-next.0",
+  "version": "1.6.0-next.1",
   "description": "Forge Key Value Store SDK",
   "author": "Atlassian",
   "license": "SEE LICENSE IN LICENSE.txt",
   "main": "out/index.js",
Index: package/out/utils/error-handling.d.ts.map
===================================================================
--- package/out/utils/error-handling.d.ts.map
+++ package/out/utils/error-handling.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"error-handling.d.ts","sourceRoot":"","sources":["../../src/utils/error-handling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAA2B,UAAU,EAAoB,MAAM,WAAW,CAAC;AAElF,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,UAAU,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAMnE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAEnE;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB7E"}
\ No newline at end of file
+{"version":3,"file":"error-handling.d.ts","sourceRoot":"","sources":["../../src/utils/error-handling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAC;AAElF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,UAAU,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAMnE;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,WAAW,EACrB,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,cAAc,GAC9B,uBAAuB,CASzB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAEnE;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB9G"}
\ No newline at end of file
Index: package/out/errors.d.ts.map
===================================================================
--- package/out/errors.d.ts.map
+++ package/out/errors.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,eAAe,EAAE,uBAAuB,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAErB,eAAe,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU;CAU7E"}
\ No newline at end of file
+{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,cAAc,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,MAAwB;CAKtE;AAED,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,eAAe,EAAE,uBAAuB,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAErB,eAAe,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU;CAU7E"}
\ No newline at end of file
Index: package/out/storage-api.d.ts.map
===================================================================
--- package/out/storage-api.d.ts.map
+++ package/out/storage-api.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"storage-api.d.ts","sourceRoot":"","sources":["../src/storage-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EAGX,SAAS,EAGT,cAAc,EACf,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,kBAAkB,EAElB,eAAe,EACf,eAAe,EAEf,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAEhB,kBAAkB,EAElB,gBAAgB,EAChB,UAAU,EAEV,YAAY,EAEZ,mBAAmB,EACnB,gBAAgB,EAEhB,gBAAgB,EAChB,UAAU,EAEV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAQ9B,qBAAa,UAAU;IACT,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,WAAW;IAEpC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO/D,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO3E,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO3E,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKrE,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQpD,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQhE,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3D,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3D,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAQ9D,QAAQ,CAAC,CAAC,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAI7D,iBAAiB;YAWjB,OAAO;IA6CrB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,kBAAkB;CAgB3B"}
\ No newline at end of file
+{"version":3,"file":"storage-api.d.ts","sourceRoot":"","sources":["../src/storage-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,EAGX,SAAS,EAGT,cAAc,EACf,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,EACL,kBAAkB,EAElB,eAAe,EACf,eAAe,EAEf,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAEhB,kBAAkB,EAElB,gBAAgB,EAChB,UAAU,EAEV,YAAY,EAEZ,mBAAmB,EACnB,gBAAgB,EAEhB,gBAAgB,EAChB,UAAU,EAEV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAQ9B,qBAAa,UAAU;IACT,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,WAAW;IAEpC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO/D,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO3E,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAO3E,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKrE,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQpD,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAQhE,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3D,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3D,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAQ9D,QAAQ,CAAC,CAAC,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAI7D,iBAAiB;YAWjB,OAAO;IA8CrB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,kBAAkB;CAgB3B"}
\ No newline at end of file
Index: package/out/utils/error-handling.d.ts
===================================================================
--- package/out/utils/error-handling.d.ts
+++ package/out/utils/error-handling.d.ts
@@ -1,7 +1,12 @@
 import { APIResponse } from '@forge/api';
-import { ForgeError } from '../errors';
+import { APIErrorResponseDetails, ForgeError } from '../errors';
+export interface RequestContext {
+    httpMethod: string;
+    httpPath: string;
+}
 export declare function isForgeError(body: unknown): body is ForgeError;
 export declare function safeGetParsedBody(text: string): unknown | undefined;
+export declare function getAPIErrorResponseDetails(response: APIResponse, responseText: string, requestContext?: RequestContext): APIErrorResponseDetails;
 export declare function extractTraceId(response: APIResponse): string | null;
-export declare function checkResponseError(response: APIResponse): Promise<void>;
+export declare function checkResponseError(response: APIResponse, requestContext?: RequestContext): Promise<void>;
 //# sourceMappingURL=error-handling.d.ts.map
\ No newline at end of file
Index: package/out/errors.d.ts
===================================================================
--- package/out/errors.d.ts
+++ package/out/errors.d.ts
@@ -6,11 +6,15 @@
 export interface APIErrorResponseDetails {
     status: number;
     statusText: string;
     traceId?: string | null;
+    httpMethod?: string;
+    httpPath?: string;
+    responseBodyLength?: number;
 }
 export declare class ForgeKvsError extends Error {
-    constructor(message: string);
+    packageVersion: string;
+    constructor(message: string, packageVersion?: string);
 }
 export declare class ForgeKvsAPIError extends ForgeKvsError {
     responseDetails: APIErrorResponseDetails;
     code: string;