shelving 1.185.2 → 1.186.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 +1 -1
- package/schema/ChoiceSchema.js +1 -1
- package/schema/CurrencyAmountSchema.d.ts +33 -0
- package/schema/CurrencyAmountSchema.js +41 -0
- package/schema/CurrencyCodeSchema.d.ts +21 -0
- package/schema/CurrencyCodeSchema.js +35 -0
- package/schema/DateSchema.d.ts +6 -3
- package/schema/DateSchema.js +8 -9
- package/schema/DateTimeSchema.d.ts +1 -2
- package/schema/DateTimeSchema.js +2 -5
- package/schema/FileSchema.js +2 -2
- package/schema/NumberSchema.d.ts +8 -4
- package/schema/NumberSchema.js +19 -18
- package/schema/PhoneSchema.d.ts +4 -1
- package/schema/PhoneSchema.js +6 -6
- package/schema/StringSchema.d.ts +2 -2
- package/schema/StringSchema.js +13 -13
- package/schema/TimeSchema.d.ts +1 -2
- package/schema/TimeSchema.js +2 -5
- package/schema/URISchema.js +2 -2
- package/schema/URLSchema.js +2 -2
- package/schema/index.d.ts +2 -0
- package/schema/index.js +2 -0
- package/util/currency.d.ts +28 -0
- package/util/currency.js +46 -0
- package/util/format.js +2 -1
- package/util/index.d.ts +1 -0
- package/util/index.js +1 -0
package/package.json
CHANGED
package/schema/ChoiceSchema.js
CHANGED
|
@@ -26,7 +26,7 @@ export class ChoiceSchema extends Schema {
|
|
|
26
26
|
validate(unsafeValue = this.value) {
|
|
27
27
|
if (typeof unsafeValue === "string" && isOption(this.options, unsafeValue))
|
|
28
28
|
return unsafeValue;
|
|
29
|
-
throw unsafeValue ?
|
|
29
|
+
throw unsafeValue ? `Unknown ${this.one}` : "Required";
|
|
30
30
|
}
|
|
31
31
|
// Implement iterable.
|
|
32
32
|
*[Symbol.iterator]() {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CurrencyCode } from "../util/currency.js";
|
|
2
|
+
import { NumberSchema, type NumberSchemaOptions } from "./NumberSchema.js";
|
|
3
|
+
/** Allowed options for `CurrencyAmountSchema`. */
|
|
4
|
+
export interface CurrencyAmountSchemaOptions extends NumberSchemaOptions {
|
|
5
|
+
readonly symbol?: string | undefined;
|
|
6
|
+
readonly currency: CurrencyCode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Schema representing a numeric amount in a specific currency.
|
|
10
|
+
*
|
|
11
|
+
* - The validation step is inferred from the currency's minor units.
|
|
12
|
+
* - The default formatter renders amounts using shelving's currency helpers.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const PRICE = new CurrencyAmountSchema({ currency: "GBP", min: 0 });
|
|
16
|
+
* PRICE.validate("12.345"); // 12.35
|
|
17
|
+
* PRICE.format(12.3); // "£12.30"
|
|
18
|
+
*/
|
|
19
|
+
export declare class CurrencyAmountSchema extends NumberSchema {
|
|
20
|
+
readonly currency: CurrencyCode;
|
|
21
|
+
readonly symbol: string;
|
|
22
|
+
constructor({ currency, one, title, symbol, step, format, ...options }: CurrencyAmountSchemaOptions);
|
|
23
|
+
}
|
|
24
|
+
/** Valid non-negative monetary amount in the a currency. */
|
|
25
|
+
export declare const CURRENCY_AMOUNT: (currency: CurrencyCode) => CurrencyAmountSchema;
|
|
26
|
+
export declare const USD_AMOUNT: CurrencyAmountSchema;
|
|
27
|
+
export declare const GBP_AMOUNT: CurrencyAmountSchema;
|
|
28
|
+
export declare const EUR_AMOUNT: CurrencyAmountSchema;
|
|
29
|
+
/** Valid optional monetary amount in the default currency, or `null`. */
|
|
30
|
+
export declare const NULLABLE_CURRENCY_AMOUNT: (currency: CurrencyCode) => import("./NullableSchema.js").NullableSchema<number>;
|
|
31
|
+
export declare const NULLABLE_USD_AMOUNT: import("./NullableSchema.js").NullableSchema<number>;
|
|
32
|
+
export declare const NULLABLE_GBP_AMOUNT: import("./NullableSchema.js").NullableSchema<number>;
|
|
33
|
+
export declare const NULLABLE_EUR_AMOUNT: import("./NullableSchema.js").NullableSchema<number>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getCurrencyStep, getCurrencySymbol, requireCurrencyCode } from "../util/currency.js";
|
|
2
|
+
import { formatCurrency } from "../util/format.js";
|
|
3
|
+
import { NULLABLE } from "./NullableSchema.js";
|
|
4
|
+
import { NumberSchema } from "./NumberSchema.js";
|
|
5
|
+
/**
|
|
6
|
+
* Schema representing a numeric amount in a specific currency.
|
|
7
|
+
*
|
|
8
|
+
* - The validation step is inferred from the currency's minor units.
|
|
9
|
+
* - The default formatter renders amounts using shelving's currency helpers.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const PRICE = new CurrencyAmountSchema({ currency: "GBP", min: 0 });
|
|
13
|
+
* PRICE.validate("12.345"); // 12.35
|
|
14
|
+
* PRICE.format(12.3); // "£12.30"
|
|
15
|
+
*/
|
|
16
|
+
export class CurrencyAmountSchema extends NumberSchema {
|
|
17
|
+
currency;
|
|
18
|
+
symbol;
|
|
19
|
+
constructor({ currency, one = "amount", title = "Amount", symbol, step, format = (value, options) => formatCurrency(value, currency, options), ...options }) {
|
|
20
|
+
const validCurrency = requireCurrencyCode(currency, CurrencyAmountSchema);
|
|
21
|
+
super({
|
|
22
|
+
one,
|
|
23
|
+
title,
|
|
24
|
+
step: step ?? getCurrencyStep(validCurrency, CurrencyAmountSchema),
|
|
25
|
+
format,
|
|
26
|
+
...options,
|
|
27
|
+
});
|
|
28
|
+
this.currency = validCurrency;
|
|
29
|
+
this.symbol = symbol ?? getCurrencySymbol(validCurrency, CurrencyAmountSchema);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Valid non-negative monetary amount in the a currency. */
|
|
33
|
+
export const CURRENCY_AMOUNT = (currency) => new CurrencyAmountSchema({ currency });
|
|
34
|
+
export const USD_AMOUNT = new CurrencyAmountSchema({ currency: "USD" });
|
|
35
|
+
export const GBP_AMOUNT = new CurrencyAmountSchema({ currency: "GBP" });
|
|
36
|
+
export const EUR_AMOUNT = new CurrencyAmountSchema({ currency: "EUR" });
|
|
37
|
+
/** Valid optional monetary amount in the default currency, or `null`. */
|
|
38
|
+
export const NULLABLE_CURRENCY_AMOUNT = (currency) => NULLABLE(CURRENCY_AMOUNT(currency));
|
|
39
|
+
export const NULLABLE_USD_AMOUNT = NULLABLE(USD_AMOUNT);
|
|
40
|
+
export const NULLABLE_GBP_AMOUNT = NULLABLE(GBP_AMOUNT);
|
|
41
|
+
export const NULLABLE_EUR_AMOUNT = NULLABLE(EUR_AMOUNT);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ImmutableArray } from "../util/array.js";
|
|
2
|
+
import { type CurrencyCode } from "../util/currency.js";
|
|
3
|
+
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
4
|
+
import { StringSchema } from "./StringSchema.js";
|
|
5
|
+
/** Options for a `CurrencyCodeSchema` */
|
|
6
|
+
export interface CurrencyCodeSchemaOptions extends Omit<StringSchemaOptions, "input" | "min" | "max" | "match" | "multiline"> {
|
|
7
|
+
currencies?: ImmutableArray<CurrencyCode>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Type of `StringSchema` that defines a valid currency code.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CurrencyCodeSchema extends StringSchema {
|
|
13
|
+
readonly currencies: ImmutableArray<CurrencyCode>;
|
|
14
|
+
constructor({ one, title, currencies, ...options }: CurrencyCodeSchemaOptions);
|
|
15
|
+
sanitize(insaneString: string): string;
|
|
16
|
+
validate(value?: unknown): string;
|
|
17
|
+
}
|
|
18
|
+
/** Valid currency code, e.g. `GBP` */
|
|
19
|
+
export declare const CURRENCY_CODE: CurrencyCodeSchema;
|
|
20
|
+
/** Valid currency code, e.g. `GBP`, or `null` */
|
|
21
|
+
export declare const NULLABLE_CURRENCY_CODE: import("./NullableSchema.js").NullableSchema<string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CURRENCY_CODES } from "../util/currency.js";
|
|
2
|
+
import { NULLABLE } from "./NullableSchema.js";
|
|
3
|
+
import { StringSchema } from "./StringSchema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Type of `StringSchema` that defines a valid currency code.
|
|
6
|
+
*/
|
|
7
|
+
export class CurrencyCodeSchema extends StringSchema {
|
|
8
|
+
currencies;
|
|
9
|
+
constructor({ one = "currency", title = "Currency", currencies = CURRENCY_CODES, ...options }) {
|
|
10
|
+
super({
|
|
11
|
+
one,
|
|
12
|
+
title,
|
|
13
|
+
...options,
|
|
14
|
+
min: 3,
|
|
15
|
+
max: 3, // Valid currency code is 3 uppercase letters.
|
|
16
|
+
case: "upper",
|
|
17
|
+
match: /^[A-Z]{3}$/, // Valid currency code is 3 uppercase letters.
|
|
18
|
+
});
|
|
19
|
+
this.currencies = currencies;
|
|
20
|
+
}
|
|
21
|
+
sanitize(insaneString) {
|
|
22
|
+
// Strip characters that aren't A-Z (including whitespace).
|
|
23
|
+
return super.sanitize(insaneString).replace(/[^A-Z+]/g, "");
|
|
24
|
+
}
|
|
25
|
+
validate(value) {
|
|
26
|
+
const currency = super.validate(value);
|
|
27
|
+
if (!this.currencies.includes(currency))
|
|
28
|
+
throw "Unknown currency code";
|
|
29
|
+
return currency;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Valid currency code, e.g. `GBP` */
|
|
33
|
+
export const CURRENCY_CODE = new CurrencyCodeSchema({});
|
|
34
|
+
/** Valid currency code, e.g. `GBP`, or `null` */
|
|
35
|
+
export const NULLABLE_CURRENCY_CODE = NULLABLE(CURRENCY_CODE);
|
package/schema/DateSchema.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PossibleDate } from "../util/date.js";
|
|
2
|
+
import { formatDate } from "../util/format.js";
|
|
2
3
|
import type { Nullish } from "../util/null.js";
|
|
3
4
|
import type { SchemaOptions } from "./Schema.js";
|
|
4
5
|
import { Schema } from "./Schema.js";
|
|
@@ -10,6 +11,8 @@ export interface DateSchemaOptions extends SchemaOptions {
|
|
|
10
11
|
readonly min?: Nullish<PossibleDate>;
|
|
11
12
|
readonly max?: Nullish<PossibleDate>;
|
|
12
13
|
readonly input?: DateInputType | undefined;
|
|
14
|
+
/** Format the date for display in downstream UIs. */
|
|
15
|
+
readonly format?: typeof formatDate | undefined;
|
|
13
16
|
/**
|
|
14
17
|
* Rounding step (in milliseconds, because that's the base unit for time).
|
|
15
18
|
* - E.g. `1000 * 60` will round to the nearest minute.
|
|
@@ -18,15 +21,15 @@ export interface DateSchemaOptions extends SchemaOptions {
|
|
|
18
21
|
readonly step?: number | undefined;
|
|
19
22
|
}
|
|
20
23
|
export declare class DateSchema extends Schema<string> {
|
|
21
|
-
readonly value: PossibleDate;
|
|
24
|
+
readonly value: PossibleDate | undefined;
|
|
22
25
|
readonly min: Date | undefined;
|
|
23
26
|
readonly max: Date | undefined;
|
|
24
27
|
readonly input: DateInputType;
|
|
25
28
|
readonly step: number | undefined;
|
|
26
|
-
|
|
29
|
+
format: (value: Date) => string;
|
|
30
|
+
constructor({ one, min, max, value, input, step, format, ...options }: DateSchemaOptions);
|
|
27
31
|
validate(value?: unknown): string;
|
|
28
32
|
stringify(value: Date): string;
|
|
29
|
-
format(value: Date): string;
|
|
30
33
|
}
|
|
31
34
|
/** Valid date, e.g. `2005-09-12` (required because falsy values are invalid). */
|
|
32
35
|
export declare const DATE: DateSchema;
|
package/schema/DateSchema.js
CHANGED
|
@@ -8,30 +8,29 @@ export class DateSchema extends Schema {
|
|
|
8
8
|
max;
|
|
9
9
|
input;
|
|
10
10
|
step;
|
|
11
|
-
|
|
11
|
+
format;
|
|
12
|
+
constructor({ one = "date", min, max, value, input = "date", step, format = formatDate, ...options }) {
|
|
12
13
|
super({ one, title: "Date", value, ...options });
|
|
13
14
|
this.min = getDate(min);
|
|
14
15
|
this.max = getDate(max);
|
|
15
16
|
this.input = input;
|
|
16
17
|
this.step = step;
|
|
18
|
+
this.format = format;
|
|
17
19
|
}
|
|
18
20
|
validate(value = this.value) {
|
|
19
21
|
const date = getDate(value);
|
|
20
22
|
if (!date)
|
|
21
|
-
throw value ?
|
|
22
|
-
const
|
|
23
|
-
if (this.min &&
|
|
23
|
+
throw value ? `Invalid ${this.one} format` : "Required";
|
|
24
|
+
const stepped = typeof this.step === "number" ? new Date(roundStep(date.getTime(), this.step)) : date;
|
|
25
|
+
if (this.min && stepped < this.min)
|
|
24
26
|
throw `Minimum ${this.format(this.min)}`;
|
|
25
|
-
if (this.max &&
|
|
27
|
+
if (this.max && stepped > this.max)
|
|
26
28
|
throw `Maximum ${this.format(this.max)}`;
|
|
27
|
-
return this.stringify(
|
|
29
|
+
return this.stringify(stepped);
|
|
28
30
|
}
|
|
29
31
|
stringify(value) {
|
|
30
32
|
return requireDateString(value);
|
|
31
33
|
}
|
|
32
|
-
format(value) {
|
|
33
|
-
return formatDate(value);
|
|
34
|
-
}
|
|
35
34
|
}
|
|
36
35
|
/** Valid date, e.g. `2005-09-12` (required because falsy values are invalid). */
|
|
37
36
|
export const DATE = new DateSchema({});
|
|
@@ -6,8 +6,7 @@ import { DateSchema, type DateSchemaOptions } from "./DateSchema.js";
|
|
|
6
6
|
* - If you wish to define an _abstract_ time without a timezone, e.g. a daily alarm, use `TimeSchema` instead.
|
|
7
7
|
*/
|
|
8
8
|
export declare class DateTimeSchema extends DateSchema {
|
|
9
|
-
constructor({ one, title, input, ...options }: DateSchemaOptions);
|
|
10
|
-
format(value: Date): string;
|
|
9
|
+
constructor({ one, title, input, format, ...options }: DateSchemaOptions);
|
|
11
10
|
stringify(value: Date): string;
|
|
12
11
|
}
|
|
13
12
|
/** Valid datetime, e.g. `2005-09-12T08:00:00Z` (required because falsy values are invalid). */
|
package/schema/DateTimeSchema.js
CHANGED
|
@@ -8,11 +8,8 @@ import { NULLABLE } from "./NullableSchema.js";
|
|
|
8
8
|
* - If you wish to define an _abstract_ time without a timezone, e.g. a daily alarm, use `TimeSchema` instead.
|
|
9
9
|
*/
|
|
10
10
|
export class DateTimeSchema extends DateSchema {
|
|
11
|
-
constructor({ one = "time", title = "Time", input = "datetime-local", ...options }) {
|
|
12
|
-
super({ one, title, input, ...options });
|
|
13
|
-
}
|
|
14
|
-
format(value) {
|
|
15
|
-
return formatDateTime(value);
|
|
11
|
+
constructor({ one = "time", title = "Time", input = "datetime-local", format = formatDateTime, ...options }) {
|
|
12
|
+
super({ one, title, input, format, ...options });
|
|
16
13
|
}
|
|
17
14
|
stringify(value) {
|
|
18
15
|
return value.toISOString();
|
package/schema/FileSchema.js
CHANGED
|
@@ -13,9 +13,9 @@ export class FileSchema extends StringSchema {
|
|
|
13
13
|
const path = super.validate(unsafeValue);
|
|
14
14
|
const extension = getFileExtension(path);
|
|
15
15
|
if (!extension)
|
|
16
|
-
throw
|
|
16
|
+
throw `Must have extension`;
|
|
17
17
|
if (this.types && !isProp(this.types, extension))
|
|
18
|
-
throw
|
|
18
|
+
throw `Invalid extension`;
|
|
19
19
|
return path;
|
|
20
20
|
}
|
|
21
21
|
}
|
package/schema/NumberSchema.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatNumber } from "../util/format.js";
|
|
1
2
|
import type { SchemaOptions } from "./Schema.js";
|
|
2
3
|
import { Schema } from "./Schema.js";
|
|
3
4
|
/** Allowed options for `NumberSchema` */
|
|
@@ -6,17 +7,20 @@ export interface NumberSchemaOptions extends SchemaOptions {
|
|
|
6
7
|
readonly min?: number | undefined;
|
|
7
8
|
readonly max?: number | undefined;
|
|
8
9
|
readonly step?: number | undefined;
|
|
10
|
+
/** Format the number for display in downstream UIs. */
|
|
11
|
+
readonly format?: typeof formatNumber | undefined;
|
|
9
12
|
}
|
|
10
13
|
/** Schema that defines a valid number. */
|
|
11
14
|
export declare class NumberSchema extends Schema<number> {
|
|
12
|
-
readonly value: number;
|
|
15
|
+
readonly value: number | undefined;
|
|
13
16
|
readonly min: number;
|
|
14
17
|
readonly max: number;
|
|
15
18
|
readonly step: number | undefined;
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
format: (value: number) => string;
|
|
20
|
+
constructor({ one, title, min, max, step, format, value, ...options }: NumberSchemaOptions);
|
|
21
|
+
validate(value?: unknown): number;
|
|
18
22
|
}
|
|
19
|
-
/** Valid number, e.g. `2048.12345` or `0` zero. */
|
|
23
|
+
/** Valid number, e.g. `2048.12345` or `0` zero and a default value of zero. */
|
|
20
24
|
export declare const NUMBER: NumberSchema;
|
|
21
25
|
/** Valid optional number, e.g. `2048.12345` or `0` zero, or `null` */
|
|
22
26
|
export declare const NULLABLE_NUMBER: import("./NullableSchema.js").NullableSchema<number>;
|
package/schema/NumberSchema.js
CHANGED
|
@@ -7,38 +7,40 @@ export class NumberSchema extends Schema {
|
|
|
7
7
|
min;
|
|
8
8
|
max;
|
|
9
9
|
step;
|
|
10
|
-
|
|
10
|
+
format;
|
|
11
|
+
constructor({ one = "number", title = "Number", min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY, step, format = formatNumber, value, ...options }) {
|
|
11
12
|
super({ one, title, value, ...options });
|
|
12
13
|
this.min = min;
|
|
13
14
|
this.max = max;
|
|
14
15
|
this.step = step;
|
|
16
|
+
this.format = format;
|
|
15
17
|
}
|
|
16
|
-
validate(
|
|
17
|
-
const
|
|
18
|
-
if (typeof
|
|
19
|
-
throw
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
throw !
|
|
23
|
-
if (
|
|
24
|
-
throw `Maximum ${
|
|
25
|
-
return
|
|
18
|
+
validate(value = this.value) {
|
|
19
|
+
const number = getNumber(value);
|
|
20
|
+
if (typeof number !== "number")
|
|
21
|
+
throw value ? `Must be ${this.one}` : "Required";
|
|
22
|
+
const stepped = typeof this.step === "number" ? roundStep(number, this.step) : number;
|
|
23
|
+
if (stepped < this.min)
|
|
24
|
+
throw !number ? "Required" : `Minimum ${this.format(this.min)}`;
|
|
25
|
+
if (stepped > this.max)
|
|
26
|
+
throw `Maximum ${this.format(this.max)}`;
|
|
27
|
+
return stepped;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
/** Valid number, e.g. `2048.12345` or `0` zero. */
|
|
30
|
+
/** Valid number, e.g. `2048.12345` or `0` zero and a default value of zero. */
|
|
29
31
|
export const NUMBER = new NumberSchema({ title: "Number" });
|
|
30
32
|
/** Valid optional number, e.g. `2048.12345` or `0` zero, or `null` */
|
|
31
33
|
export const NULLABLE_NUMBER = NULLABLE(NUMBER);
|
|
32
34
|
/** Valid integer number, e.g. `2048` or `0` zero. */
|
|
33
|
-
export const INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER
|
|
35
|
+
export const INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER });
|
|
34
36
|
/** Valid positive integer number, e.g. `1,2,3` (not including zero). */
|
|
35
|
-
export const POSITIVE_INTEGER = new NumberSchema({ step: 1, min: 1, max: Number.MAX_SAFE_INTEGER
|
|
37
|
+
export const POSITIVE_INTEGER = new NumberSchema({ step: 1, min: 1, max: Number.MAX_SAFE_INTEGER });
|
|
36
38
|
/** Valid non-negative integer number, e.g. `0,1,2,3` (including zero). */
|
|
37
|
-
export const NON_NEGATIVE_INTEGER = new NumberSchema({ step: 1, min: 0, max: Number.MAX_SAFE_INTEGER
|
|
39
|
+
export const NON_NEGATIVE_INTEGER = new NumberSchema({ step: 1, min: 0, max: Number.MAX_SAFE_INTEGER });
|
|
38
40
|
/** Valid negative integer number, e.g. `-1,-2,-3` (not including zero). */
|
|
39
|
-
export const NEGATIVE_INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: -1
|
|
41
|
+
export const NEGATIVE_INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: -1 });
|
|
40
42
|
/** Valid non-positive integer number, e.g. `0,-1,-2,-3` (including zero). */
|
|
41
|
-
export const NON_POSITIVE_INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: 0
|
|
43
|
+
export const NON_POSITIVE_INTEGER = new NumberSchema({ step: 1, min: Number.MIN_SAFE_INTEGER, max: 0 });
|
|
42
44
|
/** Valid optional integer number, e.g. `2048` or `0` zero, or `null` */
|
|
43
45
|
export const NULLABLE_INTEGER = NULLABLE(INTEGER);
|
|
44
46
|
/** Valid Unix timestamp (including milliseconds). */
|
|
@@ -47,7 +49,6 @@ export const TIMESTAMP = new NumberSchema({
|
|
|
47
49
|
step: 1,
|
|
48
50
|
min: Number.MIN_SAFE_INTEGER,
|
|
49
51
|
max: Number.MAX_SAFE_INTEGER,
|
|
50
|
-
value: 0,
|
|
51
52
|
});
|
|
52
53
|
/** Valid Unix timestamp (including milliseconds). */
|
|
53
54
|
export const NULLABLE_TIMESTAMP = NULLABLE_INTEGER;
|
package/schema/PhoneSchema.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
2
2
|
import { StringSchema } from "./StringSchema.js";
|
|
3
|
+
/** Options for a `PhoneSchema` */
|
|
4
|
+
export interface PhoneSchemaOptions extends Omit<StringSchemaOptions, "input" | "min" | "max" | "match" | "multiline"> {
|
|
5
|
+
}
|
|
3
6
|
/**
|
|
4
7
|
* Type of `StringSchema` that defines a valid phone number.
|
|
5
8
|
* - Multiple string formats are automatically converted to E.164 format (starting with `+` plus).
|
|
6
9
|
* - Falsy values are converted to `""` empty string.
|
|
7
10
|
*/
|
|
8
11
|
export declare class PhoneSchema extends StringSchema {
|
|
9
|
-
constructor({ one, title, ...options }:
|
|
12
|
+
constructor({ one, title, ...options }: PhoneSchemaOptions);
|
|
10
13
|
sanitize(insaneString: string): string;
|
|
11
14
|
}
|
|
12
15
|
/** Valid phone number, e.g. `+441234567890` */
|
package/schema/PhoneSchema.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { NULLABLE } from "./NullableSchema.js";
|
|
2
2
|
import { StringSchema } from "./StringSchema.js";
|
|
3
|
-
// Valid phone number is max 16 digits made up of:
|
|
4
|
-
// - Country code (`+` plus character and 1-3 digits, e.g. `+44` or `+1`).
|
|
5
|
-
// - Subscriber number (5-12 digits — the Solomon Islands have five-digit phone numbers apparently).
|
|
6
|
-
const PHONE_REGEXP = /^\+[1-9][0-9]{0,2}[0-9]{5,12}$/;
|
|
7
3
|
/**
|
|
8
4
|
* Type of `StringSchema` that defines a valid phone number.
|
|
9
5
|
* - Multiple string formats are automatically converted to E.164 format (starting with `+` plus).
|
|
@@ -17,8 +13,12 @@ export class PhoneSchema extends StringSchema {
|
|
|
17
13
|
...options,
|
|
18
14
|
input: "tel",
|
|
19
15
|
min: 1,
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
// Valid phone number is 16 digits or fewer (15 numerals with a leading `+` plus).
|
|
17
|
+
max: 16,
|
|
18
|
+
// Valid phone number is max 16 digits made up of:
|
|
19
|
+
// - Country code (`+` plus character and 1-3 digits, e.g. `+44` or `+1`).
|
|
20
|
+
// - Subscriber number (5-12 digits — the Solomon Islands have five-digit phone numbers apparently).
|
|
21
|
+
match: /^\+[1-9][0-9]{0,2}[0-9]{5,12}$/,
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
sanitize(insaneString) {
|
package/schema/StringSchema.d.ts
CHANGED
|
@@ -35,8 +35,8 @@ export declare class StringSchema extends Schema<string> {
|
|
|
35
35
|
readonly multiline: boolean;
|
|
36
36
|
readonly match: RegExp | undefined;
|
|
37
37
|
readonly case: "upper" | "lower" | undefined;
|
|
38
|
-
constructor({ min, max, value, multiline, match, case: _case, input, ...options }: StringSchemaOptions);
|
|
39
|
-
validate(
|
|
38
|
+
constructor({ one, min, max, value, multiline, match, case: _case, input, ...options }: StringSchemaOptions);
|
|
39
|
+
validate(value?: unknown): string;
|
|
40
40
|
/** Sanitize the string by removing unwanted characters. */
|
|
41
41
|
sanitize(str: string): string;
|
|
42
42
|
}
|
package/schema/StringSchema.js
CHANGED
|
@@ -23,8 +23,8 @@ export class StringSchema extends Schema {
|
|
|
23
23
|
multiline;
|
|
24
24
|
match;
|
|
25
25
|
case;
|
|
26
|
-
constructor({ min = 0, max = Number.POSITIVE_INFINITY, value = "", multiline = false, match, case: _case, input = "text", ...options }) {
|
|
27
|
-
super({ value, ...options });
|
|
26
|
+
constructor({ one = "string", min = 0, max = Number.POSITIVE_INFINITY, value = "", multiline = false, match, case: _case, input = "text", ...options }) {
|
|
27
|
+
super({ one, value, ...options });
|
|
28
28
|
this.min = min;
|
|
29
29
|
this.max = max;
|
|
30
30
|
this.multiline = multiline;
|
|
@@ -32,18 +32,18 @@ export class StringSchema extends Schema {
|
|
|
32
32
|
this.case = _case;
|
|
33
33
|
this.input = input;
|
|
34
34
|
}
|
|
35
|
-
validate(
|
|
36
|
-
const
|
|
37
|
-
if (typeof
|
|
38
|
-
throw
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
throw
|
|
42
|
-
if (
|
|
35
|
+
validate(value = this.value) {
|
|
36
|
+
const str = typeof value === "number" ? value.toString() : value;
|
|
37
|
+
if (typeof str !== "string")
|
|
38
|
+
throw value ? `Must be ${this.one}` : "Required";
|
|
39
|
+
const sane = this.sanitize(str);
|
|
40
|
+
if (this.match && !this.match.test(sane))
|
|
41
|
+
throw str.length ? `Invalid ${this.one}` : "Required";
|
|
42
|
+
if (sane.length < this.min)
|
|
43
|
+
throw str.length ? `Minimum ${this.min} characters` : "Required";
|
|
44
|
+
if (sane.length > this.max)
|
|
43
45
|
throw `Maximum ${this.max} characters`;
|
|
44
|
-
|
|
45
|
-
throw saneString ? "Invalid format" : "Required";
|
|
46
|
-
return saneString;
|
|
46
|
+
return sane;
|
|
47
47
|
}
|
|
48
48
|
/** Sanitize the string by removing unwanted characters. */
|
|
49
49
|
sanitize(str) {
|
package/schema/TimeSchema.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { DateSchema, type DateSchemaOptions } from "./DateSchema.js";
|
|
2
2
|
/** Define a valid time in 24h hh:mm:ss.fff format, e.g. `23:59` or `24:00 */
|
|
3
3
|
export declare class TimeSchema extends DateSchema {
|
|
4
|
-
constructor({ one, title, input, ...options }: DateSchemaOptions);
|
|
4
|
+
constructor({ one, title, input, format, ...options }: DateSchemaOptions);
|
|
5
5
|
stringify(value: Date): string;
|
|
6
|
-
format(value: Date): string;
|
|
7
6
|
}
|
|
8
7
|
/** Valid time, e.g. `2005-09-12` (required because falsy values are invalid). */
|
|
9
8
|
export declare const TIME: TimeSchema;
|
package/schema/TimeSchema.js
CHANGED
|
@@ -4,15 +4,12 @@ import { DateSchema } from "./DateSchema.js";
|
|
|
4
4
|
import { NULLABLE } from "./NullableSchema.js";
|
|
5
5
|
/** Define a valid time in 24h hh:mm:ss.fff format, e.g. `23:59` or `24:00 */
|
|
6
6
|
export class TimeSchema extends DateSchema {
|
|
7
|
-
constructor({ one = "time", title = "Time", input = "time", ...options }) {
|
|
8
|
-
super({ one, title, input, ...options });
|
|
7
|
+
constructor({ one = "time", title = "Time", input = "time", format = formatTime, ...options }) {
|
|
8
|
+
super({ one, title, input, format, ...options });
|
|
9
9
|
}
|
|
10
10
|
stringify(value) {
|
|
11
11
|
return requireTimeString(value);
|
|
12
12
|
}
|
|
13
|
-
format(value) {
|
|
14
|
-
return formatTime(value);
|
|
15
|
-
}
|
|
16
13
|
}
|
|
17
14
|
/** Valid time, e.g. `2005-09-12` (required because falsy values are invalid). */
|
|
18
15
|
export const TIME = new TimeSchema({});
|
package/schema/URISchema.js
CHANGED
|
@@ -25,9 +25,9 @@ export class URISchema extends StringSchema {
|
|
|
25
25
|
const str = super.validate(unsafeValue);
|
|
26
26
|
const uri = getURI(str);
|
|
27
27
|
if (!uri)
|
|
28
|
-
throw str ?
|
|
28
|
+
throw str ? `Invalid ${this.one} format` : "Required";
|
|
29
29
|
if (this.schemes && !this.schemes.includes(uri.protocol))
|
|
30
|
-
throw
|
|
30
|
+
throw `Invalid ${this.one} scheme`;
|
|
31
31
|
return uri.href;
|
|
32
32
|
}
|
|
33
33
|
}
|
package/schema/URLSchema.js
CHANGED
|
@@ -28,9 +28,9 @@ export class URLSchema extends StringSchema {
|
|
|
28
28
|
const str = super.validate(unsafeValue);
|
|
29
29
|
const url = getURL(str, this.base);
|
|
30
30
|
if (!url)
|
|
31
|
-
throw str ?
|
|
31
|
+
throw str ? `Invalid ${this.one} format` : "Required";
|
|
32
32
|
if (this.schemes && !this.schemes.includes(url.protocol))
|
|
33
|
-
throw
|
|
33
|
+
throw `Invalid ${this.one} scheme`;
|
|
34
34
|
return url.href;
|
|
35
35
|
}
|
|
36
36
|
}
|
package/schema/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from "./BooleanSchema.js";
|
|
|
4
4
|
export * from "./ChoiceSchema.js";
|
|
5
5
|
export * from "./ColorSchema.js";
|
|
6
6
|
export * from "./CountrySchema.js";
|
|
7
|
+
export * from "./CurrencyAmountSchema.js";
|
|
8
|
+
export * from "./CurrencyCodeSchema.js";
|
|
7
9
|
export * from "./DataSchema.js";
|
|
8
10
|
export * from "./DateSchema.js";
|
|
9
11
|
export * from "./DateTimeSchema.js";
|
package/schema/index.js
CHANGED
|
@@ -4,6 +4,8 @@ export * from "./BooleanSchema.js";
|
|
|
4
4
|
export * from "./ChoiceSchema.js";
|
|
5
5
|
export * from "./ColorSchema.js";
|
|
6
6
|
export * from "./CountrySchema.js";
|
|
7
|
+
export * from "./CurrencyAmountSchema.js";
|
|
8
|
+
export * from "./CurrencyCodeSchema.js";
|
|
7
9
|
export * from "./DataSchema.js";
|
|
8
10
|
export * from "./DateSchema.js";
|
|
9
11
|
export * from "./DateTimeSchema.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ImmutableArray } from "./array.js";
|
|
2
|
+
import type { AnyCaller } from "./function.js";
|
|
3
|
+
/** ISO 4217 currency code, e.g. `GBP` or `USD`. */
|
|
4
|
+
export type CurrencyCode = string;
|
|
5
|
+
/** Array of all supported currency codes in this runtime. */
|
|
6
|
+
export declare const CURRENCY_CODES: ImmutableArray<CurrencyCode>;
|
|
7
|
+
/**
|
|
8
|
+
* Require that a value is a valid ISO 4217 currency code, and return it as a `Currency` type.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCurrencyCode(value: string): CurrencyCode | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Require that a value is a valid ISO 4217 currency code, and return it as a `Currency` type.
|
|
13
|
+
*/
|
|
14
|
+
export declare function requireCurrencyCode(value: string, caller?: AnyCaller): CurrencyCode;
|
|
15
|
+
/**
|
|
16
|
+
* Get the display symbol used for a currency.
|
|
17
|
+
*
|
|
18
|
+
* @throws {RequiredError} If the currency code is malformed or unsupported.
|
|
19
|
+
*
|
|
20
|
+
* @example getCurrencySymbol("GBP"); // "£"
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCurrencySymbol(currency: CurrencyCode, caller?: AnyCaller): string;
|
|
23
|
+
/**
|
|
24
|
+
* Get the "step" value for a currency, i.e. the smallest fractional unit that is used for that currency.
|
|
25
|
+
* - E.g. `0.01` for USD, `0.001` for some cryptocurrencies, and `1` for JPY.
|
|
26
|
+
* @throws {RequiredError} If the currency code is malformed or unsupported.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getCurrencyStep(currency: CurrencyCode, caller?: AnyCaller): number;
|
package/util/currency.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
/** Array of all supported currency codes in this runtime. */
|
|
3
|
+
export const CURRENCY_CODES = Intl.supportedValuesOf("currency");
|
|
4
|
+
/**
|
|
5
|
+
* Require that a value is a valid ISO 4217 currency code, and return it as a `Currency` type.
|
|
6
|
+
*/
|
|
7
|
+
export function getCurrencyCode(value) {
|
|
8
|
+
const currency = value.toUpperCase().trim();
|
|
9
|
+
return CURRENCY_CODES.includes(currency) ? currency : undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Require that a value is a valid ISO 4217 currency code, and return it as a `Currency` type.
|
|
13
|
+
*/
|
|
14
|
+
export function requireCurrencyCode(value, caller = requireCurrencyCode) {
|
|
15
|
+
const currency = getCurrencyCode(value);
|
|
16
|
+
if (!currency)
|
|
17
|
+
throw new RequiredError("Unknown currency code", { received: value, caller });
|
|
18
|
+
return currency;
|
|
19
|
+
}
|
|
20
|
+
function _formatter(currency, caller) {
|
|
21
|
+
return new Intl.NumberFormat(undefined, {
|
|
22
|
+
style: "currency",
|
|
23
|
+
currency: requireCurrencyCode(currency, caller),
|
|
24
|
+
currencyDisplay: "narrowSymbol",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const _isCurrencyNumberPart = ({ type }) => type === "currency";
|
|
28
|
+
/**
|
|
29
|
+
* Get the display symbol used for a currency.
|
|
30
|
+
*
|
|
31
|
+
* @throws {RequiredError} If the currency code is malformed or unsupported.
|
|
32
|
+
*
|
|
33
|
+
* @example getCurrencySymbol("GBP"); // "£"
|
|
34
|
+
*/
|
|
35
|
+
export function getCurrencySymbol(currency, caller = getCurrencySymbol) {
|
|
36
|
+
return _formatter(currency, caller).formatToParts(0).find(_isCurrencyNumberPart)?.value;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the "step" value for a currency, i.e. the smallest fractional unit that is used for that currency.
|
|
40
|
+
* - E.g. `0.01` for USD, `0.001` for some cryptocurrencies, and `1` for JPY.
|
|
41
|
+
* @throws {RequiredError} If the currency code is malformed or unsupported.
|
|
42
|
+
*/
|
|
43
|
+
export function getCurrencyStep(currency, caller = getCurrencyStep) {
|
|
44
|
+
const { minimumFractionDigits = 0 } = _formatter(currency, caller).resolvedOptions();
|
|
45
|
+
return 1 / 10 ** minimumFractionDigits;
|
|
46
|
+
}
|
package/util/format.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isArray } from "./array.js";
|
|
2
2
|
import { SECOND } from "./constants.js";
|
|
3
|
+
import { requireCurrencyCode } from "./currency.js";
|
|
3
4
|
import { isDate, requireDate } from "./date.js";
|
|
4
5
|
import { getBestTimeUnit, getMilliseconds } from "./duration.js";
|
|
5
6
|
import { getPercent } from "./number.js";
|
|
@@ -39,7 +40,7 @@ export function formatUnit(num, unit, options) {
|
|
|
39
40
|
export function formatCurrency(amount, currency, options) {
|
|
40
41
|
return Intl.NumberFormat(undefined, {
|
|
41
42
|
style: "currency",
|
|
42
|
-
currency,
|
|
43
|
+
currency: requireCurrencyCode(currency, formatCurrency),
|
|
43
44
|
...options,
|
|
44
45
|
}).format(amount);
|
|
45
46
|
}
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED