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.
@@ -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 (!(thrown instanceof Feedback))
121
+ if (typeof thrown !== "string")
123
122
  throw thrown;
124
- throw new ValueError(`Invalid data for "${collection}"\n${thrown.message}`, { item, caller });
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 (!(thrown instanceof Feedback))
140
+ if (typeof thrown !== "string")
142
141
  throw thrown;
143
- messages.push(getNamedMessage(item.id, thrown.message));
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 (!(thrown instanceof Feedback))
153
+ if (typeof thrown !== "string")
155
154
  throw thrown;
156
- throw new ValueError(`Invalid updates for "${collection}"\n${thrown.message}`, { updates, caller });
155
+ throw new ValueError(`Invalid updates for "${collection}"\n${thrown}`, { updates, caller });
157
156
  }
158
157
  }
@@ -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 `Feedback` if the payload is invalid.
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 `Feedback` if the payload is invalid.
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 `Feedback` if the payload is invalid.
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
  */
@@ -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 `Feedback` if the payload is invalid.
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 instanceof Feedback)
61
- throw new ValueError(`Invalid result for ${this.toString()}:\n${thrown.message}`, {
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 `Feedback` if the payload is invalid.
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 instanceof Feedback)
114
- throw new ResponseError(`Invalid result for ${this.toString()}:\n${thrown.message}`, {
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 `Feedback` if the payload is invalid.
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
@@ -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 "./markup/index.js";
11
10
  export * from "./schema/index.js";
12
11
  export * from "./sequence/index.js";
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.
@@ -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.170.2",
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",
@@ -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 new ValueFeedback("Must be array", unsafeValue);
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 new ValueFeedback(uniqueArray.length ? `Minimum ${this.min} ${this.many}` : "Required", uniqueArray);
45
+ throw uniqueArray.length ? `Minimum ${this.min} ${this.many}` : "Required";
47
46
  if (uniqueArray.length > this.max)
48
- throw new ValueFeedback(`Maximum ${this.max} ${this.many}`, uniqueArray);
47
+ throw `Maximum ${this.max} ${this.many}`;
49
48
  return uniqueArray;
50
49
  }
51
50
  }
@@ -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 new Feedback("Required");
12
+ throw "Required";
14
13
  return value;
15
14
  }
16
15
  }
@@ -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 new ValueFeedback("Unknown value", unsafeValue);
29
+ throw "Unknown value";
31
30
  }
32
31
  // Implement iterable.
33
32
  *[Symbol.iterator]() {
@@ -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. */
@@ -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 new ValueFeedback("Must be object", unsafeValue);
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
  }
@@ -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 new ValueFeedback(value ? "Invalid date" : "Required", value);
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 new ValueFeedback(`Minimum ${this.format(this.min)}`, rounded);
24
+ throw `Minimum ${this.format(this.min)}`;
26
25
  if (this.max && rounded > this.max)
27
- throw new ValueFeedback(`Maximum ${this.format(this.max)}`, rounded);
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 new ValueFeedback("Must be object", unsafeValue);
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 new ValueFeedback(length ? `Minimum ${this.min} ${this.many}` : "Required", validDictionary);
21
+ throw length ? `Minimum ${this.min} ${this.many}` : "Required";
23
22
  if (length > this.max)
24
- throw new ValueFeedback(`Maximum ${this.max} ${this.many}`, validDictionary);
23
+ throw `Maximum ${this.max} ${this.many}`;
25
24
  return validDictionary;
26
25
  }
27
26
  }
@@ -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 new ValueFeedback("Must be entity", unsafeValue);
16
+ throw "Must be entity";
18
17
  if (this.types && !isArrayItem(this.types, type))
19
- throw new ValueFeedback("Invalid entity type", type);
18
+ throw "Invalid entity type";
20
19
  return entity;
21
20
  }
22
21
  }
@@ -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 new ValueFeedback("Must be file name with extension", unsafeValue);
16
+ throw "Must be file name with extension";
18
17
  if (this.types && !isProp(this.types, extension))
19
- throw new ValueFeedback("Invalid file type", extension);
18
+ throw "Invalid file type";
20
19
  return path;
21
20
  }
22
21
  }
@@ -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 new ValueFeedback("Must be number", unsafeValue);
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 new ValueFeedback(!optionalNumber ? "Required" : `Minimum ${formatNumber(this.min)}`, roundedNumber);
22
+ throw !optionalNumber ? "Required" : `Minimum ${formatNumber(this.min)}`;
24
23
  if (roundedNumber > this.max)
25
- throw new ValueFeedback(`Maximum ${formatNumber(this.max)}`, roundedNumber);
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 `Feedback` if the validated value is falsy. */
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
  }
@@ -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 `Feedback` if the validated value is falsy. */
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 new ValueFeedback("Required", safeValue);
7
+ throw "Required";
9
8
  return safeValue;
10
9
  }
11
10
  }
@@ -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]>;
@@ -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 new ValueFeedback("Must be string", unsafeValue);
38
+ throw "Must be string";
40
39
  const saneString = this.sanitize(possibleString);
41
40
  if (saneString.length < this.min)
42
- throw new ValueFeedback(possibleString.length ? `Minimum ${this.min} characters` : "Required", saneString);
41
+ throw possibleString.length ? `Minimum ${this.min} characters` : "Required";
43
42
  if (saneString.length > this.max)
44
- throw new ValueFeedback(`Maximum ${this.max} characters`, saneString);
43
+ throw `Maximum ${this.max} characters`;
45
44
  if (this.match && !this.match.test(saneString))
46
- throw new ValueFeedback(saneString ? "Invalid format" : "Required", saneString);
45
+ throw saneString ? "Invalid format" : "Required";
47
46
  return saneString;
48
47
  }
49
48
  /** Sanitize the string by removing unwanted characters. */
@@ -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 new ValueFeedback(str ? "Invalid format" : "Required", str);
28
+ throw str ? "Invalid format" : "Required";
30
29
  if (this.schemes && !this.schemes.includes(uri.protocol))
31
- throw new ValueFeedback("Invalid URI scheme", str);
30
+ throw "Invalid URI scheme";
32
31
  return uri.href;
33
32
  }
34
33
  }
@@ -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 new ValueFeedback(str ? "Invalid format" : "Required", str);
31
+ throw str ? "Invalid format" : "Required";
33
32
  if (this.schemes && !this.schemes.includes(url.protocol))
34
- throw new ValueFeedback("Invalid URL scheme", str);
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 `Feedback` instance, return a 400 response with the feedback's message as JSON, e.g. `{ message: "Invalid input" }`
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 `Feedback` instance, return a 400 response with the feedback's message as JSON, e.g. `{ message: "Invalid input" }`
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 'Feedback' to return `{ message: "etc" }` to the client, e.g. for input validation.
105
- if (reason instanceof Feedback)
106
- return Response.json(reason, { status: 422 }); // HTTP 422 Unprocessable Entity
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).
@@ -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 `Feedback` If the value is invalid and cannot be fixed and we want to explain why to an end user.
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 Feedback if one or more items did not validate.
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 Feedback if one or more entry values did not validate.
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 Feedback if one or more entry values did not validate.
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 Feedback if one or more props did not validate.
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 instanceof Feedback)
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 instanceof Feedback)
26
- throw new RequiredError(thrown.message, { cause: thrown, caller });
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 Feedback if one or more items did not validate.
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 (!(thrown instanceof Feedback))
43
+ if (typeof thrown !== "string")
45
44
  throw thrown;
46
- messages.push(getNamedMessage(index.toString(), thrown.message));
45
+ messages.push(getNamedMessage(index.toString(), thrown));
47
46
  }
48
47
  index++;
49
48
  }
50
49
  if (messages.length)
51
- throw new ValueFeedback(messages.join("\n"), unsafeItems);
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 Feedback if one or more entry values did not validate.
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 (!(thrown instanceof Feedback))
71
+ if (typeof thrown !== "string")
73
72
  throw thrown;
74
- messages.push(getNamedMessage(index.toString(), thrown.message));
73
+ messages.push(getNamedMessage(index.toString(), thrown));
75
74
  }
76
75
  index++;
77
76
  }
78
77
  if (messages.length)
79
- throw new ValueFeedback(messages.join("\n"), unsafeArray);
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 Feedback if one or more entry values did not validate.
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 (!(thrown instanceof Feedback))
98
+ if (typeof thrown !== "string")
100
99
  throw thrown;
101
- messages.push(getNamedMessage(key, thrown.message));
100
+ messages.push(getNamedMessage(key, thrown));
102
101
  }
103
102
  }
104
103
  if (messages.length)
105
- throw new ValueFeedback(messages.join("\n"), unsafeDictionary);
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 Feedback if one or more props did not validate.
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 (!(thrown instanceof Feedback))
139
+ if (typeof thrown !== "string")
141
140
  throw thrown;
142
- messages.push(getNamedMessage(key, thrown.message));
141
+ messages.push(getNamedMessage(key, thrown));
143
142
  }
144
143
  }
145
144
  if (messages.length)
146
- throw new ValueFeedback(messages.join("\n"), unsafeData);
145
+ throw messages.join("\n");
147
146
  if (changes)
148
147
  return safeData;
149
148
  // Check that no excess keys exist.
@@ -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
- }
@@ -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
- }
@@ -1 +0,0 @@
1
- export * from "./Feedback.js";
package/feedback/index.js DELETED
@@ -1 +0,0 @@
1
- export * from "./Feedback.js";