@superblocksteam/sabs-client 0.258.0 → 0.261.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +272 -0
- package/dist/errors.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/sabs.d.ts +110 -0
- package/dist/sabs.d.ts.map +1 -1
- package/dist/sabs.js +159 -29
- package/dist/sabs.js.map +1 -1
- package/dist/sabs.test.js +245 -4
- package/dist/sabs.test.js.map +1 -1
- package/package.json +3 -2
- package/src/errors.test.ts +341 -0
- package/src/errors.ts +60 -0
- package/src/index.ts +1 -0
- package/src/sabs.test.ts +284 -5
- package/src/sabs.ts +159 -30
- package/tsconfig.tsbuildinfo +1 -1
package/src/sabs.test.ts
CHANGED
|
@@ -807,10 +807,8 @@ describe('sabs service', () => {
|
|
|
807
807
|
|
|
808
808
|
const anyBuildId = 'anyBuildId';
|
|
809
809
|
|
|
810
|
-
const expectedError = 'sabs service request failed: request failed\n[500] internal server error: "failed to process request"';
|
|
811
|
-
|
|
812
810
|
const sabs = new SabsClient('http://localhost:3000');
|
|
813
|
-
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow(
|
|
811
|
+
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
814
812
|
});
|
|
815
813
|
|
|
816
814
|
test('re-raises error when error is not axios error', async () => {
|
|
@@ -819,10 +817,291 @@ describe('sabs service', () => {
|
|
|
819
817
|
|
|
820
818
|
const anyBuildId = 'anyBuildId';
|
|
821
819
|
|
|
822
|
-
const
|
|
820
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
821
|
+
await expect(sabs.status({ buildId: anyBuildId, accessToken: anyAccessToken })).rejects.toThrow();
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
describe('backward compatibility', () => {
|
|
826
|
+
test('errors can be caught as generic Error instances (backward compatibility)', async () => {
|
|
827
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
828
|
+
mockAxios.mockRejectedValue(
|
|
829
|
+
new AxiosError('Service unavailable', 'ECONNREFUSED', undefined, undefined, {
|
|
830
|
+
headers: {},
|
|
831
|
+
config: {
|
|
832
|
+
headers: new AxiosHeaders()
|
|
833
|
+
},
|
|
834
|
+
status: 503,
|
|
835
|
+
statusText: 'Service Unavailable',
|
|
836
|
+
data: { message: 'Server temporarily unavailable' }
|
|
837
|
+
})
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
841
|
+
const applicationId = 'test-app';
|
|
842
|
+
const organizationId = 'test-org';
|
|
843
|
+
const directoryHash = 'abc123';
|
|
844
|
+
|
|
845
|
+
// This simulates how existing applications might handle errors
|
|
846
|
+
try {
|
|
847
|
+
const result = await sabs.build({
|
|
848
|
+
directoryHash,
|
|
849
|
+
meta: new ApplicationMetadata({
|
|
850
|
+
id: applicationId,
|
|
851
|
+
organizationId
|
|
852
|
+
}),
|
|
853
|
+
buildKey: anyBuildKey,
|
|
854
|
+
accessToken: anyAccessToken
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Should not reach here
|
|
858
|
+
expect(result).toBeUndefined();
|
|
859
|
+
} catch (error) {
|
|
860
|
+
// Legacy error handling - should still work with new error types
|
|
861
|
+
expect(error).toBeInstanceOf(Error);
|
|
862
|
+
expect(error.message).toContain('SABS API Error (503)');
|
|
863
|
+
|
|
864
|
+
// Verify we can still access basic Error properties
|
|
865
|
+
expect(typeof error.message).toBe('string');
|
|
866
|
+
expect(error.name).toBeDefined();
|
|
867
|
+
|
|
868
|
+
// This is how existing code might log and re-throw
|
|
869
|
+
const loggedError = {
|
|
870
|
+
error,
|
|
871
|
+
applicationId,
|
|
872
|
+
organizationId,
|
|
873
|
+
directoryHash,
|
|
874
|
+
message: error.message
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
expect(loggedError.error).toBe(error);
|
|
878
|
+
expect(loggedError.message).toBe(error.message);
|
|
879
|
+
|
|
880
|
+
// Re-throwing as generic Error should work
|
|
881
|
+
expect(() => {
|
|
882
|
+
throw new Error('Unable to launch build');
|
|
883
|
+
}).toThrow('Unable to launch build');
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test('new error types provide additional functionality when accessed', async () => {
|
|
888
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
889
|
+
mockAxios.mockRejectedValue(
|
|
890
|
+
new AxiosError('Unauthorized', 'UNAUTHORIZED', undefined, undefined, {
|
|
891
|
+
headers: {},
|
|
892
|
+
config: {
|
|
893
|
+
headers: new AxiosHeaders()
|
|
894
|
+
},
|
|
895
|
+
status: 401,
|
|
896
|
+
statusText: 'Unauthorized',
|
|
897
|
+
data: { message: 'Invalid token' }
|
|
898
|
+
})
|
|
899
|
+
);
|
|
823
900
|
|
|
824
901
|
const sabs = new SabsClient('http://localhost:3000');
|
|
825
|
-
|
|
902
|
+
|
|
903
|
+
try {
|
|
904
|
+
await sabs.status({ buildId: 'test-build', accessToken: 'invalid-token' });
|
|
905
|
+
} catch (error) {
|
|
906
|
+
// Backward compatible - can still catch as Error
|
|
907
|
+
expect(error).toBeInstanceOf(Error);
|
|
908
|
+
|
|
909
|
+
// But applications can now also check for specific error types
|
|
910
|
+
const { UnauthorizedError } = await import('./errors');
|
|
911
|
+
expect(error).toBeInstanceOf(UnauthorizedError);
|
|
912
|
+
|
|
913
|
+
// And access the status code if needed
|
|
914
|
+
if ('status' in error) {
|
|
915
|
+
expect(error.status).toBe(401);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
describe('response data handling', () => {
|
|
921
|
+
test('handles responseData with message property', async () => {
|
|
922
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
923
|
+
mockAxios.mockRejectedValue(
|
|
924
|
+
new AxiosError('Bad Request', 'BAD_REQUEST', undefined, undefined, {
|
|
925
|
+
headers: {},
|
|
926
|
+
config: {
|
|
927
|
+
headers: new AxiosHeaders()
|
|
928
|
+
},
|
|
929
|
+
status: 400,
|
|
930
|
+
statusText: 'Bad Request',
|
|
931
|
+
data: { message: 'Directory hash is required' }
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
await sabs.build({
|
|
939
|
+
directoryHash: 'test', // Use non-empty value to avoid client-side validation
|
|
940
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
941
|
+
buildKey: 'test',
|
|
942
|
+
accessToken: 'test'
|
|
943
|
+
});
|
|
944
|
+
} catch (error) {
|
|
945
|
+
expect(error).toBeInstanceOf(Error);
|
|
946
|
+
expect(error.message).toBe('SABS API Error (400): Directory hash is required');
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test('handles responseData as string', async () => {
|
|
951
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
952
|
+
mockAxios.mockRejectedValue(
|
|
953
|
+
new AxiosError('Internal Server Error', 'INTERNAL_SERVER_ERROR', undefined, undefined, {
|
|
954
|
+
headers: {},
|
|
955
|
+
config: {
|
|
956
|
+
headers: new AxiosHeaders()
|
|
957
|
+
},
|
|
958
|
+
status: 500,
|
|
959
|
+
statusText: 'Internal Server Error',
|
|
960
|
+
data: 'Database connection failed'
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
await sabs.build({
|
|
968
|
+
directoryHash: 'test',
|
|
969
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
970
|
+
buildKey: 'test',
|
|
971
|
+
accessToken: 'test'
|
|
972
|
+
});
|
|
973
|
+
} catch (error) {
|
|
974
|
+
expect(error).toBeInstanceOf(Error);
|
|
975
|
+
expect(error.message).toBe('SABS API Error (500): Database connection failed');
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test('handles null responseData', async () => {
|
|
980
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
981
|
+
mockAxios.mockRejectedValue(
|
|
982
|
+
new AxiosError('Service Unavailable', 'SERVICE_UNAVAILABLE', undefined, undefined, {
|
|
983
|
+
headers: {},
|
|
984
|
+
config: {
|
|
985
|
+
headers: new AxiosHeaders()
|
|
986
|
+
},
|
|
987
|
+
status: 503,
|
|
988
|
+
statusText: 'Service Unavailable',
|
|
989
|
+
data: null
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
994
|
+
|
|
995
|
+
try {
|
|
996
|
+
await sabs.build({
|
|
997
|
+
directoryHash: 'test',
|
|
998
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
999
|
+
buildKey: 'test',
|
|
1000
|
+
accessToken: 'test'
|
|
1001
|
+
});
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
expect(error).toBeInstanceOf(Error);
|
|
1004
|
+
expect(error.message).toBe('SABS API Error (503): Service Unavailable (503)');
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
test('handles undefined responseData', async () => {
|
|
1009
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1010
|
+
mockAxios.mockRejectedValue(
|
|
1011
|
+
new AxiosError('Gateway Timeout', 'GATEWAY_TIMEOUT', undefined, undefined, {
|
|
1012
|
+
headers: {},
|
|
1013
|
+
config: {
|
|
1014
|
+
headers: new AxiosHeaders()
|
|
1015
|
+
},
|
|
1016
|
+
status: 504,
|
|
1017
|
+
statusText: 'Gateway Timeout',
|
|
1018
|
+
data: undefined
|
|
1019
|
+
})
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1023
|
+
|
|
1024
|
+
try {
|
|
1025
|
+
await sabs.build({
|
|
1026
|
+
directoryHash: 'test',
|
|
1027
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1028
|
+
buildKey: 'test',
|
|
1029
|
+
accessToken: 'test'
|
|
1030
|
+
});
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
expect(error).toBeInstanceOf(Error);
|
|
1033
|
+
expect(error.message).toBe('SABS API Error (504): Gateway Timeout (504)');
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
test('handles responseData object without message property', async () => {
|
|
1038
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1039
|
+
mockAxios.mockRejectedValue(
|
|
1040
|
+
new AxiosError('Bad Request', 'BAD_REQUEST', undefined, undefined, {
|
|
1041
|
+
headers: {},
|
|
1042
|
+
config: {
|
|
1043
|
+
headers: new AxiosHeaders()
|
|
1044
|
+
},
|
|
1045
|
+
status: 400,
|
|
1046
|
+
statusText: 'Bad Request',
|
|
1047
|
+
data: { error: 'validation_failed', details: ['field is required'] }
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1052
|
+
|
|
1053
|
+
try {
|
|
1054
|
+
await sabs.build({
|
|
1055
|
+
directoryHash: 'test',
|
|
1056
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1057
|
+
buildKey: 'test',
|
|
1058
|
+
accessToken: 'test'
|
|
1059
|
+
});
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
expect(error).toBeInstanceOf(Error);
|
|
1062
|
+
expect(error.message).toBe('SABS API Error (400): Bad Request (400)');
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
test('all errors have consistent HttpError interface', async () => {
|
|
1067
|
+
const responseData = { code: 'TIMEOUT', retryAfter: 30 };
|
|
1068
|
+
const mockAxios = jest.spyOn(axios, 'request');
|
|
1069
|
+
mockAxios.mockRejectedValue(
|
|
1070
|
+
new AxiosError('Request timeout', 'TIMEOUT', undefined, undefined, {
|
|
1071
|
+
headers: {},
|
|
1072
|
+
config: {
|
|
1073
|
+
headers: new AxiosHeaders()
|
|
1074
|
+
},
|
|
1075
|
+
status: 500,
|
|
1076
|
+
statusText: 'Internal Server Error',
|
|
1077
|
+
data: responseData
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
const sabs = new SabsClient('http://localhost:3000');
|
|
1082
|
+
|
|
1083
|
+
try {
|
|
1084
|
+
await sabs.build({
|
|
1085
|
+
directoryHash: 'test',
|
|
1086
|
+
meta: new ApplicationMetadata({ id: 'test', organizationId: 'test' }),
|
|
1087
|
+
buildKey: 'test',
|
|
1088
|
+
accessToken: 'test'
|
|
1089
|
+
});
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
expect(error).toBeInstanceOf(Error);
|
|
1092
|
+
expect(error.message).toBe('SABS API Error (500): Internal Server Error (500)');
|
|
1093
|
+
|
|
1094
|
+
// All error types now have consistent HttpError interface
|
|
1095
|
+
expect('status' in error).toBe(true);
|
|
1096
|
+
expect('title' in error).toBe(true);
|
|
1097
|
+
if ('status' in error) {
|
|
1098
|
+
expect(error.status).toBe(500);
|
|
1099
|
+
}
|
|
1100
|
+
if ('title' in error) {
|
|
1101
|
+
expect(error.title).toBe('Internal server error');
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
826
1105
|
});
|
|
827
1106
|
});
|
|
828
1107
|
});
|
package/src/sabs.ts
CHANGED
|
@@ -17,6 +17,17 @@ import {
|
|
|
17
17
|
} from '@superblocksteam/sabs-types';
|
|
18
18
|
import axios, { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios';
|
|
19
19
|
|
|
20
|
+
import { createErrorFromStatusCode, createClientError, createNetworkError } from './errors';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* SABS (Superblocks Application Build System) TypeScript Client
|
|
24
|
+
*
|
|
25
|
+
* Provides methods to interact with the SABS API for building and managing applications.
|
|
26
|
+
*
|
|
27
|
+
* All error types inherit from the standard `HttpError` class that extends the standard `Error` class,
|
|
28
|
+
* ensuring backward compatibility with existing error handling code that catches generic `Error` instances
|
|
29
|
+
* used in earlier versions of the client library.
|
|
30
|
+
*/
|
|
20
31
|
export class SabsClient {
|
|
21
32
|
private readonly baseUrl: string;
|
|
22
33
|
|
|
@@ -24,6 +35,20 @@ export class SabsClient {
|
|
|
24
35
|
this.baseUrl = baseUrl;
|
|
25
36
|
}
|
|
26
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Start a new build for an application
|
|
40
|
+
*
|
|
41
|
+
* @param params - Build parameters
|
|
42
|
+
* @param params.directoryHash - Hash of the application directory to build
|
|
43
|
+
* @param params.meta - Application metadata (ID and organization ID)
|
|
44
|
+
* @param params.buildKey - Secret build key for authentication
|
|
45
|
+
* @param params.accessToken - JWT access token for authorization
|
|
46
|
+
* @returns Promise resolving to build information including build ID
|
|
47
|
+
* @throws BadRequestError if the directory hash, application metadata, application ID, organization ID, build key, or access token are empty or invalid
|
|
48
|
+
* @throws ForbiddenError if the access token is invalid or missing the required scopes
|
|
49
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
50
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
51
|
+
*/
|
|
27
52
|
public async build({
|
|
28
53
|
directoryHash,
|
|
29
54
|
meta,
|
|
@@ -36,22 +61,22 @@ export class SabsClient {
|
|
|
36
61
|
accessToken?: string;
|
|
37
62
|
}): Promise<BuildResponse> {
|
|
38
63
|
if (!directoryHash || directoryHash.length === 0) {
|
|
39
|
-
throw
|
|
64
|
+
throw createClientError('Directory hash is required');
|
|
40
65
|
}
|
|
41
66
|
if (!meta) {
|
|
42
|
-
throw
|
|
67
|
+
throw createClientError('Application metadata is required');
|
|
43
68
|
}
|
|
44
69
|
if (!meta.id || meta.id.length === 0) {
|
|
45
|
-
throw
|
|
70
|
+
throw createClientError('Application ID is required');
|
|
46
71
|
}
|
|
47
72
|
if (!meta.organizationId || meta.organizationId.length === 0) {
|
|
48
|
-
throw
|
|
73
|
+
throw createClientError('Organization ID is required');
|
|
49
74
|
}
|
|
50
75
|
if (!buildKey || buildKey.length === 0) {
|
|
51
|
-
throw
|
|
76
|
+
throw createClientError('Build key is required');
|
|
52
77
|
}
|
|
53
78
|
if (!accessToken || accessToken.length === 0) {
|
|
54
|
-
throw
|
|
79
|
+
throw createClientError('Access token is required');
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
const data = new BuildRequest({
|
|
@@ -70,12 +95,25 @@ export class SabsClient {
|
|
|
70
95
|
);
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Get the status of a build
|
|
100
|
+
*
|
|
101
|
+
* @param params - Status query parameters
|
|
102
|
+
* @param params.buildId - ID of the build to check
|
|
103
|
+
* @param params.accessToken - JWT access token for authorization
|
|
104
|
+
* @returns Promise resolving to build status information
|
|
105
|
+
* @throws BadRequestError if the build ID or access token is empty or invalid
|
|
106
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
107
|
+
* @throws NotFoundError if the build ID is invalid
|
|
108
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
109
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
110
|
+
*/
|
|
73
111
|
public async status({ buildId, accessToken }: { buildId: string; accessToken?: string }): Promise<StatusResponse> {
|
|
74
112
|
if (!buildId || buildId.length === 0) {
|
|
75
|
-
throw
|
|
113
|
+
throw createClientError('Build ID is required');
|
|
76
114
|
}
|
|
77
115
|
if (!accessToken || accessToken.length === 0) {
|
|
78
|
-
throw
|
|
116
|
+
throw createClientError('Access token is required');
|
|
79
117
|
}
|
|
80
118
|
|
|
81
119
|
return this.executeRequest<StatusResponse>(
|
|
@@ -87,6 +125,21 @@ export class SabsClient {
|
|
|
87
125
|
);
|
|
88
126
|
}
|
|
89
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Get the status of multiple builds at once
|
|
130
|
+
*
|
|
131
|
+
* @param params - Bulk status query parameters
|
|
132
|
+
* @param params.organizationId - Organization ID
|
|
133
|
+
* @param params.applicationId - Application ID
|
|
134
|
+
* @param params.directoryHashes - Array of directory hashes to check
|
|
135
|
+
* @param params.accessToken - JWT access token for authorization
|
|
136
|
+
* @returns Promise resolving to multiple build status information
|
|
137
|
+
* @throws BadRequestError if the organization ID, application ID, directory hashes, or access token are empty or invalid
|
|
138
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
139
|
+
* @throws NotFoundError if the organization ID or application ID is invalid
|
|
140
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
141
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
142
|
+
*/
|
|
90
143
|
public async bulkStatus({
|
|
91
144
|
organizationId,
|
|
92
145
|
applicationId,
|
|
@@ -99,16 +152,16 @@ export class SabsClient {
|
|
|
99
152
|
accessToken?: string;
|
|
100
153
|
}): Promise<BulkStatusResponse> {
|
|
101
154
|
if (!organizationId || organizationId.length === 0) {
|
|
102
|
-
throw
|
|
155
|
+
throw createClientError('Organization ID is required');
|
|
103
156
|
}
|
|
104
157
|
if (!applicationId || applicationId.length === 0) {
|
|
105
|
-
throw
|
|
158
|
+
throw createClientError('Application ID is required');
|
|
106
159
|
}
|
|
107
160
|
if (!directoryHashes || directoryHashes.length === 0) {
|
|
108
|
-
throw
|
|
161
|
+
throw createClientError('Directory hashes are required');
|
|
109
162
|
}
|
|
110
163
|
if (!accessToken || accessToken.length === 0) {
|
|
111
|
-
throw
|
|
164
|
+
throw createClientError('Access token is required');
|
|
112
165
|
}
|
|
113
166
|
|
|
114
167
|
const data = new BulkStatusRequest({
|
|
@@ -127,6 +180,21 @@ export class SabsClient {
|
|
|
127
180
|
);
|
|
128
181
|
}
|
|
129
182
|
|
|
183
|
+
/**
|
|
184
|
+
* List all builds for a specific application and directory
|
|
185
|
+
*
|
|
186
|
+
* @param params - List query parameters
|
|
187
|
+
* @param params.organizationId - Organization ID
|
|
188
|
+
* @param params.applicationId - Application ID
|
|
189
|
+
* @param params.directoryHash - Hash of the application directory
|
|
190
|
+
* @param params.accessToken - JWT access token for authorization
|
|
191
|
+
* @returns Promise resolving to list of builds
|
|
192
|
+
* @throws BadRequestError if the organization ID, application ID, directory hash, or access token are empty or invalid
|
|
193
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
194
|
+
* @throws NotFoundError if the organization ID or application ID is invalid
|
|
195
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
196
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
197
|
+
*/
|
|
130
198
|
public async list({
|
|
131
199
|
organizationId,
|
|
132
200
|
applicationId,
|
|
@@ -139,16 +207,16 @@ export class SabsClient {
|
|
|
139
207
|
accessToken?: string;
|
|
140
208
|
}): Promise<ListResponse> {
|
|
141
209
|
if (!organizationId || organizationId.length === 0) {
|
|
142
|
-
throw
|
|
210
|
+
throw createClientError('Organization ID is required');
|
|
143
211
|
}
|
|
144
212
|
if (!applicationId || applicationId.length === 0) {
|
|
145
|
-
throw
|
|
213
|
+
throw createClientError('Application ID is required');
|
|
146
214
|
}
|
|
147
215
|
if (!directoryHash || directoryHash.length === 0) {
|
|
148
|
-
throw
|
|
216
|
+
throw createClientError('Directory hash is required');
|
|
149
217
|
}
|
|
150
218
|
if (!accessToken || accessToken.length === 0) {
|
|
151
|
-
throw
|
|
219
|
+
throw createClientError('Access token is required');
|
|
152
220
|
}
|
|
153
221
|
|
|
154
222
|
const data = new ListRequest({
|
|
@@ -167,6 +235,22 @@ export class SabsClient {
|
|
|
167
235
|
);
|
|
168
236
|
}
|
|
169
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Terminate a running build with a final status
|
|
240
|
+
*
|
|
241
|
+
* @param params - Termination parameters
|
|
242
|
+
* @param params.buildId - ID of the build to terminate
|
|
243
|
+
* @param params.status - Final status of the build
|
|
244
|
+
* @param params.buildKey - Secret build key for authentication
|
|
245
|
+
* @param params.error - Optional error message if build failed
|
|
246
|
+
* @param params.accessToken - JWT access token for authorization
|
|
247
|
+
* @returns Promise resolving to termination confirmation
|
|
248
|
+
* @throws BadRequestError if the build ID, build status, build key, or access token are empty or invalid
|
|
249
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
250
|
+
* @throws NotFoundError if the build ID is invalid
|
|
251
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
252
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
253
|
+
*/
|
|
170
254
|
public async terminate({
|
|
171
255
|
buildId,
|
|
172
256
|
status,
|
|
@@ -181,16 +265,16 @@ export class SabsClient {
|
|
|
181
265
|
accessToken?: string;
|
|
182
266
|
}): Promise<TerminateResponse> {
|
|
183
267
|
if (!buildId || buildId.length === 0) {
|
|
184
|
-
throw
|
|
268
|
+
throw createClientError('Build ID is required');
|
|
185
269
|
}
|
|
186
270
|
if (!status) {
|
|
187
|
-
throw
|
|
271
|
+
throw createClientError('Build status is required');
|
|
188
272
|
}
|
|
189
273
|
if (!buildKey || buildKey.length === 0) {
|
|
190
|
-
throw
|
|
274
|
+
throw createClientError('Build key is required');
|
|
191
275
|
}
|
|
192
276
|
if (!accessToken || accessToken.length === 0) {
|
|
193
|
-
throw
|
|
277
|
+
throw createClientError('Access token is required');
|
|
194
278
|
}
|
|
195
279
|
|
|
196
280
|
const data = new TerminateRequest({
|
|
@@ -210,6 +294,21 @@ export class SabsClient {
|
|
|
210
294
|
);
|
|
211
295
|
}
|
|
212
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Create a new live edit session for real-time development
|
|
299
|
+
*
|
|
300
|
+
* @param params - Live edit creation parameters
|
|
301
|
+
* @param params.applicationId - Application ID
|
|
302
|
+
* @param params.organizationId - Organization ID
|
|
303
|
+
* @param params.branch - Git branch name
|
|
304
|
+
* @param params.expiresIn - Session duration in seconds
|
|
305
|
+
* @param params.accessToken - JWT access token for authorization
|
|
306
|
+
* @returns Promise resolving to live edit session information
|
|
307
|
+
* @throws BadRequestError if the application ID, organization ID, branch, or access token are empty or invalid, or expiresIn is not greater than 0
|
|
308
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
309
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
310
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
311
|
+
*/
|
|
213
312
|
public async createLiveEdit({
|
|
214
313
|
applicationId,
|
|
215
314
|
organizationId,
|
|
@@ -224,16 +323,19 @@ export class SabsClient {
|
|
|
224
323
|
accessToken: string;
|
|
225
324
|
}): Promise<CreateLiveEditResponse> {
|
|
226
325
|
if (!applicationId || applicationId.length === 0) {
|
|
227
|
-
throw
|
|
326
|
+
throw createClientError('Application ID is required');
|
|
228
327
|
}
|
|
229
328
|
if (!organizationId || organizationId.length === 0) {
|
|
230
|
-
throw
|
|
329
|
+
throw createClientError('Organization ID is required');
|
|
231
330
|
}
|
|
232
331
|
if (!branch || branch.length === 0) {
|
|
233
|
-
throw
|
|
332
|
+
throw createClientError('Branch is required');
|
|
234
333
|
}
|
|
235
334
|
if (!accessToken || accessToken.length === 0) {
|
|
236
|
-
throw
|
|
335
|
+
throw createClientError('Access token is required');
|
|
336
|
+
}
|
|
337
|
+
if (!expiresIn || expiresIn <= 0) {
|
|
338
|
+
throw createClientError('Expires in is required and must be greater than 0');
|
|
237
339
|
}
|
|
238
340
|
|
|
239
341
|
const data = new CreateLiveEditRequest({
|
|
@@ -256,6 +358,19 @@ export class SabsClient {
|
|
|
256
358
|
);
|
|
257
359
|
}
|
|
258
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Terminate an active live edit session
|
|
363
|
+
*
|
|
364
|
+
* @param params - Live edit termination parameters
|
|
365
|
+
* @param params.liveEditId - ID of the live edit session to terminate
|
|
366
|
+
* @param params.accessToken - JWT access token for authorization
|
|
367
|
+
* @returns Promise resolving to termination confirmation
|
|
368
|
+
* @throws BadRequestError if the live edit ID or access token are empty or invalid
|
|
369
|
+
* @throws UnauthorizedError if the access token is invalid or missing the required scopes
|
|
370
|
+
* @throws NotFoundError if the live edit ID is invalid
|
|
371
|
+
* @throws InternalServerError if the service has an unexpected error while performing this request
|
|
372
|
+
* @throws HttpError if the request fails for an unknown reason
|
|
373
|
+
*/
|
|
259
374
|
public async terminateLiveEdit({
|
|
260
375
|
liveEditId,
|
|
261
376
|
accessToken
|
|
@@ -264,10 +379,10 @@ export class SabsClient {
|
|
|
264
379
|
accessToken: string;
|
|
265
380
|
}): Promise<TerminateLiveEditResponse> {
|
|
266
381
|
if (!liveEditId || liveEditId.length === 0) {
|
|
267
|
-
throw
|
|
382
|
+
throw createClientError('Live edit ID is required');
|
|
268
383
|
}
|
|
269
384
|
if (!accessToken || accessToken.length === 0) {
|
|
270
|
-
throw
|
|
385
|
+
throw createClientError('Access token is required');
|
|
271
386
|
}
|
|
272
387
|
|
|
273
388
|
const data = new TerminateLiveEditRequest({
|
|
@@ -300,12 +415,26 @@ export class SabsClient {
|
|
|
300
415
|
});
|
|
301
416
|
return response.data;
|
|
302
417
|
} catch (error) {
|
|
303
|
-
let errMsg = `sabs service request failed: ${error.message}`;
|
|
304
418
|
if (axios.isAxiosError(error)) {
|
|
305
|
-
|
|
306
|
-
|
|
419
|
+
const statusCode = error.response?.status ?? 500;
|
|
420
|
+
const statusText = error.response?.statusText ?? 'Unknown Error';
|
|
421
|
+
const responseData = error.response?.data;
|
|
307
422
|
|
|
308
|
-
|
|
423
|
+
let message: string;
|
|
424
|
+
if (responseData && typeof responseData === 'object' && responseData.message) {
|
|
425
|
+
message = responseData.message;
|
|
426
|
+
} else if (responseData && typeof responseData === 'string') {
|
|
427
|
+
message = responseData;
|
|
428
|
+
} else {
|
|
429
|
+
message = `${statusText} (${statusCode})`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
throw createErrorFromStatusCode(statusCode, message);
|
|
433
|
+
} else {
|
|
434
|
+
// Network error or other non-HTTP error
|
|
435
|
+
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
436
|
+
throw createNetworkError(message, error instanceof Error ? error : undefined);
|
|
437
|
+
}
|
|
309
438
|
}
|
|
310
439
|
}
|
|
311
440
|
}
|