facility-core 2.0.0 → 2.2.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/package.json +16 -10
- package/src/facilityCore.d.ts +7 -7
- package/src/facilityCore.js +32 -16
- package/src/facilityCore.js.map +1 -1
- package/src/facilityCore.ts +49 -29
package/package.json
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "facility-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Common code for the Facility API Framework.",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "tsc &&
|
|
7
|
-
"test": "npm run --silent build && mocha"
|
|
6
|
+
"build": "tsc && eslint src --ext .ts",
|
|
7
|
+
"test": "npm run --silent build && mocha",
|
|
8
|
+
"format": "prettier --write src/*.ts"
|
|
8
9
|
},
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
11
12
|
"url": "https://github.com/FacilityApi/FacilityJavaScript.git"
|
|
12
13
|
},
|
|
13
14
|
"author": "Ed Ball",
|
|
14
|
-
"license": "
|
|
15
|
+
"license": "MIT",
|
|
15
16
|
"files": [
|
|
16
17
|
"src"
|
|
17
18
|
],
|
|
18
19
|
"main": "src/facilityCore.js",
|
|
19
20
|
"types": "src/facilityCore.d.ts",
|
|
20
21
|
"devDependencies": {
|
|
21
|
-
"@types/chai": "^4.
|
|
22
|
-
"@types/mocha": "^
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
22
|
+
"@types/chai": "^4.2.18",
|
|
23
|
+
"@types/mocha": "^8.2.2",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
|
25
|
+
"@typescript-eslint/parser": "^4.26.1",
|
|
26
|
+
"chai": "^4.3.4",
|
|
27
|
+
"eslint": "^7.28.0",
|
|
28
|
+
"eslint-config-prettier": "^8.3.0",
|
|
29
|
+
"eslint-plugin-prettier": "^3.4.0",
|
|
30
|
+
"mocha": "^8.4.0",
|
|
31
|
+
"prettier": "^2.3.1",
|
|
32
|
+
"typescript": "~3.9.9"
|
|
27
33
|
}
|
|
28
34
|
}
|
package/src/facilityCore.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface IServiceError {
|
|
|
15
15
|
/** The error message. (For developers, not end users.) */
|
|
16
16
|
message?: string;
|
|
17
17
|
/** Advanced error details. */
|
|
18
|
-
details?:
|
|
18
|
+
details?: Record<string, unknown>;
|
|
19
19
|
/** The inner error. */
|
|
20
20
|
innerError?: IServiceError;
|
|
21
21
|
}
|
|
@@ -30,12 +30,12 @@ export interface IHttpClientOptions {
|
|
|
30
30
|
export declare namespace HttpClientUtility {
|
|
31
31
|
/** The fetch function. */
|
|
32
32
|
interface IFetch {
|
|
33
|
-
(uri: string, request: IFetchRequest): Promise<IFetchResponse>;
|
|
33
|
+
(uri: string, request: IFetchRequest, context?: unknown): Promise<IFetchResponse>;
|
|
34
34
|
}
|
|
35
35
|
/** The minimal fetch request. */
|
|
36
36
|
interface IFetchRequest {
|
|
37
37
|
method?: string;
|
|
38
|
-
headers?:
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
39
|
body?: string;
|
|
40
40
|
}
|
|
41
41
|
/** The minimal fetch response. */
|
|
@@ -44,19 +44,19 @@ export declare namespace HttpClientUtility {
|
|
|
44
44
|
headers: {
|
|
45
45
|
get(name: string): string | null;
|
|
46
46
|
};
|
|
47
|
-
json(): Promise<
|
|
47
|
+
json(): Promise<unknown>;
|
|
48
48
|
}
|
|
49
49
|
/** A fetch response with any fetched content. */
|
|
50
50
|
interface IFetchedResponseWithContent {
|
|
51
51
|
/** The fetch response. */
|
|
52
52
|
response: IFetchResponse;
|
|
53
53
|
/** The fetched JSON, if any. */
|
|
54
|
-
json?:
|
|
54
|
+
json?: unknown;
|
|
55
55
|
}
|
|
56
56
|
/** Fetch JSON using the specified fetch, URI, and request. */
|
|
57
|
-
function fetchResponse(fetch: IFetch, uri: string, request: IFetchRequest): Promise<IFetchedResponseWithContent>;
|
|
57
|
+
function fetchResponse(fetch: IFetch, uri: string, request: IFetchRequest, context?: unknown): Promise<IFetchedResponseWithContent>;
|
|
58
58
|
/** Creates an error result for the specified response. */
|
|
59
|
-
function createResponseError(status: number, json?:
|
|
59
|
+
function createResponseError(status: number, json?: unknown): IServiceResultBase;
|
|
60
60
|
/** Creates an error result for a required request field. */
|
|
61
61
|
function createRequiredRequestFieldError(name: string): IServiceResultBase;
|
|
62
62
|
}
|
package/src/facilityCore.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpClientUtility = void 0;
|
|
3
4
|
/** Helpers for HTTP clients. */
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
4
6
|
var HttpClientUtility;
|
|
5
7
|
(function (HttpClientUtility) {
|
|
6
|
-
|
|
8
|
+
const standardErrorCodes = {
|
|
7
9
|
'304': 'NotModified',
|
|
8
10
|
'400': 'InvalidRequest',
|
|
9
11
|
'401': 'NotAuthenticated',
|
|
@@ -13,26 +15,28 @@ var HttpClientUtility;
|
|
|
13
15
|
'413': 'RequestTooLarge',
|
|
14
16
|
'429': 'TooManyRequests',
|
|
15
17
|
'500': 'InternalError',
|
|
16
|
-
'503': 'ServiceUnavailable'
|
|
18
|
+
'503': 'ServiceUnavailable',
|
|
17
19
|
};
|
|
18
|
-
|
|
20
|
+
const jsonContentType = 'application/json';
|
|
19
21
|
/** Fetch JSON using the specified fetch, URI, and request. */
|
|
20
|
-
function fetchResponse(fetch, uri, request) {
|
|
21
|
-
return fetch(uri, request)
|
|
22
|
-
.then(function (response) {
|
|
22
|
+
function fetchResponse(fetch, uri, request, context) {
|
|
23
|
+
return fetch(uri, request, context).then((response) => {
|
|
23
24
|
if (!response.headers || !response.status || typeof response.json !== 'function') {
|
|
24
25
|
throw new TypeError('fetch must resolve Promise with { status, headers, json() }.');
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
const contentType = response.headers.get('content-type');
|
|
27
28
|
if (!contentType) {
|
|
28
29
|
return Promise.resolve({ response: response, json: {} });
|
|
29
30
|
}
|
|
30
31
|
if (contentType.toLowerCase().substr(0, jsonContentType.length) === jsonContentType) {
|
|
31
|
-
|
|
32
|
+
const jsonPromise = response.json();
|
|
32
33
|
if (!jsonPromise || typeof jsonPromise.then !== 'function') {
|
|
33
34
|
throw new TypeError('json() of fetch response must return a Promise.');
|
|
34
35
|
}
|
|
35
|
-
return jsonPromise.then(
|
|
36
|
+
return jsonPromise.then((json) => ({
|
|
37
|
+
response: response,
|
|
38
|
+
json: json,
|
|
39
|
+
}));
|
|
36
40
|
}
|
|
37
41
|
return Promise.resolve({ response: response });
|
|
38
42
|
});
|
|
@@ -40,20 +44,32 @@ var HttpClientUtility;
|
|
|
40
44
|
HttpClientUtility.fetchResponse = fetchResponse;
|
|
41
45
|
/** Creates an error result for the specified response. */
|
|
42
46
|
function createResponseError(status, json) {
|
|
43
|
-
if (json
|
|
47
|
+
if (isServiceError(json)) {
|
|
44
48
|
return { error: json };
|
|
45
49
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const isClientError = status >= 400 && status <= 499;
|
|
51
|
+
const isServerError = status >= 500 && status <= 599;
|
|
52
|
+
const errorCode = standardErrorCodes[status] || (isClientError ? 'InvalidRequest' : 'InvalidResponse');
|
|
53
|
+
const message = isServerError
|
|
54
|
+
? 'HTTP server error'
|
|
55
|
+
: isClientError
|
|
56
|
+
? 'HTTP client error'
|
|
57
|
+
: 'Unexpected HTTP status code';
|
|
58
|
+
return { error: { code: errorCode, message: `${message}: ${status}` } };
|
|
51
59
|
}
|
|
52
60
|
HttpClientUtility.createResponseError = createResponseError;
|
|
53
61
|
/** Creates an error result for a required request field. */
|
|
54
62
|
function createRequiredRequestFieldError(name) {
|
|
55
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
error: {
|
|
65
|
+
code: 'InvalidRequest',
|
|
66
|
+
message: `The request field '${name}' is required.`,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
56
69
|
}
|
|
57
70
|
HttpClientUtility.createRequiredRequestFieldError = createRequiredRequestFieldError;
|
|
58
71
|
})(HttpClientUtility = exports.HttpClientUtility || (exports.HttpClientUtility = {}));
|
|
72
|
+
function isServiceError(json) {
|
|
73
|
+
return typeof json === 'object' && json != null && typeof json.code === 'string';
|
|
74
|
+
}
|
|
59
75
|
//# sourceMappingURL=facilityCore.js.map
|
package/src/facilityCore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilityCore.js","sourceRoot":"","sources":["facilityCore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"facilityCore.js","sourceRoot":"","sources":["facilityCore.ts"],"names":[],"mappings":"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"}
|
package/src/facilityCore.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface IServiceError {
|
|
|
17
17
|
/** The error message. (For developers, not end users.) */
|
|
18
18
|
message?: string;
|
|
19
19
|
/** Advanced error details. */
|
|
20
|
-
details?:
|
|
20
|
+
details?: Record<string, unknown>;
|
|
21
21
|
/** The inner error. */
|
|
22
22
|
innerError?: IServiceError;
|
|
23
23
|
}
|
|
@@ -31,17 +31,17 @@ export interface IHttpClientOptions {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** Helpers for HTTP clients. */
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
34
35
|
export namespace HttpClientUtility {
|
|
35
|
-
|
|
36
36
|
/** The fetch function. */
|
|
37
37
|
export interface IFetch {
|
|
38
|
-
(uri: string, request: IFetchRequest): Promise<IFetchResponse>;
|
|
38
|
+
(uri: string, request: IFetchRequest, context?: unknown): Promise<IFetchResponse>;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** The minimal fetch request. */
|
|
42
42
|
export interface IFetchRequest {
|
|
43
43
|
method?: string;
|
|
44
|
-
headers?:
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
45
|
body?: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -51,7 +51,7 @@ export namespace HttpClientUtility {
|
|
|
51
51
|
headers: {
|
|
52
52
|
get(name: string): string | null;
|
|
53
53
|
};
|
|
54
|
-
json(): Promise<
|
|
54
|
+
json(): Promise<unknown>;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/** A fetch response with any fetched content. */
|
|
@@ -59,7 +59,7 @@ export namespace HttpClientUtility {
|
|
|
59
59
|
/** The fetch response. */
|
|
60
60
|
response: IFetchResponse;
|
|
61
61
|
/** The fetched JSON, if any. */
|
|
62
|
-
json?:
|
|
62
|
+
json?: unknown;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const standardErrorCodes: { [index: number]: string } = {
|
|
@@ -72,47 +72,67 @@ export namespace HttpClientUtility {
|
|
|
72
72
|
'413': 'RequestTooLarge',
|
|
73
73
|
'429': 'TooManyRequests',
|
|
74
74
|
'500': 'InternalError',
|
|
75
|
-
'503': 'ServiceUnavailable'
|
|
75
|
+
'503': 'ServiceUnavailable',
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const jsonContentType = 'application/json';
|
|
79
79
|
|
|
80
80
|
/** Fetch JSON using the specified fetch, URI, and request. */
|
|
81
|
-
export function fetchResponse(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
81
|
+
export function fetchResponse(
|
|
82
|
+
fetch: IFetch,
|
|
83
|
+
uri: string,
|
|
84
|
+
request: IFetchRequest,
|
|
85
|
+
context?: unknown
|
|
86
|
+
): Promise<IFetchedResponseWithContent> {
|
|
87
|
+
return fetch(uri, request, context).then((response) => {
|
|
88
|
+
if (!response.headers || !response.status || typeof response.json !== 'function') {
|
|
89
|
+
throw new TypeError('fetch must resolve Promise with { status, headers, json() }.');
|
|
90
|
+
}
|
|
91
|
+
const contentType = response.headers.get('content-type');
|
|
92
|
+
if (!contentType) {
|
|
93
|
+
return Promise.resolve({ response: response, json: {} });
|
|
94
|
+
}
|
|
95
|
+
if (contentType.toLowerCase().substr(0, jsonContentType.length) === jsonContentType) {
|
|
96
|
+
const jsonPromise = response.json();
|
|
97
|
+
if (!jsonPromise || typeof jsonPromise.then !== 'function') {
|
|
98
|
+
throw new TypeError('json() of fetch response must return a Promise.');
|
|
97
99
|
}
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
+
return jsonPromise.then((json) => ({
|
|
101
|
+
response: response,
|
|
102
|
+
json: json,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return Promise.resolve({ response: response });
|
|
106
|
+
});
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
/** Creates an error result for the specified response. */
|
|
103
|
-
export function createResponseError(status: number, json?:
|
|
104
|
-
if (json
|
|
110
|
+
export function createResponseError(status: number, json?: unknown): IServiceResultBase {
|
|
111
|
+
if (isServiceError(json)) {
|
|
105
112
|
return { error: json };
|
|
106
113
|
}
|
|
107
114
|
const isClientError = status >= 400 && status <= 499;
|
|
108
115
|
const isServerError = status >= 500 && status <= 599;
|
|
109
116
|
const errorCode = standardErrorCodes[status] || (isClientError ? 'InvalidRequest' : 'InvalidResponse');
|
|
110
|
-
const message = isServerError
|
|
117
|
+
const message = isServerError
|
|
118
|
+
? 'HTTP server error'
|
|
119
|
+
: isClientError
|
|
120
|
+
? 'HTTP client error'
|
|
121
|
+
: 'Unexpected HTTP status code';
|
|
111
122
|
return { error: { code: errorCode, message: `${message}: ${status}` } };
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
/** Creates an error result for a required request field. */
|
|
115
126
|
export function createRequiredRequestFieldError(name: string): IServiceResultBase {
|
|
116
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
error: {
|
|
129
|
+
code: 'InvalidRequest',
|
|
130
|
+
message: `The request field '${name}' is required.`,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
117
133
|
}
|
|
118
134
|
}
|
|
135
|
+
|
|
136
|
+
function isServiceError(json: unknown): json is IServiceError {
|
|
137
|
+
return typeof json === 'object' && json != null && typeof (json as Record<string, unknown>).code === 'string';
|
|
138
|
+
}
|