@forge/object-store 1.1.0-next.1 → 1.1.1-next.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/out/__test__/object-store.test.d.ts +2 -0
- package/out/__test__/object-store.test.d.ts.map +1 -0
- package/out/__test__/object-store.test.js +166 -0
- package/out/errorCodes.d.ts +6 -0
- package/out/errorCodes.d.ts.map +1 -0
- package/out/errorCodes.js +8 -0
- package/out/errors.d.ts +21 -0
- package/out/errors.d.ts.map +1 -0
- package/out/errors.js +26 -0
- package/out/index.d.ts +7 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +8 -0
- package/out/object-store.d.ts +11 -0
- package/out/object-store.d.ts.map +1 -0
- package/out/object-store.js +66 -0
- package/out/types.d.ts +20 -0
- package/out/types.d.ts.map +1 -0
- package/out/types.js +2 -0
- package/out/utils/__test__/error-handling.test.d.ts +2 -0
- package/out/utils/__test__/error-handling.test.d.ts.map +1 -0
- package/out/utils/__test__/error-handling.test.js +137 -0
- package/out/utils/error-handling.d.ts +7 -0
- package/out/utils/error-handling.d.ts.map +1 -0
- package/out/utils/error-handling.js +49 -0
- package/out/utils/types.d.ts +3 -0
- package/out/utils/types.d.ts.map +1 -0
- package/out/utils/types.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store.test.d.ts","sourceRoot":"","sources":["../../src/__test__/object-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const api_1 = require("@forge/api");
|
|
4
|
+
const object_store_1 = require("../object-store");
|
|
5
|
+
jest.mock('@forge/api');
|
|
6
|
+
const DEFAULT_HEADERS = {
|
|
7
|
+
Accept: 'application/json',
|
|
8
|
+
'Content-Type': 'application/json'
|
|
9
|
+
};
|
|
10
|
+
const createMockBody = (body) => {
|
|
11
|
+
return jest.fn().mockResolvedValue(body);
|
|
12
|
+
};
|
|
13
|
+
describe('ObjectStoreClient', () => {
|
|
14
|
+
let osClient;
|
|
15
|
+
let mockFetch;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
osClient = new object_store_1.ObjectStoreClient();
|
|
18
|
+
mockFetch = jest.fn();
|
|
19
|
+
api_1.__fetchProduct.mockReturnValue(mockFetch);
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
describe('sendRequest', () => {
|
|
23
|
+
it('should send a request with the correct options and return the response', async () => {
|
|
24
|
+
const mockResponse = { ok: true, status: 200 };
|
|
25
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
26
|
+
const path = 'api/v1/objects/1';
|
|
27
|
+
const options = { method: 'GET', headers: { ...DEFAULT_HEADERS, 'Custom-Header': 'value' } };
|
|
28
|
+
const response = await osClient['sendRequest'](path, options);
|
|
29
|
+
expect(mockFetch).toHaveBeenCalledWith(path, {
|
|
30
|
+
...options,
|
|
31
|
+
redirect: 'follow',
|
|
32
|
+
headers: {
|
|
33
|
+
...options.headers,
|
|
34
|
+
...DEFAULT_HEADERS
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
expect(response).toBe(mockResponse);
|
|
38
|
+
});
|
|
39
|
+
it('should handle requests without options', async () => {
|
|
40
|
+
const mockResponse = { ok: true, status: 200 };
|
|
41
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
42
|
+
const path = 'api/v1/objects/1';
|
|
43
|
+
const response = await osClient['sendRequest'](path);
|
|
44
|
+
expect(mockFetch).toHaveBeenCalledWith(path, {
|
|
45
|
+
redirect: 'follow'
|
|
46
|
+
});
|
|
47
|
+
expect(response).toBe(mockResponse);
|
|
48
|
+
});
|
|
49
|
+
it('should handle requests that set headers', async () => {
|
|
50
|
+
const mockResponse = { ok: true, status: 200 };
|
|
51
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
52
|
+
const path = 'api/v1/objects/1';
|
|
53
|
+
const options = { method: 'GET', headers: { 'Content-Type': 'application/octet-stream' } };
|
|
54
|
+
const response = await osClient['sendRequest'](path, options);
|
|
55
|
+
expect(mockFetch).toHaveBeenCalledWith(path, {
|
|
56
|
+
redirect: 'follow',
|
|
57
|
+
method: 'GET',
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'application/octet-stream'
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
expect(response).toBe(mockResponse);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('get', () => {
|
|
66
|
+
it('should send a POST request with the correct headers and body', async () => {
|
|
67
|
+
const mockResponse = {
|
|
68
|
+
ok: true,
|
|
69
|
+
status: 200,
|
|
70
|
+
text: createMockBody(JSON.stringify({ message: 'Blåhaj' }))
|
|
71
|
+
};
|
|
72
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
73
|
+
const objectId = '1';
|
|
74
|
+
const response = await osClient.get(objectId);
|
|
75
|
+
expect(mockFetch).toHaveBeenCalledWith('api/v1/metadata', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
redirect: 'follow',
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: 'application/json',
|
|
80
|
+
'Content-Type': 'application/json'
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
key: objectId
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
expect(mockResponse.status).toBe(200);
|
|
87
|
+
expect(mockResponse.text).toHaveBeenCalled();
|
|
88
|
+
expect(response).toEqual({ message: 'Blåhaj' });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('delete', () => {
|
|
92
|
+
it('should send a POST request with the correct headers and body', async () => {
|
|
93
|
+
const mockResponse = { ok: true, status: 204, text: createMockBody(null) };
|
|
94
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
95
|
+
const objectId = '1';
|
|
96
|
+
await osClient.delete(objectId);
|
|
97
|
+
expect(mockFetch).toHaveBeenCalledWith('api/v1/delete', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
redirect: 'follow',
|
|
100
|
+
headers: {
|
|
101
|
+
Accept: 'application/json',
|
|
102
|
+
'Content-Type': 'application/json'
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ key: objectId })
|
|
105
|
+
});
|
|
106
|
+
expect(mockResponse.status).toBe(204);
|
|
107
|
+
expect(mockResponse.text).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('createUploadUrl', () => {
|
|
111
|
+
it('should send a POST request with the correct headers and body, and return a URL', async () => {
|
|
112
|
+
const mockResponse = {
|
|
113
|
+
ok: true,
|
|
114
|
+
status: 200,
|
|
115
|
+
text: createMockBody(JSON.stringify({ url: 'upload-url' }))
|
|
116
|
+
};
|
|
117
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
118
|
+
const objectId = '1';
|
|
119
|
+
const fileProperties = {
|
|
120
|
+
key: objectId,
|
|
121
|
+
length: 123,
|
|
122
|
+
checksum: 'abc123',
|
|
123
|
+
checksumType: 'SHA1',
|
|
124
|
+
ttlSeconds: 3600,
|
|
125
|
+
overwrite: true
|
|
126
|
+
};
|
|
127
|
+
const response = await osClient.createUploadUrl(fileProperties);
|
|
128
|
+
expect(mockFetch).toHaveBeenCalledWith('api/v1/upload-url', {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
redirect: 'follow',
|
|
131
|
+
headers: {
|
|
132
|
+
Accept: 'application/json',
|
|
133
|
+
'Content-Type': 'application/json'
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify(fileProperties)
|
|
136
|
+
});
|
|
137
|
+
expect(mockResponse.status).toBe(200);
|
|
138
|
+
expect(mockResponse.text).toHaveBeenCalled();
|
|
139
|
+
expect(response).toEqual({ url: 'upload-url' });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('createDownloadUrl', () => {
|
|
143
|
+
it('should send a POST request with the correct headers and body, and return a URL', async () => {
|
|
144
|
+
const mockResponse = {
|
|
145
|
+
ok: true,
|
|
146
|
+
status: 200,
|
|
147
|
+
text: createMockBody(JSON.stringify({ url: 'download-url' }))
|
|
148
|
+
};
|
|
149
|
+
mockFetch.mockResolvedValue(mockResponse);
|
|
150
|
+
const objectId = '1';
|
|
151
|
+
const response = await osClient.createDownloadUrl(objectId);
|
|
152
|
+
expect(mockFetch).toHaveBeenCalledWith('api/v1/download-url', {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
redirect: 'follow',
|
|
155
|
+
headers: {
|
|
156
|
+
Accept: 'application/json',
|
|
157
|
+
'Content-Type': 'application/json'
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({ key: objectId })
|
|
160
|
+
});
|
|
161
|
+
expect(mockResponse.status).toBe(200);
|
|
162
|
+
expect(mockResponse.text).toHaveBeenCalled();
|
|
163
|
+
expect(response).toEqual({ url: 'download-url' });
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorCodes.d.ts","sourceRoot":"","sources":["../src/errorCodes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;CAIb,CAAC"}
|
package/out/errors.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ForgeError {
|
|
2
|
+
code: string;
|
|
3
|
+
message: string;
|
|
4
|
+
context?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface APIErrorResponseDetails {
|
|
7
|
+
status: number;
|
|
8
|
+
statusText: string;
|
|
9
|
+
traceId?: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare class ForgeObjectStoreError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class ForgeObjectStoreAPIError extends ForgeObjectStoreError {
|
|
15
|
+
responseDetails: APIErrorResponseDetails;
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
context: Record<string, unknown>;
|
|
19
|
+
constructor(responseDetails: APIErrorResponseDetails, forgeError: ForgeError);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,wBAAyB,SAAQ,qBAAqB;IACjE,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"}
|
package/out/errors.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForgeObjectStoreAPIError = exports.ForgeObjectStoreError = void 0;
|
|
4
|
+
class ForgeObjectStoreError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'ForgeObjectStoreError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ForgeObjectStoreError = ForgeObjectStoreError;
|
|
11
|
+
class ForgeObjectStoreAPIError extends ForgeObjectStoreError {
|
|
12
|
+
responseDetails;
|
|
13
|
+
code;
|
|
14
|
+
message;
|
|
15
|
+
context;
|
|
16
|
+
constructor(responseDetails, forgeError) {
|
|
17
|
+
super(forgeError.message);
|
|
18
|
+
const { status, statusText, traceId } = responseDetails;
|
|
19
|
+
this.responseDetails = { status, statusText, traceId };
|
|
20
|
+
const { code, message, context, ...bodyData } = forgeError;
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.message = message;
|
|
23
|
+
this.context = { ...context, ...bodyData };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.ForgeObjectStoreAPIError = ForgeObjectStoreAPIError;
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { objectStore } from './object-store';
|
|
2
|
+
import { errorCodes } from './errorCodes';
|
|
3
|
+
import { ForgeError } from './errors';
|
|
4
|
+
import { ObjectReference } from './types';
|
|
5
|
+
export { errorCodes, objectStore, ForgeError, ObjectReference };
|
|
6
|
+
export default objectStore;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;AAEhE,eAAe,WAAW,CAAC"}
|
package/out/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.objectStore = exports.errorCodes = void 0;
|
|
4
|
+
const object_store_1 = require("./object-store");
|
|
5
|
+
Object.defineProperty(exports, "objectStore", { enumerable: true, get: function () { return object_store_1.objectStore; } });
|
|
6
|
+
const errorCodes_1 = require("./errorCodes");
|
|
7
|
+
Object.defineProperty(exports, "errorCodes", { enumerable: true, get: function () { return errorCodes_1.errorCodes; } });
|
|
8
|
+
exports.default = object_store_1.objectStore;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ObjectReference, PresignedUrlResponse, UploadUrlBody } from './types';
|
|
2
|
+
export declare class ObjectStoreClient {
|
|
3
|
+
private sendRequest;
|
|
4
|
+
private requestJson;
|
|
5
|
+
get(key: string): Promise<ObjectReference | undefined>;
|
|
6
|
+
delete(key: string): Promise<void>;
|
|
7
|
+
createUploadUrl(body: UploadUrlBody): Promise<PresignedUrlResponse | undefined>;
|
|
8
|
+
createDownloadUrl(key: string): Promise<PresignedUrlResponse | undefined>;
|
|
9
|
+
}
|
|
10
|
+
export declare const objectStore: ObjectStoreClient;
|
|
11
|
+
//# sourceMappingURL=object-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store.d.ts","sourceRoot":"","sources":["../src/object-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAS/E,qBAAa,iBAAiB;YAEd,WAAW;YASX,WAAW;IAmCZ,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAgBtD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalC,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAgB/E,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;CAWvF;AAED,eAAO,MAAM,WAAW,mBAA0B,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.objectStore = exports.ObjectStoreClient = void 0;
|
|
4
|
+
const api_1 = require("@forge/api");
|
|
5
|
+
const error_handling_1 = require("./utils/error-handling");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
var ValidHttpMethod;
|
|
8
|
+
(function (ValidHttpMethod) {
|
|
9
|
+
ValidHttpMethod["PUT"] = "PUT";
|
|
10
|
+
ValidHttpMethod["GET"] = "GET";
|
|
11
|
+
ValidHttpMethod["DELETE"] = "DELETE";
|
|
12
|
+
ValidHttpMethod["POST"] = "POST";
|
|
13
|
+
})(ValidHttpMethod || (ValidHttpMethod = {}));
|
|
14
|
+
class ObjectStoreClient {
|
|
15
|
+
async sendRequest(path, options) {
|
|
16
|
+
return await (0, api_1.__fetchProduct)({ provider: 'app', remote: 'os', type: 'os' })(path, {
|
|
17
|
+
...options,
|
|
18
|
+
redirect: 'follow',
|
|
19
|
+
headers: options?.headers
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async requestJson(method, path, headers, body) {
|
|
23
|
+
const response = await this.sendRequest(path, {
|
|
24
|
+
method,
|
|
25
|
+
headers,
|
|
26
|
+
body
|
|
27
|
+
});
|
|
28
|
+
if (response.status === 404) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
await (0, error_handling_1.checkResponseError)(response);
|
|
32
|
+
const responseText = await response.text();
|
|
33
|
+
if (!responseText || responseText.trim().length === 0) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(responseText);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new errors_1.ForgeObjectStoreError(`Unexpected error. Response was not valid JSON: ${responseText}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async get(key) {
|
|
44
|
+
return this.requestJson(ValidHttpMethod.POST, `api/v1/metadata`, {
|
|
45
|
+
Accept: 'application/json',
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
}, JSON.stringify({ key }));
|
|
48
|
+
}
|
|
49
|
+
async delete(key) {
|
|
50
|
+
return this.requestJson(ValidHttpMethod.POST, `api/v1/delete`, { Accept: 'application/json', 'Content-Type': 'application/json' }, JSON.stringify({ key }));
|
|
51
|
+
}
|
|
52
|
+
async createUploadUrl(body) {
|
|
53
|
+
return this.requestJson(ValidHttpMethod.POST, 'api/v1/upload-url', {
|
|
54
|
+
Accept: 'application/json',
|
|
55
|
+
'Content-Type': 'application/json'
|
|
56
|
+
}, JSON.stringify(body));
|
|
57
|
+
}
|
|
58
|
+
async createDownloadUrl(key) {
|
|
59
|
+
return this.requestJson(ValidHttpMethod.POST, `api/v1/download-url`, {
|
|
60
|
+
Accept: 'application/json',
|
|
61
|
+
'Content-Type': 'application/json'
|
|
62
|
+
}, JSON.stringify({ key }));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ObjectStoreClient = ObjectStoreClient;
|
|
66
|
+
exports.objectStore = new ObjectStoreClient();
|
package/out/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ObjectReference {
|
|
2
|
+
key: string;
|
|
3
|
+
checksum: string;
|
|
4
|
+
size: number;
|
|
5
|
+
createdAt?: string;
|
|
6
|
+
currentVersion?: string;
|
|
7
|
+
contentType?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UploadUrlBody {
|
|
10
|
+
key: string;
|
|
11
|
+
length: number;
|
|
12
|
+
checksum: string;
|
|
13
|
+
checksumType: 'SHA1' | 'SHA256' | 'CRC32' | 'CRC32C';
|
|
14
|
+
ttlSeconds?: number;
|
|
15
|
+
overwrite?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface PresignedUrlResponse {
|
|
18
|
+
url: string;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;CACb"}
|
package/out/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handling.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__test__/error-handling.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const errorCodes_1 = require("../../errorCodes");
|
|
4
|
+
const error_handling_1 = require("../error-handling");
|
|
5
|
+
const errors_1 = require("../../errors");
|
|
6
|
+
describe('error-handling', () => {
|
|
7
|
+
it('isForgeError', () => {
|
|
8
|
+
expect((0, error_handling_1.isForgeError)({ code: 'code', message: 'message' })).toBe(true);
|
|
9
|
+
expect((0, error_handling_1.isForgeError)({ code: 'code' })).toBe(false);
|
|
10
|
+
expect((0, error_handling_1.isForgeError)({ message: 'message' })).toBe(false);
|
|
11
|
+
expect((0, error_handling_1.isForgeError)({})).toBe(false);
|
|
12
|
+
expect((0, error_handling_1.isForgeError)(null)).toBe(false);
|
|
13
|
+
expect((0, error_handling_1.isForgeError)(undefined)).toBe(false);
|
|
14
|
+
expect((0, error_handling_1.isForgeError)('string')).toBe(false);
|
|
15
|
+
expect((0, error_handling_1.isForgeError)(123)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('safeGetParsedBody', () => {
|
|
18
|
+
expect((0, error_handling_1.safeGetParsedBody)('{"valid": "json"}')).toEqual({ valid: 'json' });
|
|
19
|
+
expect((0, error_handling_1.safeGetParsedBody)('invalid json')).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
describe('checkResponseError', () => {
|
|
22
|
+
const traceId = 'trace-id';
|
|
23
|
+
it('should do nothing if response is ok', async () => {
|
|
24
|
+
const mockResponse = new Response('OK', {
|
|
25
|
+
status: 200,
|
|
26
|
+
statusText: 'OK'
|
|
27
|
+
});
|
|
28
|
+
await expect((0, error_handling_1.checkResponseError)(mockResponse)).resolves.toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
describe('Forge errors - ForgeObjectStoreAPIError', () => {
|
|
31
|
+
const message = 'A test error has occurred';
|
|
32
|
+
const code = 'ERROR_CODE';
|
|
33
|
+
it('should return a ForgeObjectStoreError when response body is a Forge error', async () => {
|
|
34
|
+
const mockResponse = new Response(JSON.stringify({ code, message }), {
|
|
35
|
+
status: 400,
|
|
36
|
+
statusText: 'Bad Request',
|
|
37
|
+
headers: { 'x-trace-id': traceId }
|
|
38
|
+
});
|
|
39
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
40
|
+
status: 400,
|
|
41
|
+
statusText: 'Bad Request',
|
|
42
|
+
traceId
|
|
43
|
+
}, { code, message }));
|
|
44
|
+
});
|
|
45
|
+
it('should include context if present in the Forge error', async () => {
|
|
46
|
+
const context = { key: 'value' };
|
|
47
|
+
const mockResponse = new Response(JSON.stringify({ code, message, context }), {
|
|
48
|
+
status: 400,
|
|
49
|
+
statusText: 'Bad Request',
|
|
50
|
+
headers: { 'x-trace-id': traceId }
|
|
51
|
+
});
|
|
52
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
53
|
+
status: 400,
|
|
54
|
+
statusText: 'Bad Request',
|
|
55
|
+
traceId
|
|
56
|
+
}, { code, message, context }));
|
|
57
|
+
});
|
|
58
|
+
it('should include top level additional fields if present in the Forge error', async () => {
|
|
59
|
+
const extraFields = { extraValue: 'value', debug: true };
|
|
60
|
+
const mockResponse = new Response(JSON.stringify({ code, message, ...extraFields }), {
|
|
61
|
+
status: 400,
|
|
62
|
+
statusText: 'Bad Request',
|
|
63
|
+
headers: { 'x-trace-id': traceId }
|
|
64
|
+
});
|
|
65
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
66
|
+
status: 400,
|
|
67
|
+
statusText: 'Bad Request',
|
|
68
|
+
traceId
|
|
69
|
+
}, { code, message, context: extraFields }));
|
|
70
|
+
});
|
|
71
|
+
it('should merge context and additional top level fields if both present in the Forge error', async () => {
|
|
72
|
+
const context = { key: 'value' };
|
|
73
|
+
const extraFields = { extraValue: 'value', debug: true };
|
|
74
|
+
const mockResponse = new Response(JSON.stringify({ code, message, context, ...extraFields }), {
|
|
75
|
+
status: 400,
|
|
76
|
+
statusText: 'Bad Request',
|
|
77
|
+
headers: { 'x-trace-id': traceId }
|
|
78
|
+
});
|
|
79
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toThrowError(new errors_1.ForgeObjectStoreAPIError({
|
|
80
|
+
status: 400,
|
|
81
|
+
statusText: 'Bad Request',
|
|
82
|
+
traceId
|
|
83
|
+
}, { code, message, context: { ...context, ...extraFields } }));
|
|
84
|
+
});
|
|
85
|
+
describe('Handle non forge errors', () => {
|
|
86
|
+
it('returns an UNKNOWN_ERROR when no response body', async () => {
|
|
87
|
+
const mockResponse = new Response(undefined, {
|
|
88
|
+
status: 404,
|
|
89
|
+
statusText: 'Not Found',
|
|
90
|
+
headers: { 'x-trace-id': traceId }
|
|
91
|
+
});
|
|
92
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
93
|
+
status: 404,
|
|
94
|
+
statusText: 'Not Found',
|
|
95
|
+
traceId
|
|
96
|
+
}, {
|
|
97
|
+
code: errorCodes_1.errorCodes.UNKNOWN_ERROR,
|
|
98
|
+
context: { responseText: '' },
|
|
99
|
+
message: 'Unexpected error in Forge OS API request'
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
it("returns UNKNOWN_ERROR if there is a JSON body that isn't a forge error", async () => {
|
|
103
|
+
const body = JSON.stringify({ not: 'a forge error' });
|
|
104
|
+
const mockResponse = new Response(body, {
|
|
105
|
+
status: 500,
|
|
106
|
+
statusText: 'Internal Server Error',
|
|
107
|
+
headers: { 'x-trace-id': traceId }
|
|
108
|
+
});
|
|
109
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
110
|
+
status: 500,
|
|
111
|
+
statusText: 'Internal Server Error',
|
|
112
|
+
traceId
|
|
113
|
+
}, {
|
|
114
|
+
code: errorCodes_1.errorCodes.UNKNOWN_ERROR,
|
|
115
|
+
context: { responseText: body },
|
|
116
|
+
message: 'Unexpected error in Forge OS API request'
|
|
117
|
+
}));
|
|
118
|
+
});
|
|
119
|
+
it('returns RATE_LIMIT_EXCEEDED when status is 429', async () => {
|
|
120
|
+
const mockResponse = new Response(undefined, {
|
|
121
|
+
status: 429,
|
|
122
|
+
statusText: 'Rate Limit Exceeded',
|
|
123
|
+
headers: { 'x-trace-id': traceId }
|
|
124
|
+
});
|
|
125
|
+
await expect(async () => await (0, error_handling_1.checkResponseError)(mockResponse)).rejects.toMatchError(new errors_1.ForgeObjectStoreAPIError({
|
|
126
|
+
status: 429,
|
|
127
|
+
statusText: 'Rate Limit Exceeded',
|
|
128
|
+
traceId
|
|
129
|
+
}, {
|
|
130
|
+
code: errorCodes_1.errorCodes.RATE_LIMIT_EXCEEDED,
|
|
131
|
+
message: 'You have exceeded the rate limits for OS. Please wait and try again later.'
|
|
132
|
+
}));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { APIResponse } from '@forge/api';
|
|
2
|
+
import { ForgeError } from '../errors';
|
|
3
|
+
export declare function isForgeError(body: unknown): body is ForgeError;
|
|
4
|
+
export declare function safeGetParsedBody(text: string): unknown | undefined;
|
|
5
|
+
export declare function extractTraceId(response: APIResponse): string | null;
|
|
6
|
+
export declare function checkResponseError(response: APIResponse, message?: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=error-handling.d.ts.map
|
|
@@ -0,0 +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,EAA4B,MAAM,WAAW,CAAC;AAG1F,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,CAInE;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B/F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkResponseError = exports.extractTraceId = exports.safeGetParsedBody = exports.isForgeError = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
const errorCodes_1 = require("../errorCodes");
|
|
6
|
+
function isForgeError(body) {
|
|
7
|
+
return typeof body === 'object' && body !== null && 'code' in body && 'message' in body;
|
|
8
|
+
}
|
|
9
|
+
exports.isForgeError = isForgeError;
|
|
10
|
+
function safeGetParsedBody(text) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(text);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.safeGetParsedBody = safeGetParsedBody;
|
|
19
|
+
function extractTraceId(response) {
|
|
20
|
+
return (response.headers.get('x-b3-traceid') || response.headers.get('x-trace-id') || response.headers.get('atl-traceid'));
|
|
21
|
+
}
|
|
22
|
+
exports.extractTraceId = extractTraceId;
|
|
23
|
+
async function checkResponseError(response, message) {
|
|
24
|
+
if (response.ok) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const responseText = await response.text();
|
|
28
|
+
const details = {
|
|
29
|
+
status: response.status,
|
|
30
|
+
statusText: response.statusText,
|
|
31
|
+
traceId: extractTraceId(response)
|
|
32
|
+
};
|
|
33
|
+
if (details.status === 429) {
|
|
34
|
+
throw new errors_1.ForgeObjectStoreAPIError(details, {
|
|
35
|
+
code: errorCodes_1.errorCodes.RATE_LIMIT_EXCEEDED,
|
|
36
|
+
message: 'You have exceeded the rate limits for OS. Please wait and try again later.'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const parsedBody = safeGetParsedBody(responseText);
|
|
40
|
+
if (parsedBody && isForgeError(parsedBody)) {
|
|
41
|
+
throw new errors_1.ForgeObjectStoreAPIError(details, parsedBody);
|
|
42
|
+
}
|
|
43
|
+
throw new errors_1.ForgeObjectStoreAPIError(details, {
|
|
44
|
+
code: errorCodes_1.errorCodes.UNKNOWN_ERROR,
|
|
45
|
+
message: message || 'Unexpected error in Forge OS API request',
|
|
46
|
+
context: { responseText }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
exports.checkResponseError = checkResponseError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,oBAAY,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/object-store",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1-next.0",
|
|
4
4
|
"description": "Forge Object Store SDK",
|
|
5
5
|
"author": "Atlassian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"jest-when": "^3.6.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@forge/api": "^6.4.
|
|
23
|
+
"@forge/api": "^6.4.1-next.0"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"registry": "https://packages.atlassian.com/api/npm/npm-public/"
|