shelving 1.170.2 → 1.172.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/db/ValidationProvider.js +6 -7
- package/endpoint/Endpoint.d.ts +3 -3
- package/endpoint/Endpoint.js +7 -13
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/markup/util/regexp.js +3 -3
- package/package.json +1 -2
- package/schema/ArraySchema.js +3 -4
- package/schema/BooleanSchema.js +1 -2
- package/schema/ChoiceSchema.js +1 -2
- package/schema/DataSchema.d.ts +2 -0
- package/schema/DataSchema.js +3 -2
- package/schema/DateSchema.js +3 -4
- package/schema/DictionarySchema.js +3 -4
- package/schema/EntitySchema.js +2 -3
- package/schema/FileSchema.js +2 -3
- package/schema/NumberSchema.js +3 -4
- package/schema/RequiredSchema.d.ts +1 -1
- package/schema/RequiredSchema.js +2 -3
- package/schema/Schema.d.ts +2 -0
- package/schema/StringSchema.js +4 -5
- package/schema/URISchema.js +2 -3
- package/schema/URLSchema.js +2 -3
- package/util/array.d.ts +4 -0
- package/util/array.js +11 -0
- package/util/http.d.ts +1 -1
- package/util/http.js +4 -5
- package/util/validate.d.ts +5 -5
- package/util/validate.js +19 -20
- package/feedback/Feedback.d.ts +0 -19
- package/feedback/Feedback.js +0 -26
- package/feedback/index.d.ts +0 -1
- package/feedback/index.js +0 -1
package/db/ValidationProvider.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ValueError } from "../error/ValueError.js";
|
|
2
|
-
import { Feedback } from "../feedback/Feedback.js";
|
|
3
2
|
import { PARTIAL } from "../schema/DataSchema.js";
|
|
4
3
|
import { getNamedMessage } from "../util/error.js";
|
|
5
4
|
import { validateData } from "../util/validate.js";
|
|
@@ -119,9 +118,9 @@ function _validateItem(collection, item, identifier, schema, caller) {
|
|
|
119
118
|
return validateData(item, { id: identifier, ...schema.props });
|
|
120
119
|
}
|
|
121
120
|
catch (thrown) {
|
|
122
|
-
if (
|
|
121
|
+
if (typeof thrown !== "string")
|
|
123
122
|
throw thrown;
|
|
124
|
-
throw new ValueError(`Invalid data for "${collection}"\n${thrown
|
|
123
|
+
throw new ValueError(`Invalid data for "${collection}"\n${thrown}`, { item, caller });
|
|
125
124
|
}
|
|
126
125
|
}
|
|
127
126
|
/**
|
|
@@ -138,9 +137,9 @@ function* _yieldValidItems(collection, items, validators, caller) {
|
|
|
138
137
|
yield validateData(item, validators);
|
|
139
138
|
}
|
|
140
139
|
catch (thrown) {
|
|
141
|
-
if (
|
|
140
|
+
if (typeof thrown !== "string")
|
|
142
141
|
throw thrown;
|
|
143
|
-
messages.push(getNamedMessage(item.id, thrown
|
|
142
|
+
messages.push(getNamedMessage(item.id, thrown));
|
|
144
143
|
}
|
|
145
144
|
}
|
|
146
145
|
if (messages.length)
|
|
@@ -151,8 +150,8 @@ function _validateUpdates(collection, updates, schema, caller) {
|
|
|
151
150
|
return validateData(updates, PARTIAL(schema).props);
|
|
152
151
|
}
|
|
153
152
|
catch (thrown) {
|
|
154
|
-
if (
|
|
153
|
+
if (typeof thrown !== "string")
|
|
155
154
|
throw thrown;
|
|
156
|
-
throw new ValueError(`Invalid updates for "${collection}"\n${thrown
|
|
155
|
+
throw new ValueError(`Invalid updates for "${collection}"\n${thrown}`, { updates, caller });
|
|
157
156
|
}
|
|
158
157
|
}
|
package/endpoint/Endpoint.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare class Endpoint<P, R> {
|
|
|
34
34
|
* @param unsafePayload The payload to pass into the callback (will be validated against this endpoint's payload schema).
|
|
35
35
|
* @param request The entire HTTP request that is being handled (payload was possibly extracted from this somehow).
|
|
36
36
|
*
|
|
37
|
-
* @throws `
|
|
37
|
+
* @throws `string` if the payload is invalid.
|
|
38
38
|
* @throws `ValueError` if `callback()` returns an invalid result.
|
|
39
39
|
*/
|
|
40
40
|
handle(callback: EndpointCallback<P, R>, unsafePayload: unknown, request: Request, caller?: AnyCaller): Promise<Response>;
|
|
@@ -48,7 +48,7 @@ export declare class Endpoint<P, R> {
|
|
|
48
48
|
* - Validates a payload against this endpoints payload schema
|
|
49
49
|
* - Return an HTTP `Request` that will send it the valid payload to this endpoint.
|
|
50
50
|
*
|
|
51
|
-
* @throws `
|
|
51
|
+
* @throws `string` if the payload is invalid.
|
|
52
52
|
*/
|
|
53
53
|
request(payload: P, options?: RequestOptions, caller?: AnyCaller): Request;
|
|
54
54
|
/**
|
|
@@ -63,7 +63,7 @@ export declare class Endpoint<P, R> {
|
|
|
63
63
|
* - Validate the `payload` against this endpoint's payload schema.
|
|
64
64
|
* - Validate the returned response against this endpoint's result schema.
|
|
65
65
|
*
|
|
66
|
-
* @throws `
|
|
66
|
+
* @throws `string` if the payload is invalid.
|
|
67
67
|
* @throws `ResponseError` if the response status is not ok (200-299)
|
|
68
68
|
* @throws `ResponseError` if the response content is invalid.
|
|
69
69
|
*/
|
package/endpoint/Endpoint.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ResponseError } from "../error/ResponseError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
|
-
import { Feedback } from "../feedback/Feedback.js";
|
|
4
3
|
import { UNDEFINED } from "../schema/Schema.js";
|
|
5
4
|
import { assertDictionary } from "../util/dictionary.js";
|
|
6
5
|
import { getMessage } from "../util/error.js";
|
|
@@ -44,7 +43,7 @@ export class Endpoint {
|
|
|
44
43
|
* @param unsafePayload The payload to pass into the callback (will be validated against this endpoint's payload schema).
|
|
45
44
|
* @param request The entire HTTP request that is being handled (payload was possibly extracted from this somehow).
|
|
46
45
|
*
|
|
47
|
-
* @throws `
|
|
46
|
+
* @throws `string` if the payload is invalid.
|
|
48
47
|
* @throws `ValueError` if `callback()` returns an invalid result.
|
|
49
48
|
*/
|
|
50
49
|
async handle(callback, unsafePayload, request, caller = this.handle) {
|
|
@@ -57,8 +56,8 @@ export class Endpoint {
|
|
|
57
56
|
return getResponse(this.result.validate(unsafeResult));
|
|
58
57
|
}
|
|
59
58
|
catch (thrown) {
|
|
60
|
-
if (thrown
|
|
61
|
-
throw new ValueError(`Invalid result for ${this.toString()}:\n${thrown
|
|
59
|
+
if (typeof thrown === "string")
|
|
60
|
+
throw new ValueError(`Invalid result for ${this.toString()}:\n${thrown}`, {
|
|
62
61
|
endpoint: this,
|
|
63
62
|
callback,
|
|
64
63
|
cause: thrown,
|
|
@@ -87,7 +86,7 @@ export class Endpoint {
|
|
|
87
86
|
* - Validates a payload against this endpoints payload schema
|
|
88
87
|
* - Return an HTTP `Request` that will send it the valid payload to this endpoint.
|
|
89
88
|
*
|
|
90
|
-
* @throws `
|
|
89
|
+
* @throws `string` if the payload is invalid.
|
|
91
90
|
*/
|
|
92
91
|
request(payload, options = {}, caller = this.request) {
|
|
93
92
|
return getRequest(this.method, this.url, this.payload.validate(payload), options, caller);
|
|
@@ -110,13 +109,8 @@ export class Endpoint {
|
|
|
110
109
|
return this.result.validate(content);
|
|
111
110
|
}
|
|
112
111
|
catch (thrown) {
|
|
113
|
-
if (thrown
|
|
114
|
-
throw new ResponseError(`Invalid result for ${this.toString()}:\n${thrown
|
|
115
|
-
endpoint: this,
|
|
116
|
-
code: 422,
|
|
117
|
-
cause: thrown,
|
|
118
|
-
caller,
|
|
119
|
-
});
|
|
112
|
+
if (typeof thrown === "string")
|
|
113
|
+
throw new ResponseError(`Invalid result for ${this.toString()}:\n${thrown}`, { endpoint: this, code: 422, caller });
|
|
120
114
|
throw thrown;
|
|
121
115
|
}
|
|
122
116
|
}
|
|
@@ -125,7 +119,7 @@ export class Endpoint {
|
|
|
125
119
|
* - Validate the `payload` against this endpoint's payload schema.
|
|
126
120
|
* - Validate the returned response against this endpoint's result schema.
|
|
127
121
|
*
|
|
128
|
-
* @throws `
|
|
122
|
+
* @throws `string` if the payload is invalid.
|
|
129
123
|
* @throws `ResponseError` if the response status is not ok (200-299)
|
|
130
124
|
* @throws `ResponseError` if the response content is invalid.
|
|
131
125
|
*/
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
export * from "./db/index.js";
|
|
7
7
|
export * from "./endpoint/index.js";
|
|
8
8
|
export * from "./error/index.js";
|
|
9
|
-
export * from "./feedback/index.js";
|
|
10
9
|
// export * from "./firestore/client/index.js"; // Not exported.
|
|
11
10
|
// export * from "./firestore/lite/index.js"; // Not exported.
|
|
12
11
|
// export * from "./firestore/server/index.js"; // Not exported.
|
package/markup/util/regexp.js
CHANGED
|
@@ -11,11 +11,11 @@ export const WORD_CONTENT_REGEXP = "[\\p{L}\\p{N}]+"; // Word content (at least
|
|
|
11
11
|
export const WORD_START_REGEXP = "(?<![\\p{L}\\p{N}])"; // Start of word (previous character is not a letter or number).
|
|
12
12
|
export const WORD_END_REGEXP = "(?![\\p{L}\\p{N}])"; // End of word (next character is not a letter or number).
|
|
13
13
|
export function getBlockRegExp(content, start = BLOCK_START_REGEXP, end = BLOCK_END_REGEXP) {
|
|
14
|
-
return new RegExp(`${start}${getRegExpSource(content)}${end}
|
|
14
|
+
return new RegExp(`${start}${getRegExpSource(content)}${end}`, "u");
|
|
15
15
|
}
|
|
16
16
|
export function getLineRegExp(content = LINE_CONTENT_REGEXP, end = LINE_END_REGEXP, start = LINE_START_REGEXP) {
|
|
17
|
-
return new RegExp(`${start}${getRegExpSource(content)}${end}
|
|
17
|
+
return new RegExp(`${start}${getRegExpSource(content)}${end}`, "u");
|
|
18
18
|
}
|
|
19
19
|
export function getWordRegExp(content = WORD_CONTENT_REGEXP, start = WORD_START_REGEXP, end = WORD_END_REGEXP) {
|
|
20
|
-
return new RegExp(`${start}${getRegExpSource(content)}${end}
|
|
20
|
+
return new RegExp(`${start}${getRegExpSource(content)}${end}`, "u");
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"state-management",
|
|
12
12
|
"query-builder"
|
|
13
13
|
],
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.172.0",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/dhoulb/shelving.git"
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"./api": "./api/index.js",
|
|
28
28
|
"./db": "./db/index.js",
|
|
29
29
|
"./error": "./error/index.js",
|
|
30
|
-
"./feedback": "./feedback/index.js",
|
|
31
30
|
"./firestore/client": "./firestore/client/index.js",
|
|
32
31
|
"./firestore/lite": "./firestore/lite/index.js",
|
|
33
32
|
"./firestore/server": "./firestore/server/index.js",
|
package/schema/ArraySchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { getUniqueArray, isArray } from "../util/array.js";
|
|
3
2
|
import { validateArray } from "../util/validate.js";
|
|
4
3
|
import { Schema } from "./Schema.js";
|
|
@@ -39,13 +38,13 @@ export class ArraySchema extends Schema {
|
|
|
39
38
|
validate(unsafeValue = this.value) {
|
|
40
39
|
const unsafeArray = typeof unsafeValue === "string" ? unsafeValue.split(this.separator).filter(Boolean) : unsafeValue;
|
|
41
40
|
if (!isArray(unsafeArray))
|
|
42
|
-
throw
|
|
41
|
+
throw "Must be array";
|
|
43
42
|
const validArray = validateArray(unsafeArray, this.items);
|
|
44
43
|
const uniqueArray = this.unique ? getUniqueArray(validArray) : validArray;
|
|
45
44
|
if (uniqueArray.length < this.min)
|
|
46
|
-
throw
|
|
45
|
+
throw uniqueArray.length ? `Minimum ${this.min} ${this.many}` : "Required";
|
|
47
46
|
if (uniqueArray.length > this.max)
|
|
48
|
-
throw
|
|
47
|
+
throw `Maximum ${this.max} ${this.many}`;
|
|
49
48
|
return uniqueArray;
|
|
50
49
|
}
|
|
51
50
|
}
|
package/schema/BooleanSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Feedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { Schema } from "./Schema.js";
|
|
3
2
|
const NEGATIVE = ["", "false", "0", "no", "n", "off"];
|
|
4
3
|
/** Define a valid boolean. */
|
|
@@ -10,7 +9,7 @@ export class BooleanSchema extends Schema {
|
|
|
10
9
|
validate(unsafeValue = this.value) {
|
|
11
10
|
const value = typeof unsafeValue === "string" ? !NEGATIVE.includes(unsafeValue.toLowerCase().trim()) : !!unsafeValue;
|
|
12
11
|
if (this.required && !value)
|
|
13
|
-
throw
|
|
12
|
+
throw "Required";
|
|
14
13
|
return value;
|
|
15
14
|
}
|
|
16
15
|
}
|
package/schema/ChoiceSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { isArray, requireFirst } from "../util/array.js";
|
|
3
2
|
import { getKeys, getProps, isProp } from "../util/object.js";
|
|
4
3
|
import { Schema } from "./Schema.js";
|
|
@@ -27,7 +26,7 @@ export class ChoiceSchema extends Schema {
|
|
|
27
26
|
validate(unsafeValue = this.value) {
|
|
28
27
|
if (typeof unsafeValue === "string" && isOption(this.options, unsafeValue))
|
|
29
28
|
return unsafeValue;
|
|
30
|
-
throw
|
|
29
|
+
throw "Unknown value";
|
|
31
30
|
}
|
|
32
31
|
// Implement iterable.
|
|
33
32
|
*[Symbol.iterator]() {
|
package/schema/DataSchema.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export declare class DataSchema<T extends Data> extends Schema<unknown> {
|
|
|
16
16
|
readonly props: Schemas<T>;
|
|
17
17
|
constructor({ one, title, props, value: partialValue, ...options }: DataSchemaOptions<T>);
|
|
18
18
|
validate(unsafeValue?: unknown): T;
|
|
19
|
+
/** Make a new `DataSchema` that only uses a defined subset of the current props. */
|
|
19
20
|
pick<K extends Key<T>>(...keys: K[]): DataSchema<Pick<T, K>>;
|
|
21
|
+
/** Make a new `DataSchema` that omits one or more of the current props. */
|
|
20
22
|
omit<K extends Key<T>>(...keys: K[]): DataSchema<Omit<T, K>>;
|
|
21
23
|
}
|
|
22
24
|
/** Set of named data schemas. */
|
package/schema/DataSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { isData } from "../util/data.js";
|
|
3
2
|
import { omitProps, pickProps } from "../util/object.js";
|
|
4
3
|
import { mapProps } from "../util/transform.js";
|
|
@@ -17,12 +16,14 @@ export class DataSchema extends Schema {
|
|
|
17
16
|
}
|
|
18
17
|
validate(unsafeValue = this.value) {
|
|
19
18
|
if (!isData(unsafeValue))
|
|
20
|
-
throw
|
|
19
|
+
throw "Must be object";
|
|
21
20
|
return validateData(unsafeValue, this.props);
|
|
22
21
|
}
|
|
22
|
+
/** Make a new `DataSchema` that only uses a defined subset of the current props. */
|
|
23
23
|
pick(...keys) {
|
|
24
24
|
return new DataSchema({ ...this, props: pickProps(this.props, ...keys) });
|
|
25
25
|
}
|
|
26
|
+
/** Make a new `DataSchema` that omits one or more of the current props. */
|
|
26
27
|
omit(...keys) {
|
|
27
28
|
return new DataSchema({ ...this, props: omitProps(this.props, ...keys) });
|
|
28
29
|
}
|
package/schema/DateSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { getDate, requireDateString } from "../util/date.js";
|
|
3
2
|
import { formatDate } from "../util/format.js";
|
|
4
3
|
import { roundStep } from "../util/number.js";
|
|
@@ -19,12 +18,12 @@ export class DateSchema extends Schema {
|
|
|
19
18
|
validate(value = this.value) {
|
|
20
19
|
const date = getDate(value);
|
|
21
20
|
if (!date)
|
|
22
|
-
throw
|
|
21
|
+
throw value ? "Invalid date" : "Required";
|
|
23
22
|
const rounded = typeof this.step === "number" ? new Date(roundStep(date.getTime(), this.step)) : date;
|
|
24
23
|
if (this.min && rounded < this.min)
|
|
25
|
-
throw
|
|
24
|
+
throw `Minimum ${this.format(this.min)}`;
|
|
26
25
|
if (this.max && rounded > this.max)
|
|
27
|
-
throw
|
|
26
|
+
throw `Maximum ${this.format(this.max)}`;
|
|
28
27
|
return this.stringify(rounded);
|
|
29
28
|
}
|
|
30
29
|
stringify(value) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { isDictionary } from "../util/dictionary.js";
|
|
3
2
|
import { validateDictionary } from "../util/validate.js";
|
|
4
3
|
import { Schema } from "./Schema.js";
|
|
@@ -15,13 +14,13 @@ export class DictionarySchema extends Schema {
|
|
|
15
14
|
}
|
|
16
15
|
validate(unsafeValue = this.value) {
|
|
17
16
|
if (!isDictionary(unsafeValue))
|
|
18
|
-
throw
|
|
17
|
+
throw "Must be object";
|
|
19
18
|
const validDictionary = validateDictionary(unsafeValue, this.items);
|
|
20
19
|
const length = Object.keys(validDictionary).length;
|
|
21
20
|
if (length < this.min)
|
|
22
|
-
throw
|
|
21
|
+
throw length ? `Minimum ${this.min} ${this.many}` : "Required";
|
|
23
22
|
if (length > this.max)
|
|
24
|
-
throw
|
|
23
|
+
throw `Maximum ${this.max} ${this.many}`;
|
|
25
24
|
return validDictionary;
|
|
26
25
|
}
|
|
27
26
|
}
|
package/schema/EntitySchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { isArrayItem } from "../util/array.js";
|
|
3
2
|
import { getEntity } from "../util/entity.js";
|
|
4
3
|
import { NULLABLE } from "./NullableSchema.js";
|
|
@@ -14,9 +13,9 @@ export class EntitySchema extends StringSchema {
|
|
|
14
13
|
const entity = super.validate(unsafeValue);
|
|
15
14
|
const [type] = getEntity(entity);
|
|
16
15
|
if (!type)
|
|
17
|
-
throw
|
|
16
|
+
throw "Must be entity";
|
|
18
17
|
if (this.types && !isArrayItem(this.types, type))
|
|
19
|
-
throw
|
|
18
|
+
throw "Invalid entity type";
|
|
20
19
|
return entity;
|
|
21
20
|
}
|
|
22
21
|
}
|
package/schema/FileSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { getFileExtension } from "../util/file.js";
|
|
3
2
|
import { isProp } from "../util/object.js";
|
|
4
3
|
import { NULLABLE } from "./NullableSchema.js";
|
|
@@ -14,9 +13,9 @@ export class FileSchema extends StringSchema {
|
|
|
14
13
|
const path = super.validate(unsafeValue);
|
|
15
14
|
const extension = getFileExtension(path);
|
|
16
15
|
if (!extension)
|
|
17
|
-
throw
|
|
16
|
+
throw "Must be file name with extension";
|
|
18
17
|
if (this.types && !isProp(this.types, extension))
|
|
19
|
-
throw
|
|
18
|
+
throw "Invalid file type";
|
|
20
19
|
return path;
|
|
21
20
|
}
|
|
22
21
|
}
|
package/schema/NumberSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { formatNumber } from "../util/format.js";
|
|
3
2
|
import { getNumber, roundStep } from "../util/number.js";
|
|
4
3
|
import { NULLABLE } from "./NullableSchema.js";
|
|
@@ -17,12 +16,12 @@ export class NumberSchema extends Schema {
|
|
|
17
16
|
validate(unsafeValue = this.value) {
|
|
18
17
|
const optionalNumber = getNumber(unsafeValue);
|
|
19
18
|
if (typeof optionalNumber !== "number")
|
|
20
|
-
throw
|
|
19
|
+
throw "Must be number";
|
|
21
20
|
const roundedNumber = typeof this.step === "number" ? roundStep(optionalNumber, this.step) : optionalNumber;
|
|
22
21
|
if (roundedNumber < this.min)
|
|
23
|
-
throw
|
|
22
|
+
throw !optionalNumber ? "Required" : `Minimum ${formatNumber(this.min)}`;
|
|
24
23
|
if (roundedNumber > this.max)
|
|
25
|
-
throw
|
|
24
|
+
throw `Maximum ${formatNumber(this.max)}`;
|
|
26
25
|
return roundedNumber;
|
|
27
26
|
}
|
|
28
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Schema } from "./Schema.js";
|
|
2
2
|
import { ThroughSchema } from "./ThroughSchema.js";
|
|
3
|
-
/** Validate a value of a specifed type, but throw `
|
|
3
|
+
/** Validate a value of a specifed type, but throw `"Required"` if the validated value is falsy. */
|
|
4
4
|
export declare class RequiredSchema<T> extends ThroughSchema<T> {
|
|
5
5
|
validate(unsafeValue: unknown): T;
|
|
6
6
|
}
|
package/schema/RequiredSchema.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { ThroughSchema } from "./ThroughSchema.js";
|
|
3
|
-
/** Validate a value of a specifed type, but throw `
|
|
2
|
+
/** Validate a value of a specifed type, but throw `"Required"` if the validated value is falsy. */
|
|
4
3
|
export class RequiredSchema extends ThroughSchema {
|
|
5
4
|
validate(unsafeValue) {
|
|
6
5
|
const safeValue = super.validate(unsafeValue);
|
|
7
6
|
if (!safeValue)
|
|
8
|
-
throw
|
|
7
|
+
throw "Required";
|
|
9
8
|
return safeValue;
|
|
10
9
|
}
|
|
11
10
|
}
|
package/schema/Schema.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export declare abstract class Schema<T = unknown> implements Validator<T> {
|
|
|
37
37
|
/** Every schema must implement a `validate()` method. */
|
|
38
38
|
abstract validate(unsafeValue: unknown): T;
|
|
39
39
|
}
|
|
40
|
+
/** Extract the type from a schema. */
|
|
41
|
+
export type SchemaType<X> = X extends Schema<infer Y> ? Y : never;
|
|
40
42
|
/** A set of named schemas in `{ name: schema }` format. */
|
|
41
43
|
export type Schemas<T extends Data = Data> = {
|
|
42
44
|
readonly [K in keyof T]: Schema<T[K]>;
|
package/schema/StringSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { sanitizeMultilineText, sanitizeText } from "../util/string.js";
|
|
3
2
|
import { NULLABLE } from "./NullableSchema.js";
|
|
4
3
|
import { Schema } from "./Schema.js";
|
|
@@ -36,14 +35,14 @@ export class StringSchema extends Schema {
|
|
|
36
35
|
validate(unsafeValue = this.value) {
|
|
37
36
|
const possibleString = typeof unsafeValue === "number" ? unsafeValue.toString() : unsafeValue;
|
|
38
37
|
if (typeof possibleString !== "string")
|
|
39
|
-
throw
|
|
38
|
+
throw "Must be string";
|
|
40
39
|
const saneString = this.sanitize(possibleString);
|
|
41
40
|
if (saneString.length < this.min)
|
|
42
|
-
throw
|
|
41
|
+
throw possibleString.length ? `Minimum ${this.min} characters` : "Required";
|
|
43
42
|
if (saneString.length > this.max)
|
|
44
|
-
throw
|
|
43
|
+
throw `Maximum ${this.max} characters`;
|
|
45
44
|
if (this.match && !this.match.test(saneString))
|
|
46
|
-
throw
|
|
45
|
+
throw saneString ? "Invalid format" : "Required";
|
|
47
46
|
return saneString;
|
|
48
47
|
}
|
|
49
48
|
/** Sanitize the string by removing unwanted characters. */
|
package/schema/URISchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { getURI, HTTP_SCHEMES } from "../util/uri.js";
|
|
3
2
|
import { NULLABLE } from "./NullableSchema.js";
|
|
4
3
|
import { StringSchema } from "./StringSchema.js";
|
|
@@ -26,9 +25,9 @@ export class URISchema extends StringSchema {
|
|
|
26
25
|
const str = super.validate(unsafeValue);
|
|
27
26
|
const uri = getURI(str);
|
|
28
27
|
if (!uri)
|
|
29
|
-
throw
|
|
28
|
+
throw str ? "Invalid format" : "Required";
|
|
30
29
|
if (this.schemes && !this.schemes.includes(uri.protocol))
|
|
31
|
-
throw
|
|
30
|
+
throw "Invalid URI scheme";
|
|
32
31
|
return uri.href;
|
|
33
32
|
}
|
|
34
33
|
}
|
package/schema/URLSchema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
1
|
import { HTTP_SCHEMES } from "../util/uri.js";
|
|
3
2
|
import { getURL } from "../util/url.js";
|
|
4
3
|
import { NULLABLE } from "./NullableSchema.js";
|
|
@@ -29,9 +28,9 @@ export class URLSchema extends StringSchema {
|
|
|
29
28
|
const str = super.validate(unsafeValue);
|
|
30
29
|
const url = getURL(str, this.base);
|
|
31
30
|
if (!url)
|
|
32
|
-
throw
|
|
31
|
+
throw str ? "Invalid format" : "Required";
|
|
33
32
|
if (this.schemes && !this.schemes.includes(url.protocol))
|
|
34
|
-
throw
|
|
33
|
+
throw "Invalid URL scheme";
|
|
35
34
|
return url.href;
|
|
36
35
|
}
|
|
37
36
|
}
|
package/util/array.d.ts
CHANGED
|
@@ -105,6 +105,10 @@ export declare function countArray<T>(arr: ImmutableArray<T>): number;
|
|
|
105
105
|
/** Interleave array items with a separator */
|
|
106
106
|
export declare function interleaveArray<T>(items: PossibleArray<T>, separator: T): ImmutableArray<T>;
|
|
107
107
|
export declare function interleaveArray<A, B>(items: PossibleArray<A>, separator: B): ImmutableArray<A | B>;
|
|
108
|
+
/** Return a new array with a new value replacing a specific index in the array (or the same array if the value was unchanged). */
|
|
109
|
+
export declare function withArrayIndex<T>(arr: ImmutableArray<T>, index: number, value: T): ImmutableArray<T>;
|
|
110
|
+
/** Return a new array without a specific index in the array (or the same array if the value was unchanged). */
|
|
111
|
+
export declare function omitArrayIndex<T>(arr: ImmutableArray<T>, index: number): ImmutableArray<T>;
|
|
108
112
|
/** Get the first item from an array or iterable, or `undefined` if it didn't exist. */
|
|
109
113
|
export declare function getFirst<T>(items: PossibleArray<T>): T | undefined;
|
|
110
114
|
/** Get the first item from an array or iterable. */
|
package/util/array.js
CHANGED
|
@@ -124,6 +124,17 @@ export function interleaveArray(items, separator) {
|
|
|
124
124
|
return items; // Return same empty array if empty or only one item.
|
|
125
125
|
return Array.from(interleaveItems(items, separator));
|
|
126
126
|
}
|
|
127
|
+
/** Return a new array with a new value replacing a specific index in the array (or the same array if the value was unchanged). */
|
|
128
|
+
export function withArrayIndex(arr, index, value) {
|
|
129
|
+
if (arr[index] === value)
|
|
130
|
+
return arr;
|
|
131
|
+
return [...arr.slice(0, index), value, ...arr.slice(index + 1)];
|
|
132
|
+
}
|
|
133
|
+
/** Return a new array without a specific index in the array (or the same array if the value was unchanged). */
|
|
134
|
+
export function omitArrayIndex(arr, index) {
|
|
135
|
+
const output = [...arr.slice(0, index), ...arr.slice(index + 1)];
|
|
136
|
+
return arr.length !== output.length ? output : arr;
|
|
137
|
+
}
|
|
127
138
|
/** Get the first item from an array or iterable, or `undefined` if it didn't exist. */
|
|
128
139
|
export function getFirst(items) {
|
|
129
140
|
if (isArray(items))
|
package/util/http.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare function getResponse(value: unknown): Response;
|
|
|
42
42
|
*
|
|
43
43
|
* Returns the correct `Response` based on the type of error thrown:
|
|
44
44
|
* - If `reason` is a `Response` instance, return it directly.
|
|
45
|
-
* - If `reason` is a
|
|
45
|
+
* - If `reason` is a string, return a 422 response with the string message, e.g. `"Invalid input"`
|
|
46
46
|
* - If `reason` is an `RequestError` instance, return a response with the error's message and code (but only if `debug` is true so we don't leak error details to the client).
|
|
47
47
|
* - If `reason` is an `Error` instance, return a 500 response with the error's message (but only if `debug` is true so we don't leak error details to the client).
|
|
48
48
|
* - Anything else returns a 500 response.
|
package/util/http.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { RequestError } from "../error/RequestError.js";
|
|
2
2
|
import { ResponseError } from "../error/ResponseError.js";
|
|
3
|
-
import { Feedback } from "../feedback/Feedback.js";
|
|
4
3
|
import { assertDictionary } from "./dictionary.js";
|
|
5
4
|
import { isError } from "./error.js";
|
|
6
5
|
import { getPlaceholders, renderTemplate } from "./template.js";
|
|
@@ -89,7 +88,7 @@ export function getResponse(value) {
|
|
|
89
88
|
*
|
|
90
89
|
* Returns the correct `Response` based on the type of error thrown:
|
|
91
90
|
* - If `reason` is a `Response` instance, return it directly.
|
|
92
|
-
* - If `reason` is a
|
|
91
|
+
* - If `reason` is a string, return a 422 response with the string message, e.g. `"Invalid input"`
|
|
93
92
|
* - If `reason` is an `RequestError` instance, return a response with the error's message and code (but only if `debug` is true so we don't leak error details to the client).
|
|
94
93
|
* - If `reason` is an `Error` instance, return a 500 response with the error's message (but only if `debug` is true so we don't leak error details to the client).
|
|
95
94
|
* - Anything else returns a 500 response.
|
|
@@ -101,9 +100,9 @@ export function getErrorResponse(reason, debug = false) {
|
|
|
101
100
|
// If it's already a `Response`, return it directly.
|
|
102
101
|
if (reason instanceof Response)
|
|
103
102
|
return reason;
|
|
104
|
-
// Throw
|
|
105
|
-
if (reason
|
|
106
|
-
return Response
|
|
103
|
+
// Throw validation message strings to return `{ message: "etc" }` to the client.
|
|
104
|
+
if (typeof reason === "string")
|
|
105
|
+
return new Response(reason, { status: 422 }); // HTTP 422 Unprocessable Entity
|
|
107
106
|
// Throw `RequestError` to set a custom status code (e.g. `UnauthorizedError`).
|
|
108
107
|
const status = reason instanceof RequestError ? reason.code : 500;
|
|
109
108
|
// Throw `Error` to return `{ message: "etc" }` to the client (but only if `debug` is true so we don't leak error details to the client).
|
package/util/validate.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface Validator<T> {
|
|
|
12
12
|
* @return Valid value.
|
|
13
13
|
*
|
|
14
14
|
* @throws `Error` If the value is invalid and cannot be fixed.
|
|
15
|
-
* @throws `
|
|
15
|
+
* @throws `string` If the value is invalid and cannot be fixed and we want to explain why to an end user.
|
|
16
16
|
*/
|
|
17
17
|
validate(unsafeValue: unknown): T;
|
|
18
18
|
}
|
|
@@ -34,20 +34,20 @@ export declare function requireValid<T>(value: unknown, validator: Validator<T>,
|
|
|
34
34
|
* Validate an iterable set of items with a validator.
|
|
35
35
|
*
|
|
36
36
|
* @yield Valid items.
|
|
37
|
-
* @throw
|
|
37
|
+
* @throw string if one or more items did not validate.
|
|
38
38
|
*/
|
|
39
39
|
export declare function validateItems<T>(unsafeItems: PossibleArray<unknown>, validator: Validator<T>): Iterable<T>;
|
|
40
40
|
/**
|
|
41
41
|
* Validate an array of items.
|
|
42
42
|
*
|
|
43
43
|
* @return Array with valid items.
|
|
44
|
-
* @throw
|
|
44
|
+
* @throw string if one or more entry values did not validate.
|
|
45
45
|
*/
|
|
46
46
|
export declare function validateArray<T>(unsafeArray: PossibleArray<unknown>, validator: Validator<T>): ImmutableArray<T>;
|
|
47
47
|
/**
|
|
48
48
|
* Validate the values of the entries in a dictionary object.
|
|
49
49
|
*
|
|
50
|
-
* @throw
|
|
50
|
+
* @throw string if one or more entry values did not validate.
|
|
51
51
|
*/
|
|
52
52
|
export declare function validateDictionary<T>(unsafeDictionary: ImmutableDictionary<unknown>, validator: Validator<T>): ImmutableDictionary<T>;
|
|
53
53
|
/**
|
|
@@ -57,6 +57,6 @@ export declare function validateDictionary<T>(unsafeDictionary: ImmutableDiction
|
|
|
57
57
|
* - `undefined` props after validation will not be set in the output object.
|
|
58
58
|
*
|
|
59
59
|
* @return Valid object.
|
|
60
|
-
* @throw
|
|
60
|
+
* @throw string if one or more props did not validate.
|
|
61
61
|
*/
|
|
62
62
|
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T>): T;
|
package/util/validate.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
-
import { Feedback, ValueFeedback } from "../feedback/Feedback.js";
|
|
3
2
|
import { isArray } from "./array.js";
|
|
4
3
|
import { getDataProps } from "./data.js";
|
|
5
4
|
import { getDictionaryItems } from "./dictionary.js";
|
|
@@ -11,7 +10,7 @@ export function getValid(value, validator) {
|
|
|
11
10
|
return validator.validate(value);
|
|
12
11
|
}
|
|
13
12
|
catch (thrown) {
|
|
14
|
-
if (thrown
|
|
13
|
+
if (typeof thrown === "string")
|
|
15
14
|
return undefined;
|
|
16
15
|
throw thrown;
|
|
17
16
|
}
|
|
@@ -22,8 +21,8 @@ export function requireValid(value, validator, caller = requireValid) {
|
|
|
22
21
|
return validator.validate(value);
|
|
23
22
|
}
|
|
24
23
|
catch (thrown) {
|
|
25
|
-
if (thrown
|
|
26
|
-
throw new RequiredError(thrown
|
|
24
|
+
if (typeof thrown === "string")
|
|
25
|
+
throw new RequiredError(thrown, { cause: thrown, caller });
|
|
27
26
|
throw thrown;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
@@ -31,7 +30,7 @@ export function requireValid(value, validator, caller = requireValid) {
|
|
|
31
30
|
* Validate an iterable set of items with a validator.
|
|
32
31
|
*
|
|
33
32
|
* @yield Valid items.
|
|
34
|
-
* @throw
|
|
33
|
+
* @throw string if one or more items did not validate.
|
|
35
34
|
*/
|
|
36
35
|
export function* validateItems(unsafeItems, validator) {
|
|
37
36
|
let index = 0;
|
|
@@ -41,20 +40,20 @@ export function* validateItems(unsafeItems, validator) {
|
|
|
41
40
|
yield validator.validate(unsafeItem);
|
|
42
41
|
}
|
|
43
42
|
catch (thrown) {
|
|
44
|
-
if (
|
|
43
|
+
if (typeof thrown !== "string")
|
|
45
44
|
throw thrown;
|
|
46
|
-
messages.push(getNamedMessage(index.toString(), thrown
|
|
45
|
+
messages.push(getNamedMessage(index.toString(), thrown));
|
|
47
46
|
}
|
|
48
47
|
index++;
|
|
49
48
|
}
|
|
50
49
|
if (messages.length)
|
|
51
|
-
throw
|
|
50
|
+
throw messages.join("\n");
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Validate an array of items.
|
|
55
54
|
*
|
|
56
55
|
* @return Array with valid items.
|
|
57
|
-
* @throw
|
|
56
|
+
* @throw string if one or more entry values did not validate.
|
|
58
57
|
*/
|
|
59
58
|
export function validateArray(unsafeArray, validator) {
|
|
60
59
|
let index = 0;
|
|
@@ -69,20 +68,20 @@ export function validateArray(unsafeArray, validator) {
|
|
|
69
68
|
changed = true;
|
|
70
69
|
}
|
|
71
70
|
catch (thrown) {
|
|
72
|
-
if (
|
|
71
|
+
if (typeof thrown !== "string")
|
|
73
72
|
throw thrown;
|
|
74
|
-
messages.push(getNamedMessage(index.toString(), thrown
|
|
73
|
+
messages.push(getNamedMessage(index.toString(), thrown));
|
|
75
74
|
}
|
|
76
75
|
index++;
|
|
77
76
|
}
|
|
78
77
|
if (messages.length)
|
|
79
|
-
throw
|
|
78
|
+
throw messages.join("\n");
|
|
80
79
|
return changed || !isArray(unsafeArray) ? safeArray : unsafeArray;
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
82
|
* Validate the values of the entries in a dictionary object.
|
|
84
83
|
*
|
|
85
|
-
* @throw
|
|
84
|
+
* @throw string if one or more entry values did not validate.
|
|
86
85
|
*/
|
|
87
86
|
export function validateDictionary(unsafeDictionary, validator) {
|
|
88
87
|
let changed = false;
|
|
@@ -96,13 +95,13 @@ export function validateDictionary(unsafeDictionary, validator) {
|
|
|
96
95
|
changed = true;
|
|
97
96
|
}
|
|
98
97
|
catch (thrown) {
|
|
99
|
-
if (
|
|
98
|
+
if (typeof thrown !== "string")
|
|
100
99
|
throw thrown;
|
|
101
|
-
messages.push(getNamedMessage(key, thrown
|
|
100
|
+
messages.push(getNamedMessage(key, thrown));
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
if (messages.length)
|
|
105
|
-
throw
|
|
104
|
+
throw messages.join("\n");
|
|
106
105
|
return changed || isIterable(unsafeDictionary) ? safeDictionary : unsafeDictionary;
|
|
107
106
|
}
|
|
108
107
|
/**
|
|
@@ -112,7 +111,7 @@ export function validateDictionary(unsafeDictionary, validator) {
|
|
|
112
111
|
* - `undefined` props after validation will not be set in the output object.
|
|
113
112
|
*
|
|
114
113
|
* @return Valid object.
|
|
115
|
-
* @throw
|
|
114
|
+
* @throw string if one or more props did not validate.
|
|
116
115
|
*/
|
|
117
116
|
export function validateData(unsafeData, validators) {
|
|
118
117
|
let changes = 0;
|
|
@@ -137,13 +136,13 @@ export function validateData(unsafeData, validators) {
|
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
138
|
catch (thrown) {
|
|
140
|
-
if (
|
|
139
|
+
if (typeof thrown !== "string")
|
|
141
140
|
throw thrown;
|
|
142
|
-
messages.push(getNamedMessage(key, thrown
|
|
141
|
+
messages.push(getNamedMessage(key, thrown));
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
if (messages.length)
|
|
146
|
-
throw
|
|
145
|
+
throw messages.join("\n");
|
|
147
146
|
if (changes)
|
|
148
147
|
return safeData;
|
|
149
148
|
// Check that no excess keys exist.
|
package/feedback/Feedback.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `Feedback` class represents a feedback message that should be shown to the user.
|
|
3
|
-
* - Basic `Feedback` is neither good nor bad, `Feedback` indicates good news, and `Feedback` indicates bad news.
|
|
4
|
-
*
|
|
5
|
-
* Conceptually different to a Javascript `Error`...
|
|
6
|
-
* - `Error`: a program error designed to help developers fix an issue in their code.
|
|
7
|
-
* - `Feedback`: generated in reaction to something a user did, and helps them understand what to do next.
|
|
8
|
-
*/
|
|
9
|
-
export declare class Feedback {
|
|
10
|
-
/** String feedback message that is safe to show to a user. */
|
|
11
|
-
readonly message: string;
|
|
12
|
-
constructor(message: string);
|
|
13
|
-
toString(): string;
|
|
14
|
-
}
|
|
15
|
-
/** Feedback with a known and typed `.value` field. */
|
|
16
|
-
export declare class ValueFeedback<T> extends Feedback {
|
|
17
|
-
readonly value: T;
|
|
18
|
-
constructor(message: string, value: T);
|
|
19
|
-
}
|
package/feedback/Feedback.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `Feedback` class represents a feedback message that should be shown to the user.
|
|
3
|
-
* - Basic `Feedback` is neither good nor bad, `Feedback` indicates good news, and `Feedback` indicates bad news.
|
|
4
|
-
*
|
|
5
|
-
* Conceptually different to a Javascript `Error`...
|
|
6
|
-
* - `Error`: a program error designed to help developers fix an issue in their code.
|
|
7
|
-
* - `Feedback`: generated in reaction to something a user did, and helps them understand what to do next.
|
|
8
|
-
*/
|
|
9
|
-
export class Feedback {
|
|
10
|
-
/** String feedback message that is safe to show to a user. */
|
|
11
|
-
message;
|
|
12
|
-
constructor(message) {
|
|
13
|
-
this.message = message;
|
|
14
|
-
}
|
|
15
|
-
toString() {
|
|
16
|
-
return this.message;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/** Feedback with a known and typed `.value` field. */
|
|
20
|
-
export class ValueFeedback extends Feedback {
|
|
21
|
-
value;
|
|
22
|
-
constructor(message, value) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.value = value;
|
|
25
|
-
}
|
|
26
|
-
}
|
package/feedback/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./Feedback.js";
|
package/feedback/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./Feedback.js";
|